[lexical-playground] Feature: Add file attachment support with pluggable storage abstraction#8084
[lexical-playground] Feature: Add file attachment support with pluggable storage abstraction#8084Yjason-K wants to merge 8 commits intofacebook:mainfrom
Conversation
- Created AttachmentNode and AttachmentPlugin to handle file attachments. - Added AttachmentComponent for rendering file previews with metadata (name, size, type). - Integrated attachment insertion into the toolbar and drag-and-drop/paste workflow. - Supported various file types including PDFs, documents, spreadsheets, videos, and audio. - Implemented base64 serialization for non-URL-based attachments to ensure persistence in JSON. - Added a 3MB file size limit for playground attachments. - Added styling and a new paperclip icon for the attachment UI.
- Add AttachmentStore and DemoAttachmentStore for managed file handling. - Introduce AttachmentStoreContext for global store access. - Update AttachmentNode to include attachmentId for reference-based storage. - Update plugins to utilize the store for file uploads and serialization.
- Inserting attachments via the toolbar. - Selection and floating toolbar visibility. - File download functionality from the floating toolbar. - Keyboard navigation (arrow keys) around attachment nodes. - Deletion via Backspace, Delete keys, and the toolbar button. - Copy/Paste support for attachment nodes. - Multiple attachment management. - Proper rendering and HTML structure verification.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
A bit related, in my image component / image upload experiments (for GitHub repo as a storage), I implemented the following design:
I think this kind of abstraction (temporary and final URLs) can be useful for other image/media storage backends too (and can support the notion of "commit" where we don't want to upload anything if the user hasn't "committed"/"approved" or when it can take time for the uploaded document to become available for serving via final https url - true for SSG sites) Also, for practical dealing with images, some way for installing preprocessing hooks can be needed: e.g. resizing in multiple sizes before uploading to the static storage |
|
Hi, @vadimkantorov, I also implemented a similar image loading mechanism and for this I added a callback that communicates between the editor and my main application which implements the interface for user commits changes. Here's what the interface for such a callback might look like: type FileLoadCallback = (
// File object from URL.createObjectURL or FileReader
file: File,
// Signal about deleting a file from the lexical document
signal: AbortSignal,
// Callback for successful file upload to the server
onSuccess: (
// The final source to the uploaded file on the server (S3, Cloudflare R2, etc.)
src: string
) => Promise<void>,
// Callback for failed file upload
onError: (
// Error message
error: string
) => Promise<void>
) => void;It can be called every time an image or other resource is added to the document, and I think I'm interested in this PR, so maybe my idea will help you come up with concrete ways to improve @Yjason-K implementation |
|
@levensta If you feel like checking my design, it's at https://github.com/vadimkantorov/moncms One nugget is I have a "url resolver" hook, so that the url in object model (e.g. relative image urls in markdown) can be dynamically resolved into a real servable image url (in the simplest case it means just prepending a prefix) In https://github.com/vadimkantorov/moncms/blob/master/src/main.tsx it's implemented in the |
etrepum
left a comment
There was a problem hiding this comment.
I haven't done a close look at this PR because it has not had a successful e2e test run
|
It still doesn't pass tests, something in here is probably not backwards compatible. |
- Add waitForSelector in insertAttachment test helper to prevent race condition when modal has not fully mounted before file input interaction - Fix showFloatingToolbar toggle bug: clicking attachment toggled toolbar off immediately (true→false) instead of showing it; now always sets true on select - Add KEY_BACKSPACE_COMMAND handler alongside KEY_DELETE_COMMAND for consistent deletion behavior - Use showFloatingToolbar state in render condition so dismiss actually works - Lazy-initialize drag image element to avoid module-scope document access
|
Doesn't look like those changes fixed the failures |
Description
This PR is a revised implementation of #7895, addressing the feedback about storage architecture.
Changes from previous PR (#7895)
Feedback addressed:
AttachmentStoreinterface allowing custom backend implementations (S3, Cloudflare R2, etc.)New architecture:
AttachmentStoreinterface withupload(),delete(),getUrl(),toBase64()methodsDemoAttachmentStore: Default blob URL implementation for playground demoAttachmentStoreContext: React context for dependency injectionattachmentIdfield for store reference instead of embedding file dataserializeAsBase64option for self-contained export when neededCore Implementation (from #7895)
Test plan
Storage Abstraction
DemoAttachmentStorecreates blob URLs for session-scoped filesattachmentIdproperly persisted in serializationAttachmentStoreProviderFile Upload & Display
Serialization
E2E Test Coverage
Related
Closes #7895 (supersedes with improved architecture)