Compare commits
125 Commits
bundler-we
...
richtext-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94de5c6c24 | ||
|
|
5d4ef620b1 | ||
|
|
cac33ac275 | ||
|
|
219aa3b2f3 | ||
|
|
dd0ff50621 | ||
|
|
591c0a0786 | ||
|
|
a197161390 | ||
|
|
ae5b397bc8 | ||
|
|
e6b2d3d1fc | ||
|
|
6b19525a65 | ||
|
|
c86ae0a9d2 | ||
|
|
9277b306de | ||
|
|
156eae2551 | ||
|
|
e197e0316f | ||
|
|
863b79348b | ||
|
|
30ff65d0b4 | ||
|
|
3185771551 | ||
|
|
ea83c3f3a2 | ||
|
|
072f7febd2 | ||
|
|
b5c7bbed93 | ||
|
|
931f6ff519 | ||
|
|
0af36af16c | ||
|
|
f6adbae0c7 | ||
|
|
67d61df563 | ||
|
|
01380bebe5 | ||
|
|
5719b1b39a | ||
|
|
f259645488 | ||
|
|
eec60d5883 | ||
|
|
d4548d73d5 | ||
|
|
ccc7c51c90 | ||
|
|
ec5e35ff71 | ||
|
|
55b9bf40df | ||
|
|
5282673746 | ||
|
|
298ca0b7ae | ||
|
|
71407e19e2 | ||
|
|
09078bdb40 | ||
|
|
e84f5ded28 | ||
|
|
a475b9b28b | ||
|
|
baad7d3360 | ||
|
|
7fcb972dfa | ||
|
|
01245b07f8 | ||
|
|
d2f45343da | ||
|
|
5ba95df674 | ||
|
|
b0a62442e5 | ||
|
|
927a1ab049 | ||
|
|
f23ae28d45 | ||
|
|
36740b70d4 | ||
|
|
5d1677a84e | ||
|
|
4fab26db9d | ||
|
|
56cf767e18 | ||
|
|
0a45389a25 | ||
|
|
2abdce31f8 | ||
|
|
0221394c06 | ||
|
|
741ab0487d | ||
|
|
8a513ba7af | ||
|
|
dfb9a93547 | ||
|
|
a2e336470a | ||
|
|
f6994e57dd | ||
|
|
bfe8de3fd6 | ||
|
|
bd16e9fb53 | ||
|
|
8ddbb67f07 | ||
|
|
60c14557ff | ||
|
|
a38b43dc4f | ||
|
|
12e85f654e | ||
|
|
c02463be69 | ||
|
|
1b6d0cf4da | ||
|
|
e59e6ed65e | ||
|
|
d6a11921e0 | ||
|
|
573c8de380 | ||
|
|
e7ac1819ce | ||
|
|
288ff2b094 | ||
|
|
aca534ec59 | ||
|
|
a8951cb741 | ||
|
|
7f9dd2b4e1 | ||
|
|
07b970027d | ||
|
|
71f6542341 | ||
|
|
c90830f961 | ||
|
|
d46d2c0595 | ||
|
|
16d6c26387 | ||
|
|
32df3067e1 | ||
|
|
3a7440dcb9 | ||
|
|
417f4b7aa9 | ||
|
|
822aec0a5c | ||
|
|
455622fa57 | ||
|
|
f93316e588 | ||
|
|
b7e65d1024 | ||
|
|
b5728104dd | ||
|
|
604197bb98 | ||
|
|
6b30a9702b | ||
|
|
ab974ee587 | ||
|
|
3a9efb21e0 | ||
|
|
2dd395f718 | ||
|
|
2df28355cf | ||
|
|
7607c17041 | ||
|
|
f81b4d3a1b | ||
|
|
8305b65b98 | ||
|
|
275d15cfdc | ||
|
|
c09667edfc | ||
|
|
2cbb14f8dd | ||
|
|
936c125a42 | ||
|
|
5a8cdef103 | ||
|
|
26bc1b46c1 | ||
|
|
639a832600 | ||
|
|
ba4d751831 | ||
|
|
32a0972855 | ||
|
|
d354610978 | ||
|
|
97bd414d3d | ||
|
|
9f396598a0 | ||
|
|
c2e20277ec | ||
|
|
7e6f35f380 | ||
|
|
750646b3b8 | ||
|
|
eef80a8239 | ||
|
|
339fb96b7d | ||
|
|
fe8254c73d | ||
|
|
aef868f471 | ||
|
|
8e02db10ae | ||
|
|
44dd66cb72 | ||
|
|
713c6738aa | ||
|
|
f70a7b80fc | ||
|
|
32665d11c5 | ||
|
|
1ed4c096a3 | ||
|
|
339ab3a838 | ||
|
|
cc9f9dd704 | ||
|
|
c13acfe47a | ||
|
|
715e13b78e |
60
.github/workflows/main.yml
vendored
60
.github/workflows/main.yml
vendored
@@ -7,7 +7,36 @@ on:
|
||||
branches: ['main']
|
||||
|
||||
jobs:
|
||||
changes:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: read
|
||||
outputs:
|
||||
needs_build: ${{ steps.filter.outputs.needs_build }}
|
||||
templates: ${{ steps.filter.outputs.templates }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
needs_build:
|
||||
- 'packages/**'
|
||||
- 'test/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'package.json'
|
||||
templates:
|
||||
- 'templates/**'
|
||||
- name: Log all filter results
|
||||
run: |
|
||||
echo "needs_build: ${{ steps.filter.outputs.needs_build }}"
|
||||
echo "templates: ${{ steps.filter.outputs.templates }}"
|
||||
|
||||
core-build:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.needs_build == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -246,3 +275,34 @@ jobs:
|
||||
- name: Test ${{ matrix.pkg }}
|
||||
run: pnpm --filter ${{ matrix.pkg }} run test
|
||||
if: matrix.pkg != 'create-payload-app' # degit doesn't work within GitHub Actions
|
||||
|
||||
templates:
|
||||
needs: changes
|
||||
if: ${{ needs.changes.outputs.templates == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
template: [blank, website, ecommerce]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 25
|
||||
|
||||
- name: Use Node.js 18
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.10.0
|
||||
with:
|
||||
mongodb-version: 6.0
|
||||
|
||||
- name: Build Website
|
||||
run: |
|
||||
cd templates/${{ matrix.template }}
|
||||
cp .env.example .env
|
||||
yarn install
|
||||
yarn build
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,3 +1,25 @@
|
||||
## [2.0.11](https://github.com/payloadcms/payload/compare/v2.0.10...v2.0.11) (2023-10-19)
|
||||
|
||||
### Features
|
||||
|
||||
* add ability to opt out of type gen declare statement ([#3765](https://github.com/payloadcms/payload/pull/3765))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* corrects versions collection casing ([#3739](https://github.com/payloadcms/payload/pull/3739))
|
||||
* updates req after file resize ([#3754](https://github.com/payloadcms/payload/pull/3754))
|
||||
* correctly renders focal point when crop is set to false ([#3759](https://github.com/payloadcms/payload/pull/3759))
|
||||
* account for many slug types in generate types ([#3698](https://github.com/payloadcms/payload/pull/3698))
|
||||
* handle graphQL: false on globals when building policy type ([#3729](https://github.com/payloadcms/payload/pull/3729))
|
||||
* renders id as fallback title in DeleteDocument ([#3745](https://github.com/payloadcms/payload/pull/3745))
|
||||
* properly handles hideAPIURL ([#3721](https://github.com/payloadcms/payload/pull/3721))
|
||||
* filesRequiredOnCreate typing, tests, linting ([#3737](https://github.com/payloadcms/payload/pull/3737))
|
||||
|
||||
* **webpack-bundler:** corrects payload alias ([#3769](https://github.com/payloadcms/payload/pull/3769))
|
||||
* **bundler-webpack:** better node_modules resolution ([#3744](https://github.com/payloadcms/payload/pull/3744))
|
||||
* **db-postgres:** block and array inserts error ([#3714](https://github.com/payloadcms/payload/pull/3714))
|
||||
* **live-preview:** properly handles uploads and hasOne monomorphic relationships ([#3719](https://github.com/payloadcms/payload/pull/3719))
|
||||
|
||||
## [2.0.10](https://github.com/payloadcms/payload/compare/v2.0.9...v2.0.10) (2023-10-17)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -62,7 +62,7 @@ All field-level hooks are formatted to accept the same arguments, although some
|
||||
Field Hooks receive one `args` argument that contains the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. |
|
||||
| **`siblingData`** | The sibling data passed to a field that the hook is running against. |
|
||||
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. |
|
||||
@@ -73,6 +73,10 @@ Field Hooks receive one `args` argument that contains the following properties:
|
||||
| **`req`** | The Express `request` object. It is mocked for Local API operations. |
|
||||
| **`value`** | The value of the field. |
|
||||
| **`previousValue`** | The previous value of the field, before changes were applied, only in `afterChange` hooks. |
|
||||
| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) |
|
||||
| **`field`** | The field which the hook is running against. |
|
||||
| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. |
|
||||
| **`global`** | The global which the field belongs to. If the field belongs to a collection, this will be null. |
|
||||
|
||||
#### Return value
|
||||
|
||||
|
||||
@@ -21,16 +21,6 @@ export default buildConfig({
|
||||
components: {
|
||||
beforeLogin: [BeforeLogin],
|
||||
},
|
||||
webpack: config => ({
|
||||
...config,
|
||||
resolve: {
|
||||
...config.resolve,
|
||||
alias: {
|
||||
...config.resolve.alias,
|
||||
dotenv: path.resolve(__dirname, './dotenv.js'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
editor: slateEditor({}),
|
||||
db: mongooseAdapter({
|
||||
|
||||
@@ -29,7 +29,7 @@ const start = async (): Promise<void> => {
|
||||
app.listen(PORT, async () => {
|
||||
payload.logger.info(`Next.js is now building...`)
|
||||
// @ts-expect-error
|
||||
await nextBuild(path.join(__dirname, '../'))
|
||||
await nextBuild(path.join(__dirname, '..'))
|
||||
process.exit()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.4",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.9",
|
||||
"version": "0.1.10",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "The official live preview React SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.5",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -6,7 +6,7 @@ export type MergeLiveDataArgs<T> = {
|
||||
apiRoute?: string
|
||||
depth: number
|
||||
fieldSchema: ReturnType<typeof fieldSchemaToJSON>
|
||||
incomingData: T
|
||||
incomingData: Partial<T>
|
||||
initialData: T
|
||||
serverURL: string
|
||||
}
|
||||
|
||||
@@ -53,34 +53,36 @@ export const traverseFields = <T>({
|
||||
|
||||
case 'blocks':
|
||||
if (Array.isArray(incomingData[fieldName])) {
|
||||
result[fieldName] = incomingData[fieldName].map((row, i) => {
|
||||
const matchedBlock = fieldJSON.blocks[row.blockType]
|
||||
result[fieldName] = incomingData[fieldName].map((incomingBlock, i) => {
|
||||
const incomingBlockJSON = fieldJSON.blocks[incomingBlock.blockType]
|
||||
|
||||
const hasExistingRow =
|
||||
// Compare the index and id to determine if this block already exists in the result
|
||||
// If so, we want to use the existing block as the base, otherwise take the incoming block
|
||||
// Either way, we will traverse the fields of the block to populate relationships
|
||||
const isExistingBlock =
|
||||
Array.isArray(result[fieldName]) &&
|
||||
typeof result[fieldName][i] === 'object' &&
|
||||
result[fieldName][i] !== null &&
|
||||
result[fieldName][i].blockType === row.blockType
|
||||
result[fieldName][i].id === incomingBlock.id
|
||||
|
||||
const newRow = hasExistingRow
|
||||
? { ...result[fieldName][i] }
|
||||
: {
|
||||
blockType: matchedBlock.slug,
|
||||
}
|
||||
const block = isExistingBlock ? result[fieldName][i] : incomingBlock
|
||||
|
||||
traverseFields({
|
||||
apiRoute,
|
||||
depth,
|
||||
fieldSchema: matchedBlock.fields,
|
||||
incomingData: row,
|
||||
fieldSchema: incomingBlockJSON.fields,
|
||||
incomingData: incomingBlock,
|
||||
populationPromises,
|
||||
result: newRow,
|
||||
result: block,
|
||||
serverURL,
|
||||
})
|
||||
|
||||
return newRow
|
||||
return block
|
||||
})
|
||||
} else {
|
||||
result[fieldName] = []
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case 'tabs':
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.0.11",
|
||||
"version": "2.0.12",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
@@ -130,7 +130,7 @@
|
||||
"sass": "1.69.4",
|
||||
"scheduler": "0.23.0",
|
||||
"scmp": "2.1.0",
|
||||
"sharp": "0.31.3",
|
||||
"sharp": "0.32.6",
|
||||
"swc-loader": "0.2.3",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-essentials": "7.0.3",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.global-default-edit {
|
||||
.document-fields {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
--doc-sidebar-width: 325px;
|
||||
|
||||
&--has-sidebar {
|
||||
.global-default-edit {
|
||||
.document-fields {
|
||||
&__edit {
|
||||
[dir='ltr'] & {
|
||||
top: 0;
|
||||
@@ -46,10 +46,6 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__auth {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: var(--doc-controls-height);
|
||||
@@ -62,9 +58,6 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__sidebar-sticky-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
@@ -88,7 +81,7 @@
|
||||
display: block;
|
||||
|
||||
&--has-sidebar {
|
||||
.global-default-edit {
|
||||
.document-fields {
|
||||
&__main {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -124,8 +117,6 @@
|
||||
width: 100%;
|
||||
height: initial;
|
||||
border-left: 0;
|
||||
margin-top: calc(var(--base) / 2);
|
||||
width: var(--doc-sidebar-width);
|
||||
}
|
||||
|
||||
&__form {
|
||||
@@ -136,6 +127,7 @@
|
||||
padding-top: 0;
|
||||
padding-left: var(--gutter-h);
|
||||
padding-right: var(--gutter-h);
|
||||
padding-bottom: 0;
|
||||
gap: base(0.5);
|
||||
|
||||
[dir='ltr'] & {
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { CollectionPermission, GlobalPermission } from '../../../../auth'
|
||||
import type { FieldWithPath } from '../../../../fields/config/types'
|
||||
import type { Description } from '../../forms/FieldDescription/types'
|
||||
|
||||
import RenderFields from '../../forms/RenderFields'
|
||||
import { filterFields } from '../../forms/RenderFields/filterFields'
|
||||
import { fieldTypes } from '../../forms/field-types'
|
||||
import { Gutter } from '../Gutter'
|
||||
import ViewDescription from '../ViewDescription'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'document-fields'
|
||||
|
||||
export const DocumentFields: React.FC<{
|
||||
AfterFields?: React.FC
|
||||
BeforeFields?: React.FC
|
||||
description?: Description
|
||||
fields: FieldWithPath[]
|
||||
hasSavePermission: boolean
|
||||
permissions: CollectionPermission | GlobalPermission
|
||||
}> = (props) => {
|
||||
const { AfterFields, BeforeFields, description, fields, hasSavePermission, permissions } = props
|
||||
|
||||
const sidebarFields = filterFields({
|
||||
fieldSchema: fields,
|
||||
fieldTypes,
|
||||
filter: (field) => field?.admin?.position === 'sidebar',
|
||||
permissions: permissions.fields,
|
||||
readOnly: !hasSavePermission,
|
||||
})
|
||||
|
||||
const hasSidebar = sidebarFields && sidebarFields.length > 0
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div
|
||||
className={[
|
||||
baseClass,
|
||||
hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Gutter className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
{description && (
|
||||
<div className={`${baseClass}__sub-header`}>
|
||||
<ViewDescription description={description} />
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{BeforeFields && <BeforeFields />}
|
||||
<RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldSchema={fields}
|
||||
fieldTypes={fieldTypes}
|
||||
filter={(field) =>
|
||||
!field.admin.position ||
|
||||
(field.admin.position && field.admin.position !== 'sidebar')
|
||||
}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
{AfterFields && <AfterFields />}
|
||||
</Gutter>
|
||||
</div>
|
||||
{hasSidebar && (
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-fields`}>
|
||||
<RenderFields
|
||||
fieldTypes={fieldTypes}
|
||||
fields={sidebarFields}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
@@ -62,6 +62,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
|
||||
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||
data = await collection.admin.hooks.beforeDuplicate({
|
||||
collection,
|
||||
data,
|
||||
locale,
|
||||
})
|
||||
@@ -108,6 +109,7 @@ const Duplicate: React.FC<Props> = ({ id, collection, slug }) => {
|
||||
|
||||
if (typeof collection.admin.hooks?.beforeDuplicate === 'function') {
|
||||
localizedDoc = await collection.admin.hooks.beforeDuplicate({
|
||||
collection,
|
||||
data: localizedDoc,
|
||||
locale,
|
||||
})
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useWindowInfo } from '@faceless-ui/window-info'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import AnimateHeight from 'react-animate-height'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { fieldAffectsData } from '../../../../fields/config/types'
|
||||
import flattenFields from '../../../../utilities/flattenTopLevelFields'
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import Chevron from '../../icons/Chevron'
|
||||
import { useSearchParams } from '../../utilities/SearchParams'
|
||||
@@ -27,22 +25,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'list-controls'
|
||||
|
||||
const getUseAsTitle = (collection: SanitizedCollectionConfig) => {
|
||||
const {
|
||||
admin: { useAsTitle },
|
||||
fields,
|
||||
} = collection
|
||||
|
||||
const topLevelFields = flattenFields(fields)
|
||||
return topLevelFields.find((field) => fieldAffectsData(field) && field.name === useAsTitle)
|
||||
}
|
||||
|
||||
/**
|
||||
* The ListControls component is used to render the controls (search, filter, where)
|
||||
* for a collection's list view. You can find those directly above the table which lists
|
||||
* the collection's documents.
|
||||
*/
|
||||
const ListControls: React.FC<Props> = (props) => {
|
||||
export const ListControls: React.FC<Props> = (props) => {
|
||||
const {
|
||||
collection: {
|
||||
admin: { listSearchableFields },
|
||||
@@ -51,21 +39,20 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
collection,
|
||||
enableColumns = true,
|
||||
enableSort = false,
|
||||
handleSearchChange,
|
||||
handleSortChange,
|
||||
handleWhereChange,
|
||||
modifySearchQuery = true,
|
||||
resetParams,
|
||||
titleField,
|
||||
} = props
|
||||
|
||||
const params = useSearchParams()
|
||||
const shouldInitializeWhereOpened = validateWhereQuery(params?.where)
|
||||
|
||||
const [titleField, setTitleField] = useState(getUseAsTitle(collection))
|
||||
useEffect(() => {
|
||||
setTitleField(getUseAsTitle(collection))
|
||||
}, [collection])
|
||||
|
||||
const [textFieldsToBeSearched] = useState(getTextFieldsToBeSearched(listSearchableFields, fields))
|
||||
const [textFieldsToBeSearched, setFieldsToBeSearched] = useState(
|
||||
getTextFieldsToBeSearched(listSearchableFields, fields),
|
||||
)
|
||||
const [visibleDrawer, setVisibleDrawer] = useState<'columns' | 'sort' | 'where'>(
|
||||
shouldInitializeWhereOpened ? 'where' : undefined,
|
||||
)
|
||||
@@ -74,18 +61,19 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
breakpoints: { s: smallBreak },
|
||||
} = useWindowInfo()
|
||||
|
||||
React.useEffect(() => {
|
||||
setFieldsToBeSearched(getTextFieldsToBeSearched(listSearchableFields, fields))
|
||||
}, [listSearchableFields, fields])
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<SearchFilter
|
||||
fieldLabel={
|
||||
(titleField &&
|
||||
fieldAffectsData(titleField) &&
|
||||
getTranslation(titleField.label || titleField.name, i18n)) ??
|
||||
undefined
|
||||
(titleField && getTranslation(titleField.label || titleField.name, i18n)) ?? undefined
|
||||
}
|
||||
fieldName={titleField && fieldAffectsData(titleField) ? titleField.name : undefined}
|
||||
handleChange={handleWhereChange}
|
||||
handleChange={handleSearchChange}
|
||||
listSearchableFields={textFieldsToBeSearched}
|
||||
modifySearchQuery={modifySearchQuery}
|
||||
/>
|
||||
@@ -179,5 +167,3 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ListControls
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { FieldAffectingData } from '../../../../exports/types'
|
||||
import type { Where } from '../../../../types'
|
||||
import type { Props as ListProps } from '../../views/collections/List/types'
|
||||
import type { Column } from '../Table/types'
|
||||
@@ -7,10 +8,12 @@ export type Props = {
|
||||
collection: SanitizedCollectionConfig
|
||||
enableColumns?: boolean
|
||||
enableSort?: boolean
|
||||
handleSearchChange?: (search: string) => void
|
||||
handleSortChange?: (sort: string) => void
|
||||
handleWhereChange?: (where: Where) => void
|
||||
modifySearchQuery?: boolean
|
||||
resetParams?: ListProps['resetParams']
|
||||
titleField: FieldAffectingData
|
||||
}
|
||||
|
||||
export type ListControls = {
|
||||
|
||||
@@ -3,12 +3,14 @@ import React, { useCallback, useEffect, useReducer, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../../../../collections/config/types'
|
||||
import type { Where } from '../../../../exports/types'
|
||||
import type { Field } from '../../../../fields/config/types'
|
||||
import type { ListDrawerProps } from './types'
|
||||
|
||||
import { baseClass } from '.'
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import usePayloadAPI from '../../../hooks/usePayloadAPI'
|
||||
import { useUseTitleField } from '../../../hooks/useUseAsTitle'
|
||||
import Label from '../../forms/Label'
|
||||
import X from '../../icons/X'
|
||||
import { useAuth } from '../../utilities/Auth'
|
||||
@@ -24,6 +26,22 @@ import ReactSelect from '../ReactSelect'
|
||||
import { TableColumnsProvider } from '../TableColumns'
|
||||
import ViewDescription from '../ViewDescription'
|
||||
|
||||
const hoistQueryParamsToAnd = (where: Where, queryParams: Where) => {
|
||||
if ('and' in where) {
|
||||
where.and.push(queryParams)
|
||||
} else if ('or' in where) {
|
||||
where = {
|
||||
and: [where, queryParams],
|
||||
}
|
||||
} else {
|
||||
where = {
|
||||
and: [where, queryParams],
|
||||
}
|
||||
}
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
collectionSlugs,
|
||||
customHeader,
|
||||
@@ -40,6 +58,8 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
const [sort, setSort] = useState(null)
|
||||
const [page, setPage] = useState(1)
|
||||
const [where, setWhere] = useState(null)
|
||||
const [search, setSearch] = useState('')
|
||||
|
||||
const {
|
||||
collections,
|
||||
routes: { api },
|
||||
@@ -69,6 +89,8 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
|
||||
const [fields, setFields] = useState<Field[]>(() => formatFields(selectedCollectionConfig))
|
||||
|
||||
const titleField = useUseTitleField(selectedCollectionConfig)
|
||||
|
||||
useEffect(() => {
|
||||
setFields(formatFields(selectedCollectionConfig))
|
||||
}, [selectedCollectionConfig])
|
||||
@@ -111,31 +133,58 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
const moreThanOneAvailableCollection = enabledCollectionConfigs.length > 1
|
||||
|
||||
useEffect(() => {
|
||||
const { admin: { listSearchableFields } = {}, slug } = selectedCollectionConfig
|
||||
const params: {
|
||||
cacheBust?: number
|
||||
limit?: number
|
||||
page?: number
|
||||
search?: string
|
||||
sort?: string
|
||||
where?: unknown
|
||||
} = {}
|
||||
|
||||
if (page) params.page = page
|
||||
let copyOfWhere = { ...(where || {}) }
|
||||
|
||||
params.where = {
|
||||
...(where ? { ...where } : {}),
|
||||
...(filterOptions?.[selectedCollectionConfig.slug]
|
||||
? {
|
||||
...filterOptions[selectedCollectionConfig.slug],
|
||||
}
|
||||
: {}),
|
||||
if (filterOptions) {
|
||||
copyOfWhere = hoistQueryParamsToAnd(copyOfWhere, filterOptions[slug])
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const searchAsConditions = (listSearchableFields || [titleField?.name]).map((fieldName) => {
|
||||
return {
|
||||
[fieldName]: {
|
||||
like: search,
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (searchAsConditions.length > 0) {
|
||||
const searchFilter: Where = {
|
||||
or: [...searchAsConditions],
|
||||
}
|
||||
|
||||
copyOfWhere = hoistQueryParamsToAnd(copyOfWhere, searchFilter)
|
||||
}
|
||||
}
|
||||
|
||||
if (page) params.page = page
|
||||
if (sort) params.sort = sort
|
||||
if (limit) params.limit = limit
|
||||
if (cacheBust) params.cacheBust = cacheBust
|
||||
if (copyOfWhere) params.where = copyOfWhere
|
||||
|
||||
setParams(params)
|
||||
}, [setParams, page, sort, where, limit, cacheBust, filterOptions, selectedCollectionConfig])
|
||||
}, [
|
||||
page,
|
||||
sort,
|
||||
where,
|
||||
search,
|
||||
cacheBust,
|
||||
filterOptions,
|
||||
selectedCollectionConfig,
|
||||
t,
|
||||
setParams,
|
||||
titleField?.name,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const newPreferences = {
|
||||
@@ -241,6 +290,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
data,
|
||||
handlePageChange: setPage,
|
||||
handlePerPageChange: setLimit,
|
||||
handleSearchChange: setSearch,
|
||||
handleSortChange: setSort,
|
||||
handleWhereChange: setWhere,
|
||||
hasCreatePermission,
|
||||
@@ -249,6 +299,7 @@ export const ListDrawerContent: React.FC<ListDrawerProps> = ({
|
||||
newDocumentURL: null,
|
||||
setLimit,
|
||||
setSort,
|
||||
titleField,
|
||||
}}
|
||||
/>
|
||||
</DocumentInfoProvider>
|
||||
|
||||
@@ -16,11 +16,12 @@ export const MultiValue: React.FC<MultiValueProps<Option>> = (props) => {
|
||||
innerProps,
|
||||
isDisabled,
|
||||
// @ts-expect-error // TODO Fix this - moduleResolution 16 breaks our declare module
|
||||
selectProps: { customProps: { disableMouseDown } = {} } = {},
|
||||
selectProps: { customProps: { disableMouseDown } = {}, isSortable } = {},
|
||||
} = props
|
||||
|
||||
const { attributes, isDragging, listeners, setNodeRef, transform } = useDraggableSortable({
|
||||
id: value.toString(),
|
||||
disabled: !isSortable,
|
||||
})
|
||||
|
||||
const classes = [
|
||||
|
||||
@@ -33,6 +33,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
components,
|
||||
customProps,
|
||||
disabled = false,
|
||||
filterOption = undefined,
|
||||
isClearable = true,
|
||||
@@ -45,7 +46,6 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
onMenuOpen,
|
||||
options,
|
||||
placeholder = t('general:selectValue'),
|
||||
selectProps,
|
||||
showError,
|
||||
value,
|
||||
} = props
|
||||
@@ -58,7 +58,7 @@ const SelectAdapter: React.FC<ReactSelectAdapterProps> = (props) => {
|
||||
return (
|
||||
<Select
|
||||
captureMenuScroll
|
||||
customProps={selectProps}
|
||||
customProps={customProps}
|
||||
isLoading={isLoading}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
{...props}
|
||||
|
||||
@@ -78,10 +78,6 @@ export type Props = {
|
||||
onMenuScrollToBottom?: () => void
|
||||
options: Option[] | OptionGroup[]
|
||||
placeholder?: string
|
||||
/**
|
||||
* @deprecated Since version 1.0. Will be deleted in version 2.0. Use customProps instead.
|
||||
*/
|
||||
selectProps?: CustomSelectProps
|
||||
showError?: boolean
|
||||
value?: Option | Option[]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
|
||||
import type { Where, WhereField } from '../../../../types'
|
||||
import type { Props } from './types'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
@@ -27,7 +26,7 @@ const SearchFilter: React.FC<Props> = (props) => {
|
||||
const history = useHistory()
|
||||
const { i18n, t } = useTranslation('general')
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const [search, setSearch] = useState(typeof params?.search === 'string' ? params?.search : '')
|
||||
const [previousSearch, setPreviousSearch] = useState('')
|
||||
|
||||
const placeholder = useRef(t('searchBy', { label: getTranslation(fieldLabel, i18n) }))
|
||||
@@ -35,48 +34,15 @@ const SearchFilter: React.FC<Props> = (props) => {
|
||||
const debouncedSearch = useDebounce(search, 300)
|
||||
|
||||
useEffect(() => {
|
||||
const newWhere: Where = {
|
||||
...(typeof params?.where === 'object' ? (params.where as Where) : {}),
|
||||
}
|
||||
const fieldNamesToSearch =
|
||||
listSearchableFields?.length > 0
|
||||
? [...listSearchableFields.map(({ name }) => name)]
|
||||
: [fieldName]
|
||||
|
||||
fieldNamesToSearch.forEach((fieldNameToSearch) => {
|
||||
const hasOrQuery = Array.isArray(newWhere.or)
|
||||
const existingFieldSearchIndex = hasOrQuery
|
||||
? newWhere.or.findIndex((condition) => {
|
||||
return (condition?.[fieldNameToSearch] as WhereField)?.like
|
||||
})
|
||||
: -1
|
||||
if (debouncedSearch) {
|
||||
if (!hasOrQuery) newWhere.or = []
|
||||
|
||||
if (existingFieldSearchIndex > -1) {
|
||||
;(newWhere.or[existingFieldSearchIndex][fieldNameToSearch] as WhereField).like =
|
||||
debouncedSearch
|
||||
} else {
|
||||
newWhere.or.push({
|
||||
[fieldNameToSearch]: {
|
||||
like: debouncedSearch,
|
||||
},
|
||||
})
|
||||
}
|
||||
} else if (existingFieldSearchIndex > -1) {
|
||||
newWhere.or.splice(existingFieldSearchIndex, 1)
|
||||
}
|
||||
})
|
||||
|
||||
if (debouncedSearch !== previousSearch) {
|
||||
if (handleChange) handleChange(newWhere)
|
||||
if (handleChange) handleChange(debouncedSearch)
|
||||
|
||||
if (modifySearchQuery) {
|
||||
history.replace({
|
||||
search: queryString.stringify({
|
||||
...params,
|
||||
page: 1,
|
||||
where: newWhere,
|
||||
search: debouncedSearch || undefined,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import type { FieldAffectingData } from '../../../../fields/config/types'
|
||||
import type { Where } from '../../../../types'
|
||||
|
||||
export type Props = {
|
||||
fieldLabel?: string
|
||||
fieldName?: string
|
||||
handleChange?: (where: Where) => void
|
||||
handleChange?: (search: string) => void
|
||||
listSearchableFields?: FieldAffectingData[]
|
||||
modifySearchQuery?: boolean
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ const Condition: React.FC<Props> = (props) => {
|
||||
onChange={(field) =>
|
||||
dispatch({
|
||||
andIndex,
|
||||
field: field.value,
|
||||
field: field?.value || undefined,
|
||||
orIndex,
|
||||
type: 'update',
|
||||
})
|
||||
|
||||
@@ -12,7 +12,17 @@ export type RichTextFieldProps<Value extends object, AdapterProps> = Omit<
|
||||
export type RichTextAdapter<Value extends object = object, AdapterProps = any> = {
|
||||
CellComponent: React.FC<CellComponentProps<RichTextField<Value, AdapterProps>>>
|
||||
FieldComponent: React.FC<RichTextFieldProps<Value, AdapterProps>>
|
||||
afterReadPromise?: (data: {
|
||||
afterReadPromise?: ({
|
||||
field,
|
||||
incomingEditorState,
|
||||
siblingDoc,
|
||||
}: {
|
||||
field: RichTextField<Value, AdapterProps>
|
||||
incomingEditorState: Value
|
||||
siblingDoc: Record<string, unknown>
|
||||
}) => Promise<void> | null
|
||||
|
||||
populationPromise?: (data: {
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
field: RichTextField<Value, AdapterProps>
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Translation } from '../../../../translations/type'
|
||||
import type { CollectionEditViewProps } from '../types'
|
||||
|
||||
import { DocumentControls } from '../../elements/DocumentControls'
|
||||
import { DocumentFields } from '../../elements/DocumentFields'
|
||||
import { DocumentHeader } from '../../elements/DocumentHeader'
|
||||
import { Gutter } from '../../elements/Gutter'
|
||||
import { LoadingOverlayToggle } from '../../elements/Loading'
|
||||
import ReactSelect from '../../elements/ReactSelect'
|
||||
import Form from '../../forms/Form'
|
||||
import Label from '../../forms/Label'
|
||||
import RenderFields from '../../forms/RenderFields'
|
||||
import { fieldTypes } from '../../forms/field-types'
|
||||
import { LeaveWithoutSaving } from '../../modals/LeaveWithoutSaving'
|
||||
import { useAuth } from '../../utilities/Auth'
|
||||
import Meta from '../../utilities/Meta'
|
||||
import { OperationContext } from '../../utilities/OperationProvider'
|
||||
import Auth from '../collections/Edit/Auth'
|
||||
import { ToggleTheme } from './ToggleTheme'
|
||||
import { Settings } from './Settings'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'account'
|
||||
@@ -39,14 +34,7 @@ const DefaultAccount: React.FC<CollectionEditViewProps> = (props) => {
|
||||
const { auth, fields } = collection
|
||||
|
||||
const { refreshCookieAsync } = useAuth()
|
||||
const { i18n, t } = useTranslation('authentication')
|
||||
|
||||
const languageOptions = Object.entries(i18n.options.resources || {}).map(
|
||||
([language, resource]) => ({
|
||||
label: (resource as Translation).general.thisLanguage,
|
||||
value: language,
|
||||
}),
|
||||
)
|
||||
const { t } = useTranslation('authentication')
|
||||
|
||||
const onSave = useCallback(async () => {
|
||||
await refreshCookieAsync()
|
||||
@@ -55,91 +43,49 @@ const DefaultAccount: React.FC<CollectionEditViewProps> = (props) => {
|
||||
}
|
||||
}, [onSaveFromProps, refreshCookieAsync])
|
||||
|
||||
const classes = [baseClass].filter(Boolean).join(' ')
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Meta description={t('accountOfCurrentUser')} keywords={t('account')} title={t('account')} />
|
||||
<LoadingOverlayToggle name="account" show={isLoading} type="withoutNav" />
|
||||
{!isLoading && (
|
||||
<div className={classes}>
|
||||
<OperationContext.Provider value="update">
|
||||
<Form
|
||||
action={action}
|
||||
className={`${baseClass}__form`}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
method="patch"
|
||||
onSuccess={onSave}
|
||||
>
|
||||
<DocumentHeader apiURL={apiURL} collection={collection} data={data} />
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
collection={collection}
|
||||
data={data}
|
||||
hasSavePermission={hasSavePermission}
|
||||
isAccountView
|
||||
permissions={permissions}
|
||||
/>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Meta
|
||||
description={t('accountOfCurrentUser')}
|
||||
keywords={t('account')}
|
||||
title={t('account')}
|
||||
<OperationContext.Provider value="update">
|
||||
<Form
|
||||
action={action}
|
||||
disabled={!hasSavePermission}
|
||||
initialState={initialState}
|
||||
method="patch"
|
||||
onSuccess={onSave}
|
||||
>
|
||||
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && (
|
||||
<LeaveWithoutSaving />
|
||||
)}
|
||||
<DocumentHeader apiURL={apiURL} collection={collection} data={data} />
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
collection={collection}
|
||||
data={data}
|
||||
hasSavePermission={hasSavePermission}
|
||||
isAccountView
|
||||
permissions={permissions}
|
||||
/>
|
||||
<DocumentFields
|
||||
AfterFields={() => <Settings className={`${baseClass}__settings`} />}
|
||||
BeforeFields={() => (
|
||||
<Auth
|
||||
className={`${baseClass}__auth`}
|
||||
collection={collection}
|
||||
email={data?.email}
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
useAPIKey={auth.useAPIKey}
|
||||
/>
|
||||
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) && (
|
||||
<LeaveWithoutSaving />
|
||||
)}
|
||||
<div className={`${baseClass}__edit`}>
|
||||
<Gutter className={`${baseClass}__header`}>
|
||||
<Auth
|
||||
className={`${baseClass}__auth`}
|
||||
collection={collection}
|
||||
email={data?.email}
|
||||
operation="update"
|
||||
readOnly={!hasSavePermission}
|
||||
useAPIKey={auth.useAPIKey}
|
||||
/>
|
||||
<RenderFields
|
||||
fieldSchema={fields}
|
||||
fieldTypes={fieldTypes}
|
||||
filter={(field) => field?.admin?.position !== 'sidebar'}
|
||||
permissions={permissions?.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</Gutter>
|
||||
<Gutter className={`${baseClass}__payload-settings`}>
|
||||
<h3>{t('general:payloadSettings')}</h3>
|
||||
<div className={`${baseClass}__language`}>
|
||||
<Label htmlFor="language-select" label={t('general:language')} />
|
||||
<ReactSelect
|
||||
inputId="language-select"
|
||||
onChange={({ value }) => i18n.changeLanguage(value)}
|
||||
options={languageOptions}
|
||||
value={languageOptions.find((language) => language.value === i18n.language)}
|
||||
/>
|
||||
</div>
|
||||
<ToggleTheme />
|
||||
</Gutter>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__sidebar-fields`}>
|
||||
<RenderFields
|
||||
fieldSchema={fields}
|
||||
fieldTypes={fieldTypes}
|
||||
filter={(field) => field?.admin?.position === 'sidebar'}
|
||||
permissions={permissions?.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</OperationContext.Provider>
|
||||
</div>
|
||||
)}
|
||||
fields={fields}
|
||||
hasSavePermission={hasSavePermission}
|
||||
permissions={permissions}
|
||||
/>
|
||||
</Form>
|
||||
</OperationContext.Provider>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
|
||||
.payload-settings {
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 1px;
|
||||
background: var(--theme-elevation-100);
|
||||
width: calc(100% + calc(var(--base) * 5));
|
||||
left: calc(var(--gutter-h) * -1);
|
||||
top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
bottom: 0;
|
||||
top: unset;
|
||||
}
|
||||
|
||||
margin-top: base(3);
|
||||
padding-top: base(3);
|
||||
padding-bottom: base(3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
|
||||
@include mid-break {
|
||||
margin-bottom: var(--base);
|
||||
padding-top: base(2);
|
||||
margin-top: base(2);
|
||||
padding-bottom: base(2);
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import type { Translation } from '../../../../../translations/type'
|
||||
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import Label from '../../../forms/Label'
|
||||
import { ToggleTheme } from '../ToggleTheme'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'payload-settings'
|
||||
|
||||
export const Settings: React.FC<{
|
||||
className?: string
|
||||
}> = (props) => {
|
||||
const { className } = props
|
||||
|
||||
const { i18n, t } = useTranslation('authentication')
|
||||
|
||||
const languageOptions = Object.entries(i18n.options.resources || {}).map(
|
||||
([language, resource]) => ({
|
||||
label: (resource as Translation).general.thisLanguage,
|
||||
value: language,
|
||||
}),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
<h3>{t('general:payloadSettings')}</h3>
|
||||
<div className={`${baseClass}__language`}>
|
||||
<Label htmlFor="language-select" label={t('general:language')} />
|
||||
<ReactSelect
|
||||
inputId="language-select"
|
||||
onChange={({ value }) => i18n.changeLanguage(value)}
|
||||
options={languageOptions}
|
||||
value={languageOptions.find((language) => language.value === i18n.language)}
|
||||
/>
|
||||
</div>
|
||||
<ToggleTheme />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,47 +1,19 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
|
||||
.account {
|
||||
width: 100%;
|
||||
padding-bottom: var(--spacing-view-bottom);
|
||||
|
||||
&__form {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
margin-top: calc(var(--base) * 3);
|
||||
}
|
||||
|
||||
&__auth {
|
||||
margin-bottom: var(--base);
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
margin-top: calc(var(--base) * 0.5);
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__payload-settings {
|
||||
margin-top: base(3);
|
||||
padding-top: base(3);
|
||||
border-top: 1px solid var(--theme-elevation-100);
|
||||
}
|
||||
|
||||
&__language {
|
||||
margin-bottom: $baseline;
|
||||
&___settings {
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__edit {
|
||||
margin: var(--base) 0;
|
||||
}
|
||||
|
||||
&__payload-settings {
|
||||
margin-top: base(1);
|
||||
padding-top: base(1);
|
||||
padding-bottom: base(0.5);
|
||||
border-top: 1px solid var(--theme-elevation-100);
|
||||
border-bottom: 1px solid var(--theme-elevation-100);
|
||||
&__auth {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,39 +5,27 @@ import type { GlobalEditViewProps } from '../../types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { DocumentControls } from '../../../elements/DocumentControls'
|
||||
import { Gutter } from '../../../elements/Gutter'
|
||||
import ViewDescription from '../../../elements/ViewDescription'
|
||||
import RenderFields from '../../../forms/RenderFields'
|
||||
import { filterFields } from '../../../forms/RenderFields/filterFields'
|
||||
import { fieldTypes } from '../../../forms/field-types'
|
||||
import { DocumentFields } from '../../../elements/DocumentFields'
|
||||
import { LeaveWithoutSaving } from '../../../modals/LeaveWithoutSaving'
|
||||
import Meta from '../../../utilities/Meta'
|
||||
import { SetStepNav } from '../../collections/Edit/SetStepNav'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'global-default-edit'
|
||||
|
||||
export const DefaultGlobalEdit: React.FC<GlobalEditViewProps> = (props) => {
|
||||
const { i18n } = useTranslation('general')
|
||||
|
||||
const { apiURL, data, global, permissions } = props
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const { admin: { description } = {}, fields, label } = global
|
||||
|
||||
const hasSavePermission = permissions?.update?.permission
|
||||
|
||||
const sidebarFields = filterFields({
|
||||
fieldSchema: fields,
|
||||
fieldTypes,
|
||||
filter: (field) => field?.admin?.position === 'sidebar',
|
||||
permissions: permissions.fields,
|
||||
readOnly: !hasSavePermission,
|
||||
})
|
||||
|
||||
const hasSidebar = sidebarFields && sidebarFields.length > 0
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Meta
|
||||
description={getTranslation(label, i18n)}
|
||||
keywords={`${getTranslation(label, i18n)}, Payload, CMS`}
|
||||
title={getTranslation(label, i18n)}
|
||||
/>
|
||||
{!(global.versions?.drafts && global.versions?.drafts?.autosave) && <LeaveWithoutSaving />}
|
||||
<SetStepNav global={global} />
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
@@ -47,60 +35,12 @@ export const DefaultGlobalEdit: React.FC<GlobalEditViewProps> = (props) => {
|
||||
isEditing
|
||||
permissions={permissions}
|
||||
/>
|
||||
<div
|
||||
className={[
|
||||
baseClass,
|
||||
hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Meta
|
||||
description={getTranslation(label, i18n)}
|
||||
keywords={`${getTranslation(label, i18n)}, Payload, CMS`}
|
||||
title={getTranslation(label, i18n)}
|
||||
/>
|
||||
{!(global.versions?.drafts && global.versions?.drafts?.autosave) && (
|
||||
<LeaveWithoutSaving />
|
||||
)}
|
||||
<Gutter className={`${baseClass}__edit`}>
|
||||
<header className={`${baseClass}__header`}>
|
||||
{description && (
|
||||
<div className={`${baseClass}__sub-header`}>
|
||||
<ViewDescription description={description} />
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<RenderFields
|
||||
fieldSchema={fields}
|
||||
fieldTypes={fieldTypes}
|
||||
filter={(field) =>
|
||||
!field.admin.position ||
|
||||
(field.admin.position && field.admin.position !== 'sidebar')
|
||||
}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</Gutter>
|
||||
</div>
|
||||
{hasSidebar && (
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__sidebar-fields`}>
|
||||
<RenderFields
|
||||
fieldTypes={fieldTypes}
|
||||
fields={sidebarFields}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DocumentFields
|
||||
description={description}
|
||||
fields={fields}
|
||||
hasSavePermission={hasSavePermission}
|
||||
permissions={permissions}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,168 +1,15 @@
|
||||
@import '../../../../../scss/styles.scss';
|
||||
|
||||
.collection-default-edit {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
--doc-sidebar-width: 500px;
|
||||
|
||||
&--has-sidebar {
|
||||
.collection-default-edit {
|
||||
&__edit {
|
||||
[dir='ltr'] & {
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-right: 1px solid var(--theme-elevation-100);
|
||||
padding-right: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-left: 1px solid var(--theme-elevation-100);
|
||||
padding-left: calc(var(--base) * 2);
|
||||
}
|
||||
}
|
||||
|
||||
&__fields {
|
||||
& > .tabs-field,
|
||||
& > .group-field {
|
||||
margin-right: calc(var(--base) * -2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__main {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: calc(var(--base) * 1.5);
|
||||
padding-bottom: var(--spacing-view-bottom);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__auth {
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: sticky;
|
||||
top: var(--doc-controls-height);
|
||||
height: calc(100vh - var(--doc-controls-height));
|
||||
width: var(--doc-sidebar-width);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&__sidebar-sticky-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
&__sidebar-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--base);
|
||||
padding-top: calc(var(--base) * 1.5);
|
||||
padding-left: calc(var(--base) * 2);
|
||||
padding-right: var(--gutter-h);
|
||||
padding-bottom: var(--spacing-view-bottom);
|
||||
}
|
||||
|
||||
&__label {
|
||||
color: var(--theme-elevation-400);
|
||||
}
|
||||
|
||||
@include large-break {
|
||||
--doc-sidebar-width: 350px;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
display: block;
|
||||
|
||||
&--has-sidebar {
|
||||
.collection-default-edit {
|
||||
&__main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
[dir='ltr'] & {
|
||||
border-right: 0;
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
border-left: 0;
|
||||
padding-left: var(--gutter-h);
|
||||
}
|
||||
}
|
||||
|
||||
&__fields {
|
||||
& > .tabs-field,
|
||||
& > .group-field {
|
||||
margin-right: calc(var(--gutter-h) * -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__main {
|
||||
width: 100%;
|
||||
min-height: initial;
|
||||
}
|
||||
|
||||
&__sidebar-wrap {
|
||||
position: static;
|
||||
width: 100%;
|
||||
height: initial;
|
||||
border-left: 0;
|
||||
margin-top: calc(var(--base) / 2);
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__sidebar-fields {
|
||||
padding-top: 0;
|
||||
padding-left: var(--gutter-h);
|
||||
padding-right: var(--gutter-h);
|
||||
gap: base(0.5);
|
||||
|
||||
[dir='ltr'] & {
|
||||
padding-right: var(--gutter-h);
|
||||
}
|
||||
|
||||
[dir='rtl'] & {
|
||||
padding-left: var(--gutter-h);
|
||||
}
|
||||
}
|
||||
|
||||
&__sidebar {
|
||||
padding-bottom: base(3.5);
|
||||
overflow: visible;
|
||||
}
|
||||
margin-bottom: calc(var(--base) * 2);
|
||||
margin-top: calc(var(--base) * 0.5);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
&__sidebar-wrap {
|
||||
min-width: initial;
|
||||
}
|
||||
|
||||
&__edit {
|
||||
padding-top: calc(var(--base) / 2);
|
||||
&__auth {
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@ import type { CollectionEditViewProps } from '../../../types'
|
||||
|
||||
import { getTranslation } from '../../../../../../utilities/getTranslation'
|
||||
import { DocumentControls } from '../../../../elements/DocumentControls'
|
||||
import { Gutter } from '../../../../elements/Gutter'
|
||||
import RenderFields from '../../../../forms/RenderFields'
|
||||
import { filterFields } from '../../../../forms/RenderFields/filterFields'
|
||||
import { fieldTypes } from '../../../../forms/field-types'
|
||||
import { DocumentFields } from '../../../../elements/DocumentFields'
|
||||
import { LeaveWithoutSaving } from '../../../../modals/LeaveWithoutSaving'
|
||||
import Meta from '../../../../utilities/Meta'
|
||||
import Auth from '../Auth'
|
||||
@@ -38,18 +35,21 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
|
||||
|
||||
const operation = isEditing ? 'update' : 'create'
|
||||
|
||||
const sidebarFields = filterFields({
|
||||
fieldSchema: fields,
|
||||
fieldTypes,
|
||||
filter: (field) => field?.admin?.position === 'sidebar',
|
||||
permissions: permissions.fields,
|
||||
readOnly: !hasSavePermission,
|
||||
})
|
||||
|
||||
const hasSidebar = sidebarFields && sidebarFields.length > 0
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Meta
|
||||
description={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
|
||||
title={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
/>
|
||||
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) &&
|
||||
!disableLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<SetStepNav collection={collection} id={id} isEditing={isEditing} />
|
||||
<DocumentControls
|
||||
apiURL={apiURL}
|
||||
@@ -61,29 +61,9 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
|
||||
isEditing={isEditing}
|
||||
permissions={permissions}
|
||||
/>
|
||||
<div
|
||||
className={[
|
||||
baseClass,
|
||||
hasSidebar ? `${baseClass}--has-sidebar` : `${baseClass}--no-sidebar`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__main`}>
|
||||
<Meta
|
||||
description={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
keywords={`${getTranslation(collection.labels.singular, i18n)}, Payload, CMS`}
|
||||
title={`${isEditing ? t('editing') : t('creating')} - ${getTranslation(
|
||||
collection.labels.singular,
|
||||
i18n,
|
||||
)}`}
|
||||
/>
|
||||
{!(collection.versions?.drafts && collection.versions?.drafts?.autosave) &&
|
||||
!disableLeaveWithoutSaving && <LeaveWithoutSaving />}
|
||||
<Gutter className={`${baseClass}__edit`}>
|
||||
<DocumentFields
|
||||
BeforeFields={() => (
|
||||
<Fragment>
|
||||
{auth && (
|
||||
<Auth
|
||||
className={`${baseClass}__auth`}
|
||||
@@ -97,33 +77,12 @@ export const DefaultCollectionEdit: React.FC<CollectionEditViewProps> = (props)
|
||||
/>
|
||||
)}
|
||||
{upload && <Upload collection={collection} internalState={internalState} />}
|
||||
<RenderFields
|
||||
className={`${baseClass}__fields`}
|
||||
fieldSchema={fields}
|
||||
fieldTypes={fieldTypes}
|
||||
filter={(field) => !field?.admin?.position || field?.admin?.position !== 'sidebar'}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</Gutter>
|
||||
</div>
|
||||
{hasSidebar && (
|
||||
<div className={`${baseClass}__sidebar-wrap`}>
|
||||
<div className={`${baseClass}__sidebar`}>
|
||||
<div className={`${baseClass}__sidebar-sticky-wrap`}>
|
||||
<div className={`${baseClass}__sidebar-fields`}>
|
||||
<RenderFields
|
||||
fieldTypes={fieldTypes}
|
||||
fields={sidebarFields}
|
||||
permissions={permissions.fields}
|
||||
readOnly={!hasSavePermission}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
fields={fields}
|
||||
hasSavePermission={hasSavePermission}
|
||||
permissions={permissions}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import Button from '../../../elements/Button'
|
||||
import DeleteMany from '../../../elements/DeleteMany'
|
||||
import EditMany from '../../../elements/EditMany'
|
||||
import { Gutter } from '../../../elements/Gutter'
|
||||
import ListControls from '../../../elements/ListControls'
|
||||
import { ListControls } from '../../../elements/ListControls'
|
||||
import ListSelection from '../../../elements/ListSelection'
|
||||
import Paginator from '../../../elements/Paginator'
|
||||
import PerPage from '../../../elements/PerPage'
|
||||
@@ -41,6 +41,7 @@ const DefaultList: React.FC<Props> = (props) => {
|
||||
data,
|
||||
handlePageChange,
|
||||
handlePerPageChange,
|
||||
handleSearchChange,
|
||||
handleSortChange,
|
||||
handleWhereChange,
|
||||
hasCreatePermission,
|
||||
@@ -48,6 +49,7 @@ const DefaultList: React.FC<Props> = (props) => {
|
||||
modifySearchParams,
|
||||
newDocumentURL,
|
||||
resetParams,
|
||||
titleField,
|
||||
} = props
|
||||
|
||||
const {
|
||||
@@ -99,10 +101,12 @@ const DefaultList: React.FC<Props> = (props) => {
|
||||
</header>
|
||||
<ListControls
|
||||
collection={collection}
|
||||
handleSearchChange={handleSearchChange}
|
||||
handleSortChange={handleSortChange}
|
||||
handleWhereChange={handleWhereChange}
|
||||
modifySearchQuery={modifySearchParams}
|
||||
resetParams={resetParams}
|
||||
titleField={titleField}
|
||||
/>
|
||||
{Array.isArray(BeforeListTable) &&
|
||||
BeforeListTable.map((Component, i) => <Component key={i} {...props} />)}
|
||||
|
||||
@@ -4,10 +4,12 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useHistory } from 'react-router-dom'
|
||||
import { v4 as uuid } from 'uuid'
|
||||
|
||||
import type { Field } from '../../../../../fields/config/types'
|
||||
import type { Where } from '../../../../../exports/types'
|
||||
import type { ListIndexProps, ListPreferences, Props } from './types'
|
||||
|
||||
import { type Field } from '../../../../../fields/config/types'
|
||||
import usePayloadAPI from '../../../../hooks/usePayloadAPI'
|
||||
import { useUseTitleField } from '../../../../hooks/useUseAsTitle'
|
||||
import { useStepNav } from '../../../elements/StepNav'
|
||||
import { TableColumnsProvider } from '../../../elements/TableColumns'
|
||||
import { useAuth } from '../../../utilities/Auth'
|
||||
@@ -18,6 +20,22 @@ import { useSearchParams } from '../../../utilities/SearchParams'
|
||||
import DefaultList from './Default'
|
||||
import formatFields from './formatFields'
|
||||
|
||||
const hoistQueryParamsToAnd = (where: Where, queryParams: Where) => {
|
||||
if ('and' in where) {
|
||||
where.and.push(queryParams)
|
||||
} else if ('or' in where) {
|
||||
where = {
|
||||
and: [where, queryParams],
|
||||
}
|
||||
} else {
|
||||
where = {
|
||||
and: [where, queryParams],
|
||||
}
|
||||
}
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
/**
|
||||
* The ListView component is table which lists the collection's documents.
|
||||
* The default list view can be found at the {@link DefaultList} component.
|
||||
@@ -30,6 +48,7 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
||||
collection: {
|
||||
admin: {
|
||||
components: { views: { List: CustomList } = {} } = {},
|
||||
listSearchableFields,
|
||||
pagination: { defaultLimit },
|
||||
},
|
||||
labels: { plural },
|
||||
@@ -45,7 +64,7 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
||||
const { permissions } = useAuth()
|
||||
const { setStepNav } = useStepNav()
|
||||
const { getPreference, setPreference } = usePreferences()
|
||||
const { limit, page, sort, where } = useSearchParams()
|
||||
const { limit, page, search, sort, where } = useSearchParams()
|
||||
const history = useHistory()
|
||||
const { t } = useTranslation('general')
|
||||
const [fetchURL, setFetchURL] = useState<string>('')
|
||||
@@ -54,6 +73,7 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
||||
const hasCreatePermission = collectionPermissions?.create?.permission
|
||||
const newDocumentURL = `${admin}/collections/${slug}/create`
|
||||
const [{ data }, { setParams }] = usePayloadAPI(fetchURL, { initialParams: { page: 1 } })
|
||||
const titleField = useUseTitleField(collection)
|
||||
|
||||
useEffect(() => {
|
||||
setStepNav([
|
||||
@@ -69,23 +89,45 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
||||
|
||||
const resetParams = useCallback<Props['resetParams']>(
|
||||
(overrides = {}) => {
|
||||
const params: Record<string, unknown> = {
|
||||
const params: Record<string, unknown> & { where?: Where } = {
|
||||
depth: 0,
|
||||
draft: 'true',
|
||||
limit,
|
||||
page: overrides?.page,
|
||||
search: overrides?.search,
|
||||
sort: overrides?.sort,
|
||||
where: overrides?.where,
|
||||
where: overrides?.where || {},
|
||||
}
|
||||
|
||||
if (page) params.page = page
|
||||
if (sort) params.sort = sort
|
||||
if (where) params.where = where
|
||||
if (where) params.where = where as Where
|
||||
params.invoke = uuid()
|
||||
|
||||
if (search) {
|
||||
let copyOfWhere = { ...((where as Where) || {}) }
|
||||
|
||||
const searchAsConditions = (listSearchableFields || [titleField?.name]).map((fieldName) => {
|
||||
return {
|
||||
[fieldName]: {
|
||||
like: search,
|
||||
},
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (searchAsConditions.length > 0) {
|
||||
const conditionalSearchFields = {
|
||||
or: [...searchAsConditions],
|
||||
}
|
||||
copyOfWhere = hoistQueryParamsToAnd(copyOfWhere, conditionalSearchFields)
|
||||
}
|
||||
|
||||
params.where = copyOfWhere
|
||||
}
|
||||
|
||||
setParams(params)
|
||||
},
|
||||
[limit, page, setParams, sort, where],
|
||||
[limit, page, setParams, sort, where, search, listSearchableFields, titleField?.name],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -178,6 +220,7 @@ const ListView: React.FC<ListIndexProps> = (props) => {
|
||||
limit: limit || defaultLimit,
|
||||
newDocumentURL,
|
||||
resetParams,
|
||||
titleField,
|
||||
}}
|
||||
/>
|
||||
</TableColumnsProvider>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../../../collections/config/types'
|
||||
import type { PaginatedDocs } from '../../../../../database/types'
|
||||
import type { FieldAffectingData } from '../../../../../exports/types'
|
||||
import type { Where } from '../../../../../types'
|
||||
import type { Props as ListControlsProps } from '../../../elements/ListControls/types'
|
||||
import type { Props as PaginatorProps } from '../../../elements/Paginator/types'
|
||||
@@ -12,6 +13,7 @@ export type Props = {
|
||||
handleDelete?: () => void
|
||||
handlePageChange?: PaginatorProps['onChange']
|
||||
handlePerPageChange?: PerPageProps['handleChange']
|
||||
handleSearchChange?: ListControlsProps['handleSearchChange']
|
||||
handleSortChange?: ListControlsProps['handleSortChange']
|
||||
handleWhereChange?: ListControlsProps['handleWhereChange']
|
||||
hasCreatePermission: boolean
|
||||
@@ -19,10 +21,16 @@ export type Props = {
|
||||
modifySearchParams?: boolean
|
||||
newDocumentURL: string
|
||||
onCreateNewClick?: () => void
|
||||
resetParams: (overrides?: { page?: number; sort?: string; where?: Where }) => void
|
||||
resetParams: (overrides?: {
|
||||
page?: number
|
||||
search?: string
|
||||
sort?: string
|
||||
where?: Where
|
||||
}) => void
|
||||
setLimit: (limit: number) => void
|
||||
setListControls: (controls: unknown) => void
|
||||
setSort: (sort: string) => void
|
||||
titleField?: FieldAffectingData
|
||||
toggleColumn: (column: string) => void
|
||||
}
|
||||
|
||||
|
||||
16
packages/payload/src/admin/hooks/useUseAsTitle.tsx
Normal file
16
packages/payload/src/admin/hooks/useUseAsTitle.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { FieldAffectingData, SanitizedCollectionConfig } from '../../exports/types'
|
||||
|
||||
import { fieldAffectsData } from '../../exports/types'
|
||||
import flattenFields from '../../utilities/flattenTopLevelFields'
|
||||
|
||||
export const useUseTitleField = (collection: SanitizedCollectionConfig): FieldAffectingData => {
|
||||
const {
|
||||
admin: { useAsTitle },
|
||||
fields,
|
||||
} = collection
|
||||
|
||||
const topLevelFields = flattenFields(fields)
|
||||
return topLevelFields.find(
|
||||
(field) => fieldAffectsData(field) && field.name === useAsTitle,
|
||||
) as FieldAffectingData
|
||||
}
|
||||
@@ -38,6 +38,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'forgotPassword',
|
||||
})) || args
|
||||
@@ -139,7 +140,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
|
||||
|
||||
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => {
|
||||
await priorHook
|
||||
await hook({ args, context: req.context })
|
||||
await hook({ args, collection: args.collection?.config, context: req.context })
|
||||
}, Promise.resolve())
|
||||
|
||||
// /////////////////////////////////////
|
||||
@@ -148,6 +149,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<null | string> {
|
||||
|
||||
token = await buildAfterOperation({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
operation: 'forgotPassword',
|
||||
result: token,
|
||||
})
|
||||
|
||||
@@ -30,7 +30,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
|
||||
expiration,
|
||||
req = {} as PayloadRequest,
|
||||
} = options
|
||||
setRequestContext(options.req)
|
||||
setRequestContext(req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
res,
|
||||
showHiddenFields,
|
||||
} = options
|
||||
setRequestContext(options.req)
|
||||
setRequestContext(req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
|
||||
options: Options<T>,
|
||||
): Promise<Result> {
|
||||
const { collection: collectionSlug, data, overrideAccess, req = {} as PayloadRequest } = options
|
||||
setRequestContext(options.req)
|
||||
|
||||
setRequestContext(req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
|
||||
overrideAccess = true,
|
||||
req = {} as PayloadRequest,
|
||||
} = options
|
||||
setRequestContext(options.req)
|
||||
setRequestContext(req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ async function localVerifyEmail<T extends keyof GeneratedTypes['collections']>(
|
||||
options: Options<T>,
|
||||
): Promise<boolean> {
|
||||
const { collection: collectionSlug, req = {} as PayloadRequest, token } = options
|
||||
setRequestContext(options.req)
|
||||
setRequestContext(req)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'login',
|
||||
})) || args
|
||||
@@ -138,6 +139,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
user,
|
||||
@@ -175,6 +177,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
req: args.req,
|
||||
token,
|
||||
@@ -187,10 +190,11 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
user = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: user,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -205,6 +209,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
@@ -220,6 +225,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
user =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
doc: user,
|
||||
req,
|
||||
@@ -238,6 +244,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
operation: 'login',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -46,6 +46,7 @@ async function logout(incomingArgs: Arguments): Promise<string> {
|
||||
|
||||
args =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
res,
|
||||
|
||||
@@ -69,6 +69,7 @@ async function me({ collection, req }: Arguments): Promise<Result> {
|
||||
|
||||
response =
|
||||
(await hook({
|
||||
collection: collection?.config,
|
||||
context: req.context,
|
||||
req,
|
||||
response,
|
||||
|
||||
@@ -39,6 +39,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
operation: 'refresh',
|
||||
})) || args
|
||||
@@ -112,6 +113,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: args.collection?.config,
|
||||
context: args.req.context,
|
||||
exp,
|
||||
req: args.req,
|
||||
@@ -126,6 +128,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
|
||||
|
||||
result = await buildAfterOperation({
|
||||
args,
|
||||
collection: args.collection?.config,
|
||||
operation: 'refresh',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -41,6 +41,8 @@ type CreateOrUpdateOperation = Extract<HookOperationType, 'create' | 'update'>
|
||||
|
||||
export type BeforeOperationHook = (args: {
|
||||
args?: any
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
/**
|
||||
* Hook operation being performed
|
||||
@@ -49,6 +51,8 @@ export type BeforeOperationHook = (args: {
|
||||
}) => any
|
||||
|
||||
export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
data?: Partial<T>
|
||||
/**
|
||||
@@ -65,6 +69,8 @@ export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type BeforeChangeHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
data: Partial<T>
|
||||
/**
|
||||
@@ -81,6 +87,8 @@ export type BeforeChangeHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type AfterChangeHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
doc: T
|
||||
/**
|
||||
@@ -92,6 +100,8 @@ export type AfterChangeHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type BeforeReadHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
doc: T
|
||||
query: { [key: string]: any }
|
||||
@@ -99,6 +109,8 @@ export type BeforeReadHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type AfterReadHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
doc: T
|
||||
findMany?: boolean
|
||||
@@ -107,12 +119,16 @@ export type AfterReadHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type BeforeDeleteHook = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
id: number | string
|
||||
req: PayloadRequest
|
||||
}) => any
|
||||
|
||||
export type AfterDeleteHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
doc: T
|
||||
id: number | string
|
||||
@@ -127,15 +143,21 @@ export type AfterErrorHook = (
|
||||
err: Error,
|
||||
res: unknown,
|
||||
context: RequestContext,
|
||||
/** The collection which this hook is being run on. This is null if the AfterError hook was be added to the payload-wide config */
|
||||
collection: SanitizedCollectionConfig | null,
|
||||
) => { response: any; status: number } | void
|
||||
|
||||
export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
req: PayloadRequest
|
||||
user: T
|
||||
}) => any
|
||||
|
||||
export type AfterLoginHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
req: PayloadRequest
|
||||
token: string
|
||||
@@ -143,18 +165,24 @@ export type AfterLoginHook<T extends TypeWithID = any> = (args: {
|
||||
}) => any
|
||||
|
||||
export type AfterLogoutHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
req: PayloadRequest
|
||||
res: Response
|
||||
}) => any
|
||||
|
||||
export type AfterMeHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
req: PayloadRequest
|
||||
response: unknown
|
||||
}) => any
|
||||
|
||||
export type AfterRefreshHook<T extends TypeWithID = any> = (args: {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
exp: number
|
||||
req: PayloadRequest
|
||||
@@ -162,9 +190,16 @@ export type AfterRefreshHook<T extends TypeWithID = any> = (args: {
|
||||
token: string
|
||||
}) => any
|
||||
|
||||
export type AfterForgotPasswordHook = (args: { args?: any; context: RequestContext }) => any
|
||||
export type AfterForgotPasswordHook = (args: {
|
||||
args?: any
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
context: RequestContext
|
||||
}) => any
|
||||
|
||||
type BeforeDuplicateArgs<T> = {
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
data: T
|
||||
locale?: string
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@ import { afterChange } from '../../fields/hooks/afterChange'
|
||||
import { afterRead } from '../../fields/hooks/afterRead'
|
||||
import { beforeChange } from '../../fields/hooks/beforeChange'
|
||||
import { beforeValidate } from '../../fields/hooks/beforeValidate'
|
||||
import fileExists from '../../uploads/fileExists'
|
||||
import { generateFileData } from '../../uploads/generateFileData'
|
||||
import { unlinkTempFiles } from '../../uploads/unlinkTempFiles'
|
||||
import { uploadFiles } from '../../uploads/uploadFiles'
|
||||
import { initTransaction } from '../../utilities/initTransaction'
|
||||
import { killTransaction } from '../../utilities/killTransaction'
|
||||
@@ -65,6 +67,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'create',
|
||||
})) || args
|
||||
@@ -139,10 +142,11 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await beforeValidate({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: {},
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'create',
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -158,6 +162,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
@@ -184,6 +189,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'create',
|
||||
@@ -196,11 +202,12 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
const resultWithLocales = await beforeChange<Record<string, unknown>>({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: {},
|
||||
docWithLocales: {},
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'create',
|
||||
req,
|
||||
skipValidation: shouldSaveDraft,
|
||||
@@ -293,10 +300,11 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -311,6 +319,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -322,27 +331,16 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'create',
|
||||
previousDoc: {},
|
||||
req,
|
||||
})
|
||||
|
||||
// Remove temp files if enabled, as express-fileupload does not do this automatically
|
||||
if (config.upload?.useTempFiles && collectionConfig.upload) {
|
||||
const { files } = req
|
||||
const fileArray = Array.isArray(files) ? files : [files]
|
||||
await mapAsync(fileArray, async ({ file }) => {
|
||||
// Still need this check because this will not be populated if using local API
|
||||
if (file.tempFilePath) {
|
||||
await unlinkFile(file.tempFilePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// /////////////////////////////////////
|
||||
// afterChange - Collection
|
||||
// /////////////////////////////////////
|
||||
@@ -353,6 +351,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'create',
|
||||
@@ -369,21 +368,12 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'create',
|
||||
result,
|
||||
})
|
||||
|
||||
// Remove temp files if enabled, as express-fileupload does not do this automatically
|
||||
if (config.upload?.useTempFiles && collectionConfig.upload) {
|
||||
const { files } = req
|
||||
const fileArray = Array.isArray(files) ? files : [files]
|
||||
await mapAsync(fileArray, async ({ file }) => {
|
||||
// Still need this check because this will not be populated if using local API
|
||||
if (file.tempFilePath) {
|
||||
await unlinkFile(file.tempFilePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
await unlinkTempFiles({ collectionConfig, config, req })
|
||||
|
||||
// /////////////////////////////////////
|
||||
// Return results
|
||||
|
||||
@@ -49,6 +49,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'delete',
|
||||
})) || args
|
||||
@@ -126,6 +127,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
@@ -171,10 +173,11 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result || doc,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -189,6 +192,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result || doc,
|
||||
req,
|
||||
@@ -205,6 +209,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -249,6 +254,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'delete',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -40,6 +40,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'delete',
|
||||
})) || args
|
||||
@@ -82,6 +83,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
return hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
req,
|
||||
})
|
||||
@@ -148,10 +150,11 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -166,6 +169,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -182,6 +186,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
result =
|
||||
(await hook({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -194,6 +199,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'deleteByID',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -46,6 +46,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
})) || args
|
||||
@@ -166,6 +167,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef,
|
||||
query: fullWhere,
|
||||
@@ -187,12 +189,13 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
docs: await Promise.all(
|
||||
result.docs.map(async (doc) =>
|
||||
afterRead<T>({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
entityConfig: collectionConfig,
|
||||
findMany: true,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -216,6 +219,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
|
||||
docRef =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef,
|
||||
findMany: true,
|
||||
@@ -235,6 +239,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
|
||||
|
||||
result = await buildAfterOperation<T>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'find',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -39,6 +39,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'read',
|
||||
})) || args
|
||||
@@ -138,6 +139,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
@@ -150,11 +152,12 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -169,6 +172,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
query: findOneArgs.where,
|
||||
@@ -182,6 +186,7 @@ async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<
|
||||
|
||||
result = await buildAfterOperation<T>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'findByID',
|
||||
result: result as any,
|
||||
}) // TODO: fix this typing
|
||||
|
||||
@@ -93,6 +93,7 @@ async function findVersionByID<T extends TypeWithID = any>(
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
@@ -105,11 +106,12 @@ async function findVersionByID<T extends TypeWithID = any>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result.version = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc: result.version,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -124,6 +126,7 @@ async function findVersionByID<T extends TypeWithID = any>(
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
query: fullWhere,
|
||||
|
||||
@@ -94,6 +94,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: docRef.version,
|
||||
query: fullWhere,
|
||||
@@ -116,11 +117,12 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
result.docs.map(async (data) => ({
|
||||
...data,
|
||||
version: await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: data.version,
|
||||
entityConfig: collectionConfig,
|
||||
findMany: true,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -144,6 +146,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
|
||||
docRef.version =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: doc.version,
|
||||
findMany: true,
|
||||
|
||||
@@ -56,7 +56,7 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
|
||||
user,
|
||||
where,
|
||||
} = options
|
||||
setRequestContext(options.req, context)
|
||||
setRequestContext(req, context)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
const defaultLocale = payload?.config?.localization
|
||||
|
||||
@@ -47,7 +47,7 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
|
||||
showHiddenFields,
|
||||
user,
|
||||
} = options
|
||||
setRequestContext(options.req, context)
|
||||
setRequestContext(req, context)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
const defaultLocale = payload?.config?.localization
|
||||
|
||||
@@ -44,7 +44,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
|
||||
req = {} as PayloadRequest,
|
||||
showHiddenFields,
|
||||
} = options
|
||||
setRequestContext(options.req, context)
|
||||
setRequestContext(req, context)
|
||||
|
||||
const collection = payload.collections[collectionSlug]
|
||||
const defaultLocale = payload?.config?.localization
|
||||
|
||||
@@ -135,10 +135,11 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -153,6 +154,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -164,10 +166,11 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data: result,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
previousDoc: prevDocWithLocales,
|
||||
req,
|
||||
@@ -182,6 +185,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
|
||||
@@ -56,6 +56,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
})) || args
|
||||
@@ -169,10 +170,11 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
try {
|
||||
const originalDoc = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth: 0,
|
||||
doc,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
showHiddenFields: true,
|
||||
@@ -193,10 +195,11 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data = await beforeValidate<DeepPartial<GeneratedTypes['collections'][TSlug]>>({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -211,6 +214,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
@@ -236,6 +240,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
@@ -250,11 +255,12 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
let result = await beforeChange<GeneratedTypes['collections'][TSlug]>({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
docWithLocales: doc,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
req,
|
||||
skipValidation: shouldSaveDraft || data._status === 'draft',
|
||||
@@ -297,10 +303,11 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -315,6 +322,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -326,10 +334,11 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange<GeneratedTypes['collections'][TSlug]>({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
@@ -344,6 +353,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
@@ -385,6 +395,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'update',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -55,6 +55,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
args =
|
||||
(await hook({
|
||||
args,
|
||||
collection: args.collection.config,
|
||||
context: args.req.context,
|
||||
operation: 'update',
|
||||
})) || args
|
||||
@@ -123,10 +124,11 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
if (!docWithLocales && hasWherePolicy) throw new Forbidden(t)
|
||||
|
||||
const originalDoc = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth: 0,
|
||||
doc: docWithLocales,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
showHiddenFields: true,
|
||||
@@ -166,10 +168,11 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data = await beforeValidate<DeepPartial<GeneratedTypes['collections'][TSlug]>>({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -184,6 +187,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
@@ -209,6 +213,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
operation: 'update',
|
||||
@@ -223,11 +228,12 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
let result = await beforeChange<GeneratedTypes['collections'][TSlug]>({
|
||||
id,
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
docWithLocales,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
req,
|
||||
skipValidation: shouldSaveDraft || data._status === 'draft',
|
||||
@@ -285,10 +291,11 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -303,6 +310,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
req,
|
||||
@@ -314,10 +322,11 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange<GeneratedTypes['collections'][TSlug]>({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: result,
|
||||
entityConfig: collectionConfig,
|
||||
global: null,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
@@ -332,6 +341,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
collection: collectionConfig,
|
||||
context: req.context,
|
||||
doc: result,
|
||||
operation: 'update',
|
||||
@@ -346,6 +356,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
|
||||
|
||||
result = await buildAfterOperation<GeneratedTypes['collections'][TSlug]>({
|
||||
args,
|
||||
collection: collectionConfig,
|
||||
operation: 'updateByID',
|
||||
result,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type forgotPassword from '../../auth/operations/forgotPassword'
|
||||
import type login from '../../auth/operations/login'
|
||||
import type refresh from '../../auth/operations/refresh'
|
||||
import type { AfterOperationHook, TypeWithID } from '../config/types'
|
||||
import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types'
|
||||
import type create from './create'
|
||||
import type deleteOperation from './delete'
|
||||
import type deleteByID from './deleteByID'
|
||||
@@ -25,51 +25,71 @@ export type AfterOperationMap<T extends TypeWithID> = {
|
||||
export type AfterOperationArg<T extends TypeWithID> =
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['create']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'create'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['create']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['delete']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'delete'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['delete']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['deleteByID']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'deleteByID'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['deleteByID']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['find']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'find'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['find']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['findByID']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'findByID'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['findByID']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['forgotPassword']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'forgotPassword'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['forgotPassword']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['login']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'login'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['login']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['refresh']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'refresh'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['refresh']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['update']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'update'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['update']>>
|
||||
}
|
||||
| {
|
||||
args: Parameters<AfterOperationMap<T>['updateByID']>[0]
|
||||
/** The collection which this hook is being run on */
|
||||
collection: SanitizedCollectionConfig
|
||||
operation: 'updateByID'
|
||||
result: Awaited<ReturnType<AfterOperationMap<T>['updateByID']>>
|
||||
}
|
||||
@@ -82,7 +102,7 @@ export const buildAfterOperation = async <
|
||||
>(
|
||||
operationArgs: AfterOperationArg<T> & { operation: O },
|
||||
): Promise<Awaited<ReturnType<AfterOperationMap<T>[O]>>> => {
|
||||
const { args, operation, result } = operationArgs
|
||||
const { args, collection, operation, result } = operationArgs
|
||||
|
||||
let newResult = result
|
||||
|
||||
@@ -92,6 +112,7 @@ export const buildAfterOperation = async <
|
||||
|
||||
const hookResult = await hook({
|
||||
args,
|
||||
collection,
|
||||
operation,
|
||||
result: newResult,
|
||||
} as AfterOperationArg<T>)
|
||||
|
||||
@@ -90,12 +90,17 @@ export default joi.object({
|
||||
debug: joi.boolean(),
|
||||
defaultDepth: joi.number().min(0).max(30),
|
||||
defaultMaxTextLength: joi.number(),
|
||||
editor: joi.object().required().keys({
|
||||
CellComponent: component.required(),
|
||||
FieldComponent: component.required(),
|
||||
afterReadPromise: joi.func().required(),
|
||||
validate: joi.func().required(),
|
||||
}),
|
||||
editor: joi
|
||||
.object()
|
||||
.required()
|
||||
.keys({
|
||||
CellComponent: component.required(),
|
||||
FieldComponent: component.required(),
|
||||
afterReadPromise: joi.func().optional(),
|
||||
populationPromise: joi.func().optional(),
|
||||
validate: joi.func().required(),
|
||||
})
|
||||
.unknown(),
|
||||
email: joi.object(),
|
||||
endpoints: endpointsSchema,
|
||||
express: joi.object().keys({
|
||||
|
||||
@@ -47,11 +47,17 @@ const errorHandler =
|
||||
err,
|
||||
response,
|
||||
req.context,
|
||||
req.collection.config,
|
||||
)) || { response, status })
|
||||
}
|
||||
|
||||
if (typeof config.hooks.afterError === 'function') {
|
||||
;({ response, status } = (await config.hooks.afterError(err, response, req.context)) || {
|
||||
;({ response, status } = (await config.hooks.afterError(
|
||||
err,
|
||||
response,
|
||||
req.context,
|
||||
req.collection.config,
|
||||
)) || {
|
||||
response,
|
||||
status,
|
||||
})
|
||||
|
||||
@@ -353,13 +353,17 @@ export const blocks = baseField.keys({
|
||||
export const richText = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.default(),
|
||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func()),
|
||||
editor: joi.object().keys({
|
||||
CellComponent: componentSchema.required(),
|
||||
FieldComponent: componentSchema.required(),
|
||||
afterReadPromise: joi.func().required(),
|
||||
validate: joi.func().required(),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.array().items(joi.object()), joi.func(), joi.object()),
|
||||
editor: joi
|
||||
.object()
|
||||
.keys({
|
||||
CellComponent: componentSchema.required(),
|
||||
FieldComponent: componentSchema.required(),
|
||||
afterReadPromise: joi.func().optional(),
|
||||
populationPromise: joi.func().optional(),
|
||||
validate: joi.func().required(),
|
||||
})
|
||||
.unknown(),
|
||||
type: joi.string().valid('richText').required(),
|
||||
})
|
||||
|
||||
|
||||
@@ -9,18 +9,25 @@ import type { Description } from '../../admin/components/forms/FieldDescription/
|
||||
import type { RowLabel } from '../../admin/components/forms/RowLabel/types'
|
||||
import type { RichTextAdapter } from '../../admin/components/forms/field-types/RichText/types'
|
||||
import type { User } from '../../auth'
|
||||
import type { TypeWithID } from '../../collections/config/types'
|
||||
import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types'
|
||||
import type { SanitizedConfig } from '../../config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types'
|
||||
import type { Payload } from '../../payload'
|
||||
import type { Operation, Where } from '../../types'
|
||||
|
||||
export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
|
||||
/** The collection which the field belongs to. If the field belongs to a global, this will be null. */
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
/** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */
|
||||
data?: Partial<T>
|
||||
/** The field which the hook is running against. */
|
||||
field: FieldAffectingData
|
||||
/** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */
|
||||
findMany?: boolean
|
||||
/** The global which the field belongs to. If the field belongs to a collection, this will be null. */
|
||||
global: SanitizedGlobalConfig | null
|
||||
/** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */
|
||||
operation?: 'create' | 'delete' | 'read' | 'update'
|
||||
/** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */
|
||||
|
||||
@@ -6,20 +6,23 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args<T> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown> | T
|
||||
doc: Record<string, unknown> | T
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
previousDoc: Record<string, unknown> | T
|
||||
req: PayloadRequest
|
||||
}
|
||||
|
||||
export const afterChange = async <T extends Record<string, unknown>>({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
|
||||
doc: incomingDoc,
|
||||
entityConfig,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
req,
|
||||
@@ -27,10 +30,12 @@ export const afterChange = async <T extends Record<string, unknown>>({
|
||||
const doc = deepCopyObject(incomingDoc)
|
||||
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: entityConfig.fields,
|
||||
fields: collection?.fields || global?.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc,
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
field: Field | TabAsField
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
previousDoc: Record<string, unknown>
|
||||
previousSiblingDoc: Record<string, unknown>
|
||||
@@ -22,10 +26,12 @@ type Args = {
|
||||
// - Execute field hooks
|
||||
|
||||
export const promise = async ({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
@@ -40,8 +46,11 @@ export const promise = async ({
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
previousDoc,
|
||||
@@ -63,10 +72,12 @@ export const promise = async ({
|
||||
switch (field.type) {
|
||||
case 'group': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc[field.name] as Record<string, unknown>,
|
||||
@@ -86,10 +97,12 @@ export const promise = async ({
|
||||
rows.forEach((row, i) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as Record<string, unknown>),
|
||||
@@ -115,10 +128,12 @@ export const promise = async ({
|
||||
if (block) {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: block.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc:
|
||||
@@ -139,10 +154,12 @@ export const promise = async ({
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...previousSiblingDoc },
|
||||
@@ -166,10 +183,12 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: tabPreviousSiblingDoc,
|
||||
@@ -183,10 +202,12 @@ export const promise = async ({
|
||||
|
||||
case 'tabs': {
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc: { ...previousSiblingDoc },
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { promise } from './promise'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
fields: (Field | TabAsField)[]
|
||||
global: SanitizedGlobalConfig | null
|
||||
operation: 'create' | 'update'
|
||||
previousDoc: Record<string, unknown>
|
||||
previousSiblingDoc: Record<string, unknown>
|
||||
@@ -17,10 +21,12 @@ type Args = {
|
||||
}
|
||||
|
||||
export const traverseFields = async ({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
@@ -33,10 +39,12 @@ export const traverseFields = async ({
|
||||
fields.forEach((field) => {
|
||||
promises.push(
|
||||
promise({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
previousDoc,
|
||||
previousSiblingDoc,
|
||||
|
||||
@@ -6,13 +6,14 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
currentDepth?: number
|
||||
depth: number
|
||||
doc: Record<string, unknown>
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
findMany?: boolean
|
||||
flattenLocales?: boolean
|
||||
global: SanitizedGlobalConfig | null
|
||||
overrideAccess: boolean
|
||||
req: PayloadRequest
|
||||
showHiddenFields: boolean
|
||||
@@ -20,13 +21,14 @@ type Args = {
|
||||
|
||||
export async function afterRead<T = any>(args: Args): Promise<T> {
|
||||
const {
|
||||
collection,
|
||||
context,
|
||||
currentDepth: incomingCurrentDepth,
|
||||
depth: incomingDepth,
|
||||
doc: incomingDoc,
|
||||
entityConfig,
|
||||
findMany,
|
||||
flattenLocales = true,
|
||||
global,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -45,14 +47,16 @@ export async function afterRead<T = any>(args: Args): Promise<T> {
|
||||
const currentDepth = incomingCurrentDepth || 1
|
||||
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc,
|
||||
fieldPromises,
|
||||
fields: entityConfig.fields,
|
||||
fields: collection?.fields || global?.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { RichTextAdapter } from '../../../admin/components/forms/field-types/RichText/types'
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName } from '../../config/types'
|
||||
@@ -8,6 +10,7 @@ import relationshipPopulationPromise from './relationshipPopulationPromise'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
currentDepth: number
|
||||
depth: number
|
||||
@@ -16,6 +19,7 @@ type Args = {
|
||||
fieldPromises: Promise<void>[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
global: SanitizedGlobalConfig | null
|
||||
overrideAccess: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
@@ -32,6 +36,7 @@ type Args = {
|
||||
// - Populate relationships
|
||||
|
||||
export const promise = async ({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -40,6 +45,7 @@ export const promise = async ({
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -129,8 +135,9 @@ export const promise = async ({
|
||||
|
||||
case 'richText': {
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
if (editor?.afterReadPromise) {
|
||||
const afterReadPromise = editor.afterReadPromise({
|
||||
// This is run here AND in the GraphQL Resolver
|
||||
if (editor?.populationPromise) {
|
||||
const populationPromise = editor.populationPromise({
|
||||
currentDepth,
|
||||
depth,
|
||||
field,
|
||||
@@ -140,6 +147,19 @@ export const promise = async ({
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
if (populationPromise) {
|
||||
populationPromises.push(populationPromise)
|
||||
}
|
||||
}
|
||||
|
||||
// This is only run here, independent of depth
|
||||
if (editor?.afterReadPromise) {
|
||||
const afterReadPromise = editor?.afterReadPromise({
|
||||
field,
|
||||
incomingEditorState: siblingDoc[field.name] as object,
|
||||
siblingDoc,
|
||||
})
|
||||
|
||||
if (afterReadPromise) {
|
||||
populationPromises.push(afterReadPromise)
|
||||
}
|
||||
@@ -179,8 +199,11 @@ export const promise = async ({
|
||||
const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) =>
|
||||
(async () => {
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
global,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
req,
|
||||
@@ -197,9 +220,12 @@ export const promise = async ({
|
||||
await Promise.all(hookPromises)
|
||||
} else {
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data: doc,
|
||||
field,
|
||||
findMany,
|
||||
global,
|
||||
operation: 'read',
|
||||
originalDoc: doc,
|
||||
req,
|
||||
@@ -252,6 +278,7 @@ export const promise = async ({
|
||||
if (typeof siblingDoc[field.name] !== 'object') groupDoc = {}
|
||||
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -260,6 +287,7 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -276,6 +304,7 @@ export const promise = async ({
|
||||
if (Array.isArray(rows)) {
|
||||
rows.forEach((row) => {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -284,6 +313,7 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -296,6 +326,7 @@ export const promise = async ({
|
||||
if (Array.isArray(localeRows)) {
|
||||
localeRows.forEach((row) => {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -304,6 +335,7 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -328,6 +360,7 @@ export const promise = async ({
|
||||
|
||||
if (block) {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -336,6 +369,7 @@ export const promise = async ({
|
||||
fields: block.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -352,6 +386,7 @@ export const promise = async ({
|
||||
|
||||
if (block) {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -360,6 +395,7 @@ export const promise = async ({
|
||||
fields: block.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -380,6 +416,7 @@ export const promise = async ({
|
||||
case 'row':
|
||||
case 'collapsible': {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -388,6 +425,7 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -406,6 +444,7 @@ export const promise = async ({
|
||||
}
|
||||
|
||||
await traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -414,6 +453,7 @@ export const promise = async ({
|
||||
fields: field.fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -426,6 +466,7 @@ export const promise = async ({
|
||||
|
||||
case 'tabs': {
|
||||
traverseFields({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -434,6 +475,7 @@ export const promise = async ({
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { promise } from './promise'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
currentDepth: number
|
||||
depth: number
|
||||
@@ -12,6 +15,7 @@ type Args = {
|
||||
fields: (Field | TabAsField)[]
|
||||
findMany: boolean
|
||||
flattenLocales: boolean
|
||||
global: SanitizedGlobalConfig | null
|
||||
overrideAccess: boolean
|
||||
populationPromises: Promise<void>[]
|
||||
req: PayloadRequest
|
||||
@@ -20,6 +24,7 @@ type Args = {
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -28,6 +33,7 @@ export const traverseFields = ({
|
||||
fields,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
@@ -37,6 +43,7 @@ export const traverseFields = ({
|
||||
fields.forEach((field) => {
|
||||
fieldPromises.push(
|
||||
promise({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
depth,
|
||||
@@ -45,6 +52,7 @@ export const traverseFields = ({
|
||||
fieldPromises,
|
||||
findMany,
|
||||
flattenLocales,
|
||||
global,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
|
||||
@@ -8,11 +8,12 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args<T> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown> | T
|
||||
doc: Record<string, unknown> | T
|
||||
docWithLocales: Record<string, unknown>
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
operation: Operation
|
||||
req: PayloadRequest
|
||||
@@ -21,11 +22,12 @@ type Args<T> = {
|
||||
|
||||
export const beforeChange = async <T extends Record<string, unknown>>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data: incomingData,
|
||||
doc,
|
||||
docWithLocales,
|
||||
entityConfig,
|
||||
global,
|
||||
operation,
|
||||
req,
|
||||
skipValidation,
|
||||
@@ -36,12 +38,14 @@ export const beforeChange = async <T extends Record<string, unknown>>({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: entityConfig.fields,
|
||||
fields: collection?.fields || global?.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: '',
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import merge from 'deepmerge'
|
||||
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Operation } from '../../../types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
@@ -10,12 +12,14 @@ import { getExistingRowDoc } from './getExistingRowDoc'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
docWithLocales: Record<string, unknown>
|
||||
errors: { field: string; message: string }[]
|
||||
field: Field | TabAsField
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => void)[]
|
||||
operation: Operation
|
||||
@@ -36,12 +40,14 @@ type Args = {
|
||||
|
||||
export const promise = async ({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
@@ -75,8 +81,11 @@ export const promise = async ({
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
req,
|
||||
@@ -183,12 +192,14 @@ export const promise = async ({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: `${path}${field.name}.`,
|
||||
@@ -211,12 +222,14 @@ export const promise = async ({
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: `${path}${field.name}.${i}.`,
|
||||
@@ -251,12 +264,14 @@ export const promise = async ({
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: block.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: `${path}${field.name}.${i}.`,
|
||||
@@ -280,12 +295,14 @@ export const promise = async ({
|
||||
case 'collapsible': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
@@ -319,12 +336,14 @@ export const promise = async ({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path: tabPath,
|
||||
@@ -341,12 +360,14 @@ export const promise = async ({
|
||||
case 'tabs': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Operation } from '../../../types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { promise } from './promise'
|
||||
|
||||
type Args = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown>
|
||||
doc: Record<string, unknown>
|
||||
docWithLocales: Record<string, unknown>
|
||||
errors: { field: string; message: string }[]
|
||||
fields: (Field | TabAsField)[]
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
mergeLocaleActions: (() => void)[]
|
||||
operation: Operation
|
||||
@@ -24,12 +28,14 @@ type Args = {
|
||||
|
||||
export const traverseFields = async ({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
fields,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
@@ -45,12 +51,14 @@ export const traverseFields = async ({
|
||||
promises.push(
|
||||
promise({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
docWithLocales,
|
||||
errors,
|
||||
field,
|
||||
global,
|
||||
mergeLocaleActions,
|
||||
operation,
|
||||
path,
|
||||
|
||||
@@ -6,10 +6,11 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args<T> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: Record<string, unknown> | T
|
||||
doc?: Record<string, unknown> | T
|
||||
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
@@ -18,10 +19,11 @@ type Args<T> = {
|
||||
|
||||
export const beforeValidate = async <T extends Record<string, unknown>>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data: incomingData,
|
||||
doc,
|
||||
entityConfig,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -30,10 +32,12 @@ export const beforeValidate = async <T extends Record<string, unknown>>({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: entityConfig.fields,
|
||||
fields: collection?.fields || global?.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { fieldAffectsData, tabHasName, valueIsValueWithRelation } from '../../config/types'
|
||||
@@ -9,10 +11,12 @@ import { getExistingRowDoc } from '../beforeChange/getExistingRowDoc'
|
||||
import { traverseFields } from './traverseFields'
|
||||
|
||||
type Args<T> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: T
|
||||
doc: T
|
||||
field: Field | TabAsField
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
@@ -30,10 +34,12 @@ type Args<T> = {
|
||||
|
||||
export const promise = async <T>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -209,8 +215,11 @@ export const promise = async <T>({
|
||||
await priorHook
|
||||
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
originalDoc: doc,
|
||||
req,
|
||||
@@ -263,10 +272,12 @@ export const promise = async <T>({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -282,14 +293,16 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
rows.forEach((row) => {
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -308,7 +321,7 @@ export const promise = async <T>({
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
const promises = []
|
||||
rows.forEach((row, i) => {
|
||||
rows.forEach((row) => {
|
||||
const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name])
|
||||
const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType
|
||||
const block = field.blocks.find((blockType) => blockType.slug === blockTypeToMatch)
|
||||
@@ -319,10 +332,12 @@ export const promise = async <T>({
|
||||
promises.push(
|
||||
traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: block.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -342,10 +357,12 @@ export const promise = async <T>({
|
||||
case 'collapsible': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -372,10 +389,12 @@ export const promise = async <T>({
|
||||
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -389,10 +408,12 @@ export const promise = async <T>({
|
||||
case 'tabs': {
|
||||
await traverseFields({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })),
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import type { SanitizedCollectionConfig } from '../../../collections/config/types'
|
||||
import type { PayloadRequest, RequestContext } from '../../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../../globals/config/types'
|
||||
import type { Field, TabAsField } from '../../config/types'
|
||||
|
||||
import { promise } from './promise'
|
||||
|
||||
type Args<T> = {
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
data: T
|
||||
doc: T
|
||||
fields: (Field | TabAsField)[]
|
||||
global: SanitizedGlobalConfig | null
|
||||
id?: number | string
|
||||
operation: 'create' | 'update'
|
||||
overrideAccess: boolean
|
||||
@@ -18,10 +22,12 @@ type Args<T> = {
|
||||
|
||||
export const traverseFields = async <T>({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
fields,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -33,10 +39,12 @@ export const traverseFields = async <T>({
|
||||
promises.push(
|
||||
promise({
|
||||
id,
|
||||
collection,
|
||||
context,
|
||||
data,
|
||||
doc,
|
||||
field,
|
||||
global,
|
||||
operation,
|
||||
overrideAccess,
|
||||
req,
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
LivePreviewConfig,
|
||||
} from '../../config/types'
|
||||
import type { PayloadRequest } from '../../express/types'
|
||||
import type { RequestContext } from '../../express/types'
|
||||
import type { Field } from '../../fields/config/types'
|
||||
import type { Where } from '../../types'
|
||||
import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types'
|
||||
@@ -27,20 +28,46 @@ export type TypeWithID = {
|
||||
}
|
||||
|
||||
export type BeforeValidateHook = (args: {
|
||||
context: RequestContext
|
||||
data?: any
|
||||
/** The global which this hook is being run on */
|
||||
global: SanitizedGlobalConfig
|
||||
originalDoc?: any
|
||||
req?: PayloadRequest
|
||||
}) => any
|
||||
|
||||
export type BeforeChangeHook = (args: { data: any; originalDoc?: any; req: PayloadRequest }) => any
|
||||
export type BeforeChangeHook = (args: {
|
||||
context: RequestContext
|
||||
data: any
|
||||
/** The global which this hook is being run on */
|
||||
global: SanitizedGlobalConfig
|
||||
originalDoc?: any
|
||||
req: PayloadRequest
|
||||
}) => any
|
||||
|
||||
export type AfterChangeHook = (args: { doc: any; previousDoc: any; req: PayloadRequest }) => any
|
||||
export type AfterChangeHook = (args: {
|
||||
context: RequestContext
|
||||
doc: any
|
||||
/** The global which this hook is being run on */
|
||||
global: SanitizedGlobalConfig
|
||||
previousDoc: any
|
||||
req: PayloadRequest
|
||||
}) => any
|
||||
|
||||
export type BeforeReadHook = (args: { doc: any; req: PayloadRequest }) => any
|
||||
export type BeforeReadHook = (args: {
|
||||
context: RequestContext
|
||||
doc: any
|
||||
/** The global which this hook is being run on */
|
||||
global: SanitizedGlobalConfig
|
||||
req: PayloadRequest
|
||||
}) => any
|
||||
|
||||
export type AfterReadHook = (args: {
|
||||
context: RequestContext
|
||||
doc: any
|
||||
findMany?: boolean
|
||||
/** The global which this hook is being run on */
|
||||
global: SanitizedGlobalConfig
|
||||
query?: Where
|
||||
req: PayloadRequest
|
||||
}) => any
|
||||
|
||||
@@ -83,7 +83,9 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
@@ -93,10 +95,11 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
|
||||
// /////////////////////////////////////
|
||||
|
||||
doc = await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -111,7 +114,9 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
|
||||
|
||||
doc =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || doc
|
||||
}, Promise.resolve())
|
||||
|
||||
@@ -87,7 +87,9 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result.version
|
||||
}, Promise.resolve())
|
||||
@@ -97,11 +99,12 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
|
||||
// /////////////////////////////////////
|
||||
|
||||
result.version = await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
currentDepth,
|
||||
depth,
|
||||
doc: result.version,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -116,7 +119,9 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
|
||||
|
||||
result.version =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result.version,
|
||||
global: globalConfig,
|
||||
query: findGlobalVersionsArgs.where,
|
||||
req,
|
||||
})) || result.version
|
||||
|
||||
@@ -88,11 +88,12 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
paginatedDocs.docs.map(async (data) => ({
|
||||
...data,
|
||||
version: await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: data.version,
|
||||
entityConfig: globalConfig,
|
||||
findMany: true,
|
||||
global: globalConfig,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -115,8 +116,14 @@ async function findVersions<T extends TypeWithVersion<T>>(
|
||||
await priorHook
|
||||
|
||||
docRef.version =
|
||||
(await hook({ doc: doc.version, findMany: true, query: fullWhere, req })) ||
|
||||
doc.version
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: doc.version,
|
||||
findMany: true,
|
||||
global: globalConfig,
|
||||
query: fullWhere,
|
||||
req,
|
||||
})) || doc.version
|
||||
}, Promise.resolve())
|
||||
|
||||
return docRef
|
||||
|
||||
@@ -97,10 +97,11 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -115,7 +116,9 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
@@ -125,10 +128,11 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
data: result,
|
||||
doc: result,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
operation: 'update',
|
||||
previousDoc,
|
||||
req,
|
||||
@@ -143,7 +147,9 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc,
|
||||
req,
|
||||
})) || result
|
||||
|
||||
@@ -92,10 +92,11 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
}
|
||||
|
||||
const originalDoc = await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth: 0,
|
||||
doc: globalJSON,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -106,10 +107,11 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
data = await beforeValidate({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
operation: 'update',
|
||||
overrideAccess,
|
||||
req,
|
||||
@@ -124,7 +126,9 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
@@ -139,7 +143,9 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
|
||||
data =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
data,
|
||||
global: globalConfig,
|
||||
originalDoc,
|
||||
req,
|
||||
})) || data
|
||||
@@ -150,11 +156,12 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
let result = await beforeChange({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: originalDoc,
|
||||
docWithLocales: globalJSON,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
operation: 'update',
|
||||
req,
|
||||
skipValidation: shouldSaveDraft,
|
||||
@@ -204,10 +211,11 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterRead({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
depth,
|
||||
doc: result,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
@@ -222,7 +230,9 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
req,
|
||||
})) || result
|
||||
}, Promise.resolve())
|
||||
@@ -232,10 +242,11 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
// /////////////////////////////////////
|
||||
|
||||
result = await afterChange({
|
||||
collection: null,
|
||||
context: req.context,
|
||||
data,
|
||||
doc: result,
|
||||
entityConfig: globalConfig,
|
||||
global: globalConfig,
|
||||
operation: 'update',
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
@@ -250,7 +261,9 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
|
||||
|
||||
result =
|
||||
(await hook({
|
||||
context: req.context,
|
||||
doc: result,
|
||||
global: globalConfig,
|
||||
previousDoc: originalDoc,
|
||||
req,
|
||||
})) || result
|
||||
|
||||
@@ -35,7 +35,7 @@ const errorHandler = async (
|
||||
}
|
||||
|
||||
if (afterErrorHook) {
|
||||
;({ response } = (await afterErrorHook(err, response, null)) || { response })
|
||||
;({ response } = (await afterErrorHook(err, response, null, null)) || { response })
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
@@ -429,8 +429,13 @@ function buildObjectType({
|
||||
if (typeof args.depth !== 'undefined') depth = args.depth
|
||||
const editor: RichTextAdapter = field?.editor
|
||||
|
||||
if (editor?.afterReadPromise) {
|
||||
await editor?.afterReadPromise({
|
||||
// RichText fields have their own depth argument in GraphQL.
|
||||
// This is why the populationPromise (which populates richtext fields like uploads and relationships)
|
||||
// is run here again, with the provided depth.
|
||||
// In the graphql find.ts resolver, the depth is then hard-coded to 0.
|
||||
// Effectively, this means that the populationPromise for GraphQL is only run here, and not in the find.ts resolver / normal population promise.
|
||||
if (editor?.populationPromise) {
|
||||
await editor?.populationPromise({
|
||||
depth,
|
||||
field,
|
||||
req: context.req,
|
||||
|
||||
7
packages/plugin-stripe/.gitignore
vendored
Normal file
7
packages/plugin-stripe/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.env
|
||||
dist
|
||||
demo/uploads
|
||||
build
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
15
packages/plugin-stripe/.swcrc
Normal file
15
packages/plugin-stripe/.swcrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/swcrc",
|
||||
"sourceMaps": "inline",
|
||||
"jsc": {
|
||||
"target": "esnext",
|
||||
"parser": {
|
||||
"syntax": "typescript",
|
||||
"tsx": true,
|
||||
"dts": true
|
||||
}
|
||||
},
|
||||
"module": {
|
||||
"type": "commonjs"
|
||||
}
|
||||
}
|
||||
289
packages/plugin-stripe/README.md
Normal file
289
packages/plugin-stripe/README.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# Payload Stripe Plugin
|
||||
|
||||
[](https://www.npmjs.com/package/@payloadcms/plugin-stripe)
|
||||
|
||||
A plugin for [Payload](https://github.com/payloadcms/payload) to connect [Stripe](https://stripe.com) and Payload.
|
||||
|
||||
Core features:
|
||||
|
||||
- Hides your Stripe credentials when shipping SaaS applications
|
||||
- Allows restricted keys through [Payload access control](https://payloadcms.com/docs/access-control/overview)
|
||||
- Enables a two-way communication channel between Stripe and Payload
|
||||
- Proxies the [Stripe REST API](https://stripe.com/docs/api)
|
||||
- Proxies [Stripe webhooks](https://stripe.com/docs/webhooks)
|
||||
- Automatically syncs data between the two platforms
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @payloadcms/plugin-stripe
|
||||
# OR
|
||||
npm i @payloadcms/plugin-stripe
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
In the `plugins` array of your [Payload config](https://payloadcms.com/docs/configuration/overview), call the plugin with [options](#options):
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config'
|
||||
import stripePlugin from '@payloadcms/plugin-stripe'
|
||||
|
||||
const config = buildConfig({
|
||||
plugins: [
|
||||
stripePlugin({
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `stripeSecretKey`: string
|
||||
|
||||
Required. Your Stripe secret key.
|
||||
|
||||
- `sync`: array
|
||||
|
||||
Optional. An array of sync configs. This will automatically configure a sync between Payload collections and Stripe resources on create, delete, and update. See [sync](#sync) for more details.
|
||||
|
||||
- `stripeWebhooksEndpointSecret`: string
|
||||
|
||||
Optional. Your Stripe webhook endpoint secret. This is needed only if you wish to sync data _from_ Stripe _to_ Payload.
|
||||
|
||||
- `rest`: boolean
|
||||
|
||||
Optional. When `true`, opens the `/api/stripe/rest` endpoint. See [endpoints](#endpoints) for more details.
|
||||
|
||||
- `webhooks`: object | function
|
||||
|
||||
Optional. Either a function to handle all webhooks events, or an object of Stripe webhook handlers, keyed to the name of the event. See [webhooks](#webhooks) for more details or for a list of all available webhooks, see [here](https://stripe.com/docs/cli/trigger#trigger-event).
|
||||
|
||||
- `logs`: boolean
|
||||
|
||||
Optional. When `true`, logs sync events to the console as they happen.
|
||||
|
||||
## Sync
|
||||
|
||||
This option will setup a basic sync between Payload collections and Stripe resources for you automatically. It will create all the necessary hooks and webhooks handlers, so the only thing you have to do is map your Payload fields to their corresponding Stripe properties. As documents are created, updated, and deleted from either Stripe or Payload, the changes are reflected on either side.
|
||||
|
||||
> NOTE: If you wish to enable a _two-way_ sync, be sure to setup [`webhooks`](#webhooks) and pass the `stripeWebhooksEndpointSecret` through your config.
|
||||
|
||||
> NOTE: Due to limitations in the Stripe API, this currently only works with top-level fields. This is because every Stripe object is a separate entity, making it difficult to abstract into a simple reusable library. In the future, we may find a pattern around this. But for now, cases like that will need to be hard-coded. See the [demo](./demo) for an example of this.
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config'
|
||||
import stripePlugin from '@payloadcms/plugin-stripe'
|
||||
|
||||
const config = buildConfig({
|
||||
plugins: [
|
||||
stripePlugin({
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
|
||||
sync: [
|
||||
{
|
||||
collection: 'customers',
|
||||
stripeResourceType: 'customers',
|
||||
stripeResourceTypeSingular: 'customer',
|
||||
fields: [
|
||||
{
|
||||
fieldPath: 'name', // this is a field on your own Payload config
|
||||
stripeProperty: 'name', // use dot notation, if applicable
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
Using `sync` will do the following:
|
||||
|
||||
- Adds and maintains a `stripeID` read-only field on each collection, this is a field generated _by Stripe_ and used as a cross-reference
|
||||
- Adds a direct link to the resource on Stripe.com
|
||||
- Adds and maintains an `skipSync` read-only flag on each collection to prevent infinite syncs when hooks trigger webhooks
|
||||
- Adds the following hooks to each collection:
|
||||
- `beforeValidate`: `createNewInStripe`
|
||||
- `beforeChange`: `syncExistingWithStripe`
|
||||
- `afterDelete`: `deleteFromStripe`
|
||||
- Handles the following Stripe webhooks
|
||||
- `STRIPE_TYPE.created`: `handleCreatedOrUpdated`
|
||||
- `STRIPE_TYPE.updated`: `handleCreatedOrUpdated`
|
||||
- `STRIPE_TYPE.deleted`: `handleDeleted`
|
||||
|
||||
### Endpoints
|
||||
|
||||
The following custom endpoints are automatically opened for you:
|
||||
|
||||
> NOTE: the `/api` part of these routes may be different based on the settings defined in your Payload config.
|
||||
|
||||
- #### `POST /api/stripe/rest`
|
||||
|
||||
If `rest` is true, proxies the [Stripe REST API](https://stripe.com/docs/api) behind [Payload access control](https://payloadcms.com/docs/access-control/overview) and returns the result. If you need to proxy the API server-side, use the [stripeProxy](#node) function.
|
||||
|
||||
```js
|
||||
const res = await fetch(`/api/stripe/rest`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
// Authorization: `JWT ${token}` // NOTE: do this if not in a browser (i.e. curl or Postman)
|
||||
},
|
||||
body: JSON.stringify({
|
||||
stripeMethod: 'stripe.subscriptions.list',
|
||||
stripeArgs: [
|
||||
{
|
||||
customer: 'abc',
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
```
|
||||
|
||||
- #### `POST /stripe/webhooks`
|
||||
|
||||
Returns an http status code. This is where all Stripe webhook events are sent to be handled. See [webhooks](#webhooks).
|
||||
|
||||
### Webhooks
|
||||
|
||||
[Stripe webhooks](https://stripe.com/docs/webhooks) are used to sync from Stripe to Payload. Webhooks listen for events on your Stripe account so you can trigger reactions to them. Follow the steps below to enable webhooks.
|
||||
|
||||
Development:
|
||||
|
||||
1. Login using Stripe cli `stripe login`
|
||||
1. Forward events to localhost `stripe listen --forward-to localhost:3000/stripe/webhooks`
|
||||
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
|
||||
|
||||
Production:
|
||||
|
||||
1. Login and [create a new webhook](https://dashboard.stripe.com/test/webhooks/create) from the Stripe dashboard
|
||||
1. Paste `YOUR_DOMAIN_NAME/api/stripe/webhooks` as the "Webhook Endpoint URL"
|
||||
1. Select which events to broadcast
|
||||
1. Paste the given secret into your `.env` file as `STRIPE_WEBHOOKS_ENDPOINT_SECRET`
|
||||
1. Then, handle these events using the `webhooks` portion of this plugin's config:
|
||||
|
||||
```js
|
||||
import { buildConfig } from 'payload/config'
|
||||
import stripePlugin from '@payloadcms/plugin-stripe'
|
||||
|
||||
const config = buildConfig({
|
||||
plugins: [
|
||||
stripePlugin({
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
stripeWebhooksEndpointSecret: process.env.STRIPE_WEBHOOKS_ENDPOINT_SECRET,
|
||||
webhooks: {
|
||||
'customer.subscription.updated': ({ event, stripe, stripeConfig }) => {
|
||||
// do something...
|
||||
},
|
||||
},
|
||||
// NOTE: you can also catch all Stripe webhook events and handle the event types yourself
|
||||
// webhooks: (event, stripe, stripeConfig) => {
|
||||
// switch (event.type): {
|
||||
// case 'customer.subscription.updated': {
|
||||
// // do something...
|
||||
// break;
|
||||
// }
|
||||
// default: {
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
export default config
|
||||
```
|
||||
|
||||
For a full list of available webhooks, see [here](https://stripe.com/docs/cli/trigger#trigger-event).
|
||||
|
||||
### Node
|
||||
|
||||
On the server you should interface with Stripe directly using the [stripe](https://www.npmjs.com/package/stripe) npm module. That might look something like this:
|
||||
|
||||
```js
|
||||
import Stripe from 'stripe'
|
||||
|
||||
const stripeSecretKey = process.env.STRIPE_SECRET_KEY
|
||||
const stripe = new Stripe(stripeSecretKey, { apiVersion: '2022-08-01' })
|
||||
|
||||
export const MyFunction = async () => {
|
||||
try {
|
||||
const customer = await stripe.customers.create({
|
||||
email: data.email,
|
||||
})
|
||||
|
||||
// do something...
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, you can interface with the Stripe using the `stripeProxy`, which is exactly what the `/api/stripe/rest` endpoint does behind-the-scenes. Here's the same example as above, but piped through the proxy:
|
||||
|
||||
```js
|
||||
import { stripeProxy } from '@payloadcms/plugin-stripe'
|
||||
|
||||
export const MyFunction = async () => {
|
||||
try {
|
||||
const customer = await stripeProxy({
|
||||
stripeSecretKey: process.env.STRIPE_SECRET_KEY,
|
||||
stripeMethod: 'customers.create',
|
||||
stripeArgs: [
|
||||
{
|
||||
email: data.email,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
if (customer.status === 200) {
|
||||
// do something...
|
||||
}
|
||||
|
||||
if (customer.status >= 400) {
|
||||
throw new Error(customer.message)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error.message)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types can be directly imported:
|
||||
|
||||
```js
|
||||
import {
|
||||
StripeConfig,
|
||||
StripeWebhookHandler,
|
||||
StripeProxy,
|
||||
...
|
||||
} from '@payloadcms/plugin-stripe/types';
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
For development purposes, there is a full working example of how this plugin might be used in the [demo](./demo) of this repo. This demo can be developed locally using any Stripe account, you just need a working API key. Then:
|
||||
|
||||
```bash
|
||||
git clone git@github.com:payloadcms/plugin-stripe.git \
|
||||
cd plugin-stripe && yarn \
|
||||
cd demo && yarn \
|
||||
cp .env.example .env \
|
||||
vim .env \ # add your Stripe creds to this file
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Now you have a running Payload server with this plugin installed, so you can authenticate and begin hitting the routes. To do this, open [Postman](https://www.postman.com/) and import [our config](https://github.com/payloadcms/plugin-stripe/blob/main/src/payload-stripe-plugin.postman_collection.json). First, login to retrieve your Payload access token. This token is automatically attached to the header of all other requests.
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!--  -->
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user