mirror of
https://github.com/stablyai/orca.git
synced 2026-10-15 08:03:08 +00:00
Include h1 headings in markdown table of contents
Previously h1 headings were excluded, treating them as a document-level root. Now h1 entries appear as TOC items alongside h2/h3, giving users a more complete and consistent navigation outline.
This commit is contained in:
@@ -5,54 +5,57 @@ import {
|
||||
} from './markdown-table-of-contents'
|
||||
|
||||
describe('markdown table of contents', () => {
|
||||
it('builds a nested h2-h3 outline excluding h1', () => {
|
||||
it('builds a nested h1-h3 outline', () => {
|
||||
const toc = buildMarkdownTableOfContents('# Intro\n\n## Setup\n\n### Install\n\n## Usage')
|
||||
|
||||
expect(toc).toEqual([
|
||||
{
|
||||
id: 'setup',
|
||||
level: 2,
|
||||
title: 'Setup',
|
||||
id: 'intro',
|
||||
level: 1,
|
||||
title: 'Intro',
|
||||
children: [
|
||||
{
|
||||
id: 'install',
|
||||
level: 3,
|
||||
title: 'Install',
|
||||
id: 'setup',
|
||||
level: 2,
|
||||
title: 'Setup',
|
||||
children: [
|
||||
{
|
||||
id: 'install',
|
||||
level: 3,
|
||||
title: 'Install',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'usage',
|
||||
level: 2,
|
||||
title: 'Usage',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'usage',
|
||||
level: 2,
|
||||
title: 'Usage',
|
||||
children: []
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('skips front matter, h1, and unsupported heading depths', () => {
|
||||
const toc = buildMarkdownTableOfContents(
|
||||
'---\ntitle: Doc\n---\n# Hidden H1\n## Visible\n#### Hidden H4'
|
||||
)
|
||||
it('skips front matter and unsupported heading depths', () => {
|
||||
const toc = buildMarkdownTableOfContents('---\ntitle: Doc\n---\n# Visible\n#### Hidden')
|
||||
|
||||
expect(toc.map((item) => item.title)).toEqual(['Visible'])
|
||||
})
|
||||
|
||||
it('skips headings inside fenced code blocks', () => {
|
||||
const toc = buildMarkdownTableOfContents(
|
||||
'# Hidden H1\n\n## Setup\n\n```sh\n# not a heading\n```\n\n## Real'
|
||||
)
|
||||
const toc = buildMarkdownTableOfContents('# Install\n\n```sh\n# not a heading\n```\n\n## Real')
|
||||
|
||||
expect(toc.map((item) => item.title)).toEqual(['Setup', 'Real'])
|
||||
expect(toc[0].children.map((item) => item.title)).toEqual(['Real'])
|
||||
})
|
||||
|
||||
it('includes rendered markdown heading forms', () => {
|
||||
const toc = buildMarkdownTableOfContents(
|
||||
'# Hidden H1\n\n ## Indented\n\nSetext *Title*\n---\n\n### https://example.com'
|
||||
'# Intro\n\n ## Indented\n\nSetext *Title*\n---\n\n### https://example.com'
|
||||
)
|
||||
|
||||
expect(toc).toEqual([
|
||||
expect(toc[0].children).toEqual([
|
||||
{
|
||||
id: 'indented',
|
||||
level: 2,
|
||||
@@ -76,13 +79,13 @@ describe('markdown table of contents', () => {
|
||||
})
|
||||
|
||||
it('uses GitHub-compatible duplicate slugs', () => {
|
||||
const toc = buildMarkdownTableOfContents('## Repeat\n## Repeat')
|
||||
const toc = buildMarkdownTableOfContents('# Repeat\n# Repeat')
|
||||
|
||||
expect(toc.map((item) => item.id)).toEqual(['repeat', 'repeat-1'])
|
||||
})
|
||||
|
||||
it('decodes HTML entities before slugging headings', () => {
|
||||
const toc = buildMarkdownTableOfContents('## A & B')
|
||||
const toc = buildMarkdownTableOfContents('# A & B')
|
||||
|
||||
expect(toc[0]).toMatchObject({
|
||||
id: 'a--b',
|
||||
|
||||
@@ -4,7 +4,7 @@ import remarkParse from 'remark-parse'
|
||||
import { unified } from 'unified'
|
||||
import { MarkdownHeadingSlugger } from './markdown-heading-slug'
|
||||
|
||||
export type MarkdownTocLevel = 2 | 3
|
||||
export type MarkdownTocLevel = 1 | 2 | 3
|
||||
|
||||
export type MarkdownTocItem = {
|
||||
children: MarkdownTocItem[]
|
||||
@@ -23,7 +23,7 @@ const htmlEntitiesForToc = new Map([
|
||||
])
|
||||
|
||||
function isMarkdownTocLevel(value: number): value is MarkdownTocLevel {
|
||||
return value === 2 || value === 3
|
||||
return value === 1 || value === 2 || value === 3
|
||||
}
|
||||
|
||||
// Scoped local fork of the tiny entities@6.0.1 surface Orca used here.
|
||||
@@ -99,7 +99,7 @@ function markdownAstNodeToText(node: MarkdownAstNode): string {
|
||||
|
||||
export function buildMarkdownTableOfContents(markdown: string): MarkdownTocItem[] {
|
||||
const slugger = new MarkdownHeadingSlugger()
|
||||
const root: MarkdownTocItem = { id: 'toc-root', level: 1, title: '', children: [] }
|
||||
const root = { id: 'toc-root', level: 1 as const, title: '', children: [] }
|
||||
const stack: MarkdownTocItem[] = [root]
|
||||
|
||||
// Why: parsing Markdown keeps the TOC aligned with rendered setext/GFM/entity
|
||||
|
||||
Reference in New Issue
Block a user