@strapi/admin 4.14.0-beta.0 → 4.14.1
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/admin/src/components/RBACProvider/index.js +1 -1
- package/admin/src/content-manager/components/BlocksEditor/BlocksInput/index.js +87 -0
- package/admin/src/content-manager/components/BlocksEditor/Toolbar/index.js +463 -0
- package/admin/src/content-manager/components/BlocksEditor/hooks/useBlocksStore.js +451 -0
- package/admin/src/content-manager/components/BlocksEditor/hooks/useModifiersStore.js +102 -0
- package/admin/src/content-manager/components/BlocksEditor/index.js +126 -0
- package/admin/src/content-manager/components/InputUID/index.js +92 -109
- package/admin/src/content-manager/components/Inputs/index.js +3 -1
- package/admin/src/content-manager/components/Inputs/utils/getInputType.js +2 -0
- package/admin/src/content-manager/components/RelationInput/RelationInput.js +59 -43
- package/admin/src/content-manager/hooks/useRelation/useRelation.js +84 -86
- package/admin/src/content-manager/pages/ListView/index.js +1 -0
- package/admin/src/content-manager/utils/schema.js +22 -0
- package/admin/src/index.js +7 -1
- package/admin/src/pages/MarketplacePage/components/EmptyNpmPackageSearch/index.js +1 -1
- package/admin/src/pages/MarketplacePage/components/NpmPackagesFilters/index.js +0 -1
- package/admin/src/pages/MarketplacePage/components/NpmPackagesGrid/index.js +1 -1
- package/admin/src/translations/en.json +17 -0
- package/build/{1049.bf5230e6.chunk.js → 1049.acb0e730.chunk.js} +1 -1
- package/build/{1227.4f48119b.chunk.js → 1227.969e24e6.chunk.js} +1 -1
- package/build/{1386.8c070d59.chunk.js → 1386.db9a2795.chunk.js} +1 -1
- package/build/{2225.73c9a224.chunk.js → 2225.78fb9b89.chunk.js} +2 -2
- package/build/{2379.27c6ab54.chunk.js → 2379.906334f0.chunk.js} +1 -1
- package/build/{2395.120256d6.chunk.js → 2395.f6ac2863.chunk.js} +1 -1
- package/build/2614.3e088d3e.chunk.js +35 -0
- package/build/{2801.fdd4d8a2.chunk.js → 2801.2afb4757.chunk.js} +1 -1
- package/build/{4174.924ebd4c.chunk.js → 4174.3e13fb26.chunk.js} +1 -1
- package/build/4546.1203ac95.chunk.js +1 -0
- package/build/{502.d26ea06b.chunk.js → 502.9918bff7.chunk.js} +1 -1
- package/build/{6266.c652bdb1.chunk.js → 6266.e8990811.chunk.js} +5 -5
- package/build/6812.00ef5b0d.chunk.js +26 -0
- package/build/{7464.d3d1414f.chunk.js → 7464.0280cf59.chunk.js} +1 -1
- package/build/{7897.cf22d5fe.chunk.js → 7897.4a39de37.chunk.js} +1 -1
- package/build/{8276.b55f4600.chunk.js → 8276.951e198e.chunk.js} +2 -2
- package/build/9832.65ed5a44.chunk.js +181 -0
- package/build/{Admin-authenticatedApp.a687d9c6.chunk.js → Admin-authenticatedApp.c0c1c027.chunk.js} +2 -2
- package/build/{Admin_InternalErrorPage.18ba7fd6.chunk.js → Admin_InternalErrorPage.b3163562.chunk.js} +1 -1
- package/build/{Admin_homePage.86c8d4c9.chunk.js → Admin_homePage.6cb51f18.chunk.js} +1 -1
- package/build/{Admin_marketplace.e75c9cf4.chunk.js → Admin_marketplace.3eb5e132.chunk.js} +4 -4
- package/build/{Admin_pluginsPage.d1afbf28.chunk.js → Admin_pluginsPage.b9fa2947.chunk.js} +1 -1
- package/build/{Admin_profilePage.079903a3.chunk.js → Admin_profilePage.a4d41380.chunk.js} +2 -2
- package/build/{Admin_settingsPage.60a3e46e.chunk.js → Admin_settingsPage.6dc2af9f.chunk.js} +1 -1
- package/build/{Upload_ConfigureTheView.949535b8.chunk.js → Upload_ConfigureTheView.cc7ca628.chunk.js} +1 -1
- package/build/{admin-app.4654dc77.chunk.js → admin-app.98cdf43a.chunk.js} +5 -5
- package/build/{admin-edit-roles-page.6597d934.chunk.js → admin-edit-roles-page.418bb1c5.chunk.js} +3 -3
- package/build/{admin-edit-users.3014605e.chunk.js → admin-edit-users.9b42cc9e.chunk.js} +2 -2
- package/build/{admin-roles-list.ab6fcfb7.chunk.js → admin-roles-list.cf964578.chunk.js} +1 -1
- package/build/{admin-users.81bf5f4d.chunk.js → admin-users.8385dd73.chunk.js} +2 -2
- package/build/{api-tokens-create-page.f5a85725.chunk.js → api-tokens-create-page.2f25ddf6.chunk.js} +1 -1
- package/build/{api-tokens-edit-page.06a32cef.chunk.js → api-tokens-edit-page.45faac16.chunk.js} +1 -1
- package/build/{api-tokens-list-page.bb27d544.chunk.js → api-tokens-list-page.5baabf1a.chunk.js} +2 -2
- package/build/{audit-logs-settings-page.4eb6cdf8.chunk.js → audit-logs-settings-page.91489670.chunk.js} +1 -1
- package/build/content-manager.0d2b4a60.chunk.js +1199 -0
- package/build/{content-type-builder-list-view.b75fb938.chunk.js → content-type-builder-list-view.aa8a5d1a.chunk.js} +2 -2
- package/build/content-type-builder-translation-en-json.b9e5cacd.chunk.js +1 -0
- package/build/content-type-builder.885f2cad.chunk.js +146 -0
- package/build/{email-settings-page.07d417d5.chunk.js → email-settings-page.6bd7b280.chunk.js} +1 -1
- package/build/{en-json.e12fd5fc.chunk.js → en-json.a3973ff5.chunk.js} +1 -1
- package/build/{i18n-settings-page.7107e28a.chunk.js → i18n-settings-page.6c0157e7.chunk.js} +1 -1
- package/build/index.html +1 -1
- package/build/main.105dcf23.js +2665 -0
- package/build/{review-workflows-settings-create-view.5d8806b2.chunk.js → review-workflows-settings-create-view.ae369a88.chunk.js} +1 -1
- package/build/{review-workflows-settings-edit-view.634903ed.chunk.js → review-workflows-settings-edit-view.9a61c69f.chunk.js} +1 -1
- package/build/{review-workflows-settings-list-view.d138c3b5.chunk.js → review-workflows-settings-list-view.067e0c35.chunk.js} +2 -2
- package/build/{runtime~main.9589b498.js → runtime~main.6c489074.js} +2 -2
- package/build/{sso-settings-page.caa35f7b.chunk.js → sso-settings-page.a29e6c38.chunk.js} +1 -1
- package/build/{transfer-tokens-create-page.6e7049ff.chunk.js → transfer-tokens-create-page.6e1b8cee.chunk.js} +1 -1
- package/build/{transfer-tokens-edit-page.449b4502.chunk.js → transfer-tokens-edit-page.10bb22e2.chunk.js} +1 -1
- package/build/{transfer-tokens-list-page.34caf827.chunk.js → transfer-tokens-list-page.0306652c.chunk.js} +2 -2
- package/build/{upload-settings.fede24b9.chunk.js → upload-settings.0af6edc5.chunk.js} +1 -1
- package/build/{upload.a96e2452.chunk.js → upload.19e14c8e.chunk.js} +1 -1
- package/build/{users-advanced-settings-page.ac7968e7.chunk.js → users-advanced-settings-page.ed69812f.chunk.js} +1 -1
- package/build/{users-email-settings-page.125a89e2.chunk.js → users-email-settings-page.131a00fb.chunk.js} +1 -1
- package/build/{users-providers-settings-page.ce34951c.chunk.js → users-providers-settings-page.b3dca41d.chunk.js} +1 -1
- package/build/{users-roles-settings-page.d415835a.chunk.js → users-roles-settings-page.afab5a0d.chunk.js} +4 -4
- package/build/{webhook-edit-page.7498417e.chunk.js → webhook-edit-page.4c037da4.chunk.js} +2 -2
- package/build/{webhook-list-page.1b085c7f.chunk.js → webhook-list-page.56c82f4a.chunk.js} +1 -1
- package/ee/admin/pages/SettingsPage/pages/AuditLogs/ListView/utils/getDisplayedFilters.js +1 -1
- package/ee/server/bootstrap.js +1 -1
- package/ee/server/content-types/workflow-stage/index.js +1 -1
- package/ee/server/controllers/admin.js +1 -1
- package/ee/server/controllers/user.js +1 -1
- package/ee/server/destroy.js +1 -1
- package/ee/server/register.js +1 -1
- package/ee/server/routes/utils.js +1 -1
- package/ee/server/services/audit-logs.js +1 -1
- package/ee/server/services/passport/sso.js +1 -1
- package/ee/server/services/passport.js +1 -1
- package/ee/server/services/seat-enforcement.js +1 -1
- package/ee/server/utils/sso-lock.js +1 -1
- package/ee/server/validation/role.js +1 -1
- package/ee/server/validation/user.js +1 -1
- package/package.json +20 -12
- package/server/controllers/admin.js +1 -1
- package/server/domain/permission/index.js +8 -1
- package/server/validation/permission.js +1 -1
- package/utils/plugins.js +7 -1
- package/build/4546.9710f321.chunk.js +0 -1
- package/build/6272.4017459a.chunk.js +0 -160
- package/build/6812.31979984.chunk.js +0 -26
- package/build/content-manager.9187db78.chunk.js +0 -1097
- package/build/content-type-builder-translation-en-json.ed29ff4d.chunk.js +0 -1
- package/build/content-type-builder.5ff93edd.chunk.js +0 -170
- package/build/main.da000219.js +0 -2860
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
38
|
-
const [
|
|
39
|
-
|
|
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
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setIsLoading(false);
|
|
75
|
+
return data;
|
|
76
|
+
},
|
|
77
|
+
onError(err) {
|
|
83
78
|
toggleNotification({
|
|
84
79
|
type: 'warning',
|
|
85
|
-
message: formatAPIError(
|
|
80
|
+
message: formatAPIError(err),
|
|
86
81
|
});
|
|
87
|
-
}
|
|
88
|
-
|
|
82
|
+
},
|
|
83
|
+
enabled: !value && required,
|
|
84
|
+
});
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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(
|
|
110
|
+
message: formatAPIError(err),
|
|
111
111
|
});
|
|
112
|
-
}
|
|
113
|
-
};
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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 (
|
|
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
|
-
}, [
|
|
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
|
|
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 && !
|
|
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
|
-
{
|
|
209
|
+
{showRegenerate && (
|
|
226
210
|
<TextValidation alignItems="center" justifyContent="flex-end" gap={1}>
|
|
227
211
|
<Typography textColor="primary600" variant="pi">
|
|
228
|
-
{
|
|
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={() =>
|
|
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={
|
|
240
|
-
onMouseLeave={
|
|
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={
|
|
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
|
|
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
|
|
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
|
};
|
|
@@ -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
|
|
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
|
|
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
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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>
|
|
@@ -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(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
/**
|