Compare commits
32 Commits
v3.0.0-bet
...
v3.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78c8bb81a1 | ||
|
|
419b274bb1 | ||
|
|
ef818fd5c8 | ||
|
|
0aaf3af1ea | ||
|
|
18b0806b5b | ||
|
|
3d9051ad34 | ||
|
|
e4ef47b938 | ||
|
|
c7e7dc71d3 | ||
|
|
375671c162 | ||
|
|
23b495b145 | ||
|
|
27d743e2a8 | ||
|
|
8c9ff3d54b | ||
|
|
5c447252e7 | ||
|
|
a76be81368 | ||
|
|
5d97d57e70 | ||
|
|
de7ff1f8c6 | ||
|
|
3d714d3e72 | ||
|
|
2bbb02b9c0 | ||
|
|
0533e7f5db | ||
|
|
23c5ef428d | ||
|
|
f046a04510 | ||
|
|
4cda7d2363 | ||
|
|
ea48cfbfe9 | ||
|
|
1aeb912762 | ||
|
|
ce2cb35d71 | ||
|
|
d3ec68ac2f | ||
|
|
05bf52aac3 | ||
|
|
fed7f2fa5b | ||
|
|
686b0865b2 | ||
|
|
dfb4c8eb4c | ||
|
|
ad7a387e19 | ||
|
|
d05be016ce |
@@ -196,6 +196,48 @@ import { MyFieldComponent } from 'my-external-package/client'
|
||||
|
||||
which is a valid way to access MyFieldComponent that can be resolved by the consuming project.
|
||||
|
||||
### Custom Components from unknown locations
|
||||
|
||||
By default, any component paths from known locations are added to the import map. However, if you need to add any components from unknown locations to the import map, you can do so by adding them to the `admin.dependencies` array in your Payload Config. This is mostly only relevant for plugin authors and not for regular Payload users.
|
||||
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export default {
|
||||
// ...
|
||||
admin: {
|
||||
// ...
|
||||
dependencies: {
|
||||
myTestComponent: { // myTestComponent is the key - can be anything
|
||||
path: '/components/TestComponent.js#TestComponent',
|
||||
type: 'component',
|
||||
clientProps: {
|
||||
test: 'hello',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This way, `TestComponent` is added to the import map, no matter if it's referenced in a known location or not. On the client, you can then use the component like this:
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { RenderComponent, useConfig } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const CustomView = () => {
|
||||
const { config } = useConfig()
|
||||
return (
|
||||
<div>
|
||||
<RenderComponent mappedComponent={config.admin.dependencies?.myTestComponent} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Root Components
|
||||
|
||||
Root Components are those that effect the [Admin Panel](./overview) generally, such as the logo or the main nav.
|
||||
|
||||
@@ -5,6 +5,8 @@ import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { NotFoundPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap.js'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
segments: string[]
|
||||
@@ -17,6 +19,7 @@ type Args = {
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const NotFound = ({ params, searchParams }: Args) => NotFoundPage({ config, params, searchParams })
|
||||
const NotFound = ({ params, searchParams }: Args) =>
|
||||
NotFoundPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default NotFound
|
||||
|
||||
@@ -5,6 +5,8 @@ import config from '@payload-config'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import { RootPage, generatePageMetadata } from '@payloadcms/next/views'
|
||||
|
||||
import { importMap } from '../importMap.js'
|
||||
|
||||
type Args = {
|
||||
params: {
|
||||
segments: string[]
|
||||
@@ -17,6 +19,7 @@ type Args = {
|
||||
export const generateMetadata = ({ params, searchParams }: Args): Promise<Metadata> =>
|
||||
generatePageMetadata({ config, params, searchParams })
|
||||
|
||||
const Page = ({ params, searchParams }: Args) => RootPage({ config, params, searchParams })
|
||||
const Page = ({ params, searchParams }: Args) =>
|
||||
RootPage({ config, importMap, params, searchParams })
|
||||
|
||||
export default Page
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { TenantFieldComponent as TenantFieldComponent_0 } from '@/fields/TenantField/components/Field'
|
||||
import { TenantSelectorRSC as TenantSelectorRSC_1 } from '@/components/TenantSelector'
|
||||
|
||||
export const importMap = {
|
||||
'@/fields/TenantField/components/Field#TenantFieldComponent': TenantFieldComponent_0,
|
||||
'@/components/TenantSelector#TenantSelectorRSC': TenantSelectorRSC_1,
|
||||
}
|
||||
@@ -1,18 +1,21 @@
|
||||
/* THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD. */
|
||||
import configPromise from "@payload-config";
|
||||
import "@payloadcms/next/css";
|
||||
import { RootLayout } from "@payloadcms/next/layouts";
|
||||
import configPromise from '@payload-config'
|
||||
import '@payloadcms/next/css'
|
||||
import { RootLayout } from '@payloadcms/next/layouts'
|
||||
/* DO NOT MODIFY IT BECAUSE IT COULD BE REWRITTEN AT ANY TIME. */
|
||||
import React from "react";
|
||||
import React from 'react'
|
||||
|
||||
import "./custom.scss";
|
||||
import { importMap } from './admin/importMap.js'
|
||||
import './custom.scss'
|
||||
|
||||
type Args = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Layout = ({ children }: Args) => (
|
||||
<RootLayout config={configPromise}>{children}</RootLayout>
|
||||
);
|
||||
<RootLayout config={configPromise} importMap={importMap}>
|
||||
{children}
|
||||
</RootLayout>
|
||||
)
|
||||
|
||||
export default Layout;
|
||||
export default Layout
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Access } from 'payload'
|
||||
|
||||
import type { User } from '../../../../payload-types'
|
||||
import type { User } from '../../../payload-types'
|
||||
|
||||
import { isSuperAdmin } from '../../../access/isSuperAdmin'
|
||||
import { getTenantAdminTenantAccessIDs } from '../../../utilities/getTenantAccessIDs'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import type { User } from '../../../payload-types'
|
||||
import type { User } from '../../payload-types'
|
||||
|
||||
import { getTenantAdminTenantAccessIDs } from '../../utilities/getTenantAccessIDs'
|
||||
import { createAccess } from './access/create'
|
||||
@@ -37,32 +37,6 @@ const Users: CollectionConfig = {
|
||||
{
|
||||
name: 'tenant',
|
||||
type: 'relationship',
|
||||
filterOptions: ({ user }) => {
|
||||
if (!user) {
|
||||
// Would like to query where exists true on id
|
||||
// but that is not working
|
||||
return {
|
||||
id: {
|
||||
like: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
if (user?.roles?.includes('super-admin')) {
|
||||
// Would like to query where exists true on id
|
||||
// but that is not working
|
||||
return {
|
||||
id: {
|
||||
like: '',
|
||||
},
|
||||
}
|
||||
}
|
||||
const adminTenantAccessIDs = getTenantAdminTenantAccessIDs(user as User)
|
||||
return {
|
||||
id: {
|
||||
in: adminTenantAccessIDs,
|
||||
},
|
||||
}
|
||||
},
|
||||
index: true,
|
||||
relationTo: 'tenants',
|
||||
required: true,
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
import type { Option } from '@payloadcms/ui/elements/ReactSelect'
|
||||
import type { OptionObject } from 'payload'
|
||||
|
||||
import { getTenantAdminTenantAccessIDs } from '@/utilities/getTenantAccessIDs'
|
||||
import { SelectInput, useAuth } from '@payloadcms/ui'
|
||||
import * as qs from 'qs-esm'
|
||||
import React from 'react'
|
||||
|
||||
import type { Tenant, User } from '../../../payload-types.js'
|
||||
import type { Tenant, User } from '../../payload-types'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
export const TenantSelector = ({ initialCookie }: { initialCookie?: string }) => {
|
||||
const { user } = useAuth<User>()
|
||||
const [options, setOptions] = React.useState<OptionObject[]>([])
|
||||
const [value, setValue] = React.useState<string | undefined>(initialCookie)
|
||||
|
||||
const isSuperAdmin = user?.roles?.includes('super-admin')
|
||||
const tenantIDs =
|
||||
@@ -28,18 +29,6 @@ export const TenantSelector = ({ initialCookie }: { initialCookie?: string }) =>
|
||||
document.cookie = name + '=' + (value || '') + expires + '; path=/'
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchTenants = async () => {
|
||||
const res = await fetch(`/api/tenants?depth=0&limit=100&sort=name`, {
|
||||
credentials: 'include',
|
||||
}).then((res) => res.json())
|
||||
|
||||
setOptions(res.docs.map((doc: Tenant) => ({ label: doc.name, value: doc.id })))
|
||||
}
|
||||
|
||||
void fetchTenants()
|
||||
}, [])
|
||||
|
||||
const handleChange = React.useCallback((option: Option | Option[]) => {
|
||||
if (!option) {
|
||||
setCookie('payload-tenant', undefined)
|
||||
@@ -50,7 +39,44 @@ export const TenantSelector = ({ initialCookie }: { initialCookie?: string }) =>
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (isSuperAdmin || tenantIDs.length > 1) {
|
||||
React.useEffect(() => {
|
||||
const fetchTenants = async () => {
|
||||
const adminOfTenants = getTenantAdminTenantAccessIDs(user ?? null)
|
||||
|
||||
const queryString = qs.stringify(
|
||||
{
|
||||
depth: 0,
|
||||
limit: 100,
|
||||
sort: 'name',
|
||||
where: {
|
||||
id: {
|
||||
in: adminOfTenants,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
addQueryPrefix: true,
|
||||
},
|
||||
)
|
||||
|
||||
const res = await fetch(`/api/tenants${queryString}`, {
|
||||
credentials: 'include',
|
||||
}).then((res) => res.json())
|
||||
|
||||
const optionsToSet = res.docs.map((doc: Tenant) => ({ label: doc.name, value: doc.id }))
|
||||
|
||||
if (optionsToSet.length === 1) {
|
||||
setCookie('payload-tenant', optionsToSet[0].value)
|
||||
}
|
||||
setOptions(optionsToSet)
|
||||
}
|
||||
|
||||
if (user) {
|
||||
void fetchTenants()
|
||||
}
|
||||
}, [user])
|
||||
|
||||
if ((isSuperAdmin || tenantIDs.length > 1) && options.length > 1) {
|
||||
return (
|
||||
<div className="tenant-selector">
|
||||
<SelectInput
|
||||
@@ -59,7 +85,7 @@ export const TenantSelector = ({ initialCookie }: { initialCookie?: string }) =>
|
||||
onChange={handleChange}
|
||||
options={options}
|
||||
path="setTenant"
|
||||
value={value}
|
||||
value={options.find((opt) => opt.value === initialCookie)?.value}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client'
|
||||
import { RelationshipField, useField } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
type Props = {
|
||||
initialValue?: string
|
||||
path: string
|
||||
readOnly: boolean
|
||||
}
|
||||
export function TenantFieldComponentClient({ initialValue, path, readOnly }: Props) {
|
||||
const { formInitializing, setValue } = useField({ path })
|
||||
const hasSetInitialValue = React.useRef(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!hasSetInitialValue.current && !formInitializing && initialValue) {
|
||||
setValue(initialValue)
|
||||
hasSetInitialValue.current = true
|
||||
}
|
||||
}, [initialValue, setValue, formInitializing])
|
||||
|
||||
return (
|
||||
<RelationshipField
|
||||
field={{
|
||||
name: path,
|
||||
type: 'relationship',
|
||||
_path: path,
|
||||
label: 'Tenant',
|
||||
relationTo: 'tenants',
|
||||
required: true,
|
||||
}}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,26 +1,32 @@
|
||||
'use client'
|
||||
import { RelationshipField, useAuth, useFieldProps } from '@payloadcms/ui'
|
||||
import type { Payload } from 'payload'
|
||||
|
||||
import { cookies as getCookies, headers as getHeaders } from 'next/headers'
|
||||
import React from 'react'
|
||||
|
||||
import type { User } from '../../../../payload-types.js'
|
||||
import { TenantFieldComponentClient } from './Field.client'
|
||||
|
||||
export const TenantFieldComponent = () => {
|
||||
const { user } = useAuth<User>()
|
||||
const { path, readOnly } = useFieldProps()
|
||||
export const TenantFieldComponent: React.FC<{
|
||||
path: string
|
||||
payload: Payload
|
||||
readOnly: boolean
|
||||
}> = async (args) => {
|
||||
const cookies = getCookies()
|
||||
const headers = getHeaders()
|
||||
const { user } = await args.payload.auth({ headers })
|
||||
|
||||
if (user) {
|
||||
if ((user.tenants && user.tenants.length > 1) || user?.roles?.includes('super-admin')) {
|
||||
return (
|
||||
<RelationshipField
|
||||
label="Tenant"
|
||||
name={path}
|
||||
path={path}
|
||||
readOnly={readOnly}
|
||||
relationTo="tenants"
|
||||
required
|
||||
/>
|
||||
)
|
||||
}
|
||||
if (
|
||||
user &&
|
||||
((Array.isArray(user.tenants) && user.tenants.length > 1) ||
|
||||
user?.roles?.includes('super-admin'))
|
||||
) {
|
||||
return (
|
||||
<TenantFieldComponentClient
|
||||
initialValue={cookies.get('payload-tenant')?.value || undefined}
|
||||
path={args.path}
|
||||
readOnly={args.readOnly}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Field } from 'payload'
|
||||
|
||||
import { isSuperAdmin } from '../../access/isSuperAdmin'
|
||||
import { tenantFieldUpdate } from './access/update'
|
||||
import { TenantFieldComponent } from './components/Field'
|
||||
import { autofillTenant } from './hooks/autofillTenant'
|
||||
|
||||
export const tenantField: Field = {
|
||||
@@ -17,7 +16,7 @@ export const tenantField: Field = {
|
||||
},
|
||||
admin: {
|
||||
components: {
|
||||
Field: TenantFieldComponent,
|
||||
Field: '@/fields/TenantField/components/Field#TenantFieldComponent',
|
||||
},
|
||||
position: 'sidebar',
|
||||
},
|
||||
|
||||
@@ -17,6 +17,9 @@ export interface Config {
|
||||
'payload-preferences': PayloadPreference;
|
||||
'payload-migrations': PayloadMigration;
|
||||
};
|
||||
db: {
|
||||
defaultIDType: string;
|
||||
};
|
||||
globals: {};
|
||||
locale: null;
|
||||
user: User & {
|
||||
@@ -26,15 +29,20 @@ export interface Config {
|
||||
export interface UserAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
login: {
|
||||
password: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
registerFirstUser: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
unlock: {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
||||
@@ -7,7 +7,6 @@ import { fileURLToPath } from 'url'
|
||||
import { Pages } from './collections/Pages'
|
||||
import { Tenants } from './collections/Tenants'
|
||||
import Users from './collections/Users'
|
||||
import { TenantSelectorRSC } from './components/TenantSelector'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
@@ -15,7 +14,7 @@ const dirname = path.dirname(filename)
|
||||
export default buildConfig({
|
||||
admin: {
|
||||
components: {
|
||||
afterNavLinks: [TenantSelectorRSC],
|
||||
afterNavLinks: ['@/components/TenantSelector#TenantSelectorRSC'],
|
||||
},
|
||||
user: 'users',
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { User } from '../../payload-types'
|
||||
import type { User } from '../payload-types'
|
||||
|
||||
export const getTenantAccessIDs = (user: User | null): string[] => {
|
||||
if (!user) return []
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ export default withBundleAnalyzer(
|
||||
env: {
|
||||
PAYLOAD_CORE_DEV: 'true',
|
||||
ROOT_DIR: path.resolve(dirname),
|
||||
PAYLOAD_DISABLE_DEPENDENCY_CHECKER: 'true',
|
||||
PAYLOAD_CI_DEPENDENCY_CHECKER: 'true',
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
||||
70
package.json
70
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -10,46 +10,46 @@
|
||||
"build:app": "next build",
|
||||
"build:app:analyze": "cross-env ANALYZE=true next build",
|
||||
"build:clean": "pnpm clean:build",
|
||||
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\"",
|
||||
"build:core:force": "pnpm clean:build && turbo build --filter \"!@payloadcms/plugin-*\" --no-cache --force",
|
||||
"build:core": "turbo build --filter \"!@payloadcms/plugin-*\" --filter \"!@payloadcms/storage-*\"",
|
||||
"build:core:force": "pnpm clean:build && pnpm build:core --no-cache --force",
|
||||
"build:create-payload-app": "turbo build --filter create-payload-app",
|
||||
"build:db-mongodb": "turbo build --filter db-mongodb",
|
||||
"build:db-postgres": "turbo build --filter db-postgres",
|
||||
"build:db-sqlite": "turbo build --filter db-sqlite",
|
||||
"build:db-vercel-postgres": "turbo build --filter db-vercel-postgres",
|
||||
"build:drizzle": "turbo build --filter drizzle",
|
||||
"build:email-nodemailer": "turbo build --filter email-nodemailer",
|
||||
"build:email-resend": "turbo build --filter email-resend",
|
||||
"build:eslint-config": "turbo build --filter eslint-config",
|
||||
"build:db-mongodb": "turbo build --filter \"@payloadcms/db-mongodb\"",
|
||||
"build:db-postgres": "turbo build --filter \"@payloadcms/db-postgres\"",
|
||||
"build:db-sqlite": "turbo build --filter \"@payloadcms/db-sqlite\"",
|
||||
"build:db-vercel-postgres": "turbo build --filter \"@payloadcms/db-vercel-postgres\"",
|
||||
"build:drizzle": "turbo build --filter \"@payloadcms/drizzle\"",
|
||||
"build:email-nodemailer": "turbo build --filter \"@payloadcms/email-nodemailer\"",
|
||||
"build:email-resend": "turbo build --filter \"@payloadcms/email-resend\"",
|
||||
"build:eslint-config": "turbo build --filter \"@payloadcms/eslint-config\"",
|
||||
"build:essentials:force": "pnpm clean:build && turbo build --filter=\"payload...\" --filter=\"@payloadcms/ui\" --filter=\"@payloadcms/next\" --filter=\"@payloadcms/db-mongodb\" --filter=\"@payloadcms/db-postgres\" --filter=\"@payloadcms/richtext-lexical\" --filter=\"@payloadcms/translations\" --filter=\"@payloadcms/plugin-cloud\" --filter=\"@payloadcms/graphql\" --no-cache --force",
|
||||
"build:force": "pnpm run build:core:force",
|
||||
"build:graphql": "turbo build --filter graphql",
|
||||
"build:live-preview": "turbo build --filter live-preview",
|
||||
"build:live-preview-react": "turbo build --filter live-preview-react",
|
||||
"build:live-preview-vue": "turbo build --filter live-preview-vue",
|
||||
"build:graphql": "turbo build --filter \"@payloadcms/graphql\"",
|
||||
"build:live-preview": "turbo build --filter \"@payloadcms/live-preview\"",
|
||||
"build:live-preview-react": "turbo build --filter \"@payloadcms/live-preview-react\"",
|
||||
"build:live-preview-vue": "turbo build --filter \"@payloadcms/live-preview-vue\"",
|
||||
"build:next": "turbo build --filter \"@payloadcms/next\"",
|
||||
"build:payload": "turbo build --filter payload",
|
||||
"build:plugin-cloud": "turbo build --filter plugin-cloud",
|
||||
"build:plugin-cloud-storage": "turbo build --filter plugin-cloud-storage",
|
||||
"build:plugin-form-builder": "turbo build --filter plugin-form-builder",
|
||||
"build:plugin-nested-docs": "turbo build --filter plugin-nested-docs",
|
||||
"build:plugin-redirects": "turbo build --filter plugin-redirects",
|
||||
"build:plugin-relationship-object-ids": "turbo build --filter plugin-relationship-object-ids",
|
||||
"build:plugin-search": "turbo build --filter plugin-search",
|
||||
"build:plugin-sentry": "turbo build --filter plugin-sentry",
|
||||
"build:plugin-seo": "turbo build --filter plugin-seo",
|
||||
"build:plugin-stripe": "turbo build --filter plugin-stripe",
|
||||
"build:plugin-cloud": "turbo build --filter \"@payloadcms/plugin-cloud\"",
|
||||
"build:plugin-cloud-storage": "turbo build --filter \"@payloadcms/plugin-cloud-storage\"",
|
||||
"build:plugin-form-builder": "turbo build --filter \"@payloadcms/plugin-form-builder\"",
|
||||
"build:plugin-nested-docs": "turbo build --filter \"@payloadcms/plugin-nested-docs\"",
|
||||
"build:plugin-redirects": "turbo build --filter \"@payloadcms/plugin-redirects\"",
|
||||
"build:plugin-relationship-object-ids": "turbo build --filter \"@payloadcms/plugin-relationship-object-ids\"",
|
||||
"build:plugin-search": "turbo build --filter \"@payloadcms/plugin-search\"",
|
||||
"build:plugin-sentry": "turbo build --filter \"@payloadcms/plugin-sentry\"",
|
||||
"build:plugin-seo": "turbo build --filter \"@payloadcms/plugin-seo\"",
|
||||
"build:plugin-stripe": "turbo build --filter \"@payloadcms/plugin-stripe\"",
|
||||
"build:plugins": "turbo build --filter \"@payloadcms/plugin-*\"",
|
||||
"build:richtext-lexical": "turbo build --filter richtext-lexical",
|
||||
"build:richtext-slate": "turbo build --filter richtext-slate",
|
||||
"build:storage-azure": "turbo build --filter storage-azure",
|
||||
"build:storage-gcs": "turbo build --filter storage-gcs",
|
||||
"build:storage-s3": "turbo build --filter storage-s3",
|
||||
"build:storage-uploadthing": "turbo build --filter storage-uploadthing",
|
||||
"build:storage-vercel-blob": "turbo build --filter storage-vercel-blob",
|
||||
"build:richtext-lexical": "turbo build --filter \"@payloadcms/richtext-lexical\"",
|
||||
"build:richtext-slate": "turbo build --filter \"@payloadcms/richtext-slate\"",
|
||||
"build:storage-azure": "turbo build --filter \"@payloadcms/storage-azure\"",
|
||||
"build:storage-gcs": "turbo build --filter \"@payloadcms/storage-gcs\"",
|
||||
"build:storage-s3": "turbo build --filter \"@payloadcms/storage-s3\"",
|
||||
"build:storage-uploadthing": "turbo build --filter \"@payloadcms/storage-uploadthing\"",
|
||||
"build:storage-vercel-blob": "turbo build --filter \"@payloadcms/storage-vercel-blob\"",
|
||||
"build:tests": "pnpm --filter payload-test-suite run typecheck",
|
||||
"build:translations": "turbo build --filter translations",
|
||||
"build:ui": "turbo build --filter ui",
|
||||
"build:translations": "turbo build --filter \"@payloadcms/translations\"",
|
||||
"build:ui": "turbo build --filter \"@payloadcms/ui\"",
|
||||
"clean": "turbo clean",
|
||||
"clean:all": "node ./scripts/delete-recursively.js '@node_modules' 'media/*' '**/dist/' '**/.cache/*' '**/.next/*' '**/.turbo/*' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
|
||||
"clean:build": "node ./scripts/delete-recursively.js 'media/' '**/dist/' '**/.cache/' '**/.next/' '**/.turbo/' '**/tsconfig.tsbuildinfo' '**/payload*.tgz' '**/meta_*.json'",
|
||||
@@ -159,7 +159,7 @@
|
||||
"swc-plugin-transform-remove-imports": "1.15.0",
|
||||
"tempy": "1.0.1",
|
||||
"tsx": "4.17.0",
|
||||
"turbo": "^2.0.14",
|
||||
"turbo": "^2.1.0",
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -58,9 +58,17 @@ type Args = {
|
||||
tableName: string
|
||||
timestamps?: boolean
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
hasLocalizedManyNumberField: boolean
|
||||
hasLocalizedManyTextField: boolean
|
||||
hasLocalizedRelationshipField: boolean
|
||||
hasManyNumberField: 'index' | boolean
|
||||
hasManyTextField: 'index' | boolean
|
||||
relationsToBuild: RelationMap
|
||||
@@ -81,6 +89,7 @@ export const buildTable = ({
|
||||
tableName,
|
||||
timestamps,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
const isRoot = !incomingRootTableName
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
@@ -128,6 +137,7 @@ export const buildTable = ({
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
// split the relationsToBuild by localized and non-localized
|
||||
@@ -478,5 +488,12 @@ export const buildTable = ({
|
||||
return result
|
||||
})
|
||||
|
||||
return { hasManyNumberField, hasManyTextField, relationsToBuild }
|
||||
return {
|
||||
hasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField,
|
||||
hasManyNumberField,
|
||||
hasManyTextField,
|
||||
relationsToBuild,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,11 @@ type Args = {
|
||||
rootTableIDColType: IDType
|
||||
rootTableName: string
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
@@ -84,6 +89,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
let hasLocalizedField = false
|
||||
let hasLocalizedRelationshipField = false
|
||||
@@ -150,7 +156,11 @@ export const traverseFields = ({
|
||||
switch (field.type) {
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
@@ -179,7 +189,11 @@ export const traverseFields = ({
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
@@ -255,7 +269,11 @@ export const traverseFields = ({
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = text('locale', { enum: locales }).notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
@@ -337,13 +355,20 @@ export const traverseFields = ({
|
||||
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = text('_locale', { enum: locales }).notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${arrayTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -360,8 +385,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -453,13 +491,20 @@ export const traverseFields = ({
|
||||
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = text('_locale', { enum: locales }).notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${blockTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -476,8 +521,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: blockTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -577,6 +635,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -618,6 +677,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -660,6 +720,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) hasLocalizedField = true
|
||||
@@ -702,6 +763,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) hasLocalizedField = true
|
||||
@@ -753,7 +815,10 @@ export const traverseFields = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
if (adapter.payload.config.localization && field.localized) {
|
||||
if (
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -54,9 +54,17 @@ type Args = {
|
||||
tableName: string
|
||||
timestamps?: boolean
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
hasLocalizedManyNumberField: boolean
|
||||
hasLocalizedManyTextField: boolean
|
||||
hasLocalizedRelationshipField: boolean
|
||||
hasManyNumberField: 'index' | boolean
|
||||
hasManyTextField: 'index' | boolean
|
||||
relationsToBuild: RelationMap
|
||||
@@ -76,6 +84,7 @@ export const buildTable = ({
|
||||
tableName,
|
||||
timestamps,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
const isRoot = !incomingRootTableName
|
||||
const rootTableName = incomingRootTableName || tableName
|
||||
@@ -122,6 +131,7 @@ export const buildTable = ({
|
||||
rootTableIDColType: rootTableIDColType || idColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
// split the relationsToBuild by localized and non-localized
|
||||
@@ -464,5 +474,12 @@ export const buildTable = ({
|
||||
return result
|
||||
})
|
||||
|
||||
return { hasManyNumberField, hasManyTextField, relationsToBuild }
|
||||
return {
|
||||
hasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField,
|
||||
hasManyNumberField,
|
||||
hasManyTextField,
|
||||
relationsToBuild,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ type Args = {
|
||||
rootTableIDColType: string
|
||||
rootTableName: string
|
||||
versions: boolean
|
||||
/**
|
||||
* Tracks whether or not this table is built
|
||||
* from the result of a localized array or block field at some point
|
||||
*/
|
||||
withinLocalizedArrayOrBlock?: boolean
|
||||
}
|
||||
|
||||
type Result = {
|
||||
@@ -89,6 +94,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
}: Args): Result => {
|
||||
const throwValidationError = true
|
||||
let hasLocalizedField = false
|
||||
@@ -156,7 +162,11 @@ export const traverseFields = ({
|
||||
switch (field.type) {
|
||||
case 'text': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyTextField = true
|
||||
}
|
||||
|
||||
@@ -185,7 +195,11 @@ export const traverseFields = ({
|
||||
|
||||
case 'number': {
|
||||
if (field.hasMany) {
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
hasLocalizedManyNumberField = true
|
||||
}
|
||||
|
||||
@@ -276,7 +290,11 @@ export const traverseFields = ({
|
||||
parentIdx: (cols) => index(`${selectTableName}_parent_idx`).on(cols.parent),
|
||||
}
|
||||
|
||||
if (field.localized) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns.locale = adapter.enums.enum__locales('locale').notNull()
|
||||
baseExtraConfig.localeIdx = (cols) =>
|
||||
index(`${selectTableName}_locale_idx`).on(cols.locale)
|
||||
@@ -354,13 +372,20 @@ export const traverseFields = ({
|
||||
_parentIDIdx: (cols) => index(`${arrayTableName}_parent_id_idx`).on(cols._parentID),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${arrayTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -377,8 +402,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: arrayTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -466,13 +504,20 @@ export const traverseFields = ({
|
||||
_pathIdx: (cols) => index(`${blockTableName}_path_idx`).on(cols._path),
|
||||
}
|
||||
|
||||
if (field.localized && adapter.payload.config.localization) {
|
||||
const isLocalized =
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
|
||||
if (isLocalized) {
|
||||
baseColumns._locale = adapter.enums.enum__locales('_locale').notNull()
|
||||
baseExtraConfig._localeIdx = (cols) =>
|
||||
index(`${blockTableName}_locale_idx`).on(cols._locale)
|
||||
}
|
||||
|
||||
const {
|
||||
hasLocalizedManyNumberField: subHasLocalizedManyNumberField,
|
||||
hasLocalizedManyTextField: subHasLocalizedManyTextField,
|
||||
hasLocalizedRelationshipField: subHasLocalizedRelationshipField,
|
||||
hasManyNumberField: subHasManyNumberField,
|
||||
hasManyTextField: subHasManyTextField,
|
||||
relationsToBuild: subRelationsToBuild,
|
||||
@@ -489,8 +534,21 @@ export const traverseFields = ({
|
||||
rootTableName,
|
||||
tableName: blockTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock: isLocalized,
|
||||
})
|
||||
|
||||
if (subHasLocalizedManyNumberField) {
|
||||
hasLocalizedManyNumberField = subHasLocalizedManyNumberField
|
||||
}
|
||||
|
||||
if (subHasLocalizedRelationshipField) {
|
||||
hasLocalizedRelationshipField = subHasLocalizedRelationshipField
|
||||
}
|
||||
|
||||
if (subHasLocalizedManyTextField) {
|
||||
hasLocalizedManyTextField = subHasLocalizedManyTextField
|
||||
}
|
||||
|
||||
if (subHasManyTextField) {
|
||||
if (!hasManyTextField || subHasManyTextField === 'index')
|
||||
hasManyTextField = subHasManyTextField
|
||||
@@ -589,6 +647,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -629,6 +688,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (groupHasLocalizedField) hasLocalizedField = true
|
||||
@@ -670,6 +730,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (tabHasLocalizedField) hasLocalizedField = true
|
||||
@@ -711,6 +772,7 @@ export const traverseFields = ({
|
||||
rootTableIDColType,
|
||||
rootTableName,
|
||||
versions,
|
||||
withinLocalizedArrayOrBlock,
|
||||
})
|
||||
|
||||
if (rowHasLocalizedField) hasLocalizedField = true
|
||||
@@ -761,7 +823,11 @@ export const traverseFields = ({
|
||||
}
|
||||
break
|
||||
}
|
||||
if (adapter.payload.config.localization && field.localized) {
|
||||
|
||||
if (
|
||||
Boolean(field.localized && adapter.payload.config.localization) ||
|
||||
withinLocalizedArrayOrBlock
|
||||
) {
|
||||
hasLocalizedRelationshipField = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { NumberField } from 'payload'
|
||||
|
||||
type Args = {
|
||||
@@ -6,10 +5,29 @@ type Args = {
|
||||
locale?: string
|
||||
numberRows: Record<string, unknown>[]
|
||||
ref: Record<string, unknown>
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformHasManyNumber = ({ field, locale, numberRows, ref }: Args) => {
|
||||
const result = numberRows.map(({ number }) => number)
|
||||
export const transformHasManyNumber = ({
|
||||
field,
|
||||
locale,
|
||||
numberRows,
|
||||
ref,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown[]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
result = numberRows.reduce((acc, { locale, number }) => {
|
||||
if (locale === withinArrayOrBlockLocale) {
|
||||
acc.push(number)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
result = numberRows.map(({ number }) => number)
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[field.name][locale] = result
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { TextField } from 'payload'
|
||||
|
||||
type Args = {
|
||||
@@ -6,10 +5,29 @@ type Args = {
|
||||
locale?: string
|
||||
ref: Record<string, unknown>
|
||||
textRows: Record<string, unknown>[]
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformHasManyText = ({ field, locale, ref, textRows }: Args) => {
|
||||
const result = textRows.map(({ text }) => text)
|
||||
export const transformHasManyText = ({
|
||||
field,
|
||||
locale,
|
||||
ref,
|
||||
textRows,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown[]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
result = textRows.reduce((acc, { locale, text }) => {
|
||||
if (locale === withinArrayOrBlockLocale) {
|
||||
acc.push(text)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
} else {
|
||||
result = textRows.map(({ text }) => text)
|
||||
}
|
||||
|
||||
if (locale) {
|
||||
ref[field.name][locale] = result
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { RelationshipField, UploadField } from 'payload'
|
||||
|
||||
type Args = {
|
||||
@@ -6,21 +5,31 @@ type Args = {
|
||||
locale?: string
|
||||
ref: Record<string, unknown>
|
||||
relations: Record<string, unknown>[]
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformRelationship = ({ field, locale, ref, relations }: Args) => {
|
||||
export const transformRelationship = ({
|
||||
field,
|
||||
locale,
|
||||
ref,
|
||||
relations,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
let result: unknown
|
||||
|
||||
if (!('hasMany' in field) || field.hasMany === false) {
|
||||
const relation = relations[0]
|
||||
let relation = relations[0]
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
relation = relations.find((rel) => rel.locale === withinArrayOrBlockLocale)
|
||||
}
|
||||
|
||||
if (relation) {
|
||||
// Handle hasOne Poly
|
||||
if (Array.isArray(field.relationTo)) {
|
||||
const matchedRelation = Object.entries(relation).find(
|
||||
([key, val]) =>
|
||||
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
|
||||
)
|
||||
const matchedRelation = Object.entries(relation).find(([key, val]) => {
|
||||
return val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key)
|
||||
})
|
||||
|
||||
if (matchedRelation) {
|
||||
const relationTo = matchedRelation[0].replace('ID', '')
|
||||
@@ -36,18 +45,26 @@ export const transformRelationship = ({ field, locale, ref, relations }: Args) =
|
||||
const transformedRelations = []
|
||||
|
||||
relations.forEach((relation) => {
|
||||
let matchedLocale = true
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
matchedLocale = relation.locale === withinArrayOrBlockLocale
|
||||
}
|
||||
|
||||
// Handle hasMany
|
||||
if (!Array.isArray(field.relationTo)) {
|
||||
const relatedData = relation[`${field.relationTo}ID`]
|
||||
|
||||
if (relatedData) {
|
||||
if (relatedData && matchedLocale) {
|
||||
transformedRelations.push(relatedData)
|
||||
}
|
||||
} else {
|
||||
// Handle hasMany Poly
|
||||
const matchedRelation = Object.entries(relation).find(
|
||||
([key, val]) =>
|
||||
val !== null && !['id', 'locale', 'order', 'parent', 'path'].includes(key),
|
||||
val !== null &&
|
||||
!['id', 'locale', 'order', 'parent', 'path'].includes(key) &&
|
||||
matchedLocale,
|
||||
)
|
||||
|
||||
if (matchedRelation) {
|
||||
|
||||
@@ -58,6 +58,10 @@ type TraverseFieldsArgs = {
|
||||
* All hasMany text fields, as returned by Drizzle, keyed on an object by field path
|
||||
*/
|
||||
texts: Record<string, Record<string, unknown>[]>
|
||||
/**
|
||||
* Set to a locale if this group of fields is within a localized array or block.
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
// Traverse fields recursively, transforming data
|
||||
@@ -75,6 +79,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: TraverseFieldsArgs): T => {
|
||||
const sanitizedPath = path ? `${path}.` : path
|
||||
|
||||
@@ -93,6 +98,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -114,6 +120,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -157,6 +164,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: locale,
|
||||
})
|
||||
|
||||
if ('_order' in rowResult) {
|
||||
@@ -192,6 +200,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -237,6 +246,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: locale,
|
||||
})
|
||||
|
||||
delete blockResult._order
|
||||
@@ -247,7 +257,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
})
|
||||
})
|
||||
} else {
|
||||
result[field.name] = blocks[blockFieldPath].map((row, i) => {
|
||||
result[field.name] = blocks[blockFieldPath].reduce((acc, row, i) => {
|
||||
delete row._order
|
||||
if (row._uuid) {
|
||||
row.id = row._uuid
|
||||
@@ -256,24 +266,40 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
const block = field.blocks.find(({ slug }) => slug === row.blockType)
|
||||
|
||||
if (block) {
|
||||
return traverseFields<T>({
|
||||
adapter,
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
})
|
||||
if (
|
||||
!withinArrayOrBlockLocale ||
|
||||
(withinArrayOrBlockLocale && withinArrayOrBlockLocale === row._locale)
|
||||
) {
|
||||
if (row._locale) {
|
||||
delete row._locale
|
||||
}
|
||||
|
||||
acc.push(
|
||||
traverseFields<T>({
|
||||
adapter,
|
||||
blocks,
|
||||
config,
|
||||
dataRef: row,
|
||||
deletions,
|
||||
fieldPrefix: '',
|
||||
fields: block.fields,
|
||||
numbers,
|
||||
path: `${blockFieldPath}.${i}`,
|
||||
relationships,
|
||||
table: row,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}),
|
||||
)
|
||||
|
||||
return acc
|
||||
}
|
||||
} else {
|
||||
acc.push({})
|
||||
}
|
||||
|
||||
return {}
|
||||
})
|
||||
return acc
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,6 +360,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
ref: result,
|
||||
relations: relationPathMatch,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
return result
|
||||
@@ -368,6 +395,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
ref: result,
|
||||
textRows: textPathMatch,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -402,6 +430,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
field,
|
||||
numberRows: numberPathMatch,
|
||||
ref: result,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -466,6 +495,7 @@ export const traverseFields = <T extends Record<string, unknown>>({
|
||||
relationships,
|
||||
table,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
if ('_order' in ref) {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { ArrayField } from 'payload'
|
||||
|
||||
import type { DrizzleAdapter } from '../../types.js'
|
||||
@@ -26,6 +25,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const transformArray = ({
|
||||
@@ -43,6 +47,7 @@ export const transformArray = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
const newRows: ArrayRowToInsert[] = []
|
||||
|
||||
@@ -78,6 +83,10 @@ export const transformArray = ({
|
||||
newRow.row._locale = locale
|
||||
}
|
||||
|
||||
if (withinArrayOrBlockLocale) {
|
||||
newRow.row._locale = withinArrayOrBlockLocale
|
||||
}
|
||||
|
||||
traverseFields({
|
||||
adapter,
|
||||
arrays: newRow.arrays,
|
||||
@@ -97,6 +106,7 @@ export const transformArray = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
newRows.push(newRow)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import type { BlockField } from 'payload'
|
||||
|
||||
import toSnakeCase from 'to-snake-case'
|
||||
@@ -26,6 +25,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
export const transformBlocks = ({
|
||||
adapter,
|
||||
@@ -41,6 +45,7 @@ export const transformBlocks = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
data.forEach((blockRow, i) => {
|
||||
if (typeof blockRow.blockType !== 'string') return
|
||||
@@ -60,6 +65,7 @@ export const transformBlocks = ({
|
||||
}
|
||||
|
||||
if (field.localized && locale) newRow.row._locale = locale
|
||||
if (withinArrayOrBlockLocale) newRow.row._locale = withinArrayOrBlockLocale
|
||||
|
||||
const blockTableName = adapter.tableNameMap.get(`${baseTableName}_blocks_${blockType}`)
|
||||
|
||||
@@ -94,6 +100,7 @@ export const transformBlocks = ({
|
||||
row: newRow.row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
blocks[blockType].push(newRow)
|
||||
|
||||
@@ -57,6 +57,11 @@ type Args = {
|
||||
[tableName: string]: Record<string, unknown>[]
|
||||
}
|
||||
texts: Record<string, unknown>[]
|
||||
/**
|
||||
* Set to a locale code if this set of fields is traversed within a
|
||||
* localized array or block field
|
||||
*/
|
||||
withinArrayOrBlockLocale?: string
|
||||
}
|
||||
|
||||
export const traverseFields = ({
|
||||
@@ -80,6 +85,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
}: Args) => {
|
||||
fields.forEach((field) => {
|
||||
let columnName = ''
|
||||
@@ -116,6 +122,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
@@ -137,6 +144,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
arrays[arrayTableName] = arrays[arrayTableName].concat(newRows)
|
||||
@@ -168,6 +176,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -186,6 +195,7 @@ export const traverseFields = ({
|
||||
relationshipsToDelete,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -217,6 +227,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
@@ -240,6 +251,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -274,6 +286,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale: localeKey,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
@@ -297,6 +310,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -321,6 +335,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -347,6 +362,7 @@ export const traverseFields = ({
|
||||
row,
|
||||
selects,
|
||||
texts,
|
||||
withinArrayOrBlockLocale,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -387,6 +403,7 @@ export const traverseFields = ({
|
||||
|
||||
transformRelationship({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: relationshipPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -440,6 +457,7 @@ export const traverseFields = ({
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
transformTexts({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: textPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -471,6 +489,7 @@ export const traverseFields = ({
|
||||
} else if (Array.isArray(fieldData)) {
|
||||
transformNumbers({
|
||||
baseRow: {
|
||||
locale: withinArrayOrBlockLocale,
|
||||
path: numberPath,
|
||||
},
|
||||
data: fieldData,
|
||||
@@ -503,6 +522,7 @@ export const traverseFields = ({
|
||||
const newRows = transformSelects({
|
||||
id: data._uuid || data.id,
|
||||
data: data[field.name],
|
||||
locale: withinArrayOrBlockLocale,
|
||||
})
|
||||
|
||||
selects[selectTableName] = selects[selectTableName].concat(newRows)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -88,7 +88,7 @@ export const DefaultNavClient: React.FC = () => {
|
||||
LinkWithDefault) as typeof LinkWithDefault.default
|
||||
|
||||
const LinkElement = Link || 'a'
|
||||
const activeCollection = pathname.endsWith(href)
|
||||
const activeCollection = pathname.startsWith(href)
|
||||
|
||||
return (
|
||||
<LinkElement
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { MappedComponent, ServerProps, VisibleEntities } from 'payload'
|
||||
|
||||
import { AppHeader, EntityVisibilityProvider, NavToggler } from '@payloadcms/ui'
|
||||
import { AppHeader, BulkUploadProvider, EntityVisibilityProvider, NavToggler } from '@payloadcms/ui'
|
||||
import { RenderComponent, getCreateMappedComponent } from '@payloadcms/ui/shared'
|
||||
import React from 'react'
|
||||
|
||||
@@ -59,21 +59,23 @@ export const DefaultTemplate: React.FC<DefaultTemplateProps> = ({
|
||||
|
||||
return (
|
||||
<EntityVisibilityProvider visibleEntities={visibleEntities}>
|
||||
<div>
|
||||
<div className={`${baseClass}__nav-toggler-wrapper`} id="nav-toggler">
|
||||
<NavToggler className={`${baseClass}__nav-toggler`}>
|
||||
<NavHamburger />
|
||||
</NavToggler>
|
||||
</div>
|
||||
<Wrapper baseClass={baseClass} className={className}>
|
||||
<RenderComponent mappedComponent={MappedDefaultNav} />
|
||||
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<AppHeader />
|
||||
{children}
|
||||
<BulkUploadProvider>
|
||||
<div>
|
||||
<div className={`${baseClass}__nav-toggler-wrapper`} id="nav-toggler">
|
||||
<NavToggler className={`${baseClass}__nav-toggler`}>
|
||||
<NavHamburger />
|
||||
</NavToggler>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</div>
|
||||
<Wrapper baseClass={baseClass} className={className}>
|
||||
<RenderComponent mappedComponent={MappedDefaultNav} />
|
||||
|
||||
<div className={`${baseClass}__wrap`}>
|
||||
<AppHeader />
|
||||
{children}
|
||||
</div>
|
||||
</Wrapper>
|
||||
</div>
|
||||
</BulkUploadProvider>
|
||||
</EntityVisibilityProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -280,6 +280,7 @@ export const Document: React.FC<AdminViewProps> = async ({
|
||||
initialData={data}
|
||||
initialState={formState}
|
||||
isEditing={isEditing}
|
||||
key={locale?.code}
|
||||
>
|
||||
{!RootViewOverride && (
|
||||
<DocumentHeader
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ClientCollectionConfig } from 'payload'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
BulkUploadDrawer,
|
||||
Button,
|
||||
DeleteMany,
|
||||
EditMany,
|
||||
@@ -14,7 +13,6 @@ import {
|
||||
ListSelection,
|
||||
Pagination,
|
||||
PerPage,
|
||||
PopupList,
|
||||
PublishMany,
|
||||
RelationshipProvider,
|
||||
RenderComponent,
|
||||
@@ -24,7 +22,7 @@ import {
|
||||
Table,
|
||||
UnpublishMany,
|
||||
ViewDescription,
|
||||
bulkUploadDrawerSlug,
|
||||
useBulkUpload,
|
||||
useConfig,
|
||||
useEditDepth,
|
||||
useListInfo,
|
||||
@@ -60,6 +58,8 @@ export const DefaultListView: React.FC = () => {
|
||||
const { searchParams } = useSearchParams()
|
||||
const { openModal } = useModal()
|
||||
const { clearRouteCache } = useRouteCache()
|
||||
const { setCollectionSlug, setOnSuccess } = useBulkUpload()
|
||||
const { drawerSlug } = useBulkUpload()
|
||||
|
||||
const { getEntityConfig } = useConfig()
|
||||
|
||||
@@ -106,6 +106,12 @@ export const DefaultListView: React.FC = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const openBulkUpload = React.useCallback(() => {
|
||||
setCollectionSlug(collectionSlug)
|
||||
openModal(drawerSlug)
|
||||
setOnSuccess(clearRouteCache)
|
||||
}, [clearRouteCache, collectionSlug, drawerSlug, openModal, setCollectionSlug, setOnSuccess])
|
||||
|
||||
useEffect(() => {
|
||||
if (drawerDepth <= 1) {
|
||||
setStepNav([
|
||||
@@ -116,6 +122,8 @@ export const DefaultListView: React.FC = () => {
|
||||
}
|
||||
}, [setStepNav, labels, drawerDepth])
|
||||
|
||||
const isBulkUploadEnabled = isUploadCollection && collectionConfig.upload.bulkUpload
|
||||
|
||||
return (
|
||||
<div className={`${baseClass} ${baseClass}--${collectionSlug}`}>
|
||||
<SetViewActions actions={actions} />
|
||||
@@ -126,23 +134,15 @@ export const DefaultListView: React.FC = () => {
|
||||
<ListHeader heading={getTranslation(labels?.plural, i18n)}>
|
||||
{hasCreatePermission && (
|
||||
<Button
|
||||
Link={Link}
|
||||
SubMenuPopupContent={
|
||||
isUploadCollection && collectionConfig.upload.bulkUpload ? (
|
||||
<PopupList.ButtonGroup>
|
||||
<PopupList.Button onClick={() => openModal(bulkUploadDrawerSlug)}>
|
||||
{t('upload:bulkUpload')}
|
||||
</PopupList.Button>
|
||||
</PopupList.ButtonGroup>
|
||||
) : null
|
||||
}
|
||||
Link={!isBulkUploadEnabled ? Link : undefined}
|
||||
aria-label={i18n.t('general:createNewLabel', {
|
||||
label: getTranslation(labels?.singular, i18n),
|
||||
})}
|
||||
buttonStyle="pill"
|
||||
el="link"
|
||||
el={!isBulkUploadEnabled ? 'link' : 'button'}
|
||||
onClick={isBulkUploadEnabled ? openBulkUpload : undefined}
|
||||
size="small"
|
||||
to={newDocumentURL}
|
||||
to={!isBulkUploadEnabled ? newDocumentURL : undefined}
|
||||
>
|
||||
{i18n.t('general:createNew')}
|
||||
</Button>
|
||||
@@ -155,12 +155,6 @@ export const DefaultListView: React.FC = () => {
|
||||
<ViewDescription Description={Description} description={description} />
|
||||
</div>
|
||||
)}
|
||||
{isUploadCollection && collectionConfig.upload.bulkUpload ? (
|
||||
<BulkUploadDrawer
|
||||
collectionSlug={collectionSlug}
|
||||
onSuccess={() => clearRouteCache()}
|
||||
/>
|
||||
) : null}
|
||||
</ListHeader>
|
||||
)}
|
||||
<ListControls collectionConfig={collectionConfig} fields={fields} />
|
||||
|
||||
@@ -37,6 +37,8 @@ export const DeviceContainer: React.FC<{
|
||||
x = '-50%'
|
||||
|
||||
if (
|
||||
desiredSize &&
|
||||
measuredDeviceSize &&
|
||||
typeof zoom === 'number' &&
|
||||
typeof desiredSize.width === 'number' &&
|
||||
typeof desiredSize.height === 'number' &&
|
||||
|
||||
@@ -100,7 +100,7 @@ const RenderFieldsToDiff: React.FC<Props> = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (field.type === 'tabs' && 'fields' in field) {
|
||||
if (field.type === 'tabs' && 'tabs' in field) {
|
||||
const Tabs = diffComponents.tabs
|
||||
|
||||
return (
|
||||
|
||||
@@ -66,7 +66,8 @@ export const VersionsViewClient: React.FC<{
|
||||
limit={data.limit}
|
||||
nextPage={data.nextPage}
|
||||
numberOfNeighbors={1}
|
||||
onChange={() => handlePageChange}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onChange={handlePageChange}
|
||||
page={data.page}
|
||||
prevPage={data.prevPage}
|
||||
totalPages={data.totalPages}
|
||||
@@ -81,7 +82,8 @@ export const VersionsViewClient: React.FC<{
|
||||
{i18n.t('general:of')} {data.totalDocs}
|
||||
</div>
|
||||
<PerPage
|
||||
handleChange={() => handlePerPageChange}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
handleChange={handlePerPageChange}
|
||||
limit={limit ? Number(limit) : 10}
|
||||
limits={paginationLimits}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { BinScript } from '../config/types.js'
|
||||
import { findConfig } from '../config/find.js'
|
||||
import { generateImportMap } from './generateImportMap/index.js'
|
||||
import { generateTypes } from './generateTypes.js'
|
||||
import { info } from './info.js'
|
||||
import { loadEnv } from './loadEnv.js'
|
||||
import { migrate } from './migrate.js'
|
||||
|
||||
@@ -16,6 +17,11 @@ export const bin = async () => {
|
||||
const args = minimist(process.argv.slice(2))
|
||||
const script = (typeof args._[0] === 'string' ? args._[0] : '').toLowerCase()
|
||||
|
||||
if (script === 'info') {
|
||||
await info()
|
||||
return
|
||||
}
|
||||
|
||||
if (script === 'run') {
|
||||
const scriptPath = args._[1]
|
||||
if (!scriptPath) {
|
||||
|
||||
63
packages/payload/src/bin/info.ts
Normal file
63
packages/payload/src/bin/info.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { execFileSync } from 'child_process'
|
||||
import os from 'os'
|
||||
|
||||
import { getDependencies } from '../index.js'
|
||||
import { PAYLOAD_PACKAGE_LIST } from '../versions/payloadPackageList.js'
|
||||
|
||||
export const info = async () => {
|
||||
const deps = await getDependencies(process.cwd(), [
|
||||
...PAYLOAD_PACKAGE_LIST,
|
||||
'next',
|
||||
'react',
|
||||
'react-dom',
|
||||
])
|
||||
|
||||
const formattedDeps = Array.from(deps.resolved.entries()).map(([name, { version }]) => ({
|
||||
name,
|
||||
version,
|
||||
}))
|
||||
|
||||
console.log(generateOutput(formattedDeps))
|
||||
}
|
||||
|
||||
function generateOutput(packages: Array<{ name: string; version: string }>) {
|
||||
const cpuCores = os.cpus().length
|
||||
|
||||
const primaryDeps = packages.filter(({ name }) => name === 'payload' || name === 'next')
|
||||
const otherDeps = packages
|
||||
.filter(({ name }) => name !== 'payload' && name !== 'next')
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
const formattedDeps = [...primaryDeps, ...otherDeps]
|
||||
.map(({ name, version }) => ` ${name}: ${version}`)
|
||||
.join('\n')
|
||||
|
||||
return `
|
||||
Binaries:
|
||||
Node: ${process.versions.node}
|
||||
npm: ${getBinaryVersion('npm')}
|
||||
Yarn: ${getBinaryVersion('yarn')}
|
||||
pnpm: ${getBinaryVersion('pnpm')}
|
||||
Relevant Packages:
|
||||
${formattedDeps}
|
||||
Operating System:
|
||||
Platform: ${os.platform()}
|
||||
Arch: ${os.arch()}
|
||||
Version: ${os.version()}
|
||||
Available memory (MB): ${Math.ceil(os.totalmem() / 1024 / 1024)}
|
||||
Available CPU cores: ${cpuCores > 0 ? cpuCores : 'N/A'}
|
||||
`
|
||||
}
|
||||
|
||||
function getBinaryVersion(binaryName: string) {
|
||||
try {
|
||||
return execFileSync(binaryName, ['--version']).toString().trim()
|
||||
} catch {
|
||||
return 'N/A'
|
||||
}
|
||||
}
|
||||
|
||||
// Direct execution
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
void info()
|
||||
}
|
||||
59
packages/payload/src/checkPayloadDependencies.ts
Normal file
59
packages/payload/src/checkPayloadDependencies.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { CustomVersionParser } from './utilities/dependencies/dependencyChecker.js'
|
||||
|
||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
||||
import { PAYLOAD_PACKAGE_LIST } from './versions/payloadPackageList.js'
|
||||
|
||||
const customReactVersionParser: CustomVersionParser = (version) => {
|
||||
const [mainVersion, ...preReleases] = version.split('-')
|
||||
|
||||
if (preReleases?.length === 3) {
|
||||
// Needs different handling, as it's in a format like 19.0.0-rc-06d0b89e-20240801 format
|
||||
const date = preReleases[2]
|
||||
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases: [date] }
|
||||
}
|
||||
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases }
|
||||
}
|
||||
|
||||
export async function checkPayloadDependencies() {
|
||||
const dependencies = [...PAYLOAD_PACKAGE_LIST]
|
||||
|
||||
if (process.env.PAYLOAD_CI_DEPENDENCY_CHECKER !== 'true') {
|
||||
dependencies.push('@payloadcms/plugin-sentry')
|
||||
}
|
||||
|
||||
// First load. First check if there are mismatching dependency versions of payload packages
|
||||
await checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'payload',
|
||||
dependencies,
|
||||
targetVersionDependency: 'payload',
|
||||
},
|
||||
{
|
||||
name: 'react',
|
||||
dependencies: ['react', 'react-dom'],
|
||||
targetVersionDependency: 'react',
|
||||
},
|
||||
],
|
||||
dependencyVersions: {
|
||||
next: {
|
||||
required: false,
|
||||
version: '>=15.0.0-canary.104',
|
||||
},
|
||||
react: {
|
||||
customVersionParser: customReactVersionParser,
|
||||
required: false,
|
||||
version: '>=19.0.0-rc-06d0b89e-20240801',
|
||||
},
|
||||
'react-dom': {
|
||||
customVersionParser: customReactVersionParser,
|
||||
required: false,
|
||||
version: '>=19.0.0-rc-06d0b89e-20240801',
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -14,6 +14,15 @@ export type ServerOnlyCollectionAdminProperties = keyof Pick<
|
||||
'hidden' | 'preview'
|
||||
>
|
||||
|
||||
export type ServerOnlyUploadProperties = keyof Pick<
|
||||
SanitizedCollectionConfig['upload'],
|
||||
| 'adminThumbnail'
|
||||
| 'externalFileHeaderFilter'
|
||||
| 'handlers'
|
||||
| 'modifyResponseHeaders'
|
||||
| 'withMetadata'
|
||||
>
|
||||
|
||||
export type ClientCollectionConfig = {
|
||||
_isPreviewEnabled?: true
|
||||
admin: {
|
||||
|
||||
@@ -39,8 +39,9 @@ export type ClientConfig = {
|
||||
Logo: MappedComponent
|
||||
}
|
||||
}
|
||||
dependencies?: Record<string, MappedComponent>
|
||||
livePreview?: Omit<LivePreviewConfig, ServerOnlyLivePreviewProperties>
|
||||
} & Omit<SanitizedConfig['admin'], 'components' | 'livePreview'>
|
||||
} & Omit<SanitizedConfig['admin'], 'components' | 'dependencies' | 'livePreview'>
|
||||
collections: ClientCollectionConfig[]
|
||||
custom?: Record<string, any>
|
||||
globals: ClientGlobalConfig[]
|
||||
|
||||
@@ -115,11 +115,18 @@ export type FieldHookArgs<TData extends TypeWithID = any, TValue = any, TSibling
|
||||
/** The collection which the field belongs to. If the field belongs to a global, this will be null. */
|
||||
collection: SanitizedCollectionConfig | null
|
||||
context: RequestContext
|
||||
/**
|
||||
* Only available in `afterRead` hooks
|
||||
*/
|
||||
currentDepth?: number /**
|
||||
* Only available in `afterRead` hooks
|
||||
*/
|
||||
/** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */
|
||||
data?: Partial<TData>
|
||||
/**
|
||||
* Only available in the `afterRead` hook.
|
||||
*/
|
||||
depth?: number
|
||||
draft?: boolean
|
||||
/** The field which the hook is running against. */
|
||||
field: FieldAffectingData
|
||||
@@ -1600,7 +1607,7 @@ export function optionIsValue(option: Option): option is string {
|
||||
export function fieldSupportsMany<TField extends ClientField | Field>(
|
||||
field: TField,
|
||||
): field is TField & (TField extends ClientField ? FieldWithManyClient : FieldWithMany) {
|
||||
return field.type === 'select' || field.type === 'relationship'
|
||||
return field.type === 'select' || field.type === 'relationship' || field.type === 'upload'
|
||||
}
|
||||
|
||||
export function fieldHasMaxDepth<TField extends ClientField | Field>(
|
||||
|
||||
@@ -204,7 +204,9 @@ export const promise = async ({
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
data: doc,
|
||||
depth,
|
||||
draft,
|
||||
field,
|
||||
findMany,
|
||||
@@ -231,7 +233,9 @@ export const promise = async ({
|
||||
const hookedValue = await currentHook({
|
||||
collection,
|
||||
context,
|
||||
currentDepth,
|
||||
data: doc,
|
||||
depth,
|
||||
draft,
|
||||
field,
|
||||
findMany,
|
||||
|
||||
@@ -57,11 +57,12 @@ import type { TypeWithVersion } from './versions/types.js'
|
||||
import { decrypt, encrypt } from './auth/crypto.js'
|
||||
import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
|
||||
import { JWTAuthentication } from './auth/strategies/jwt.js'
|
||||
import { checkPayloadDependencies } from './checkPayloadDependencies.js'
|
||||
import localOperations from './collections/operations/local/index.js'
|
||||
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
|
||||
import { fieldAffectsData } from './fields/config/types.js'
|
||||
import localGlobalOperations from './globals/operations/local/index.js'
|
||||
import { getDependencies } from './utilities/dependencies/getDependencies.js'
|
||||
import { checkDependencies } from './utilities/dependencies/dependencyChecker.js'
|
||||
import flattenFields from './utilities/flattenTopLevelFields.js'
|
||||
import { getLogger } from './utilities/logger.js'
|
||||
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
|
||||
@@ -430,58 +431,7 @@ export class BasePayload {
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
||||
) {
|
||||
// First load. First check if there are mismatching dependency versions of payload packages
|
||||
const resolvedDependencies = await getDependencies(dirname, [
|
||||
'@payloadcms/ui/shared',
|
||||
'payload',
|
||||
'@payloadcms/next/utilities',
|
||||
'@payloadcms/richtext-lexical',
|
||||
'@payloadcms/richtext-slate',
|
||||
'@payloadcms/graphql',
|
||||
'@payloadcms/plugin-cloud',
|
||||
'@payloadcms/db-mongodb',
|
||||
'@payloadcms/db-postgres',
|
||||
'@payloadcms/plugin-form-builder',
|
||||
'@payloadcms/plugin-nested-docs',
|
||||
'@payloadcms/plugin-seo',
|
||||
'@payloadcms/plugin-search',
|
||||
'@payloadcms/plugin-cloud-storage',
|
||||
'@payloadcms/plugin-stripe',
|
||||
'@payloadcms/plugin-zapier',
|
||||
'@payloadcms/plugin-redirects',
|
||||
'@payloadcms/plugin-sentry',
|
||||
'@payloadcms/bundler-webpack',
|
||||
'@payloadcms/bundler-vite',
|
||||
'@payloadcms/live-preview',
|
||||
'@payloadcms/live-preview-react',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/email-nodemailer',
|
||||
'@payloadcms/email-resend',
|
||||
'@payloadcms/storage-azure',
|
||||
'@payloadcms/storage-s3',
|
||||
'@payloadcms/storage-gcs',
|
||||
'@payloadcms/storage-vercel-blob',
|
||||
'@payloadcms/storage-uploadthing',
|
||||
])
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
|
||||
throw new Error(
|
||||
`Mismatching payload dependency versions found: ${formattedVersionsWithPackageNameString}. All payload and @payloadcms/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all payload and @payloadcms/* packages have the same version.`,
|
||||
)
|
||||
}
|
||||
await checkPayloadDependencies()
|
||||
}
|
||||
|
||||
this.importMap = options.importMap
|
||||
@@ -713,6 +663,7 @@ export type { ClientCollectionConfig } from './collections/config/client.js'
|
||||
export type {
|
||||
ServerOnlyCollectionAdminProperties,
|
||||
ServerOnlyCollectionProperties,
|
||||
ServerOnlyUploadProperties,
|
||||
} from './collections/config/client.js'
|
||||
export type {
|
||||
AfterChangeHook as CollectionAfterChangeHook,
|
||||
@@ -1047,6 +998,7 @@ export {
|
||||
deepMergeWithReactComponents,
|
||||
deepMergeWithSourceArrays,
|
||||
} from './utilities/deepMerge.js'
|
||||
export { getDependencies } from './utilities/dependencies/getDependencies.js'
|
||||
export { default as flattenTopLevelFields } from './utilities/flattenTopLevelFields.js'
|
||||
export { formatLabels, formatNames, toWords } from './utilities/formatLabels.js'
|
||||
export { getCollectionIDFieldTypes } from './utilities/getCollectionIDFieldTypes.js'
|
||||
@@ -1061,7 +1013,7 @@ export { mapAsync } from './utilities/mapAsync.js'
|
||||
export { mergeListSearchAndWhere } from './utilities/mergeListSearchAndWhere.js'
|
||||
export { buildVersionCollectionFields } from './versions/buildCollectionFields.js'
|
||||
export { buildVersionGlobalFields } from './versions/buildGlobalFields.js'
|
||||
export { getDependencies }
|
||||
export { checkDependencies }
|
||||
export { versionDefaults } from './versions/defaults.js'
|
||||
export { deleteCollectionVersions } from './versions/deleteCollectionVersions.js'
|
||||
export { enforceMaxVersions } from './versions/enforceMaxVersions.js'
|
||||
|
||||
114
packages/payload/src/utilities/dependencies/dependencyChecker.ts
Normal file
114
packages/payload/src/utilities/dependencies/dependencyChecker.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import path from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
import { getDependencies } from '../../index.js'
|
||||
import { compareVersions } from './versionUtils.js'
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export type CustomVersionParser = (version: string) => { parts: number[]; preReleases: string[] }
|
||||
|
||||
export type DependencyCheckerArgs = {
|
||||
/**
|
||||
* Define dependency groups to ensure that all dependencies within that group are on the same version, and that no dependencies in that group with different versions are found
|
||||
*/
|
||||
dependencyGroups?: {
|
||||
dependencies: string[]
|
||||
/**
|
||||
* Name of the dependency group to be displayed in the error message
|
||||
*/
|
||||
name: string
|
||||
targetVersion?: string
|
||||
targetVersionDependency?: string
|
||||
}[]
|
||||
/**
|
||||
* Dependency package names keyed to their required versions. Supports >= (greater or equal than version) as a prefix, or no prefix for the exact version
|
||||
*/
|
||||
dependencyVersions?: {
|
||||
[dependency: string]: {
|
||||
customVersionParser?: CustomVersionParser
|
||||
required?: boolean
|
||||
version?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkDependencies({
|
||||
dependencyGroups,
|
||||
dependencyVersions,
|
||||
}: DependencyCheckerArgs): Promise<void> {
|
||||
if (dependencyGroups?.length) {
|
||||
for (const dependencyGroup of dependencyGroups) {
|
||||
const resolvedDependencies = await getDependencies(dirname, dependencyGroup.dependencies)
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const targetVersion =
|
||||
dependencyGroup.targetVersion ??
|
||||
resolvedDependencies.resolved.get(dependencyGroup.targetVersionDependency)?.version
|
||||
if (targetVersion) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.filter(([version]) => version !== targetVersion)
|
||||
.map(([version, pkg]) => `${pkg}@${version} (Please change this to ${targetVersion})`)
|
||||
.join(', ')
|
||||
throw new Error(
|
||||
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
|
||||
)
|
||||
} else {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
throw new Error(
|
||||
`Mismatching "${dependencyGroup.name}" dependency versions found: ${formattedVersionsWithPackageNameString}. All "${dependencyGroup.name}" packages must have the same version. This is an error with your set-up, not a bug in Payload. Please go to your package.json and ensure all "${dependencyGroup.name}" packages have the same version.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencyVersions && Object.keys(dependencyVersions).length) {
|
||||
const resolvedDependencies = await getDependencies(dirname, Object.keys(dependencyVersions))
|
||||
for (const [dependency, settings] of Object.entries(dependencyVersions)) {
|
||||
const resolvedDependency = resolvedDependencies.resolved.get(dependency)
|
||||
if (!resolvedDependency) {
|
||||
if (!settings.required) {
|
||||
continue
|
||||
}
|
||||
throw new Error(`Dependency ${dependency} not found. Please ensure it is installed.`)
|
||||
}
|
||||
|
||||
if (settings.version) {
|
||||
const settingsVersionToCheck = settings.version.startsWith('>=')
|
||||
? settings.version.slice(2)
|
||||
: settings.version
|
||||
|
||||
const versionCompareResult = compareVersions(
|
||||
resolvedDependency.version,
|
||||
settingsVersionToCheck,
|
||||
settings.customVersionParser,
|
||||
)
|
||||
|
||||
if (settings.version.startsWith('>=')) {
|
||||
if (versionCompareResult === 'lower') {
|
||||
throw new Error(
|
||||
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} or greater is required. Please update this dependency.`,
|
||||
)
|
||||
}
|
||||
} else if (versionCompareResult === 'lower' || versionCompareResult === 'greater') {
|
||||
throw new Error(
|
||||
`Dependency ${dependency} is on version ${resolvedDependency.version}, but ${settings.version} is required. Please update this dependency.`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
packages/payload/src/utilities/dependencies/versionUtils.ts
Normal file
75
packages/payload/src/utilities/dependencies/versionUtils.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { CustomVersionParser } from './dependencyChecker.js'
|
||||
|
||||
export function parseVersion(version: string): { parts: number[]; preReleases: string[] } {
|
||||
const [mainVersion, ...preReleases] = version.split('-')
|
||||
const parts = mainVersion.split('.').map(Number)
|
||||
return { parts, preReleases }
|
||||
}
|
||||
|
||||
function extractNumbers(str: string): number[] {
|
||||
const matches = str.match(/\d+/g) || []
|
||||
return matches.map(Number)
|
||||
}
|
||||
|
||||
function comparePreRelease(v1: string, v2: string): number {
|
||||
const num1 = extractNumbers(v1)
|
||||
const num2 = extractNumbers(v2)
|
||||
|
||||
for (let i = 0; i < Math.max(num1.length, num2.length); i++) {
|
||||
if ((num1[i] || 0) < (num2[i] || 0)) return -1
|
||||
if ((num1[i] || 0) > (num2[i] || 0)) return 1
|
||||
}
|
||||
|
||||
// If numeric parts are equal, compare the whole string
|
||||
if (v1 < v2) return -1
|
||||
if (v1 > v2) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two semantic version strings, including handling pre-release identifiers.
|
||||
*
|
||||
* This function first compares the major, minor, and patch components as integers.
|
||||
* If these components are equal, it then moves on to compare pre-release versions.
|
||||
* Pre-release versions are compared first by extracting and comparing any numerical values.
|
||||
* If numerical values are equal, it compares the whole pre-release string lexicographically.
|
||||
*
|
||||
* @param {string} compare - The first version string to compare.
|
||||
* @param {string} to - The second version string to compare.
|
||||
* @param {function} [customVersionParser] - An optional function to parse version strings into parts and pre-releases.
|
||||
* @returns {string} - Returns greater if compare is greater than to, lower if compare is less than to, and equal if they are equal.
|
||||
*/
|
||||
export function compareVersions(
|
||||
compare: string,
|
||||
to: string,
|
||||
customVersionParser?: CustomVersionParser,
|
||||
): 'equal' | 'greater' | 'lower' {
|
||||
const { parts: parts1, preReleases: preReleases1 } = customVersionParser
|
||||
? customVersionParser(compare)
|
||||
: parseVersion(compare)
|
||||
const { parts: parts2, preReleases: preReleases2 } = customVersionParser
|
||||
? customVersionParser(to)
|
||||
: parseVersion(to)
|
||||
|
||||
// Compare main version parts
|
||||
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||
if ((parts1[i] || 0) > (parts2[i] || 0)) return 'greater'
|
||||
if ((parts1[i] || 0) < (parts2[i] || 0)) return 'lower'
|
||||
}
|
||||
|
||||
// Compare pre-release parts if main versions are equal
|
||||
if (preReleases1?.length || preReleases2?.length) {
|
||||
for (let i = 0; i < Math.max(preReleases1.length, preReleases2.length); i++) {
|
||||
if (!preReleases1[i]) return 'greater'
|
||||
if (!preReleases2[i]) return 'lower'
|
||||
|
||||
const result = comparePreRelease(preReleases1[i], preReleases2[i])
|
||||
if (result !== 0) {
|
||||
return result === 1 ? 'greater' : 'lower'
|
||||
}
|
||||
// Equal => continue for loop to check for next pre-release part
|
||||
}
|
||||
}
|
||||
|
||||
return 'equal'
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FormState } from 'payload'
|
||||
import type { FormState } from '../admin/types.js'
|
||||
|
||||
import { unflatten } from './unflatten.js'
|
||||
|
||||
|
||||
31
packages/payload/src/versions/payloadPackageList.ts
Normal file
31
packages/payload/src/versions/payloadPackageList.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const PAYLOAD_PACKAGE_LIST = [
|
||||
'payload',
|
||||
'@payloadcms/bundler-vite',
|
||||
'@payloadcms/bundler-webpack',
|
||||
'@payloadcms/db-mongodb',
|
||||
'@payloadcms/db-postgres',
|
||||
'@payloadcms/email-nodemailer',
|
||||
'@payloadcms/email-resend',
|
||||
'@payloadcms/graphql',
|
||||
'@payloadcms/live-preview-react',
|
||||
'@payloadcms/live-preview',
|
||||
'@payloadcms/next/utilities',
|
||||
'@payloadcms/plugin-cloud-storage',
|
||||
'@payloadcms/plugin-cloud',
|
||||
'@payloadcms/plugin-form-builder',
|
||||
'@payloadcms/plugin-nested-docs',
|
||||
'@payloadcms/plugin-redirects',
|
||||
'@payloadcms/plugin-search',
|
||||
'@payloadcms/plugin-seo',
|
||||
'@payloadcms/plugin-stripe',
|
||||
'@payloadcms/plugin-zapier',
|
||||
'@payloadcms/richtext-lexical',
|
||||
'@payloadcms/richtext-slate',
|
||||
'@payloadcms/storage-azure',
|
||||
'@payloadcms/storage-gcs',
|
||||
'@payloadcms/storage-s3',
|
||||
'@payloadcms/storage-uploadthing',
|
||||
'@payloadcms/storage-vercel-blob',
|
||||
'@payloadcms/translations',
|
||||
'@payloadcms/ui/shared',
|
||||
]
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-relationship-object-ids",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
@@ -56,18 +56,18 @@
|
||||
"lint:fix": "eslint --fix --ext .ts,.tsx src",
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.0",
|
||||
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
"react-dom": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { FieldType, Options, UploadFieldProps } from '@payloadcms/ui'
|
||||
import type { FieldType, Options } from '@payloadcms/ui'
|
||||
import type { UploadFieldProps } from 'payload'
|
||||
|
||||
import {
|
||||
FieldLabel,
|
||||
@@ -156,6 +157,7 @@ export const MetaImageComponent: React.FC<MetaImageProps> = (props) => {
|
||||
setValue(null)
|
||||
}
|
||||
}}
|
||||
path={field.path}
|
||||
relationTo={relationTo}
|
||||
required={required}
|
||||
serverURL={serverURL}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { Config, GroupField, TabsField, TextField } from 'payload'
|
||||
|
||||
import { addDataAndFileToRequest } from '@payloadcms/next/utilities'
|
||||
import { deepMergeSimple } from 'payload/shared'
|
||||
|
||||
import type {
|
||||
@@ -133,7 +132,11 @@ export const seoPlugin =
|
||||
...(config.endpoints ?? []),
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateTitle
|
||||
? await pluginConfig.generateTitle({
|
||||
@@ -148,7 +151,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateDescription
|
||||
? await pluginConfig.generateDescription({
|
||||
@@ -163,7 +170,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateURL
|
||||
? await pluginConfig.generateURL({
|
||||
@@ -178,7 +189,11 @@ export const seoPlugin =
|
||||
},
|
||||
{
|
||||
handler: async (req) => {
|
||||
await addDataAndFileToRequest(req)
|
||||
const data = await req.json()
|
||||
|
||||
if (data) {
|
||||
req.data = data
|
||||
}
|
||||
|
||||
const result = pluginConfig.generateImage
|
||||
? await pluginConfig.generateImage({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
@@ -54,6 +54,7 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"lodash.get": "^4.4.2",
|
||||
"stripe": "^10.2.0",
|
||||
@@ -62,8 +63,6 @@
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/express": "^4.17.9",
|
||||
"@types/lodash.get": "^4.4.7",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.0",
|
||||
@@ -72,8 +71,6 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -59,9 +59,12 @@
|
||||
"@lexical/rich-text": "0.17.0",
|
||||
"@lexical/selection": "0.17.0",
|
||||
"@lexical/utils": "0.17.0",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/uuid": "10.0.0",
|
||||
"bson-objectid": "2.0.4",
|
||||
"dequal": "2.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"lexical": "0.17.0",
|
||||
"react-error-boundary": "4.0.13",
|
||||
"uuid": "10.0.0"
|
||||
@@ -74,9 +77,7 @@
|
||||
"@babel/preset-typescript": "^7.24.1",
|
||||
"@lexical/eslint-plugin": "0.17.0",
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/escape-html": "1.0.4",
|
||||
"@types/json-schema": "7.0.15",
|
||||
"@types/node": "20.12.5",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.0",
|
||||
@@ -103,8 +104,6 @@
|
||||
"@lexical/table": "0.17.0",
|
||||
"@lexical/utils": "0.17.0",
|
||||
"@payloadcms/next": "workspace:*",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"lexical": "0.17.0",
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801",
|
||||
|
||||
@@ -6,8 +6,8 @@ import { QuoteNode } from '@lexical/rich-text'
|
||||
import { createServerFeature } from '../../../utilities/createServerFeature.js'
|
||||
import { convertLexicalNodesToHTML } from '../../converters/html/converter/index.js'
|
||||
import { createNode } from '../../typeUtilities.js'
|
||||
import { i18n } from './i18n.js'
|
||||
import { MarkdownTransformer } from '../markdownTransformer.js'
|
||||
import { i18n } from './i18n.js'
|
||||
|
||||
export type SerializedQuoteNode = Spread<
|
||||
{
|
||||
@@ -28,6 +28,8 @@ export const BlockquoteFeature = createServerFeature({
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -37,6 +39,8 @@ export const BlockquoteFeature = createServerFeature({
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
|
||||
@@ -5,9 +5,21 @@ import type { HTMLConverter } from '../types.js'
|
||||
import { convertLexicalNodesToHTML } from '../index.js'
|
||||
|
||||
export const ParagraphHTMLConverter: HTMLConverter<SerializedParagraphNode> = {
|
||||
async converter({ converters, draft, node, overrideAccess, parent, req, showHiddenFields }) {
|
||||
async converter({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import type { SerializedTextNode } from 'lexical'
|
||||
|
||||
import escapeHTML from 'escape-html'
|
||||
|
||||
import type { HTMLConverter } from '../types.js'
|
||||
|
||||
import { NodeFormat } from '../../../../../lexical/utils/nodeFormat.js'
|
||||
|
||||
export const TextHTMLConverter: HTMLConverter<SerializedTextNode> = {
|
||||
converter({ node }) {
|
||||
let text = node.text
|
||||
let text = escapeHTML(node.text)
|
||||
|
||||
if (node.format & NodeFormat.IS_BOLD) {
|
||||
text = `<strong>${text}</strong>`
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { HTMLConverter, SerializedLexicalNodeWithParent } from './types.js'
|
||||
|
||||
export type ConvertLexicalToHTMLArgs = {
|
||||
converters: HTMLConverter[]
|
||||
currentDepth?: number
|
||||
data: SerializedEditorState
|
||||
depth?: number
|
||||
draft?: boolean // default false
|
||||
overrideAccess?: boolean // default false
|
||||
showHiddenFields?: boolean // default false
|
||||
@@ -42,7 +44,9 @@ export type ConvertLexicalToHTMLArgs = {
|
||||
|
||||
export async function convertLexicalToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
data,
|
||||
depth,
|
||||
draft,
|
||||
overrideAccess,
|
||||
payload,
|
||||
@@ -54,8 +58,18 @@ export async function convertLexicalToHTML({
|
||||
req = await createLocalReq({}, payload)
|
||||
}
|
||||
|
||||
if (!currentDepth) {
|
||||
currentDepth = 0
|
||||
}
|
||||
|
||||
if (!depth) {
|
||||
depth = req?.payload?.config?.defaultDepth
|
||||
}
|
||||
|
||||
return await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft: draft === undefined ? false : draft,
|
||||
lexicalNodes: data?.root?.children,
|
||||
overrideAccess: overrideAccess === undefined ? false : overrideAccess,
|
||||
@@ -69,6 +83,8 @@ export async function convertLexicalToHTML({
|
||||
|
||||
export async function convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes,
|
||||
overrideAccess,
|
||||
@@ -77,6 +93,8 @@ export async function convertLexicalNodesToHTML({
|
||||
showHiddenFields,
|
||||
}: {
|
||||
converters: HTMLConverter[]
|
||||
currentDepth: number
|
||||
depth: number
|
||||
draft: boolean
|
||||
lexicalNodes: SerializedLexicalNode[]
|
||||
overrideAccess: boolean
|
||||
@@ -100,6 +118,8 @@ export async function convertLexicalNodesToHTML({
|
||||
return await unknownConverter.converter({
|
||||
childIndex: i,
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -113,6 +133,8 @@ export async function convertLexicalNodesToHTML({
|
||||
return await converterForNode.converter({
|
||||
childIndex: i,
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
|
||||
@@ -5,6 +5,8 @@ export type HTMLConverter<T extends SerializedLexicalNode = SerializedLexicalNod
|
||||
converter: (args: {
|
||||
childIndex: number
|
||||
converters: HTMLConverter<any>[]
|
||||
currentDepth: number
|
||||
depth: number
|
||||
draft: boolean
|
||||
node: T
|
||||
overrideAccess: boolean
|
||||
|
||||
@@ -162,6 +162,8 @@ export const lexicalHTML: (
|
||||
afterRead: [
|
||||
async ({
|
||||
collection,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
field,
|
||||
global,
|
||||
@@ -217,7 +219,9 @@ export const lexicalHTML: (
|
||||
|
||||
return await convertLexicalToHTML({
|
||||
converters: finalConverters,
|
||||
currentDepth,
|
||||
data: lexicalFieldData,
|
||||
depth,
|
||||
draft,
|
||||
overrideAccess,
|
||||
req,
|
||||
|
||||
@@ -73,6 +73,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -82,6 +84,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
@@ -104,6 +108,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -113,6 +119,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
@@ -144,6 +152,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -153,6 +163,8 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
|
||||
@@ -46,6 +46,8 @@ export const HeadingFeature = createServerFeature<
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -55,6 +57,8 @@ export const HeadingFeature = createServerFeature<
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
|
||||
@@ -7,7 +7,7 @@ import type {
|
||||
User,
|
||||
} from 'payload'
|
||||
|
||||
import { validateUrl } from '../../../lexical/utils/url.js'
|
||||
import { validateUrl, validateUrlMinimal } from '../../../lexical/utils/url.js'
|
||||
|
||||
export const getBaseFields = (
|
||||
config: SanitizedConfig,
|
||||
@@ -64,10 +64,20 @@ export const getBaseFields = (
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ value }) => {
|
||||
if (!validateUrl(value)) {
|
||||
return encodeURIComponent(value)
|
||||
}
|
||||
return value
|
||||
},
|
||||
],
|
||||
},
|
||||
label: ({ t }) => t('fields:enterURL'),
|
||||
required: true,
|
||||
validate: (value: string) => {
|
||||
if (!validateUrl(value)) {
|
||||
if (!validateUrlMinimal(value)) {
|
||||
return 'Invalid URL'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CollectionSlug, Config, Field, FieldAffectingData, SanitizedConfig } from 'payload'
|
||||
|
||||
import escapeHTML from 'escape-html'
|
||||
import { sanitizeFields } from 'payload'
|
||||
import { deepCopyObject } from 'payload/shared'
|
||||
|
||||
@@ -116,6 +117,8 @@ export const LinkFeature = createServerFeature<
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -125,6 +128,8 @@ export const LinkFeature = createServerFeature<
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
@@ -161,6 +166,8 @@ export const LinkFeature = createServerFeature<
|
||||
html: {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
@@ -170,6 +177,8 @@ export const LinkFeature = createServerFeature<
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
@@ -186,7 +195,7 @@ export const LinkFeature = createServerFeature<
|
||||
|
||||
const href: string =
|
||||
node.fields.linkType === 'custom'
|
||||
? node.fields.url
|
||||
? escapeHTML(node.fields.url)
|
||||
: (node.fields.doc?.value as string)
|
||||
|
||||
return `<a href="${href}"${target}${rel}>${childrenText}</a>`
|
||||
|
||||
@@ -7,9 +7,21 @@ import type { SerializedListItemNode, SerializedListNode } from './plugin/index.
|
||||
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
|
||||
|
||||
export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
converter: async ({ converters, draft, node, overrideAccess, parent, req, showHiddenFields }) => {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
@@ -27,11 +39,23 @@ export const ListHTMLConverter: HTMLConverter<SerializedListNode> = {
|
||||
}
|
||||
|
||||
export const ListItemHTMLConverter: HTMLConverter<SerializedListItemNode> = {
|
||||
converter: async ({ converters, draft, node, overrideAccess, parent, req, showHiddenFields }) => {
|
||||
converter: async ({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
parent,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
const hasSubLists = node.children.some((child) => child.type === 'list')
|
||||
|
||||
const childrenText = await convertLexicalNodesToHTML({
|
||||
converters,
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
lexicalNodes: node.children,
|
||||
overrideAccess,
|
||||
|
||||
@@ -41,62 +41,67 @@ export type RelationshipFeatureProps = {
|
||||
|
||||
export const RelationshipFeature = createServerFeature<
|
||||
RelationshipFeatureProps,
|
||||
RelationshipFeatureProps
|
||||
RelationshipFeatureProps,
|
||||
ExclusiveRelationshipFeatureProps
|
||||
>({
|
||||
feature: ({ props }) => ({
|
||||
ClientFeature: '@payloadcms/richtext-lexical/client#RelationshipFeatureClient',
|
||||
i18n,
|
||||
nodes: [
|
||||
createNode({
|
||||
graphQLPopulationPromises: [relationshipPopulationPromiseHOC(props)],
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
if (!node?.value) {
|
||||
feature: ({ props }) => {
|
||||
// we don't need to pass maxDepth to the client, it's only used on the server
|
||||
const { maxDepth, ...clientFeatureProps } = props ?? {}
|
||||
return {
|
||||
ClientFeature: '@payloadcms/richtext-lexical/client#RelationshipFeatureClient',
|
||||
clientFeatureProps,
|
||||
i18n,
|
||||
nodes: [
|
||||
createNode({
|
||||
graphQLPopulationPromises: [relationshipPopulationPromiseHOC(props)],
|
||||
hooks: {
|
||||
afterRead: [
|
||||
({
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
populationPromises,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
if (!node?.value) {
|
||||
return node
|
||||
}
|
||||
const collection = req.payload.collections[node?.relationTo]
|
||||
|
||||
if (!collection) {
|
||||
return node
|
||||
}
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
const populateDepth = maxDepth !== undefined && maxDepth < depth ? maxDepth : depth
|
||||
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
draft,
|
||||
key: 'value',
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}),
|
||||
)
|
||||
|
||||
return node
|
||||
}
|
||||
const collection = req.payload.collections[node?.relationTo]
|
||||
|
||||
if (!collection) {
|
||||
return node
|
||||
}
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
const populateDepth =
|
||||
props?.maxDepth !== undefined && props?.maxDepth < depth ? props?.maxDepth : depth
|
||||
|
||||
populationPromises.push(
|
||||
populate({
|
||||
id,
|
||||
collectionSlug: collection.config.slug,
|
||||
currentDepth,
|
||||
data: node,
|
||||
depth: populateDepth,
|
||||
draft,
|
||||
key: 'value',
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}),
|
||||
)
|
||||
|
||||
return node
|
||||
},
|
||||
],
|
||||
},
|
||||
node: RelationshipServerNode,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
node: RelationshipServerNode,
|
||||
}),
|
||||
],
|
||||
}
|
||||
},
|
||||
key: 'relationship',
|
||||
})
|
||||
|
||||
@@ -97,7 +97,15 @@ export const UploadFeature = createServerFeature<
|
||||
createNode({
|
||||
converters: {
|
||||
html: {
|
||||
converter: async ({ draft, node, overrideAccess, req, showHiddenFields }) => {
|
||||
converter: async ({
|
||||
currentDepth,
|
||||
depth,
|
||||
draft,
|
||||
node,
|
||||
overrideAccess,
|
||||
req,
|
||||
showHiddenFields,
|
||||
}) => {
|
||||
// @ts-expect-error
|
||||
const id = node?.value?.id || node?.value // for backwards-compatibility
|
||||
|
||||
@@ -110,9 +118,9 @@ export const UploadFeature = createServerFeature<
|
||||
await populate({
|
||||
id,
|
||||
collectionSlug: node.relationTo,
|
||||
currentDepth: 0,
|
||||
currentDepth,
|
||||
data: uploadDocument,
|
||||
depth: 1,
|
||||
depth,
|
||||
draft,
|
||||
key: 'value',
|
||||
overrideAccess,
|
||||
|
||||
@@ -5,16 +5,14 @@ import type {
|
||||
SerializedLexicalNode,
|
||||
} from 'lexical'
|
||||
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
import {
|
||||
afterChangeTraverseFields,
|
||||
afterReadTraverseFields,
|
||||
beforeChangeTraverseFields,
|
||||
beforeValidateTraverseFields,
|
||||
checkDependencies,
|
||||
deepCopyObject,
|
||||
deepCopyObjectSimple,
|
||||
getDependencies,
|
||||
withNullableJSONSchemaType,
|
||||
} from 'payload'
|
||||
|
||||
@@ -42,46 +40,32 @@ import { richTextValidateHOC } from './validate/index.js'
|
||||
|
||||
let defaultSanitizedServerEditorConfig: SanitizedServerEditorConfig = null
|
||||
|
||||
const filename = fileURLToPath(import.meta.url)
|
||||
const dirname = path.dirname(filename)
|
||||
|
||||
export function lexicalEditor(props?: LexicalEditorProps): LexicalRichTextAdapterProvider {
|
||||
return async ({ config, isRoot }) => {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
process.env.PAYLOAD_DISABLE_DEPENDENCY_CHECKER !== 'true'
|
||||
) {
|
||||
const resolvedDependencies = await getDependencies(dirname, [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/markdown',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
])
|
||||
|
||||
// Go through each resolved dependency. If any dependency has a mismatching version, throw an error
|
||||
const foundVersions: {
|
||||
[version: string]: string
|
||||
} = {}
|
||||
for (const [_pkg, { version }] of resolvedDependencies.resolved) {
|
||||
if (!Object.keys(foundVersions).includes(version)) {
|
||||
foundVersions[version] = _pkg
|
||||
}
|
||||
}
|
||||
if (Object.keys(foundVersions).length > 1) {
|
||||
const formattedVersionsWithPackageNameString = Object.entries(foundVersions)
|
||||
.map(([version, pkg]) => `${pkg}@${version}`)
|
||||
.join(', ')
|
||||
|
||||
throw new Error(
|
||||
`Mismatching lexical dependency versions found: ${formattedVersionsWithPackageNameString}. All lexical and @lexical/* packages must have the same version. This is an error with your set-up, caused by you, not a bug in payload. Please go to your package.json and ensure all lexical and @lexical/* packages have the same version.`,
|
||||
)
|
||||
}
|
||||
await checkDependencies({
|
||||
dependencyGroups: [
|
||||
{
|
||||
name: 'lexical',
|
||||
dependencies: [
|
||||
'lexical',
|
||||
'@lexical/headless',
|
||||
'@lexical/link',
|
||||
'@lexical/list',
|
||||
'@lexical/mark',
|
||||
'@lexical/markdown',
|
||||
'@lexical/react',
|
||||
'@lexical/rich-text',
|
||||
'@lexical/selection',
|
||||
'@lexical/utils',
|
||||
],
|
||||
targetVersion: '0.17.0',
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
let features: FeatureProviderServer<any, any, any>[] = []
|
||||
@@ -991,8 +975,9 @@ export type * from './nodeTypes.js'
|
||||
|
||||
export { defaultRichTextValue } from './populateGraphQL/defaultValue.js'
|
||||
|
||||
export { populate } from './populateGraphQL/populate.js'
|
||||
export type { LexicalEditorProps, LexicalRichTextAdapter } from './types.js'
|
||||
export { createServerFeature } from './utilities/createServerFeature.js'
|
||||
export type { FieldsDrawerProps } from './utilities/fieldsDrawer/Drawer.js'
|
||||
|
||||
export type { FieldsDrawerProps } from './utilities/fieldsDrawer/Drawer.js'
|
||||
export { upgradeLexicalData } from './utilities/upgradeLexicalData/index.js'
|
||||
|
||||
@@ -25,6 +25,16 @@ const absoluteRegExp =
|
||||
* */
|
||||
const relativeOrAnchorRegExp = /^[\w\-./]*(?:#\w[\w-]*)?$/
|
||||
|
||||
/**
|
||||
* Prevents unreasonable URLs from being inserted into the editor.
|
||||
* @param url
|
||||
*/
|
||||
export function validateUrlMinimal(url: string): boolean {
|
||||
if (!url) return false
|
||||
|
||||
return !url.includes(' ')
|
||||
}
|
||||
|
||||
// Do not keep validateUrl function too loose. This is run when pasting in text, to determine if links are in that text and if it should create AutoLinkNodes.
|
||||
// This is why we do not allow stuff like anchors here, as we don't want copied anchors to be turned into AutoLinkNodes.
|
||||
export function validateUrl(url: string): boolean {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -38,6 +38,8 @@
|
||||
"prepublishOnly": "pnpm clean && pnpm turbo build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"is-hotkey": "0.2.0",
|
||||
"slate": "0.91.4",
|
||||
"slate-history": "0.86.0",
|
||||
@@ -46,7 +48,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@payloadcms/eslint-config": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/is-hotkey": "^0.1.10",
|
||||
"@types/node": "20.12.5",
|
||||
"@types/react": "npm:types-react@19.0.0-rc.0",
|
||||
@@ -54,8 +55,6 @@
|
||||
"payload": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"payload": "workspace:*",
|
||||
"react": "^19.0.0 || ^19.0.0-rc-06d0b89e-20240801"
|
||||
},
|
||||
|
||||
@@ -63,3 +63,4 @@ export { UnderlineLeafButton } from '../../field/leaves/underline/LeafButton.js'
|
||||
export { UnderlineLeaf } from '../../field/leaves/underline/Underline/index.js'
|
||||
|
||||
export { useLeaf } from '../../field/providers/LeafProvider.js'
|
||||
export { useSlatePlugin } from '../../utilities/useSlatePlugin.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.0.0-beta.91",
|
||||
"version": "3.0.0-beta.95",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -7,9 +7,22 @@
|
||||
height: 100%;
|
||||
padding: calc(var(--base) * 2) var(--gutter-h);
|
||||
}
|
||||
|
||||
|
||||
.dropzone {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
gap: var(--base);
|
||||
background-color: var(--theme-elevation-50);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__dragAndDropText {
|
||||
margin: 0;
|
||||
text-transform: lowercase;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { Button } from '../../Button/index.js'
|
||||
import { Dropzone } from '../../Dropzone/index.js'
|
||||
import { DrawerHeader } from '../Header/index.js'
|
||||
import './index.scss'
|
||||
@@ -16,11 +17,43 @@ type Props = {
|
||||
export function AddFilesView({ onCancel, onDrop }: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const inputRef = React.useRef(null)
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<DrawerHeader onClose={onCancel} title={t('upload:addFiles')} />
|
||||
<div className={`${baseClass}__dropArea`}>
|
||||
<Dropzone multipleFiles onChange={onDrop} />
|
||||
<Dropzone multipleFiles onChange={onDrop}>
|
||||
<Button
|
||||
buttonStyle="pill"
|
||||
iconPosition="left"
|
||||
onClick={() => {
|
||||
if (inputRef.current) {
|
||||
inputRef.current.click()
|
||||
}
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
{t('upload:selectFile')}
|
||||
</Button>
|
||||
<input
|
||||
aria-hidden="true"
|
||||
className={`${baseClass}__hidden-input`}
|
||||
hidden
|
||||
onChange={(e) => {
|
||||
if (e.target.files && e.target.files.length > 0) {
|
||||
onDrop(e.target.files)
|
||||
}
|
||||
}}
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
/>
|
||||
|
||||
<p className={`${baseClass}__dragAndDropText`}>
|
||||
{t('general:or')} {t('upload:dragAndDrop')}
|
||||
</p>
|
||||
</Dropzone>
|
||||
{/* <Dropzone multipleFiles onChange={onDrop} /> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useConfig } from '../../../providers/Config/index.js'
|
||||
import { DocumentInfoProvider } from '../../../providers/DocumentInfo/index.js'
|
||||
import { useTranslation } from '../../../providers/Translation/index.js'
|
||||
import { ActionsBar } from '../ActionsBar/index.js'
|
||||
import { discardBulkUploadModalSlug } from '../DiscardWithoutSaving/index.js'
|
||||
import { DiscardWithoutSaving, discardBulkUploadModalSlug } from '../DiscardWithoutSaving/index.js'
|
||||
import { EditForm } from '../EditForm/index.js'
|
||||
import { FileSidebar } from '../FileSidebar/index.js'
|
||||
import { useFormsManager } from '../FormsManager/index.js'
|
||||
@@ -44,20 +44,24 @@ export function AddingFilesView() {
|
||||
onClose={() => openModal(discardBulkUploadModalSlug)}
|
||||
title={getTranslation(collection.labels.singular, i18n)}
|
||||
/>
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={collectionSlug}
|
||||
docPermissions={docPermissions}
|
||||
hasPublishPermission={hasPublishPermission}
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={null}
|
||||
initialData={reduceFieldsToValues(activeForm.formState, true)}
|
||||
initialState={activeForm.formState}
|
||||
key={`${activeIndex}-${forms.length}`}
|
||||
>
|
||||
<ActionsBar />
|
||||
<EditForm submitted={hasSubmitted} />
|
||||
</DocumentInfoProvider>
|
||||
{activeForm ? (
|
||||
<DocumentInfoProvider
|
||||
collectionSlug={collectionSlug}
|
||||
docPermissions={docPermissions}
|
||||
hasPublishPermission={hasPublishPermission}
|
||||
hasSavePermission={hasSavePermission}
|
||||
id={null}
|
||||
initialData={reduceFieldsToValues(activeForm.formState, true)}
|
||||
initialState={activeForm.formState}
|
||||
key={`${activeIndex}-${forms.length}`}
|
||||
>
|
||||
<ActionsBar />
|
||||
<EditForm submitted={hasSubmitted} />
|
||||
</DocumentInfoProvider>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<DiscardWithoutSaving />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user