Files
payloadcms/packages/richtext-lexical/src/features/upload/client/nodes/UploadNode.tsx
Alessio Gravili bea77f2f24 refactor(richtext-lexical): new upload node design (#13901)
This changes the design of lexical upload nodes to better show the
actual media instead of the metadata.

## Updated Design


https://github.com/user-attachments/assets/49096378-35c2-4eb0-b4b6-5f138d49bdad

Light mode:

<img width="780" height="962" alt="Screenshot 2025-09-24 at 10 11 32@2x"
src="https://github.com/user-attachments/assets/7611e659-3914-46e9-9c8c-db88c180227b"
/>


## Previous Design

> Before:
> 
> <img width="1358" height="860" alt="Screenshot 2025-09-22 at 16 01
16@2x"
src="https://github.com/user-attachments/assets/7831761c-6c3c-4072-82ed-68b88e3842b7"
/>
> 
> After:
> 
> <img width="1776" height="1632" alt="Screenshot 2025-09-22 at 16 01
00@2x"
src="https://github.com/user-attachments/assets/b434b6d5-a965-4c2b-adba-c1bf2a3be4bc"
/>
> 
> 
>
https://github.com/user-attachments/assets/f2749a38-c191-4b50-a521-8f722ed42a8f
> 



---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211429812808983
2025-09-24 20:22:03 +00:00

91 lines
2.7 KiB
TypeScript

'use client'
import type { DOMConversionMap, LexicalNode } from 'lexical'
import type { JSX } from 'react'
import ObjectID from 'bson-objectid'
import { $applyNodeReplacement } from 'lexical'
import * as React from 'react'
import type {
Internal_UploadData,
SerializedUploadNode,
UploadData,
} from '../../server/nodes/UploadNode.js'
import { $convertUploadElement } from '../../server/nodes/conversions.js'
import { UploadServerNode } from '../../server/nodes/UploadNode.js'
import { PendingUploadComponent } from '../component/pending/index.js'
const RawUploadComponent = React.lazy(() =>
import('../../client/component/index.js').then((module) => ({ default: module.UploadComponent })),
)
export class UploadNode extends UploadServerNode {
static override clone(node: UploadServerNode): UploadServerNode {
return super.clone(node)
}
static override getType(): string {
return super.getType()
}
static override importDOM(): DOMConversionMap<HTMLImageElement> {
return {
img: (node) => ({
conversion: (domNode) => $convertUploadElement(domNode, $createUploadNode),
priority: 0,
}),
}
}
static override 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: Internal_UploadData = {
id: serializedNode.id,
fields: serializedNode.fields,
pending: (serializedNode as Internal_UploadData).pending,
relationTo: serializedNode.relationTo,
value: serializedNode.value,
}
const node = $createUploadNode({ data: importedData })
node.setFormat(serializedNode.format)
return node
}
override decorate(): JSX.Element {
if ((this.__data as Internal_UploadData).pending) {
return <PendingUploadComponent />
}
return <RawUploadComponent data={this.__data} format={this.__format} nodeKey={this.getKey()} />
}
override 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
}