feat(editor): paste copied files as Markdown links (#599)

Pasting files copied in the project view into a Markdown document now inserts `[](relative/path)`, one link per line, encoded the same way as Markdown images. Other editors and terminals keep inserting the raw path, copied images keep their ![](...) route, and remote windows keep raw paths because the clipboard holds client paths. A context-menu Paste that the native command rejects falls back to reading the clipboard.

Fixes #573
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-01 01:41:58 +08:00
1 джерело b3c3150489
коміт 0a71f6be88
4 files changed
+175 -16

No files matched your search

+26 -7
Переглянути файл
@@ -1,13 +1,23 @@
import { call, commands, isTauri } from "./ipc";
import { ipcPath } from "./paths";
type FormatFiles = (paths: string[]) => string;
const rawPaths: FormatFiles = (paths) => paths.join("\n");
/** WebKit exposes native file URLs as Files, hiding the pasteboard's text flavor. */
export async function readClipboardText(transfer?: DataTransfer | null): Promise<string> {
export async function readClipboardText(
transfer?: DataTransfer | null,
formatFiles: FormatFiles = rawPaths,
): Promise<string> {
const text = transfer ? transfer.getData("text/plain") : ((await navigator.clipboard?.readText()) ?? "");
if (text || (transfer && !hasClipboardFiles(transfer)) || !isTauri()) return text;
if ((text && formatFiles === rawPaths) || (transfer && !hasClipboardFiles(transfer)) || !isTauri()) return text;
try {
return (await call(commands.clipboardFileList())).join("\n");
const paths = await call(commands.clipboardFileList());
// Windows also supplies the file list as text; unrelated text still wins.
if (text && text.replace(/\r\n/g, "\n").split("\n").map(ipcPath).join("\n") !== paths.join("\n")) return text;
return formatFiles(paths);
} catch {
return "";
return text;
}
}
@@ -16,13 +26,22 @@ function hasClipboardFiles(transfer: DataTransfer): boolean {
}
/** Claim synchronously so the native handler cannot emit an empty paste while IPC runs. */
export function pasteClipboardFiles(event: ClipboardEvent, paste: (text: string) => void): boolean {
export function pasteClipboardFiles(
event: ClipboardEvent,
paste: (text: string) => void,
formatFiles: FormatFiles = rawPaths,
): boolean {
const transfer = event.clipboardData;
if (event.defaultPrevented || !transfer || transfer.getData("text/plain") || !hasClipboardFiles(transfer))
if (
event.defaultPrevented ||
!transfer ||
(transfer.getData("text/plain") && formatFiles === rawPaths) ||
!hasClipboardFiles(transfer)
)
return false;
event.preventDefault();
event.stopImmediatePropagation();
void readClipboardText(transfer).then((text) => {
void readClipboardText(transfer, formatFiles).then((text) => {
if (text) paste(text);
});
return true;