Treat SVG attachments as files
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
getMimeTypeFromPath,
|
||||||
|
getRasterImageMimeTypeFromPath,
|
||||||
|
isRasterImageFile,
|
||||||
|
isRasterImageMimeType,
|
||||||
|
isRasterImagePath,
|
||||||
|
RASTER_IMAGE_FILE_EXTENSIONS,
|
||||||
|
} from "./file-types";
|
||||||
|
|
||||||
|
describe("attachment file types", () => {
|
||||||
|
it("keeps SVG as a file while treating raster image files as images", () => {
|
||||||
|
expect(getMimeTypeFromPath("/tmp/logo.svg")).toBe("image/svg+xml");
|
||||||
|
expect(isRasterImagePath("/tmp/logo.svg")).toBe(false);
|
||||||
|
expect(isRasterImageMimeType("image/svg+xml")).toBe(false);
|
||||||
|
expect(isRasterImageFile(new File(["<svg />"], "logo.svg", { type: "image/svg+xml" }))).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getRasterImageMimeTypeFromPath("/tmp/screenshot.PNG?cache=1")).toBe("image/png");
|
||||||
|
expect(isRasterImagePath("/tmp/screenshot.PNG?cache=1")).toBe(true);
|
||||||
|
expect(isRasterImageMimeType("image/png; charset=binary")).toBe(true);
|
||||||
|
expect(isRasterImageFile(new File([new Uint8Array([0])], "screenshot.png"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not offer SVG in the image picker extension list", () => {
|
||||||
|
expect(new Set(RASTER_IMAGE_FILE_EXTENSIONS)).toEqual(
|
||||||
|
new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp", "heic", "heif", "avif", "tif", "tiff"]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
const MIME_TYPE_BY_EXTENSION: Record<string, string> = {
|
||||||
|
".png": "image/png",
|
||||||
|
".jpg": "image/jpeg",
|
||||||
|
".jpeg": "image/jpeg",
|
||||||
|
".gif": "image/gif",
|
||||||
|
".webp": "image/webp",
|
||||||
|
".bmp": "image/bmp",
|
||||||
|
".svg": "image/svg+xml",
|
||||||
|
".heic": "image/heic",
|
||||||
|
".heif": "image/heif",
|
||||||
|
".avif": "image/avif",
|
||||||
|
".tif": "image/tiff",
|
||||||
|
".tiff": "image/tiff",
|
||||||
|
".pdf": "application/pdf",
|
||||||
|
".txt": "text/plain",
|
||||||
|
".md": "text/markdown",
|
||||||
|
".json": "application/json",
|
||||||
|
".js": "text/javascript",
|
||||||
|
".ts": "text/typescript",
|
||||||
|
".tsx": "text/typescript-jsx",
|
||||||
|
".jsx": "text/javascript",
|
||||||
|
".html": "text/html",
|
||||||
|
".css": "text/css",
|
||||||
|
".xml": "text/xml",
|
||||||
|
".csv": "text/csv",
|
||||||
|
".zip": "application/zip",
|
||||||
|
".gz": "application/gzip",
|
||||||
|
".tar": "application/x-tar",
|
||||||
|
".mp3": "audio/mpeg",
|
||||||
|
".mp4": "video/mp4",
|
||||||
|
".mov": "video/quicktime",
|
||||||
|
".webm": "video/webm",
|
||||||
|
".wav": "audio/wav",
|
||||||
|
".ogg": "audio/ogg",
|
||||||
|
".doc": "application/msword",
|
||||||
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
".xls": "application/vnd.ms-excel",
|
||||||
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
".ppt": "application/vnd.ms-powerpoint",
|
||||||
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||||
|
};
|
||||||
|
|
||||||
|
const RASTER_IMAGE_MIME_TYPE_BY_EXTENSION: Record<string, string> = {
|
||||||
|
".png": "image/png",
|
||||||
|
".jpg": "image/jpeg",
|
||||||
|
".jpeg": "image/jpeg",
|
||||||
|
".gif": "image/gif",
|
||||||
|
".webp": "image/webp",
|
||||||
|
".bmp": "image/bmp",
|
||||||
|
".heic": "image/heic",
|
||||||
|
".heif": "image/heif",
|
||||||
|
".avif": "image/avif",
|
||||||
|
".tif": "image/tiff",
|
||||||
|
".tiff": "image/tiff",
|
||||||
|
};
|
||||||
|
|
||||||
|
const RASTER_IMAGE_MIME_TYPES = new Set(Object.values(RASTER_IMAGE_MIME_TYPE_BY_EXTENSION));
|
||||||
|
|
||||||
|
export const RASTER_IMAGE_FILE_EXTENSIONS = Object.keys(RASTER_IMAGE_MIME_TYPE_BY_EXTENSION).map(
|
||||||
|
(extension) => extension.slice(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function getFileExtension(path: string): string {
|
||||||
|
const normalizedPath = path.split("#", 1)[0]?.split("?", 1)[0] ?? path;
|
||||||
|
const extensionIndex = normalizedPath.lastIndexOf(".");
|
||||||
|
if (extensionIndex < 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return normalizedPath.slice(extensionIndex).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMimeTypeFromPath(path: string): string {
|
||||||
|
return MIME_TYPE_BY_EXTENSION[getFileExtension(path)] ?? "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRasterImageMimeTypeFromPath(path: string): string | null {
|
||||||
|
return RASTER_IMAGE_MIME_TYPE_BY_EXTENSION[getFileExtension(path)] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRasterImagePath(path: string): boolean {
|
||||||
|
return getRasterImageMimeTypeFromPath(path) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRasterImageMimeType(mimeType: string | null | undefined): boolean {
|
||||||
|
const normalized = mimeType?.split(";", 1)[0]?.trim().toLowerCase();
|
||||||
|
return Boolean(normalized && RASTER_IMAGE_MIME_TYPES.has(normalized));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isRasterImageFile(file: Pick<File, "name" | "type">): boolean {
|
||||||
|
if (isRasterImageMimeType(file.type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return file.type.trim().length === 0 && isRasterImagePath(file.name);
|
||||||
|
}
|
||||||
@@ -53,4 +53,8 @@ describe("parseImageDataUrl", () => {
|
|||||||
it("ignores non-image data URLs", () => {
|
it("ignores non-image data URLs", () => {
|
||||||
expect(parseImageDataUrl("data:text/plain;base64,SGVsbG8=")).toBeNull();
|
expect(parseImageDataUrl("data:text/plain;base64,SGVsbG8=")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores SVG data URLs", () => {
|
||||||
|
expect(parseImageDataUrl("data:image/svg+xml;base64,PHN2ZyAvPg==")).toBeNull();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { generateMessageId } from "@/types/stream";
|
import { generateMessageId } from "@/types/stream";
|
||||||
import { isAbsolutePath } from "@/utils/path";
|
import { isAbsolutePath } from "@/utils/path";
|
||||||
|
import { isRasterImageMimeType } from "./file-types";
|
||||||
|
|
||||||
export function generateAttachmentId(): string {
|
export function generateAttachmentId(): string {
|
||||||
return `att_${generateMessageId()}`;
|
return `att_${generateMessageId()}`;
|
||||||
@@ -52,7 +53,7 @@ export function parseImageDataUrl(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = parseDataUrl(uri);
|
const parsed = parseDataUrl(uri);
|
||||||
if (!parsed.mimeType.toLowerCase().startsWith("image/")) {
|
if (!isRasterImageMimeType(parsed.mimeType)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const fingerprint = `${parsed.mimeType}\0${parsed.base64.length}\0${parsed.base64.slice(0, 64)}\0${parsed.base64.slice(-64)}`;
|
const fingerprint = `${parsed.mimeType}\0${parsed.base64.length}\0${parsed.base64.slice(0, 64)}\0${parsed.base64.slice(-64)}`;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { DesktopDialogBridge } from "@/desktop/host";
|
import type { DesktopDialogBridge } from "@/desktop/host";
|
||||||
|
import { RASTER_IMAGE_FILE_EXTENSIONS } from "@/attachments/file-types";
|
||||||
import { i18n } from "@/i18n/i18next";
|
import { i18n } from "@/i18n/i18next";
|
||||||
import { isAbsolutePath } from "@/utils/path";
|
import { isAbsolutePath } from "@/utils/path";
|
||||||
|
|
||||||
@@ -17,20 +18,6 @@ export interface ExpoImagePickerAssetLike {
|
|||||||
file?: File | null;
|
file?: File | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMAGE_FILE_EXTENSIONS = [
|
|
||||||
"png",
|
|
||||||
"jpg",
|
|
||||||
"jpeg",
|
|
||||||
"gif",
|
|
||||||
"webp",
|
|
||||||
"avif",
|
|
||||||
"heic",
|
|
||||||
"heif",
|
|
||||||
"tiff",
|
|
||||||
"bmp",
|
|
||||||
"svg",
|
|
||||||
];
|
|
||||||
|
|
||||||
function shouldTreatAsFileUri(uri: string): boolean {
|
function shouldTreatAsFileUri(uri: string): boolean {
|
||||||
return uri.startsWith("file://") || isAbsolutePath(uri);
|
return uri.startsWith("file://") || isAbsolutePath(uri);
|
||||||
}
|
}
|
||||||
@@ -89,7 +76,7 @@ export async function openImagePathsWithDesktopDialog(
|
|||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: i18n.t("imageAttachmentPicker.dialogFilterName"),
|
name: i18n.t("imageAttachmentPicker.dialogFilterName"),
|
||||||
extensions: IMAGE_FILE_EXTENSIONS,
|
extensions: RASTER_IMAGE_FILE_EXTENSIONS,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
title: i18n.t("imageAttachmentPicker.dialogTitle"),
|
title: i18n.t("imageAttachmentPicker.dialogTitle"),
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { useState, useRef, useEffect } from "react";
|
|||||||
import type { ImageAttachment } from "@/composer/types";
|
import type { ImageAttachment } from "@/composer/types";
|
||||||
import { getDesktopHost } from "@/desktop/host";
|
import { getDesktopHost } from "@/desktop/host";
|
||||||
import { persistAttachmentFromBlob, persistAttachmentFromFileUri } from "@/attachments/service";
|
import { persistAttachmentFromBlob, persistAttachmentFromFileUri } from "@/attachments/service";
|
||||||
|
import {
|
||||||
|
getRasterImageMimeTypeFromPath,
|
||||||
|
isRasterImageFile,
|
||||||
|
isRasterImagePath,
|
||||||
|
} from "@/attachments/file-types";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
|
|
||||||
export interface DroppedFileItem {
|
export interface DroppedFileItem {
|
||||||
@@ -26,20 +31,6 @@ interface UseFileDropZoneReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const IS_WEB = isWeb;
|
const IS_WEB = isWeb;
|
||||||
const IMAGE_MIME_BY_EXTENSION: Record<string, string> = {
|
|
||||||
".png": "image/png",
|
|
||||||
".jpg": "image/jpeg",
|
|
||||||
".jpeg": "image/jpeg",
|
|
||||||
".gif": "image/gif",
|
|
||||||
".webp": "image/webp",
|
|
||||||
".bmp": "image/bmp",
|
|
||||||
".svg": "image/svg+xml",
|
|
||||||
".heic": "image/heic",
|
|
||||||
".heif": "image/heif",
|
|
||||||
".avif": "image/avif",
|
|
||||||
".tif": "image/tiff",
|
|
||||||
".tiff": "image/tiff",
|
|
||||||
};
|
|
||||||
|
|
||||||
type DesktopDragDropPayload =
|
type DesktopDragDropPayload =
|
||||||
| {
|
| {
|
||||||
@@ -61,26 +52,8 @@ interface DesktopDragDropEvent {
|
|||||||
payload: DesktopDragDropPayload;
|
payload: DesktopDragDropPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImageFile(file: File): boolean {
|
|
||||||
return file.type.startsWith("image/");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFileExtension(path: string): string {
|
|
||||||
const normalizedPath = path.split("#", 1)[0]?.split("?", 1)[0] ?? path;
|
|
||||||
const extensionIndex = normalizedPath.lastIndexOf(".");
|
|
||||||
if (extensionIndex < 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return normalizedPath.slice(extensionIndex).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isImagePath(path: string): boolean {
|
|
||||||
return getFileExtension(path) in IMAGE_MIME_BY_EXTENSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function filePathToImageAttachment(path: string): Promise<ImageAttachment> {
|
async function filePathToImageAttachment(path: string): Promise<ImageAttachment> {
|
||||||
const extension = getFileExtension(path);
|
const mimeType = getRasterImageMimeTypeFromPath(path) ?? "image/jpeg";
|
||||||
const mimeType = IMAGE_MIME_BY_EXTENSION[extension] ?? "image/jpeg";
|
|
||||||
return await persistAttachmentFromFileUri({ uri: path, mimeType });
|
return await persistAttachmentFromFileUri({ uri: path, mimeType });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +155,7 @@ export function useFileDropZone({
|
|||||||
onGenericFilesDroppedRef.current(items);
|
onGenericFilesDroppedRef.current(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imagePaths = payload.paths.filter(isImagePath);
|
const imagePaths = payload.paths.filter(isRasterImagePath);
|
||||||
if (imagePaths.length === 0) {
|
if (imagePaths.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -273,7 +246,7 @@ export function useFileDropZone({
|
|||||||
onGenericFilesDroppedRef.current(genericItems);
|
onGenericFilesDroppedRef.current(genericItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageFiles = files.filter(isImageFile);
|
const imageFiles = files.filter(isRasterImageFile);
|
||||||
|
|
||||||
if (imageFiles.length === 0) return;
|
if (imageFiles.length === 0) return;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { getDesktopHost, isElectronRuntime } from "@/desktop/host";
|
|||||||
import { copyDesktopAttachmentFile } from "@/desktop/attachments/desktop-file-commands";
|
import { copyDesktopAttachmentFile } from "@/desktop/attachments/desktop-file-commands";
|
||||||
import { readDesktopFileBase64 } from "@/desktop/attachments/desktop-preview-url";
|
import { readDesktopFileBase64 } from "@/desktop/attachments/desktop-preview-url";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
|
import { getFileExtension, getMimeTypeFromPath } from "@/attachments/file-types";
|
||||||
|
|
||||||
export interface PickedFile {
|
export interface PickedFile {
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@@ -10,62 +11,6 @@ export interface PickedFile {
|
|||||||
bytes: Uint8Array;
|
bytes: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIME_BY_EXTENSION: Record<string, string> = {
|
|
||||||
".png": "image/png",
|
|
||||||
".jpg": "image/jpeg",
|
|
||||||
".jpeg": "image/jpeg",
|
|
||||||
".gif": "image/gif",
|
|
||||||
".webp": "image/webp",
|
|
||||||
".bmp": "image/bmp",
|
|
||||||
".svg": "image/svg+xml",
|
|
||||||
".heic": "image/heic",
|
|
||||||
".heif": "image/heif",
|
|
||||||
".avif": "image/avif",
|
|
||||||
".tif": "image/tiff",
|
|
||||||
".tiff": "image/tiff",
|
|
||||||
".pdf": "application/pdf",
|
|
||||||
".txt": "text/plain",
|
|
||||||
".md": "text/markdown",
|
|
||||||
".json": "application/json",
|
|
||||||
".js": "text/javascript",
|
|
||||||
".ts": "text/typescript",
|
|
||||||
".tsx": "text/typescript-jsx",
|
|
||||||
".jsx": "text/javascript",
|
|
||||||
".html": "text/html",
|
|
||||||
".css": "text/css",
|
|
||||||
".xml": "text/xml",
|
|
||||||
".csv": "text/csv",
|
|
||||||
".zip": "application/zip",
|
|
||||||
".gz": "application/gzip",
|
|
||||||
".tar": "application/x-tar",
|
|
||||||
".mp3": "audio/mpeg",
|
|
||||||
".mp4": "video/mp4",
|
|
||||||
".mov": "video/quicktime",
|
|
||||||
".webm": "video/webm",
|
|
||||||
".wav": "audio/wav",
|
|
||||||
".ogg": "audio/ogg",
|
|
||||||
".doc": "application/msword",
|
|
||||||
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
||||||
".xls": "application/vnd.ms-excel",
|
|
||||||
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
||||||
".ppt": "application/vnd.ms-powerpoint",
|
|
||||||
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
||||||
};
|
|
||||||
|
|
||||||
function getFileExtension(path: string): string {
|
|
||||||
const normalizedPath = path.split("#", 1)[0]?.split("?", 1)[0] ?? path;
|
|
||||||
const extensionIndex = normalizedPath.lastIndexOf(".");
|
|
||||||
if (extensionIndex < 0) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return normalizedPath.slice(extensionIndex).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMimeTypeFromPath(path: string): string {
|
|
||||||
const ext = getFileExtension(path);
|
|
||||||
return MIME_BY_EXTENSION[ext] ?? "application/octet-stream";
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64ToUint8Array(base64: string): Uint8Array {
|
function base64ToUint8Array(base64: string): Uint8Array {
|
||||||
const binaryString = atob(base64);
|
const binaryString = atob(base64);
|
||||||
const bytes = new Uint8Array(binaryString.length);
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ import { Composer } from "@/composer";
|
|||||||
import { AgentModeControl } from "@/composer/agent-controls/mode-control";
|
import { AgentModeControl } from "@/composer/agent-controls/mode-control";
|
||||||
import { FileDropZone } from "@/components/file-drop-zone";
|
import { FileDropZone } from "@/components/file-drop-zone";
|
||||||
import { uploadFileAttachments } from "@/composer/actions";
|
import { uploadFileAttachments } from "@/composer/actions";
|
||||||
import { readDesktopFileBytes, getMimeTypeFromPath } from "@/hooks/use-file-picker";
|
import {
|
||||||
|
getMimeTypeFromPath,
|
||||||
|
isRasterImageFile,
|
||||||
|
isRasterImagePath,
|
||||||
|
} from "@/attachments/file-types";
|
||||||
|
import { readDesktopFileBytes } from "@/hooks/use-file-picker";
|
||||||
import type { DroppedItem } from "@/hooks/use-file-drop-zone";
|
import type { DroppedItem } from "@/hooks/use-file-drop-zone";
|
||||||
import type { UserComposerAttachment } from "@/attachments/types";
|
import type { UserComposerAttachment } from "@/attachments/types";
|
||||||
import { RewindComposerRestoreProvider } from "@/components/rewind/composer-restore";
|
import { RewindComposerRestoreProvider } from "@/components/rewind/composer-restore";
|
||||||
@@ -719,8 +724,8 @@ function ChatAgentContent({
|
|||||||
async (items: DroppedItem[]) => {
|
async (items: DroppedItem[]) => {
|
||||||
if (!client || !isConnected) return;
|
if (!client || !isConnected) return;
|
||||||
const nonImageItems = items.filter((item) => {
|
const nonImageItems = items.filter((item) => {
|
||||||
if (item.kind === "web-file") return !item.file.type.startsWith("image/");
|
if (item.kind === "web-file") return !isRasterImageFile(item.file);
|
||||||
return !isImagePath(item.path);
|
return !isRasterImagePath(item.path);
|
||||||
});
|
});
|
||||||
if (nonImageItems.length === 0) return;
|
if (nonImageItems.length === 0) return;
|
||||||
try {
|
try {
|
||||||
@@ -1114,25 +1119,6 @@ function ChatAgentContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImagePath(path: string): boolean {
|
|
||||||
const ext = path.slice(path.lastIndexOf(".")).toLowerCase();
|
|
||||||
const imageExts = new Set([
|
|
||||||
".png",
|
|
||||||
".jpg",
|
|
||||||
".jpeg",
|
|
||||||
".gif",
|
|
||||||
".webp",
|
|
||||||
".bmp",
|
|
||||||
".svg",
|
|
||||||
".heic",
|
|
||||||
".heif",
|
|
||||||
".avif",
|
|
||||||
".tif",
|
|
||||||
".tiff",
|
|
||||||
]);
|
|
||||||
return imageExts.has(ext);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
const ChatAgentReadyContent = memo(function ChatAgentReadyContent({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
agentId,
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ describe("collectImageFilesFromClipboardData", () => {
|
|||||||
expect(files).toEqual([imagePng]);
|
expect(files).toEqual([imagePng]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("ignores SVG clipboard files", () => {
|
||||||
|
const svgFile = new File(["<svg />"], "logo.svg", {
|
||||||
|
type: "image/svg+xml",
|
||||||
|
});
|
||||||
|
|
||||||
|
const files = collectImageFilesFromClipboardData({
|
||||||
|
items: [
|
||||||
|
createClipboardItem({
|
||||||
|
kind: "file",
|
||||||
|
type: "image/svg+xml",
|
||||||
|
file: svgFile,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(files).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns an empty array when clipboard data is missing", () => {
|
it("returns an empty array when clipboard data is missing", () => {
|
||||||
expect(collectImageFilesFromClipboardData(undefined)).toEqual([]);
|
expect(collectImageFilesFromClipboardData(undefined)).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AttachmentMetadata } from "@/attachments/types";
|
import type { AttachmentMetadata } from "@/attachments/types";
|
||||||
import { persistAttachmentFromBlob } from "@/attachments/service";
|
import { persistAttachmentFromBlob } from "@/attachments/service";
|
||||||
|
import { isRasterImageMimeType } from "@/attachments/file-types";
|
||||||
|
|
||||||
export interface ClipboardItemLike {
|
export interface ClipboardItemLike {
|
||||||
kind?: string;
|
kind?: string;
|
||||||
@@ -25,7 +26,7 @@ export function collectImageFilesFromClipboardData(
|
|||||||
if (item?.kind !== "file") {
|
if (item?.kind !== "file") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!item.type?.startsWith("image/")) {
|
if (!isRasterImageMimeType(item.type)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const file = item.getAsFile?.();
|
const file = item.getAsFile?.();
|
||||||
|
|||||||
Reference in New Issue
Block a user