/** * 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 `

` line up * with the IDs `extractToc` produces from the same body. Duplicating * the rule is the documented failure mode this comment warns against. */ export declare function headingSlug(text: string): string; export interface TocEntry { /** Display text of the heading (Markdown syntax stripped). */ text: string; /** Stable anchor id (kebab-case, collision-suffixed). */ id: string; /** Heading depth — 2 or 3 by default. */ depth: 2 | 3 | 4 | 5 | 6; } export interface ExtractTocOptions { /** Highest heading depth to include (default 2 → only `##` entries). */ maxDepth?: 2 | 3 | 4 | 5 | 6; } /** * Pull a flat list of `{ text, id, depth }` out of a Markdown body, * skipping any leading frontmatter block. ID collisions are resolved * via `uniqueSlug` so the returned IDs match what a markdown→HTML * renderer (configured with the same slug rules) will emit. */ export declare function extractToc(body: string, options?: ExtractTocOptions): TocEntry[]; export interface ReadingMetrics { /** Whitespace-separated token count of the body. */ wordCount: number; /** Reading time in minutes, rounded up, never less than 1. */ minutes: number; } export interface ReadingMetricsOptions { /** Words-per-minute baseline (default 200). */ wpm?: number; } /** * Word count + minutes for "X min read". Whitespace-only and empty * bodies return `{ wordCount: 0, minutes: 1 }` — the `minutes: 1` * floor prevents detail pages from rendering "0 min" for short * placeholder bodies. */ export declare function readingMetrics(body: string, options?: ReadingMetricsOptions): ReadingMetrics; //# sourceMappingURL=content-body.d.ts.map