diff --git a/.github/workflows/e2e.config.ts b/.github/workflows/e2e.config.ts
index 4f6587c18ac..6097ab42cc5 100644
--- a/.github/workflows/e2e.config.ts
+++ b/.github/workflows/e2e.config.ts
@@ -79,7 +79,6 @@ export default createE2EConfig([
{ file: 'lexical__collections__Lexical__e2e__main', shards: 2 },
{ file: 'lexical__collections__Lexical__e2e__blocks', shards: 2 },
{ file: 'lexical__collections__Lexical__e2e__blocks#config.blockreferences.ts', shards: 2 },
- { file: 'lexical__collections__RichText', shards: 1 },
{ file: 'query-presets', shards: 1 },
{ file: 'form-state', shards: 1 },
{ file: 'live-preview', shards: 2 },
diff --git a/packages/richtext-lexical/src/features/blockquote/server/index.ts b/packages/richtext-lexical/src/features/blockquote/server/index.ts
index 79712446816..9b68d6489a6 100644
--- a/packages/richtext-lexical/src/features/blockquote/server/index.ts
+++ b/packages/richtext-lexical/src/features/blockquote/server/index.ts
@@ -6,7 +6,6 @@ import { QuoteNode } from '@lexical/rich-text'
import type { StronglyTypedElementNode } from '../../../nodeTypes.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
-import { convertLexicalNodesToHTML } from '../../converters/lexicalToHtml_deprecated/converter/index.js'
import { createNode } from '../../typeUtilities.js'
import { MarkdownTransformer } from '../markdownTransformer.js'
import { i18n } from './i18n.js'
@@ -22,49 +21,6 @@ export const BlockquoteFeature = createServerFeature({
markdownTransformers: [MarkdownTransformer],
nodes: [
createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
- const style = [
- node.format ? `text-align: ${node.format};` : '',
- // the unit should be px. Do not change it to rem, em, or something else.
- // The quantity should be 40px. Do not change it either.
- // See rationale in
- // https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085
- node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '',
- ]
- .filter(Boolean)
- .join(' ')
-
- return `
${childrenText}
`
- },
- nodeTypes: [QuoteNode.getType()],
- },
- },
node: QuoteNode,
}),
],
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/linebreak.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/linebreak.ts
deleted file mode 100644
index 281d76c31c4..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/linebreak.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { SerializedLineBreakNode } from '../../../../../nodeTypes.js'
-import type { HTMLConverter } from '../types.js'
-
-export const LinebreakHTMLConverter: HTMLConverter = {
- converter() {
- return `
`
- },
- nodeTypes: ['linebreak'],
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/paragraph.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/paragraph.ts
deleted file mode 100644
index 5ce159e656c..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/paragraph.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { SerializedParagraphNode } from '../../../../../nodeTypes.js'
-import type { HTMLConverter } from '../types.js'
-
-import { convertLexicalNodesToHTML } from '../index.js'
-
-export const ParagraphHTMLConverter: HTMLConverter = {
- async converter({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
- const style = [
- node.format ? `text-align: ${node.format};` : '',
- // the unit should be px. Do not change it to rem, em, or something else.
- // The quantity should be 40px. Do not change it either.
- // See rationale in
- // https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085
- node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '',
- ]
- .filter(Boolean)
- .join(' ')
- return `${childrenText}
`
- },
- nodeTypes: ['paragraph'],
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/tab.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/tab.ts
deleted file mode 100644
index b4f4b5e8374..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/tab.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import type { SerializedTabNode } from '../../../../../nodeTypes.js'
-import type { HTMLConverter } from '../types.js'
-
-export const TabHTMLConverter: HTMLConverter = {
- converter({ node }) {
- return node.text
- },
- nodeTypes: ['tab'],
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/text.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/text.ts
deleted file mode 100644
index 854db6739ac..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/converters/text.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import escapeHTML from 'escape-html'
-
-import type { SerializedTextNode } from '../../../../../nodeTypes.js'
-import type { HTMLConverter } from '../types.js'
-
-import { NodeFormat } from '../../../../../lexical/utils/nodeFormat.js'
-
-export const TextHTMLConverter: HTMLConverter = {
- converter({ node }) {
- let text = escapeHTML(node.text)
-
- if (node.format & NodeFormat.IS_BOLD) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_ITALIC) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_STRIKETHROUGH) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_UNDERLINE) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_CODE) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_SUBSCRIPT) {
- text = `${text}`
- }
- if (node.format & NodeFormat.IS_SUPERSCRIPT) {
- text = `${text}`
- }
-
- return text
- },
- nodeTypes: ['text'],
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/defaultConverters.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/defaultConverters.ts
deleted file mode 100644
index 2b324514e2c..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/defaultConverters.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { HTMLConverter } from './types.js'
-
-import { LinebreakHTMLConverter } from './converters/linebreak.js'
-import { ParagraphHTMLConverter } from './converters/paragraph.js'
-import { TabHTMLConverter } from './converters/tab.js'
-import { TextHTMLConverter } from './converters/text.js'
-
-/**
- * @deprecated - will be removed in 4.0
- */
-export const defaultHTMLConverters: HTMLConverter[] = [
- ParagraphHTMLConverter,
- TextHTMLConverter,
- LinebreakHTMLConverter,
- TabHTMLConverter,
-]
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/index.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/index.ts
deleted file mode 100644
index 044f6f61d5f..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/index.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-import type { SerializedEditorState, SerializedLexicalNode } from 'lexical'
-import type { Payload, PayloadRequest } from 'payload'
-
-import { createLocalReq } from 'payload'
-
-import type { HTMLConverter, SerializedLexicalNodeWithParent } from './types.js'
-
-import { hasText } from '../../../../validate/hasText.js'
-
-/**
- * @deprecated - will be removed in 4.0
- */
-export type ConvertLexicalToHTMLArgs = {
- converters: HTMLConverter[]
- currentDepth?: number
- data: SerializedEditorState
- depth?: number
- draft?: boolean // default false
- overrideAccess?: boolean // default false
- showHiddenFields?: boolean // default false
-} & (
- | {
- /**
- * This payload property will only be used if req is undefined.
- */
- payload?: never
- /**
- * When the converter is called, req CAN be passed in depending on where it's run.
- * If this is undefined and config is passed through, lexical will create a new req object for you. If this is null or
- * config is undefined, lexical will not create a new req object for you and local API / server-side-only
- * functionality will be disabled.
- */
- req: PayloadRequest
- }
- | {
- /**
- * This payload property will only be used if req is undefined.
- */
- payload?: Payload
- /**
- * When the converter is called, req CAN be passed in depending on where it's run.
- * If this is undefined and config is passed through, lexical will create a new req object for you. If this is null or
- * config is undefined, lexical will not create a new req object for you and local API / server-side-only
- * functionality will be disabled.
- */
- req?: null | undefined
- }
-)
-
-/**
- * @deprecated - will be removed in 4.0. Use the function exported from `@payloadcms/richtext-lexical/html` instead.
- * @example
- * ```ts
- * // old (deprecated)
- * import { convertLexicalToHTML } from '@payloadcms/richtext-lexical'
- * // new (recommended)
- * import { convertLexicalToHTML } from '@payloadcms/richtext-lexical/html'
- * ```
- * For more details, you can refer to https://payloadcms.com/docs/rich-text/converting-html to see all the
- * ways to convert lexical to HTML.
- */
-export async function convertLexicalToHTML({
- converters,
- currentDepth,
- data,
- depth,
- draft,
- overrideAccess,
- payload,
- req,
- showHiddenFields,
-}: ConvertLexicalToHTMLArgs): Promise {
- if (hasText(data)) {
- if (req === undefined && payload) {
- req = await createLocalReq({}, payload)
- }
-
- if (!currentDepth) {
- currentDepth = 0
- }
-
- if (!depth) {
- depth = req?.payload?.config?.defaultDepth
- }
-
- return await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth: depth!,
- draft: draft === undefined ? false : draft,
- lexicalNodes: data?.root?.children,
- overrideAccess: overrideAccess === undefined ? false : overrideAccess,
- parent: data?.root,
- req: req!,
- showHiddenFields: showHiddenFields === undefined ? false : showHiddenFields,
- })
- }
- return ''
-}
-
-/**
- * @deprecated - will be removed in 4.0
- */
-export async function convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
-}: {
- converters: HTMLConverter[]
- currentDepth: number
- depth: number
- draft: boolean
- lexicalNodes: SerializedLexicalNode[]
- overrideAccess: boolean
- parent: SerializedLexicalNodeWithParent
- /**
- * When the converter is called, req CAN be passed in depending on where it's run.
- */
- req: null | PayloadRequest
- showHiddenFields: boolean
-}): Promise {
- const unknownConverter = converters.find((converter) => converter.nodeTypes.includes('unknown'))
-
- const htmlArray = await Promise.all(
- lexicalNodes.map(async (node, i) => {
- const converterForNode = converters.find((converter) =>
- converter.nodeTypes.includes(node.type),
- )
- try {
- if (!converterForNode) {
- if (unknownConverter) {
- return await unknownConverter.converter({
- childIndex: i,
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- })
- }
- return 'unknown node'
- }
- return await converterForNode.converter({
- childIndex: i,
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- })
- } catch (error) {
- console.error('Error converting lexical node to HTML:', error, 'node:', node)
- return ''
- }
- }),
- )
-
- return htmlArray.join('') || ''
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/types.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/types.ts
deleted file mode 100644
index 238873909d9..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/converter/types.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import type { SerializedLexicalNode } from 'lexical'
-import type { PayloadRequest } from 'payload'
-
-/**
- * @deprecated - will be removed in 4.0
- */
-export type HTMLConverter = {
- converter: (args: {
- childIndex: number
- converters: HTMLConverter[]
- currentDepth: number
- depth: number
- draft: boolean
- node: T
- overrideAccess: boolean
- parent: SerializedLexicalNodeWithParent
- /**
- * When the converter is called, req CAN be passed in depending on where it's run.
- */
- req: null | PayloadRequest
- showHiddenFields: boolean
- }) => Promise | string
- nodeTypes: string[]
-}
-
-export type SerializedLexicalNodeWithParent = {
- parent?: SerializedLexicalNode
-} & SerializedLexicalNode
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/field/index.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/field/index.ts
deleted file mode 100644
index 059faaf57f8..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/field/index.ts
+++ /dev/null
@@ -1,185 +0,0 @@
-import type { SerializedEditorState } from 'lexical'
-import type { Field } from 'payload'
-
-import type { SanitizedServerEditorConfig } from '../../../../lexical/config/types.js'
-import type { LexicalRichTextAdapter, LexicalRichTextField } from '../../../../types.js'
-import type { HTMLConverter } from '../converter/types.js'
-import type { HTMLConverterFeatureProps } from '../index.js'
-
-import { defaultHTMLConverters } from '../converter/defaultConverters.js'
-import { convertLexicalToHTML } from '../converter/index.js'
-
-type Args = {
- /**
- * Whether the lexicalHTML field should be hidden in the admin panel
- *
- * @default true
- */
- hidden?: boolean
- name: string
- /**
- * Whether the HTML should be stored in the database
- *
- * @default false
- */
- storeInDB?: boolean
-}
-
-/**
- * Combines the default HTML converters with HTML converters found in the features, and with HTML converters configured in the htmlConverter feature.
- *
- * @deprecated - will be removed in 4.0
- * @param editorConfig
- */
-export const consolidateHTMLConverters = ({
- editorConfig,
-}: {
- editorConfig: SanitizedServerEditorConfig
-}): HTMLConverter[] => {
- const htmlConverterFeature = editorConfig.resolvedFeatureMap.get('htmlConverter')
- const htmlConverterFeatureProps: HTMLConverterFeatureProps =
- htmlConverterFeature?.sanitizedServerFeatureProps
-
- const defaultConvertersWithConvertersFromFeatures = [...defaultHTMLConverters]
-
- for (const converter of editorConfig.features.converters.html) {
- defaultConvertersWithConvertersFromFeatures.push(converter)
- }
-
- const finalConverters =
- htmlConverterFeatureProps?.converters &&
- typeof htmlConverterFeatureProps?.converters === 'function'
- ? htmlConverterFeatureProps.converters({
- defaultConverters: defaultConvertersWithConvertersFromFeatures,
- })
- : (htmlConverterFeatureProps?.converters as HTMLConverter[]) ||
- defaultConvertersWithConvertersFromFeatures
-
- // filter converters by nodeTypes. The last converter in the list wins. If there are multiple converters for the same nodeType, the last one will be used and the node types will be removed from
- // previous converters. If previous converters do not have any nodeTypes left, they will be removed from the list.
- // This guarantees that user-added converters which are added after the default ones will always have precedence
- const foundNodeTypes: string[] = []
- const filteredConverters: HTMLConverter[] = []
- for (const converter of finalConverters.reverse()) {
- if (!converter.nodeTypes?.length) {
- continue
- }
- const newConverter: HTMLConverter = {
- converter: converter.converter,
- nodeTypes: [...converter.nodeTypes],
- }
- newConverter.nodeTypes = newConverter.nodeTypes.filter((nodeType) => {
- if (foundNodeTypes.includes(nodeType)) {
- return false
- }
- foundNodeTypes.push(nodeType)
- return true
- })
-
- if (newConverter.nodeTypes.length) {
- filteredConverters.push(newConverter)
- }
- }
-
- return filteredConverters
-}
-
-/**
- * @deprecated - will be removed in 4.0
- */
-export const lexicalHTML: (
- /**
- * A string which matches the lexical field name you want to convert to HTML.
- *
- * This has to be a sibling field of this lexicalHTML field - otherwise, it won't be able to find the lexical field.
- **/
- lexicalFieldName: string,
- args: Args,
-) => Field = (lexicalFieldName, args) => {
- const { name = 'lexicalHTML', hidden = true, storeInDB = false } = args
- return {
- name,
- type: 'code',
- admin: {
- editorOptions: {
- language: 'html',
- },
- hidden,
- },
- hooks: {
- afterRead: [
- async ({
- currentDepth,
- depth,
- draft,
- field,
- overrideAccess,
- req,
- showHiddenFields,
- siblingData,
- siblingFields,
- }) => {
- if (!siblingFields) {
- throw new Error(
- `Could not find sibling fields of current lexicalHTML field with name ${field?.name}`,
- )
- }
-
- const lexicalField: LexicalRichTextField = siblingFields.find(
- (field) => 'name' in field && field.name === lexicalFieldName,
- ) as LexicalRichTextField
-
- const lexicalFieldData: SerializedEditorState = siblingData[lexicalFieldName]
-
- if (!lexicalFieldData) {
- return ''
- }
-
- if (!lexicalField) {
- throw new Error(
- 'You cannot use the lexicalHTML field because the referenced lexical field was not found',
- )
- }
-
- const config = (lexicalField?.editor as LexicalRichTextAdapter)?.editorConfig
-
- if (!config) {
- throw new Error(
- 'The linked lexical field does not have an editorConfig. This is needed for the lexicalHTML field.',
- )
- }
-
- if (!config?.resolvedFeatureMap?.has('htmlConverter')) {
- throw new Error(
- 'You cannot use the lexicalHTML field because the linked lexical field does not have a HTMLConverterFeature',
- )
- }
-
- const finalConverters = consolidateHTMLConverters({
- editorConfig: config,
- })
-
- return await convertLexicalToHTML({
- converters: finalConverters,
- currentDepth,
- data: lexicalFieldData,
- depth,
- draft,
- overrideAccess,
- req,
- showHiddenFields,
- })
- },
- ],
- beforeChange: [
- ({ siblingData, value }) => {
- if (storeInDB) {
- return value
- }
- delete siblingData[name]
- return null
- },
- ],
- },
- }
-}
diff --git a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/index.ts b/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/index.ts
deleted file mode 100644
index 6f58bbe22d3..00000000000
--- a/packages/richtext-lexical/src/features/converters/lexicalToHtml_deprecated/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { HTMLConverter } from './converter/types.js'
-
-import { createServerFeature } from '../../../utilities/createServerFeature.js'
-
-export type HTMLConverterFeatureProps = {
- converters?:
- | (({ defaultConverters }: { defaultConverters: HTMLConverter[] }) => HTMLConverter[])
- | HTMLConverter[]
-}
-
-// This is just used to save the props on the richText field
-/**
- * @deprecated - will be removed in 4.0. Please refer to https://payloadcms.com/docs/rich-text/converting-html
- * to see all the ways to convert lexical to HTML.
- */
-export const HTMLConverterFeature = createServerFeature({
- feature: {},
- key: 'htmlConverter',
-})
diff --git a/packages/richtext-lexical/src/features/experimental_table/server/index.ts b/packages/richtext-lexical/src/features/experimental_table/server/index.ts
index c1648b9b762..2cea887538f 100644
--- a/packages/richtext-lexical/src/features/experimental_table/server/index.ts
+++ b/packages/richtext-lexical/src/features/experimental_table/server/index.ts
@@ -12,7 +12,6 @@ import { sanitizeFields } from 'payload'
import type { StronglyTypedElementNode } from '../../../nodeTypes.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
-import { convertLexicalNodesToHTML } from '../../converters/lexicalToHtml_deprecated/converter/index.js'
import { createNode } from '../../typeUtilities.js'
import { TableMarkdownTransformer } from '../markdownTransformer.js'
@@ -64,117 +63,12 @@ export const EXPERIMENTAL_TableFeature = createServerFeature({
markdownTransformers: [TableMarkdownTransformer],
nodes: [
createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
- return ``
- },
- nodeTypes: [TableNode.getType()],
- },
- },
node: TableNode,
}),
createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
-
- 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 && node.colSpan > 1 ? `colspan="${node.colSpan}"` : ''
- const rowSpan = node.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,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
- return `${childrenText}
`
- },
- nodeTypes: [TableRowNode.getType()],
- },
- },
node: TableRowNode,
}),
],
diff --git a/packages/richtext-lexical/src/features/heading/server/index.ts b/packages/richtext-lexical/src/features/heading/server/index.ts
index f8ff617b637..0e226f4131e 100644
--- a/packages/richtext-lexical/src/features/heading/server/index.ts
+++ b/packages/richtext-lexical/src/features/heading/server/index.ts
@@ -9,7 +9,6 @@ import { HeadingNode } from '@lexical/rich-text'
import type { StronglyTypedElementNode } from '../../../nodeTypes.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
-import { convertLexicalNodesToHTML } from '../../converters/lexicalToHtml_deprecated/converter/index.js'
import { createNode } from '../../typeUtilities.js'
import { MarkdownTransformer } from '../markdownTransformer.js'
import { i18n } from './i18n.js'
@@ -43,48 +42,6 @@ export const HeadingFeature = createServerFeature<
enabledHeadingSizes.length > 0 ? [MarkdownTransformer(enabledHeadingSizes)] : [],
nodes: [
createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
- const style = [
- node.format ? `text-align: ${node.format};` : '',
- // the unit should be px. Do not change it to rem, em, or something else.
- // The quantity should be 40px. Do not change it either.
- // See rationale in
- // https://github.com/payloadcms/payload/issues/13130#issuecomment-3058348085
- node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '',
- ]
- .filter(Boolean)
- .join(' ')
- return `<${node?.tag}${style ? ` style='${style}'` : ''}>${childrenText}${node?.tag}>`
- },
- nodeTypes: [HeadingNode.getType()],
- },
- },
node: HeadingNode,
}),
],
diff --git a/packages/richtext-lexical/src/features/horizontalRule/server/index.ts b/packages/richtext-lexical/src/features/horizontalRule/server/index.ts
index 387630ee66a..2a4971613e0 100644
--- a/packages/richtext-lexical/src/features/horizontalRule/server/index.ts
+++ b/packages/richtext-lexical/src/features/horizontalRule/server/index.ts
@@ -11,14 +11,6 @@ export const HorizontalRuleFeature = createServerFeature({
markdownTransformers: [MarkdownTransformer],
nodes: [
createNode({
- converters: {
- html: {
- converter: () => {
- return `
`
- },
- nodeTypes: [HorizontalRuleServerNode.getType()],
- },
- },
node: HorizontalRuleServerNode,
}),
],
diff --git a/packages/richtext-lexical/src/features/link/server/index.ts b/packages/richtext-lexical/src/features/link/server/index.ts
index 41badcc976a..7db1a7853d7 100644
--- a/packages/richtext-lexical/src/features/link/server/index.ts
+++ b/packages/richtext-lexical/src/features/link/server/index.ts
@@ -7,7 +7,6 @@ import type {
SanitizedConfig,
} from 'payload'
-import escapeHTML from 'escape-html'
import { sanitizeFields } from 'payload'
import type { NodeWithHooks } from '../../typesServer.js'
@@ -15,7 +14,6 @@ import type { ClientProps } from '../client/index.js'
import type { SerializedLinkNode } from '../nodes/types.js'
import { createServerFeature } from '../../../utilities/createServerFeature.js'
-import { convertLexicalNodesToHTML } from '../../converters/lexicalToHtml_deprecated/converter/index.js'
import { createNode } from '../../typeUtilities.js'
import { createLinkMarkdownTransformer } from '../markdownTransformer.js'
import { AutoLinkNode } from '../nodes/AutoLinkNode.js'
@@ -172,90 +170,11 @@ export const LinkFeature = createServerFeature<
props?.disableAutoLinks === true
? null
: createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
-
- let href: string = node.fields.url ?? ''
- if (node.fields.linkType === 'internal') {
- href =
- typeof node.fields.doc?.value !== 'object'
- ? String(node.fields.doc?.value)
- : String(node.fields.doc?.value?.id)
- }
-
- return `${childrenText}`
- },
- nodeTypes: [AutoLinkNode.getType()],
- },
- },
node: AutoLinkNode,
// Since AutoLinkNodes are just internal links, they need no hooks or graphQL population promises
validations: [linkValidation(props, sanitizedFieldsWithoutText)],
}),
createNode({
- converters: {
- html: {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
-
- const href: string =
- node.fields.linkType === 'custom'
- ? escapeHTML(node.fields.url)
- : (node.fields.doc?.value as string)
-
- return `${childrenText}`
- },
- nodeTypes: [LinkNode.getType()],
- },
- },
getSubFields: () => {
return sanitizedFieldsWithoutText
},
diff --git a/packages/richtext-lexical/src/features/lists/checklist/server/index.ts b/packages/richtext-lexical/src/features/lists/checklist/server/index.ts
index dd36e27babc..e78713e7f58 100644
--- a/packages/richtext-lexical/src/features/lists/checklist/server/index.ts
+++ b/packages/richtext-lexical/src/features/lists/checklist/server/index.ts
@@ -2,7 +2,6 @@ import { ListItemNode, ListNode } from '@lexical/list'
import { createServerFeature } from '../../../../utilities/createServerFeature.js'
import { createNode } from '../../../typeUtilities.js'
-import { ListHTMLConverter, ListItemHTMLConverter } from '../../htmlConverter.js'
import { shouldRegisterListBaseNodes } from '../../shared/shouldRegisterListBaseNodes.js'
import { CHECK_LIST } from '../markdownTransformers.js'
import { i18n } from './i18n.js'
@@ -16,15 +15,9 @@ export const ChecklistFeature = createServerFeature({
nodes: shouldRegisterListBaseNodes('checklist', featureProviderMap)
? [
createNode({
- converters: {
- html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
- },
node: ListNode,
}),
createNode({
- converters: {
- html: ListItemHTMLConverter as any,
- },
node: ListItemNode,
}),
]
diff --git a/packages/richtext-lexical/src/features/lists/htmlConverter.ts b/packages/richtext-lexical/src/features/lists/htmlConverter.ts
deleted file mode 100644
index b6e5c7631cb..00000000000
--- a/packages/richtext-lexical/src/features/lists/htmlConverter.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { ListItemNode, ListNode } from '@lexical/list'
-import { v4 as uuidv4 } from 'uuid'
-
-import type { HTMLConverter } from '../converters/lexicalToHtml_deprecated/converter/types.js'
-import type { SerializedListItemNode, SerializedListNode } from './plugin/index.js'
-
-import { convertLexicalNodesToHTML } from '../converters/lexicalToHtml_deprecated/converter/index.js'
-
-export const ListHTMLConverter: HTMLConverter = {
- converter: async ({
- converters,
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- parent,
- req,
- showHiddenFields,
- }) => {
- const childrenText = await convertLexicalNodesToHTML({
- converters,
- currentDepth,
- depth,
- draft,
- lexicalNodes: node.children,
- overrideAccess,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
-
- return `<${node?.tag} class="list-${node?.listType}">${childrenText}${node?.tag}>`
- },
- nodeTypes: [ListNode.getType()],
-}
-
-export const ListItemHTMLConverter: HTMLConverter = {
- 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,
- parent: {
- ...node,
- parent,
- },
- req,
- showHiddenFields,
- })
-
- if ('listType' in parent && parent?.listType === 'check') {
- const uuid = uuidv4()
-
- return `
- ${
- hasSubLists
- ? childrenText
- : `
-
-
- `
- }
-
-
- `
- } else {
- return `${childrenText}`
- }
- },
- nodeTypes: [ListItemNode.getType()],
-}
diff --git a/packages/richtext-lexical/src/features/lists/orderedList/server/index.ts b/packages/richtext-lexical/src/features/lists/orderedList/server/index.ts
index 693fb77e331..9d84bb38cba 100644
--- a/packages/richtext-lexical/src/features/lists/orderedList/server/index.ts
+++ b/packages/richtext-lexical/src/features/lists/orderedList/server/index.ts
@@ -2,7 +2,6 @@ import { ListItemNode, ListNode } from '@lexical/list'
import { createServerFeature } from '../../../../utilities/createServerFeature.js'
import { createNode } from '../../../typeUtilities.js'
-import { ListHTMLConverter, ListItemHTMLConverter } from '../../htmlConverter.js'
import { shouldRegisterListBaseNodes } from '../../shared/shouldRegisterListBaseNodes.js'
import { ORDERED_LIST } from '../markdownTransformer.js'
import { i18n } from './i18n.js'
@@ -16,15 +15,9 @@ export const OrderedListFeature = createServerFeature({
nodes: shouldRegisterListBaseNodes('ordered', featureProviderMap)
? [
createNode({
- converters: {
- html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
- },
node: ListNode,
}),
createNode({
- converters: {
- html: ListItemHTMLConverter as any,
- },
node: ListItemNode,
}),
]
diff --git a/packages/richtext-lexical/src/features/lists/unorderedList/server/index.ts b/packages/richtext-lexical/src/features/lists/unorderedList/server/index.ts
index 6ed27d42768..2abd9d39e14 100644
--- a/packages/richtext-lexical/src/features/lists/unorderedList/server/index.ts
+++ b/packages/richtext-lexical/src/features/lists/unorderedList/server/index.ts
@@ -2,7 +2,6 @@ import { ListItemNode, ListNode } from '@lexical/list'
import { createServerFeature } from '../../../../utilities/createServerFeature.js'
import { createNode } from '../../../typeUtilities.js'
-import { ListHTMLConverter, ListItemHTMLConverter } from '../../htmlConverter.js'
import { UNORDERED_LIST } from '../markdownTransformer.js'
import { i18n } from './i18n.js'
@@ -13,15 +12,9 @@ export const UnorderedListFeature = createServerFeature({
markdownTransformers: [UNORDERED_LIST],
nodes: [
createNode({
- converters: {
- html: ListHTMLConverter as any, // ListHTMLConverter uses a different generic type than ListNode[exportJSON], thus we need to cast as any
- },
node: ListNode,
}),
createNode({
- converters: {
- html: ListItemHTMLConverter as any,
- },
node: ListItemNode,
}),
],
diff --git a/packages/richtext-lexical/src/features/typesServer.ts b/packages/richtext-lexical/src/features/typesServer.ts
index f5725dde03b..4f3eb1a2fa2 100644
--- a/packages/richtext-lexical/src/features/typesServer.ts
+++ b/packages/richtext-lexical/src/features/typesServer.ts
@@ -28,7 +28,6 @@ import type {
import type { ServerEditorConfig } from '../lexical/config/types.js'
import type { Transformer } from '../packages/@lexical/markdown/index.js'
import type { LexicalRichTextField } from '../types.js'
-import type { HTMLConverter } from './converters/lexicalToHtml_deprecated/converter/types.js'
import type { BaseClientFeatureProps } from './typesClient.js'
export type PopulationPromise = (args: {
@@ -219,18 +218,6 @@ export type BeforeValidateNodeHook = (
// Define the node with hooks that use the node's exportJSON return type
export type NodeWithHooks = {
- /**
- * Allows you to define how a node can be serialized into different formats. Currently, only supports html.
- * Markdown converters are defined in `markdownTransformers` and not here.
- *
- * @deprecated - will be removed in 4.0
- */
- converters?: {
- /**
- * @deprecated - will be removed in 4.0
- */
- html?: HTMLConverter['exportJSON']>>
- }
/**
* If a node includes sub-fields (e.g. block and link nodes), passing those subFields here will make payload
* automatically populate, run hooks, and generate component import maps for them
@@ -367,10 +354,6 @@ export type ResolvedServerFeatureMap = Map>
export type SanitizedServerFeatures = {
- /** The node types mapped to their converters */
- converters: {
- html: HTMLConverter[]
- }
/** The keys of all enabled features */
enabledFeatures: string[]
generatedTypes: {
diff --git a/packages/richtext-lexical/src/features/upload/server/index.ts b/packages/richtext-lexical/src/features/upload/server/index.ts
index f05ed19fbb1..2f95eb73b32 100644
--- a/packages/richtext-lexical/src/features/upload/server/index.ts
+++ b/packages/richtext-lexical/src/features/upload/server/index.ts
@@ -1,13 +1,4 @@
-import type {
- Config,
- Field,
- FieldSchemaMap,
- FileData,
- FileSizeImproved,
- Payload,
- TypeWithID,
- UploadCollectionSlug,
-} from 'payload'
+import type { Config, Field, FieldSchemaMap, UploadCollectionSlug } from 'payload'
import { sanitizeFields } from 'payload'
@@ -59,13 +50,6 @@ export type UploadFeatureProps = {
maxDepth?: number
} & ExclusiveUploadFeatureProps
-/**
- * Get the absolute URL for an upload URL by potentially prepending the serverURL
- */
-function getAbsoluteURL(url: string, payload: Payload): string {
- return url?.startsWith('http') ? url : (payload?.config?.serverURL || '') + url
-}
-
export const UploadFeature = createServerFeature<
UploadFeatureProps,
UploadFeatureProps,
@@ -134,109 +118,6 @@ export const UploadFeature = createServerFeature<
markdownTransformers: [UploadMarkdownTransformer],
nodes: [
createNode({
- converters: {
- html: {
- converter: async ({
- currentDepth,
- depth,
- draft,
- node,
- overrideAccess,
- req,
- showHiddenFields,
- }) => {
- // @ts-expect-error - for backwards-compatibility
- const id = node?.value?.id || node?.value
-
- if (req?.payload) {
- const uploadDocument: {
- value?: FileData & TypeWithID
- } = {}
-
- try {
- await populate({
- id,
- collectionSlug: node.relationTo,
- currentDepth,
- data: uploadDocument,
- depth,
- draft,
- key: 'value',
- overrideAccess,
- req,
- showHiddenFields,
- })
- } catch (ignored) {
- // eslint-disable-next-line no-console
- console.error(
- 'Lexical upload node HTML converter: error fetching upload file',
- ignored,
- 'Node:',
- node,
- )
- return `
`
- }
-
- const url = getAbsoluteURL(uploadDocument?.value?.url ?? '', req?.payload)
-
- const alt =
- (node.fields?.alt as string) ||
- (uploadDocument?.value as { alt?: string })?.alt ||
- ''
-
- /**
- * If the upload is not an image, return a link to the upload
- */
- if (!uploadDocument?.value?.mimeType?.startsWith('image')) {
- return `${uploadDocument.value?.filename}`
- }
-
- /**
- * If the upload is a simple image with no different sizes, return a simple img tag
- */
- if (
- !uploadDocument?.value?.sizes ||
- !Object.keys(uploadDocument?.value?.sizes).length
- ) {
- return `
`
- }
-
- /**
- * If the upload is an image with different sizes, return a picture element
- */
- let pictureHTML = ''
-
- // Iterate through each size in the data.sizes object
- for (const size in uploadDocument.value?.sizes) {
- const imageSize = uploadDocument.value.sizes[size] as FileSizeImproved
-
- // Skip if any property of the size object is null
- if (
- !imageSize.width ||
- !imageSize.height ||
- !imageSize.mimeType ||
- !imageSize.filesize ||
- !imageSize.filename ||
- !imageSize.url
- ) {
- continue
- }
- const imageSizeURL = getAbsoluteURL(imageSize?.url, req?.payload)
-
- pictureHTML += ``
- }
-
- // Add the default img tag
- pictureHTML += `
`
- pictureHTML += ''
- return pictureHTML
- } else {
- return `
`
- }
- },
- nodeTypes: [UploadServerNode.getType()],
- },
- },
getSubFields: ({ node, req }) => {
if (!node) {
let allSubFields: Field[] = []
diff --git a/packages/richtext-lexical/src/index.ts b/packages/richtext-lexical/src/index.ts
index 23e99995a73..368bf0673da 100644
--- a/packages/richtext-lexical/src/index.ts
+++ b/packages/richtext-lexical/src/index.ts
@@ -906,27 +906,7 @@ export {
export { convertHTMLToLexical } from './features/converters/htmlToLexical/index.js'
export { lexicalHTMLField } from './features/converters/lexicalToHtml/async/field/index.js'
-export { LinebreakHTMLConverter } from './features/converters/lexicalToHtml_deprecated/converter/converters/linebreak.js'
-export { ParagraphHTMLConverter } from './features/converters/lexicalToHtml_deprecated/converter/converters/paragraph.js'
-
-export { TabHTMLConverter } from './features/converters/lexicalToHtml_deprecated/converter/converters/tab.js'
-export { TextHTMLConverter } from './features/converters/lexicalToHtml_deprecated/converter/converters/text.js'
-export { defaultHTMLConverters } from './features/converters/lexicalToHtml_deprecated/converter/defaultConverters.js'
-
-export {
- convertLexicalNodesToHTML,
- convertLexicalToHTML,
-} from './features/converters/lexicalToHtml_deprecated/converter/index.js'
-export type { HTMLConverter } from './features/converters/lexicalToHtml_deprecated/converter/types.js'
-export {
- consolidateHTMLConverters,
- lexicalHTML,
-} from './features/converters/lexicalToHtml_deprecated/field/index.js'
-export {
- HTMLConverterFeature,
- type HTMLConverterFeatureProps,
-} from './features/converters/lexicalToHtml_deprecated/index.js'
export { convertLexicalToMarkdown } from './features/converters/lexicalToMarkdown/index.js'
export { convertMarkdownToLexical } from './features/converters/markdownToLexical/index.js'
export { getPayloadPopulateFn } from './features/converters/utilities/payloadPopulateFn.js'
diff --git a/packages/richtext-lexical/src/lexical/config/server/sanitize.ts b/packages/richtext-lexical/src/lexical/config/server/sanitize.ts
index d8326c83e8b..86d4aaeebde 100644
--- a/packages/richtext-lexical/src/lexical/config/server/sanitize.ts
+++ b/packages/richtext-lexical/src/lexical/config/server/sanitize.ts
@@ -12,9 +12,6 @@ export const sanitizeServerFeatures = (
features: ResolvedServerFeatureMap,
): SanitizedServerFeatures => {
const sanitized: SanitizedServerFeatures = {
- converters: {
- html: [],
- },
enabledFeatures: [],
generatedTypes: {
modifyOutputSchemas: [],
@@ -92,9 +89,6 @@ export const sanitizeServerFeatures = (
if (node?.validations?.length) {
sanitized.validations.set(nodeType, node.validations)
}
- if (node?.converters?.html) {
- sanitized.converters.html.push(node.converters.html)
- }
if (node?.hooks?.afterChange) {
sanitized.nodeHooks?.afterChange?.set(nodeType, node.hooks.afterChange)
}
diff --git a/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts b/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts
index 31868f41b86..bd9b7986365 100644
--- a/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts
+++ b/test/lexical/collections/Lexical/e2e/main/e2e.spec.ts
@@ -28,7 +28,7 @@ import { reInitializeDB } from '../../../../../__helpers/shared/clearAndSeed/reI
import { initPayloadE2ENoConfig } from '../../../../../__helpers/shared/initPayloadE2ENoConfig.js'
import { RESTClient } from '../../../../../__helpers/shared/rest.js'
import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../../../playwright.config.js'
-import { lexicalCustomCellSlug, lexicalFieldsSlug } from '../../../../slugs.js'
+import { lexicalCustomCellSlug, lexicalFieldsSlug, richTextFieldsSlug } from '../../../../slugs.js'
import { lexicalDocData } from '../../data.js'
const filename = fileURLToPath(import.meta.url)
@@ -1465,25 +1465,44 @@ describe('lexicalMain', () => {
await page.goto(`${serverURL}${href}`)
await waitForFormReady(page)
await page.getByLabel('Title*').click()
- await page.getByLabel('Title*').fill('Indent and Text-align')
+ const docTitle = 'Indent and Text-align'
+ await page.getByLabel('Title*').fill(docTitle)
await page.getByRole('paragraph').nth(1).click()
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
- const htmlContent = `paragraph centered
Heading right
paragraph without indent
paragraph indent 1
heading indent 2
quote indent 3
`
+ const pastedHTML = `paragraph centered
Heading right
paragraph without indent
paragraph indent 1
heading indent 2
quote indent 3
`
await page.evaluate(
- async ([htmlContent]) => {
- const blob = new Blob([htmlContent], { type: 'text/html' })
+ async ([html]) => {
+ const blob = new Blob([html], { type: 'text/html' })
const clipboardItem = new ClipboardItem({ 'text/html': blob })
await navigator.clipboard.write([clipboardItem])
},
- [htmlContent],
+ [pastedHTML],
)
// eslint-disable-next-line playwright/no-conditional-in-test
const pasteKey = process.platform === 'darwin' ? 'Meta' : 'Control'
await page.keyboard.press(`${pasteKey}+v`)
- await page.getByRole('button', { name: 'Save' }).click()
- await page.getByRole('link', { name: 'API' }).click()
- const htmlOutput = page.getByText(htmlContent)
- await expect(htmlOutput).toBeVisible()
+ await saveDocAndAssert(page)
+
+ const expectedHTMLFragment = `paragraph centered
Heading right
paragraph without indent
paragraph indent 1
heading indent 2
quote indent 3
`
+
+ await expect(async () => {
+ const richTextDoc = (
+ await payload.find({
+ collection: richTextFieldsSlug,
+ depth: 0,
+ overrideAccess: true,
+ where: {
+ title: {
+ equals: docTitle,
+ },
+ },
+ })
+ ).docs[0] as { lexicalCustomFields_html?: string }
+
+ expect(richTextDoc?.lexicalCustomFields_html).toContain(expectedHTMLFragment)
+ }).toPass({
+ timeout: POLL_TOPASS_TIMEOUT,
+ })
})
test('ensure lexical fields in blocks have correct value when moving blocks', async () => {
diff --git a/test/lexical/collections/Lexical/index.ts b/test/lexical/collections/Lexical/index.ts
index 8a539829f0c..3dcb04a0773 100644
--- a/test/lexical/collections/Lexical/index.ts
+++ b/test/lexical/collections/Lexical/index.ts
@@ -248,7 +248,6 @@ export const getLexicalFieldsCollection: (args: {
...defaultEditorFeatures,
//TestRecorderFeature(),
TreeViewFeature(),
- //HTMLConverterFeature(),
FixedToolbarFeature(),
LinkFeature({
fields: ({ defaultFields }) => [
diff --git a/test/lexical/collections/RichText/e2e.spec.ts b/test/lexical/collections/RichText/e2e.spec.ts
deleted file mode 100644
index 28fd0ad1212..00000000000
--- a/test/lexical/collections/RichText/e2e.spec.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import type { Page } from '@playwright/test'
-
-import { expect, test } from '@playwright/test'
-import path from 'path'
-import { fileURLToPath } from 'url'
-
-import type { Config } from '../../payload-types.js'
-
-import {
- ensureCompilationIsDone,
- initPageConsoleErrorCatch,
-} from '../../../__helpers/e2e/helpers.js'
-import { AdminUrlUtil } from '../../../__helpers/shared/adminUrlUtil.js'
-import { reInitializeDB } from '../../../__helpers/shared/clearAndSeed/reInitializeDB.js'
-import { initPayloadE2ENoConfig } from '../../../__helpers/shared/initPayloadE2ENoConfig.js'
-import { POLL_TOPASS_TIMEOUT, TEST_TIMEOUT_LONG } from '../../../playwright.config.js'
-
-const filename = fileURLToPath(import.meta.url)
-const currentFolder = path.dirname(filename)
-const dirname = path.resolve(currentFolder, '../../')
-
-const { beforeAll, beforeEach, describe } = test
-
-let page: Page
-let serverURL: string
-// If we want to make this run in parallel: test.describe.configure({ mode: 'parallel' })
-
-describe('Rich Text', () => {
- beforeAll(async ({ browser }, testInfo) => {
- testInfo.setTimeout(TEST_TIMEOUT_LONG)
- process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
- ;({ serverURL } = await initPayloadE2ENoConfig({
- dirname,
- }))
-
- const context = await browser.newContext()
- page = await context.newPage()
- initPageConsoleErrorCatch(page)
-
- await ensureCompilationIsDone({ page, serverURL })
- })
- beforeEach(async () => {
- await reInitializeDB({
- serverURL,
- snapshotKey: 'lexicalTest',
- uploadsDir: [path.resolve(dirname, './collections/Upload/uploads')],
- })
-
- await ensureCompilationIsDone({ page, serverURL })
- })
-
- describe('cell', () => {
- test('ensure cells are smaller than 300px in height', async () => {
- const url: AdminUrlUtil = new AdminUrlUtil(serverURL, 'rich-text-fields')
- await page.goto(url.list) // Navigate to rich-text list view
-
- const table = page.locator('.table-wrap .table')
- await expect(table).toBeVisible()
-
- const lexicalCell = table.locator('.cell-lexicalCustomFields').first()
- await expect(lexicalCell).toBeVisible()
-
- const lexicalHtmlCell = table.locator('.cell-lexicalCustomFields_html').first()
- await expect(lexicalHtmlCell).toBeVisible()
-
- const entireRow = table.locator('.row-1').first()
-
- // Make sure each of the 3 above are no larger than 300px in height:
- await expect
- .poll(async () => (await lexicalCell.boundingBox())?.height, {
- timeout: POLL_TOPASS_TIMEOUT,
- })
- .toBeLessThanOrEqual(300)
-
- await expect
- .poll(async () => (await lexicalHtmlCell.boundingBox())?.height, {
- timeout: POLL_TOPASS_TIMEOUT,
- })
- .toBeLessThanOrEqual(300)
-
- await expect
- .poll(async () => (await entireRow.boundingBox())?.height, { timeout: POLL_TOPASS_TIMEOUT })
- .toBeLessThanOrEqual(300)
- })
- })
-})
diff --git a/test/lexical/collections/RichText/index.ts b/test/lexical/collections/RichText/index.ts
index 0068dde8132..ccfa75e94b0 100644
--- a/test/lexical/collections/RichText/index.ts
+++ b/test/lexical/collections/RichText/index.ts
@@ -2,9 +2,8 @@ import type { CollectionConfig } from 'payload'
import {
BlocksFeature,
- HTMLConverterFeature,
lexicalEditor,
- lexicalHTML,
+ lexicalHTMLField,
LinkFeature,
TreeViewFeature,
UploadFeature,
@@ -35,7 +34,6 @@ const RichTextFields: CollectionConfig = {
features: ({ defaultFeatures }) => [
...defaultFeatures,
TreeViewFeature(),
- HTMLConverterFeature({}),
LinkFeature({
fields: ({ defaultFields }) => [
...defaultFields,
@@ -71,7 +69,22 @@ const RichTextFields: CollectionConfig = {
],
}),
},
- lexicalHTML('lexicalCustomFields', { name: 'lexicalCustomFields_html' }),
+ lexicalHTMLField({
+ htmlFieldName: 'lexicalCustomFields_html',
+ lexicalFieldName: 'lexicalCustomFields',
+ converters: ({ defaultConverters }) => ({
+ ...defaultConverters,
+ link: async ({ node, nodesToHTML, providedStyleTag }) => {
+ const children = (await nodesToHTML({ nodes: node.children })).join('')
+ const href =
+ node.fields.linkType === 'internal' ? '#' : (node.fields.url ?? '')
+ const newTabAttrs = node.fields.newTab
+ ? ' rel="noopener noreferrer" target="_blank"'
+ : ''
+ return `${children}`
+ },
+ }),
+ }),
{
name: 'lexical',
type: 'richText',