Something went wrong. Try again.
A local-first note taking app
Something went wrong. Try again.
TypeScript
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576/** * Path helpers for wikilink resolution. * * Deliberately separate from `filesystem/vaultFs.ts`: those helpers are used by * the providers and by move-detection, and their lack of `.`/`..` handling is * load-bearing there. Wikilink targets are user-typed, so they need real * normalization — but fixing it in place would change behaviour well outside * this feature. */
/** Vault files are markdown; a bare `[[Note]]` means `Note.md`. */const MD_EXTENSION = '.md';
/** * Normalize a user-typed relative path: `\` → `/`, collapse repeated slashes, * drop `.` segments, resolve `..`, and strip any leading `/` or `./`. * * `..` that would escape the vault root is dropped rather than propagated — * a wikilink cannot address anything outside its own vault. */export function normalizeRelPath(input: string): string { const segments = input.replace(/\\/g, '/').split('/'); const out: string[] = []; for (const segment of segments) { if (segment === '' || segment === '.') continue; if (segment === '..') { out.pop(); // at the root this is a no-op: clamp instead of escaping. continue; } out.push(segment); } return out.join('/');}
/** Strip a trailing `.md` (case-insensitively); `[[Note.md]]` means `[[Note]]`. */export function stripMdExtension(name: string): string { return name.toLowerCase().endsWith(MD_EXTENSION) ? name.slice(0, -MD_EXTENSION.length) : name;}
/** * Append `.md` unless it is already there. * * Note this is NOT what `FileSystemProvider.createFile` does on its own: it * only appends when the name contains no dot at all, so it would create * `[[Release v1.2]]` as an extensionless file. Callers pass an explicit name * through this instead. */export function ensureMdExtension(name: string): string { return name.toLowerCase().endsWith(MD_EXTENSION) ? name : name + MD_EXTENSION;}
/** `entryId` expressed relative to `rootId`; `entryId` unchanged if outside. */export function relPathFrom(rootId: string, entryId: string): string { const root = rootId.replace(/\\/g, '/').replace(/\/+$/, ''); const entry = entryId.replace(/\\/g, '/'); if (!root) return normalizeRelPath(entry); return entry.startsWith(root + '/') ? entry.slice(root.length + 1) : normalizeRelPath(entry);}
/** Last path segment. */export function baseName(path: string): string { const parts = path.replace(/\\/g, '/').split('/'); return parts[parts.length - 1] ?? '';}
/** Everything before the last path segment (no trailing slash). */export function dirName(path: string): string { const normalized = path.replace(/\\/g, '/'); const cut = normalized.lastIndexOf('/'); return cut <= 0 ? '' : normalized.slice(0, cut);}