Skip to content

Commit 672b297

Browse files
committed
fix(files): read pasted images from clipboard items, not just files
Some browsers expose a pasted or copied image only via DataTransfer.items (with an empty files list), so screenshot paste was silently ignored. extractImageFiles now falls back to items; moved to a testable module with unit tests (addresses Cursor Bugbot).
1 parent 0cca61e commit 672b297

3 files changed

Lines changed: 71 additions & 6 deletions

File tree

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { extractImageFiles } from './image-paste'
6+
7+
function imageFile(name = 'shot.png'): File {
8+
return new File([''], name, { type: 'image/png' })
9+
}
10+
11+
function transfer(
12+
files: File[],
13+
items: Array<{ kind: string; type: string; file: File | null }> = []
14+
): DataTransfer {
15+
return {
16+
files,
17+
items: items.map((entry) => ({
18+
kind: entry.kind,
19+
type: entry.type,
20+
getAsFile: () => entry.file,
21+
})),
22+
} as unknown as DataTransfer
23+
}
24+
25+
describe('extractImageFiles', () => {
26+
it('returns nothing for a null payload or non-image files', () => {
27+
expect(extractImageFiles(null)).toEqual([])
28+
expect(extractImageFiles(transfer([new File([''], 'a.txt', { type: 'text/plain' })]))).toEqual(
29+
[]
30+
)
31+
})
32+
33+
it('reads images from the files list (drag-drop)', () => {
34+
const file = imageFile()
35+
expect(extractImageFiles(transfer([file]))).toEqual([file])
36+
})
37+
38+
it('falls back to items when files is empty (pasted screenshot)', () => {
39+
const file = imageFile()
40+
const result = extractImageFiles(transfer([], [{ kind: 'file', type: 'image/png', file }]))
41+
expect(result).toEqual([file])
42+
})
43+
44+
it('ignores non-file and non-image items', () => {
45+
const result = extractImageFiles(
46+
transfer(
47+
[],
48+
[
49+
{ kind: 'string', type: 'text/plain', file: null },
50+
{ kind: 'file', type: 'application/pdf', file: new File([''], 'a.pdf') },
51+
]
52+
)
53+
)
54+
expect(result).toEqual([])
55+
})
56+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Extract image `File` objects from a paste/drop payload. Reads `files` first, then falls back to
3+
* `items` — many browsers expose a pasted or copied image (e.g. a screenshot) only through
4+
* `DataTransfer.items` with an empty `files` list, so reading `files` alone misses them.
5+
*/
6+
export function extractImageFiles(transfer: DataTransfer | null): File[] {
7+
if (!transfer) return []
8+
const fromFiles = Array.from(transfer.files).filter((file) => file.type.startsWith('image/'))
9+
if (fromFiles.length > 0) return fromFiles
10+
return Array.from(transfer.items)
11+
.filter((item) => item.kind === 'file' && item.type.startsWith('image/'))
12+
.map((item) => item.getAsFile())
13+
.filter((file): file is File => file !== null)
14+
}

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/rich-markdown-editor.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { SaveStatus } from '@/hooks/use-autosave'
99
import { PreviewLoadingFrame } from '../preview-shared'
1010
import { useEditableFileContent } from '../use-editable-file-content'
1111
import { createMarkdownEditorExtensions } from './extensions'
12+
import { extractImageFiles } from './image-paste'
1213
import {
1314
applyFrontmatter,
1415
normalizeLinkHref,
@@ -23,12 +24,6 @@ const EXTENSIONS = createMarkdownEditorExtensions({
2324
placeholder: "Write something, or press '/' for commands…",
2425
})
2526

26-
/** Image files from a paste/drop payload (screenshots, dragged files, copied images). */
27-
function extractImageFiles(transfer: DataTransfer | null): File[] {
28-
if (!transfer) return []
29-
return Array.from(transfer.files).filter((file) => file.type.startsWith('image/'))
30-
}
31-
3227
interface RichMarkdownEditorProps {
3328
file: WorkspaceFileRecord
3429
workspaceId: string

0 commit comments

Comments
 (0)