Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
5.0 kB · 132 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133/** * Shell filesystem abstraction. Future: readFile, watch, capabilities, etc. * * Listing and live documents are separate concerns: a source may implement * `listFiles` only (e.g. disk) or also expose a `documents` backend injected * at the composition root (`App.tsx`). */import type { EditorKind } from '../editors/types';import type { DocumentProvider } from '../documents/types';
/** Opaque to the shell UI; used as React `key` and selection id. */export type FileSystemEntry = { id: string; name: string;};
/** Raw filesystem directory entry (name + type flags). */export type FsDirent = { name: string; isDirectory: boolean; isFile: boolean;};
/** Recursive tree node returned by `getTree()`. */export type FileSystemTreeNode = { id: string; name: string; type: 'file' | 'folder'; children?: FileSystemTreeNode[];};
export type FileSystemProvider = { /** Stable key for React and selection state (e.g. `habitat-docs`). */ id: string; /** Shown as the sidebar section heading. */ displayName: string; /** Id of this source's root directory; the move target for drops at the root. */ readonly rootId: string; /** When `isReady()` is false, shell may show this instead of a generic idle line. */ idleHint?: string; /** When false, skip `listFiles` and show idle state. */ isReady(): boolean; /** * Optional: subscribe to `isReady()` transitions (e.g. user signs in). * The listener is called when readiness may have changed; return value * unsubscribes. Used by the sidebar to re-run `listFiles` after login. */ subscribeReady?(listener: () => void): () => void; /** List entries for this source; reject on transport errors. */ listFiles(): Promise<FileSystemEntry[]>; /** * Build a recursive tree of the source's contents. * Providers without directories return a flat list of files. */ getTree(): Promise<FileSystemTreeNode[]>; /** * Create a new persisted entry in this source (e.g. a new Habitat docs * record). The shell optimistically inserts it, then refreshes the listing. */ createFile( parentId?: string, name?: string, /** Initial file contents. Defaults to empty. */ content?: string, ): Promise<FileSystemEntry>; /** * Create a new directory in this source. * Providers without directory support may throw or no-op. */ createDirectory(name: string, parentId?: string): Promise<FileSystemEntry>; /** * Rename an entry (file or directory). * Returns the updated entry with the new id and name. */ renameFile(entryId: string, newName: string): Promise<FileSystemEntry>; /** * Move an entry into a different directory (`targetDirId`). Returns the * updated entry with its new id. No-ops when the target is the current parent. */ moveFile(entryId: string, targetDirId: string): Promise<FileSystemEntry>; /** * Delete an entry (file or directory) permanently. */ deleteFile(entryId: string): Promise<void>; /** * Duplicate a file. Returns the new entry. */ copyFile(entryId: string): Promise<FileSystemEntry>; /** * Get the absolute filesystem path for an entry. * Used for "Copy Path" to clipboard. */ getPath(entryId: string): string; /** * Read a text file's contents, for cheap read-only inspection of notes that * are not open — today, offering their headings in the `[[` typeahead. * * Deliberately not `documents.openDocument`: for a synced vault that pushes * the file to the remote and acquires a live session per call, which would be * catastrophic to do per keystroke. A synced vault mirrors to disk on a ~1s * debounce, so this can lag edits made elsewhere by about a second — fine for * a typeahead. */ readFileText(entryId: string): Promise<string>; /** * When implemented, called when a listed entry's display name changes * (e.g. habitat doc heading edits). Used to keep the sidebar in sync. */ subscribeEntryTitles?( listener: (entryId: string, name: string) => void, ): () => void; /** * Backend kind used when creating new documents from this source. * TODO: Let users pick the default per source (or globally) in settings. */ /** Default editor/sync backend when creating documents from this source. */ defaultEditorKind: EditorKind; /** Live-document backend, injected at the composition root (`App.tsx`). */ documents: DocumentProvider;};
/** Low-level filesystem API used by providers that access the host disk. */export interface FilesystemApi { readdir(dirPath: string): Promise<FsDirent[]>; readFile(filePath: string): Promise<string>; writeFile(filePath: string, content: string): Promise<void>; fileExists(filePath: string): Promise<boolean>; mkdir(dirPath: string): Promise<void>; rename(oldPath: string, newPath: string): Promise<void>; deletePath(filePath: string): Promise<void>; copyFile(srcPath: string, destPath: string): Promise<void>;}