Files
payload/packages/richtext-lexical/src/features/converters/lexicalToHtml/shared/findConverterForNode.ts
Alessio Gravili d29bdfc10f feat(next): improved lexical richText diffing in version view (#11760)
This replaces our JSON-based richtext diffing with HTML-based richtext
diffing for lexical. It uses [this HTML diff
library](https://github.com/Arman19941113/html-diff) that I then
modified to handle diffing more complex elements like links, uploads and
relationships.

This makes it way easier to spot changes, replacing the lengthy Lexical
JSON with a clean visual diff that shows exactly what's different.

## Before

![CleanShot 2025-03-18 at 13 54
51@2x](https://github.com/user-attachments/assets/811a7c14-d592-4fdc-a1f4-07eeb78255fe)


## After


![CleanShot 2025-03-31 at 18 14
10@2x](https://github.com/user-attachments/assets/efb64da0-4ff8-4965-a458-558a18375c46)
![CleanShot 2025-03-31 at 18 14
26@2x](https://github.com/user-attachments/assets/133652ce-503b-4b86-9c4c-e5c7706d8ea6)
2025-04-02 20:10:20 +00:00

103 lines
3.3 KiB
TypeScript

/* eslint-disable no-console */
import type { SerializedLexicalNode } from 'lexical'
import type { SerializedBlockNode, SerializedInlineBlockNode } from '../../../../nodeTypes.js'
import type { HTMLConverterAsync, HTMLConvertersAsync } from '../async/types.js'
import type { HTMLConverter, HTMLConverters } from '../sync/types.js'
import type { ProvidedCSS } from './types.js'
export function findConverterForNode<
TConverters extends HTMLConverters | HTMLConvertersAsync,
TConverter extends HTMLConverter | HTMLConverterAsync,
>({
converters,
disableIndent,
disableTextAlign,
node,
unknownConverter,
}: {
converters: TConverters
disableIndent?: boolean | string[]
disableTextAlign?: boolean | string[]
node: SerializedLexicalNode
unknownConverter: TConverter
}): {
converterForNode: TConverter | undefined
providedCSSString: string
providedStyleTag: string
} {
let converterForNode: TConverter | undefined
if (node.type === 'block') {
converterForNode = converters?.blocks?.[
(node as SerializedBlockNode)?.fields?.blockType
] as TConverter
if (!converterForNode && !unknownConverter) {
console.error(
`Lexical => HTML converter: Blocks converter: found ${(node as SerializedBlockNode)?.fields?.blockType} block, but no converter is provided`,
)
}
} else if (node.type === 'inlineBlock') {
converterForNode = converters?.inlineBlocks?.[
(node as SerializedInlineBlockNode)?.fields?.blockType
] as TConverter
if (!converterForNode && !unknownConverter) {
console.error(
`Lexical => HTML converter: Inline Blocks converter: found ${(node as SerializedInlineBlockNode)?.fields?.blockType} inline block, but no converter is provided`,
)
}
} else {
converterForNode = converters[node.type] as TConverter
}
const style: ProvidedCSS = {}
// Check if disableTextAlign is not true and does not include node type
if (
!disableTextAlign &&
(!Array.isArray(disableTextAlign) || !disableTextAlign?.includes(node.type))
) {
if ('format' in node && node.format) {
switch (node.format) {
case 'center':
style['text-align'] = 'center'
break
case 'end':
style['text-align'] = 'right'
break
case 'justify':
style['text-align'] = 'justify'
break
case 'left':
//style['text-align'] = 'left'
// Do nothing, as left is the default
break
case 'right':
style['text-align'] = 'right'
break
case 'start':
style['text-align'] = 'left'
break
}
}
}
if (!disableIndent && (!Array.isArray(disableIndent) || !disableIndent?.includes(node.type))) {
if ('indent' in node && node.indent && node.type !== 'listitem') {
style['padding-inline-start'] = `${Number(node.indent) * 2}em`
}
}
let providedCSSString: string = ''
for (const key of Object.keys(style)) {
// @ts-expect-error we're iterating over the keys of the object
providedCSSString += `${key}: ${style[key]};`
}
const providedStyleTag = providedCSSString?.length ? ` style="${providedCSSString}"` : ''
return {
converterForNode: converterForNode ?? unknownConverter,
providedCSSString,
providedStyleTag,
}
}