@strapi/admin 4.5.0-alpha.0 → 4.5.0-beta.0

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 (191) hide show
  1. package/admin/src/StrapiApp.js +4 -12
  2. package/admin/src/components/Providers/index.js +14 -10
  3. package/admin/src/content-manager/components/CollectionTypeFormWrapper/index.js +24 -0
  4. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +20 -15
  5. package/admin/src/content-manager/components/DynamicZone/components/Component/index.js +19 -9
  6. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +50 -3
  7. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +50 -9
  8. package/admin/src/content-manager/components/FieldTypeIcon/index.js +31 -1
  9. package/admin/src/content-manager/components/Inputs/index.js +36 -14
  10. package/admin/src/content-manager/components/NonRepeatableComponent/index.js +2 -0
  11. package/admin/src/content-manager/components/RelationInput/RelationInput.js +95 -32
  12. package/admin/src/content-manager/components/RelationInput/components/RelationItem.js +2 -2
  13. package/admin/src/content-manager/components/RelationInput/constants.js +1 -1
  14. package/admin/src/content-manager/components/RelationInputDataManager/RelationInputDataManager.js +33 -22
  15. package/admin/src/content-manager/components/RelationInputDataManager/utils/index.js +1 -0
  16. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeRelations.js +10 -3
  17. package/admin/src/content-manager/components/RelationInputDataManager/utils/normalizeSearchResults.js +12 -0
  18. package/admin/src/content-manager/components/RelationInputDataManager/utils/select.js +34 -11
  19. package/admin/src/content-manager/components/RepeatableComponent/DraggedItem/index.js +5 -0
  20. package/admin/src/content-manager/components/SingleTypeFormWrapper/index.js +23 -0
  21. package/admin/src/content-manager/hooks/useRelation/useRelation.js +17 -9
  22. package/admin/src/content-manager/pages/EditSettingsView/components/FormModal.js +7 -2
  23. package/admin/src/content-manager/pages/EditSettingsView/index.js +2 -1
  24. package/admin/src/content-manager/pages/EditView/Header/index.js +118 -50
  25. package/admin/src/content-manager/pages/EditView/Header/utils/select.js +4 -0
  26. package/admin/src/content-manager/pages/EditView/index.js +102 -93
  27. package/admin/src/content-manager/pages/ListView/index.js +24 -15
  28. package/admin/src/content-manager/pages/ListView/utils/buildQueryString.js +14 -2
  29. package/admin/src/contexts/ApiTokenPermissions/index.js +24 -0
  30. package/admin/src/core/apis/CustomFields.js +80 -0
  31. package/admin/src/core/apis/index.js +1 -0
  32. package/admin/src/hooks/index.js +1 -0
  33. package/admin/src/hooks/useRegenerate/index.js +34 -0
  34. package/admin/src/pages/HomePage/SocialLinks.js +1 -1
  35. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ActionBoundRoutes/index.js +56 -0
  36. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/getMethodColor.js +41 -0
  37. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/BoundRoute/index.js +72 -0
  38. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/CheckBoxWrapper.js +30 -0
  39. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/CollapsableContentType/index.js +150 -0
  40. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContenTypesSection/index.js +37 -0
  41. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormApiTokenContainer/index.js +254 -0
  42. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormBody/index.js +77 -0
  43. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/FormHead/index.js +85 -0
  44. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Permissions/index.js +40 -0
  45. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/Regenerate/index.js +68 -0
  46. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +215 -197
  47. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/init.js +13 -0
  48. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/reducer.js +72 -0
  49. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/getDateOfExpiration.js +16 -0
  50. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/index.js +5 -0
  51. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/schema.js +2 -1
  52. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/utils/transformPermissionsData.js +36 -0
  53. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DefaultButton/index.js +63 -0
  54. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +1 -0
  55. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/ReadButton/index.js +19 -0
  56. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/UpdateButton/index.js +3 -36
  57. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +13 -11
  58. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +3 -2
  59. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/utils/tableHeaders.js +8 -8
  60. package/admin/src/pages/SettingsPage/pages/ApiTokens/ProtectedEditView/index.js +1 -1
  61. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/index.js +2 -2
  62. package/admin/src/permissions/defaultPermissions.js +2 -6
  63. package/admin/src/translations/ca.json +3 -1
  64. package/admin/src/translations/de.json +4 -1
  65. package/admin/src/translations/dk.json +3 -0
  66. package/admin/src/translations/en.json +22 -1
  67. package/admin/src/translations/es.json +156 -157
  68. package/admin/src/translations/fr.json +3 -0
  69. package/admin/src/translations/gu.json +608 -606
  70. package/admin/src/translations/hi.json +689 -687
  71. package/admin/src/translations/hu.json +2 -0
  72. package/admin/src/translations/id.json +2 -0
  73. package/admin/src/translations/it.json +2 -0
  74. package/admin/src/translations/ja.json +2 -0
  75. package/admin/src/translations/ko.json +2 -0
  76. package/admin/src/translations/ml.json +689 -687
  77. package/admin/src/translations/nl.json +3 -0
  78. package/admin/src/translations/pl.json +2 -0
  79. package/admin/src/translations/pt-BR.json +3 -0
  80. package/admin/src/translations/ru.json +489 -491
  81. package/admin/src/translations/sa.json +85 -82
  82. package/admin/src/translations/sk.json +3 -0
  83. package/admin/src/translations/sv.json +3 -0
  84. package/admin/src/translations/zh-Hans.json +4 -1
  85. package/admin/src/translations/zh.json +3 -0
  86. package/build/1856.d8f13391.chunk.js +173 -0
  87. package/build/{9311.7cc03f29.chunk.js → 1939.e3c87653.chunk.js} +108 -291
  88. package/build/{2077.c935ee42.chunk.js → 2077.31a2d91e.chunk.js} +4 -4
  89. package/build/{2912.a015078a.chunk.js → 2912.ab68a736.chunk.js} +8 -8
  90. package/build/4318.7d167b58.chunk.js +30 -0
  91. package/build/4715.44b1ef9b.chunk.js +386 -0
  92. package/build/{4982.05eda880.chunk.js → 4982.c2a311b7.chunk.js} +9 -9
  93. package/build/7379.d246dd38.chunk.js +1 -0
  94. package/build/{7841.91f793dc.chunk.js → 7841.4b67af3f.chunk.js} +8 -8
  95. package/build/{7866.1201afbd.chunk.js → 7866.5fbeb7e5.chunk.js} +10 -10
  96. package/build/{8380.8789ff76.chunk.js → 8380.9b53a31d.chunk.js} +7 -7
  97. package/build/{8549.133c4473.chunk.js → 8549.cf10b5d1.chunk.js} +4 -4
  98. package/build/8738.a30a2160.chunk.js +461 -0
  99. package/build/{9066.08049eb1.chunk.js → 9066.26faf397.chunk.js} +4 -4
  100. package/build/{9166.037339e0.chunk.js → 9166.8fcb3019.chunk.js} +5 -5
  101. package/build/{9420.43a86e7c.chunk.js → 9420.0fe11290.chunk.js} +6 -6
  102. package/build/962.8651ba3f.chunk.js +184 -0
  103. package/build/Admin-authenticatedApp.883449a5.chunk.js +80 -0
  104. package/build/{Admin_homePage.118926e0.chunk.js → Admin_homePage.4b2be829.chunk.js} +2 -2
  105. package/build/{Admin_profilePage.9d50ac44.chunk.js → Admin_profilePage.da32abbc.chunk.js} +1 -1
  106. package/build/{Admin_settingsPage.98a711e5.chunk.js → Admin_settingsPage.98e2a62b.chunk.js} +16 -16
  107. package/build/admin-app.a61d5c2e.chunk.js +112 -0
  108. package/build/admin-edit-roles-page.4dd6bcb9.chunk.js +1 -0
  109. package/build/{admin-users.97a08630.chunk.js → admin-users.d71f198a.chunk.js} +3 -3
  110. package/build/api-tokens-create-page.93dd0689.chunk.js +1 -0
  111. package/build/api-tokens-edit-page.b0adac81.chunk.js +1 -0
  112. package/build/api-tokens-list-page.bb36535f.chunk.js +16 -0
  113. package/build/{ca-json.a16899ae.chunk.js → ca-json.82df6eab.chunk.js} +1 -1
  114. package/build/content-manager.933dc286.chunk.js +1201 -0
  115. package/build/content-type-builder-list-view.5b3cd768.chunk.js +194 -0
  116. package/build/content-type-builder-translation-en-json.f985c9c4.chunk.js +1 -0
  117. package/build/content-type-builder.a6e29716.chunk.js +145 -0
  118. package/build/{de-json.aa6026b3.chunk.js → de-json.0ad554eb.chunk.js} +1 -1
  119. package/build/{dk-json.fac2bcfb.chunk.js → dk-json.e195ea1a.chunk.js} +1 -1
  120. package/build/{email-settings-page.64037147.chunk.js → email-settings-page.bfe6227f.chunk.js} +4 -4
  121. package/build/en-json.1889403c.chunk.js +1 -0
  122. package/build/{es-json.d672e181.chunk.js → es-json.09f80f6e.chunk.js} +1 -1
  123. package/build/{fr-json.71a16175.chunk.js → fr-json.606d056b.chunk.js} +1 -1
  124. package/build/{gu-json.ca345cd1.chunk.js → gu-json.9881264f.chunk.js} +1 -1
  125. package/build/{hi-json.50c7e6d4.chunk.js → hi-json.83dcf48f.chunk.js} +1 -1
  126. package/build/{hu-json.e0521dcc.chunk.js → hu-json.6f328bce.chunk.js} +1 -1
  127. package/build/{i18n-settings-page.0b73785d.chunk.js → i18n-settings-page.18166125.chunk.js} +4 -4
  128. package/build/{id-json.4b1ff8d6.chunk.js → id-json.1f3c4303.chunk.js} +1 -1
  129. package/build/index.html +1 -1
  130. package/build/{it-json.86bac220.chunk.js → it-json.494ac432.chunk.js} +1 -1
  131. package/build/{ja-json.4e44e36b.chunk.js → ja-json.6f262117.chunk.js} +1 -1
  132. package/build/{ko-json.1003756e.chunk.js → ko-json.36dc3b9a.chunk.js} +1 -1
  133. package/build/main.63e7ea0a.js +9338 -0
  134. package/build/{ml-json.c7774425.chunk.js → ml-json.9566bf9a.chunk.js} +1 -1
  135. package/build/{nl-json.f58ea235.chunk.js → nl-json.94c3a289.chunk.js} +1 -1
  136. package/build/{pl-json.fed96aba.chunk.js → pl-json.ccc6ef23.chunk.js} +1 -1
  137. package/build/{pt-BR-json.073799ab.chunk.js → pt-BR-json.744f024d.chunk.js} +1 -1
  138. package/build/{ru-json.7ad2cbbf.chunk.js → ru-json.d22ea13c.chunk.js} +1 -1
  139. package/build/{runtime~main.feeac6d3.js → runtime~main.3a5e1b07.js} +1 -1
  140. package/build/{sa-json.f0f704f0.chunk.js → sa-json.8fb1c04d.chunk.js} +1 -1
  141. package/build/{sk-json.a848961b.chunk.js → sk-json.6c7335d4.chunk.js} +1 -1
  142. package/build/sso-settings-page.9ceb0140.chunk.js +1 -0
  143. package/build/{sv-json.b038acbe.chunk.js → sv-json.2e589a7d.chunk.js} +1 -1
  144. package/build/{upload-settings.80ff0974.chunk.js → upload-settings.3d613216.chunk.js} +4 -4
  145. package/build/{upload-translation-en-json.004a86c1.chunk.js → upload-translation-en-json.86da7b0a.chunk.js} +1 -1
  146. package/build/{users-advanced-settings-page.a02f4806.chunk.js → users-advanced-settings-page.f4051d92.chunk.js} +4 -4
  147. package/build/{webhook-edit-page.d2ea3351.chunk.js → webhook-edit-page.9e46fc3f.chunk.js} +1 -1
  148. package/build/{webhook-list-page.2775a683.chunk.js → webhook-list-page.a712ae40.chunk.js} +4 -4
  149. package/build/{zh-Hans-json.03d2bda1.chunk.js → zh-Hans-json.a4d7dc69.chunk.js} +1 -1
  150. package/build/{zh-json.3d0cc664.chunk.js → zh-json.66aa2ae1.chunk.js} +1 -1
  151. package/ee/server/controllers/user.js +5 -3
  152. package/package.json +9 -8
  153. package/server/bootstrap.js +19 -1
  154. package/server/config/admin-actions.js +20 -0
  155. package/server/content-types/api-token-permission.js +36 -0
  156. package/server/content-types/api-token.js +25 -1
  157. package/server/content-types/index.js +1 -0
  158. package/server/controllers/admin.js +3 -0
  159. package/server/controllers/api-token.js +24 -1
  160. package/server/controllers/content-api.js +15 -0
  161. package/server/controllers/index.js +1 -0
  162. package/server/controllers/user.js +3 -2
  163. package/server/routes/api-tokens.js +11 -0
  164. package/server/routes/content-api.js +20 -0
  165. package/server/routes/index.js +2 -0
  166. package/server/services/api-token.js +309 -29
  167. package/server/services/constants.js +10 -0
  168. package/server/services/permission/engine.js +36 -226
  169. package/server/services/permission.js +4 -1
  170. package/server/strategies/admin.js +7 -1
  171. package/server/strategies/api-token.js +72 -11
  172. package/server/validation/api-tokens.js +12 -2
  173. package/utils/get-custom-app-config-file.js +5 -0
  174. package/build/1856.47226450.chunk.js +0 -173
  175. package/build/4715.58cd558f.chunk.js +0 -387
  176. package/build/7098.40dcd7bf.chunk.js +0 -1
  177. package/build/8851.e4ac62f2.chunk.js +0 -158
  178. package/build/Admin-authenticatedApp.e39f36c9.chunk.js +0 -80
  179. package/build/admin-app.4f7618a9.chunk.js +0 -112
  180. package/build/admin-edit-roles-page.554ba3fa.chunk.js +0 -1
  181. package/build/api-tokens-create-page.4c262d6e.chunk.js +0 -1
  182. package/build/api-tokens-edit-page.10a9d368.chunk.js +0 -1
  183. package/build/api-tokens-list-page.442c9f3c.chunk.js +0 -15
  184. package/build/content-manager.7d57c9d1.chunk.js +0 -1200
  185. package/build/content-type-builder-list-view.8cc534e0.chunk.js +0 -194
  186. package/build/content-type-builder-translation-en-json.201bfb78.chunk.js +0 -1
  187. package/build/content-type-builder.684df7a4.chunk.js +0 -142
  188. package/build/en-json.0c69c7d7.chunk.js +0 -1
  189. package/build/main.b47db1a3.js +0 -9337
  190. package/build/sso-settings-page.445184e0.chunk.js +0 -1
  191. package/server/services/permission/engine-hooks.js +0 -82
@@ -8,7 +8,7 @@ import invariant from 'invariant';
8
8
  import { Helmet } from 'react-helmet';
9
9
  import { basename, createHook } from './core/utils';
10
10
  import configureStore from './core/store/configureStore';
11
- import { Plugin } from './core/apis';
11
+ import { customFields, Plugin } from './core/apis';
12
12
  import App from './pages/App';
13
13
  import AuthLogo from './assets/images/logo_strapi_auth_v4.png';
14
14
  import MenuLogo from './assets/images/logo_strapi_menu.png';
@@ -48,6 +48,7 @@ class StrapiApp {
48
48
  this.admin = {
49
49
  injectionZones,
50
50
  };
51
+ this.customFields = customFields;
51
52
 
52
53
  this.menu = [];
53
54
  this.settings = {
@@ -281,17 +282,7 @@ class StrapiApp {
281
282
 
282
283
  async initialize() {
283
284
  Object.keys(this.appPlugins).forEach((plugin) => {
284
- this.appPlugins[plugin].register({
285
- addComponents: this.addComponents,
286
- addCorePluginMenuLink: this.addCorePluginMenuLink,
287
- addFields: this.addFields,
288
- addMenuLink: this.addMenuLink,
289
- addMiddlewares: this.addMiddlewares,
290
- addReducers: this.addReducers,
291
- createHook: this.createHook,
292
- createSettingSection: this.createSettingSection,
293
- registerPlugin: this.registerPlugin,
294
- });
285
+ this.appPlugins[plugin].register(this);
295
286
  });
296
287
  }
297
288
 
@@ -431,6 +422,7 @@ class StrapiApp {
431
422
  authLogo={this.configurations.authLogo}
432
423
  components={components}
433
424
  fields={fields}
425
+ customFields={this.customFields}
434
426
  localeNames={localeNames}
435
427
  getAdminInjectedComponents={this.getAdminInjectedComponents}
436
428
  getPlugin={this.getPlugin}
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { QueryClientProvider, QueryClient } from 'react-query';
4
- import { LibraryProvider, StrapiAppProvider } from '@strapi/helper-plugin';
4
+ import { LibraryProvider, CustomFieldsProvider, StrapiAppProvider } from '@strapi/helper-plugin';
5
5
  import { Provider } from 'react-redux';
6
6
  import { AdminContext } from '../../contexts';
7
7
  import ConfigurationsProvider from '../ConfigurationsProvider';
@@ -25,6 +25,7 @@ const Providers = ({
25
25
  authLogo,
26
26
  children,
27
27
  components,
28
+ customFields,
28
29
  fields,
29
30
  getAdminInjectedComponents,
30
31
  getPlugin,
@@ -64,15 +65,17 @@ const Providers = ({
64
65
  settings={settings}
65
66
  >
66
67
  <LibraryProvider components={components} fields={fields}>
67
- <LanguageProvider messages={messages} localeNames={localeNames}>
68
- <AutoReloadOverlayBlockerProvider>
69
- <OverlayBlocker>
70
- <GuidedTour>
71
- <Notifications>{children}</Notifications>
72
- </GuidedTour>
73
- </OverlayBlocker>
74
- </AutoReloadOverlayBlockerProvider>
75
- </LanguageProvider>
68
+ <CustomFieldsProvider customFields={customFields}>
69
+ <LanguageProvider messages={messages} localeNames={localeNames}>
70
+ <AutoReloadOverlayBlockerProvider>
71
+ <OverlayBlocker>
72
+ <GuidedTour>
73
+ <Notifications>{children}</Notifications>
74
+ </GuidedTour>
75
+ </OverlayBlocker>
76
+ </AutoReloadOverlayBlockerProvider>
77
+ </LanguageProvider>
78
+ </CustomFieldsProvider>
76
79
  </LibraryProvider>
77
80
  </StrapiAppProvider>
78
81
  </ConfigurationsProvider>
@@ -88,6 +91,7 @@ Providers.propTypes = {
88
91
  authLogo: PropTypes.oneOfType([PropTypes.string, PropTypes.any]).isRequired,
89
92
  children: PropTypes.element.isRequired,
90
93
  components: PropTypes.object.isRequired,
94
+ customFields: PropTypes.object.isRequired,
91
95
  fields: PropTypes.object.isRequired,
92
96
  getAdminInjectedComponents: PropTypes.func.isRequired,
93
97
  getPlugin: PropTypes.func.isRequired,
@@ -294,6 +294,29 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
294
294
  ]
295
295
  );
296
296
 
297
+ const onDraftRelationCheck = useCallback(async () => {
298
+ try {
299
+ trackUsageRef.current('willCheckDraftRelations');
300
+
301
+ const endPoint = getRequestUrl(
302
+ `collection-types/${slug}/${id}/actions/numberOfDraftRelations`
303
+ );
304
+ dispatch(setStatus('draft-relation-check-pending'));
305
+
306
+ const numberOfDraftRelations = await axiosInstance.get(endPoint);
307
+ trackUsageRef.current('didCheckDraftRelations');
308
+
309
+ dispatch(setStatus('resolved'));
310
+
311
+ return numberOfDraftRelations.data.data;
312
+ } catch (err) {
313
+ displayErrors(err);
314
+ dispatch(setStatus('resolved'));
315
+
316
+ return Promise.reject(err);
317
+ }
318
+ }, [displayErrors, id, slug, dispatch]);
319
+
297
320
  const onPublish = useCallback(async () => {
298
321
  try {
299
322
  trackUsageRef.current('willPublishEntry');
@@ -397,6 +420,7 @@ const CollectionTypeFormWrapper = ({ allLayoutData, children, slug, id, origin }
397
420
  onDeleteSucceeded,
398
421
  onPost,
399
422
  onPublish,
423
+ onDraftRelationCheck,
400
424
  onPut,
401
425
  onUnpublish,
402
426
  status,
@@ -1,20 +1,26 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { Link, useHistory } from 'react-router-dom';
4
+ import { useIntl } from 'react-intl';
5
+
3
6
  import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
4
7
  import { Box } from '@strapi/design-system/Box';
5
8
  import { IconButton } from '@strapi/design-system/IconButton';
6
9
  import { Tbody, Td, Tr } from '@strapi/design-system/Table';
7
10
  import { Flex } from '@strapi/design-system/Flex';
11
+
8
12
  import Trash from '@strapi/icons/Trash';
9
13
  import Duplicate from '@strapi/icons/Duplicate';
10
14
  import Pencil from '@strapi/icons/Pencil';
15
+
11
16
  import { useTracking, stopPropagation, onRowClick } from '@strapi/helper-plugin';
12
- import { useHistory } from 'react-router-dom';
13
- import { useIntl } from 'react-intl';
17
+
14
18
  import { usePluginsQueryParams } from '../../../hooks';
15
- import CellContent from '../CellContent';
19
+
16
20
  import { getFullName } from '../../../../utils';
17
21
 
22
+ import CellContent from '../CellContent';
23
+
18
24
  const TableRows = ({
19
25
  canCreate,
20
26
  canDelete,
@@ -102,13 +108,14 @@ const TableRows = ({
102
108
  <Td>
103
109
  <Flex justifyContent="end" {...stopPropagation}>
104
110
  <IconButton
111
+ forwardedAs={Link}
105
112
  onClick={() => {
106
113
  trackUsage('willEditEntryFromButton');
107
- push({
108
- pathname: `${pathname}/${data.id}`,
109
- state: { from: pathname },
110
- search: pluginsQueryParams,
111
- });
114
+ }}
115
+ to={{
116
+ pathname: `${pathname}/${data.id}`,
117
+ state: { from: pathname },
118
+ search: pluginsQueryParams,
112
119
  }}
113
120
  label={formatMessage(
114
121
  { id: 'app.component.table.edit', defaultMessage: 'Edit {target}' },
@@ -121,12 +128,11 @@ const TableRows = ({
121
128
  {canCreate && (
122
129
  <Box paddingLeft={1}>
123
130
  <IconButton
124
- onClick={() => {
125
- push({
126
- pathname: `${pathname}/create/clone/${data.id}`,
127
- state: { from: pathname },
128
- search: pluginsQueryParams,
129
- });
131
+ forwardedAs={Link}
132
+ to={{
133
+ pathname: `${pathname}/create/clone/${data.id}`,
134
+ state: { from: pathname },
135
+ search: pluginsQueryParams,
130
136
  }}
131
137
  label={formatMessage(
132
138
  {
@@ -146,7 +152,6 @@ const TableRows = ({
146
152
  <IconButton
147
153
  onClick={() => {
148
154
  trackUsage('willDeleteEntryFromList');
149
-
150
155
  onClickDelete(data.id);
151
156
  }}
152
157
  label={formatMessage(
@@ -1,4 +1,4 @@
1
- import React, { memo, useMemo } from 'react';
1
+ import React, { memo, Suspense, useMemo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import styled from 'styled-components';
4
4
  import isEqual from 'react-fast-compare';
@@ -8,7 +8,9 @@ import { Accordion, AccordionToggle, AccordionContent } from '@strapi/design-sys
8
8
  import { IconButton } from '@strapi/design-system/IconButton';
9
9
  import { FocusTrap } from '@strapi/design-system/FocusTrap';
10
10
  import { Box } from '@strapi/design-system/Box';
11
+ import { Flex } from '@strapi/design-system/Flex';
11
12
  import { Stack } from '@strapi/design-system/Stack';
13
+ import { Loader } from '@strapi/design-system/Loader';
12
14
  import Trash from '@strapi/icons/Trash';
13
15
  import ArrowDown from '@strapi/icons/ArrowDown';
14
16
  import ArrowUp from '@strapi/icons/ArrowUp';
@@ -147,14 +149,22 @@ const Component = ({
147
149
  />
148
150
  <AccordionContent>
149
151
  <AccordionContentRadius background="neutral0">
150
- <FocusTrap onEscape={() => onToggle(index)}>
151
- <FieldComponent
152
- componentUid={componentUid}
153
- icon={icon}
154
- name={`${name}.${index}`}
155
- isFromDynamicZone
156
- />
157
- </FocusTrap>
152
+ <Suspense
153
+ fallback={
154
+ <Flex justifyContent="center" paddingTop={4} paddingBottom={4}>
155
+ <Loader>Loading content.</Loader>
156
+ </Flex>
157
+ }
158
+ >
159
+ <FocusTrap onEscape={() => onToggle(index)}>
160
+ <FieldComponent
161
+ componentUid={componentUid}
162
+ icon={icon}
163
+ name={`${name}.${index}`}
164
+ isFromDynamicZone
165
+ />
166
+ </FocusTrap>
167
+ </Suspense>
158
168
  </AccordionContentRadius>
159
169
  </AccordionContent>
160
170
  </Accordion>
@@ -36,6 +36,7 @@ const EditViewDataManagerProvider = ({
36
36
  isSingleType,
37
37
  onPost,
38
38
  onPublish,
39
+ onDraftRelationCheck,
39
40
  onPut,
40
41
  onUnpublish,
41
42
  readActionAllowedFields,
@@ -46,7 +47,15 @@ const EditViewDataManagerProvider = ({
46
47
  updateActionAllowedFields,
47
48
  }) => {
48
49
  const [reducerState, dispatch] = useReducer(reducer, initialState);
49
- const { formErrors, initialData, modifiedData, modifiedDZName, shouldCheckErrors } = reducerState;
50
+ const {
51
+ formErrors,
52
+ initialData,
53
+ modifiedData,
54
+ modifiedDZName,
55
+ shouldCheckErrors,
56
+ publishConfirmation,
57
+ } = reducerState;
58
+
50
59
  const toggleNotification = useNotification();
51
60
  const { lockApp, unlockApp } = useOverlayBlocker();
52
61
 
@@ -301,6 +310,14 @@ const EditViewDataManagerProvider = ({
301
310
  return shouldNotRunValidations ? { status: 'draft' } : {};
302
311
  }, [hasDraftAndPublish, shouldNotRunValidations]);
303
312
 
313
+ const handlePublishPromptDismissal = useCallback(async (e) => {
314
+ e.preventDefault();
315
+
316
+ return dispatch({
317
+ type: 'RESET_PUBLISH_CONFIRMATION',
318
+ });
319
+ }, []);
320
+
304
321
  const handleSubmit = useCallback(
305
322
  async (e) => {
306
323
  e.preventDefault();
@@ -346,8 +363,27 @@ const EditViewDataManagerProvider = ({
346
363
  },
347
364
  { isCreatingEntry, isDraft: false, isFromComponent: false }
348
365
  );
349
- let errors = {};
350
366
 
367
+ const draftCount = await onDraftRelationCheck();
368
+
369
+ if (!publishConfirmation.show && draftCount > 0) {
370
+ // If the warning hasn't already been shown and draft relations are found,
371
+ // abort the publish call and ask for confirmation from the user
372
+ dispatch({
373
+ type: 'SET_PUBLISH_CONFIRMATION',
374
+ publishConfirmation: {
375
+ show: true,
376
+ draftCount,
377
+ },
378
+ });
379
+
380
+ return;
381
+ }
382
+ dispatch({
383
+ type: 'RESET_PUBLISH_CONFIRMATION',
384
+ });
385
+
386
+ let errors = {};
351
387
  try {
352
388
  await schema.validate(modifiedData, { abortEarly: false });
353
389
  } catch (err) {
@@ -369,7 +405,15 @@ const EditViewDataManagerProvider = ({
369
405
  type: 'SET_FORM_ERRORS',
370
406
  errors,
371
407
  });
372
- }, [allLayoutData, currentContentTypeLayout, isCreatingEntry, modifiedData, onPublish]);
408
+ }, [
409
+ allLayoutData,
410
+ currentContentTypeLayout,
411
+ isCreatingEntry,
412
+ modifiedData,
413
+ publishConfirmation.show,
414
+ onPublish,
415
+ onDraftRelationCheck,
416
+ ]);
373
417
 
374
418
  const shouldCheckDZErrors = useCallback(
375
419
  (dzName) => {
@@ -506,6 +550,8 @@ const EditViewDataManagerProvider = ({
506
550
  slug,
507
551
  triggerFormValidation,
508
552
  updateActionAllowedFields,
553
+ onPublishPromptDismissal: handlePublishPromptDismissal,
554
+ publishConfirmation,
509
555
  }}
510
556
  >
511
557
  <>
@@ -549,6 +595,7 @@ EditViewDataManagerProvider.propTypes = {
549
595
  isSingleType: PropTypes.bool.isRequired,
550
596
  onPost: PropTypes.func.isRequired,
551
597
  onPublish: PropTypes.func.isRequired,
598
+ onDraftRelationCheck: PropTypes.func.isRequired,
552
599
  onPut: PropTypes.func.isRequired,
553
600
  onUnpublish: PropTypes.func.isRequired,
554
601
  readActionAllowedFields: PropTypes.array.isRequired,
@@ -15,6 +15,10 @@ const initialState = {
15
15
  modifiedData: null,
16
16
  shouldCheckErrors: false,
17
17
  modifiedDZName: null,
18
+ publishConfirmation: {
19
+ show: false,
20
+ draftCount: 0,
21
+ },
18
22
  };
19
23
 
20
24
  const reducer = (state, action) =>
@@ -94,20 +98,50 @@ const reducer = (state, action) =>
94
98
  const path = ['modifiedData', ...action.keys];
95
99
  const { value, replace = false } = action;
96
100
  const connectedRelations = get(state, [...path, 'connect']);
97
- const disconnectedRelations = get(state, [...path, 'disconnect']);
101
+ const disconnectedRelations = get(state, [...path, 'disconnect']) ?? [];
102
+ const savedRelations = get(state, [...path, 'results']) ?? [];
103
+ const existInSavedRelation =
104
+ savedRelations?.findIndex((savedRelations) => savedRelations.id === value.id) !== -1;
98
105
 
99
106
  if (!connectedRelations) {
100
107
  set(draftState, [...path, 'connect'], []);
101
108
  }
102
109
 
103
- if (replace) {
104
- set(draftState, [...path, 'connect'], [value]);
105
- } else {
106
- const nextValue = get(draftState, [...path, 'connect']);
107
- nextValue.push(value);
110
+ // We should add a relation in the connect array only if it is not an already saved relation
111
+ if (!existInSavedRelation) {
112
+ if (replace) {
113
+ set(draftState, [...path, 'connect'], [value]);
114
+ } else {
115
+ const nextValue = get(draftState, [...path, 'connect']);
116
+ nextValue.push(value);
117
+ }
108
118
  }
109
119
 
110
- if (disconnectedRelations?.length) {
120
+ // Disconnect array handling
121
+ if (replace) {
122
+ // In xToOne relations we should place the saved relation in disconnected array to not display it
123
+ // only needed if there is a saved relation and it is not already stored in disconnected array
124
+ if (savedRelations.length && !disconnectedRelations.length) {
125
+ set(draftState, [...path, 'disconnect'], savedRelations);
126
+ }
127
+
128
+ // If the saved relation is stored in disconnected array
129
+ // We should remove it when an action requires to reconnect this relation
130
+ // We then reset the connect/disconnect state
131
+ if (disconnectedRelations.length) {
132
+ const existsInDisconnectedRelations =
133
+ disconnectedRelations.findIndex(
134
+ (disconnectedRelation) => disconnectedRelation?.id === value.id
135
+ ) > -1;
136
+
137
+ if (existsInDisconnectedRelations) {
138
+ set(draftState, [...path, 'disconnect'], []);
139
+ set(draftState, [...path, 'connect'], []);
140
+ }
141
+ }
142
+ } else if (disconnectedRelations.length) {
143
+ // In xToMany relations, when an action requires to connect a relation
144
+ // We should remove it from the disconnected array if it existed in it
111
145
  const existsInDisconnect = disconnectedRelations.find(
112
146
  (disconnectValue) => disconnectValue.id === value.id
113
147
  );
@@ -123,8 +157,8 @@ const reducer = (state, action) =>
123
157
  case 'DISCONNECT_RELATION': {
124
158
  const path = ['modifiedData', ...action.keys];
125
159
  const { value } = action;
126
- const disconnectedRelations = get(state, [...path, 'disconnect']);
127
160
  const connectedRelations = get(state, [...path, 'connect']);
161
+ const disconnectedRelations = get(state, [...path, 'disconnect']);
128
162
 
129
163
  if (!disconnectedRelations) {
130
164
  set(draftState, [...path, 'disconnect'], []);
@@ -345,7 +379,14 @@ const reducer = (state, action) =>
345
379
 
346
380
  break;
347
381
  }
348
-
382
+ case 'SET_PUBLISH_CONFIRMATION': {
383
+ draftState.publishConfirmation = { ...action.publishConfirmation };
384
+ break;
385
+ }
386
+ case 'RESET_PUBLISH_CONFIRMATION': {
387
+ draftState.publishConfirmation = { ...state.publishConfirmation, show: false };
388
+ break;
389
+ }
349
390
  default:
350
391
  return draftState;
351
392
  }
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import { Box } from '@strapi/design-system/Box';
4
+ import { useCustomFields } from '@strapi/helper-plugin';
3
5
  import Date from '@strapi/icons/Date';
4
6
  import Boolean from '@strapi/icons/Boolean';
5
7
  import Email from '@strapi/icons/Email';
@@ -40,10 +42,38 @@ const iconByTypes = {
40
42
  dynamiczone: <DynamicZone />,
41
43
  };
42
44
 
43
- const FieldTypeIcon = ({ type }) => iconByTypes[type] || null;
45
+ const FieldTypeIcon = ({ type, customFieldUid }) => {
46
+ const customFieldsRegistry = useCustomFields();
47
+
48
+ let Compo = iconByTypes[type];
49
+
50
+ if (customFieldUid) {
51
+ const customField = customFieldsRegistry.get(customFieldUid);
52
+ const CustomFieldIcon = customField.icon;
53
+
54
+ if (CustomFieldIcon) {
55
+ Compo = (
56
+ <Box marginRight={3} width={7} height={6}>
57
+ <CustomFieldIcon />
58
+ </Box>
59
+ );
60
+ }
61
+ }
62
+
63
+ if (!iconByTypes[type]) {
64
+ return null;
65
+ }
66
+
67
+ return Compo;
68
+ };
69
+
70
+ FieldTypeIcon.defaultProps = {
71
+ customFieldUid: null,
72
+ };
44
73
 
45
74
  FieldTypeIcon.propTypes = {
46
75
  type: PropTypes.string.isRequired,
76
+ customFieldUid: PropTypes.string,
47
77
  };
48
78
 
49
79
  export default FieldTypeIcon;
@@ -5,16 +5,14 @@ import get from 'lodash/get';
5
5
  import omit from 'lodash/omit';
6
6
  import take from 'lodash/take';
7
7
  import isEqual from 'react-fast-compare';
8
- import { GenericInput, NotAllowedInput, useLibrary } from '@strapi/helper-plugin';
9
-
8
+ import { GenericInput, NotAllowedInput, useLibrary, useCustomFields } from '@strapi/helper-plugin';
9
+ import { useContentTypeLayout } from '../../hooks';
10
+ import { getFieldName } from '../../utils';
10
11
  import Wysiwyg from '../Wysiwyg';
11
12
  import InputJSON from '../InputJSON';
12
13
  import InputUID from '../InputUID';
13
14
  import { RelationInputDataManager } from '../RelationInputDataManager';
14
15
 
15
- import { useContentTypeLayout } from '../../hooks';
16
- import { getFieldName } from '../../utils';
17
-
18
16
  import {
19
17
  connect,
20
18
  generateOptions,
@@ -26,6 +24,7 @@ import {
26
24
 
27
25
  function Inputs({
28
26
  allowedFields,
27
+ componentUid,
29
28
  fieldSchema,
30
29
  formErrors,
31
30
  isCreatingEntry,
@@ -42,9 +41,10 @@ function Inputs({
42
41
  const { fields } = useLibrary();
43
42
  const { formatMessage } = useIntl();
44
43
  const { contentType: currentContentTypeLayout } = useContentTypeLayout();
44
+ const customFieldsRegistry = useCustomFields();
45
45
 
46
46
  const disabled = useMemo(() => !get(metadatas, 'editable', true), [metadatas]);
47
- const type = fieldSchema.type;
47
+ const { type, customField: customFieldUid } = fieldSchema;
48
48
  const error = get(formErrors, [keys], null);
49
49
 
50
50
  const fieldName = useMemo(() => {
@@ -194,6 +194,19 @@ function Inputs({
194
194
  return minutes % metadatas.step === 0 ? metadatas.step : step;
195
195
  }, [inputType, inputValue, metadatas.step, step]);
196
196
 
197
+ // Memoize the component to avoid remounting it and losing state
198
+ const CustomFieldInput = useMemo(() => {
199
+ if (customFieldUid) {
200
+ const customField = customFieldsRegistry.get(customFieldUid);
201
+ const CustomFieldInput = React.lazy(customField.components.Input);
202
+
203
+ return CustomFieldInput;
204
+ }
205
+
206
+ // Not a custom field, component won't be used
207
+ return null;
208
+ }, [customFieldUid, customFieldsRegistry]);
209
+
197
210
  if (visible === false) {
198
211
  return null;
199
212
  }
@@ -216,6 +229,7 @@ function Inputs({
216
229
  <RelationInputDataManager
217
230
  {...metadatas}
218
231
  {...fieldSchema}
232
+ componentUid={componentUid}
219
233
  description={
220
234
  metadatas.description
221
235
  ? formatMessage({
@@ -248,6 +262,18 @@ function Inputs({
248
262
  );
249
263
  }
250
264
 
265
+ const customInputs = {
266
+ json: InputJSON,
267
+ uid: InputUID,
268
+ media: fields.media,
269
+ wysiwyg: Wysiwyg,
270
+ ...fields,
271
+ };
272
+
273
+ if (customFieldUid) {
274
+ customInputs[customFieldUid] = CustomFieldInput;
275
+ }
276
+
251
277
  return (
252
278
  <GenericInput
253
279
  attribute={fieldSchema}
@@ -260,13 +286,7 @@ function Inputs({
260
286
  error={error}
261
287
  labelAction={labelAction}
262
288
  contentTypeUID={currentContentTypeLayout.uid}
263
- customInputs={{
264
- json: InputJSON,
265
- uid: InputUID,
266
- media: fields.media,
267
- wysiwyg: Wysiwyg,
268
- ...fields,
269
- }}
289
+ customInputs={customInputs}
270
290
  multiple={fieldSchema.multiple || false}
271
291
  name={keys}
272
292
  onChange={onChange}
@@ -274,7 +294,7 @@ function Inputs({
274
294
  placeholder={placeholder ? { id: placeholder, defaultMessage: placeholder } : null}
275
295
  required={fieldSchema.required || false}
276
296
  step={inputStep}
277
- type={inputType}
297
+ type={customFieldUid || inputType}
278
298
  // validations={validations}
279
299
  value={inputValue}
280
300
  withDefaultValue={false}
@@ -283,6 +303,7 @@ function Inputs({
283
303
  }
284
304
 
285
305
  Inputs.defaultProps = {
306
+ componentUid: undefined,
286
307
  formErrors: {},
287
308
  labelAction: undefined,
288
309
  size: undefined,
@@ -292,6 +313,7 @@ Inputs.defaultProps = {
292
313
 
293
314
  Inputs.propTypes = {
294
315
  allowedFields: PropTypes.array.isRequired,
316
+ componentUid: PropTypes.string,
295
317
  fieldSchema: PropTypes.object.isRequired,
296
318
  formErrors: PropTypes.object,
297
319
  keys: PropTypes.string.isRequired,
@@ -61,10 +61,12 @@ const NonRepeatableComponent = ({ componentUid, isFromDynamicZone, isNested, nam
61
61
  return (
62
62
  <GridItem col={size} key={fieldName} s={12} xs={12}>
63
63
  <Inputs
64
+ componentUid={componentUid}
64
65
  keys={keys}
65
66
  fieldSchema={fieldSchema}
66
67
  metadatas={metadatas}
67
68
  queryInfos={queryInfos}
69
+ size={size}
68
70
  />
69
71
  </GridItem>
70
72
  );