Compare commits

..

12 Commits

Author SHA1 Message Date
Elliot DeNolf
0b9397399a chore(release): v3.0.0-beta.74 [skip ci] 2024-08-06 09:38:20 -04:00
Jarrod Flesch
cdcc35ccdb chore: fixes build error stemming from LoginField (#7532) 2024-08-06 09:15:21 -04:00
Jarrod Flesch
442189ec48 fix: email and username fields rendering in drawers (#7520)
Fixes https://github.com/payloadcms/payload/issues/7428

Now email and username fields are rendered with the RenderFields
component, making them behave similarly to other fields. They now appear
and can respect doc permissions, readOnly settings, etc.
2024-08-05 20:18:32 -04:00
Alessio Gravili
5d1cc760c9 fix(richtext-lexical): various table and icon style issues (#7522) 2024-08-05 22:10:18 +00:00
Alessio Gravili
2f90683c7d Merge PR: upgrade lexical, add table feature converter (#7521)
This PR
- upgrades lexical and ports all bug fixes from the playground over
- adds table action buttons. When hovering the edges of the table,
buttons pop up to easily add a new table column or row
- adds an html converter for the table feature
- makes the placeholder shown in the editor when no text is present
accessible

**BREAKING:** This upgrades lexical from 0.16.1 to 0.17.0. If you have
any lexical packages installed in your project, please update them
accordingly. Additionally, if you depend on the lexical APIs, please
consult their changelog, as lexical may introduce breaking changes:
https://github.com/facebook/lexical/releases/tag/v0.17.0
2024-08-05 17:18:57 -04:00
Patrik
3f5403a52a fix(ui): prevents hasMany text going outside of input boundaries (#7455)
## Description

V2 PR [here](https://github.com/payloadcms/payload/pull/7454)

`Before`:
![Screenshot 2024-07-31 at 12 40
50 PM](https://github.com/user-attachments/assets/ce61f4fc-e676-4273-aa4c-72610cb459b3)

`After`:
![Screenshot 2024-07-31 at 12 40
23 PM](https://github.com/user-attachments/assets/d92631eb-28fb-46ca-bc23-46c7916bba34)

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-08-05 17:10:35 -04:00
Alessio Gravili
9bccdfd60a feat(richtext-lexical): add HTML converter to table feature 2024-08-05 17:01:21 -04:00
Patrik
62666a9897 fix(ui): properly handles ID field component type based on payload.db.defaultIDType (#7416)
## Description

Fixes #7354 

Since the `defaultIDType` for IDs in `postgres` are of type `number` -
the `contains` operator should be available in the filter options.

This PR checks the `defaultIDType` of ID and properly outputs the
correct component type for IDs

I.e if ID is of type `number` - the filter operators for ID should
correspond to the the operators of type number as well

The `contains` operator only belongs on fields of type string, aka of
component type `text`

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

## Type of change

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

## Checklist:

- [x] Existing test suite passes locally with my changes
2024-08-05 16:39:27 -04:00
Alessio Gravili
eb27b84854 chore(richtext-lexical): backport various minor bugfixes from lexical playground 2024-08-05 16:35:13 -04:00
Alessio Gravili
c3480811d3 feat(richtext-lexical): accessible editor placeholders 2024-08-05 16:19:02 -04:00
Alessio Gravili
12ba820de4 feat(richtext-lexical): add table hover actions to quickly add rows or columns 2024-08-05 16:08:31 -04:00
Alessio Gravili
e65b6478c9 feat(richtext-lexical)!: upgrade lexical from 0.16.1 to 0.17.0 2024-08-05 09:58:27 -04:00
92 changed files with 1076 additions and 1875 deletions

7
.vscode/launch.json vendored
View File

@@ -56,6 +56,13 @@
"request": "launch",
"type": "node-terminal"
},
{
"command": "node --no-deprecation test/dev.js login-with-username",
"cwd": "${workspaceFolder}",
"name": "Run Dev Login-With-Username",
"request": "launch",
"type": "node-terminal"
},
{
"command": "pnpm run dev plugin-cloud-storage",
"cwd": "${workspaceFolder}",

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"private": true,
"type": "module",
"scripts": {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
import type { AnySQLiteColumn } from 'drizzle-orm/sqlite-core'
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/drizzle",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "A library of shared functions used by different payload database adapters",
"homepage": "https://payloadcms.com",
"repository": {

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,45 +1,103 @@
'use client'
import type { LoginWithUsernameOptions } from 'payload'
import type { FieldPermissions, LoginWithUsernameOptions } from 'payload'
import { EmailField, TextField, useTranslation } from '@payloadcms/ui'
import { EmailField, RenderFields, TextField, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
type Props = {
loginWithUsername?: LoginWithUsernameOptions | false
}
export const EmailAndUsernameFields: React.FC<Props> = ({ loginWithUsername }) => {
function EmailFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireEmail = !loginWithUsername || (loginWithUsername && loginWithUsername.requireEmail)
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showEmailField =
!loginWithUsername || loginWithUsername?.requireEmail || loginWithUsername?.allowEmailLogin
if (showEmailField) {
return (
<EmailField
autoComplete="off"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)
}
return null
}
function UsernameFieldComponent(props: Props) {
const { loginWithUsername } = props
const { t } = useTranslation()
const requireUsername = loginWithUsername && loginWithUsername.requireUsername
const showUsernameField = Boolean(loginWithUsername)
return (
<React.Fragment>
{showEmailField && (
<EmailField
autoComplete="email"
label={t('general:email')}
name="email"
path="email"
required={requireEmail}
validate={email}
/>
)}
if (showUsernameField) {
return (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)
}
{showUsernameField && (
<TextField
label={t('authentication:username')}
name="username"
path="username"
required={requireUsername}
validate={username}
/>
)}
</React.Fragment>
return null
}
type RenderEmailAndUsernameFieldsProps = {
className?: string
loginWithUsername?: LoginWithUsernameOptions | false
operation?: 'create' | 'update'
permissions?: {
[fieldName: string]: FieldPermissions
}
readOnly: boolean
}
export function RenderEmailAndUsernameFields(props: RenderEmailAndUsernameFieldsProps) {
const { className, loginWithUsername, operation, permissions, readOnly } = props
return (
<RenderFields
className={className}
fieldMap={[
{
name: 'email',
type: 'text',
CustomField: <EmailFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'email', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
{
name: 'username',
type: 'text',
CustomField: <UsernameFieldComponent loginWithUsername={loginWithUsername} />,
cellComponentProps: null,
fieldComponentProps: { type: 'text', readOnly },
fieldIsPresentational: false,
isFieldAffectingData: true,
localized: false,
},
]}
forceRender
operation={operation}
path=""
permissions={permissions}
readOnly={readOnly}
schemaPath=""
/>
)
}

View File

@@ -15,7 +15,7 @@ import {
import { getFormState } from '@payloadcms/ui/shared'
import React from 'react'
import { EmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
import { RenderEmailAndUsernameFields } from '../../elements/EmailAndUsername/index.js'
export const CreateFirstUserClient: React.FC<{
initialState: FormState
@@ -57,7 +57,12 @@ export const CreateFirstUserClient: React.FC<{
redirect={admin}
validationOperation="create"
>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
<RenderEmailAndUsernameFields
className="emailAndUsername"
loginWithUsername={loginWithUsername}
operation="create"
readOnly={false}
/>
<PasswordField
label={t('authentication:newPassword')}
name="password"

View File

@@ -3,3 +3,7 @@
margin-bottom: var(--base);
}
}
.emailAndUsername {
margin-bottom: var(--base);
}

View File

@@ -17,7 +17,7 @@ import { toast } from 'sonner'
import type { Props } from './types.js'
import { EmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
import { RenderEmailAndUsernameFields } from '../../../../elements/EmailAndUsername/index.js'
import { APIKey } from './APIKey.js'
import './index.scss'
@@ -47,7 +47,7 @@ export const Auth: React.FC<Props> = (props) => {
const dispatchFields = useFormFields((reducer) => reducer[1])
const modified = useFormModified()
const { i18n, t } = useTranslation()
const { isInitializing } = useDocumentInfo()
const { docPermissions, isInitializing } = useDocumentInfo()
const {
routes: { api },
@@ -138,7 +138,12 @@ export const Auth: React.FC<Props> = (props) => {
<div className={[baseClass, className].filter(Boolean).join(' ')}>
{!disableLocalStrategy && (
<React.Fragment>
<EmailAndUsernameFields loginWithUsername={loginWithUsername} />
<RenderEmailAndUsernameFields
loginWithUsername={loginWithUsername}
operation={operation}
permissions={docPermissions?.fields}
readOnly={readOnly}
/>
{(showPasswordFields || requirePassword) && (
<div className={`${baseClass}__changing-password`}>
<PasswordField

View File

@@ -45,13 +45,10 @@ export const LoginField: React.FC<LoginFieldProps> = ({ type, required = true })
path="username"
required={required}
validate={(value, options) => {
const passesUsername = username(
value,
options as ValidateOptions<any, { email?: string }, any, string>,
)
const passesUsername = username(value, options)
const passesEmail = email(
value,
options as ValidateOptions<any, { username?: string }, any, string>,
options as ValidateOptions<any, { username?: string }, any, any>,
)
if (!passesEmail && !passesUsername) {

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -6,7 +6,7 @@ import type { SanitizedCollectionConfig, TypeWithID } from '../collections/confi
import type { SanitizedConfig } from '../config/types.js'
import type { Field, FieldAffectingData, RichTextField, Validate } from '../fields/config/types.js'
import type { SanitizedGlobalConfig } from '../globals/config/types.js'
import type { JsonObject, PayloadRequest, RequestContext } from '../types/index.js'
import type { JsonObject, Payload, PayloadRequest, RequestContext } from '../types/index.js'
import type { WithServerSidePropsComponentProps } from './elements/WithServerSideProps.js'
export type RichTextFieldProps<Value extends object, AdapterProps, ExtraFieldProperties = {}> = {
@@ -189,6 +189,7 @@ type RichTextAdapterBase<
WithServerSideProps: React.FC<Omit<WithServerSidePropsComponentProps, 'serverOnlyProps'>>
config: SanitizedConfig
i18n: I18nClient
payload: Payload
schemaPath: string
}) => Map<string, React.ReactNode>
generateSchemaMap?: (args: {

View File

@@ -15,7 +15,7 @@ export type ArrayFieldProps = {
name?: string
validate?: ArrayFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type ArrayFieldLabelComponent = LabelComponent<'array'>

View File

@@ -15,7 +15,7 @@ export type BlocksFieldProps = {
slug?: string
validate?: BlockFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type ReducedBlock = {
LabelComponent: Block['admin']['components']['Label']

View File

@@ -12,7 +12,7 @@ export type CheckboxFieldProps = {
path?: string
validate?: CheckboxFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type CheckboxFieldLabelComponent = LabelComponent<'checkbox'>

View File

@@ -10,7 +10,7 @@ export type CodeFieldProps = {
path?: string
validate?: CodeFieldValidation
width: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type CodeFieldLabelComponent = LabelComponent<'code'>

View File

@@ -10,7 +10,7 @@ export type DateFieldProps = {
placeholder?: DateField['admin']['placeholder'] | string
validate?: DateFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type DateFieldLabelComponent = LabelComponent<'date'>

View File

@@ -10,7 +10,7 @@ export type EmailFieldProps = {
placeholder?: EmailField['admin']['placeholder']
validate?: EmailFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type EmailFieldLabelComponent = LabelComponent<'email'>

View File

@@ -10,7 +10,7 @@ export type JSONFieldProps = {
path?: string
validate?: JSONFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type JSONFieldLabelComponent = LabelComponent<'json'>

View File

@@ -15,7 +15,7 @@ export type NumberFieldProps = {
step?: number
validate?: NumberFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type NumberFieldLabelComponent = LabelComponent<'number'>

View File

@@ -9,7 +9,7 @@ export type PointFieldProps = {
step?: number
validate?: PointFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type PointFieldLabelComponent = LabelComponent<'point'>

View File

@@ -12,7 +12,7 @@ export type RadioFieldProps = {
validate?: RadioFieldValidation
value?: string
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type OnChange<T = string> = (value: T) => void

View File

@@ -12,7 +12,7 @@ export type RelationshipFieldProps = {
sortOptions?: RelationshipField['admin']['sortOptions']
validate?: RelationshipFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type RelationshipFieldLabelComponent = LabelComponent<'relationship'>

View File

@@ -8,7 +8,7 @@ export type RichTextComponentProps = {
richTextComponentMap?: Map<string, MappedField[] | React.ReactNode>
validate?: RichTextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type RichTextFieldLabelComponent = LabelComponent<'richText'>

View File

@@ -14,7 +14,7 @@ export type SelectFieldProps = {
validate?: SelectFieldValidation
value?: string
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type SelectFieldLabelComponent = LabelComponent<'select'>

View File

@@ -16,7 +16,7 @@ export type TextFieldProps = {
placeholder?: TextField['admin']['placeholder']
validate?: TextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type TextFieldLabelComponent = LabelComponent<'text'>

View File

@@ -12,7 +12,7 @@ export type TextareaFieldProps = {
rows?: number
validate?: TextareaFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type TextareaFieldLabelComponent = LabelComponent<'textarea'>

View File

@@ -15,7 +15,7 @@ export type UploadFieldProps = {
relationTo?: UploadField['relationTo']
validate?: UploadFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
export type UploadFieldLabelComponent = LabelComponent<'upload'>

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,13 +1,15 @@
'use client'
import type { TextFieldProps } from 'payload'
import type { SelectFieldValidation, TextFieldProps } from 'payload'
import { SelectField, useForm } from '@payloadcms/ui'
import React, { useEffect, useState } from 'react'
import type { SelectFieldOption } from '../../types.js'
export const DynamicFieldSelector: React.FC<TextFieldProps> = (props) => {
export const DynamicFieldSelector: React.FC<
{ validate: SelectFieldValidation } & TextFieldProps
> = (props) => {
const { fields, getDataByPath } = useForm()
const [options, setOptions] = useState<SelectFieldOption[]>([])

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-relationship-object-ids",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {
@@ -41,24 +41,24 @@
"translateNewKeys": "tsx scripts/translateNewKeys.ts"
},
"dependencies": {
"@lexical/headless": "0.16.1",
"@lexical/link": "0.16.1",
"@lexical/list": "0.16.1",
"@lexical/mark": "0.16.1",
"@lexical/markdown": "0.16.1",
"@lexical/react": "0.16.1",
"@lexical/rich-text": "0.16.1",
"@lexical/selection": "0.16.1",
"@lexical/utils": "0.16.1",
"@lexical/headless": "0.17.0",
"@lexical/link": "0.17.0",
"@lexical/list": "0.17.0",
"@lexical/mark": "0.17.0",
"@lexical/markdown": "0.17.0",
"@lexical/react": "0.17.0",
"@lexical/rich-text": "0.17.0",
"@lexical/selection": "0.17.0",
"@lexical/utils": "0.17.0",
"@types/uuid": "10.0.0",
"bson-objectid": "2.0.4",
"dequal": "2.0.3",
"lexical": "0.16.1",
"lexical": "0.17.0",
"react-error-boundary": "4.0.13",
"uuid": "10.0.0"
},
"devDependencies": {
"@lexical/eslint-plugin": " 0.16.1",
"@lexical/eslint-plugin": "0.17.0",
"@payloadcms/eslint-config": "workspace:*",
"@payloadcms/next": "workspace:*",
"@payloadcms/translations": "workspace:*",
@@ -75,20 +75,20 @@
"peerDependencies": {
"@faceless-ui/modal": "3.0.0-beta.2",
"@faceless-ui/scroll-info": "2.0.0-beta.0",
"@lexical/headless": "0.16.1",
"@lexical/link": "0.16.1",
"@lexical/list": "0.16.1",
"@lexical/mark": "0.16.1",
"@lexical/markdown": "0.16.1",
"@lexical/react": "0.16.1",
"@lexical/rich-text": "0.16.1",
"@lexical/selection": "0.16.1",
"@lexical/table": "0.16.1",
"@lexical/utils": "0.16.1",
"@lexical/headless": "0.17.0",
"@lexical/link": "0.17.0",
"@lexical/list": "0.17.0",
"@lexical/mark": "0.17.0",
"@lexical/markdown": "0.17.0",
"@lexical/react": "0.17.0",
"@lexical/rich-text": "0.17.0",
"@lexical/selection": "0.17.0",
"@lexical/table": "0.17.0",
"@lexical/utils": "0.17.0",
"@payloadcms/next": "workspace:*",
"@payloadcms/translations": "workspace:*",
"@payloadcms/ui": "workspace:*",
"lexical": "0.16.1",
"lexical": "0.17.0",
"payload": "workspace:*",
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",
"react-dom": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610"

View File

@@ -8,6 +8,7 @@ import { slashMenuBasicGroupWithItems } from '../shared/slashMenu/basicGroup.js'
import { toolbarAddDropdownGroupWithItems } from '../shared/toolbar/addDropdownGroup.js'
import { TableActionMenuPlugin } from './plugins/TableActionMenuPlugin/index.js'
import { TableCellResizerPlugin } from './plugins/TableCellResizerPlugin/index.js'
import { TableHoverActionsPlugin } from './plugins/TableHoverActionsPlugin/index.js'
import {
OPEN_TABLE_DRAWER_COMMAND,
TableContext,
@@ -29,6 +30,10 @@ export const TableFeatureClient = createClientFeature({
Component: TableActionMenuPlugin,
position: 'floatingAnchorElem',
},
{
Component: TableHoverActionsPlugin,
position: 'floatingAnchorElem',
},
],
providers: [TableContext],
slashMenu: {

View File

@@ -6,6 +6,8 @@ import { sanitizeFields } from 'payload'
// eslint-disable-next-line payload/no-imports-from-exports-dir
import { TableFeatureClient } from '../../exports/client/index.js'
import { createServerFeature } from '../../utilities/createServerFeature.js'
import { convertLexicalNodesToHTML } from '../converters/html/converter/index.js'
import { createNode } from '../typeUtilities.js'
const fields: Field[] = [
{
@@ -41,15 +43,75 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
return schemaMap
},
nodes: [
{
createNode({
converters: {
html: {
converter: async ({ converters, node, parent, req }) => {
const childrenText = await convertLexicalNodesToHTML({
converters,
lexicalNodes: node.children,
parent: {
...node,
parent,
},
req,
})
return `<table class="lexical-table" style="border-collapse: collapse;">${childrenText}</table>`
},
nodeTypes: [TableNode.getType()],
},
},
node: TableNode,
},
{
}),
createNode({
converters: {
html: {
converter: async ({ converters, node, parent, req }) => {
const childrenText = await convertLexicalNodesToHTML({
converters,
lexicalNodes: node.children,
parent: {
...node,
parent,
},
req,
})
const tagName = node.headerState > 0 ? 'th' : 'td'
const headerStateClass = `lexical-table-cell-header-${node.headerState}`
const backgroundColor = node.backgroundColor
? `background-color: ${node.backgroundColor};`
: ''
const colSpan = node.colSpan > 1 ? `colspan="${node.colSpan}"` : ''
const rowSpan = node.rowSpan > 1 ? `rowspan="${node.rowSpan}"` : ''
return `<${tagName} class="lexical-table-cell ${headerStateClass}" style="border: 1px solid #ccc; padding: 8px; ${backgroundColor}" ${colSpan} ${rowSpan}>${childrenText}</${tagName}>`
},
nodeTypes: [TableCellNode.getType()],
},
},
node: TableCellNode,
},
{
}),
createNode({
converters: {
html: {
converter: async ({ converters, node, parent, req }) => {
const childrenText = await convertLexicalNodesToHTML({
converters,
lexicalNodes: node.children,
parent: {
...node,
parent,
},
req,
})
return `<tr class="lexical-table-row">${childrenText}</tr>`
},
nodeTypes: [TableRowNode.getType()],
},
},
node: TableRowNode,
},
}),
],
}
},

View File

@@ -9,9 +9,6 @@
.table-cell-action-button {
background-color: var(--theme-elevation-200);
display: flex;
justify-content: center;
align-items: center;
border: 0;
padding: 2px;
position: relative;

View File

@@ -160,15 +160,19 @@ function TableActionMenu({
const { y } = useScrollInfo()
useEffect(() => {
return editor.registerMutationListener(TableCellNode, (nodeMutations) => {
const nodeUpdated = nodeMutations.get(tableCellNode.getKey()) === 'updated'
return editor.registerMutationListener(
TableCellNode,
(nodeMutations) => {
const nodeUpdated = nodeMutations.get(tableCellNode.getKey()) === 'updated'
if (nodeUpdated) {
editor.getEditorState().read(() => {
updateTableCellNode(tableCellNode.getLatest())
})
}
})
if (nodeUpdated) {
editor.getEditorState().read(() => {
updateTableCellNode(tableCellNode.getLatest())
})
}
},
{ skipInitialization: true },
)
}, [editor, tableCellNode])
useEffect(() => {

View File

@@ -1,3 +0,0 @@
.TableCellResizer__resizer {
position: absolute;
}

View File

@@ -20,9 +20,9 @@ import * as React from 'react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import type { PluginComponent, PluginComponentWithAnchor } from '../../../typesClient.js'
import type { PluginComponent } from '../../../typesClient.js'
import './index.scss'
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'
type MousePosition = {
x: number
@@ -38,6 +38,7 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
const targetRef = useRef<HTMLElement | null>(null)
const resizerRef = useRef<HTMLDivElement | null>(null)
const tableRectRef = useRef<ClientRect | null>(null)
const editorConfig = useEditorConfigContext()
const mouseStartPosRef = useRef<MousePosition | null>(null)
const [mouseCurrentPos, updateMouseCurrentPos] = useState<MousePosition | null>(null)
@@ -117,14 +118,18 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
}, 0)
}
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mousedown', onMouseDown)
document.addEventListener('mouseup', onMouseUp)
const removeRootListener = editor.registerRootListener((rootElement, prevRootElement) => {
rootElement?.addEventListener('mousemove', onMouseMove)
rootElement?.addEventListener('mousedown', onMouseDown)
rootElement?.addEventListener('mouseup', onMouseUp)
prevRootElement?.removeEventListener('mousemove', onMouseMove)
prevRootElement?.removeEventListener('mousedown', onMouseDown)
prevRootElement?.removeEventListener('mouseup', onMouseUp)
})
return () => {
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mousedown', onMouseDown)
document.removeEventListener('mouseup', onMouseUp)
removeRootListener()
}
}, [activeCell, draggingDirection, editor, resetState])
@@ -378,12 +383,12 @@ function TableCellResizer({ editor }: { editor: LexicalEditor }): JSX.Element {
{activeCell != null && !isMouseDown && (
<React.Fragment>
<div
className="TableCellResizer__resizer TableCellResizer__ui"
className={`${editorConfig.editorConfig.lexical.theme.tableCellResizer} TableCellResizer__ui`}
onMouseDown={toggleResize('right')}
style={resizerStyles.right || undefined}
/>
<div
className="TableCellResizer__resizer TableCellResizer__ui"
className={`${editorConfig.editorConfig.lexical.theme.tableCellResizer} TableCellResizer__ui`}
onMouseDown={toggleResize('bottom')}
style={resizerStyles.bottom || undefined}
/>

View File

@@ -0,0 +1,243 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type { TableCellNode, TableRowNode } from '@lexical/table'
import type { EditorConfig, NodeKey } from 'lexical'
import type { JSX } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
$getTableColumnIndexFromTableCellNode,
$getTableRowIndexFromTableCellNode,
$insertTableColumn__EXPERIMENTAL,
$insertTableRow__EXPERIMENTAL,
$isTableCellNode,
$isTableNode,
TableNode,
} from '@lexical/table'
import { $findMatchingParent, mergeRegister } from '@lexical/utils'
import { $getNearestNodeFromDOMNode } from 'lexical'
import { useEffect, useRef, useState } from 'react'
import * as React from 'react'
import { createPortal } from 'react-dom'
import { useEditorConfigContext } from '../../../../lexical/config/client/EditorConfigProvider.js'
import { useDebounce } from '../../utils/useDebounce.js'
const BUTTON_WIDTH_PX = 20
function TableHoverActionsContainer({ anchorElem }: { anchorElem: HTMLElement }): JSX.Element {
const [editor] = useLexicalComposerContext()
const editorConfig = useEditorConfigContext()
const [isShownRow, setShownRow] = useState<boolean>(false)
const [isShownColumn, setShownColumn] = useState<boolean>(false)
const [shouldListenMouseMove, setShouldListenMouseMove] = useState<boolean>(false)
const [position, setPosition] = useState({})
const codeSetRef = useRef<Set<NodeKey>>(new Set())
const tableDOMNodeRef = useRef<HTMLElement | null>(null)
const debouncedOnMouseMove = useDebounce(
(event: MouseEvent) => {
const { isOutside, tableDOMNode } = getMouseInfo(event, editorConfig.editorConfig?.lexical)
if (isOutside) {
setShownRow(false)
setShownColumn(false)
return
}
if (!tableDOMNode) {
return
}
tableDOMNodeRef.current = tableDOMNode
let hoveredRowNode: TableCellNode | null = null
let hoveredColumnNode: TableCellNode | null = null
let tableDOMElement: HTMLElement | null = null
editor.update(() => {
const maybeTableCell = $getNearestNodeFromDOMNode(tableDOMNode)
if ($isTableCellNode(maybeTableCell)) {
const table = $findMatchingParent(maybeTableCell, (node) => $isTableNode(node))
if (!$isTableNode(table)) {
return
}
tableDOMElement = editor.getElementByKey(table?.getKey())
if (tableDOMElement) {
const rowCount = table.getChildrenSize()
const colCount =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
((table as TableNode).getChildAtIndex(0) as TableRowNode)?.getChildrenSize()
const rowIndex = $getTableRowIndexFromTableCellNode(maybeTableCell)
const colIndex = $getTableColumnIndexFromTableCellNode(maybeTableCell)
if (rowIndex === rowCount - 1) {
hoveredRowNode = maybeTableCell
} else if (colIndex === colCount - 1) {
hoveredColumnNode = maybeTableCell
}
}
}
})
if (tableDOMElement) {
const {
bottom: tableElemBottom,
height: tableElemHeight,
right: tableElemRight,
width: tableElemWidth,
x: tableElemX,
y: tableElemY,
} = (tableDOMElement as HTMLTableElement).getBoundingClientRect()
const { left: editorElemLeft, y: editorElemY } = anchorElem.getBoundingClientRect()
if (hoveredRowNode) {
setShownColumn(false)
setShownRow(true)
setPosition({
height: BUTTON_WIDTH_PX,
left: tableElemX - editorElemLeft,
top: tableElemBottom - editorElemY + 5,
width: tableElemWidth,
})
} else if (hoveredColumnNode) {
setShownColumn(true)
setShownRow(false)
setPosition({
height: tableElemHeight,
left: tableElemRight - editorElemLeft + 5,
top: tableElemY - editorElemY,
width: BUTTON_WIDTH_PX,
})
}
}
},
50,
250,
)
useEffect(() => {
if (!shouldListenMouseMove) {
return
}
document.addEventListener('mousemove', debouncedOnMouseMove)
return () => {
setShownRow(false)
setShownColumn(false)
document.removeEventListener('mousemove', debouncedOnMouseMove)
}
}, [shouldListenMouseMove, debouncedOnMouseMove])
useEffect(() => {
return mergeRegister(
editor.registerMutationListener(
TableNode,
(mutations) => {
editor.getEditorState().read(() => {
for (const [key, type] of mutations) {
switch (type) {
case 'created':
codeSetRef.current.add(key)
setShouldListenMouseMove(codeSetRef.current.size > 0)
break
case 'destroyed':
codeSetRef.current.delete(key)
setShouldListenMouseMove(codeSetRef.current.size > 0)
break
default:
break
}
}
})
},
{ skipInitialization: false },
),
)
}, [editor])
const insertAction = (insertRow: boolean) => {
editor.update(() => {
if (tableDOMNodeRef.current) {
const maybeTableNode = $getNearestNodeFromDOMNode(tableDOMNodeRef.current)
maybeTableNode?.selectEnd()
if (insertRow) {
$insertTableRow__EXPERIMENTAL()
setShownRow(false)
} else {
$insertTableColumn__EXPERIMENTAL()
setShownColumn(false)
}
}
})
}
return (
<>
{isShownRow && (
<button
className={editorConfig.editorConfig.lexical.theme.tableAddRows}
onClick={() => insertAction(true)}
style={{ ...position }}
/>
)}
{isShownColumn && (
<button
className={editorConfig.editorConfig.lexical.theme.tableAddColumns}
onClick={() => insertAction(false)}
style={{ ...position }}
/>
)}
</>
)
}
function getMouseInfo(
event: MouseEvent,
editorConfig: EditorConfig,
): {
isOutside: boolean
tableDOMNode: HTMLElement | null
} {
const target = event.target
if (target && target instanceof HTMLElement) {
const tableDOMNode = target.closest<HTMLElement>(
`td.${editorConfig.theme.tableCell}, th.${editorConfig.theme.tableCell}`,
)
const isOutside = !(
tableDOMNode ||
target.closest<HTMLElement>(`button.${editorConfig.theme.tableAddRows}`) ||
target.closest<HTMLElement>(`button.${editorConfig.theme.tableAddColumns}`) ||
target.closest<HTMLElement>(`div.${editorConfig.theme.tableCellResizer}`)
)
return { isOutside, tableDOMNode }
} else {
return { isOutside: true, tableDOMNode: null }
}
}
export function TableHoverActionsPlugin({
anchorElem = document.body,
}: {
anchorElem?: HTMLElement
}): React.ReactPortal | null {
return createPortal(<TableHoverActionsContainer anchorElem={anchorElem} />, anchorElem)
}

View File

@@ -4,11 +4,11 @@
&__table {
border-collapse: collapse;
border-spacing: 0;
max-width: 100%;
overflow-y: scroll;
overflow-x: scroll;
table-layout: fixed;
width: calc(100% - 25px);
margin: 30px 0;
width: max-content;
margin: 0 25px 30px 0;
::selection {
background: rgba(172, 206, 247);
@@ -81,35 +81,39 @@
}
&__tableAddColumns {
position: absolute;
top: 0;
width: 20px;
background-color: #eee;
height: 100%;
right: 0;
}
&__tableAddColumns, &__tableAddRows {
position: absolute;
background-color: var(--theme-elevation-100);
animation: table-controls 0.2s ease;
border: 0;
cursor: pointer;
min-width: 24px;
min-height: 24px;
}
&__tableAddColumns:hover {
background-color: #c9dbf0;
&__tableAddColumns:after, &__tableAddRows:after {
background-image: url(../../../../lexical/ui/icons/Add/index.svg);
background-position: center;
background-repeat: no-repeat;
display: block;
content: ' ';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.4;
}
&__tableAddColumns:hover, &__tableAddRows:hover {
background-color: var(--theme-elevation-200);
}
&__tableAddRows {
position: absolute;
bottom: -25px;
width: calc(100% - 25px);
background-color: #eee;
height: 20px;
left: 0;
animation: table-controls 0.2s ease;
border: 0;
cursor: pointer;
}
&__tableAddRows:hover {
background-color: #c9dbf0;
}
@keyframes table-controls {
@@ -162,5 +166,9 @@ html[data-theme='dark'] {
&__tableCellHeader {
background-color: var(--theme-elevation-50);
}
&__tableAddColumns:after, &__tableAddRows:after {
background-image: url(../../../../lexical/ui/icons/Add/light.svg);
}
}
}

View File

@@ -0,0 +1,244 @@
// Copied & modified from https://github.com/lodash/lodash/blob/main/src/debounce.ts
/*
The MIT License
Copyright JS Foundation and other contributors <https://js.foundation/>
Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash
The following license applies to all parts of this software except as
documented below:
====
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.
*/
/** Error message constants. */
const FUNC_ERROR_TEXT = 'Expected a function'
/* Built-in method references for those with the same name as other `lodash` methods. */
const nativeMax = Math.max,
nativeMin = Math.min
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked. The debounced function comes with a `cancel` method to cancel
* delayed `func` invocations and a `flush` method to immediately invoke them.
* Provide `options` to indicate whether `func` should be invoked on the
* leading and/or trailing edge of the `wait` timeout. The `func` is invoked
* with the last arguments provided to the debounced function. Subsequent
* calls to the debounced function return the result of the last `func`
* invocation.
*
* **Note:** If `leading` and `trailing` options are `true`, `func` is
* invoked on the trailing edge of the timeout only if the debounced function
* is invoked more than once during the `wait` timeout.
*
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
* until to the next tick, similar to `setTimeout` with a timeout of `0`.
*
* See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
* for details over the differences between `_.debounce` and `_.throttle`.
*
* @static
* @memberOf _
* @since 0.1.0
* @category Function
* @param {Function} func The function to debounce.
* @param {number} [wait=0] The number of milliseconds to delay.
* @param {Object} [options={}] The options object.
* @param {boolean} [options.leading=false]
* Specify invoking on the leading edge of the timeout.
* @param {number} [options.maxWait]
* The maximum time `func` is allowed to be delayed before it's invoked.
* @param {boolean} [options.trailing=true]
* Specify invoking on the trailing edge of the timeout.
* @returns {Function} Returns the new debounced function.
* @example
*
* // Avoid costly calculations while the window size is in flux.
* jQuery(window).on('resize', _.debounce(calculateLayout, 150));
*
* // Invoke `sendMail` when clicked, debouncing subsequent calls.
* jQuery(element).on('click', _.debounce(sendMail, 300, {
* 'leading': true,
* 'trailing': false
* }));
*
* // Ensure `batchLog` is invoked once after 1 second of debounced calls.
* var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
* var source = new EventSource('/stream');
* jQuery(source).on('message', debounced);
*
* // Cancel the trailing debounced invocation.
* jQuery(window).on('popstate', debounced.cancel);
*/
function debounce(func, wait, options) {
let lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT)
}
wait = wait || 0
if (typeof options === 'object') {
leading = !!options.leading
maxing = 'maxWait' in options
maxWait = maxing ? nativeMax(options.maxWait || 0, wait) : maxWait
trailing = 'trailing' in options ? !!options.trailing : trailing
}
function invokeFunc(time) {
const args = lastArgs,
thisArg = lastThis
lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time
// Start the timer for the trailing edge.
timerId = setTimeout(timerExpired, wait)
// Invoke the leading edge.
return leading ? invokeFunc(time) : result
}
function remainingWait(time) {
const timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime,
timeWaiting = wait - timeSinceLastCall
return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting
}
function shouldInvoke(time) {
const timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime
// Either this is the first call, activity has stopped and we're at the
// trailing edge, the system time has gone backwards and we're treating
// it as the trailing edge, or we've hit the `maxWait` limit.
return (
lastCallTime === undefined ||
timeSinceLastCall >= wait ||
timeSinceLastCall < 0 ||
(maxing && timeSinceLastInvoke >= maxWait)
)
}
function timerExpired() {
const time = Date.now()
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// Restart the timer.
timerId = setTimeout(timerExpired, remainingWait(time))
}
function trailingEdge(time) {
timerId = undefined
// Only invoke if we have `lastArgs` which means `func` has been
// debounced at least once.
if (trailing && lastArgs) {
return invokeFunc(time)
}
lastArgs = lastThis = undefined
return result
}
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId)
}
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
function debounced() {
const time = Date.now(),
isInvoking = shouldInvoke(time)
// eslint-disable-next-line prefer-rest-params
lastArgs = arguments
// eslint-disable-next-line @typescript-eslint/no-this-alias
lastThis = this
lastCallTime = time
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime)
}
if (maxing) {
// Handle invocations in a tight loop.
clearTimeout(timerId)
timerId = setTimeout(timerExpired, wait)
return invokeFunc(lastCallTime)
}
}
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait)
}
return result
}
debounced.cancel = cancel
debounced.flush = flush
return debounced
}
export default debounce

View File

@@ -0,0 +1,26 @@
import { useMemo, useRef } from 'react'
import debounce from './debounce.js'
export function useDebounce<T extends (...args: never[]) => void>(
fn: T,
ms: number,
maxWait?: number,
) {
const funcRef = useRef<T | null>(null)
funcRef.current = fn
return useMemo(
() =>
debounce(
(...args: Parameters<T>) => {
if (funcRef.current) {
funcRef.current(...args)
}
},
ms,
{ maxWait },
),
[ms, maxWait],
)
}

View File

@@ -77,8 +77,19 @@ function startsWithSeparator(textContent: string): boolean {
return isSeparator(textContent[0])
}
function startsWithFullStop(textContent: string): boolean {
return /^\.[a-z\d]+/i.test(textContent)
/**
* Check if the text content starts with a fullstop followed by a top-level domain.
* Meaning if the text content can be a beginning of a top level domain.
* @param textContent
* @param isEmail
* @returns boolean
*/
function startsWithTLD(textContent: string, isEmail: boolean): boolean {
if (isEmail) {
return /^\.[a-z]{2,}/i.test(textContent)
} else {
return /^\.[a-z0-9]+/i.test(textContent)
}
}
function isPreviousNodeValid(node: LexicalNode): boolean {
@@ -343,13 +354,15 @@ function handleBadNeighbors(
const nextSibling = textNode.getNextSibling()
const text = textNode.getTextContent()
if (
$isAutoLinkNode(previousSibling) &&
(!startsWithSeparator(text) || startsWithFullStop(text))
) {
previousSibling.append(textNode)
handleLinkEdit(previousSibling, matchers, onChange)
onChange(null, previousSibling.getFields()?.url ?? null)
if ($isAutoLinkNode(previousSibling)) {
const isEmailURI = previousSibling.getFields()?.url
? previousSibling.getFields()?.url?.startsWith('mailto:')
: false
if (!startsWithSeparator(text) || startsWithTLD(text, isEmailURI)) {
previousSibling.append(textNode)
handleLinkEdit(previousSibling, matchers, onChange)
onChange(null, previousSibling.getFields()?.url ?? null)
}
}
if ($isAutoLinkNode(nextSibling) && !endsWithSeparator(text)) {

View File

@@ -99,6 +99,7 @@ export function convertParagraphNode(
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
version: 1,
}
}

View File

@@ -101,7 +101,7 @@ html[data-theme='dark'] {
}
}
> .editor-placeholder {
> .editor-scroller > .editor > div > .editor-placeholder {
top: calc(var(--base) * 1.25);
}
}

View File

@@ -11,6 +11,7 @@ import type {
import type {
Field,
JsonObject,
Payload,
PayloadRequest,
ReplaceAny,
RequestContext,
@@ -283,6 +284,7 @@ export type ServerFeature<ServerProps, ClientFeatureProps> = {
generateComponentMap?: (args: {
config: SanitizedConfig
i18n: I18nClient
payload: Payload
props: ServerProps
schemaPath: string
}) => {

View File

@@ -25,12 +25,12 @@
}
&--show-gutter {
> .rich-text-lexical__wrap > .editor-container > .editor-placeholder {
> .rich-text-lexical__wrap > .editor-container > .editor-scroller > .editor > div > .editor-placeholder {
left: 3rem;
}
}
&:not(&--show-gutter) > .rich-text-lexical__wrap > .editor-container > .editor-placeholder {
&:not(&--show-gutter) > .rich-text-lexical__wrap > .editor-container > .editor-scroller > .editor > div > .editor-placeholder {
left: 0;
}

View File

@@ -5,7 +5,6 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin.js'
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin.js'
import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin.js'
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin.js'
import { useTranslation } from '@payloadcms/ui'
import { BLUR_COMMAND, COMMAND_PRIORITY_LOW, FOCUS_COMMAND } from 'lexical'
import * as React from 'react'
import { useEffect, useState } from 'react'
@@ -29,7 +28,6 @@ export const LexicalEditor: React.FC<
const { editorConfig, editorContainerRef, onChange } = props
const editorConfigContext = useEditorConfigContext()
const [editor] = useLexicalComposerContext()
const { t } = useTranslation<{}, string>()
const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null)
const onRef = (_floatingAnchorElem: HTMLDivElement) => {
@@ -122,7 +120,6 @@ export const LexicalEditor: React.FC<
</div>
</div>
}
placeholder={<p className="editor-placeholder">{t('lexical:general:placeholder')}</p>}
/>
<OnChangePlugin
// Selection changes can be ignored here, reducing the

View File

@@ -1,10 +1,19 @@
import type { JSX } from 'react'
import { ContentEditable } from '@lexical/react/LexicalContentEditable.js'
import { useTranslation } from '@payloadcms/ui'
import * as React from 'react'
import './ContentEditable.scss'
export function LexicalContentEditable({ className }: { className?: string }): JSX.Element {
return <ContentEditable className={className ?? 'ContentEditable__root'} />
const { t } = useTranslation<{}, string>()
return (
<ContentEditable
aria-placeholder={t('lexical:general:placeholder')}
className={className ?? 'ContentEditable__root'}
placeholder={<p className="editor-placeholder">{t('lexical:general:placeholder')}</p>}
/>
)
}

View File

@@ -2,7 +2,14 @@ import React from 'react'
export const TableIcon: React.FC = () => {
return (
<svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg">
<svg
className="icon"
fill="none"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
>
<path
clipRule="evenodd"
d="M5.33333 4.5C4.8731 4.5 4.5 4.8731 4.5 5.33333V7.5H9.5V4.5H5.33333ZM5.33333 3.5C4.32081 3.5 3.5 4.32081 3.5 5.33333V14.6667C3.5 15.6792 4.32081 16.5 5.33333 16.5H14.6667C15.6792 16.5 16.5 15.6792 16.5 14.6667V5.33333C16.5 4.32081 15.6792 3.5 14.6667 3.5H5.33333ZM10.5 4.5V7.5H15.5V5.33333C15.5 4.8731 15.1269 4.5 14.6667 4.5H10.5ZM15.5 8.5H10.5V11.5H15.5V8.5ZM15.5 12.5H10.5V15.5H14.6667C15.1269 15.5 15.5 15.1269 15.5 14.6667V12.5ZM9.5 15.5V12.5H4.5V14.6667C4.5 15.1269 4.8731 15.5 5.33333 15.5H9.5ZM4.5 11.5H9.5V8.5H4.5V11.5Z"

View File

@@ -19,7 +19,7 @@ export const UploadIcon: React.FC = () => (
/>
<path
d="M7.99984 9.33366C8.73622 9.33366 9.33317 8.73671 9.33317 8.00033C9.33317 7.26395 8.73622 6.66699 7.99984 6.66699C7.26346 6.66699 6.6665 7.26395 6.6665 8.00033C6.6665 8.73671 7.26346 9.33366 7.99984 9.33366Z"
stroke="#currentColor"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>

View File

@@ -21,6 +21,7 @@ export const defaultRichTextValue: SerializedEditorState = {
format: '',
indent: 0,
textFormat: 0,
textStyle: '',
version: 1,
} as SerializedParagraphNode,
],

View File

@@ -10,7 +10,7 @@ export const getGenerateComponentMap =
(args: {
resolvedFeatureMap: ResolvedServerFeatureMap
}): RichTextAdapter['generateComponentMap'] =>
({ WithServerSideProps, config, i18n, schemaPath }) => {
({ WithServerSideProps, config, i18n, payload, schemaPath }) => {
const componentMap = new Map()
// turn args.resolvedFeatureMap into an array of [key, value] pairs, ordered by value.order, lowest order first:
@@ -35,6 +35,7 @@ export const getGenerateComponentMap =
const components = resolvedFeature.generateComponentMap({
config,
i18n,
payload,
props: resolvedFeature.sanitizedServerFeatureProps,
schemaPath,
})
@@ -80,6 +81,7 @@ export const getGenerateComponentMap =
fieldSchema: fields,
i18n,
parentPath: `${schemaPath}.lexical_internal_feature.${featureKey}.fields.${schemaKey}`,
payload,
readOnly: false,
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -58,7 +58,7 @@ const RichTextField: React.FC<
richTextComponentMap: Map<string, React.ReactNode>
validate?: RichTextFieldValidation
width?: string
} & FormFieldBase
} & Omit<FormFieldBase, 'validate'>
> = (props) => {
const {
name,

View File

@@ -12,7 +12,7 @@ import { defaultLeaves as leafTypes } from './field/leaves/index.js'
export const getGenerateComponentMap =
(args: AdapterArguments): RichTextAdapter['generateComponentMap'] =>
({ WithServerSideProps, config, i18n }) => {
({ WithServerSideProps, config, i18n, payload }) => {
const componentMap = new Map()
;(args?.admin?.leaves || Object.values(leafTypes)).forEach((leaf) => {
@@ -67,6 +67,7 @@ export const getGenerateComponentMap =
config,
fieldSchema: args.admin?.link?.fields as Field[],
i18n,
payload,
readOnly: false,
})
@@ -93,6 +94,7 @@ export const getGenerateComponentMap =
config,
fieldSchema: args?.admin?.upload?.collections[collection.slug]?.fields,
i18n,
payload,
readOnly: false,
})

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-beta.73",
"version": "3.0.0-beta.74",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -21,7 +21,7 @@
}
&--is-multi {
width: calc(100% + base(0.5));
width: calc(100% + base(0.25));
&.rs__value-container--has-value {
padding: 0;

View File

@@ -10,6 +10,12 @@
}
}
.has-many {
.rs__input-container {
overflow: hidden;
}
}
html[data-theme='light'] {
.field-type.text {
&.error {

View File

@@ -2,6 +2,7 @@ import type { I18nClient } from '@payloadcms/translations'
import type {
AdminViewProps,
EditViewProps,
Payload,
SanitizedCollectionConfig,
SanitizedConfig,
} from 'payload'
@@ -26,6 +27,7 @@ export const mapCollections = (args: {
collections: SanitizedCollectionConfig[]
config: SanitizedConfig
i18n: I18nClient
payload: Payload
readOnly?: boolean
}): {
[key: SanitizedCollectionConfig['slug']]: CollectionComponentMap
@@ -38,6 +40,7 @@ export const mapCollections = (args: {
config,
i18n,
i18n: { t },
payload,
readOnly: readOnlyOverride,
} = args
@@ -189,6 +192,7 @@ export const mapCollections = (args: {
config,
fieldSchema: fields,
i18n,
payload,
readOnly: readOnlyOverride,
}),
isPreviewEnabled: !!collectionConfig?.admin?.preview,

View File

@@ -22,6 +22,7 @@ import type {
MappedTab,
NumberFieldProps,
Option,
Payload,
PointFieldProps,
RadioFieldProps,
ReducedBlock,
@@ -67,6 +68,7 @@ export const mapFields = (args: {
filter?: (field: Field) => boolean
i18n: I18nClient
parentPath?: string
payload: Payload
readOnly?: boolean
}): FieldMap => {
const {
@@ -78,6 +80,7 @@ export const mapFields = (args: {
i18n,
i18n: { t },
parentPath,
payload,
readOnly: readOnlyOverride,
} = args
@@ -193,6 +196,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: path,
payload,
readOnly: readOnlyOverride,
}),
isSortable: field.admin?.isSortable,
@@ -217,6 +221,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: `${path}.${block.slug}`,
payload,
readOnly: readOnlyOverride,
})
@@ -303,6 +308,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: path,
payload,
readOnly: readOnlyOverride,
}),
initCollapsed: field.admin?.initCollapsed,
@@ -364,6 +370,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: path,
payload,
readOnly: readOnlyOverride,
}),
hideGutter: field.admin?.hideGutter,
@@ -494,6 +501,7 @@ export const mapFields = (args: {
WithServerSideProps,
config,
i18n,
payload,
schemaPath: path,
})
@@ -526,6 +534,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: path,
payload,
readOnly: readOnlyOverride,
}),
readOnly: field.admin?.readOnly,
@@ -548,6 +557,7 @@ export const mapFields = (args: {
filter,
i18n,
parentPath: path,
payload,
readOnly: readOnlyOverride,
})
@@ -835,7 +845,7 @@ export const mapFields = (args: {
// TODO: For all fields (not just this one) we need to add the name to both .fieldComponentPropsBase.name AND .name. This can probably be improved
result.push({
name: 'id',
type: 'text',
type: payload.db.defaultIDType === 'number' ? 'number' : 'text',
CustomField: null,
cellComponentProps: {
name: 'id',

View File

@@ -1,5 +1,5 @@
import type { I18nClient } from '@payloadcms/translations'
import type { EditViewProps, SanitizedConfig, SanitizedGlobalConfig } from 'payload'
import type { EditViewProps, Payload, SanitizedConfig, SanitizedGlobalConfig } from 'payload'
import { isReactComponentOrFunction } from 'payload/shared'
import React from 'react'
@@ -23,6 +23,7 @@ export const mapGlobals = ({
config: SanitizedConfig
globals: SanitizedGlobalConfig[]
i18n: I18nClient
payload: Payload
readOnly?: boolean
}
}): {
@@ -35,6 +36,7 @@ export const mapGlobals = ({
globals,
i18n,
i18n: { t },
payload,
readOnly: readOnlyOverride,
} = args
@@ -122,6 +124,7 @@ export const mapGlobals = ({
config,
fieldSchema: fields,
i18n,
payload,
readOnly: readOnlyOverride,
}),
isPreviewEnabled: !!globalConfig?.admin?.preview,

View File

@@ -52,6 +52,7 @@ export const buildComponentMap = (args: {
collections: config.collections,
config,
i18n,
payload,
readOnly,
})
@@ -62,6 +63,7 @@ export const buildComponentMap = (args: {
config,
globals: config.globals,
i18n,
payload,
readOnly,
},
})

304
pnpm-lock.yaml generated
View File

@@ -1177,35 +1177,35 @@ importers:
specifier: 2.0.0-beta.0
version: 2.0.0-beta.0(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)
'@lexical/headless':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/link':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/list':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/mark':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/markdown':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/react':
specifier: 0.16.1
version: 0.16.1(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)(yjs@13.6.18)
specifier: 0.17.0
version: 0.17.0(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)(yjs@13.6.18)
'@lexical/rich-text':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/selection':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/table':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/utils':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@types/uuid':
specifier: 10.0.0
version: 10.0.0
@@ -1216,8 +1216,8 @@ importers:
specifier: 2.0.3
version: 2.0.3
lexical:
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
react:
specifier: ^19.0.0-rc-6230622a1a-20240610
version: 19.0.0-rc-fb9a90fa48-20240614
@@ -1232,8 +1232,8 @@ importers:
version: 10.0.0
devDependencies:
'@lexical/eslint-plugin':
specifier: ' 0.16.1'
version: 0.16.1(eslint@9.6.0)
specifier: 0.17.0
version: 0.17.0(eslint@9.6.0)
'@payloadcms/eslint-config':
specifier: workspace:*
version: link:../eslint-config
@@ -1555,11 +1555,11 @@ importers:
specifier: ^3.525.0
version: 3.614.0
'@lexical/headless':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@lexical/markdown':
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
'@payloadcms/db-mongodb':
specifier: workspace:*
version: link:../packages/db-mongodb
@@ -1690,8 +1690,8 @@ importers:
specifier: 4.0.0
version: 4.0.0
lexical:
specifier: 0.16.1
version: 0.16.1
specifier: 0.17.0
version: 0.17.0
payload:
specifier: workspace:*
version: link:../packages/payload
@@ -5693,154 +5693,154 @@ packages:
resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
dev: false
/@lexical/clipboard@0.16.1:
resolution: {integrity: sha512-0dWs/SwKS5KPpuf6fUVVt9vSCl6HAqcDGhSITw/okv0rrIlXTUT6WhVsMJtXfFxTyVvwMeOecJHvQH3i/jRQtA==}
/@lexical/clipboard@0.17.0:
resolution: {integrity: sha512-wYtC6VJhuSxUZc69VTU+vBgzB4HQqhve2hLrr3v+3tR2aimx3KnKphCCP1TexCntxpEnOTPXafEgpOW/EVQE+Q==}
dependencies:
'@lexical/html': 0.16.1
'@lexical/list': 0.16.1
'@lexical/selection': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/html': 0.17.0
'@lexical/list': 0.17.0
'@lexical/selection': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/code@0.16.1:
resolution: {integrity: sha512-pOC28rRZ2XkmI2nIJm50DbKaCJtk5D0o7r6nORYp4i0z+lxt5Sf2m82DL9ksUHJRqKy87pwJDpoWvJ2SAI0ohw==}
/@lexical/code@0.17.0:
resolution: {integrity: sha512-8zrgHzf27aYySfUVeSKw8YP/LkRlXHSwD03BKlkSZAb4HX/WC60SGmdXUhtyTIBucqe0pnuGsRYfR9euD0/tfw==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
prismjs: 1.29.0
/@lexical/devtools-core@0.16.1(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614):
resolution: {integrity: sha512-8CvGERGL7ySDVGLU+YPeq+JupIXsOFlXa3EuJ88koLKqXxYenwMleZgGqayFp6lCP78xqPKnATVeoOZUt/NabQ==}
/@lexical/devtools-core@0.17.0(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614):
resolution: {integrity: sha512-0ftqWsoCb96oTc8Ok+uvjGAXZpsN9oc6ml3d46BdufdZyxHXC4qU3YVoPfLkgAHzH+4fQlNypu7u3Ym3dZ2rJg==}
peerDependencies:
react: ^19.0.0-rc-6230622a1a-20240610
react-dom: ^19.0.0-rc-6230622a1a-20240610
dependencies:
'@lexical/html': 0.16.1
'@lexical/link': 0.16.1
'@lexical/mark': 0.16.1
'@lexical/table': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/html': 0.17.0
'@lexical/link': 0.17.0
'@lexical/mark': 0.17.0
'@lexical/table': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
react: 19.0.0-rc-fb9a90fa48-20240614
react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614)
dev: false
/@lexical/dragon@0.16.1:
resolution: {integrity: sha512-Rvd60GIYN5kpjjBumS34EnNbBaNsoseI0AlzOdtIV302jiHPCLH0noe9kxzu9nZy+MZmjZy8Dx2zTbQT2mueRw==}
/@lexical/dragon@0.17.0:
resolution: {integrity: sha512-XSsrHVwhjBIVF9VN9MFm6Go8fquj5H/jlYuyNzemHq0tOli8NaoSovGc5q0LwXr88RPsuIt1jluazR7Q1+kxTQ==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
dev: false
/@lexical/eslint-plugin@0.16.1(eslint@9.6.0):
resolution: {integrity: sha512-C68eSFBAJ5H8vDae46l9iPUYYw6btC4ZAOr2vMdri8tuN4Aid5c2skDv/Ruiyk12SWsgzz9jHiyo5Fsa1ESLdg==}
/@lexical/eslint-plugin@0.17.0(eslint@9.6.0):
resolution: {integrity: sha512-O6RyQBXAdi90jlthWwfOuxYG4zqzWkpNwsX1V6N8t5iH80Te04LsnfG+hIB/5V8rxm8WPkTjMrqAX3UEZy5Shg==}
peerDependencies:
eslint: '>=7.31.0 || ^8.0.0'
dependencies:
eslint: 9.6.0
dev: true
/@lexical/hashtag@0.16.1:
resolution: {integrity: sha512-G+YOxStAKs3q1utqm9KR4D4lCkwIH52Rctm4RgaVTI+4lvTaybeDRGFV75P/pI/qlF7/FvAYHTYEzCjtC3GNMQ==}
/@lexical/hashtag@0.17.0:
resolution: {integrity: sha512-E6nSoz9haB6JypQtYxG5OYr36AHgam/FBMu77OWNl1KsJbkP8nInm+P22QFsNnEvs4Hk6/0FJ5g42+lTEnGmIg==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
dev: false
/@lexical/headless@0.16.1:
resolution: {integrity: sha512-L00TQk9vD1o7c25QMNdwD7MvkmQYP/jebG2M8GbX/3KSjHag2QB8MwcFlccSGXBhmbVm1X1Zo7z7urxY//3atw==}
/@lexical/headless@0.17.0:
resolution: {integrity: sha512-yKvXcq2F6S1lwDLcwv+bHht/al1LcFmidPT3rjISRxLX+/YjUcUT8MmvV773Du4piV4rFPbVlBPFBZfHJkDxXw==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
/@lexical/history@0.16.1:
resolution: {integrity: sha512-WQhScx0TJeKSQAnEkRpIaWdUXqirrNrom2MxbBUc/32zEUMm9FzV7nRGknvUabEFUo7vZq6xTZpOExQJqHInQA==}
/@lexical/history@0.17.0:
resolution: {integrity: sha512-SfeUKAXf9pZpqee9rMOTt33V0J0p/AS9TZLT9Un9dU6wAaHfv6NFax1ND0JoG1a9YkTc539mufxVLNjsNRc0ag==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
dev: false
/@lexical/html@0.16.1:
resolution: {integrity: sha512-vbtAdCvQ3PaAqa5mFmtmrvbiAvjCu1iXBAJ0bsHqFXCF2Sba5LwHVe8dUAOTpfEZEMbiHfjul6b5fj4vNPGF2A==}
/@lexical/html@0.17.0:
resolution: {integrity: sha512-sI458CEP/j+Gd2YEo1+vTax31ZAjdq5jmRJMgSKxzKlkVYAUY9eH5u3Y3awPLwLVXJHiIopMX02GeZytibuTiw==}
dependencies:
'@lexical/selection': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/selection': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/link@0.16.1:
resolution: {integrity: sha512-zG36gEnEqbIe6tK/MhXi7wn/XMY/zdivnPcOY5WyC3derkEezeLSSIFsC1u5UNeK5pbpNMSy4LDpLhi1Ww4Y5w==}
/@lexical/link@0.17.0:
resolution: {integrity: sha512-Kux6yvPit6y0ksPpwimv3seVrXAsggkqB6oT6oAVBaDpYuygVEwNDqg/rCTtB3mHQ4eeuU33mdK7MSXZ34bZRQ==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/list@0.16.1:
resolution: {integrity: sha512-i9YhLAh5N6YO9dP+R1SIL9WEdCKeTiQQYVUzj84vDvX5DIBxMPUjTmMn3LXu9T+QO3h1s2L/vJusZASrl45eAw==}
/@lexical/list@0.17.0:
resolution: {integrity: sha512-anDuSUykTv+lqyCwl1m+sThrB15OKCa00Eo68/d2HQSHDD3KNWgSx709dcR17bD9oT204yOhMJbQGywuzcEyGQ==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/mark@0.16.1:
resolution: {integrity: sha512-CZRGMLcxn5D+jzf1XnH+Z+uUugmpg1mBwTbGybCPm8UWpBrKDHkrscfMgWz62iRWz0cdVjM5+0zWpNElxFTRjQ==}
/@lexical/mark@0.17.0:
resolution: {integrity: sha512-Ynqh9KHXUcB9qLOTGC9s+bbWtawOwRStkeIeAugTqrwckyYWeDaePpyJ6IhBBJy1E1CfpiZn71NDeP+FuRjnXQ==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
dev: false
/@lexical/markdown@0.16.1:
resolution: {integrity: sha512-0sBLttMvfQO/hVaIqpHdvDowpgV2CoRuWo2CNwvRLZPPWvPVjL4Nkb73wmi8zAZsAOTbX2aw+g4m/+k5oJqNig==}
/@lexical/markdown@0.17.0:
resolution: {integrity: sha512-6IuJ2l5p/Ma+VBUIStIRXwTC01GEzx21gvqqywuqBUzAOiMr1oRM+DGsQgrzZrcjX+LzUlZ5ZgjuWtK8XKVAZw==}
dependencies:
'@lexical/code': 0.16.1
'@lexical/link': 0.16.1
'@lexical/list': 0.16.1
'@lexical/rich-text': 0.16.1
'@lexical/text': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/code': 0.17.0
'@lexical/link': 0.17.0
'@lexical/list': 0.17.0
'@lexical/rich-text': 0.17.0
'@lexical/text': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/offset@0.16.1:
resolution: {integrity: sha512-/i2J04lQmFeydUZIF8tKXLQTXiJDTQ6GRnkfv1OpxU4amc0rwGa7+qAz/PuF1n58rP6InpLmSHxgY5JztXa2jw==}
/@lexical/offset@0.17.0:
resolution: {integrity: sha512-onE6SD2mIAwBLTT5v5fVBVtRg/NpQj+o10vTWJ1ImvEUERpSoCyHMTy3IMoSMuCRwuOG9C0cFEret2u+QS8Icw==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
dev: false
/@lexical/overflow@0.16.1:
resolution: {integrity: sha512-xh5YpoxwA7K4wgMQF/Sjl8sdjaxqesLCtH5ZrcMsaPlmucDIEEs+i8xxk+kDUTEY7y+3FvRxs4lGNgX8RVWkvQ==}
/@lexical/overflow@0.17.0:
resolution: {integrity: sha512-dh+nQAmeobKvZFodWyzNh1ZjX043Patk/1Lwct9XmtAGMUdXL+tB0bbguWVcDfY8OYu1CTQGfbdq2oMEJYzwsg==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
dev: false
/@lexical/plain-text@0.16.1:
resolution: {integrity: sha512-GjY4ylrBZIaAVIF8IFnmW0XGyHAuRmWA6gKB8iTTlsjgFrCHFIYC74EeJSp309O0Hflg9rRBnKoX1TYruFHVwA==}
/@lexical/plain-text@0.17.0:
resolution: {integrity: sha512-AEk+3ttbRyRi7m9UbU1CdLUtGsXh4FFZkBC12twV3U82lZHOdHocLlTutP+lcbYlGjeq6UF43NxOSGzsYEunsA==}
dependencies:
'@lexical/clipboard': 0.16.1
'@lexical/selection': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/clipboard': 0.17.0
'@lexical/selection': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
dev: false
/@lexical/react@0.16.1(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)(yjs@13.6.18):
resolution: {integrity: sha512-SsGgLt9iKfrrMRy9lFb6ROVPUYOgv6b+mCn9Al+TLqs/gBReDBi3msA7m526nrtBUKYUnjHdQ1QXIJzuKgOxcg==}
/@lexical/react@0.17.0(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)(yjs@13.6.18):
resolution: {integrity: sha512-HZ3joq+5g2++2vo/6scTd60Y2bsu8ya8EUdopyudnmGZGKAcAPue9pLOlBaEpsYZ7vqTuGjiPgtEBfFzDy9rlg==}
peerDependencies:
react: ^19.0.0-rc-6230622a1a-20240610
react-dom: ^19.0.0-rc-6230622a1a-20240610
dependencies:
'@lexical/clipboard': 0.16.1
'@lexical/code': 0.16.1
'@lexical/devtools-core': 0.16.1(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)
'@lexical/dragon': 0.16.1
'@lexical/hashtag': 0.16.1
'@lexical/history': 0.16.1
'@lexical/link': 0.16.1
'@lexical/list': 0.16.1
'@lexical/mark': 0.16.1
'@lexical/markdown': 0.16.1
'@lexical/overflow': 0.16.1
'@lexical/plain-text': 0.16.1
'@lexical/rich-text': 0.16.1
'@lexical/selection': 0.16.1
'@lexical/table': 0.16.1
'@lexical/text': 0.16.1
'@lexical/utils': 0.16.1
'@lexical/yjs': 0.16.1(yjs@13.6.18)
lexical: 0.16.1
'@lexical/clipboard': 0.17.0
'@lexical/code': 0.17.0
'@lexical/devtools-core': 0.17.0(react-dom@19.0.0-rc-fb9a90fa48-20240614)(react@19.0.0-rc-fb9a90fa48-20240614)
'@lexical/dragon': 0.17.0
'@lexical/hashtag': 0.17.0
'@lexical/history': 0.17.0
'@lexical/link': 0.17.0
'@lexical/list': 0.17.0
'@lexical/mark': 0.17.0
'@lexical/markdown': 0.17.0
'@lexical/overflow': 0.17.0
'@lexical/plain-text': 0.17.0
'@lexical/rich-text': 0.17.0
'@lexical/selection': 0.17.0
'@lexical/table': 0.17.0
'@lexical/text': 0.17.0
'@lexical/utils': 0.17.0
'@lexical/yjs': 0.17.0(yjs@13.6.18)
lexical: 0.17.0
react: 19.0.0-rc-fb9a90fa48-20240614
react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614)
react-error-boundary: 3.1.4(react@19.0.0-rc-fb9a90fa48-20240614)
@@ -5848,45 +5848,45 @@ packages:
- yjs
dev: false
/@lexical/rich-text@0.16.1:
resolution: {integrity: sha512-4uEVXJur7tdSbqbmsToCW4YVm0AMh4y9LK077Yq2O9hSuA5dqpI8UbTDnxZN2D7RfahNvwlqp8eZKFB1yeiJGQ==}
/@lexical/rich-text@0.17.0:
resolution: {integrity: sha512-XJc8gQBSwppCkESQaNcGtyTaPXZaeCQDcUVpnDjDK0vM/ZZN8TErxbujwbSqA3kO2dBds9N8WxNboSwuncMBcQ==}
dependencies:
'@lexical/clipboard': 0.16.1
'@lexical/selection': 0.16.1
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/clipboard': 0.17.0
'@lexical/selection': 0.17.0
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/selection@0.16.1:
resolution: {integrity: sha512-+nK3RvXtyQvQDq7AZ46JpphmM33pwuulwiRfeXR5T9iFQTtgWOEjsAi/KKX7vGm70BxACfiSxy5QCOgBWFwVJg==}
/@lexical/selection@0.17.0:
resolution: {integrity: sha512-UTjlvyhFY/lmHtBaIaVRwYnRfO9gR4I32+PT7vHQr4v3VfcgS63YEGSgEZy3Gh1pfeJqaZATN58+jCuMAQXlWQ==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
/@lexical/table@0.16.1:
resolution: {integrity: sha512-GWb0/MM1sVXpi1p2HWWOBldZXASMQ4c6WRNYnRmq7J/aB5N66HqQgJGKp3m66Kz4k1JjhmZfPs7F018qIBhnFQ==}
/@lexical/table@0.17.0:
resolution: {integrity: sha512-RQF7IG0rGL2/bPaPFUIMgDA3QMdDflvXSnE7Udgbj9yMqSKhYkaERVfNyoLckDUSuusGJd6XV+qum6JWn0nSNA==}
dependencies:
'@lexical/utils': 0.16.1
lexical: 0.16.1
'@lexical/utils': 0.17.0
lexical: 0.17.0
/@lexical/text@0.16.1:
resolution: {integrity: sha512-Os/nKQegORTrKKN6vL3/FMVszyzyqaotlisPynvTaHTUC+yY4uyjM2hlF93i5a2ixxyiPLF9bDroxUP96TMPXg==}
/@lexical/text@0.17.0:
resolution: {integrity: sha512-kFH0V6yjW8YswmoY7vHT4zHFDflGfamuUxTPHROpdnq/JMjHeaVwtmFBdrP0gknaC8XMRXdr3EsemQ7cbOoDPA==}
dependencies:
lexical: 0.16.1
lexical: 0.17.0
/@lexical/utils@0.16.1:
resolution: {integrity: sha512-BVyJxDQi/rIxFTDjf2zE7rMDKSuEaeJ4dybHRa/hRERt85gavGByQawSLeQlTjLaYLVsy+x7wCcqh2fNhlLf0g==}
/@lexical/utils@0.17.0:
resolution: {integrity: sha512-B/n0rRGDmdMrqi2qnprLt6SntC6jb4JItLmPl8zDDdg7/HxMdLq3F93vogeiXQJn0mlNqgiENWHvLAy5K2C2uQ==}
dependencies:
'@lexical/list': 0.16.1
'@lexical/selection': 0.16.1
'@lexical/table': 0.16.1
lexical: 0.16.1
'@lexical/list': 0.17.0
'@lexical/selection': 0.17.0
'@lexical/table': 0.17.0
lexical: 0.17.0
/@lexical/yjs@0.16.1(yjs@13.6.18):
resolution: {integrity: sha512-QHw1bmzB/IypIV1tRWMH4hhwE1xX7wV+HxbzBS8oJAkoU5AYXM/kyp/sQicgqiwVfpai1Px7zatOoUDFgbyzHQ==}
/@lexical/yjs@0.17.0(yjs@13.6.18):
resolution: {integrity: sha512-xJv3frcK/jskssLbzdY4yfBaM7+LWaZD4YjYkJ/bvRDTey2w+McF+SvsJ/yBA8YF1oaL3rT+0aIQJ7rfH+AxjA==}
peerDependencies:
yjs: '>=13.5.22'
dependencies:
'@lexical/offset': 0.16.1
lexical: 0.16.1
'@lexical/offset': 0.17.0
lexical: 0.17.0
yjs: 13.6.18
dev: false
@@ -12284,8 +12284,8 @@ packages:
prelude-ls: 1.2.1
type-check: 0.4.0
/lexical@0.16.1:
resolution: {integrity: sha512-+R05d3+N945OY8pTUjTqQrWoApjC+ctzvjnmNETtx9WmVAaiW0tQVG+AYLt5pDGY8dQXtd4RPorvnxBTECt9SA==}
/lexical@0.17.0:
resolution: {integrity: sha512-cCFmANO5rIf34NF0go/hxp5S3V5Z8G2Rsa1FJy50qF2WM5EJNJ/MqN75TApjfgMkfrbO6gau3X12nCqwsT7aDg==}
/lib0@0.2.94:
resolution: {integrity: sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==}

File diff suppressed because it is too large Load Diff

View File

@@ -3,12 +3,6 @@ import type { IndexDirection, IndexOptions } from 'mongoose'
import type { PaginatedDocs, Payload } from 'payload'
import { reload } from '@payloadcms/next/utilities'
import fs from 'fs/promises'
import { fileURLToPath } from 'node:url'
import path from 'path'
import { generateTypes } from 'payload/node'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import type { NextRESTClient } from '../helpers/NextRESTClient.js'
import type { GroupField, RichTextField } from './payload-types.js'
@@ -1701,19 +1695,4 @@ describe('Fields', () => {
expect(result.docs).toHaveLength(1)
})
})
describe('TypeScript generated types', () => {
/**
* Check that the generated types have not unintentionally changed.
*
* If they must change:
*
* AFTER REVIEWING THE CHANGES, update the snapshot with `pnpm test:int fields --updateSnapshot`
*/
it('should not unintentionally change the generated types', async () => {
await generateTypes(payload.config)
const payloadTypes = await fs.readFile(path.resolve(dirname, './payload-types.ts'), 'utf-8')
expect(payloadTypes).toMatchSnapshot()
})
})
})

View File

@@ -74,7 +74,6 @@ export interface Config {
export interface UserAuthOperations {
forgotPassword: {
email: string;
password: string;
};
login: {
email: string;
@@ -86,7 +85,6 @@ export interface UserAuthOperations {
};
unlock: {
email: string;
password: string;
};
}
/**
@@ -281,7 +279,7 @@ export interface User {
hash?: string | null;
loginAttempts?: number | null;
lockUntil?: string | null;
password: string | null;
password?: string | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema

View File

@@ -12,7 +12,11 @@ const Hooks: CollectionConfig = {
hooks: {
beforeOperation: [
({ operation, req }) => {
if (typeof req.payload.db.beginTransaction === 'function' && !req.transactionID && ['create', 'delete', 'update'].includes(operation)) {
if (
typeof req.payload.db.beginTransaction === 'function' &&
!req.transactionID &&
['create', 'delete', 'update'].includes(operation)
) {
throw new Error('transactionID is missing in beforeOperation hook')
}
},

View File

@@ -23,8 +23,8 @@
},
"devDependencies": {
"@aws-sdk/client-s3": "^3.525.0",
"@lexical/headless": "0.16.1",
"@lexical/markdown": "0.16.1",
"@lexical/headless": "0.17.0",
"@lexical/markdown": "0.17.0",
"@payloadcms/db-mongodb": "workspace:*",
"@payloadcms/db-postgres": "workspace:*",
"@payloadcms/db-sqlite": "workspace:*",
@@ -68,7 +68,7 @@
"file-type": "17.1.6",
"http-status": "1.6.2",
"jwt-decode": "4.0.0",
"lexical": "0.16.1",
"lexical": "0.17.0",
"payload": "workspace:*",
"qs-esm": "7.0.2",
"server-only": "^0.0.1",