Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

143 lines
4.0 KiB
TypeScript

const WORKSPACE_FILE_DROP_MIME_TYPE =
"application/x-tsh-workspace-file-paths+json";
export type WorkspaceFileDropEntryKind = "file" | "directory" | "unknown";
export interface WorkspaceFileDropEntry {
path: string;
name: string;
kind: WorkspaceFileDropEntryKind;
}
interface WorkspaceFileDropPayload {
entries?: unknown;
}
function normalizeWorkspaceFileDropEntryKind(
kind: unknown
): WorkspaceFileDropEntryKind {
return kind === "file" || kind === "directory" ? kind : "unknown";
}
function basenameWorkspacePath(path: string): string {
const normalized = path.trim().replace(/\\/g, "/").replace(/\/+$/g, "");
const segments = normalized.split("/").filter(Boolean);
return segments.at(-1) ?? normalized;
}
function normalizeWorkspaceFileDropEntries(
entries: readonly WorkspaceFileDropEntry[]
): WorkspaceFileDropEntry[] {
const uniquePaths = new Set<string>();
const normalizedEntries: WorkspaceFileDropEntry[] = [];
for (const entry of entries) {
const path = entry.path.trim();
if (!path || uniquePaths.has(path)) {
continue;
}
uniquePaths.add(path);
normalizedEntries.push({
path,
name: entry.name.trim() || basenameWorkspacePath(path),
kind: normalizeWorkspaceFileDropEntryKind(entry.kind)
});
}
return normalizedEntries;
}
function normalizeWorkspaceFileDropPaths(paths: readonly string[]): string[] {
return normalizeWorkspaceFileDropEntries(
paths.map((path) => ({
path,
name: basenameWorkspacePath(path),
kind: "unknown"
}))
).map((entry) => entry.path);
}
export function writeWorkspaceFileDropData(
dataTransfer: DataTransfer,
entries: readonly WorkspaceFileDropEntry[]
): void {
if (entries.length === 0) {
return;
}
const normalizedEntries = normalizeWorkspaceFileDropEntries(entries);
if (normalizedEntries.length === 0) {
return;
}
const normalizedPaths = normalizedEntries.map((entry) => entry.path);
dataTransfer.effectAllowed = "copy";
dataTransfer.setData(
WORKSPACE_FILE_DROP_MIME_TYPE,
JSON.stringify({ entries: normalizedEntries })
);
dataTransfer.setData("text/plain", normalizedPaths.join("\n"));
}
export function hasWorkspaceFileDropData(
dataTransfer: DataTransfer | null | undefined
): boolean {
if (!dataTransfer) {
return false;
}
return Array.from(dataTransfer.types ?? []).includes(
WORKSPACE_FILE_DROP_MIME_TYPE
);
}
export function readWorkspaceFileDropPaths(
dataTransfer: DataTransfer | null | undefined
): string[] {
return readWorkspaceFileDropEntries(dataTransfer).map((entry) => entry.path);
}
export function readWorkspaceFileDropEntries(
dataTransfer: DataTransfer | null | undefined
): WorkspaceFileDropEntry[] {
if (!hasWorkspaceFileDropData(dataTransfer)) {
return [];
}
const raw = dataTransfer?.getData(WORKSPACE_FILE_DROP_MIME_TYPE).trim() ?? "";
if (!raw) {
return [];
}
try {
const parsed = JSON.parse(raw) as WorkspaceFileDropPayload;
if (Array.isArray(parsed.entries)) {
return normalizeWorkspaceFileDropEntries(
parsed.entries.flatMap((entry) => {
if (!entry || typeof entry !== "object") {
return [];
}
const path =
"path" in entry && typeof entry.path === "string" ? entry.path : "";
const name =
"name" in entry && typeof entry.name === "string" ? entry.name : "";
const kind = "kind" in entry ? entry.kind : "unknown";
return [
{ path, name, kind: normalizeWorkspaceFileDropEntryKind(kind) }
];
})
);
}
return [];
} catch {
return [];
}
}
export function quoteWorkspacePathForTerminal(path: string): string {
return `'${path.replace(/'/g, `'"'"'`)}'`;
}
export function buildWorkspaceFileDropTerminalInput(
paths: readonly string[]
): string {
const normalizedPaths = normalizeWorkspaceFileDropPaths(paths);
if (normalizedPaths.length === 0) {
return "";
}
return `${normalizedPaths.map((path) => quoteWorkspacePathForTerminal(path)).join(" ")} `;
}