Files
payload/packages/richtext-lexical/src/field/Diff/converters/link.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

60 lines
2.0 KiB
TypeScript

import { createHash } from 'crypto'
import type {
HTMLConvertersAsync,
HTMLPopulateFn,
} from '../../../features/converters/lexicalToHtml/async/types.js'
import type { SerializedAutoLinkNode, SerializedLinkNode } from '../../../nodeTypes.js'
export const LinkDiffHTMLConverterAsync: (args: {
internalDocToHref?: (args: {
linkNode: SerializedLinkNode
populate?: HTMLPopulateFn
}) => Promise<string> | string
}) => HTMLConvertersAsync<SerializedAutoLinkNode | SerializedLinkNode> = ({
internalDocToHref,
}) => ({
autolink: async ({ node, nodesToHTML, providedStyleTag }) => {
const children = (
await nodesToHTML({
nodes: node.children,
})
).join('')
// hash fields to ensure they are diffed if they change
const nodeFieldsHash = createHash('sha256').update(JSON.stringify(node.fields)).digest('hex')
return `<a${providedStyleTag} data-fields-hash="${nodeFieldsHash}" data-enable-match="true" href="${node.fields.url}"${node.fields.newTab ? ' rel="noopener noreferrer" target="_blank"' : ''}>
${children}
</a>`
},
link: async ({ node, nodesToHTML, populate, providedStyleTag }) => {
const children = (
await nodesToHTML({
nodes: node.children,
})
).join('')
let href: string = node.fields.url ?? ''
if (node.fields.linkType === 'internal') {
if (internalDocToHref) {
href = await internalDocToHref({ linkNode: node, populate })
} else {
console.error(
'Lexical => HTML converter: Link converter: found internal link, but internalDocToHref is not provided',
)
href = '#' // fallback
}
}
// hash fields to ensure they are diffed if they change
const nodeFieldsHash = createHash('sha256')
.update(JSON.stringify(node.fields ?? {}))
.digest('hex')
return `<a${providedStyleTag} data-fields-hash="${nodeFieldsHash}" data-enable-match="true" href="${href}"${node.fields.newTab ? ' rel="noopener noreferrer" target="_blank"' : ''}>
${children}
</a>`
},
})