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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useRef, useState, useEffect } from 'react';
|
|
2
2
|
import { useIntl } from 'react-intl';
|
|
3
3
|
import { IconButton } from '@strapi/design-system/IconButton';
|
|
4
|
-
import { Searchbar
|
|
4
|
+
import { Searchbar } from '@strapi/design-system/Searchbar';
|
|
5
5
|
import SearchIcon from "@strapi/icons/Search";
|
|
6
6
|
import { getTradId } from '../../translations';
|
|
7
7
|
|
|
@@ -11,32 +11,30 @@ const Search = ({ value, setValue }) => {
|
|
|
11
11
|
const { formatMessage } = useIntl();
|
|
12
12
|
|
|
13
13
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
if (isOpen) {
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
wrapperRef.current.querySelector('input').focus();
|
|
17
|
+
}, 0);
|
|
18
|
+
}
|
|
19
19
|
}, [isOpen]);
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
if (isOpen) {
|
|
22
22
|
return (
|
|
23
23
|
<div ref={wrapperRef}>
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
placeholder
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
</Searchbar>
|
|
39
|
-
</SearchForm>
|
|
24
|
+
<Searchbar
|
|
25
|
+
name="searchbar"
|
|
26
|
+
onClear={() => { setValue(''); setIsOpen(false); }}
|
|
27
|
+
value={value}
|
|
28
|
+
size="S"
|
|
29
|
+
onChange={(e) => setValue(e.target.value)}
|
|
30
|
+
clearLabel="Clearing the search"
|
|
31
|
+
placeholder={formatMessage({
|
|
32
|
+
id: getTradId('popup.item.form.audience.placeholder'),
|
|
33
|
+
defaultMessage: 'Type to start searching...',
|
|
34
|
+
})}
|
|
35
|
+
>
|
|
36
|
+
Search for navigation items
|
|
37
|
+
</Searchbar>
|
|
40
38
|
</div>
|
|
41
39
|
);
|
|
42
40
|
} else {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
const initSize = 92;
|
|
5
|
+
|
|
6
|
+
const NavigationIcon = ({ width = 24, height = 24 }) =>
|
|
7
|
+
<svg viewBox={`0 0 ${width} ${height}`} xmlns="http://www.w3.org/2000/svg"><g style={ {transform: `scale(${width/initSize})` } }>
|
|
8
|
+
<path d="M78,23.5H14c-3.6,0-6.5-2.9-6.5-6.5s2.9-6.5,6.5-6.5h64c3.6,0,6.5,2.9,6.5,6.5S81.6,23.5,78,23.5z M84.5,46
|
|
9
|
+
c0-3.6-2.9-6.5-6.5-6.5H14c-3.6,0-6.5,2.9-6.5,6.5s2.9,6.5,6.5,6.5h64C81.6,52.5,84.5,49.6,84.5,46z M84.5,75c0-3.6-2.9-6.5-6.5-6.5
|
|
10
|
+
H14c-3.6,0-6.5,2.9-6.5,6.5s2.9,6.5,6.5,6.5h64C81.6,81.5,84.5,78.6,84.5,75z"/>
|
|
11
|
+
</g>
|
|
12
|
+
</svg>;
|
|
13
|
+
|
|
14
|
+
export default NavigationIcon;
|
package/admin/src/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { prefixPluginTranslations } from '@strapi/helper-plugin';
|
|
2
|
-
import PluginIcon from './components/PluginIcon';
|
|
3
2
|
import pluginPkg from '../../package.json';
|
|
4
3
|
import pluginId from './pluginId';
|
|
4
|
+
import pluginPermissions from './permissions';
|
|
5
|
+
import NavigationIcon from './components/icons/navigation';
|
|
5
6
|
|
|
6
7
|
const name = pluginPkg.strapi.name;
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ export default {
|
|
|
9
10
|
register(app) {
|
|
10
11
|
app.addMenuLink({
|
|
11
12
|
to: `/plugins/${pluginId}`,
|
|
12
|
-
icon:
|
|
13
|
+
icon: NavigationIcon,
|
|
13
14
|
intlLabel: {
|
|
14
15
|
id: `${pluginId}.plugin.name`,
|
|
15
16
|
defaultMessage: 'Navigation',
|
|
@@ -19,7 +20,7 @@ export default {
|
|
|
19
20
|
|
|
20
21
|
return component;
|
|
21
22
|
},
|
|
22
|
-
permissions:
|
|
23
|
+
permissions: pluginPermissions.access,
|
|
23
24
|
});
|
|
24
25
|
app.registerPlugin({
|
|
25
26
|
id: pluginId,
|
|
@@ -3,57 +3,63 @@ import { useIntl } from 'react-intl';
|
|
|
3
3
|
import { HeaderLayout } from '@strapi/design-system/Layout';
|
|
4
4
|
import { Stack } from '@strapi/design-system/Stack';
|
|
5
5
|
import { Button } from '@strapi/design-system/Button';
|
|
6
|
-
import { IconButton } from '@strapi/design-system/IconButton';
|
|
7
6
|
import Check from '@strapi/icons/Check';
|
|
8
7
|
import More from '@strapi/icons/More';
|
|
9
|
-
import Plus from '@strapi/icons/Plus';
|
|
10
|
-
import styled from 'styled-components';
|
|
11
8
|
import { getTrad } from '../../../../translations';
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
padding: ${({ theme }) => theme.spaces[2]};
|
|
16
|
-
|
|
17
|
-
svg {
|
|
18
|
-
width: ${18 / 16}rem;
|
|
19
|
-
height: ${18 / 16}rem;
|
|
20
|
-
}
|
|
21
|
-
`;
|
|
9
|
+
import { MoreButton } from './styles';
|
|
10
|
+
import { Select, Option } from '@strapi/design-system/Select';
|
|
11
|
+
import { Box } from '@strapi/design-system/Box'
|
|
22
12
|
|
|
23
13
|
const NavigationHeader = ({
|
|
14
|
+
activeNavigation,
|
|
15
|
+
availableNavigations,
|
|
24
16
|
structureHasErrors,
|
|
17
|
+
structureHasChanged,
|
|
18
|
+
handleChangeSelection,
|
|
25
19
|
handleSave,
|
|
26
20
|
}) => {
|
|
27
21
|
const { formatMessage } = useIntl();
|
|
28
|
-
|
|
29
22
|
return (
|
|
30
23
|
<HeaderLayout
|
|
31
|
-
|
|
24
|
+
primaryAction={
|
|
32
25
|
<Stack horizontal size={2}>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
<Box width="10vw">
|
|
27
|
+
<Select
|
|
28
|
+
type="select"
|
|
29
|
+
placeholder={'Change navigation'}
|
|
30
|
+
name={`navigationSelect`}
|
|
31
|
+
onChange={handleChangeSelection}
|
|
32
|
+
value={activeNavigation?.id}
|
|
33
|
+
size="S"
|
|
34
|
+
style={null}
|
|
38
35
|
>
|
|
39
|
-
{
|
|
40
|
-
</
|
|
41
|
-
|
|
36
|
+
{availableNavigations.map(({ id, name }) => <Option key={id} value={id}>{name}</Option>)}
|
|
37
|
+
</Select >
|
|
38
|
+
</Box>
|
|
39
|
+
<Button
|
|
40
|
+
onClick={handleSave}
|
|
41
|
+
startIcon={<Check />}
|
|
42
|
+
disabled={structureHasErrors || !structureHasChanged}
|
|
43
|
+
type="submit"
|
|
44
|
+
>
|
|
45
|
+
{formatMessage(getTrad('submit.cta.save'))}
|
|
46
|
+
</Button>
|
|
47
|
+
{/* <MoreButton
|
|
42
48
|
id="more"
|
|
43
49
|
label="More"
|
|
44
50
|
icon={<More />}
|
|
45
|
-
/>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
/> */}
|
|
52
|
+
</Stack>
|
|
53
|
+
}
|
|
54
|
+
title={formatMessage({
|
|
55
|
+
id: getTrad('header.title'),
|
|
56
|
+
defaultMessage: 'UI Navigation',
|
|
57
|
+
})}
|
|
58
|
+
subtitle={formatMessage({
|
|
59
|
+
id: getTrad('header.description'),
|
|
60
|
+
defaultMessage: 'Define your portal navigation',
|
|
61
|
+
})}
|
|
62
|
+
/>
|
|
57
63
|
);
|
|
58
64
|
};
|
|
59
65
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import styled from 'styled-components';
|
|
2
|
+
import { IconButton } from '@strapi/design-system/IconButton';
|
|
3
|
+
|
|
4
|
+
export const MoreButton = styled(IconButton)`
|
|
5
|
+
margin: ${({ theme }) => `0 ${theme.spaces[2]}`};
|
|
6
|
+
padding: ${({ theme }) => theme.spaces[2]};
|
|
7
|
+
|
|
8
|
+
svg {
|
|
9
|
+
width: ${18 / 16}rem;
|
|
10
|
+
height: ${18 / 16}rem;
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
|
|
@@ -6,18 +6,19 @@ import { Formik } from 'formik'
|
|
|
6
6
|
|
|
7
7
|
// Design System
|
|
8
8
|
import { ModalBody } from '@strapi/design-system/ModalLayout';
|
|
9
|
+
import { Select, Option } from '@strapi/design-system/Select';
|
|
9
10
|
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
|
|
|
14
15
|
|
|
15
|
-
import { navigationItemType } from '../../utils/enums';
|
|
16
|
+
import { navigationItemAdditionalFields, navigationItemType } from '../../utils/enums';
|
|
16
17
|
import slugify from 'slugify';
|
|
17
18
|
import { extractRelatedItemLabel } from '../../utils/parsers';
|
|
18
19
|
import { form as formDefinition } from './utils/form';
|
|
19
20
|
import { checkFormValidity } from '../../utils/form';
|
|
20
|
-
import { getTradId } from '../../../../translations';
|
|
21
|
+
import { getTradId, getTrad } from '../../../../translations';
|
|
21
22
|
|
|
22
23
|
const NavigationItemForm = ({
|
|
23
24
|
isLoading,
|
|
@@ -52,20 +53,29 @@ const NavigationItemForm = ({
|
|
|
52
53
|
...data,
|
|
53
54
|
type: data.type || navigationItemType.INTERNAL,
|
|
54
55
|
related: data.related?.value,
|
|
55
|
-
relatedType: data.relatedType?.value
|
|
56
|
+
relatedType: data.relatedType?.value,
|
|
57
|
+
audience: data.audience?.map(item => item.id),
|
|
56
58
|
});
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
const audience = get(form, `${inputsPrefix}audience`, []);
|
|
62
|
+
const audienceOptions = availableAudience.map((item) => ({
|
|
63
|
+
value: get(item, 'id', " "),
|
|
64
|
+
label: get(item, 'name', " "),
|
|
65
|
+
}));
|
|
66
|
+
|
|
59
67
|
const generatePreviewPath = () => {
|
|
60
68
|
if (!isExternal) {
|
|
69
|
+
const value = `${data.levelPath !== '/' ? `${data.levelPath}` : ''}/${form.path !== '/' ? form.path || '' : ''}`;
|
|
61
70
|
return {
|
|
62
|
-
id:
|
|
63
|
-
defaultMessage:
|
|
71
|
+
id: getTradId('popup.item.form.type.external.description'),
|
|
72
|
+
defaultMessage: '',
|
|
73
|
+
values: { value }
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
return null;
|
|
67
77
|
};
|
|
68
|
-
|
|
78
|
+
|
|
69
79
|
const sanitizePayload = (payload = {}) => {
|
|
70
80
|
const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, ...purePayload } = payload;
|
|
71
81
|
const sanitizedType = purePayload.type || navigationItemType.INTERNAL;
|
|
@@ -101,6 +111,10 @@ const NavigationItemForm = ({
|
|
|
101
111
|
const onTypeChange = ({ target: { name, value } }) =>
|
|
102
112
|
onChange({ target: { name, value: value ? navigationItemType.INTERNAL : navigationItemType.EXTERNAL } });
|
|
103
113
|
|
|
114
|
+
const onAudienceChange = (value) => {
|
|
115
|
+
onChange({target: {name: `${inputsPrefix}audience`, value}});
|
|
116
|
+
}
|
|
117
|
+
|
|
104
118
|
const onChange = ({ target: { name, value } }) => {
|
|
105
119
|
setFormState(prevState => ({
|
|
106
120
|
...prevState,
|
|
@@ -137,7 +151,7 @@ const NavigationItemForm = ({
|
|
|
137
151
|
.filter((item) => {
|
|
138
152
|
const usedContentTypeEntitiesOfSameType = usedContentTypeEntities
|
|
139
153
|
.filter(uctItem => relatedTypeSelectValue === uctItem.__collectionUid);
|
|
140
|
-
return !find(usedContentTypeEntitiesOfSameType, uctItem => (item.id === uctItem.id && uctItem.id !== form.related));
|
|
154
|
+
return !find(usedContentTypeEntitiesOfSameType, uctItem => (item.id === uctItem.id && uctItem.id !== form.related));
|
|
141
155
|
})
|
|
142
156
|
.map((item) => {
|
|
143
157
|
const label = appendLabelPublicationStatus(
|
|
@@ -257,6 +271,10 @@ const NavigationItemForm = ({
|
|
|
257
271
|
}}
|
|
258
272
|
name={`${inputsPrefix}title`}
|
|
259
273
|
placeholder={{
|
|
274
|
+
id: "e.g. Blog",
|
|
275
|
+
defaultMessage: 'e.g. Blog',
|
|
276
|
+
}}
|
|
277
|
+
description={{
|
|
260
278
|
id: getTradId('popup.item.form.title.placeholder'),
|
|
261
279
|
defaultMessage: 'e.g. Blog',
|
|
262
280
|
}}
|
|
@@ -329,6 +347,15 @@ const NavigationItemForm = ({
|
|
|
329
347
|
onChange={onChangeRelatedType}
|
|
330
348
|
options={relatedTypeSelectOptions}
|
|
331
349
|
value={relatedTypeSelectValue}
|
|
350
|
+
disabled={isLoading || isEmpty(relatedTypeSelectOptions)}
|
|
351
|
+
description={
|
|
352
|
+
!isLoading && isEmpty(relatedTypeSelectOptions)
|
|
353
|
+
? {
|
|
354
|
+
id: getTradId('popup.item.form.relatedType.empty'),
|
|
355
|
+
defaultMessage: 'There are no more content types',
|
|
356
|
+
}
|
|
357
|
+
: undefined
|
|
358
|
+
}
|
|
332
359
|
/>
|
|
333
360
|
</GridItem>
|
|
334
361
|
{relatedTypeSelectValue && !isSingleSelected && (
|
|
@@ -365,6 +392,22 @@ const NavigationItemForm = ({
|
|
|
365
392
|
)}
|
|
366
393
|
</>
|
|
367
394
|
)}
|
|
395
|
+
|
|
396
|
+
{additionalFields.includes(navigationItemAdditionalFields.AUDIENCE) && (
|
|
397
|
+
<GridItem key={`${inputsPrefix}audience`} col={6} lg={12}>
|
|
398
|
+
<Select
|
|
399
|
+
id={`${inputsPrefix}audience`}
|
|
400
|
+
placeholder={formatMessage(getTrad('popup.item.form.audience.placeholder'))}
|
|
401
|
+
label={formatMessage(getTrad('popup.item.form.audience.label'))}
|
|
402
|
+
onChange={onAudienceChange}
|
|
403
|
+
value={audience}
|
|
404
|
+
multi
|
|
405
|
+
withTags
|
|
406
|
+
>
|
|
407
|
+
{audienceOptions.map(({ value, label }) => <Option key={value} value={value}>{label}</Option>)}
|
|
408
|
+
</Select>
|
|
409
|
+
</GridItem>
|
|
410
|
+
)}
|
|
368
411
|
</Grid>
|
|
369
412
|
</ModalBody>
|
|
370
413
|
</Form>
|
|
@@ -6,14 +6,12 @@ import { ModalHeader } from '@strapi/design-system/ModalLayout';
|
|
|
6
6
|
import { useIntl } from 'react-intl';
|
|
7
7
|
import { getTrad } from '../../../../translations';
|
|
8
8
|
|
|
9
|
-
export const NavigationItemPopupHeader = () => {
|
|
9
|
+
export const NavigationItemPopupHeader = ({isNewItem}) => {
|
|
10
10
|
const { formatMessage } = useIntl();
|
|
11
11
|
return (
|
|
12
12
|
<ModalHeader>
|
|
13
13
|
<ButtonText textColor="neutral800" as="h2" id="asset-dialog-title">
|
|
14
|
-
{formatMessage(
|
|
15
|
-
getTrad('popup.item.header'),
|
|
16
|
-
)}
|
|
14
|
+
{formatMessage(getTrad(`popup.item.header.${isNewItem ? 'new' : 'edit'}`))}
|
|
17
15
|
</ButtonText>
|
|
18
16
|
</ModalHeader>
|
|
19
17
|
);
|
|
@@ -80,7 +80,7 @@ const NavigationItemPopUp = ({
|
|
|
80
80
|
|
|
81
81
|
return (
|
|
82
82
|
<ModalLayout labelledBy="condition-modal-breadcrumbs" onClose={onClose} isOpen={isOpen}>
|
|
83
|
-
<NavigationItemPopupHeader />
|
|
83
|
+
<NavigationItemPopupHeader isNewItem={!data.viewId}/>
|
|
84
84
|
<NavigationItemForm
|
|
85
85
|
data={prepareFormData(data)}
|
|
86
86
|
isLoading={isLoading}
|
|
@@ -10,10 +10,13 @@ import { isEmpty, get } from "lodash";
|
|
|
10
10
|
|
|
11
11
|
// Design System
|
|
12
12
|
import { Main } from '@strapi/design-system/Main';
|
|
13
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
13
14
|
import { ContentLayout } from '@strapi/design-system/Layout';
|
|
15
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
16
|
+
import { Box } from '@strapi/design-system/Box';
|
|
17
|
+
import { Icon } from '@strapi/design-system/Icon';
|
|
14
18
|
import { Button } from '@strapi/design-system/Button';
|
|
15
19
|
import { LoadingIndicatorPage } from "@strapi/helper-plugin";
|
|
16
|
-
import { EmptyStateLayout } from '@strapi/design-system/EmptyStateLayout';
|
|
17
20
|
import EmptyDocumentsIcon from '@strapi/icons/EmptyDocuments';
|
|
18
21
|
import PlusIcon from "@strapi/icons/Plus";
|
|
19
22
|
|
|
@@ -22,6 +25,7 @@ import List from '../../components/NavigationItemList';
|
|
|
22
25
|
import NavigationContentHeader from './components/NavigationContentHeader';
|
|
23
26
|
import NavigationHeader from './components/NavigationHeader';
|
|
24
27
|
import NavigationItemPopUp from "./components/NavigationItemPopup";
|
|
28
|
+
import Search from '../../components/Search';
|
|
25
29
|
import useDataManager from "../../hooks/useDataManager";
|
|
26
30
|
import { getTrad } from '../../translations';
|
|
27
31
|
import {
|
|
@@ -30,7 +34,6 @@ import {
|
|
|
30
34
|
usedContentTypes,
|
|
31
35
|
validateNavigationStructure,
|
|
32
36
|
} from './utils/parsers';
|
|
33
|
-
import Search from '../../components/Search';
|
|
34
37
|
|
|
35
38
|
const View = () => {
|
|
36
39
|
const {
|
|
@@ -55,6 +58,7 @@ const View = () => {
|
|
|
55
58
|
const { formatMessage } = useIntl();
|
|
56
59
|
|
|
57
60
|
const [searchValue, setSearchValue] = useState('');
|
|
61
|
+
const [structureChanged, setStructureChanged] = useState(false);
|
|
58
62
|
const isSearchEmpty = isEmpty(searchValue);
|
|
59
63
|
|
|
60
64
|
const structureHasErrors = !validateNavigationStructure((changedActiveNavigation || {}).items);
|
|
@@ -104,6 +108,7 @@ const View = () => {
|
|
|
104
108
|
items: transformItemToViewPayload(payload, changedActiveNavigation.items, config),
|
|
105
109
|
};
|
|
106
110
|
handleChangeNavigationData(changedStructure, true);
|
|
111
|
+
setStructureChanged(true);
|
|
107
112
|
};
|
|
108
113
|
|
|
109
114
|
const filteredListFactory = (items, filterFunction) => items.reduce((acc, item) => {
|
|
@@ -147,10 +152,19 @@ const View = () => {
|
|
|
147
152
|
changeNavigationItemPopupState(false);
|
|
148
153
|
};
|
|
149
154
|
|
|
155
|
+
const handleChangeNavigationSelection = (...args) => {
|
|
156
|
+
handleChangeSelection(...args);
|
|
157
|
+
setSearchValue('');
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
return (
|
|
151
161
|
<Main labelledBy="title" aria-busy={isLoadingForSubmit}>
|
|
152
162
|
<NavigationHeader
|
|
153
163
|
structureHasErrors={structureHasErrors}
|
|
164
|
+
structureHasChanged={structureChanged}
|
|
165
|
+
availableNavigations={availableNavigations}
|
|
166
|
+
activeNavigation={activeNavigation}
|
|
167
|
+
handleChangeSelection={handleChangeNavigationSelection}
|
|
154
168
|
handleSave={handleSave}
|
|
155
169
|
/>
|
|
156
170
|
<ContentLayout>
|
|
@@ -158,7 +172,7 @@ const View = () => {
|
|
|
158
172
|
{changedActiveNavigation && (
|
|
159
173
|
<>
|
|
160
174
|
<NavigationContentHeader
|
|
161
|
-
startActions={<Search value={searchValue} setValue={setSearchValue}/>}
|
|
175
|
+
startActions={<Search value={searchValue} setValue={setSearchValue} />}
|
|
162
176
|
endActions={<Button
|
|
163
177
|
onClick={addNewNavigationItem}
|
|
164
178
|
startIcon={<PlusIcon />}
|
|
@@ -169,20 +183,20 @@ const View = () => {
|
|
|
169
183
|
</Button>}
|
|
170
184
|
/>
|
|
171
185
|
{isEmpty(changedActiveNavigation.items || []) && (
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
+
<Flex direction="column" minHeight="400px" justifyContent="center">
|
|
187
|
+
<Icon as={EmptyDocumentsIcon} width="160px" height="88px" color=""/>
|
|
188
|
+
<Box padding={4}>
|
|
189
|
+
<Typography variant="beta" textColor="neutral600">{formatMessage(getTrad('empty'))}</Typography>
|
|
190
|
+
</Box>
|
|
191
|
+
<Button
|
|
192
|
+
variant='secondary'
|
|
193
|
+
startIcon={<PlusIcon />}
|
|
194
|
+
label={formatMessage(getTrad('empty.cta'))}
|
|
195
|
+
onClick={addNewNavigationItem}
|
|
196
|
+
>
|
|
197
|
+
{formatMessage(getTrad('empty.cta'))}
|
|
198
|
+
</Button>
|
|
199
|
+
</Flex>
|
|
186
200
|
)}
|
|
187
201
|
{
|
|
188
202
|
!isEmpty(changedActiveNavigation.items || [])
|
|
@@ -7,6 +7,7 @@ export const transformItemToRESTPayload = (
|
|
|
7
7
|
parent = undefined,
|
|
8
8
|
master = undefined,
|
|
9
9
|
config = {},
|
|
10
|
+
parentAttachedToMenu = true,
|
|
10
11
|
) => {
|
|
11
12
|
const {
|
|
12
13
|
id,
|
|
@@ -34,7 +35,7 @@ export const transformItemToRESTPayload = (
|
|
|
34
35
|
find(contentTypes,
|
|
35
36
|
ct => ct.uid === relatedType) :
|
|
36
37
|
undefined;
|
|
37
|
-
|
|
38
|
+
const itemAttachedToMenu = menuAttached && parentAttachedToMenu
|
|
38
39
|
return {
|
|
39
40
|
id,
|
|
40
41
|
parent,
|
|
@@ -45,7 +46,7 @@ export const transformItemToRESTPayload = (
|
|
|
45
46
|
removed,
|
|
46
47
|
order,
|
|
47
48
|
uiRouterKey,
|
|
48
|
-
menuAttached,
|
|
49
|
+
menuAttached: itemAttachedToMenu,
|
|
49
50
|
audience: audience.map((audienceItem) =>
|
|
50
51
|
isObject(audienceItem) ? audienceItem.value : audienceItem,
|
|
51
52
|
),
|
|
@@ -60,7 +61,7 @@ export const transformItemToRESTPayload = (
|
|
|
60
61
|
field: relatedContentType && relatedContentType.relatedField ? relatedContentType.relatedField : 'navigation',
|
|
61
62
|
},
|
|
62
63
|
],
|
|
63
|
-
items: items.map((iItem) => transformItemToRESTPayload(iItem, id, master, config)),
|
|
64
|
+
items: items.map((iItem) => transformItemToRESTPayload(iItem, id, master, config, itemAttachedToMenu)),
|
|
64
65
|
};
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -118,6 +119,7 @@ const linkRelations = (item, config) => {
|
|
|
118
119
|
|
|
119
120
|
const shouldFindRelated = (isNumber(related) || isUuid(related) || isString(related)) && !relatedRef;
|
|
120
121
|
const shouldBuildRelated = !relatedRef || (relatedRef && (relatedRef.id !== relatedId));
|
|
122
|
+
|
|
121
123
|
if (shouldBuildRelated && !shouldFindRelated) {
|
|
122
124
|
const relatedContentType = find(contentTypes,
|
|
123
125
|
ct => ct.uid === relatedItem.__contentType, {});
|
|
@@ -136,6 +138,7 @@ const linkRelations = (item, config) => {
|
|
|
136
138
|
const relatedRef = find(contentTypeItems, cti => cti.id === relatedId);
|
|
137
139
|
const relatedContentType = find(contentTypes, ct => ct.uid === relatedType);
|
|
138
140
|
const { uid, contentTypeName, labelSingular, isSingle } = relatedContentType;
|
|
141
|
+
|
|
139
142
|
relation = {
|
|
140
143
|
relatedRef: {
|
|
141
144
|
...relatedRef,
|
|
@@ -249,11 +252,14 @@ export const prepareItemToViewPayload = (items = [], viewParentId = null, config
|
|
|
249
252
|
}));
|
|
250
253
|
|
|
251
254
|
export const extractRelatedItemLabel = (item = {}, fields = {}, config = {}) => {
|
|
255
|
+
if (get(item, 'isSingle', false)) {
|
|
256
|
+
return get(item, 'labelSingular', '');
|
|
257
|
+
}
|
|
252
258
|
const { contentTypes = [] } = config;
|
|
253
259
|
const { __collectionUid } = item;
|
|
254
260
|
const contentType = contentTypes.find(_ => _.uid === __collectionUid)
|
|
255
261
|
const { default: defaultFields = [] } = fields;
|
|
256
|
-
return get(fields, `${contentType ? contentType.
|
|
262
|
+
return get(fields, `${contentType ? contentType.uid : __collectionUid}`, defaultFields).map((_) => item[_]).filter((_) => _)[0] || '';
|
|
257
263
|
};
|
|
258
264
|
|
|
259
265
|
export const usedContentTypes = (items = []) => items.flatMap(
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const permissions = require('./../../permissions');
|
|
2
|
+
|
|
3
|
+
const pluginPermissions = {
|
|
4
|
+
access: [{ action: permissions.render(permissions.navigation.read), subject: null }],
|
|
5
|
+
update: [{ action: permissions.render(permissions.navigation.update), subject: null }],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default pluginPermissions;
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
"header.action.newItem": "New Item",
|
|
6
6
|
"submit.cta.cancel": "Cancel",
|
|
7
7
|
"submit.cta.save": "Save",
|
|
8
|
-
"empty": "
|
|
8
|
+
"empty": "Your navigation is empty",
|
|
9
9
|
"empty.cta": "Create first item",
|
|
10
|
-
"popup.item.header": "
|
|
10
|
+
"popup.item.header.edit": "Edit navigation item",
|
|
11
|
+
"popup.item.header.new": "New navigation item",
|
|
11
12
|
"popup.item.form.title.label": "Title",
|
|
12
13
|
"popup.item.form.title.placeholder": "Enter the item title or leave blank to pull from related entity",
|
|
13
14
|
"popup.item.form.uiRouterKey.label": "UI router key",
|
|
@@ -22,11 +23,13 @@
|
|
|
22
23
|
"popup.item.form.type.label": "Internal link",
|
|
23
24
|
"popup.item.form.type.internal.label": "Internal source",
|
|
24
25
|
"popup.item.form.type.external.label": "External source",
|
|
26
|
+
"popup.item.form.type.external.description": "Output path: {value}",
|
|
25
27
|
"popup.item.form.audience.label": "Audience",
|
|
26
|
-
"popup.item.form.audience.placeholder": "
|
|
28
|
+
"popup.item.form.audience.placeholder": "Select audience...",
|
|
27
29
|
"popup.item.form.relatedSection.label": "Relation to",
|
|
28
30
|
"popup.item.form.relatedType.label": "Content Type",
|
|
29
31
|
"popup.item.form.relatedType.placeholder": "Select content type...",
|
|
32
|
+
"popup.item.form.relatedType.empty": "There are no content types to select",
|
|
30
33
|
"popup.item.form.related.label": "Entity",
|
|
31
34
|
"popup.item.form.related.empty": "There are no more entities of \"{ contentTypeName }\" to select",
|
|
32
35
|
"popup.item.form.button.create": "Create item",
|
|
@@ -40,9 +43,9 @@
|
|
|
40
43
|
"notification.navigation.item.relation": "Entity relation does not exist!",
|
|
41
44
|
"notification.navigation.item.relation.status.draft": "draft",
|
|
42
45
|
"notification.navigation.item.relation.status.published": "published",
|
|
43
|
-
"navigation.item.action.newItem": "
|
|
46
|
+
"navigation.item.action.newItem": "Add nested item",
|
|
44
47
|
"navigation.item.badge.removed": "Removed",
|
|
45
|
-
"navigation.item.badge.draft": "Draft",
|
|
46
|
-
"navigation.item.badge.published": "Published"
|
|
48
|
+
"navigation.item.badge.draft": "{type}: Draft",
|
|
49
|
+
"navigation.item.badge.published": "{type}: Published"
|
|
47
50
|
}
|
|
48
51
|
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-navigation",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Strapi - Navigation plugin",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"name": "navigation",
|
|
7
|
-
"
|
|
8
|
-
"description": "
|
|
7
|
+
"displayName": "Navigation",
|
|
8
|
+
"description": "Create consumable navigation with a simple and straightforward visual builder",
|
|
9
9
|
"kind": "plugin"
|
|
10
10
|
},
|
|
11
11
|
"repository": {
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
"url": "https://github.com/VirtusLab/strapi-plugin-navigation"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
+
"publish": "npm publish --tag latest",
|
|
16
17
|
"test:unit": "jest --verbose --coverage"
|
|
17
18
|
},
|
|
18
19
|
"dependencies": {
|
|
20
|
+
"@strapi/utils": "^4.0.7",
|
|
19
21
|
"uuid": "^8.3.0",
|
|
20
22
|
"bad-words": "^3.0.3",
|
|
21
23
|
"lodash": "^4.17.11",
|
package/permissions.js
ADDED
package/server/bootstrap.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { isEmpty } = require("lodash");
|
|
2
|
+
const permissions = require('./../permissions');
|
|
2
3
|
|
|
3
4
|
module.exports = async ({ strapi }) => {
|
|
4
5
|
// Check if the plugin users-permissions is installed because the navigation needs it
|
|
@@ -11,14 +12,14 @@ module.exports = async ({ strapi }) => {
|
|
|
11
12
|
const actions = [
|
|
12
13
|
{
|
|
13
14
|
section: "plugins",
|
|
14
|
-
displayName: "
|
|
15
|
-
uid:
|
|
15
|
+
displayName: "Read",
|
|
16
|
+
uid: permissions.navigation.read,
|
|
16
17
|
pluginName: "navigation",
|
|
17
18
|
},
|
|
18
19
|
{
|
|
19
20
|
section: "plugins",
|
|
20
|
-
displayName: "
|
|
21
|
-
uid:
|
|
21
|
+
displayName: "Update",
|
|
22
|
+
uid: permissions.navigation.update,
|
|
22
23
|
pluginName: "navigation",
|
|
23
24
|
},
|
|
24
25
|
];
|