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

80 lines
2.6 KiB
TypeScript

import type { FileData, PayloadRequest, TypeWithID } from 'payload'
import { getTranslation, type I18nClient } from '@payloadcms/translations'
import './index.scss'
import type { HTMLConvertersAsync } from '../../../../features/converters/lexicalToHtml/async/types.js'
import type { SerializedRelationshipNode } from '../../../../nodeTypes.js'
const baseClass = 'lexical-relationship-diff'
export const RelationshipDiffHTMLConverterAsync: (args: {
i18n: I18nClient
req: PayloadRequest
}) => HTMLConvertersAsync<SerializedRelationshipNode> = ({ i18n, req }) => {
return {
relationship: async ({ node, populate, providedCSSString }) => {
let data: (Record<string, any> & TypeWithID) | undefined = undefined
// If there's no valid upload data, populate return an empty string
if (typeof node.value !== 'object') {
if (!populate) {
return ''
}
data = await populate<FileData & TypeWithID>({
id: node.value,
collectionSlug: node.relationTo,
})
} else {
data = node.value as unknown as FileData & TypeWithID
}
const relatedCollection = req.payload.collections[node.relationTo]?.config
const ReactDOMServer = (await import('react-dom/server')).default
const JSX = (
<div
className={`${baseClass}${providedCSSString}`}
data-enable-match="true"
data-id={node.value}
data-slug={node.relationTo}
>
<div className={`${baseClass}__card`}>
<div className={`${baseClass}__collectionLabel`}>
{i18n.t('fields:labelRelationship', {
label: relatedCollection?.labels?.singular
? getTranslation(relatedCollection?.labels?.singular, i18n)
: relatedCollection?.slug,
})}
</div>
{data &&
relatedCollection?.admin?.useAsTitle &&
data[relatedCollection.admin.useAsTitle] ? (
<strong className={`${baseClass}__title`} data-enable-match="false">
<a
className={`${baseClass}__link`}
data-enable-match="false"
href={`/${relatedCollection.slug}/${data.id}`}
rel="noopener noreferrer"
target="_blank"
>
{data[relatedCollection.admin.useAsTitle]}
</a>
</strong>
) : (
<strong>{node.value as string}</strong>
)}
</div>
</div>
)
// Render to HTML
const html = ReactDOMServer.renderToString(JSX)
return html
},
}
}