chore: merge master
This commit is contained in:
@@ -63,7 +63,7 @@ const RelationshipField: React.FC<Props> = (props) => {
|
||||
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`);
|
||||
|
||||
if (response.ok) {
|
||||
const data: PaginatedDocs = await response.json();
|
||||
const data: PaginatedDocs<any> = await response.json();
|
||||
if (data.docs.length > 0) {
|
||||
resultsFetched += data.docs.length;
|
||||
addOptions(data, relation);
|
||||
|
||||
@@ -20,7 +20,7 @@ type CLEAR = {
|
||||
|
||||
type ADD = {
|
||||
type: 'ADD'
|
||||
data: PaginatedDocs
|
||||
data: PaginatedDocs<any>
|
||||
relation: string
|
||||
hasMultipleRelations: boolean
|
||||
collection: SanitizedCollectionConfig
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Pill from '../../../elements/Pill';
|
||||
import { Props } from './types';
|
||||
|
||||
@@ -10,7 +10,7 @@ const baseClass = 'section-title';
|
||||
const SectionTitle: React.FC<Props> = (props) => {
|
||||
const { label, path, readOnly } = props;
|
||||
|
||||
const { value, setValue } = useFieldType({ path });
|
||||
const { value, setValue } = useField({ path });
|
||||
|
||||
const classes = [
|
||||
baseClass,
|
||||
|
||||
@@ -7,7 +7,7 @@ import DraggableSection from '../../DraggableSection';
|
||||
import reducer from '../rowReducer';
|
||||
import { useForm } from '../../Form/context';
|
||||
import buildStateFromSchema from '../../Form/buildStateFromSchema';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Error from '../../Error';
|
||||
import { array } from '../../../../../fields/validations';
|
||||
import Banner from '../../../elements/Banner';
|
||||
@@ -66,7 +66,7 @@ const ArrayFieldType: React.FC<Props> = (props) => {
|
||||
errorMessage,
|
||||
value,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { useForm } from '../../Form/context';
|
||||
import buildStateFromSchema from '../../Form/buildStateFromSchema';
|
||||
import DraggableSection from '../../DraggableSection';
|
||||
import Error from '../../Error';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Popup from '../../../elements/Popup';
|
||||
import BlockSelector from './BlockSelector';
|
||||
import { blocks as blocksValidator } from '../../../../../fields/validations';
|
||||
@@ -76,7 +76,7 @@ const Blocks: React.FC<Props> = (props) => {
|
||||
errorMessage,
|
||||
value,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
@@ -110,7 +110,7 @@ const Blocks: React.FC<Props> = (props) => {
|
||||
|
||||
if (preferencesKey) {
|
||||
const preferences: DocumentPreferences = await getPreference(preferencesKey);
|
||||
const preferencesToSet = preferences || { fields: { } };
|
||||
const preferencesToSet = preferences || { fields: {} };
|
||||
let newCollapsedState = preferencesToSet?.fields?.[path]?.collapsed
|
||||
.filter((filterID) => (rows.find((row) => row.id === filterID)))
|
||||
|| [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Error from '../../Error';
|
||||
import { checkbox } from '../../../../../fields/validations';
|
||||
@@ -41,7 +41,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
errorMessage,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
disableFormData,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Editor from 'react-simple-code-editor';
|
||||
import { highlight, languages } from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-javascript';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
@@ -52,7 +52,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { useWatchForm } from '../../Form/context';
|
||||
@@ -23,7 +23,7 @@ const ConfirmPassword: React.FC = () => {
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path: 'confirm-password',
|
||||
disableFormData: true,
|
||||
validate,
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
|
||||
|
||||
import DatePicker from '../../../elements/DatePicker';
|
||||
import withCondition from '../../withCondition';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -43,7 +43,7 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
errorMessage,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import withCondition from '../../withCondition';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -34,7 +34,7 @@ const Email: React.FC<Props> = (props) => {
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const fieldType = useFieldType({
|
||||
const fieldType = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import { Props } from './types';
|
||||
|
||||
@@ -13,7 +13,7 @@ const HiddenInput: React.FC<Props> = (props) => {
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const { value, setValue } = useFieldType({
|
||||
const { value, setValue } = useField({
|
||||
path,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -41,7 +41,7 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import withCondition from '../../withCondition';
|
||||
@@ -33,7 +33,7 @@ const Password: React.FC<Props> = (props) => {
|
||||
formProcessing,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -41,7 +41,7 @@ const PointField: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType<[number, number]>({
|
||||
} = useField<[number, number]>({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Error from '../../Error';
|
||||
import Label from '../../Label';
|
||||
@@ -44,7 +44,7 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
errorMessage,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useConfig } from '@payloadcms/config-provider';
|
||||
import withCondition from '../../withCondition';
|
||||
import ReactSelect from '../../../elements/ReactSelect';
|
||||
import { Value } from '../../../elements/ReactSelect/types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -70,7 +70,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
errorMessage,
|
||||
setValue,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path: path || name,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
@@ -106,7 +106,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`);
|
||||
|
||||
if (response.ok) {
|
||||
const data: PaginatedDocs = await response.json();
|
||||
const data: PaginatedDocs<any> = await response.json();
|
||||
if (data.docs.length > 0) {
|
||||
resultsFetched += data.docs.length;
|
||||
addOptions(data, relation);
|
||||
|
||||
@@ -19,7 +19,7 @@ type CLEAR = {
|
||||
|
||||
type ADD = {
|
||||
type: 'ADD'
|
||||
data: PaginatedDocs
|
||||
data: PaginatedDocs<any>
|
||||
relation: string
|
||||
hasMultipleRelations: boolean
|
||||
collection: SanitizedCollectionConfig
|
||||
|
||||
@@ -4,7 +4,7 @@ import { createEditor, Transforms, Node, Element as SlateElement, Text, BaseEdit
|
||||
import { ReactEditor, Editable, withReact, Slate } from 'slate-react';
|
||||
import { HistoryEditor, withHistory } from 'slate-history';
|
||||
import { richText } from '../../../../../fields/validations';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
@@ -29,7 +29,7 @@ const defaultElements: RichTextElement[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
const defaultLeaves: RichTextLeaf[] = ['bold', 'italic', 'underline', 'strikethrough', 'code'];
|
||||
|
||||
const baseClass = 'rich-text';
|
||||
type CustomText = { text: string; [x: string]: unknown }
|
||||
type CustomText = { text: string;[x: string]: unknown }
|
||||
|
||||
type CustomElement = { type: string; children: CustomText[] }
|
||||
|
||||
@@ -115,7 +115,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const fieldType = useFieldType({
|
||||
const fieldType = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
stringify: true,
|
||||
@@ -194,7 +194,7 @@ const RichText: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
{ !hideGutter && (<FieldTypeGutter />) }
|
||||
{!hideGutter && (<FieldTypeGutter />)}
|
||||
<Error
|
||||
showError={showError}
|
||||
message={errorMessage}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { useCallback, useState, useEffect } from 'react';
|
||||
import withCondition from '../../withCondition';
|
||||
import ReactSelect from '../../../elements/ReactSelect';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import FieldDescription from '../../FieldDescription';
|
||||
@@ -40,6 +40,8 @@ const Select: React.FC<Props> = (props) => {
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
value: valueFromProps,
|
||||
onChange: onChangeFromProps
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
@@ -52,16 +54,42 @@ const Select: React.FC<Props> = (props) => {
|
||||
}, [validate, required, options]);
|
||||
|
||||
const {
|
||||
value,
|
||||
value: valueFromContext,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const onChange = useCallback((selectedOption) => {
|
||||
if (!readOnly) {
|
||||
let newValue;
|
||||
if (hasMany) {
|
||||
if (Array.isArray(selectedOption)) {
|
||||
newValue = selectedOption.map((option) => option.value);
|
||||
} else {
|
||||
newValue = [];
|
||||
}
|
||||
} else {
|
||||
newValue = selectedOption.value;
|
||||
}
|
||||
|
||||
if (typeof onChangeFromProps === 'function') {
|
||||
onChangeFromProps(newValue);
|
||||
} else {
|
||||
setValue(newValue);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
readOnly,
|
||||
hasMany,
|
||||
onChangeFromProps,
|
||||
setValue
|
||||
])
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
@@ -71,6 +99,8 @@ const Select: React.FC<Props> = (props) => {
|
||||
|
||||
let valueToRender;
|
||||
|
||||
const value = valueFromProps || valueFromContext || '';
|
||||
|
||||
if (hasMany && Array.isArray(value)) {
|
||||
valueToRender = value.map((val) => options.find((option) => option.value === val));
|
||||
} else {
|
||||
@@ -95,17 +125,7 @@ const Select: React.FC<Props> = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
<ReactSelect
|
||||
onChange={!readOnly ? (selectedOption) => {
|
||||
if (hasMany) {
|
||||
if (Array.isArray(selectedOption)) {
|
||||
setValue(selectedOption.map((option) => option.value));
|
||||
} else {
|
||||
setValue([]);
|
||||
}
|
||||
} else {
|
||||
setValue(selectedOption.value);
|
||||
}
|
||||
} : undefined}
|
||||
onChange={onChange}
|
||||
value={valueToRender}
|
||||
showError={showError}
|
||||
isDisabled={readOnly}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
@@ -24,11 +24,13 @@ const Text: React.FC<Props> = (props) => {
|
||||
description,
|
||||
condition,
|
||||
} = {},
|
||||
value: valueFromProps,
|
||||
onChange: onChangeFromProps,
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
|
||||
const fieldType = useFieldType<string>({
|
||||
const fieldType = useField<string>({
|
||||
path,
|
||||
validate,
|
||||
enableDebouncedValue: true,
|
||||
@@ -36,12 +38,24 @@ const Text: React.FC<Props> = (props) => {
|
||||
});
|
||||
|
||||
const {
|
||||
value,
|
||||
value: valueFromContext,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = fieldType;
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
const { value: incomingValue } = e.target;
|
||||
if (typeof onChangeFromProps === 'function') {
|
||||
onChangeFromProps(incomingValue);
|
||||
} else {
|
||||
setValue(e);
|
||||
}
|
||||
}, [
|
||||
onChangeFromProps,
|
||||
setValue,
|
||||
]);
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
'text',
|
||||
@@ -49,6 +63,8 @@ const Text: React.FC<Props> = (props) => {
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const value = valueFromProps || valueFromContext || '';
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
@@ -67,8 +83,8 @@ const Text: React.FC<Props> = (props) => {
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
value={value || ''}
|
||||
onChange={setValue}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={readOnly}
|
||||
placeholder={placeholder}
|
||||
type="text"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
@@ -41,7 +41,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = useFieldType({
|
||||
} = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
enableDebouncedValue: true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useModal } from '@faceless-ui/modal';
|
||||
import { useConfig } from '@payloadcms/config-provider';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import useField from '../../useField';
|
||||
import withCondition from '../../withCondition';
|
||||
import Button from '../../../elements/Button';
|
||||
import Label from '../../Label';
|
||||
@@ -38,6 +38,8 @@ const Upload: React.FC<Props> = (props) => {
|
||||
validate = upload,
|
||||
relationTo,
|
||||
fieldTypes,
|
||||
value: valueFromProps,
|
||||
onChange: onChangeFromProps,
|
||||
} = props;
|
||||
|
||||
const collection = collections.find((coll) => coll.slug === relationTo);
|
||||
@@ -51,19 +53,21 @@ const Upload: React.FC<Props> = (props) => {
|
||||
return validationResult;
|
||||
}, [validate, required]);
|
||||
|
||||
const fieldType = useFieldType({
|
||||
const fieldType = useField({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
condition,
|
||||
});
|
||||
|
||||
const {
|
||||
value,
|
||||
value: valueFromContext,
|
||||
showError,
|
||||
setValue,
|
||||
errorMessage,
|
||||
} = fieldType;
|
||||
|
||||
const value = valueFromProps || valueFromContext || '';
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
@@ -81,14 +85,28 @@ const Upload: React.FC<Props> = (props) => {
|
||||
setInternalValue(json);
|
||||
} else {
|
||||
setInternalValue(undefined);
|
||||
setValue(null);
|
||||
setMissingFile(true);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFile();
|
||||
}
|
||||
}, [value, setInternalValue, relationTo, api, serverURL, setValue]);
|
||||
}, [
|
||||
value,
|
||||
relationTo,
|
||||
api,
|
||||
serverURL,
|
||||
setValue
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const { id: incomingID } = internalValue || {};
|
||||
if (typeof onChangeFromProps === 'function') {
|
||||
onChangeFromProps(incomingID)
|
||||
} else {
|
||||
setValue(incomingID);
|
||||
}
|
||||
}, [internalValue]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -147,7 +165,6 @@ const Upload: React.FC<Props> = (props) => {
|
||||
fieldTypes,
|
||||
setValue: (val) => {
|
||||
setMissingFile(false);
|
||||
setValue(val.id);
|
||||
setInternalValue(val);
|
||||
},
|
||||
}}
|
||||
@@ -157,7 +174,6 @@ const Upload: React.FC<Props> = (props) => {
|
||||
slug: selectExistingModalSlug,
|
||||
setValue: (val) => {
|
||||
setMissingFile(false);
|
||||
setValue(val.id);
|
||||
setInternalValue(val);
|
||||
},
|
||||
addModalSlug,
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '.
|
||||
import useDebounce from '../../../hooks/useDebounce';
|
||||
import { Options, FieldType } from './types';
|
||||
|
||||
const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
const useField = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
const {
|
||||
path,
|
||||
validate,
|
||||
@@ -22,8 +22,10 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
const modified = useFormModified();
|
||||
|
||||
const {
|
||||
dispatchFields, getField, setModified,
|
||||
} = formContext;
|
||||
dispatchFields,
|
||||
getField,
|
||||
setModified,
|
||||
} = formContext || {};
|
||||
|
||||
const [internalValue, setInternalValue] = useState(undefined);
|
||||
|
||||
@@ -64,21 +66,38 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
fieldToDispatch.valid = validationResult;
|
||||
}
|
||||
|
||||
dispatchFields(fieldToDispatch);
|
||||
}, [path, dispatchFields, validate, disableFormData, ignoreWhileFlattening, initialValue, stringify, condition]);
|
||||
if (typeof dispatchFields === 'function') {
|
||||
dispatchFields(fieldToDispatch);
|
||||
}
|
||||
}, [
|
||||
path,
|
||||
dispatchFields,
|
||||
validate,
|
||||
disableFormData,
|
||||
ignoreWhileFlattening,
|
||||
initialValue,
|
||||
stringify,
|
||||
condition
|
||||
]);
|
||||
|
||||
// Method to return from `useFieldType`, used to
|
||||
// Method to return from `useField`, used to
|
||||
// update internal field values from field component(s)
|
||||
// as fast as they arrive. NOTE - this method is NOT debounced
|
||||
const setValue = useCallback((e, modifyForm = true) => {
|
||||
const val = (e && e.target) ? e.target.value : e;
|
||||
|
||||
if ((!ignoreWhileFlattening && !modified) && modifyForm) {
|
||||
setModified(true);
|
||||
if (typeof setModified === 'function') {
|
||||
setModified(true);
|
||||
}
|
||||
}
|
||||
|
||||
setInternalValue(val);
|
||||
}, [setModified, modified, ignoreWhileFlattening]);
|
||||
}, [
|
||||
setModified,
|
||||
modified,
|
||||
ignoreWhileFlattening
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(initialValue);
|
||||
@@ -94,7 +113,11 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
if (field?.value !== valueToSend && valueToSend !== undefined) {
|
||||
sendField(valueToSend);
|
||||
}
|
||||
}, [valueToSend, sendField, field]);
|
||||
}, [
|
||||
valueToSend,
|
||||
sendField,
|
||||
field
|
||||
]);
|
||||
|
||||
return {
|
||||
...options,
|
||||
@@ -107,4 +130,4 @@ const useFieldType = <T extends unknown>(options: Options): FieldType<T> => {
|
||||
};
|
||||
};
|
||||
|
||||
export default useFieldType;
|
||||
export default useField;
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import useFieldType from '../../../../forms/useFieldType';
|
||||
import useField from '../../../../forms/useField';
|
||||
import Label from '../../../../forms/Label';
|
||||
import CopyToClipboard from '../../../../elements/CopyToClipboard';
|
||||
import { text } from '../../../../../../fields/validations';
|
||||
@@ -31,7 +31,7 @@ const APIKey: React.FC = () => {
|
||||
</div>
|
||||
), [apiKeyValue]);
|
||||
|
||||
const fieldType = useFieldType({
|
||||
const fieldType = useField({
|
||||
path: 'apiKey',
|
||||
validate,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
useState, useRef, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import useFieldType from '../../../../forms/useFieldType';
|
||||
import useField from '../../../../forms/useField';
|
||||
import Button from '../../../../elements/Button';
|
||||
import FileDetails from '../../../../elements/FileDetails';
|
||||
import Error from '../../../../forms/Error';
|
||||
@@ -45,7 +45,7 @@ const Upload: React.FC<Props> = (props) => {
|
||||
setValue,
|
||||
showError,
|
||||
errorMessage,
|
||||
} = useFieldType<{name: string}>({
|
||||
} = useField<{ name: string }>({
|
||||
path: 'file',
|
||||
validate,
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Column } from '../../../elements/Table/types';
|
||||
|
||||
export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
data: PaginatedDocs
|
||||
data: PaginatedDocs<any>
|
||||
newDocumentURL: string
|
||||
setListControls: (controls: unknown) => void
|
||||
setSort: (sort: string) => void
|
||||
|
||||
@@ -363,6 +363,10 @@ export function generateTypes(): void {
|
||||
|
||||
compile(jsonSchema, 'Config', {
|
||||
unreachableDefinitions: true,
|
||||
bannerComment: '/* tslint:disable */\n/**\n* This file was automatically generated by Payload CMS.\n* DO NOT MODIFY IT BY HAND. Instead, modify your source Payload config,\n* and re-run `payload generate:types` to regenerate this file.\n*/',
|
||||
style: {
|
||||
singleQuote: true,
|
||||
},
|
||||
}).then((compiled) => {
|
||||
fs.writeFileSync(config.typescript.outputFile, compiled);
|
||||
payload.logger.info(`Types written to ${config.typescript.outputFile}`);
|
||||
|
||||
@@ -18,47 +18,71 @@ export interface AuthCollectionModel extends CollectionModel {
|
||||
}
|
||||
|
||||
export type HookOperationType =
|
||||
| 'create'
|
||||
| 'read'
|
||||
| 'update'
|
||||
| 'delete'
|
||||
| 'refresh'
|
||||
| 'login'
|
||||
| 'forgotPassword';
|
||||
| 'create'
|
||||
| 'read'
|
||||
| 'update'
|
||||
| 'delete'
|
||||
| 'refresh'
|
||||
| 'login'
|
||||
| 'forgotPassword';
|
||||
|
||||
type CreateOrUpdateOperation = Extract<HookOperationType, 'create' | 'update'>;
|
||||
|
||||
export type BeforeOperationHook = (args: {
|
||||
args?: any;
|
||||
/**
|
||||
* Hook operation being performed
|
||||
*/
|
||||
operation: HookOperationType;
|
||||
}) => any;
|
||||
|
||||
export type BeforeValidateHook = (args: {
|
||||
data?: any;
|
||||
export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
|
||||
data?: Partial<T>;
|
||||
req?: PayloadRequest;
|
||||
operation: 'create' | 'update';
|
||||
originalDoc?: any; // undefined on 'create' operation
|
||||
/**
|
||||
* Hook operation being performed
|
||||
*/
|
||||
operation: CreateOrUpdateOperation;
|
||||
/**
|
||||
* Original document before change
|
||||
*
|
||||
* `undefined` on 'create' operation
|
||||
*/
|
||||
originalDoc?: T;
|
||||
}) => any;
|
||||
|
||||
export type BeforeChangeHook = (args: {
|
||||
data: any;
|
||||
export type BeforeChangeHook<T extends TypeWithID = any> = (args: {
|
||||
data: Partial<T>;
|
||||
req: PayloadRequest;
|
||||
operation: 'create' | 'update'
|
||||
originalDoc?: any; // undefined on 'create' operation
|
||||
/**
|
||||
* Hook operation being performed
|
||||
*/
|
||||
operation: CreateOrUpdateOperation;
|
||||
/**
|
||||
* Original document before change
|
||||
*
|
||||
* `undefined` on 'create' operation
|
||||
*/
|
||||
originalDoc?: T;
|
||||
}) => any;
|
||||
|
||||
export type AfterChangeHook = (args: {
|
||||
doc: any;
|
||||
export type AfterChangeHook<T extends TypeWithID = any> = (args: {
|
||||
doc: T;
|
||||
req: PayloadRequest;
|
||||
operation: 'create' | 'update';
|
||||
/**
|
||||
* Hook operation being performed
|
||||
*/
|
||||
operation: CreateOrUpdateOperation;
|
||||
}) => any;
|
||||
|
||||
export type BeforeReadHook = (args: {
|
||||
doc: any;
|
||||
export type BeforeReadHook<T extends TypeWithID = any> = (args: {
|
||||
doc: T;
|
||||
req: PayloadRequest;
|
||||
query: { [key: string]: any };
|
||||
}) => any;
|
||||
|
||||
export type AfterReadHook = (args: {
|
||||
doc: any;
|
||||
export type AfterReadHook<T extends TypeWithID = any> = (args: {
|
||||
doc: T;
|
||||
req: PayloadRequest;
|
||||
query?: { [key: string]: any };
|
||||
}) => any;
|
||||
@@ -68,10 +92,10 @@ export type BeforeDeleteHook = (args: {
|
||||
id: string;
|
||||
}) => any;
|
||||
|
||||
export type AfterDeleteHook = (args: {
|
||||
export type AfterDeleteHook<T extends TypeWithID = any> = (args: {
|
||||
doc: T;
|
||||
req: PayloadRequest;
|
||||
id: string;
|
||||
doc: any;
|
||||
}) => any;
|
||||
|
||||
export type AfterErrorHook = (err: Error, res: unknown) => { response: any, status: number } | void;
|
||||
@@ -80,9 +104,9 @@ export type BeforeLoginHook = (args: {
|
||||
req: PayloadRequest;
|
||||
}) => any;
|
||||
|
||||
export type AfterLoginHook = (args: {
|
||||
export type AfterLoginHook<T extends TypeWithID = any> = (args: {
|
||||
req: PayloadRequest;
|
||||
doc: any;
|
||||
doc: T;
|
||||
token: string;
|
||||
}) => any;
|
||||
|
||||
@@ -90,31 +114,57 @@ export type AfterForgotPasswordHook = (args: {
|
||||
args?: any;
|
||||
}) => any;
|
||||
|
||||
export type CollectionAdminOptions = {
|
||||
/**
|
||||
* Field to use as title in Edit view and first column in List view
|
||||
*/
|
||||
useAsTitle?: string;
|
||||
/**
|
||||
* Default columns to show in list view
|
||||
*/
|
||||
defaultColumns?: string[];
|
||||
/**
|
||||
* Custom description for collection
|
||||
*/
|
||||
description?: string | (() => string) | React.FC;
|
||||
disableDuplicate?: boolean;
|
||||
/**
|
||||
* Custom admin components
|
||||
*/
|
||||
components?: {
|
||||
views?: {
|
||||
Edit?: React.ComponentType
|
||||
List?: React.ComponentType
|
||||
}
|
||||
};
|
||||
pagination?: {
|
||||
defaultLimit?: number
|
||||
limits?: number[]
|
||||
}
|
||||
enableRichTextRelationship?: boolean
|
||||
/**
|
||||
* Function to generate custom preview URL
|
||||
*/
|
||||
preview?: GeneratePreviewURL
|
||||
}
|
||||
|
||||
export type CollectionConfig = {
|
||||
slug: string;
|
||||
/**
|
||||
* Label configuration
|
||||
*/
|
||||
labels?: {
|
||||
singular?: string;
|
||||
plural?: string;
|
||||
};
|
||||
fields: Field[];
|
||||
admin?: {
|
||||
useAsTitle?: string;
|
||||
defaultColumns?: string[];
|
||||
description?: string | (() => string);
|
||||
disableDuplicate?: boolean;
|
||||
components?: {
|
||||
views?: {
|
||||
Edit?: React.ComponentType
|
||||
List?: React.ComponentType
|
||||
}
|
||||
};
|
||||
pagination?: {
|
||||
defaultLimit?: number
|
||||
limits?: number[]
|
||||
}
|
||||
enableRichTextRelationship?: boolean
|
||||
preview?: GeneratePreviewURL
|
||||
};
|
||||
/**
|
||||
* Collection admin options
|
||||
*/
|
||||
admin?: CollectionAdminOptions;
|
||||
/**
|
||||
* Hooks to modify Payload functionality
|
||||
*/
|
||||
hooks?: {
|
||||
beforeOperation?: BeforeOperationHook[];
|
||||
beforeValidate?: BeforeValidateHook[];
|
||||
@@ -129,6 +179,9 @@ export type CollectionConfig = {
|
||||
afterLogin?: AfterLoginHook[];
|
||||
afterForgotPassword?: AfterForgotPasswordHook[];
|
||||
};
|
||||
/**
|
||||
* Access control
|
||||
*/
|
||||
access?: {
|
||||
create?: Access;
|
||||
read?: Access;
|
||||
@@ -138,7 +191,15 @@ export type CollectionConfig = {
|
||||
unlock?: Access;
|
||||
readRevisions?: Access;
|
||||
};
|
||||
/**
|
||||
* Collection login options
|
||||
*
|
||||
* Use `true` to enable with default options
|
||||
*/
|
||||
auth?: IncomingAuthType | boolean;
|
||||
/**
|
||||
* Upload options
|
||||
*/
|
||||
upload?: IncomingUploadType | boolean;
|
||||
revisions?: IncomingRevisionsType | boolean;
|
||||
timestamps?: boolean
|
||||
@@ -160,8 +221,8 @@ export type AuthCollection = {
|
||||
config: SanitizedCollectionConfig;
|
||||
}
|
||||
|
||||
export type PaginatedDocs = {
|
||||
docs: any[]
|
||||
export type PaginatedDocs<T extends TypeWithID = any> = {
|
||||
docs: T[]
|
||||
totalDocs: number
|
||||
limit: number
|
||||
totalPages: number
|
||||
@@ -172,3 +233,7 @@ export type PaginatedDocs = {
|
||||
prevPage: number | null
|
||||
nextPage: number | null
|
||||
}
|
||||
|
||||
export type TypeWithID = {
|
||||
id: string | number
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ export type Arguments = {
|
||||
overrideAccess?: boolean
|
||||
showHiddenFields?: boolean
|
||||
data: Record<string, unknown>
|
||||
overwriteExistingFiles?: boolean
|
||||
}
|
||||
|
||||
async function create(this: Payload, incomingArgs: Arguments): Promise<Document> {
|
||||
@@ -59,6 +60,7 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
depth,
|
||||
overrideAccess,
|
||||
showHiddenFields,
|
||||
overwriteExistingFiles = false,
|
||||
} = args;
|
||||
|
||||
let { data } = args;
|
||||
@@ -108,7 +110,7 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
mkdirp.sync(staticPath);
|
||||
}
|
||||
|
||||
const fsSafeName = await getSafeFilename(staticPath, file.name);
|
||||
const fsSafeName = !overwriteExistingFiles ? await getSafeFilename(Model, staticPath, file.name) : file.name;
|
||||
|
||||
try {
|
||||
if (!disableLocalStorage) {
|
||||
@@ -122,7 +124,15 @@ async function create(this: Payload, incomingArgs: Arguments): Promise<Document>
|
||||
|
||||
if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') {
|
||||
req.payloadUploadSizes = {};
|
||||
fileData.sizes = await resizeAndSave(req, file.data, dimensions, staticPath, collectionConfig, fsSafeName, fileData.mimeType);
|
||||
fileData.sizes = await resizeAndSave({
|
||||
req,
|
||||
file: file.data,
|
||||
dimensions,
|
||||
staticPath,
|
||||
config: collectionConfig,
|
||||
savedFilename: fsSafeName,
|
||||
mimeType: fileData.mimeType,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { PayloadRequest } from '../../express/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { NotFound, Forbidden, ErrorDeletingFile } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import fileExists from '../../uploads/fileExists';
|
||||
import fileOrDocExists from '../../uploads/fileOrDocExists';
|
||||
import { BeforeOperationHook, Collection } from '../config/types';
|
||||
import { Document, Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
@@ -46,7 +46,6 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
req,
|
||||
req: {
|
||||
locale,
|
||||
fallbackLocale,
|
||||
},
|
||||
overrideAccess,
|
||||
showHiddenFields,
|
||||
@@ -106,7 +105,8 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
const staticPath = path.resolve(this.config.paths.configDir, staticDir);
|
||||
|
||||
const fileToDelete = `${staticPath}/${resultToDelete.filename}`;
|
||||
if (await fileExists(fileToDelete)) {
|
||||
|
||||
if (await fileOrDocExists(Model, staticPath, resultToDelete.filename)) {
|
||||
fs.unlink(fileToDelete, (err) => {
|
||||
if (err) {
|
||||
throw new ErrorDeletingFile();
|
||||
@@ -116,7 +116,7 @@ async function deleteQuery(incomingArgs: Arguments): Promise<Document> {
|
||||
|
||||
if (resultToDelete.sizes) {
|
||||
Object.values(resultToDelete.sizes).forEach(async (size: FileData) => {
|
||||
if (await fileExists(`${staticPath}/${size.filename}`)) {
|
||||
if (await fileOrDocExists(Model, staticPath, size.filename)) {
|
||||
fs.unlink(`${staticPath}/${size.filename}`, (err) => {
|
||||
if (err) {
|
||||
throw new ErrorDeletingFile();
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Where } from '../../types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { Collection, PaginatedDocs } from '../config/types';
|
||||
import { Collection, TypeWithID, PaginatedDocs } from '../config/types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
|
||||
|
||||
@@ -18,7 +18,7 @@ export type Arguments = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
|
||||
async function find<T extends TypeWithID = any>(incomingArgs: Arguments): Promise<PaginatedDocs<T>> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -145,7 +145,7 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
|
||||
|
||||
return docRef;
|
||||
})),
|
||||
} as PaginatedDocs;
|
||||
} as PaginatedDocs<T>;
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterRead - Fields
|
||||
@@ -195,7 +195,7 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
|
||||
|
||||
result = {
|
||||
...result,
|
||||
docs: result.docs.map((doc) => sanitizeInternalFields(doc)),
|
||||
docs: result.docs.map((doc) => sanitizeInternalFields<T>(doc)),
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import memoize from 'micro-memoize';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { Collection } from '../config/types';
|
||||
import { Collection, TypeWithID } from '../config/types';
|
||||
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
|
||||
import { Forbidden, NotFound } from '../../errors';
|
||||
import executeAccess from '../../auth/executeAccess';
|
||||
import { Document, Where } from '../../types';
|
||||
import { Where } from '../../types';
|
||||
import { hasWhereAccessResult } from '../../auth/types';
|
||||
|
||||
export type Arguments = {
|
||||
@@ -19,7 +19,7 @@ export type Arguments = {
|
||||
depth?: number
|
||||
}
|
||||
|
||||
async function findByID(incomingArgs: Arguments): Promise<Document> {
|
||||
async function findByID<T extends TypeWithID = any>(incomingArgs: Arguments): Promise<T> {
|
||||
let args = incomingArgs;
|
||||
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -12,6 +12,7 @@ export type Options = {
|
||||
disableVerificationEmail?: boolean
|
||||
showHiddenFields?: boolean
|
||||
filePath?: string
|
||||
overwriteExistingFiles?: boolean
|
||||
}
|
||||
export default async function create(options: Options): Promise<Document> {
|
||||
const {
|
||||
@@ -25,6 +26,7 @@ export default async function create(options: Options): Promise<Document> {
|
||||
disableVerificationEmail,
|
||||
showHiddenFields,
|
||||
filePath,
|
||||
overwriteExistingFiles = false,
|
||||
} = options;
|
||||
|
||||
const collection = this.collections[collectionSlug];
|
||||
@@ -36,6 +38,7 @@ export default async function create(options: Options): Promise<Document> {
|
||||
overrideAccess,
|
||||
disableVerificationEmail,
|
||||
showHiddenFields,
|
||||
overwriteExistingFiles,
|
||||
req: {
|
||||
user,
|
||||
payloadAPI: 'local',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { Document } from '../../../types';
|
||||
|
||||
export type Options = {
|
||||
@@ -11,7 +12,7 @@ export type Options = {
|
||||
showHiddenFields?: boolean
|
||||
}
|
||||
|
||||
export default async function localDelete(options: Options): Promise<Document> {
|
||||
export default async function localDelete<T extends TypeWithID = any>(options: Options): Promise<T> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PaginatedDocs } from '../../config/types';
|
||||
import { PaginatedDocs, TypeWithID } from '../../config/types';
|
||||
import { Document, Where } from '../../../types';
|
||||
|
||||
export type Options = {
|
||||
@@ -15,7 +15,7 @@ export type Options = {
|
||||
where?: Where
|
||||
}
|
||||
|
||||
export default async function find(options: Options): Promise<PaginatedDocs> {
|
||||
export default async function find<T extends TypeWithID = any>(options: Options): Promise<PaginatedDocs<T>> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { PayloadRequest } from '../../../express/types';
|
||||
import { Document } from '../../../types';
|
||||
|
||||
@@ -14,7 +15,7 @@ export type Options = {
|
||||
req?: PayloadRequest
|
||||
}
|
||||
|
||||
export default async function findByID(options: Options): Promise<Document> {
|
||||
export default async function findByID<T extends TypeWithID = any>(options: Options): Promise<T> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { TypeWithID } from '../../config/types';
|
||||
import { Document } from '../../../types';
|
||||
import getFileByPath from '../../../uploads/getFileByPath';
|
||||
|
||||
@@ -15,7 +16,7 @@ export type Options = {
|
||||
overwriteExistingFiles?: boolean
|
||||
}
|
||||
|
||||
export default async function update(options: Options): Promise<Document> {
|
||||
export default async function update<T extends TypeWithID = any>(options: Options): Promise<T> {
|
||||
const {
|
||||
collection: collectionSlug,
|
||||
depth,
|
||||
|
||||
@@ -138,7 +138,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
const file = ((req.files && req.files.file) ? req.files.file : req.file) as UploadedFile;
|
||||
|
||||
if (file) {
|
||||
const fsSafeName = !overwriteExistingFiles ? await getSafeFilename(staticPath, file.name) : file.name;
|
||||
const fsSafeName = !overwriteExistingFiles ? await getSafeFilename(Model, staticPath, file.name) : file.name;
|
||||
|
||||
try {
|
||||
if (!disableLocalStorage) {
|
||||
@@ -156,7 +156,15 @@ async function update(incomingArgs: Arguments): Promise<Document> {
|
||||
|
||||
if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') {
|
||||
req.payloadUploadSizes = {};
|
||||
fileData.sizes = await resizeAndSave(req, file.data, dimensions, staticPath, collectionConfig, fsSafeName, fileData.mimeType);
|
||||
fileData.sizes = await resizeAndSave({
|
||||
req,
|
||||
file: file.data,
|
||||
dimensions,
|
||||
staticPath,
|
||||
config: collectionConfig,
|
||||
savedFilename: fsSafeName,
|
||||
mimeType: fileData.mimeType,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Response, NextFunction } from 'express';
|
||||
import httpStatus from 'http-status';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { PaginatedDocs } from '../config/types';
|
||||
import { PaginatedDocs, TypeWithID } from '../config/types';
|
||||
|
||||
export default async function find(req: PayloadRequest, res: Response, next: NextFunction): Promise<Response<PaginatedDocs> | void> {
|
||||
export default async function find<T extends TypeWithID = any>(req: PayloadRequest, res: Response, next: NextFunction): Promise<Response<PaginatedDocs<T>> | void> {
|
||||
try {
|
||||
let page;
|
||||
|
||||
|
||||
@@ -68,6 +68,10 @@ export type InitOptions = {
|
||||
};
|
||||
|
||||
export type AccessResult = boolean | Where;
|
||||
|
||||
/**
|
||||
* Access function
|
||||
*/
|
||||
export type Access = (args?: any) => AccessResult;
|
||||
|
||||
export type Config = {
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
/* eslint-disable no-use-before-define */
|
||||
import { CSSProperties } from 'react';
|
||||
import { Editor } from 'slate';
|
||||
import { TypeWithID } from '../../collections/config/types';
|
||||
import { PayloadRequest } from '../../express/types';
|
||||
import { Document } from '../../types';
|
||||
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
|
||||
import { Description } from '../../admin/components/forms/FieldDescription/types';
|
||||
|
||||
export type FieldHook = (args: {
|
||||
value?: unknown,
|
||||
originalDoc?: Document,
|
||||
data?: {
|
||||
[key: string]: unknown
|
||||
},
|
||||
export type FieldHookArgs<T extends TypeWithID = any, P = any> = {
|
||||
value?: P,
|
||||
originalDoc?: T,
|
||||
data?: Partial<T>,
|
||||
operation?: 'create' | 'read' | 'update' | 'delete',
|
||||
req: PayloadRequest
|
||||
}) => Promise<unknown> | unknown;
|
||||
}
|
||||
|
||||
export type FieldAccess = (args: {
|
||||
export type FieldHook<T extends TypeWithID = any, P = any> = (args: FieldHookArgs<T, P>) => Promise<P> | P;
|
||||
|
||||
export type FieldAccess<T extends TypeWithID = any, P = any> = (args: {
|
||||
req: PayloadRequest
|
||||
id?: string
|
||||
data: Record<string, unknown>
|
||||
siblingData: Record<string, unknown>
|
||||
data: Partial<T>
|
||||
siblingData: Partial<P>
|
||||
}) => Promise<boolean> | boolean;
|
||||
|
||||
export type Condition = (data: Record<string, unknown>, siblingData: Record<string, unknown>) => boolean;
|
||||
export type Condition<T extends TypeWithID = any, P = any> = (data: Partial<T>, siblingData: Partial<P>) => boolean;
|
||||
|
||||
type Admin = {
|
||||
position?: string;
|
||||
position?: 'sidebar';
|
||||
width?: string;
|
||||
style?: CSSProperties;
|
||||
readOnly?: boolean;
|
||||
@@ -46,7 +46,7 @@ export type Labels = {
|
||||
plural: string;
|
||||
};
|
||||
|
||||
export type Validate = (value: unknown, options?: any) => string | true | Promise<string | true>;
|
||||
export type Validate<T = any> = (value?: T, options?: any) => string | true | Promise<string | true>;
|
||||
|
||||
export type OptionObject = {
|
||||
label: string
|
||||
@@ -99,6 +99,8 @@ export type TextField = FieldBase & {
|
||||
placeholder?: string
|
||||
autoComplete?: string
|
||||
}
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
|
||||
export type EmailField = FieldBase & {
|
||||
@@ -167,9 +169,11 @@ export type UIField = {
|
||||
}
|
||||
|
||||
export type UploadField = FieldBase & {
|
||||
type: 'upload';
|
||||
relationTo: string;
|
||||
maxDepth?: number;
|
||||
type: 'upload'
|
||||
relationTo: string
|
||||
maxDepth?: number
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
|
||||
type CodeAdmin = Admin & {
|
||||
@@ -184,9 +188,11 @@ export type CodeField = Omit<FieldBase, 'admin'> & {
|
||||
}
|
||||
|
||||
export type SelectField = FieldBase & {
|
||||
type: 'select';
|
||||
options: Option[];
|
||||
hasMany?: boolean;
|
||||
type: 'select'
|
||||
options: Option[]
|
||||
hasMany?: boolean
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
|
||||
export type RelationshipField = FieldBase & {
|
||||
@@ -378,4 +384,4 @@ export function fieldAffectsData(field: Field): field is FieldAffectingData {
|
||||
return 'name' in field && !fieldIsPresentationalOnly(field);
|
||||
}
|
||||
|
||||
export type HookName = 'beforeChange' | 'beforeValidate' | 'afterChange' | 'afterRead';
|
||||
export type HookName = 'beforeRead' | 'beforeChange' | 'beforeValidate' | 'afterChange' | 'afterRead';
|
||||
|
||||
29
src/index.ts
29
src/index.ts
@@ -1,14 +1,15 @@
|
||||
import express, { Express, Router } from 'express';
|
||||
import crypto from 'crypto';
|
||||
import { Document } from 'mongoose';
|
||||
import {
|
||||
TypeWithID,
|
||||
Collection, CollectionModel, PaginatedDocs,
|
||||
} from './collections/config/types';
|
||||
import {
|
||||
SanitizedConfig,
|
||||
EmailOptions,
|
||||
InitOptions,
|
||||
} from './config/types';
|
||||
import {
|
||||
Collection, CollectionModel, PaginatedDocs,
|
||||
} from './collections/config/types';
|
||||
|
||||
import Logger from './utilities/logger';
|
||||
import bindOperations from './init/bindOperations';
|
||||
import bindRequestHandlers, { RequestHandlers } from './init/bindRequestHandlers';
|
||||
@@ -210,7 +211,7 @@ export class Payload {
|
||||
* @param options
|
||||
* @returns created document
|
||||
*/
|
||||
create = async (options: CreateOptions): Promise<Document> => {
|
||||
create = async <T>(options: CreateOptions): Promise<T> => {
|
||||
let { create } = localOperations;
|
||||
create = create.bind(this);
|
||||
return create(options);
|
||||
@@ -221,19 +222,19 @@ export class Payload {
|
||||
* @param options
|
||||
* @returns documents satisfying query
|
||||
*/
|
||||
find = async (options: FindOptions): Promise<PaginatedDocs> => {
|
||||
find = async <T extends TypeWithID = any>(options: FindOptions): Promise<PaginatedDocs<T>> => {
|
||||
let { find } = localOperations;
|
||||
find = find.bind(this);
|
||||
return find(options);
|
||||
}
|
||||
|
||||
findGlobal = async (options): Promise<any> => {
|
||||
findGlobal = async <T>(options): Promise<T> => {
|
||||
let { findOne } = localGlobalOperations;
|
||||
findOne = findOne.bind(this);
|
||||
return findOne(options);
|
||||
}
|
||||
|
||||
updateGlobal = async (options): Promise<any> => {
|
||||
updateGlobal = async <T>(options): Promise<T> => {
|
||||
let { update } = localGlobalOperations;
|
||||
update = update.bind(this);
|
||||
return update(options);
|
||||
@@ -244,10 +245,10 @@ export class Payload {
|
||||
* @param options
|
||||
* @returns document with specified ID
|
||||
*/
|
||||
findByID = async (options: FindByIDOptions): Promise<Document> => {
|
||||
findByID = async <T extends TypeWithID = any>(options: FindByIDOptions): Promise<T> => {
|
||||
let { findByID } = localOperations;
|
||||
findByID = findByID.bind(this);
|
||||
return findByID(options);
|
||||
return findByID<T>(options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,16 +256,16 @@ export class Payload {
|
||||
* @param options
|
||||
* @returns Updated document
|
||||
*/
|
||||
update = async (options: UpdateOptions): Promise<Document> => {
|
||||
update = async <T extends TypeWithID = any>(options: UpdateOptions): Promise<T> => {
|
||||
let { update } = localOperations;
|
||||
update = update.bind(this);
|
||||
return update(options);
|
||||
return update<T>(options);
|
||||
}
|
||||
|
||||
delete = async (options: DeleteOptions): Promise<Document> => {
|
||||
delete = async <T extends TypeWithID = any>(options: DeleteOptions): Promise<T> => {
|
||||
let { localDelete: deleteOperation } = localOperations;
|
||||
deleteOperation = deleteOperation.bind(this);
|
||||
return deleteOperation(options);
|
||||
return deleteOperation<T>(options);
|
||||
}
|
||||
|
||||
login = async (options): Promise<any> => {
|
||||
|
||||
@@ -49,7 +49,6 @@ describe('Revisions - REST', () => {
|
||||
}).then((res) => res.json());
|
||||
|
||||
expect(typeof revision.doc.id).toBe('string');
|
||||
expect(revision.doc._status).toBe('draft');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,14 @@ import { promisify } from 'util';
|
||||
|
||||
const stat = promisify(fs.stat);
|
||||
|
||||
export default async (fileName: string): Promise<boolean> => {
|
||||
const fileExists = async (filename: string): Promise<boolean> => {
|
||||
try {
|
||||
await stat(fileName);
|
||||
await stat(filename);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default fileExists;
|
||||
|
||||
20
src/uploads/fileOrDocExists.ts
Normal file
20
src/uploads/fileOrDocExists.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
import { CollectionModel } from '../collections/config/types';
|
||||
|
||||
const stat = promisify(fs.stat);
|
||||
|
||||
const fileOrDocExists = async (Model: CollectionModel, path: string, filename: string): Promise<boolean> => {
|
||||
try {
|
||||
const doc = await Model.findOne({ filename });
|
||||
if (doc) return true;
|
||||
|
||||
await stat(`${path}/${filename}`);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default fileOrDocExists;
|
||||
@@ -67,6 +67,7 @@ const getBaseUploadFields = ({ config, collection }: Options): Field[] => {
|
||||
label: 'File Name',
|
||||
type: 'text',
|
||||
index: true,
|
||||
unique: true,
|
||||
admin: {
|
||||
readOnly: true,
|
||||
disabled: true,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import sanitize from 'sanitize-filename';
|
||||
import fileExists from './fileExists';
|
||||
import { CollectionModel } from '../collections/config/types';
|
||||
import fileOrDocExists from './fileOrDocExists';
|
||||
|
||||
const incrementName = (name: string) => {
|
||||
const extension = name.split('.').pop();
|
||||
@@ -19,11 +20,11 @@ const incrementName = (name: string) => {
|
||||
return `${incrementedName}.${extension}`;
|
||||
};
|
||||
|
||||
async function getSafeFileName(staticPath: string, desiredFilename: string): Promise<string> {
|
||||
async function getSafeFileName(Model: CollectionModel, staticPath: string, desiredFilename: string): Promise<string> {
|
||||
let modifiedFilename = desiredFilename;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
while (await fileExists(`${staticPath}/${modifiedFilename}`)) {
|
||||
while (await fileOrDocExists(Model, staticPath, modifiedFilename)) {
|
||||
modifiedFilename = incrementName(modifiedFilename);
|
||||
}
|
||||
return modifiedFilename;
|
||||
|
||||
@@ -7,6 +7,16 @@ import { SanitizedCollectionConfig } from '../collections/config/types';
|
||||
import { FileSizes, ImageSize } from './types';
|
||||
import { PayloadRequest } from '../express/types';
|
||||
|
||||
type Args = {
|
||||
req: PayloadRequest,
|
||||
file: Buffer,
|
||||
dimensions: ProbedImageSize,
|
||||
staticPath: string,
|
||||
config: SanitizedCollectionConfig,
|
||||
savedFilename: string,
|
||||
mimeType: string,
|
||||
}
|
||||
|
||||
function getOutputImage(sourceImage: string, size: ImageSize) {
|
||||
const extension = sourceImage.split('.').pop();
|
||||
const name = sanitize(sourceImage.substr(0, sourceImage.lastIndexOf('.')) || sourceImage);
|
||||
@@ -27,15 +37,15 @@ function getOutputImage(sourceImage: string, size: ImageSize) {
|
||||
* @param mimeType
|
||||
* @returns image sizes keyed to strings
|
||||
*/
|
||||
export default async function resizeAndSave(
|
||||
req: PayloadRequest,
|
||||
file: Buffer,
|
||||
dimensions: ProbedImageSize,
|
||||
staticPath: string,
|
||||
config: SanitizedCollectionConfig,
|
||||
savedFilename: string,
|
||||
mimeType: string,
|
||||
): Promise<FileSizes> {
|
||||
export default async function resizeAndSave({
|
||||
req,
|
||||
file,
|
||||
dimensions,
|
||||
staticPath,
|
||||
config,
|
||||
savedFilename,
|
||||
mimeType,
|
||||
}: Args): Promise<FileSizes> {
|
||||
const { imageSizes, disableLocalStorage } = config.upload;
|
||||
|
||||
const sizes = imageSizes
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { TypeWithID } from '../collections/config/types';
|
||||
|
||||
const internalFields = ['__v', 'salt', 'hash'];
|
||||
|
||||
const sanitizeInternalFields = (incomingDoc) => Object.entries(incomingDoc).reduce((newDoc, [key, val]) => {
|
||||
const sanitizeInternalFields = <T extends TypeWithID = any>(incomingDoc): T => Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
|
||||
if (key === '_id') {
|
||||
return {
|
||||
...newDoc,
|
||||
@@ -16,6 +18,6 @@ const sanitizeInternalFields = (incomingDoc) => Object.entries(incomingDoc).redu
|
||||
...newDoc,
|
||||
[key]: val,
|
||||
};
|
||||
}, {});
|
||||
}, {} as T);
|
||||
|
||||
export default sanitizeInternalFields;
|
||||
|
||||
Reference in New Issue
Block a user