|
1 | | -import { useEffect, useRef, useState } from 'react' |
| 1 | +import React, { useEffect, useRef, useState } from 'react' |
2 | 2 | import { Terminal } from '@xterm/xterm' |
3 | 3 | import { FitAddon } from '@xterm/addon-fit' |
4 | 4 | import { SerializeAddon } from '@xterm/addon-serialize' |
@@ -356,8 +356,38 @@ export function XTerminal({ terminalId, cwd, type, visible, claudeCommand, sessi |
356 | 356 | } |
357 | 357 | }, [visible, terminalId]) |
358 | 358 |
|
| 359 | + const handleDragOver = (e: React.DragEvent<HTMLDivElement>): void => { |
| 360 | + if (!e.dataTransfer.types.includes('Files')) return |
| 361 | + e.preventDefault() |
| 362 | + e.dataTransfer.dropEffect = 'copy' |
| 363 | + } |
| 364 | + |
| 365 | + const handleDrop = (e: React.DragEvent<HTMLDivElement>): void => { |
| 366 | + const files = Array.from(e.dataTransfer.files) |
| 367 | + if (files.length === 0) return |
| 368 | + e.preventDefault() |
| 369 | + const paths = files |
| 370 | + .map((f) => window.api.getFilePath(f)) |
| 371 | + .filter((p) => p && p.length > 0) |
| 372 | + if (paths.length === 0) return |
| 373 | + // Match iTerm2: shell-quote each path, join with spaces, wrap in |
| 374 | + // bracketed-paste markers so Claude (and other readline-style prompts) |
| 375 | + // treat it as a paste rather than per-keystroke input. |
| 376 | + // Claude Code detects image paths in pasted text and renders them as |
| 377 | + // attachments, but only when escaped iTerm2-style (backslash-escaped |
| 378 | + // special chars, not POSIX single-quoting). |
| 379 | + const escapeForDrop = (p: string): string => p.replace(/([ \t"'`$\\!?*()[\]{}|;<>&#])/g, '\\$1') |
| 380 | + const text = paths.map(escapeForDrop).join(' ') |
| 381 | + window.api.writeTerminal(terminalId, '\x1b[200~' + text + '\x1b[201~') |
| 382 | + terminalRef.current?.focus() |
| 383 | + } |
| 384 | + |
359 | 385 | return ( |
360 | | - <div className="w-full h-full relative"> |
| 386 | + <div |
| 387 | + className="w-full h-full relative" |
| 388 | + onDragOver={handleDragOver} |
| 389 | + onDrop={handleDrop} |
| 390 | + > |
361 | 391 | <div ref={containerRef} className="w-full h-full" /> |
362 | 392 | {loading && ( |
363 | 393 | <div className="absolute inset-0 flex items-center justify-center bg-app/70 pointer-events-none"> |
|
0 commit comments