Compare commits
3 Commits
test-ignor
...
chore/defa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c9e613643e | ||
|
|
825d68e816 | ||
|
|
0fb09d69bc |
245
.github/workflows/main.yml
vendored
245
.github/workflows/main.yml
vendored
@@ -266,12 +266,257 @@ jobs:
|
||||
command: pnpm test:int
|
||||
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile
|
||||
|
||||
tests-e2e:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
name: e2e-${{ matrix.suite }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# find test -type f -name 'e2e.spec.ts' | sort | xargs dirname | xargs -I {} basename {}
|
||||
suite:
|
||||
- _community
|
||||
- access-control
|
||||
- admin__e2e__general
|
||||
- admin__e2e__list-view
|
||||
- admin__e2e__document-view
|
||||
- admin-root
|
||||
- auth
|
||||
- auth-basic
|
||||
- joins
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
- fields__collections__Array
|
||||
- fields__collections__Blocks
|
||||
- fields__collections__Collapsible
|
||||
- fields__collections__ConditionalLogic
|
||||
- fields__collections__CustomID
|
||||
- fields__collections__Date
|
||||
- fields__collections__Email
|
||||
- fields__collections__Indexed
|
||||
- fields__collections__JSON
|
||||
- fields__collections__Lexical__e2e__main
|
||||
- fields__collections__Lexical__e2e__blocks
|
||||
- fields__collections__Number
|
||||
- fields__collections__Point
|
||||
- fields__collections__Radio
|
||||
- fields__collections__Relationship
|
||||
- fields__collections__RichText
|
||||
- fields__collections__Row
|
||||
- fields__collections__Select
|
||||
- fields__collections__Tabs
|
||||
- fields__collections__Tabs2
|
||||
- fields__collections__Text
|
||||
- fields__collections__UI
|
||||
- fields__collections__Upload
|
||||
- live-preview
|
||||
- localization
|
||||
- locked-documents
|
||||
- i18n
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-seo
|
||||
- versions
|
||||
- uploads
|
||||
env:
|
||||
SUITE_NAME: ${{ matrix.suite }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start LocalStack
|
||||
run: pnpm docker:start
|
||||
if: ${{ matrix.suite == 'plugin-cloud-storage' }}
|
||||
|
||||
- name: Store Playwright's Version
|
||||
run: |
|
||||
# Extract the version number using a more targeted regex pattern with awk
|
||||
PLAYWRIGHT_VERSION=$(pnpm ls @playwright/test --depth=0 | awk '/@playwright\/test/ {print $2}')
|
||||
echo "Playwright's Version: $PLAYWRIGHT_VERSION"
|
||||
echo "PLAYWRIGHT_VERSION=$PLAYWRIGHT_VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Cache Playwright Browsers for Playwright's Version
|
||||
id: cache-playwright-browsers
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-browsers-${{ env.PLAYWRIGHT_VERSION }}
|
||||
|
||||
- name: Setup Playwright - Browsers and Dependencies
|
||||
if: steps.cache-playwright-browsers.outputs.cache-hit != 'true'
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Setup Playwright - Dependencies-only
|
||||
if: steps.cache-playwright-browsers.outputs.cache-hit == 'true'
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: E2E Tests
|
||||
uses: nick-fields/retry@v3
|
||||
with:
|
||||
retry_on: any
|
||||
max_attempts: 5
|
||||
timeout_minutes: 20
|
||||
command: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
|
||||
on_retry_command: pnpm clean:build && pnpm install --no-frozen-lockfile && pnpm build:all
|
||||
env:
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-${{ matrix.suite }}
|
||||
path: test/test-results/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
|
||||
# Disabled until this is fixed: https://github.com/daun/playwright-report-summary/issues/156
|
||||
# - uses: daun/playwright-report-summary@v3
|
||||
# with:
|
||||
# report-file: results_${{ matrix.suite }}.json
|
||||
# report-tag: ${{ matrix.suite }}
|
||||
# job-summary: true
|
||||
|
||||
# Build listed templates with packed local packages
|
||||
build-templates:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- template: blank
|
||||
database: mongodb
|
||||
- template: website
|
||||
database: mongodb
|
||||
- template: with-payload-cloud
|
||||
database: mongodb
|
||||
- template: with-vercel-mongodb
|
||||
database: mongodb
|
||||
# Postgres
|
||||
- template: with-postgres
|
||||
database: postgres
|
||||
- template: with-vercel-postgres
|
||||
database: postgres
|
||||
|
||||
- template: plugin
|
||||
|
||||
# Re-enable once PG conncection is figured out
|
||||
# - template: with-vercel-website
|
||||
# database: postgres
|
||||
|
||||
name: ${{ matrix.template }}-${{ matrix.database }}
|
||||
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: payloadtests
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Start PostgreSQL
|
||||
uses: CasperWA/postgresql-action@v1.2
|
||||
with:
|
||||
postgresql version: '14' # See https://hub.docker.com/_/postgres for available versions
|
||||
postgresql db: ${{ env.POSTGRES_DB }}
|
||||
postgresql user: ${{ env.POSTGRES_USER }}
|
||||
postgresql password: ${{ env.POSTGRES_PASSWORD }}
|
||||
if: matrix.database == 'postgres'
|
||||
|
||||
- name: Wait for PostgreSQL
|
||||
run: sleep 30
|
||||
if: matrix.database == 'postgres'
|
||||
|
||||
- name: Configure PostgreSQL
|
||||
run: |
|
||||
psql "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" -c "CREATE ROLE runner SUPERUSER LOGIN;"
|
||||
psql "postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" -c "SELECT version();"
|
||||
echo "POSTGRES_URL=postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@localhost:5432/$POSTGRES_DB" >> $GITHUB_ENV
|
||||
if: matrix.database == 'postgres'
|
||||
|
||||
- name: Start MongoDB
|
||||
uses: supercharge/mongodb-github-action@1.11.0
|
||||
with:
|
||||
mongodb-version: 6.0
|
||||
if: matrix.database == 'mongodb'
|
||||
|
||||
- name: Build Template
|
||||
run: |
|
||||
pnpm run script:pack --dest templates/${{ matrix.template }}
|
||||
pnpm runts scripts/build-template-with-local-pkgs.ts ${{ matrix.template }} $POSTGRES_URL
|
||||
|
||||
tests-type-generation:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: ${{ needs.changes.outputs.needs_tests == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Node setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
pnpm-version: ${{ env.PNPM_VERSION }}
|
||||
pnpm-run-install: false
|
||||
pnpm-restore-cache: false # Full build is restored below
|
||||
pnpm-install-cache-key: pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
|
||||
- name: Restore build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ./*
|
||||
key: ${{ github.sha }}-${{ github.run_number }}
|
||||
|
||||
- name: Generate Payload Types
|
||||
run: pnpm dev:generate-types fields
|
||||
|
||||
- name: Generate GraphQL schema file
|
||||
run: pnpm dev:generate-graphql-schema graphql-schema-gen
|
||||
|
||||
all-green:
|
||||
name: All Green
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- lint
|
||||
- build
|
||||
- build-templates
|
||||
- tests-unit
|
||||
- tests-int
|
||||
- tests-e2e
|
||||
- tests-types
|
||||
- tests-type-generation
|
||||
|
||||
steps:
|
||||
- if: ${{ always() && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')) }}
|
||||
|
||||
@@ -65,18 +65,18 @@ export const MyCollection: SanitizedCollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`beforeList`** | An array of components to inject _before_ the built-in List View |
|
||||
| **`beforeListTable`** | An array of components to inject _before_ the built-in List View's table |
|
||||
| **`afterList`** | An array of components to inject _after_ the built-in List View |
|
||||
| **`afterListTable`** | An array of components to inject _after_ the built-in List View's table |
|
||||
| **`Description`** | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. |
|
||||
| **`edit.SaveButton`** | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. |
|
||||
| Path | Description |
|
||||
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`beforeList`** | An array of components to inject _before_ the built-in List View |
|
||||
| **`beforeListTable`** | An array of components to inject _before_ the built-in List View's table |
|
||||
| **`afterList`** | An array of components to inject _after_ the built-in List View |
|
||||
| **`afterListTable`** | An array of components to inject _after_ the built-in List View's table
|
||||
| **`Description`** | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. |
|
||||
| **`edit.SaveButton`** | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. |
|
||||
| **`edit.SaveDraftButton`** | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. |
|
||||
| **`edit.PublishButton`** | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. |
|
||||
| **`edit.PreviewButton`** | Replace the default Preview Button with a Custom Component. [Preview](#preview) must be enabled. |
|
||||
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
|
||||
| **`edit.PreviewButton`** | Replace the default Preview Button with a Custom Component. [Preview](#preview) must be enabled. |
|
||||
| **`views`** | Override or create new views within the Admin Panel. [More details](./views). |
|
||||
|
||||
<Banner type="success">
|
||||
**Note:**
|
||||
|
||||
@@ -242,31 +242,31 @@ _For details on how to build Custom Components, see [Building Custom Components]
|
||||
|
||||
All Field Components receive the following props by default:
|
||||
|
||||
| Property | Description |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document. |
|
||||
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
|
||||
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
|
||||
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`validate`** | A function that can be used to validate the field. |
|
||||
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
|
||||
| **`schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `posts.myGroup.myArray.myField`. |
|
||||
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
|
||||
| Property | Description |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`docPreferences`** | An object that contains the [Preferences](./preferences) for the document.
|
||||
| **`field`** | In Client Components, this is the sanitized Client Field Config. In Server Components, this is the original Field Config. Server Components will also receive the sanitized field config through the`clientField` prop (see below). |
|
||||
| **`locale`** | The locale of the field. [More details](../configuration/localization). |
|
||||
| **`readOnly`** | A boolean value that represents if the field is read-only or not. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`validate`** | A function that can be used to validate the field. |
|
||||
| **`path`** | A string representing the direct, dynamic path to the field at runtime, i.e. `myGroup.myArray.0.myField`. |
|
||||
| **`schemaPath`** | A string representing the direct, static path to the [Field Config](../fields/overview), i.e. `posts.myGroup.myArray.myField`. |
|
||||
| **`indexPath`** | A hyphen-notated string representing the path to the field _within the nearest named ancestor field_, i.e. `0-0` |
|
||||
|
||||
In addition to the above props, all Server Components will also receive the following props:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------------- |
|
||||
| **`clientField`** | The serializable Client Field Config. |
|
||||
| **`field`** | The Field Config. [More details](../fields/overview). |
|
||||
| **`data`** | The current document being edited. |
|
||||
| **`i18n`** | The [i18n](../configuration/i18n) object. |
|
||||
| **`payload`** | The [Payload](../local-api/overview) class. |
|
||||
| **`permissions`** | The field permissions based on the currently authenticated user. |
|
||||
| **`siblingData`** | The data of the field's siblings. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`value`** | The value of the field at render-time. |
|
||||
| Property | Description |
|
||||
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`clientField`** | The serializable Client Field Config. |
|
||||
| **`field`** | The Field Config. [More details](../fields/overview). |
|
||||
| **`data`** | The current document being edited. |
|
||||
| **`i18n`** | The [i18n](../configuration/i18n) object.
|
||||
| **`payload`** | The [Payload](../local-api/overview) class. |
|
||||
| **`permissions`** | The field permissions based on the currently authenticated user. |
|
||||
| **`siblingData`** | The data of the field's siblings. |
|
||||
| **`user`** | The currently authenticated user. [More details](../authentication/overview). |
|
||||
| **`value`** | The value of the field at render-time. |
|
||||
|
||||
#### Sending and receiving values from the form
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ You can send the following actions to the `dispatchFields` function.
|
||||
| **`REPLACE_STATE`** | Completely replaces form state |
|
||||
| **`UPDATE`** | Update any property of a specific field's state |
|
||||
|
||||
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/main/packages/ui/src/forms/Form/types.ts).
|
||||
To see types for each action supported within the `dispatchFields` hook, check out the Form types [here](https://github.com/payloadcms/payload/blob/main/packages/payload/src/admin/components/forms/Form/types.ts).
|
||||
|
||||
## useForm
|
||||
|
||||
@@ -679,25 +679,20 @@ const CustomComponent: React.FC = () => {
|
||||
|
||||
## useDocumentInfo
|
||||
|
||||
The `useDocumentInfo` hook provides information about the current document being edited, including the following:
|
||||
The `useDocumentInfo` hook provides lots of information about the document currently being edited, including the following:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`currentEditor`** | The user currently editing the document. |
|
||||
| **`docConfig`** | Either the Collection or Global config of the document, depending on what is being edited. |
|
||||
| **`documentIsLocked`** | Whether the document is currently locked by another user. |
|
||||
| **`collection`** | If the doc is a collection, its Collection Config will be returned |
|
||||
| **`global`** | If the doc is a global, its Global Config will be returned |
|
||||
| **`id`** | If the doc is a collection, its ID will be returned |
|
||||
| **`getDocPermissions`** | Method to retrieve document-level user preferences. |
|
||||
| **`getDocPreferences`** | Method to retrieve document-level user preferences. |
|
||||
| **`hasPublishedDoc`** | Whether the document has a published version. |
|
||||
| **`incrementVersionCount`** | Method to increment the version count of the document. |
|
||||
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences. |
|
||||
| **`versions`** | Versions of the current doc. |
|
||||
| **`unpublishedVersions`** | Unpublished versions of the current doc. |
|
||||
| **`publishedDoc`** | The currently published version of the doc being edited. |
|
||||
| **`getVersions`** | Method to retrieve document versions. |
|
||||
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create). |
|
||||
| **`versionCount`** | The current version count of the document. |
|
||||
| **`preferencesKey`** | The `preferences` key to use when interacting with document-level user preferences |
|
||||
| **`versions`** | Versions of the current doc |
|
||||
| **`unpublishedVersions`** | Unpublished versions of the current doc |
|
||||
| **`publishedDoc`** | The currently published version of the doc being edited |
|
||||
| **`getVersions`** | Method to trigger the retrieval of document versions |
|
||||
| **`docPermissions`** | The current documents permissions. Collection document permissions fallback when no id is present (i.e. on create) |
|
||||
| **`getDocPermissions`** | Method to trigger the retrieval of document level permissions |
|
||||
|
||||
**Example:**
|
||||
|
||||
@@ -723,37 +718,6 @@ const LinkFromCategoryToPosts: React.FC = () => {
|
||||
}
|
||||
```
|
||||
|
||||
## useListQuery
|
||||
|
||||
The `useListQuery` hook is used to subscribe to the data, current query, and other properties used within the List View. You can use this hook within any Custom Component rendered within the List View.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { useListQuery } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const { data, query } = useListQuery()
|
||||
// highlight-end
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The `useListQuery` hook returns an object with the following properties:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------- |
|
||||
| **`data`** | The data that is being displayed in the List View. |
|
||||
| **`defaultLimit`**| The default limit of items to display in the List View. |
|
||||
| **`defaultSort`** | The default sort order of items in the List View. |
|
||||
| **`handlePageChange`** | A method to handle page changes in the List View. |
|
||||
| **`handlePerPageChange`** | A method to handle per page changes in the List View. |
|
||||
| **`handleSearchChange`** | A method to handle search changes in the List View. |
|
||||
| **`handleSortChange`** | A method to handle sort changes in the List View. |
|
||||
| **`handleWhereChange`** | A method to handle where changes in the List View. |
|
||||
| **`query`** | The current query that is being used to fetch the data in the List View. |
|
||||
|
||||
## useLocale
|
||||
|
||||
In any Custom Component you can get the selected locale object with the `useLocale` hook. `useLocale` gives you the full locale object, consisting of a `label`, `rtl`(right-to-left) property, and then `code`. Here is a simple example:
|
||||
|
||||
@@ -89,14 +89,14 @@ For more granular control, pass a configuration object instead. Payload exposes
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`Component`** * | Pass in the component path that should be rendered when a user navigates to this route. |
|
||||
| **`path`** * | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
|
||||
| **`Component`** \* | Pass in the component path that should be rendered when a user navigates to this route. |
|
||||
| **`path`** \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
|
||||
| **`exact`** | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
|
||||
| **`strict`** | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
|
||||
| **`sensitive`** | When true, will match if the path is case sensitive.|
|
||||
| **`meta`** | Page metadata overrides to apply to this view within the Admin Panel. [More details](./metadata). |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Adding New Views
|
||||
|
||||
|
||||
@@ -19,15 +19,15 @@ A strategy is made up of the following:
|
||||
|
||||
| Parameter | Description |
|
||||
| --------------------------- | ------------------------------------------------------------------------- |
|
||||
| **`name`** * | The name of your strategy |
|
||||
| **`authenticate`** * | A function that takes in the parameters below and returns a user or null. |
|
||||
| **`name`** \* | The name of your strategy |
|
||||
| **`authenticate`** \* | A function that takes in the parameters below and returns a user or null. |
|
||||
|
||||
The `authenticate` function is passed the following arguments:
|
||||
|
||||
| Argument | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **`headers`** * | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
|
||||
| **`payload`** * | The Payload class. Useful for authenticating the identifiable information against Payload. |
|
||||
| **`headers`** \* | The headers on the incoming request. Useful for retrieving identifiable information on a request. |
|
||||
| **`payload`** \* | The Payload class. Useful for authenticating the identifiable information against Payload. |
|
||||
| **`isGraphQL`** | Whether or not the request was made from a GraphQL endpoint. Default is `false`. |
|
||||
|
||||
|
||||
|
||||
@@ -269,10 +269,6 @@ const result = await payload.verifyEmail({
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** the token you need to pass to the `verifyEmail` function is unique to verification and is not the same as the token that you can retrieve from the `forgotPassword` operation. It can be found on the user document, as a hidden `_verificationToken` field. If you'd like to retrieve this token, you can use the Local API's `find` or `findByID` methods, setting `showHiddenFields: true`.
|
||||
|
||||
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to create one for you if the user was created via REST or GraphQL by looking at the incoming `req`. But this is not supported if you are creating the user via the Local API's `payload.create()` method. If this applies to you, and you do not have a `serverURL` set, you may want to override your `verify.generateEmailHTML` function to provide a full URL to link the user to a proper verification page.
|
||||
|
||||
## Unlock
|
||||
|
||||
If a user locks themselves out and you wish to deliberately unlock them, you can utilize the Unlock operation. The [Admin Panel](../admin/overview) features an Unlock control automatically for all collections that feature max login attempts, but you can programmatically unlock users as well by using the Unlock operation.
|
||||
@@ -348,8 +344,6 @@ const token = await payload.forgotPassword({
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** if you do not have a `config.serverURL` set, Payload will attempt to create one for you if the `forgot-password` operation was triggered via REST or GraphQL by looking at the incoming `req`. But this is not supported if you are calling `payload.forgotPassword()` via the Local API. If you do not have a `serverURL` set, you may want to override your `auth.forgotPassword.generateEmailHTML` function to provide a full URL to link the user to a proper reset-password page.
|
||||
|
||||
<Banner type="success">
|
||||
**Tip:**
|
||||
|
||||
|
||||
@@ -67,19 +67,19 @@ The following options are available:
|
||||
| **`defaultSort`** | Pass a top-level field to sort by default in the Collection List View. Prefix the name of the field with a minus symbol ("-") to sort in descending order. Multiple fields can be specified by using a string array. |
|
||||
| **`dbName`** | Custom table or Collection name depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. Set to `false` to disable routes. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** * | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
|
||||
| **`graphQL`** | Manage GraphQL-related properties for this collection. [More](#graphql) |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Collection. [More details](../fields/overview). |
|
||||
| **`graphQL`** | An object with `singularName` and `pluralName` strings used in schema generation. Auto-generated from slug if not defined. Set to `false` to disable GraphQL. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#collection-hooks). |
|
||||
| **`labels`** | Singular and plural labels for use in identifying this Collection throughout Payload. Auto-generated from slug if not defined. |
|
||||
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
|
||||
| **`slug`** * | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Collection. |
|
||||
| **`timestamps`** | Set to false to disable documents' automatically generated `createdAt` and `updatedAt` timestamps. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`upload`** | Specify options if you would like this Collection to support file uploads. For more, consult the [Uploads](../upload/overview) documentation. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#collection-config). |
|
||||
| **`defaultPopulate`** | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
|
||||
| **`defaultPopulate`** | Specify which fields to select when this Collection is populated from another document. [More Details](../queries/select#defaultpopulate-collection-config-property). |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Fields
|
||||
|
||||
@@ -97,19 +97,6 @@ Fields define the schema of the Documents within a Collection. To learn more, go
|
||||
|
||||
You can customize the way that the [Admin Panel](../admin/overview) behaves on a Collection-by-Collection basis. To learn more, go to the [Collection Admin Options](../admin/collections) documentation.
|
||||
|
||||
## GraphQL
|
||||
|
||||
You can completely disable GraphQL for this collection by passing `graphQL: false` to your collection config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.
|
||||
|
||||
You can also pass an object to the collection's `graphQL` property, which allows you to define the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- |
|
||||
| **`singularName`** | Override the "singular" name that will be used in GraphQL schema generation. |
|
||||
| **`pluralName`** | Override the "plural" name that will be used in GraphQL schema generation. |
|
||||
| **`disableQueries`** | Disable all GraphQL queries that correspond to this collection by passing `true`. |
|
||||
| **`disableMutations`** | Disable all GraphQL mutations that correspond to this collection by passing `true`. |
|
||||
|
||||
## TypeScript
|
||||
|
||||
You can import types from Payload to help make writing your Collection configs easier and type-safe. There are two main types that represent the Collection Config, `CollectionConfig` and `SanitizeCollectionConfig`.
|
||||
|
||||
@@ -73,16 +73,16 @@ The following options are available:
|
||||
| **`dbName`** | Custom table or collection name for this Global depending on the Database Adapter. Auto-generated from slug if not defined. |
|
||||
| **`description`** | Text or React component to display below the Global header to give editors more information. |
|
||||
| **`endpoints`** | Add custom routes to the REST API. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`fields`** * | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
|
||||
| **`graphQL`** | Manage GraphQL-related properties related to this global. [More details](#graphql) |
|
||||
| **`fields`** \* | Array of field types that will determine the structure and functionality of the data stored within this Global. [More details](../fields/overview). |
|
||||
| **`graphQL.name`** | Text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`hooks`** | Entry point for Hooks. [More details](../hooks/overview#global-hooks). |
|
||||
| **`label`** | Text for the name in the Admin Panel or an object with keys for each language. Auto-generated from slug if not defined. |
|
||||
| **`lockDocuments`** | Enables or disables document locking. By default, document locking is enabled. Set to an object to configure, or set to `false` to disable locking. [More details](../admin/locked-documents). |
|
||||
| **`slug`** * | Unique, URL-friendly string that will act as an identifier for this Global. |
|
||||
| **`slug`** \* | Unique, URL-friendly string that will act as an identifier for this Global. |
|
||||
| **`typescript`** | An object with property `interface` as the text used in schema generation. Auto-generated from slug if not defined. |
|
||||
| **`versions`** | Set to true to enable default options, or configure with object properties. [More details](../versions/overview#globals-config). |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### Fields
|
||||
|
||||
@@ -100,18 +100,6 @@ Fields define the schema of the Global. To learn more, go to the [Fields](../fie
|
||||
|
||||
You can customize the way that the [Admin Panel](../admin/overview) behaves on a Global-by-Global basis. To learn more, go to the [Global Admin Options](../admin/globals) documentation.
|
||||
|
||||
## GraphQL
|
||||
|
||||
You can completely disable GraphQL for this global by passing `graphQL: false` to your global config. This will completely disable all queries, mutations, and types from appearing in your GraphQL schema.
|
||||
|
||||
You can also pass an object to the global's `graphQL` property, which allows you to define the following properties:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------- |
|
||||
| **`name`** | Override the name that will be used in GraphQL schema generation. |
|
||||
| **`disableQueries`** | Disable all GraphQL queries that correspond to this global by passing `true`. |
|
||||
| **`disableMutations`** | Disable all GraphQL mutations that correspond to this global by passing `true`. |
|
||||
|
||||
## TypeScript
|
||||
|
||||
You can import types from Payload to help make writing your Global configs easier and type-safe. There are two main types that represent the Global Config, `GlobalConfig` and `SanitizeGlobalConfig`.
|
||||
|
||||
@@ -93,12 +93,12 @@ The locale codes do not need to be in any specific format. It's up to you to def
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`code`** * | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
|
||||
| **`code`** \* | Unique code to identify the language throughout the APIs for `locale` and `fallbackLocale` |
|
||||
| **`label`** | A string to use for the selector when choosing a language, or an object keyed on the i18n keys for different languages in use. |
|
||||
| **`rtl`** | A boolean that when true will make the admin UI display in Right-To-Left. |
|
||||
| **`fallbackLocale`** | The code for this language to fallback to when properties of a document are not present. |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Field Localization
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ The following options are available:
|
||||
| **`admin`** | The configuration options for the Admin Panel, including Custom Components, Live Preview, etc. [More details](../admin/overview#admin-options). |
|
||||
| **`bin`** | Register custom bin scripts for Payload to execute. |
|
||||
| **`editor`** | The Rich Text Editor which will be used by `richText` fields. [More details](../rich-text/overview). |
|
||||
| **`db`** * | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`db`** \* | The Database Adapter which will be used by Payload. [More details](../database/overview). |
|
||||
| **`serverURL`** | A string used to define the absolute URL of your app. This includes the protocol, for example `https://example.com`. No paths allowed, only protocol, domain and (optionally) port. |
|
||||
| **`collections`** | An array of Collections for Payload to manage. [More details](./collections). |
|
||||
| **`compatibility`** | Compatibility flags for earlier versions of Payload. [More details](#compatibility-flags). |
|
||||
@@ -95,17 +95,18 @@ The following options are available:
|
||||
| **`endpoints`** | An array of Custom Endpoints added to the Payload router. [More details](../rest-api/overview#custom-endpoints). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins). |
|
||||
| **`i18n`** | Internationalization configuration. Pass all i18n languages you'd like the admin UI to support. Defaults to English-only. [More details](./i18n). |
|
||||
| **`secret`** * | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`secret`** \* | A secure, unguessable string that Payload will use for any encryption workflows - for example, password salt / hashing. |
|
||||
| **`sharp`** | If you would like Payload to offer cropping, focal point selection, and automatic media resizing, install and pass the Sharp module to the config here. |
|
||||
| **`typescript`** | Configure TypeScript settings here. [More details](#typescript). |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
**Note:**
|
||||
Some properties are removed from the client-side bundle. [More details](../admin/components#accessing-the-payload-config).
|
||||
</Banner>
|
||||
|
||||
|
||||
### Typescript Config
|
||||
|
||||
Payload exposes a variety of TypeScript settings that you can leverage. These settings are used to auto-generate TypeScript interfaces for your [Collections](../configuration/collections) and [Globals](../configuration/globals), and to ensure that Payload uses your [Generated Types](../typescript/overview) for all [Local API](../local-api/overview) methods.
|
||||
@@ -139,7 +140,7 @@ For Payload command-line scripts, we need to be able to locate your Payload Conf
|
||||
1. The `compilerOptions` in your `tsconfig`*
|
||||
1. The `dist` directory*
|
||||
|
||||
_* Config location detection is different between development and production environments. See below for more details._
|
||||
_\* Config location detection is different between development and production environments. See below for more details._
|
||||
|
||||
<Banner type="warning">
|
||||
**Important:**
|
||||
|
||||
@@ -59,7 +59,7 @@ export default buildConfig({
|
||||
|
||||
| Option | Description |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pool` * | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
|
||||
| `pool` \* | [Pool connection options](https://orm.drizzle.team/docs/quick-postgresql/node-postgres) that will be passed to Drizzle and `node-postgres` or to `@vercel/postgres` |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `schemaName` (experimental) | A string for the postgres schema to use, defaults to 'public'. |
|
||||
@@ -260,26 +260,24 @@ postgresAdapter({
|
||||
beforeSchemaInit: [
|
||||
({ schema, adapter }) => {
|
||||
// Add a new table
|
||||
adapter.rawTables.myTable = {
|
||||
schema.rawTables.myTable = {
|
||||
name: 'my_table',
|
||||
columns: {
|
||||
my_id: {
|
||||
name: 'my_id',
|
||||
type: 'serial',
|
||||
primaryKey: true
|
||||
}
|
||||
}
|
||||
columns: [{
|
||||
name: 'my_id',
|
||||
type: 'serial',
|
||||
primaryKey: true
|
||||
}],
|
||||
}
|
||||
|
||||
// Add a new column to generated by Payload table:
|
||||
adapter.rawTables.posts.columns.customColumn = {
|
||||
schema.rawTables.posts.columns.customColumn = {
|
||||
name: 'custom_column',
|
||||
// Note that Payload SQL doesn't support everything that Drizzle does.
|
||||
type: 'integer',
|
||||
notNull: true
|
||||
}
|
||||
// Add a new index to generated by Payload table:
|
||||
adapter.rawTables.posts.indexes.customColumnIdx = {
|
||||
schema.rawTables.posts.indexes.customColumnIdx = {
|
||||
name: 'custom_column_idx',
|
||||
unique: true,
|
||||
on: ['custom_column']
|
||||
|
||||
@@ -36,7 +36,7 @@ export default buildConfig({
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `client` * | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
|
||||
| `client` \* | [Client connection options](https://orm.drizzle.team/docs/get-started-sqlite#turso) that will be passed to `createClient` from `@libsql/client`. |
|
||||
| `push` | Disable Drizzle's [`db push`](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push) in development mode. By default, `push` is enabled for development mode only. |
|
||||
| `migrationDir` | Customize the directory that migrations are stored. |
|
||||
| `logger` | The instance of the logger to be passed to drizzle. By default Payload's will be used. |
|
||||
@@ -172,7 +172,7 @@ sqliteAdapter({
|
||||
})
|
||||
```
|
||||
|
||||
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
|
||||
Make sure Payload doesn't overlap table names with its collections. For example, if you already have a collection with slug "users", you should either change the slug or `dbName` to change the table name for this collection.
|
||||
|
||||
|
||||
### afterSchemaInit
|
||||
@@ -237,26 +237,24 @@ sqliteAdapter({
|
||||
beforeSchemaInit: [
|
||||
({ schema, adapter }) => {
|
||||
// Add a new table
|
||||
adapter.rawTables.myTable = {
|
||||
schema.rawTables.myTable = {
|
||||
name: 'my_table',
|
||||
columns: {
|
||||
my_id: {
|
||||
name: 'my_id',
|
||||
type: 'integer',
|
||||
primaryKey: true
|
||||
}
|
||||
}
|
||||
columns: [{
|
||||
name: 'my_id',
|
||||
type: 'integer',
|
||||
primaryKey: true
|
||||
}],
|
||||
}
|
||||
|
||||
// Add a new column to generated by Payload table:
|
||||
adapter.rawTables.posts.columns.customColumn = {
|
||||
schema.rawTables.posts.columns.customColumn = {
|
||||
name: 'custom_column',
|
||||
// Note that Payload SQL doesn't support everything that Drizzle does.
|
||||
type: 'integer',
|
||||
notNull: true
|
||||
}
|
||||
// Add a new index to generated by Payload table:
|
||||
adapter.rawTables.posts.indexes.customColumnIdx = {
|
||||
schema.rawTables.posts.indexes.customColumnIdx = {
|
||||
name: 'custom_column_idx',
|
||||
unique: true,
|
||||
on: ['custom_column']
|
||||
|
||||
@@ -24,8 +24,8 @@ An email adapter will require at least the following fields:
|
||||
|
||||
| Option | Description |
|
||||
| --------------------------- | -------------------------------------------------------------------------------- |
|
||||
| **`defaultFromName`** * | The name part of the From field that will be seen on the delivered email |
|
||||
| **`defaultFromAddress`** * | The email address part of the From field that will be used when delivering email |
|
||||
| **`defaultFromName`** \* | The name part of the From field that will be seen on the delivered email |
|
||||
| **`defaultFromAddress`** \* | The email address part of the From field that will be used when delivering email |
|
||||
|
||||
|
||||
### Official Email Adapters
|
||||
|
||||
@@ -41,9 +41,9 @@ export const MyArrayField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as the heading in the [Admin Panel](../admin/overview) or an object with keys for each language. Auto-generated from name if not defined. |
|
||||
| **`fields`** * | Array of field types to correspond to each row of the Array. |
|
||||
| **`fields`** \* | Array of field types to correspond to each row of the Array. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the [Admin Panel](../admin/overview) and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
|
||||
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
|
||||
@@ -62,7 +62,7 @@ export const MyArrayField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ export const MyBlocksField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as the heading in the Admin Panel or an object with keys for each language. Auto-generated from name if not defined. |
|
||||
| **`blocks`** * | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
|
||||
| **`blocks`** \* | Array of [block configs](/docs/fields/blocks#block-configs) to be made available to this field. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. |
|
||||
| **`maxRows`** | A number for the most allowed items during validation when a value is present. |
|
||||
@@ -60,7 +60,7 @@ export const MyBlocksField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
@@ -145,14 +145,14 @@ Blocks are defined as separate configs of their own.
|
||||
|
||||
| Option | Description |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`slug`** * | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
||||
| **`fields`** * | Array of fields to be stored in this block. |
|
||||
| **`slug`** \* | Identifier for this block type. Will be saved on each block as the `blockType` property. |
|
||||
| **`fields`** \* | Array of fields to be stored in this block. |
|
||||
| **`labels`** | Customize the block labels that appear in the Admin dashboard. Auto-generated from slug if not defined. |
|
||||
| **`imageURL`** | Provide a custom image thumbnail to help editors identify this block in the Admin UI. |
|
||||
| **`imageAltText`** | Customize this block's image thumbnail alt text. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`graphQL.singularName`** | Text to use for the GraphQL schema name. Auto-generated from slug if not defined. NOTE: this is set for deprecation, prefer `interfaceName`. |
|
||||
| **`dbName`** | Custom table name for this block type when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined. |
|
||||
| **`dbName`** | Custom table name for this block type when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from slug if not defined.
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
### Auto-generated data per block
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyCheckboxField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
@@ -46,7 +46,7 @@ export const MyCheckboxField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export const MyBlocksField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database#overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
@@ -50,7 +50,7 @@ export const MyBlocksField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ export const MyCollapsibleField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`label`** * | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
|
||||
| **`fields`** * | Array of field types to nest within this Collapsible. |
|
||||
| **`label`** \* | A label to render within the header of the collapsible component. This can be a string, function or react component. Function/components receive `({ data, path })` as args. |
|
||||
| **`fields`** \* | Array of field types to nest within this Collapsible. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyDateField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
@@ -46,7 +46,7 @@ export const MyDateField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
@@ -70,17 +70,17 @@ The Date Field inherits all of the default options from the base [Field Admin Co
|
||||
| **`placeholder`** | Placeholder text for the field. |
|
||||
| **`date`** | Pass options to customize date field appearance. |
|
||||
| **`date.displayFormat`** | Format date to be shown in field **cell**. |
|
||||
| **`date.pickerAppearance`** * | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
|
||||
| **`date.monthsToShow`** * | Number of months to display max is 2. Defaults to 1. |
|
||||
| **`date.minDate`** * | Min date value to allow. |
|
||||
| **`date.maxDate`** * | Max date value to allow. |
|
||||
| **`date.minTime`** * | Min time value to allow. |
|
||||
| **`date.maxTime`** * | Max date value to allow. |
|
||||
| **`date.overrides`** * | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
|
||||
| **`date.timeIntervals`** * | Time intervals to display. Defaults to 30 minutes. |
|
||||
| **`date.timeFormat`** * | Determines time format. Defaults to `'h:mm aa'`. |
|
||||
| **`date.pickerAppearance`** \* | Determines the appearance of the datepicker: `dayAndTime` `timeOnly` `dayOnly` `monthOnly`. |
|
||||
| **`date.monthsToShow`** \* | Number of months to display max is 2. Defaults to 1. |
|
||||
| **`date.minDate`** \* | Min date value to allow. |
|
||||
| **`date.maxDate`** \* | Max date value to allow. |
|
||||
| **`date.minTime`** \* | Min time value to allow. |
|
||||
| **`date.maxTime`** \* | Max date value to allow. |
|
||||
| **`date.overrides`** \* | Pass any valid props directly to the [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md) |
|
||||
| **`date.timeIntervals`** \* | Time intervals to display. Defaults to 30 minutes. |
|
||||
| **`date.timeFormat`** \* | Determines time format. Defaults to `'h:mm aa'`. |
|
||||
|
||||
_* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md). ._
|
||||
_\* This property is passed directly to [react-datepicker](https://github.com/Hacker0x01/react-datepicker/blob/master/docs/datepicker.md). ._
|
||||
|
||||
### Display Format and Picker Appearance
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyEmailField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
@@ -47,7 +47,7 @@ export const MyEmailField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ export const MyGroupField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`fields`** * | Array of field types to nest within this Group. |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`fields`** \* | Array of field types to nest within this Group. |
|
||||
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
|
||||
@@ -51,7 +51,7 @@ export const MyGroupField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ APIs.
|
||||
|
||||
The Join field is useful in scenarios including:
|
||||
|
||||
- To surface `Orders` for a given `Product`
|
||||
- To surface `Order`s for a given `Product`
|
||||
- To view and edit `Posts` belonging to a `Category`
|
||||
- To work with any bi-directional relationship data
|
||||
- Displaying where a document or upload is used in other documents
|
||||
@@ -123,9 +123,9 @@ powerful Admin UI.
|
||||
|
||||
| Option | Description |
|
||||
|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`name`** * | To be used as the property name when retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`collection`** * | The `slug`s having the relationship field. |
|
||||
| **`on`** * | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. |
|
||||
| **`name`** \* | To be used as the property name when retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`collection`** \* | The `slug`s having the relationship field. |
|
||||
| **`on`** \* | The name of the relationship or upload field that relates to the collection document. Use dot notation for nested paths, like 'myGroup.relationName'. |
|
||||
| **`where`** | A `Where` query to hide related documents from appearing. Will be merged with any `where` specified in the request. |
|
||||
| **`maxDepth`** | Default is 1, Sets a maximum population depth for this field, regardless of the remaining depth when this field is reached. [Max Depth](/docs/getting-started/concepts#field-level-max-depth). |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
@@ -138,7 +138,7 @@ powerful Admin UI.
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema. |
|
||||
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
|
||||
## Admin Config Options
|
||||
|
||||
@@ -31,7 +31,7 @@ export const MyJSONField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
@@ -49,7 +49,7 @@ export const MyJSONField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyNumberField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`min`** | Minimum value accepted. Used in the default `validation` function. |
|
||||
| **`max`** | Maximum value accepted. Used in the default `validation` function. |
|
||||
@@ -52,7 +52,7 @@ export const MyNumberField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MyPointField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Used as a field label in the Admin Panel and to name the generated GraphQL type. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. To support location queries, point index defaults to `2dsphere`, to disable the index set to `false`. |
|
||||
@@ -52,7 +52,7 @@ export const MyPointField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ export const MyRadioField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`options`** * | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing an `label` string and a `value` string. |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`index`** | Build an [index](/docs/database/overview) for this field to produce faster queries. Set this field to `true` if your users will perform queries on this field's data often. |
|
||||
@@ -53,7 +53,7 @@ export const MyRadioField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
**Important:**
|
||||
|
||||
@@ -39,8 +39,8 @@ export const MyRelationshipField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`relationTo`** * | Provide one or many collection `slug`s to be able to assign relationships to. |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`relationTo`** \* | Provide one or many collection `slug`s to be able to assign relationships to. |
|
||||
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-relationship-options). |
|
||||
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many relations instead of only one. |
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with `hasMany`. |
|
||||
@@ -63,7 +63,7 @@ export const MyRelationshipField: Field = {
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="success">
|
||||
**Tip:**
|
||||
|
||||
@@ -6,12 +6,12 @@ desc: The Rich Text field allows dynamic content to be written through the Admin
|
||||
keywords: rich text, fields, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
The Rich Text Field lets editors write and format dynamic content in a familiar interface.
|
||||
The Rich Text Field lets editors write and format dynamic content in a familiar interface.
|
||||
The content is saved as JSON in the database and can be converted to HTML or any other format needed.
|
||||
|
||||
Consistent with Payload's goal of making you learn as little of Payload as possible, customizing
|
||||
Consistent with Payload's goal of making you learn as little of Payload as possible, customizing
|
||||
and using the Rich Text Editor does not involve learning how to develop for a Payload rich text editor.
|
||||
Instead, you can invest your time and effort into learning the underlying open-source tools that will allow
|
||||
Instead, you can invest your time and effort into learning the underlying open-source tools that will allow
|
||||
you to apply your learnings elsewhere as well.
|
||||
|
||||
<LightDarkImage
|
||||
@@ -25,7 +25,7 @@ you to apply your learnings elsewhere as well.
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`validate`** | Provide a custom validation function that will be executed on both the Admin Panel and the backend. [More](/docs/fields/overview#validation) |
|
||||
| **`saveToJWT`** | If this field is top-level and nested in a config supporting [Authentication](/docs/authentication/overview), include its data in the user JWT. |
|
||||
@@ -41,11 +41,11 @@ you to apply your learnings elsewhere as well.
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
The customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option. The Rich Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options)
|
||||
The customize the appearance and behavior of the Rich Text Field in the [Admin Panel](../admin/overview), you can use the `admin` option:
|
||||
|
||||
```ts
|
||||
import type { Field } from 'payload'
|
||||
@@ -58,7 +58,13 @@ export const MyRichTextField: Field = {
|
||||
}
|
||||
```
|
||||
|
||||
Further customization can be done with editor-specific options.
|
||||
The Rich Text Field inherits all of the default options from the base [Field Admin Config](../admin/fields#admin-options), plus the following additional options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
| **`rtl`** | Override the default text direction of the Admin Panel for this field. Set to `true` to force right-to-left text direction. |
|
||||
|
||||
## Editor-specific Options
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ export const MyRowField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`fields`** * | Array of field types to nest within this Row. |
|
||||
| **`fields`** \* | Array of field types to nest within this Row. |
|
||||
| **`admin`** | Admin-specific configuration excluding `description`, `readOnly`, and `hidden`. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ export const MySelectField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`options`** * | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`options`** \* | Array of options to allow the field to store. Can either be an array of strings, or an array of objects containing a `label` string and a `value` string. |
|
||||
| **`hasMany`** | Boolean when, if set to `true`, allows this field to have many selections instead of only one. |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
@@ -56,7 +56,7 @@ export const MySelectField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
<Banner type="warning">
|
||||
**Important:**
|
||||
|
||||
@@ -35,7 +35,7 @@ export const MyTabsField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`tabs`** * | Array of tabs to render within this Tabs field. |
|
||||
| **`tabs`** \* | Array of tabs to render within this Tabs field. |
|
||||
| **`admin`** | Admin-specific configuration. [More details](../admin/fields#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
@@ -47,12 +47,12 @@ Each tab must have either a `name` or `label` and the required `fields` array. Y
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`name`** | Groups field data into an object when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | The label to render on the tab itself. Required when name is undefined, defaults to name converted to words. |
|
||||
| **`fields`** * | The fields to render within this tab. |
|
||||
| **`fields`** \* | The fields to render within this tab. |
|
||||
| **`description`** | Optionally render a description within this tab to describe the contents of the tab itself. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). (`name` must be present) |
|
||||
| **`virtual`** | Provide `true` to disable field in the database (`name` must be present). See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyTextField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
|
||||
@@ -52,7 +52,7 @@ export const MyTextField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ export const MyTextareaField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`label`** | Text used as a field label in the Admin Panel or an object with keys for each language. |
|
||||
| **`unique`** | Enforce that each entry in the Collection has a unique value for this field. |
|
||||
| **`minLength`** | Used by the default validation function to ensure values are of a minimum character length. |
|
||||
@@ -49,7 +49,7 @@ export const MyTextareaField: Field = {
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Admin Options
|
||||
|
||||
|
||||
@@ -30,14 +30,14 @@ export const MyUIField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`name`** * | A unique identifier for this field. |
|
||||
| **`name`** \* | A unique identifier for this field. |
|
||||
| **`label`** | Human-readable label for this UI field. |
|
||||
| **`admin.components.Field`** * | React component to be rendered for this field within the Edit View. [More](../admin/components/#field) |
|
||||
| **`admin.components.Field`** \* | React component to be rendered for this field within the Edit View. [More](../admin/components/#field) |
|
||||
| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](../admin/components/#field) |
|
||||
| **`admin.disableListColumn`** | Set `disableListColumn` to `true` to prevent the UI field from appearing in the list view column selector. |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ export const MyUploadField: Field = {
|
||||
|
||||
| Option | Description |
|
||||
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **`name`** * | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`relationTo`** * | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** |
|
||||
| **`name`** \* | To be used as the property name when stored and retrieved from the database. [More](/docs/fields/overview#field-names) |
|
||||
| **`relationTo`** \* | Provide a single collection `slug` to allow this field to accept a relation to. **Note: the related collection must be configured to support Uploads.** |
|
||||
| **`filterOptions`** | A query to filter which options appear in the UI and validate against. [More](#filtering-upload-options). |
|
||||
| **`hasMany`** | Boolean which, if set to true, allows this field to have many relations instead of only one. |
|
||||
| **`minRows`** | A number for the fewest allowed items during validation when a value is present. Used with hasMany. |
|
||||
@@ -70,7 +70,7 @@ export const MyUploadField: Field = {
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
| **`graphQL`** | Custom graphQL configuration for the field. [More details](/docs/graphql/overview#field-complexity) |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -186,13 +186,13 @@ const beforeReadHook: CollectionBeforeReadHook = async ({
|
||||
|
||||
The following arguments are provided to the `beforeRead` hook:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request. |
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
| Option | Description |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request.
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
|
||||
### afterRead
|
||||
|
||||
@@ -210,13 +210,13 @@ const afterReadHook: CollectionAfterReadHook = async ({
|
||||
|
||||
The following arguments are provided to the `afterRead` hook:
|
||||
|
||||
| Option | Description |
|
||||
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request. |
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
| Option | Description |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`collection`** | The [Collection](../configuration/collections) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request.
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
|
||||
### beforeDelete
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ const Customer: CollectionConfig = {
|
||||
slug: 'customers',
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async ({ doc, req }) => {
|
||||
await req.payload.update({
|
||||
async ({ doc }) => {
|
||||
await payload.update({
|
||||
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
|
||||
collection: 'customers',
|
||||
id: doc.id,
|
||||
@@ -101,12 +101,12 @@ const MyCollection: CollectionConfig = {
|
||||
slug: 'slug',
|
||||
hooks: {
|
||||
afterChange: [
|
||||
async ({ context, doc, req }) => {
|
||||
async ({ context, doc }) => {
|
||||
// return if flag was previously set
|
||||
if (context.triggerAfterChange === false) {
|
||||
return
|
||||
}
|
||||
await req.payload.update({
|
||||
await payload.update({
|
||||
collection: contextHooksSlug,
|
||||
id: doc.id,
|
||||
data: {
|
||||
|
||||
@@ -163,14 +163,14 @@ const afterReadHook: GlobalAfterReadHook = async ({
|
||||
|
||||
The following arguments are provided to the `beforeRead` hook:
|
||||
|
||||
| Option | Description |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many (useful in versions). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request. |
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
| Option | Description |
|
||||
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`global`** | The [Global](../configuration/globals) in which this Hook is running against. |
|
||||
| **`context`** | Custom context passed between hooks. [More details](./context). |
|
||||
| **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many (useful in versions). |
|
||||
| **`doc`** | The resulting Document after changes are applied. |
|
||||
| **`query`** | The [Query](../queries/overview) of the request.
|
||||
| **`req`** | The [Web Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) object. This is mocked for [Local API](../local-api/overview) operations. |
|
||||
|
||||
## TypeScript
|
||||
|
||||
|
||||
@@ -18,12 +18,12 @@ By default, all hooks accept the following args:
|
||||
|
||||
| Path | Description |
|
||||
| ------------------ | -------------------------------------------------------------------------------------- |
|
||||
| **`serverURL`** * | The URL of your Payload server. |
|
||||
| **`serverURL`** \* | The URL of your Payload server. |
|
||||
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
|
||||
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
|
||||
| **`apiRoute`** | The path of your API route as defined in `routes.api`. Defaults to `/api`. |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
And return the following values:
|
||||
|
||||
@@ -151,8 +151,8 @@ The `subscribe` function takes the following args:
|
||||
|
||||
| Path | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------- |
|
||||
| **`callback`** * | A callback function that is called with `data` every time a change is made to the document. |
|
||||
| **`serverURL`** * | The URL of your Payload server. |
|
||||
| **`callback`** \* | A callback function that is called with `data` every time a change is made to the document. |
|
||||
| **`serverURL`** \* | The URL of your Payload server. |
|
||||
| **`initialData`** | The initial data of the document. The live data will be merged in as changes are made. |
|
||||
| **`depth`** | The depth of the relationships to fetch. Defaults to `0`. |
|
||||
|
||||
|
||||
@@ -43,12 +43,12 @@ The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`url`** * | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
|
||||
| **`url`** \* | String, or function that returns a string, pointing to your front-end application. This value is used as the iframe `src`. [More details](#url). |
|
||||
| **`breakpoints`** | Array of breakpoints to be used as “device sizes” in the preview window. Each item appears as an option in the toolbar. [More details](#breakpoints). |
|
||||
| **`collections`** | Array of collection slugs to enable Live Preview on. |
|
||||
| **`globals`** | Array of global slugs to enable Live Preview on. |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
### URL
|
||||
|
||||
@@ -150,12 +150,12 @@ The following options are available for each breakpoint:
|
||||
|
||||
| Path | Description |
|
||||
| --------------- | --------------------------------------------------------------------------- |
|
||||
| **`label`** * | The label to display in the drop-down. This is what the user will see. |
|
||||
| **`name`** * | The name of the breakpoint. |
|
||||
| **`width`** * | The width of the breakpoint. This is used to set the width of the iframe. |
|
||||
| **`height`** * | The height of the breakpoint. This is used to set the height of the iframe. |
|
||||
| **`label`** \* | The label to display in the drop-down. This is what the user will see. |
|
||||
| **`name`** \* | The name of the breakpoint. |
|
||||
| **`width`** \* | The width of the breakpoint. This is used to set the width of the iframe. |
|
||||
| **`height`** \* | The height of the breakpoint. This is used to set the height of the iframe. |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
{/* IMAGE OF TOOLBAR HERE */}
|
||||
|
||||
|
||||
@@ -65,14 +65,14 @@ export default config
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
| ------------------------------ | ------------------ | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `stripeSecretKey` * | string | `undefined` | Your Stripe secret key |
|
||||
| `stripeSecretKey` \* | string | `undefined` | Your Stripe secret key |
|
||||
| `stripeWebhooksEndpointSecret` | string | `undefined` | Your Stripe webhook endpoint secret |
|
||||
| `rest` | boolean | `false` | When `true`, opens the `/api/stripe/rest` endpoint |
|
||||
| `webhooks` | object or function | `undefined` | Either a function to handle all webhooks events, or an object of Stripe webhook handlers, keyed to the name of the event |
|
||||
| `webhooks` | object \| function | `undefined` | Either a function to handle all webhooks events, or an object of Stripe webhook handlers, keyed to the name of the event |
|
||||
| `sync` | array | `undefined` | An array of sync configs |
|
||||
| `logs` | boolean | `false` | When `true`, logs sync events to the console as they happen |
|
||||
|
||||
_* An asterisk denotes that a property is required._
|
||||
_\* An asterisk denotes that a property is required._
|
||||
|
||||
## Endpoints
|
||||
|
||||
|
||||
@@ -10,18 +10,18 @@ All collection `find` queries are paginated automatically. Responses are returne
|
||||
|
||||
**`Find` response properties:**
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | --------------------------------------------------------- |
|
||||
| `docs` | Array of documents in the collection |
|
||||
| `totalDocs` | Total available documents within the collection |
|
||||
| `limit` | Limit query parameter - defaults to `10` |
|
||||
| `totalPages` | Total pages available, based upon the `limit` queried for |
|
||||
| `page` | Current page number |
|
||||
| `pagingCounter` | `number` of the first doc on the current page |
|
||||
| `hasPrevPage` | `true/false` if previous page exists |
|
||||
| `hasNextPage` | `true/false` if next page exists |
|
||||
| `prevPage` | `number` of previous page, `null` if it doesn't exist |
|
||||
| `nextPage` | `number` of next page, `null` if it doesn't exist |
|
||||
| Property | Description |
|
||||
| ------------- | --------------------------------------------------------- |
|
||||
| docs | Array of documents in the collection |
|
||||
| totalDocs | Total available documents within the collection |
|
||||
| limit | Limit query parameter - defaults to `10` |
|
||||
| totalPages | Total pages available, based upon the `limit` queried for |
|
||||
| page | Current page number |
|
||||
| pagingCounter | `number` of the first doc on the current page |
|
||||
| hasPrevPage | `true/false` if previous page exists |
|
||||
| hasNextPage | `true/false` if next page exists |
|
||||
| prevPage | `number` of previous page, `null` if it doesn't exist |
|
||||
| nextPage | `number` of next page, `null` if it doesn't exist |
|
||||
|
||||
**Example response:**
|
||||
|
||||
|
||||
@@ -10,154 +10,12 @@ Before you begin building custom features for Lexical, it is crucial to familiar
|
||||
|
||||
Lexical features are designed to be modular, meaning each piece of functionality is encapsulated within just two specific interfaces: one for server-side code and one for client-side code.
|
||||
|
||||
By convention, these are named `feature.server.ts` for server-side functionality and `feature.client.ts` for client-side functionality. The primary functionality is housed within `feature.server.ts`, which users will import into their projects. The client-side feature, although defined separately, is integrated and rendered server-side through the server feature.
|
||||
By convention, these are named feature.server.ts for server-side functionality and feature.client.ts for client-side functionality. The primary functionality is housed within feature.server.ts, which users will import into their projects. The client-side feature, although defined separately, is integrated and rendered server-side through the server feature. That way, we still maintain a clear boundary between server and client code, while also centralizing the code needed for a feature in basically one place. This approach is beneficial for managing all the bits and pieces which make up your feature as a whole, such as toolbar entries, buttons, or new nodes, allowing each feature to be neatly contained and managed independently.
|
||||
|
||||
That way, we still maintain a clear boundary between server and client code, while also centralizing the code needed for a feature in basically one place. This approach is beneficial for managing all the bits and pieces which make up your feature as a whole, such as toolbar entries, buttons, or new nodes, allowing each feature to be neatly contained and managed independently.
|
||||
|
||||
<Banner type="warning">
|
||||
**Important:**
|
||||
Do not import directly from core lexical packages - this may break in minor Payload version bumps.
|
||||
|
||||
Instead, import the re-exported versions from `@payloadcms/richtext-lexical`. For example, change `import { $insertNodeToNearestRoot } from '@lexical/utils'` to `import { $insertNodeToNearestRoot } from '@payloadcms/richtext-lexical/lexical/utils'`
|
||||
</Banner>
|
||||
|
||||
## Do I need a custom feature?
|
||||
|
||||
Before you start building a custom feature, consider whether you can achieve your desired functionality using the existing `BlocksFeature`. The `BlocksFeature` is a powerful feature that allows you to create custom blocks with a variety of options, including custom React components, markdown converters, and more. If you can achieve your desired functionality using the `BlocksFeature`, it is recommended to use it instead of building a custom feature.
|
||||
|
||||
Using the BlocksFeature, you can add both inline blocks (= can be inserted into a paragraph, in between text) and block blocks (= take up the whole line) to the editor. If you simply want to bring custom react components into the editor, this is the way to go.
|
||||
|
||||
### Example: Code Field Block with language picker
|
||||
|
||||
This example demonstrates how to create a custom code field block with a language picker using the `BlocksFeature`. Make sure to manually install `@payloadcms/ui`first.
|
||||
|
||||
Field config:
|
||||
|
||||
```ts
|
||||
import {
|
||||
BlocksFeature,
|
||||
lexicalEditor,
|
||||
} from '@payloadcms/richtext-lexical'
|
||||
|
||||
export const languages = {
|
||||
ts: 'TypeScript',
|
||||
plaintext: 'Plain Text',
|
||||
tsx: 'TSX',
|
||||
js: 'JavaScript',
|
||||
jsx: 'JSX',
|
||||
}
|
||||
|
||||
// ...
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
features: ({ defaultFeatures }) => [
|
||||
...defaultFeatures,
|
||||
BlocksFeature({
|
||||
blocks: [
|
||||
{
|
||||
slug: 'Code',
|
||||
fields: [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'language',
|
||||
options: Object.entries(languages).map(([key, value]) => ({
|
||||
label: value,
|
||||
value: key,
|
||||
})),
|
||||
defaultValue: 'ts',
|
||||
},
|
||||
{
|
||||
admin: {
|
||||
components: {
|
||||
Field: './path/to/CodeComponent#Code',
|
||||
},
|
||||
},
|
||||
name: 'code',
|
||||
type: 'code',
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
inlineBlocks: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
```
|
||||
|
||||
CodeComponent.tsx:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import type { CodeFieldClient, CodeFieldClientProps } from 'payload'
|
||||
|
||||
import { CodeField, useFormFields } from '@payloadcms/ui'
|
||||
import React, { useMemo } from 'react'
|
||||
|
||||
import { languages } from './yourFieldConfig'
|
||||
|
||||
const languageKeyToMonacoLanguageMap = {
|
||||
plaintext: 'plaintext',
|
||||
ts: 'typescript',
|
||||
tsx: 'typescript',
|
||||
}
|
||||
|
||||
export const Code: React.FC<CodeFieldClientProps> = ({
|
||||
autoComplete,
|
||||
field,
|
||||
forceRender,
|
||||
path,
|
||||
permissions,
|
||||
readOnly,
|
||||
renderedBlocks,
|
||||
schemaPath,
|
||||
validate,
|
||||
}) => {
|
||||
const languageField = useFormFields(([fields]) => fields['language'])
|
||||
|
||||
const language: string =
|
||||
(languageField?.value as string) || (languageField.initialValue as string) || 'typescript'
|
||||
|
||||
const label = languages[language as keyof typeof languages]
|
||||
|
||||
const props: CodeFieldClient = useMemo<CodeFieldClient>(
|
||||
() => ({
|
||||
...field,
|
||||
type: 'code',
|
||||
admin: {
|
||||
...field.admin,
|
||||
label,
|
||||
language: languageKeyToMonacoLanguageMap[language] || language,
|
||||
},
|
||||
}),
|
||||
[field, language, label],
|
||||
)
|
||||
|
||||
const key = `${field.name}-${language}-${label}`
|
||||
|
||||
return (
|
||||
<CodeField
|
||||
autoComplete={autoComplete}
|
||||
field={props}
|
||||
forceRender={forceRender}
|
||||
key={key}
|
||||
path={path}
|
||||
permissions={permissions}
|
||||
readOnly={readOnly}
|
||||
renderedBlocks={renderedBlocks}
|
||||
schemaPath={schemaPath}
|
||||
validate={validate}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Server Feature
|
||||
|
||||
Custom Blocks are not enough? To start building a custom feature, you should start with the server feature, which is the entry-point.
|
||||
To start building new features, you should start with the server feature, which is the entry-point.
|
||||
|
||||
**Example myFeature/feature.server.ts:**
|
||||
|
||||
@@ -191,7 +49,8 @@ import { lexicalEditor } from '@payloadcms/richtext-lexical';
|
||||
},
|
||||
```
|
||||
|
||||
By default, this server feature does nothing - you haven't added any functionality yet. Depending on what you want your feature to do, the ServerFeature type exposes various properties you can set to inject custom functionality into the lexical editor.
|
||||
By default, this server feature does nothing - you haven't added any functionality yet. Depending on what you want your
|
||||
feature to do, the ServerFeature type exposes various properties you can set to inject custom functionality into the lexical editor.
|
||||
|
||||
### i18n
|
||||
|
||||
@@ -407,22 +266,6 @@ export const MyClientFeature = createClientFeature({
|
||||
|
||||
Explore the APIs available through ClientFeature to add the specific functionality you need. Remember, do not import directly from `'@payloadcms/richtext-lexical'` when working on the client-side, as it will cause errors with webpack or turbopack. Instead, use `'@payloadcms/richtext-lexical/client'` for all client-side imports. Type-imports are excluded from this rule and can always be imported.
|
||||
|
||||
### Adding a client feature to the server feature
|
||||
|
||||
Inside of your server feature, you can provide an [import path](/docs/admin/components#component-paths) to the client feature like this:
|
||||
|
||||
```ts
|
||||
import { createServerFeature } from '@payloadcms/richtext-lexical';
|
||||
|
||||
export const MyFeature = createServerFeature({
|
||||
feature: {
|
||||
ClientFeature: './path/to/feature.client#MyClientFeature',
|
||||
},
|
||||
key: 'myFeature',
|
||||
dependenciesPriority: ['otherFeature'],
|
||||
})
|
||||
```
|
||||
|
||||
### Nodes#client-feature-nodes
|
||||
|
||||
Add nodes to the `nodes` array in **both** your client & server feature. On the server side, nodes are utilized for backend operations like HTML conversion in a headless editor. On the client side, these nodes are integral to how content is displayed and managed in the editor, influencing how they are rendered, behave, and saved in the database.
|
||||
|
||||
@@ -8,8 +8,8 @@ keywords: lexical, rich text, editor, headless cms
|
||||
|
||||
<Banner type="warning">
|
||||
|
||||
The Payload editor is based on Lexical, Meta's rich text editor. The previous default editor was
|
||||
based on Slate and is still supported. You can read [its documentation](/docs/rich-text/slate),
|
||||
The Payload editor is based on Lexical, Meta's rich text editor. The previous default editor was
|
||||
based on Slate and is still supported. You can read [its documentation](/docs/rich-text/slate),
|
||||
or the optional [migration guide](/docs/rich-text/migration) to migrate from Slate to Lexical (recommended).
|
||||
|
||||
</Banner>
|
||||
@@ -298,43 +298,3 @@ Make sure to only use types exported from `@payloadcms/richtext-lexical`, not fr
|
||||
### Automatic type generation
|
||||
|
||||
Lexical does not generate the accurate type definitions for your richText fields for you yet - this will be improved in the future. Currently, it only outputs the rough shape of the editor JSON which you can enhance using type assertions.
|
||||
|
||||
## Admin customization
|
||||
|
||||
The Rich Text Field editor configuration has an `admin` property with the following options:
|
||||
|
||||
| Property | Description |
|
||||
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`placeholder`** | Set this property to define a placeholder string for the field. |
|
||||
| **`hideGutter`** | Set this property to `true` to hide this field's gutter within the Admin Panel. |
|
||||
|
||||
### Disable the gutter
|
||||
|
||||
You can disable the gutter (the vertical line padding between the editor and the left edge of the screen) by setting the `hideGutter` prop to `true`:
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
admin: {
|
||||
hideGutter: true
|
||||
},
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
### Customize the placeholder
|
||||
|
||||
You can customize the placeholder (the text that appears in the editor when it's empty) by setting the `placeholder` prop:
|
||||
|
||||
```ts
|
||||
{
|
||||
name: 'richText',
|
||||
type: 'richText',
|
||||
editor: lexicalEditor({
|
||||
admin: {
|
||||
placeholder: 'Type your content here...'
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -151,8 +151,8 @@ Once you're up to speed with the general concepts involved, you can pass in your
|
||||
|
||||
| Property | Description |
|
||||
| --------------- | ---------------------------------------------------------- |
|
||||
| **`name`** * | The default name to be used as a `type` for this element. |
|
||||
| **`Button`** * | A React component to be rendered in the Rich Text toolbar. |
|
||||
| **`name`** \* | The default name to be used as a `type` for this element. |
|
||||
| **`Button`** \* | A React component to be rendered in the Rich Text toolbar. |
|
||||
| **`plugins`** | An array of plugins to provide to the Rich Text editor. |
|
||||
| **`type`** | A type that overrides the default type used by `name` |
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ keywords: admin, components, custom, customize, documentation, Content Managemen
|
||||
|
||||
### "Unauthorized, you must be logged in to make this request" when attempting to log in
|
||||
|
||||
This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve check the following settings in your Payload Config:
|
||||
This means that your auth cookie is not being set or accepted correctly upon logging in. To resolve heck the following settings in your Payload Config:
|
||||
|
||||
- CORS - If you are using the '*', try to explicitly only allow certain domains instead including the one you have specified.
|
||||
- CORS - If you are using the '\*', try to explicitly only allow certain domains instead including the one you have specified.
|
||||
- CSRF - Do you have this set? if so, make sure your domain is whitelisted within the csrf domains. If not, probably not the issue, but probably can't hurt to whitelist it anyway.
|
||||
- Cookie settings. If these are completely undefined, then that's fine. but if you have cookie domain set, or anything similar, make sure you don't have the domain misconfigured
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ _An asterisk denotes that an option is required._
|
||||
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |
|
||||
| **`externalFileHeaderFilter`** | Accepts existing headers and returns the headers after filtering or modifying. |
|
||||
| **`filesRequiredOnCreate`** | Mandate file data on creation, default is true. |
|
||||
| **`filenameCompoundIndex`** | Field slugs to use for a compound index instead of the default filename index. |
|
||||
| **`filenameCompoundIndex`** | Field slugs to use for a compound index instead of the default filename index.
|
||||
| **`focalPoint`** | Set to `false` to disable the focal point selection tool in the [Admin Panel](../admin/overview). The focal point selector is only available when `imageSizes` or `resizeOptions` are defined. [More](#crop-and-focal-point-selector) |
|
||||
| **`formatOptions`** | An object with `format` and `options` that are used with the Sharp image library to format the upload file. [More](https://sharp.pixelplumbing.com/api-output#toformat) |
|
||||
| **`handlers`** | Array of Request handlers to execute when fetching a file, if a handler returns a Response it will be sent to the client. Otherwise Payload will retrieve and send back the file. |
|
||||
|
||||
@@ -309,14 +309,14 @@ This plugin is configurable to work across many different Payload collections. A
|
||||
|
||||
| Option | Type | Description |
|
||||
| ---------------- | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `collections` * | `Record<string, CollectionOptions>` | Object with keys set to the slug of collections you want to enable the plugin for, and values set to collection-specific options. |
|
||||
| `collections` \* | `Record<string, CollectionOptions>` | Object with keys set to the slug of collections you want to enable the plugin for, and values set to collection-specific options. |
|
||||
| `enabled` | `boolean` | To conditionally enable/disable plugin. Default: `true`. |
|
||||
|
||||
## Collection-specific options
|
||||
|
||||
| Option | Type | Description |
|
||||
| ----------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `adapter` * | [Adapter](https://github.com/payloadcms/plugin-cloud-storage/blob/master/src/types.ts#L51) | Pass in the adapter that you'd like to use for this collection. You can also set this field to `null` for local development if you'd like to bypass cloud storage in certain scenarios and use local storage. |
|
||||
| `adapter` \* | [Adapter](https://github.com/payloadcms/plugin-cloud-storage/blob/master/src/types.ts#L51) | Pass in the adapter that you'd like to use for this collection. You can also set this field to `null` for local development if you'd like to bypass cloud storage in certain scenarios and use local storage. |
|
||||
| `disableLocalStorage` | `boolean` | Choose to disable local storage on this collection. Defaults to `true`. |
|
||||
| `disablePayloadAccessControl` | `true` | Set to `true` to disable Payload's Access Control. [More](#payload-access-control) |
|
||||
| `prefix` | `string` | Set to `media/images` to upload files inside `media/images` folder in the bucket. |
|
||||
|
||||
@@ -73,10 +73,6 @@ If you simply fetch your created document using a `find` or `findByID` operation
|
||||
|
||||
But, if you specify `draft` as `true`, Payload will automatically replace your published document's content with content coming from the most recently saved `version`. In this case, as we have created _two_ versions in the above scenario, Payload will send back data from the newest (second) draft and your document will appear as the most recently drafted version instead of the published version.
|
||||
|
||||
<Banner type="error">
|
||||
**Important:** the `draft` argument on its own will not restrict documents with `_status: 'draft'` from being returned from the API. You need to use Access Control to prevent documents with `_status: 'draft'` from being returned to unauthenticated users. Read below for more information on how this works.
|
||||
</Banner>
|
||||
|
||||
## Controlling who can see Collection drafts
|
||||
|
||||
<Banner type="warning">
|
||||
|
||||
@@ -56,7 +56,7 @@ Configuring Versions is done by adding the `versions` key to your Collection con
|
||||
| Option | Description |
|
||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `maxPerDoc` | Use this setting to control how many versions to keep on a document by document basis. Must be an integer. Defaults to 100, use 0 to save all versions. |
|
||||
| `drafts` | Enable [Drafts](/docs/versions/drafts) mode for this collection. To enable, set to `true` or pass an object with `draft` [options](/docs/versions/drafts#options). |
|
||||
| `drafts ` | Enable [Drafts](/docs/versions/drafts) mode for this collection. To enable, set to `true` or pass an object with `draft` [options](/docs/versions/drafts#options). |
|
||||
|
||||
## Global config
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -64,8 +64,8 @@
|
||||
"dev:vercel-postgres": "cross-env PAYLOAD_DATABASE=vercel-postgres pnpm runts ./test/dev.ts",
|
||||
"devsafe": "node ./scripts/delete-recursively.js '**/.next' && pnpm dev",
|
||||
"docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start",
|
||||
"docker:start": "docker compose -f test/docker-compose.yml up -d",
|
||||
"docker:stop": "docker compose -f test/docker-compose.yml down",
|
||||
"docker:start": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml up -d",
|
||||
"docker:stop": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml down",
|
||||
"force:build": "pnpm run build:core:force",
|
||||
"lint": "turbo run lint --concurrency 1 --continue",
|
||||
"lint-staged": "lint-staged",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -87,7 +87,6 @@ export async function updatePayloadInProject(
|
||||
copyRecursiveSync(
|
||||
templateSrcDir,
|
||||
path.resolve(projectDir, appDetails.isSrcDir ? 'src/app' : 'app', '(payload)'),
|
||||
['custom.scss$'], // Do not overwrite user's custom.scss
|
||||
)
|
||||
|
||||
return { message: 'Payload updated successfully.', success: true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
import minimist from 'minimist'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { findConfig, loadEnv } from 'payload/node'
|
||||
|
||||
import { generateSchema } from './generateSchema.js'
|
||||
@@ -8,7 +7,7 @@ import { generateSchema } from './generateSchema.js'
|
||||
export const bin = async () => {
|
||||
loadEnv()
|
||||
const configPath = findConfig()
|
||||
const config = await (await import(pathToFileURL(configPath).toString())).default
|
||||
const config = await (await import(configPath)).default
|
||||
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const script = (typeof args._[0] === 'string' ? args._[0] : '').toLowerCase()
|
||||
|
||||
@@ -63,9 +63,7 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
|
||||
let singularName
|
||||
let pluralName
|
||||
|
||||
const fromSlug = formatNames(collection.config.slug)
|
||||
|
||||
if (graphQL.singularName) {
|
||||
singularName = toWords(graphQL.singularName, true)
|
||||
} else {
|
||||
@@ -170,133 +168,124 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
collection.graphQL.updateMutationInputType = new GraphQLNonNull(updateMutationInputType)
|
||||
}
|
||||
|
||||
const queriesEnabled =
|
||||
typeof collectionConfig.graphQL !== 'object' || !collectionConfig.graphQL.disableQueries
|
||||
const mutationsEnabled =
|
||||
typeof collectionConfig.graphQL !== 'object' || !collectionConfig.graphQL.disableMutations
|
||||
|
||||
if (queriesEnabled) {
|
||||
graphqlResult.Query.fields[singularName] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findByIDResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[pluralName] = {
|
||||
type: buildPaginatedListType(pluralName, collection.graphQL.type),
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
where: { type: collection.graphQL.whereInputType },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`count${pluralName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: `count${pluralName}`,
|
||||
fields: {
|
||||
totalDocs: { type: GraphQLInt },
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
where: { type: collection.graphQL.whereInputType },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: countResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`docAccess${singularName}`] = {
|
||||
type: buildPolicyType({
|
||||
type: 'collection',
|
||||
entity: collectionConfig,
|
||||
scope: 'docAccess',
|
||||
typeSuffix: 'DocAccess',
|
||||
}),
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: docAccessResolver(collection),
|
||||
}
|
||||
graphqlResult.Query.fields[singularName] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findByIDResolver(collection),
|
||||
}
|
||||
|
||||
if (mutationsEnabled) {
|
||||
graphqlResult.Mutation.fields[`create${singularName}`] = {
|
||||
graphqlResult.Query.fields[pluralName] = {
|
||||
type: buildPaginatedListType(pluralName, collection.graphQL.type),
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
where: { type: collection.graphQL.whereInputType },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`count${pluralName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: `count${pluralName}`,
|
||||
fields: {
|
||||
totalDocs: { type: GraphQLInt },
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
where: { type: collection.graphQL.whereInputType },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: countResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`docAccess${singularName}`] = {
|
||||
type: buildPolicyType({
|
||||
type: 'collection',
|
||||
entity: collectionConfig,
|
||||
scope: 'docAccess',
|
||||
typeSuffix: 'DocAccess',
|
||||
}),
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: docAccessResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`create${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
...(createMutationInputType
|
||||
? { data: { type: collection.graphQL.mutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: createResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`update${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
autosave: { type: GraphQLBoolean },
|
||||
...(updateMutationInputType
|
||||
? { data: { type: collection.graphQL.updateMutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: updateResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`delete${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: getDeleteResolver(collection),
|
||||
}
|
||||
|
||||
if (collectionConfig.disableDuplicate !== true) {
|
||||
graphqlResult.Mutation.fields[`duplicate${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
...(createMutationInputType
|
||||
? { data: { type: collection.graphQL.mutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: createResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`update${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
autosave: { type: GraphQLBoolean },
|
||||
...(updateMutationInputType
|
||||
? { data: { type: collection.graphQL.updateMutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: updateResolver(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`delete${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
},
|
||||
resolve: getDeleteResolver(collection),
|
||||
}
|
||||
|
||||
if (collectionConfig.disableDuplicate !== true) {
|
||||
graphqlResult.Mutation.fields[`duplicate${singularName}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: new GraphQLNonNull(idType) },
|
||||
...(createMutationInputType
|
||||
? { data: { type: collection.graphQL.mutationInputType } }
|
||||
: {}),
|
||||
},
|
||||
resolve: duplicateResolver(collection),
|
||||
}
|
||||
resolve: duplicateResolver(collection),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,57 +318,52 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
parentName: `${singularName}Version`,
|
||||
})
|
||||
|
||||
if (queriesEnabled) {
|
||||
graphqlResult.Query.fields[`version${formatName(singularName)}`] = {
|
||||
type: collection.graphQL.versionType,
|
||||
args: {
|
||||
id: { type: versionIDType },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findVersionByIDResolver(collection),
|
||||
}
|
||||
graphqlResult.Query.fields[`versions${pluralName}`] = {
|
||||
type: buildPaginatedListType(
|
||||
`versions${formatName(pluralName)}`,
|
||||
collection.graphQL.versionType,
|
||||
),
|
||||
args: {
|
||||
where: {
|
||||
type: buildWhereInputType({
|
||||
name: `versions${singularName}`,
|
||||
fields: versionCollectionFields,
|
||||
parentName: `versions${singularName}`,
|
||||
}),
|
||||
},
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersionsResolver(collection),
|
||||
}
|
||||
graphqlResult.Query.fields[`version${formatName(singularName)}`] = {
|
||||
type: collection.graphQL.versionType,
|
||||
args: {
|
||||
id: { type: versionIDType },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findVersionByIDResolver(collection),
|
||||
}
|
||||
|
||||
if (mutationsEnabled) {
|
||||
graphqlResult.Mutation.fields[`restoreVersion${formatName(singularName)}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: versionIDType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
graphqlResult.Query.fields[`versions${pluralName}`] = {
|
||||
type: buildPaginatedListType(
|
||||
`versions${formatName(pluralName)}`,
|
||||
collection.graphQL.versionType,
|
||||
),
|
||||
args: {
|
||||
where: {
|
||||
type: buildWhereInputType({
|
||||
name: `versions${singularName}`,
|
||||
fields: versionCollectionFields,
|
||||
parentName: `versions${singularName}`,
|
||||
}),
|
||||
},
|
||||
resolve: restoreVersionResolver(collection),
|
||||
}
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersionsResolver(collection),
|
||||
}
|
||||
graphqlResult.Mutation.fields[`restoreVersion${formatName(singularName)}`] = {
|
||||
type: collection.graphQL.type,
|
||||
args: {
|
||||
id: { type: versionIDType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: restoreVersionResolver(collection),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,20 +397,90 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
parentName: formatName(`${slug}JWT`),
|
||||
})
|
||||
|
||||
if (queriesEnabled) {
|
||||
graphqlResult.Query.fields[`me${singularName}`] = {
|
||||
graphqlResult.Query.fields[`me${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}Me`),
|
||||
fields: {
|
||||
collection: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
exp: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
strategy: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
token: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
user: {
|
||||
type: collection.graphQL.type,
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve: me(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`initialized${singularName}`] = {
|
||||
type: GraphQLBoolean,
|
||||
resolve: init(collection.config.slug),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`refreshToken${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}Refreshed${singularName}`),
|
||||
fields: {
|
||||
exp: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
refreshedToken: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
strategy: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
user: {
|
||||
type: collection.graphQL.JWT,
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve: refresh(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`logout${singularName}`] = {
|
||||
type: GraphQLString,
|
||||
resolve: logout(collection),
|
||||
}
|
||||
|
||||
if (!collectionConfig.auth.disableLocalStrategy) {
|
||||
const authArgs = {}
|
||||
|
||||
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(
|
||||
collectionConfig.auth.loginWithUsername,
|
||||
)
|
||||
|
||||
if (canLoginWithEmail) {
|
||||
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }
|
||||
}
|
||||
if (canLoginWithUsername) {
|
||||
authArgs['username'] = { type: new GraphQLNonNull(GraphQLString) }
|
||||
}
|
||||
|
||||
if (collectionConfig.auth.maxLoginAttempts > 0) {
|
||||
graphqlResult.Mutation.fields[`unlock${singularName}`] = {
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
args: authArgs,
|
||||
resolve: unlock(collection),
|
||||
}
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`login${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}Me`),
|
||||
name: formatName(`${slug}LoginResult`),
|
||||
fields: {
|
||||
collection: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
exp: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
strategy: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
token: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
@@ -435,118 +489,44 @@ export function initCollections({ config, graphqlResult }: InitCollectionsGraphQ
|
||||
},
|
||||
},
|
||||
}),
|
||||
resolve: me(collection),
|
||||
args: {
|
||||
...authArgs,
|
||||
password: { type: GraphQLString },
|
||||
},
|
||||
resolve: login(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`initialized${singularName}`] = {
|
||||
type: GraphQLBoolean,
|
||||
resolve: init(collection.config.slug),
|
||||
graphqlResult.Mutation.fields[`forgotPassword${singularName}`] = {
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
args: {
|
||||
disableEmail: { type: GraphQLBoolean },
|
||||
expiration: { type: GraphQLInt },
|
||||
...authArgs,
|
||||
},
|
||||
resolve: forgotPassword(collection),
|
||||
}
|
||||
}
|
||||
|
||||
if (mutationsEnabled) {
|
||||
graphqlResult.Mutation.fields[`refreshToken${singularName}`] = {
|
||||
graphqlResult.Mutation.fields[`resetPassword${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}Refreshed${singularName}`),
|
||||
name: formatName(`${slug}ResetPassword`),
|
||||
fields: {
|
||||
exp: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
refreshedToken: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
strategy: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
user: {
|
||||
type: collection.graphQL.JWT,
|
||||
},
|
||||
token: { type: GraphQLString },
|
||||
user: { type: collection.graphQL.type },
|
||||
},
|
||||
}),
|
||||
resolve: refresh(collection),
|
||||
args: {
|
||||
password: { type: GraphQLString },
|
||||
token: { type: GraphQLString },
|
||||
},
|
||||
resolve: resetPassword(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`logout${singularName}`] = {
|
||||
type: GraphQLString,
|
||||
resolve: logout(collection),
|
||||
}
|
||||
|
||||
if (!collectionConfig.auth.disableLocalStrategy) {
|
||||
const authArgs = {}
|
||||
|
||||
const { canLoginWithEmail, canLoginWithUsername } = getLoginOptions(
|
||||
collectionConfig.auth.loginWithUsername,
|
||||
)
|
||||
|
||||
if (canLoginWithEmail) {
|
||||
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }
|
||||
}
|
||||
if (canLoginWithUsername) {
|
||||
authArgs['username'] = { type: new GraphQLNonNull(GraphQLString) }
|
||||
}
|
||||
|
||||
if (collectionConfig.auth.maxLoginAttempts > 0) {
|
||||
graphqlResult.Mutation.fields[`unlock${singularName}`] = {
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
args: authArgs,
|
||||
resolve: unlock(collection),
|
||||
}
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`login${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}LoginResult`),
|
||||
fields: {
|
||||
exp: {
|
||||
type: GraphQLInt,
|
||||
},
|
||||
token: {
|
||||
type: GraphQLString,
|
||||
},
|
||||
user: {
|
||||
type: collection.graphQL.type,
|
||||
},
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
...authArgs,
|
||||
password: { type: GraphQLString },
|
||||
},
|
||||
resolve: login(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`forgotPassword${singularName}`] = {
|
||||
type: new GraphQLNonNull(GraphQLBoolean),
|
||||
args: {
|
||||
disableEmail: { type: GraphQLBoolean },
|
||||
expiration: { type: GraphQLInt },
|
||||
...authArgs,
|
||||
},
|
||||
resolve: forgotPassword(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`resetPassword${singularName}`] = {
|
||||
type: new GraphQLObjectType({
|
||||
name: formatName(`${slug}ResetPassword`),
|
||||
fields: {
|
||||
token: { type: GraphQLString },
|
||||
user: { type: collection.graphQL.type },
|
||||
},
|
||||
}),
|
||||
args: {
|
||||
password: { type: GraphQLString },
|
||||
token: { type: GraphQLString },
|
||||
},
|
||||
resolve: resetPassword(collection),
|
||||
}
|
||||
|
||||
graphqlResult.Mutation.fields[`verifyEmail${singularName}`] = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
token: { type: GraphQLString },
|
||||
},
|
||||
resolve: verifyEmail(collection),
|
||||
}
|
||||
graphqlResult.Mutation.fields[`verifyEmail${singularName}`] = {
|
||||
type: GraphQLBoolean,
|
||||
args: {
|
||||
token: { type: GraphQLString },
|
||||
},
|
||||
resolve: verifyEmail(collection),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,51 +61,44 @@ export function initGlobals({ config, graphqlResult }: InitGlobalsGraphQLArgs):
|
||||
: null,
|
||||
}
|
||||
|
||||
const queriesEnabled = typeof global.graphQL !== 'object' || !global.graphQL.disableQueries
|
||||
const mutationsEnabled = typeof global.graphQL !== 'object' || !global.graphQL.disableMutations
|
||||
|
||||
if (queriesEnabled) {
|
||||
graphqlResult.Query.fields[formattedName] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findOne(global),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`docAccess${formattedName}`] = {
|
||||
type: buildPolicyType({
|
||||
type: 'global',
|
||||
entity: global,
|
||||
scope: 'docAccess',
|
||||
typeSuffix: 'DocAccess',
|
||||
}),
|
||||
resolve: docAccessResolver(global),
|
||||
}
|
||||
graphqlResult.Query.fields[formattedName] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findOne(global),
|
||||
}
|
||||
|
||||
if (mutationsEnabled) {
|
||||
graphqlResult.Mutation.fields[`update${formattedName}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
...(updateMutationInputType
|
||||
? { data: { type: graphqlResult.globals.graphQL[slug].mutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: update(global),
|
||||
}
|
||||
graphqlResult.Mutation.fields[`update${formattedName}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
...(updateMutationInputType
|
||||
? { data: { type: graphqlResult.globals.graphQL[slug].mutationInputType } }
|
||||
: {}),
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: update(global),
|
||||
}
|
||||
|
||||
graphqlResult.Query.fields[`docAccess${formattedName}`] = {
|
||||
type: buildPolicyType({
|
||||
type: 'global',
|
||||
entity: global,
|
||||
scope: 'docAccess',
|
||||
typeSuffix: 'DocAccess',
|
||||
}),
|
||||
resolve: docAccessResolver(global),
|
||||
}
|
||||
|
||||
if (global.versions) {
|
||||
@@ -138,58 +131,53 @@ export function initGlobals({ config, graphqlResult }: InitGlobalsGraphQLArgs):
|
||||
parentName: `${formattedName}Version`,
|
||||
})
|
||||
|
||||
if (queriesEnabled) {
|
||||
graphqlResult.Query.fields[`version${formatName(formattedName)}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].versionType,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findVersionByID(global),
|
||||
}
|
||||
graphqlResult.Query.fields[`versions${formattedName}`] = {
|
||||
type: buildPaginatedListType(
|
||||
`versions${formatName(formattedName)}`,
|
||||
graphqlResult.globals.graphQL[slug].versionType,
|
||||
),
|
||||
args: {
|
||||
where: {
|
||||
type: buildWhereInputType({
|
||||
name: `versions${formattedName}`,
|
||||
fields: versionGlobalFields,
|
||||
parentName: `versions${formattedName}`,
|
||||
}),
|
||||
},
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersions(global),
|
||||
}
|
||||
graphqlResult.Query.fields[`version${formatName(formattedName)}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].versionType,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
resolve: findVersionByID(global),
|
||||
}
|
||||
|
||||
if (mutationsEnabled) {
|
||||
graphqlResult.Mutation.fields[`restoreVersion${formatName(formattedName)}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
graphqlResult.Query.fields[`versions${formattedName}`] = {
|
||||
type: buildPaginatedListType(
|
||||
`versions${formatName(formattedName)}`,
|
||||
graphqlResult.globals.graphQL[slug].versionType,
|
||||
),
|
||||
args: {
|
||||
where: {
|
||||
type: buildWhereInputType({
|
||||
name: `versions${formattedName}`,
|
||||
fields: versionGlobalFields,
|
||||
parentName: `versions${formattedName}`,
|
||||
}),
|
||||
},
|
||||
resolve: restoreVersion(global),
|
||||
}
|
||||
...(config.localization
|
||||
? {
|
||||
fallbackLocale: { type: graphqlResult.types.fallbackLocaleInputType },
|
||||
locale: { type: graphqlResult.types.localeInputType },
|
||||
}
|
||||
: {}),
|
||||
limit: { type: GraphQLInt },
|
||||
page: { type: GraphQLInt },
|
||||
pagination: { type: GraphQLBoolean },
|
||||
sort: { type: GraphQLString },
|
||||
},
|
||||
resolve: findVersions(global),
|
||||
}
|
||||
graphqlResult.Mutation.fields[`restoreVersion${formatName(formattedName)}`] = {
|
||||
type: graphqlResult.globals.graphQL[slug].type,
|
||||
args: {
|
||||
id: { type: idType },
|
||||
draft: { type: GraphQLBoolean },
|
||||
},
|
||||
resolve: restoreVersion(global),
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -37,7 +37,7 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
|
||||
const message = req.t('general:deletedCountSuccessfully', {
|
||||
count: result.docs.length,
|
||||
label: getTranslation(
|
||||
collection.config.labels[result.docs.length === 1 ? 'singular' : 'plural'],
|
||||
collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
|
||||
req.i18n,
|
||||
),
|
||||
})
|
||||
@@ -58,7 +58,7 @@ export const deleteDoc: CollectionRouteHandler = async ({ collection, req }) =>
|
||||
|
||||
const message = req.t('error:unableToDeleteCount', {
|
||||
count: result.errors.length,
|
||||
label: getTranslation(collection.config.labels[total === 1 ? 'singular' : 'plural'], req.i18n),
|
||||
label: getTranslation(collection.config.labels[total > 1 ? 'plural' : 'singular'], req.i18n),
|
||||
total,
|
||||
})
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const message = req.t('general:updatedCountSuccessfully', {
|
||||
count: result.docs.length,
|
||||
label: getTranslation(
|
||||
collection.config.labels[result.docs.length === 1 ? 'singular' : 'plural'],
|
||||
collection.config.labels[result.docs.length > 1 ? 'plural' : 'singular'],
|
||||
req.i18n,
|
||||
),
|
||||
})
|
||||
@@ -62,7 +62,7 @@ export const update: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
const total = result.docs.length + result.errors.length
|
||||
const message = req.t('error:unableToUpdateCount', {
|
||||
count: result.errors.length,
|
||||
label: getTranslation(collection.config.labels[total === 1 ? 'singular' : 'plural'], req.i18n),
|
||||
label: getTranslation(collection.config.labels[total > 1 ? 'plural' : 'singular'], req.i18n),
|
||||
total,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import type {
|
||||
Data,
|
||||
DocumentPreferences,
|
||||
FormState,
|
||||
Locale,
|
||||
PayloadRequest,
|
||||
VisibleEntities,
|
||||
} from 'payload'
|
||||
import type { Data, DocumentPreferences, FormState, PayloadRequest, VisibleEntities } from 'payload'
|
||||
|
||||
import { getClientConfig } from '@payloadcms/ui/utilities/getClientConfig'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
@@ -26,7 +19,6 @@ export const renderDocumentHandler = async (args: {
|
||||
drawerSlug?: string
|
||||
initialData?: Data
|
||||
initialState?: FormState
|
||||
locale?: Locale
|
||||
overrideEntityVisibility?: boolean
|
||||
redirectAfterDelete: boolean
|
||||
redirectAfterDuplicate: boolean
|
||||
@@ -38,7 +30,6 @@ export const renderDocumentHandler = async (args: {
|
||||
docID,
|
||||
drawerSlug,
|
||||
initialData,
|
||||
locale,
|
||||
overrideEntityVisibility,
|
||||
redirectAfterDelete,
|
||||
redirectAfterDuplicate,
|
||||
@@ -154,7 +145,6 @@ export const renderDocumentHandler = async (args: {
|
||||
docID,
|
||||
globalConfig: payload.config.globals.find((global) => global.slug === collectionSlug),
|
||||
languageOptions: undefined, // TODO
|
||||
locale,
|
||||
permissions,
|
||||
req,
|
||||
translations: undefined, // TODO
|
||||
|
||||
@@ -222,6 +222,7 @@ export const renderListView = async (
|
||||
const clientProps: ListViewClientProps = {
|
||||
...listViewSlots,
|
||||
...sharedClientProps,
|
||||
collectionConfig: clientCollectionConfig,
|
||||
columnState,
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
|
||||
@@ -34,12 +34,11 @@ const generateLabelFromValue = (
|
||||
}
|
||||
|
||||
let relatedDoc: RelationshipValue
|
||||
let valueToReturn: RelationshipValue | string = ''
|
||||
let valueToReturn = '' as any
|
||||
|
||||
const relationTo = 'relationTo' in field ? field.relationTo : undefined
|
||||
|
||||
if (value === null || typeof value === 'undefined') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- We want to return a string specifilly for null and undefined
|
||||
return String(value)
|
||||
}
|
||||
|
||||
@@ -77,22 +76,19 @@ const generateLabelFromValue = (
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
|
||||
if (typeof valueToReturn === 'object' && titleFieldIsLocalized && valueToReturn?.[locale]) {
|
||||
if (typeof valueToReturn === 'object' && titleFieldIsLocalized) {
|
||||
valueToReturn = valueToReturn[locale]
|
||||
}
|
||||
} else if (relatedDoc) {
|
||||
// Handle non-polymorphic `hasMany` relationships or fallback
|
||||
if (typeof relatedDoc?.id !== 'undefined') {
|
||||
valueToReturn = String(relatedDoc.id)
|
||||
valueToReturn = relatedDoc.id
|
||||
} else {
|
||||
valueToReturn = relatedDoc
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(valueToReturn && typeof valueToReturn === 'object' && valueToReturn !== null) ||
|
||||
typeof valueToReturn !== 'string'
|
||||
) {
|
||||
if (typeof valueToReturn === 'object' && valueToReturn !== null) {
|
||||
valueToReturn = JSON.stringify(valueToReturn)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -102,14 +102,10 @@ export const bin = async () => {
|
||||
|
||||
return
|
||||
} else {
|
||||
await payload.jobs.run({
|
||||
return await payload.jobs.run({
|
||||
limit,
|
||||
queue,
|
||||
})
|
||||
|
||||
await payload.db.destroy() // close database connections after running jobs so process can exit cleanly
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -424,8 +424,6 @@ export type CollectionConfig<TSlug extends CollectionSlug = any> = {
|
||||
*/
|
||||
graphQL?:
|
||||
| {
|
||||
disableMutations?: true
|
||||
disableQueries?: true
|
||||
pluralName?: string
|
||||
singularName?: string
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { ca } from '@payloadcms/translations/languages/ca'
|
||||
@@ -1 +0,0 @@
|
||||
export { et } from '@payloadcms/translations/languages/et'
|
||||
@@ -160,8 +160,6 @@ export type GlobalConfig = {
|
||||
fields: Field[]
|
||||
graphQL?:
|
||||
| {
|
||||
disableMutations?: true
|
||||
disableQueries?: true
|
||||
name?: string
|
||||
}
|
||||
| false
|
||||
|
||||
@@ -170,8 +170,6 @@ export const getBaseUploadFields = ({ collection, config }: Options): Field[] =>
|
||||
name,
|
||||
type: 'number',
|
||||
admin: {
|
||||
disableListColumn: true,
|
||||
disableListFilter: true,
|
||||
hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.13.0",
|
||||
"version": "3.12.0",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -33,6 +33,26 @@
|
||||
"import": "./src/exports/utilities.ts",
|
||||
"types": "./src/exports/utilities.ts",
|
||||
"default": "./src/exports/utilities.ts"
|
||||
},
|
||||
"./azure": {
|
||||
"import": "./src/exports/azure.ts",
|
||||
"types": "./src/exports/azure.ts",
|
||||
"default": "./src/exports/azure.ts"
|
||||
},
|
||||
"./gcs": {
|
||||
"import": "./src/exports/gcs.ts",
|
||||
"types": "./src/exports/gcs.ts",
|
||||
"default": "./src/exports/gcs.ts"
|
||||
},
|
||||
"./s3": {
|
||||
"import": "./src/exports/s3.ts",
|
||||
"types": "./src/exports/s3.ts",
|
||||
"default": "./src/exports/s3.ts"
|
||||
},
|
||||
"./vercelBlob": {
|
||||
"import": "./src/exports/vercelBlob.ts",
|
||||
"types": "./src/exports/vercelBlob.ts",
|
||||
"default": "./src/exports/vercelBlob.ts"
|
||||
}
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
@@ -57,12 +77,43 @@
|
||||
"range-parser": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.614.0",
|
||||
"@aws-sdk/lib-storage": "^3.614.0",
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@google-cloud/storage": "^7.7.0",
|
||||
"@types/find-node-modules": "^2.1.2",
|
||||
"@vercel/blob": "^0.22.3",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@aws-sdk/client-s3": "^3.614.0",
|
||||
"@aws-sdk/lib-storage": "^3.614.0",
|
||||
"@azure/abort-controller": "^1.0.0",
|
||||
"@azure/storage-blob": "^12.11.0",
|
||||
"@google-cloud/storage": "^7.7.0",
|
||||
"@vercel/blob": "^0.22.3",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@aws-sdk/client-s3": {
|
||||
"optional": true
|
||||
},
|
||||
"@aws-sdk/lib-storage": {
|
||||
"optional": true
|
||||
},
|
||||
"@azure/abort-controller": {
|
||||
"optional": true
|
||||
},
|
||||
"@azure/storage-blob": {
|
||||
"optional": true
|
||||
},
|
||||
"@google-cloud/storage": {
|
||||
"optional": true
|
||||
},
|
||||
"@vercel/blob": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -79,6 +130,26 @@
|
||||
"import": "./dist/exports/utilities.js",
|
||||
"types": "./dist/exports/utilities.d.ts",
|
||||
"default": "./dist/exports/utilities.js"
|
||||
},
|
||||
"./azure": {
|
||||
"import": "./dist/exports/azure.js",
|
||||
"types": "./dist/exports/azure.d.ts",
|
||||
"default": "./dist/exports/azure.js"
|
||||
},
|
||||
"./gcs": {
|
||||
"import": "./dist/exports/gcs.js",
|
||||
"types": "./dist/exports/gcs.d.ts",
|
||||
"default": "./dist/exports/gcs.js"
|
||||
},
|
||||
"./s3": {
|
||||
"import": "./dist/exports/s3.js",
|
||||
"types": "./dist/exports/s3.d.ts",
|
||||
"default": "./dist/exports/s3.js"
|
||||
},
|
||||
"./vercelBlob": {
|
||||
"import": "./dist/exports/vercelBlob.js",
|
||||
"types": "./dist/exports/vercelBlob.d.ts",
|
||||
"default": "./dist/exports/vercelBlob.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
azure-storage:
|
||||
image: mcr.microsoft.com/azure-storage/azurite:3.18.0
|
||||
restart: always
|
||||
command: 'azurite --loose --blobHost 0.0.0.0 --tableHost 0.0.0.0 --queueHost 0.0.0.0'
|
||||
ports:
|
||||
- '10000:10000'
|
||||
- '10001:10001'
|
||||
- '10002:10002'
|
||||
volumes:
|
||||
- ./azurestoragedata:/data"
|
||||
|
||||
volumes:
|
||||
azurestoragedata:
|
||||
@@ -0,0 +1,14 @@
|
||||
import path from 'path'
|
||||
|
||||
import type { GenerateURL } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
baseURL: string
|
||||
containerName: string
|
||||
}
|
||||
|
||||
export const getGenerateURL =
|
||||
({ baseURL, containerName }: Args): GenerateURL =>
|
||||
({ filename, prefix = '' }) => {
|
||||
return `${baseURL}/${containerName}/${path.posix.join(prefix, filename)}`
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { ContainerClient } from '@azure/storage-blob'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { HandleDelete } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => ContainerClient
|
||||
}
|
||||
|
||||
export const getHandleDelete = ({ getStorageClient }: Args): HandleDelete => {
|
||||
return async ({ doc: { prefix = '' }, filename }) => {
|
||||
const blockBlobClient = getStorageClient().getBlockBlobClient(path.posix.join(prefix, filename))
|
||||
await blockBlobClient.deleteIfExists()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { ContainerClient } from '@azure/storage-blob'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { AbortController } from '@azure/abort-controller'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { Readable } from 'stream'
|
||||
|
||||
import type { HandleUpload } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => ContainerClient
|
||||
prefix?: string
|
||||
}
|
||||
|
||||
const multipartThreshold = 1024 * 1024 * 50 // 50MB
|
||||
export const getHandleUpload = ({ getStorageClient, prefix = '' }: Args): HandleUpload => {
|
||||
return async ({ data, file }) => {
|
||||
const fileKey = path.posix.join(data.prefix || prefix, file.filename)
|
||||
|
||||
const blockBlobClient = getStorageClient().getBlockBlobClient(fileKey)
|
||||
|
||||
// when there are no temp files, or the upload is less than the threshold size, do not stream files
|
||||
if (!file.tempFilePath && file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
|
||||
await blockBlobClient.upload(file.buffer, file.buffer.byteLength, {
|
||||
blobHTTPHeaders: { blobContentType: file.mimeType },
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const fileBufferOrStream: Readable = file.tempFilePath
|
||||
? fs.createReadStream(file.tempFilePath)
|
||||
: Readable.from(file.buffer)
|
||||
|
||||
await blockBlobClient.uploadStream(fileBufferOrStream, 4 * 1024 * 1024, 4, {
|
||||
abortSignal: AbortController.timeout(30 * 60 * 1000),
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
74
packages/plugin-cloud-storage/src/adapters/azure/index.ts
Normal file
74
packages/plugin-cloud-storage/src/adapters/azure/index.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { ContainerClient } from '@azure/storage-blob'
|
||||
|
||||
import { BlobServiceClient } from '@azure/storage-blob'
|
||||
|
||||
import type { Adapter, GeneratedAdapter } from '../../types.js'
|
||||
|
||||
import { getGenerateURL } from './generateURL.js'
|
||||
import { getHandleDelete } from './handleDelete.js'
|
||||
import { getHandleUpload } from './handleUpload.js'
|
||||
import { getHandler } from './staticHandler.js'
|
||||
|
||||
export interface Args {
|
||||
allowContainerCreate: boolean
|
||||
baseURL: string
|
||||
connectionString: string
|
||||
containerName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use [`@payloadcms/azure`](https://www.npmjs.com/package/@payloadcms/azure) instead.
|
||||
*
|
||||
* This adapter has been superceded by `@payloadcms/azure` and will be removed in Payload 3.0.
|
||||
*/
|
||||
export const azureBlobStorageAdapter = ({
|
||||
allowContainerCreate,
|
||||
baseURL,
|
||||
connectionString,
|
||||
containerName,
|
||||
}: Args): Adapter => {
|
||||
if (!BlobServiceClient) {
|
||||
throw new Error(
|
||||
'The package @azure/storage-blob is not installed, but is required for the plugin-cloud-storage Azure adapter. Please install it.',
|
||||
)
|
||||
}
|
||||
|
||||
let storageClient: ContainerClient | null = null
|
||||
const getStorageClient = () => {
|
||||
if (storageClient) {
|
||||
return storageClient
|
||||
}
|
||||
let blobServiceClient = null
|
||||
try {
|
||||
blobServiceClient = BlobServiceClient.fromConnectionString(connectionString)
|
||||
} catch (error) {
|
||||
if (/is not a constructor$/.test(error.message)) {
|
||||
throw new Error(
|
||||
'The package @azure/storage-blob is not installed, but is required for the plugin-cloud-storage Azure adapter. Please install it.',
|
||||
)
|
||||
}
|
||||
// Re-throw other unexpected errors.
|
||||
throw error
|
||||
}
|
||||
return (storageClient = blobServiceClient.getContainerClient(containerName))
|
||||
}
|
||||
|
||||
const createContainerIfNotExists = () => {
|
||||
getStorageClient().createIfNotExists({ access: 'blob' })
|
||||
}
|
||||
|
||||
return ({ collection, prefix }): GeneratedAdapter => {
|
||||
return {
|
||||
name: 'azure',
|
||||
generateURL: getGenerateURL({ baseURL, containerName }),
|
||||
handleDelete: getHandleDelete({ collection, getStorageClient }),
|
||||
handleUpload: getHandleUpload({
|
||||
collection,
|
||||
getStorageClient,
|
||||
prefix,
|
||||
}),
|
||||
staticHandler: getHandler({ collection, getStorageClient }),
|
||||
...(allowContainerCreate && { onInit: createContainerIfNotExists }),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { ContainerClient } from '@azure/storage-blob'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { StaticHandler } from '../../types.js'
|
||||
|
||||
import { getFilePrefix } from '../../utilities/getFilePrefix.js'
|
||||
import getRangeFromHeader from '../../utilities/getRangeFromHeader.js'
|
||||
|
||||
interface Args {
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => ContainerClient
|
||||
}
|
||||
|
||||
export const getHandler = ({ collection, getStorageClient }: Args): StaticHandler => {
|
||||
return async (req, { params: { filename } }) => {
|
||||
try {
|
||||
const prefix = await getFilePrefix({ collection, filename, req })
|
||||
const blockBlobClient = getStorageClient().getBlockBlobClient(
|
||||
path.posix.join(prefix, filename),
|
||||
)
|
||||
|
||||
const { end, start } = await getRangeFromHeader(blockBlobClient, req.headers.get('range'))
|
||||
|
||||
const blob = await blockBlobClient.download(start, end)
|
||||
|
||||
const response = blob._response
|
||||
|
||||
// Manually create a ReadableStream for the web from a Node.js stream.
|
||||
const readableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
const nodeStream = blob.readableStreamBody
|
||||
nodeStream.on('data', (chunk) => {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
})
|
||||
nodeStream.on('end', () => {
|
||||
controller.close()
|
||||
})
|
||||
nodeStream.on('error', (err) => {
|
||||
controller.error(err)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(readableStream, {
|
||||
headers: response.headers.rawHeaders(),
|
||||
status: response.status,
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
req.payload.logger.error(err)
|
||||
return new Response('Internal Server Error', { status: 500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
google-cloud-storage:
|
||||
image: fsouza/fake-gcs-server
|
||||
restart: always
|
||||
command:
|
||||
[
|
||||
'-scheme',
|
||||
'http',
|
||||
'-port',
|
||||
'4443',
|
||||
'-public-host',
|
||||
'http://localhost:4443',
|
||||
'-external-url',
|
||||
'http://localhost:4443',
|
||||
'-backend',
|
||||
'memory',
|
||||
]
|
||||
ports:
|
||||
- '4443:4443'
|
||||
volumes:
|
||||
- ./google-cloud-storage/payload-bucket:/data/payload-bucket
|
||||
|
||||
volumes:
|
||||
google-cloud-storage:
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Storage } from '@google-cloud/storage'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { GenerateURL } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
getStorageClient: () => Storage
|
||||
}
|
||||
|
||||
export const getGenerateURL =
|
||||
({ bucket, getStorageClient }: Args): GenerateURL =>
|
||||
({ filename, prefix = '' }) => {
|
||||
return decodeURIComponent(
|
||||
getStorageClient().bucket(bucket).file(path.posix.join(prefix, filename)).publicUrl(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Storage } from '@google-cloud/storage'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { HandleDelete } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
getStorageClient: () => Storage
|
||||
}
|
||||
|
||||
export const getHandleDelete = ({ bucket, getStorageClient }: Args): HandleDelete => {
|
||||
return async ({ doc: { prefix = '' }, filename }) => {
|
||||
await getStorageClient().bucket(bucket).file(path.posix.join(prefix, filename)).delete({
|
||||
ignoreNotFound: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Storage } from '@google-cloud/storage'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { HandleUpload } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
acl?: 'Private' | 'Public'
|
||||
bucket: string
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => Storage
|
||||
prefix?: string
|
||||
}
|
||||
|
||||
export const getHandleUpload = ({
|
||||
acl,
|
||||
bucket,
|
||||
getStorageClient,
|
||||
prefix = '',
|
||||
}: Args): HandleUpload => {
|
||||
return async ({ data, file }) => {
|
||||
const fileKey = path.posix.join(data.prefix || prefix, file.filename)
|
||||
|
||||
const gcsFile = getStorageClient().bucket(bucket).file(fileKey)
|
||||
await gcsFile.save(file.buffer, {
|
||||
metadata: {
|
||||
contentType: file.mimeType,
|
||||
},
|
||||
})
|
||||
|
||||
if (acl) {
|
||||
await gcsFile[`make${acl}`]()
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
65
packages/plugin-cloud-storage/src/adapters/gcs/index.ts
Normal file
65
packages/plugin-cloud-storage/src/adapters/gcs/index.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { StorageOptions } from '@google-cloud/storage'
|
||||
|
||||
import { Storage } from '@google-cloud/storage'
|
||||
|
||||
import type { Adapter, GeneratedAdapter } from '../../types.js'
|
||||
|
||||
import { getGenerateURL } from './generateURL.js'
|
||||
import { getHandleDelete } from './handleDelete.js'
|
||||
import { getHandleUpload } from './handleUpload.js'
|
||||
import { getHandler } from './staticHandler.js'
|
||||
|
||||
export interface Args {
|
||||
acl?: 'Private' | 'Public'
|
||||
bucket: string
|
||||
options: StorageOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use [`@payloadcms/storage-gcs`](https://www.npmjs.com/package/@payloadcms/storage-gcs) instead.
|
||||
*
|
||||
* This adapter has been superceded by `@payloadcms/storage-gcs` and will be removed in Payload 3.0.
|
||||
*/
|
||||
export const gcsAdapter =
|
||||
({ acl, bucket, options }: Args): Adapter =>
|
||||
({ collection, prefix }): GeneratedAdapter => {
|
||||
if (!Storage) {
|
||||
throw new Error(
|
||||
'The package @google-cloud/storage is not installed, but is required for the plugin-cloud-storage GCS adapter. Please install it.',
|
||||
)
|
||||
}
|
||||
|
||||
let storageClient: null | Storage = null
|
||||
|
||||
const getStorageClient = (): Storage => {
|
||||
if (storageClient) {
|
||||
return storageClient
|
||||
}
|
||||
try {
|
||||
storageClient = new Storage(options)
|
||||
} catch (error) {
|
||||
if (/is not a constructor$/.test(error.message)) {
|
||||
throw new Error(
|
||||
'The package @google-cloud/storage is not installed, but is required for the plugin-cloud-storage GCS adapter. Please install it.',
|
||||
)
|
||||
}
|
||||
// Re-throw other unexpected errors.
|
||||
throw error
|
||||
}
|
||||
return storageClient
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'gcs',
|
||||
generateURL: getGenerateURL({ bucket, getStorageClient }),
|
||||
handleDelete: getHandleDelete({ bucket, getStorageClient }),
|
||||
handleUpload: getHandleUpload({
|
||||
acl,
|
||||
bucket,
|
||||
collection,
|
||||
getStorageClient,
|
||||
prefix,
|
||||
}),
|
||||
staticHandler: getHandler({ bucket, collection, getStorageClient }),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { Storage } from '@google-cloud/storage'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { StaticHandler } from '../../types.js'
|
||||
|
||||
import { getFilePrefix } from '../../utilities/getFilePrefix.js'
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => Storage
|
||||
}
|
||||
|
||||
export const getHandler = ({ bucket, collection, getStorageClient }: Args): StaticHandler => {
|
||||
return async (req, { params: { filename } }) => {
|
||||
try {
|
||||
const prefix = await getFilePrefix({ collection, filename, req })
|
||||
const file = getStorageClient().bucket(bucket).file(path.posix.join(prefix, filename))
|
||||
|
||||
const [metadata] = await file.getMetadata()
|
||||
|
||||
// Manually create a ReadableStream for the web from a Node.js stream.
|
||||
const readableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
const nodeStream = file.createReadStream()
|
||||
nodeStream.on('data', (chunk) => {
|
||||
controller.enqueue(new Uint8Array(chunk))
|
||||
})
|
||||
nodeStream.on('end', () => {
|
||||
controller.close()
|
||||
})
|
||||
nodeStream.on('error', (err) => {
|
||||
controller.error(err)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return new Response(readableStream, {
|
||||
headers: new Headers({
|
||||
'Content-Length': String(metadata.size),
|
||||
'Content-Type': metadata.contentType,
|
||||
ETag: metadata.etag,
|
||||
}),
|
||||
status: 200,
|
||||
})
|
||||
} catch (err: unknown) {
|
||||
req.payload.logger.error(err)
|
||||
return new Response('Internal Server Error', { status: 500 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
version: '3.2'
|
||||
services:
|
||||
localstack:
|
||||
image: localstack/localstack:latest
|
||||
container_name: localstack_demo
|
||||
ports:
|
||||
- '4563-4599:4563-4599'
|
||||
- '8055:8080'
|
||||
environment:
|
||||
- SERVICES=s3
|
||||
- DEBUG=1
|
||||
- DATA_DIR=/tmp/localstack/data
|
||||
volumes:
|
||||
- './.localstack:/var/lib/localstack'
|
||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||
16
packages/plugin-cloud-storage/src/adapters/s3/generateURL.ts
Normal file
16
packages/plugin-cloud-storage/src/adapters/s3/generateURL.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type * as AWS from '@aws-sdk/client-s3'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { GenerateURL } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
config: AWS.S3ClientConfig
|
||||
}
|
||||
|
||||
export const getGenerateURL =
|
||||
({ bucket, config: { endpoint } }: Args): GenerateURL =>
|
||||
({ filename, prefix = '' }) => {
|
||||
return `${endpoint}/${bucket}/${path.posix.join(prefix, filename)}`
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import type * as AWS from '@aws-sdk/client-s3'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
import type { HandleDelete } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
bucket: string
|
||||
getStorageClient: () => AWS.S3
|
||||
}
|
||||
|
||||
export const getHandleDelete = ({ bucket, getStorageClient }: Args): HandleDelete => {
|
||||
return async ({ doc: { prefix = '' }, filename }) => {
|
||||
await getStorageClient().deleteObject({
|
||||
Bucket: bucket,
|
||||
Key: path.posix.join(prefix, filename),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type * as AWS from '@aws-sdk/client-s3'
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import type stream from 'stream'
|
||||
|
||||
import { Upload } from '@aws-sdk/lib-storage'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import type { HandleUpload } from '../../types.js'
|
||||
|
||||
interface Args {
|
||||
acl?: 'private' | 'public-read'
|
||||
bucket: string
|
||||
collection: CollectionConfig
|
||||
getStorageClient: () => AWS.S3
|
||||
prefix?: string
|
||||
}
|
||||
|
||||
const multipartThreshold = 1024 * 1024 * 50 // 50MB
|
||||
|
||||
export const getHandleUpload = ({
|
||||
acl,
|
||||
bucket,
|
||||
getStorageClient,
|
||||
prefix = '',
|
||||
}: Args): HandleUpload => {
|
||||
return async ({ data, file }) => {
|
||||
const fileKey = path.posix.join(data.prefix || prefix, file.filename)
|
||||
|
||||
const fileBufferOrStream: Buffer | stream.Readable = file.tempFilePath
|
||||
? fs.createReadStream(file.tempFilePath)
|
||||
: file.buffer
|
||||
|
||||
if (file.buffer.length > 0 && file.buffer.length < multipartThreshold) {
|
||||
await getStorageClient().putObject({
|
||||
ACL: acl,
|
||||
Body: fileBufferOrStream,
|
||||
Bucket: bucket,
|
||||
ContentType: file.mimeType,
|
||||
Key: fileKey,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const parallelUploadS3 = new Upload({
|
||||
client: getStorageClient(),
|
||||
params: {
|
||||
ACL: acl,
|
||||
Body: fileBufferOrStream,
|
||||
Bucket: bucket,
|
||||
ContentType: file.mimeType,
|
||||
Key: fileKey,
|
||||
},
|
||||
partSize: multipartThreshold,
|
||||
queueSize: 4,
|
||||
})
|
||||
|
||||
await parallelUploadS3.done()
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user