payload 1.2.0 → 1.2.2
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/CHANGELOG.md +161 -16
- package/components/fields/Array.ts +1 -2
- package/components/fields/Blocks.ts +1 -2
- package/components/fields/Cell.ts +1 -1
- package/components/fields/Checkbox.ts +1 -2
- package/components/fields/Code.ts +1 -2
- package/components/fields/DateTime.ts +1 -2
- package/components/fields/Email.ts +1 -2
- package/components/fields/Group.ts +1 -2
- package/components/fields/Number.ts +1 -2
- package/components/fields/Password.ts +1 -2
- package/components/fields/RadioGroup/RadioInput.ts +1 -2
- package/components/fields/RadioGroup/index.ts +1 -2
- package/components/fields/Relationship.ts +1 -2
- package/components/fields/RichText.ts +1 -2
- package/components/fields/Row.ts +1 -2
- package/components/fields/Select.ts +1 -2
- package/components/fields/Text.ts +1 -2
- package/components/fields/Textarea.ts +1 -2
- package/components/fields/Upload.ts +1 -2
- package/components/views/Cell.ts +1 -2
- package/components/views/Edit.ts +1 -2
- package/components/views/List.ts +1 -2
- package/dist/admin/components/elements/ReactSelect/index.js +4 -4
- package/dist/admin/components/elements/ReactSelect/types.d.ts +11 -10
- package/dist/admin/components/elements/Status/index.js +1 -1
- package/dist/admin/components/elements/VersionsCount/index.js +1 -1
- package/dist/admin/components/elements/WhereBuilder/Condition/Text/index.js +5 -1
- package/dist/admin/components/forms/Form/context.d.ts +15 -0
- package/dist/admin/components/forms/Form/context.js +16 -1
- package/dist/admin/components/forms/field-types/Blocks/SectionTitle/index.scss +3 -2
- package/dist/admin/components/forms/field-types/Blocks/index.js +1 -1
- package/dist/admin/components/forms/field-types/Code/Code.js +3 -4
- package/dist/admin/components/forms/field-types/Code/index.scss +177 -78
- package/dist/admin/components/forms/field-types/Relationship/AddNew/index.js +0 -1
- package/dist/admin/components/forms/field-types/Relationship/GetFilterOptions/index.d.ts +11 -0
- package/dist/admin/components/forms/field-types/Relationship/GetFilterOptions/index.js +37 -0
- package/dist/admin/components/forms/field-types/Relationship/findOptionsByValue.d.ts +8 -0
- package/dist/admin/components/forms/field-types/Relationship/findOptionsByValue.js +29 -0
- package/dist/admin/components/forms/field-types/Relationship/index.js +89 -139
- package/dist/admin/components/forms/field-types/Relationship/optionsReducer.d.ts +2 -2
- package/dist/admin/components/forms/field-types/Relationship/optionsReducer.js +2 -29
- package/dist/admin/components/forms/field-types/Relationship/types.d.ts +14 -2
- package/dist/admin/components/forms/field-types/RichText/elements/link/Element.js +3 -3
- package/dist/admin/components/forms/field-types/RichText/elements/link/index.scss +2 -1
- package/dist/admin/components/forms/field-types/RichText/elements/upload/Button/index.js +1 -1
- package/dist/admin/components/forms/field-types/Row/index.js +1 -1
- package/dist/admin/components/forms/field-types/Select/Input.d.ts +2 -2
- package/dist/admin/components/forms/field-types/Select/Input.js +13 -3
- package/dist/admin/components/forms/field-types/Textarea/Input.js +1 -1
- package/dist/admin/components/forms/field-types/Textarea/index.scss +3 -6
- package/dist/admin/components/forms/useField/index.d.ts +5 -0
- package/dist/admin/components/forms/useField/index.js +5 -0
- package/dist/admin/components/utilities/I18n/index.js +1 -1
- package/dist/admin/components/views/Account/index.js +6 -1
- package/dist/admin/components/views/Global/index.scss +31 -24
- package/dist/admin/components/views/Login/index.js +1 -1
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Iterable/index.js +1 -1
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Nested/index.js +15 -9
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Relationship/index.js +5 -2
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Select/index.d.ts +5 -0
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Select/index.js +34 -0
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Select/index.scss +10 -0
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/Text/index.js +3 -2
- package/dist/admin/components/views/Version/RenderFieldsToDiff/fields/index.js +3 -2
- package/dist/admin/components/views/Versions/index.js +1 -1
- package/dist/admin/components/views/collections/Edit/Default.js +2 -0
- package/dist/admin/components/views/collections/Edit/SetStepNav.d.ts +7 -0
- package/dist/admin/components/views/collections/Edit/SetStepNav.js +31 -0
- package/dist/admin/components/views/collections/Edit/index.js +2 -36
- package/dist/admin/components/views/collections/Edit/index.scss +19 -4
- package/dist/admin/hooks/usePayloadAPI.js +3 -1
- package/dist/collections/buildSchema.js +5 -1
- package/dist/collections/config/types.d.ts +16 -3
- package/dist/collections/operations/find.js +11 -6
- package/dist/collections/operations/findVersions.js +4 -5
- package/dist/collections/operations/update.js +8 -9
- package/dist/collections/requestHandlers/find.js +4 -3
- package/dist/collections/requestHandlers/update.js +3 -3
- package/dist/config/schema.js +2 -2
- package/dist/config/types.d.ts +269 -6
- package/dist/config/types.js +1 -1
- package/dist/email/build.js +2 -2
- package/dist/express/types.d.ts +26 -2
- package/dist/fields/config/sanitize.spec.js +5 -5
- package/dist/fields/config/types.d.ts +3 -3
- package/dist/fields/hooks/afterRead/relationshipPopulationPromise.js +73 -21
- package/dist/fields/hooks/beforeChange/promise.js +7 -1
- package/dist/globals/operations/findVersions.js +4 -5
- package/dist/globals/operations/update.js +4 -4
- package/dist/globals/requestHandlers/update.js +3 -3
- package/dist/graphql/schema/fieldToWhereInputSchemaMap.js +7 -7
- package/dist/graphql/schema/operators.d.ts +1 -0
- package/dist/graphql/schema/operators.js +2 -1
- package/dist/mongoose/types.d.ts +3 -3
- package/dist/translations/de.json +100 -100
- package/dist/translations/en.json +3 -1
- package/dist/translations/es.json +1 -1
- package/dist/translations/fr.json +1 -1
- package/dist/translations/index.d.ts +5 -0
- package/dist/translations/it.json +19 -16
- package/dist/translations/my.json +1 -1
- package/dist/translations/nl.json +1 -1
- package/dist/translations/pl.json +1 -1
- package/dist/translations/pt.json +1 -1
- package/dist/translations/ru.json +1 -1
- package/dist/translations/sv.json +1 -1
- package/dist/translations/vi.json +1 -1
- package/dist/utilities/isNumber.d.ts +1 -0
- package/dist/utilities/isNumber.js +8 -0
- package/dist/versions/drafts/saveCollectionDraft.js +4 -3
- package/dist/versions/drafts/saveGlobalDraft.js +4 -3
- package/dist/versions/ensurePublishedCollectionVersion.js +2 -1
- package/dist/versions/ensurePublishedGlobalVersion.js +2 -1
- package/dist/versions/getLatestCollectionVersion.d.ts +10 -0
- package/dist/versions/getLatestCollectionVersion.js +27 -0
- package/dist/versions/getLatestGlobalVersion.d.ts +10 -0
- package/dist/versions/getLatestGlobalVersion.js +27 -0
- package/docs/admin/components.mdx +1 -5
- package/docs/authentication/config.mdx +1 -1
- package/docs/configuration/globals.mdx +12 -12
- package/docs/configuration/i18n.mdx +1 -1
- package/docs/fields/array.mdx +14 -14
- package/docs/fields/blocks.mdx +1 -1
- package/docs/fields/checkbox.mdx +2 -2
- package/docs/fields/code.mdx +1 -1
- package/docs/fields/date.mdx +1 -1
- package/docs/fields/email.mdx +1 -1
- package/docs/fields/number.mdx +1 -1
- package/docs/fields/radio.mdx +1 -1
- package/docs/fields/relationship.mdx +1 -1
- package/docs/fields/rich-text.mdx +1 -1
- package/docs/fields/select.mdx +2 -2
- package/docs/fields/text.mdx +2 -2
- package/docs/fields/textarea.mdx +1 -1
- package/docs/fields/upload.mdx +51 -17
- package/docs/production/preventing-abuse.mdx +6 -6
- package/package.json +3 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState, useReducer, useRef, } from 'react';
|
|
2
|
-
import equal from 'deep-equal';
|
|
3
2
|
import qs from 'qs';
|
|
4
3
|
import { useTranslation } from 'react-i18next';
|
|
5
4
|
import { useConfig } from '../../../utilities/Config';
|
|
@@ -11,16 +10,14 @@ import Label from '../../Label';
|
|
|
11
10
|
import Error from '../../Error';
|
|
12
11
|
import FieldDescription from '../../FieldDescription';
|
|
13
12
|
import { relationship } from '../../../../../fields/validations';
|
|
14
|
-
import { useFormProcessing
|
|
13
|
+
import { useFormProcessing } from '../../Form/context';
|
|
15
14
|
import optionsReducer from './optionsReducer';
|
|
16
15
|
import { createRelationMap } from './createRelationMap';
|
|
17
16
|
import { useDebouncedCallback } from '../../../../hooks/useDebouncedCallback';
|
|
18
|
-
import { useDocumentInfo } from '../../../utilities/DocumentInfo';
|
|
19
|
-
import { getFilterOptionsQuery } from '../getFilterOptionsQuery';
|
|
20
17
|
import wordBoundariesRegex from '../../../../../utilities/wordBoundariesRegex';
|
|
21
|
-
import reduceFieldsToValues from '../../Form/reduceFieldsToValues';
|
|
22
|
-
import getSiblingData from '../../Form/getSiblingData';
|
|
23
18
|
import { AddNewRelation } from './AddNew';
|
|
19
|
+
import { findOptionsByValue } from './findOptionsByValue';
|
|
20
|
+
import { GetFilterOptions } from './GetFilterOptions';
|
|
24
21
|
import './index.scss';
|
|
25
22
|
const maxResultsPerRequest = 10;
|
|
26
23
|
const baseClass = 'relationship';
|
|
@@ -28,21 +25,19 @@ const Relationship = (props) => {
|
|
|
28
25
|
const { relationTo, validate = relationship, path, name, required, label, hasMany, filterOptions, admin: { readOnly, style, className, width, description, condition, isSortable = true, } = {}, } = props;
|
|
29
26
|
const { serverURL, routes: { api, }, collections, } = useConfig();
|
|
30
27
|
const { t, i18n } = useTranslation('fields');
|
|
31
|
-
const {
|
|
32
|
-
const { user, permissions } = useAuth();
|
|
33
|
-
const [fields] = useAllFormFields();
|
|
28
|
+
const { permissions } = useAuth();
|
|
34
29
|
const formProcessing = useFormProcessing();
|
|
35
30
|
const hasMultipleRelations = Array.isArray(relationTo);
|
|
36
|
-
const [options, dispatchOptions] = useReducer(optionsReducer,
|
|
31
|
+
const [options, dispatchOptions] = useReducer(optionsReducer, []);
|
|
37
32
|
const [lastFullyLoadedRelation, setLastFullyLoadedRelation] = useState(-1);
|
|
38
33
|
const [lastLoadedPage, setLastLoadedPage] = useState(1);
|
|
39
34
|
const [errorLoading, setErrorLoading] = useState('');
|
|
40
|
-
const [
|
|
41
|
-
const [hasLoadedValueOptions, setHasLoadedValueOptions] = useState(false);
|
|
35
|
+
const [filterOptionsResult, setFilterOptionsResult] = useState();
|
|
42
36
|
const [search, setSearch] = useState('');
|
|
37
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
38
|
+
const [hasLoadedFirstPage, setHasLoadedFirstPage] = useState(false);
|
|
43
39
|
const [enableWordBoundarySearch, setEnableWordBoundarySearch] = useState(false);
|
|
44
40
|
const firstRun = useRef(true);
|
|
45
|
-
const fieldsRef = useRef(fields);
|
|
46
41
|
const memoizedValidate = useCallback((value, validationOptions) => {
|
|
47
42
|
return validate(value, { ...validationOptions, required });
|
|
48
43
|
}, [validate, required]);
|
|
@@ -51,13 +46,7 @@ const Relationship = (props) => {
|
|
|
51
46
|
validate: memoizedValidate,
|
|
52
47
|
condition,
|
|
53
48
|
});
|
|
54
|
-
const
|
|
55
|
-
return [
|
|
56
|
-
reduceFieldsToValues(fieldsRef.current, true),
|
|
57
|
-
getSiblingData(fieldsRef.current, path),
|
|
58
|
-
];
|
|
59
|
-
}, [fieldsRef, path]);
|
|
60
|
-
const getResults = useCallback(async ({ lastFullyLoadedRelation: lastFullyLoadedRelationArg, lastLoadedPage: lastLoadedPageArg, search: searchArg, value: valueArg, sort, }) => {
|
|
49
|
+
const getResults = useCallback(async ({ lastFullyLoadedRelation: lastFullyLoadedRelationArg, lastLoadedPage: lastLoadedPageArg, search: searchArg, value: valueArg, sort, onSuccess, }) => {
|
|
61
50
|
if (!permissions) {
|
|
62
51
|
return;
|
|
63
52
|
}
|
|
@@ -96,8 +85,8 @@ const Relationship = (props) => {
|
|
|
96
85
|
},
|
|
97
86
|
});
|
|
98
87
|
}
|
|
99
|
-
if (
|
|
100
|
-
query.where.and.push(
|
|
88
|
+
if (filterOptionsResult === null || filterOptionsResult === void 0 ? void 0 : filterOptionsResult[relation]) {
|
|
89
|
+
query.where.and.push(filterOptionsResult[relation]);
|
|
101
90
|
}
|
|
102
91
|
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
|
|
103
92
|
credentials: 'include',
|
|
@@ -109,7 +98,7 @@ const Relationship = (props) => {
|
|
|
109
98
|
const data = await response.json();
|
|
110
99
|
if (data.docs.length > 0) {
|
|
111
100
|
resultsFetched += data.docs.length;
|
|
112
|
-
dispatchOptions({ type: 'ADD', docs: data.docs,
|
|
101
|
+
dispatchOptions({ type: 'ADD', docs: data.docs, collection, sort, i18n });
|
|
113
102
|
setLastLoadedPage(data.page);
|
|
114
103
|
if (!data.nextPage) {
|
|
115
104
|
setLastFullyLoadedRelation(relations.indexOf(relation));
|
|
@@ -124,13 +113,15 @@ const Relationship = (props) => {
|
|
|
124
113
|
else if (response.status === 403) {
|
|
125
114
|
setLastFullyLoadedRelation(relations.indexOf(relation));
|
|
126
115
|
lastLoadedPageToUse = 1;
|
|
127
|
-
dispatchOptions({ type: 'ADD', docs: [],
|
|
116
|
+
dispatchOptions({ type: 'ADD', docs: [], collection, sort, ids: relationMap[relation], i18n });
|
|
128
117
|
}
|
|
129
118
|
else {
|
|
130
119
|
setErrorLoading(t('error:unspecific'));
|
|
131
120
|
}
|
|
132
121
|
}
|
|
133
122
|
}, Promise.resolve());
|
|
123
|
+
if (typeof onSuccess === 'function')
|
|
124
|
+
onSuccess();
|
|
134
125
|
}
|
|
135
126
|
}, [
|
|
136
127
|
permissions,
|
|
@@ -138,58 +129,12 @@ const Relationship = (props) => {
|
|
|
138
129
|
hasMany,
|
|
139
130
|
errorLoading,
|
|
140
131
|
collections,
|
|
141
|
-
|
|
132
|
+
filterOptionsResult,
|
|
142
133
|
serverURL,
|
|
143
134
|
api,
|
|
144
|
-
hasMultipleRelations,
|
|
145
135
|
t,
|
|
146
136
|
i18n,
|
|
147
137
|
]);
|
|
148
|
-
const findOptionsByValue = useCallback(() => {
|
|
149
|
-
if (value) {
|
|
150
|
-
if (hasMany) {
|
|
151
|
-
if (Array.isArray(value)) {
|
|
152
|
-
return value.map((val) => {
|
|
153
|
-
if (hasMultipleRelations) {
|
|
154
|
-
let matchedOption;
|
|
155
|
-
options.forEach((opt) => {
|
|
156
|
-
if (opt.options) {
|
|
157
|
-
opt.options.some((subOpt) => {
|
|
158
|
-
if ((subOpt === null || subOpt === void 0 ? void 0 : subOpt.value) === val.value) {
|
|
159
|
-
matchedOption = subOpt;
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
return matchedOption;
|
|
167
|
-
}
|
|
168
|
-
return options.find((opt) => opt.value === val);
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
if (hasMultipleRelations) {
|
|
174
|
-
let matchedOption;
|
|
175
|
-
const valueWithRelation = value;
|
|
176
|
-
options.forEach((opt) => {
|
|
177
|
-
if (opt === null || opt === void 0 ? void 0 : opt.options) {
|
|
178
|
-
opt.options.some((subOpt) => {
|
|
179
|
-
if ((subOpt === null || subOpt === void 0 ? void 0 : subOpt.value) === valueWithRelation.value) {
|
|
180
|
-
matchedOption = subOpt;
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
return false;
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
return matchedOption;
|
|
188
|
-
}
|
|
189
|
-
return options.find((opt) => opt.value === value);
|
|
190
|
-
}
|
|
191
|
-
return undefined;
|
|
192
|
-
}, [hasMany, hasMultipleRelations, value, options]);
|
|
193
138
|
const updateSearch = useDebouncedCallback((searchArg, valueArg) => {
|
|
194
139
|
getResults({ search: searchArg, value: valueArg, sort: true });
|
|
195
140
|
setSearch(searchArg);
|
|
@@ -199,72 +144,60 @@ const Relationship = (props) => {
|
|
|
199
144
|
updateSearch(searchArg, valueArg);
|
|
200
145
|
}
|
|
201
146
|
}, [search, updateSearch]);
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
147
|
+
// ///////////////////////////////////
|
|
148
|
+
// Ensure we have an option for each value
|
|
149
|
+
// ///////////////////////////////////
|
|
205
150
|
useEffect(() => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
151
|
+
const relationMap = createRelationMap({
|
|
152
|
+
hasMany,
|
|
153
|
+
relationTo,
|
|
154
|
+
value,
|
|
155
|
+
});
|
|
156
|
+
Object.entries(relationMap).reduce(async (priorRelation, [relation, ids]) => {
|
|
157
|
+
await priorRelation;
|
|
158
|
+
const idsToLoad = ids.filter((id) => {
|
|
159
|
+
return !options.find((optionGroup) => { var _a; return (_a = optionGroup === null || optionGroup === void 0 ? void 0 : optionGroup.options) === null || _a === void 0 ? void 0 : _a.find((option) => option.value === id && option.relationTo === relation); });
|
|
211
160
|
});
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
id: {
|
|
218
|
-
in: ids,
|
|
219
|
-
},
|
|
161
|
+
if (idsToLoad.length > 0) {
|
|
162
|
+
const query = {
|
|
163
|
+
where: {
|
|
164
|
+
id: {
|
|
165
|
+
in: idsToLoad,
|
|
220
166
|
},
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
167
|
+
},
|
|
168
|
+
depth: 0,
|
|
169
|
+
limit: idsToLoad.length,
|
|
170
|
+
};
|
|
171
|
+
if (!errorLoading) {
|
|
172
|
+
const response = await fetch(`${serverURL}${api}/${relation}?${qs.stringify(query)}`, {
|
|
173
|
+
credentials: 'include',
|
|
174
|
+
headers: {
|
|
175
|
+
'Accept-Language': i18n.language,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
const collection = collections.find((coll) => coll.slug === relation);
|
|
179
|
+
if (response.ok) {
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
dispatchOptions({ type: 'ADD', docs: data.docs, collection, sort: true, ids: idsToLoad, i18n });
|
|
182
|
+
}
|
|
183
|
+
else if (response.status === 403) {
|
|
184
|
+
dispatchOptions({ type: 'ADD', docs: [], collection, sort: true, ids: idsToLoad, i18n });
|
|
239
185
|
}
|
|
240
186
|
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
256
|
-
if (!equal(newOptionFilters, optionFilters)) {
|
|
257
|
-
setOptionFilters(newOptionFilters);
|
|
258
|
-
}
|
|
259
|
-
}, [relationTo, filterOptions, optionFilters, id, getFormData, path, user]);
|
|
260
|
-
useEffect(() => {
|
|
261
|
-
if (optionFilters || !filterOptions) {
|
|
262
|
-
setHasLoadedValueOptions(false);
|
|
263
|
-
getResults({
|
|
264
|
-
value: initialValue,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}, [initialValue, getResults, optionFilters, filterOptions]);
|
|
187
|
+
}
|
|
188
|
+
}, Promise.resolve());
|
|
189
|
+
}, [
|
|
190
|
+
options,
|
|
191
|
+
value,
|
|
192
|
+
hasMany,
|
|
193
|
+
errorLoading,
|
|
194
|
+
collections,
|
|
195
|
+
hasMultipleRelations,
|
|
196
|
+
serverURL,
|
|
197
|
+
api,
|
|
198
|
+
i18n,
|
|
199
|
+
relationTo,
|
|
200
|
+
]);
|
|
268
201
|
// Determine if we should switch to word boundary search
|
|
269
202
|
useEffect(() => {
|
|
270
203
|
const relations = Array.isArray(relationTo) ? relationTo : [relationTo];
|
|
@@ -276,7 +209,7 @@ const Relationship = (props) => {
|
|
|
276
209
|
}, true);
|
|
277
210
|
setEnableWordBoundarySearch(!isIdOnly);
|
|
278
211
|
}, [relationTo, collections]);
|
|
279
|
-
// When relationTo changes, reset
|
|
212
|
+
// When relationTo or filterOptionsResult changes, reset component
|
|
280
213
|
// Note - effect should not run on first run
|
|
281
214
|
useEffect(() => {
|
|
282
215
|
if (firstRun.current) {
|
|
@@ -284,8 +217,10 @@ const Relationship = (props) => {
|
|
|
284
217
|
return;
|
|
285
218
|
}
|
|
286
219
|
dispatchOptions({ type: 'CLEAR' });
|
|
287
|
-
|
|
288
|
-
|
|
220
|
+
setLastFullyLoadedRelation(-1);
|
|
221
|
+
setLastLoadedPage(1);
|
|
222
|
+
setHasLoadedFirstPage(false);
|
|
223
|
+
}, [relationTo, filterOptionsResult]);
|
|
289
224
|
const classes = [
|
|
290
225
|
'field-type',
|
|
291
226
|
baseClass,
|
|
@@ -294,8 +229,8 @@ const Relationship = (props) => {
|
|
|
294
229
|
errorLoading && 'error-loading',
|
|
295
230
|
readOnly && `${baseClass}--read-only`,
|
|
296
231
|
].filter(Boolean).join(' ');
|
|
297
|
-
const valueToRender =
|
|
298
|
-
if ((valueToRender === null || valueToRender === void 0 ? void 0 : valueToRender.value) === 'null')
|
|
232
|
+
const valueToRender = findOptionsByValue({ value, options });
|
|
233
|
+
if (!Array.isArray(valueToRender) && (valueToRender === null || valueToRender === void 0 ? void 0 : valueToRender.value) === 'null')
|
|
299
234
|
valueToRender.value = null;
|
|
300
235
|
return (React.createElement("div", { id: `field-${(path || name).replace(/\./gi, '__')}`, className: classes, style: {
|
|
301
236
|
...style,
|
|
@@ -303,9 +238,13 @@ const Relationship = (props) => {
|
|
|
303
238
|
} },
|
|
304
239
|
React.createElement(Error, { showError: showError, message: errorMessage }),
|
|
305
240
|
React.createElement(Label, { htmlFor: path, label: label, required: required }),
|
|
241
|
+
React.createElement(GetFilterOptions, { ...{ filterOptionsResult, setFilterOptionsResult, filterOptions, path, relationTo } }),
|
|
306
242
|
!errorLoading && (React.createElement("div", { className: `${baseClass}__wrap` },
|
|
307
243
|
React.createElement(ReactSelect, { isDisabled: readOnly, onInputChange: (newSearch) => handleInputChange(newSearch, value), onChange: !readOnly ? (selected) => {
|
|
308
|
-
if (
|
|
244
|
+
if (selected === null) {
|
|
245
|
+
setValue(hasMany ? [] : null);
|
|
246
|
+
}
|
|
247
|
+
else if (hasMany) {
|
|
309
248
|
setValue(selected ? selected.map((option) => {
|
|
310
249
|
if (hasMultipleRelations) {
|
|
311
250
|
return {
|
|
@@ -333,7 +272,18 @@ const Relationship = (props) => {
|
|
|
333
272
|
value: initialValue,
|
|
334
273
|
sort: false,
|
|
335
274
|
});
|
|
336
|
-
}, value: valueToRender, showError: showError, disabled: formProcessing, options: options, isMulti: hasMany, isSortable: isSortable,
|
|
275
|
+
}, value: valueToRender, showError: showError, disabled: formProcessing, options: options, isMulti: hasMany, isSortable: isSortable, isLoading: isLoading, onMenuOpen: () => {
|
|
276
|
+
if (!hasLoadedFirstPage) {
|
|
277
|
+
setIsLoading(true);
|
|
278
|
+
getResults({
|
|
279
|
+
value: initialValue,
|
|
280
|
+
onSuccess: () => {
|
|
281
|
+
setHasLoadedFirstPage(true);
|
|
282
|
+
setIsLoading(false);
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}, filterOption: enableWordBoundarySearch ? (item, searchFilter) => {
|
|
337
287
|
const r = wordBoundariesRegex(searchFilter || '');
|
|
338
288
|
return r.test(item.label);
|
|
339
289
|
} : undefined }),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
declare const optionsReducer: (state:
|
|
1
|
+
import { Action, OptionGroup } from './types';
|
|
2
|
+
declare const optionsReducer: (state: OptionGroup[], action: Action) => OptionGroup[];
|
|
3
3
|
export default optionsReducer;
|
|
@@ -24,37 +24,10 @@ const optionsReducer = (state, action) => {
|
|
|
24
24
|
return [];
|
|
25
25
|
}
|
|
26
26
|
case 'ADD': {
|
|
27
|
-
const {
|
|
27
|
+
const { collection, docs, sort, ids = [], i18n } = action;
|
|
28
28
|
const relation = collection.slug;
|
|
29
29
|
const labelKey = collection.admin.useAsTitle || 'id';
|
|
30
30
|
const loadedIDs = reduceToIDs(state);
|
|
31
|
-
if (!hasMultipleRelations) {
|
|
32
|
-
const options = [
|
|
33
|
-
...state,
|
|
34
|
-
...docs.reduce((docOptions, doc) => {
|
|
35
|
-
if (loadedIDs.indexOf(doc.id) === -1) {
|
|
36
|
-
loadedIDs.push(doc.id);
|
|
37
|
-
return [
|
|
38
|
-
...docOptions,
|
|
39
|
-
{
|
|
40
|
-
label: doc[labelKey] || `${i18n.t('general:untitled')} - ID: ${doc.id}`,
|
|
41
|
-
value: doc.id,
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
return docOptions;
|
|
46
|
-
}, []),
|
|
47
|
-
];
|
|
48
|
-
ids.forEach((id) => {
|
|
49
|
-
if (!loadedIDs.includes(id)) {
|
|
50
|
-
options.push({
|
|
51
|
-
label: labelKey === 'id' ? id : `${i18n.t('general:untitled')} - ID: ${id}`,
|
|
52
|
-
value: id,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
return sort ? sortOptions(options) : options;
|
|
57
|
-
}
|
|
58
31
|
const newOptions = [...state];
|
|
59
32
|
const optionsToAddTo = newOptions.find((optionGroup) => optionGroup.label === collection.labels.plural);
|
|
60
33
|
const newSubOptions = docs.reduce((docSubOptions, doc) => {
|
|
@@ -74,6 +47,7 @@ const optionsReducer = (state, action) => {
|
|
|
74
47
|
ids.forEach((id) => {
|
|
75
48
|
if (!loadedIDs.includes(id)) {
|
|
76
49
|
newSubOptions.push({
|
|
50
|
+
relationTo: relation,
|
|
77
51
|
label: labelKey === 'id' ? id : `${i18n.t('general:untitled')} - ID: ${id}`,
|
|
78
52
|
value: id,
|
|
79
53
|
});
|
|
@@ -90,7 +64,6 @@ const optionsReducer = (state, action) => {
|
|
|
90
64
|
newOptions.push({
|
|
91
65
|
label: getTranslation(collection.labels.plural, i18n),
|
|
92
66
|
options: sort ? sortOptions(newSubOptions) : newSubOptions,
|
|
93
|
-
value: undefined,
|
|
94
67
|
});
|
|
95
68
|
}
|
|
96
69
|
return newOptions;
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import i18n from 'i18next';
|
|
2
2
|
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
|
|
3
3
|
import { RelationshipField } from '../../../../../fields/config/types';
|
|
4
|
+
import { Where } from '../../../../../types';
|
|
4
5
|
export declare type Props = Omit<RelationshipField, 'type'> & {
|
|
5
6
|
path?: string;
|
|
6
7
|
};
|
|
7
8
|
export declare type Option = {
|
|
8
9
|
label: string;
|
|
9
|
-
value: string;
|
|
10
|
+
value: string | number;
|
|
10
11
|
relationTo?: string;
|
|
11
12
|
options?: Option[];
|
|
12
13
|
};
|
|
14
|
+
export declare type OptionGroup = {
|
|
15
|
+
label: string;
|
|
16
|
+
options: Option[];
|
|
17
|
+
};
|
|
18
|
+
export declare type Value = {
|
|
19
|
+
relationTo: string;
|
|
20
|
+
value: string | number;
|
|
21
|
+
} | string | number;
|
|
13
22
|
declare type CLEAR = {
|
|
14
23
|
type: 'CLEAR';
|
|
15
24
|
};
|
|
16
25
|
declare type ADD = {
|
|
17
26
|
type: 'ADD';
|
|
18
27
|
docs: any[];
|
|
19
|
-
hasMultipleRelations: boolean;
|
|
20
28
|
collection: SanitizedCollectionConfig;
|
|
21
29
|
sort?: boolean;
|
|
22
30
|
ids?: unknown[];
|
|
@@ -33,5 +41,9 @@ export declare type GetResults = (args: {
|
|
|
33
41
|
search?: string;
|
|
34
42
|
value?: unknown;
|
|
35
43
|
sort?: boolean;
|
|
44
|
+
onSuccess?: () => void;
|
|
36
45
|
}) => Promise<void>;
|
|
46
|
+
export declare type FilterOptionsResult = {
|
|
47
|
+
[relation: string]: Where;
|
|
48
|
+
};
|
|
37
49
|
export {};
|
|
@@ -15,6 +15,7 @@ import { getBaseFields } from './Modal/baseFields';
|
|
|
15
15
|
import reduceFieldsToValues from '../../../../Form/reduceFieldsToValues';
|
|
16
16
|
import deepCopyObject from '../../../../../../../utilities/deepCopyObject';
|
|
17
17
|
import Button from '../../../../../elements/Button';
|
|
18
|
+
import { getTranslation } from '../../../../../../../utilities/getTranslation';
|
|
18
19
|
import './index.scss';
|
|
19
20
|
const baseClass = 'rich-text-link';
|
|
20
21
|
// TODO: Multiple modal windows stacked go boom (rip). Edit Upload in fields -> rich text
|
|
@@ -25,7 +26,7 @@ export const LinkElement = ({ attributes, children, element, editorRef, fieldPro
|
|
|
25
26
|
const config = useConfig();
|
|
26
27
|
const { user } = useAuth();
|
|
27
28
|
const locale = useLocale();
|
|
28
|
-
const { t } = useTranslation('fields');
|
|
29
|
+
const { t, i18n } = useTranslation('fields');
|
|
29
30
|
const { openModal, toggleModal } = useModal();
|
|
30
31
|
const [renderModal, setRenderModal] = useState(false);
|
|
31
32
|
const [renderPopup, setRenderPopup] = useState(false);
|
|
@@ -100,8 +101,7 @@ export const LinkElement = ({ attributes, children, element, editorRef, fieldPro
|
|
|
100
101
|
React.createElement(Popup, { buttonType: "none", size: "small", forceOpen: renderPopup, onToggleOpen: handleTogglePopup, horizontalAlign: "left", verticalAlign: "bottom", boundingRef: editorRef, render: () => {
|
|
101
102
|
var _a, _b, _c, _d;
|
|
102
103
|
return (React.createElement("div", { className: `${baseClass}__popup` },
|
|
103
|
-
element.linkType === 'internal' && ((_a = element.doc) === null || _a === void 0 ? void 0 : _a.relationTo) && ((_b = element.doc) === null || _b === void 0 ? void 0 : _b.value) && (React.createElement(Trans, { i18nKey: "linkedTo", values: { label: (_d = (_c = config.collections.find(({ slug }) => slug === element.doc.relationTo)) === null || _c === void 0 ? void 0 : _c.labels) === null || _d === void 0 ? void 0 : _d.singular } },
|
|
104
|
-
"linkedTo",
|
|
104
|
+
element.linkType === 'internal' && ((_a = element.doc) === null || _a === void 0 ? void 0 : _a.relationTo) && ((_b = element.doc) === null || _b === void 0 ? void 0 : _b.value) && (React.createElement(Trans, { i18nKey: "fields:linkedTo", values: { label: getTranslation((_d = (_c = config.collections.find(({ slug }) => slug === element.doc.relationTo)) === null || _c === void 0 ? void 0 : _c.labels) === null || _d === void 0 ? void 0 : _d.singular, i18n) } },
|
|
105
105
|
React.createElement("a", { className: `${baseClass}__link-label`, href: `${config.routes.admin}/collections/${element.doc.relationTo}/${element.doc.value}`, target: "_blank", rel: "noreferrer" }, "label"))),
|
|
106
106
|
(element.linkType === 'custom' || !element.linkType) && (React.createElement("a", { className: `${baseClass}__link-label`, href: element.url, target: "_blank", rel: "noreferrer" }, element.url)),
|
|
107
107
|
React.createElement(Button, { className: `${baseClass}__link-edit`, icon: "edit", round: true, buttonStyle: "icon-label", onClick: (e) => {
|
|
@@ -96,7 +96,7 @@ const UploadButton = ({ path }) => {
|
|
|
96
96
|
React.createElement(UploadIcon, null)),
|
|
97
97
|
renderModal && (React.createElement(Modal, { className: baseModalClass, slug: modalSlug }, isOpen && (React.createElement(MinimalTemplate, { width: "wide" },
|
|
98
98
|
React.createElement("header", { className: `${baseModalClass}__header` },
|
|
99
|
-
React.createElement("h1", null, t('addLabel', { label: getTranslation(modalCollection.labels.singular, i18n) })),
|
|
99
|
+
React.createElement("h1", null, t('fields:addLabel', { label: getTranslation(modalCollection.labels.singular, i18n) })),
|
|
100
100
|
React.createElement(Button, { icon: "x", round: true, buttonStyle: "icon-label", iconStyle: "with-border", onClick: () => {
|
|
101
101
|
toggleModal(modalSlug);
|
|
102
102
|
setRenderModal(false);
|
|
@@ -4,7 +4,7 @@ import withCondition from '../../withCondition';
|
|
|
4
4
|
import { getFieldPath } from '../getFieldPath';
|
|
5
5
|
import './index.scss';
|
|
6
6
|
const Row = (props) => {
|
|
7
|
-
const { fields, fieldTypes, path, permissions, admin: { readOnly, className, }, indexPath } = props;
|
|
7
|
+
const { fields, fieldTypes, path, permissions, admin: { readOnly, className, }, indexPath, } = props;
|
|
8
8
|
const classes = [
|
|
9
9
|
'field-type',
|
|
10
10
|
'row',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { OptionObject, SelectField } from '../../../../../fields/config/types';
|
|
3
3
|
import { Description } from '../../FieldDescription/types';
|
|
4
|
-
import {
|
|
4
|
+
import { Option } from '../../../elements/ReactSelect/types';
|
|
5
5
|
import './index.scss';
|
|
6
6
|
export declare type SelectInputProps = Omit<SelectField, 'type' | 'value' | 'options'> & {
|
|
7
7
|
showError?: boolean;
|
|
@@ -11,7 +11,7 @@ export declare type SelectInputProps = Omit<SelectField, 'type' | 'value' | 'opt
|
|
|
11
11
|
required?: boolean;
|
|
12
12
|
value?: string | string[];
|
|
13
13
|
description?: Description;
|
|
14
|
-
onChange?: (value:
|
|
14
|
+
onChange?: (value: Option) => void;
|
|
15
15
|
style?: React.CSSProperties;
|
|
16
16
|
className?: string;
|
|
17
17
|
width?: string;
|
|
@@ -18,10 +18,20 @@ const SelectInput = (props) => {
|
|
|
18
18
|
].filter(Boolean).join(' ');
|
|
19
19
|
let valueToRender;
|
|
20
20
|
if (hasMany && Array.isArray(value)) {
|
|
21
|
-
valueToRender = value.map((val) =>
|
|
21
|
+
valueToRender = value.map((val) => {
|
|
22
|
+
const matchingOption = options.find((option) => option.value === val);
|
|
23
|
+
return {
|
|
24
|
+
label: getTranslation(matchingOption.label, i18n),
|
|
25
|
+
value: matchingOption.value,
|
|
26
|
+
};
|
|
27
|
+
});
|
|
22
28
|
}
|
|
23
|
-
else {
|
|
24
|
-
|
|
29
|
+
else if (value) {
|
|
30
|
+
const matchingOption = options.find((option) => option.value === value);
|
|
31
|
+
valueToRender = {
|
|
32
|
+
label: getTranslation(matchingOption.label, i18n),
|
|
33
|
+
value: matchingOption.value,
|
|
34
|
+
};
|
|
25
35
|
}
|
|
26
36
|
return (React.createElement("div", { id: `field-${path.replace(/\./gi, '__')}`, className: classes, style: {
|
|
27
37
|
...style,
|
|
@@ -21,7 +21,7 @@ const TextareaInput = (props) => {
|
|
|
21
21
|
} },
|
|
22
22
|
React.createElement(Error, { showError: showError, message: errorMessage }),
|
|
23
23
|
React.createElement(Label, { htmlFor: `field-${path.replace(/\./gi, '__')}`, label: label, required: required }),
|
|
24
|
-
React.createElement("
|
|
24
|
+
React.createElement("label", { className: "textarea-outer", htmlFor: `field-${path.replace(/\./gi, '__')}` },
|
|
25
25
|
React.createElement("div", { className: "textarea-inner" },
|
|
26
26
|
React.createElement("div", { className: "textarea-clone", "data-value": value || placeholder || '' }),
|
|
27
27
|
React.createElement("textarea", { className: "textarea-element", id: `field-${path.replace(/\./gi, '__')}`, value: value || '', onChange: onChange, disabled: readOnly, placeholder: getTranslation(placeholder, i18n), name: path, rows: rows }))),
|
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
.textarea-outer {
|
|
8
8
|
@include formInput();
|
|
9
|
+
display: flex;
|
|
9
10
|
resize: vertical;
|
|
10
|
-
height: auto;
|
|
11
11
|
min-height: base(3);
|
|
12
|
+
height: 100%;
|
|
12
13
|
|
|
13
14
|
// Scrollbar for giant content
|
|
14
|
-
max-height:
|
|
15
|
+
max-height: calc(100vh - $top-header-offset - calc(#{base(5)}));
|
|
15
16
|
overflow: auto;
|
|
16
17
|
|
|
17
18
|
@include mid-break {
|
|
@@ -31,7 +32,6 @@
|
|
|
31
32
|
position: relative;
|
|
32
33
|
line-height: inherit;
|
|
33
34
|
flex-grow: 1;
|
|
34
|
-
box-sizing: border-box;
|
|
35
35
|
background: none;
|
|
36
36
|
outline: none;
|
|
37
37
|
}
|
|
@@ -49,7 +49,6 @@
|
|
|
49
49
|
line-height: inherit;
|
|
50
50
|
color: inherit;
|
|
51
51
|
background: none;
|
|
52
|
-
box-sizing: border-box;
|
|
53
52
|
overflow: auto;
|
|
54
53
|
resize: none;
|
|
55
54
|
outline: none;
|
|
@@ -65,8 +64,6 @@
|
|
|
65
64
|
vertical-align: top;
|
|
66
65
|
display: inline-block;
|
|
67
66
|
flex-grow: 1;
|
|
68
|
-
white-space: pre;
|
|
69
|
-
box-sizing: border-box;
|
|
70
67
|
overflow: hidden;
|
|
71
68
|
text-overflow: ellipsis;
|
|
72
69
|
white-space: pre-wrap;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { Options, FieldType } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Get and set the value of a form field.
|
|
4
|
+
*
|
|
5
|
+
* @see https://payloadcms.com/docs/admin/hooks#usefield
|
|
6
|
+
*/
|
|
2
7
|
declare const useField: <T extends unknown>(options: Options) => FieldType<T>;
|
|
3
8
|
export default useField;
|
|
@@ -5,6 +5,11 @@ import { useFormProcessing, useFormSubmitted, useFormModified, useForm, useFormF
|
|
|
5
5
|
import { useDocumentInfo } from '../../utilities/DocumentInfo';
|
|
6
6
|
import { useOperation } from '../../utilities/OperationProvider';
|
|
7
7
|
import useThrottledEffect from '../../../hooks/useThrottledEffect';
|
|
8
|
+
/**
|
|
9
|
+
* Get and set the value of a form field.
|
|
10
|
+
*
|
|
11
|
+
* @see https://payloadcms.com/docs/admin/hooks#usefield
|
|
12
|
+
*/
|
|
8
13
|
const useField = (options) => {
|
|
9
14
|
const { path, validate, disableFormData = false, condition, } = options;
|
|
10
15
|
const submitted = useFormSubmitted();
|