strapi-plugin-navigation 2.0.5 → 2.0.8

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 (28) hide show
  1. package/README.md +2 -1
  2. package/admin/src/components/CollapseButton/index.js +31 -0
  3. package/admin/src/components/ConfirmationDialog/index.js +1 -1
  4. package/admin/src/components/Item/ItemCardBadge/index.js +4 -4
  5. package/admin/src/components/Item/ItemCardHeader/Wrapper.js +0 -4
  6. package/admin/src/components/Item/ItemCardHeader/index.js +28 -6
  7. package/admin/src/components/Item/Wrapper.js +1 -1
  8. package/admin/src/components/Item/index.js +59 -44
  9. package/admin/src/components/NavigationItemList/Wrapper.js +1 -1
  10. package/admin/src/components/NavigationItemList/index.js +3 -0
  11. package/admin/src/pages/SettingsPage/index.js +42 -22
  12. package/admin/src/pages/View/components/NavigationHeader/index.js +1 -1
  13. package/admin/src/pages/View/components/NavigationItemForm/index.js +47 -30
  14. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +3 -3
  15. package/admin/src/pages/View/index.js +73 -9
  16. package/admin/src/pages/View/utils/enums.js +1 -0
  17. package/admin/src/pages/View/utils/parsers.js +6 -3
  18. package/admin/src/translations/en.json +12 -3
  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/services/navigation.js +44 -19
  27. package/server/services/utils/functions.js +3 -2
  28. 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;
@@ -27,7 +27,7 @@ const ConfirmationDialog = ({
27
27
  }) => (
28
28
  <Dialog onClose={onCancel} title={header || getMessage('components.confirmation.dialog.header', 'Confirmation')} isOpen={isVisible}>
29
29
  <DialogBody icon={<ExclamationMarkCircle />}>
30
- <Stack size={2}>
30
+ <Stack spacing={2}>
31
31
  <Flex justifyContent="center">
32
32
  <Typography id="dialog-confirm-description">{children || getMessage('components.confirmation.dialog.description')}</Typography>
33
33
  </Flex>
@@ -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
  }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import styled from 'styled-components';
2
3
 
3
4
  import { Flex } from '@strapi/design-system/Flex';
4
5
  import { IconButton } from '@strapi/design-system/IconButton';
@@ -10,20 +11,44 @@ import Wrapper from './Wrapper';
10
11
  import ItemCardBadge from '../ItemCardBadge';
11
12
  import { getMessage } from '../../../utils';
12
13
 
14
+ const IconWrapper = styled.div`
15
+ display: flex;
16
+ align-items: center;
17
+ justify-content: center;
18
+ height: ${32 / 16}rem;
19
+ width: ${32 / 16}rem;
20
+
21
+ cursor: pointer;
22
+ padding: ${({ theme }) => theme.spaces[2]};
23
+ border-radius: ${({ theme }) => theme.borderRadius};
24
+ background: ${({ theme }) => theme.colors.neutral0};
25
+ border: 1px solid ${({ theme }) => theme.colors.neutral200};
26
+
27
+ svg {
28
+ > g,
29
+ path {
30
+ fill: ${({ theme }) => theme.colors.neutral500};
31
+ }
32
+ }
33
+ `
34
+
13
35
  const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore, dragRef }) => {
14
36
  return (
15
37
  <Wrapper>
16
38
  <Flex alignItems="center">
17
- <Icon as={icon} />
39
+ <IconWrapper ref={dragRef}>
40
+ <Icon as={Drag} />
41
+ </IconWrapper>
18
42
  <Typography variant="omega" fontWeight="bold">
19
43
  {title}
20
44
  </Typography>
21
45
  <Typography variant="omega" fontWeight="bold" textColor='neutral500'>
22
46
  {path}
23
47
  </Typography>
48
+ <Icon as={icon} />
24
49
  </Flex>
25
50
  <Flex alignItems="center" style={{ zIndex: 2 }}>
26
- {removed &&
51
+ {removed &&
27
52
  (<ItemCardBadge
28
53
  borderColor={`danger200`}
29
54
  backgroundColor={`danger100`}
@@ -36,10 +61,7 @@ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit,
36
61
  <IconButton disabled={removed} onClick={onItemEdit} label="Edit" icon={<Pencil />} />
37
62
  {removed ?
38
63
  <IconButton onClick={onItemRestore} label="Restore" icon={<Refresh />} /> :
39
- <>
40
- <IconButton ref={dragRef} label="Drag" icon={<Drag />} />
41
- <IconButton onClick={onItemRemove} label="Remove" icon={<Trash />} />
42
- </>
64
+ <IconButton onClick={onItemRemove} label="Remove" icon={<Trash />} />
43
65
  }
44
66
  </Flex>
45
67
  </Wrapper>
@@ -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,17 +1,15 @@
1
- import React, { useRef, useEffect } from 'react';
1
+ import React, { useRef } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useDrag, useDrop } from 'react-dnd';
4
- import { getEmptyImage } from 'react-dnd-html5-backend';
5
- import { drop, isEmpty, isNumber } from 'lodash';
4
+ import { isEmpty, isNumber } from 'lodash';
6
5
 
7
- import { Box } from '@strapi/design-system/Box';
8
6
  import { Card, CardBody } from '@strapi/design-system/Card';
9
7
  import { Divider } from '@strapi/design-system/Divider';
10
8
  import { Flex } from '@strapi/design-system/Flex';
11
9
  import { Link } from '@strapi/design-system/Link';
12
10
  import { TextButton } from '@strapi/design-system/TextButton';
13
11
  import { Typography } from '@strapi/design-system/Typography';
14
- import { ArrowRight, Link as LinkIcon, Earth, Plus } from '@strapi/icons';
12
+ import { ArrowRight, Link as LinkIcon, Earth, Plus, Cog } from '@strapi/icons';
15
13
 
16
14
  import { navigationItemType } from '../../pages/View/utils/enums';
17
15
  import ItemCardHeader from './ItemCardHeader';
@@ -21,6 +19,7 @@ import { extractRelatedItemLabel } from '../../pages/View/utils/parsers';
21
19
  import ItemCardBadge from './ItemCardBadge';
22
20
  import { ItemCardRemovedOverlay } from './ItemCardRemovedOverlay';
23
21
  import { getMessage, ItemTypes } from '../../utils';
22
+ import CollapseButton from '../CollapseButton';
24
23
 
25
24
  const Item = (props) => {
26
25
  const {
@@ -36,6 +35,7 @@ const Item = (props) => {
36
35
  onItemRestore,
37
36
  onItemEdit,
38
37
  onItemReOrder,
38
+ onItemToggleCollapse,
39
39
  error,
40
40
  displayChildren,
41
41
  config = {},
@@ -49,10 +49,12 @@ const Item = (props) => {
49
49
  removed,
50
50
  externalPath,
51
51
  menuAttached,
52
+ collapsed,
52
53
  } = item;
53
54
 
54
55
  const { contentTypes, contentTypesNameFields } = config;
55
56
  const isExternal = type === navigationItemType.EXTERNAL;
57
+ const isWrapper = type === navigationItemType.WRAPPER;
56
58
  const isPublished = relatedRef && relatedRef?.publishedAt;
57
59
  const isNextMenuAllowedLevel = isNumber(allowedLevels) ? level < (allowedLevels - 1) : true;
58
60
  const isMenuAllowedLevel = isNumber(allowedLevels) ? level < allowedLevels : true;
@@ -87,7 +89,16 @@ const Item = (props) => {
87
89
  const isAfter = hoverClientY > hoverMiddleY;
88
90
  const newOrder = isAfter ? item.order + 0.5 : item.order - 0.5;
89
91
 
92
+ if (dragIndex < dropIndex && hoverClientY < hoverMiddleY) {
93
+ return;
94
+ }
95
+ // Dragging upwards
96
+ if (dragIndex > dropIndex && hoverClientY > hoverMiddleY) {
97
+ return;
98
+ }
99
+
90
100
  onItemReOrder({ ...hoveringItem }, newOrder);
101
+ hoveringItem.order = newOrder;
91
102
  },
92
103
  collect: monitor => ({
93
104
  isOverCurrent: monitor.isOver({ shallow: true }),
@@ -97,7 +108,7 @@ const Item = (props) => {
97
108
  const [{ isDragging }, drag, dragPreview] = useDrag({
98
109
  type: `${ItemTypes.NAVIGATION_ITEM}_${levelPath}`,
99
110
  item: () => {
100
- return { ...item };
111
+ return { ...item, relatedRef };
101
112
  },
102
113
  collect: monitor => ({
103
114
  isDragging: monitor.isDragging(),
@@ -119,7 +130,7 @@ const Item = (props) => {
119
130
  <ItemCardHeader
120
131
  title={title}
121
132
  path={isExternal ? externalPath : absolutePath}
122
- icon={isExternal ? Earth : LinkIcon}
133
+ icon={isExternal ? Earth : isWrapper ? Cog : LinkIcon}
123
134
  onItemRemove={() => onItemRemove({
124
135
  ...item,
125
136
  relatedRef,
@@ -128,6 +139,7 @@ const Item = (props) => {
128
139
  ...item,
129
140
  isMenuAllowedLevel,
130
141
  isParentAttachedToMenu,
142
+ relatedRef,
131
143
  }, levelPath, isParentAttachedToMenu)}
132
144
  onItemRestore={() => onItemRestore({
133
145
  ...item,
@@ -138,49 +150,50 @@ const Item = (props) => {
138
150
  />
139
151
  </CardBody>
140
152
  <Divider />
141
- {!isExternal && (<CardBody style={{ margin: '8px' }}>
142
- <Flex style={{ width: '100%' }} direction="row" alignItems="center" justifyContent="space-between">
143
- <TextButton
144
- disabled={removed}
145
- startIcon={<Plus />}
146
- onClick={(e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached)}
147
- >
148
- <Typography variant="pi" fontWeight="bold" textColor={removed ? "neutral600" : "primary600"}>
149
- {getMessage("components.navigationItem.action.newItem")}
150
- </Typography>
151
- </TextButton>
152
- {relatedItemLabel && (<Box>
153
- <ItemCardBadge
154
- borderColor={`${relatedBadgeColor}200`}
155
- backgroundColor={`${relatedBadgeColor}100`}
156
- textColor={`${relatedBadgeColor}600`}
157
- className="action"
158
- small
159
- >
160
- {getMessage({
161
- id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}`, props: {
162
- type: relatedTypeLabel
163
- }
164
- })}
165
- </ItemCardBadge>
166
- <Typography variant="pi" fontWeight="bold" textColor="neutral600">
167
- {relatedItemLabel}
168
- <Link
169
- to={`/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`}
170
- endIcon={<ArrowRight />}>&nbsp;</Link>
171
- </Typography>
172
- </Box>)
173
- }
174
- </Flex>
175
- </CardBody>)}
153
+ {!isExternal && (
154
+ <CardBody style={{ padding: '8px' }}>
155
+ <Flex style={{ width: '100%' }} direction="row" alignItems="center" justifyContent="space-between">
156
+ <Flex>
157
+ {!isEmpty(item.items) && <CollapseButton toggle={() => onItemToggleCollapse({...item, relatedRef})} collapsed={collapsed} itemsCount={item.items.length}/>}
158
+ <TextButton
159
+ disabled={removed}
160
+ startIcon={<Plus />}
161
+ onClick={(e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached)}
162
+ >
163
+ <Typography variant="pi" fontWeight="bold" textColor={removed ? "neutral600" : "primary600"}>
164
+ {getMessage("components.navigationItem.action.newItem")}
165
+ </Typography>
166
+ </TextButton>
167
+ </Flex>
168
+ {relatedItemLabel && (
169
+ <Flex justifyContent='center' alignItems='center'>
170
+ <ItemCardBadge
171
+ borderColor={`${relatedBadgeColor}200`}
172
+ backgroundColor={`${relatedBadgeColor}100`}
173
+ textColor={`${relatedBadgeColor}600`}
174
+ className="action"
175
+ small
176
+ >
177
+ {getMessage({id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}`})}
178
+ </ItemCardBadge>
179
+ <Typography variant="omega" textColor='neutral600'>{relatedTypeLabel}&nbsp;/&nbsp;</Typography>
180
+ <Typography variant="omega" textColor='neutral800'>{relatedItemLabel}</Typography>
181
+ <Link
182
+ to={`/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`}
183
+ endIcon={<ArrowRight />}>&nbsp;</Link>
184
+ </Flex>)
185
+ }
186
+ </Flex>
187
+ </CardBody>)}
176
188
  </div>
177
189
  </Card>
178
- {hasChildren && !removed && <List
190
+ {hasChildren && !removed && !collapsed && <List
179
191
  onItemLevelAdd={onItemLevelAdd}
180
192
  onItemRemove={onItemRemove}
181
193
  onItemEdit={onItemEdit}
182
194
  onItemRestore={onItemRestore}
183
195
  onItemReOrder={onItemReOrder}
196
+ onItemToggleCollapse={onItemToggleCollapse}
184
197
  error={error}
185
198
  allowedLevels={allowedLevels}
186
199
  isParentAttachedToMenu={menuAttached}
@@ -204,7 +217,8 @@ Item.propTypes = {
204
217
  path: PropTypes.string,
205
218
  externalPath: PropTypes.string,
206
219
  related: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
207
- menuAttached: PropTypes.bool
220
+ menuAttached: PropTypes.bool,
221
+ collapsed: PropTypes.bool,
208
222
  }).isRequired,
209
223
  relatedRef: PropTypes.object,
210
224
  level: PropTypes.number,
@@ -214,6 +228,7 @@ Item.propTypes = {
214
228
  onItemLevelAdd: PropTypes.func.isRequired,
215
229
  onItemRemove: PropTypes.func.isRequired,
216
230
  onItemReOrder: PropTypes.func.isRequired,
231
+ onItemToggleCollapse: PropTypes.func.isRequired,
217
232
  config: PropTypes.shape({
218
233
  contentTypes: PropTypes.array.isRequired,
219
234
  contentTypesNameFields: PropTypes.object.isRequired,
@@ -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};
@@ -16,6 +16,7 @@ const List = ({
16
16
  onItemRemove,
17
17
  onItemRestore,
18
18
  onItemReOrder,
19
+ onItemToggleCollapse,
19
20
  displayFlat,
20
21
  contentTypes,
21
22
  contentTypesNameFields,
@@ -38,6 +39,7 @@ const List = ({
38
39
  onItemRemove={onItemRemove}
39
40
  onItemEdit={onItemEdit}
40
41
  onItemReOrder={onItemReOrder}
42
+ onItemToggleCollapse={onItemToggleCollapse}
41
43
  error={error}
42
44
  displayChildren={displayFlat}
43
45
  config={{
@@ -60,6 +62,7 @@ List.propTypes = {
60
62
  onItemRestore: PropTypes.func.isRequired,
61
63
  onItemRestore: PropTypes.func.isRequired,
62
64
  onItemReOrder: PropTypes.func.isRequired,
65
+ onItemToggleCollapse: PropTypes.func.isRequired,
63
66
  contentTypes: PropTypes.array.isRequired,
64
67
  contentTypesNameFields: PropTypes.object.isRequired
65
68
  };
@@ -49,13 +49,14 @@ const SettingsPage = () => {
49
49
  padding: 6,
50
50
  };
51
51
 
52
- const preparePayload = ({ selectedContentTypes, nameFields, audienceFieldChecked, allowedLevels }) => ({
52
+ const preparePayload = ({ selectedContentTypes, nameFields, audienceFieldChecked, allowedLevels, populate }) => ({
53
53
  contentTypes: selectedContentTypes,
54
54
  contentTypesNameFields: nameFields,
55
+ contentTypesPopulate: populate,
55
56
  additionalFields: audienceFieldChecked ? [navigationItemAdditionalFields.AUDIENCE] : [],
56
57
  allowedLevels: allowedLevels,
57
58
  gql: {
58
- 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, ''))
59
60
  }
60
61
  });
61
62
 
@@ -110,8 +111,9 @@ const SettingsPage = () => {
110
111
  const allContentTypes = !isLoading && Object.values(allContentTypesData).filter(item => item.uid.includes('api::'));
111
112
  const selectedContentTypes = navigationConfigData?.contentTypes.map(item => item.uid);
112
113
  const audienceFieldChecked = navigationConfigData?.additionalFields.includes(navigationItemAdditionalFields.AUDIENCE);
113
- const allowedLevels = navigationConfigData?.allowedLevels;
114
- const nameFields = navigationConfigData?.contentTypesNameFields
114
+ const allowedLevels = navigationConfigData?.allowedLevels || 2;
115
+ const nameFields = navigationConfigData?.contentTypesNameFields || {}
116
+ const populate = navigationConfigData?.contentTypesPopulate || {}
115
117
 
116
118
  return (
117
119
  <>
@@ -125,6 +127,7 @@ const SettingsPage = () => {
125
127
  audienceFieldChecked,
126
128
  allowedLevels,
127
129
  nameFields,
130
+ populate,
128
131
  }}
129
132
  onSubmit={onSave}
130
133
  >
@@ -142,7 +145,7 @@ const SettingsPage = () => {
142
145
  }
143
146
  />
144
147
  <ContentLayout>
145
- <Stack size={7}>
148
+ <Stack spacing={7}>
146
149
  {isRestartRequired && (
147
150
  <RestartAlert
148
151
  closeLabel={getMessage('pages.settings.actions.restart.alert.cancel')}
@@ -152,7 +155,7 @@ const SettingsPage = () => {
152
155
  {getMessage('pages.settings.actions.restart.alert.description')}
153
156
  </RestartAlert>)}
154
157
  <Box {...boxDefaultProps} >
155
- <Stack size={4}>
158
+ <Stack spacing={4}>
156
159
  <Typography variant="delta" as="h2">
157
160
  {getMessage('pages.settings.general.title')}
158
161
  </Typography>
@@ -183,6 +186,7 @@ const SettingsPage = () => {
183
186
  {orderBy(values.selectedContentTypes).map(uid => {
184
187
  const { attributes, info: { displayName } } = allContentTypes.find(item => item.uid == uid);
185
188
  const stringAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'string');
189
+ const relationAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'relation');
186
190
  const key = `collectionSettings-${uid}`;
187
191
  return (<Accordion
188
192
  expanded={contentTypeExpanded === key}
@@ -193,24 +197,40 @@ const SettingsPage = () => {
193
197
  <AccordionToggle title={displayName} togglePosition="left" />
194
198
  <AccordionContent>
195
199
  <Box padding={6}>
196
- <Stack size={4}>
200
+ <Stack spacing={4}>
197
201
  <Select
198
202
  name={`collectionSettings-${uid}-entryLabel`}
199
203
  label={getMessage('pages.settings.form.nameField.label')}
200
- hint={getMessage('pages.settings.form.nameField.hint')}
204
+ hint={getMessage(`pages.settings.form.populate.${isEmpty(stringAttributes) ? 'empty' : 'hint'}`)}
201
205
  placeholder={getMessage('pages.settings.form.nameField.placeholder')}
202
206
  onClear={() => null}
203
207
  value={values.nameFields[uid] || []}
204
208
  onChange={(value) => setFieldValue('nameFields', prepareNameFieldFor(uid, values.nameFields, value))}
205
209
  multi
206
210
  withTags
207
- disabled={isRestartRequired}
211
+ disabled={isRestartRequired || isEmpty(stringAttributes)}
208
212
  >
209
213
  {stringAttributes.map(key =>
210
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>))}
211
230
  </Select>
212
231
  </Stack>
213
232
  </Box>
233
+
214
234
  </AccordionContent>
215
235
  </Accordion>);
216
236
  })}
@@ -220,22 +240,22 @@ const SettingsPage = () => {
220
240
  </Stack>
221
241
  </Box>
222
242
  <Box {...boxDefaultProps} >
223
- <Stack size={4}>
243
+ <Stack spacing={4}>
224
244
  <Typography variant="delta" as="h2">
225
245
  {getMessage('pages.settings.additional.title')}
226
246
  </Typography>
227
247
  <Grid gap={4}>
228
248
  <GridItem col={3} s={6} xs={12}>
229
- <NumberInput
230
- name="allowedLevels"
231
- label={getMessage('pages.settings.form.allowedLevels.label')}
232
- placeholder={getMessage('pages.settings.form.allowedLevels.placeholder')}
233
- hint={getMessage('pages.settings.form.allowedLevels.hint')}
234
- onValueChange={(value) => setFieldValue('allowedLevels', value, false)}
235
- value={values.allowedLevels}
236
- disabled={isRestartRequired}
237
- />
238
- </GridItem>
249
+ <NumberInput
250
+ name="allowedLevels"
251
+ label={getMessage('pages.settings.form.allowedLevels.label')}
252
+ placeholder={getMessage('pages.settings.form.allowedLevels.placeholder')}
253
+ hint={getMessage('pages.settings.form.allowedLevels.hint')}
254
+ onValueChange={(value) => setFieldValue('allowedLevels', value, false)}
255
+ value={values.allowedLevels}
256
+ disabled={isRestartRequired}
257
+ />
258
+ </GridItem>
239
259
  <GridItem col={6} s={12} xs={12}>
240
260
  <ToggleInput
241
261
  name="audienceFieldChecked"
@@ -252,7 +272,7 @@ const SettingsPage = () => {
252
272
  </Stack>
253
273
  </Box>
254
274
  <Box {...boxDefaultProps} >
255
- <Stack size={4}>
275
+ <Stack spacing={4}>
256
276
  <Typography variant="delta" as="h2">
257
277
  {getMessage('pages.settings.restoring.title')}
258
278
  </Typography>
@@ -292,4 +312,4 @@ const SettingsPage = () => {
292
312
  }
293
313
 
294
314
 
295
- export default SettingsPage;
315
+ export default SettingsPage;
@@ -22,7 +22,7 @@ const NavigationHeader = ({
22
22
  return (
23
23
  <HeaderLayout
24
24
  primaryAction={
25
- <Stack horizontal size={2}>
25
+ <Stack horizontal spacing={2}>
26
26
  <Box width="10vw">
27
27
  <Select
28
28
  type="select"