(root: string)
| 445 | /* ---------- Listing --------------------------------------------------- */ |
| 446 | |
| 447 | export async function listNotes(root: string): Promise<NoteMeta[]> { |
| 448 | const out: NoteMeta[] = [] |
| 449 | const walk = async ( |
| 450 | folder: NoteFolder, |
| 451 | dirAbs: string, |
| 452 | topAbs: string, |
| 453 | isPrimaryRoot: boolean |
| 454 | ): Promise<void> => { |
| 455 | let entries |
| 456 | try { |
| 457 | entries = await fs.readdir(dirAbs, { withFileTypes: true }) |
| 458 | } catch { |
| 459 | return |
| 460 | } |
| 461 | for (const entry of entries) { |
| 462 | const full = path.join(dirAbs, entry.name) |
| 463 | if (entry.isDirectory()) { |
| 464 | if (entry.name.startsWith('.')) continue |
| 465 | if (isFormDirName(entry.name)) continue // database folder — not loose notes |
| 466 | // When walking the vault root in primary='root' mode, system |
| 467 | // subdirectories (quick/, archive/, trash/, attachments) are |
| 468 | // not part of inbox — they're walked separately as their own |
| 469 | // top-level folder. |
| 470 | if (isPrimaryRoot && dirAbs === topAbs && HIDDEN_PRIMARY_ROOT_NAMES.has(entry.name)) { |
| 471 | continue |
| 472 | } |
| 473 | await walk(folder, full, topAbs, isPrimaryRoot) |
| 474 | continue |
| 475 | } |
| 476 | if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) { |
| 477 | out.push(await readMeta(root, full, folder)) |
| 478 | } |
| 479 | } |
| 480 | } |
| 481 | for (const folder of FOLDERS) { |
| 482 | const topAbs = await folderRoot(root, folder) |
| 483 | const isPrimaryRoot = folder === 'inbox' && path.resolve(topAbs) === path.resolve(root) |
| 484 | await walk(folder, topAbs, topAbs, isPrimaryRoot) |
| 485 | } |
| 486 | return out |
| 487 | } |
| 488 | |
| 489 | export async function listFolders(root: string): Promise<{ folder: NoteFolder; subpath: string }[]> { |
| 490 | const out: { folder: NoteFolder; subpath: string }[] = [] |
no test coverage detected