// How an attachment is drawn, decided in one place for the card and the editor.
/** Whether a file is shown as a picture rather than as a chip to download.
*
* Every `image/` type except SVG. An SVG is a document that can carry its own
* script, so neither surface serves one to render: the server sends it as a download
* (#1981) and the desktop's blob scheme as opaque bytes, where an `
` of it would
* only ever show broken. */
export function rendersInline(mime: string): boolean {
return mime.startsWith("image/") && mime !== "image/svg+xml";
}
/** Whether this browser can put a picture on the clipboard at all.
*
* The async clipboard only exists in a secure context, so a server reached over plain
* http has none — the viewer then leaves copying to the browser's own context menu
* on the full-size image. */
export function canCopyImages(): boolean {
return typeof ClipboardItem !== "undefined" && typeof navigator.clipboard?.write === "function";
}
/** Put the image at `url` on the clipboard.
*
* As PNG whatever it was stored as: PNG is the one image type every browser's
* clipboard accepts, so a JPEG or WebP is redrawn through a canvas first. The item is
* given a promise rather than a blob so the write starts inside the click — Safari
* refuses a clipboard write that begins after an await. */
export async function copyImage(url: string): Promise {
await navigator.clipboard.write([new ClipboardItem({ "image/png": asPng(url) })]);
}
async function asPng(url: string): Promise {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blob = await res.blob();
if (blob.type === "image/png") return blob;
const bitmap = await createImageBitmap(blob);
const canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
canvas.getContext("2d")?.drawImage(bitmap, 0, 0);
bitmap.close();
return new Promise((resolve, reject) =>
canvas.toBlob((png) => (png ? resolve(png) : reject(new Error("Could not encode the image."))), "image/png"),
);
}