06b8d9f64b
- WrappedRun Prisma model + migration (aggregates only, no note names/text) - /wrapped authed route: FSA + webkitdirectory + drag-drop reader, Web Worker parser - archetype + score heuristics, ДС-2 result card - POST /api/wrapped/run (userId from session), public /share/wrapped/[token] - next/og OG image (Satori+resvg-wasm, alpine-safe) + Fira Mono ttf - middleware: /share/wrapped public Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.0 KiB
TypeScript
80 lines
3.0 KiB
TypeScript
// Чтение хранилища в браузере — три пути ввода, все → RawNote[].
|
|
// Содержимое заметок остаётся в браузере (приватность-моат).
|
|
|
|
import type { RawNote } from "./types";
|
|
|
|
const SKIP = ["/.obsidian/", "/.trash/", "/.git/"];
|
|
function isMd(path: string): boolean {
|
|
return path.toLowerCase().endsWith(".md") && !SKIP.some((s) => path.includes(s));
|
|
}
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
|
|
export function supportsFSA(): boolean {
|
|
return typeof window !== "undefined" && "showDirectoryPicker" in window;
|
|
}
|
|
|
|
// 1. File System Access API (Chromium: Chrome/Edge/Brave)
|
|
export async function readViaFSA(): Promise<RawNote[]> {
|
|
const dir: any = await (window as any).showDirectoryPicker();
|
|
const notes: RawNote[] = [];
|
|
async function walk(handle: any, prefix: string): Promise<void> {
|
|
for await (const [name, h] of handle.entries()) {
|
|
const path = prefix + "/" + name;
|
|
if (h.kind === "directory") {
|
|
if (name.startsWith(".")) continue;
|
|
await walk(h, path);
|
|
} else if (isMd(path)) {
|
|
const file: File = await h.getFile();
|
|
notes.push({ name, text: await file.text(), ctime: file.lastModified, mtime: file.lastModified });
|
|
}
|
|
}
|
|
}
|
|
await walk(dir, "");
|
|
return notes;
|
|
}
|
|
|
|
// 2. <input type="file" webkitdirectory> (Safari / Firefox)
|
|
export async function readViaInput(files: FileList): Promise<RawNote[]> {
|
|
const notes: RawNote[] = [];
|
|
for (const file of Array.from(files)) {
|
|
const path = (file as any).webkitRelativePath || file.name;
|
|
if (!isMd(path)) continue;
|
|
notes.push({ name: file.name, text: await file.text(), ctime: file.lastModified, mtime: file.lastModified });
|
|
}
|
|
return notes;
|
|
}
|
|
|
|
// 3. drag-drop папки (webkitGetAsEntry)
|
|
export async function readViaDrop(items: DataTransferItemList): Promise<RawNote[]> {
|
|
const notes: RawNote[] = [];
|
|
const roots: any[] = [];
|
|
for (const item of Array.from(items)) {
|
|
const entry = (item as any).webkitGetAsEntry?.();
|
|
if (entry) roots.push(entry);
|
|
}
|
|
async function readEntry(entry: any, prefix: string): Promise<void> {
|
|
const path = prefix + "/" + entry.name;
|
|
if (entry.isDirectory) {
|
|
if (entry.name.startsWith(".")) return;
|
|
const reader = entry.createReader();
|
|
const children: any[] = await new Promise((resolve) => {
|
|
const all: any[] = [];
|
|
const readBatch = () =>
|
|
reader.readEntries((batch: any[]) => {
|
|
if (!batch.length) return resolve(all);
|
|
all.push(...batch);
|
|
readBatch();
|
|
});
|
|
readBatch();
|
|
});
|
|
for (const c of children) await readEntry(c, path);
|
|
} else if (isMd(path)) {
|
|
const file: File = await new Promise((resolve, reject) => entry.file(resolve, reject));
|
|
notes.push({ name: entry.name, text: await file.text(), ctime: file.lastModified, mtime: file.lastModified });
|
|
}
|
|
}
|
|
for (const e of roots) await readEntry(e, "");
|
|
return notes;
|
|
}
|