Compare commits
83 Commits
feat/auth-
...
feat/ecomm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc8c6d3261 | ||
|
|
4a088eb9f4 | ||
|
|
884563c246 | ||
|
|
ebf23dd8a7 | ||
|
|
dfd8bd1998 | ||
|
|
88f51b09c4 | ||
|
|
a3b28715f8 | ||
|
|
337f6188da | ||
|
|
48218bccb5 | ||
|
|
bd512f1eda | ||
|
|
c08cdff498 | ||
|
|
cbc37d84bd | ||
|
|
76bf459ff2 | ||
|
|
30bb749e25 | ||
|
|
2bd098c9ea | ||
|
|
08ec837339 | ||
|
|
505eaa2bba | ||
|
|
6ec21a53ff | ||
|
|
625d8d9319 | ||
|
|
a9ff375cc0 | ||
|
|
0ceb96b12d | ||
|
|
319d3355de | ||
|
|
2b40e0f21f | ||
|
|
30dd9a23a3 | ||
|
|
c639c5f278 | ||
|
|
05eeddba7c | ||
|
|
08a6f88a4b | ||
|
|
ede5c671b8 | ||
|
|
684c43604a | ||
|
|
8199a7d32a | ||
|
|
6f8cff7764 | ||
|
|
89ced5ec6b | ||
|
|
836fd86090 | ||
|
|
7c094dc572 | ||
|
|
c83e791014 | ||
|
|
6119d89fa5 | ||
|
|
d5611953a7 | ||
|
|
71df378fb0 | ||
|
|
5e3a94bbc9 | ||
|
|
3670886bee | ||
|
|
6888f13f27 | ||
|
|
12395e497b | ||
|
|
a17d84e570 | ||
|
|
ca6f849b53 | ||
|
|
7e873a9d63 | ||
|
|
c471a1a106 | ||
|
|
e67e48b054 | ||
|
|
b723f9e817 | ||
|
|
d85909e5ae | ||
|
|
699af8dc5b | ||
|
|
0c0b0fe0f8 | ||
|
|
bfdcb51793 | ||
|
|
ca26402377 | ||
|
|
3022cab8ac | ||
|
|
8a7ac784c4 | ||
|
|
54a04840c7 | ||
|
|
f2b54b5b43 | ||
|
|
4a41369a00 | ||
|
|
7fa879c3a0 | ||
|
|
166dafe05e | ||
|
|
1415ff63ea | ||
|
|
f2c881a414 | ||
|
|
68ba24d91f | ||
|
|
20f7017758 | ||
|
|
0204f0dcbc | ||
|
|
032375b016 | ||
|
|
8448e5b6b6 | ||
|
|
1e79392f90 | ||
|
|
8e056bbd36 | ||
|
|
d6f6b05d77 | ||
|
|
feb7e082af | ||
|
|
dfa0974894 | ||
|
|
f2b6c4a707 | ||
|
|
60163337ac | ||
|
|
69757780bb | ||
|
|
ba14c00786 | ||
|
|
609daaf8b4 | ||
|
|
5d5a5d0c57 | ||
|
|
ae0ac8c669 | ||
|
|
d077e00138 | ||
|
|
4289d68908 | ||
|
|
1c42bff054 | ||
|
|
4a0ed68751 |
6
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
6
.github/ISSUE_TEMPLATE/1.bug_report_v3.yml
vendored
@@ -43,6 +43,7 @@ body:
|
||||
- 'plugin: cloud'
|
||||
- 'plugin: cloud-storage'
|
||||
- 'plugin: form-builder'
|
||||
- 'plugin: multi-tenant'
|
||||
- 'plugin: nested-docs'
|
||||
- 'plugin: richtext-lexical'
|
||||
- 'plugin: richtext-slate'
|
||||
@@ -59,10 +60,7 @@ body:
|
||||
label: Environment Info
|
||||
description: Paste output from `pnpm payload info` _or_ Payload, Node.js, and Next.js versions. Please avoid using "latest"—specific version numbers help us accurately diagnose and resolve issues.
|
||||
render: text
|
||||
placeholder: |
|
||||
Payload:
|
||||
Node.js:
|
||||
Next.js:
|
||||
placeholder: Run `pnpm payload info` in your terminal and paste the output here.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
137
.github/workflows/main.yml
vendored
137
.github/workflows/main.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
- labeled
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -383,6 +384,142 @@ jobs:
|
||||
# report-tag: ${{ matrix.suite }}
|
||||
# job-summary: true
|
||||
|
||||
tests-e2e-turbo:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: [changes, build]
|
||||
if: >-
|
||||
needs.changes.outputs.needs_tests == 'true' &&
|
||||
(
|
||||
contains(github.event.pull_request.labels.*.name, 'run-e2e-turbo') ||
|
||||
github.event.label.name == 'run-e2e-turbo'
|
||||
)
|
||||
name: e2e-turbo-${{ 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-bar
|
||||
- admin-root
|
||||
- auth
|
||||
- auth-basic
|
||||
- bulk-edit
|
||||
- joins
|
||||
- field-error-states
|
||||
- fields-relationship
|
||||
- fields__collections__Array
|
||||
- fields__collections__Blocks
|
||||
- fields__collections__Blocks#config.blockreferences.ts
|
||||
- fields__collections__Checkbox
|
||||
- fields__collections__Collapsible
|
||||
- fields__collections__ConditionalLogic
|
||||
- fields__collections__CustomID
|
||||
- fields__collections__Date
|
||||
- fields__collections__Email
|
||||
- fields__collections__Indexed
|
||||
- fields__collections__JSON
|
||||
- fields__collections__Number
|
||||
- fields__collections__Point
|
||||
- fields__collections__Radio
|
||||
- fields__collections__Relationship
|
||||
- fields__collections__Row
|
||||
- fields__collections__Select
|
||||
- fields__collections__Tabs
|
||||
- fields__collections__Tabs2
|
||||
- fields__collections__Text
|
||||
- fields__collections__UI
|
||||
- fields__collections__Upload
|
||||
- hooks
|
||||
- lexical__collections__Lexical__e2e__main
|
||||
- lexical__collections__Lexical__e2e__blocks
|
||||
- lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts
|
||||
- lexical__collections__RichText
|
||||
- query-presets
|
||||
- form-state
|
||||
- live-preview
|
||||
- localization
|
||||
- locked-documents
|
||||
- i18n
|
||||
- plugin-cloud-storage
|
||||
- plugin-form-builder
|
||||
- plugin-import-export
|
||||
- plugin-nested-docs
|
||||
- plugin-seo
|
||||
- sort
|
||||
- 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
|
||||
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci:turbo ${{ matrix.suite }}
|
||||
env:
|
||||
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: test-results-turbo${{ 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
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -311,6 +311,8 @@ test/admin-bar/app/(payload)/admin/importMap.js
|
||||
/test/admin-bar/app/(payload)/admin/importMap.js
|
||||
test/live-preview/app/(payload)/admin/importMap.js
|
||||
/test/live-preview/app/(payload)/admin/importMap.js
|
||||
test/plugin-ecommerce/app/(payload)/admin/importMap.js
|
||||
/test/plugin-ecommerce/app/(payload)/admin/importMap.js
|
||||
test/admin-root/app/(payload)/admin/importMap.js
|
||||
/test/admin-root/app/(payload)/admin/importMap.js
|
||||
test/app/(payload)/admin/importMap.js
|
||||
|
||||
@@ -87,41 +87,43 @@ You can run the entire test suite using `pnpm test`. If you wish to only run e2e
|
||||
|
||||
By default, `pnpm test:int` will only run int test against MongoDB. To run int tests against postgres, you can use `pnpm test:int:postgres`. You will have to have postgres installed on your system for this to work.
|
||||
|
||||
### Commits
|
||||
### Pull Requests
|
||||
|
||||
We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for our commit messages. Please follow this format when creating commits. Here are some examples:
|
||||
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR description.
|
||||
|
||||
- `feat: adds new feature`
|
||||
- `fix: fixes bug`
|
||||
- `docs: adds documentation`
|
||||
- `chore: does chore`
|
||||
All commits within a PR are squashed when merged, using the PR title as the commit message. For that reason, please use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR titles.
|
||||
|
||||
Here's a breakdown of the format. At the top-level, we use the following types to categorize our commits:
|
||||
Here are some examples:
|
||||
|
||||
- `feat`: new feature that adds functionality. These are automatically added to the changelog when creating new releases.
|
||||
- `fix`: a fix to an existing feature. These are automatically added to the changelog when creating new releases.
|
||||
- `docs`: changes to [docs](./docs) only. These do not appear in the changelog.
|
||||
- `chore`: changes to code that is neither a fix nor a feature (e.g. refactoring, adding tests, etc.). These do not appear in the changelog.
|
||||
- `feat: add new feature`
|
||||
- `fix: fix bug`
|
||||
- `docs: add documentation`
|
||||
- `test: add/fix tests`
|
||||
- `refactor: refactor code`
|
||||
- `chore: anything that does not fit into the above categories`
|
||||
|
||||
If applicable, you must indicate the affected packages in parentheses to "scope" the changes. Changes to the payload chore package do not require scoping.
|
||||
|
||||
Here are some examples:
|
||||
|
||||
- `feat(ui): add new feature`
|
||||
- `fix(richtext-lexical): fix bug`
|
||||
|
||||
If you are committing to [templates](./templates) or [examples](./examples), use the `chore` type with the proper scope, like this:
|
||||
|
||||
- `chore(templates): adds feature to template`
|
||||
- `chore(examples): fixes bug in example`
|
||||
|
||||
## Pull Requests
|
||||
|
||||
For all Pull Requests, you should be extremely descriptive about both your problem and proposed solution. If there are any affected open or closed issues, please leave the issue number in your PR message.
|
||||
|
||||
## Previewing docs
|
||||
|
||||
This is how you can preview changes you made locally to the docs:
|
||||
|
||||
1. Clone our [website repository](https://github.com/payloadcms/website)
|
||||
2. Run `yarn install`
|
||||
2. Run `pnpm install`
|
||||
3. Duplicate the `.env.example` file and rename it to `.env`
|
||||
4. Add a `DOCS_DIR` environment variable to the `.env` file which points to the absolute path of your modified docs folder. For example `DOCS_DIR=/Users/yourname/Documents/GitHub/payload/docs`
|
||||
5. Run `yarn run fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
|
||||
6. You're done! Now you can start the website locally using `yarn run dev` and preview the docs under [http://localhost:3000/docs/](http://localhost:3000/docs/)
|
||||
5. Run `pnpm fetchDocs:local`. If this was successful, you should see no error messages and the following output: _Docs successfully written to /.../website/src/app/docs.json_. There could be error messages if you have incorrect markdown in your local docs folder. In this case, it will tell you how you can fix it
|
||||
6. You're done! Now you can start the website locally using `pnpm dev` and preview the docs under [http://localhost:3000/docs/local](http://localhost:3000/docs/local)
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
|
||||
@@ -981,7 +981,15 @@ const MyComponent: React.FC = () => {
|
||||
|
||||
## useTableColumns
|
||||
|
||||
Returns methods to manipulate table columns
|
||||
Returns properties and methods to manipulate table columns:
|
||||
| Property | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------ |
|
||||
| **`columns`** | The current state of columns including their active status and configuration |
|
||||
| **`LinkedCellOverride`** | A component override for linked cells in the table |
|
||||
| **`moveColumn`** | A method to reorder columns. Accepts `{ fromIndex: number, toIndex: number }` as arguments |
|
||||
| **`resetColumnsState`** | A method to reset columns back to their default configuration as defined in the collection config |
|
||||
| **`setActiveColumns`** | A method to set specific columns to active state while preserving the existing column order. Accepts an array of column names to activate |
|
||||
| **`toggleColumn`** | A method to toggle a single column's visibility. Accepts a column name as string |
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
@@ -989,17 +997,30 @@ import { useTableColumns } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const { setActiveColumns } = useTableColumns()
|
||||
const { setActiveColumns, resetColumnsState } = useTableColumns()
|
||||
|
||||
const resetColumns = () => {
|
||||
setActiveColumns(['id', 'createdAt', 'updatedAt'])
|
||||
const activateSpecificColumns = () => {
|
||||
// Only activates the id and createdAt columns
|
||||
// Other columns retain their current active/inactive state
|
||||
// The original column order is preserved
|
||||
setActiveColumns(['id', 'createdAt'])
|
||||
}
|
||||
|
||||
const resetToDefaults = () => {
|
||||
// Resets to the default columns defined in the collection config
|
||||
resetColumnsState()
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<button type="button" onClick={resetColumns}>
|
||||
Reset columns
|
||||
</button>
|
||||
<div>
|
||||
<button type="button" onClick={activateSpecificColumns}>
|
||||
Activate Specific Columns
|
||||
</button>
|
||||
<button type="button" onClick={resetToDefaults}>
|
||||
Reset To Defaults
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -25,11 +25,12 @@ A strategy is made up of the following:
|
||||
|
||||
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. |
|
||||
| **`isGraphQL`** | Whether or not the request was made from a GraphQL endpoint. Default is `false`. |
|
||||
| Argument | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`canSetHeaders`** \* | Whether or not the strategy is being executed from a context where response headers can be set. Default is `false`. |
|
||||
| **`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 strategy is being executed within the GraphQL endpoint. Default is `false`. |
|
||||
|
||||
### Example Strategy
|
||||
|
||||
|
||||
@@ -142,14 +142,17 @@ import { buildConfig } from 'payload'
|
||||
export default buildConfig({
|
||||
// ...
|
||||
// highlight-start
|
||||
autoLogin:
|
||||
process.env.NEXT_PUBLIC_ENABLE_AUTOLOGIN === 'true'
|
||||
? {
|
||||
email: 'test@example.com',
|
||||
password: 'test',
|
||||
prefillOnly: true,
|
||||
}
|
||||
: false,
|
||||
admin: {
|
||||
autoLogin:
|
||||
process.env.NODE_ENV === 'development'
|
||||
? {
|
||||
email: 'test@example.com',
|
||||
password: 'test',
|
||||
prefillOnly: true,
|
||||
}
|
||||
: false,
|
||||
},
|
||||
|
||||
// highlight-end
|
||||
})
|
||||
```
|
||||
|
||||
@@ -194,13 +194,15 @@ export const MyCollection: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Option | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
|
||||
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
|
||||
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
|
||||
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
|
||||
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
|
||||
| Option | Description |
|
||||
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](../custom-components/edit-view#beforedocumentcontrols). |
|
||||
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document controls bar. [More details](../custom-components/edit-view#editmenuitems). |
|
||||
| `SaveButton` | Replace the default Save Button within the Edit View. [Drafts](../versions/drafts) must be disabled. [More details](../custom-components/edit-view#savebutton). |
|
||||
| `SaveDraftButton` | Replace the default Save Draft Button within the Edit View. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. [More details](../custom-components/edit-view#savedraftbutton). |
|
||||
| `PublishButton` | Replace the default Publish Button within the Edit View. [Drafts](../versions/drafts) must be enabled. [More details](../custom-components/edit-view#publishbutton). |
|
||||
| `PreviewButton` | Replace the default Preview Button within the Edit View. [Preview](../admin/preview) must be enabled. [More details](../custom-components/edit-view#previewbutton). |
|
||||
| `Upload` | Replace the default Upload component within the Edit View. [Upload](../upload/overview) must be enabled. [More details](../custom-components/edit-view#upload). |
|
||||
|
||||
<Banner type="success">
|
||||
**Note:** For details on how to build Custom Components, see [Building Custom
|
||||
|
||||
@@ -101,15 +101,16 @@ export const MyCollection: CollectionConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Collection. [More details](#description). |
|
||||
| `Upload` | A file upload component. [More details](#upload). |
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Collection. [More details](#description). |
|
||||
| `Upload` | A file upload component. [More details](#upload). |
|
||||
|
||||
#### Globals
|
||||
|
||||
@@ -134,14 +135,15 @@ export const MyGlobal: GlobalConfig = {
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Global. [More details](#description). |
|
||||
| Path | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `beforeDocumentControls` | Inject custom components before the Save / Publish buttons. [More details](#beforedocumentcontrols). |
|
||||
| `editMenuItems` | Inject custom components within the 3-dot menu dropdown located in the document control bar. [More details](#editmenuitems). |
|
||||
| `SaveButton` | A button that saves the current document. [More details](#savebutton). |
|
||||
| `SaveDraftButton` | A button that saves the current document as a draft. [More details](#savedraftbutton). |
|
||||
| `PublishButton` | A button that publishes the current document. [More details](#publishbutton). |
|
||||
| `PreviewButton` | A button that previews the current document. [More details](#previewbutton). |
|
||||
| `Description` | A description of the Global. [More details](#description). |
|
||||
|
||||
### SaveButton
|
||||
|
||||
@@ -260,6 +262,92 @@ export function MyCustomDocumentControlButton(
|
||||
}
|
||||
```
|
||||
|
||||
### editMenuItems
|
||||
|
||||
The `editMenuItems` property allows you to inject custom components into the 3-dot menu dropdown located in the document controls bar. This dropdown contains default options including `Create New`, `Duplicate`, `Delete`, and other options when additional features are enabled. Any custom components you add will appear below these default items.
|
||||
|
||||
To add `editMenuItems`, use the `components.edit.editMenuItems` property in your [Collection Config](../configuration/collections):
|
||||
|
||||
#### Config Example
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
admin: {
|
||||
components: {
|
||||
edit: {
|
||||
// highlight-start
|
||||
editMenuItems: ['/path/to/CustomEditMenuItem'],
|
||||
// highlight-end
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Here's an example of a custom `editMenuItems` component:
|
||||
|
||||
#### Server Component
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { PopupList } from '@payloadcms/ui'
|
||||
|
||||
import type { EditMenuItemsServerProps } from 'payload'
|
||||
|
||||
export const EditMenuItems = async (props: EditMenuItemsServerProps) => {
|
||||
const href = `/custom-action?id=${props.id}`
|
||||
|
||||
return (
|
||||
<PopupList.ButtonGroup>
|
||||
<PopupList.Button href={href}>Custom Edit Menu Item</PopupList.Button>
|
||||
<PopupList.Button href={href}>
|
||||
Another Custom Edit Menu Item - add as many as you need!
|
||||
</PopupList.Button>
|
||||
</PopupList.ButtonGroup>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Client Component
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import { PopupList } from '@payloadcms/ui'
|
||||
|
||||
import type { EditViewMenuItemClientProps } from 'payload'
|
||||
|
||||
export const EditMenuItems = (props: EditViewMenuItemClientProps) => {
|
||||
const handleClick = () => {
|
||||
console.log('Custom button clicked!')
|
||||
}
|
||||
|
||||
return (
|
||||
<PopupList.ButtonGroup>
|
||||
<PopupList.Button onClick={handleClick}>
|
||||
Custom Edit Menu Item
|
||||
</PopupList.Button>
|
||||
<PopupList.Button onClick={handleClick}>
|
||||
Another Custom Edit Menu Item - add as many as you need!
|
||||
</PopupList.Button>
|
||||
</PopupList.ButtonGroup>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
<Banner type="info">
|
||||
**Styling:** Use Payload's built-in <code>PopupList.Button</code> to
|
||||
ensure your menu items automatically match the default dropdown styles. If you
|
||||
want a different look, you can customize the appearance by passing your own{' '}
|
||||
<code>className</code> to <code>PopupList.Button</code>, or use a completely
|
||||
custom button built with a standard HTML <code><button></code> element
|
||||
or any other component that fits your design preferences.
|
||||
</Banner>
|
||||
|
||||
### SaveDraftButton
|
||||
|
||||
The `SaveDraftButton` property allows you to render a custom Save Draft Button in the Edit View.
|
||||
|
||||
@@ -94,6 +94,7 @@ The following options are available:
|
||||
| `beforeListTable` | An array of custom components to inject before the table of documents in the List View. [More details](#beforelisttable). |
|
||||
| `afterList` | An array of custom components to inject after the list of documents in the List View. [More details](#afterlist). |
|
||||
| `afterListTable` | An array of custom components to inject after the table of documents in the List View. [More details](#afterlisttable). |
|
||||
| `listMenuItems` | An array of components to render within a menu next to the List Controls (after the Columns and Filters options) |
|
||||
| `Description` | A component to render a description of the Collection. [More details](#description). |
|
||||
|
||||
### beforeList
|
||||
|
||||
@@ -183,13 +183,13 @@ Depending on which Database Adapter you use, your migration workflow might diffe
|
||||
|
||||
In relational databases, migrations will be **required** for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).
|
||||
|
||||
#### MongoDB
|
||||
#### MongoDB#mongodb-migrations
|
||||
|
||||
In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.
|
||||
|
||||
In this case, you can create a migration by running `pnpm payload migrate:create`, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the `pnpm payload migrate` command.
|
||||
|
||||
#### Postgres
|
||||
#### Postgres#postgres-migrations
|
||||
|
||||
In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export const MyGroupField: Field = {
|
||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`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. Required when name is undefined, defaults to name converted to words. |
|
||||
| **`label`** | Used as a heading in the Admin Panel and to name the generated GraphQL type. Defaults to the field name, if defined. |
|
||||
| **`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. |
|
||||
| **`hooks`** | Provide Field Hooks to control logic for this field. [More details](../hooks/fields). |
|
||||
@@ -113,8 +113,7 @@ export const ExampleCollection: CollectionConfig = {
|
||||
|
||||
## Presentational group fields
|
||||
|
||||
You can also use the Group field to create a presentational group of fields. This is useful when you want to group fields together visually without affecting the data structure.
|
||||
The label will be required when a `name` is not provided.
|
||||
You can also use the Group field to only visually group fields without affecting the data structure. Not defining a label will render just the grouped fields.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
@@ -70,7 +70,7 @@ _\* An asterisk denotes that a property is required._
|
||||
|
||||
### filterOptions
|
||||
|
||||
Used to dynamically filter which options are available based on the user, data, etc.
|
||||
Used to dynamically filter which options are available based on the current user, document data, or other criteria.
|
||||
|
||||
Some examples of this might include:
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ On the payload config, you can configure the following settings under the `folde
|
||||
// Type definition
|
||||
|
||||
type RootFoldersConfiguration = {
|
||||
/**
|
||||
* If true, the browse by folder view will be enabled
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
browseByFolder?: boolean
|
||||
/**
|
||||
* An array of functions to be ran when the folder collection is initialized
|
||||
* This allows plugins to modify the collection configuration
|
||||
@@ -82,7 +88,16 @@ To enable folders on a collection, you need to set the `admin.folders` property
|
||||
```ts
|
||||
// Type definition
|
||||
|
||||
type CollectionFoldersConfiguration = boolean
|
||||
type CollectionFoldersConfiguration =
|
||||
| boolean
|
||||
| {
|
||||
/**
|
||||
* If true, the collection will be included in the browse by folder view
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
browseByFolder?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
|
||||
@@ -329,7 +329,7 @@ available:
|
||||
// responseHeaders: { ... } // returned headers from the response
|
||||
// }
|
||||
|
||||
const result = await payload.auth({ headers })
|
||||
const result = await payload.auth({ headers, canSetHeaders: false })
|
||||
```
|
||||
|
||||
### Login
|
||||
|
||||
@@ -16,8 +16,8 @@ This plugin sets up multi-tenancy for your application from within your [Admin P
|
||||
If you need help, check out our [Community
|
||||
Help](https://payloadcms.com/community-help). If you think you've found a bug,
|
||||
please [open a new
|
||||
issue](https://github.com/payloadcms/payload/issues/new?assignees=&labels=plugin%3A%multi-tenant&template=bug_report.md&title=plugin-multi-tenant%3A)
|
||||
with as much detail as possible.
|
||||
issue](https://github.com/payloadcms/payload/issues/new/choose) with as much
|
||||
detail as possible.
|
||||
</Banner>
|
||||
|
||||
## Core features
|
||||
@@ -35,7 +35,7 @@ This plugin sets up multi-tenancy for your application from within your [Admin P
|
||||
By default this plugin cleans up documents when a tenant is deleted. You should ensure you have
|
||||
strong access control on your tenants collection to prevent deletions by unauthorized users.
|
||||
|
||||
You can disabled this behavior by setting `cleanupAfterTenantDelete` to `false` in the plugin options.
|
||||
You can disable this behavior by setting `cleanupAfterTenantDelete` to `false` in the plugin options.
|
||||
|
||||
</Banner>
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ Set the `uploadsCollection` to your application's upload-enabled collection slug
|
||||
##### `tabbedUI`
|
||||
|
||||
When the `tabbedUI` property is `true`, it appends an `SEO` tab onto your config using Payload's [Tabs Field](../fields/tabs). If your collection is not already tab-enabled, meaning the first field in your config is not of type `tabs`, then one will be created for you called `Content`. Defaults to `false`.
|
||||
Note that the order of plugins or fields in your config may affect whether or not the plugin can smartly merge tabs with your existing fields. If you have a complex structure we recommend you [make use of the fields directly](#direct-use-of-fields) instead of relying on this config option.
|
||||
|
||||
<Banner type="info">
|
||||
If you wish to continue to use top-level or sidebar fields with `tabbedUI`,
|
||||
|
||||
32
docs/production/building-without-a-db-connection.mdx
Normal file
32
docs/production/building-without-a-db-connection.mdx
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Building without a DB connection
|
||||
label: Building without a DB connection
|
||||
order: 10
|
||||
desc: You don't want to have a DB connection while building your Docker container? Learn how to prevent that!
|
||||
keywords: deployment, production, config, configuration, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
|
||||
---
|
||||
|
||||
# Building without a DB connection
|
||||
|
||||
One of the most common problems when building a site for production, especially with Docker - is the DB connection requirement.
|
||||
|
||||
The important note is that Payload by itself does not have this requirement, But [Next.js' SSG ](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation) does if any of your route segments have SSG enabled (which is default, unless you opted out or used a [Dynamic API](https://nextjs.org/docs/app/deep-dive/caching#dynamic-apis)) and use the Payload Local API.
|
||||
|
||||
Solutions:
|
||||
|
||||
## Using the experimental-build-mode Next.js build flag
|
||||
|
||||
You can run Next.js build using the `pnpx next build --experimental-build-mode compile` command to only compile the code without static generation, which does not require a DB connection. In that case, your pages will be rendered dynamically, but after that, you can still generate static pages using the `pnpx next build --experimental-build-mode generate` command when you have a DB connection.
|
||||
|
||||
[Next.js documentation](https://nextjs.org/docs/pages/api-reference/cli/next#next-build-options)
|
||||
|
||||
## Opting-out of SSG
|
||||
|
||||
You can opt out of SSG by adding this all the route segment files:
|
||||
|
||||
```ts
|
||||
export const dynamic = 'force-dynamic'
|
||||
```
|
||||
|
||||
**Note that it will disable static optimization and your site will be slower**.
|
||||
More on [Next.js documentation](https://nextjs.org/docs/app/deep-dive/caching#opting-out-2)
|
||||
@@ -150,7 +150,7 @@ Follow the docs to configure any one of these storage providers. For local devel
|
||||
## Docker
|
||||
|
||||
This is an example of a multi-stage docker build of Payload for production. Ensure you are setting your environment
|
||||
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed.
|
||||
variables on deployment, like `PAYLOAD_SECRET`, `PAYLOAD_CONFIG_PATH`, and `DATABASE_URI` if needed. If you don't want to have a DB connection and your build requires that, learn [here](./building-without-a-db-connection) how to prevent that.
|
||||
|
||||
In your Next.js config, set the `output` property `standalone`.
|
||||
|
||||
|
||||
@@ -46,11 +46,12 @@ const config = buildConfig({
|
||||
|
||||
The following options are available for Query Presets:
|
||||
|
||||
| Option | Description |
|
||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `access` | Used to define custom collection-level access control that applies to all presets. [More details](#access-control). |
|
||||
| `constraints` | Used to define custom document-level access control that apply to individual presets. [More details](#document-access-control). |
|
||||
| `labels` | Custom labels to use for the Query Presets collection. |
|
||||
| Option | Description |
|
||||
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `access` | Used to define custom collection-level access control that applies to all presets. [More details](#access-control). |
|
||||
| `filterConstraints` | Used to define which constraints are available to users when managing presets. [More details](#constraint-access-control). |
|
||||
| `constraints` | Used to define custom document-level access control that apply to individual presets. [More details](#document-access-control). |
|
||||
| `labels` | Custom labels to use for the Query Presets collection. |
|
||||
|
||||
## Access Control
|
||||
|
||||
@@ -59,7 +60,7 @@ Query Presets are subject to the same [Access Control](../access-control/overvie
|
||||
Access Control for Query Presets can be customized in two ways:
|
||||
|
||||
1. [Collection Access Control](#collection-access-control): Applies to all presets. These rules are not controllable by the user and are statically defined in the config.
|
||||
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are saved to the document.
|
||||
2. [Document Access Control](#document-access-control): Applies to each individual preset. These rules are controllable by the user and are dynamically defined on each record in the database.
|
||||
|
||||
### Collection Access Control
|
||||
|
||||
@@ -97,7 +98,7 @@ This example restricts all Query Presets to users with the role of `admin`.
|
||||
|
||||
### Document Access Control
|
||||
|
||||
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each document.
|
||||
You can also define access control rules that apply to each specific preset. Users have the ability to define and modify these rules on the fly as they manage presets. These are saved dynamically in the database on each record.
|
||||
|
||||
When a user manages a preset, document-level access control options will be available to them in the Admin Panel for each operation.
|
||||
|
||||
@@ -150,8 +151,8 @@ const config = buildConfig({
|
||||
}),
|
||||
},
|
||||
],
|
||||
// highlight-end
|
||||
},
|
||||
// highlight-end
|
||||
},
|
||||
})
|
||||
```
|
||||
@@ -171,3 +172,39 @@ The following options are available for each constraint:
|
||||
| `value` | The value to store in the database when this constraint is selected. |
|
||||
| `fields` | An array of fields to render when this constraint is selected. |
|
||||
| `access` | A function that determines the access control rules for this constraint. |
|
||||
|
||||
### Constraint Access Control
|
||||
|
||||
Used to dynamically filter which constraints are available based on the current user, document data, or other criteria.
|
||||
|
||||
Some examples of this might include:
|
||||
|
||||
- Ensuring that only "admins" are allowed to make a preset available to "everyone"
|
||||
- Preventing the "onlyMe" option from being selected based on a hypothetical "disablePrivatePresets" checkbox
|
||||
|
||||
When a user lacks the permission to set a constraint, the option will either be hidden from them, or disabled if it is already saved to that preset.
|
||||
|
||||
To do this, you can use the `filterConstraints` property in your [Payload Config](../configuration/overview):
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload'
|
||||
|
||||
const config = buildConfig({
|
||||
// ...
|
||||
queryPresets: {
|
||||
// ...
|
||||
// highlight-start
|
||||
filterConstraints: ({ req, options }) =>
|
||||
!req.user?.roles?.includes('admin')
|
||||
? options.filter(
|
||||
(option) =>
|
||||
(typeof option === 'string' ? option : option.value) !==
|
||||
'everyone',
|
||||
)
|
||||
: options,
|
||||
// highlight-end
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The `filterConstraints` function receives the same arguments as [`filterOptions`](../fields/select#filterOptions) in the [Select field](../fields/select).
|
||||
|
||||
@@ -738,7 +738,7 @@ Payload supports a method override feature that allows you to send GET requests
|
||||
|
||||
### How to Use
|
||||
|
||||
To use this feature, include the `X-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
|
||||
To use this feature, include the `X-Payload-HTTP-Method-Override` header set to `GET` in your POST request. The parameters should be sent in the body of the request with the `Content-Type` set to `application/x-www-form-urlencoded`.
|
||||
|
||||
### Example
|
||||
|
||||
@@ -753,7 +753,7 @@ const res = await fetch(`${api}/${collectionSlug}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-HTTP-Method-Override': 'GET',
|
||||
'X-Payload-HTTP-Method-Override': 'GET',
|
||||
},
|
||||
body: qs.stringify({
|
||||
depth: 1,
|
||||
|
||||
@@ -42,6 +42,7 @@ export const rootEslintConfig = [
|
||||
...defaultESLintIgnores,
|
||||
'packages/eslint-*/**',
|
||||
'test/live-preview/next-app',
|
||||
'test/plugin-ecommerce/next-app',
|
||||
'packages/**/*.spec.ts',
|
||||
'templates/**',
|
||||
'examples/**',
|
||||
|
||||
@@ -3,14 +3,8 @@
|
||||
width: var(--base);
|
||||
|
||||
.stroke {
|
||||
stroke-width: 1px;
|
||||
stroke-width: 2px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
}
|
||||
|
||||
&:local() {
|
||||
.stroke {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
examples/live-preview/next-env.d.ts
vendored
2
examples/live-preview/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"description": "Payload Live Preview example.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"build": "cross-env NODE_OPTIONS=--no-deprecation next build",
|
||||
|
||||
3278
examples/live-preview/pnpm-lock.yaml
generated
3278
examples/live-preview/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,9 @@
|
||||
import type { MigrateUpArgs } from '@payloadcms/db-mongodb'
|
||||
|
||||
import type { Page } from '../payload-types'
|
||||
import { DefaultDocumentIDType } from 'payload'
|
||||
|
||||
export const home: Partial<Page> = {
|
||||
export const home = (id: DefaultDocumentIDType): Partial<Page> => ({
|
||||
slug: 'home',
|
||||
richText: [
|
||||
{
|
||||
@@ -41,11 +42,26 @@ export const home: Partial<Page> = {
|
||||
{
|
||||
text: ' you can edit this page in the admin panel and see the changes reflected here in real time.',
|
||||
},
|
||||
...(id
|
||||
? [
|
||||
{
|
||||
text: ' To get started, visit ',
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
children: [{ text: 'this page' }],
|
||||
linkType: 'custom',
|
||||
newTab: true,
|
||||
url: `/admin/collections/pages/${id}/preview`,
|
||||
},
|
||||
{ text: '.' },
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
],
|
||||
title: 'Home',
|
||||
}
|
||||
})
|
||||
|
||||
export const examplePage: Partial<Page> = {
|
||||
slug: 'example-page',
|
||||
@@ -83,11 +99,18 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
data: examplePage as any, // eslint-disable-line
|
||||
})
|
||||
|
||||
const homepageJSON = JSON.parse(JSON.stringify(home))
|
||||
|
||||
const { id: homePageID } = await payload.create({
|
||||
const { id: ogHomePageID } = await payload.create({
|
||||
collection: 'pages',
|
||||
data: homepageJSON,
|
||||
data: {
|
||||
title: 'Home',
|
||||
richText: [],
|
||||
},
|
||||
})
|
||||
|
||||
const { id: homePageID } = await payload.update({
|
||||
id: ogHomePageID,
|
||||
collection: 'pages',
|
||||
data: home(ogHomePageID),
|
||||
})
|
||||
|
||||
await payload.updateGlobal({
|
||||
@@ -121,7 +144,7 @@ export async function up({ payload }: MigrateUpArgs): Promise<void> {
|
||||
type: 'custom',
|
||||
label: 'Dashboard',
|
||||
reference: undefined,
|
||||
url: 'http://localhost:3000/admin',
|
||||
url: '/admin',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -6,10 +6,66 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Brisbane'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
pages: Page;
|
||||
users: User;
|
||||
|
||||
35
package.json
35
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -84,7 +84,7 @@
|
||||
"publish-prerelease": "pnpm --filter releaser publish-prerelease",
|
||||
"reinstall": "pnpm clean:all && pnpm install",
|
||||
"release": "pnpm --filter releaser release --tag latest",
|
||||
"runts": "cross-env NODE_OPTIONS=--no-deprecation node --no-deprecation --import @swc-node/register/esm-register",
|
||||
"runts": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" node --no-deprecation --no-experimental-strip-types --import @swc-node/register/esm-register",
|
||||
"script:build-template-with-local-pkgs": "pnpm --filter scripts build-template-with-local-pkgs",
|
||||
"script:gen-templates": "pnpm --filter scripts gen-templates",
|
||||
"script:gen-templates:build": "pnpm --filter scripts gen-templates --build",
|
||||
@@ -93,17 +93,19 @@
|
||||
"script:pack": "pnpm --filter scripts pack-all-to-dest",
|
||||
"pretest": "pnpm build",
|
||||
"test": "pnpm test:int && pnpm test:components && pnpm test:e2e",
|
||||
"test:components": "cross-env NODE_OPTIONS=\" --no-deprecation\" jest --config=jest.components.config.js",
|
||||
"test:components": "cross-env NODE_OPTIONS=\" --no-deprecation --no-experimental-strip-types\" jest --config=jest.components.config.js",
|
||||
"test:e2e": "pnpm runts ./test/runE2E.ts",
|
||||
"test:e2e:debug": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 PWDEBUG=1 DISABLE_LOGGING=true playwright test",
|
||||
"test:e2e:headed": "cross-env NODE_OPTIONS=--no-deprecation NODE_NO_WARNINGS=1 DISABLE_LOGGING=true playwright test --headed",
|
||||
"test:e2e:debug": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 PWDEBUG=1 DISABLE_LOGGING=true playwright test",
|
||||
"test:e2e:headed": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true playwright test --headed",
|
||||
"test:e2e:prod": "pnpm prepare-run-test-against-prod && pnpm runts ./test/runE2E.ts --prod",
|
||||
"test:e2e:prod:ci": "pnpm prepare-run-test-against-prod:ci && pnpm runts ./test/runE2E.ts --prod",
|
||||
"test:int": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:int:postgres": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:int:sqlite": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=sqlite DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:e2e:prod:ci:turbo": "pnpm prepare-run-test-against-prod:ci && pnpm runts ./test/runE2E.ts --prod --turbo",
|
||||
"test:e2e:turbo": "pnpm runts ./test/runE2E.ts --turbo",
|
||||
"test:int": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:int:postgres": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:int:sqlite": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=sqlite DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand",
|
||||
"test:types": "tstyche",
|
||||
"test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
|
||||
"test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation --no-experimental-strip-types\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
|
||||
"translateNewKeys": "pnpm --filter translations run translateNewKeys"
|
||||
},
|
||||
"lint-staged": {
|
||||
@@ -120,7 +122,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@libsql/client": "0.14.0",
|
||||
"@next/bundle-analyzer": "15.3.0",
|
||||
"@next/bundle-analyzer": "15.3.2",
|
||||
"@payloadcms/db-postgres": "workspace:*",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
@@ -128,9 +130,9 @@
|
||||
"@playwright/test": "1.50.0",
|
||||
"@sentry/nextjs": "^8.33.1",
|
||||
"@sentry/node": "^8.33.1",
|
||||
"@swc-node/register": "1.10.9",
|
||||
"@swc/cli": "0.6.0",
|
||||
"@swc/jest": "0.2.37",
|
||||
"@swc-node/register": "1.10.10",
|
||||
"@swc/cli": "0.7.7",
|
||||
"@swc/jest": "0.2.38",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/minimist": "1.2.5",
|
||||
@@ -145,8 +147,6 @@
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "16.4.7",
|
||||
"drizzle-kit": "0.28.0",
|
||||
"drizzle-orm": "0.36.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"execa": "5.1.1",
|
||||
"form-data": "3.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
@@ -156,7 +156,7 @@
|
||||
"lint-staged": "15.2.7",
|
||||
"minimist": "1.2.8",
|
||||
"mongodb-memory-server": "^10",
|
||||
"next": "15.3.0",
|
||||
"next": "15.3.2",
|
||||
"open": "^10.1.0",
|
||||
"p-limit": "^5.0.0",
|
||||
"playwright": "1.50.0",
|
||||
@@ -169,7 +169,7 @@
|
||||
"shelljs": "0.8.5",
|
||||
"slash": "3.0.0",
|
||||
"sort-package-json": "^2.10.0",
|
||||
"swc-plugin-transform-remove-imports": "3.1.0",
|
||||
"swc-plugin-transform-remove-imports": "4.0.4",
|
||||
"tempy": "1.0.1",
|
||||
"tstyche": "^3.1.1",
|
||||
"tsx": "4.19.2",
|
||||
@@ -186,7 +186,6 @@
|
||||
"copyfiles": "$copyfiles",
|
||||
"cross-env": "$cross-env",
|
||||
"dotenv": "$dotenv",
|
||||
"drizzle-orm": "$drizzle-orm",
|
||||
"graphql": "^16.8.1",
|
||||
"mongodb-memory-server": "$mongodb-memory-server",
|
||||
"react": "$react",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/admin-bar",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "An admin bar for React apps using Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,6 +16,7 @@
|
||||
"url": "https://payloadcms.com"
|
||||
}
|
||||
],
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./types": {
|
||||
@@ -60,7 +61,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^0.7.0",
|
||||
"@sindresorhus/slugify": "^1.1.0",
|
||||
"@swc/core": "1.10.12",
|
||||
"@swc/core": "1.11.29",
|
||||
"arg": "^5.0.0",
|
||||
"chalk": "^4.1.0",
|
||||
"comment-json": "^4.2.3",
|
||||
|
||||
@@ -16,12 +16,15 @@ export const configurePluginProject = ({
|
||||
const devPayloadConfigPath = path.resolve(projectDirPath, './dev/payload.config.ts')
|
||||
const devTsConfigPath = path.resolve(projectDirPath, './dev/tsconfig.json')
|
||||
const indexTsPath = path.resolve(projectDirPath, './src/index.ts')
|
||||
const devImportMapPath = path.resolve(projectDirPath, './dev/app/(payload)/admin/importMap.js')
|
||||
|
||||
const devPayloadConfig = fse.readFileSync(devPayloadConfigPath, 'utf8')
|
||||
const devTsConfig = fse.readFileSync(devTsConfigPath, 'utf8')
|
||||
const indexTs = fse.readFileSync(indexTsPath, 'utf-8')
|
||||
const devImportMap = fse.readFileSync(devImportMapPath, 'utf-8')
|
||||
|
||||
const updatedTsConfig = devTsConfig.replaceAll('plugin-package-name-placeholder', projectName)
|
||||
const updatedImportMap = devImportMap.replaceAll('plugin-package-name-placeholder', projectName)
|
||||
let updatedIndexTs = indexTs.replaceAll('plugin-package-name-placeholder', projectName)
|
||||
|
||||
const pluginExportVariableName = toCamelCase(projectName)
|
||||
@@ -43,4 +46,5 @@ export const configurePluginProject = ({
|
||||
fse.writeFileSync(devPayloadConfigPath, updatedPayloadConfig)
|
||||
fse.writeFileSync(devTsConfigPath, updatedTsConfig)
|
||||
fse.writeFileSync(indexTsPath, updatedIndexTs)
|
||||
fse.writeFileSync(devImportMapPath, updatedImportMap)
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import type { CliArgs, DbType, ProjectExample, ProjectTemplate } from '../types.
|
||||
import { createProject } from './create-project.js'
|
||||
import { dbReplacements } from './replacements.js'
|
||||
import { getValidTemplates } from './templates.js'
|
||||
import { manageEnvFiles } from './manage-env-files.js'
|
||||
|
||||
describe('createProject', () => {
|
||||
let projectDir: string
|
||||
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log = jest.fn()
|
||||
@@ -63,6 +63,30 @@ describe('createProject', () => {
|
||||
expect(packageJson.name).toStrictEqual(projectName)
|
||||
})
|
||||
|
||||
it('updates project name in plugin template importMap file', async () => {
|
||||
const projectName = 'my-custom-plugin'
|
||||
const template: ProjectTemplate = {
|
||||
name: 'plugin',
|
||||
type: 'plugin',
|
||||
description: 'Template for creating a Payload plugin',
|
||||
url: 'https://github.com/payloadcms/payload/templates/plugin',
|
||||
}
|
||||
|
||||
await createProject({
|
||||
cliArgs: { ...args, '--local-template': 'plugin' } as CliArgs,
|
||||
packageManager,
|
||||
projectDir,
|
||||
projectName,
|
||||
template,
|
||||
})
|
||||
|
||||
const importMapPath = path.resolve(projectDir, './dev/app/(payload)/admin/importMap.js')
|
||||
const importMapFile = fse.readFileSync(importMapPath, 'utf-8')
|
||||
|
||||
expect(importMapFile).not.toContain('plugin-package-name-placeholder')
|
||||
expect(importMapFile).toContain('my-custom-plugin')
|
||||
})
|
||||
|
||||
it('creates example', async () => {
|
||||
const projectName = 'custom-server-example'
|
||||
const example: ProjectExample = {
|
||||
@@ -155,75 +179,5 @@ describe('createProject', () => {
|
||||
expect(content).toContain(dbReplacement.configReplacement().join('\n'))
|
||||
})
|
||||
})
|
||||
describe('managing env files', () => {
|
||||
it('updates .env files without overwriting existing data', async () => {
|
||||
const envFilePath = path.join(projectDir, '.env')
|
||||
const envExampleFilePath = path.join(projectDir, '.env.example')
|
||||
|
||||
fse.ensureDirSync(projectDir)
|
||||
fse.ensureFileSync(envFilePath)
|
||||
fse.ensureFileSync(envExampleFilePath)
|
||||
|
||||
const initialEnvContent = `CUSTOM_VAR=custom-value\nDATABASE_URI=old-connection\n`
|
||||
const initialEnvExampleContent = `CUSTOM_VAR=custom-value\nDATABASE_URI=old-connection\nPAYLOAD_SECRET=YOUR_SECRET_HERE\n`
|
||||
|
||||
fse.writeFileSync(envFilePath, initialEnvContent)
|
||||
fse.writeFileSync(envExampleFilePath, initialEnvExampleContent)
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseType: 'mongodb',
|
||||
databaseUri: 'mongodb://localhost:27017/test',
|
||||
payloadSecret: 'test-secret',
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvContent).toContain('CUSTOM_VAR=custom-value')
|
||||
expect(updatedEnvContent).toContain('DATABASE_URI=mongodb://localhost:27017/test')
|
||||
expect(updatedEnvContent).toContain('PAYLOAD_SECRET=test-secret')
|
||||
|
||||
const updatedEnvExampleContent = fse.readFileSync(envExampleFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvExampleContent).toContain('CUSTOM_VAR=custom-value')
|
||||
expect(updatedEnvContent).toContain('DATABASE_URI=mongodb://localhost:27017/test')
|
||||
expect(updatedEnvContent).toContain('PAYLOAD_SECRET=test-secret')
|
||||
})
|
||||
|
||||
it('creates .env and .env.example if they do not exist', async () => {
|
||||
const envFilePath = path.join(projectDir, '.env')
|
||||
const envExampleFilePath = path.join(projectDir, '.env.example')
|
||||
|
||||
fse.ensureDirSync(projectDir)
|
||||
|
||||
if (fse.existsSync(envFilePath)) fse.removeSync(envFilePath)
|
||||
if (fse.existsSync(envExampleFilePath)) fse.removeSync(envExampleFilePath)
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseUri: '',
|
||||
payloadSecret: '',
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
expect(fse.existsSync(envFilePath)).toBe(true)
|
||||
expect(fse.existsSync(envExampleFilePath)).toBe(true)
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
expect(updatedEnvContent).toContain('DATABASE_URI=your-connection-string-here')
|
||||
expect(updatedEnvContent).toContain('PAYLOAD_SECRET=YOUR_SECRET_HERE')
|
||||
|
||||
const updatedEnvExampleContent = fse.readFileSync(envExampleFilePath, 'utf-8')
|
||||
expect(updatedEnvExampleContent).toContain('DATABASE_URI=your-connection-string-here')
|
||||
expect(updatedEnvExampleContent).toContain('PAYLOAD_SECRET=YOUR_SECRET_HERE')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -144,17 +144,14 @@ export async function createProject(
|
||||
}
|
||||
}
|
||||
|
||||
// Call manageEnvFiles before initializing Git
|
||||
if (dbDetails) {
|
||||
await manageEnvFiles({
|
||||
cliArgs,
|
||||
databaseType: dbDetails.type,
|
||||
databaseUri: dbDetails.dbUri,
|
||||
payloadSecret: generateSecret(),
|
||||
projectDir,
|
||||
template: 'template' in args ? args.template : undefined,
|
||||
})
|
||||
}
|
||||
await manageEnvFiles({
|
||||
cliArgs,
|
||||
databaseType: dbDetails?.type,
|
||||
databaseUri: dbDetails?.dbUri,
|
||||
payloadSecret: generateSecret(),
|
||||
projectDir,
|
||||
template: 'template' in args ? args.template : undefined,
|
||||
})
|
||||
|
||||
// Remove yarn.lock file. This is only desired in Payload Cloud.
|
||||
const lockPath = path.resolve(projectDir, 'pnpm-lock.yaml')
|
||||
|
||||
165
packages/create-payload-app/src/lib/manage-env-files.spec.ts
Normal file
165
packages/create-payload-app/src/lib/manage-env-files.spec.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import fs from 'fs'
|
||||
import fse from 'fs-extra'
|
||||
import * as os from 'node:os'
|
||||
import path from 'path'
|
||||
|
||||
import type { CliArgs } from '../types.js'
|
||||
|
||||
import { manageEnvFiles } from './manage-env-files.js'
|
||||
|
||||
describe('createProject', () => {
|
||||
let projectDir: string
|
||||
let envFilePath = ''
|
||||
let envExampleFilePath = ''
|
||||
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log = jest.fn()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const tempDirectory = fs.realpathSync(os.tmpdir())
|
||||
projectDir = `${tempDirectory}/${Math.random().toString(36).substring(7)}`
|
||||
|
||||
envFilePath = path.join(projectDir, '.env')
|
||||
envExampleFilePath = path.join(projectDir, '.env.example')
|
||||
|
||||
if (fse.existsSync(envFilePath)) {
|
||||
fse.removeSync(envFilePath)
|
||||
}
|
||||
|
||||
fse.ensureDirSync(projectDir)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
if (fse.existsSync(projectDir)) {
|
||||
fse.rmSync(projectDir, { recursive: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('generates .env using defaults (not from .env.example)', async () => {
|
||||
// ensure no .env.example exists so that the default values are used
|
||||
// the `manageEnvFiles` function will look for .env.example in the file system
|
||||
if (fse.existsSync(envExampleFilePath)) {
|
||||
fse.removeSync(envExampleFilePath)
|
||||
}
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseUri: '', // omitting this will ensure the default vars are used
|
||||
payloadSecret: '', // omitting this will ensure the default vars are used
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
expect(fse.existsSync(envFilePath)).toBe(true)
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvContent).toBe(
|
||||
`# Added by Payload\nPAYLOAD_SECRET=YOUR_SECRET_HERE\nDATABASE_URI=your-connection-string-here`,
|
||||
)
|
||||
})
|
||||
|
||||
it('generates .env from .env.example', async () => {
|
||||
// create or override the .env.example file with a connection string that will NOT be overridden
|
||||
fse.ensureFileSync(envExampleFilePath)
|
||||
fse.writeFileSync(
|
||||
envExampleFilePath,
|
||||
`DATABASE_URI=example-connection-string\nCUSTOM_VAR=custom-value\n`,
|
||||
)
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseUri: '', // omitting this will ensure the `.env.example` vars are used
|
||||
payloadSecret: '', // omitting this will ensure the `.env.example` vars are used
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
expect(fse.existsSync(envFilePath)).toBe(true)
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvContent).toBe(
|
||||
`DATABASE_URI=example-connection-string\nCUSTOM_VAR=custom-value\nPAYLOAD_SECRET=YOUR_SECRET_HERE\n# Added by Payload`,
|
||||
)
|
||||
})
|
||||
|
||||
it('updates existing .env without overriding vars', async () => {
|
||||
// create an existing .env file with some custom variables that should NOT be overridden
|
||||
fse.ensureFileSync(envFilePath)
|
||||
fse.writeFileSync(
|
||||
envFilePath,
|
||||
`CUSTOM_VAR=custom-value\nDATABASE_URI=example-connection-string\n`,
|
||||
)
|
||||
|
||||
// create an .env.example file to ensure that its contents DO NOT override existing .env vars
|
||||
fse.ensureFileSync(envExampleFilePath)
|
||||
fse.writeFileSync(
|
||||
envExampleFilePath,
|
||||
`CUSTOM_VAR=custom-value-2\nDATABASE_URI=example-connection-string-2\n`,
|
||||
)
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseUri: '', // omitting this will ensure the `.env` vars are kept
|
||||
payloadSecret: '', // omitting this will ensure the `.env` vars are kept
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
expect(fse.existsSync(envFilePath)).toBe(true)
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvContent).toBe(
|
||||
`# Added by Payload\nPAYLOAD_SECRET=YOUR_SECRET_HERE\nDATABASE_URI=example-connection-string\nCUSTOM_VAR=custom-value`,
|
||||
)
|
||||
})
|
||||
|
||||
it('sanitizes .env based on selected database type', async () => {
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseType: 'mongodb', // this mimics the CLI selection and will be used as the DATABASE_URI
|
||||
databaseUri: 'mongodb://localhost:27017/test', // this mimics the CLI selection and will be used as the DATABASE_URI
|
||||
payloadSecret: 'test-secret', // this mimics the CLI selection and will be used as the PAYLOAD_SECRET
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
const updatedEnvContent = fse.readFileSync(envFilePath, 'utf-8')
|
||||
|
||||
expect(updatedEnvContent).toBe(
|
||||
`# Added by Payload\nPAYLOAD_SECRET=test-secret\nDATABASE_URI=mongodb://localhost:27017/test`,
|
||||
)
|
||||
|
||||
// delete the generated .env file and do it again, but this time, omit the databaseUri to ensure the default is generated
|
||||
fse.removeSync(envFilePath)
|
||||
|
||||
await manageEnvFiles({
|
||||
cliArgs: {
|
||||
'--debug': true,
|
||||
} as CliArgs,
|
||||
databaseType: 'mongodb', // this mimics the CLI selection and will be used as the DATABASE_URI
|
||||
databaseUri: '', // omit this to ensure the default is generated based on the selected database type
|
||||
payloadSecret: 'test-secret',
|
||||
projectDir,
|
||||
template: undefined,
|
||||
})
|
||||
|
||||
const updatedEnvContentWithDefault = fse.readFileSync(envFilePath, 'utf-8')
|
||||
expect(updatedEnvContentWithDefault).toBe(
|
||||
`# Added by Payload\nPAYLOAD_SECRET=test-secret\nDATABASE_URI=mongodb://127.0.0.1/your-database-name`,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -6,21 +6,42 @@ import type { CliArgs, DbType, ProjectTemplate } from '../types.js'
|
||||
import { debug, error } from '../utils/log.js'
|
||||
import { dbChoiceRecord } from './select-db.js'
|
||||
|
||||
const updateEnvExampleVariables = (
|
||||
contents: string,
|
||||
databaseType: DbType | undefined,
|
||||
payloadSecret?: string,
|
||||
databaseUri?: string,
|
||||
): string => {
|
||||
const sanitizeEnv = ({
|
||||
contents,
|
||||
databaseType,
|
||||
databaseUri,
|
||||
payloadSecret,
|
||||
}: {
|
||||
contents: string
|
||||
databaseType: DbType | undefined
|
||||
databaseUri?: string
|
||||
payloadSecret?: string
|
||||
}): string => {
|
||||
const seenKeys = new Set<string>()
|
||||
const updatedEnv = contents
|
||||
|
||||
// add defaults
|
||||
let withDefaults = contents
|
||||
|
||||
if (
|
||||
!contents.includes('DATABASE_URI') &&
|
||||
!contents.includes('POSTGRES_URL') &&
|
||||
!contents.includes('MONGODB_URI')
|
||||
) {
|
||||
withDefaults += '\nDATABASE_URI=your-connection-string-here'
|
||||
}
|
||||
|
||||
if (!contents.includes('PAYLOAD_SECRET')) {
|
||||
withDefaults += '\nPAYLOAD_SECRET=YOUR_SECRET_HERE'
|
||||
}
|
||||
|
||||
let updatedEnv = withDefaults
|
||||
.split('\n')
|
||||
.map((line) => {
|
||||
if (line.startsWith('#') || !line.includes('=')) {
|
||||
return line
|
||||
}
|
||||
|
||||
const [key] = line.split('=')
|
||||
const [key, value] = line.split('=')
|
||||
|
||||
if (!key) {
|
||||
return
|
||||
@@ -28,6 +49,7 @@ const updateEnvExampleVariables = (
|
||||
|
||||
if (key === 'DATABASE_URI' || key === 'POSTGRES_URL' || key === 'MONGODB_URI') {
|
||||
const dbChoice = databaseType ? dbChoiceRecord[databaseType] : null
|
||||
|
||||
if (dbChoice) {
|
||||
const placeholderUri = databaseUri
|
||||
? databaseUri
|
||||
@@ -36,6 +58,8 @@ const updateEnvExampleVariables = (
|
||||
databaseType === 'vercel-postgres'
|
||||
? `POSTGRES_URL=${placeholderUri}`
|
||||
: `DATABASE_URI=${placeholderUri}`
|
||||
} else {
|
||||
line = `${key}=${value}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +80,10 @@ const updateEnvExampleVariables = (
|
||||
.reverse()
|
||||
.join('\n')
|
||||
|
||||
if (!updatedEnv.includes('# Added by Payload')) {
|
||||
updatedEnv = `# Added by Payload\n${updatedEnv}`
|
||||
}
|
||||
|
||||
return updatedEnv
|
||||
}
|
||||
|
||||
@@ -63,7 +91,7 @@ const updateEnvExampleVariables = (
|
||||
export async function manageEnvFiles(args: {
|
||||
cliArgs: CliArgs
|
||||
databaseType?: DbType
|
||||
databaseUri: string
|
||||
databaseUri?: string
|
||||
payloadSecret: string
|
||||
projectDir: string
|
||||
template?: ProjectTemplate
|
||||
@@ -77,70 +105,63 @@ export async function manageEnvFiles(args: {
|
||||
return
|
||||
}
|
||||
|
||||
const envExamplePath = path.join(projectDir, '.env.example')
|
||||
const pathToEnvExample = path.join(projectDir, '.env.example')
|
||||
const envPath = path.join(projectDir, '.env')
|
||||
const emptyEnvContent = `# Added by Payload\nDATABASE_URI=your-connection-string-here\nPAYLOAD_SECRET=YOUR_SECRET_HERE\n`
|
||||
try {
|
||||
let updatedExampleContents: string
|
||||
|
||||
let exampleEnv: null | string = ''
|
||||
|
||||
try {
|
||||
if (template?.type === 'plugin') {
|
||||
if (debugFlag) {
|
||||
debug(`plugin template detected - no .env added .env.example added`)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!fs.existsSync(envExamplePath)) {
|
||||
updatedExampleContents = updateEnvExampleVariables(
|
||||
emptyEnvContent,
|
||||
databaseType,
|
||||
payloadSecret,
|
||||
databaseUri,
|
||||
)
|
||||
// If there's a .env.example file, use it to create or update the .env file
|
||||
if (fs.existsSync(pathToEnvExample)) {
|
||||
const envExampleContents = await fs.readFile(pathToEnvExample, 'utf8')
|
||||
|
||||
await fs.writeFile(envExamplePath, updatedExampleContents)
|
||||
if (debugFlag) {
|
||||
debug(`.env.example file successfully created`)
|
||||
}
|
||||
} else {
|
||||
const envExampleContents = await fs.readFile(envExamplePath, 'utf8')
|
||||
const mergedEnvs = envExampleContents + '\n' + emptyEnvContent
|
||||
updatedExampleContents = updateEnvExampleVariables(
|
||||
mergedEnvs,
|
||||
exampleEnv = sanitizeEnv({
|
||||
contents: envExampleContents,
|
||||
databaseType,
|
||||
payloadSecret,
|
||||
databaseUri,
|
||||
)
|
||||
payloadSecret,
|
||||
})
|
||||
|
||||
await fs.writeFile(envExamplePath, updatedExampleContents)
|
||||
if (debugFlag) {
|
||||
debug(`.env.example file successfully updated`)
|
||||
debug(`.env.example file successfully read`)
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no .env file, create it using the .env.example content (if it exists)
|
||||
if (!fs.existsSync(envPath)) {
|
||||
const envContent = updateEnvExampleVariables(
|
||||
emptyEnvContent,
|
||||
const envContent = sanitizeEnv({
|
||||
contents: exampleEnv,
|
||||
databaseType,
|
||||
payloadSecret,
|
||||
databaseUri,
|
||||
)
|
||||
payloadSecret,
|
||||
})
|
||||
|
||||
await fs.writeFile(envPath, envContent)
|
||||
|
||||
if (debugFlag) {
|
||||
debug(`.env file successfully created`)
|
||||
}
|
||||
} else {
|
||||
// If the .env file already exists, sanitize it as-is
|
||||
const envContents = await fs.readFile(envPath, 'utf8')
|
||||
const mergedEnvs = envContents + '\n' + emptyEnvContent
|
||||
const updatedEnvContents = updateEnvExampleVariables(
|
||||
mergedEnvs,
|
||||
|
||||
const updatedEnvContents = sanitizeEnv({
|
||||
contents: envContents,
|
||||
databaseType,
|
||||
payloadSecret,
|
||||
databaseUri,
|
||||
)
|
||||
payloadSecret,
|
||||
})
|
||||
|
||||
await fs.writeFile(envPath, updatedEnvContents)
|
||||
|
||||
if (debugFlag) {
|
||||
debug(`.env file successfully updated`)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -417,7 +417,7 @@ export const sanitizeQueryValue = ({
|
||||
return buildExistsQuery(
|
||||
formattedValue,
|
||||
path,
|
||||
!['relationship', 'upload'].includes(field.type),
|
||||
!['checkbox', 'relationship', 'upload'].includes(field.type),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -17,6 +17,7 @@
|
||||
"url": "https://payloadcms.com"
|
||||
}
|
||||
],
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -85,10 +86,10 @@
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hyrious/esbuild-plugin-commonjs": "^0.2.4",
|
||||
"@hyrious/esbuild-plugin-commonjs": "0.2.6",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/to-snake-case": "1.0.0",
|
||||
"esbuild": "0.24.2",
|
||||
"esbuild": "0.25.5",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -51,13 +51,6 @@ export const connect: Connect = async function connect(
|
||||
) {
|
||||
const { hotReload } = options
|
||||
|
||||
this.schema = {
|
||||
pgSchema: this.pgSchema,
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
...this.enums,
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.pool) {
|
||||
this.pool = new this.pg.Pool(this.poolOptions)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -15,11 +15,6 @@ export const connect: Connect = async function connect(
|
||||
) {
|
||||
const { hotReload } = options
|
||||
|
||||
this.schema = {
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.client) {
|
||||
this.client = createClient(this.clientConfig)
|
||||
|
||||
@@ -36,4 +36,9 @@ export const init: Init = async function init(this: SQLiteAdapter) {
|
||||
})
|
||||
|
||||
await executeSchemaHooks({ type: 'afterSchemaInit', adapter: this })
|
||||
|
||||
this.schema = {
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -17,6 +17,7 @@
|
||||
"url": "https://payloadcms.com"
|
||||
}
|
||||
],
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -85,11 +86,11 @@
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hyrious/esbuild-plugin-commonjs": "^0.2.4",
|
||||
"@hyrious/esbuild-plugin-commonjs": "0.2.6",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/pg": "8.10.2",
|
||||
"@types/to-snake-case": "1.0.0",
|
||||
"esbuild": "0.24.2",
|
||||
"esbuild": "0.25.5",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -16,13 +16,6 @@ export const connect: Connect = async function connect(
|
||||
) {
|
||||
const { hotReload } = options
|
||||
|
||||
this.schema = {
|
||||
pgSchema: this.pgSchema,
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
...this.enums,
|
||||
}
|
||||
|
||||
try {
|
||||
const logger = this.logger || false
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -35,4 +35,11 @@ export const init: Init = async function init(this: BasePostgresAdapter) {
|
||||
})
|
||||
|
||||
await executeSchemaHooks({ type: 'afterSchemaInit', adapter: this })
|
||||
|
||||
this.schema = {
|
||||
pgSchema: this.pgSchema,
|
||||
...this.tables,
|
||||
...this.relations,
|
||||
...this.enums,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +367,25 @@ export function parseParams({
|
||||
break
|
||||
}
|
||||
|
||||
constraints.push(adapter.operators[queryOperator](resolvedColumn, queryValue))
|
||||
const orConditions: SQL<unknown>[] = []
|
||||
let resolvedQueryValue = queryValue
|
||||
if (
|
||||
operator === 'in' &&
|
||||
Array.isArray(queryValue) &&
|
||||
queryValue.some((v) => v === null)
|
||||
) {
|
||||
orConditions.push(isNull(resolvedColumn))
|
||||
resolvedQueryValue = queryValue.filter((v) => v !== null)
|
||||
}
|
||||
let constraint = adapter.operators[queryOperator](
|
||||
resolvedColumn,
|
||||
resolvedQueryValue,
|
||||
)
|
||||
if (orConditions.length) {
|
||||
orConditions.push(constraint)
|
||||
constraint = or(...orConditions)
|
||||
}
|
||||
constraints.push(constraint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,6 @@ export const withDefault = (column: RawColumn, field: FieldAffectingData): RawCo
|
||||
return column
|
||||
}
|
||||
|
||||
if (typeof field.defaultValue === 'string' && field.defaultValue.includes("'")) {
|
||||
const escapedString = field.defaultValue.replaceAll("'", "''")
|
||||
return {
|
||||
...column,
|
||||
default: escapedString,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...column,
|
||||
default: field.defaultValue,
|
||||
|
||||
@@ -98,6 +98,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
withinArrayOrBlockLocale,
|
||||
}: TraverseFieldsArgs): T => {
|
||||
const sanitizedPath = path ? `${path}.` : path
|
||||
const localeCodes =
|
||||
adapter.payload.config.localization && adapter.payload.config.localization.localeCodes
|
||||
|
||||
const formatted = fields.reduce((result, field) => {
|
||||
if (fieldIsVirtual(field)) {
|
||||
@@ -506,6 +508,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
if (field.type === 'text' && field?.hasMany) {
|
||||
const textPathMatch = texts[`${sanitizedPath}${field.name}`]
|
||||
if (!textPathMatch) {
|
||||
result[field.name] =
|
||||
isLocalized && localeCodes
|
||||
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
|
||||
: []
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -545,6 +551,10 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
if (field.type === 'number' && field.hasMany) {
|
||||
const numberPathMatch = numbers[`${sanitizedPath}${field.name}`]
|
||||
if (!numberPathMatch) {
|
||||
result[field.name] =
|
||||
isLocalized && localeCodes
|
||||
? Object.fromEntries(localeCodes.map((locale) => [locale, []]))
|
||||
: []
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -606,10 +616,8 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
if (isLocalized && Array.isArray(table._locales)) {
|
||||
if (!table._locales.length && adapter.payload.config.localization) {
|
||||
adapter.payload.config.localization.localeCodes.forEach((_locale) =>
|
||||
(table._locales as unknown[]).push({ _locale }),
|
||||
)
|
||||
if (!table._locales.length && localeCodes) {
|
||||
localeCodes.forEach((_locale) => (table._locales as unknown[]).push({ _locale }))
|
||||
}
|
||||
|
||||
table._locales.forEach((localeRow) => {
|
||||
@@ -725,8 +733,6 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
return result
|
||||
}, dataRef)
|
||||
|
||||
if (Array.isArray(table._locales)) {
|
||||
|
||||
@@ -3,7 +3,13 @@ import type { FlattenedArrayField } from 'payload'
|
||||
import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js'
|
||||
import type {
|
||||
ArrayRowToInsert,
|
||||
BlockRowToInsert,
|
||||
NumberToDelete,
|
||||
RelationshipToDelete,
|
||||
TextToDelete,
|
||||
} from './types.js'
|
||||
|
||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -20,6 +26,7 @@ type Args = {
|
||||
field: FlattenedArrayField
|
||||
locale?: string
|
||||
numbers: Record<string, unknown>[]
|
||||
numbersToDelete: NumberToDelete[]
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
relationships: Record<string, unknown>[]
|
||||
@@ -28,6 +35,7 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
textsToDelete: TextToDelete[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
@@ -45,12 +53,14 @@ export const transformArray = ({
|
||||
field,
|
||||
locale,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
const newRows: ArrayRowToInsert[] = []
|
||||
@@ -104,6 +114,7 @@ export const transformArray = ({
|
||||
insideArrayOrBlock: true,
|
||||
locales: newRow.locales,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName: arrayTableName,
|
||||
path: `${path || ''}${field.name}.${i}.`,
|
||||
@@ -112,6 +123,7 @@ export const transformArray = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,12 @@ import { fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { BlockRowToInsert, RelationshipToDelete } from './types.js'
|
||||
import type {
|
||||
BlockRowToInsert,
|
||||
NumberToDelete,
|
||||
RelationshipToDelete,
|
||||
TextToDelete,
|
||||
} from './types.js'
|
||||
|
||||
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||
import { traverseFields } from './traverseFields.js'
|
||||
@@ -20,6 +25,7 @@ type Args = {
|
||||
field: FlattenedBlocksField
|
||||
locale?: string
|
||||
numbers: Record<string, unknown>[]
|
||||
numbersToDelete: NumberToDelete[]
|
||||
parentIsLocalized: boolean
|
||||
path: string
|
||||
relationships: Record<string, unknown>[]
|
||||
@@ -28,6 +34,7 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
textsToDelete: TextToDelete[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
@@ -43,12 +50,14 @@ export const transformBlocks = ({
|
||||
field,
|
||||
locale,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
data.forEach((blockRow, i) => {
|
||||
@@ -117,6 +126,7 @@ export const transformBlocks = ({
|
||||
insideArrayOrBlock: true,
|
||||
locales: newRow.locales,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName: blockTableName,
|
||||
path: `${path || ''}${field.name}.${i}.`,
|
||||
@@ -125,6 +135,7 @@ export const transformBlocks = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
|
||||
@@ -29,11 +29,13 @@ export const transformForWrite = ({
|
||||
blocksToDelete: new Set(),
|
||||
locales: {},
|
||||
numbers: [],
|
||||
numbersToDelete: [],
|
||||
relationships: [],
|
||||
relationshipsToDelete: [],
|
||||
row: {},
|
||||
selects: {},
|
||||
texts: [],
|
||||
textsToDelete: [],
|
||||
}
|
||||
|
||||
// This function is responsible for building up the
|
||||
@@ -50,6 +52,7 @@ export const transformForWrite = ({
|
||||
fields,
|
||||
locales: rowToInsert.locales,
|
||||
numbers: rowToInsert.numbers,
|
||||
numbersToDelete: rowToInsert.numbersToDelete,
|
||||
parentIsLocalized,
|
||||
parentTableName: tableName,
|
||||
path,
|
||||
@@ -58,6 +61,7 @@ export const transformForWrite = ({
|
||||
row: rowToInsert.row,
|
||||
selects: rowToInsert.selects,
|
||||
texts: rowToInsert.texts,
|
||||
textsToDelete: rowToInsert.textsToDelete,
|
||||
})
|
||||
|
||||
return rowToInsert
|
||||
|
||||
@@ -5,7 +5,13 @@ import { fieldIsVirtual, fieldShouldBeLocalized } from 'payload/shared'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
import type { ArrayRowToInsert, BlockRowToInsert, RelationshipToDelete } from './types.js'
|
||||
import type {
|
||||
ArrayRowToInsert,
|
||||
BlockRowToInsert,
|
||||
NumberToDelete,
|
||||
RelationshipToDelete,
|
||||
TextToDelete,
|
||||
} from './types.js'
|
||||
|
||||
import { isArrayOfRows } from '../../utilities/isArrayOfRows.js'
|
||||
import { resolveBlockTableName } from '../../utilities/validateExistingBlockIsIdentical.js'
|
||||
@@ -51,6 +57,7 @@ type Args = {
|
||||
[locale: string]: Record<string, unknown>
|
||||
}
|
||||
numbers: Record<string, unknown>[]
|
||||
numbersToDelete: NumberToDelete[]
|
||||
parentIsLocalized: boolean
|
||||
/**
|
||||
* This is the name of the parent table
|
||||
@@ -64,6 +71,7 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
textsToDelete: TextToDelete[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
@@ -86,6 +94,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock = false,
|
||||
locales,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized,
|
||||
parentTableName,
|
||||
path,
|
||||
@@ -94,6 +103,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
if (row._uuid) {
|
||||
@@ -136,12 +146,14 @@ export const traverseFields = ({
|
||||
field,
|
||||
locale: localeKey,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
|
||||
@@ -159,12 +171,14 @@ export const traverseFields = ({
|
||||
data: data[field.name],
|
||||
field,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
@@ -202,12 +216,14 @@ export const traverseFields = ({
|
||||
field,
|
||||
locale: localeKey,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
}
|
||||
@@ -222,12 +238,14 @@ export const traverseFields = ({
|
||||
data: fieldData,
|
||||
field,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
path,
|
||||
relationships,
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
@@ -257,6 +275,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock,
|
||||
locales,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName,
|
||||
path: `${path || ''}${field.name}.`,
|
||||
@@ -265,6 +284,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
})
|
||||
@@ -287,6 +307,7 @@ export const traverseFields = ({
|
||||
insideArrayOrBlock,
|
||||
locales,
|
||||
numbers,
|
||||
numbersToDelete,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
parentTableName,
|
||||
path: `${path || ''}${field.name}.`,
|
||||
@@ -295,6 +316,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
textsToDelete,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
@@ -380,6 +402,11 @@ export const traverseFields = ({
|
||||
if (typeof fieldData === 'object') {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
if (!localeData.length) {
|
||||
textsToDelete.push({ locale: localeKey, path: textPath })
|
||||
return
|
||||
}
|
||||
|
||||
transformTexts({
|
||||
baseRow: {
|
||||
locale: localeKey,
|
||||
@@ -392,6 +419,11 @@ export const traverseFields = ({
|
||||
})
|
||||
}
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
if (!fieldData.length) {
|
||||
textsToDelete.push({ locale: withinArrayOrBlockLocale, path: textPath })
|
||||
return
|
||||
}
|
||||
|
||||
transformTexts({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
@@ -412,6 +444,11 @@ export const traverseFields = ({
|
||||
if (typeof fieldData === 'object') {
|
||||
Object.entries(fieldData).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
if (!localeData.length) {
|
||||
numbersToDelete.push({ locale: localeKey, path: numberPath })
|
||||
return
|
||||
}
|
||||
|
||||
transformNumbers({
|
||||
baseRow: {
|
||||
locale: localeKey,
|
||||
@@ -424,6 +461,11 @@ export const traverseFields = ({
|
||||
})
|
||||
}
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
if (!fieldData.length) {
|
||||
numbersToDelete.push({ locale: withinArrayOrBlockLocale, path: numberPath })
|
||||
return
|
||||
}
|
||||
|
||||
transformNumbers({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
|
||||
@@ -23,6 +23,16 @@ export type RelationshipToDelete = {
|
||||
path: string
|
||||
}
|
||||
|
||||
export type TextToDelete = {
|
||||
locale?: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type NumberToDelete = {
|
||||
locale?: string
|
||||
path: string
|
||||
}
|
||||
|
||||
export type RowToInsert = {
|
||||
arrays: {
|
||||
[tableName: string]: ArrayRowToInsert[]
|
||||
@@ -35,6 +45,7 @@ export type RowToInsert = {
|
||||
[locale: string]: Record<string, unknown>
|
||||
}
|
||||
numbers: Record<string, unknown>[]
|
||||
numbersToDelete: NumberToDelete[]
|
||||
relationships: Record<string, unknown>[]
|
||||
relationshipsToDelete: RelationshipToDelete[]
|
||||
row: Record<string, unknown>
|
||||
@@ -42,4 +53,5 @@ export type RowToInsert = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
textsToDelete: TextToDelete[]
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
parentColumnName: 'parent',
|
||||
parentID: insertedRow.id,
|
||||
pathColumnName: 'path',
|
||||
rows: textsToInsert,
|
||||
rows: [...textsToInsert, ...rowToInsert.textsToDelete],
|
||||
tableName: textsTableName,
|
||||
})
|
||||
}
|
||||
@@ -238,7 +238,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
|
||||
parentColumnName: 'parent',
|
||||
parentID: insertedRow.id,
|
||||
pathColumnName: 'path',
|
||||
rows: numbersToInsert,
|
||||
rows: [...numbersToInsert, ...rowToInsert.numbersToDelete],
|
||||
tableName: numbersTableName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"eslint-plugin-jest-dom": "5.5.0",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-perfectionist": "3.9.1",
|
||||
"eslint-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
|
||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "0.0.0-experimental-d331ba04-20250307",
|
||||
"eslint-plugin-regexp": "2.7.0",
|
||||
"globals": "16.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -17,6 +17,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -2,18 +2,27 @@
|
||||
import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
// To prevent the flicker of missing data on initial load,
|
||||
// you can pass in the initial page data from the server
|
||||
// To prevent the flicker of stale data while the post message is being sent,
|
||||
// you can conditionally render loading UI based on the `isLoading` state
|
||||
|
||||
export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
/**
|
||||
* This is a React hook to implement {@link https://payloadcms.com/docs/live-preview/overview Payload Live Preview}.
|
||||
*
|
||||
* @link https://payloadcms.com/docs/live-preview/frontend
|
||||
*/
|
||||
// NOTE: cannot use Record<string, unknown> here bc generated interfaces will not satisfy the type constraint
|
||||
export const useLivePreview = <T extends Record<string, any>>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
/**
|
||||
* To prevent the flicker of missing data on initial load,
|
||||
* you can pass in the initial page data from the server.
|
||||
*/
|
||||
initialData: T
|
||||
serverURL: string
|
||||
}): {
|
||||
data: T
|
||||
/**
|
||||
* To prevent the flicker of stale data while the post message is being sent,
|
||||
* you can conditionally render loading UI based on the `isLoading` state.
|
||||
*/
|
||||
isLoading: boolean
|
||||
} => {
|
||||
const { apiRoute, depth, initialData, serverURL } = props
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -4,17 +4,25 @@ import { ready, subscribe, unsubscribe } from '@payloadcms/live-preview'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
/**
|
||||
* Vue composable to implement Payload CMS Live Preview.
|
||||
* This is a Vue composable to implement {@link https://payloadcms.com/docs/live-preview/overview Payload Live Preview}.
|
||||
*
|
||||
* {@link https://payloadcms.com/docs/live-preview/frontend View the documentation}
|
||||
* @link https://payloadcms.com/docs/live-preview/frontend
|
||||
*/
|
||||
export const useLivePreview = <T extends Record<string, unknown>>(props: {
|
||||
export const useLivePreview = <T extends Record<string, any>>(props: {
|
||||
apiRoute?: string
|
||||
depth?: number
|
||||
/**
|
||||
* To prevent the flicker of missing data on initial load,
|
||||
* you can pass in the initial page data from the server.
|
||||
*/
|
||||
initialData: T
|
||||
serverURL: string
|
||||
}): {
|
||||
data: Ref<T>
|
||||
/**
|
||||
* To prevent the flicker of stale data while the post message is being sent,
|
||||
* you can conditionally render loading UI based on the `isLoading` state.
|
||||
*/
|
||||
isLoading: Ref<boolean>
|
||||
} => {
|
||||
const { apiRoute, depth, initialData, serverURL } = props
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -18,6 +18,7 @@
|
||||
}
|
||||
],
|
||||
"type": "module",
|
||||
"sideEffects": false,
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./src/index.ts",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.39.1",
|
||||
"version": "3.40.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -16,6 +16,10 @@
|
||||
"url": "https://payloadcms.com"
|
||||
}
|
||||
],
|
||||
"sideEffects": [
|
||||
"*.scss",
|
||||
"*.css"
|
||||
],
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
@@ -104,22 +108,22 @@
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "7.26.4",
|
||||
"@babel/core": "7.26.7",
|
||||
"@babel/preset-env": "7.26.7",
|
||||
"@babel/preset-react": "7.26.3",
|
||||
"@babel/preset-typescript": "7.26.0",
|
||||
"@next/eslint-plugin-next": "15.3.0",
|
||||
"@babel/cli": "7.27.2",
|
||||
"@babel/core": "7.27.3",
|
||||
"@babel/preset-env": "7.27.2",
|
||||
"@babel/preset-react": "7.27.1",
|
||||
"@babel/preset-typescript": "7.27.1",
|
||||
"@next/eslint-plugin-next": "15.3.2",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@types/busboy": "1.5.4",
|
||||
"@types/react": "19.1.0",
|
||||
"@types/react-dom": "19.1.2",
|
||||
"@types/uuid": "10.0.0",
|
||||
"babel-plugin-react-compiler": "19.0.0-beta-e993439-20250405",
|
||||
"esbuild": "0.24.2",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"esbuild": "0.25.5",
|
||||
"esbuild-sass-plugin": "3.3.1",
|
||||
"payload": "workspace:*",
|
||||
"swc-plugin-transform-remove-imports": "3.1.0"
|
||||
"swc-plugin-transform-remove-imports": "4.0.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"graphql": "^16.8.1",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.doc-tab {
|
||||
display: flex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.doc-tabs {
|
||||
|
||||
@@ -62,11 +62,31 @@ export const DocumentTabs: React.FC<{
|
||||
(condition &&
|
||||
Boolean(condition({ collectionConfig, config, globalConfig, permissions })))
|
||||
|
||||
const path = viewConfig && 'path' in viewConfig ? viewConfig.path : ''
|
||||
|
||||
if (meetsCondition) {
|
||||
if (tabFromConfig?.Component) {
|
||||
return RenderServerComponent({
|
||||
clientProps: {
|
||||
path,
|
||||
} satisfies DocumentTabClientProps,
|
||||
Component: tabFromConfig.Component,
|
||||
importMap: payload.importMap,
|
||||
key: `tab-${index}`,
|
||||
serverProps: {
|
||||
collectionConfig,
|
||||
globalConfig,
|
||||
i18n,
|
||||
payload,
|
||||
permissions,
|
||||
} satisfies DocumentTabServerPropsOnly,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentTab
|
||||
key={`tab-${index}`}
|
||||
path={viewConfig && 'path' in viewConfig ? viewConfig.path : ''}
|
||||
path={path}
|
||||
{...{
|
||||
...props,
|
||||
...(tab || {}),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.doc-header {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.nav {
|
||||
|
||||
@@ -23,20 +23,11 @@ export const DefaultNavClient: React.FC<{
|
||||
admin: {
|
||||
routes: { browseByFolder: foldersRoute },
|
||||
},
|
||||
collections,
|
||||
folders,
|
||||
routes: { admin: adminRoute },
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const [folderCollectionSlugs] = React.useState<string[]>(() => {
|
||||
return collections.reduce<string[]>((acc, collection) => {
|
||||
if (collection.folders) {
|
||||
acc.push(collection.slug)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
})
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const folderURL = formatAdminURL({
|
||||
@@ -48,7 +39,7 @@ export const DefaultNavClient: React.FC<{
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{folderCollectionSlugs.length > 0 && <BrowseByFolderButton active={viewingRootFolderView} />}
|
||||
{folders && folders.browseByFolder && <BrowseByFolderButton active={viewingRootFolderView} />}
|
||||
{groups.map(({ entities, label }, key) => {
|
||||
return (
|
||||
<NavGroup isOpen={navPreferences?.groups?.[label]?.open} key={key} label={label}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.nav {
|
||||
|
||||
@@ -99,6 +99,7 @@ export const POST =
|
||||
(config: Promise<SanitizedConfig> | SanitizedConfig) => async (request: Request) => {
|
||||
const originalRequest = request.clone()
|
||||
const req = await createPayloadRequest({
|
||||
canSetHeaders: true,
|
||||
config,
|
||||
request,
|
||||
})
|
||||
|
||||
@@ -1,207 +0,0 @@
|
||||
@layer payload-default, payload;
|
||||
|
||||
@import 'styles';
|
||||
@import './toasts.scss';
|
||||
@import './colors.scss';
|
||||
|
||||
@layer payload-default {
|
||||
:root {
|
||||
--base-px: 20;
|
||||
--base-body-size: 13;
|
||||
--base: calc((var(--base-px) / var(--base-body-size)) * 1rem);
|
||||
|
||||
--breakpoint-xs-width: #{$breakpoint-xs-width};
|
||||
--breakpoint-s-width: #{$breakpoint-s-width};
|
||||
--breakpoint-m-width: #{$breakpoint-m-width};
|
||||
--breakpoint-l-width: #{$breakpoint-l-width};
|
||||
--scrollbar-width: 17px;
|
||||
|
||||
--theme-bg: var(--theme-elevation-0);
|
||||
--theme-input-bg: var(--theme-elevation-0);
|
||||
--theme-text: var(--theme-elevation-800);
|
||||
--theme-overlay: rgba(5, 5, 5, 0.5);
|
||||
--theme-baseline: #{$baseline-px};
|
||||
--theme-baseline-body-size: #{$baseline-body-size};
|
||||
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
sans-serif;
|
||||
--font-serif: 'Georgia', 'Bitstream Charter', 'Charis SIL', Utopia, 'URW Bookman L', serif;
|
||||
--font-mono: 'SF Mono', Menlo, Consolas, Monaco, monospace;
|
||||
|
||||
--style-radius-s: #{$style-radius-s};
|
||||
--style-radius-m: #{$style-radius-m};
|
||||
--style-radius-l: #{$style-radius-l};
|
||||
|
||||
--z-popup: 10;
|
||||
--z-nav: 20;
|
||||
--z-modal: 30;
|
||||
--z-status: 40;
|
||||
|
||||
--accessibility-outline: 2px solid var(--theme-text);
|
||||
--accessibility-outline-offset: 2px;
|
||||
|
||||
--gutter-h: #{base(3)};
|
||||
--spacing-view-bottom: var(--gutter-h);
|
||||
--doc-controls-height: calc(var(--base) * 2.8);
|
||||
--app-header-height: calc(var(--base) * 2.8);
|
||||
--nav-width: 275px;
|
||||
--nav-trans-time: 150ms;
|
||||
|
||||
@include mid-break {
|
||||
--gutter-h: #{base(2)};
|
||||
--app-header-height: calc(var(--base) * 2.4);
|
||||
--doc-controls-height: calc(var(--base) * 2.4);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
--gutter-h: #{base(0.8)};
|
||||
--spacing-view-bottom: calc(var(--base) * 2);
|
||||
--nav-width: 100vw;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// GLOBAL STYLES
|
||||
/////////////////////////////
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
@extend %body;
|
||||
background: var(--theme-bg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
&[data-theme='dark'] {
|
||||
--theme-bg: var(--theme-elevation-0);
|
||||
--theme-text: var(--theme-elevation-1000);
|
||||
--theme-input-bg: var(--theme-elevation-50);
|
||||
--theme-overlay: rgba(5, 5, 5, 0.75);
|
||||
color-scheme: dark;
|
||||
|
||||
::selection {
|
||||
color: var(--color-base-1000);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
color: var(--color-base-1000);
|
||||
}
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 400;
|
||||
color: var(--theme-text);
|
||||
margin: 0;
|
||||
// this is for the nav to be able to push the document over
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-success-250);
|
||||
color: var(--theme-base-800);
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--color-success-250);
|
||||
color: var(--theme-base-800);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@extend %h1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@extend %h2;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@extend %h3;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@extend %h4;
|
||||
}
|
||||
|
||||
h5 {
|
||||
@extend %h5;
|
||||
}
|
||||
|
||||
h6 {
|
||||
@extend %h6;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: $baseline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: var(--accessibility-outline);
|
||||
}
|
||||
|
||||
a {
|
||||
color: currentColor;
|
||||
|
||||
&:focus {
|
||||
&:not(:focus-visible) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
dialog {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.payload__modal-item {
|
||||
min-height: 100%;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.payload__modal-container--enterDone {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.payload__modal-item--enter,
|
||||
.payload__modal-item--enterDone {
|
||||
z-index: var(--z-modal);
|
||||
}
|
||||
|
||||
// @import '~payload-user-css'; TODO: re-enable this
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
@layer payload-default {
|
||||
:root {
|
||||
--color-base-0: rgb(255, 255, 255);
|
||||
--color-base-50: rgb(245, 245, 245);
|
||||
--color-base-100: rgb(235, 235, 235);
|
||||
--color-base-150: rgb(221, 221, 221);
|
||||
--color-base-200: rgb(208, 208, 208);
|
||||
--color-base-250: rgb(195, 195, 195);
|
||||
--color-base-300: rgb(181, 181, 181);
|
||||
--color-base-350: rgb(168, 168, 168);
|
||||
--color-base-400: rgb(154, 154, 154);
|
||||
--color-base-450: rgb(141, 141, 141);
|
||||
--color-base-500: rgb(128, 128, 128);
|
||||
--color-base-550: rgb(114, 114, 114);
|
||||
--color-base-600: rgb(101, 101, 101);
|
||||
--color-base-650: rgb(87, 87, 87);
|
||||
--color-base-700: rgb(74, 74, 74);
|
||||
--color-base-750: rgb(60, 60, 60);
|
||||
--color-base-800: rgb(47, 47, 47);
|
||||
--color-base-850: rgb(34, 34, 34);
|
||||
--color-base-900: rgb(20, 20, 20);
|
||||
--color-base-950: rgb(7, 7, 7);
|
||||
--color-base-1000: rgb(0, 0, 0);
|
||||
|
||||
--color-success-50: rgb(237, 245, 249);
|
||||
--color-success-100: rgb(218, 237, 248);
|
||||
--color-success-150: rgb(188, 225, 248);
|
||||
--color-success-200: rgb(156, 216, 253);
|
||||
--color-success-250: rgb(125, 204, 248);
|
||||
--color-success-300: rgb(97, 190, 241);
|
||||
--color-success-350: rgb(65, 178, 236);
|
||||
--color-success-400: rgb(36, 164, 223);
|
||||
--color-success-450: rgb(18, 148, 204);
|
||||
--color-success-500: rgb(21, 135, 186);
|
||||
--color-success-550: rgb(12, 121, 168);
|
||||
--color-success-600: rgb(11, 110, 153);
|
||||
--color-success-650: rgb(11, 97, 135);
|
||||
--color-success-700: rgb(17, 88, 121);
|
||||
--color-success-750: rgb(17, 76, 105);
|
||||
--color-success-800: rgb(18, 66, 90);
|
||||
--color-success-850: rgb(18, 56, 76);
|
||||
--color-success-900: rgb(19, 44, 58);
|
||||
--color-success-950: rgb(22, 33, 39);
|
||||
|
||||
--color-error-50: rgb(250, 241, 240);
|
||||
--color-error-100: rgb(252, 229, 227);
|
||||
--color-error-150: rgb(247, 208, 204);
|
||||
--color-error-200: rgb(254, 193, 188);
|
||||
--color-error-250: rgb(253, 177, 170);
|
||||
--color-error-300: rgb(253, 154, 146);
|
||||
--color-error-350: rgb(253, 131, 123);
|
||||
--color-error-400: rgb(246, 109, 103);
|
||||
--color-error-450: rgb(234, 90, 86);
|
||||
--color-error-500: rgb(218, 75, 72);
|
||||
--color-error-550: rgb(200, 62, 61);
|
||||
--color-error-600: rgb(182, 54, 54);
|
||||
--color-error-650: rgb(161, 47, 47);
|
||||
--color-error-700: rgb(144, 44, 43);
|
||||
--color-error-750: rgb(123, 41, 39);
|
||||
--color-error-800: rgb(105, 39, 37);
|
||||
--color-error-850: rgb(86, 36, 33);
|
||||
--color-error-900: rgb(64, 32, 29);
|
||||
--color-error-950: rgb(44, 26, 24);
|
||||
|
||||
--color-warning-50: rgb(249, 242, 237);
|
||||
--color-warning-100: rgb(248, 232, 219);
|
||||
--color-warning-150: rgb(243, 212, 186);
|
||||
--color-warning-200: rgb(243, 200, 162);
|
||||
--color-warning-250: rgb(240, 185, 136);
|
||||
--color-warning-300: rgb(238, 166, 98);
|
||||
--color-warning-350: rgb(234, 148, 58);
|
||||
--color-warning-400: rgb(223, 132, 17);
|
||||
--color-warning-450: rgb(204, 120, 15);
|
||||
--color-warning-500: rgb(185, 108, 13);
|
||||
--color-warning-550: rgb(167, 97, 10);
|
||||
--color-warning-600: rgb(150, 87, 11);
|
||||
--color-warning-650: rgb(134, 78, 11);
|
||||
--color-warning-700: rgb(120, 70, 13);
|
||||
--color-warning-750: rgb(105, 61, 13);
|
||||
--color-warning-800: rgb(90, 55, 19);
|
||||
--color-warning-850: rgb(73, 47, 21);
|
||||
--color-warning-900: rgb(56, 38, 20);
|
||||
--color-warning-950: rgb(38, 29, 21);
|
||||
|
||||
--color-blue-50: rgb(237, 245, 249);
|
||||
--color-blue-100: rgb(218, 237, 248);
|
||||
--color-blue-150: rgb(188, 225, 248);
|
||||
--color-blue-200: rgb(156, 216, 253);
|
||||
--color-blue-250: rgb(125, 204, 248);
|
||||
--color-blue-300: rgb(97, 190, 241);
|
||||
--color-blue-350: rgb(65, 178, 236);
|
||||
--color-blue-400: rgb(36, 164, 223);
|
||||
--color-blue-450: rgb(18, 148, 204);
|
||||
--color-blue-500: rgb(21, 135, 186);
|
||||
--color-blue-550: rgb(12, 121, 168);
|
||||
--color-blue-600: rgb(11, 110, 153);
|
||||
--color-blue-650: rgb(11, 97, 135);
|
||||
--color-blue-700: rgb(17, 88, 121);
|
||||
--color-blue-750: rgb(17, 76, 105);
|
||||
--color-blue-800: rgb(18, 66, 90);
|
||||
--color-blue-850: rgb(18, 56, 76);
|
||||
--color-blue-900: rgb(19, 44, 58);
|
||||
--color-blue-950: rgb(22, 33, 39);
|
||||
|
||||
--theme-border-color: var(--theme-elevation-150);
|
||||
|
||||
--theme-success-50: var(--color-success-50);
|
||||
--theme-success-100: var(--color-success-100);
|
||||
--theme-success-150: var(--color-success-150);
|
||||
--theme-success-200: var(--color-success-200);
|
||||
--theme-success-250: var(--color-success-250);
|
||||
--theme-success-300: var(--color-success-300);
|
||||
--theme-success-350: var(--color-success-350);
|
||||
--theme-success-400: var(--color-success-400);
|
||||
--theme-success-450: var(--color-success-450);
|
||||
--theme-success-500: var(--color-success-500);
|
||||
--theme-success-550: var(--color-success-550);
|
||||
--theme-success-600: var(--color-success-600);
|
||||
--theme-success-650: var(--color-success-650);
|
||||
--theme-success-700: var(--color-success-700);
|
||||
--theme-success-750: var(--color-success-750);
|
||||
--theme-success-800: var(--color-success-800);
|
||||
--theme-success-850: var(--color-success-850);
|
||||
--theme-success-900: var(--color-success-900);
|
||||
--theme-success-950: var(--color-success-950);
|
||||
|
||||
--theme-warning-50: var(--color-warning-50);
|
||||
--theme-warning-100: var(--color-warning-100);
|
||||
--theme-warning-150: var(--color-warning-150);
|
||||
--theme-warning-200: var(--color-warning-200);
|
||||
--theme-warning-250: var(--color-warning-250);
|
||||
--theme-warning-300: var(--color-warning-300);
|
||||
--theme-warning-350: var(--color-warning-350);
|
||||
--theme-warning-400: var(--color-warning-400);
|
||||
--theme-warning-450: var(--color-warning-450);
|
||||
--theme-warning-500: var(--color-warning-500);
|
||||
--theme-warning-550: var(--color-warning-550);
|
||||
--theme-warning-600: var(--color-warning-600);
|
||||
--theme-warning-650: var(--color-warning-650);
|
||||
--theme-warning-700: var(--color-warning-700);
|
||||
--theme-warning-750: var(--color-warning-750);
|
||||
--theme-warning-800: var(--color-warning-800);
|
||||
--theme-warning-850: var(--color-warning-850);
|
||||
--theme-warning-900: var(--color-warning-900);
|
||||
--theme-warning-950: var(--color-warning-950);
|
||||
|
||||
--theme-error-50: var(--color-error-50);
|
||||
--theme-error-100: var(--color-error-100);
|
||||
--theme-error-150: var(--color-error-150);
|
||||
--theme-error-200: var(--color-error-200);
|
||||
--theme-error-250: var(--color-error-250);
|
||||
--theme-error-300: var(--color-error-300);
|
||||
--theme-error-350: var(--color-error-350);
|
||||
--theme-error-400: var(--color-error-400);
|
||||
--theme-error-450: var(--color-error-450);
|
||||
--theme-error-500: var(--color-error-500);
|
||||
--theme-error-550: var(--color-error-550);
|
||||
--theme-error-600: var(--color-error-600);
|
||||
--theme-error-650: var(--color-error-650);
|
||||
--theme-error-700: var(--color-error-700);
|
||||
--theme-error-750: var(--color-error-750);
|
||||
--theme-error-800: var(--color-error-800);
|
||||
--theme-error-850: var(--color-error-850);
|
||||
--theme-error-900: var(--color-error-900);
|
||||
--theme-error-950: var(--color-error-950);
|
||||
|
||||
--theme-elevation-0: var(--color-base-0);
|
||||
--theme-elevation-50: var(--color-base-50);
|
||||
--theme-elevation-100: var(--color-base-100);
|
||||
--theme-elevation-150: var(--color-base-150);
|
||||
--theme-elevation-200: var(--color-base-200);
|
||||
--theme-elevation-250: var(--color-base-250);
|
||||
--theme-elevation-300: var(--color-base-300);
|
||||
--theme-elevation-350: var(--color-base-350);
|
||||
--theme-elevation-400: var(--color-base-400);
|
||||
--theme-elevation-450: var(--color-base-450);
|
||||
--theme-elevation-500: var(--color-base-500);
|
||||
--theme-elevation-550: var(--color-base-550);
|
||||
--theme-elevation-600: var(--color-base-600);
|
||||
--theme-elevation-650: var(--color-base-650);
|
||||
--theme-elevation-700: var(--color-base-700);
|
||||
--theme-elevation-750: var(--color-base-750);
|
||||
--theme-elevation-800: var(--color-base-800);
|
||||
--theme-elevation-850: var(--color-base-850);
|
||||
--theme-elevation-900: var(--color-base-900);
|
||||
--theme-elevation-950: var(--color-base-950);
|
||||
--theme-elevation-1000: var(--color-base-1000);
|
||||
}
|
||||
|
||||
html[data-theme='dark'] {
|
||||
--theme-border-color: var(--theme-elevation-150);
|
||||
|
||||
--theme-elevation-0: var(--color-base-900);
|
||||
--theme-elevation-50: var(--color-base-850);
|
||||
--theme-elevation-100: var(--color-base-800);
|
||||
--theme-elevation-150: var(--color-base-750);
|
||||
--theme-elevation-200: var(--color-base-700);
|
||||
--theme-elevation-250: var(--color-base-650);
|
||||
--theme-elevation-300: var(--color-base-600);
|
||||
--theme-elevation-350: var(--color-base-550);
|
||||
--theme-elevation-400: var(--color-base-450);
|
||||
--theme-elevation-450: var(--color-base-400);
|
||||
--theme-elevation-550: var(--color-base-350);
|
||||
--theme-elevation-600: var(--color-base-300);
|
||||
--theme-elevation-650: var(--color-base-250);
|
||||
--theme-elevation-700: var(--color-base-200);
|
||||
--theme-elevation-750: var(--color-base-150);
|
||||
--theme-elevation-800: var(--color-base-100);
|
||||
--theme-elevation-850: var(--color-base-50);
|
||||
--theme-elevation-900: var(--color-base-0);
|
||||
--theme-elevation-950: var(--color-base-0);
|
||||
--theme-elevation-1000: var(--color-base-0);
|
||||
|
||||
--theme-success-50: var(--color-success-950);
|
||||
--theme-success-100: var(--color-success-900);
|
||||
--theme-success-150: var(--color-success-850);
|
||||
--theme-success-200: var(--color-success-800);
|
||||
--theme-success-250: var(--color-success-750);
|
||||
--theme-success-300: var(--color-success-700);
|
||||
--theme-success-350: var(--color-success-650);
|
||||
--theme-success-400: var(--color-success-600);
|
||||
--theme-success-450: var(--color-success-550);
|
||||
--theme-success-550: var(--color-success-450);
|
||||
--theme-success-600: var(--color-success-400);
|
||||
--theme-success-650: var(--color-success-350);
|
||||
--theme-success-700: var(--color-success-300);
|
||||
--theme-success-750: var(--color-success-250);
|
||||
--theme-success-800: var(--color-success-200);
|
||||
--theme-success-850: var(--color-success-150);
|
||||
--theme-success-900: var(--color-success-100);
|
||||
--theme-success-950: var(--color-success-50);
|
||||
|
||||
--theme-warning-50: var(--color-warning-950);
|
||||
--theme-warning-100: var(--color-warning-900);
|
||||
--theme-warning-150: var(--color-warning-850);
|
||||
--theme-warning-200: var(--color-warning-800);
|
||||
--theme-warning-250: var(--color-warning-750);
|
||||
--theme-warning-300: var(--color-warning-700);
|
||||
--theme-warning-350: var(--color-warning-650);
|
||||
--theme-warning-400: var(--color-warning-600);
|
||||
--theme-warning-450: var(--color-warning-550);
|
||||
--theme-warning-550: var(--color-warning-450);
|
||||
--theme-warning-600: var(--color-warning-400);
|
||||
--theme-warning-650: var(--color-warning-350);
|
||||
--theme-warning-700: var(--color-warning-300);
|
||||
--theme-warning-750: var(--color-warning-250);
|
||||
--theme-warning-800: var(--color-warning-200);
|
||||
--theme-warning-850: var(--color-warning-150);
|
||||
--theme-warning-900: var(--color-warning-100);
|
||||
--theme-warning-950: var(--color-warning-50);
|
||||
|
||||
--theme-error-50: var(--color-error-950);
|
||||
--theme-error-100: var(--color-error-900);
|
||||
--theme-error-150: var(--color-error-850);
|
||||
--theme-error-200: var(--color-error-800);
|
||||
--theme-error-250: var(--color-error-750);
|
||||
--theme-error-300: var(--color-error-700);
|
||||
--theme-error-350: var(--color-error-650);
|
||||
--theme-error-400: var(--color-error-600);
|
||||
--theme-error-450: var(--color-error-550);
|
||||
--theme-error-550: var(--color-error-450);
|
||||
--theme-error-600: var(--color-error-400);
|
||||
--theme-error-650: var(--color-error-350);
|
||||
--theme-error-700: var(--color-error-300);
|
||||
--theme-error-750: var(--color-error-250);
|
||||
--theme-error-800: var(--color-error-200);
|
||||
--theme-error-850: var(--color-error-150);
|
||||
--theme-error-900: var(--color-error-100);
|
||||
--theme-error-950: var(--color-error-50);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/* Used as a placeholder for when the Payload app does not have custom CSS */
|
||||
@@ -1,27 +0,0 @@
|
||||
////////////////////////////
|
||||
// MEDIA QUERIES
|
||||
/////////////////////////////
|
||||
|
||||
@mixin extra-small-break {
|
||||
@media (max-width: $breakpoint-xs-width) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin small-break {
|
||||
@media (max-width: $breakpoint-s-width) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin mid-break {
|
||||
@media (max-width: $breakpoint-m-width) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large-break {
|
||||
@media (max-width: $breakpoint-l-width) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
@layer payload-default {
|
||||
%btn-reset {
|
||||
border: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
color: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin btn-reset {
|
||||
border: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
color: currentColor;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
@import 'vars';
|
||||
@import 'z-index';
|
||||
|
||||
//////////////////////////////
|
||||
// IMPORT OVERRIDES
|
||||
//////////////////////////////
|
||||
|
||||
@import 'type';
|
||||
@import 'queries';
|
||||
@import 'resets';
|
||||
@import 'svg';
|
||||
@@ -1,10 +0,0 @@
|
||||
@mixin color-svg($color) {
|
||||
.stroke {
|
||||
stroke: $color;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.fill {
|
||||
fill: $color;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
@import 'vars';
|
||||
@import 'queries';
|
||||
|
||||
@layer payload-default {
|
||||
.Toastify {
|
||||
.Toastify__toast-container {
|
||||
left: base(5);
|
||||
transform: none;
|
||||
right: base(5);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.Toastify__toast {
|
||||
padding: base(0.5);
|
||||
border-radius: $style-radius-m;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.Toastify__close-button {
|
||||
align-self: center;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__toast--success {
|
||||
color: var(--color-success-900);
|
||||
background: var(--color-success-500);
|
||||
|
||||
.Toastify__progress-bar {
|
||||
background-color: var(--color-success-900);
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__close-button--success {
|
||||
color: var(--color-success-900);
|
||||
}
|
||||
|
||||
.Toastify__toast--error {
|
||||
background: var(--theme-error-500);
|
||||
color: #fff;
|
||||
|
||||
.Toastify__progress-bar {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.Toastify__close-button--light {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
.Toastify__toast-container {
|
||||
left: $baseline;
|
||||
right: $baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
@import './styles.scss';
|
||||
|
||||
@layer payload-default {
|
||||
.payload-toast-container {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.payload-toast-close-button {
|
||||
position: absolute;
|
||||
order: 3;
|
||||
left: unset;
|
||||
inset-inline-end: base(0.8);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: var(--theme-elevation-600);
|
||||
background: unset;
|
||||
border: none;
|
||||
|
||||
svg {
|
||||
width: base(0.8);
|
||||
height: base(0.8);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-elevation-250);
|
||||
background: none;
|
||||
}
|
||||
|
||||
[dir='RTL'] & {
|
||||
right: unset;
|
||||
left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
line-height: base(1);
|
||||
margin-right: base(1);
|
||||
}
|
||||
|
||||
.payload-toast-item {
|
||||
padding: base(0.8);
|
||||
color: var(--theme-elevation-800);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--theme-border-color);
|
||||
background: var(--theme-input-bg);
|
||||
box-shadow:
|
||||
0px 10px 4px -8px rgba(0, 2, 4, 0.02),
|
||||
0px 2px 3px 0px rgba(0, 2, 4, 0.05);
|
||||
|
||||
.toast-content {
|
||||
transition: opacity 100ms cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[data-front='false'] {
|
||||
.toast-content {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-expanded='true'] {
|
||||
.toast-content {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
width: base(0.8);
|
||||
height: base(0.8);
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
& > * {
|
||||
width: base(1.2);
|
||||
height: base(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-warning {
|
||||
color: var(--theme-warning-800);
|
||||
border-color: var(--theme-warning-250);
|
||||
background-color: var(--theme-warning-100);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-warning-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-warning-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-error {
|
||||
color: var(--theme-error-800);
|
||||
border-color: var(--theme-error-250);
|
||||
background-color: var(--theme-error-100);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-error-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-error-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-success {
|
||||
color: var(--theme-success-800);
|
||||
border-color: var(--theme-success-250);
|
||||
background-color: var(--theme-success-100);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-success-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-success-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.toast-info {
|
||||
color: var(--theme-elevation-800);
|
||||
border-color: var(--theme-elevation-250);
|
||||
background-color: var(--theme-elevation-100);
|
||||
|
||||
.payload-toast-close-button {
|
||||
color: var(--theme-elevation-600);
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-elevation-250);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
@import 'vars';
|
||||
@import 'queries';
|
||||
|
||||
/////////////////////////////
|
||||
// HEADINGS
|
||||
/////////////////////////////
|
||||
@layer payload-default {
|
||||
%h1,
|
||||
%h2,
|
||||
%h3,
|
||||
%h4,
|
||||
%h5,
|
||||
%h6 {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
%h1 {
|
||||
margin: 0;
|
||||
font-size: base(1.6);
|
||||
line-height: base(1.8);
|
||||
|
||||
@include small-break {
|
||||
letter-spacing: -0.5px;
|
||||
font-size: base(1.25);
|
||||
}
|
||||
}
|
||||
|
||||
%h2 {
|
||||
margin: 0;
|
||||
font-size: base(1.3);
|
||||
line-height: base(1.6);
|
||||
|
||||
@include small-break {
|
||||
font-size: base(0.85);
|
||||
}
|
||||
}
|
||||
|
||||
%h3 {
|
||||
margin: 0;
|
||||
font-size: base(1);
|
||||
line-height: base(1.2);
|
||||
|
||||
@include small-break {
|
||||
font-size: base(0.65);
|
||||
line-height: 1.25;
|
||||
}
|
||||
}
|
||||
|
||||
%h4 {
|
||||
margin: 0;
|
||||
font-size: base(0.8);
|
||||
line-height: base(1);
|
||||
letter-spacing: -0.375px;
|
||||
}
|
||||
|
||||
%h5 {
|
||||
margin: 0;
|
||||
font-size: base(0.65);
|
||||
line-height: base(0.8);
|
||||
}
|
||||
|
||||
%h6 {
|
||||
margin: 0;
|
||||
font-size: base(0.6);
|
||||
line-height: base(0.8);
|
||||
}
|
||||
|
||||
%small {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// TYPE STYLES
|
||||
/////////////////////////////
|
||||
|
||||
%large-body {
|
||||
font-size: base(0.6);
|
||||
line-height: base(1);
|
||||
letter-spacing: base(0.02);
|
||||
|
||||
@include mid-break {
|
||||
font-size: base(0.7);
|
||||
line-height: base(1);
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
font-size: base(0.55);
|
||||
line-height: base(0.75);
|
||||
}
|
||||
}
|
||||
|
||||
%body {
|
||||
font-size: $baseline-body-size;
|
||||
line-height: $baseline-px;
|
||||
font-weight: normal;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
|
||||
%code {
|
||||
font-size: base(0.4);
|
||||
color: var(--theme-elevation-400);
|
||||
|
||||
span {
|
||||
color: var(--theme-elevation-800);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
@use 'sass:math';
|
||||
|
||||
/////////////////////////////
|
||||
// BREAKPOINTS
|
||||
/////////////////////////////
|
||||
|
||||
$breakpoint-xs-width: 400px !default;
|
||||
$breakpoint-s-width: 768px !default;
|
||||
$breakpoint-m-width: 1024px !default;
|
||||
$breakpoint-l-width: 1440px !default;
|
||||
|
||||
//////////////////////////////
|
||||
// BASELINE GRID
|
||||
//////////////////////////////
|
||||
|
||||
$baseline-px: 20px !default;
|
||||
$baseline-body-size: 13px !default;
|
||||
$baseline: math.div($baseline-px, $baseline-body-size) + rem;
|
||||
|
||||
@function base($multiplier) {
|
||||
@return (math.div($baseline-px, $baseline-body-size) * $multiplier) + rem;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// COLORS (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
|
||||
//////////////////////////////
|
||||
|
||||
$color-dark-gray: #333333 !default;
|
||||
$color-gray: #9a9a9a !default;
|
||||
$color-light-gray: #dadada !default;
|
||||
$color-background-gray: #f3f3f3 !default;
|
||||
$color-red: #ff6f76 !default;
|
||||
$color-yellow: #fdffa4 !default;
|
||||
$color-green: #b2ffd6 !default;
|
||||
$color-purple: #f3ddf3 !default;
|
||||
|
||||
//////////////////////////////
|
||||
// STYLES
|
||||
//////////////////////////////
|
||||
|
||||
$style-radius-s: 3px !default;
|
||||
$style-radius-m: 4px !default;
|
||||
$style-radius-l: 8px !default;
|
||||
$style-stroke-width: 1px !default;
|
||||
|
||||
$style-stroke-width-s: 1px !default;
|
||||
$style-stroke-width-m: 2px !default;
|
||||
|
||||
//////////////////////////////
|
||||
// MISC
|
||||
//////////////////////////////
|
||||
|
||||
$top-header-offset: calc(base(1) - 1px);
|
||||
$top-header-offset-m: base(3);
|
||||
$focus-box-shadow: 0 0 0 $style-stroke-width-m var(--theme-success-500);
|
||||
|
||||
//////////////////////////////
|
||||
// SHADOWS
|
||||
//////////////////////////////
|
||||
|
||||
@mixin shadow-sm {
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-m {
|
||||
box-shadow: 0 4px 8px -3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@mixin shadow-lg {
|
||||
box-shadow: 0 -2px 16px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin shadow-lg-top {
|
||||
box-shadow: 0 2px 16px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@mixin inputShadow {
|
||||
@include shadow-sm;
|
||||
|
||||
&:not(:disabled) {
|
||||
&:hover {
|
||||
box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin soft-shadow-bottom {
|
||||
box-shadow: 0 7px 14px 0px rgb(0 0 0 / 5%);
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// STYLE MIXINS
|
||||
//////////////////////////////
|
||||
|
||||
@mixin blur-bg($color: var(--theme-bg), $opacity: 0.75) {
|
||||
&:before,
|
||||
&:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
background: $color;
|
||||
opacity: $opacity;
|
||||
}
|
||||
|
||||
&:after {
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin blur-bg-light {
|
||||
@include blur-bg(var(--theme-bg), 0.3);
|
||||
}
|
||||
|
||||
@mixin readOnly {
|
||||
background: var(--theme-elevation-100);
|
||||
color: var(--theme-elevation-400);
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--theme-elevation-150);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin formInput() {
|
||||
@include inputShadow;
|
||||
font-family: var(--font-body);
|
||||
width: 100%;
|
||||
border: 1px solid var(--theme-elevation-150);
|
||||
border-radius: var(--style-radius-s);
|
||||
background: var(--theme-input-bg);
|
||||
color: var(--theme-elevation-800);
|
||||
font-size: 1rem;
|
||||
height: base(2);
|
||||
line-height: base(1);
|
||||
padding: base(0.4) base(0.75);
|
||||
-webkit-appearance: none;
|
||||
transition-property: border, box-shadow;
|
||||
transition-duration: 100ms;
|
||||
transition-timing-function: cubic-bezier(0, 0.2, 0.2, 1);
|
||||
|
||||
&[data-rtl='true'] {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
&::-webkit-input-placeholder {
|
||||
color: var(--theme-elevation-400);
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&::-moz-placeholder {
|
||||
color: var(--theme-elevation-400);
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--theme-elevation-250);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:focus-within,
|
||||
&:active {
|
||||
border-color: var(--theme-elevation-400);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
@include readOnly;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin lightInputError {
|
||||
background-color: var(--theme-error-50);
|
||||
border: 1px solid var(--theme-error-500);
|
||||
}
|
||||
|
||||
@mixin darkInputError {
|
||||
background-color: var(--theme-error-100);
|
||||
border: 1px solid var(--theme-error-400);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--theme-error-500);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/////////////////////////////
|
||||
// Z-INDEX CHART (DEPRECATED. DO NOT USE. PREFER CSS VARIABLES)
|
||||
/////////////////////////////
|
||||
|
||||
$z-page: 20;
|
||||
$z-page-content: 30;
|
||||
$z-nav: 40;
|
||||
$z-modal: 50;
|
||||
$z-status: 60;
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.template-default {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.template-default {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.template-minimal {
|
||||
|
||||
@@ -49,11 +49,13 @@ const reqCache = selectiveCache<Result>('req')
|
||||
* As access control and getting the request locale is dependent on the current URL and
|
||||
*/
|
||||
export const initReq = async function ({
|
||||
canSetHeaders,
|
||||
configPromise,
|
||||
importMap,
|
||||
key,
|
||||
overrides,
|
||||
}: {
|
||||
canSetHeaders?: boolean
|
||||
configPromise: Promise<SanitizedConfig> | SanitizedConfig
|
||||
importMap: ImportMap
|
||||
key: string
|
||||
@@ -77,6 +79,7 @@ export const initReq = async function ({
|
||||
})
|
||||
|
||||
const { responseHeaders, user } = await executeAuthStrategies({
|
||||
canSetHeaders,
|
||||
headers,
|
||||
payload,
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
$tab-width: 16px;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.query-inspector {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.payload-settings {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
BuildCollectionFolderViewResult,
|
||||
FolderListViewServerPropsOnly,
|
||||
ListQuery,
|
||||
Where,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultBrowseByFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
@@ -10,6 +11,7 @@ import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerCompo
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import { getFolderData } from 'payload'
|
||||
import { buildFolderWhereConstraints } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
@@ -29,10 +31,10 @@ export const buildBrowseByFolderView = async (
|
||||
args: BuildFolderViewArgs,
|
||||
): Promise<BuildCollectionFolderViewResult> => {
|
||||
const {
|
||||
browseByFolderSlugs: browseByFolderSlugsFromArgs = [],
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
enableRowSelections,
|
||||
folderCollectionSlugs,
|
||||
folderID,
|
||||
initPageResult,
|
||||
isInDrawer,
|
||||
@@ -54,30 +56,83 @@ export const buildBrowseByFolderView = async (
|
||||
visibleEntities,
|
||||
} = initPageResult
|
||||
|
||||
const collections = folderCollectionSlugs.filter(
|
||||
const browseByFolderSlugs = browseByFolderSlugsFromArgs.filter(
|
||||
(collectionSlug) =>
|
||||
permissions?.collections?.[collectionSlug]?.read &&
|
||||
visibleEntities.collections.includes(collectionSlug),
|
||||
)
|
||||
|
||||
if (!collections.length) {
|
||||
if (config.folders === false || config.folders.browseByFolder === false) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
const query = queryFromArgs || queryFromReq
|
||||
const selectedCollectionSlugs: string[] =
|
||||
Array.isArray(query?.relationTo) && query.relationTo.length
|
||||
? query.relationTo
|
||||
: [...folderCollectionSlugs, config.folders.slug]
|
||||
? query.relationTo.filter(
|
||||
(slug) =>
|
||||
browseByFolderSlugs.includes(slug) || (config.folders && slug === config.folders.slug),
|
||||
)
|
||||
: [...browseByFolderSlugs, config.folders.slug]
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
const folderCollectionConfig = payload.collections[config.folders.slug].config
|
||||
|
||||
const browseByFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
'browse-by-folder',
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
|
||||
let documentWhere: undefined | Where = undefined
|
||||
let folderWhere: undefined | Where = undefined
|
||||
// if folderID, dont make a documentWhere since it only queries root folders
|
||||
for (const collectionSlug of selectedCollectionSlugs) {
|
||||
if (collectionSlug === config.folders.slug) {
|
||||
const folderCollectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: folderCollectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
})
|
||||
|
||||
if (folderCollectionConstraints) {
|
||||
folderWhere = folderCollectionConstraints
|
||||
}
|
||||
} else if (folderID) {
|
||||
if (!documentWhere) {
|
||||
documentWhere = {
|
||||
or: [],
|
||||
}
|
||||
}
|
||||
|
||||
const collectionConfig = payload.collections[collectionSlug].config
|
||||
if (collectionConfig.folders && collectionConfig.folders.browseByFolder === true) {
|
||||
const collectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
})
|
||||
|
||||
if (collectionConstraints) {
|
||||
documentWhere.or.push(collectionConstraints)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { breadcrumbs, documents, subfolders } = await getFolderData({
|
||||
documentWhere,
|
||||
folderID,
|
||||
folderWhere,
|
||||
req: initPageResult.req,
|
||||
search: query?.search as string,
|
||||
})
|
||||
|
||||
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
|
||||
@@ -96,13 +151,6 @@ export const buildBrowseByFolderView = async (
|
||||
)
|
||||
}
|
||||
|
||||
const browseByFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
'browse-by-folder',
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
|
||||
const serverProps: Omit<FolderListViewServerPropsOnly, 'collectionConfig' | 'listPreferences'> = {
|
||||
documents,
|
||||
i18n,
|
||||
@@ -125,7 +173,7 @@ export const buildBrowseByFolderView = async (
|
||||
|
||||
// documents cannot be created without a parent folder in this view
|
||||
const hasCreatePermissionCollectionSlugs = folderID
|
||||
? [config.folders.slug, ...folderCollectionSlugs]
|
||||
? [config.folders.slug, ...browseByFolderSlugs]
|
||||
: [config.folders.slug]
|
||||
|
||||
return {
|
||||
@@ -134,7 +182,8 @@ export const buildBrowseByFolderView = async (
|
||||
breadcrumbs={breadcrumbs}
|
||||
documents={documents}
|
||||
filteredCollectionSlugs={selectedCollectionSlugs}
|
||||
folderCollectionSlugs={folderCollectionSlugs}
|
||||
folderCollectionSlugs={browseByFolderSlugs}
|
||||
folderFieldName={config.folders.fieldName}
|
||||
folderID={folderID}
|
||||
subfolders={subfolders}
|
||||
>
|
||||
|
||||
@@ -8,9 +8,10 @@ import type {
|
||||
|
||||
import { DefaultCollectionFolderView, FolderProvider, HydrateAuthProvider } from '@payloadcms/ui'
|
||||
import { RenderServerComponent } from '@payloadcms/ui/elements/RenderServerComponent'
|
||||
import { formatAdminURL, mergeListSearchAndWhere } from '@payloadcms/ui/shared'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import { getFolderData, parseDocumentID } from 'payload'
|
||||
import { getFolderData } from 'payload'
|
||||
import { buildFolderWhereConstraints } from 'payload/shared'
|
||||
import React from 'react'
|
||||
|
||||
import { getPreferences } from '../../utilities/getPreferences.js'
|
||||
@@ -37,7 +38,6 @@ export const buildCollectionFolderView = async (
|
||||
disableBulkDelete,
|
||||
disableBulkEdit,
|
||||
enableRowSelections,
|
||||
folderCollectionSlugs,
|
||||
folderID,
|
||||
initPageResult,
|
||||
isInDrawer,
|
||||
@@ -69,12 +69,12 @@ export const buildCollectionFolderView = async (
|
||||
if (collectionConfig) {
|
||||
const query = queryFromArgs || queryFromReq
|
||||
|
||||
const collectionFolderPreferences = await getPreferences<{ viewPreference: string }>(
|
||||
`${collectionSlug}-collection-folder`,
|
||||
payload,
|
||||
user.id,
|
||||
user.collection,
|
||||
)
|
||||
const collectionFolderPreferences = await getPreferences<{
|
||||
sort?: string
|
||||
viewPreference: string
|
||||
}>(`${collectionSlug}-collection-folder`, payload, user.id, user.collection)
|
||||
|
||||
const sortPreference = collectionFolderPreferences?.value.sort
|
||||
|
||||
const {
|
||||
routes: { admin: adminRoute },
|
||||
@@ -82,38 +82,45 @@ export const buildCollectionFolderView = async (
|
||||
|
||||
if (
|
||||
(!visibleEntities.collections.includes(collectionSlug) && !overrideEntityVisibility) ||
|
||||
!folderCollectionSlugs.includes(collectionSlug)
|
||||
!config.folders
|
||||
) {
|
||||
throw new Error('not-found')
|
||||
}
|
||||
|
||||
const whereConstraints = [
|
||||
mergeListSearchAndWhere({
|
||||
collectionConfig,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
where: (query?.where as Where) || undefined,
|
||||
}),
|
||||
]
|
||||
let folderWhere: undefined | Where
|
||||
const folderCollectionConfig = payload.collections[config.folders.slug].config
|
||||
const folderCollectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig: folderCollectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
sort: sortPreference,
|
||||
})
|
||||
|
||||
if (folderID) {
|
||||
whereConstraints.push({
|
||||
[config.folders.fieldName]: {
|
||||
equals: parseDocumentID({ id: folderID, collectionSlug, payload }),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
whereConstraints.push({
|
||||
[config.folders.fieldName]: {
|
||||
exists: false,
|
||||
},
|
||||
})
|
||||
if (folderCollectionConstraints) {
|
||||
folderWhere = folderCollectionConstraints
|
||||
}
|
||||
|
||||
let documentWhere: undefined | Where
|
||||
const collectionConstraints = await buildFolderWhereConstraints({
|
||||
collectionConfig,
|
||||
folderID,
|
||||
localeCode: fullLocale?.code,
|
||||
req: initPageResult.req,
|
||||
search: typeof query?.search === 'string' ? query.search : undefined,
|
||||
sort: sortPreference,
|
||||
})
|
||||
if (collectionConstraints) {
|
||||
documentWhere = collectionConstraints
|
||||
}
|
||||
|
||||
const { breadcrumbs, documents, subfolders } = await getFolderData({
|
||||
collectionSlug,
|
||||
documentWhere,
|
||||
folderID,
|
||||
folderWhere,
|
||||
req: initPageResult.req,
|
||||
search: query?.search as string,
|
||||
})
|
||||
|
||||
const resolvedFolderID = breadcrumbs[breadcrumbs.length - 1]?.id
|
||||
@@ -175,7 +182,8 @@ export const buildCollectionFolderView = async (
|
||||
breadcrumbs={breadcrumbs}
|
||||
collectionSlug={collectionSlug}
|
||||
documents={documents}
|
||||
folderCollectionSlugs={folderCollectionSlugs}
|
||||
folderCollectionSlugs={[collectionSlug]}
|
||||
folderFieldName={config.folders.fieldName}
|
||||
folderID={folderID}
|
||||
search={search}
|
||||
subfolders={subfolders}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.create-first-user {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.dashboard {
|
||||
|
||||
@@ -2,6 +2,7 @@ import type {
|
||||
BeforeDocumentControlsServerPropsOnly,
|
||||
DefaultServerFunctionArgs,
|
||||
DocumentSlots,
|
||||
EditMenuItemsServerPropsOnly,
|
||||
PayloadRequest,
|
||||
PreviewButtonServerPropsOnly,
|
||||
PublishButtonServerPropsOnly,
|
||||
@@ -55,6 +56,16 @@ export const renderDocumentSlots: (args: {
|
||||
})
|
||||
}
|
||||
|
||||
const EditMenuItems = collectionConfig?.admin?.components?.edit?.editMenuItems
|
||||
|
||||
if (EditMenuItems) {
|
||||
components.EditMenuItems = RenderServerComponent({
|
||||
Component: EditMenuItems,
|
||||
importMap: req.payload.importMap,
|
||||
serverProps: serverProps satisfies EditMenuItemsServerPropsOnly,
|
||||
})
|
||||
}
|
||||
|
||||
const CustomPreviewButton =
|
||||
collectionConfig?.admin?.components?.edit?.PreviewButton ||
|
||||
globalConfig?.admin?.components?.elements?.PreviewButton
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.live-preview-window {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import '../../../../scss/styles.scss';
|
||||
@import '~@payloadcms/ui/scss';
|
||||
|
||||
@layer payload-default {
|
||||
.live-preview-toolbar-controls {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user