0.1.04 文件树与导入mmap
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"use client";
|
||||
|
||||
import type { FileTreeRow } from "./types";
|
||||
import { parseFileTreeRowId } from "./types";
|
||||
|
||||
export type FileTreeClipboardAction = "copy";
|
||||
|
||||
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<FileTreeClipboardPayloadV1>;
|
||||
if (parsed?.type !== "mnote-file-tree" || parsed.version !== 1) return null;
|
||||
if (parsed.action !== "copy") 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<void> {
|
||||
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<FileTreeClipboardPayloadV1 | null> {
|
||||
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 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<string, FileTreeRow>;
|
||||
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") {
|
||||
const row = rowById.get(focusedRowId);
|
||||
return row?.kind === "asset" ? row.docId : null;
|
||||
}
|
||||
}
|
||||
return activeDocId || null;
|
||||
}
|
||||
Reference in New Issue
Block a user