Compare commits
64 Commits
db-postgre
...
db-postgre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c39472259a | ||
|
|
e2d36c3cab | ||
|
|
0e682a32c3 | ||
|
|
266c3274d0 | ||
|
|
67b3baaa44 | ||
|
|
55659c7c36 | ||
|
|
6a0a859563 | ||
|
|
57da3c99a7 | ||
|
|
611438177b | ||
|
|
d068ef7e24 | ||
|
|
7a9af4417a | ||
|
|
8d14c213c8 | ||
|
|
182c57b191 | ||
|
|
15459fb8e3 | ||
|
|
3ca71c4def | ||
|
|
64136a6b17 | ||
|
|
acba5e482b | ||
|
|
571f190f34 | ||
|
|
131d89c3f5 | ||
|
|
55c38a8934 | ||
|
|
2abb46f4f1 | ||
|
|
f41780ef33 | ||
|
|
105392cf07 | ||
|
|
fa2e68ad1c | ||
|
|
2053e4eeab | ||
|
|
432794fa55 | ||
|
|
6787f0dfd9 | ||
|
|
0b0a40c9fb | ||
|
|
95c43a2ab4 | ||
|
|
f4037a6bdc | ||
|
|
c4d173ae0f | ||
|
|
3e5149bc43 | ||
|
|
17f7b94555 | ||
|
|
04850694c1 | ||
|
|
eb42c031ef | ||
|
|
dc253676e8 | ||
|
|
926372f15a | ||
|
|
b008b6c646 | ||
|
|
a67a9379ce | ||
|
|
3e9826d7ae | ||
|
|
f8a095e7f4 | ||
|
|
9c046d049a | ||
|
|
0871f299ef | ||
|
|
a074a5b376 | ||
|
|
abf3378441 | ||
|
|
c1b41b75c4 | ||
|
|
19706617e5 | ||
|
|
cc28df1324 | ||
|
|
607d345eb2 | ||
|
|
e41515564b | ||
|
|
f615b8cdf2 | ||
|
|
9182e79c2d | ||
|
|
ed95722a50 | ||
|
|
d7adb094a5 | ||
|
|
717e01bbbf | ||
|
|
3987953947 | ||
|
|
b7c750220e | ||
|
|
33f9357e58 | ||
|
|
9109f7094b | ||
|
|
92bd914966 | ||
|
|
b210551e96 | ||
|
|
5e64e52dab | ||
|
|
90e9dd7f47 | ||
|
|
f867d7a615 |
50
.github/CODEOWNERS
vendored
Normal file
50
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
# Order matters. The last matching pattern takes precedence.
|
||||
|
||||
### Catch-all ###
|
||||
* @denolfe @jmikrut @DanRibbens
|
||||
.* @denolfe @jmikrut @DanRibbens
|
||||
|
||||
### Core ###
|
||||
/packages/payload/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/payload/src/uploads/ @denolfe
|
||||
/packages/payload/src/admin/ @jmikrut @jacobsfletch @JarrodMFlesch
|
||||
|
||||
### Adapters ###
|
||||
/packages/bundler-*/ @denolfe @jmikrut @DanRibbens @JarrodMFlesch
|
||||
/packages/db-*/ @denolfe @jmikrut @DanRibbens
|
||||
/packages/richtext-*/ @denolfe @jmikrut @DanRibbens @AlessioGr
|
||||
|
||||
### Plugins ###
|
||||
/packages/plugin-*/ @denolfe @jmikrut @DanRibbens @jacobsfletch @JarrodMFlesch @AlessioGr
|
||||
/packages/plugin-cloud*/ @denolfe
|
||||
/packages/plugin-form-builder/ @jacobsfletch
|
||||
/packages/plugin-live-preview*/ @jacobsfletch
|
||||
/packages/plugin-nested-docs/ @jacobsfletch
|
||||
/packages/plugin-password-protection/ @jmikrut
|
||||
/packages/plugin-redirects/ @jacobsfletch
|
||||
/packages/plugin-search/ @jacobsfletch
|
||||
/packages/plugin-sentry/ @JessChowdhury
|
||||
/packages/plugin-seo/ @jacobsfletch
|
||||
/packages/plugin-stripe/ @jacobsfletch
|
||||
/packages/plugin-zapier/ @JarrodMFlesch
|
||||
|
||||
### Examples ###
|
||||
/examples/ @jacobsfletch
|
||||
/examples/testing/ @JarrodMFlesch
|
||||
/examples/email/ @JessChowdhury
|
||||
/examples/whitelabel/ @JessChowdhury
|
||||
|
||||
### Templates ###
|
||||
/templates/ @jacobsfletch
|
||||
/templates/blank/ @denolfe
|
||||
|
||||
### Misc ###
|
||||
/packages/create-payload-app/ @denolfe
|
||||
/packages/eslint-config-payload/ @denolfe
|
||||
/packages/payload-admin-bar/ @jacobsfletch
|
||||
|
||||
### Root ###
|
||||
/package.json @denolfe
|
||||
/scripts/ @denolfe
|
||||
/.github/ @denolfe
|
||||
/.github/CODEOWNERS @denolfe
|
||||
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
with:
|
||||
filters: |
|
||||
needs_build:
|
||||
- '.github/workflows/**'
|
||||
- 'packages/**'
|
||||
- 'test/**'
|
||||
- 'pnpm-lock.yaml'
|
||||
@@ -131,6 +132,7 @@ jobs:
|
||||
- name: Integration Tests
|
||||
run: pnpm test:int
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8096
|
||||
PAYLOAD_DATABASE: ${{ matrix.database }}
|
||||
POSTGRES_URL: ${{ env.POSTGRES_URL }}
|
||||
|
||||
@@ -140,7 +142,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
part: [1/4, 2/4, 3/4, 4/4]
|
||||
part: [1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8, 8/8]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
@@ -253,6 +255,7 @@ jobs:
|
||||
- plugin-form-builder
|
||||
- plugin-nested-docs
|
||||
- plugin-search
|
||||
- plugin-sentry
|
||||
|
||||
steps:
|
||||
- name: Use Node.js 18
|
||||
|
||||
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -47,6 +47,13 @@
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "pnpm run dev uploads",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"name": "Run Dev Uploads",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
},
|
||||
{
|
||||
"command": "PAYLOAD_BUNDLER=vite pnpm run dev fields",
|
||||
"cwd": "${workspaceFolder}",
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,3 +1,25 @@
|
||||
## [2.1.0](https://github.com/payloadcms/payload/compare/v2.0.15...v2.1.0) (2023-11-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add internationalization (i18n) to locales ([#4005](https://github.com/payloadcms/payload/issues/4005)) ([6a0a859](https://github.com/payloadcms/payload/commit/6a0a859563ed9e742260ea51a1839a1ef0f61fce))
|
||||
* Custom Error, Label, and before/after field components ([#3747](https://github.com/payloadcms/payload/issues/3747)) ([266c327](https://github.com/payloadcms/payload/commit/266c3274d03e4fd52c692eeef1ee9248dcf66189))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* error on graphql multiple queries ([#3985](https://github.com/payloadcms/payload/issues/3985)) ([57da3c9](https://github.com/payloadcms/payload/commit/57da3c99a7e4ce5d3d1e17315e3691815f363704))
|
||||
* focal and cropping issues, adds test ([#4039](https://github.com/payloadcms/payload/issues/4039)) ([acba5e4](https://github.com/payloadcms/payload/commit/acba5e482b7ddc6e3dc6ba9b7736022770d69a55))
|
||||
* handle invalid tokens in refresh token operation ([#3647](https://github.com/payloadcms/payload/issues/3647)) ([131d89c](https://github.com/payloadcms/payload/commit/131d89c3f50c237e1ab2d7cd32d7a8226a9f8ce3))
|
||||
* hasMany number and select fields unable to save within arrays ([#4047](https://github.com/payloadcms/payload/issues/4047)) ([182c57b](https://github.com/payloadcms/payload/commit/182c57b191010ce3dcf659f39c1dc2f7cf80662e))
|
||||
* injects array and block ids into fieldSchemaToJSON ([#4043](https://github.com/payloadcms/payload/issues/4043)) ([d068ef7](https://github.com/payloadcms/payload/commit/d068ef7e2483d49dc41bdd7735042ddcaa0a684c))
|
||||
* parse predefined migrations via file arg or name prefix ([#4001](https://github.com/payloadcms/payload/issues/4001)) ([eb42c03](https://github.com/payloadcms/payload/commit/eb42c031ef980558ed051d4163925aa28d6ab090))
|
||||
* polymorphic hasMany relationships missing in postgres admin ([#4053](https://github.com/payloadcms/payload/issues/4053)) ([7a9af44](https://github.com/payloadcms/payload/commit/7a9af4417a56c621f01195f9a2904b9adffaad7a))
|
||||
* resets list filter row when the filter on field is changed ([#3956](https://github.com/payloadcms/payload/issues/3956)) ([8d14c21](https://github.com/payloadcms/payload/commit/8d14c213c878a1afda2b3bf03431fed5aa2a44e3))
|
||||
* Update API Views ([b008b6c](https://github.com/payloadcms/payload/commit/b008b6c6463c9dc3d8e61eaa0a9210aa1a189442))
|
||||
* vite not replacing env vars correctly when building ([67b3baa](https://github.com/payloadcms/payload/commit/67b3baaa445a13246be8178d57eaeba92888bef1))
|
||||
|
||||
## [2.0.15](https://github.com/payloadcms/payload/compare/v2.0.14...v2.0.15) (2023-11-03)
|
||||
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<hr/>
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 🎉 <strong>Payload 2.0 is now available!<strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
||||
> 🎉 <strong>Payload 2.0 is now available!</strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
@@ -51,7 +51,7 @@ Create a cloud account, connect your GitHub, and [deploy in minutes](https://pay
|
||||
Before beginning to work with Payload, make sure you have all of the [required software](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
```text
|
||||
npx create-payload-app
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
|
||||
|
||||
@@ -432,6 +432,15 @@ All Payload fields support the ability to swap in your own React components. So,
|
||||
| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) |
|
||||
| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) |
|
||||
|
||||
As an alternative to replacing the entire Field component, you may want to keep the majority of the default Field component and only swap components within. This allows you to replace the **`Label`** or **`Error`** within a field component or add additional components inside the field with **`BeforeInput`** or **`AfterInput`**. **`BeforeInput`** and **`AfterInput`** are allowed in any fields that don't contain other fields, except [UI](/docs/fields/ui) and [Rich Text](/docs/fields/rich-text).
|
||||
|
||||
| Component | Description |
|
||||
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **`Label`** | Override the default Label in the Field Component. [More](#label-component) |
|
||||
| **`Error`** | Override the default Label in the Field Component. [More](#error-component) |
|
||||
| **`BeforeInput`** | An array of elements that will be added before `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
| **`AfterInput`** | An array of elements that will be added after `input`/`textarea` elements. [More](#afterinput-and-beforeinput) |
|
||||
|
||||
## Cell Component
|
||||
|
||||
These are the props that will be passed to your custom Cell to use in your own components.
|
||||
@@ -487,6 +496,103 @@ const CustomTextField: React.FC<Props> = ({ path }) => {
|
||||
components, including the <strong>useField</strong> hook, [click here](/docs/admin/hooks).
|
||||
</Banner>
|
||||
|
||||
## Label Component
|
||||
|
||||
These are the props that will be passed to your custom Label.
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ---------------------------------------------------------------- |
|
||||
| **`htmlFor`** | Property used to set `for` attribute for label. |
|
||||
| **`label`** | Label value provided in field, it can be used with i18n. |
|
||||
| **`required`** | A boolean value that represents if the field is required or not. |
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from 'payload/utilities/getTranslation'
|
||||
|
||||
type Props = {
|
||||
htmlFor?: string
|
||||
label?: Record<string, string> | false | string
|
||||
required?: boolean
|
||||
}
|
||||
|
||||
const CustomLabel: React.FC<Props> = (props) => {
|
||||
const { htmlFor, label, required = false } = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
if (label) {
|
||||
return (<span>
|
||||
{getTranslation(label, i18n)}
|
||||
{required && <span className="required">*</span>}
|
||||
</span>);
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
## Error Component
|
||||
|
||||
These are the props that will be passed to your custom Error.
|
||||
|
||||
| Property | Description |
|
||||
| ---------------- | ------------------------------------------------------------- |
|
||||
| **`message`** | The error message. |
|
||||
| **`showError`** | A boolean value that represents if the error should be shown. |
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
message: string
|
||||
showError?: boolean
|
||||
}
|
||||
|
||||
const CustomError: React.FC<Props> = (props) => {
|
||||
const { message, showError } = props
|
||||
|
||||
if (showError) {
|
||||
return <p style={{color: 'red'}}>{message}</p>
|
||||
} else return null;
|
||||
}
|
||||
```
|
||||
|
||||
## AfterInput and BeforeInput
|
||||
|
||||
With these properties you can add multiple components before and after the input element. For example, you can add an absolutely positioned button to clear the current field value.
|
||||
|
||||
#### Example
|
||||
|
||||
```tsx
|
||||
import React from 'react'
|
||||
import './style.scss'
|
||||
|
||||
const ClearButton: React.FC = () => {
|
||||
return <button onClick={() => {/* ... */}}>X</button>
|
||||
}
|
||||
|
||||
const fieldField: Field = {
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
admin: {
|
||||
components: {
|
||||
AfterInput: [
|
||||
<ClearButton />
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default titleField;
|
||||
```
|
||||
|
||||
## Custom providers
|
||||
|
||||
As your admin customizations gets more complex you may want to share state between fields or other components. You can add custom providers to do add your own context to any Payload app for use in other custom components within the admin panel. Within your config add `admin.components.providers`, these can be used to share context or provide other custom functionality. Read the [React context](https://reactjs.org/docs/context.html) docs to learn more.
|
||||
|
||||
@@ -758,3 +758,29 @@ const MyComponent: React.FC = () => {
|
||||
### usePreferences
|
||||
|
||||
Returns methods to set and get user preferences. More info can be found [here](https://payloadcms.com/docs/admin/preferences).
|
||||
|
||||
### useTableColumns
|
||||
|
||||
Returns methods to manipulate table columns
|
||||
|
||||
```tsx
|
||||
import { useTableColumns } from 'payload/components/utilities'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// highlight-start
|
||||
const { setActiveColumns } = useTableColumns()
|
||||
|
||||
const resetColumns = () => {
|
||||
setActiveColumns(['id', 'createdAt', 'updatedAt'])
|
||||
}
|
||||
// highlight-end
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={resetColumns}
|
||||
>
|
||||
Reset columns
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,6 +57,38 @@ export default buildConfig({
|
||||
})
|
||||
```
|
||||
|
||||
**Example Payload config set up for localization with full locales objects (including [internationalization](/docs/configuration/i18n) support):**
|
||||
|
||||
```ts
|
||||
import { buildConfig } from 'payload/config'
|
||||
|
||||
export default buildConfig({
|
||||
collections: [
|
||||
// collections go here
|
||||
],
|
||||
localization: {
|
||||
locales: [
|
||||
{
|
||||
label: {
|
||||
en: 'English', // English label
|
||||
nb: 'Engelsk', // Norwegian label
|
||||
},
|
||||
code: 'en',
|
||||
},
|
||||
{
|
||||
label: {
|
||||
en: 'Norwegian', // English label
|
||||
nb: 'Norsk', // Norwegian label
|
||||
},
|
||||
code: 'nb',
|
||||
},
|
||||
],
|
||||
defaultLocale: 'en',
|
||||
fallback: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Here is a brief explanation of each of the options available within the `localization` property:**
|
||||
|
||||
**`locales`**
|
||||
|
||||
@@ -23,7 +23,7 @@ Payload requires the following software:
|
||||
To quickly scaffold a new Payload app in the fastest way possible, you can use [create-payload-app](https://npmjs.com/package/create-payload-app). To do so, run the following command:
|
||||
|
||||
```
|
||||
npx create-payload-app
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
Then just follow the prompts! You'll get set up with a new folder and a functioning Payload app inside.
|
||||
|
||||
@@ -84,7 +84,7 @@ If you're starting from scratch, you can easily setup a dev environment lik
|
||||
```
|
||||
mkdir dev
|
||||
cd dev
|
||||
npx create-payload-app
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
If you're using the plugin template, the dev folder is built out for you and the `samplePlugin` has already been installed in `dev/payload.config()`.
|
||||
|
||||
@@ -9,7 +9,7 @@ keywords: headless cms, typescript, documentation, Content Management System, cm
|
||||
Payload supports TypeScript natively, and not only that, the entirety of the CMS is built with TypeScript. To get started developing with Payload and TypeScript, you can use one of Payload's built-in boilerplates in one line via `create-payload-app`:
|
||||
|
||||
```
|
||||
npx create-payload-app
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
Pick a TypeScript project type to get started easily.
|
||||
|
||||
12
package.json
12
package.json
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@playwright/test": "1.38.1",
|
||||
"@playwright/test": "1.39.0",
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/jest": "0.2.29",
|
||||
"@swc/register": "0.1.10",
|
||||
@@ -45,7 +45,7 @@
|
||||
"@types/conventional-changelog-core": "^4.2.5",
|
||||
"@types/conventional-changelog-preset-loader": "^2.3.4",
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/jest": "29.5.4",
|
||||
"@types/jest": "29.5.7",
|
||||
"@types/minimist": "1.2.2",
|
||||
"@types/node": "20.5.7",
|
||||
"@types/prompts": "^2.4.5",
|
||||
@@ -64,6 +64,7 @@
|
||||
"copyfiles": "2.4.1",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "8.6.0",
|
||||
"drizzle-orm": "0.28.5",
|
||||
"express": "4.18.2",
|
||||
"form-data": "3.0.1",
|
||||
"fs-extra": "10.1.0",
|
||||
@@ -73,8 +74,8 @@
|
||||
"graphql-request": "6.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"isomorphic-fetch": "3.0.0",
|
||||
"jest": "29.6.4",
|
||||
"jest-environment-jsdom": "29.6.4",
|
||||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jwt-decode": "3.1.2",
|
||||
"lint-staged": "^14.0.1",
|
||||
"minimist": "1.2.8",
|
||||
@@ -111,5 +112,8 @@
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@sentry/react": "^7.77.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,6 @@ export const getViteConfig = async (payloadConfig: SanitizedConfig): Promise<Inl
|
||||
'module.hot': 'undefined',
|
||||
'process.argv': '[]',
|
||||
'process.cwd': 'function () { return "/" }',
|
||||
'process.env': '{}',
|
||||
'process?.cwd': 'function () { return "/" }',
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "1.0.6",
|
||||
"version": "1.0.7",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -30,8 +30,11 @@ export const createMigration: CreateMigration = async function createMigration({
|
||||
|
||||
let migrationFileContent: string | undefined
|
||||
|
||||
// Check for predefined migration
|
||||
if (file) {
|
||||
// Check for predefined migration.
|
||||
// Either passed in via --file or prefixed with @payloadcms/db-mongodb/
|
||||
if (file || migrationName.startsWith('@payloadcms/db-mongodb/')) {
|
||||
if (!file) file = migrationName
|
||||
|
||||
const predefinedMigrationName = file.replace('@payloadcms/db-mongodb/', '')
|
||||
migrationName = predefinedMigrationName
|
||||
const cleanPath = path.join(__dirname, `../predefinedMigrations/${predefinedMigrationName}.js`)
|
||||
|
||||
@@ -4,6 +4,7 @@ export const commitTransaction: CommitTransaction = async function commitTransac
|
||||
if (!this.sessions[id]?.inTransaction()) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.sessions[id].commitTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
delete this.sessions[id]
|
||||
|
||||
@@ -3,10 +3,20 @@ import type { RollbackTransaction } from 'payload/database'
|
||||
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
|
||||
id = '',
|
||||
) {
|
||||
if (!this.sessions[id]?.inTransaction()) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
// if multiple operations are using the same transaction, the first will flow through and delete the session.
|
||||
// subsequent calls should be ignored.
|
||||
if (!this.sessions[id]) {
|
||||
return
|
||||
}
|
||||
|
||||
// when session exists but is not inTransaction something unexpected is happening to the session
|
||||
if (!this.sessions[id].inTransaction()) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
delete this.sessions[id]
|
||||
return
|
||||
}
|
||||
|
||||
// the first call for rollback should be aborted and deleted causing any other operations with the same transaction to fail
|
||||
await this.sessions[id].abortTransaction()
|
||||
await this.sessions[id].endSession()
|
||||
delete this.sessions[id]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.13",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"repository": "https://github.com/payloadcms/payload",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -23,6 +23,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
@@ -37,6 +38,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!collection.versions?.drafts,
|
||||
disableUnique: true,
|
||||
@@ -52,6 +54,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global?.versions?.drafts,
|
||||
disableUnique: false,
|
||||
@@ -66,6 +69,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
|
||||
|
||||
buildTable({
|
||||
adapter: this,
|
||||
buildNumbers: true,
|
||||
buildRelationships: true,
|
||||
disableNotNull: !!global.versions?.drafts,
|
||||
disableUnique: true,
|
||||
|
||||
@@ -26,6 +26,7 @@ type Args = {
|
||||
adapter: PostgresAdapter
|
||||
baseColumns?: Record<string, PgColumnBuilder>
|
||||
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
|
||||
buildNumbers?: boolean
|
||||
buildRelationships?: boolean
|
||||
disableNotNull: boolean
|
||||
disableUnique: boolean
|
||||
@@ -39,6 +40,7 @@ type Args = {
|
||||
}
|
||||
|
||||
type Result = {
|
||||
hasManyNumberField: 'index' | boolean
|
||||
relationsToBuild: Map<string, string>
|
||||
}
|
||||
|
||||
@@ -46,6 +48,7 @@ export const buildTable = ({
|
||||
adapter,
|
||||
baseColumns = {},
|
||||
baseExtraConfig = {},
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
disableNotNull,
|
||||
disableUnique = false,
|
||||
@@ -53,10 +56,11 @@ export const buildTable = ({
|
||||
rootRelationsToBuild,
|
||||
rootRelationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
rootTableName: incomingRootTableName,
|
||||
tableName,
|
||||
timestamps,
|
||||
}: Args): Result => {
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
const columns: Record<string, PgColumnBuilder> = baseColumns
|
||||
const indexes: Record<string, (cols: GenericColumns) => IndexBuilder> = {}
|
||||
|
||||
@@ -102,6 +106,7 @@ export const buildTable = ({
|
||||
hasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columns,
|
||||
disableNotNull,
|
||||
@@ -116,7 +121,7 @@ export const buildTable = ({
|
||||
relationships,
|
||||
rootRelationsToBuild: rootRelationsToBuild || relationsToBuild,
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName: rootTableName || tableName,
|
||||
rootTableName,
|
||||
}))
|
||||
|
||||
if (timestamps) {
|
||||
@@ -185,8 +190,8 @@ export const buildTable = ({
|
||||
adapter.relations[`relations_${localeTableName}`] = localesTableRelations
|
||||
}
|
||||
|
||||
if (hasManyNumberField) {
|
||||
const numbersTableName = `${tableName}_numbers`
|
||||
if (hasManyNumberField && buildNumbers) {
|
||||
const numbersTableName = `${rootTableName}_numbers`
|
||||
const columns: Record<string, PgColumnBuilder> = {
|
||||
id: serial('id').primaryKey(),
|
||||
number: numeric('number'),
|
||||
@@ -327,5 +332,5 @@ export const buildTable = ({
|
||||
|
||||
adapter.relations[`relations_${tableName}`] = tableRelations
|
||||
|
||||
return { relationsToBuild }
|
||||
return { hasManyNumberField, relationsToBuild }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Relation } from 'drizzle-orm'
|
||||
import { relations } from 'drizzle-orm'
|
||||
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
|
||||
import { relations } from 'drizzle-orm'
|
||||
import {
|
||||
PgNumericBuilder,
|
||||
PgVarcharBuilder,
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
PgNumericBuilder,
|
||||
PgVarcharBuilder,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from 'drizzle-orm/pg-core'
|
||||
import type { Field, TabAsField } from 'payload/types'
|
||||
import { fieldAffectsData, optionIsObject } from 'payload/types'
|
||||
import { InvalidConfiguration } from 'payload/errors'
|
||||
import { fieldAffectsData, optionIsObject } from 'payload/types'
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
|
||||
import type { GenericColumns, PostgresAdapter } from '../types'
|
||||
@@ -31,6 +32,7 @@ import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdent
|
||||
|
||||
type Args = {
|
||||
adapter: PostgresAdapter
|
||||
buildNumbers: boolean
|
||||
buildRelationships: boolean
|
||||
columnPrefix?: string
|
||||
columns: Record<string, PgColumnBuilder>
|
||||
@@ -60,6 +62,7 @@ type Result = {
|
||||
|
||||
export const traverseFields = ({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -283,19 +286,25 @@ export const traverseFields = ({
|
||||
baseExtraConfig._localeIdx = (cols) => index('_locale_idx').on(cols._locale)
|
||||
}
|
||||
|
||||
const { relationsToBuild: subRelationsToBuild } = buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
||||
rootRelationsToBuild,
|
||||
rootRelationships: relationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
})
|
||||
const { hasManyNumberField: subHasManyNumberField, relationsToBuild: subRelationsToBuild } =
|
||||
buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
disableNotNull: disableNotNullFromHere,
|
||||
disableUnique,
|
||||
fields: disableUnique ? idToUUID(field.fields) : field.fields,
|
||||
rootRelationsToBuild,
|
||||
rootRelationships: relationships,
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
})
|
||||
|
||||
if (subHasManyNumberField) {
|
||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||
hasManyNumberField = subHasManyNumberField
|
||||
}
|
||||
|
||||
relationsToBuild.set(fieldName, arrayTableName)
|
||||
|
||||
@@ -351,7 +360,10 @@ export const traverseFields = ({
|
||||
baseExtraConfig._localeIdx = (cols) => index('locale_idx').on(cols._locale)
|
||||
}
|
||||
|
||||
const { relationsToBuild: subRelationsToBuild } = buildTable({
|
||||
const {
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
} = buildTable({
|
||||
adapter,
|
||||
baseColumns,
|
||||
baseExtraConfig,
|
||||
@@ -365,6 +377,11 @@ export const traverseFields = ({
|
||||
tableName: blockTableName,
|
||||
})
|
||||
|
||||
if (subHasManyNumberField) {
|
||||
if (!hasManyNumberField || subHasManyNumberField === 'index')
|
||||
hasManyNumberField = subHasManyNumberField
|
||||
}
|
||||
|
||||
const blockTableRelations = relations(
|
||||
adapter.tables[blockTableName],
|
||||
({ many, one }) => {
|
||||
@@ -413,6 +430,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: groupHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -449,6 +467,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: groupHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix: `${columnName}_`,
|
||||
columns,
|
||||
@@ -486,6 +505,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: tabHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
@@ -524,6 +544,7 @@ export const traverseFields = ({
|
||||
hasManyNumberField: rowHasManyNumberField,
|
||||
} = traverseFields({
|
||||
adapter,
|
||||
buildNumbers,
|
||||
buildRelationships,
|
||||
columnPrefix,
|
||||
columns,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CommitTransaction } from 'payload/database'
|
||||
|
||||
export const commitTransaction: CommitTransaction = async function commitTransaction(id) {
|
||||
// if the session was deleted it has already been aborted
|
||||
if (!this.sessions[id]) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ import type { RollbackTransaction } from 'payload/database'
|
||||
export const rollbackTransaction: RollbackTransaction = async function rollbackTransaction(
|
||||
id = '',
|
||||
) {
|
||||
// if multiple operations are using the same transaction, the first will flow through and delete the session.
|
||||
// subsequent calls should be ignored.
|
||||
if (!this.sessions[id]) {
|
||||
this.payload.logger.warn('rollbackTransaction called when no transaction exists')
|
||||
return
|
||||
}
|
||||
|
||||
// end the session promise in failure by calling reject
|
||||
await this.sessions[id].reject()
|
||||
|
||||
// delete the session causing any other operations with the same transaction to fail
|
||||
delete this.sessions[id]
|
||||
}
|
||||
|
||||
@@ -3,16 +3,18 @@ import { isArrayOfRows } from '../../utilities/isArrayOfRows'
|
||||
|
||||
type Args = {
|
||||
data: unknown
|
||||
id?: unknown
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export const transformSelects = ({ data, locale }: Args) => {
|
||||
export const transformSelects = ({ id, data, locale }: Args) => {
|
||||
const newRows: Record<string, unknown>[] = []
|
||||
|
||||
if (isArrayOfRows(data)) {
|
||||
data.forEach((value, i) => {
|
||||
const newRow: Record<string, unknown> = {
|
||||
order: i + 1,
|
||||
parent: id,
|
||||
value,
|
||||
}
|
||||
|
||||
|
||||
@@ -422,6 +422,7 @@ export const traverseFields = ({
|
||||
Object.entries(data[field.name]).forEach(([localeKey, localeData]) => {
|
||||
if (Array.isArray(localeData)) {
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: localeData,
|
||||
locale: localeKey,
|
||||
})
|
||||
@@ -432,6 +433,7 @@ export const traverseFields = ({
|
||||
}
|
||||
} else if (Array.isArray(data[field.name])) {
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: data[field.name],
|
||||
})
|
||||
|
||||
|
||||
@@ -102,7 +102,9 @@ export const upsertRow = async <T extends TypeWithID>({
|
||||
if (Object.keys(rowToInsert.selects).length > 0) {
|
||||
Object.entries(rowToInsert.selects).forEach(([selectTableName, selectRows]) => {
|
||||
selectRows.forEach((row) => {
|
||||
row.parent = insertedRow.id
|
||||
if (typeof row.parent === 'undefined') {
|
||||
row.parent = insertedRow.id
|
||||
}
|
||||
if (!selectsToInsert[selectTableName]) selectsToInsert[selectTableName] = []
|
||||
selectsToInsert[selectTableName].push(row)
|
||||
})
|
||||
|
||||
@@ -26,9 +26,8 @@
|
||||
</h4>
|
||||
<hr/>
|
||||
|
||||
<h3>
|
||||
🎉 Payload 2.0 is now available! Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>
|
||||
</h3>
|
||||
> [!IMPORTANT]
|
||||
> 🎉 <strong>Payload 2.0 is now available!</strong> Read more in the <a target="_blank" href="https://payloadcms.com/blog/payload-2-0" rel="dofollow"><strong>announcement post</strong></a>.
|
||||
|
||||
<h3>Benefits over a regular CMS</h3>
|
||||
<ul>
|
||||
@@ -52,7 +51,7 @@ Create a cloud account, connect your GitHub, and [deploy in minutes](https://pay
|
||||
Before beginning to work with Payload, make sure you have all of the [required software](https://payloadcms.com/docs/getting-started/installation).
|
||||
|
||||
```text
|
||||
npx create-payload-app
|
||||
npx create-payload-app@latest
|
||||
```
|
||||
|
||||
Alternatively, it only takes about five minutes to [create an app from scratch](https://payloadcms.com/docs/getting-started/installation#from-scratch).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "2.0.15",
|
||||
"version": "2.1.0",
|
||||
"description": "Node, React and MongoDB Headless CMS and Application Framework",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.js",
|
||||
@@ -135,7 +135,7 @@
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-essentials": "7.0.3",
|
||||
"use-context-selector": "1.4.1",
|
||||
"uuid": "8.3.2"
|
||||
"uuid": "9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
|
||||
@@ -83,7 +83,7 @@ export const EditUpload: React.FC<{
|
||||
setFormQueryParams({
|
||||
...formQueryParams,
|
||||
uploadEdits: {
|
||||
crop: crop ? crop : undefined,
|
||||
crop: crop || undefined,
|
||||
focalPoint: pointPosition ? pointPosition : undefined,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,28 +1,33 @@
|
||||
import React from 'react'
|
||||
import { Chevron } from '../../..'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { Chevron } from '../../..'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'localizer-button'
|
||||
|
||||
export const LocalizerLabel: React.FC<{
|
||||
className?: string
|
||||
ariaLabel?: string
|
||||
className?: string
|
||||
}> = (props) => {
|
||||
const { className, ariaLabel } = props
|
||||
const { ariaLabel, className } = props
|
||||
const locale = useLocale()
|
||||
const { t } = useTranslation('general')
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
aria-label={ariaLabel || t('locale')}
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__label`}>{`${t('locale')}:`}</div>
|
||||
|
||||
<span className={`${baseClass}__current-label`}>{`${locale.label}`}</span>
|
||||
<span className={`${baseClass}__current-label`}>{`${getTranslation(
|
||||
locale.label,
|
||||
i18n,
|
||||
)}`}</span>
|
||||
|
||||
<Chevron className={`${baseClass}__chevron`} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import qs from 'qs'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { getTranslation } from '../../../../utilities/getTranslation'
|
||||
import { useConfig } from '../../utilities/Config'
|
||||
import { useLocale } from '../../utilities/Locale'
|
||||
import { useSearchParams } from '../../utilities/SearchParams'
|
||||
@@ -18,9 +20,12 @@ const Localizer: React.FC<{
|
||||
const config = useConfig()
|
||||
const { localization } = config
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
const locale = useLocale()
|
||||
const searchParams = useSearchParams()
|
||||
|
||||
const localeLabel = getTranslation(locale.label, i18n)
|
||||
|
||||
if (localization) {
|
||||
const { locales } = localization
|
||||
|
||||
@@ -44,8 +49,8 @@ const Localizer: React.FC<{
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{locale.label}
|
||||
{locale.label !== locale.code && ` (${locale.code})`}
|
||||
{localeLabel}
|
||||
{localeLabel !== locale.code && ` (${locale.code})`}
|
||||
</PopupList.Button>
|
||||
) : null}
|
||||
|
||||
@@ -57,11 +62,12 @@ const Localizer: React.FC<{
|
||||
locale: localeOption.code,
|
||||
}
|
||||
const search = qs.stringify(newParams)
|
||||
const localeOptionLabel = getTranslation(localeOption.label, i18n)
|
||||
|
||||
return (
|
||||
<PopupList.Button key={localeOption.code} onClick={close} to={{ search }}>
|
||||
{localeOption.label}
|
||||
{localeOption.label !== localeOption.code && ` (${localeOption.code})`}
|
||||
{localeOptionLabel}
|
||||
{localeOptionLabel !== localeOption.code && ` (${localeOption.code})`}
|
||||
</PopupList.Button>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -6,9 +6,9 @@ import DatePicker from '../../../DatePicker'
|
||||
|
||||
const baseClass = 'condition-value-date'
|
||||
|
||||
const DateField: React.FC<Props> = ({ onChange, value }) => (
|
||||
const DateField: React.FC<Props> = ({ disabled, onChange, value }) => (
|
||||
<div className={baseClass}>
|
||||
<DatePicker onChange={onChange} value={value} />
|
||||
<DatePicker onChange={onChange} readOnly={disabled} value={value} />
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: () => void
|
||||
value: Date
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'condition-value-number'
|
||||
|
||||
const NumberField: React.FC<Props> = ({ onChange, value }) => {
|
||||
const NumberField: React.FC<Props> = ({ disabled, onChange, value }) => {
|
||||
const { t } = useTranslation('general')
|
||||
return (
|
||||
<input
|
||||
className={baseClass}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={t('enterAValue')}
|
||||
type="number"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (e: string) => void
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const baseClass = 'condition-value-relationship'
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
const RelationshipField: React.FC<Props> = (props) => {
|
||||
const { admin: { isSortable } = {}, hasMany, onChange, relationTo, value } = props
|
||||
const { admin: { isSortable } = {}, disabled, hasMany, onChange, relationTo, value } = props
|
||||
|
||||
const {
|
||||
collections,
|
||||
@@ -261,6 +261,7 @@ const RelationshipField: React.FC<Props> = (props) => {
|
||||
<div className={classes}>
|
||||
{!errorLoading && (
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
isMulti={hasMany}
|
||||
isSortable={isSortable}
|
||||
onChange={(selected) => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PaginatedDocs } from '../../../../../../database/types'
|
||||
import type { RelationshipField } from '../../../../../../fields/config/types'
|
||||
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: unknown) => void
|
||||
value: unknown
|
||||
} & RelationshipField
|
||||
|
||||
@@ -20,6 +20,7 @@ const formatOptions = (options: Option[]): OptionObject[] =>
|
||||
})
|
||||
|
||||
export const Select: React.FC<Props> = ({
|
||||
disabled,
|
||||
onChange,
|
||||
operator,
|
||||
options: optionsFromProps,
|
||||
@@ -79,6 +80,7 @@ export const Select: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<ReactSelect
|
||||
disabled={disabled}
|
||||
isMulti={isMulti}
|
||||
onChange={onSelect}
|
||||
options={options.map((option) => ({ ...option, label: getTranslation(option.label, i18n) }))}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Option } from '../../../../../../fields/config/types'
|
||||
import type { Operator } from '../../../../../../types'
|
||||
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: string) => void
|
||||
operator: Operator
|
||||
options: Option[]
|
||||
|
||||
@@ -7,11 +7,12 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'condition-value-text'
|
||||
|
||||
const Text: React.FC<Props> = ({ onChange, value }) => {
|
||||
const Text: React.FC<Props> = ({ disabled, onChange, value }) => {
|
||||
const { t } = useTranslation('general')
|
||||
return (
|
||||
<input
|
||||
className={baseClass}
|
||||
disabled={disabled}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={t('enterAValue')}
|
||||
type="text"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export type Props = {
|
||||
disabled?: boolean
|
||||
onChange: (val: string) => void
|
||||
value: string
|
||||
}
|
||||
|
||||
@@ -26,25 +26,29 @@ const baseClass = 'condition'
|
||||
|
||||
const Condition: React.FC<Props> = (props) => {
|
||||
const { andIndex, dispatch, fields, orIndex, value } = props
|
||||
const fieldValue = Object.keys(value)[0]
|
||||
const operatorAndValue = value?.[fieldValue] ? Object.entries(value[fieldValue])[0] : undefined
|
||||
|
||||
const operatorValue = operatorAndValue?.[0]
|
||||
const queryValue = operatorAndValue?.[1]
|
||||
|
||||
const fieldName = Object.keys(value)[0]
|
||||
const [activeField, setActiveField] = useState<FieldCondition>(() =>
|
||||
fields.find((field) => fieldValue === field.value),
|
||||
fields.find((field) => fieldName === field.value),
|
||||
)
|
||||
|
||||
const operatorAndValue = value?.[fieldName] ? Object.entries(value[fieldName])[0] : undefined
|
||||
const queryValue = operatorAndValue?.[1]
|
||||
const operatorValue = operatorAndValue?.[0]
|
||||
|
||||
const [internalValue, setInternalValue] = useState(queryValue)
|
||||
const [internalOperatorField, setInternalOperatorField] = useState(operatorValue)
|
||||
|
||||
const debouncedValue = useDebounce(internalValue, 300)
|
||||
|
||||
useEffect(() => {
|
||||
const newActiveField = fields.find((field) => fieldValue === field.value)
|
||||
const newActiveField = fields.find(({ value: name }) => name === fieldName)
|
||||
|
||||
if (newActiveField) {
|
||||
if (newActiveField && newActiveField !== activeField) {
|
||||
setActiveField(newActiveField)
|
||||
setInternalOperatorField(null)
|
||||
setInternalValue('')
|
||||
}
|
||||
}, [fieldValue, fields])
|
||||
}, [fieldName, fields, activeField])
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
@@ -73,21 +77,23 @@ const Condition: React.FC<Props> = (props) => {
|
||||
<div className={`${baseClass}__inputs`}>
|
||||
<div className={`${baseClass}__field`}>
|
||||
<ReactSelect
|
||||
onChange={(field) =>
|
||||
isClearable={false}
|
||||
onChange={(field) => {
|
||||
dispatch({
|
||||
andIndex,
|
||||
field: field?.value || undefined,
|
||||
orIndex,
|
||||
andIndex: andIndex,
|
||||
field: field?.value,
|
||||
orIndex: orIndex,
|
||||
type: 'update',
|
||||
})
|
||||
}
|
||||
}}
|
||||
options={fields}
|
||||
value={fields.find((field) => fieldValue === field.value)}
|
||||
value={fields.find((field) => fieldName === field.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__operator`}>
|
||||
<ReactSelect
|
||||
disabled={!fieldValue}
|
||||
disabled={!fieldName}
|
||||
isClearable={false}
|
||||
onChange={(operator) => {
|
||||
dispatch({
|
||||
andIndex,
|
||||
@@ -95,9 +101,14 @@ const Condition: React.FC<Props> = (props) => {
|
||||
orIndex,
|
||||
type: 'update',
|
||||
})
|
||||
setInternalOperatorField(operator.value)
|
||||
}}
|
||||
options={activeField.operators}
|
||||
value={activeField.operators.find((operator) => operatorValue === operator.value)}
|
||||
value={
|
||||
activeField.operators.find(
|
||||
(operator) => internalOperatorField === operator.value,
|
||||
) || null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__value`}>
|
||||
@@ -106,6 +117,7 @@ const Condition: React.FC<Props> = (props) => {
|
||||
DefaultComponent={ValueComponent}
|
||||
componentProps={{
|
||||
...activeField?.props,
|
||||
disabled: !operatorValue,
|
||||
onChange: setInternalValue,
|
||||
operator: operatorValue,
|
||||
options: valueOptions,
|
||||
|
||||
@@ -59,17 +59,17 @@ const reducer = (state: Where[], action: Action): Where[] => {
|
||||
|
||||
if (field) {
|
||||
newState[orIndex].and[andIndex] = {
|
||||
[field]: {
|
||||
[Object.keys(existingCondition)[0]]: Object.values(existingCondition)[0],
|
||||
},
|
||||
[field]: operator ? { [operator]: value } : {},
|
||||
}
|
||||
}
|
||||
|
||||
if (value !== undefined) {
|
||||
newState[orIndex].and[andIndex] = {
|
||||
[existingFieldName]: {
|
||||
[Object.keys(existingCondition)[0]]: value,
|
||||
},
|
||||
[existingFieldName]: Object.keys(existingCondition)[0]
|
||||
? {
|
||||
[Object.keys(existingCondition)[0]]: value,
|
||||
}
|
||||
: {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,9 +281,9 @@ export const addFieldStatePromise = async ({
|
||||
return {
|
||||
relationTo: relationship.relationTo,
|
||||
value:
|
||||
typeof relationship.value === 'string'
|
||||
? relationship.value
|
||||
: relationship.value?.id,
|
||||
relationship.value && typeof relationship.value === 'object'
|
||||
? relationship.value?.id
|
||||
: relationship.value,
|
||||
}
|
||||
}
|
||||
if (typeof relationship === 'object' && relationship !== null) {
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import React from 'react'
|
||||
|
||||
import type { Props as LabelProps } from '../../Label/types'
|
||||
|
||||
import Check from '../../../icons/Check'
|
||||
import Line from '../../../icons/Line'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'checkbox-input'
|
||||
|
||||
type CheckboxInputProps = {
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
'aria-label'?: string
|
||||
checked?: boolean
|
||||
className?: string
|
||||
@@ -25,6 +30,9 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
AfterInput,
|
||||
BeforeInput,
|
||||
Label,
|
||||
'aria-label': ariaLabel,
|
||||
checked,
|
||||
className,
|
||||
@@ -36,6 +44,8 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
required,
|
||||
} = props
|
||||
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -48,6 +58,7 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
.join(' ')}
|
||||
>
|
||||
<div className={`${baseClass}__input`}>
|
||||
{BeforeInput}
|
||||
<input
|
||||
aria-label={ariaLabel}
|
||||
defaultChecked={Boolean(checked)}
|
||||
@@ -58,12 +69,13 @@ export const CheckboxInput: React.FC<CheckboxInputProps> = (props) => {
|
||||
ref={inputRef}
|
||||
type="checkbox"
|
||||
/>
|
||||
{AfterInput}
|
||||
<span className={`${baseClass}__icon ${!partialChecked ? 'check' : 'partial'}`}>
|
||||
{!partialChecked && <Check />}
|
||||
{partialChecked && <Line />}
|
||||
</span>
|
||||
</div>
|
||||
{label && <Label htmlFor={id} label={label} required={required} />}
|
||||
{label && <LabelComp htmlFor={id} label={label} required={required} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { Props } from './types'
|
||||
|
||||
import { checkbox } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
@@ -18,7 +18,15 @@ const baseClass = 'checkbox'
|
||||
const Checkbox: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
disableFormData,
|
||||
label,
|
||||
onChange,
|
||||
@@ -27,6 +35,8 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
validate = checkbox,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -72,7 +82,7 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error alignCaret="left" message={errorMessage} showError={showError} />
|
||||
<ErrorComp alignCaret="left" message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<CheckboxInput
|
||||
checked={Boolean(value)}
|
||||
@@ -81,6 +91,9 @@ const Checkbox: React.FC<Props> = (props) => {
|
||||
name={path}
|
||||
onToggle={onToggle}
|
||||
readOnly={readOnly}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
required={required}
|
||||
/>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { Props } from './types'
|
||||
|
||||
import { code } from '../../../../../fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -31,6 +31,7 @@ const Code: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -38,6 +39,9 @@ const Code: React.FC<Props> = (props) => {
|
||||
validate = code,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
const memoizedValidate = useCallback(
|
||||
@@ -69,8 +73,8 @@ const Code: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<CodeEditor
|
||||
defaultLanguage={prismToMonacoLanguageMap[language] || language}
|
||||
onChange={readOnly ? () => null : (val) => setValue(val)}
|
||||
|
||||
@@ -6,9 +6,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import DatePicker from '../../../elements/DatePicker'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -16,6 +16,12 @@ const baseClass = 'date-time-field'
|
||||
|
||||
export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
|
||||
className?: string
|
||||
components: {
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
datePickerProps?: DateField['admin']['date']
|
||||
description?: Description
|
||||
errorMessage?: string
|
||||
@@ -33,6 +39,7 @@ export type DateTimeInputProps = Omit<DateField, 'admin' | 'name' | 'type'> & {
|
||||
export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
const {
|
||||
className,
|
||||
components: { AfterInput, BeforeInput, Error, Label } = {},
|
||||
datePickerProps,
|
||||
description,
|
||||
errorMessage,
|
||||
@@ -48,6 +55,9 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
width,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
return (
|
||||
@@ -67,10 +77,11 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<Label htmlFor={path} label={label} required={required} />
|
||||
<LabelComp htmlFor={path} label={label} required={required} />
|
||||
<div className={`${baseClass}__input-wrapper`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{BeforeInput}
|
||||
<DatePicker
|
||||
{...datePickerProps}
|
||||
onChange={onChange}
|
||||
@@ -78,6 +89,7 @@ export const DateTimeInput: React.FC<DateTimeInputProps> = (props) => {
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription description={description} value={value} />
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,17 @@ import './index.scss'
|
||||
const DateTime: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, date, description, placeholder, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
components,
|
||||
condition,
|
||||
date,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
@@ -36,6 +46,7 @@ const DateTime: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<DateTimeInput
|
||||
className={className}
|
||||
components={components}
|
||||
datePickerProps={date}
|
||||
description={description}
|
||||
errorMessage={errorMessage}
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Props } from './types'
|
||||
|
||||
import { email } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -25,6 +25,7 @@ const Email: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
@@ -51,6 +52,9 @@ const Email: React.FC<Props> = (props) => {
|
||||
|
||||
const { errorMessage, setValue, showError, value } = fieldType
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'email', className, showError && 'error', readOnly && 'read-only']
|
||||
@@ -61,18 +65,22 @@ const Email: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={Boolean(readOnly)}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={setValue}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
type="email"
|
||||
value={(value as string) || ''}
|
||||
/>
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
autoComplete={autoComplete}
|
||||
disabled={Boolean(readOnly)}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={setValue}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
type="email"
|
||||
value={(value as string) || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription description={description} value={value} />
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -4,9 +4,9 @@ import type { Props } from './types'
|
||||
|
||||
import { json } from '../../../../../fields/validations'
|
||||
import { CodeEditor } from '../../../elements/CodeEditor'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -17,13 +17,25 @@ const baseClass = 'json-field'
|
||||
const JSONField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, editorOptions, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
editorOptions,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = json,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
const [stringValue, setStringValue] = useState<string>()
|
||||
const [jsonError, setJsonError] = useState<string>()
|
||||
@@ -76,8 +88,8 @@ const JSONField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<CodeEditor
|
||||
defaultLanguage="json"
|
||||
onChange={handleChange}
|
||||
|
||||
@@ -8,9 +8,9 @@ import { number } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import { isNumber } from '../../../../../utilities/isNumber'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -19,7 +19,17 @@ import { fieldBaseClass } from '../shared'
|
||||
const NumberField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, step, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
step,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
max,
|
||||
@@ -31,6 +41,9 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
validate = number,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const { i18n, t } = useTranslation()
|
||||
|
||||
const path = pathFromProps || name
|
||||
@@ -118,8 +131,8 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
{hasMany ? (
|
||||
<ReactSelect
|
||||
className={`field-${path.replace(/\./g, '__')}`}
|
||||
@@ -148,21 +161,25 @@ const NumberField: React.FC<Props> = (props) => {
|
||||
value={valueToRender as Option[]}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={handleChange}
|
||||
onWheel={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
e.target.blur()
|
||||
}}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={handleChange}
|
||||
onWheel={(e) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
e.target.blur()
|
||||
}}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={typeof value === 'number' ? value : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Props } from './types'
|
||||
|
||||
import { point } from '../../../../../fields/validations'
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import './index.scss'
|
||||
@@ -18,13 +18,26 @@ const baseClass = 'point'
|
||||
const PointField: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, step, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
step,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
path: pathFromProps,
|
||||
required,
|
||||
validate = point,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
const { i18n, t } = useTranslation('fields')
|
||||
@@ -76,41 +89,49 @@ const PointField: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<ul className={`${baseClass}__wrap`}>
|
||||
<li>
|
||||
<Label
|
||||
<LabelComp
|
||||
htmlFor={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
label={`${getTranslation(label || name, i18n)} - ${t('longitude')}`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.longitude`}
|
||||
onChange={(e) => handleChange(e, 0)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[0] === 'number' ? value[0] : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-longitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.longitude`}
|
||||
onChange={(e) => handleChange(e, 0)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[0] === 'number' ? value[0] : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<Label
|
||||
<LabelComp
|
||||
htmlFor={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
label={`${getTranslation(label || name, i18n)} - ${t('latitude')}`}
|
||||
required={required}
|
||||
/>
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.latitude`}
|
||||
onChange={(e) => handleChange(e, 1)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[1] === 'number' ? value[1] : ''}
|
||||
/>
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
disabled={readOnly}
|
||||
id={`field-latitude-${path.replace(/\./g, '__')}`}
|
||||
name={`${path}.latitude`}
|
||||
onChange={(e) => handleChange(e, 1)}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
step={step}
|
||||
type="number"
|
||||
value={value && typeof value[1] === 'number' ? value[1] : ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -5,9 +5,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
import type { OnChange } from './types'
|
||||
|
||||
import { optionIsObject } from '../../../../../fields/config/types'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import RadioInput from './RadioInput'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
@@ -28,6 +28,8 @@ export type RadioGroupInputProps = Omit<RadioField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
@@ -47,8 +49,13 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const path = pathFromProps || name
|
||||
|
||||
return (
|
||||
@@ -69,9 +76,9 @@ const RadioGroupInput: React.FC<RadioGroupInputProps> = (props) => {
|
||||
}}
|
||||
>
|
||||
<div className={`${baseClass}__error-wrap`}>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
</div>
|
||||
<Label htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<LabelComp htmlFor={`field-${path}`} label={label} required={required} />
|
||||
<ul className={`${baseClass}--group`} id={`field-${path.replace(/\./g, '__')}`}>
|
||||
{options.map((option) => {
|
||||
let optionValue = ''
|
||||
|
||||
@@ -18,6 +18,7 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
label,
|
||||
options,
|
||||
@@ -57,6 +58,8 @@ const RadioGroup: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@ import { useAuth } from '../../../utilities/Auth'
|
||||
import { useConfig } from '../../../utilities/Config'
|
||||
import { GetFilterOptions } from '../../../utilities/GetFilterOptions'
|
||||
import { useLocale } from '../../../utilities/Locale'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import { useFormProcessing } from '../../Form/context'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import useField from '../../useField'
|
||||
import withCondition from '../../withCondition'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import { AddNewRelation } from './AddNew'
|
||||
import { createRelationMap } from './createRelationMap'
|
||||
import { findOptionsByValue } from './findOptionsByValue'
|
||||
@@ -28,7 +29,6 @@ import './index.scss'
|
||||
import optionsReducer from './optionsReducer'
|
||||
import { MultiValueLabel } from './select-components/MultiValueLabel'
|
||||
import { SingleValue } from './select-components/SingleValue'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
const maxResultsPerRequest = 10
|
||||
|
||||
@@ -46,6 +46,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
filterOptions,
|
||||
hasMany,
|
||||
@@ -56,6 +57,9 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
validate = relationship,
|
||||
} = props
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const config = useConfig()
|
||||
|
||||
const {
|
||||
@@ -391,6 +395,7 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
}, [])
|
||||
|
||||
const valueToRender = findOptionsByValue({ options, value })
|
||||
|
||||
if (!Array.isArray(valueToRender) && valueToRender?.value === 'null') valueToRender.value = null
|
||||
|
||||
return (
|
||||
@@ -411,8 +416,8 @@ const Relationship: React.FC<Props> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={pathOrName} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={pathOrName} label={label} required={required} />
|
||||
<GetFilterOptions
|
||||
{...{
|
||||
filterOptions,
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import ReactSelect from '../../../elements/ReactSelect'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -29,6 +29,8 @@ export type SelectInputProps = Omit<SelectField, 'options' | 'type' | 'value'> &
|
||||
style?: React.CSSProperties
|
||||
value?: string | string[]
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
@@ -50,10 +52,15 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
let valueToRender = defaultValue
|
||||
|
||||
if (hasMany && Array.isArray(value)) {
|
||||
@@ -89,8 +96,8 @@ const SelectInput: React.FC<SelectInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ReactSelect
|
||||
disabled={readOnly}
|
||||
isClearable={isClearable}
|
||||
|
||||
@@ -32,6 +32,7 @@ const Select: React.FC<Props> = (props) => {
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
hasMany,
|
||||
label,
|
||||
@@ -103,6 +104,8 @@ const Select: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string | string[]}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { TextField } from '../../../../../fields/config/types'
|
||||
import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -29,6 +29,10 @@ export type TextInputProps = Omit<TextField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
|
||||
const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
@@ -49,10 +53,17 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
BeforeInput,
|
||||
AfterInput,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'text', className, showError && 'error', readOnly && 'read-only']
|
||||
@@ -63,20 +74,24 @@ const TextInput: React.FC<TextInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<input
|
||||
data-rtl={rtl}
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={value || ''}
|
||||
/>
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<div className="input-wrapper">
|
||||
{BeforeInput}
|
||||
<input
|
||||
data-rtl={rtl}
|
||||
disabled={readOnly}
|
||||
id={`field-${path.replace(/\./g, '__')}`}
|
||||
name={path}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
placeholder={getTranslation(placeholder, i18n)}
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={value || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
<FieldDescription
|
||||
className={`field-description-${path.replace(/\./g, '__')}`}
|
||||
description={description}
|
||||
|
||||
@@ -13,7 +13,17 @@ import TextInput from './Input'
|
||||
const Text: React.FC<Props> = (props) => {
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, placeholder, readOnly, rtl, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
placeholder,
|
||||
readOnly,
|
||||
rtl,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
inputRef,
|
||||
label,
|
||||
localized,
|
||||
@@ -68,6 +78,10 @@ const Text: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import type { TextareaField } from '../../../../../fields/config/types'
|
||||
import type { Description } from '../../FieldDescription/types'
|
||||
|
||||
import { getTranslation } from '../../../../../utilities/getTranslation'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import './index.scss'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
|
||||
@@ -28,6 +28,10 @@ export type TextAreaInputProps = Omit<TextareaField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
|
||||
const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
@@ -47,10 +51,17 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
BeforeInput,
|
||||
AfterInput,
|
||||
} = props
|
||||
|
||||
const { i18n } = useTranslation()
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -67,11 +78,12 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<label className="textarea-outer" htmlFor={`field-${path.replace(/\./g, '__')}`}>
|
||||
<div className="textarea-inner">
|
||||
<div className="textarea-clone" data-value={value || placeholder || ''} />
|
||||
{BeforeInput}
|
||||
<textarea
|
||||
className="textarea-element"
|
||||
data-rtl={rtl}
|
||||
@@ -83,6 +95,7 @@ const TextareaInput: React.FC<TextAreaInputProps> = (props) => {
|
||||
rows={rows}
|
||||
value={value || ''}
|
||||
/>
|
||||
{AfterInput}
|
||||
</div>
|
||||
</label>
|
||||
<FieldDescription description={description} value={value} />
|
||||
|
||||
@@ -26,6 +26,7 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
rtl,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label, BeforeInput, AfterInput } = {},
|
||||
} = {},
|
||||
label,
|
||||
localized,
|
||||
@@ -82,6 +83,10 @@ const Textarea: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
BeforeInput={BeforeInput}
|
||||
AfterInput={AfterInput}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import { useDocumentDrawer } from '../../../elements/DocumentDrawer'
|
||||
import FileDetails from '../../../elements/FileDetails'
|
||||
import { useListDrawer } from '../../../elements/ListDrawer'
|
||||
import { GetFilterOptions } from '../../../utilities/GetFilterOptions'
|
||||
import Error from '../../Error'
|
||||
import DefaultError from '../../Error'
|
||||
import FieldDescription from '../../FieldDescription'
|
||||
import Label from '../../Label'
|
||||
import DefaultLabel from '../../Label'
|
||||
import { fieldBaseClass } from '../shared'
|
||||
import './index.scss'
|
||||
|
||||
@@ -41,6 +41,8 @@ export type UploadInputProps = Omit<UploadField, 'type'> & {
|
||||
style?: React.CSSProperties
|
||||
value?: string
|
||||
width?: string
|
||||
Error?: React.ComponentType<any>
|
||||
Label?: React.ComponentType<any>
|
||||
}
|
||||
|
||||
const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
@@ -62,10 +64,15 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
style,
|
||||
value,
|
||||
width,
|
||||
Error,
|
||||
Label,
|
||||
} = props
|
||||
|
||||
const { i18n, t } = useTranslation('fields')
|
||||
|
||||
const ErrorComp = Error || DefaultError
|
||||
const LabelComp = Label || DefaultLabel
|
||||
|
||||
const [file, setFile] = useState(undefined)
|
||||
const [missingFile, setMissingFile] = useState(false)
|
||||
const [collectionSlugs] = useState([collection?.slug])
|
||||
@@ -149,8 +156,8 @@ const UploadInput: React.FC<UploadInputProps> = (props) => {
|
||||
setFilterOptionsResult,
|
||||
}}
|
||||
/>
|
||||
<Error message={errorMessage} showError={showError} />
|
||||
<Label htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
<ErrorComp message={errorMessage} showError={showError} />
|
||||
<LabelComp htmlFor={`field-${path.replace(/\./g, '__')}`} label={label} required={required} />
|
||||
{collection?.upload && (
|
||||
<React.Fragment>
|
||||
{file && !missingFile && (
|
||||
|
||||
@@ -18,7 +18,15 @@ const Upload: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
name,
|
||||
admin: { className, condition, description, readOnly, style, width } = {},
|
||||
admin: {
|
||||
className,
|
||||
condition,
|
||||
description,
|
||||
readOnly,
|
||||
style,
|
||||
width,
|
||||
components: { Error, Label } = {},
|
||||
} = {},
|
||||
fieldTypes,
|
||||
filterOptions,
|
||||
label,
|
||||
@@ -75,6 +83,8 @@ const Upload: React.FC<Props> = (props) => {
|
||||
style={style}
|
||||
value={value as string}
|
||||
width={width}
|
||||
Error={Error}
|
||||
Label={Label}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -293,6 +293,4 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
)
|
||||
}
|
||||
|
||||
type UseAuth<T = User> = () => AuthContext<T>
|
||||
|
||||
export const useAuth: UseAuth = () => useContext(Context)
|
||||
export const useAuth = <T = User,>(): AuthContext<T> => useContext(Context) as AuthContext<T>
|
||||
|
||||
@@ -104,6 +104,8 @@ const RecursivelyRenderObjectData = ({
|
||||
type = 'object'
|
||||
} else if (typeof value === 'number') {
|
||||
type = 'number'
|
||||
} else if (typeof value === 'boolean') {
|
||||
type = 'boolean'
|
||||
} else {
|
||||
type = 'string'
|
||||
}
|
||||
@@ -121,7 +123,7 @@ const RecursivelyRenderObjectData = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (type === 'date' || type === 'string' || type === 'null' || type === 'number') {
|
||||
if (type === 'date' || type === 'string' || type === 'null' || type === 'number' || type === 'boolean') {
|
||||
const parentHasKey = Boolean(parentType === 'object' && key)
|
||||
|
||||
const rowClasses = [
|
||||
@@ -136,11 +138,7 @@ const RecursivelyRenderObjectData = ({
|
||||
<li className={rowClasses} key={`${key}-${keyIndex}`}>
|
||||
{parentHasKey ? <span>{`"${key}": `}</span> : null}
|
||||
|
||||
{type === 'string' ? (
|
||||
<span className={`${baseClass}__value`}>{`"${value}"`}</span>
|
||||
) : (
|
||||
<span className={`${baseClass}__value`}>{value}</span>
|
||||
)}
|
||||
<span className={`${baseClass}__value`}>{JSON.stringify(value)}</span>
|
||||
{isLastKey ? '' : ','}
|
||||
</li>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
import type { Payload } from '../../../payload'
|
||||
|
||||
import formatName from '../../../graphql/utilities/formatName'
|
||||
@@ -18,7 +19,7 @@ const formatConfigNames = (results, configs) => {
|
||||
function accessResolver(payload: Payload) {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const accessResults = await access(options)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import forgotPassword from '../../operations/forgotPassword'
|
||||
|
||||
@@ -11,7 +12,7 @@ function forgotPasswordResolver(collection: Collection): any {
|
||||
},
|
||||
disableEmail: args.disableEmail,
|
||||
expiration: args.expiration,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
await forgotPassword(options)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import init from '../../operations/init'
|
||||
|
||||
function initResolver(collection: string) {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
return init(options)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import login from '../../operations/login'
|
||||
|
||||
@@ -11,7 +12,7 @@ function loginResolver(collection: Collection) {
|
||||
password: args.password,
|
||||
},
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import logout from '../../operations/logout'
|
||||
|
||||
@@ -6,7 +7,7 @@ function logoutResolver(collection: Collection): any {
|
||||
async function resolver(_, args, context) {
|
||||
const options = {
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import me from '../../operations/me'
|
||||
|
||||
@@ -7,10 +8,11 @@ function meResolver(collection: Collection): any {
|
||||
const options = {
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
return me(options)
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import getExtractJWT from '../../getExtractJWT'
|
||||
import refresh from '../../operations/refresh'
|
||||
@@ -17,7 +18,7 @@ function refreshResolver(collection: Collection) {
|
||||
const options = {
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
token,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import resetPassword from '../../operations/resetPassword'
|
||||
|
||||
@@ -13,7 +14,7 @@ function resetPasswordResolver(collection: Collection) {
|
||||
collection,
|
||||
data: args,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import unlock from '../../operations/unlock'
|
||||
|
||||
@@ -7,12 +8,13 @@ function unlockResolver(collection: Collection) {
|
||||
const options = {
|
||||
collection,
|
||||
data: { email: args.email },
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await unlock(options)
|
||||
return result
|
||||
}
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { Collection } from '../../../collections/config/types'
|
||||
import type { PayloadRequest } from '../../../express/types'
|
||||
|
||||
import verifyEmail from '../../operations/verifyEmail'
|
||||
|
||||
@@ -11,7 +12,7 @@ function verifyEmailResolver(collection: Collection) {
|
||||
const options = {
|
||||
api: 'GraphQL',
|
||||
collection,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
res: context.res,
|
||||
token: args.token,
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
|
||||
},
|
||||
} = args
|
||||
|
||||
if (typeof args.token !== 'string') throw new Forbidden(args.req.t)
|
||||
if (typeof args.token !== 'string' || !args.req.user) throw new Forbidden(args.req.t)
|
||||
|
||||
const parsedURL = url.parse(args.req.url)
|
||||
const isGraphQL = parsedURL.pathname === config.routes.graphQL
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function createResolver<TSlug extends keyof GeneratedTypes['colle
|
||||
data: args.data,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await create(options)
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function getDeleteResolver<TSlug extends keyof GeneratedTypes['co
|
||||
id: args.id,
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await deleteByID(options)
|
||||
|
||||
@@ -18,7 +18,7 @@ export function docAccessResolver(): Resolver {
|
||||
async function resolver(_, args, context) {
|
||||
return docAccess({
|
||||
id: args.id,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function findResolver(collection: Collection): Resolver {
|
||||
draft: args.draft,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function findVersionByIDResolver(collection: Collection): Resolve
|
||||
collection,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await findVersionByID(options)
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function findVersionsResolver(collection: Collection): Resolver {
|
||||
depth: 0,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function restoreVersionResolver(collection: Collection): Resolver
|
||||
id: args.id,
|
||||
collection,
|
||||
depth: 0,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await restoreVersion(options)
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['colle
|
||||
data: args.data,
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await updateByID<TSlug>(options)
|
||||
|
||||
@@ -134,7 +134,13 @@ export default joi.object({
|
||||
joi.array().items(
|
||||
joi.object().keys({
|
||||
code: joi.string(),
|
||||
label: joi.string(),
|
||||
label: joi
|
||||
.alternatives()
|
||||
.try(
|
||||
joi.object().pattern(joi.string(), [joi.string()]),
|
||||
joi.string(),
|
||||
joi.valid(false),
|
||||
),
|
||||
rtl: joi.boolean(),
|
||||
toString: joi.func(),
|
||||
}),
|
||||
|
||||
@@ -288,7 +288,7 @@ export type Locale = {
|
||||
* label of supported locale
|
||||
* @example "English"
|
||||
*/
|
||||
label: string
|
||||
label: string | Record<string, string>
|
||||
/**
|
||||
* if true, defaults textAligmnent on text fields to RTL
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { useStepNav } from '../../admin/components/elements/StepNav'
|
||||
export { useTableColumns } from '../../admin/components/elements/TableColumns'
|
||||
export { default as useDebounce } from '../../admin/hooks/useDebounce'
|
||||
export { useDebouncedCallback } from '../../admin/hooks/useDebouncedCallback'
|
||||
export { useDelay } from '../../admin/hooks/useDelay'
|
||||
|
||||
@@ -75,6 +75,12 @@ export const text = baseField.keys({
|
||||
.alternatives()
|
||||
.try(joi.object().pattern(joi.string(), [joi.string()]), joi.string()),
|
||||
rtl: joi.boolean(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -88,6 +94,18 @@ export const number = baseField.keys({
|
||||
autoComplete: joi.string(),
|
||||
placeholder: joi.string(),
|
||||
step: joi.number(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi
|
||||
.array()
|
||||
.items(componentSchema)
|
||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
||||
AfterInput: joi
|
||||
.array()
|
||||
.items(componentSchema)
|
||||
.when('hasMany', { not: true, otherwise: joi.forbidden() }),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.number(), joi.func()),
|
||||
hasMany: joi.boolean().default(false),
|
||||
@@ -104,6 +122,12 @@ export const textarea = baseField.keys({
|
||||
placeholder: joi.string(),
|
||||
rows: joi.number(),
|
||||
rtl: joi.boolean(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -116,6 +140,12 @@ export const email = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
autoComplete: joi.string(),
|
||||
placeholder: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
maxLength: joi.number(),
|
||||
@@ -128,6 +158,10 @@ export const code = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
editorOptions: joi.object().unknown(), // Editor['options'] @monaco-editor/react
|
||||
language: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
type: joi.string().valid('code').required(),
|
||||
@@ -135,6 +169,12 @@ export const code = baseField.keys({
|
||||
|
||||
export const json = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.array(), joi.object()),
|
||||
type: joi.string().valid('json').required(),
|
||||
})
|
||||
@@ -144,6 +184,10 @@ export const select = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
isClearable: joi.boolean().default(false),
|
||||
isSortable: joi.boolean().default(false),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi
|
||||
.alternatives()
|
||||
@@ -171,6 +215,10 @@ export const radio = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
layout: joi.string().valid('vertical', 'horizontal'),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string().allow(''), joi.func()),
|
||||
options: joi
|
||||
@@ -268,6 +316,12 @@ export const array = baseField.keys({
|
||||
|
||||
export const upload = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.object(), joi.func()),
|
||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
||||
maxDepth: joi.number(),
|
||||
@@ -277,12 +331,28 @@ export const upload = baseField.keys({
|
||||
|
||||
export const checkbox = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.boolean(), joi.func()),
|
||||
type: joi.string().valid('checkbox').required(),
|
||||
})
|
||||
|
||||
export const point = baseField.keys({
|
||||
name: joi.string().required(),
|
||||
admin: baseAdminFields.keys({
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.array().items(joi.number()).max(2).min(2), joi.func()),
|
||||
type: joi.string().valid('point').required(),
|
||||
})
|
||||
@@ -292,6 +362,10 @@ export const relationship = baseField.keys({
|
||||
admin: baseAdminFields.keys({
|
||||
allowCreate: joi.boolean().default(true),
|
||||
isSortable: joi.boolean().default(false),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.func()),
|
||||
filterOptions: joi.alternatives().try(joi.object(), joi.func()),
|
||||
@@ -382,6 +456,12 @@ export const date = baseField.keys({
|
||||
timeIntervals: joi.number(),
|
||||
}),
|
||||
placeholder: joi.string(),
|
||||
components: baseAdminComponentFields.keys({
|
||||
Label: componentSchema,
|
||||
Error: componentSchema,
|
||||
BeforeInput: joi.array().items(componentSchema),
|
||||
AfterInput: joi.array().items(componentSchema),
|
||||
}),
|
||||
}),
|
||||
defaultValue: joi.alternatives().try(joi.string(), joi.func()),
|
||||
type: joi.string().valid('date').required(),
|
||||
|
||||
@@ -15,6 +15,8 @@ import type { PayloadRequest, RequestContext } from '../../express/types'
|
||||
import type { SanitizedGlobalConfig } from '../../globals/config/types'
|
||||
import type { Payload } from '../../payload'
|
||||
import type { Operation, Where } from '../../types'
|
||||
import type { Props as ErrorProps } from '../../admin/components/forms/Error/types'
|
||||
import type { Props as LabelProps } from '../../admin/components/forms/Label/types'
|
||||
|
||||
export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
|
||||
/** The collection which the field belongs to. If the field belongs to a global, this will be null. */
|
||||
@@ -158,6 +160,12 @@ export type NumberField = FieldBase & {
|
||||
placeholder?: Record<string, string> | string
|
||||
/** Set a value for the number field to increment / decrement using browser controls. */
|
||||
step?: number
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
/** Maximum value accepted. Used in the default `validation` function. */
|
||||
max?: number
|
||||
@@ -188,6 +196,12 @@ export type TextField = FieldBase & {
|
||||
autoComplete?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
rtl?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
@@ -198,6 +212,12 @@ export type EmailField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
autoComplete?: string
|
||||
placeholder?: Record<string, string> | string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
type: 'email'
|
||||
}
|
||||
@@ -207,6 +227,12 @@ export type TextareaField = FieldBase & {
|
||||
placeholder?: Record<string, string> | string
|
||||
rows?: number
|
||||
rtl?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
maxLength?: number
|
||||
minLength?: number
|
||||
@@ -215,12 +241,26 @@ export type TextareaField = FieldBase & {
|
||||
|
||||
export type CheckboxField = FieldBase & {
|
||||
type: 'checkbox'
|
||||
admin?: Admin & {
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type DateField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
date?: ConditionalDateProps
|
||||
placeholder?: Record<string, string> | string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
BeforeInput?: React.ReactElement<any>[]
|
||||
AfterInput?: React.ReactElement<any>[]
|
||||
}
|
||||
}
|
||||
type: 'date'
|
||||
}
|
||||
@@ -315,6 +355,12 @@ export type UIField = {
|
||||
}
|
||||
|
||||
export type UploadField = FieldBase & {
|
||||
admin?: {
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
filterOptions?: FilterOptions
|
||||
maxDepth?: number
|
||||
relationTo: string
|
||||
@@ -324,6 +370,10 @@ export type UploadField = FieldBase & {
|
||||
type CodeAdmin = Admin & {
|
||||
editorOptions?: EditorProps['options']
|
||||
language?: string
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
|
||||
export type CodeField = Omit<FieldBase, 'admin'> & {
|
||||
@@ -335,6 +385,10 @@ export type CodeField = Omit<FieldBase, 'admin'> & {
|
||||
|
||||
type JSONAdmin = Admin & {
|
||||
editorOptions?: EditorProps['options']
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
|
||||
export type JSONField = Omit<FieldBase, 'admin'> & {
|
||||
@@ -346,6 +400,10 @@ export type SelectField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
isClearable?: boolean
|
||||
isSortable?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
hasMany?: boolean
|
||||
options: Option[]
|
||||
@@ -356,6 +414,10 @@ export type RelationshipField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
allowCreate?: boolean
|
||||
isSortable?: boolean
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
filterOptions?: FilterOptions
|
||||
hasMany?: boolean
|
||||
@@ -440,6 +502,10 @@ export type ArrayField = FieldBase & {
|
||||
export type RadioField = FieldBase & {
|
||||
admin?: Admin & {
|
||||
layout?: 'horizontal' | 'vertical'
|
||||
components?: {
|
||||
Error?: React.ComponentType<ErrorProps>
|
||||
Label?: React.ComponentType<LabelProps>
|
||||
}
|
||||
}
|
||||
options: Option[]
|
||||
type: 'radio'
|
||||
|
||||
@@ -16,7 +16,7 @@ export function docAccessResolver(global: SanitizedGlobalConfig): Resolver {
|
||||
async function resolver(_, context) {
|
||||
return docAccess({
|
||||
globalConfig: global,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
import type { Document } from '../../../types'
|
||||
import type { Document, PayloadRequest } from '../../../types'
|
||||
import type { SanitizedGlobalConfig } from '../../config/types'
|
||||
|
||||
import findOne from '../../operations/findOne'
|
||||
@@ -16,7 +16,7 @@ export default function findOneResolver(globalConfig: SanitizedGlobalConfig): Do
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
slug,
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function findVersionByIDResolver(globalConfig: SanitizedGlobalCon
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await findVersionByID(options)
|
||||
|
||||
@@ -29,7 +29,7 @@ export default function findVersionsResolver(globalConfig: SanitizedGlobalConfig
|
||||
globalConfig,
|
||||
limit: args.limit,
|
||||
page: args.page,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
sort: args.sort,
|
||||
where: args.where,
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function restoreVersionResolver(globalConfig: SanitizedGlobalConf
|
||||
id: args.id,
|
||||
depth: 0,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
}
|
||||
|
||||
const result = await restoreVersion(options)
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function updateResolver<TSlug extends keyof GeneratedTypes['globa
|
||||
depth: 0,
|
||||
draft: args.draft,
|
||||
globalConfig,
|
||||
req: context.req,
|
||||
req: { ...context.req } as PayloadRequest,
|
||||
slug,
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ export const generateFileData = async <T>({
|
||||
let fileBuffer: { data: Buffer; info: OutputInfo }
|
||||
let ext
|
||||
let mime: string
|
||||
const isSharpRequired =
|
||||
const fileHasAdjustments =
|
||||
fileSupportsResize &&
|
||||
Boolean(resizeOptions || formatOptions || trimOptions || file.tempFilePath)
|
||||
|
||||
@@ -107,7 +107,7 @@ export const generateFileData = async <T>({
|
||||
|
||||
if (fileIsAnimated) sharpOptions.animated = true
|
||||
|
||||
if (isSharpRequired) {
|
||||
if (fileHasAdjustments) {
|
||||
if (file.tempFilePath) {
|
||||
sharpFile = sharp(file.tempFilePath, sharpOptions).rotate() // pass rotate() to auto-rotate based on EXIF data. https://github.com/payloadcms/payload/pull/3081
|
||||
} else {
|
||||
@@ -174,7 +174,7 @@ export const generateFileData = async <T>({
|
||||
fileData.filename = fsSafeName
|
||||
let fileForResize = file
|
||||
|
||||
if (isSharpRequired && cropData) {
|
||||
if (cropData) {
|
||||
const { data: croppedImage, info } = await cropImage({ cropData, dimensions, file })
|
||||
|
||||
filesToSave.push({
|
||||
|
||||
@@ -113,36 +113,83 @@ const createResult = (
|
||||
*
|
||||
* @param resizeConfig - object containing the requested dimensions and resize options
|
||||
* @param original - the original image size
|
||||
* @returns true if the image needs to be resized, false otherwise
|
||||
* @returns true if resizing is not needed, false otherwise
|
||||
*/
|
||||
const needsResize = (
|
||||
const preventResize = (
|
||||
{ height: desiredHeight, width: desiredWidth, withoutEnlargement, withoutReduction }: ImageSize,
|
||||
original: ProbedImageSize,
|
||||
): boolean => {
|
||||
// allow enlargement or prevent reduction (our default is to prevent
|
||||
// enlargement and allow reduction)
|
||||
if (withoutEnlargement !== undefined || withoutReduction !== undefined) {
|
||||
return true // needs resize
|
||||
// default is to allow reduction
|
||||
if (withoutReduction !== undefined) {
|
||||
return false // needs resize
|
||||
}
|
||||
|
||||
// default is to prevent enlargement
|
||||
if (withoutEnlargement !== undefined) {
|
||||
return false // needs resize
|
||||
}
|
||||
|
||||
const isWidthOrHeightNotDefined = !desiredHeight || !desiredWidth
|
||||
|
||||
if (isWidthOrHeightNotDefined) {
|
||||
// If with and height are not defined, it means there is a format conversion
|
||||
// and the image needs to be "resized" (transformed).
|
||||
return true // needs resize
|
||||
return false // needs resize
|
||||
}
|
||||
|
||||
const hasInsufficientWidth = original.width < desiredWidth
|
||||
const hasInsufficientHeight = original.height < desiredHeight
|
||||
const hasInsufficientWidth = desiredWidth > original.width
|
||||
const hasInsufficientHeight = desiredHeight > original.height
|
||||
if (hasInsufficientWidth && hasInsufficientHeight) {
|
||||
// doesn't need resize - prevent enlargement. This should only happen if both width and height are insufficient.
|
||||
// if only one dimension is insufficient and the other is sufficient, resizing needs to happen, as the image
|
||||
// should be resized to the sufficient dimension.
|
||||
return false
|
||||
return true // do not create a new size
|
||||
}
|
||||
|
||||
return true // needs resize
|
||||
return false // needs resize
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the image should be passed directly to sharp without payload adjusting properties.
|
||||
*
|
||||
* @param resizeConfig - object containing the requested dimensions and resize options
|
||||
* @param original - the original image size
|
||||
* @returns true if the image should passed directly to sharp
|
||||
*/
|
||||
const applyPayloadAdjustments = (
|
||||
{ height, width, withoutEnlargement, withoutReduction }: ImageSize,
|
||||
original: ProbedImageSize,
|
||||
) => {
|
||||
if (!isNumber(height) && !isNumber(width)) return false
|
||||
|
||||
const targetAspectRatio = width / height
|
||||
const originalAspectRatio = original.width / original.height
|
||||
if (originalAspectRatio === targetAspectRatio) return false
|
||||
|
||||
const skipEnlargement = withoutEnlargement && (original.height < height || original.width < width)
|
||||
const skipReduction = withoutReduction && (original.height > height || original.width > width)
|
||||
if (skipEnlargement || skipReduction) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the resize config. If the resize config has the `withoutReduction`
|
||||
* property set to true, the `fit` and `position` properties will be set to `contain`
|
||||
* and `top left` respectively.
|
||||
*
|
||||
* @param resizeConfig - the resize config
|
||||
* @returns a sanitized resize config
|
||||
*/
|
||||
const sanitizeResizeConfig = (resizeConfig: ImageSize): ImageSize => {
|
||||
if (resizeConfig.withoutReduction) {
|
||||
return {
|
||||
...resizeConfig,
|
||||
// Why fit `contain` should also be set to https://github.com/lovell/sharp/issues/3595
|
||||
fit: resizeConfig?.fit || 'contain',
|
||||
position: resizeConfig?.position || 'left top',
|
||||
}
|
||||
}
|
||||
return resizeConfig
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,66 +223,65 @@ export default async function resizeAndTransformImageSizes({
|
||||
|
||||
const results: ImageSizesResult[] = await Promise.all(
|
||||
imageSizes.map(async (imageResizeConfig): Promise<ImageSizesResult> => {
|
||||
imageResizeConfig = sanitizeResizeConfig(imageResizeConfig)
|
||||
|
||||
// This checks if a resize should happen. If not, the resized image will be
|
||||
// skipped COMPLETELY and thus will not be included in the resulting images.
|
||||
// All further format/trim options will thus be skipped as well.
|
||||
if (!needsResize(imageResizeConfig, dimensions)) {
|
||||
if (preventResize(imageResizeConfig, dimensions)) {
|
||||
return createResult(imageResizeConfig.name)
|
||||
}
|
||||
let resized = sharpBase.clone()
|
||||
|
||||
const hasEdits = req.query?.uploadEdits
|
||||
const imageToResize = sharpBase.clone()
|
||||
let resized = imageToResize.resize(imageResizeConfig)
|
||||
|
||||
if (hasEdits && imageResizeConfig.width && imageResizeConfig.height) {
|
||||
const { height, width } = imageResizeConfig
|
||||
|
||||
const targetAspectRatio = width / height
|
||||
if (
|
||||
req.query?.uploadEdits?.focalPoint &&
|
||||
applyPayloadAdjustments(imageResizeConfig, dimensions)
|
||||
) {
|
||||
const { height: resizeHeight, width: resizeWidth } = imageResizeConfig
|
||||
const resizeAspectRatio = resizeWidth / resizeHeight
|
||||
const originalAspectRatio = dimensions.width / dimensions.height
|
||||
const prioritizeHeight = resizeAspectRatio < originalAspectRatio
|
||||
|
||||
if (originalAspectRatio === targetAspectRatio) {
|
||||
resized = resized.resize(imageResizeConfig)
|
||||
} else {
|
||||
const focalPoint = {
|
||||
x: 0.5,
|
||||
y: 0.5,
|
||||
}
|
||||
// Scale the image up or down to fit the resize dimensions
|
||||
const scaledImage = imageToResize.resize({
|
||||
height: prioritizeHeight ? resizeHeight : null,
|
||||
width: prioritizeHeight ? null : resizeWidth,
|
||||
})
|
||||
const { info: scaledImageInfo } = await scaledImage.toBuffer({ resolveWithObject: true })
|
||||
|
||||
if (req.query.uploadEdits?.focalPoint) {
|
||||
if (isNumber(req.query.uploadEdits.focalPoint?.x)) {
|
||||
focalPoint.x = req.query.uploadEdits.focalPoint.x
|
||||
}
|
||||
if (isNumber(req.query.uploadEdits.focalPoint?.y)) {
|
||||
focalPoint.y = req.query.uploadEdits.focalPoint.y
|
||||
}
|
||||
}
|
||||
|
||||
const prioritizeHeight = originalAspectRatio > targetAspectRatio
|
||||
|
||||
const { info } = await resized
|
||||
.resize({
|
||||
height: prioritizeHeight ? height : null,
|
||||
width: prioritizeHeight ? null : width,
|
||||
})
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
|
||||
const maxOffsetX = Math.max(info.width - width, 0)
|
||||
const maxOffsetY = Math.max(info.height - height, 0)
|
||||
|
||||
const focalPointX = Math.floor((info.width / 100) * focalPoint.x)
|
||||
const focalPointY = Math.floor((info.height / 100) * focalPoint.y)
|
||||
|
||||
const offsetX = Math.min(Math.max(focalPointX - width / 2, 0), maxOffsetX)
|
||||
const offsetY = Math.min(Math.max(focalPointY - height / 2, 0), maxOffsetY)
|
||||
|
||||
resized = resized.extract({
|
||||
height,
|
||||
left: offsetX,
|
||||
top: offsetY,
|
||||
width,
|
||||
})
|
||||
// Focal point adjustments
|
||||
const focalPoint = {
|
||||
x: isNumber(req.query.uploadEdits.focalPoint?.x)
|
||||
? req.query.uploadEdits.focalPoint.x
|
||||
: 50,
|
||||
y: isNumber(req.query.uploadEdits.focalPoint?.y)
|
||||
? req.query.uploadEdits.focalPoint.y
|
||||
: 50,
|
||||
}
|
||||
} else {
|
||||
resized = resized.resize(imageResizeConfig)
|
||||
|
||||
const safeResizeWidth = resizeWidth ?? scaledImageInfo.width
|
||||
const maxOffsetX = scaledImageInfo.width - safeResizeWidth
|
||||
const leftFocalEdge = Math.round(
|
||||
scaledImageInfo.width * (focalPoint.x / 100) - safeResizeWidth / 2,
|
||||
)
|
||||
const safeOffsetX = Math.min(Math.max(0, leftFocalEdge), maxOffsetX)
|
||||
|
||||
const safeResizeHeight = resizeHeight ?? scaledImageInfo.height
|
||||
const maxOffsetY = scaledImageInfo.height - safeResizeHeight
|
||||
const topFocalEdge = Math.round(
|
||||
scaledImageInfo.height * (focalPoint.y / 100) - safeResizeHeight / 2,
|
||||
)
|
||||
const safeOffsetY = Math.min(Math.max(0, topFocalEdge), maxOffsetY)
|
||||
|
||||
// extract the focal area from the scaled image
|
||||
resized = scaledImage.extract({
|
||||
height: safeResizeHeight,
|
||||
left: safeOffsetX,
|
||||
top: safeOffsetY,
|
||||
width: safeResizeWidth,
|
||||
})
|
||||
}
|
||||
|
||||
if (imageResizeConfig.formatOptions) {
|
||||
|
||||
@@ -17,12 +17,27 @@ export const fieldSchemaToJSON = (fields: Field[]): FieldSchemaJSON => {
|
||||
|
||||
switch (field.type) {
|
||||
case 'group':
|
||||
case 'array':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
fields: fieldSchemaToJSON(field.fields),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'array':
|
||||
acc.push({
|
||||
name: field.name,
|
||||
fields: fieldSchemaToJSON([
|
||||
...field.fields,
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
]),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'blocks':
|
||||
@@ -30,19 +45,25 @@ export const fieldSchemaToJSON = (fields: Field[]): FieldSchemaJSON => {
|
||||
name: field.name,
|
||||
blocks: field.blocks.reduce((acc, block) => {
|
||||
acc[block.slug] = {
|
||||
fields: fieldSchemaToJSON(block.fields),
|
||||
fields: fieldSchemaToJSON([
|
||||
...block.fields,
|
||||
{
|
||||
name: 'id',
|
||||
type: 'text',
|
||||
},
|
||||
]),
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {}),
|
||||
type: field.type,
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
case 'row':
|
||||
case 'collapsible':
|
||||
result = result.concat(fieldSchemaToJSON(field.fields))
|
||||
|
||||
break
|
||||
|
||||
case 'tabs': {
|
||||
|
||||
37
packages/plugin-sentry/.eslintrc.js
Normal file
37
packages/plugin-sentry/.eslintrc.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/** @type {import('eslint').ESLint.ConfigData } */
|
||||
module.exports = {
|
||||
extends: ['@payloadcms'],
|
||||
overrides: [
|
||||
{
|
||||
extends: ['plugin:@typescript-eslint/disable-type-checked'],
|
||||
files: ['*.js', '*.cjs', '*.json', '*.md', '*.yml', '*.yaml'],
|
||||
},
|
||||
{
|
||||
files: ['package.json', 'tsconfig.json'],
|
||||
rules: {
|
||||
'perfectionist/sort-array-includes': 'off',
|
||||
'perfectionist/sort-astro-attributes': 'off',
|
||||
'perfectionist/sort-classes': 'off',
|
||||
'perfectionist/sort-enums': 'off',
|
||||
'perfectionist/sort-exports': 'off',
|
||||
'perfectionist/sort-imports': 'off',
|
||||
'perfectionist/sort-interfaces': 'off',
|
||||
'perfectionist/sort-jsx-props': 'off',
|
||||
'perfectionist/sort-keys': 'off',
|
||||
'perfectionist/sort-maps': 'off',
|
||||
'perfectionist/sort-named-exports': 'off',
|
||||
'perfectionist/sort-named-imports': 'off',
|
||||
'perfectionist/sort-object-types': 'off',
|
||||
'perfectionist/sort-objects': 'off',
|
||||
'perfectionist/sort-svelte-attributes': 'off',
|
||||
'perfectionist/sort-union-types': 'off',
|
||||
'perfectionist/sort-vue-attributes': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
root: true,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user