Compare commits
33 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d6e7f8a37 | ||
|
|
3d37d74c6e | ||
|
|
de19822ed4 | ||
|
|
2b2bcb5264 | ||
|
|
e9b01e6d9f | ||
|
|
b0a760193e | ||
|
|
95569e44e4 | ||
|
|
11816080a6 | ||
|
|
3a86822f0a | ||
|
|
6f8604e18c | ||
|
|
aec3f5e308 | ||
|
|
e0a5de6730 | ||
|
|
5eee49da9a | ||
|
|
b7d01dec70 | ||
|
|
0618130fe3 | ||
|
|
cd245793fc | ||
|
|
3a6c75a1a3 | ||
|
|
5a683b6947 | ||
|
|
9b27f03e61 | ||
|
|
89746ebe09 | ||
|
|
eacf2030cd | ||
|
|
86428539f5 | ||
|
|
a7f519c53a | ||
|
|
49a2d70fbb | ||
|
|
cb7fa00a6f | ||
|
|
a212cdef3f | ||
|
|
4f323a3754 | ||
|
|
f5e7578b41 | ||
|
|
0bf27b117a | ||
|
|
806c22e6bd | ||
|
|
39d7b717a9 | ||
|
|
9d1997e6a0 | ||
|
|
c65f5027d6 |
2
.github/actions/setup/action.yml
vendored
2
.github/actions/setup/action.yml
vendored
@@ -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
|
||||
|
||||
20
.github/workflows/main.yml
vendored
20
.github/workflows/main.yml
vendored
@@ -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
|
||||
@@ -217,7 +217,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
|
||||
@@ -332,7 +332,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
|
||||
@@ -407,7 +407,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 +420,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 +451,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 +492,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 +520,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
|
||||
|
||||
@@ -145,7 +145,7 @@ Instead, we utilize component paths to reference React Components. This method e
|
||||
|
||||
When constructing the `ClientConfig`, Payload uses the component paths as keys to fetch the corresponding React Component imports from the Import Map. It then substitutes the `PayloadComponent` with a `MappedComponent`. A `MappedComponent` includes the React Component and additional metadata, such as whether it's a server or a client component and which props it should receive. These components are then rendered through the `<RenderComponent />` component within the Payload Admin Panel.
|
||||
|
||||
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map.
|
||||
Import maps are regenerated whenever you modify any element related to component paths. This regeneration occurs at startup and whenever Hot Module Replacement (HMR) runs. If the import maps fail to regenerate during HMR, you can restart your application and execute the `payload generate:importmap` command to manually create a new import map. If you encounter any errors running this command, see the [Troubleshooting](/docs/beta/local-api/outside-nextjs#troubleshooting) section.
|
||||
|
||||
### Component paths in external packages
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ For more granular control, pass a configuration object instead. Payload exposes
|
||||
|
||||
| Property | Description |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| **`Component`** \* | Pass in the component that should be rendered when a user navigates to this route. |
|
||||
| **`Component`** \* | Pass in the component path that should be rendered when a user navigates to this route. |
|
||||
| **`path`** \* | Any valid URL path or array of paths that [`path-to-regexp`](https://www.npmjs.com/package/path-to-regex) understands. |
|
||||
| **`exact`** | Boolean. When true, will only match if the path matches the `usePathname()` exactly. |
|
||||
| **`strict`** | When true, a path that has a trailing slash will only match a `location.pathname` with a trailing slash. This has no effect when there are additional URL segments in the pathname. |
|
||||
@@ -111,7 +111,20 @@ export const MyCollectionConfig: SanitizedCollectionConfig = {
|
||||
components: {
|
||||
views: {
|
||||
edit: {
|
||||
Component: '/path/to/MyCustomEditView', // highlight-line
|
||||
root: {
|
||||
Component: '/path/to/MyCustomEditView', // highlight-line
|
||||
}
|
||||
// other options include:
|
||||
// default
|
||||
// versions
|
||||
// version
|
||||
// api
|
||||
// livePreview
|
||||
// [key: string]
|
||||
// See "Document Views" for more details
|
||||
},
|
||||
list: {
|
||||
Component: '/path/to/MyCustomListView',
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -123,7 +136,7 @@ _For details on how to build Custom Views, see [Building Custom Views](#building
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
|
||||
The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
|
||||
</Banner>
|
||||
|
||||
The following options are available:
|
||||
@@ -152,18 +165,29 @@ export const MyGlobalConfig: SanitizedGlobalConfig = {
|
||||
admin: {
|
||||
components: {
|
||||
views: {
|
||||
edit: '/path/to/MyCustomEditView', // highlight-line
|
||||
edit: {
|
||||
root: {
|
||||
Component: '/path/to/MyCustomEditView', // highlight-line
|
||||
}
|
||||
// other options include:
|
||||
// default
|
||||
// versions
|
||||
// version
|
||||
// api
|
||||
// livePreview
|
||||
// [key: string]
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
The `Edit` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `Edit.Default` key instead.
|
||||
The `root` property will replace the _entire_ Edit View, including the title, tabs, etc., _as well as all nested [Document Views](#document-views)_, such as the API, Live Preview, and Version views. To replace only the Edit View precisely, use the `edit.default` key instead.
|
||||
</Banner>
|
||||
|
||||
The following options are available:
|
||||
@@ -199,25 +223,26 @@ export const MyCollectionOrGlobalConfig: SanitizedCollectionConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
_For details on how to build Custom Views, see [Building Custom Views](#building-custom-views)._
|
||||
|
||||
<Banner type="warning">
|
||||
<strong>Note:</strong>
|
||||
If you need to replace the _entire_ Edit View, including _all_ nested Document Views, use the `Edit` key itself. See [Custom Collection Views](#collection-views) or [Custom Global Views](#global-views) for more information.
|
||||
If you need to replace the _entire_ Edit View, including _all_ nested Document Views, use the `root` key. See [Custom Collection Views](#collection-views) or [Custom Global Views](#global-views) for more information.
|
||||
</Banner>
|
||||
|
||||
The following options are available:
|
||||
|
||||
| Property | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`default`** | The Default view is the primary view in which your document is edited. |
|
||||
| **`versions`** | The Versions view is used to view the version history of a single document. [More details](../versions). |
|
||||
| **`version`** | The Version view is used to view a single version of a single document for a given collection. [More details](../versions). |
|
||||
| **`api`** | The API view is used to display the REST API JSON response for a given document. |
|
||||
| **`livePreview`** | The LivePreview view is used to display the Live Preview interface. [More details](../live-preview). |
|
||||
| **`root`** | The Root View overrides all other nested views and routes. No document controls or tabs are rendered when this key is set. |
|
||||
| **`default`** | The Default View is the primary view in which your document is edited. It is rendered within the "Edit" tab. |
|
||||
| **`versions`** | The Versions View is used to navigate the version history of a single document. It is rendered within the "Versions" tab. [More details](../versions). |
|
||||
| **`version`** | The Version View is used to edit a single version of a document. It is rendered within the "Version" tab. [More details](../versions). |
|
||||
| **`api`** | The API View is used to display the REST API JSON response for a given document. It is rendered within the "API" tab. |
|
||||
| **`livePreview`** | The LivePreview view is used to display the Live Preview interface. It is rendered within the "Live Preview" tab. [More details](../live-preview). |
|
||||
|
||||
### Document Tabs
|
||||
|
||||
|
||||
@@ -61,14 +61,27 @@ payload run src/seed.ts
|
||||
The `payload run` command does two things for you:
|
||||
|
||||
1. It loads the environment variables the same way Next.js loads them, eliminating the need for additional dependencies like `dotenv`. The usage of `dotenv` is not recommended, as Next.js loads environment variables differently. By using `payload run`, you ensure consistent environment variable handling across your Payload and Next.js setup.
|
||||
2. It initializes swc, allowing direct execution of TypeScript files without requiring tools like tsx or ts-node.
|
||||
2. It initializes tsx, allowing direct execution of TypeScript files manually installing tools like tsx or ts-node.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If you encounter import-related errors, try running the script in TSX mode:
|
||||
If you encounter import-related errors, you have 2 options:
|
||||
|
||||
#### Option 1: enable swc mode by appending `--use-swc` to the `payload` command:
|
||||
|
||||
Example:
|
||||
```sh
|
||||
payload run src/seed.ts --use-tsx
|
||||
payload run src/seed.ts --use-swc
|
||||
```
|
||||
|
||||
Note: Install tsx in your project first. Be aware that TSX mode is slower than the default swc mode, so only use it if necessary.
|
||||
Note: Install @swc-node/register in your project first. While swc mode is faster than the default tsx mode, it might break for some imports.
|
||||
|
||||
#### Option 2: use an alternative runtime like bun
|
||||
|
||||
While we do not guarantee support for alternative runtimes, you are free to use them and disable payloads own transpilation by appending the `--disable-transpilation` flag to the `payload` command:
|
||||
|
||||
```sh
|
||||
bunx --bun payload run src/seed.ts --disable-transpile
|
||||
```
|
||||
|
||||
You will need to have bun installed on your system for this to work.
|
||||
|
||||
@@ -66,6 +66,8 @@ export default config
|
||||
| ------------- | ---------- | ----------------------------------------------------------------------------------------------- |
|
||||
| `collections` | `string[]` | An array of collection slugs to populate in the `to` field of each redirect. |
|
||||
| `overrides` | `object` | A partial collection config that allows you to override anything on the `redirects` collection. |
|
||||
| `redirectTypes` | `string[]` | Provide an array of redirects if you want to provide options for the type of redirects to be supported. |
|
||||
| `redirectTypeFieldOverride` | `Field` | A partial Field config that allows you to override the Redirect Type field if enabled above. |
|
||||
|
||||
Note that the fields in overrides take a function that receives the default fields and returns an array of fields. This allows you to add fields to the collection.
|
||||
|
||||
@@ -83,6 +85,10 @@ redirectsPlugin({
|
||||
]
|
||||
},
|
||||
},
|
||||
redirectTypes: ['301', '302'],
|
||||
redirectTypeFieldOverride: {
|
||||
label: 'Redirect Type (Overridden)',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -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}`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -103,6 +103,8 @@
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/eslint-plugin": "workspace:*",
|
||||
"@payloadcms/live-preview-react": "workspace:*",
|
||||
"@payloadcms/db-postgres": "workspace:*",
|
||||
"drizzle-kit": "0.23.2-df9e596",
|
||||
"@playwright/test": "1.46.0",
|
||||
"@swc-node/register": "1.10.9",
|
||||
"@swc/cli": "0.4.0",
|
||||
@@ -164,6 +166,7 @@
|
||||
"node": "^18.20.2 || >=20.9.0",
|
||||
"pnpm": "^9.7.0"
|
||||
},
|
||||
"packageManager": "pnpm@9.7.0",
|
||||
"pnpm": {
|
||||
"allowedDeprecatedVersions": {
|
||||
"abab": "2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -58,11 +58,13 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./types": {
|
||||
"import": "./dist/types.js",
|
||||
"types": "./dist/types.d.ts"
|
||||
"types": "./dist/types.d.ts",
|
||||
"default": "./dist/types.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
67
packages/eslint-plugin/customRules/no-imports-from-self.js
Normal file
67
packages/eslint-plugin/customRules/no-imports-from-self.js
Normal 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
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,21 +1,54 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env node --no-deprecation
|
||||
|
||||
import { register } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
// Allow disabling SWC for debugging
|
||||
if (process.env.DISABLE_SWC !== 'true') {
|
||||
const useSwc = process.argv.includes('--use-swc')
|
||||
const disableTranspile = process.argv.includes('--disable-transpile')
|
||||
|
||||
if (disableTranspile) {
|
||||
// Remove --disable-transpile from arguments
|
||||
process.argv = process.argv.filter((arg) => arg !== '--disable-transpile')
|
||||
|
||||
const start = async () => {
|
||||
const { bin } = await import('./dist/bin/index.js')
|
||||
await bin()
|
||||
}
|
||||
|
||||
void start()
|
||||
} else {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const url = pathToFileURL(dirname).toString() + '/'
|
||||
|
||||
register('@swc-node/register/esm', url)
|
||||
}
|
||||
if (!useSwc) {
|
||||
const start = async () => {
|
||||
// Use tsx
|
||||
let tsImport = (await import('tsx/esm/api')).tsImport
|
||||
|
||||
const start = async () => {
|
||||
const { bin } = await import('./dist/bin/index.js')
|
||||
await bin()
|
||||
}
|
||||
const { bin } = await tsImport('./dist/bin/index.js', url)
|
||||
await bin()
|
||||
}
|
||||
|
||||
void start()
|
||||
void start()
|
||||
} else if (useSwc) {
|
||||
const { register } = await import('node:module')
|
||||
// Remove --use-swc from arguments
|
||||
process.argv = process.argv.filter((arg) => arg !== '--use-swc')
|
||||
|
||||
try {
|
||||
register('@swc-node/register/esm', url)
|
||||
} catch (_) {
|
||||
console.error(
|
||||
'@swc-node/register is not installed. Please install @swc-node/register in your project, if you want to use swc in payload run.',
|
||||
)
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
const { bin } = await import('./dist/bin/index.js')
|
||||
await bin()
|
||||
}
|
||||
|
||||
void start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -43,7 +43,7 @@
|
||||
"dependencies": {
|
||||
"graphql-scalars": "1.22.2",
|
||||
"pluralize": "8.0.0",
|
||||
"@swc-node/register": "1.10.9",
|
||||
"tsx": "4.17.0",
|
||||
"ts-essentials": "7.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -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.2);
|
||||
line-height: 1;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@include mid-break {
|
||||
|
||||
@@ -50,6 +50,7 @@ const Component: React.FC<{
|
||||
}
|
||||
|
||||
export const LeaveWithoutSaving: React.FC = () => {
|
||||
const { closeModal } = useModal()
|
||||
const modified = useFormModified()
|
||||
const { user } = useAuth()
|
||||
const [show, setShow] = React.useState(false)
|
||||
@@ -61,7 +62,11 @@ export const LeaveWithoutSaving: React.FC = () => {
|
||||
setShow(true)
|
||||
}, [])
|
||||
|
||||
usePreventLeave({ hasAccepted, onPrevent, prevent })
|
||||
const handleAccept = useCallback(() => {
|
||||
closeModal(modalSlug)
|
||||
}, [closeModal])
|
||||
|
||||
usePreventLeave({ hasAccepted, onAccept: handleAccept, onPrevent, prevent })
|
||||
|
||||
return (
|
||||
<Component
|
||||
|
||||
@@ -57,12 +57,14 @@ export const useBeforeUnload = (enabled: (() => boolean) | boolean = true, messa
|
||||
export const usePreventLeave = ({
|
||||
hasAccepted = false,
|
||||
message = 'Are you sure want to leave this page?',
|
||||
onAccept,
|
||||
onPrevent,
|
||||
prevent = true,
|
||||
}: {
|
||||
hasAccepted: boolean
|
||||
// if no `onPrevent` is provided, the message will be displayed in a confirm dialog
|
||||
message?: string
|
||||
onAccept?: () => void
|
||||
// to use a custom confirmation dialog, provide a function that returns a boolean
|
||||
onPrevent?: () => void
|
||||
prevent: boolean
|
||||
@@ -142,7 +144,8 @@ export const usePreventLeave = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (hasAccepted && cancelledURL.current) {
|
||||
if (onAccept) onAccept()
|
||||
router.push(cancelledURL.current)
|
||||
}
|
||||
}, [hasAccepted, router])
|
||||
}, [hasAccepted, onAccept, router])
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
width: var(--nav-width);
|
||||
border-right: 1px solid var(--theme-elevation-100);
|
||||
opacity: 0;
|
||||
transition: opacity var(--nav-trans-time) ease-in-out;
|
||||
|
||||
&--nav-animate {
|
||||
transition: opacity var(--nav-trans-time) ease-in-out;
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
opacity: 1;
|
||||
|
||||
@@ -10,10 +10,19 @@ export const NavWrapper: React.FC<{
|
||||
}> = (props) => {
|
||||
const { baseClass, children } = props
|
||||
|
||||
const { navOpen, navRef } = useNav()
|
||||
const { hydrated, navOpen, navRef, shouldAnimate } = useNav()
|
||||
|
||||
return (
|
||||
<aside className={[baseClass, navOpen && `${baseClass}--nav-open`].filter(Boolean).join(' ')}>
|
||||
<aside
|
||||
className={[
|
||||
baseClass,
|
||||
navOpen && `${baseClass}--nav-open`,
|
||||
shouldAnimate && `${baseClass}--nav-animate`,
|
||||
hydrated && `${baseClass}--nav-hydrated`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__scroll`} ref={navRef}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -9,9 +9,12 @@
|
||||
width: var(--nav-width);
|
||||
border-right: 1px solid var(--theme-elevation-100);
|
||||
opacity: 0;
|
||||
transition: opacity var(--nav-trans-time) ease-in-out;
|
||||
overflow: hidden;
|
||||
|
||||
&--nav-animate {
|
||||
transition: opacity var(--nav-trans-time) ease-in-out;
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@@ -106,6 +106,39 @@ export const RootLayout = async ({
|
||||
})
|
||||
}
|
||||
|
||||
const navPreferences = user
|
||||
? (
|
||||
await payload.find({
|
||||
collection: 'payload-preferences',
|
||||
depth: 0,
|
||||
limit: 1,
|
||||
req,
|
||||
user,
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
key: {
|
||||
equals: 'nav',
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.relationTo': {
|
||||
equals: user.collection,
|
||||
},
|
||||
},
|
||||
{
|
||||
'user.value': {
|
||||
equals: user.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
)?.docs?.[0]
|
||||
: null
|
||||
|
||||
const isNavOpen = (navPreferences?.value as any)?.open ?? true
|
||||
|
||||
return (
|
||||
<html data-theme={theme} dir={dir} lang={languageCode}>
|
||||
<body>
|
||||
@@ -113,6 +146,7 @@ export const RootLayout = async ({
|
||||
config={clientConfig}
|
||||
dateFNSKey={i18n.dateFNSKey}
|
||||
fallbackLang={clientConfig.i18n.fallbackLanguage}
|
||||
isNavOpen={isNavOpen}
|
||||
languageCode={languageCode}
|
||||
languageOptions={languageOptions}
|
||||
permissions={permissions}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
position: relative;
|
||||
grid-template-columns: 0 auto;
|
||||
transition: grid-template-columns var(--nav-trans-time) linear;
|
||||
isolation: isolate;
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&--nav-animate {
|
||||
transition: grid-template-columns var(--nav-trans-time) linear;
|
||||
}
|
||||
|
||||
&--nav-open {
|
||||
width: 100%;
|
||||
grid-template-columns: var(--nav-width) auto;
|
||||
|
||||
.template-default {
|
||||
&__nav-overlay {
|
||||
@@ -23,3 +23,35 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1441px) {
|
||||
.template-default {
|
||||
grid-template-columns: 0 auto;
|
||||
|
||||
&--nav-open {
|
||||
grid-template-columns: var(--nav-width) auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.template-default--nav-hydrated.template-default--nav-open {
|
||||
grid-template-columns: var(--nav-width) auto;
|
||||
}
|
||||
|
||||
.template-default {
|
||||
grid-template-columns: 1fr auto;
|
||||
|
||||
.nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--nav-hydrated {
|
||||
grid-template-columns: 0 auto;
|
||||
|
||||
.nav {
|
||||
display: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,17 @@ export const Wrapper: React.FC<{
|
||||
className?: string
|
||||
}> = (props) => {
|
||||
const { baseClass, children, className } = props
|
||||
const { navOpen } = useNav()
|
||||
const { hydrated, navOpen, shouldAnimate } = useNav()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, className, navOpen && `${baseClass}--nav-open`]
|
||||
className={[
|
||||
baseClass,
|
||||
className,
|
||||
navOpen && `${baseClass}--nav-open`,
|
||||
shouldAnimate && `${baseClass}--nav-animate`,
|
||||
hydrated && `${baseClass}--nav-hydrated`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
|
||||
@@ -66,139 +66,89 @@ export const getViewsFromConfig = ({
|
||||
config?.admin?.livePreview?.globals?.includes(globalConfig?.slug)
|
||||
|
||||
if (collectionConfig) {
|
||||
const editConfig = collectionConfig?.admin?.components?.views?.edit
|
||||
const EditOverride = typeof editConfig === 'function' ? editConfig : null
|
||||
const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
|
||||
routeSegments
|
||||
|
||||
if (EditOverride) {
|
||||
CustomView = EditOverride
|
||||
}
|
||||
|
||||
if (!EditOverride) {
|
||||
const [collectionEntity, collectionSlug, segment3, segment4, segment5, ...remainingSegments] =
|
||||
routeSegments
|
||||
|
||||
if (!docPermissions?.read?.permission) {
|
||||
notFound()
|
||||
} else {
|
||||
// `../:id`, or `../create`
|
||||
switch (routeSegments.length) {
|
||||
case 3: {
|
||||
switch (segment3) {
|
||||
case 'create': {
|
||||
if ('create' in docPermissions && docPermissions?.create?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
if (!docPermissions?.read?.permission) {
|
||||
notFound()
|
||||
} else {
|
||||
// `../:id`, or `../create`
|
||||
switch (routeSegments.length) {
|
||||
case 3: {
|
||||
switch (segment3) {
|
||||
case 'create': {
|
||||
if ('create' in docPermissions && docPermissions?.create?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
break
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
|
||||
case 4: {
|
||||
switch (segment4) {
|
||||
case 'api': {
|
||||
if (collectionConfig?.admin?.hideAPIURL !== true) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'api'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultAPIView,
|
||||
}
|
||||
}
|
||||
break
|
||||
default: {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
|
||||
case 'preview': {
|
||||
if (livePreviewEnabled) {
|
||||
DefaultView = {
|
||||
Component: DefaultLivePreviewView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionsView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
const baseRoute = [
|
||||
adminRoute !== '/' && adminRoute,
|
||||
'collections',
|
||||
collectionSlug,
|
||||
segment3,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
}
|
||||
break
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// `../:id/versions/:version`, etc
|
||||
default: {
|
||||
if (segment4 === 'versions') {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
// `../:id/api`, `../:id/preview`, `../:id/versions`, etc
|
||||
case 4: {
|
||||
switch (segment4) {
|
||||
case 'api': {
|
||||
if (collectionConfig?.admin?.hideAPIURL !== true) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'version'),
|
||||
payloadComponent: getCustomViewByKey(views, 'api'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionView,
|
||||
Component: DefaultAPIView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'preview': {
|
||||
if (livePreviewEnabled) {
|
||||
DefaultView = {
|
||||
Component: DefaultLivePreviewView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionsView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
const baseRoute = [
|
||||
adminRoute !== '/' && adminRoute,
|
||||
collectionEntity,
|
||||
'collections',
|
||||
collectionSlug,
|
||||
segment3,
|
||||
]
|
||||
@@ -216,144 +166,176 @@ export const getViewsFromConfig = ({
|
||||
views,
|
||||
}),
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// `../:id/versions/:version`, etc
|
||||
default: {
|
||||
if (segment4 === 'versions') {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'version'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const baseRoute = [
|
||||
adminRoute !== '/' && adminRoute,
|
||||
collectionEntity,
|
||||
collectionSlug,
|
||||
segment3,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
const currentRoute = [baseRoute, segment4, segment5, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (globalConfig) {
|
||||
const editConfig = globalConfig?.admin?.components?.views?.edit
|
||||
const EditOverride = typeof editConfig === 'function' ? editConfig : null
|
||||
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
|
||||
|
||||
if (EditOverride) {
|
||||
CustomView = EditOverride
|
||||
}
|
||||
|
||||
if (!EditOverride) {
|
||||
const [globalEntity, globalSlug, segment3, ...remainingSegments] = routeSegments
|
||||
|
||||
if (!docPermissions?.read?.permission) {
|
||||
notFound()
|
||||
} else {
|
||||
switch (routeSegments.length) {
|
||||
case 2: {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
break
|
||||
if (!docPermissions?.read?.permission) {
|
||||
notFound()
|
||||
} else {
|
||||
switch (routeSegments.length) {
|
||||
case 2: {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'default'),
|
||||
}
|
||||
|
||||
case 3: {
|
||||
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
|
||||
switch (segment3) {
|
||||
case 'api': {
|
||||
if (globalConfig?.admin?.hideAPIURL !== true) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'api'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultAPIView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'preview': {
|
||||
if (livePreviewEnabled) {
|
||||
DefaultView = {
|
||||
Component: DefaultLivePreviewView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionsView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
if (docPermissions?.read?.permission) {
|
||||
const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
const currentRoute = [baseRoute, segment3, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
// `../:slug/versions/:version`, etc
|
||||
if (segment3 === 'versions') {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
case 3: {
|
||||
// `../:slug/api`, `../:slug/preview`, `../:slug/versions`, etc
|
||||
switch (segment3) {
|
||||
case 'api': {
|
||||
if (globalConfig?.admin?.hideAPIURL !== true) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'version'),
|
||||
payloadComponent: getCustomViewByKey(views, 'api'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionView,
|
||||
Component: DefaultAPIView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'preview': {
|
||||
if (livePreviewEnabled) {
|
||||
DefaultView = {
|
||||
Component: DefaultLivePreviewView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'versions': {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByKey(views, 'versions'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionsView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const baseRoute = [adminRoute !== '/' && adminRoute, 'globals', globalSlug]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
break
|
||||
}
|
||||
|
||||
const currentRoute = [baseRoute, segment3, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
default: {
|
||||
if (docPermissions?.read?.permission) {
|
||||
const baseRoute = [adminRoute, globalEntity, globalSlug, segment3]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
const currentRoute = [baseRoute, segment3, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultEditView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
// `../:slug/versions/:version`, etc
|
||||
if (segment3 === 'versions') {
|
||||
if (docPermissions?.readVersions?.permission) {
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
payloadComponent: getCustomViewByKey(views, 'version'),
|
||||
}
|
||||
DefaultView = {
|
||||
Component: DefaultVersionView,
|
||||
}
|
||||
} else {
|
||||
ErrorView = {
|
||||
Component: UnauthorizedView,
|
||||
}
|
||||
}
|
||||
break
|
||||
} else {
|
||||
const baseRoute = [adminRoute !== '/' && adminRoute, 'globals', globalSlug]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
const currentRoute = [baseRoute, segment3, ...remainingSegments]
|
||||
.filter(Boolean)
|
||||
.join('/')
|
||||
|
||||
CustomView = {
|
||||
payloadComponent: getCustomViewByRoute({
|
||||
baseRoute,
|
||||
currentRoute,
|
||||
views,
|
||||
}),
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
|
||||
const isEditing = getIsEditing({ id, collectionSlug, globalSlug })
|
||||
|
||||
let ViewOverride: MappedComponent<ServerSideEditViewProps>
|
||||
let RootViewOverride: MappedComponent<ServerSideEditViewProps>
|
||||
let CustomView: MappedComponent<ServerSideEditViewProps>
|
||||
let DefaultView: MappedComponent<ServerSideEditViewProps>
|
||||
let ErrorView: MappedComponent<AdminViewProps>
|
||||
@@ -115,19 +115,18 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
|
||||
apiURL = `${serverURL}${apiRoute}/${collectionSlug}/${id}${apiQueryParams}`
|
||||
|
||||
ViewOverride =
|
||||
collectionConfig?.admin?.components?.views?.edit?.default &&
|
||||
'Component' in collectionConfig.admin.components.views.edit.default
|
||||
RootViewOverride =
|
||||
collectionConfig?.admin?.components?.views?.edit?.root &&
|
||||
'Component' in collectionConfig.admin.components.views.edit.root
|
||||
? createMappedComponent(
|
||||
collectionConfig?.admin?.components?.views?.edit?.default
|
||||
?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
|
||||
collectionConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
|
||||
undefined,
|
||||
undefined,
|
||||
'collectionConfig?.admin?.components?.views?.edit?.default',
|
||||
'collectionConfig?.admin?.components?.views?.edit?.root',
|
||||
)
|
||||
: null
|
||||
|
||||
if (!ViewOverride) {
|
||||
if (!RootViewOverride) {
|
||||
const collectionViews = getViewsFromConfig({
|
||||
collectionConfig,
|
||||
config,
|
||||
@@ -157,7 +156,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
)
|
||||
}
|
||||
|
||||
if (!CustomView && !DefaultView && !ViewOverride && !ErrorView) {
|
||||
if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
|
||||
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
|
||||
}
|
||||
}
|
||||
@@ -170,9 +169,11 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
const params = new URLSearchParams({
|
||||
locale: locale?.code,
|
||||
})
|
||||
|
||||
if (globalConfig.versions?.drafts) {
|
||||
params.append('draft', 'true')
|
||||
}
|
||||
|
||||
if (locale?.code) {
|
||||
params.append('locale', locale.code)
|
||||
}
|
||||
@@ -181,10 +182,18 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
|
||||
apiURL = `${serverURL}${apiRoute}/${globalSlug}${apiQueryParams}`
|
||||
|
||||
const editConfig = globalConfig?.admin?.components?.views?.edit
|
||||
ViewOverride = typeof editConfig === 'function' ? editConfig : null
|
||||
RootViewOverride =
|
||||
globalConfig?.admin?.components?.views?.edit?.root &&
|
||||
'Component' in globalConfig.admin.components.views.edit.root
|
||||
? createMappedComponent(
|
||||
globalConfig?.admin?.components?.views?.edit?.root?.Component as EditViewComponent, // some type info gets lost from Config => SanitizedConfig due to our usage of Deep type operations from ts-essentials. Despite .Component being defined as EditViewComponent, this info is lost and we need cast it here.
|
||||
undefined,
|
||||
undefined,
|
||||
'globalConfig?.admin?.components?.views?.edit?.root',
|
||||
)
|
||||
: null
|
||||
|
||||
if (!ViewOverride) {
|
||||
if (!RootViewOverride) {
|
||||
const globalViews = getViewsFromConfig({
|
||||
config,
|
||||
docPermissions,
|
||||
@@ -213,7 +222,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
'globalViews?.ErrorView.payloadComponent',
|
||||
)
|
||||
|
||||
if (!CustomView && !DefaultView && !ViewOverride && !ErrorView) {
|
||||
if (!CustomView && !DefaultView && !RootViewOverride && !ErrorView) {
|
||||
ErrorView = createMappedComponent(undefined, undefined, NotFoundView, 'NotFoundView')
|
||||
}
|
||||
}
|
||||
@@ -268,7 +277,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
initialState={formState}
|
||||
isEditing={isEditing}
|
||||
>
|
||||
{!ViewOverride && (
|
||||
{!RootViewOverride && (
|
||||
<DocumentHeader
|
||||
collectionConfig={collectionConfig}
|
||||
globalConfig={globalConfig}
|
||||
@@ -294,7 +303,9 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
<RenderComponent mappedComponent={ErrorView} />
|
||||
) : (
|
||||
<RenderComponent
|
||||
mappedComponent={ViewOverride ? ViewOverride : CustomView ? CustomView : DefaultView}
|
||||
mappedComponent={
|
||||
RootViewOverride ? RootViewOverride : CustomView ? CustomView : DefaultView
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</EditDepthProvider>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: base(0.8);
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
.pill {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
margin: 0 0 base(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
#!/usr/bin/env node --no-deprecation
|
||||
#!/usr/bin/env -S node --no-deprecation
|
||||
|
||||
import { register } from 'node:module'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
|
||||
const useTsx = process.argv.includes('--use-tsx')
|
||||
const useSwc = process.argv.includes('--use-swc')
|
||||
const disableTranspile = process.argv.includes('--disable-transpile')
|
||||
|
||||
// Allow disabling SWC/TSX for debugging
|
||||
if (process.env.DISABLE_SWC !== 'true' && !useTsx) {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const url = pathToFileURL(dirname).toString() + '/'
|
||||
|
||||
register('@swc-node/register/esm', url)
|
||||
if (disableTranspile) {
|
||||
// Remove --disable-transpile from arguments
|
||||
process.argv = process.argv.filter((arg) => arg !== '--disable-transpile')
|
||||
|
||||
const start = async () => {
|
||||
const { bin } = await import('./dist/bin/index.js')
|
||||
@@ -20,25 +16,39 @@ if (process.env.DISABLE_SWC !== 'true' && !useTsx) {
|
||||
}
|
||||
|
||||
void start()
|
||||
} else if (useTsx) {
|
||||
// Remove --use-tsx from arguments
|
||||
process.argv = process.argv.filter((arg) => arg !== '--use-tsx')
|
||||
} else {
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
const url = pathToFileURL(dirname).toString() + '/'
|
||||
|
||||
const start = async () => {
|
||||
// Use tsx
|
||||
let tsImport
|
||||
try {
|
||||
tsImport = (await import('tsx/esm/api')).tsImport
|
||||
} catch (_) {
|
||||
console.error(
|
||||
'tsx is not installed. Please install tsx in your project, if you want to use tsx in payload run.',
|
||||
)
|
||||
return
|
||||
if (!useSwc) {
|
||||
const start = async () => {
|
||||
// Use tsx
|
||||
let tsImport = (await import('tsx/esm/api')).tsImport
|
||||
|
||||
const { bin } = await tsImport('./dist/bin/index.js', url)
|
||||
await bin()
|
||||
}
|
||||
|
||||
const { bin } = await tsImport('./dist/bin/index.js', import.meta.url)
|
||||
await bin()
|
||||
}
|
||||
void start()
|
||||
} else if (useSwc) {
|
||||
const { register } = await import('node:module')
|
||||
// Remove --use-swc from arguments
|
||||
process.argv = process.argv.filter((arg) => arg !== '--use-swc')
|
||||
|
||||
void start()
|
||||
try {
|
||||
register('@swc-node/register/esm', url)
|
||||
} catch (_) {
|
||||
console.error(
|
||||
'@swc-node/register is not installed. Please install @swc-node/register in your project, if you want to use swc in payload run.',
|
||||
)
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
const { bin } = await import('./dist/bin/index.js')
|
||||
await bin()
|
||||
}
|
||||
|
||||
void start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
@@ -86,7 +86,7 @@
|
||||
"dependencies": {
|
||||
"@next/env": "^15.0.0-canary.104",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@swc-node/register": "1.10.9",
|
||||
"tsx": "4.17.0",
|
||||
"ajv": "8.14.0",
|
||||
"bson-objectid": "2.0.4",
|
||||
"ci-info": "^4.0.0",
|
||||
|
||||
@@ -227,8 +227,13 @@ export type MappedClientComponent<TComponentClientProps extends JsonObject = Jso
|
||||
type: 'client'
|
||||
}
|
||||
|
||||
export type MappedEmptyComponent = {
|
||||
type: 'empty'
|
||||
}
|
||||
|
||||
export type MappedComponent<TComponentClientProps extends JsonObject = JsonObject> =
|
||||
| MappedClientComponent<TComponentClientProps>
|
||||
| MappedEmptyComponent
|
||||
| MappedServerComponent<TComponentClientProps>
|
||||
| undefined
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export const apiKeyFields = [
|
||||
type: 'checkbox',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
},
|
||||
label: ({ t }) => t('authentication:enableAPIKey'),
|
||||
@@ -23,7 +23,7 @@ export const apiKeyFields = [
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const emailFieldConfig: EmailField = {
|
||||
type: 'email',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const usernameFieldConfig: TextField = {
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
},
|
||||
hooks: {
|
||||
|
||||
@@ -26,7 +26,7 @@ export const verificationFields: Field[] = [
|
||||
},
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
},
|
||||
label: ({ t }) => t('authentication:verified'),
|
||||
|
||||
@@ -44,11 +44,11 @@ export const ensureUsernameOrEmail = <TSlug extends CollectionSlug>({
|
||||
missingFields = true
|
||||
}
|
||||
// prevent clearing email if no username
|
||||
if ('email' in data && !data.email && !originalDoc.username) {
|
||||
if ('email' in data && !data.email && !originalDoc.username && !data?.username) {
|
||||
missingFields = true
|
||||
}
|
||||
// prevent clearing username if no email
|
||||
if ('username' in data && !data.username && !originalDoc.email) {
|
||||
if ('username' in data && !data.username && !originalDoc.email && !data?.email) {
|
||||
missingFields = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export function addPayloadComponentToImportMap({
|
||||
imports[importIdentifier] = {
|
||||
path:
|
||||
componentPath.startsWith('.') || componentPath.startsWith('/')
|
||||
? path.join(baseDir, componentPath.slice(1))
|
||||
? path.posix.join(baseDir.replace(/\\/g, '/'), componentPath.slice(1))
|
||||
: componentPath,
|
||||
specifier: exportName,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,9 @@ export function parsePayloadComponent(payloadComponent: PayloadComponent): {
|
||||
exportName: string
|
||||
path: string
|
||||
} {
|
||||
if (!payloadComponent) {
|
||||
return null
|
||||
}
|
||||
const pathAndMaybeExport =
|
||||
typeof payloadComponent === 'string' ? payloadComponent : payloadComponent.path
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import minimist from 'minimist'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
import type { BinScript } from '../config/types.js'
|
||||
@@ -29,7 +30,7 @@ export const bin = async () => {
|
||||
process.argv = [process.argv[0], process.argv[1], ...args._.slice(2)]
|
||||
|
||||
try {
|
||||
await import(absoluteScriptPath)
|
||||
await import(pathToFileURL(absoluteScriptPath).toString())
|
||||
} catch (error) {
|
||||
console.error(`Error running script: ${absoluteScriptPath}`)
|
||||
console.error(error)
|
||||
@@ -42,7 +43,7 @@ export const bin = async () => {
|
||||
}
|
||||
|
||||
const configPath = findConfig()
|
||||
const configPromise = await import(configPath)
|
||||
const configPromise = await import(pathToFileURL(configPath).toString())
|
||||
let config = await configPromise
|
||||
if (config.default) config = await config.default
|
||||
|
||||
@@ -52,7 +53,7 @@ export const bin = async () => {
|
||||
|
||||
if (userBinScript) {
|
||||
try {
|
||||
const script: BinScript = await import(userBinScript.scriptPath)
|
||||
const script: BinScript = await import(pathToFileURL(userBinScript.scriptPath).toString())
|
||||
await script(config)
|
||||
} catch (err) {
|
||||
console.log(`Could not find associated bin script for the ${userBinScript.key} command`)
|
||||
|
||||
@@ -22,10 +22,7 @@ const getTSConfigPaths = (): {
|
||||
const rootConfigDir = path.resolve(tsConfigDir, tsConfig.compilerOptions.baseUrl || '')
|
||||
const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src')
|
||||
const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist')
|
||||
let configPath = path.resolve(
|
||||
rootConfigDir,
|
||||
tsConfig.compilerOptions?.paths?.['@payload-config']?.[0],
|
||||
)
|
||||
let configPath = tsConfig.compilerOptions?.paths?.['@payload-config']?.[0]
|
||||
|
||||
if (configPath) {
|
||||
configPath = path.resolve(rootConfigDir, configPath)
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { default as sharp } from 'sharp'
|
||||
import type { DeepRequired } from 'ts-essentials'
|
||||
|
||||
import type { RichTextAdapterProvider } from '../admin/RichText.js'
|
||||
import type { DocumentTabConfig, MappedComponent, RichTextAdapter } from '../admin/types.js'
|
||||
import type { DocumentTabConfig, RichTextAdapter } from '../admin/types.js'
|
||||
import type { AdminViewConfig, ServerSideEditViewProps } from '../admin/views/types.js'
|
||||
import type { Permissions } from '../auth/index.js'
|
||||
import type {
|
||||
@@ -38,12 +38,12 @@ import type { PayloadLogger } from '../utilities/logger.js'
|
||||
/**
|
||||
* The string path pointing to the React component. If one of the generics is `never`, you effectively mark it as a server-only or client-only component.
|
||||
*
|
||||
* If the path is an empty string, it will be treated as () => null
|
||||
* If it is `false` an empty component will be rendered.
|
||||
*/
|
||||
export type PayloadComponent<
|
||||
TComponentServerProps extends never | object = Record<string, any>,
|
||||
TComponentClientProps extends never | object = Record<string, any>,
|
||||
> = RawPayloadComponent<TComponentServerProps, TComponentClientProps> | string
|
||||
> = RawPayloadComponent<TComponentServerProps, TComponentClientProps> | false | string
|
||||
|
||||
// We need the actual object as its own type, otherwise the infers for the PayloadClientReactComponent / PayloadServerReactComponent will not work due to the string union.
|
||||
// We also NEED to actually use those generics for this to work, thus they are part of the props.
|
||||
@@ -912,28 +912,44 @@ export type SanitizedConfig = {
|
||||
'collections' | 'editor' | 'endpoint' | 'globals' | 'i18n' | 'localization' | 'upload'
|
||||
>
|
||||
|
||||
export type EditConfig = {
|
||||
[key: string]: Partial<EditViewConfig>
|
||||
/**
|
||||
* Replace or modify individual nested routes, or add new ones:
|
||||
* + `default` - `/admin/collections/:collection/:id`
|
||||
* + `api` - `/admin/collections/:collection/:id/api`
|
||||
* + `livePreview` - `/admin/collections/:collection/:id/preview`
|
||||
* + `references` - `/admin/collections/:collection/:id/references`
|
||||
* + `relationships` - `/admin/collections/:collection/:id/relationships`
|
||||
* + `versions` - `/admin/collections/:collection/:id/versions`
|
||||
* + `version` - `/admin/collections/:collection/:id/versions/:version`
|
||||
* + `customView` - `/admin/collections/:collection/:id/:path`
|
||||
*/
|
||||
api?: Partial<EditViewConfig>
|
||||
default?: Partial<EditViewConfig>
|
||||
livePreview?: Partial<EditViewConfig>
|
||||
version?: Partial<EditViewConfig>
|
||||
versions?: Partial<EditViewConfig>
|
||||
// TODO: uncomment these as they are built
|
||||
// references?: EditView
|
||||
// relationships?: EditView
|
||||
}
|
||||
export type EditConfig =
|
||||
| {
|
||||
[key: string]: EditViewConfig
|
||||
/**
|
||||
* Replace or modify individual nested routes, or add new ones:
|
||||
* + `default` - `/admin/collections/:collection/:id`
|
||||
* + `api` - `/admin/collections/:collection/:id/api`
|
||||
* + `livePreview` - `/admin/collections/:collection/:id/preview`
|
||||
* + `references` - `/admin/collections/:collection/:id/references`
|
||||
* + `relationships` - `/admin/collections/:collection/:id/relationships`
|
||||
* + `versions` - `/admin/collections/:collection/:id/versions`
|
||||
* + `version` - `/admin/collections/:collection/:id/versions/:version`
|
||||
* + `customView` - `/admin/collections/:collection/:id/:path`
|
||||
*
|
||||
* To override the entire Edit View including all nested views, use the `root` key.
|
||||
*/
|
||||
api?: Partial<EditViewConfig>
|
||||
default?: Partial<EditViewConfig>
|
||||
livePreview?: Partial<EditViewConfig>
|
||||
root?: never
|
||||
version?: Partial<EditViewConfig>
|
||||
versions?: Partial<EditViewConfig>
|
||||
// TODO: uncomment these as they are built
|
||||
// references?: EditView
|
||||
// relationships?: EditView
|
||||
}
|
||||
| {
|
||||
api?: never
|
||||
default?: never
|
||||
livePreview?: never
|
||||
/**
|
||||
* Replace or modify _all_ nested document views and routes, including the document header, controls, and tabs. This cannot be used in conjunction with other nested views.
|
||||
* + `root` - `/admin/collections/:collection/:id/**\/*`
|
||||
*/
|
||||
root: Partial<EditViewConfig>
|
||||
version?: never
|
||||
versions?: never
|
||||
}
|
||||
|
||||
export type EntityDescriptionComponent = CustomComponent
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ export { setsAreEqual } from '../utilities/setsAreEqual.js'
|
||||
|
||||
export { default as toKebabCase } from '../utilities/toKebabCase.js'
|
||||
|
||||
export { unflatten } from '../utilities/unflatten.js'
|
||||
|
||||
export { wait } from '../utilities/wait.js'
|
||||
|
||||
export { default as wordBoundariesRegex } from '../utilities/wordBoundariesRegex.js'
|
||||
|
||||
@@ -962,7 +962,9 @@ export type SingleRelationshipFieldClient = {
|
||||
|
||||
export type RelationshipField = PolymorphicRelationshipField | SingleRelationshipField
|
||||
|
||||
export type RelationshipFieldClient = PolymorphicRelationshipFieldClient
|
||||
export type RelationshipFieldClient =
|
||||
| PolymorphicRelationshipFieldClient
|
||||
| SingleRelationshipFieldClient
|
||||
|
||||
export type ValueWithRelation = {
|
||||
relationTo: CollectionSlug
|
||||
|
||||
@@ -17,7 +17,7 @@ const baseVersionFields: Field[] = [
|
||||
type: 'select',
|
||||
admin: {
|
||||
components: {
|
||||
Field: '@payloadcms/ui/shared#emptyComponent',
|
||||
Field: false,
|
||||
},
|
||||
disableBulkEdit: true,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-relationship-object-ids",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -61,7 +61,7 @@ export const MetaDescriptionComponent: React.FC<MetaDescriptionProps> = (props)
|
||||
...docInfo,
|
||||
doc: { ...getData() },
|
||||
locale: typeof locale === 'object' ? locale?.code : locale,
|
||||
} satisfies Parameters<GenerateDescription>[0]),
|
||||
} satisfies Omit<Parameters<GenerateDescription>[0], 'req'>),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -57,7 +57,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
...docInfo,
|
||||
doc: { ...getData() },
|
||||
locale: typeof locale === 'object' ? locale?.code : locale,
|
||||
} satisfies Parameters<GenerateImage>[0]),
|
||||
} satisfies Omit<Parameters<GenerateImage>[0], 'req'>),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -62,7 +62,7 @@ export const MetaTitleComponent: React.FC<MetaTitleProps> = (props) => {
|
||||
...docInfo,
|
||||
doc: { ...getData() },
|
||||
locale: typeof locale === 'object' ? locale?.code : locale,
|
||||
} satisfies Parameters<GenerateTitle>[0]),
|
||||
} satisfies Omit<Parameters<GenerateTitle>[0], 'req'>),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -49,7 +49,7 @@ export const PreviewComponent: React.FC<PreviewProps> = ({
|
||||
...docInfo,
|
||||
doc: { ...getData() },
|
||||
locale: typeof locale === 'object' ? locale?.code : locale,
|
||||
} satisfies Parameters<GenerateURL>[0]),
|
||||
} satisfies Omit<Parameters<GenerateURL>[0], 'req'>),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
@@ -134,11 +134,12 @@ export const seoPlugin =
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
req.t
|
||||
|
||||
const result = pluginConfig.generateTitle
|
||||
? await pluginConfig.generateTitle(
|
||||
req.data as unknown as Parameters<GenerateTitle>[0],
|
||||
)
|
||||
? await pluginConfig.generateTitle({
|
||||
...req.data,
|
||||
req,
|
||||
} as unknown as Parameters<GenerateTitle>[0])
|
||||
: ''
|
||||
return new Response(JSON.stringify({ result }), { status: 200 })
|
||||
},
|
||||
@@ -148,10 +149,12 @@ export const seoPlugin =
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
|
||||
const result = pluginConfig.generateDescription
|
||||
? await pluginConfig.generateDescription(
|
||||
req.data as unknown as Parameters<GenerateDescription>[0],
|
||||
)
|
||||
? await pluginConfig.generateDescription({
|
||||
...req.data,
|
||||
req,
|
||||
} as unknown as Parameters<GenerateDescription>[0])
|
||||
: ''
|
||||
return new Response(JSON.stringify({ result }), { status: 200 })
|
||||
},
|
||||
@@ -161,8 +164,12 @@ export const seoPlugin =
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
|
||||
const result = pluginConfig.generateURL
|
||||
? await pluginConfig.generateURL(req.data as unknown as Parameters<GenerateURL>[0])
|
||||
? await pluginConfig.generateURL({
|
||||
...req.data,
|
||||
req,
|
||||
} as unknown as Parameters<GenerateURL>[0])
|
||||
: ''
|
||||
return new Response(JSON.stringify({ result }), { status: 200 })
|
||||
},
|
||||
@@ -172,10 +179,12 @@ export const seoPlugin =
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
|
||||
const result = pluginConfig.generateImage
|
||||
? await pluginConfig.generateImage(
|
||||
req.data as unknown as Parameters<GenerateImage>[0],
|
||||
)
|
||||
? await pluginConfig.generateImage({
|
||||
...req.data,
|
||||
req,
|
||||
} as unknown as Parameters<GenerateImage>[0])
|
||||
: ''
|
||||
return new Response(result, { status: 200 })
|
||||
},
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import type { DocumentInfoContext } from '@payloadcms/ui'
|
||||
import type { Field, TextField, TextareaField, UploadField } from 'payload'
|
||||
import type { Field, PayloadRequest, TextField, TextareaField, UploadField } from 'payload'
|
||||
|
||||
export type GenerateTitle<T = any> = (
|
||||
args: { doc: T; locale?: string } & DocumentInfoContext,
|
||||
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
|
||||
) => Promise<string> | string
|
||||
|
||||
export type GenerateDescription<T = any> = (
|
||||
args: {
|
||||
doc: T
|
||||
locale?: string
|
||||
req: PayloadRequest
|
||||
} & DocumentInfoContext,
|
||||
) => Promise<string> | string
|
||||
|
||||
export type GenerateImage<T = any> = (
|
||||
args: { doc: T; locale?: string } & DocumentInfoContext,
|
||||
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
|
||||
) => Promise<string> | string
|
||||
|
||||
export type GenerateURL<T = any> = (
|
||||
args: { doc: T; locale?: string } & DocumentInfoContext,
|
||||
args: { doc: T; locale?: string; req: PayloadRequest } & DocumentInfoContext,
|
||||
) => Promise<string> | string
|
||||
|
||||
export type SEOPluginConfig = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -40,7 +40,7 @@ const RichTextComponent: React.FC<
|
||||
readOnly: readOnlyFromAdmin,
|
||||
style,
|
||||
width,
|
||||
},
|
||||
} = {},
|
||||
label,
|
||||
required,
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ const RichTextEditor = lazy(() =>
|
||||
|
||||
export const RichTextField: React.FC<LexicalRichTextFieldProps> = (props) => {
|
||||
const {
|
||||
admin,
|
||||
admin = {},
|
||||
field: { richTextComponentMap },
|
||||
lexicalEditorConfig,
|
||||
} = props
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -64,7 +64,7 @@ const RichTextField: React.FC<LoadedSlateFieldProps> = (props) => {
|
||||
readOnly: readOnlyFromAdmin,
|
||||
style,
|
||||
width,
|
||||
},
|
||||
} = {},
|
||||
label,
|
||||
required,
|
||||
},
|
||||
|
||||
@@ -3,5 +3,5 @@ import type { RichTextCustomElement } from '../../../types.js'
|
||||
export const textAlign: RichTextCustomElement = {
|
||||
name: 'alignment',
|
||||
Button: '@payloadcms/richtext-slate/client#TextAlignElementButton',
|
||||
Element: '@payloadcms/ui/shared#emptyComponent',
|
||||
Element: false,
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.0.0-beta.79",
|
||||
"version": "3.0.0-beta.84",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -155,7 +155,7 @@ export const Autosave: React.FC<Props> = ({
|
||||
}
|
||||
})
|
||||
.then((json) => {
|
||||
if (versionsConfig?.drafts && versionsConfig?.drafts?.validate && json.errors) {
|
||||
if (versionsConfig?.drafts && versionsConfig?.drafts?.validate && json?.errors) {
|
||||
if (Array.isArray(json.errors)) {
|
||||
const [fieldErrors, nonFieldErrors] = json.errors.reduce(
|
||||
([fieldErrs, nonFieldErrs], err) => {
|
||||
@@ -224,7 +224,7 @@ export const Autosave: React.FC<Props> = ({
|
||||
if (autosaveTimeout) clearTimeout(autosaveTimeout)
|
||||
if (abortController.signal) {
|
||||
try {
|
||||
abortController.abort()
|
||||
abortController.abort('Autosave closed early.')
|
||||
} catch (error) {
|
||||
// swallow error
|
||||
}
|
||||
|
||||
@@ -13,13 +13,20 @@ const handleDragOver = (e: DragEvent) => {
|
||||
const baseClass = 'dropzone'
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
mimeTypes?: string[]
|
||||
onChange: (e: FileList) => void
|
||||
onPasteUrlClick?: () => void
|
||||
readonly className?: string
|
||||
readonly mimeTypes?: string[]
|
||||
readonly multipleFiles?: boolean
|
||||
readonly onChange: (e: FileList) => void
|
||||
readonly onPasteUrlClick?: () => void
|
||||
}
|
||||
|
||||
export const Dropzone: React.FC<Props> = ({ className, mimeTypes, onChange, onPasteUrlClick }) => {
|
||||
export const Dropzone: React.FC<Props> = ({
|
||||
className,
|
||||
mimeTypes,
|
||||
multipleFiles,
|
||||
onChange,
|
||||
onPasteUrlClick,
|
||||
}) => {
|
||||
const dropRef = React.useRef<HTMLDivElement>(null)
|
||||
const [dragging, setDragging] = React.useState(false)
|
||||
const inputRef = React.useRef(null)
|
||||
@@ -111,17 +118,21 @@ export const Dropzone: React.FC<Props> = ({ className, mimeTypes, onChange, onPa
|
||||
>
|
||||
{t('upload:selectFile')}
|
||||
</Button>
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
className={`${baseClass}__file-button`}
|
||||
onClick={onPasteUrlClick}
|
||||
size="medium"
|
||||
>
|
||||
{t('upload:pasteURL')}
|
||||
</Button>
|
||||
{typeof onPasteUrlClick === 'function' && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
className={`${baseClass}__file-button`}
|
||||
onClick={onPasteUrlClick}
|
||||
size="medium"
|
||||
>
|
||||
{t('upload:pasteURL')}
|
||||
</Button>
|
||||
)}
|
||||
<input
|
||||
accept={mimeTypes?.join(',')}
|
||||
aria-hidden="true"
|
||||
className={`${baseClass}__hidden-input`}
|
||||
multiple={multipleFiles}
|
||||
onChange={handleFileSelection}
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
'use client'
|
||||
import { CloseMenuIcon, MenuIcon } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { ChevronIcon } from '../../icons/Chevron/index.js'
|
||||
import { CloseMenuIcon } from '../../icons/CloseMenu/index.js'
|
||||
import { MenuIcon } from '../../icons/Menu/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'hamburger'
|
||||
|
||||
export const Hamburger: React.FC<{
|
||||
closeIcon?: 'collapse' | 'x'
|
||||
isActive?: boolean
|
||||
readonly closeIcon?: 'collapse' | 'x'
|
||||
readonly isActive?: boolean
|
||||
}> = (props) => {
|
||||
const { t } = useTranslation()
|
||||
const { closeIcon = 'x', isActive = false } = props
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
@import '../../scss/styles';
|
||||
|
||||
.id-label {
|
||||
font-size: base(0.75);
|
||||
font-size: base(0.8);
|
||||
line-height: base(1.2);
|
||||
font-weight: normal;
|
||||
color: var(--theme-elevation-600);
|
||||
background: var(--theme-elevation-100);
|
||||
padding: 0 base(0.6);
|
||||
padding: base(0.2) base(0.4);
|
||||
border-radius: $style-radius-m;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@@ -9,11 +9,10 @@ import AnimateHeightImport from 'react-animate-height'
|
||||
const AnimateHeight = (AnimateHeightImport.default ||
|
||||
AnimateHeightImport) as typeof AnimateHeightImport.default
|
||||
|
||||
import { useListInfo } from '@payloadcms/ui'
|
||||
|
||||
import { useUseTitleField } from '../../hooks/useUseAsTitle.js'
|
||||
import { ChevronIcon } from '../../icons/Chevron/index.js'
|
||||
import { SearchIcon } from '../../icons/Search/index.js'
|
||||
import { useListInfo } from '../../providers/ListInfo/index.js'
|
||||
import { useListQuery } from '../../providers/ListQuery/index.js'
|
||||
import { useSearchParams } from '../../providers/SearchParams/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
@@ -32,13 +31,13 @@ import './index.scss'
|
||||
const baseClass = 'list-controls'
|
||||
|
||||
export type ListControlsProps = {
|
||||
collectionConfig: ClientCollectionConfig
|
||||
enableColumns?: boolean
|
||||
enableSort?: boolean
|
||||
fields: ClientField[]
|
||||
handleSearchChange?: (search: string) => void
|
||||
handleSortChange?: (sort: string) => void
|
||||
handleWhereChange?: (where: Where) => void
|
||||
readonly collectionConfig: ClientCollectionConfig
|
||||
readonly enableColumns?: boolean
|
||||
readonly enableSort?: boolean
|
||||
readonly fields: ClientField[]
|
||||
readonly handleSearchChange?: (search: string) => void
|
||||
readonly handleSortChange?: (sort: string) => void
|
||||
readonly handleWhereChange?: (where: Where) => void
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,15 +6,19 @@ import React, { useEffect, useRef } from 'react'
|
||||
import { usePreferences } from '../../providers/Preferences/index.js'
|
||||
|
||||
type NavContextType = {
|
||||
hydrated: boolean
|
||||
navOpen: boolean
|
||||
navRef: React.RefObject<HTMLDivElement | null>
|
||||
setNavOpen: (value: boolean) => void
|
||||
shouldAnimate: boolean
|
||||
}
|
||||
|
||||
export const NavContext = React.createContext<NavContextType>({
|
||||
hydrated: false,
|
||||
navOpen: true,
|
||||
navRef: null,
|
||||
setNavOpen: () => {},
|
||||
shouldAnimate: false,
|
||||
})
|
||||
|
||||
export const useNav = () => React.useContext(NavContext)
|
||||
@@ -31,7 +35,8 @@ const getNavPreference = async (getPreference): Promise<boolean> => {
|
||||
|
||||
export const NavProvider: React.FC<{
|
||||
children: React.ReactNode
|
||||
}> = ({ children }) => {
|
||||
initialIsOpen?: boolean
|
||||
}> = ({ children, initialIsOpen }) => {
|
||||
const {
|
||||
breakpoints: { l: largeBreak, m: midBreak, s: smallBreak },
|
||||
} = useWindowInfo()
|
||||
@@ -43,7 +48,10 @@ export const NavProvider: React.FC<{
|
||||
// this is because getting the preference is async
|
||||
// so instead of closing it after the preference is loaded
|
||||
// we will open it after the preference is loaded
|
||||
const [navOpen, setNavOpen] = React.useState(false)
|
||||
const [navOpen, setNavOpen] = React.useState(initialIsOpen)
|
||||
|
||||
const [shouldAnimate, setShouldAnimate] = React.useState(false)
|
||||
const [hydrated, setHydrated] = React.useState(false)
|
||||
|
||||
// on load check the user's preference and set "initial" state
|
||||
useEffect(() => {
|
||||
@@ -53,7 +61,7 @@ export const NavProvider: React.FC<{
|
||||
setNavOpen(preferredState)
|
||||
}
|
||||
|
||||
setNavFromPreferences() // eslint-disable-line @typescript-eslint/no-floating-promises
|
||||
void setNavFromPreferences()
|
||||
}
|
||||
}, [largeBreak, getPreference, setNavOpen])
|
||||
|
||||
@@ -76,9 +84,14 @@ export const NavProvider: React.FC<{
|
||||
// close the nav when the user resizes down to mobile
|
||||
// the sidebar is a modal on mobile
|
||||
useEffect(() => {
|
||||
if (largeBreak === false || midBreak === false || smallBreak === false) {
|
||||
if (largeBreak === true || midBreak === true || smallBreak === true) {
|
||||
setNavOpen(false)
|
||||
}
|
||||
setHydrated(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setShouldAnimate(true)
|
||||
}, 100)
|
||||
}, [largeBreak, midBreak, smallBreak])
|
||||
|
||||
// when the component unmounts, clear all body scroll locks
|
||||
@@ -89,6 +102,8 @@ export const NavProvider: React.FC<{
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<NavContext.Provider value={{ navOpen, navRef, setNavOpen }}>{children}</NavContext.Provider>
|
||||
<NavContext.Provider value={{ hydrated, navOpen, navRef, setNavOpen, shouldAnimate }}>
|
||||
{children}
|
||||
</NavContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import type { DropdownIndicatorProps } from 'react-select'
|
||||
|
||||
import { ChevronIcon } from '@payloadcms/ui'
|
||||
import React, { type JSX } from 'react'
|
||||
|
||||
import type { Option as OptionType } from '../types.js'
|
||||
|
||||
import { ChevronIcon } from '../../../icons/Chevron/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'dropdown-indicator'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
.render-title {
|
||||
display: inline-flex;
|
||||
display: inline-block;
|
||||
&__id {
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
|
||||
@@ -8,10 +8,9 @@ import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { StepNavProvider, useStepNav } from './context.js'
|
||||
import './index.scss'
|
||||
export { SetStepNav } from './SetStepNav.js'
|
||||
import { PayloadIcon } from '@payloadcms/ui'
|
||||
|
||||
import type { StepNavItem } from './types.js'
|
||||
|
||||
import { PayloadIcon } from '../../graphics/Icon/index.js'
|
||||
import { RenderComponent } from '../../providers/Config/RenderComponent.js'
|
||||
|
||||
const baseClass = 'step-nav'
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
import type { DateFieldClient, DefaultCellComponentProps } from 'payload'
|
||||
|
||||
import { useConfig } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import { useConfig } from '../../../../../providers/Config/index.js'
|
||||
import { useTranslation } from '../../../../../providers/Translation/index.js'
|
||||
import { formatDate } from '../../../../../utilities/formatDate.js'
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ import type {
|
||||
StaticLabel,
|
||||
} from 'payload'
|
||||
|
||||
import { DefaultCell } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
import type { ColumnPreferences } from '../../providers/ListInfo/index.js'
|
||||
import type { Column } from '../Table/index.js'
|
||||
|
||||
import { DefaultCell } from '../../elements/Table/DefaultCell/index.js'
|
||||
import { FieldLabel } from '../../fields/FieldLabel/index.js'
|
||||
import { flattenFieldMap } from '../../utilities/flattenFieldMap.js'
|
||||
import { SelectAll } from '../SelectAll/index.js'
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
'use client'
|
||||
import type { FormState, SanitizedCollectionConfig, UploadEdits } from 'payload'
|
||||
|
||||
import { useForm, useUploadEdits } from '@payloadcms/ui'
|
||||
import { isImage, reduceFieldsToValues } from 'payload/shared'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { FieldError } from '../../fields/FieldError/index.js'
|
||||
import { fieldBaseClass } from '../../fields/shared/index.js'
|
||||
import { useForm } from '../../forms/Form/index.js'
|
||||
import { useField } from '../../forms/useField/index.js'
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { useUploadEdits } from '../../providers/UploadEdits/index.js'
|
||||
import { Button } from '../Button/index.js'
|
||||
import { Drawer, DrawerToggler } from '../Drawer/index.js'
|
||||
import { Dropzone } from '../Dropzone/index.js'
|
||||
@@ -33,10 +34,10 @@ const validate = (value) => {
|
||||
}
|
||||
|
||||
type UploadActionsArgs = {
|
||||
customActions?: React.ReactNode[]
|
||||
enableAdjustments: boolean
|
||||
enablePreviewSizes: boolean
|
||||
mimeType: string
|
||||
readonly customActions?: React.ReactNode[]
|
||||
readonly enableAdjustments: boolean
|
||||
readonly enablePreviewSizes: boolean
|
||||
readonly mimeType: string
|
||||
}
|
||||
|
||||
export const UploadActions = ({
|
||||
@@ -77,11 +78,11 @@ export const UploadActions = ({
|
||||
}
|
||||
|
||||
export type UploadProps = {
|
||||
collectionSlug: string
|
||||
customActions?: React.ReactNode[]
|
||||
initialState?: FormState
|
||||
onChange?: (file?: File) => void
|
||||
uploadConfig: SanitizedCollectionConfig['upload']
|
||||
readonly collectionSlug: string
|
||||
readonly customActions?: React.ReactNode[]
|
||||
readonly initialState?: FormState
|
||||
readonly onChange?: (file?: File) => void
|
||||
readonly uploadConfig: SanitizedCollectionConfig['upload']
|
||||
}
|
||||
|
||||
export const Upload: React.FC<UploadProps> = (props) => {
|
||||
@@ -108,15 +109,7 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
||||
const handleFileChange = useCallback(
|
||||
(newFile: File) => {
|
||||
if (newFile instanceof File) {
|
||||
const fileReader = new FileReader()
|
||||
fileReader.onload = (e) => {
|
||||
const imgSrc = e.target?.result
|
||||
|
||||
if (typeof imgSrc === 'string') {
|
||||
setFileSrc(imgSrc)
|
||||
}
|
||||
}
|
||||
fileReader.readAsDataURL(newFile)
|
||||
setFileSrc(URL.createObjectURL(newFile))
|
||||
}
|
||||
|
||||
setValue(newFile)
|
||||
@@ -201,6 +194,9 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
setDoc(reduceFieldsToValues(initialState || {}, true))
|
||||
if (initialState?.file?.value instanceof File) {
|
||||
setFileSrc(URL.createObjectURL(initialState.file.value))
|
||||
}
|
||||
setReplacingFile(false)
|
||||
}, [initialState])
|
||||
|
||||
@@ -265,7 +261,9 @@ export const Upload: React.FC<UploadProps> = (props) => {
|
||||
<div className={`${baseClass}__add-file-wrap`}>
|
||||
<button
|
||||
className={`${baseClass}__add-file`}
|
||||
onClick={handleUrlSubmit}
|
||||
onClick={() => {
|
||||
void handleUrlSubmit()
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{t('upload:addFile')}
|
||||
|
||||
@@ -32,6 +32,7 @@ export { Collapsible } from '../../elements/Collapsible/index.js'
|
||||
export { CopyToClipboard } from '../../elements/CopyToClipboard/index.js'
|
||||
export { DeleteMany } from '../../elements/DeleteMany/index.js'
|
||||
export { DocumentControls } from '../../elements/DocumentControls/index.js'
|
||||
export { Dropzone } from '../../elements/Dropzone/index.js'
|
||||
export { useDocumentDrawer } from '../../elements/DocumentDrawer/index.js'
|
||||
export { DocumentFields } from '../../elements/DocumentFields/index.js'
|
||||
export { Drawer, DrawerToggler, formatDrawerSlug } from '../../elements/Drawer/index.js'
|
||||
|
||||
@@ -21,5 +21,3 @@ export {
|
||||
} from '../../utilities/groupNavItems.js'
|
||||
export { hasSavePermission } from '../../utilities/hasSavePermission.js'
|
||||
export { isEditing } from '../../utilities/isEditing.js'
|
||||
|
||||
export const emptyComponent = () => null
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user