"use client"; import type { FileTreeRow } from "./types"; import { parseFileTreeRowId } from "./types"; export type FileTreeClipboardAction = "copy" | "cut"; export type FileTreeClipboardPayloadV1 = { type: "mnote-file-tree"; version: 1; action: FileTreeClipboardAction; rowIds: string[]; }; const PREFIX = "mnote-file-tree-clipboard:v1:"; let memoryClipboardText: string | null = null; function encodeBase64(text: string): string { const bytes = new TextEncoder().encode(text); let binary = ""; bytes.forEach((b) => { binary += String.fromCharCode(b); }); return btoa(binary); } function decodeBase64(base64: string): string { const binary = atob(base64); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i += 1) { bytes[i] = binary.charCodeAt(i); } return new TextDecoder().decode(bytes); } export function encodeFileTreeClipboardPayload(payload: FileTreeClipboardPayloadV1): string { return `${PREFIX}${encodeBase64(JSON.stringify(payload))}`; } export function decodeFileTreeClipboardPayload(text: string): FileTreeClipboardPayloadV1 | null { if (!text || !text.startsWith(PREFIX)) return null; const base64 = text.slice(PREFIX.length); try { const raw = decodeBase64(base64); const parsed = JSON.parse(raw) as Partial; if (parsed?.type !== "mnote-file-tree" || parsed.version !== 1) return null; if (parsed.action !== "copy" && parsed.action !== "cut") return null; if (!Array.isArray(parsed.rowIds) || parsed.rowIds.some((id) => typeof id !== "string")) return null; return parsed as FileTreeClipboardPayloadV1; } catch { return null; } } export async function writeFileTreeClipboardPayload(payload: FileTreeClipboardPayloadV1): Promise { const text = encodeFileTreeClipboardPayload(payload); memoryClipboardText = text; if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text); } catch { // ignore, fallback to memory } } } export async function readFileTreeClipboardPayload(): Promise { let text: string | null = memoryClipboardText; if (typeof navigator !== "undefined" && navigator.clipboard?.readText) { try { text = await navigator.clipboard.readText(); } catch { // ignore, fallback to memory } } if (!text) return null; return decodeFileTreeClipboardPayload(text); } export async function clearFileTreeClipboardPayload(): Promise { memoryClipboardText = null; if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(""); } catch { // ignore, memory fallback 已清空 } } } export function isTextInputTarget(target: EventTarget | null): boolean { const el = target as HTMLElement | null; if (!el) return false; if (el.isContentEditable) return true; const contentEditable = el.getAttribute?.("contenteditable"); if (contentEditable && contentEditable.toLowerCase() !== "false") { return true; } const tag = el.tagName?.toLowerCase(); return tag === "input" || tag === "textarea" || el.getAttribute?.("role") === "textbox"; } export function inferPasteTargetDocId({ focusedRowId, rowById, activeDocId, }: { focusedRowId: string | null; rowById: Map; activeDocId: string | null; }): string | null { if (focusedRowId) { const parsed = parseFileTreeRowId(focusedRowId); if (parsed?.kind === "doc") return parsed.docId; if (parsed?.kind === "index") return parsed.docId; if (parsed?.kind === "asset-folder") { const row = rowById.get(focusedRowId); return row?.kind === "asset-folder" ? row.docId : null; } if (parsed?.kind === "asset") { const row = rowById.get(focusedRowId); return row?.kind === "asset" ? row.docId : null; } } return activeDocId || null; }