diff --git a/packages/plugin-form-builder/src/utilities/lexical/converters/heading.ts b/packages/plugin-form-builder/src/utilities/lexical/converters/heading.ts index 681a17474a0..c7b1a8a6e85 100644 --- a/packages/plugin-form-builder/src/utilities/lexical/converters/heading.ts +++ b/packages/plugin-form-builder/src/utilities/lexical/converters/heading.ts @@ -13,8 +13,13 @@ export const HeadingHTMLConverter: HTMLConverter = { }, submissionData, }) - - return '<' + node?.tag + '>' + childrenText + '' + const style = [ + node.format ? `text-align: ${node.format};` : '', + node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '', + ] + .filter(Boolean) + .join(' ') + return `<${node?.tag}${style ? ` style='${style}'` : ''}>${childrenText}` }, nodeTypes: ['heading'], } diff --git a/packages/plugin-form-builder/src/utilities/lexical/converters/quote.ts b/packages/plugin-form-builder/src/utilities/lexical/converters/quote.ts index 41d22d540eb..af63bfaddb1 100644 --- a/packages/plugin-form-builder/src/utilities/lexical/converters/quote.ts +++ b/packages/plugin-form-builder/src/utilities/lexical/converters/quote.ts @@ -13,8 +13,14 @@ export const QuoteHTMLConverter: HTMLConverter = { }, submissionData, }) + const style = [ + node.format ? `text-align: ${node.format};` : '', + node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '', + ] + .filter(Boolean) + .join(' ') - return `
${childrenText}
` + return `${childrenText}` }, nodeTypes: ['quote'], } diff --git a/packages/richtext-lexical/src/features/blockquote/server/index.ts b/packages/richtext-lexical/src/features/blockquote/server/index.ts index 2248ef3c9b5..4f4ccfc1dfd 100644 --- a/packages/richtext-lexical/src/features/blockquote/server/index.ts +++ b/packages/richtext-lexical/src/features/blockquote/server/index.ts @@ -51,8 +51,14 @@ export const BlockquoteFeature = createServerFeature({ req, showHiddenFields, }) + const style = [ + node.format ? `text-align: ${node.format};` : '', + node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '', + ] + .filter(Boolean) + .join(' ') - return `
${childrenText}
` + return `${childrenText}` }, nodeTypes: [QuoteNode.getType()], }, diff --git a/packages/richtext-lexical/src/features/converters/html/converter/converters/paragraph.ts b/packages/richtext-lexical/src/features/converters/html/converter/converters/paragraph.ts index 1f8ba4631e8..723673aa182 100644 --- a/packages/richtext-lexical/src/features/converters/html/converter/converters/paragraph.ts +++ b/packages/richtext-lexical/src/features/converters/html/converter/converters/paragraph.ts @@ -30,7 +30,13 @@ export const ParagraphHTMLConverter: HTMLConverter = { req, showHiddenFields, }) - return `

${childrenText}

` + const style = [ + node.format ? `text-align: ${node.format};` : '', + 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/heading/server/index.ts b/packages/richtext-lexical/src/features/heading/server/index.ts index 30e5cc4ff09..35ed46ada83 100644 --- a/packages/richtext-lexical/src/features/heading/server/index.ts +++ b/packages/richtext-lexical/src/features/heading/server/index.ts @@ -69,8 +69,13 @@ export const HeadingFeature = createServerFeature< req, showHiddenFields, }) - - return '<' + node?.tag + '>' + childrenText + '' + const style = [ + node.format ? `text-align: ${node.format};` : '', + node.indent > 0 ? `padding-inline-start: ${node.indent * 40}px;` : '', + ] + .filter(Boolean) + .join(' ') + return `<${node?.tag}${style ? ` style='${style}'` : ''}>${childrenText}` }, nodeTypes: [HeadingNode.getType()], }, diff --git a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts index dd35d56ab92..75bcd895a3f 100644 --- a/test/fields/collections/Lexical/e2e/main/e2e.spec.ts +++ b/test/fields/collections/Lexical/e2e/main/e2e.spec.ts @@ -1016,6 +1016,34 @@ describe('lexicalMain', () => { }) }) + // https://github.com/payloadcms/payload/issues/5146 + test('Preserve indent and text-align when converting Lexical <-> HTML', async () => { + await page.goto('http://localhost:3000/admin/collections/rich-text-fields?limit=10') + await page.getByLabel('Create new Rich Text Field').click() + await page.getByLabel('Title*').click() + await page.getByLabel('Title*').fill('Indent and Text-align') + 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
` + await page.evaluate( + async ([htmlContent]) => { + const blob = new Blob([htmlContent], { type: 'text/html' }) + const clipboardItem = new ClipboardItem({ 'text/html': blob }) + await navigator.clipboard.write([clipboardItem]) + }, + [htmlContent], + ) + // eslint-disable-next-line playwright/no-conditional-in-test + const pasteKey = process.platform === 'darwin' ? 'Meta' : 'Control' + await page.keyboard.press(`${pasteKey}+v`) + await page.locator('#field-richText').click() + await page.locator('#field-richText').fill('asd') + await page.getByRole('button', { name: 'Save' }).click() + await page.getByRole('link', { name: 'API' }).click() + const htmlOutput = page.getByText(htmlContent) + await expect(htmlOutput).toBeVisible() + }) + describe('localization', () => { test.skip('ensure simple localized lexical field works', async () => { await navigateToLexicalFields(true, true)