strapi-plugin-navigation 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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
@@ -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
  }
@@ -14,16 +14,17 @@ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit,
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`}
@@ -36,10 +37,7 @@ const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit,
36
37
  <IconButton disabled={removed} onClick={onItemEdit} label="Edit" icon={<Pencil />} />
37
38
  {removed ?
38
39
  <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
- </>
40
+ <IconButton onClick={onItemRemove} label="Remove" icon={<Trash />} />
43
41
  }
44
42
  </Flex>
45
43
  </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 {
@@ -11,7 +11,7 @@ import { Flex } from '@strapi/design-system/Flex';
11
11
  import { Link } from '@strapi/design-system/Link';
12
12
  import { TextButton } from '@strapi/design-system/TextButton';
13
13
  import { Typography } from '@strapi/design-system/Typography';
14
- import { ArrowRight, Link as LinkIcon, Earth, Plus } from '@strapi/icons';
14
+ import { ArrowRight, Link as LinkIcon, Earth, Plus, Cog } from '@strapi/icons';
15
15
 
16
16
  import { navigationItemType } from '../../pages/View/utils/enums';
17
17
  import ItemCardHeader from './ItemCardHeader';
@@ -21,6 +21,7 @@ import { extractRelatedItemLabel } from '../../pages/View/utils/parsers';
21
21
  import ItemCardBadge from './ItemCardBadge';
22
22
  import { ItemCardRemovedOverlay } from './ItemCardRemovedOverlay';
23
23
  import { getMessage, ItemTypes } from '../../utils';
24
+ import CollapseButton from '../CollapseButton';
24
25
 
25
26
  const Item = (props) => {
26
27
  const {
@@ -36,6 +37,7 @@ const Item = (props) => {
36
37
  onItemRestore,
37
38
  onItemEdit,
38
39
  onItemReOrder,
40
+ onItemToggleCollapse,
39
41
  error,
40
42
  displayChildren,
41
43
  config = {},
@@ -49,10 +51,12 @@ const Item = (props) => {
49
51
  removed,
50
52
  externalPath,
51
53
  menuAttached,
54
+ collapsed,
52
55
  } = item;
53
56
 
54
57
  const { contentTypes, contentTypesNameFields } = config;
55
58
  const isExternal = type === navigationItemType.EXTERNAL;
59
+ const isWrapper = type === navigationItemType.WRAPPER;
56
60
  const isPublished = relatedRef && relatedRef?.publishedAt;
57
61
  const isNextMenuAllowedLevel = isNumber(allowedLevels) ? level < (allowedLevels - 1) : true;
58
62
  const isMenuAllowedLevel = isNumber(allowedLevels) ? level < allowedLevels : true;
@@ -97,7 +101,7 @@ const Item = (props) => {
97
101
  const [{ isDragging }, drag, dragPreview] = useDrag({
98
102
  type: `${ItemTypes.NAVIGATION_ITEM}_${levelPath}`,
99
103
  item: () => {
100
- return { ...item };
104
+ return { ...item, relatedRef };
101
105
  },
102
106
  collect: monitor => ({
103
107
  isDragging: monitor.isDragging(),
@@ -119,7 +123,7 @@ const Item = (props) => {
119
123
  <ItemCardHeader
120
124
  title={title}
121
125
  path={isExternal ? externalPath : absolutePath}
122
- icon={isExternal ? Earth : LinkIcon}
126
+ icon={isExternal ? Earth : isWrapper ? Cog : LinkIcon}
123
127
  onItemRemove={() => onItemRemove({
124
128
  ...item,
125
129
  relatedRef,
@@ -128,6 +132,7 @@ const Item = (props) => {
128
132
  ...item,
129
133
  isMenuAllowedLevel,
130
134
  isParentAttachedToMenu,
135
+ relatedRef,
131
136
  }, levelPath, isParentAttachedToMenu)}
132
137
  onItemRestore={() => onItemRestore({
133
138
  ...item,
@@ -138,49 +143,50 @@ const Item = (props) => {
138
143
  />
139
144
  </CardBody>
140
145
  <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>)}
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)}
155
+ >
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>)}
176
181
  </div>
177
182
  </Card>
178
- {hasChildren && !removed && <List
183
+ {hasChildren && !removed && !collapsed && <List
179
184
  onItemLevelAdd={onItemLevelAdd}
180
185
  onItemRemove={onItemRemove}
181
186
  onItemEdit={onItemEdit}
182
187
  onItemRestore={onItemRestore}
183
188
  onItemReOrder={onItemReOrder}
189
+ onItemToggleCollapse={onItemToggleCollapse}
184
190
  error={error}
185
191
  allowedLevels={allowedLevels}
186
192
  isParentAttachedToMenu={menuAttached}
@@ -204,7 +210,8 @@ Item.propTypes = {
204
210
  path: PropTypes.string,
205
211
  externalPath: PropTypes.string,
206
212
  related: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
207
- menuAttached: PropTypes.bool
213
+ menuAttached: PropTypes.bool,
214
+ collapsed: PropTypes.bool,
208
215
  }).isRequired,
209
216
  relatedRef: PropTypes.object,
210
217
  level: PropTypes.number,
@@ -214,6 +221,7 @@ Item.propTypes = {
214
221
  onItemLevelAdd: PropTypes.func.isRequired,
215
222
  onItemRemove: PropTypes.func.isRequired,
216
223
  onItemReOrder: PropTypes.func.isRequired,
224
+ onItemToggleCollapse: PropTypes.func.isRequired,
217
225
  config: PropTypes.shape({
218
226
  contentTypes: PropTypes.array.isRequired,
219
227
  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,9 +49,10 @@ 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: {
@@ -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
  >
@@ -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}
@@ -197,20 +201,36 @@ const SettingsPage = () => {
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
  })}
@@ -226,16 +246,16 @@ const SettingsPage = () => {
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"
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState, useCallback } from 'react';
2
2
  import { debounce, find, get, isEmpty, isEqual, isNil, isString } from 'lodash';
3
3
  import PropTypes from 'prop-types';
4
4
  import { Formik } from 'formik'
5
+ import slugify from 'slugify';
5
6
 
6
7
  // Design System
7
8
  import { ModalBody } from '@strapi/design-system/ModalLayout';
@@ -10,10 +11,7 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
10
11
  import { Form, GenericInput } from '@strapi/helper-plugin';
11
12
 
12
13
  import { NavigationItemPopupFooter } from '../NavigationItemPopup/NavigationItemPopupFooter';
13
-
14
-
15
14
  import { navigationItemAdditionalFields, navigationItemType } from '../../utils/enums';
16
- import slugify from 'slugify';
17
15
  import { extractRelatedItemLabel } from '../../utils/parsers';
18
16
  import { form as formDefinition } from './utils/form';
19
17
  import { checkFormValidity } from '../../utils/form';
@@ -76,20 +74,19 @@ const NavigationItemForm = ({
76
74
  };
77
75
 
78
76
  const sanitizePayload = (payload = {}) => {
79
- const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, ...purePayload } = payload;
80
- const sanitizedType = purePayload.type || navigationItemType.INTERNAL;
77
+ const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, type, ...purePayload } = payload;
81
78
  const relatedId = related
82
79
  const relatedCollectionType = relatedType;
83
80
  const title = payload.title || relatedSelectOptions.find(v => v.key == relatedId)?.label
84
81
  return {
85
82
  ...purePayload,
86
83
  title,
84
+ type,
87
85
  menuAttached: isNil(menuAttached) ? false : menuAttached,
88
- type: sanitizedType,
89
- path: sanitizedType === navigationItemType.INTERNAL ? purePayload.path : undefined,
90
- externalPath: sanitizedType === navigationItemType.EXTERNAL ? purePayload.externalPath : undefined,
91
- related: relatedId,
92
- relatedType: relatedCollectionType,
86
+ path: type !== navigationItemType.EXTERNAL ? purePayload.path : undefined,
87
+ externalPath: type === navigationItemType.EXTERNAL ? purePayload.externalPath : undefined,
88
+ related: type === navigationItemType.INTERNAL ? relatedId : undefined,
89
+ relatedType: type === navigationItemType.INTERNAL ? relatedCollectionType : undefined,
93
90
  isSingle: isSingleSelected,
94
91
  uiRouterKey: generateUiRouterKey(purePayload.title, relatedId, relatedCollectionType),
95
92
  };
@@ -109,11 +106,8 @@ const NavigationItemForm = ({
109
106
  }
110
107
  };
111
108
 
112
- const onTypeChange = ({ target: { name, value } }) =>
113
- onChange({ target: { name, value: value ? navigationItemType.INTERNAL : navigationItemType.EXTERNAL } });
114
-
115
109
  const onAudienceChange = (value) => {
116
- onChange({target: {name: `${inputsPrefix}audience`, value}});
110
+ onChange({ target: { name: `${inputsPrefix}audience`, value } });
117
111
  }
118
112
 
119
113
  const onChange = ({ target: { name, value } }) => {
@@ -148,6 +142,20 @@ const NavigationItemForm = ({
148
142
  [relatedTypeSelectValue, contentTypes],
149
143
  );
150
144
 
145
+ const navigationItemTypeOptions = Object.keys(navigationItemType).map(key => {
146
+ const value = navigationItemType[key].toLowerCase();
147
+ return {
148
+ key,
149
+ value: navigationItemType[key],
150
+ metadatas: {
151
+ intlLabel: {
152
+ id: getTradId(`popup.item.form.type.${value}.label`),
153
+ defaultMessage: getTradId(`popup.item.form.type.${value}.label`),
154
+ }
155
+ }
156
+ }
157
+ });
158
+
151
159
  const relatedSelectOptions = contentTypeEntities
152
160
  .filter((item) => {
153
161
  const usedContentTypeEntitiesOfSameType = usedContentTypeEntities
@@ -178,7 +186,9 @@ const NavigationItemForm = ({
178
186
  const isExternal = form.type === navigationItemType.EXTERNAL;
179
187
  const pathSourceName = isExternal ? 'externalPath' : 'path';
180
188
 
181
- const submitDisabled = (form.type !== navigationItemType.EXTERNAL) && isNil(form.related);
189
+ const submitDisabled =
190
+ (form.type === navigationItemType.INTERNAL && isNil(get(form, `${inputsPrefix}related`))) ||
191
+ (form.type === navigationItemType.WRAPPER && isNil(get(form, `${inputsPrefix}title`)));
182
192
 
183
193
  const debouncedSearch = useCallback(
184
194
  debounce(nextValue => setContentTypeSearchQuery(nextValue), 500),
@@ -285,7 +295,21 @@ const NavigationItemForm = ({
285
295
  value={get(form, `${inputsPrefix}title`, '')}
286
296
  />
287
297
  </GridItem>
288
- <GridItem key={`${inputsPrefix}menuAttached`} col={6} lg={12}>
298
+ <GridItem key={`${inputsPrefix}type`} col={4} lg={12}>
299
+ <GenericInput
300
+ intlLabel={{
301
+ id: getTradId('popup.item.form.type.label'),
302
+ defaultMessage: 'Internal link',
303
+ }}
304
+ name={`${inputsPrefix}type`}
305
+ options={navigationItemTypeOptions}
306
+ type='select'
307
+ error={get(formErrors, `${inputsPrefix}type.id`)}
308
+ onChange={onChange}
309
+ value={get(form, `${inputsPrefix}type`, '')}
310
+ />
311
+ </GridItem>
312
+ <GridItem key={`${inputsPrefix}menuAttached`} col={4} lg={12}>
289
313
  <GenericInput
290
314
  intlLabel={{
291
315
  id: getTradId('popup.item.form.menuAttached.label'),
@@ -299,19 +323,6 @@ const NavigationItemForm = ({
299
323
  disabled={!(data.isMenuAllowedLevel && data.parentAttachedToMenu)}
300
324
  />
301
325
  </GridItem>
302
- <GridItem key={`${inputsPrefix}type`} col={6} lg={12}>
303
- <GenericInput
304
- intlLabel={{
305
- id: getTradId('popup.item.form.type.label'),
306
- defaultMessage: 'Internal link',
307
- }}
308
- name={`${inputsPrefix}type`}
309
- type='bool'
310
- error={get(formErrors, `${inputsPrefix}type.id`)}
311
- onChange={onTypeChange}
312
- value={get(form, `${inputsPrefix}type`, '') === navigationItemType.INTERNAL}
313
- />
314
- </GridItem>
315
326
  <GridItem key={`${inputsPrefix}path`} col={12}>
316
327
  <GenericInput
317
328
  intlLabel={{
@@ -330,7 +341,7 @@ const NavigationItemForm = ({
330
341
  description={generatePreviewPath()}
331
342
  />
332
343
  </GridItem>
333
- {!isExternal && (
344
+ {get(form, `${inputsPrefix}type`) === navigationItemType.INTERNAL && (
334
345
  <>
335
346
  <GridItem col={6} lg={12}>
336
347
  <GenericInput
@@ -402,8 +413,14 @@ const NavigationItemForm = ({
402
413
  label={getMessage('popup.item.form.audience.label')}
403
414
  onChange={onAudienceChange}
404
415
  value={audience}
416
+ hint={
417
+ !isLoading && isEmpty(audienceOptions)
418
+ ? getMessage('popup.item.form.audience.empty', 'There are no more audiences')
419
+ : undefined
420
+ }
405
421
  multi
406
422
  withTags
423
+ disabled={isEmpty(audienceOptions)}
407
424
  >
408
425
  {audienceOptions.map(({ value, label }) => <Option key={value} value={value}>{label}</Option>)}
409
426
  </Select>
@@ -120,6 +120,37 @@ const View = () => {
120
120
  }, []);
121
121
  const filteredList = !isSearchEmpty ? filteredListFactory(changedActiveNavigation.items, (item) => item?.title.includes(searchValue)) : [];
122
122
 
123
+ const changeCollapseItemDeep = (item, collapse) => {
124
+ if (item.collapsed !== collapse) {
125
+ return {
126
+ ...item,
127
+ collapsed: collapse,
128
+ updated: true,
129
+ items: item.items?.map(el => changeCollapseItemDeep(el, collapse))
130
+ }
131
+ }
132
+ return {
133
+ ...item,
134
+ items: item.items?.map(el => changeCollapseItemDeep(el, collapse))
135
+ }
136
+ }
137
+
138
+ const handleCollapseAll = () => {
139
+ handleChangeNavigationData({
140
+ ...changedActiveNavigation,
141
+ items: changedActiveNavigation.items.map(item => changeCollapseItemDeep(item, true))
142
+ }, true);
143
+ setStructureChanged(true);
144
+ }
145
+
146
+ const handleExpandAll = () => {
147
+ handleChangeNavigationData({
148
+ ...changedActiveNavigation,
149
+ items: changedActiveNavigation.items.map(item => changeCollapseItemDeep(item, false))
150
+ }, true);
151
+ setStructureChanged(true);
152
+ }
153
+
123
154
  const handleItemReOrder = (item, newOrder) => {
124
155
  handleSubmitNavigationItem({
125
156
  ...item,
@@ -141,6 +172,14 @@ const View = () => {
141
172
  });
142
173
  };
143
174
 
175
+ const handleItemToggleCollapse = (item) => {
176
+ handleSubmitNavigationItem({
177
+ ...item,
178
+ collapsed: !item.collapsed,
179
+ updated: true,
180
+ });
181
+ }
182
+
144
183
  const handleItemEdit = (
145
184
  item,
146
185
  levelPath = '',
@@ -164,6 +203,33 @@ const View = () => {
164
203
  setSearchValue('');
165
204
  }
166
205
 
206
+ const endActions = [
207
+ {
208
+ onClick: handleExpandAll,
209
+ disabled: isLoadingForSubmit,
210
+ type: "submit",
211
+ variant: 'tertiary',
212
+ tradId: 'header.action.expandAll',
213
+ margin: '8px',
214
+ },
215
+ {
216
+ onClick: handleCollapseAll,
217
+ disabled: isLoadingForSubmit,
218
+ type: "submit",
219
+ variant: 'tertiary',
220
+ tradId: 'header.action.collapseAll',
221
+ margin: '8px',
222
+ },
223
+ {
224
+ onClick: addNewNavigationItem,
225
+ startIcon: <PlusIcon />,
226
+ disabled: isLoadingForSubmit,
227
+ type: "submit",
228
+ tradId: 'header.action.newItem',
229
+ margin: '16px',
230
+ },
231
+ ]
232
+
167
233
  return (
168
234
  <Main labelledBy="title" aria-busy={isLoadingForSubmit}>
169
235
  <NavigationHeader
@@ -180,18 +246,15 @@ const View = () => {
180
246
  <>
181
247
  <NavigationContentHeader
182
248
  startActions={<Search value={searchValue} setValue={setSearchValue} />}
183
- endActions={<Button
184
- onClick={addNewNavigationItem}
185
- startIcon={<PlusIcon />}
186
- disabled={isLoadingForSubmit}
187
- type="submit"
188
- >
189
- {formatMessage(getTrad('header.action.newItem'))}
190
- </Button>}
249
+ endActions={endActions.map(({ tradId, margin, ...item }, i) =>
250
+ <Box marginLeft={margin} key={i}>
251
+ <Button {...item}> {formatMessage(getTrad(tradId))} </Button>
252
+ </Box>
253
+ )}
191
254
  />
192
255
  {isEmpty(changedActiveNavigation.items || []) && (
193
256
  <Flex direction="column" minHeight="400px" justifyContent="center">
194
- <Icon as={EmptyDocumentsIcon} width="160px" height="88px" color=""/>
257
+ <Icon as={EmptyDocumentsIcon} width="160px" height="88px" color="" />
195
258
  <Box padding={4}>
196
259
  <Typography variant="beta" textColor="neutral600">{formatMessage(getTrad('empty'))}</Typography>
197
260
  </Box>
@@ -214,6 +277,7 @@ const View = () => {
214
277
  onItemEdit={handleItemEdit}
215
278
  onItemRestore={handleItemRestore}
216
279
  onItemReOrder={handleItemReOrder}
280
+ onItemToggleCollapse={handleItemToggleCollapse}
217
281
  displayFlat={!isSearchEmpty}
218
282
  root
219
283
  error={error}
@@ -1,6 +1,7 @@
1
1
  export const navigationItemType = {
2
2
  INTERNAL: "INTERNAL",
3
3
  EXTERNAL: "EXTERNAL",
4
+ WRAPPER: "WRAPPER",
4
5
  };
5
6
 
6
7
  export const navigationItemAdditionalFields = {
@@ -24,12 +24,14 @@ export const transformItemToRESTPayload = (
24
24
  order,
25
25
  audience = [],
26
26
  items = [],
27
+ collapsed,
27
28
  } = item;
28
29
  const isExternal = type === navigationItemType.EXTERNAL;
30
+ const isWrapper = type === navigationItemType.WRAPPER;
29
31
  const { contentTypes = [] } = config;
30
32
 
31
33
  const parsedRelated = Number(related);
32
- const relatedId = isExternal || isNaN(parsedRelated) ? related?.value || related : parsedRelated;
34
+ const relatedId = isExternal || isWrapper || isNaN(parsedRelated) ? related?.value || related : parsedRelated;
33
35
 
34
36
  const relatedContentType = relatedType ?
35
37
  find(contentTypes,
@@ -46,13 +48,14 @@ export const transformItemToRESTPayload = (
46
48
  removed,
47
49
  order,
48
50
  uiRouterKey,
51
+ collapsed,
49
52
  menuAttached: itemAttachedToMenu,
50
53
  audience: audience.map((audienceItem) =>
51
54
  isObject(audienceItem) ? audienceItem.value : audienceItem,
52
55
  ),
53
56
  path: isExternal ? undefined : path,
54
57
  externalPath: isExternal ? externalPath : undefined,
55
- related: isExternal
58
+ related: isExternal || isWrapper
56
59
  ? undefined
57
60
  : [
58
61
  {
@@ -274,7 +277,7 @@ export const usedContentTypes = (items = []) => items.flatMap(
274
277
 
275
278
  export const isRelationCorrect = ({ related, type }) => {
276
279
  const isRelationDefined = !isNil(related);
277
- return type === navigationItemType.EXTERNAL || (type === navigationItemType.INTERNAL && isRelationDefined);
280
+ return type !== navigationItemType.INTERNAL || (type === navigationItemType.INTERNAL && isRelationDefined);
278
281
  };
279
282
 
280
283
  export const isRelationPublished = ({ relatedRef, relatedType = {}, type, isCollection }) => {
@@ -3,6 +3,8 @@
3
3
  "header.title": "Navigation",
4
4
  "header.description": "Define your portal navigation",
5
5
  "header.action.newItem": "New Item",
6
+ "header.action.collapseAll": "Collapse All",
7
+ "header.action.expandAll": "Expand All",
6
8
  "submit.cta.cancel": "Cancel",
7
9
  "submit.cta.save": "Save",
8
10
  "empty": "Your navigation is empty",
@@ -20,12 +22,14 @@
20
22
  "popup.item.form.externalPath.placeholder": "Link to the external source",
21
23
  "popup.item.form.externalPath.validation.type": "This value is not a proper url.",
22
24
  "popup.item.form.menuAttached.label": "Attach to menu",
23
- "popup.item.form.type.label": "Internal link",
25
+ "popup.item.form.type.label": "Navigation item type",
24
26
  "popup.item.form.type.internal.label": "Internal source",
25
27
  "popup.item.form.type.external.label": "External source",
28
+ "popup.item.form.type.wrapper.label": "Wrapper element",
26
29
  "popup.item.form.type.external.description": "Output path: {value}",
27
30
  "popup.item.form.audience.label": "Audience",
28
31
  "popup.item.form.audience.placeholder": "Select audience...",
32
+ "popup.item.form.audience.empty": "There are no more audiences",
29
33
  "popup.item.form.relatedSection.label": "Relation to",
30
34
  "popup.item.form.relatedType.label": "Content Type",
31
35
  "popup.item.form.relatedType.placeholder": "Select content type...",
@@ -81,12 +85,17 @@
81
85
  "pages.settings.form.nameField.label": "Name fields",
82
86
  "pages.settings.form.nameField.placeholder": "Select at least one or leave empty to apply defaults",
83
87
  "pages.settings.form.nameField.hint": "If left empty name field is going to take following ordered fields: \"title\", \"subject\" and \"name\"",
88
+ "pages.settings.form.nameField.empty": "This content type doesn't have any string attributes",
89
+ "pages.settings.form.populate.label": "Fields to populate",
90
+ "pages.settings.form.populate.placeholder": "Select at least one or leave empty to disable populating relation fields",
91
+ "pages.settings.form.populate.hint": "Selected relation fields will be populated inside API responses",
92
+ "pages.settings.form.populate.empty": "This content type doesn't have any relation fields",
84
93
  "pages.settings.form.contentTypesSettings.label": "Content types",
85
94
  "pages.settings.form.contentTypesSettings.tooltip": "Custom configuration per content type",
86
95
  "components.navigationItem.action.newItem": "Add nested item",
87
96
  "components.navigationItem.badge.removed": "Removed",
88
- "components.navigationItem.badge.draft": "{type}: Draft",
89
- "components.navigationItem.badge.published": "{type}: Published",
97
+ "components.navigationItem.badge.draft": "Draft",
98
+ "components.navigationItem.badge.published": "Published",
90
99
  "components.confirmation.dialog.button.cancel": "Cancel",
91
100
  "components.confirmation.dialog.button.confirm": "Confirm",
92
101
  "components.confirmation.dialog.description": "Do you want to continue?",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-navigation",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Strapi - Navigation plugin",
5
5
  "strapi": {
6
6
  "name": "navigation",
@@ -42,30 +42,11 @@ module.exports = async ({ strapi }) => {
42
42
  }
43
43
 
44
44
  // Initialize configuration
45
- const pluginStore = strapi.store({
46
- environment: '',
47
- type: 'plugin',
48
- name: 'navigation',
49
- });
50
-
51
- const config = await pluginStore.get({ key: 'config' });
52
- const pluginDefaultConfig = await strapi.plugin('navigation').config
53
- const defaultConfigValue = {
54
- additionalFields: pluginDefaultConfig('additionalFields'),
55
- contentTypes: pluginDefaultConfig('contentTypes'),
56
- contentTypesNameFields: pluginDefaultConfig('contentTypesNameFields'),
57
- allowedLevels: pluginDefaultConfig('allowedLevels'),
58
- gql: pluginDefaultConfig('gql'),
59
- }
60
-
61
- if (!config) {
62
- pluginStore.set({
63
- key: 'config', value: defaultConfigValue
64
- });
65
- }
45
+ const config = await strapi.plugin('navigation').service('navigation').setDefaultConfig()
66
46
 
47
+ // Initialize graphql configuration
67
48
  if (strapi.plugin('graphql')) {
68
49
  const graphqlConfiguration = require('./graphql')
69
- await graphqlConfiguration({ strapi, config: config || defaultConfigValue });
50
+ await graphqlConfiguration({ strapi, config });
70
51
  }
71
52
  };
@@ -3,6 +3,7 @@ module.exports = {
3
3
  additionalFields: [],
4
4
  contentTypes: [],
5
5
  contentTypesNameFields: {},
6
+ contentTypesPopulate: {},
6
7
  allowedLevels: 2
7
8
  }
8
9
  }
package/server/config.js CHANGED
@@ -3,6 +3,7 @@ module.exports = {
3
3
  additionalFields: [],
4
4
  contentTypes: [],
5
5
  contentTypesNameFields: {},
6
+ contentTypesPopulate: {},
6
7
  allowedLevels: 2
7
8
  }
8
9
  }
@@ -10,6 +10,7 @@ module.exports = {
10
10
  type: {
11
11
  INTERNAL: 'INTERNAL',
12
12
  EXTERNAL: 'EXTERNAL',
13
+ WRAPPER: 'WRAPPER',
13
14
  },
14
15
  additionalFields: {
15
16
  AUDIENCE: 'audience',
@@ -37,7 +37,8 @@
37
37
  "type": "enumeration",
38
38
  "enum": [
39
39
  "INTERNAL",
40
- "EXTERNAL"
40
+ "EXTERNAL",
41
+ "WRAPPER"
41
42
  ],
42
43
  "default": "INTERNAL",
43
44
  "configurable": false
@@ -65,6 +66,11 @@
65
66
  "default": 0,
66
67
  "configurable": false
67
68
  },
69
+ "collapsed": {
70
+ "type": "boolean",
71
+ "default": false,
72
+ "configurable": false
73
+ },
68
74
  "related": {
69
75
  "type": "relation",
70
76
  "relation": "oneToOne",
@@ -1,8 +1,8 @@
1
- module.exports = ({ nexus }) => nexus.objectType({
1
+ module.exports = ({ nexus, strapi }) => nexus.objectType({
2
2
  name: "ContentTypesNameFields",
3
3
  async definition(t) {
4
4
  t.nonNull.list.nonNull.string("default")
5
- const pluginStore = strapi.store({ type: 'plugin', name: 'navigation' });
5
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore();
6
6
  const config = await pluginStore.get({ key: 'config' });
7
7
  const contentTypesNameFields = config.contentTypesNameFields;
8
8
  Object.keys(contentTypesNameFields || {}).forEach(key => t.nonNull.list.string(key))
@@ -72,10 +72,11 @@ module.exports = ({ strapi }) => {
72
72
  // Get plugin config
73
73
  async config(viaSettingsPage = false) {
74
74
  const { audienceModel, service } = utilsFunctions.extractMeta(strapi.plugins);
75
- const pluginStore = await strapi.store({ type: 'plugin', name: 'navigation' });
75
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
76
76
  const config = await pluginStore.get({ key: 'config' });
77
77
  const additionalFields = config.additionalFields;
78
78
  const contentTypesNameFields = config.contentTypesNameFields;
79
+ const contentTypesPopulate = config.contentTypesPopulate;
79
80
  const allowedLevels = config.allowedLevels;
80
81
  const isGQLPluginEnabled = !isNil(strapi.plugin('graphql'));
81
82
 
@@ -86,6 +87,9 @@ module.exports = ({ strapi }) => {
86
87
  default: contentTypesNameFieldsDefaults,
87
88
  ...(isObject(contentTypesNameFields) ? contentTypesNameFields : {}),
88
89
  },
90
+ contentTypesPopulate: {
91
+ ...(isObject(contentTypesPopulate) ? contentTypesPopulate : {}),
92
+ },
89
93
  allowedLevels,
90
94
  additionalFields,
91
95
  isGQLPluginEnabled: viaSettingsPage ? isGQLPluginEnabled : undefined,
@@ -111,28 +115,42 @@ module.exports = ({ strapi }) => {
111
115
  },
112
116
 
113
117
  async updateConfig(newConfig) {
114
- const pluginStore = await strapi.store({ type: 'plugin', name: 'navigation' });
118
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
115
119
  await pluginStore.set({ key: 'config', value: newConfig });
116
120
  },
117
121
 
122
+ async getPluginStore() {
123
+ return await strapi.store({ type: 'plugin', name: 'navigation' });
124
+ },
125
+
126
+ async setDefaultConfig() {
127
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
128
+ const config = await pluginStore.get({ key: 'config' });
129
+ const pluginDefaultConfig = await strapi.plugin('navigation').config
130
+
131
+ // If new value gets introduced to the config it either is read from plugin store or from default plugin config
132
+ // This is fix for backwards compatibility and migration of config to newer version of the plugin
133
+ const defaultConfigValue = {
134
+ additionalFields: get(config, 'additionalFields', pluginDefaultConfig('additionalFields')),
135
+ contentTypes: get(config, 'contentTypes', pluginDefaultConfig('contentTypes')),
136
+ contentTypesNameFields: get(config, 'contentTypesNameFields', pluginDefaultConfig('contentTypesNameFields')),
137
+ contentTypesPopulate: get(config, 'contentTypesPopulate', pluginDefaultConfig('contentTypesPopulate')),
138
+ allowedLevels: get(config, 'allowedLevels', pluginDefaultConfig('allowedLevels')),
139
+ gql: get(config, 'gql', pluginDefaultConfig('gql')),
140
+ }
141
+ pluginStore.set({ key: 'config', value: defaultConfigValue });
142
+
143
+ return defaultConfigValue;
144
+ },
145
+
118
146
  async restoreConfig() {
119
- const pluginStore = await strapi.store({ type: 'plugin', name: 'navigation' });
120
- const defaultConfig = await strapi.plugin('navigation').config
121
-
122
- await pluginStore.delete({ key: 'config' })
123
- await pluginStore.set({
124
- key: 'config', value: {
125
- additionalFields: defaultConfig('additionalFields'),
126
- contentTypes: defaultConfig('contentTypes'),
127
- contentTypesNameFields: defaultConfig('contentTypesNameFields'),
128
- allowedLevels: defaultConfig('allowedLevels'),
129
- gql: defaultConfig('gql'),
130
- }
131
- });
147
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
148
+ await pluginStore.delete({ key: 'config' });
149
+ await strapi.plugin('navigation').service('navigation').setDefaultConfig();
132
150
  },
133
151
 
134
152
  async configContentTypes() {
135
- const pluginStore = strapi.store({ type: 'plugin', name: 'navigation' });
153
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
136
154
  const config = await pluginStore.get({ key: 'config' });
137
155
  const eligibleContentTypes =
138
156
  await Promise.all(
@@ -212,6 +230,8 @@ module.exports = ({ strapi }) => {
212
230
  },
213
231
 
214
232
  async getRelatedItems(entityItems) {
233
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
234
+ const config = await pluginStore.get({ key: 'config' });
215
235
  const relatedTypes = new Set(entityItems.flatMap((item) => get(item.related, 'related_type')));
216
236
  const groupedItems = Array.from(relatedTypes).filter((relatedType) => relatedType).reduce(
217
237
  (acc, relatedType) => Object.assign(acc, {
@@ -233,8 +253,9 @@ module.exports = ({ strapi }) => {
233
253
  .query(model)
234
254
  .findMany({
235
255
  where: {
236
- id: { $in: map(related, 'related_id') }
237
- }
256
+ id: { $in: map(related, 'related_id') },
257
+ },
258
+ populate: config.contentTypesPopulate[model] || []
238
259
  });
239
260
  return relationData
240
261
  .flatMap(_ =>
@@ -264,8 +285,12 @@ module.exports = ({ strapi }) => {
264
285
  },
265
286
 
266
287
  async getContentTypeItems(model) {
288
+ const pluginStore = await strapi.plugin('navigation').service('navigation').getPluginStore()
289
+ const config = await pluginStore.get({ key: 'config' });
267
290
  try {
268
- const contentTypeItems = await strapi.query(model).findMany()
291
+ const contentTypeItems = await strapi.query(model).findMany({
292
+ populate: config.contentTypesPopulate[model] || []
293
+ })
269
294
  return contentTypeItems;
270
295
  } catch (err) {
271
296
  return [];
@@ -213,7 +213,7 @@ module.exports = ({ strapi }) => {
213
213
  false;
214
214
  return item.type === itemType.INTERNAL ? isRelatedDefinedAndPublished : true;
215
215
  }
216
- return (item.type === itemType.EXTERNAL) || relatedItem;
216
+ return (item.type !== itemType.INTERNAL) || relatedItem;
217
217
  },
218
218
  };
219
219
  }