Files
payload/packages/richtext-lexical/src/features/upload/client/nodes/UploadNode.tsx
Alessio Gravili ebd43c7763 feat: pre-compile ui and richtext-lexical with react compiler (#7688)
This noticeably improves performance in the admin panel, for example
when there are multiple richtext editors on one page (& likely
performance in other areas too, though I mainly tested rich text).

The babel plugin currently only optimizes files with a 'use client'
directive at the top - thus we have to make sure to add use client
wherever possible, even if it's imported by a parent client component.

There's one single component that broke when it was compiled using the
React compiler (it stopped being reactive and failed one of our admin
e2e tests):
150808f608
opting out of it completely fixed that issue

Fixes https://github.com/payloadcms/payload/issues/7366
2024-08-19 17:31:36 -04:00

114 lines
3.3 KiB
TypeScript

'use client'
import type { SerializedDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode.js'
import type { DOMConversionMap, DOMConversionOutput, LexicalNode, Spread } from 'lexical'
import type { JSX } from 'react'
import ObjectID from 'bson-objectid'
import { $applyNodeReplacement } from 'lexical'
import * as React from 'react'
import type { UploadData } from '../../server/nodes/UploadNode.js'
import { UploadServerNode, isGoogleDocCheckboxImg } from '../../server/nodes/UploadNode.js'
const RawUploadComponent = React.lazy(() =>
import('../../client/component/index.js').then((module) => ({ default: module.UploadComponent })),
)
function $convertUploadElement(domNode: HTMLImageElement): DOMConversionOutput | null {
if (
domNode.hasAttribute('data-lexical-upload-relation-to') &&
domNode.hasAttribute('data-lexical-upload-id')
) {
const id = domNode.getAttribute('data-lexical-upload-id')
const relationTo = domNode.getAttribute('data-lexical-upload-relation-to')
if (id != null && relationTo != null) {
const node = $createUploadNode({
data: {
fields: {},
relationTo,
value: id,
},
})
return { node }
}
}
const img = domNode
if (img.src.startsWith('file:///') || isGoogleDocCheckboxImg(img)) {
return null
}
// TODO: Auto-upload functionality here!
//}
return null
}
export type SerializedUploadNode = {
children?: never // required so that our typed editor state doesn't automatically add children
type: 'upload'
} & Spread<UploadData, SerializedDecoratorBlockNode>
export class UploadNode extends UploadServerNode {
static clone(node: UploadServerNode): UploadServerNode {
return super.clone(node)
}
static getType(): string {
return super.getType()
}
static importDOM(): DOMConversionMap | null {
return {
img: (node: HTMLImageElement) => ({
conversion: $convertUploadElement,
priority: 0,
}),
}
}
static importJSON(serializedNode: SerializedUploadNode): UploadNode {
if (serializedNode.version === 1 && (serializedNode?.value as unknown as { id: string })?.id) {
serializedNode.value = (serializedNode.value as unknown as { id: string }).id
}
if (serializedNode.version === 2 && !serializedNode?.id) {
serializedNode.id = new ObjectID.default().toHexString()
serializedNode.version = 3
}
const importedData: UploadData = {
id: serializedNode.id,
fields: serializedNode.fields,
relationTo: serializedNode.relationTo,
value: serializedNode.value,
}
const node = $createUploadNode({ data: importedData })
node.setFormat(serializedNode.format)
return node
}
decorate(): JSX.Element {
return <RawUploadComponent data={this.__data} nodeKey={this.getKey()} />
}
exportJSON(): SerializedUploadNode {
return super.exportJSON()
}
}
export function $createUploadNode({
data,
}: {
data: Omit<UploadData, 'id'> & Partial<Pick<UploadData, 'id'>>
}): UploadNode {
if (!data?.id) {
data.id = new ObjectID.default().toHexString()
}
return $applyNodeReplacement(new UploadNode({ data: data as UploadData }))
}
export function $isUploadNode(node: LexicalNode | null | undefined): node is UploadNode {
return node instanceof UploadNode
}