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.
Files changed (29) hide show
  1. package/README.md +2 -1
  2. package/admin/src/components/CollapseButton/index.js +31 -0
  3. package/admin/src/components/Item/ItemCardBadge/index.js +4 -4
  4. package/admin/src/components/Item/ItemCardHeader/Wrapper.js +0 -4
  5. package/admin/src/components/Item/ItemCardHeader/index.js +5 -4
  6. package/admin/src/components/Item/Wrapper.js +1 -1
  7. package/admin/src/components/Item/index.js +129 -66
  8. package/admin/src/components/NavigationItemList/Wrapper.js +1 -1
  9. package/admin/src/components/NavigationItemList/index.js +6 -0
  10. package/admin/src/components/Search/index.js +1 -1
  11. package/admin/src/pages/SettingsPage/index.js +95 -91
  12. package/admin/src/pages/View/components/NavigationItemForm/index.js +49 -30
  13. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +3 -3
  14. package/admin/src/pages/View/index.js +81 -9
  15. package/admin/src/pages/View/utils/enums.js +1 -0
  16. package/admin/src/pages/View/utils/parsers.js +6 -3
  17. package/admin/src/translations/en.json +15 -3
  18. package/admin/src/utils/index.js +4 -2
  19. package/package.json +2 -3
  20. package/server/bootstrap.js +3 -22
  21. package/server/config/index.js +1 -0
  22. package/server/config.js +1 -0
  23. package/server/content-types/navigation-item/lifecycle.js +1 -0
  24. package/server/content-types/navigation-item/schema.json +7 -1
  25. package/server/graphql/types/content-types-name-fields.js +2 -2
  26. package/server/graphql/types/navigation-item.js +1 -1
  27. package/server/services/navigation.js +44 -19
  28. package/server/services/utils/functions.js +1 -1
  29. package/yarn-error.log +0 -5263
package/README.md CHANGED
@@ -41,6 +41,7 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
41
41
  - **Navigation Public API:** Simple and ready for use API endpoint for consuming the navigation structure you've created
42
42
  - **Visual builder:** Elegant and easy to use visual builder
43
43
  - **Any Content Type relation:** Navigation can by linked to any of your Content Types by default. Simply, you're controlling it and also limiting available content types by configuration props
44
+ - **Different types of navigation items:** Create navigation with items linked to internal types, to external links or wrapper elements to keep structure clean
44
45
  - **Multiple navigations:** Create as many Navigation containers as you want, setup them and use in the consumer application
45
46
  - **Customizable:** Possibility to customize the options like: available Content Types, Maximum level for "attach to menu", Additional fields (audience)
46
47
  - **[Audit log](https://github.com/VirtusLab/strapi-molecules/tree/master/packages/strapi-plugin-audit-log):** integration with Strapi Molecules Audit Log plugin that provides changes track record
@@ -81,7 +82,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
81
82
 
82
83
  **Supported Strapi versions**:
83
84
 
84
- - Strapi v4.1.0 (recently tested)
85
+ - Strapi v4.1.5 (recently tested)
85
86
  - Strapi v4.x
86
87
 
87
88
  > This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation/tree/strapi-v3).
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components'
3
+ import { Flex } from '@strapi/design-system/Flex';
4
+ import { Typography } from '@strapi/design-system/Typography';
5
+ import { Icon } from '@strapi/design-system/Icon';
6
+ import { CarretUp, CarretDown } from '@strapi/icons';
7
+
8
+ const Wrapper = styled.div`
9
+ border-radius: 50%;
10
+ background: #DCDCE4;
11
+ width: 25px;
12
+ height: 25px;
13
+ display: flex;
14
+ justify-content: center;
15
+ align-items: center;
16
+ margin-right: 8px;
17
+ `;
18
+
19
+ const CollapseButton = ({ toggle, collapsed, itemsCount }) => (
20
+ <Flex justifyContent='space-between' alignItems='center' onClick={toggle} cursor="pointer" style={{ marginRight: '16px' }}>
21
+ <Wrapper>
22
+ { collapsed ?
23
+ <Icon as={CarretDown} width='7px' height='4px' /> :
24
+ <Icon as={CarretUp} width='7px' height='4px' />
25
+ }
26
+ </Wrapper>
27
+ <Typography variant="pi">{itemsCount} nested items</Typography>
28
+ </Flex >
29
+ );
30
+
31
+ export default CollapseButton;
@@ -4,15 +4,15 @@ import { Badge } from '@strapi/design-system/Badge';
4
4
  const ItemCardBadge = styled(Badge)`
5
5
  border: 1px solid ${({ theme, borderColor }) => theme.colors[borderColor]};
6
6
 
7
- ${ props => props.small && `
8
- padding: ${props.theme.spaces[1]};
9
- margin: 0px ${props.theme.spaces[3]};
7
+ ${ ({small, theme}) => small && `
8
+ padding: ${theme.spaces[1]} ${theme.spaces[2]};
9
+ margin: 0px ${theme.spaces[3]};
10
10
  vertical-align: middle;
11
11
 
12
12
  cursor: default;
13
13
 
14
14
  span {
15
- font-size: .55rem;
15
+ font-size: .65rem;
16
16
  line-height: 1;
17
17
  vertical-align: middle;
18
18
  }
@@ -9,10 +9,6 @@ const CardItemTitle = styled(CardTitle)`
9
9
  justify-content: space-between;
10
10
  align-items: center;
11
11
 
12
- color: ${({ theme }) => theme.colors.neutral800};
13
- font-size: ${({ theme }) => theme.fontSizes[2]};
14
- font-weight: ${({ theme }) => theme.fontWeights.bold};
15
-
16
12
  > div > * {
17
13
  margin: 0px ${({ theme }) => theme.spaces[1]};
18
14
  }
@@ -4,26 +4,27 @@ import { Flex } from '@strapi/design-system/Flex';
4
4
  import { IconButton } from '@strapi/design-system/IconButton';
5
5
  import { Typography } from '@strapi/design-system/Typography';
6
6
  import { Icon } from '@strapi/design-system/Icon';
7
- import { Pencil, Trash, Refresh } from '@strapi/icons/';
7
+ import { Pencil, Trash, Refresh, Drag } from '@strapi/icons/';
8
8
 
9
9
  import Wrapper from './Wrapper';
10
10
  import ItemCardBadge from '../ItemCardBadge';
11
11
  import { getMessage } from '../../../utils';
12
12
 
13
- const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore }) => {
13
+ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore, dragRef }) => {
14
14
  return (
15
15
  <Wrapper>
16
16
  <Flex alignItems="center">
17
- <Icon as={icon} />
17
+ <IconButton ref={dragRef} label="Drag" icon={<Drag />} />
18
18
  <Typography variant="omega" fontWeight="bold">
19
19
  {title}
20
20
  </Typography>
21
21
  <Typography variant="omega" fontWeight="bold" textColor='neutral500'>
22
22
  {path}
23
23
  </Typography>
24
+ <Icon as={icon}/>
24
25
  </Flex>
25
26
  <Flex alignItems="center" style={{ zIndex: 2 }}>
26
- {removed &&
27
+ {removed &&
27
28
  (<ItemCardBadge
28
29
  borderColor={`danger200`}
29
30
  backgroundColor={`danger100`}
@@ -3,7 +3,7 @@ import styled from "styled-components";
3
3
  const Wrapper = styled.div`
4
4
  position: relative;
5
5
  margin-top: ${({theme}) => theme.spaces[2]};
6
- margin-left: ${({ theme, level }) => level && theme.spaces[8]}};
6
+ margin-left: ${({ level }) => level && '54px'}};
7
7
 
8
8
  ${({ level, theme, isLast }) => level && `
9
9
  &::before {
@@ -1,6 +1,8 @@
1
+ import React, { useRef, useEffect } from 'react';
1
2
  import PropTypes from 'prop-types';
2
- import React from 'react';
3
- import { isEmpty, isNumber } from 'lodash';
3
+ import { useDrag, useDrop } from 'react-dnd';
4
+ import { getEmptyImage } from 'react-dnd-html5-backend';
5
+ import { drop, isEmpty, isNumber } from 'lodash';
4
6
 
5
7
  import { Box } from '@strapi/design-system/Box';
6
8
  import { Card, CardBody } from '@strapi/design-system/Card';
@@ -9,7 +11,7 @@ import { Flex } from '@strapi/design-system/Flex';
9
11
  import { Link } from '@strapi/design-system/Link';
10
12
  import { TextButton } from '@strapi/design-system/TextButton';
11
13
  import { Typography } from '@strapi/design-system/Typography';
12
- import { ArrowRight, Link as LinkIcon, Earth, Plus } from '@strapi/icons';
14
+ import { ArrowRight, Link as LinkIcon, Earth, Plus, Cog } from '@strapi/icons';
13
15
 
14
16
  import { navigationItemType } from '../../pages/View/utils/enums';
15
17
  import ItemCardHeader from './ItemCardHeader';
@@ -18,7 +20,8 @@ import Wrapper from './Wrapper';
18
20
  import { extractRelatedItemLabel } from '../../pages/View/utils/parsers';
19
21
  import ItemCardBadge from './ItemCardBadge';
20
22
  import { ItemCardRemovedOverlay } from './ItemCardRemovedOverlay';
21
- import { getMessage } from '../../utils';
23
+ import { getMessage, ItemTypes } from '../../utils';
24
+ import CollapseButton from '../CollapseButton';
22
25
 
23
26
  const Item = (props) => {
24
27
  const {
@@ -33,6 +36,8 @@ const Item = (props) => {
33
36
  onItemRemove,
34
37
  onItemRestore,
35
38
  onItemEdit,
39
+ onItemReOrder,
40
+ onItemToggleCollapse,
36
41
  error,
37
42
  displayChildren,
38
43
  config = {},
@@ -46,10 +51,12 @@ const Item = (props) => {
46
51
  removed,
47
52
  externalPath,
48
53
  menuAttached,
54
+ collapsed,
49
55
  } = item;
50
56
 
51
57
  const { contentTypes, contentTypesNameFields } = config;
52
58
  const isExternal = type === navigationItemType.EXTERNAL;
59
+ const isWrapper = type === navigationItemType.WRAPPER;
53
60
  const isPublished = relatedRef && relatedRef?.publishedAt;
54
61
  const isNextMenuAllowedLevel = isNumber(allowedLevels) ? level < (allowedLevels - 1) : true;
55
62
  const isMenuAllowedLevel = isNumber(allowedLevels) ? level < allowedLevels : true;
@@ -60,73 +67,126 @@ const Item = (props) => {
60
67
  const relatedTypeLabel = relatedRef?.labelSingular;
61
68
  const relatedBadgeColor = isPublished ? 'success' : 'secondary';
62
69
 
70
+ const dragRef = useRef(null);
71
+ const dropRef = useRef(null);
72
+ const previewRef = useRef(null);
73
+
74
+ const [, drop] = useDrop({
75
+ accept: `${ItemTypes.NAVIGATION_ITEM}_${levelPath}`,
76
+ hover(hoveringItem, monitor) {
77
+ const dragIndex = hoveringItem.order;
78
+ const dropIndex = item.order;
79
+
80
+ // Don't replace items with themselves
81
+ if (dragIndex === dropIndex) {
82
+ return;
83
+ }
84
+
85
+ const hoverBoundingRect = dropRef.current.getBoundingClientRect();
86
+ const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
87
+ const clientOffset = monitor.getClientOffset();
88
+ const hoverClientY = clientOffset.y - hoverBoundingRect.top;
89
+
90
+ // Place the hovering item before or after the drop target
91
+ const isAfter = hoverClientY > hoverMiddleY;
92
+ const newOrder = isAfter ? item.order + 0.5 : item.order - 0.5;
93
+
94
+ onItemReOrder({ ...hoveringItem }, newOrder);
95
+ },
96
+ collect: monitor => ({
97
+ isOverCurrent: monitor.isOver({ shallow: true }),
98
+ })
99
+ });
100
+
101
+ const [{ isDragging }, drag, dragPreview] = useDrag({
102
+ type: `${ItemTypes.NAVIGATION_ITEM}_${levelPath}`,
103
+ item: () => {
104
+ return { ...item, relatedRef };
105
+ },
106
+ collect: monitor => ({
107
+ isDragging: monitor.isDragging(),
108
+ }),
109
+ });
110
+
111
+ const refs = {
112
+ dragRef: drag(dragRef),
113
+ dropRef: drop(dropRef),
114
+ previewRef: dragPreview(previewRef),
115
+ }
116
+
63
117
  return (
64
- <Wrapper level={level} isLast={isLast}>
118
+ <Wrapper level={level} isLast={isLast} style={{ opacity: isDragging ? 0.2 : 1 }} ref={refs ? refs.dropRef : null} >
65
119
  <Card style={{ width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' }}>
66
- { removed && (<ItemCardRemovedOverlay />) }
67
- <CardBody>
68
- <ItemCardHeader
69
- title={title}
70
- path={isExternal ? externalPath : absolutePath}
71
- icon={isExternal ? Earth : LinkIcon }
72
- onItemRemove={() => onItemRemove({
73
- ...item,
74
- relatedRef,
75
- })}
76
- onItemEdit={() => onItemEdit({
77
- ...item,
78
- isMenuAllowedLevel,
79
- isParentAttachedToMenu,
80
- }, levelPath, isParentAttachedToMenu)}
81
- onItemRestore={() => onItemRestore({
82
- ...item,
83
- relatedRef,
84
- })}
85
- removed={removed}
86
- />
87
- </CardBody>
88
- <Divider />
89
- { !isExternal && (<CardBody style={{ margin: '8px' }}>
90
- <Flex style={{ width: '100%' }} direction="row" alignItems="center" justifyContent="space-between">
91
- <TextButton
92
- disabled={removed}
93
- startIcon={<Plus />}
94
- onClick={(e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached)}
95
- >
96
- <Typography variant="pi" fontWeight="bold" textColor={removed ? "neutral600" : "primary600"}>
97
- {getMessage("components.navigationItem.action.newItem")}
98
- </Typography>
99
- </TextButton>
100
- { relatedItemLabel && (<Box>
101
- <ItemCardBadge
102
- borderColor={`${relatedBadgeColor}200`}
103
- backgroundColor={`${relatedBadgeColor}100`}
104
- textColor={`${relatedBadgeColor}600`}
105
- className="action"
106
- small
120
+ {removed && (<ItemCardRemovedOverlay />)}
121
+ <div ref={refs.previewRef}>
122
+ <CardBody>
123
+ <ItemCardHeader
124
+ title={title}
125
+ path={isExternal ? externalPath : absolutePath}
126
+ icon={isExternal ? Earth : isWrapper ? Cog : LinkIcon}
127
+ onItemRemove={() => onItemRemove({
128
+ ...item,
129
+ relatedRef,
130
+ })}
131
+ onItemEdit={() => onItemEdit({
132
+ ...item,
133
+ isMenuAllowedLevel,
134
+ isParentAttachedToMenu,
135
+ relatedRef,
136
+ }, levelPath, isParentAttachedToMenu)}
137
+ onItemRestore={() => onItemRestore({
138
+ ...item,
139
+ relatedRef,
140
+ })}
141
+ dragRef={refs.dragRef}
142
+ removed={removed}
143
+ />
144
+ </CardBody>
145
+ <Divider />
146
+ {!isExternal && (
147
+ <CardBody style={{ padding: '8px' }}>
148
+ <Flex style={{ width: '100%' }} direction="row" alignItems="center" justifyContent="space-between">
149
+ <Flex>
150
+ {!isEmpty(item.items) && <CollapseButton toggle={() => onItemToggleCollapse({...item, relatedRef})} collapsed={collapsed} itemsCount={item.items.length}/>}
151
+ <TextButton
152
+ disabled={removed}
153
+ startIcon={<Plus />}
154
+ onClick={(e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached)}
107
155
  >
108
- {getMessage({
109
- id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}`, props: {
110
- type: relatedTypeLabel
111
- }
112
- })}
113
- </ItemCardBadge>
114
- <Typography variant="pi" fontWeight="bold" textColor="neutral600">
115
- { relatedItemLabel }
116
- <Link
117
- to={`/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`}
118
- endIcon={<ArrowRight />}>&nbsp;</Link>
119
- </Typography>
120
- </Box>)
121
- }
122
- </Flex>
123
- </CardBody>)}
156
+ <Typography variant="pi" fontWeight="bold" textColor={removed ? "neutral600" : "primary600"}>
157
+ {getMessage("components.navigationItem.action.newItem")}
158
+ </Typography>
159
+ </TextButton>
160
+ </Flex>
161
+ {relatedItemLabel && (
162
+ <Flex justifyContent='center' alignItems='center'>
163
+ <ItemCardBadge
164
+ borderColor={`${relatedBadgeColor}200`}
165
+ backgroundColor={`${relatedBadgeColor}100`}
166
+ textColor={`${relatedBadgeColor}600`}
167
+ className="action"
168
+ small
169
+ >
170
+ {getMessage({id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}`})}
171
+ </ItemCardBadge>
172
+ <Typography variant="omega" textColor='neutral600'>{relatedTypeLabel}&nbsp;/&nbsp;</Typography>
173
+ <Typography variant="omega" textColor='neutral800'>{relatedItemLabel}</Typography>
174
+ <Link
175
+ to={`/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`}
176
+ endIcon={<ArrowRight />}>&nbsp;</Link>
177
+ </Flex>)
178
+ }
179
+ </Flex>
180
+ </CardBody>)}
181
+ </div>
124
182
  </Card>
125
- {hasChildren && !removed && <List
183
+ {hasChildren && !removed && !collapsed && <List
126
184
  onItemLevelAdd={onItemLevelAdd}
127
185
  onItemRemove={onItemRemove}
128
186
  onItemEdit={onItemEdit}
129
187
  onItemRestore={onItemRestore}
188
+ onItemReOrder={onItemReOrder}
189
+ onItemToggleCollapse={onItemToggleCollapse}
130
190
  error={error}
131
191
  allowedLevels={allowedLevels}
132
192
  isParentAttachedToMenu={menuAttached}
@@ -150,7 +210,8 @@ Item.propTypes = {
150
210
  path: PropTypes.string,
151
211
  externalPath: PropTypes.string,
152
212
  related: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
153
- menuAttached: PropTypes.bool
213
+ menuAttached: PropTypes.bool,
214
+ collapsed: PropTypes.bool,
154
215
  }).isRequired,
155
216
  relatedRef: PropTypes.object,
156
217
  level: PropTypes.number,
@@ -159,9 +220,11 @@ Item.propTypes = {
159
220
  onItemRestore: PropTypes.func.isRequired,
160
221
  onItemLevelAdd: PropTypes.func.isRequired,
161
222
  onItemRemove: PropTypes.func.isRequired,
223
+ onItemReOrder: PropTypes.func.isRequired,
224
+ onItemToggleCollapse: PropTypes.func.isRequired,
162
225
  config: PropTypes.shape({
163
- contentTypes: PropTypes.array.isRequired,
164
- contentTypesNameFields: PropTypes.object.isRequired,
226
+ contentTypes: PropTypes.array.isRequired,
227
+ contentTypesNameFields: PropTypes.object.isRequired,
165
228
  }).isRequired
166
229
  };
167
230
 
@@ -11,7 +11,7 @@ const Wrapper = styled.div`
11
11
 
12
12
  position: absolute;
13
13
  top: -${theme.spaces[2]};
14
- left: ${theme.spaces[4]};
14
+ left: 30px;
15
15
 
16
16
  border: 0px solid transparent;
17
17
  border-left: 4px solid ${theme.colors.neutral300};
@@ -15,6 +15,8 @@ const List = ({
15
15
  onItemLevelAdd,
16
16
  onItemRemove,
17
17
  onItemRestore,
18
+ onItemReOrder,
19
+ onItemToggleCollapse,
18
20
  displayFlat,
19
21
  contentTypes,
20
22
  contentTypesNameFields,
@@ -36,6 +38,8 @@ const List = ({
36
38
  onItemLevelAdd={onItemLevelAdd}
37
39
  onItemRemove={onItemRemove}
38
40
  onItemEdit={onItemEdit}
41
+ onItemReOrder={onItemReOrder}
42
+ onItemToggleCollapse={onItemToggleCollapse}
39
43
  error={error}
40
44
  displayChildren={displayFlat}
41
45
  config={{
@@ -57,6 +61,8 @@ List.propTypes = {
57
61
  onItemRemove: PropTypes.func.isRequired,
58
62
  onItemRestore: PropTypes.func.isRequired,
59
63
  onItemRestore: PropTypes.func.isRequired,
64
+ onItemReOrder: PropTypes.func.isRequired,
65
+ onItemToggleCollapse: PropTypes.func.isRequired,
60
66
  contentTypes: PropTypes.array.isRequired,
61
67
  contentTypesNameFields: PropTypes.object.isRequired
62
68
  };
@@ -29,7 +29,7 @@ const Search = ({ value, setValue }) => {
29
29
  onChange={(e) => setValue(e.target.value)}
30
30
  clearLabel="Clearing the search"
31
31
  placeholder={formatMessage({
32
- id: getTradId('popup.item.form.audience.placeholder'),
32
+ id: getTradId('pages.main.search.placeholder'),
33
33
  defaultMessage: 'Type to start searching...',
34
34
  })}
35
35
  >