// Copyright (c) Meta Platforms, Inc. and affiliates. /** * @file parseOutlineFromMarkdown.ts * @input Uses Markdown's canonical AST parser and heading slug helpers * @output Exports parseOutlineFromMarkdown for extracting heading outlines from Markdown * @position Pure compatibility utility; consumed by useOutlineFromMarkdown and public exports * * SYNC: When modified, update these files to stay in sync: * - /packages/core/src/Outline/Outline.doc.mjs * - /packages/core/src/Outline/index.ts */ import { parseMarkdownAstInternal, slugify, uniqueSlug } from "../Markdown/parser.js"; import { markdownAstText } from "../Markdown/ast.js"; import { markdownExtensionText, prepareMarkdownPlugins } from "../Markdown/plugins/protocol.js"; import type { MarkdownExtensionNode, MarkdownPluginEntry } from "../Markdown/plugins/protocol.js"; import type { OutlineItem } from "./types.js"; /** * Extract heading items from a Markdown string. * * Uses Markdown's parser so fenced code blocks, tables, lists, and inline * formatting are interpreted consistently with rendered Markdown output. * Ids come from the parser's shared slug helpers, so they always match the * `id` attributes Markdown renders on its headings. */ export interface ParseOutlineFromMarkdownOptions { readonly plugins?: ReadonlyArray>; /** Match Markdown's transform finality while content is streaming. */ readonly isFinal?: boolean; } export function parseOutlineFromMarkdown(markdown: string, options?: ParseOutlineFromMarkdownOptions): OutlineItem[] { const prepared = options?.plugins == null ? undefined : prepareMarkdownPlugins(options.plugins); const counts = new Map(); return parseMarkdownAstInternal(markdown, { plugins: options?.plugins }, options?.isFinal ?? true).children.filter(block => block.type === 'heading').map(block => { const label = markdownAstText(block.children, node => markdownExtensionText(prepared, node)).trim(); return { id: uniqueSlug(slugify(label), counts), label, level: block.depth }; }); }