Add image support to rich text editor (editor.js) - #6655
Conversation
🦋 Changeset detectedLatest commit: 8e1980f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #6655 +/- ##
==========================================
+ Coverage 56.80% 56.81% +0.01%
==========================================
Files 3537 3537
Lines 73007 73031 +24
Branches 18519 18899 +380
==========================================
+ Hits 41468 41492 +24
+ Misses 30885 29620 -1265
- Partials 654 1919 +1265
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Differences FoundExpandLicense Package MIT @editorjs/image SummaryExpand
|
35f1467 to
6f93eae
Compare
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR adds inline image support to the existing Editor.js-based rich text editor by registering @editorjs/image and wiring its uploader to Saleor’s fileUpload mutation (with client-side validation), while ensuring saved image blocks also render in read-only views.
Changes:
- Added
useUploadRichTextImagehook to validate and upload images viafileUpload, returning the response shape expected by@editorjs/image. - Refactored the Editor.js tools config into a
getTools()factory and registered the image tool for both editable and read-only contexts. - Introduced
RichTextImageUploadContextto allow Storybook/tests to override uploading behavior (Storybook uses an in-memory base64 uploader).
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/components/RichTextEditor/useUploadRichTextImage.ts | Adds upload hook with MIME/size validation and fileUpload integration for Editor.js image tool. |
| src/components/RichTextEditor/useUploadRichTextImage.test.tsx | Unit tests covering validation, successful upload, backend errors, and thrown mutation cases. |
| src/components/RichTextEditor/RichTextImageUploadContext.ts | Adds context override seam for pluggable image upload implementations. |
| src/components/RichTextEditor/RichTextEditorContent.tsx | Switches to getTools() for read-only rendering (ensures image blocks render). |
| src/components/RichTextEditor/RichTextEditor.tsx | Uses getTools({ uploadImage }) with optional override from RichTextImageUploadContext. |
| src/components/RichTextEditor/RichTextEditor.stories.tsx | Wraps Storybook story in upload override provider and simulates uploads via base64 data URLs. |
| src/components/RichTextEditor/fixtures.json | Adds an image block fixture for editor preview/rendering. |
| src/components/RichTextEditor/consts.ts | Registers @editorjs/image tool and converts static tools export to getTools() factory. |
| package.json | Adds @editorjs/image dependency. |
| pnpm-lock.yaml | Locks @editorjs/image@2.10.3 and its transitive dependencies. |
| locale/defaultMessages.json | Adds message strings for image validation errors. |
| .changeset/rich-text-editor-images.md | Documents the new feature and notes downstream rendering implications for clients/storefronts. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import { | ||
| ALLOWED_RICH_TEXT_IMAGE_MIME_TYPES, | ||
| type UploadRichTextImage, | ||
| } from "./useUploadRichTextImage"; |
Add the @editorjs/image plugin to the rich text editor, wired to Saleor's generic fileUpload mutation. Users can drag, drop, or paste images into any rich text field; files are uploaded to Saleor media storage and inserted inline. - New useUploadRichTextImage hook adapts fileUpload to editor.js's uploader contract, validating MIME type (matching core: avif/bmp/gif/jpeg/png/tiff/webp, SVG excluded for XSS) and size (10 MB) before upload - Convert the static tools config into a getTools factory; the image tool is always registered so saved image blocks render in read-only views too - Add RichTextImageUploadContext as an override seam, used by Storybook to simulate uploads in-memory as base64 data URLs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Uploading to Saleor media storage is deferred until the dedicated API is ready, so the image tool no longer accepts files at all. Images can only be added by pointing at an externally hosted file. - Both entry points into an empty image block (the toolbox item and the button inside the block) ask for a URL instead of opening a file picker - Drop the drag-n-drop / clipboard file paste handlers, which could only fail without an uploader; URL and <img> paste handling stays - Reject non-http(s) sources, which would otherwise inline a whole data:/blob: file into the saved rich text instead of referencing a hosted image - Remove useUploadRichTextImage (wired to the generic fileUpload mutation) and the Storybook upload override context; getTools() is a plain const again Existing image blocks still render, including in read-only views. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`ProductMedia` passed `media ?? []` into `useProductMediaDrag`. While the product query is in flight `media` is undefined, so that expression produced a new array on every render. The hook's media-sync effect lists it as a dependency and unconditionally calls `setOrderedMedia`, so every render scheduled another one - an unbounded loop that only stopped once the query resolved and the array identity became stable. Use a module-level constant for the empty case, and type the prop as `ProductMediaFragment[] | undefined` to match what the view actually passes (`data?.product?.media`) and what the component already guards for. The regression test renders and re-renders with undefined media; before the fix it exhausts the heap rather than failing an assertion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6f93eae to
3f45bc0
Compare
Adds image support to the rich text editor (editor.js), limited to externally hosted images for now.
Uploading to Saleor media storage is deferred until the dedicated API is ready, so the image tool accepts no files at all — an image is added by pointing at a URL:
<img>, still creates a block directlyhttp(s)sources are rejected, so adata:/blob:paste can't inline a whole file into the saved rich textStorefronts and other API clients that render rich text need to handle the
imageblock to display these images.Also in this branch
An unrelated fix, found while testing on the product page:
ProductMediapassedmedia ?? []intouseProductMediaDrag, producing a new array every render while the product query was in flight. The hook's sync effect setStates on that dependency, so the page re-rendered in an unbounded loop until the query resolved.Scope of the change