/** * Content body helpers — pure, dependency-free logic for reading and * shaping the Markdown sidecar that sits next to each record. * * Five concerns live here, all framework-agnostic so any renderer * (Astro today, future options) can compose them: * * 1. `resolveContentPath(contentPath, candidates?)` * Locate a `ProjectRecord.content` path on disk. The candidates * list mirrors the conventions a CLI-scaffolded project uses * (`./content/...`, bare `content/...`, the legacy `apps/example` * form for older workspaces). Returns the first existing absolute * path, or null. * * 2. `readContentFile(contentPath, candidates?)` * Combine resolve + read + frontmatter strip. Returns * `{ body, frontmatter }` where `body` is the Markdown without its * leading `---...---` block and `frontmatter` is the YAML text * between the fences (or empty string when there's no frontmatter). * * 3. `stripFrontmatter(text)` — single source of truth for the * leading-YAML-block rule. Used by `readContentFile`; also * exported for consumers who already have the file contents in * memory. * * 4. `extractToc(body, { maxDepth })` — pull h2/h3 headings out of a * body so a record page can render a Table-of-Contents. IDs go * through `slug.uniqueSlug` so they match the IDs the markdown * renderer will emit (and so two TOC entries with the same * label get `foo`, `foo-2`, `foo-3` rather than colliding). * * 5. `readingMetrics(body, { wpm })` — word count + minutes for the * "X min read" pill on a detail page. Returns zeros (not throws) * for empty input. * * Why a separate module instead of tacking these onto `markdown.ts`? * That file is the **awesome-list importer** (parses READMEs into * Grove records). Conflating record-side rendering helpers with * importer-side parsing helpers would re-create the * "what does this file do?" confusion that already exists between * `parseReadme.ts` and `markdown.ts`. */ /** * Resolve a record's `content` path against a list of candidate * roots. Returns the absolute path of the first existing candidate, * or null when none match. */ export declare function resolveContentPath(contentPath: string, candidates?: string[]): string | null; /** * Strip a leading YAML frontmatter block (`---\n...\n---`) from a * Markdown string. Returns the body with the frontmatter removed; * callers that need the frontmatter contents should use * `readContentFile` instead. * * Restricts the search to the first 200 lines so a `---` later in * the document (e.g. as a horizontal rule) isn't mistaken for the * closing fence. */ export declare function stripFrontmatter(text: string): string; export interface ReadContentFileResult { /** Markdown body with frontmatter stripped. */ body: string; /** Raw YAML text between the frontmatter fences (no fences, no trim). */ frontmatter: string; /** Absolute path the file was resolved from. */ path: string; } /** * Read a record's content file from disk and split it into * frontmatter + body. Returns null when the file can't be located. */ export declare function readContentFile(contentPath: string, candidates?: string[]): ReadContentFileResult | null; /** * Slug used for heading anchors. Unlike `slug.ts:slugify`, this is * GitHub-flavoured: * - drops diacritics / smart quotes the same way `slug.ts` does * (so README-style copy-paste gives predictable IDs) * - replaces spaces with hyphens (rather than dropping everything * that isn't `[a-z0-9]`) * - collapses repeated hyphens * - trims leading/trailing hyphens * - no length cap (headings are short by nature) * * The collision counter (via `uniqueSlug`) gives `foo`, `foo-2`, … * so two headings labelled "Examples" in the same document anchor * to distinct IDs. * * Exported because the markdown renderer in `@grove-dev/astro` uses * this same slug rule so the IDs it emits on `