Compare commits

..

60 Commits

Author SHA1 Message Date
Elliot DeNolf
f754edc375 chore(release): v3.0.0-beta.89 [skip ci] 2024-08-21 20:54:18 -04:00
Paul
d2571e10d6 feat: upload hasmany (#7796)
Supports `hasMany` upload fields, similar to how `hasMany` works in
other fields, i.e.:

```ts
{
  type: 'upload',
  relationTo: 'media',
  hasMany: true
}
```

---------

Co-authored-by: Jacob Fletcher <jacobsfletch@gmail.com>
Co-authored-by: James <james@trbl.design>
2024-08-21 20:44:04 -04:00
Alessio Gravili
a687cb9c5b Merge PR: Lexical migration and validation improvements 2024-08-21 18:31:53 -04:00
Alessio Gravili
cf6634111f fix(richtext-lexical): ensure errors during slate => lexical migration are caught and do not halt migration progress 2024-08-21 17:52:45 -04:00
Jarrod Flesch
1ee19d3016 feat: bulk upload (#7800)
## Description

Adds bulk upload functionality to upload enabled configs.

You can disable the ability by defining `upload.bulkUpload: false` in
your upload enabled config.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-21 17:44:34 -04:00
Alessio Gravili
9beaa281dc feat: log document id in ValidationError cause, if present 2024-08-21 17:31:31 -04:00
Alessio Gravili
5174c7092f fix(richtext-lexical): inaccurate detection of whether the editor is empty or not 2024-08-21 17:26:02 -04:00
Alessio Gravili
d894ac75f0 fix(richtext-lexical): migrate scripts not working due to migration hooks running during migrate script 2024-08-21 16:43:56 -04:00
Alessio Gravili
af0105ced5 fix: no longer handle disabling node deprecation warnings within bin script shebangs, as it errored on some systems (#7797)
Fixes https://github.com/payloadcms/payload/issues/7741

I have no idea why it broke and was not able to reproduce this at all.
But given the amount of people reporting this issue, it's not worth
keeping this around for the small benefit this brings
2024-08-21 17:01:57 +00:00
Tylan Davis
93e81314df fix(ui, richtext-lexical): corrects clickable areas on block headers (#7791)
## Description

Fixes an issue where Block component section titles were taking up the
entire clickable area of block headers.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-21 10:45:05 -04:00
Jarrod Flesch
163d1c85da chore: corrects icon color styles (#7792) 2024-08-21 10:28:01 -04:00
Jacob Fletcher
cb9b80aaf9 fix!: handles custom collection description components (#7789)
## Description

Closes #7784 by properly handling custom collection description
components via `admin.components.Description`. This component was
incorrectly added to the `admin.components.edit` key, and also was never
handled on the front-end. This was especially misleading because the
client-side config had a duplicative key in the proper position.

## Breaking Changes

This PR is only labeled as a breaking change because the key has changed
position within the config. If you were previously defining a custom
description component on a collection, simply move it into the correct
position:

Old:
```ts
{
  admin: {
    components: {
      edit: {
        Description: ''
      }
    }
  }
}
```

New:

```ts
{
  admin: {
    components: {
      Description: ''
    }
  }
}
```

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)
- [x] This change requires a documentation update

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [x] I have made corresponding changes to the documentation
2024-08-21 10:20:22 -04:00
Jarrod Flesch
cad1906725 feat: extends Button and extracts ListHeader components (#7777) 2024-08-21 09:37:11 -04:00
Elliot DeNolf
988c8848e9 chore(release): v3.0.0-beta.88 [skip ci] 2024-08-20 16:41:10 -04:00
Elliot DeNolf
95a8bb0d27 feat(ui): export Banner component (#7779)
Export `Banner` component
2024-08-20 20:07:03 +00:00
Paul
9c2ccbf61a fix(ui): on Table component crashing when looking for className on admin (#7776) 2024-08-20 19:03:18 +00:00
Paul
3ee0e842a5 fix(plugin-search): not being able to override labels (#7775)
Close https://github.com/payloadcms/payload/issues/7771
2024-08-20 18:54:30 +00:00
Tylan Davis
6ec982022e fix(ui): text clipping on document header title with Segoe UI font (#7774)
## Description

Fixes text clipping that occurs on the document header title when Segoe
UI font is used in the admin panel.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-20 17:35:06 +00:00
Elliot DeNolf
4f71df79fc ci: update codeowners file 2024-08-20 09:51:59 -04:00
Elliot DeNolf
227d2e0502 chore(release): v3.0.0-beta.87 [skip ci] 2024-08-20 09:10:00 -04:00
Jacob Fletcher
3a91deb0a4 feat: threads field config through components and strictly types props (#7754)
## Description

Threads the field config to all "field subcomponents" through props,
i.e. field label, description, error, etc. This way, the field config
that controls any particular component is easily accessible and strongly
typed, i.e. `props.field.maxLength`. This is true for both server and
client components, whose server-side props are now also contextually
typed. This behavior was temporarily removed in #7474 due to bloating
HTML, but has since been resolved in #7620. This PR also makes
significant improvements to component types by exporting explicit types
for _every component of every field_, each with its own client/server
variation. Now, a custom component can look something like this:

```tsx
import type { TextFieldLabelServerComponent } from 'payload'

import React from 'react'

export const CustomLabel: TextFieldLabelServerComponent = (props) => {
  return (
    <div>{`The max length of this field is: ${props?.field?.maxLength}`}</div>
  )
}
```

The following types are now available:

```ts
import type {
  TextFieldClientComponent,
  TextFieldServerComponent,
  TextFieldLabelClientComponent,
  TextFieldLabelServerComponent,
  TextFieldDescriptionClientComponent,
  TextFieldDescriptionServerComponent,
  TextFieldErrorClientComponent,
  TextFieldErrorServerComponent,
  // ...and so one for each field
} from 'payload'
```

BREAKING CHANGES:

In order to strictly type these components, a few breaking changes have
been made _solely to type definitions_. This only effects you if you are
heavily using custom components.

Old
```ts
import type { ErrorComponent, LabelComponent, DescriptionComponent } from 'payload'
```

New:
```ts
import type {
  FieldErrorClientComponent,
  FieldErrorServerComponent,
  FieldLabelClientComponent,
  FieldLabelServerComponent,
  FieldDescriptionClientComponent,
  FieldDescriptionServerComponent,
  // Note: these are the generic, underlying types of the more stricter types described above ^
  // For example, you should use the type that is explicit for your particular field and environment
  // i.e. `TextFieldLabelClientComponent` and not simply `FieldLabelClientComponent`
} from 'payload'
```

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
2024-08-20 04:25:10 +00:00
Jacob Fletcher
9e6e8357b8 docs: admin metadata (#7767) 2024-08-19 23:07:10 -04:00
Elliot DeNolf
0dd17e6347 chore(release): v3.0.0-beta.86 [skip ci] 2024-08-19 21:27:26 -04:00
James Mikrut
17312d9f90 Fix/postgres migrate args (#7766)
## Description

Replaces the export of `MigrateUpArgs` and `MigrateDownArgs` from
`db-postgres`
2024-08-20 01:00:13 +00:00
Paul
0c36cbde73 fix: type generation for block fields with no blocks (#7765) 2024-08-19 22:34:19 +00:00
Alessio Gravili
ebd43c7763 feat: pre-compile ui and richtext-lexical with react compiler (#7688)
This noticeably improves performance in the admin panel, for example
when there are multiple richtext editors on one page (& likely
performance in other areas too, though I mainly tested rich text).

The babel plugin currently only optimizes files with a 'use client'
directive at the top - thus we have to make sure to add use client
wherever possible, even if it's imported by a parent client component.

There's one single component that broke when it was compiled using the
React compiler (it stopped being reactive and failed one of our admin
e2e tests):
150808f608
opting out of it completely fixed that issue

Fixes https://github.com/payloadcms/payload/issues/7366
2024-08-19 17:31:36 -04:00
Jarrod Flesch
adf2f31178 fix: useField incorrect initialization of errorMessage on update (#7756) 2024-08-19 17:00:39 -04:00
Elliot DeNolf
beadc0158e chore(release): v3.0.0-beta.85 [skip ci] 2024-08-19 16:41:30 -04:00
Dan Ribbens
bb09da08c2 fix: migrate error on windows (#7759)
handle windows compatible file names when reading migrations

---------

Co-authored-by: Alessio Gravili <alessio@gravili.de>
2024-08-19 15:46:40 -04:00
Paul
ab09f2aff5 fix(ui): tabs preferences not being saved (#7761) 2024-08-19 19:25:31 +00:00
Alessio Gravili
2f3829083d fix(richtext-lexical): richtext editor features overriding other editor features props if multiple editors in one document (#7758)
Example: richText editor 1 and 2 both have UploadFeature. richText
editor 1 calls UploadFeature() with custom fields, richText editor 2
calls UploadFeature() with NO custom fields. Before this PR, richText
editor 1 would not have had any custom fields, as richText editor 2 will
override the feature object (specifically its props).
2024-08-19 12:01:31 -04:00
Jacob Fletcher
a526c7becd feat: custom view and document-level metadata (#7716) 2024-08-18 23:22:38 -04:00
Elliot DeNolf
2835e1d709 feat: abstract postgres base adapter (#7732)
Abstracts Postgres base adapter in order to allow future postgres-based
adapters.
2024-08-16 18:51:39 -04:00
Alessio Gravili
4808e31276 chore: fix dev:postgres command, disable dependency checker in core dev (#7733) 2024-08-16 19:46:49 +00:00
Elliot DeNolf
bd51fd1390 chore: re-enable husky pre-commit 2024-08-16 15:22:56 -04:00
Tylan Davis
b3b1cd2c23 fix: prevent vertical scrolling on tab fields (#7729)
## Description

Prevents tabs fields from displaying vertical scrollbars in certain
cases with different viewports/zoom levels.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-16 15:13:12 -04:00
Alessio Gravili
d67f674160 chore: update all templates (#7731)
Old blank templates had invalid pregenerated importMap. Would error for
fresh apps from create-payload-app. And website was on an old version
riddled with bugs
2024-08-16 18:59:58 +00:00
Alessio Gravili
6eb4438dc8 fix(ui): relationship cells in table from list drawer not shown (#7730)
Also a nice performance improvement. The list drawer was previously
fetching data with depth 1. This will cause the relationship cell to
break, as it expects the relationship data to be a string/number, not a
populated object with the id inside.

Now, it fetches using depth 0 - same as the normal list view
2024-08-16 18:44:59 +00:00
Elliot DeNolf
2d6e7f8a37 chore(release): v3.0.0-beta.84 [skip ci] 2024-08-16 13:56:50 -04:00
James Mikrut
3d37d74c6e fix: adds default drizzle package exports (#7728)
Default exports were missing for Drizzle package.
2024-08-16 13:40:41 -04:00
James Mikrut
de19822ed4 fix: ensures user is accurate in useAuth (#7727)
## Description

Fixes an issue where the `user` could be out of date after logging in.
2024-08-16 17:10:32 +00:00
Tylan Davis
2b2bcb5264 fix: corrects logout icon styling (#7726)
## Description

before: 
<img width="89" alt="Screenshot 2024-08-16 at 12 43 56 PM"
src="https://github.com/user-attachments/assets/1052cfdb-6dde-4b65-a4c0-e37a909dac34">

after:
<img width="48" alt="Screenshot 2024-08-16 at 12 43 35 PM"
src="https://github.com/user-attachments/assets/aa4d6aed-4a78-4b17-a209-df3618b273a1">

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Bug fix (non-breaking change which fixes an issue)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-16 17:01:42 +00:00
Elliot DeNolf
e9b01e6d9f chore(release): v3.0.0-beta.83 [skip ci] 2024-08-16 12:36:30 -04:00
Alessio Gravili
b0a760193e fix: type RelationshipFieldClient typed incorrectly (#7725) 2024-08-16 12:35:05 -04:00
Jarrod Flesch
95569e44e4 fix: login with username server validations (#7719) 2024-08-16 12:07:53 -04:00
Paul
11816080a6 fix: bin script error when running on linux (#7721)
Fixes https://github.com/payloadcms/payload/issues/7717
2024-08-16 10:07:03 -06:00
Paul
3a86822f0a fix(ui): ensure that aborting Autosave always has a valid reason for the controller - fixes uncaught error (#7723) 2024-08-16 16:04:32 +00:00
Jarrod Flesch
6f8604e18c fix: ensures users cannot be created without confirming pw (#7583) 2024-08-16 11:44:27 -04:00
Tylan Davis
aec3f5e308 chore: admin panel style updates (#7720)
## Description

Minor admin panel style updates:
- Adjusts document header title spacing.
- Makes toast notifications more apparent.
- Adjusts alignment of create new button.
- Improves chevron icon.

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

<!-- Please delete options that are not relevant. -->

- [x] Chore (non-breaking change which does not add functionality)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-16 15:23:08 +00:00
Jarrod Flesch
e0a5de6730 chore: extends dropzone and upload field (#7713)
## Description

Tweaks to Upload and Dropzone components, making them more extendable.

- Dropzone adds prop to allow multiple files
- Upload correctly sets url if state is initialized with a File

- [x] I have read and understand the
[CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md)
document in this repository.

## Type of change

- [x] Chore (non-breaking change which does not add functionality)

## Checklist:

- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] Existing test suite passes locally with my changes
- [ ] I have made corresponding changes to the documentation
2024-08-16 09:38:41 -04:00
Paul
5eee49da9a feat(plugin-seo): pass req through to generate functions (#7711)
Closes https://github.com/payloadcms/payload/issues/7708
2024-08-15 23:09:08 +00:00
dependabot[bot]
b7d01dec70 chore(deps): bump pnpm/action-setup from 3 to 4 in /.github/actions/setup in the github_actions group across 1 directory (#7687)
Bumps the github_actions group with 1 update in the
/.github/actions/setup directory:
[pnpm/action-setup](https://github.com/pnpm/action-setup).

Updates `pnpm/action-setup` from 3 to 4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pnpm/action-setup/releases">pnpm/action-setup's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<p>An error is thrown if one version of pnpm is specified in the
<code>packageManager</code> field of <code>package.json</code> and a
different version is specified in the action's settings <a
href="https://redirect.github.com/pnpm/action-setup/pull/122">#122</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fe02b34f77"><code>fe02b34</code></a>
docs: bump action-setup version in README</li>
<li><a
href="bee1f099e5"><code>bee1f09</code></a>
feat: throw error when multiple versions specified (<a
href="https://redirect.github.com/pnpm/action-setup/issues/122">#122</a>)</li>
<li><a
href="ce859e384f"><code>ce859e3</code></a>
refactor: replace <code>fs-extra</code> with Node.js built-in fs methods
(<a
href="https://redirect.github.com/pnpm/action-setup/issues/120">#120</a>)</li>
<li><a
href="2ab6dce4f5"><code>2ab6dce</code></a>
docs(README): fix link to LICENSE</li>
<li><a
href="e280758d01"><code>e280758</code></a>
docs(README): update dependency versions (<a
href="https://redirect.github.com/pnpm/action-setup/issues/117">#117</a>)</li>
<li><a
href="129abb77bf"><code>129abb7</code></a>
Bump undici from 5.28.2 to 5.28.3 (<a
href="https://redirect.github.com/pnpm/action-setup/issues/115">#115</a>)</li>
<li>See full diff in <a
href="https://github.com/pnpm/action-setup/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=pnpm/action-setup&package-manager=github_actions&previous-version=3&new-version=4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-15 15:57:33 -04:00
Elliot DeNolf
0618130fe3 chore(release): v3.0.0-beta.82 [skip ci] 2024-08-15 15:46:12 -04:00
Jacob Fletcher
cd245793fc chore(ui): resolves self-referencing imports (#7707) 2024-08-15 14:27:19 -04:00
Dan Ribbens
3a6c75a1a3 fix: importMap windows paths (#7706)
Fix windows compatibility for importMap generation
2024-08-15 17:57:48 +00:00
Alessio Gravili
5a683b6947 chore: fix issues running postgres in our dev test suites (#7704) 2024-08-15 16:58:00 +00:00
Elliot DeNolf
9b27f03e61 feat(eslint): no-imports-from-self rule (#7691)
New rule to prevent a package from importing from itself.
2024-08-15 09:02:29 -04:00
Elliot DeNolf
89746ebe09 chore(eslint): update relative import regex to handle more scenarios (#7690)
Updates no-relative-monorepo-import regex to handle more scenarios:

 Scenarios that will violate the rule:
```ts
import { something } from '../../payload/src/utilities/some-util.js'
import { something } from '../../../packages/payload/src/utilities/some-util.js'
import { something } from 'packages/payload/src/utilities/some-util.js'
```
2024-08-14 23:57:22 -04:00
dependabot[bot]
eacf2030cd chore(deps): bump the github_actions group with 2 updates (#7686)
Bumps the github_actions group with 2 updates:
[pnpm/action-setup](https://github.com/pnpm/action-setup) and
[supercharge/mongodb-github-action](https://github.com/supercharge/mongodb-github-action).

Updates `pnpm/action-setup` from 3 to 4
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/pnpm/action-setup/releases">pnpm/action-setup's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.0</h2>
<p>An error is thrown if one version of pnpm is specified in the
<code>packageManager</code> field of <code>package.json</code> and a
different version is specified in the action's settings <a
href="https://redirect.github.com/pnpm/action-setup/pull/122">#122</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fe02b34f77"><code>fe02b34</code></a>
docs: bump action-setup version in README</li>
<li><a
href="bee1f099e5"><code>bee1f09</code></a>
feat: throw error when multiple versions specified (<a
href="https://redirect.github.com/pnpm/action-setup/issues/122">#122</a>)</li>
<li><a
href="ce859e384f"><code>ce859e3</code></a>
refactor: replace <code>fs-extra</code> with Node.js built-in fs methods
(<a
href="https://redirect.github.com/pnpm/action-setup/issues/120">#120</a>)</li>
<li><a
href="2ab6dce4f5"><code>2ab6dce</code></a>
docs(README): fix link to LICENSE</li>
<li><a
href="e280758d01"><code>e280758</code></a>
docs(README): update dependency versions (<a
href="https://redirect.github.com/pnpm/action-setup/issues/117">#117</a>)</li>
<li><a
href="129abb77bf"><code>129abb7</code></a>
Bump undici from 5.28.2 to 5.28.3 (<a
href="https://redirect.github.com/pnpm/action-setup/issues/115">#115</a>)</li>
<li>See full diff in <a
href="https://github.com/pnpm/action-setup/compare/v3...v4">compare
view</a></li>
</ul>
</details>
<br />

Updates `supercharge/mongodb-github-action` from 1.10.0 to 1.11.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/releases">supercharge/mongodb-github-action's
releases</a>.</em></p>
<blockquote>
<p>Release 1.11.0</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/supercharge/mongodb-github-action/blob/main/CHANGELOG.md">supercharge/mongodb-github-action's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/superchargejs/mongodb-github-action/compare/v1.10.0...v1.11.0">1.11.0</a>
- 2024-05-22</h2>
<h3>Added</h3>
<ul>
<li>added <code>mongodb-container-name</code> input: this option allows
you to define the Docker container name</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>use the <code>mongo</code> command to interact with MongoDB versions
4.x or lower. Previously, we only checked for MongoDB 4 and would use
<code>mongosh</code> for MongoDB 3 (and lower). <a
href="https://redirect.github.com/supercharge/mongodb-github-action/pull/61">Thanks
to Aravind!</a></li>
</ul>
<h3>Updated</h3>
<ul>
<li>bump dependencies</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="5a87bd81f8"><code>5a87bd8</code></a>
prepare changelog for 1.11.0</li>
<li><a
href="7c12fc679c"><code>7c12fc6</code></a>
update readme</li>
<li><a
href="ad73029553"><code>ad73029</code></a>
bump mongoose dependency</li>
<li><a
href="268fb2c93c"><code>268fb2c</code></a>
Merge pull request <a
href="https://redirect.github.com/supercharge/mongodb-github-action/issues/61">#61</a>
from aravindnc/main</li>
<li><a
href="12b898a9c8"><code>12b898a</code></a>
Fix to use mongo client if MongoDB verison is less than or equal to
4.</li>
<li><a
href="b8277548e0"><code>b827754</code></a>
wait 20 seconds</li>
<li><a
href="5f37c5fb42"><code>5f37c5f</code></a>
revert ESLint to 8.x</li>
<li><a
href="fcc7443a6b"><code>fcc7443</code></a>
bump verions</li>
<li><a
href="fde299bc70"><code>fde299b</code></a>
bump deps</li>
<li><a
href="9ceda80ede"><code>9ceda80</code></a>
bump versions of GitHub Actions</li>
<li>Additional commits viewable in <a
href="https://github.com/supercharge/mongodb-github-action/compare/1.10.0...1.11.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-14 21:56:24 -04:00
Elliot DeNolf
86428539f5 chore: add packageManager property for dependabot 2024-08-14 21:30:53 -04:00
559 changed files with 8158 additions and 2698 deletions

21
.github/CODEOWNERS vendored
View File

@@ -1,24 +1,23 @@
# Order matters. The last matching pattern takes precedence.
# Approvals are not required currently but may be enabled in the future.
### Package Exports ###
/**/exports/ @denolfe @jmikrut
/**/exports/ @denolfe @jmikrut @DanRibbens
### Packages ###
/packages/richtext-*/ @AlessioGr
/packages/plugin-cloud*/ @denolfe
/packages/email-*/ @denolfe
/packages/storage-*/ @denolfe
/packages/create-payload-app/ @denolfe
/packages/eslint-*/ @denolfe
/packages/plugin-cloud*/src/ @denolfe
/packages/email-*/src/ @denolfe
/packages/storage-*/src/ @denolfe
/packages/create-payload-app/src/ @denolfe
/packages/eslint-*/ @denolfe @AlessioGr
### Templates ###
/templates/ @jacobsfletch @denolfe
/templates/_data/ @denolfe
/templates/_template/ @denolfe
### Build Files ###
/**/package.json @denolfe
/tsconfig.json @denolfe
/**/tsconfig*.json @denolfe
/jest.config.js @denolfe
/**/jest.config.js @denolfe
@@ -26,5 +25,5 @@
/package.json @denolfe
/scripts/ @denolfe
/.husky/ @denolfe
/.vscode/ @denolfe
/.vscode/ @denolfe @AlessioGr
/.github/ @denolfe

View File

@@ -25,7 +25,7 @@ runs:
node-version: ${{ inputs.node-version }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
run_install: false

View File

@@ -18,7 +18,7 @@ concurrency:
env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry
@@ -74,7 +74,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -120,7 +120,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -167,7 +167,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -207,6 +207,9 @@ jobs:
AWS_REGION: us-east-1
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 25
# https://github.com/actions/virtual-environments/issues/1187
- name: tune linux network
run: sudo ethtool -K eth0 tx off rx off
@@ -217,17 +220,12 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
- name: Restore build
uses: actions/cache@v4
timeout-minutes: 10
with:
path: ./*
key: ${{ github.sha }}-${{ github.run_number }}
- run: pnpm install
- name: Start LocalStack
run: pnpm docker:start
@@ -332,7 +330,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -371,7 +369,7 @@ jobs:
run: pnpm exec playwright install-deps chromium
- name: E2E Tests
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e ${{ matrix.suite }}
run: PLAYWRIGHT_JSON_OUTPUT_NAME=results_${{ matrix.suite }}.json pnpm test:e2e:prod:ci ${{ matrix.suite }}
env:
PLAYWRIGHT_JSON_OUTPUT_NAME: results_${{ matrix.suite }}.json
NEXT_TELEMETRY_DISABLED: 1
@@ -407,7 +405,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -420,7 +418,7 @@ jobs:
key: ${{ github.sha }}-${{ github.run_number }}
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: 6.0
@@ -451,7 +449,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false
@@ -492,7 +490,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0
uses: supercharge/mongodb-github-action@1.11.0
with:
mongodb-version: 6.0
@@ -520,7 +518,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v3
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
run_install: false

View File

@@ -7,7 +7,7 @@ on:
env:
NODE_VERSION: 18.20.2
PNPM_VERSION: 9.7.0
PNPM_VERSION: 9.7.1
DO_NOT_TRACK: 1 # Disable Turbopack telemetry
NEXT_TELEMETRY_DISABLED: 1 # Disable Next telemetry

5
.gitignore vendored
View File

@@ -5,7 +5,7 @@ dist
!/.idea/runConfigurations
!/.idea/payload.iml
test/packed
test-results
.devcontainer
.localstack
@@ -306,3 +306,6 @@ test/live-preview/app/(payload)/admin/importMap.js
/test/live-preview/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
/test/app/(payload)/admin/importMap.js
test/pnpm-lock.yaml

View File

@@ -0,0 +1 @@
pnpm run lint-staged --quiet

View File

@@ -31,12 +31,12 @@ The following options are available:
| **`hidden`** | Set to true or a function, called with the current user, returning true to exclude this Collection from navigation and admin routing. |
| **`hooks`** | Admin-specific hooks for this Collection. [More details](../hooks/collections). |
| **`useAsTitle`** | Specify a top-level field to use for a document title throughout the Admin Panel. If no field is defined, the ID of the document is used as the title. |
| **`description`** | Text or React component to display below the Collection label in the List View to give editors more information. |
| **`description`** | Text to display below the Collection label in the List View to give editors more information. Alternatively, you can use the `admin.components.Description` to render a React component. [More details](#components). |
| **`defaultColumns`** | Array of field names that correspond to which columns to show by default in this Collection's List View. |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this Collection. |
| **`enableRichTextLink`** | The [Rich Text](../fields/rich-text) field features a `Link` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| **`enableRichTextRelationship`** | The [Rich Text](../fields/rich-text) field features a `Relationship` element which allows for users to automatically reference related documents within their rich text. Set to `true` by default. |
| **`meta`** | Metadata overrides to apply to the Admin Panel. Included properties are `description` and `openGraph`. |
| **`meta`** | Page metadata overrides to apply to this Collection within the Admin Panel. [More details](./metadata). |
| **`preview`** | Function to generate preview URLs within the Admin Panel that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`components`** | Swap in your own React components to be used within this Collection. [More details](#components). |
@@ -69,7 +69,8 @@ The following options are available:
| **`beforeList`** | An array of components to inject _before_ the built-in List View |
| **`beforeListTable`** | An array of components to inject _before_ the built-in List View's table |
| **`afterList`** | An array of components to inject _after_ the built-in List View |
| **`afterListTable`** | An array of components to inject _after_ the built-in List View's table |
| **`afterListTable`** | An array of components to inject _after_ the built-in List View's table
| **`Description`** | A component to render below the Collection label in the List View. An alternative to the `admin.description` property. |
| **`edit.SaveButton`** | Replace the default Save Button with a Custom Component. [Drafts](../versions/drafts) must be disabled. |
| **`edit.SaveDraftButton`** | Replace the default Save Draft Button with a Custom Component. [Drafts](../versions/drafts) must be enabled and autosave must be disabled. |
| **`edit.PublishButton`** | Replace the default Publish Button with a Custom Component. [Drafts](../versions/drafts) must be enabled. |

View File

@@ -347,31 +347,13 @@ Custom Label Components receive all [Field Component](#the-field-component) prop
#### TypeScript
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview). The convention is to append `LabelComponent` to the type of field, i.e. `TextFieldLabelComponent`.
When building Custom Label Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Label Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `LabelServerComponent` or `LabelClientComponent` to the type of field, i.e. `TextFieldLabelClientComponent`.
```tsx
import type {
ArrayFieldLabelComponent,
BlocksFieldLabelComponent,
CheckboxFieldLabelComponent,
CodeFieldLabelComponent,
CollapsibleFieldLabelComponent,
DateFieldLabelComponent,
EmailFieldLabelComponent,
GroupFieldLabelComponent,
HiddenFieldLabelComponent,
JSONFieldLabelComponent,
NumberFieldLabelComponent,
PointFieldLabelComponent,
RadioFieldLabelComponent,
RelationshipFieldLabelComponent,
RichTextFieldLabelComponent,
RowFieldLabelComponent,
SelectFieldLabelComponent,
TabsFieldLabelComponent,
TextFieldLabelComponent,
TextareaFieldLabelComponent,
UploadFieldLabelComponent
TextFieldLabelServerComponent,
TextFieldLabelClientComponent,
// And so on for each Field Type
} from 'payload'
```
@@ -410,31 +392,13 @@ Custom Error Components receive all [Field Component](#the-field-component) prop
#### TypeScript
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview). The convention is to append `ErrorComponent` to the type of field, i.e. `TextFieldErrorComponent`.
When building Custom Error Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Error Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `ErrorServerComponent` or `ErrorClientComponent` to the type of field, i.e. `TextFieldErrorClientComponent`.
```tsx
import type {
ArrayFieldErrorComponent,
BlocksFieldErrorComponent,
CheckboxFieldErrorComponent,
CodeFieldErrorComponent,
CollapsibleFieldErrorComponent,
DateFieldErrorComponent,
EmailFieldErrorComponent,
GroupFieldErrorComponent,
HiddenFieldErrorComponent,
JSONFieldErrorComponent,
NumberFieldErrorComponent,
PointFieldErrorComponent,
RadioFieldErrorComponent,
RelationshipFieldErrorComponent,
RichTextFieldErrorComponent,
RowFieldErrorComponent,
SelectFieldErrorComponent,
TabsFieldErrorComponent,
TextFieldErrorComponent,
TextareaFieldErrorComponent,
UploadFieldErrorComponent
TextFieldErrorServerComponent,
TextFieldErrorClientComponent,
// And so on for each Field Type
} from 'payload'
```
@@ -544,31 +508,13 @@ Custom Description Components receive all [Field Component](#the-field-component
#### TypeScript
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview). The convention is to append `DescriptionComponent` to the type of field, i.e. `TextFieldDescriptionComponent`.
When building Custom Description Components, you can import the component props to ensure type safety in your component. There is an explicit type for the Description Component, one for every [Field Type](../fields/overview) and server/client environment. The convention is to append `DescriptionServerComponent` or `DescriptionClientComponent` to the type of field, i.e. `TextFieldDescriptionClientComponent`.
```tsx
import type {
ArrayFieldDescriptionComponent,
BlocksFieldDescriptionComponent,
CheckboxFieldDescriptionComponent,
CodeFieldDescriptionComponent,
CollapsibleFieldDescriptionComponent,
DateFieldDescriptionComponent,
EmailFieldDescriptionComponent,
GroupFieldDescriptionComponent,
HiddenFieldDescriptionComponent,
JSONFieldDescriptionComponent,
NumberFieldDescriptionComponent,
PointFieldDescriptionComponent,
RadioFieldDescriptionComponent,
RelationshipFieldDescriptionComponent,
RichTextFieldDescriptionComponent,
RowFieldDescriptionComponent,
SelectFieldDescriptionComponent,
TabsFieldDescriptionComponent,
TextFieldDescriptionComponent,
TextareaFieldDescriptionComponent,
UploadFieldDescriptionComponent
TextFieldDescriptionServerComponent,
TextFieldDescriptionClientComponent,
// And so on for each Field Type
} from 'payload'
```

View File

@@ -33,7 +33,7 @@ The following options are available:
| **`preview`** | Function to generate a preview URL within the Admin Panel for this Global that can point to your app. [More details](#preview). |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`hideAPIURL`** | Hides the "API URL" meta field while editing documents within this collection. |
| **`meta`** | Metadata overrides to apply to the Admin Panel. Included properties are `description` and `openGraph`. |
| **`meta`** | Page metadata overrides to apply to this Global within the Admin Panel. [More details](./metadata). |
### Components

216
docs/admin/metadata.mdx Normal file
View File

@@ -0,0 +1,216 @@
---
title: Page Metadata
label: Metadata
order: 70
desc: Customize the metadata of your pages within the Admin Panel
keywords: admin, components, custom, documentation, Content Management System, cms, headless, javascript, node, react, nextjs
---
Every page within the Admin Panel automatically receives dynamic, auto-generated metadata derived from live document data, the user's current locale, and more, without any additional configuration. This includes the page title, description, og:image and everything in between. Metadata is fully configurable at the root level and cascades down to individual collections, documents, and custom views, allowing for the ability to control metadata on any page with high precision.
Within the Admin Panel, metadata can be customized at the following levels:
- [Root Metadata](#root-metadata)
- [Collection Metadata](#collection-metadata)
- [Global Metadata](#global-metadata)
- [View Metadata](#view-metadata)
All of these types of metadata share a similar structure, with a few key differences on the Root level. To customize metadata, consult the list of available scopes. Determine the scope that corresponds to what you are trying to accomplish, then author your metadata within the Payload Config accordingly.
## Root Metadata
Root Metadata is the metadata that is applied to all pages within the Admin Panel. This is where you can control things like the suffix appended onto each page's title, the favicon displayed in the browser's tab, and the Open Graph data that is used when sharing the Admin Panel on social media.
To customize Root Metadata, use the `admin.meta` key in your Payload Config:
```ts
{
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Admin Panel',
description: 'The best admin panel in the world',
icons: [
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
},
],
},
},
}
```
The following options are available for Root Metadata:
| Key | Type | Description |
| --- | --- | --- |
| **`title`** | `string` | The title of the Admin Panel. |
| **`description`** | `string` | The description of the Admin Panel. |
| **`defaultOGImageType`** | `dynamic` (default), `static`, or `off` | The type of default OG image to use. If set to `dynamic`, Payload will use Next.js image generation to create an image with the title of the page. If set to `static`, Payload will use the `defaultOGImage` URL. If set to `off`, Payload will not generate an OG image. |
| **`icons`** | `IconConfig[]` | An array of icon objects. [More details](#icons) |
| **`keywords`** | `string` | A comma-separated list of keywords to include in the metadata of the Admin Panel. |
| **`openGraph`** | `OpenGraphConfig` | An object containing Open Graph metadata. [More details](#open-graph) |
| **`titleSuffix`** | `string` | A suffix to append to the end of the title of every page. Defaults to "- Payload". |
<Banner type="success">
<strong>Reminder:</strong>
These are the _root-level_ options for the Admin Panel. You can also customize [Collection Metadata](./collections), [Global Metadata](./globals), and [Document Metadata](./documents) in their respective configs.
</Banner>
### Icons
The Icons Config corresponds to the `<link>` tags that are used to specify icons for the Admin Panel. The `icons` key is an array of objects, each of which represents an individual icon. Icons are differentiated from one another by their `rel` attribute, which specifies the relationship between the document and the icon.
The most common icon type is the favicon, which is displayed in the browser tab. This is specified by the `rel` attribute `icon`. Other common icon types include `apple-touch-icon`, which is used by Apple devices when the Admin Panel is saved to the home screen, and `mask-icon`, which is used by Safari to mask the Admin Panel icon.
To customize icons, use the `icons` key within the `admin.meta` object in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
icons: [
// highlight-end
{
rel: 'icon',
type: 'image/png',
href: '/favicon.png',
},
{
rel: 'apple-touch-icon',
type: 'image/png',
href: '/apple-touch-icon.png',
},
],
},
},
}
```
The following options are available for Icons:
| Key | Type | Description |
| --- | --- | --- |
| **`rel`** | `string` | The HTML `rel` attribute of the icon. |
| **`type`** | `string` | The MIME type of the icon. |
| **`color`** | `string` | The color of the icon. |
| **`fetchPriority`** | `string` | The [fetch priority](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/fetchPriority) of the icon. |
| **`media`** | `string` | The [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries) of the icon. |
| **`sizes`** | `string` | The [sizes](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes) of the icon. |
| **`url`** | `string` | The URL pointing the resource of the icon. |
### Open Graph
Open Graph metadata is a set of tags that are used to control how URLs are displayed when shared on social media platforms. Open Graph metadata is automatically generated by Payload, but can be customized at the Root level.
To customize Open Graph metadata, use the `openGraph` key within the `admin.meta` object in your Payload Config:
```ts
{
// ...
admin: {
meta: {
// highlight-start
openGraph: {
// highlight-end
description: 'The best admin panel in the world',
images: [
{
url: 'https://example.com/image.jpg',
width: 800,
height: 600,
},
],
siteName: 'Payload',
title: 'My Admin Panel',
},
},
},
}
```
The following options are available for Open Graph Metadata:
| Key | Type | Description |
| --- | --- | --- |
| **`description`** | `string` | The description of the Admin Panel. |
| **`images`** | `OGImageConfig | OGImageConfig[]` | An array of image objects. |
| **`siteName`** | `string` | The name of the site. |
| **`title`** | `string` | The title of the Admin Panel. |
## Collection Metadata
Collection Metadata is the metadata that is applied to all pages within any given Collection within the Admin Panel. This metadata is used to customize the title and description of all views within any given Collection, unless overridden by the view itself.
To customize Collection Metadata, use the `admin.meta` key within your Collection Config:
```ts
import { CollectionConfig } from 'payload'
export const MyCollection: CollectionConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Collection',
description: 'The best collection in the world',
},
},
}
```
The Collection Meta config has the same options as the [Root Metadata](#root-metadata) config.
## Global Metadata
Global Metadata is the metadata that is applied to all pages within any given Global within the Admin Panel. This metadata is used to customize the title and description of all views within any given Global, unless overridden by the view itself.
To customize Global Metadata, use the `admin.meta` key within your Global Config:
```ts
import { GlobalConfig } from 'payload'
export const MyGlobal: GlobalConfig = {
// ...
admin: {
// highlight-start
meta: {
// highlight-end
title: 'My Global',
description: 'The best
},
},
}
```
The Global Meta config has the same options as the [Root Metadata](#root-metadata) config.
## View Metadata
View Metadata is the metadata that is applied to specific [Views](./views) within the Admin Panel. This metadata is used to customize the title and description of a specific view, overriding any metadata set at the [Root](#root-metadata), [Collection](#collection-metadata), or [Global](#global-metadata) level.
To customize View Metadata, use the `meta` key within your View Config:
```ts
{
// ...
admin: {
views: {
dashboard: {
// highlight-start
meta: {
// highlight-end
title: 'My Dashboard',
description: 'The best dashboard in the world',
}
},
},
},
}

View File

@@ -88,17 +88,17 @@ The following options are available:
| Option | Description |
|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `avatar` | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| `autoLogin` | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| `buildPath` | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| `components` | Component overrides that affect the entirety of the Admin Panel. [More details](./components). |
| `custom` | Any custom properties you wish to pass to the Admin Panel. |
| `dateFormat` | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| `disable` | If set to `true`, the entire Admin Panel will be disabled. |
| `livePreview` | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| `meta` | Base metadata to use for the Admin Panel. Included properties are `titleSuffix`, `icons`, and `openGraph`. Can be overridden on a per Collection or per Global basis. |
| `routes` | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| `user` | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
| **`avatar`** | Set account profile picture. Options: `gravatar`, `default` or a custom React component. |
| **`autoLogin`** | Used to automate log-in for dev and demonstration convenience. [More details](../authentication/overview). |
| **`buildPath`** | Specify an absolute path for where to store the built Admin bundle used in production. Defaults to `path.resolve(process.cwd(), 'build')`. |
| **`components`** | Component overrides that affect the entirety of the Admin Panel. [More details](./components). |
| **`custom`** | Any custom properties you wish to pass to the Admin Panel. |
| **`dateFormat`** | The date format that will be used for all dates within the Admin Panel. Any valid [date-fns](https://date-fns.org/) format pattern can be used. |
| **`disable`** | If set to `true`, the entire Admin Panel will be disabled. |
| **`livePreview`** | Enable real-time editing for instant visual feedback of your front-end application. [More details](../live-preview/overview). |
| **`meta`** | Base metadata to use for the Admin Panel. [More details](./metadata). |
| **`routes`** | Replace built-in Admin Panel routes with your own custom routes. [More details](#customizing-routes). |
| **`user`** | The `slug` of the Collection that you want to allow to login to the Admin Panel. [More details](#the-admin-user-collection). |
<Banner type="success">
<strong>Reminder:</strong>

View File

@@ -57,7 +57,8 @@ For more granular control, pass a configuration object instead. Payload exposes
| **`path`** \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
| **`exact`** | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
| **`strict`** | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
| **`sensitive`** | When true, will match if the path is case sensitive. |
| **`sensitive`** | When true, will match if the path is case sensitive.
| **`meta`** | Page metadata overrides to apply to this view within the Admin Panel. [More details](./metadata). |
_\* An asterisk denotes that a property is required._

View File

@@ -18,6 +18,7 @@ IMPORTANT: This will overwrite all slate data. We recommend doing the following
1. Take a backup of your entire database. If anything goes wrong and you do not have a backup, you are on your own and will not receive any support.
2. Make every richText field a lexical editor. This script will only convert lexical richText fields with old Slate data
3. Add the SlateToLexicalFeature (as seen below) first, and test it out by loading up the Admin Panel, to see if the migrator works as expected. You might have to build some custom converters for some fields first in order to convert custom Slate nodes. The SlateToLexicalFeature is where the converters are stored. Only fields with this feature added will be migrated.
4. If this works as expected, add the `disableHooks: true` prop everywhere you're initializing `SlateToLexicalFeature`. Example: `SlateToLexicalFeature({ disableHooks: true })`. Once you did that, you're ready to run the migration script.
```ts
import { migrateSlateToLexical } from '@payloadcms/richtext-lexical/migrate'

View File

@@ -467,10 +467,10 @@ export const ServerRenderedDescription = () => <ClientRenderedDescription />
// file: components/ClientRenderedDescription.tsx
'use client'
import React from 'react'
import type { DescriptionComponent } from 'payload'
import type { TextFieldDescriptionClientComponent } from 'payload'
import { useFieldProps, useFormFields } from '@payloadcms/ui'
export const ClientRenderedDescription: DescriptionComponent = () ={
export const ClientRenderedDescription: TextFieldDescriptionClientComponent = () ={
const { path } = useFieldProps()
const { value } = useFormFields(([fields]) => fields[path])
const customDescription = `Component description: ${path} - ${value}`
@@ -659,8 +659,8 @@ export const ClientArrayRowLabel = () => {
admin: {
components: {
views: {
Edit: {
Tab: {
edit: {
tab: {
pillLabel: '',
},
},
@@ -675,9 +675,11 @@ export const ClientArrayRowLabel = () => {
admin: {
components: {
views: {
Edit: {
Tab: {
Pill: MyPill,
edit: {
tab: {
pill: {
Component: './path/to/CustomPill.js',
},
},
},
},

View File

@@ -119,7 +119,7 @@ A function that allows you to return any meta title, including from document's c
{
// ...
seoPlugin({
generateTitle: ({ ...docInfo, doc, locale }) => `Website.com — ${doc?.title}`,
generateTitle: ({ ...docInfo, doc, locale, req }) => `Website.com — ${doc?.title}`,
})
}
```
@@ -133,7 +133,7 @@ A function that allows you to return any meta description, including from docume
{
// ...
seoPlugin({
generateDescription: ({ ...docInfo, doc, locale }) => doc?.excerpt,
generateDescription: ({ ...docInfo, doc, locale, req }) => doc?.excerpt,
})
}
```
@@ -147,7 +147,7 @@ A function that allows you to return any meta image, including from document's c
{
// ...
seoPlugin({
generateImage: ({ ...docInfo, doc, locale }) => doc?.featuredImage,
generateImage: ({ ...docInfo, doc, locale, req }) => doc?.featuredImage,
})
}
```
@@ -161,7 +161,7 @@ A function called by the search preview component to display the actual URL of y
{
// ...
seoPlugin({
generateURL: ({ ...docInfo, doc, locale }) =>
generateURL: ({ ...docInfo, doc, locale, req }) =>
`https://yoursite.com/${collection?.slug}/${doc?.slug}`,
})
}

View File

@@ -92,6 +92,7 @@ _An asterisk denotes that an option is required._
| Option | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`adminThumbnail`** | Set the way that the [Admin Panel](../admin/overview) will display thumbnails for this Collection. [More](#admin-thumbnails) |
| **`bulkUpload`** | Allow users to upload in bulk from the list view, default is true |
| **`crop`** | Set to `false` to disable the cropping tool in the [Admin Panel](../admin/overview). Crop is enabled by default. [More](#crop-and-focal-point-selector) |
| **`disableLocalStorage`** | Completely disable uploading files to disk locally. [More](#disabling-local-upload-storage) |
| **`displayPreview`** | Enable displaying preview of the uploaded file in Upload fields related to this Collection. Can be locally overridden by `displayPreview` option in Upload field. [More](/docs/fields/upload#config-options). |

View File

@@ -62,6 +62,7 @@ export const rootEslintConfig = [
'payload/no-jsx-import-statements': 'warn',
'payload/no-relative-monorepo-imports': 'error',
'payload/no-imports-from-exports-dir': 'error',
'payload/no-imports-from-self': 'error',
},
},
{

View File

@@ -23,6 +23,7 @@ export default withBundleAnalyzer(
env: {
PAYLOAD_CORE_DEV: 'true',
ROOT_DIR: path.resolve(dirname),
PAYLOAD_DISABLE_DEPENDENCY_CHECKER: 'true',
},
async redirects() {
return [

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"private": true,
"type": "module",
"scripts": {
@@ -53,12 +53,11 @@
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media/*' '**/dist/' '**/.cache/*' '**/.next/*' '**/.turbo/*' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
"clean:build": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
"clean:cache": "node ./scripts/delete-recursively.js node_modules/.cache! packages/payload/node_modules/.cache! .next/*",
"dev": "pnpm runts ./test/dev.ts",
"runts": "node --no-deprecation --import @swc-node/register/esm-register",
"dev": "tsx ./test/dev.ts",
"dev:generate-graphql-schema": "pnpm runts ./test/generateGraphQLSchema.ts",
"dev:generate-importmap": "pnpm runts ./test/generateImportMap.ts",
"dev:generate-types": "pnpm runts ./test/generateTypes.ts",
"dev:postgres": "pnpm runts ./test/dev.ts",
"dev:postgres": "cross-env PAYLOAD_DATABASE=postgres pnpm runts ./test/dev.ts",
"devsafe": "node ./scripts/delete-recursively.js '**/.next' && pnpm dev",
"docker:restart": "pnpm docker:stop --remove-orphans && pnpm docker:start",
"docker:start": "docker compose -f packages/plugin-cloud-storage/docker-compose.yml up -d",
@@ -72,6 +71,7 @@
"reinstall": "pnpm clean:all && pnpm install",
"release:alpha": "pnpm runts ./scripts/release.ts --bump prerelease --tag alpha",
"release:beta": "pnpm runts ./scripts/release.ts --bump prerelease --tag beta",
"runts": "node --no-deprecation --import @swc-node/register/esm-register",
"script:gen-templates": "pnpm runts ./scripts/generate-template-variations.ts",
"script:list-published": "pnpm runts scripts/lib/getPackageRegistryVersions.ts",
"script:pack": "pnpm runts scripts/pack-all-to-dest.ts",
@@ -81,6 +81,8 @@
"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:prod": "pnpm bf && rm -rf test/packed && rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd .. && pnpm runts ./test/runE2E.ts --prod",
"test:e2e:prod:ci": "rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd .. && 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:unit": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand",
@@ -93,13 +95,14 @@
"prettier --write",
"eslint --cache --fix"
],
"templates/website/**/*": "sh -c \"cd templates/website; pnpm install --ignore-workspace --frozen-lockfile; pnpm run lint --fix\"",
"templates/website/**/*": "sh -c \"cd templates/website; pnpm install --ignore-workspace; pnpm run lint --fix\"",
"tsconfig.json": "node scripts/reset-tsconfig.js"
},
"devDependencies": {
"@jest/globals": "29.7.0",
"@libsql/client": "0.6.2",
"@next/bundle-analyzer": "15.0.0-canary.104",
"@payloadcms/db-postgres": "workspace:*",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/eslint-plugin": "workspace:*",
"@payloadcms/live-preview-react": "workspace:*",
@@ -123,6 +126,7 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-kit": "0.23.2-df9e596",
"drizzle-orm": "0.32.1",
"escape-html": "^1.0.3",
"execa": "5.1.1",

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -595,14 +595,77 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
config: SanitizedConfig,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
const hasManyRelations = Array.isArray(field.relationTo)
let schemaToReturn: { [key: string]: any } = {}
if (field.localized && config.localization) {
schemaToReturn = {
type: config.localization.localeCodes.reduce((locales, locale) => {
let localeSchema: { [key: string]: any } = {}
if (hasManyRelations) {
localeSchema = {
...formatBaseSchema(field, buildSchemaOptions),
_id: false,
type: mongoose.Schema.Types.Mixed,
relationTo: { type: String, enum: field.relationTo },
value: {
type: mongoose.Schema.Types.Mixed,
refPath: `${field.name}.${locale}.relationTo`,
},
}
} else {
localeSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
}
}
return {
...locales,
[locale]: field.hasMany
? { type: [localeSchema], default: formatDefaultValue(field) }
: localeSchema,
}
}, {}),
localized: true,
}
} else if (hasManyRelations) {
schemaToReturn = {
...formatBaseSchema(field, buildSchemaOptions),
_id: false,
type: mongoose.Schema.Types.Mixed,
relationTo: { type: String, enum: field.relationTo },
value: {
type: mongoose.Schema.Types.Mixed,
refPath: `${field.name}.relationTo`,
},
}
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: formatDefaultValue(field),
}
}
} else {
schemaToReturn = {
...formatBaseSchema(field, buildSchemaOptions),
type: mongoose.Schema.Types.Mixed,
ref: field.relationTo,
}
if (field.hasMany) {
schemaToReturn = {
type: [schemaToReturn],
default: formatDefaultValue(field),
}
}
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: schemaToReturn,
})
},
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -32,28 +32,26 @@ import {
updateOne,
updateVersion,
} from '@payloadcms/drizzle'
import {
convertPathToJSONTraversal,
countDistinct,
createJSONQuery,
createMigration,
defaultDrizzleSnapshot,
deleteWhere,
dropDatabase,
execute,
getMigrationTemplate,
init,
insert,
requireDrizzleKit,
} from '@payloadcms/drizzle/postgres'
import { pgEnum, pgSchema, pgTable } from 'drizzle-orm/pg-core'
import { createDatabaseAdapter } from 'payload'
import type { Args, PostgresAdapter } from './types.js'
import { connect } from './connect.js'
import { countDistinct } from './countDistinct.js'
import { convertPathToJSONTraversal } from './createJSONQuery/convertPathToJSONTraversal.js'
import { createJSONQuery } from './createJSONQuery/index.js'
import { createMigration } from './createMigration.js'
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
import { deleteWhere } from './deleteWhere.js'
import { dropDatabase } from './dropDatabase.js'
import { execute } from './execute.js'
import { getMigrationTemplate } from './getMigrationTemplate.js'
import { init } from './init.js'
import { insert } from './insert.js'
import { requireDrizzleKit } from './requireDrizzleKit.js'
export type { MigrateDownArgs, MigrateUpArgs } from './types.js'
export { sql } from 'drizzle-orm'
export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter> {
const postgresIDType = args.idType || 'serial'
@@ -159,3 +157,6 @@ export function postgresAdapter(args: Args): DatabaseAdapterObj<PostgresAdapter>
init: adapter,
}
}
export type { MigrateDownArgs, MigrateUpArgs } from '@payloadcms/drizzle/postgres'
export { sql } from 'drizzle-orm'

View File

@@ -1,31 +1,14 @@
import type { Operators } from '@payloadcms/drizzle'
import type {
BuildQueryJoinAliases,
DrizzleAdapter,
TransactionPg,
} from '@payloadcms/drizzle/types'
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type {
ColumnBaseConfig,
ColumnDataType,
DrizzleConfig,
Relation,
Relations,
SQL,
} from 'drizzle-orm'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
import type {
PgColumn,
PgEnum,
PgInsertOnConflictDoUpdateConfig,
PgSchema,
PgTableWithColumns,
PgTransactionConfig,
pgEnum,
} from 'drizzle-orm/pg-core'
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
import type { Payload, PayloadRequest } from 'payload'
import type { Pool, PoolConfig, QueryResult } from 'pg'
BasePostgresAdapter,
GenericEnum,
MigrateDownArgs,
MigrateUpArgs,
PostgresDB,
} from '@payloadcms/drizzle/postgres'
import type { DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig } from 'drizzle-orm'
import type { PgSchema, PgTableFn, PgTransactionConfig } from 'drizzle-orm/pg-core'
import type { Pool, PoolConfig } from 'pg'
export type Args = {
idType?: 'serial' | 'uuid'
@@ -49,125 +32,10 @@ export type Args = {
versionsSuffix?: string
}
export type GenericColumn = PgColumn<
ColumnBaseConfig<ColumnDataType, string>,
Record<string, unknown>
>
export type GenericColumns = {
[x: string]: GenericColumn
}
export type GenericTable = PgTableWithColumns<{
columns: GenericColumns
dialect: string
name: string
schema: string
}>
export type GenericEnum = PgEnum<[string, ...string[]]>
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
export type PostgresDB = NodePgDatabase<Record<string, unknown>>
export type CountDistinct = (args: {
db: PostgresDB | TransactionPg
joins: BuildQueryJoinAliases
tableName: string
where: SQL
}) => Promise<number>
export type DeleteWhere = (args: {
db: PostgresDB | TransactionPg
tableName: string
where: SQL
}) => Promise<void>
export type DropDatabase = (args: { adapter: PostgresAdapter }) => Promise<void>
export type Execute<T> = (args: {
db?: PostgresDB | TransactionPg
drizzle?: PostgresDB
raw?: string
sql?: SQL<unknown>
}) => Promise<QueryResult<Record<string, T>>>
export type Insert = (args: {
db: PostgresDB | TransactionPg
onConflictDoUpdate?: PgInsertOnConflictDoUpdateConfig<any>
tableName: string
values: Record<string, unknown> | Record<string, unknown>[]
}) => Promise<Record<string, unknown>[]>
type PostgresDrizzleAdapter = Omit<
DrizzleAdapter,
| 'countDistinct'
| 'deleteWhere'
| 'drizzle'
| 'dropDatabase'
| 'execute'
| 'insert'
| 'operators'
| 'relations'
>
type Schema =
| {
enum: typeof pgEnum
table: PgTableFn
}
| PgSchema
export type PostgresAdapter = {
countDistinct: CountDistinct
defaultDrizzleSnapshot: DrizzleSnapshotJSON
deleteWhere: DeleteWhere
drizzle: PostgresDB
dropDatabase: DropDatabase
enums: Record<string, GenericEnum>
execute: Execute<unknown>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
*/
fieldConstraints: Record<string, Record<string, string>>
idType: Args['idType']
initializing: Promise<void>
insert: Insert
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
pgSchema?: Schema
pool: Pool
poolOptions: Args['pool']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schemaName?: Args['schemaName']
sessions: {
[id: string]: {
db: PostgresDB | TransactionPg
reject: () => Promise<void>
resolve: () => Promise<void>
}
}
tableNameMap: Map<string, string>
tables: Record<string, GenericTable>
versionsSuffix?: string
} & PostgresDrizzleAdapter
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }
poolOptions: PoolConfig
} & BasePostgresAdapter
declare module 'payload' {
export interface DatabaseAdapter

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-sqlite",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The officially supported SQLite database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -717,7 +717,7 @@ export const traverseFields = ({
case 'upload':
if (Array.isArray(field.relationTo)) {
field.relationTo.forEach((relation) => relationships.add(relation))
} else if (field.type === 'relationship' && field.hasMany) {
} else if (field.hasMany) {
relationships.add(field.relationTo)
} else {
// simple relationships get a column on the targetTable with a foreign key to the relationTo table

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {
@@ -17,6 +17,11 @@
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./postgres": {
"import": "./src/exports/postgres.ts",
"types": "./src/exports/postgres.ts",
"default": "./src/exports/postgres.ts"
},
"./types": {
"import": "./src/types.ts",
"types": "./src/types.ts",
@@ -58,11 +63,18 @@
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"./postgres": {
"import": "./dist/exports/postgres.js",
"types": "./dist/exports/postgres.d.ts",
"default": "./dist/exports/postgres.js"
},
"./types": {
"import": "./dist/types.js",
"types": "./dist/types.d.ts"
"types": "./dist/types.d.ts",
"default": "./dist/types.js"
}
},
"main": "./dist/index.js",

View File

@@ -0,0 +1,13 @@
export { countDistinct } from '../postgres/countDistinct.js'
export { convertPathToJSONTraversal } from '../postgres/createJSONQuery/convertPathToJSONTraversal.js'
export { createJSONQuery } from '../postgres/createJSONQuery/index.js'
export { createMigration } from '../postgres/createMigration.js'
export { defaultDrizzleSnapshot } from '../postgres/defaultSnapshot.js'
export { deleteWhere } from '../postgres/deleteWhere.js'
export { dropDatabase } from '../postgres/dropDatabase.js'
export { execute } from '../postgres/execute.js'
export { getMigrationTemplate } from '../postgres/getMigrationTemplate.js'
export { init } from '../postgres/init.js'
export { insert } from '../postgres/insert.js'
export { requireDrizzleKit } from '../postgres/requireDrizzleKit.js'
export * from '../postgres/types.js'

View File

@@ -1,4 +1,3 @@
/* eslint-disable no-param-reassign */
import type { Field } from 'payload'
import { fieldAffectsData, tabHasName } from 'payload/shared'
@@ -34,8 +33,9 @@ export const traverseFields = ({
// handle simple relationship
if (
depth > 0 &&
(field.type === 'upload' ||
(field.type === 'relationship' && !field.hasMany && typeof field.relationTo === 'string'))
(field.type === 'upload' || field.type === 'relationship') &&
!field.hasMany &&
typeof field.relationTo === 'string'
) {
if (field.localized) {
_locales.with[`${path}${field.name}`] = true

View File

@@ -1,12 +1,12 @@
import type { ChainedMethods, TransactionPg } from '@payloadcms/drizzle/types'
import { chainMethods } from '@payloadcms/drizzle'
import { sql } from 'drizzle-orm'
import type { CountDistinct, PostgresAdapter } from './types.js'
import type { ChainedMethods, TransactionPg } from '../types.js'
import type { BasePostgresAdapter, CountDistinct } from './types.js'
import { chainMethods } from '../find/chainMethods.js'
export const countDistinct: CountDistinct = async function countDistinct(
this: PostgresAdapter,
this: BasePostgresAdapter,
{ db, joins, tableName, where },
) {
const chainedMethods: ChainedMethods = []

View File

@@ -8,7 +8,7 @@ import { getPredefinedMigration, writeMigrationIndex } from 'payload'
import prompts from 'prompts'
import { fileURLToPath } from 'url'
import type { PostgresAdapter } from './types.js'
import type { BasePostgresAdapter } from './types.js'
import { defaultDrizzleSnapshot } from './defaultSnapshot.js'
import { getMigrationTemplate } from './getMigrationTemplate.js'
@@ -16,7 +16,7 @@ import { getMigrationTemplate } from './getMigrationTemplate.js'
const require = createRequire(import.meta.url)
export const createMigration: CreateMigration = async function createMigration(
this: PostgresAdapter,
this: BasePostgresAdapter,
{ file, forceAcceptWarning, migrationName, payload },
) {
const filename = fileURLToPath(import.meta.url)

View File

@@ -1,5 +1,4 @@
import type { TransactionPg } from '@payloadcms/drizzle/types'
import type { TransactionPg } from '../types.js'
import type { DeleteWhere } from './types.js'
export const deleteWhere: DeleteWhere = async function deleteWhere({ db, tableName, where }) {

View File

@@ -1,16 +1,15 @@
import type { Init, SanitizedCollectionConfig } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { uniqueIndex } from 'drizzle-orm/pg-core'
import { buildVersionCollectionFields, buildVersionGlobalFields } from 'payload'
import toSnakeCase from 'to-snake-case'
import type { BaseExtraConfig } from './schema/build.js'
import type { PostgresAdapter } from './types.js'
import type { BaseExtraConfig, BasePostgresAdapter } from './types.js'
import { createTableName } from '../createTableName.js'
import { buildTable } from './schema/build.js'
export const init: Init = function init(this: PostgresAdapter) {
export const init: Init = function init(this: BasePostgresAdapter) {
if (this.payload.config.localization) {
this.enums.enum__locales = this.pgSchema.enum(
'_locales',

View File

@@ -1,5 +1,4 @@
import type { TransactionPg } from '@payloadcms/drizzle/types'
import type { TransactionPg } from '../types.js'
import type { Insert } from './types.js'
export const insert: Insert = async function insert({

View File

@@ -1,5 +1,6 @@
import type { RequireDrizzleKit } from '@payloadcms/drizzle/types'
import { createRequire } from 'module'
import type { RequireDrizzleKit } from '../types.js'
const require = createRequire(import.meta.url)
export const requireDrizzleKit: RequireDrizzleKit = () => require('drizzle-kit/api')

View File

@@ -4,11 +4,9 @@ import type {
IndexBuilder,
PgColumnBuilder,
PgTableWithColumns,
UniqueConstraintBuilder,
} from 'drizzle-orm/pg-core'
import type { Field } from 'payload'
import { createTableName } from '@payloadcms/drizzle'
import { relations } from 'drizzle-orm'
import {
foreignKey,
@@ -22,21 +20,22 @@ import {
} from 'drizzle-orm/pg-core'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, GenericTable, IDType, PostgresAdapter } from '../types.js'
import type {
BaseExtraConfig,
BasePostgresAdapter,
GenericColumns,
GenericTable,
IDType,
RelationMap,
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { parentIDColumnMap } from './parentIDColumnMap.js'
import { setColumnID } from './setColumnID.js'
import { traverseFields } from './traverseFields.js'
export type BaseExtraConfig = Record<
string,
(cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>
export type RelationMap = Map<string, { localized: boolean; target: string; type: 'many' | 'one' }>
type Args = {
adapter: PostgresAdapter
adapter: BasePostgresAdapter
baseColumns?: Record<string, PgColumnBuilder>
/**
* After table is created, run these functions to add extra config to the table

View File

@@ -4,9 +4,13 @@ import { numeric, serial, uuid, varchar } from 'drizzle-orm/pg-core'
import { type Field, flattenTopLevelFields } from 'payload'
import { fieldAffectsData } from 'payload/shared'
import type { IDType, PostgresAdapter } from '../types.js'
import type { BasePostgresAdapter, IDType } from '../types.js'
type Args = { adapter: PostgresAdapter; columns: Record<string, PgColumnBuilder>; fields: Field[] }
type Args = {
adapter: BasePostgresAdapter
columns: Record<string, PgColumnBuilder>
fields: Field[]
}
export const setColumnID = ({ adapter, columns, fields }: Args): IDType => {
const idField = flattenTopLevelFields(fields).find(
(field) => fieldAffectsData(field) && field.name === 'id',

View File

@@ -2,11 +2,6 @@ import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload'
import {
createTableName,
hasLocalesTable,
validateExistingBlockIsIdentical,
} from '@payloadcms/drizzle'
import { relations } from 'drizzle-orm'
import {
PgNumericBuilder,
@@ -26,9 +21,17 @@ import { InvalidConfiguration } from 'payload'
import { fieldAffectsData, optionIsObject } from 'payload/shared'
import toSnakeCase from 'to-snake-case'
import type { GenericColumns, IDType, PostgresAdapter } from '../types.js'
import type { BaseExtraConfig, RelationMap } from './build.js'
import type {
BaseExtraConfig,
BasePostgresAdapter,
GenericColumns,
IDType,
RelationMap,
} from '../types.js'
import { createTableName } from '../../createTableName.js'
import { hasLocalesTable } from '../../utilities/hasLocalesTable.js'
import { validateExistingBlockIsIdentical } from '../../utilities/validateExistingBlockIsIdentical.js'
import { buildTable } from './build.js'
import { createIndex } from './createIndex.js'
import { idToUUID } from './idToUUID.js'
@@ -36,7 +39,7 @@ import { parentIDColumnMap } from './parentIDColumnMap.js'
import { withDefault } from './withDefault.js'
type Args = {
adapter: PostgresAdapter
adapter: BasePostgresAdapter
columnPrefix?: string
columns: Record<string, PgColumnBuilder>
disableNotNull: boolean
@@ -723,7 +726,7 @@ export const traverseFields = ({
case 'upload':
if (Array.isArray(field.relationTo)) {
field.relationTo.forEach((relation) => relationships.add(relation))
} else if (field.type === 'relationship' && field.hasMany) {
} else if (field.hasMany) {
relationships.add(field.relationTo)
} else {
// simple relationships get a column on the targetTable with a foreign key to the relationTo table

View File

@@ -0,0 +1,154 @@
import type { DrizzleSnapshotJSON } from 'drizzle-kit/api'
import type {
ColumnBaseConfig,
ColumnDataType,
DrizzleConfig,
Relation,
Relations,
SQL,
} from 'drizzle-orm'
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
import type {
ForeignKeyBuilder,
IndexBuilder,
PgColumn,
PgEnum,
PgInsertOnConflictDoUpdateConfig,
PgSchema,
PgTableWithColumns,
UniqueConstraintBuilder,
pgEnum,
} from 'drizzle-orm/pg-core'
import type { PgTableFn } from 'drizzle-orm/pg-core/table'
import type { Payload, PayloadRequest } from 'payload'
import type { QueryResult } from 'pg'
import type { Operators } from '../index.js'
import type { BuildQueryJoinAliases, DrizzleAdapter, TransactionPg } from '../types.js'
export type BaseExtraConfig = Record<
string,
(cols: GenericColumns) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>
export type RelationMap = Map<string, { localized: boolean; target: string; type: 'many' | 'one' }>
export type GenericColumn = PgColumn<
ColumnBaseConfig<ColumnDataType, string>,
Record<string, unknown>
>
export type GenericColumns = {
[x: string]: GenericColumn
}
export type GenericTable = PgTableWithColumns<{
columns: GenericColumns
dialect: string
name: string
schema: string
}>
export type GenericEnum = PgEnum<[string, ...string[]]>
export type GenericRelation = Relations<string, Record<string, Relation<string>>>
export type PostgresDB = NodePgDatabase<Record<string, unknown>>
export type CountDistinct = (args: {
db: PostgresDB | TransactionPg
joins: BuildQueryJoinAliases
tableName: string
where: SQL
}) => Promise<number>
export type DeleteWhere = (args: {
db: PostgresDB | TransactionPg
tableName: string
where: SQL
}) => Promise<void>
export type DropDatabase = (args: { adapter: BasePostgresAdapter }) => Promise<void>
export type Execute<T> = (args: {
db?: PostgresDB | TransactionPg
drizzle?: PostgresDB
raw?: string
sql?: SQL<unknown>
}) => Promise<QueryResult<Record<string, T>>>
export type Insert = (args: {
db: PostgresDB | TransactionPg
onConflictDoUpdate?: PgInsertOnConflictDoUpdateConfig<any>
tableName: string
values: Record<string, unknown> | Record<string, unknown>[]
}) => Promise<Record<string, unknown>[]>
type Schema =
| {
enum: typeof pgEnum
table: PgTableFn
}
| PgSchema
export type BasePostgresAdapter = {
countDistinct: CountDistinct
defaultDrizzleSnapshot: DrizzleSnapshotJSON
deleteWhere: DeleteWhere
drizzle: PostgresDB
dropDatabase: DropDatabase
enums: Record<string, GenericEnum>
execute: Execute<unknown>
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name
* Used for returning properly formed errors from unique fields
*/
fieldConstraints: Record<string, Record<string, string>>
idType: 'serial' | 'uuid'
initializing: Promise<void>
insert: Insert
localesSuffix?: string
logger: DrizzleConfig['logger']
operators: Operators
pgSchema?: Schema
// pool: Pool
// poolOptions: Args['pool']
prodMigrations?: {
down: (args: MigrateDownArgs) => Promise<void>
name: string
up: (args: MigrateUpArgs) => Promise<void>
}[]
push: boolean
rejectInitializing: () => void
relations: Record<string, GenericRelation>
relationshipsSuffix?: string
resolveInitializing: () => void
schemaName?: string
sessions: {
[id: string]: {
db: PostgresDB | TransactionPg
reject: () => Promise<void>
resolve: () => Promise<void>
}
}
tableNameMap: Map<string, string>
tables: Record<string, GenericTable>
versionsSuffix?: string
} & PostgresDrizzleAdapter
export type PostgresDrizzleAdapter = Omit<
DrizzleAdapter,
| 'countDistinct'
| 'deleteWhere'
| 'drizzle'
| 'dropDatabase'
| 'execute'
| 'insert'
| 'operators'
| 'relations'
>
export type IDType = 'integer' | 'numeric' | 'uuid' | 'varchar'
export type MigrateUpArgs = { payload: Payload; req?: Partial<PayloadRequest> }
export type MigrateDownArgs = { payload: Payload; req?: Partial<PayloadRequest> }

View File

@@ -445,7 +445,7 @@ export const getTableColumnFromPath = ({
case 'relationship':
case 'upload': {
const newCollectionPath = pathSegments.slice(1).join('.')
if (Array.isArray(field.relationTo) || (field.type === 'relationship' && field.hasMany)) {
if (Array.isArray(field.relationTo) || field.hasMany) {
let relationshipFields
const relationTableName = `${rootTableName}${adapter.relationshipsSuffix}`
const {

View File

@@ -351,6 +351,7 @@ export const upsertRow = async <T extends Record<string, unknown> | TypeWithID>(
throw error.code === '23505'
? new ValidationError(
{
id,
errors: [
{
field: adapter.fieldConstraints[tableName][error.constraint],

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -0,0 +1,67 @@
import fs from 'fs'
import path from 'path'
/** @type {import('eslint').Rule.RuleModule} */
export const rule = {
meta: {
docs: {
description: 'Disallow a package from importing from itself',
category: 'Best Practices',
recommended: true,
},
fixable: 'code',
schema: [],
},
create(context) {
let packageName = null
return {
ImportDeclaration(node) {
const importPath = node.source.value
const pkgName = getPackageName(context, packageName)
if (pkgName && importPath.startsWith(pkgName)) {
context.report({
node,
message: `Package "${pkgName}" should not import from itself. Use relative instead.`,
})
}
},
}
},
}
export default rule
/**
* @param {import('eslint').Rule.RuleContext} context
* @param {string|undefined} packageName
*/
function getPackageName(context, packageName) {
if (packageName) {
return packageName
}
const fileName = context.getFilename()
const pkg = findNearestPackageJson(path.dirname(fileName))
if (pkg) {
return pkg.name
}
}
/**
* @param {string} startDir
*/
function findNearestPackageJson(startDir) {
let currentDir = startDir
while (currentDir !== path.dirname(currentDir)) {
// Root directory check
const pkgPath = path.join(currentDir, 'package.json')
if (fs.existsSync(pkgPath)) {
const pkgContent = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
return pkgContent
}
currentDir = path.dirname(currentDir)
}
return null
}

View File

@@ -21,7 +21,7 @@ export const rule = {
const importPath = node.source.value
// Match imports starting with any number of "../" followed by "packages/"
const regex = /^(\.\.\/)*packages\/[^/]+\/src/
const regex = /^(\.\.\/)*((?!src\b)\w+\/)+src\//
if (regex.test(importPath)) {
context.report({

View File

@@ -3,6 +3,7 @@ import noNonRetryableAssertions from './customRules/no-non-retryable-assertions.
import noRelativeMonorepoImports from './customRules/no-relative-monorepo-imports.js'
import noImportsFromExportsDir from './customRules/no-imports-from-exports-dir.js'
import noFlakyAssertions from './customRules/no-flaky-assertions.js'
import noImportsFromSelf from './customRules/no-imports-from-self.js'
/**
* @type {import('eslint').ESLint.Plugin}
@@ -13,6 +14,7 @@ const index = {
'no-non-retryable-assertions': noNonRetryableAssertions,
'no-relative-monorepo-imports': noRelativeMonorepoImports,
'no-imports-from-exports-dir': noImportsFromExportsDir,
'no-imports-from-self': noImportsFromSelf,
'no-flaky-assertions': noFlakyAssertions,
'no-wait-function': {
create: function (context) {

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node --no-deprecation
#!/usr/bin/env node
import path from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",
@@ -43,8 +43,8 @@
"dependencies": {
"graphql-scalars": "1.22.2",
"pluralize": "8.0.0",
"tsx": "4.17.0",
"ts-essentials": "7.0.3"
"ts-essentials": "7.0.3",
"tsx": "4.17.0"
},
"devDependencies": {
"@payloadcms/eslint-config": "workspace:*",

View File

@@ -307,10 +307,51 @@ export function buildMutationInputType({
...inputObjectTypeConfig,
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
}),
upload: (inputObjectTypeConfig: InputObjectTypeConfig, field: UploadField) => ({
...inputObjectTypeConfig,
[field.name]: { type: withNullableType(field, GraphQLString, forceNullable) },
}),
upload: (inputObjectTypeConfig: InputObjectTypeConfig, field: UploadField) => {
const { relationTo } = field
type PayloadGraphQLRelationshipType =
| GraphQLInputObjectType
| GraphQLList<GraphQLScalarType>
| GraphQLScalarType
let type: PayloadGraphQLRelationshipType
if (Array.isArray(relationTo)) {
const fullName = `${combineParentName(
parentName,
toWords(field.name, true),
)}RelationshipInput`
type = new GraphQLInputObjectType({
name: fullName,
fields: {
relationTo: {
type: new GraphQLEnumType({
name: `${fullName}RelationTo`,
values: relationTo.reduce(
(values, option) => ({
...values,
[formatName(option)]: {
value: option,
},
}),
{},
),
}),
},
value: { type: GraphQLJSON },
},
})
} else {
type = getCollectionIDType(
config.db.defaultIDType,
graphqlResult.collections[relationTo].config,
)
}
return {
...inputObjectTypeConfig,
[field.name]: { type: field.hasMany ? new GraphQLList(type) : type },
}
},
}
const fieldName = formatName(name)

View File

@@ -594,49 +594,164 @@ export function buildObjectType({
}),
upload: (objectTypeConfig: ObjectTypeConfig, field: UploadField) => {
const { relationTo } = field
const isRelatedToManyCollections = Array.isArray(relationTo)
const hasManyValues = field.hasMany
const relationshipName = combineParentName(parentName, toWords(field.name, true))
const uploadName = combineParentName(parentName, toWords(field.name, true))
let type
let relationToType = null
if (Array.isArray(relationTo)) {
relationToType = new GraphQLEnumType({
name: `${relationshipName}_RelationTo`,
values: relationTo.reduce(
(relations, relation) => ({
...relations,
[formatName(relation)]: {
value: relation,
},
}),
{},
),
})
const types = relationTo.map((relation) => graphqlResult.collections[relation].graphQL.type)
type = new GraphQLObjectType({
name: `${relationshipName}_Relationship`,
fields: {
relationTo: {
type: relationToType,
},
value: {
type: new GraphQLUnionType({
name: relationshipName,
resolveType(data, { req }) {
return graphqlResult.collections[data.collection].graphQL.type.name
},
types,
}),
},
},
})
} else {
;({ type } = graphqlResult.collections[relationTo].graphQL)
}
// If the relationshipType is undefined at this point,
// it can be assumed that this blockType can have a relationship
// to itself. Therefore, we set the relationshipType equal to the blockType
// that is currently being created.
const type = withNullableType(
field,
graphqlResult.collections[relationTo].graphQL.type || newlyCreatedBlockType,
forceNullable,
type = type || newlyCreatedBlockType
const relationshipArgs: {
draft?: unknown
fallbackLocale?: unknown
limit?: unknown
locale?: unknown
page?: unknown
where?: unknown
} = {}
const relationsUseDrafts = (Array.isArray(relationTo) ? relationTo : [relationTo]).some(
(relation) => graphqlResult.collections[relation].config.versions?.drafts,
)
const uploadArgs = {} as LocaleInputType
if (relationsUseDrafts) {
relationshipArgs.draft = {
type: GraphQLBoolean,
}
}
if (config.localization) {
uploadArgs.locale = {
relationshipArgs.locale = {
type: graphqlResult.types.localeInputType,
}
uploadArgs.fallbackLocale = {
relationshipArgs.fallbackLocale = {
type: graphqlResult.types.fallbackLocaleInputType,
}
}
const relatedCollectionSlug = field.relationTo
const upload = {
type,
args: uploadArgs,
extensions: { complexity: 20 },
const relationship = {
type: withNullableType(
field,
hasManyValues ? new GraphQLList(new GraphQLNonNull(type)) : type,
forceNullable,
),
args: relationshipArgs,
extensions: { complexity: 10 },
async resolve(parent, args, context: Context) {
const value = parent[field.name]
const locale = args.locale || context.req.locale
const fallbackLocale = args.fallbackLocale || context.req.fallbackLocale
const id = value
let relatedCollectionSlug = field.relationTo
const draft = Boolean(args.draft ?? context.req.query?.draft)
if (hasManyValues) {
const results = []
const resultPromises = []
const createPopulationPromise = async (relatedDoc, i) => {
let id = relatedDoc
let collectionSlug = field.relationTo
if (isRelatedToManyCollections) {
collectionSlug = relatedDoc.relationTo
id = relatedDoc.value
}
const result = await context.req.payloadDataLoader.load(
createDataloaderCacheKey({
collectionSlug: collectionSlug as string,
currentDepth: 0,
depth: 0,
docID: id,
draft,
fallbackLocale,
locale,
overrideAccess: false,
showHiddenFields: false,
transactionID: context.req.transactionID,
}),
)
if (result) {
if (isRelatedToManyCollections) {
results[i] = {
relationTo: collectionSlug,
value: {
...result,
collection: collectionSlug,
},
}
} else {
results[i] = result
}
}
}
if (value) {
value.forEach((relatedDoc, i) => {
resultPromises.push(createPopulationPromise(relatedDoc, i))
})
}
await Promise.all(resultPromises)
return results
}
let id = value
if (isRelatedToManyCollections && value) {
id = value.value
relatedCollectionSlug = value.relationTo
}
if (id) {
const relatedDocument = await context.req.payloadDataLoader.load(
createDataloaderCacheKey({
collectionSlug: relatedCollectionSlug,
collectionSlug: relatedCollectionSlug as string,
currentDepth: 0,
depth: 0,
docID: id,
@@ -649,26 +764,30 @@ export function buildObjectType({
}),
)
return relatedDocument || null
if (relatedDocument) {
if (isRelatedToManyCollections) {
return {
relationTo: relatedCollectionSlug,
value: {
...relatedDocument,
collection: relatedCollectionSlug,
},
}
}
return relatedDocument
}
return null
}
return null
},
}
const whereFields = graphqlResult.collections[relationTo].config.fields
upload.args.where = {
type: buildWhereInputType({
name: uploadName,
fields: whereFields,
parentName: uploadName,
}),
}
return {
...objectTypeConfig,
[field.name]: upload,
[field.name]: relationship,
}
},
}

View File

@@ -130,9 +130,36 @@ const fieldToSchemaMap = ({ nestedFieldName, parentName }: Args): any => ({
textarea: (field: TextareaField) => ({
type: withOperators(field, parentName),
}),
upload: (field: UploadField) => ({
type: withOperators(field, parentName),
}),
upload: (field: UploadField) => {
if (Array.isArray(field.relationTo)) {
return {
type: new GraphQLInputObjectType({
name: `${combineParentName(parentName, field.name)}_Relation`,
fields: {
relationTo: {
type: new GraphQLEnumType({
name: `${combineParentName(parentName, field.name)}_Relation_RelationTo`,
values: field.relationTo.reduce(
(values, relation) => ({
...values,
[formatName(relation)]: {
value: relation,
},
}),
{},
),
}),
},
value: { type: GraphQLJSON },
},
}),
}
}
return {
type: withOperators(field, parentName),
}
},
})
export default fieldToSchemaMap

View File

@@ -230,9 +230,9 @@ const defaults: DefaultsType = {
},
upload: {
operators: [
...operators.equality.map((operator) => ({
...[...operators.equality, ...operators.contains].map((operator) => ({
name: operator,
type: GraphQLString,
type: GraphQLJSON,
})),
],
},

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The official React SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-vue",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The official Vue SDK for Payload Live Preview",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -7,7 +7,6 @@ import type {
} from 'payload'
import { RenderComponent, getCreateMappedComponent } from '@payloadcms/ui/shared'
import { isPlainObject } from 'payload'
import React from 'react'
import { ShouldRenderTabs } from './ShouldRenderTabs.js'

View File

@@ -2,8 +2,8 @@
.doc-header {
width: 100%;
margin-top: base(0.5);
padding-bottom: calc(var(--base) * 1.5);
margin-top: base(0.4);
padding-bottom: calc(var(--base) * 1.2);
display: flex;
align-items: center;
position: relative;
@@ -27,6 +27,9 @@
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
padding-bottom: base(0.4);
line-height: 1;
vertical-align: top;
}
@include mid-break {

View File

@@ -8,7 +8,7 @@
position: absolute;
order: 3;
left: unset;
inset-inline-end: base(0.5);
inset-inline-end: base(0.8);
top: 50%;
transform: translateY(-50%);
color: var(--theme-elevation-600);
@@ -16,8 +16,8 @@
border: none;
svg {
width: base(0.75);
height: base(0.75);
width: base(0.8);
height: base(0.8);
}
&:hover {
@@ -33,10 +33,11 @@
.toast-title {
line-height: base(1);
margin-right: base(1);
}
.payload-toast-item {
padding: base(0.5);
padding: base(0.8);
color: var(--theme-elevation-800);
font-style: normal;
font-weight: 600;
@@ -69,8 +70,8 @@
}
.toast-icon {
width: base(1);
height: base(1);
width: base(0.8);
height: base(0.8);
margin: 0;
display: flex;
align-items: center;
@@ -84,8 +85,8 @@
&.toast-warning {
color: var(--theme-warning-800);
border-color: var(--theme-warning-150);
background-color: var(--theme-warning-50);
border-color: var(--theme-warning-250);
background-color: var(--theme-warning-100);
.payload-toast-close-button {
color: var(--theme-warning-600);
@@ -98,8 +99,8 @@
&.toast-error {
color: var(--theme-error-800);
border-color: var(--theme-error-150);
background-color: var(--theme-error-50);
border-color: var(--theme-error-250);
background-color: var(--theme-error-100);
.payload-toast-close-button {
color: var(--theme-error-600);
@@ -112,8 +113,8 @@
&.toast-success {
color: var(--theme-success-800);
border-color: var(--theme-success-150);
background-color: var(--theme-success-50);
border-color: var(--theme-success-250);
background-color: var(--theme-success-100);
.payload-toast-close-button {
color: var(--theme-success-600);
@@ -126,8 +127,8 @@
&.toast-info {
color: var(--theme-elevation-800);
border-color: var(--theme-elevation-150);
background-color: var(--theme-elevation-50);
border-color: var(--theme-elevation-250);
background-color: var(--theme-elevation-100);
.payload-toast-close-button {
color: var(--theme-elevation-600);

View File

@@ -14,7 +14,11 @@ if (!cached) {
cached = global._payload = { payload: null, promise: null, reload: false, ws: null }
}
export const reload = async (config: SanitizedConfig, payload: Payload): Promise<void> => {
export const reload = async (
config: SanitizedConfig,
payload: Payload,
skipImportMapGeneration?: boolean,
): Promise<void> => {
if (typeof payload.db.destroy === 'function') {
await payload.db.destroy()
}
@@ -46,7 +50,7 @@ export const reload = async (config: SanitizedConfig, payload: Payload): Promise
}
// Generate component map
if (config.admin?.importMap?.autoGenerate !== false) {
if (skipImportMapGeneration !== true && config.admin?.importMap?.autoGenerate !== false) {
await generateImportMap(config, {
log: true,
})
@@ -87,6 +91,7 @@ export const getPayloadHMR = async (options: InitOptions): Promise<Payload> => {
return cached.payload
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (!cached.promise) {
// no need to await options.config here, as it's already awaited in the BasePayload.init
cached.promise = new BasePayload().init(options)

View File

@@ -45,7 +45,7 @@ export const meta = async (args: { serverURL: string } & MetaConfig): Promise<an
icons = customIcons
}
const metaTitle = `${title} ${titleSuffix}`
const metaTitle = [title, titleSuffix].filter(Boolean).join(' ')
const ogTitle = `${typeof openGraphFromProps?.title === 'string' ? openGraphFromProps.title : title} ${titleSuffix}`

View File

@@ -16,18 +16,25 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
? getTranslation(globalConfig.label, i18n)
: ''
const metaTitle = `API - ${entityLabel}`
const description = `API - ${entityLabel}`
return Promise.resolve(
meta({
...(config.admin.meta || {}),
description,
description: `API - ${entityLabel}`,
keywords: 'API',
serverURL: config.serverURL,
title: metaTitle,
...(collectionConfig?.admin.meta || {}),
...(globalConfig?.admin.meta || {}),
title: `API - ${entityLabel}`,
...(collectionConfig
? {
...(collectionConfig?.admin.meta || {}),
...(collectionConfig?.admin?.components?.views?.edit?.api?.meta || {}),
}
: {}),
...(globalConfig
? {
...(globalConfig?.admin.meta || {}),
...(globalConfig?.admin?.components?.views?.edit?.api?.meta || {}),
}
: {}),
}),
)
}

View File

@@ -11,9 +11,9 @@ import './index.scss'
const baseClass = 'payload-settings'
export const Settings: React.FC<{
className?: string
i18n: I18n
languageOptions: LanguageOptions
readonly className?: string
readonly i18n: I18n
readonly languageOptions: LanguageOptions
}> = (props) => {
const { className, i18n, languageOptions } = props
@@ -21,7 +21,7 @@ export const Settings: React.FC<{
<div className={[baseClass, className].filter(Boolean).join(' ')}>
<h3>{i18n.t('general:payloadSettings')}</h3>
<div className={`${baseClass}__language`}>
<FieldLabel htmlFor="language-select" label={i18n.t('general:language')} />
<FieldLabel field={null} htmlFor="language-select" label={i18n.t('general:language')} />
<LanguageSelector languageOptions={languageOptions} />
</div>
<ToggleTheme />

View File

@@ -12,25 +12,42 @@ export const getCustomViewByRoute = ({
views:
| SanitizedCollectionConfig['admin']['components']['views']
| SanitizedGlobalConfig['admin']['components']['views']
}): EditViewComponent => {
if (typeof views?.edit === 'object' && typeof views?.edit !== 'function') {
const foundViewConfig = Object.entries(views.edit).find(([, view]) => {
if (typeof view === 'object' && typeof view !== 'function' && 'path' in view) {
}): {
Component: EditViewComponent
viewKey?: string
} => {
if (typeof views?.edit === 'object') {
let viewKey: string
const foundViewConfig = Object.entries(views.edit).find(([key, view]) => {
if (typeof view === 'object' && 'path' in view) {
const viewPath = `${baseRoute}${view.path}`
return isPathMatchingRoute({
const isMatching = isPathMatchingRoute({
currentRoute,
exact: true,
path: viewPath,
})
if (isMatching) {
viewKey = key
}
return isMatching
}
return false
})?.[1]
if (foundViewConfig && 'Component' in foundViewConfig) {
return foundViewConfig.Component
return {
Component: foundViewConfig.Component,
viewKey,
}
}
}
return null
return {
Component: null,
}
}

View File

@@ -1,5 +1,5 @@
import type { Metadata } from 'next'
import type { SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
import type { EditConfig, SanitizedCollectionConfig, SanitizedGlobalConfig } from 'payload'
import type { GenerateViewMetadata } from '../Root/index.js'
@@ -10,11 +10,13 @@ import { generateMetadata as livePreviewMeta } from '../LivePreview/meta.js'
import { generateNotFoundMeta } from '../NotFound/meta.js'
import { generateMetadata as versionMeta } from '../Version/meta.js'
import { generateMetadata as versionsMeta } from '../Versions/meta.js'
import { getViewsFromConfig } from './getViewsFromConfig.js'
export type GenerateEditViewMetadata = (
args: {
collectionConfig?: SanitizedCollectionConfig | null
globalConfig?: SanitizedGlobalConfig | null
view?: keyof EditConfig
} & Parameters<GenerateViewMetadata>[0],
) => Promise<Metadata>
@@ -36,54 +38,71 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
isGlobal || Boolean(isCollection && segments?.length > 2 && segments[2] !== 'create')
if (isCollection) {
// `/:id`
// `/:collection/:id`
if (params.segments.length === 3) {
fn = editMeta
}
// `/:id/api`
if (params.segments.length === 4 && params.segments[3] === 'api') {
fn = apiMeta
// `/:collection/:id/:view`
if (params.segments.length === 4) {
switch (params.segments[3]) {
case 'api':
// `/:collection/:id/api`
fn = apiMeta
break
case 'preview':
// `/:collection/:id/preview`
fn = livePreviewMeta
break
case 'versions':
// `/:collection/:id/versions`
fn = versionsMeta
break
default:
break
}
}
// `/:id/preview`
if (params.segments.length === 4 && params.segments[3] === 'preview') {
fn = livePreviewMeta
}
// `/:id/versions`
if (params.segments.length === 4 && params.segments[3] === 'versions') {
fn = versionsMeta
}
// `/:id/versions/:version`
if (params.segments.length === 5 && params.segments[3] === 'versions') {
fn = versionMeta
// `/:collection/:id/:slug-1/:slug-2`
if (params.segments.length === 5) {
switch (params.segments[3]) {
case 'versions':
// `/:collection/:id/versions/:version`
fn = versionMeta
break
default:
break
}
}
}
if (isGlobal) {
// `/:slug`
// `/:global`
if (params.segments?.length === 2) {
fn = editMeta
}
// `/:slug/api`
if (params.segments?.length === 3 && params.segments[2] === 'api') {
fn = apiMeta
// `/:global/:view`
if (params.segments?.length === 3) {
switch (params.segments[2]) {
case 'api':
// `/:global/api`
fn = apiMeta
break
case 'preview':
// `/:global/preview`
fn = livePreviewMeta
break
case 'versions':
// `/:global/versions`
fn = versionsMeta
break
default:
break
}
}
// `/:slug/preview`
if (params.segments?.length === 3 && params.segments[2] === 'preview') {
fn = livePreviewMeta
}
// `/:slug/versions`
if (params.segments?.length === 3 && params.segments[2] === 'versions') {
fn = versionsMeta
}
// `/:slug/versions/:version`
// `/:global/versions/:version`
if (params.segments?.length === 4 && params.segments[2] === 'versions') {
fn = versionMeta
}
@@ -101,6 +120,31 @@ export const getMetaBySegment: GenerateEditViewMetadata = async ({
i18n,
isEditing,
})
} else {
const { viewKey } = getViewsFromConfig({
collectionConfig,
config,
globalConfig,
overrideDocPermissions: true,
routeSegments: typeof segments === 'string' ? [segments] : segments,
})
if (viewKey) {
const customViewConfig =
collectionConfig?.admin?.components?.views?.edit?.[viewKey] ||
globalConfig?.admin?.components?.views?.edit?.[viewKey]
if (customViewConfig) {
return editMeta({
collectionConfig,
config,
globalConfig,
i18n,
isEditing,
view: viewKey as keyof EditConfig,
})
}
}
}
return generateNotFoundMeta({ config, i18n })

View File

@@ -31,25 +31,36 @@ export const getViewsFromConfig = ({
config,
docPermissions,
globalConfig,
overrideDocPermissions,
routeSegments,
}: {
collectionConfig?: SanitizedCollectionConfig
config: SanitizedConfig
docPermissions: CollectionPermission | GlobalPermission
globalConfig?: SanitizedGlobalConfig
routeSegments: string[]
}): {
} & (
| {
docPermissions: CollectionPermission | GlobalPermission
overrideDocPermissions?: false | undefined
}
| {
docPermissions?: never
overrideDocPermissions: true
}
)): {
CustomView: ViewFromConfig<ServerSideEditViewProps>
DefaultView: ViewFromConfig<ServerSideEditViewProps>
/**
* The error view to display if CustomView or DefaultView do not exist (could be either due to not found, or unauthorized). Can be null
*/
ErrorView: ViewFromConfig<AdminViewProps>
viewKey: string
} | null => {
// Conditionally import and lazy load the default view
let DefaultView: ViewFromConfig<ServerSideEditViewProps> = null
let CustomView: ViewFromConfig<ServerSideEditViewProps> = null
let ErrorView: ViewFromConfig<AdminViewProps> = null
let viewKey: string
const {
routes: { admin: adminRoute },
@@ -69,7 +80,7 @@ export const getViewsFromConfig = ({
const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
routeSegments
if (!docPermissions?.read?.permission) {
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
notFound()
} else {
// `../:id`, or `../create`
@@ -77,7 +88,11 @@ export const getViewsFromConfig = ({
case 3: {
switch (segment3) {
case 'create': {
if ('create' in docPermissions && docPermissions?.create?.permission) {
if (
!overrideDocPermissions &&
'create' in docPermissions &&
docPermissions?.create?.permission
) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
}
@@ -93,12 +108,42 @@ export const getViewsFromConfig = ({
}
default: {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
}
DefaultView = {
Component: DefaultEditView,
const baseRoute = [
adminRoute !== '/' && adminRoute,
'collections',
collectionSlug,
segment3,
]
.filter(Boolean)
.join('/')
const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
.filter(Boolean)
.join('/')
const { Component: CustomViewComponent, viewKey: customViewKey } =
getCustomViewByRoute({
baseRoute,
currentRoute,
views,
})
if (customViewKey) {
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
}
} else {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'default'),
}
DefaultView = {
Component: DefaultEditView,
}
}
break
}
}
@@ -130,7 +175,7 @@ export const getViewsFromConfig = ({
}
case 'versions': {
if (docPermissions?.readVersions?.permission) {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'versions'),
}
@@ -159,13 +204,21 @@ export const getViewsFromConfig = ({
.filter(Boolean)
.join('/')
CustomView = {
payloadComponent: getCustomViewByRoute({
const { Component: CustomViewComponent, viewKey: customViewKey } =
getCustomViewByRoute({
baseRoute,
currentRoute,
views,
}),
})
if (customViewKey) {
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
}
}
break
}
}
@@ -175,7 +228,7 @@ export const getViewsFromConfig = ({
// `../:id/versions/:version`, etc
default: {
if (segment4 === 'versions') {
if (docPermissions?.readVersions?.permission) {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'version'),
}
@@ -201,14 +254,23 @@ export const getViewsFromConfig = ({
.filter(Boolean)
.join('/')
CustomView = {
payloadComponent: getCustomViewByRoute({
const { Component: CustomViewComponent, viewKey: customViewKey } = getCustomViewByRoute(
{
baseRoute,
currentRoute,
views,
}),
},
)
if (customViewKey) {
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
}
}
}
break
}
}
@@ -218,7 +280,7 @@ export const getViewsFromConfig = ({
if (globalConfig) {
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
if (!docPermissions?.read?.permission) {
if (!overrideDocPermissions && !docPermissions?.read?.permission) {
notFound()
} else {
switch (routeSegments.length) {
@@ -257,10 +319,11 @@ export const getViewsFromConfig = ({
}
case 'versions': {
if (docPermissions?.readVersions?.permission) {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'versions'),
}
DefaultView = {
Component: DefaultVersionsView,
}
@@ -273,7 +336,7 @@ export const getViewsFromConfig = ({
}
default: {
if (docPermissions?.read?.permission) {
if (!overrideDocPermissions && docPermissions?.read?.permission) {
const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
.filter(Boolean)
.join('/')
@@ -282,15 +345,23 @@ export const getViewsFromConfig = ({
.filter(Boolean)
.join('/')
CustomView = {
payloadComponent: getCustomViewByRoute({
const { Component: CustomViewComponent, viewKey: customViewKey } =
getCustomViewByRoute({
baseRoute,
currentRoute,
views,
}),
}
DefaultView = {
Component: DefaultEditView,
})
if (customViewKey) {
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
}
} else {
DefaultView = {
Component: DefaultEditView,
}
}
} else {
ErrorView = {
@@ -306,7 +377,7 @@ export const getViewsFromConfig = ({
default: {
// `../:slug/versions/:version`, etc
if (segment3 === 'versions') {
if (docPermissions?.readVersions?.permission) {
if (!overrideDocPermissions && docPermissions?.readVersions?.permission) {
CustomView = {
payloadComponent: getCustomViewByKey(views, 'version'),
}
@@ -327,14 +398,23 @@ export const getViewsFromConfig = ({
.filter(Boolean)
.join('/')
CustomView = {
payloadComponent: getCustomViewByRoute({
const { Component: CustomViewComponent, viewKey: customViewKey } = getCustomViewByRoute(
{
baseRoute,
currentRoute,
views,
}),
},
)
if (customViewKey) {
viewKey = customViewKey
CustomView = {
payloadComponent: CustomViewComponent,
}
}
}
break
}
}
@@ -345,5 +425,6 @@ export const getViewsFromConfig = ({
CustomView,
DefaultView,
ErrorView,
viewKey,
}
}

View File

@@ -129,6 +129,7 @@ export const APIKey: React.FC<{ readonly enabled: boolean; readonly readOnly?: b
Component: null,
RenderedComponent: APIKeyLabel,
}}
field={null}
htmlFor={path}
/>
<input

View File

@@ -149,8 +149,10 @@ export const Auth: React.FC<Props> = (props) => {
{(showPasswordFields || requirePassword) && (
<div className={`${baseClass}__changing-password`}>
<PasswordField
autoComplete="new-password"
field={{
name: 'password',
_path: 'password',
admin: {
disabled,
},

View File

@@ -98,14 +98,18 @@ export const DefaultEditView: React.FC = () => {
if (globalSlug) classes.push(`global-edit--${globalSlug}`)
if (collectionSlug) classes.push(`collection-edit--${collectionSlug}`)
const [schemaPath, setSchemaPath] = React.useState(entitySlug)
const [schemaPath, setSchemaPath] = React.useState(() => {
if (operation === 'create' && auth && !auth.disableLocalStrategy) {
return `_${entitySlug}.auth`
}
return entitySlug
})
const [validateBeforeSubmit, setValidateBeforeSubmit] = useState(() => {
if (
operation === 'create' &&
collectionConfig.auth &&
!collectionConfig.auth.disableLocalStrategy
)
if (operation === 'create' && auth && !auth.disableLocalStrategy) {
return true
}
return false
})

View File

@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import type { MetaConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
@@ -12,6 +13,7 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
globalConfig,
i18n,
isEditing,
view = 'default',
}): Promise<Metadata> => {
const { t } = i18n
@@ -21,34 +23,45 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
? getTranslation(globalConfig.label, i18n)
: ''
const metaTitle = `${isEditing ? t('general:editing') : t('general:creating')} - ${entityLabel}`
const metaToUse: MetaConfig = {
...(config.admin.meta || {}),
description: `${isEditing ? t('general:editing') : t('general:creating')} - ${entityLabel}`,
keywords: `${entityLabel}, Payload, CMS`,
title: `${isEditing ? t('general:editing') : t('general:creating')} - ${entityLabel}`,
}
const ogTitle = `${isEditing ? t('general:edit') : t('general:edit')} - ${entityLabel}`
const description = `${isEditing ? t('general:editing') : t('general:creating')} - ${entityLabel}`
const keywords = `${entityLabel}, Payload, CMS`
const baseOGOverrides = config.admin.meta.openGraph || {}
const entityOGOverrides = collectionConfig
? collectionConfig.admin?.meta?.openGraph
: globalConfig
? globalConfig.admin?.meta?.openGraph
: {}
const ogToUse: MetaConfig['openGraph'] = {
title: `${isEditing ? t('general:edit') : t('general:edit')} - ${entityLabel}`,
...(config.admin.meta.openGraph || {}),
...(collectionConfig
? {
...(collectionConfig?.admin.meta?.openGraph || {}),
...(collectionConfig?.admin?.components?.views?.edit?.[view]?.meta?.openGraph || {}),
}
: {}),
...(globalConfig
? {
...(globalConfig?.admin.meta?.openGraph || {}),
...(globalConfig?.admin?.components?.views?.edit?.[view]?.meta?.openGraph || {}),
}
: {}),
}
return meta({
...(config.admin.meta || {}),
description,
keywords,
openGraph: {
title: ogTitle,
...baseOGOverrides,
...entityOGOverrides,
},
...(collectionConfig?.admin.meta || {}),
...(globalConfig?.admin.meta || {}),
...metaToUse,
openGraph: ogToUse,
...(collectionConfig
? {
...(collectionConfig?.admin.meta || {}),
...(collectionConfig?.admin?.components?.views?.edit?.[view]?.meta || {}),
}
: {}),
...(globalConfig
? {
...(globalConfig?.admin.meta || {}),
...(globalConfig?.admin?.components?.views?.edit?.[view]?.meta || {}),
}
: {}),
serverURL: config.serverURL,
title: metaTitle,
})
}

View File

@@ -11,23 +11,15 @@
}
}
&__header {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: base(0.8);
h1 {
margin: 0;
}
.list-header {
a {
text-decoration: none;
}
.pill {
.btn--withoutPopup,
.btn--withPopup {
position: relative;
margin: 0;
margin: 0 0 base(0.2);
}
}

View File

@@ -4,15 +4,17 @@ import type { ClientCollectionConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import {
BulkUploadDrawer,
Button,
DeleteMany,
EditMany,
Gutter,
ListControls,
ListHeader,
ListSelection,
Pagination,
PerPage,
Pill,
PopupList,
PublishMany,
RelationshipProvider,
RenderComponent,
@@ -22,10 +24,13 @@ import {
Table,
UnpublishMany,
ViewDescription,
bulkUploadDrawerSlug,
useConfig,
useEditDepth,
useListInfo,
useListQuery,
useModal,
useRouteCache,
useSearchParams,
useStepNav,
useTranslation,
@@ -41,9 +46,20 @@ const baseClass = 'collection-list'
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
export const DefaultListView: React.FC = () => {
const { Header, collectionSlug, hasCreatePermission, newDocumentURL } = useListInfo()
const {
Header,
beforeActions,
collectionSlug,
disableBulkDelete,
disableBulkEdit,
hasCreatePermission,
newDocumentURL,
} = useListInfo()
const { data, defaultLimit, handlePageChange, handlePerPageChange } = useListQuery()
const { searchParams } = useSearchParams()
const { openModal } = useModal()
const { clearRouteCache } = useRouteCache()
const { getEntityConfig } = useConfig()
@@ -67,7 +83,7 @@ export const DefaultListView: React.FC = () => {
labels,
} = collectionConfig
const { i18n } = useTranslation()
const { i18n, t } = useTranslation()
const drawerDepth = useEditDepth()
@@ -79,7 +95,9 @@ export const DefaultListView: React.FC = () => {
let docs = data.docs || []
if (collectionConfig.upload) {
const isUploadCollection = Boolean(collectionConfig.upload)
if (isUploadCollection) {
docs = docs?.map((doc) => {
return {
...doc,
@@ -104,35 +122,47 @@ export const DefaultListView: React.FC = () => {
<RenderComponent mappedComponent={beforeList} />
<SelectionProvider docs={data.docs} totalDocs={data.totalDocs}>
<Gutter className={`${baseClass}__wrap`}>
<header className={`${baseClass}__header`}>
{Header || (
<Fragment>
<h1>{getTranslation(labels?.plural, i18n)}</h1>
{hasCreatePermission && (
<Pill
aria-label={i18n.t('general:createNewLabel', {
label: getTranslation(labels?.singular, i18n),
})}
to={newDocumentURL}
>
{i18n.t('general:createNew')}
</Pill>
)}
{!smallBreak && (
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
)}
{description && (
<div className={`${baseClass}__sub-header`}>
<RenderComponent
Component={ViewDescription}
clientProps={{ description }}
mappedComponent={Description}
/>
</div>
)}
</Fragment>
)}
</header>
{Header || (
<ListHeader heading={getTranslation(labels?.plural, i18n)}>
{hasCreatePermission && (
<Button
Link={Link}
SubMenuPopupContent={
isUploadCollection && collectionConfig.upload.bulkUpload ? (
<PopupList.ButtonGroup>
<PopupList.Button onClick={() => openModal(bulkUploadDrawerSlug)}>
{t('upload:bulkUpload')}
</PopupList.Button>
</PopupList.ButtonGroup>
) : null
}
aria-label={i18n.t('general:createNewLabel', {
label: getTranslation(labels?.singular, i18n),
})}
buttonStyle="pill"
el="link"
size="small"
to={newDocumentURL}
>
{i18n.t('general:createNew')}
</Button>
)}
{!smallBreak && (
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
)}
{(description || Description) && (
<div className={`${baseClass}__sub-header`}>
<ViewDescription Description={Description} description={description} />
</div>
)}
{isUploadCollection && collectionConfig.upload.bulkUpload ? (
<BulkUploadDrawer
collectionSlug={collectionSlug}
onSuccess={() => clearRouteCache()}
/>
) : null}
</ListHeader>
)}
<ListControls collectionConfig={collectionConfig} fields={fields} />
<RenderComponent mappedComponent={beforeListTable} />
{!data.docs && (
@@ -174,7 +204,7 @@ export const DefaultListView: React.FC = () => {
limit={data.limit}
nextPage={data.nextPage}
numberOfNeighbors={1}
onChange={handlePageChange}
onChange={(page) => void handlePageChange(page)}
page={data.page}
prevPage={data.prevPage}
totalPages={data.totalPages}
@@ -189,7 +219,7 @@ export const DefaultListView: React.FC = () => {
{i18n.t('general:of')} {data.totalDocs}
</div>
<PerPage
handleChange={handlePerPageChange}
handleChange={(limit) => void handlePerPageChange(limit)}
limit={
isNumber(searchParams?.limit) ? Number(searchParams.limit) : defaultLimit
}
@@ -200,10 +230,15 @@ export const DefaultListView: React.FC = () => {
<div className={`${baseClass}__list-selection`}>
<ListSelection label={getTranslation(collectionConfig.labels.plural, i18n)} />
<div className={`${baseClass}__list-selection-actions`}>
<EditMany collection={collectionConfig} fields={fields} />
<PublishMany collection={collectionConfig} />
<UnpublishMany collection={collectionConfig} />
<DeleteMany collection={collectionConfig} />
{beforeActions && beforeActions}
{!disableBulkEdit && (
<Fragment>
<EditMany collection={collectionConfig} fields={fields} />
<PublishMany collection={collectionConfig} />
<UnpublishMany collection={collectionConfig} />
</Fragment>
)}
{!disableBulkDelete && <DeleteMany collection={collectionConfig} />}
</div>
</div>
)}

View File

@@ -17,4 +17,5 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
globalConfig,
i18n,
isEditing,
view: 'livePreview',
})

View File

@@ -0,0 +1,42 @@
import type { I18nClient } from '@payloadcms/translations'
import type { Metadata } from 'next'
import type {
AdminViewConfig,
SanitizedCollectionConfig,
SanitizedConfig,
SanitizedGlobalConfig,
} from 'payload'
import { meta } from '../../utilities/meta.js'
export const generateCustomViewMetadata = async (args: {
collectionConfig?: SanitizedCollectionConfig
config: SanitizedConfig
globalConfig?: SanitizedGlobalConfig
i18n: I18nClient
viewConfig: AdminViewConfig
}): Promise<Metadata> => {
const {
config,
// i18n: { t },
viewConfig,
} = args
if (!viewConfig) {
return null
}
return meta({
description: `Payload`,
keywords: `Payload`,
serverURL: config.serverURL,
title: 'Payload',
...(config.admin.meta || {}),
...(viewConfig.meta || {}),
openGraph: {
title: 'Payload',
...(config.admin.meta?.openGraph || {}),
...(viewConfig.meta?.openGraph || {}),
},
})
}

View File

@@ -0,0 +1,65 @@
import type { AdminViewConfig, SanitizedConfig } from 'payload'
import type { ViewFromConfig } from './getViewFromConfig.js'
import { isPathMatchingRoute } from './isPathMatchingRoute.js'
export const getCustomViewByRoute = ({
config,
currentRoute: currentRouteWithAdmin,
}: {
config: SanitizedConfig
currentRoute: string
}): {
view: ViewFromConfig
viewConfig: AdminViewConfig
viewKey: string
} => {
const {
admin: {
components: { views },
},
routes: { admin: adminRoute },
} = config
const currentRoute = currentRouteWithAdmin.replace(adminRoute, '')
let viewKey: string
const foundViewConfig =
(views &&
typeof views === 'object' &&
Object.entries(views).find(([key, view]) => {
const isMatching = isPathMatchingRoute({
currentRoute,
exact: view.exact,
path: view.path,
sensitive: view.sensitive,
strict: view.strict,
})
if (isMatching) {
viewKey = key
}
return isMatching
})?.[1]) ||
undefined
if (!foundViewConfig) {
return {
view: {
Component: null,
},
viewConfig: null,
viewKey: null,
}
}
return {
view: {
payloadComponent: foundViewConfig.Component,
},
viewConfig: foundViewConfig,
viewKey,
}
}

View File

@@ -1,43 +0,0 @@
import type { SanitizedConfig } from 'payload'
import type { ViewFromConfig } from './getViewFromConfig.js'
import { isPathMatchingRoute } from './isPathMatchingRoute.js'
export const getCustomViewByRoute = ({
config,
currentRoute: currentRouteWithAdmin,
}: {
config: SanitizedConfig
currentRoute: string
}): ViewFromConfig => {
const {
admin: {
components: { views },
},
routes: { admin: adminRoute },
} = config
const currentRoute = currentRouteWithAdmin.replace(adminRoute, '')
const foundViewConfig =
views &&
typeof views === 'object' &&
Object.entries(views).find(([, view]) => {
return isPathMatchingRoute({
currentRoute,
exact: view.exact,
path: view.path,
sensitive: view.sensitive,
strict: view.strict,
})
})?.[1]
if (!foundViewConfig) {
return null
}
return {
payloadComponent: foundViewConfig.Component,
}
}

View File

@@ -99,7 +99,7 @@ export const getViewFromConfig = ({
case 1: {
// users can override the default routes via `admin.routes` config
// i.e.{ admin: { routes: { logout: '/sign-out', inactivity: '/idle' }}}
let viewToRender: keyof typeof oneSegmentViews
let viewKey: keyof typeof oneSegmentViews
if (config.admin.routes) {
const matchedRoute = Object.entries(config.admin.routes).find(([, route]) => {
@@ -111,11 +111,11 @@ export const getViewFromConfig = ({
})
if (matchedRoute) {
viewToRender = matchedRoute[0] as keyof typeof oneSegmentViews
viewKey = matchedRoute[0] as keyof typeof oneSegmentViews
}
}
if (oneSegmentViews[viewToRender]) {
if (oneSegmentViews[viewKey]) {
// --> /account
// --> /create-first-user
// --> /forgot
@@ -125,12 +125,13 @@ export const getViewFromConfig = ({
// --> /unauthorized
ViewToRender = {
Component: oneSegmentViews[viewToRender],
Component: oneSegmentViews[viewKey],
}
templateClassName = baseClasses[viewToRender]
templateClassName = baseClasses[viewKey]
templateType = 'minimal'
if (viewToRender === 'account') {
if (viewKey === 'account') {
initPageOptions.redirectUnauthenticatedUser = true
templateType = 'default'
}
@@ -150,17 +151,21 @@ export const getViewFromConfig = ({
if (isCollection) {
// --> /collections/:collectionSlug
initPageOptions.redirectUnauthenticatedUser = true
ViewToRender = {
Component: ListView,
}
templateClassName = `${segmentTwo}-list`
templateType = 'default'
} else if (isGlobal) {
// --> /globals/:globalSlug
initPageOptions.redirectUnauthenticatedUser = true
ViewToRender = {
Component: DocumentView,
}
templateClassName = 'global-edit'
templateType = 'default'
}
@@ -172,6 +177,7 @@ export const getViewFromConfig = ({
ViewToRender = {
Component: Verify,
}
templateClassName = 'verify'
templateType = 'minimal'
} else if (isCollection) {
@@ -182,9 +188,11 @@ export const getViewFromConfig = ({
// --> /collections/:collectionSlug/:id/versions/:versionId
// --> /collections/:collectionSlug/:id/api
initPageOptions.redirectUnauthenticatedUser = true
ViewToRender = {
Component: DocumentView,
}
templateClassName = `collection-default-edit`
templateType = 'default'
} else if (isGlobal) {
@@ -194,9 +202,11 @@ export const getViewFromConfig = ({
// --> /globals/:globalSlug/versions/:versionId
// --> /globals/:globalSlug/api
initPageOptions.redirectUnauthenticatedUser = true
ViewToRender = {
Component: DocumentView,
}
templateClassName = `global-edit`
templateType = 'default'
}
@@ -204,7 +214,7 @@ export const getViewFromConfig = ({
}
if (!ViewToRender) {
ViewToRender = getCustomViewByRoute({ config, currentRoute })
ViewToRender = getCustomViewByRoute({ config, currentRoute })?.view
}
return {

View File

@@ -13,6 +13,8 @@ import { generateNotFoundMeta } from '../NotFound/meta.js'
import { generateResetPasswordMetadata } from '../ResetPassword/index.js'
import { generateUnauthorizedMetadata } from '../Unauthorized/index.js'
import { generateVerifyMetadata } from '../Verify/index.js'
import { generateCustomViewMetadata } from './generateCustomViewMetadata.js'
import { getCustomViewByRoute } from './getCustomViewByRoute.js'
const oneSegmentMeta = {
'create-first-user': generateCreateFirstUserMetadata,
@@ -38,6 +40,7 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
const segments = Array.isArray(params.segments) ? params.segments : []
const currentRoute = `/${segments.join('/')}`
const [segmentOne, segmentTwo] = segments
const isGlobal = segmentOne === 'globals'
@@ -130,7 +133,22 @@ export const generatePageMetadata = async ({ config: configPromise, params }: Ar
}
if (!meta) {
meta = await generateNotFoundMeta({ config, i18n })
const { viewConfig, viewKey } = getCustomViewByRoute({
config,
currentRoute,
})
if (viewKey) {
// Custom Views
// --> /:path
meta = await generateCustomViewMetadata({
config,
i18n,
viewConfig,
})
} else {
meta = await generateNotFoundMeta({ config, i18n })
}
}
return meta

View File

@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import type { MetaConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
import { formatDate } from '@payloadcms/ui/shared'
@@ -15,9 +16,9 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
}): Promise<Metadata> => {
const { t } = i18n
let title: string = ''
let description: string = ''
const keywords: string = ''
let metaToUse: MetaConfig = {
...(config.admin.meta || {}),
}
const doc: any = {} // TODO: figure this out
@@ -29,23 +30,30 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
const useAsTitle = collectionConfig?.admin?.useAsTitle || 'id'
const entityLabel = getTranslation(collectionConfig.labels.singular, i18n)
const titleFromData = doc?.[useAsTitle]
title = `${t('version:version')}${formattedCreatedAt ? ` - ${formattedCreatedAt}` : ''}${titleFromData ? ` - ${titleFromData}` : ''} - ${entityLabel}`
description = t('version:viewingVersion', { documentTitle: doc[useAsTitle], entityLabel })
metaToUse = {
...(config.admin.meta || {}),
description: t('version:viewingVersion', { documentTitle: doc[useAsTitle], entityLabel }),
title: `${t('version:version')}${formattedCreatedAt ? ` - ${formattedCreatedAt}` : ''}${titleFromData ? ` - ${titleFromData}` : ''} - ${entityLabel}`,
...(collectionConfig?.admin?.meta || {}),
...(collectionConfig?.admin?.components?.views?.edit?.version?.meta || {}),
}
}
if (globalConfig) {
const entityLabel = getTranslation(globalConfig.label, i18n)
title = `${t('version:version')}${formattedCreatedAt ? ` - ${formattedCreatedAt}` : ''}${entityLabel}`
description = t('version:viewingVersionGlobal', { entityLabel })
metaToUse = {
...(config.admin.meta || {}),
description: t('version:viewingVersionGlobal', { entityLabel }),
title: `${t('version:version')}${formattedCreatedAt ? ` - ${formattedCreatedAt}` : ''}${entityLabel}`,
...(globalConfig?.admin?.meta || {}),
...(globalConfig?.admin?.components?.views?.edit?.version?.meta || {}),
}
}
return meta({
...(config.admin.meta || {}),
description,
keywords,
...metaToUse,
serverURL: config.serverURL,
title,
...(collectionConfig?.admin.meta || {}),
...(globalConfig?.admin.meta || {}),
})
}

View File

@@ -1,4 +1,5 @@
import type { Metadata } from 'next'
import type { MetaConfig } from 'payload'
import { getTranslation } from '@payloadcms/translations'
@@ -20,34 +21,40 @@ export const generateMetadata: GenerateEditViewMetadata = async ({
? getTranslation(globalConfig.label, i18n)
: ''
let title: string = ''
let description: string = ''
const keywords: string = ''
let metaToUse: MetaConfig = {
...(config.admin.meta || {}),
}
const data: any = {} // TODO: figure this out
if (collectionConfig) {
const useAsTitle = collectionConfig?.admin?.useAsTitle || 'id'
const titleFromData = data?.[useAsTitle]
title = `${t('version:versions')}${titleFromData ? ` - ${titleFromData}` : ''} - ${entityLabel}`
description = t('version:viewingVersions', {
documentTitle: data?.[useAsTitle],
entitySlug: collectionConfig.slug,
})
metaToUse = {
...(config.admin.meta || {}),
description: t('version:viewingVersions', {
documentTitle: data?.[useAsTitle],
entitySlug: collectionConfig.slug,
}),
title: `${t('version:versions')}${titleFromData ? ` - ${titleFromData}` : ''} - ${entityLabel}`,
...(collectionConfig?.admin.meta || {}),
...(collectionConfig?.admin?.components?.views?.edit?.versions?.meta || {}),
}
}
if (globalConfig) {
title = `${t('version:versions')} - ${entityLabel}`
description = t('version:viewingVersionsGlobal', { entitySlug: globalConfig.slug })
metaToUse = {
...(config.admin.meta || {}),
description: t('version:viewingVersionsGlobal', { entitySlug: globalConfig.slug }),
title: `${t('version:versions')} - ${entityLabel}`,
...(globalConfig?.admin.meta || {}),
...(globalConfig?.admin?.components?.views?.edit?.versions?.meta || {}),
}
}
return meta({
...(config.admin.meta || {}),
description,
keywords,
...metaToUse,
serverURL: config.serverURL,
title,
...(collectionConfig?.admin.meta || {}),
...(globalConfig?.admin.meta || {}),
})
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env node --no-deprecation
#!/usr/bin/env node
import path from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.81",
"version": "3.0.0-beta.89",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",
@@ -86,7 +86,6 @@
"dependencies": {
"@next/env": "^15.0.0-canary.104",
"@payloadcms/translations": "workspace:*",
"tsx": "4.17.0",
"ajv": "8.14.0",
"bson-objectid": "2.0.4",
"ci-info": "^4.0.0",
@@ -108,6 +107,7 @@
"sanitize-filename": "1.6.3",
"scmp": "2.1.0",
"ts-essentials": "7.0.3",
"tsx": "4.17.0",
"uuid": "10.0.0"
},
"devDependencies": {

View File

@@ -1,23 +1,33 @@
import type { MarkOptional } from 'ts-essentials'
import type { ArrayFieldClient } from '../../fields/config/types.js'
import type { ArrayField, ArrayFieldClient } from '../../fields/config/types.js'
import type { ArrayFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
DescriptionComponent,
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
LabelComponent,
MappedComponent,
} from '../types.js'
type ArrayFieldClientWithoutType = MarkOptional<ArrayFieldClient, 'type'>
export type ArrayFieldProps = {
readonly CustomRowLabel?: MappedComponent
readonly field: MarkOptional<ArrayFieldClient, 'type'>
readonly validate?: ArrayFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<ArrayFieldClientWithoutType>, 'validate'>
export type ArrayFieldLabelComponent = LabelComponent<'array'>
export type ArrayFieldLabelServerComponent = FieldLabelServerComponent<ArrayField>
export type ArrayFieldDescriptionComponent = DescriptionComponent<'array'>
export type ArrayFieldLabelClientComponent = FieldLabelClientComponent<ArrayFieldClientWithoutType>
export type ArrayFieldErrorComponent = ErrorComponent<'array'>
export type ArrayFieldDescriptionServerComponent = FieldDescriptionServerComponent<ArrayField>
export type ArrayFieldDescriptionClientComponent =
FieldDescriptionClientComponent<ArrayFieldClientWithoutType>
export type ArrayFieldErrorServerComponent = FieldErrorServerComponent<ArrayField>
export type ArrayFieldErrorClientComponent = FieldErrorClientComponent<ArrayFieldClientWithoutType>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { BlockFieldClient } from '../../fields/config/types.js'
import type { BlockField, BlockFieldClient } from '../../fields/config/types.js'
import type { BlockFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type BlocksFieldClientWithoutType = MarkOptional<BlockFieldClient, 'type'>
export type BlockFieldProps = {
readonly field: MarkOptional<BlockFieldClient, 'type'>
readonly validate?: BlockFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<BlocksFieldClientWithoutType>, 'validate'>
export type BlockFieldLabelComponent = LabelComponent<'blocks'>
export type BlockFieldLabelServerComponent = FieldLabelServerComponent<BlockField>
export type BlockFieldDescriptionComponent = DescriptionComponent<'blocks'>
export type BlockFieldLabelClientComponent = FieldLabelClientComponent<BlocksFieldClientWithoutType>
export type BlockFieldErrorComponent = ErrorComponent<'blocks'>
export type BlockFieldDescriptionServerComponent = FieldDescriptionServerComponent<BlockField>
export type BlockFieldDescriptionClientComponent =
FieldDescriptionClientComponent<BlocksFieldClientWithoutType>
export type BlockFieldErrorServerComponent = FieldErrorServerComponent<BlockField>
export type BlockFieldErrorClientComponent = FieldErrorClientComponent<BlocksFieldClientWithoutType>

View File

@@ -1,22 +1,38 @@
import type { MarkOptional } from 'ts-essentials'
import type { CheckboxFieldClient } from '../../fields/config/types.js'
import type { CheckboxField, CheckboxFieldClient } from '../../fields/config/types.js'
import type { CheckboxFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type CheckboxFieldClientWithoutType = MarkOptional<CheckboxFieldClient, 'type'>
export type CheckboxFieldProps = {
readonly checked?: boolean
readonly disableFormData?: boolean
readonly field: MarkOptional<CheckboxFieldClient, 'type'>
readonly id?: string
readonly onChange?: (value: boolean) => void
readonly partialChecked?: boolean
readonly validate?: CheckboxFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<CheckboxFieldClientWithoutType>, 'validate'>
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>
export type CheckboxFieldLabelServerComponent = FieldLabelServerComponent<CheckboxField>
export type CheckboxFieldDescriptionComponent = DescriptionComponent<'checkbox'>
export type CheckboxFieldLabelClientComponent =
FieldLabelClientComponent<CheckboxFieldClientWithoutType>
export type CheckboxFieldErrorComponent = ErrorComponent<'checkbox'>
export type CheckboxFieldDescriptionServerComponent = FieldDescriptionServerComponent<CheckboxField>
export type CheckboxFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CheckboxFieldClientWithoutType>
export type CheckboxFieldErrorServerComponent = FieldErrorServerComponent<CheckboxField>
export type CheckboxFieldErrorClientComponent =
FieldErrorClientComponent<CheckboxFieldClientWithoutType>

View File

@@ -1,18 +1,32 @@
import type { MarkOptional } from 'ts-essentials'
import type { CodeFieldClient } from '../../fields/config/types.js'
import type { CodeField, CodeFieldClient } from '../../fields/config/types.js'
import type { CodeFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type CodeFieldClientWithoutType = MarkOptional<CodeFieldClient, 'type'>
export type CodeFieldProps = {
readonly autoComplete?: string
readonly field: MarkOptional<CodeFieldClient, 'type'>
readonly validate?: CodeFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<CodeFieldClientWithoutType>, 'validate'>
export type CodeFieldLabelComponent = LabelComponent<'code'>
export type CodeFieldLabelServerComponent = FieldLabelServerComponent<CodeField>
export type CodeFieldDescriptionComponent = DescriptionComponent<'code'>
export type CodeFieldLabelClientComponent = FieldLabelClientComponent<CodeFieldClientWithoutType>
export type CodeFieldErrorComponent = ErrorComponent<'code'>
export type CodeFieldDescriptionServerComponent = FieldDescriptionServerComponent<CodeField>
export type CodeFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CodeFieldClientWithoutType>
export type CodeFieldErrorServerComponent = FieldErrorServerComponent<CodeField>
export type CodeFieldErrorClientComponent = FieldErrorClientComponent<CodeFieldClientWithoutType>

View File

@@ -1,15 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { CollapsibleFieldClient } from '../../fields/config/types.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { CollapsibleField, CollapsibleFieldClient } from '../../fields/config/types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
export type CollapsibleFieldProps = {
readonly field: MarkOptional<CollapsibleFieldClient, 'type'>
} & FormFieldBase
type CollapsibleFieldClientWithoutType = MarkOptional<CollapsibleFieldClient, 'type'>
export type CollapsibleFieldLabelComponent = LabelComponent<'collapsible'>
export type CollapsibleFieldProps = FormFieldBase<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldDescriptionComponent = DescriptionComponent<'collapsible'>
export type CollapsibleFieldLabelServerComponent = FieldLabelServerComponent<CollapsibleField>
export type CollapsibleFieldErrorComponent = ErrorComponent<'collapsible'>
export type CollapsibleFieldLabelClientComponent =
FieldLabelClientComponent<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldDescriptionServerComponent =
FieldDescriptionServerComponent<CollapsibleField>
export type CollapsibleFieldDescriptionClientComponent =
FieldDescriptionClientComponent<CollapsibleFieldClientWithoutType>
export type CollapsibleFieldErrorServerComponent = FieldErrorServerComponent<CollapsibleField>
export type CollapsibleFieldErrorClientComponent =
FieldErrorClientComponent<CollapsibleFieldClientWithoutType>

View File

@@ -1,17 +1,31 @@
import type { MarkOptional } from 'ts-essentials'
import type { DateFieldClient } from '../../fields/config/types.js'
import type { DateField, DateFieldClient } from '../../fields/config/types.js'
import type { DateFieldValidation } from '../../fields/validations.js'
import type { ErrorComponent } from '../forms/Error.js'
import type { DescriptionComponent, FormFieldBase, LabelComponent } from '../types.js'
import type { FieldErrorClientComponent, FieldErrorServerComponent } from '../forms/Error.js'
import type {
FieldDescriptionClientComponent,
FieldDescriptionServerComponent,
FieldLabelClientComponent,
FieldLabelServerComponent,
FormFieldBase,
} from '../types.js'
type DateFieldClientWithoutType = MarkOptional<DateFieldClient, 'type'>
export type DateFieldProps = {
readonly field: MarkOptional<DateFieldClient, 'type'>
readonly validate?: DateFieldValidation
} & Omit<FormFieldBase, 'validate'>
} & Omit<FormFieldBase<DateFieldClientWithoutType>, 'validate'>
export type DateFieldLabelComponent = LabelComponent<'date'>
export type DateFieldLabelServerComponent = FieldLabelServerComponent<DateField>
export type DateFieldDescriptionComponent = DescriptionComponent<'date'>
export type DateFieldLabelClientComponent = FieldLabelClientComponent<DateFieldClientWithoutType>
export type DateFieldErrorComponent = ErrorComponent<'date'>
export type DateFieldDescriptionServerComponent = FieldDescriptionServerComponent<DateField>
export type DateFieldDescriptionClientComponent =
FieldDescriptionClientComponent<DateFieldClientWithoutType>
export type DateFieldErrorServerComponent = FieldErrorServerComponent<DateField>
export type DateFieldErrorClientComponent = FieldErrorClientComponent<DateFieldClientWithoutType>

Some files were not shown because too many files have changed in this diff Show More