feat(lexical): support local image data URLs

Allow the image insertion dialog to select a local image and embed it as a Base64 data URL, while retaining URL-based insertion and leaving video URL-only.

Extract shared abortable FileReader handling for clipboard uploads and dialog selection; add English and Simplified Chinese messages plus an interaction test covering file selection through insertion.
This commit is contained in:
Maofeng
2026-07-31 16:03:54 +08:00
parent cbf5ee784f
commit 263b400860
7 changed files with 274 additions and 87 deletions
@@ -6,6 +6,7 @@ import {
render,
screen,
waitFor,
within,
} from "@testing-library/react"
import { afterEach, describe, expect, it } from "vitest"
@@ -190,6 +191,52 @@ describe("Lexical advanced actions", () => {
expect((caption as HTMLTextAreaElement).value).toBe("Updated caption")
})
it("reads an image selected from the insert dialog as a Base64 data URL", async () => {
const { container } = render(
<LexicalRoot value="<p>before</p>" onChange={() => undefined}>
<LexicalActions>
<Image />
</LexicalActions>
<LexicalFixedToolbar />
<LexicalContent />
</LexicalRoot>
)
fireEvent.click(screen.getByRole("button", { name: "Insert image" }))
const dialog = await screen.findByRole("dialog")
const file = new File([new Uint8Array([137, 80, 78, 71])], "local.png", {
type: "image/png",
})
fireEvent.change(within(dialog).getByLabelText("Image file"), {
target: { files: [file] },
})
const urlInput = within(dialog).getByRole("textbox", {
name: "Image URL",
})
await waitFor(() => {
expect((urlInput as HTMLInputElement).value).toMatch(
/^data:image\/png;base64,/
)
})
fireEvent.change(
within(dialog).getByRole("textbox", { name: "Alternative text" }),
{
target: { value: "Local image" },
}
)
fireEvent.submit(dialog.querySelector("form")!)
await waitFor(() => {
expect(
screen.getByRole("img", { name: "Local image" }).getAttribute("src")
).toMatch(/^data:image\/png;base64,/)
})
expect(container.querySelector("figure")).not.toBeNull()
})
it("lets a custom Video control insert a video through execute", async () => {
const { container } = render(
<LexicalRoot value="<p>before</p>" onChange={() => undefined}>