// src/utils/folder-path.ts // // Shared folder-path normalization and validation. Used by: // - src/editor/attachment-settings.ts (resolver input; permissive) // - src/plugins/daily-notes (settings; validation + normalize) // - src/layout/settings/FolderPickerPopup (inline-create input; UX validation) // // Two layers: // normalizeFolderPath: whitespace-trim, slash-trim, collapse `/+`. Returns // empty string for root-equivalent inputs. Pure shape // transformation; never inspects segment content. // validateFolderPath: normalize + reject paths containing `.`/`..`/blank // segments OR segments whose own whitespace differs // from their trim. Returns either the normalized form // or a user-readable rejection reason. // // Two layers because callers have different concerns: // - The resolver in attachment-settings just needs "give me a clean path or // vault root"; bad input falls back to root rather than throwing. // - The UI submit path needs a discriminated success/failure so it can show // a useful error message. export interface ValidatedFolderPath { ok: true /** The normalized (canonical) form of the input. Never starts/ends with `/`, * never contains `//`. Empty string means vault root. */ normalized: string } export interface InvalidFolderPath { ok: false /** Human-readable rejection reason suitable for inline UI display. */ reason: string } export type FolderPathValidation = ValidatedFolderPath | InvalidFolderPath /** * Normalize a folder path. Pure shape transformation: * - Trim outer whitespace. * - Strip leading and trailing slashes. * - Collapse consecutive slashes (`a//b` → `a/b`). * Returns empty string for root-equivalent inputs (`''`, `'/'`, `' '`). * * Does NOT inspect segment content. Callers that need strict checks * (reject `..`, blank segments, inner whitespace) should use * `validateFolderPath` instead. */ export function normalizeFolderPath(raw: string): string { return raw .trim() .replace(/\/+/g, '/') .replace(/^\/+/, '') .replace(/\/+$/, '') } /** * Validate a folder path for use as a vault-relative folder reference. * Normalizes the input, then rejects it if any segment is: * - `.` or `..` — path traversal * - empty after trim — would create `//` * - differs from its own trim — leading/trailing whitespace inside a segment * * Empty input (after normalize) is treated as the vault root and considered * valid (`{ ok: true, normalized: '' }`). Callers that require a non-empty * folder (e.g. inline-create UI) must check `normalized !== ''` themselves. * * The rejection reasons are intentionally short and user-facing. */ export function validateFolderPath(raw: string): FolderPathValidation { const normalized = normalizeFolderPath(raw) if (normalized === '') return { ok: true, normalized: '' } const segments = normalized.split('/') for (const seg of segments) { if (seg === '.' || seg === '..') { return { ok: false, reason: `Path segment '${seg}' is not allowed.` } } if (seg !== seg.trim()) { return { ok: false, reason: `Segment '${seg}' has leading or trailing whitespace.` } } } return { ok: true, normalized } }