@strapi/admin 4.14.0-alpha.0 → 4.14.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 (155) hide show
  1. package/admin/src/components/RBACProvider/index.js +1 -1
  2. package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +87 -0
  3. package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +463 -0
  4. package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +451 -0
  5. package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +102 -0
  6. package/admin/src/content-manager/components/BlocksEditor/index.js +126 -0
  7. package/admin/src/content-manager/components/InputUID/index.js +92 -109
  8. package/admin/src/content-manager/components/Inputs/index.js +3 -1
  9. package/admin/src/content-manager/components/Inputs/utils/getInputType.js +2 -0
  10. package/admin/src/content-manager/components/RelationInput/RelationInput.js +59 -43
  11. package/admin/src/content-manager/components/Wysiwyg/EditorLayout.js +2 -2
  12. package/admin/src/content-manager/hooks/useRelation/useRelation.js +84 -86
  13. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/ConfirmBulkActionDialog/index.js +3 -0
  14. package/admin/src/content-manager/pages/ListView/components/BulkActionButtons/SelectedEntriesModal/index.js +2 -1
  15. package/admin/src/content-manager/pages/ListView/index.js +1 -0
  16. package/admin/src/content-manager/utils/schema.js +22 -0
  17. package/admin/src/index.js +7 -1
  18. package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +1 -1
  19. package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +0 -1
  20. package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +1 -1
  21. package/admin/src/translations/en.json +17 -0
  22. package/build/1049.acb0e730.chunk.js +1 -0
  23. package/build/1227.969e24e6.chunk.js +1 -0
  24. package/build/{1386.ea73b677.chunk.js → 1386.db9a2795.chunk.js} +1 -1
  25. package/build/2225.78fb9b89.chunk.js +79 -0
  26. package/build/2379.906334f0.chunk.js +1 -0
  27. package/build/{2395.0e5e8ded.chunk.js → 2395.f6ac2863.chunk.js} +2 -2
  28. package/build/2614.3e088d3e.chunk.js +35 -0
  29. package/build/{6691.4985ef22.chunk.js → 2659.cb94f1e7.chunk.js} +28 -28
  30. package/build/{2801.8e1aa82a.chunk.js → 2801.2afb4757.chunk.js} +1 -1
  31. package/build/{1387.a86ac314.chunk.js → 2950.216f2e89.chunk.js} +1 -1
  32. package/build/3021.33ad47fb.chunk.js +103 -0
  33. package/build/3911.488fbde3.chunk.js +95 -0
  34. package/build/4174.3e13fb26.chunk.js +1 -0
  35. package/build/4546.1203ac95.chunk.js +1 -0
  36. package/build/{502.7bba43b1.chunk.js → 502.9918bff7.chunk.js} +1 -1
  37. package/build/{4628.9cbb6df5.chunk.js → 5158.c85f841a.chunk.js} +1 -1
  38. package/build/6266.e8990811.chunk.js +146 -0
  39. package/build/6715.48e37308.chunk.js +1 -0
  40. package/build/6812.00ef5b0d.chunk.js +26 -0
  41. package/build/{7464.eb057bec.chunk.js → 7464.0280cf59.chunk.js} +1 -1
  42. package/build/7897.4a39de37.chunk.js +6 -0
  43. package/build/{8276.be3ed581.chunk.js → 8276.951e198e.chunk.js} +2 -2
  44. package/build/9832.65ed5a44.chunk.js +181 -0
  45. package/build/{Admin-authenticatedApp.d200a4ee.chunk.js → Admin-authenticatedApp.7e17bf9f.chunk.js} +7 -7
  46. package/build/Admin_InternalErrorPage.b3163562.chunk.js +1 -0
  47. package/build/{Admin_homePage.6f128523.chunk.js → Admin_homePage.6cb51f18.chunk.js} +10 -10
  48. package/build/Admin_marketplace.3eb5e132.chunk.js +55 -0
  49. package/build/Admin_pluginsPage.b9fa2947.chunk.js +6 -0
  50. package/build/{Admin_profilePage.678bce24.chunk.js → Admin_profilePage.a4d41380.chunk.js} +2 -2
  51. package/build/Admin_settingsPage.6dc2af9f.chunk.js +111 -0
  52. package/build/Upload_ConfigureTheView.cc7ca628.chunk.js +1 -0
  53. package/build/{admin-app.582877a3.chunk.js → admin-app.98cdf43a.chunk.js} +8 -8
  54. package/build/{admin-edit-roles-page.0aa65505.chunk.js → admin-edit-roles-page.418bb1c5.chunk.js} +30 -30
  55. package/build/admin-edit-users.9b42cc9e.chunk.js +10 -0
  56. package/build/admin-roles-list.cf964578.chunk.js +22 -0
  57. package/build/admin-users.8385dd73.chunk.js +11 -0
  58. package/build/{api-tokens-create-page.e0c15627.chunk.js → api-tokens-create-page.2f25ddf6.chunk.js} +1 -1
  59. package/build/{api-tokens-edit-page.9f2dce47.chunk.js → api-tokens-edit-page.45faac16.chunk.js} +1 -1
  60. package/build/api-tokens-list-page.5baabf1a.chunk.js +16 -0
  61. package/build/audit-logs-settings-page.91489670.chunk.js +1 -0
  62. package/build/content-manager.0d2b4a60.chunk.js +1199 -0
  63. package/build/{content-type-builder-list-view.b71cf240.chunk.js → content-type-builder-list-view.aa8a5d1a.chunk.js} +14 -14
  64. package/build/content-type-builder-translation-en-json.b9e5cacd.chunk.js +1 -0
  65. package/build/content-type-builder.885f2cad.chunk.js +146 -0
  66. package/build/email-settings-page.6bd7b280.chunk.js +11 -0
  67. package/build/{en-json.e12fd5fc.chunk.js → en-json.a3973ff5.chunk.js} +1 -1
  68. package/build/i18n-settings-page.6c0157e7.chunk.js +9 -0
  69. package/build/i18n-translation-tr-json.3bfc812f.chunk.js +1 -0
  70. package/build/index.html +1 -1
  71. package/build/main.105dcf23.js +2665 -0
  72. package/build/review-workflows-settings-create-view.ae369a88.chunk.js +1 -0
  73. package/build/review-workflows-settings-edit-view.9a61c69f.chunk.js +1 -0
  74. package/build/{review-workflows-settings-list-view.7e300ecb.chunk.js → review-workflows-settings-list-view.067e0c35.chunk.js} +5 -5
  75. package/build/{runtime~main.9de029f4.js → runtime~main.866715be.js} +2 -2
  76. package/build/sso-settings-page.a29e6c38.chunk.js +1 -0
  77. package/build/{transfer-tokens-create-page.ebba16d8.chunk.js → transfer-tokens-create-page.6e1b8cee.chunk.js} +1 -1
  78. package/build/{transfer-tokens-edit-page.d7bb2b3e.chunk.js → transfer-tokens-edit-page.10bb22e2.chunk.js} +1 -1
  79. package/build/transfer-tokens-list-page.0306652c.chunk.js +16 -0
  80. package/build/upload-settings.0af6edc5.chunk.js +14 -0
  81. package/build/upload.19e14c8e.chunk.js +58 -0
  82. package/build/users-advanced-settings-page.ed69812f.chunk.js +9 -0
  83. package/build/users-email-settings-page.131a00fb.chunk.js +9 -0
  84. package/build/users-providers-settings-page.b3dca41d.chunk.js +14 -0
  85. package/build/{users-roles-settings-page.2b051e6a.chunk.js → users-roles-settings-page.afab5a0d.chunk.js} +4 -4
  86. package/build/{webhook-edit-page.de45c635.chunk.js → webhook-edit-page.4c037da4.chunk.js} +3 -3
  87. package/build/webhook-list-page.56c82f4a.chunk.js +63 -0
  88. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +5 -15
  89. package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
  90. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +48 -20
  91. package/ee/server/bootstrap.js +3 -3
  92. package/ee/server/content-types/workflow-stage/index.js +1 -1
  93. package/ee/server/controllers/admin.js +1 -1
  94. package/ee/server/controllers/user.js +1 -1
  95. package/ee/server/destroy.js +1 -1
  96. package/ee/server/migrations/review-workflows-stages-roles.js +68 -0
  97. package/ee/server/migrations/review-workflows-workflow-name.js +7 -0
  98. package/ee/server/register.js +3 -1
  99. package/ee/server/routes/utils.js +1 -1
  100. package/ee/server/services/audit-logs.js +1 -1
  101. package/ee/server/services/passport/sso.js +1 -1
  102. package/ee/server/services/passport.js +1 -1
  103. package/ee/server/services/review-workflows/stage-permissions.js +1 -1
  104. package/ee/server/services/review-workflows/stages.js +10 -2
  105. package/ee/server/services/seat-enforcement.js +1 -1
  106. package/ee/server/utils/sso-lock.js +1 -1
  107. package/ee/server/validation/role.js +1 -1
  108. package/ee/server/validation/user.js +1 -1
  109. package/package.json +21 -13
  110. package/server/bootstrap.js +3 -1
  111. package/server/controllers/admin.js +1 -1
  112. package/server/domain/permission/index.js +8 -1
  113. package/server/services/metrics.js +17 -0
  114. package/server/validation/permission.js +1 -1
  115. package/utils/plugins.js +7 -1
  116. package/build/1049.ec69f5e0.chunk.js +0 -1
  117. package/build/1227.ec336799.chunk.js +0 -1
  118. package/build/1504.eff012f7.chunk.js +0 -95
  119. package/build/2225.649fb7bc.chunk.js +0 -79
  120. package/build/2379.1f98a31a.chunk.js +0 -1
  121. package/build/3739.63e352f1.chunk.js +0 -103
  122. package/build/4174.4587c7f6.chunk.js +0 -1
  123. package/build/448.829e1344.chunk.js +0 -1
  124. package/build/4546.6dbd695f.chunk.js +0 -1
  125. package/build/6266.53be9ea3.chunk.js +0 -124
  126. package/build/7897.eac204a4.chunk.js +0 -6
  127. package/build/9806.5d5a0e8d.chunk.js +0 -160
  128. package/build/9944.7af075a5.chunk.js +0 -26
  129. package/build/Admin_InternalErrorPage.38155af3.chunk.js +0 -1
  130. package/build/Admin_marketplace.061a6e5a.chunk.js +0 -55
  131. package/build/Admin_pluginsPage.16f837b8.chunk.js +0 -6
  132. package/build/Admin_settingsPage.af7309e4.chunk.js +0 -111
  133. package/build/Upload_ConfigureTheView.3fc1c100.chunk.js +0 -1
  134. package/build/admin-edit-users.9215912a.chunk.js +0 -10
  135. package/build/admin-roles-list.824a50de.chunk.js +0 -22
  136. package/build/admin-users.f6b3c643.chunk.js +0 -11
  137. package/build/api-tokens-list-page.d747051c.chunk.js +0 -16
  138. package/build/audit-logs-settings-page.be2cb4dd.chunk.js +0 -1
  139. package/build/content-manager.06a2f7ec.chunk.js +0 -1097
  140. package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
  141. package/build/content-type-builder.e5669749.chunk.js +0 -170
  142. package/build/email-settings-page.2809f0bf.chunk.js +0 -11
  143. package/build/i18n-settings-page.5f716172.chunk.js +0 -9
  144. package/build/i18n-translation-tr-json.10f0600d.chunk.js +0 -1
  145. package/build/main.0b88b960.js +0 -2863
  146. package/build/review-workflows-settings-create-view.604cffa0.chunk.js +0 -1
  147. package/build/review-workflows-settings-edit-view.73c57f07.chunk.js +0 -1
  148. package/build/sso-settings-page.94373f78.chunk.js +0 -1
  149. package/build/transfer-tokens-list-page.cfe1736c.chunk.js +0 -16
  150. package/build/upload-settings.5e55266d.chunk.js +0 -14
  151. package/build/upload.0660a088.chunk.js +0 -58
  152. package/build/users-advanced-settings-page.818d84eb.chunk.js +0 -9
  153. package/build/users-email-settings-page.c1967c09.chunk.js +0 -9
  154. package/build/users-providers-settings-page.11893e08.chunk.js +0 -14
  155. package/build/webhook-list-page.ca91df8b.chunk.js +0 -63
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useEffect, useRef, useState } from 'react';
1
+ import * as React from 'react';
2
2
 
3
3
  import { Flex, TextInput, Typography } from '@strapi/design-system';
4
4
  import {
@@ -10,16 +10,16 @@ import {
10
10
  import { CheckCircle, ExclamationMarkCircle, Loader, Refresh } from '@strapi/icons';
11
11
  import PropTypes from 'prop-types';
12
12
  import { useIntl } from 'react-intl';
13
+ import { useMutation, useQuery } from 'react-query';
13
14
 
14
15
  import useDebounce from '../../../hooks/useDebounce';
15
16
 
16
17
  import { FieldActionWrapper, LoadingWrapper, TextValidation } from './endActionStyle';
17
18
  import UID_REGEX from './regex';
18
19
 
19
- const InputUID = forwardRef(
20
+ const InputUID = React.forwardRef(
20
21
  (
21
22
  {
22
- attribute,
23
23
  contentTypeUID,
24
24
  hint,
25
25
  disabled,
@@ -34,20 +34,16 @@ const InputUID = forwardRef(
34
34
  },
35
35
  forwardedRef
36
36
  ) => {
37
- const { modifiedData, initialData, layout } = useCMEditViewDataManager();
38
- const [isLoading, setIsLoading] = useState(false);
39
- const [availability, setAvailability] = useState(null);
37
+ const [availability, setAvailability] = React.useState(null);
38
+ const [showRegenerate, setShowRegenerate] = React.useState(false);
39
+ /**
40
+ * @type {string | null}
41
+ */
40
42
  const debouncedValue = useDebounce(value, 300);
41
- const generateUid = useRef();
43
+ const { modifiedData, initialData } = useCMEditViewDataManager();
42
44
  const toggleNotification = useNotification();
43
45
  const { formatAPIError } = useAPIErrorHandler();
44
- const initialValue = initialData[name];
45
46
  const { formatMessage } = useIntl();
46
- const createdAtName = layout?.options?.timestamps ?? 0;
47
- const isCreation = !initialData[createdAtName];
48
- const debouncedTargetFieldValue = useDebounce(modifiedData[attribute.targetField], 300);
49
- const [isCustomized, setIsCustomized] = useState(false);
50
- const [regenerateLabel, setRegenerateLabel] = useState(null);
51
47
  const { post } = useFetchClient();
52
48
 
53
49
  const label = intlLabel.id
@@ -64,76 +60,96 @@ const InputUID = forwardRef(
64
60
  )
65
61
  : '';
66
62
 
67
- generateUid.current = async (shouldSetInitialValue = false) => {
68
- setIsLoading(true);
63
+ /**
64
+ * @type {import('react-query').UseQueryResult<string>}
65
+ */
66
+ const { data: defaultGeneratedUID, isLoading: isGeneratingDefaultUID } = useQuery({
67
+ queryKey: ['uid', { contentTypeUID, field: name, data: modifiedData }],
68
+ async queryFn({ queryKey }) {
69
+ const [, body] = queryKey;
69
70
 
70
- try {
71
71
  const {
72
72
  data: { data },
73
- } = await post('/content-manager/uid/generate', {
74
- contentTypeUID,
75
- field: name,
76
- data: modifiedData,
77
- });
73
+ } = await post('/content-manager/uid/generate', body);
78
74
 
79
- onChange({ target: { name, value: data, type: 'text' } }, shouldSetInitialValue);
80
- setIsLoading(false);
81
- } catch (error) {
82
- setIsLoading(false);
75
+ return data;
76
+ },
77
+ onError(err) {
83
78
  toggleNotification({
84
79
  type: 'warning',
85
- message: formatAPIError(error),
80
+ message: formatAPIError(err),
86
81
  });
87
- }
88
- };
82
+ },
83
+ enabled: !value && required,
84
+ });
89
85
 
90
- const checkAvailability = async () => {
91
- if (!value) {
92
- return;
86
+ /**
87
+ * If the defaultGeneratedUID is available, then we set it as the value,
88
+ * but we also want to set it as the initialValue too.
89
+ */
90
+ React.useEffect(() => {
91
+ if (defaultGeneratedUID) {
92
+ onChange({ target: { name, value: defaultGeneratedUID, type: 'text' } }, true);
93
93
  }
94
+ }, [defaultGeneratedUID, name, onChange]);
94
95
 
95
- setIsLoading(true);
96
-
97
- try {
98
- const { data } = await post('/content-manager/uid/check-availability', {
99
- contentTypeUID,
100
- field: name,
101
- value: value ? value.trim() : '',
102
- });
96
+ const { mutate: generateUID, isLoading: isGeneratingUID } = useMutation({
97
+ async mutationFn(body) {
98
+ const {
99
+ data: { data },
100
+ } = await post('/content-manager/uid/generate', body);
103
101
 
104
- setIsLoading(false);
105
- setAvailability(data);
106
- } catch (error) {
107
- setIsLoading(false);
102
+ return data;
103
+ },
104
+ onSuccess(data) {
105
+ onChange({ target: { name, value: data, type: 'text' } });
106
+ },
107
+ onError(err) {
108
108
  toggleNotification({
109
109
  type: 'warning',
110
- message: formatAPIError(error),
110
+ message: formatAPIError(err),
111
111
  });
112
- }
113
- };
112
+ },
113
+ });
114
114
 
115
- // FIXME: we need to find a better way to autofill the input when it is required.
116
- useEffect(() => {
117
- if (!value && attribute.required) {
118
- generateUid.current(true);
119
- }
120
- }, [attribute.required, generateUid, value]);
115
+ /**
116
+ * @type {import('react-query').UseQueryResult<{ isAvailable: boolean }>
117
+ */
118
+ const { data: availabilityData, isLoading: isCheckingAvailability } = useQuery({
119
+ queryKey: [
120
+ 'uid',
121
+ { contentTypeUID, field: name, value: debouncedValue ? debouncedValue.trim() : '' },
122
+ ],
123
+ async queryFn({ queryKey }) {
124
+ const [, body] = queryKey;
121
125
 
122
- useEffect(() => {
123
- if (debouncedValue?.trim().match(UID_REGEX) && debouncedValue !== initialValue) {
124
- checkAvailability();
125
- }
126
+ const { data } = await post('/content-manager/uid/check-availability', body);
126
127
 
127
- if (!debouncedValue) {
128
- setAvailability(null);
129
- }
130
- // eslint-disable-next-line react-hooks/exhaustive-deps
131
- }, [initialValue, debouncedValue]);
128
+ return data;
129
+ },
130
+ enabled: Boolean(
131
+ debouncedValue !== initialData[name] &&
132
+ debouncedValue &&
133
+ UID_REGEX.test(debouncedValue.trim())
134
+ ),
135
+ onError(err) {
136
+ toggleNotification({
137
+ type: 'warning',
138
+ message: formatAPIError(err),
139
+ });
140
+ },
141
+ });
142
+
143
+ React.useEffect(() => {
144
+ /**
145
+ * always store the data in state because that way as seen below
146
+ * we can then remove the data to stop showing the label.
147
+ */
148
+ setAvailability(availabilityData);
132
149
 
133
- useEffect(() => {
134
150
  let timer;
135
151
 
136
- if (availability?.isAvailable) {
152
+ if (availabilityData?.isAvailable) {
137
153
  timer = setTimeout(() => {
138
154
  setAvailability(null);
139
155
  }, 4000);
@@ -144,41 +160,9 @@ const InputUID = forwardRef(
144
160
  clearTimeout(timer);
145
161
  }
146
162
  };
147
- }, [availability]);
148
-
149
- useEffect(() => {
150
- if (
151
- !isCustomized &&
152
- isCreation &&
153
- debouncedTargetFieldValue &&
154
- modifiedData[attribute.targetField] &&
155
- !value
156
- ) {
157
- generateUid.current(true);
158
- }
159
- // eslint-disable-next-line react-hooks/exhaustive-deps
160
- }, [debouncedTargetFieldValue, isCustomized, isCreation]);
163
+ }, [availabilityData]);
161
164
 
162
- const handleGenerateMouseEnter = () => {
163
- setRegenerateLabel(
164
- formatMessage({
165
- id: 'content-manager.components.uid.regenerate',
166
- defaultMessage: 'Regenerate',
167
- })
168
- );
169
- };
170
-
171
- const handleGenerateMouseLeave = () => {
172
- setRegenerateLabel(null);
173
- };
174
-
175
- const handleChange = (e) => {
176
- if (e.target.value && isCreation) {
177
- setIsCustomized(true);
178
- }
179
-
180
- onChange(e);
181
- };
165
+ const isLoading = isGeneratingDefaultUID || isGeneratingUID || isCheckingAvailability;
182
166
 
183
167
  return (
184
168
  <TextInput
@@ -187,7 +171,7 @@ const InputUID = forwardRef(
187
171
  error={error}
188
172
  endAction={
189
173
  <Flex position="relative" gap={1}>
190
- {availability && !regenerateLabel && (
174
+ {availability && !showRegenerate && (
191
175
  <TextValidation
192
176
  alignItems="center"
193
177
  gap={1}
@@ -222,22 +206,25 @@ const InputUID = forwardRef(
222
206
 
223
207
  {!disabled && (
224
208
  <>
225
- {regenerateLabel && (
209
+ {showRegenerate && (
226
210
  <TextValidation alignItems="center" justifyContent="flex-end" gap={1}>
227
211
  <Typography textColor="primary600" variant="pi">
228
- {regenerateLabel}
212
+ {formatMessage({
213
+ id: 'content-manager.components.uid.regenerate',
214
+ defaultMessage: 'Regenerate',
215
+ })}
229
216
  </Typography>
230
217
  </TextValidation>
231
218
  )}
232
219
 
233
220
  <FieldActionWrapper
234
- onClick={() => generateUid.current()}
221
+ onClick={() => generateUID({ contentTypeUID, field: name, data: modifiedData })}
235
222
  label={formatMessage({
236
223
  id: 'content-manager.components.uid.regenerate',
237
224
  defaultMessage: 'Regenerate',
238
225
  })}
239
- onMouseEnter={handleGenerateMouseEnter}
240
- onMouseLeave={handleGenerateMouseLeave}
226
+ onMouseEnter={() => setShowRegenerate(true)}
227
+ onMouseLeave={() => setShowRegenerate(false)}
241
228
  >
242
229
  {isLoading ? (
243
230
  <LoadingWrapper data-testid="loading-wrapper">
@@ -255,7 +242,7 @@ const InputUID = forwardRef(
255
242
  label={label}
256
243
  labelAction={labelAction}
257
244
  name={name}
258
- onChange={handleChange}
245
+ onChange={onChange}
259
246
  placeholder={formattedPlaceholder}
260
247
  value={value || ''}
261
248
  required={required}
@@ -265,10 +252,6 @@ const InputUID = forwardRef(
265
252
  );
266
253
 
267
254
  InputUID.propTypes = {
268
- attribute: PropTypes.shape({
269
- targetField: PropTypes.string,
270
- required: PropTypes.bool,
271
- }).isRequired,
272
255
  contentTypeUID: PropTypes.string.isRequired,
273
256
  disabled: PropTypes.bool,
274
257
  error: PropTypes.string,
@@ -300,4 +283,4 @@ InputUID.defaultProps = {
300
283
  hint: '',
301
284
  };
302
285
 
303
- export default InputUID;
286
+ export { InputUID };
@@ -10,7 +10,8 @@ import { useIntl } from 'react-intl';
10
10
 
11
11
  import { useContentTypeLayout } from '../../hooks';
12
12
  import { getFieldName } from '../../utils';
13
- import InputUID from '../InputUID';
13
+ import Blocks from '../BlocksEditor';
14
+ import { InputUID } from '../InputUID';
14
15
  import { RelationInputDataManager } from '../RelationInputDataManager';
15
16
  import Wysiwyg from '../Wysiwyg';
16
17
 
@@ -207,6 +208,7 @@ function Inputs({
207
208
  uid: InputUID,
208
209
  media: fields.media,
209
210
  wysiwyg: Wysiwyg,
211
+ blocks: Blocks,
210
212
  ...fields,
211
213
  ...customFieldInputs,
212
214
  };
@@ -2,6 +2,8 @@ import toLower from 'lodash/toLower';
2
2
 
3
3
  const getInputType = (type = '') => {
4
4
  switch (toLower(type)) {
5
+ case 'blocks':
6
+ return 'blocks';
5
7
  case 'boolean':
6
8
  return 'bool';
7
9
  case 'biginteger':
@@ -48,6 +48,10 @@ export const DisconnectButton = styled.button`
48
48
  }
49
49
  `;
50
50
 
51
+ const ComboboxWrapper = styled(Box)`
52
+ align-self: flex-start;
53
+ `;
54
+
51
55
  const RelationInput = ({
52
56
  canReorder,
53
57
  description,
@@ -192,13 +196,13 @@ const RelationInput = ({
192
196
  updatedRelationsWith.current === 'onChange' &&
193
197
  relations.length !== previewRelationsLength
194
198
  ) {
195
- listRef.current.scrollToItem(relations.length, 'end');
199
+ listRef.current?.scrollToItem(relations.length, 'end');
196
200
  updatedRelationsWith.current = undefined;
197
201
  } else if (
198
202
  updatedRelationsWith.current === 'loadMore' &&
199
203
  relations.length !== previewRelationsLength
200
204
  ) {
201
- listRef.current.scrollToItem(0, 'start');
205
+ listRef.current?.scrollToItem(0, 'start');
202
206
  updatedRelationsWith.current = undefined;
203
207
  }
204
208
  }, [previewRelationsLength, relations]);
@@ -206,58 +210,70 @@ const RelationInput = ({
206
210
  const ariaDescriptionId = `${name}-item-instructions`;
207
211
 
208
212
  return (
209
- <Flex gap={3} justifyContent="space-between" alignItems="end" wrap="wrap">
210
- <Flex direction="column" alignItems="stretch" basis={size <= 6 ? '100%' : '70%'} gap={2}>
211
- <Combobox
212
- ref={fieldRef}
213
- autocomplete="list"
214
- error={error}
215
- name={name}
216
- hint={description}
217
- id={id}
218
- required={required}
219
- label={label}
220
- labelAction={labelAction}
221
- disabled={disabled}
222
- placeholder={placeholder}
223
- hasMoreItems={searchResults.hasNextPage}
224
- loading={searchResults.isLoading}
225
- onOpenChange={handleMenuOpen}
226
- noOptionsMessage={() => noRelationsMessage}
227
- loadingMessage={loadingMessage}
228
- onLoadMore={() => {
229
- onSearchNextPage();
230
- }}
231
- textValue={textValue}
232
- onChange={(relationId) => {
233
- if (!relationId) {
234
- return;
235
- }
236
- onRelationConnect(options.find((opt) => opt.id === relationId));
237
- updatedRelationsWith.current = 'onChange';
238
- }}
239
- onTextValueChange={(text) => {
240
- setTextValue(text);
241
- }}
242
- onInputChange={(event) => {
243
- onSearch(event.currentTarget.value);
244
- }}
245
- >
246
- {options.map((opt) => {
247
- return <Option key={opt.id} {...opt} />;
248
- })}
249
- </Combobox>
213
+ <Flex
214
+ direction="column"
215
+ gap={3}
216
+ justifyContent="space-between"
217
+ alignItems="stretch"
218
+ wrap="wrap"
219
+ >
220
+ <Flex direction="row" alignItems="end" justifyContent="end" gap={2} width="100%">
221
+ <ComboboxWrapper marginRight="auto" maxWidth={size <= 6 ? '100%' : '70%'} width="100%">
222
+ <Combobox
223
+ ref={fieldRef}
224
+ autocomplete="list"
225
+ error={error}
226
+ name={name}
227
+ hint={description}
228
+ id={id}
229
+ required={required}
230
+ label={label}
231
+ labelAction={labelAction}
232
+ disabled={disabled}
233
+ placeholder={placeholder}
234
+ hasMoreItems={searchResults.hasNextPage}
235
+ loading={searchResults.isLoading}
236
+ onOpenChange={handleMenuOpen}
237
+ noOptionsMessage={() => noRelationsMessage}
238
+ loadingMessage={loadingMessage}
239
+ onLoadMore={() => {
240
+ onSearchNextPage();
241
+ }}
242
+ textValue={textValue}
243
+ onChange={(relationId) => {
244
+ if (!relationId) {
245
+ return;
246
+ }
247
+ onRelationConnect(options.find((opt) => opt.id === relationId));
248
+ updatedRelationsWith.current = 'onChange';
249
+ }}
250
+ onTextValueChange={(text) => {
251
+ setTextValue(text);
252
+ }}
253
+ onInputChange={(event) => {
254
+ onSearch(event.currentTarget.value);
255
+ }}
256
+ >
257
+ {options.map((opt) => {
258
+ return <Option key={opt.id} {...opt} />;
259
+ })}
260
+ </Combobox>
261
+ </ComboboxWrapper>
262
+
250
263
  {shouldDisplayLoadMoreButton && (
251
264
  <TextButton
252
265
  disabled={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
253
266
  onClick={handleLoadMore}
254
267
  loading={paginatedRelations.isLoading || paginatedRelations.isFetchingNextPage}
255
268
  startIcon={<Refresh />}
269
+ // prevent the label from line-wrapping
270
+ shrink={0}
256
271
  >
257
272
  {labelLoadMore}
258
273
  </TextButton>
259
274
  )}
260
275
  </Flex>
276
+
261
277
  {relations.length > 0 && (
262
278
  <RelationList overflow={overflow}>
263
279
  <VisuallyHidden id={ariaDescriptionId}>{listAriaDescription}</VisuallyHidden>
@@ -62,8 +62,8 @@ export const EditorLayout = ({ children, isExpandMode, error, previewContent, on
62
62
  hasRadius
63
63
  shadow="popupShadow"
64
64
  overflow="hidden"
65
- width="70%"
66
- height="70%"
65
+ width="90%"
66
+ height="90%"
67
67
  onClick={(e) => e.stopPropagation()}
68
68
  >
69
69
  <Flex height="100%" alignItems="flex-start">
@@ -9,100 +9,84 @@ export const useRelation = (cacheKey, { relation, search }) => {
9
9
  const [searchParams, setSearchParams] = useState({});
10
10
  const [currentPage, setCurrentPage] = useState(0);
11
11
  const { get } = useFetchClient();
12
- /**
13
- * This runs in `useInfiniteQuery` to actually fetch the data
14
- */
15
- const fetchRelations = async ({ pageParam = 1 }) => {
16
- try {
17
- const { data } = await get(relation?.endpoint, {
18
- params: {
19
- ...(relation.pageParams ?? {}),
20
- page: pageParam,
21
- },
22
- });
23
-
24
- setCurrentPage(pageParam);
25
-
26
- return data;
27
- } catch (err) {
28
- return null;
29
- }
30
- };
31
-
32
- const fetchSearch = async ({ pageParam = 1 }) => {
33
- try {
34
- const { data } = await get(search.endpoint, {
35
- params: {
36
- ...(search.pageParams ?? {}),
37
- ...searchParams,
38
- page: pageParam,
39
- },
40
- });
41
-
42
- return data;
43
- } catch (err) {
44
- return null;
45
- }
46
- };
47
12
 
48
13
  const { onLoad: onLoadRelations, normalizeArguments = {} } = relation;
49
14
 
50
- const relationsRes = useInfiniteQuery(['relation', ...cacheKey], fetchRelations, {
51
- cacheTime: 0,
52
- enabled: relation.enabled,
53
- /**
54
- * @type {(lastPage:
55
- * | { data: null }
56
- * | { results: any[],
57
- * pagination: {
58
- * page: number,
59
- * pageCount: number,
60
- * pageSize: number,
61
- * total: number
62
- * }
63
- * }
64
- * ) => number}
65
- */
66
- getNextPageParam(lastPage) {
67
- const isXToOneRelation = !lastPage?.pagination;
68
-
69
- if (
70
- !lastPage || // the API may send an empty 204 response
71
- isXToOneRelation || // xToOne relations do not have a pagination
72
- lastPage?.pagination.page >= lastPage?.pagination.pageCount
73
- ) {
74
- return undefined;
15
+ const relationsRes = useInfiniteQuery(
16
+ ['relation', ...cacheKey],
17
+ async ({ pageParam = 1 }) => {
18
+ try {
19
+ const { data } = await get(relation?.endpoint, {
20
+ params: {
21
+ ...(relation.pageParams ?? {}),
22
+ page: pageParam,
23
+ },
24
+ });
25
+
26
+ setCurrentPage(pageParam);
27
+
28
+ return data;
29
+ } catch (err) {
30
+ return null;
75
31
  }
76
-
77
- // eslint-disable-next-line consistent-return
78
- return lastPage.pagination.page + 1;
79
32
  },
80
- select: (data) => ({
81
- ...data,
82
- pages: data.pages.map((page) => {
83
- if (!page) {
84
- return page;
85
- }
86
-
87
- const { data, results, pagination } = page;
88
- const isXToOneRelation = !!data;
89
- let normalizedResults = [];
33
+ {
34
+ cacheTime: 0,
35
+ enabled: relation.enabled,
36
+ /**
37
+ * @type {(lastPage:
38
+ * | { data: null }
39
+ * | { results: any[],
40
+ * pagination: {
41
+ * page: number,
42
+ * pageCount: number,
43
+ * pageSize: number,
44
+ * total: number
45
+ * }
46
+ * }
47
+ * ) => number}
48
+ */
49
+ getNextPageParam(lastPage) {
50
+ const isXToOneRelation = !lastPage?.pagination;
90
51
 
91
- // xToOne relations return an object, which we normalize so that relations
92
- // always have the same shape
93
- if (isXToOneRelation) {
94
- normalizedResults = [data];
95
- } else if (results) {
96
- normalizedResults = [...results].reverse();
52
+ if (
53
+ !lastPage || // the API may send an empty 204 response
54
+ isXToOneRelation || // xToOne relations do not have a pagination
55
+ lastPage?.pagination.page >= lastPage?.pagination.pageCount
56
+ ) {
57
+ return undefined;
97
58
  }
98
59
 
99
- return {
100
- pagination,
101
- results: normalizedResults,
102
- };
60
+ // eslint-disable-next-line consistent-return
61
+ return lastPage.pagination.page + 1;
62
+ },
63
+ select: (data) => ({
64
+ ...data,
65
+ pages: data.pages.map((page) => {
66
+ if (!page) {
67
+ return page;
68
+ }
69
+
70
+ const { data, results, pagination } = page;
71
+ const isXToOneRelation = !!data;
72
+ let normalizedResults = [];
73
+
74
+ // xToOne relations return an object, which we normalize so that relations
75
+ // always have the same shape
76
+ if (isXToOneRelation) {
77
+ normalizedResults = [data];
78
+ } else if (results) {
79
+ normalizedResults = [...results].reverse();
80
+ }
81
+
82
+ return {
83
+ pagination,
84
+ results: normalizedResults,
85
+ };
86
+ }),
103
87
  }),
104
- }),
105
- });
88
+ }
89
+ );
106
90
 
107
91
  const { pageGoal } = relation;
108
92
 
@@ -137,7 +121,21 @@ export const useRelation = (cacheKey, { relation, search }) => {
137
121
 
138
122
  const searchRes = useInfiniteQuery(
139
123
  ['relation', ...cacheKey, 'search', JSON.stringify(searchParams)],
140
- fetchSearch,
124
+ async ({ pageParam = 1 }) => {
125
+ try {
126
+ const { data } = await get(search.endpoint, {
127
+ params: {
128
+ ...(search.pageParams ?? {}),
129
+ ...searchParams,
130
+ page: pageParam,
131
+ },
132
+ });
133
+
134
+ return data;
135
+ } catch (err) {
136
+ return null;
137
+ }
138
+ },
141
139
  {
142
140
  enabled: Object.keys(searchParams).length > 0,
143
141
  /**