fix(ui): label inline data images without showing their payload

This commit is contained in:
mateo-berri 2026-09-25 20:41:16 -07:00
parent e06d3faf76
commit 8fc135daad
2 changed files with 23 additions and 4 deletions

View file

@ -41,6 +41,19 @@ describe("MarkdownImage", () => {
expect(screen.getByRole("button", { name: "Image mailto:ops@example.com Load image" })).toBeInTheDocument();
});
it("labels an inline data image without showing its payload", () => {
const payload = `data:image/png;base64,${"A".repeat(4096)}`;
render(<MarkdownImage src={payload} alt="Weekly chart" />);
const load = screen.getByRole("button", { name: "Weekly chart inline image Load image" });
expect(load).not.toHaveAttribute("title");
expect(load).not.toHaveTextContent("base64");
fireEvent.click(load);
expect(screen.getByRole("img", { name: "Weekly chart" })).toHaveAttribute("src", payload);
});
it("renders only the alt text when there is no source", () => {
render(<MarkdownImage alt="Weekly chart" />);

View file

@ -3,9 +3,14 @@ import React, { useState } from "react";
type MarkdownImageProps = Pick<React.ComponentPropsWithoutRef<"img">, "src" | "alt" | "title">;
function describeImageSource(src: string): string {
type ImageSource = { readonly label: string; readonly tooltip: string | undefined };
function describeImageSource(src: string): ImageSource {
if (src.startsWith("data:")) {
return { label: "inline image", tooltip: undefined };
}
const host = URL.canParse(src) ? new URL(src).host : "";
return host || src;
return { label: host || src, tooltip: src };
}
export function MarkdownImage({ src, alt, title }: MarkdownImageProps) {
@ -20,6 +25,7 @@ export function MarkdownImage({ src, alt, title }: MarkdownImageProps) {
return <img src={src} alt={alt ?? ""} title={title} className="max-w-full rounded-md border border-border" />;
}
const source = describeImageSource(src);
return (
<button
type="button"
@ -27,12 +33,12 @@ export function MarkdownImage({ src, alt, title }: MarkdownImageProps) {
event.preventDefault();
setLoaded(true);
}}
title={src}
title={source.tooltip}
className="inline-flex max-w-full items-center gap-2 rounded-md border border-border bg-muted px-3 py-1.5 text-sm text-foreground hover:bg-accent"
>
<ImageIcon className="size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
<span className="truncate">{alt || "Image"}</span>{" "}
<span className="truncate text-muted-foreground">{describeImageSource(src)}</span>{" "}
<span className="truncate text-muted-foreground">{source.label}</span>{" "}
<span className="shrink-0 font-medium">Load image</span>
</button>
);