merges with master
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload",
|
||||
"version": "0.0.30",
|
||||
"version": "0.0.32",
|
||||
"description": "CMS and Application Framework",
|
||||
"license": "ISC",
|
||||
"author": "Payload CMS LLC",
|
||||
|
||||
@@ -27,7 +27,18 @@ async function register(args) {
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 2. Execute before create hook
|
||||
// 2. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeCreate',
|
||||
operationName: 'create',
|
||||
req,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before create hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeCreate.reduce(async (priorHook, hook) => {
|
||||
@@ -39,17 +50,6 @@ async function register(args) {
|
||||
})) || data;
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeCreate',
|
||||
operationName: 'create',
|
||||
req,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 6. Perform register
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -54,7 +54,19 @@ async function update(args) {
|
||||
let { data } = args;
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 2. Execute before update hook
|
||||
// 2. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
hook: 'beforeUpdate',
|
||||
operationName: 'update',
|
||||
originalDoc,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before update hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeUpdate.reduce(async (priorHook, hook) => {
|
||||
@@ -68,23 +80,11 @@ async function update(args) {
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Merge updates into existing data
|
||||
// 4. Merge updates into existing data
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 4. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
hook: 'beforeUpdate',
|
||||
operationName: 'update',
|
||||
originalDoc,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 5. Handle password update
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -51,6 +51,7 @@ div.react-select {
|
||||
}
|
||||
|
||||
.rs__menu {
|
||||
z-index: 2;
|
||||
border-radius: 0;
|
||||
@include inputShadowActive;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
@@ -34,6 +34,7 @@ const DraggableSection = (props) => {
|
||||
actionPanelVerticalAlignment,
|
||||
permissions,
|
||||
isOpen,
|
||||
readOnly,
|
||||
} = props;
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
@@ -48,6 +49,7 @@ const DraggableSection = (props) => {
|
||||
<Draggable
|
||||
draggableId={id}
|
||||
index={rowIndex}
|
||||
isDropDisabled={readOnly}
|
||||
>
|
||||
{(providedDrag) => (
|
||||
<div
|
||||
@@ -79,6 +81,7 @@ const DraggableSection = (props) => {
|
||||
<SectionTitle
|
||||
label={singularLabel}
|
||||
path={`${parentPath}.${rowIndex}.blockName`}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<Button
|
||||
@@ -96,6 +99,7 @@ const DraggableSection = (props) => {
|
||||
duration={0}
|
||||
>
|
||||
<RenderFields
|
||||
readOnly={readOnly}
|
||||
customComponentsPath={customComponentsPath}
|
||||
fieldTypes={fieldTypes}
|
||||
key={rowIndex}
|
||||
@@ -113,15 +117,17 @@ const DraggableSection = (props) => {
|
||||
className="actions"
|
||||
dragHandleProps={providedDrag.dragHandleProps}
|
||||
>
|
||||
<ActionPanel
|
||||
rowIndex={rowIndex}
|
||||
addRow={addRow}
|
||||
removeRow={removeRow}
|
||||
singularLabel={singularLabel}
|
||||
verticalAlignment={actionPanelVerticalAlignment}
|
||||
isHovered={isHovered}
|
||||
{...props}
|
||||
/>
|
||||
{!readOnly && (
|
||||
<ActionPanel
|
||||
rowIndex={rowIndex}
|
||||
addRow={addRow}
|
||||
removeRow={removeRow}
|
||||
singularLabel={singularLabel}
|
||||
verticalAlignment={actionPanelVerticalAlignment}
|
||||
isHovered={isHovered}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</FieldTypeGutter>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,6 +147,7 @@ DraggableSection.defaultProps = {
|
||||
positionPanelVerticalAlignment: 'sticky',
|
||||
actionPanelVerticalAlignment: 'sticky',
|
||||
permissions: {},
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
DraggableSection.propTypes = {
|
||||
@@ -162,6 +169,7 @@ DraggableSection.propTypes = {
|
||||
positionPanelVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
|
||||
actionPanelVerticalAlignment: PropTypes.oneOf(['top', 'center', 'sticky']),
|
||||
permissions: PropTypes.shape({}),
|
||||
readOnly: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default DraggableSection;
|
||||
|
||||
@@ -80,6 +80,8 @@ const RenderFields = (props) => {
|
||||
|
||||
if (readOnlyOverride) readOnly = true;
|
||||
|
||||
if (operation === 'create') readOnly = false;
|
||||
|
||||
if (permissions?.[field?.name]?.read?.permission !== false) {
|
||||
if (permissions?.[field?.name]?.[operation]?.permission === false) {
|
||||
readOnly = true;
|
||||
|
||||
@@ -29,6 +29,9 @@ const ArrayFieldType = (props) => {
|
||||
minRows,
|
||||
singularLabel,
|
||||
permissions,
|
||||
admin: {
|
||||
readOnly,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const [rows, dispatchRows] = useReducer(reducer, []);
|
||||
@@ -113,6 +116,7 @@ const ArrayFieldType = (props) => {
|
||||
fields={fields}
|
||||
permissions={permissions}
|
||||
value={value}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -125,6 +129,7 @@ ArrayFieldType.defaultProps = {
|
||||
minRows: undefined,
|
||||
singularLabel: 'Row',
|
||||
permissions: {},
|
||||
admin: {},
|
||||
};
|
||||
|
||||
ArrayFieldType.propTypes = {
|
||||
@@ -143,6 +148,9 @@ ArrayFieldType.propTypes = {
|
||||
permissions: PropTypes.shape({
|
||||
fields: PropTypes.shape({}),
|
||||
}),
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
const RenderArray = React.memo((props) => {
|
||||
@@ -163,6 +171,7 @@ const RenderArray = React.memo((props) => {
|
||||
fields,
|
||||
permissions,
|
||||
value,
|
||||
readOnly,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -183,6 +192,7 @@ const RenderArray = React.memo((props) => {
|
||||
>
|
||||
{rows.length > 0 && rows.map((row, i) => (
|
||||
<DraggableSection
|
||||
readOnly={readOnly}
|
||||
key={row.key}
|
||||
id={row.key}
|
||||
blockType="array"
|
||||
@@ -205,18 +215,19 @@ const RenderArray = React.memo((props) => {
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className={`${baseClass}__add-button-wrap`}>
|
||||
<Button
|
||||
onClick={() => addRow(value)}
|
||||
buttonStyle="icon-label"
|
||||
icon="plus"
|
||||
iconStyle="with-border"
|
||||
iconPosition="left"
|
||||
>
|
||||
{`Add ${singularLabel}`}
|
||||
</Button>
|
||||
</div>
|
||||
{!readOnly && (
|
||||
<div className={`${baseClass}__add-button-wrap`}>
|
||||
<Button
|
||||
onClick={() => addRow(value)}
|
||||
buttonStyle="icon-label"
|
||||
icon="plus"
|
||||
iconStyle="with-border"
|
||||
iconPosition="left"
|
||||
>
|
||||
{`Add ${singularLabel}`}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</DragDropContext>
|
||||
);
|
||||
@@ -231,6 +242,7 @@ RenderArray.defaultProps = {
|
||||
path: '',
|
||||
customComponentsPath: undefined,
|
||||
value: undefined,
|
||||
readOnly: false,
|
||||
};
|
||||
|
||||
RenderArray.propTypes = {
|
||||
@@ -256,6 +268,7 @@ RenderArray.propTypes = {
|
||||
permissions: PropTypes.shape({
|
||||
fields: PropTypes.shape({}),
|
||||
}).isRequired,
|
||||
readOnly: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default withCondition(ArrayFieldType);
|
||||
|
||||
@@ -33,6 +33,9 @@ const Blocks = (props) => {
|
||||
required,
|
||||
validate,
|
||||
permissions,
|
||||
admin: {
|
||||
readOnly,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
@@ -130,6 +133,7 @@ const Blocks = (props) => {
|
||||
permissions={permissions}
|
||||
value={value}
|
||||
blocks={blocks}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -142,6 +146,7 @@ Blocks.defaultProps = {
|
||||
maxRows: undefined,
|
||||
minRows: undefined,
|
||||
permissions: {},
|
||||
admin: {},
|
||||
};
|
||||
|
||||
Blocks.propTypes = {
|
||||
@@ -160,6 +165,9 @@ Blocks.propTypes = {
|
||||
permissions: PropTypes.shape({
|
||||
fields: PropTypes.shape({}),
|
||||
}),
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
const RenderBlocks = React.memo((props) => {
|
||||
@@ -181,6 +189,7 @@ const RenderBlocks = React.memo((props) => {
|
||||
value,
|
||||
toggleCollapse,
|
||||
blocks,
|
||||
readOnly,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -195,7 +204,10 @@ const RenderBlocks = React.memo((props) => {
|
||||
/>
|
||||
</header>
|
||||
|
||||
<Droppable droppableId="blocks-drop">
|
||||
<Droppable
|
||||
droppableId="blocks-drop"
|
||||
isDropDisabled={readOnly}
|
||||
>
|
||||
{(provided) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
@@ -208,6 +220,7 @@ const RenderBlocks = React.memo((props) => {
|
||||
if (blockToRender) {
|
||||
return (
|
||||
<DraggableSection
|
||||
readOnly={readOnly}
|
||||
key={row.key}
|
||||
id={row.key}
|
||||
blockType="blocks"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import useFieldType from '../../useFieldType';
|
||||
import withCondition from '../../withCondition';
|
||||
@@ -45,17 +45,21 @@ const Checkbox = (props) => {
|
||||
disableFormData,
|
||||
});
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
value && `${baseClass}--checked`,
|
||||
readOnly && 'read-only',
|
||||
].filter(Boolean).join(' ');
|
||||
useEffect(() => {
|
||||
if (value === null || value === undefined) {
|
||||
setValue(false);
|
||||
}
|
||||
}, [value, setValue]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes}
|
||||
className={[
|
||||
'field-type',
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
value && `${baseClass}--checked`,
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
].filter(Boolean).join(' ')}
|
||||
style={{
|
||||
...style,
|
||||
width,
|
||||
@@ -74,7 +78,7 @@ const Checkbox = (props) => {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onClick={readOnly ? undefined : () => {
|
||||
setValue(!value);
|
||||
if (typeof onChange === 'function') onChange(!value);
|
||||
}}
|
||||
|
||||
@@ -55,4 +55,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--read-only {
|
||||
&__input {
|
||||
background-color: lighten($color-gray, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,14 @@ const baseClass = 'group';
|
||||
|
||||
const Group = (props) => {
|
||||
const {
|
||||
label, fields, name, path: pathFromProps, fieldTypes,
|
||||
label,
|
||||
fields,
|
||||
name,
|
||||
path: pathFromProps,
|
||||
fieldTypes,
|
||||
admin: {
|
||||
readOnly,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const path = pathFromProps || name;
|
||||
@@ -26,6 +33,7 @@ const Group = (props) => {
|
||||
|
||||
<div className={`${baseClass}__fields-wrapper`}>
|
||||
<RenderFields
|
||||
readOnly={readOnly}
|
||||
fieldTypes={fieldTypes}
|
||||
customComponentsPath={`${customComponentsPath}${name}.fields.`}
|
||||
fieldSchema={fields.map((subField) => ({
|
||||
@@ -42,6 +50,7 @@ const Group = (props) => {
|
||||
Group.defaultProps = {
|
||||
label: '',
|
||||
path: '',
|
||||
admin: {},
|
||||
};
|
||||
|
||||
Group.propTypes = {
|
||||
@@ -52,6 +61,9 @@ Group.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
path: PropTypes.string,
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
export default withCondition(Group);
|
||||
|
||||
@@ -59,3 +59,29 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.radio-group--read-only {
|
||||
.radio-input {
|
||||
&__label {
|
||||
color: $color-gray;
|
||||
}
|
||||
|
||||
&--is-selected {
|
||||
.radio-input__styled-radio {
|
||||
&:before {
|
||||
background-color: $color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(&--is-selected) {
|
||||
&:hover {
|
||||
.radio-input__styled-radio {
|
||||
&:before {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { radio } from '../../../../../fields/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'radio-group';
|
||||
|
||||
const RadioGroup = (props) => {
|
||||
const {
|
||||
name,
|
||||
@@ -19,6 +21,7 @@ const RadioGroup = (props) => {
|
||||
label,
|
||||
admin: {
|
||||
readOnly,
|
||||
layout = 'horizontal',
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
@@ -45,9 +48,10 @@ const RadioGroup = (props) => {
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
'radio-group',
|
||||
baseClass,
|
||||
`${baseClass}--layout-${layout}`,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
@@ -67,18 +71,22 @@ const RadioGroup = (props) => {
|
||||
label={label}
|
||||
required={required}
|
||||
/>
|
||||
{options?.map((option) => {
|
||||
const isSelected = option.value === value;
|
||||
<ul className={`${baseClass}--group`}>
|
||||
{options?.map((option) => {
|
||||
const isSelected = option.value === value;
|
||||
|
||||
return (
|
||||
<RadioInput
|
||||
key={option.value}
|
||||
isSelected={isSelected}
|
||||
option={option}
|
||||
onChange={readOnly ? undefined : setValue}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<li key={option.value}>
|
||||
<RadioInput
|
||||
key={option.value}
|
||||
isSelected={isSelected}
|
||||
option={option}
|
||||
onChange={readOnly ? undefined : setValue}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.radio-group {
|
||||
&--layout-horizontal {
|
||||
ul {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
li {
|
||||
padding-right: $baseline;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,18 @@ class Relationship extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { relationTo, hasMultipleRelations } = this.props;
|
||||
const { relationTo, hasMultipleRelations, required } = this.props;
|
||||
const relations = hasMultipleRelations ? relationTo : [relationTo];
|
||||
|
||||
this.initialOptions = required ? [] : [{ value: 'null', label: 'None' }];
|
||||
|
||||
this.state = {
|
||||
relations,
|
||||
lastFullyLoadedRelation: -1,
|
||||
lastLoadedPage: 1,
|
||||
options: [],
|
||||
errorLoading: false,
|
||||
loadedIDs: [],
|
||||
options: this.initialOptions,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,11 +44,15 @@ class Relationship extends Component {
|
||||
this.getNextOptions();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { search } = this.state;
|
||||
componentDidUpdate(_, prevState) {
|
||||
const { search, options } = this.state;
|
||||
if (search !== prevState.search) {
|
||||
this.getNextOptions({ clear: true });
|
||||
}
|
||||
|
||||
if (options !== prevState.options) {
|
||||
this.ensureValueHasOption();
|
||||
}
|
||||
}
|
||||
|
||||
getNextOptions = (params = {}) => {
|
||||
@@ -54,7 +61,9 @@ class Relationship extends Component {
|
||||
|
||||
if (clear) {
|
||||
this.setState({
|
||||
options: [],
|
||||
options: this.initialOptions,
|
||||
loadedIDs: [],
|
||||
lastFullyLoadedRelation: -1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +72,7 @@ class Relationship extends Component {
|
||||
relations, lastFullyLoadedRelation, lastLoadedPage, search,
|
||||
} = this.state;
|
||||
|
||||
const relationsToSearch = relations.slice(lastFullyLoadedRelation + 1);
|
||||
const relationsToSearch = lastFullyLoadedRelation === -1 ? relations : relations.slice(lastFullyLoadedRelation + 1);
|
||||
|
||||
if (relationsToSearch.length > 0) {
|
||||
some(relationsToSearch, async (relation, callback) => {
|
||||
@@ -97,6 +106,9 @@ class Relationship extends Component {
|
||||
if (nextPage) {
|
||||
const { data, relation } = nextPage;
|
||||
this.addOptions(data, relation);
|
||||
this.setState({
|
||||
lastLoadedPage: lastLoadedPage + 1,
|
||||
});
|
||||
} else {
|
||||
const { data, relation } = lastPage;
|
||||
this.addOptions(data, relation);
|
||||
@@ -131,7 +143,7 @@ class Relationship extends Component {
|
||||
|
||||
if (hasMultipleRelations) {
|
||||
options.forEach((option) => {
|
||||
const potentialValue = option.options.find((subOption) => {
|
||||
const potentialValue = option.options && option.options.find((subOption) => {
|
||||
if (subOption?.value?.value && value?.value) {
|
||||
return subOption.value.value === value.value;
|
||||
}
|
||||
@@ -154,61 +166,124 @@ class Relationship extends Component {
|
||||
|
||||
addOptions = (data, relation) => {
|
||||
const { hasMultipleRelations } = this.props;
|
||||
const { lastLoadedPage, options } = this.state;
|
||||
const { options, loadedIDs } = this.state;
|
||||
const collection = collections.find((coll) => coll.slug === relation);
|
||||
|
||||
if (!hasMultipleRelations) {
|
||||
this.setState({
|
||||
options: [
|
||||
...options,
|
||||
...data.docs.map((doc) => ({
|
||||
label: doc[collection?.admin?.useAsTitle || 'id'],
|
||||
value: doc.id,
|
||||
})),
|
||||
],
|
||||
});
|
||||
} else {
|
||||
const allOptionGroups = [...options];
|
||||
const optionsToAddTo = allOptionGroups.find((optionGroup) => optionGroup.label === collection.labels.plural);
|
||||
const newlyLoadedIDs = [];
|
||||
|
||||
const newOptions = data.docs.map((doc) => ({
|
||||
label: doc[collection?.admin?.useAsTitle || 'id'],
|
||||
value: {
|
||||
relationTo: collection.slug,
|
||||
value: doc.id,
|
||||
},
|
||||
}));
|
||||
let newOptions = [];
|
||||
|
||||
if (!hasMultipleRelations) {
|
||||
newOptions = [
|
||||
...options,
|
||||
...data.docs.reduce((docs, doc) => {
|
||||
if (loadedIDs.indexOf(doc.id) === -1) {
|
||||
newlyLoadedIDs.push(doc.id);
|
||||
|
||||
return [
|
||||
...docs,
|
||||
{
|
||||
label: doc[collection?.admin?.useAsTitle || 'id'],
|
||||
value: doc.id,
|
||||
},
|
||||
];
|
||||
}
|
||||
return docs;
|
||||
}, []),
|
||||
];
|
||||
} else {
|
||||
newOptions = [...options];
|
||||
const optionsToAddTo = newOptions.find((optionGroup) => optionGroup.label === collection.labels.plural);
|
||||
|
||||
const newSubOptions = data.docs.reduce((docs, doc) => {
|
||||
if (loadedIDs.indexOf(doc.id) === -1) {
|
||||
newlyLoadedIDs.push(doc.id);
|
||||
|
||||
return [
|
||||
...docs,
|
||||
{
|
||||
label: doc[collection?.admin?.useAsTitle || 'id'],
|
||||
value: {
|
||||
relationTo: collection.slug,
|
||||
value: doc.id,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return docs;
|
||||
}, []);
|
||||
|
||||
if (optionsToAddTo) {
|
||||
optionsToAddTo.options = [
|
||||
...optionsToAddTo.options,
|
||||
...newOptions,
|
||||
...newSubOptions,
|
||||
];
|
||||
} else {
|
||||
allOptionGroups.push({
|
||||
newOptions.push({
|
||||
label: collection.labels.plural,
|
||||
options: newOptions,
|
||||
options: newSubOptions,
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
options: [
|
||||
...allOptionGroups,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
lastLoadedPage: lastLoadedPage + 1,
|
||||
options: newOptions,
|
||||
loadedIDs: [
|
||||
...loadedIDs,
|
||||
...newlyLoadedIDs,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
ensureValueHasOption = async () => {
|
||||
const { relationTo, hasMany, value } = this.props;
|
||||
const { options } = this.state;
|
||||
const locatedValue = this.findValueInOptions(options, value);
|
||||
|
||||
const hasMultipleRelations = Array.isArray(relationTo);
|
||||
|
||||
if (!locatedValue && value) {
|
||||
if (hasMany) {
|
||||
value.forEach((val) => {
|
||||
if (hasMultipleRelations) {
|
||||
this.addOptionByID(val.value, val.relationTo);
|
||||
} else {
|
||||
this.addOptionByID(val, relationTo);
|
||||
}
|
||||
});
|
||||
} else if (hasMultipleRelations) {
|
||||
this.addOptionByID(value.value, value.relationTo);
|
||||
} else {
|
||||
this.addOptionByID(value, relationTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addOptionByID = async (id, relation) => {
|
||||
const { errorLoading } = this.state;
|
||||
if (!errorLoading) {
|
||||
const response = await fetch(`${serverURL}${api}/${relation}/${id}`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
this.addOptions({ docs: [data] }, relation);
|
||||
} else {
|
||||
console.log(`There was a problem loading the document with ID of ${id}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChange = (search) => {
|
||||
this.setState({
|
||||
search,
|
||||
lastFullyLoadedRelation: -1,
|
||||
lastLoadedPage: 1,
|
||||
});
|
||||
const { search: existingSearch } = this.state;
|
||||
|
||||
if (search !== existingSearch) {
|
||||
this.setState({
|
||||
search,
|
||||
lastFullyLoadedRelation: -1,
|
||||
lastLoadedPage: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuScrollToBottom = () => {
|
||||
@@ -240,10 +315,10 @@ class Relationship extends Component {
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
errorLoading && 'error-loading',
|
||||
readOnly && 'read-only',
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const valueToRender = this.findValueInOptions(options, value);
|
||||
const valueToRender = this.findValueInOptions(options, value) || value;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -264,6 +339,7 @@ class Relationship extends Component {
|
||||
/>
|
||||
{!errorLoading && (
|
||||
<ReactSelect
|
||||
isDisabled={readOnly}
|
||||
onInputChange={this.handleInputChange}
|
||||
onChange={!readOnly ? setValue : undefined}
|
||||
formatValue={this.formatSelectedValue}
|
||||
|
||||
@@ -11,3 +11,11 @@
|
||||
background-color: $color-red;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.relationship--read-only {
|
||||
div.react-select {
|
||||
div.rs__control {
|
||||
background: lighten($color-light-gray, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,11 +7,18 @@ import './index.scss';
|
||||
|
||||
const Row = (props) => {
|
||||
const {
|
||||
fields, fieldTypes, path, permissions,
|
||||
fields,
|
||||
fieldTypes,
|
||||
path,
|
||||
permissions,
|
||||
admin: {
|
||||
readOnly,
|
||||
},
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<RenderFields
|
||||
readOnly={readOnly}
|
||||
className="field-type row"
|
||||
permissions={permissions}
|
||||
fieldTypes={fieldTypes}
|
||||
@@ -26,6 +33,7 @@ const Row = (props) => {
|
||||
Row.defaultProps = {
|
||||
path: '',
|
||||
permissions: {},
|
||||
admin: {},
|
||||
};
|
||||
|
||||
Row.propTypes = {
|
||||
@@ -35,6 +43,9 @@ Row.propTypes = {
|
||||
fieldTypes: PropTypes.shape({}).isRequired,
|
||||
path: PropTypes.string,
|
||||
permissions: PropTypes.shape({}),
|
||||
admin: PropTypes.shape({
|
||||
readOnly: PropTypes.bool,
|
||||
}),
|
||||
};
|
||||
|
||||
export default withCondition(Row);
|
||||
|
||||
@@ -9,6 +9,8 @@ import { select } from '../../../../../fields/validations';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const baseClass = 'select';
|
||||
|
||||
const findFullOption = (value, options) => {
|
||||
const matchedOption = options.find((option) => option?.value === value);
|
||||
|
||||
@@ -97,9 +99,9 @@ const Select = (props) => {
|
||||
|
||||
const classes = [
|
||||
'field-type',
|
||||
'select',
|
||||
baseClass,
|
||||
showError && 'error',
|
||||
readOnly && 'read-only',
|
||||
readOnly && `${baseClass}--read-only`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
const valueToRender = formatRenderValue(value, options);
|
||||
@@ -126,7 +128,7 @@ const Select = (props) => {
|
||||
value={valueToRender}
|
||||
formatValue={formatFormValue}
|
||||
showError={showError}
|
||||
disabled={readOnly}
|
||||
isDisabled={readOnly}
|
||||
options={options}
|
||||
isMulti={hasMany}
|
||||
/>
|
||||
|
||||
@@ -3,3 +3,11 @@
|
||||
.field-type.select {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.select--read-only {
|
||||
div.react-select {
|
||||
div.rs__control {
|
||||
background: lighten($color-light-gray, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: $color-light-gray;
|
||||
color: $color-gray;
|
||||
background: lighten($color-light-gray, 5%);
|
||||
color: darken($color-gray, 5%);
|
||||
|
||||
&:hover {
|
||||
border-color: $color-light-gray;
|
||||
|
||||
@@ -46,7 +46,7 @@ const CreateFirstUser = (props) => {
|
||||
|
||||
return (
|
||||
<MinimalTemplate className={baseClass}>
|
||||
<h1>Welcome to Payload</h1>
|
||||
<h1>Welcome</h1>
|
||||
<p>To begin, create your first user.</p>
|
||||
<Form
|
||||
onSuccess={onSuccess}
|
||||
|
||||
@@ -45,6 +45,7 @@ const DefaultEditView = (props) => {
|
||||
fields,
|
||||
admin: {
|
||||
useAsTitle,
|
||||
disableDuplicate,
|
||||
},
|
||||
timestamps,
|
||||
preview,
|
||||
@@ -110,10 +111,12 @@ const DefaultEditView = (props) => {
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
{isEditing ? (
|
||||
<ul className={`${baseClass}__collection-actions`}>
|
||||
{permissions?.create?.permission && (
|
||||
{(permissions?.create?.permission) && (
|
||||
<React.Fragment>
|
||||
<li><Link to={`${admin}/collections/${slug}/create`}>Create New</Link></li>
|
||||
<li><DuplicateDocument slug={slug} /></li>
|
||||
{!disableDuplicate && (
|
||||
<li><DuplicateDocument slug={slug} /></li>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
{permissions?.delete?.permission && (
|
||||
@@ -220,6 +223,7 @@ DefaultEditView.propTypes = {
|
||||
slug: PropTypes.string,
|
||||
admin: PropTypes.shape({
|
||||
useAsTitle: PropTypes.string,
|
||||
disableDuplicate: PropTypes.bool,
|
||||
}),
|
||||
fields: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
preview: PropTypes.func,
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import config from 'payload/config';
|
||||
|
||||
const { collections } = config;
|
||||
|
||||
const RelationshipCell = (props) => {
|
||||
const { field, cellData } = props;
|
||||
const { relationTo } = field;
|
||||
|
||||
const [data, setData] = useState();
|
||||
|
||||
console.log(cellData);
|
||||
|
||||
useEffect(() => {
|
||||
const hasManyRelations = Array.isArray(relationTo);
|
||||
|
||||
if (cellData) {
|
||||
if (Array.isArray(cellData)) {
|
||||
setData(cellData.reduce((newData, value) => {
|
||||
const relation = hasManyRelations ? value?.relationTo : relationTo;
|
||||
const doc = hasManyRelations ? value.value : value;
|
||||
|
||||
const collection = collections.find((coll) => coll.slug === relation);
|
||||
|
||||
if (collection) {
|
||||
const useAsTitle = collection.admin.useAsTitle ? collection.admin.useAsTitle : 'id';
|
||||
|
||||
return newData ? `${newData}, ${doc[useAsTitle]}` : doc[useAsTitle];
|
||||
}
|
||||
|
||||
return newData;
|
||||
}, ''));
|
||||
} else {
|
||||
const relation = hasManyRelations ? cellData?.relationTo : relationTo;
|
||||
const doc = hasManyRelations ? cellData.value : cellData;
|
||||
const collection = collections.find((coll) => coll.slug === relation);
|
||||
|
||||
if (collection) {
|
||||
const useAsTitle = collection.admin.useAsTitle ? collection.admin.useAsTitle : 'id';
|
||||
|
||||
setData(doc[useAsTitle]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [cellData, relationTo, field]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{data}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
RelationshipCell.defaultProps = {
|
||||
cellData: undefined,
|
||||
};
|
||||
|
||||
RelationshipCell.propTypes = {
|
||||
cellData: PropTypes.oneOfType([
|
||||
PropTypes.shape({}),
|
||||
PropTypes.array,
|
||||
PropTypes.string,
|
||||
]),
|
||||
field: PropTypes.shape({
|
||||
relationTo: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.string,
|
||||
),
|
||||
PropTypes.string,
|
||||
]),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default RelationshipCell;
|
||||
@@ -3,8 +3,9 @@ import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import format from 'date-fns/format';
|
||||
import config from 'payload/config';
|
||||
import RenderCustomComponent from '../../../utilities/RenderCustomComponent';
|
||||
import Thumbnail from '../../../elements/Thumbnail';
|
||||
import RenderCustomComponent from '../../../../utilities/RenderCustomComponent';
|
||||
import Thumbnail from '../../../../elements/Thumbnail';
|
||||
import Relationship from './Relationship';
|
||||
|
||||
const { routes: { admin } } = config;
|
||||
|
||||
@@ -50,6 +51,17 @@ const DefaultCell = (props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'relationship') {
|
||||
return (
|
||||
<WrapElement {...wrapElementProps}>
|
||||
<Relationship
|
||||
field={field}
|
||||
cellData={cellData}
|
||||
/>
|
||||
</WrapElement>
|
||||
);
|
||||
}
|
||||
|
||||
if (field.type === 'date' && cellData) {
|
||||
return (
|
||||
<WrapElement {...wrapElementProps}>
|
||||
@@ -65,6 +77,7 @@ const DefaultCell = (props) => {
|
||||
{field.type !== 'date' && (
|
||||
<React.Fragment>
|
||||
{typeof cellData === 'string' && cellData}
|
||||
{typeof cellData === 'number' && cellData}
|
||||
{typeof cellData === 'object' && JSON.stringify(cellData)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -48,7 +48,7 @@ const DefaultList = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
const params = {
|
||||
depth: 0,
|
||||
depth: 2,
|
||||
};
|
||||
|
||||
if (page) params.page = page;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Payload</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -33,7 +33,18 @@ async function create(args) {
|
||||
await executeAccess({ req }, collectionConfig.access.create);
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 2. Execute before collection hook
|
||||
// 2. Execute field-level access, hooks, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeCreate',
|
||||
operationName: 'create',
|
||||
req,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before collection hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeCreate.reduce(async (priorHook, hook) => {
|
||||
@@ -46,37 +57,39 @@ async function create(args) {
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Upload and resize any files that may be present
|
||||
// 4. Upload and resize any files that may be present
|
||||
// /////////////////////////////////////
|
||||
|
||||
if (collectionConfig.upload) {
|
||||
const { staticDir, imageSizes } = collectionConfig.upload;
|
||||
|
||||
const fileData = {};
|
||||
|
||||
if (!req.files || Object.keys(req.files).length === 0) {
|
||||
const { staticDir, imageSizes } = collectionConfig.upload;
|
||||
|
||||
const file = (req.files && req.files.file) ? req.files.file : req.file;
|
||||
|
||||
if (!file) {
|
||||
throw new MissingFile();
|
||||
}
|
||||
|
||||
mkdirp.sync(staticDir);
|
||||
|
||||
const fsSafeName = await getSafeFilename(staticDir, req.files.file.name);
|
||||
const fsSafeName = await getSafeFilename(staticDir, file.name);
|
||||
|
||||
await req.files.file.mv(`${staticDir}/${fsSafeName}`);
|
||||
await file.mv(`${staticDir}/${fsSafeName}`);
|
||||
|
||||
if (imageMIMETypes.indexOf(req.files.file.mimetype) > -1) {
|
||||
if (imageMIMETypes.indexOf(file.mimetype) > -1) {
|
||||
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
|
||||
fileData.width = dimensions.width;
|
||||
fileData.height = dimensions.height;
|
||||
|
||||
if (Array.isArray(imageSizes) && req.files.file.mimetype !== 'image/svg+xml') {
|
||||
if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') {
|
||||
fileData.sizes = await resizeAndSave(collectionConfig, fsSafeName, fileData.mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
fileData.filename = fsSafeName;
|
||||
fileData.filesize = req.files.file.size;
|
||||
fileData.mimeType = req.files.file.mimetype;
|
||||
fileData.filesize = file.size;
|
||||
fileData.mimeType = file.mimetype;
|
||||
|
||||
data = {
|
||||
...data,
|
||||
@@ -84,17 +97,6 @@ async function create(args) {
|
||||
};
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 4. Execute field-level access, hooks, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
hook: 'beforeCreate',
|
||||
operationName: 'create',
|
||||
req,
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 5. Perform database operation
|
||||
// /////////////////////////////////////
|
||||
|
||||
@@ -22,6 +22,7 @@ async function find(options) {
|
||||
payloadAPI: 'local',
|
||||
locale,
|
||||
fallbackLocale,
|
||||
payload: this,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,11 +64,23 @@ async function update(args) {
|
||||
const originalDoc = doc.toJSON({ virtuals: true });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 2. Execute before update hook
|
||||
// 2. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
let { data } = args;
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
originalDoc,
|
||||
hook: 'beforeUpdate',
|
||||
operationName: 'update',
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Execute before update hook
|
||||
// /////////////////////////////////////
|
||||
|
||||
await collectionConfig.hooks.beforeUpdate.reduce(async (priorHook, hook) => {
|
||||
await priorHook;
|
||||
|
||||
@@ -80,23 +92,11 @@ async function update(args) {
|
||||
}, Promise.resolve());
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 3. Merge updates into existing data
|
||||
// 4. Merge updates into existing data
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 4. Execute field-level hooks, access, and validation
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await this.performFieldOperations(collectionConfig, {
|
||||
data,
|
||||
req,
|
||||
originalDoc,
|
||||
hook: 'beforeUpdate',
|
||||
operationName: 'update',
|
||||
});
|
||||
|
||||
// /////////////////////////////////////
|
||||
// 5. Upload and resize any files that may be present
|
||||
// /////////////////////////////////////
|
||||
@@ -106,21 +106,23 @@ async function update(args) {
|
||||
|
||||
const { staticDir, imageSizes } = collectionConfig.upload;
|
||||
|
||||
if (req.files && req.files.file) {
|
||||
const fsSafeName = await getSafeFilename(staticDir, req.files.file.name);
|
||||
const file = (req.files && req.files.file) ? req.files.file : req.fileData;
|
||||
|
||||
await req.files.file.mv(`${staticDir}/${fsSafeName}`);
|
||||
if (file) {
|
||||
const fsSafeName = await getSafeFilename(staticDir, file.name);
|
||||
|
||||
await file.mv(`${staticDir}/${fsSafeName}`);
|
||||
|
||||
fileData.filename = fsSafeName;
|
||||
fileData.filesize = req.files.file.size;
|
||||
fileData.mimeType = req.files.file.mimetype;
|
||||
fileData.filesize = file.size;
|
||||
fileData.mimeType = file.mimetype;
|
||||
|
||||
if (imageMIMETypes.indexOf(req.files.file.mimetype) > -1) {
|
||||
if (imageMIMETypes.indexOf(file.mimetype) > -1) {
|
||||
const dimensions = await getImageSize(`${staticDir}/${fsSafeName}`);
|
||||
fileData.width = dimensions.width;
|
||||
fileData.height = dimensions.height;
|
||||
|
||||
if (Array.isArray(imageSizes) && req.files.file.mimetype !== 'image/svg+xml') {
|
||||
if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') {
|
||||
fileData.sizes = await resizeAndSave(collectionConfig, fsSafeName, fileData.mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,14 @@ const sanitizeCollection = (collections, collection) => {
|
||||
{
|
||||
name: 'filename',
|
||||
label: 'Filename',
|
||||
hooks: {
|
||||
beforeCreate: [
|
||||
({ req }) => {
|
||||
const file = (req.files && req.files.file) ? req.files.file : req.file;
|
||||
return file.name;
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
|
||||
@@ -2,7 +2,7 @@ const httpStatus = require('http-status');
|
||||
const formatErrorResponse = require('../responses/formatError');
|
||||
const logger = require('../../utilities/logger')();
|
||||
|
||||
const errorHandler = (config) => async (err, req, res) => {
|
||||
const errorHandler = (config) => async (err, req, res, next) => {
|
||||
const data = formatErrorResponse(err);
|
||||
let response;
|
||||
let status = err.status || httpStatus.INTERNAL_SERVER_ERROR;
|
||||
|
||||
@@ -24,17 +24,18 @@ const middleware = (payload) => [
|
||||
}),
|
||||
(req, _, next) => {
|
||||
req.payload = payload;
|
||||
return next();
|
||||
next();
|
||||
},
|
||||
(req, res, next) => {
|
||||
if (payload.config.cors) {
|
||||
if (payload.config.cors.indexOf(req.headers.origin) > -1) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
|
||||
}
|
||||
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Origin X-Requested-With, Content-Type, Accept, Authorization');
|
||||
|
||||
res.header('Access-Control-Allow-Headers',
|
||||
'Origin X-Requested-With, Content-Type, Accept, Authorization');
|
||||
if (payload.config.cors === '*') {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
} else if (Array.isArray(payload.config.cors) && payload.config.cors.indexOf(req.headers.origin) > -1) {
|
||||
res.setHeader('Access-Control-Allow-Origin', req.headers.origin);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
|
||||
@@ -73,12 +73,12 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
const hookPromises = [];
|
||||
const errors = [];
|
||||
|
||||
const createValidationPromise = async (newValue, existingValue, field, path) => {
|
||||
const createValidationPromise = async (newData, existingData, field, path) => {
|
||||
const hasCondition = field.admin && field.admin.condition;
|
||||
const shouldValidate = field.validate && !hasCondition;
|
||||
|
||||
let valueToValidate = newValue;
|
||||
if (valueToValidate === undefined) valueToValidate = existingValue;
|
||||
let valueToValidate = newData[field.name];
|
||||
if (valueToValidate === undefined) valueToValidate = existingData[field.name];
|
||||
if (valueToValidate === undefined) valueToValidate = field.defaultValue;
|
||||
|
||||
const result = shouldValidate ? await field.validate(valueToValidate, field) : true;
|
||||
@@ -131,9 +131,13 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
}
|
||||
};
|
||||
|
||||
const createHookPromise = async (data, field) => {
|
||||
const createHookPromise = async (data, originalDoc, field) => {
|
||||
const resultingData = data;
|
||||
|
||||
if ((field.type === 'relationship' || field.type === 'upload') && (data[field.name] === 'null' || data[field.name] === null)) {
|
||||
resultingData[field.name] = null;
|
||||
}
|
||||
|
||||
if (hook === 'afterRead') {
|
||||
if ((field.type === 'relationship' || field.type === 'upload')) {
|
||||
const hasManyRelations = Array.isArray(field.relationTo);
|
||||
@@ -235,14 +239,16 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
}
|
||||
|
||||
if (field.hooks && field.hooks[hook]) {
|
||||
field.hooks[hook].forEach(async (fieldHook) => {
|
||||
resultingData[field.name] = await fieldHook({
|
||||
await field.hooks[hook].reduce(async (priorHook, currentHook) => {
|
||||
await priorHook;
|
||||
|
||||
resultingData[field.name] = await currentHook({
|
||||
value: data[field.name],
|
||||
originalDoc: fullOriginalDoc,
|
||||
data: fullData,
|
||||
req,
|
||||
});
|
||||
});
|
||||
}) || resultingData[field.name];
|
||||
}, Promise.resolve());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -262,7 +268,7 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
}
|
||||
|
||||
accessPromises.push(createAccessPromise(data, originalDoc, field));
|
||||
hookPromises.push(createHookPromise(data, field));
|
||||
hookPromises.push(createHookPromise(data, originalDoc, field));
|
||||
|
||||
if (field.fields) {
|
||||
if (field.name === undefined) {
|
||||
@@ -293,9 +299,9 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
const hasRowsOfExistingData = Array.isArray(originalDoc[field.name]);
|
||||
const existingRowCount = hasRowsOfExistingData ? originalDoc[field.name].length : 0;
|
||||
|
||||
validationPromises.push(createValidationPromise(newRowCount, existingRowCount, field, path));
|
||||
validationPromises.push(() => createValidationPromise({ [field.name]: newRowCount }, { [field.name]: existingRowCount }, field, path));
|
||||
} else if (field.name) {
|
||||
validationPromises.push(createValidationPromise(data[field.name], originalDoc[field.name], field, path));
|
||||
validationPromises.push(() => createValidationPromise(data, originalDoc, field, path, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -306,6 +312,11 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
// //////////////////////////////////////////
|
||||
|
||||
traverseFields(entityConfig.fields, fullData, fullOriginalDoc, '');
|
||||
|
||||
await Promise.all(hookPromises);
|
||||
|
||||
validationPromises.forEach((promise) => promise());
|
||||
|
||||
await Promise.all(validationPromises);
|
||||
|
||||
if (errors.length > 0) {
|
||||
@@ -314,7 +325,6 @@ async function performFieldOperations(entityConfig, operation) {
|
||||
|
||||
await Promise.all(accessPromises);
|
||||
await Promise.all(relationshipPopulationPromises);
|
||||
await Promise.all(hookPromises);
|
||||
|
||||
return fullData;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user