strapi-plugin-navigation 2.0.7 → 2.0.10

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.
@@ -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>
@@ -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,18 +11,41 @@ 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
- <IconButton ref={dragRef} label="Drag" icon={<Drag />} />
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>
24
- <Icon as={icon}/>
48
+ <Icon as={icon} />
25
49
  </Flex>
26
50
  <Flex alignItems="center" style={{ zIndex: 2 }}>
27
51
  {removed &&
@@ -1,10 +1,8 @@
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';
@@ -57,7 +55,8 @@ const Item = (props) => {
57
55
  const { contentTypes, contentTypesNameFields } = config;
58
56
  const isExternal = type === navigationItemType.EXTERNAL;
59
57
  const isWrapper = type === navigationItemType.WRAPPER;
60
- const isPublished = relatedRef && relatedRef?.publishedAt;
58
+ const isHandledByPublishFlow = relatedRef && typeof relatedRef.publishedAt !== 'undefined';
59
+ const isPublished = isHandledByPublishFlow && relatedRef.publishedAt;
61
60
  const isNextMenuAllowedLevel = isNumber(allowedLevels) ? level < (allowedLevels - 1) : true;
62
61
  const isMenuAllowedLevel = isNumber(allowedLevels) ? level < allowedLevels : true;
63
62
  const hasChildren = !isEmpty(item.items) && !isExternal && !displayChildren;
@@ -91,7 +90,16 @@ const Item = (props) => {
91
90
  const isAfter = hoverClientY > hoverMiddleY;
92
91
  const newOrder = isAfter ? item.order + 0.5 : item.order - 0.5;
93
92
 
93
+ if (dragIndex < dropIndex && hoverClientY < hoverMiddleY) {
94
+ return;
95
+ }
96
+ // Dragging upwards
97
+ if (dragIndex > dropIndex && hoverClientY > hoverMiddleY) {
98
+ return;
99
+ }
100
+
94
101
  onItemReOrder({ ...hoveringItem }, newOrder);
102
+ hoveringItem.order = newOrder;
95
103
  },
96
104
  collect: monitor => ({
97
105
  isOverCurrent: monitor.isOver({ shallow: true }),
@@ -160,15 +168,15 @@ const Item = (props) => {
160
168
  </Flex>
161
169
  {relatedItemLabel && (
162
170
  <Flex justifyContent='center' alignItems='center'>
163
- <ItemCardBadge
171
+ {isHandledByPublishFlow && <ItemCardBadge
164
172
  borderColor={`${relatedBadgeColor}200`}
165
173
  backgroundColor={`${relatedBadgeColor}100`}
166
174
  textColor={`${relatedBadgeColor}600`}
167
175
  className="action"
168
176
  small
169
177
  >
170
- {getMessage({id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}`})}
171
- </ItemCardBadge>
178
+ {getMessage({ id: `components.navigationItem.badge.${isPublished ? 'published' : 'draft'}` })}
179
+ </ItemCardBadge>}
172
180
  <Typography variant="omega" textColor='neutral600'>{relatedTypeLabel}&nbsp;/&nbsp;</Typography>
173
181
  <Typography variant="omega" textColor='neutral800'>{relatedItemLabel}</Typography>
174
182
  <Link
@@ -94,8 +94,8 @@ const DataManagerProvider = ({ children }) => {
94
94
  } catch (err) {
95
95
  console.error({ err });
96
96
  toggleNotification({
97
- type: 'error',
98
- message: { id: 'notification.error' },
97
+ type: 'warning',
98
+ message: { id: getTrad('notification.error') },
99
99
  });
100
100
  }
101
101
  };
@@ -133,8 +133,8 @@ const DataManagerProvider = ({ children }) => {
133
133
  } catch (err) {
134
134
  console.error({ err });
135
135
  toggleNotification({
136
- type: 'error',
137
- message: { id: 'notification.error' },
136
+ type: 'warning',
137
+ message: { id: getTrad('notification.error') },
138
138
  });
139
139
  }
140
140
  };
@@ -254,7 +254,7 @@ const DataManagerProvider = ({ children }) => {
254
254
 
255
255
  if (err.response.payload.data && err.response.payload.data.errorTitles) {
256
256
  return toggleNotification({
257
- type: 'error',
257
+ type: 'warning',
258
258
  message: {
259
259
  id: formatMessage(
260
260
  getTrad('notification.navigation.error'),
@@ -264,7 +264,7 @@ const DataManagerProvider = ({ children }) => {
264
264
  });
265
265
  }
266
266
  toggleNotification({
267
- type: 'error',
267
+ type: 'warning',
268
268
  message: { id: getTrad('notification.error') },
269
269
  });
270
270
  }
@@ -56,7 +56,13 @@ const SettingsPage = () => {
56
56
  additionalFields: audienceFieldChecked ? [navigationItemAdditionalFields.AUDIENCE] : [],
57
57
  allowedLevels: allowedLevels,
58
58
  gql: {
59
- navigationItemRelated: selectedContentTypes.map(uid => allContentTypes.find(ct => ct.uid === uid).info.displayName.replace(/\s+/g, ''))
59
+ navigationItemRelated: selectedContentTypes.map(uid => {
60
+ const singularName = allContentTypes.find(ct => ct.uid === uid).info.singularName;
61
+ const globalIdLike = singularName.split('-')
62
+ .map(_ => capitalize(_))
63
+ .join('')
64
+ return globalIdLike;
65
+ })
60
66
  }
61
67
  });
62
68
 
@@ -145,7 +151,7 @@ const SettingsPage = () => {
145
151
  }
146
152
  />
147
153
  <ContentLayout>
148
- <Stack size={7}>
154
+ <Stack spacing={7}>
149
155
  {isRestartRequired && (
150
156
  <RestartAlert
151
157
  closeLabel={getMessage('pages.settings.actions.restart.alert.cancel')}
@@ -155,7 +161,7 @@ const SettingsPage = () => {
155
161
  {getMessage('pages.settings.actions.restart.alert.description')}
156
162
  </RestartAlert>)}
157
163
  <Box {...boxDefaultProps} >
158
- <Stack size={4}>
164
+ <Stack spacing={4}>
159
165
  <Typography variant="delta" as="h2">
160
166
  {getMessage('pages.settings.general.title')}
161
167
  </Typography>
@@ -197,7 +203,7 @@ const SettingsPage = () => {
197
203
  <AccordionToggle title={displayName} togglePosition="left" />
198
204
  <AccordionContent>
199
205
  <Box padding={6}>
200
- <Stack size={4}>
206
+ <Stack spacing={4}>
201
207
  <Select
202
208
  name={`collectionSettings-${uid}-entryLabel`}
203
209
  label={getMessage('pages.settings.form.nameField.label')}
@@ -240,7 +246,7 @@ const SettingsPage = () => {
240
246
  </Stack>
241
247
  </Box>
242
248
  <Box {...boxDefaultProps} >
243
- <Stack size={4}>
249
+ <Stack spacing={4}>
244
250
  <Typography variant="delta" as="h2">
245
251
  {getMessage('pages.settings.additional.title')}
246
252
  </Typography>
@@ -272,7 +278,7 @@ const SettingsPage = () => {
272
278
  </Stack>
273
279
  </Box>
274
280
  <Box {...boxDefaultProps} >
275
- <Stack size={4}>
281
+ <Stack spacing={4}>
276
282
  <Typography variant="delta" as="h2">
277
283
  {getMessage('pages.settings.restoring.title')}
278
284
  </Typography>
@@ -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"
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState, useCallback } from 'react';
2
- import { debounce, find, get, isEmpty, isEqual, isNil, isString } from 'lodash';
2
+ import { debounce, find, get, first, isEmpty, isEqual, isNil, isString } from 'lodash';
3
3
  import PropTypes from 'prop-types';
4
4
  import { Formik } from 'formik'
5
5
  import slugify from 'slugify';
@@ -75,9 +75,11 @@ const NavigationItemForm = ({
75
75
 
76
76
  const sanitizePayload = (payload = {}) => {
77
77
  const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, type, ...purePayload } = payload;
78
- const relatedId = related
78
+ const relatedId = related;
79
+ const singleRelatedItem = isSingleSelected ? first(contentTypeEntities) : undefined;
79
80
  const relatedCollectionType = relatedType;
80
- const title = payload.title || relatedSelectOptions.find(v => v.key == relatedId)?.label
81
+ const title = payload.title;
82
+
81
83
  return {
82
84
  ...purePayload,
83
85
  title,
@@ -88,7 +90,8 @@ const NavigationItemForm = ({
88
90
  related: type === navigationItemType.INTERNAL ? relatedId : undefined,
89
91
  relatedType: type === navigationItemType.INTERNAL ? relatedCollectionType : undefined,
90
92
  isSingle: isSingleSelected,
91
- uiRouterKey: generateUiRouterKey(purePayload.title, relatedId, relatedCollectionType),
93
+ singleRelatedItem,
94
+ uiRouterKey: generateUiRouterKey(title, relatedId, relatedCollectionType),
92
95
  };
93
96
  };
94
97
 
@@ -134,6 +137,7 @@ const NavigationItemForm = ({
134
137
  return undefined;
135
138
  };
136
139
 
140
+ const initialRelatedTypeSelected = data?.relatedType?.value;
137
141
  const relatedTypeSelectValue = form.relatedType;
138
142
  const relatedSelectValue = form.related;
139
143
 
@@ -187,7 +191,7 @@ const NavigationItemForm = ({
187
191
  const pathSourceName = isExternal ? 'externalPath' : 'path';
188
192
 
189
193
  const submitDisabled =
190
- (form.type === navigationItemType.INTERNAL && isNil(get(form, `${inputsPrefix}related`))) ||
194
+ (form.type === navigationItemType.INTERNAL && !isSingleSelected && isNil(get(form, `${inputsPrefix}related`))) ||
191
195
  (form.type === navigationItemType.WRAPPER && isNil(get(form, `${inputsPrefix}title`)));
192
196
 
193
197
  const debouncedSearch = useCallback(
@@ -219,6 +223,9 @@ const NavigationItemForm = ({
219
223
  () => contentTypes
220
224
  .filter((contentType) => {
221
225
  if (contentType.isSingle) {
226
+ if (relatedTypeSelectValue && [relatedTypeSelectValue, initialRelatedTypeSelected].includes(contentType.uid)) {
227
+ return true;
228
+ }
222
229
  return !usedContentTypesData.some((_) => _.__collectionUid === contentType.uid && _.__collectionUid !== form.relatedType);
223
230
  }
224
231
  return true;
@@ -232,9 +239,9 @@ const NavigationItemForm = ({
232
239
  }
233
240
  },
234
241
  value: get(item, 'uid'),
235
- label: appendLabelPublicationStatus(get(item, 'label', get(item, 'name')), item, true),
242
+ label: get(item, 'label', get(item, 'name')),
236
243
  })),
237
- [contentTypes, usedContentTypesData],
244
+ [contentTypes, usedContentTypesData, relatedTypeSelectValue],
238
245
  );
239
246
 
240
247
  const thereAreNoMoreContentTypes = isEmpty(relatedSelectOptions) && !contentTypeSearchQuery;
@@ -89,10 +89,12 @@ const linkRelations = (item, config) => {
89
89
 
90
90
  if (isSingle && relatedType) {
91
91
  const relatedContentType = contentTypes.find(_ => relatedType === _.uid) || {};
92
+ const { singleRelatedItem = {} } = item;
92
93
  return {
93
94
  ...item,
94
95
  relatedType,
95
96
  relatedRef: {
97
+ ...singleRelatedItem,
96
98
  ...omit(relatedContentType, 'collectionName'),
97
99
  isSingle,
98
100
  __collectionUid: relatedContentType.uid,
@@ -122,7 +124,7 @@ const linkRelations = (item, config) => {
122
124
 
123
125
  const shouldFindRelated = (isNumber(related) || isUuid(related) || isString(related)) && !relatedRef;
124
126
  const shouldBuildRelated = !relatedRef || (relatedRef && (relatedRef.id !== relatedId));
125
-
127
+
126
128
  if (shouldBuildRelated && !shouldFindRelated) {
127
129
  const relatedContentType = find(contentTypes,
128
130
  ct => ct.uid === relatedItem.__contentType, {});
@@ -295,9 +297,10 @@ export const isRelationPublished = ({ relatedRef, relatedType = {}, type, isColl
295
297
 
296
298
  export const validateNavigationStructure = (items = []) =>
297
299
  items.map(item =>
298
- (item.removed || isRelationCorrect({
299
- related: item.related,
300
- type: item.type,
301
- })) &&
300
+ (
301
+ item.removed ||
302
+ isRelationCorrect({ related: item.related, type: item.type }) ||
303
+ (item.isSingle && isRelationCorrect({ related: item.relatedType, type: item.type }))
304
+ ) &&
302
305
  validateNavigationStructure(item.items)
303
306
  ).filter(item => !item).length === 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-navigation",
3
- "version": "2.0.7",
3
+ "version": "2.0.10",
4
4
  "description": "Strapi - Navigation plugin",
5
5
  "strapi": {
6
6
  "name": "navigation",
@@ -8,7 +8,9 @@ module.exports = ({ strapi, nexus, config }) => {
8
8
  definition(t) {
9
9
  t.members(...related)
10
10
  },
11
- resolveType: (item) => strapi.contentTypes[item.__contentType]?.globalId
11
+ resolveType: (item) => {
12
+ return strapi.contentTypes[item.__contentType]?.globalId
13
+ }
12
14
  });
13
15
  }
14
16
 
@@ -33,9 +33,7 @@ module.exports = ({ strapi }) => {
33
33
  const entities = await strapi
34
34
  .query(masterModel.uid)
35
35
  .findMany({
36
- paggination: {
37
- limit: -1,
38
- }
36
+ limit: 0
39
37
  });
40
38
  return entities;
41
39
  },
@@ -52,9 +50,7 @@ module.exports = ({ strapi }) => {
52
50
  where: {
53
51
  master: id,
54
52
  },
55
- paggination: {
56
- limit: -1,
57
- },
53
+ limit: 0,
58
54
  sort: ['order:asc'],
59
55
  populate: ['related', 'parent', 'audience']
60
56
  });
@@ -99,9 +95,7 @@ module.exports = ({ strapi }) => {
99
95
  const audienceItems = await strapi
100
96
  .query(audienceModel.uid)
101
97
  .findMany({
102
- paggination: {
103
- limit: -1,
104
- }
98
+ limit: 0
105
99
  });
106
100
  extendedResult = {
107
101
  ...extendedResult,
@@ -133,10 +127,10 @@ module.exports = ({ strapi }) => {
133
127
  const defaultConfigValue = {
134
128
  additionalFields: get(config, 'additionalFields', pluginDefaultConfig('additionalFields')),
135
129
  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')),
130
+ contentTypesNameFields: get(config, 'contentTypesNameFields', pluginDefaultConfig('contentTypesNameFields')),
131
+ contentTypesPopulate: get(config, 'contentTypesPopulate', pluginDefaultConfig('contentTypesPopulate')),
132
+ allowedLevels: get(config, 'allowedLevels', pluginDefaultConfig('allowedLevels')),
133
+ gql: get(config, 'gql', pluginDefaultConfig('gql')),
140
134
  }
141
135
  pluginStore.set({ key: 'config', value: defaultConfigValue });
142
136
 
@@ -399,9 +393,7 @@ module.exports = ({ strapi }) => {
399
393
  master: entity.id,
400
394
  ...itemCriteria,
401
395
  },
402
- paggination: {
403
- limit: -1,
404
- },
396
+ limit: 0,
405
397
  sort: ['order:asc'],
406
398
  populate: ['related', 'audience', 'parent'],
407
399
  });
@@ -428,6 +420,7 @@ module.exports = ({ strapi }) => {
428
420
  id: item.id,
429
421
  title: utilsFunctions.composeItemTitle(item, contentTypesNameFields, contentTypes),
430
422
  menuAttached: item.menuAttached,
423
+ order: item.order,
431
424
  path: isExternal ? item.externalPath : parentPath,
432
425
  type: item.type,
433
426
  uiRouterKey: item.uiRouterKey,
@@ -511,7 +504,10 @@ module.exports = ({ strapi }) => {
511
504
  .filter(utilsFunctions.filterOutUnpublished)
512
505
  .map(item => itemParser({
513
506
  ...item,
514
- }, path, field));
507
+ }, path, field))
508
+ .sort((x, y) => {
509
+ return x.order - y.order;
510
+ });
515
511
  },
516
512
 
517
513
  renderRFR({ items, parent = null, parentNavItem = null, contentTypes = [] }) {
@@ -632,7 +628,7 @@ module.exports = ({ strapi }) => {
632
628
  }
633
629
  const navigationItem = await strapi
634
630
  .query(itemModel.uid)
635
- .create({ data });
631
+ .create({ data, populate: ['related', 'items'] });
636
632
  return !isEmpty(item.items)
637
633
  ? service.createBranch(
638
634
  item.items,
@@ -735,7 +731,6 @@ module.exports = ({ strapi }) => {
735
731
  if (relatedItems) {
736
732
  return Promise.all(relatedItems.map(async relatedItem => {
737
733
  try {
738
-
739
734
  const model = strapi.query('plugin::navigation.navigations-items-related');
740
735
  const entity = await model
741
736
  .findOne({
@@ -7,7 +7,8 @@ const {
7
7
  isString,
8
8
  get,
9
9
  isNil,
10
- isArray
10
+ isArray,
11
+ first,
11
12
  } = require('lodash');
12
13
 
13
14
  const { type: itemType } = require('../../content-types/navigation-item/lifecycle');