Skip to content

Commit 78c2e66

Browse files
authored
fix(plugins): headings plugin has remove false by default (#187)
1 parent cc9aa51 commit 78c2e66

3 files changed

Lines changed: 83 additions & 11 deletions

File tree

docs/content/4.plugins/1.built-in/headings.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ links:
1818
variant: soft
1919
---
2020

21-
The `comark/plugins/headings` plugin extracts the page title and description from the top of a document and stores them in `tree.meta.title` and `tree.meta.description`. By default it reads the first `h1` as the title and the first `p` as the description, then removes both nodes from the tree so they are not rendered twice.
21+
The `comark/plugins/headings` plugin extracts the page title and description from the top of a document and stores them in `tree.meta.title` and `tree.meta.description`. By default it reads the first `h1` as the title and the first `p` as the description, keeping both nodes in the tree. Set `remove: true` to strip them so they are not rendered twice.
2222

2323
## Usage
2424

@@ -62,7 +62,7 @@ The description check always looks at the node **immediately after** the title
6262
|---|---|---|---|
6363
| [`titleTag`](#options-code-titletag) | `string` | `'h1'` | Element tag to extract as the page title |
6464
| [`descriptionTag`](#options-code-descriptiontag) | `string` | `'p'` | Element tag to extract as the page description |
65-
| [`remove`](#options-code-remove) | `boolean` | `true` | Remove extracted nodes from the tree after extraction |
65+
| [`remove`](#options-code-remove) | `boolean` | `false` | Remove extracted nodes from the tree after extraction |
6666

6767
### `titleTag`
6868

@@ -86,13 +86,13 @@ headings({ descriptionTag: 'blockquote' })
8686

8787
### `remove`
8888

89-
Whether to remove the extracted nodes from the tree after extraction. Set to `false` when you want the metadata available but still need the nodes rendered — for example, when a custom component handles the `h1` display.
89+
Whether to remove the extracted nodes from the tree after extraction. Set to `true` when you don't want the title and description rendered — for example, when a layout already displays them separately.
9090

9191
```typescript
92-
headings({ remove: false })
92+
headings({ remove: true })
9393
```
9494

95-
**Default:** `true`
95+
**Default:** `false`
9696

9797
---
9898

@@ -122,7 +122,7 @@ const result = await parse(content, {
122122

123123
console.log(result.meta.title) // "My Page Title"
124124
console.log(result.meta.description) // "This is the opening paragraph used as the description."
125-
// result.nodes no longer contains the h1 or the first p
125+
// result.nodes still contains the h1 and the first p (use remove: true to strip them)
126126
```
127127

128128
::

packages/comark/src/plugins/headings.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,19 @@ function flattenNodeText(node: ComarkNode): string {
5959
* content is written to `tree.meta.description`. When no title was found,
6060
* this check starts from the very first content node.
6161
*
62-
* Both nodes are removed from the tree by default so they are not rendered
63-
* twice. Set `remove: false` to keep them in place.
62+
* By default the extracted nodes are kept in the tree. Set `remove: true`
63+
* to strip them so they are not rendered twice.
6464
*
6565
* @example
6666
* ```ts
67-
* // Default — h1 as title, first paragraph as description
67+
* // Default — h1 as title, first paragraph as description, nodes kept in tree
6868
* headings()
6969
*
7070
* // Use a blockquote as the description instead of a paragraph
7171
* headings({ descriptionTag: 'blockquote' })
7272
*
73-
* // Extract metadata without removing the nodes from the tree
74-
* headings({ remove: false })
73+
* // Extract metadata and remove the matched nodes from the tree
74+
* headings({ remove: true })
7575
* ```
7676
*/
7777
export default defineComarkPlugin((options: HeadingsOptions = {}) => {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { parse } from '../src/parse'
3+
import headings from '../src/plugins/headings'
4+
5+
const CONTENT = `# My Page Title
6+
7+
This is the description paragraph.
8+
9+
## Section One
10+
11+
More content here.
12+
`
13+
14+
describe('headings plugin', () => {
15+
it('extracts title and description into meta', async () => {
16+
const tree = await parse(CONTENT, { plugins: [headings()] })
17+
18+
expect(tree.meta.title).toBe('My Page Title')
19+
expect(tree.meta.description).toBe('This is the description paragraph.')
20+
})
21+
22+
it('keeps extracted nodes in the tree by default (remove: false)', async () => {
23+
const tree = await parse(CONTENT, { plugins: [headings()] })
24+
25+
const tags = tree.nodes.filter((n) => Array.isArray(n)).map((n) => (n as any)[0])
26+
27+
expect(tags).toContain('h1')
28+
expect(tags).toContain('p')
29+
})
30+
31+
it('removes extracted nodes when remove: true', async () => {
32+
const tree = await parse(CONTENT, { plugins: [headings({ remove: true })] })
33+
34+
expect(tree.meta.title).toBe('My Page Title')
35+
expect(tree.meta.description).toBe('This is the description paragraph.')
36+
37+
const tags = tree.nodes.filter((n) => Array.isArray(n)).map((n) => (n as any)[0])
38+
39+
expect(tags).not.toContain('h1')
40+
expect(tags).toContain('h2')
41+
// The first <p> (description) should be removed, but the second section content stays
42+
const paragraphs = tree.nodes.filter((n) => Array.isArray(n) && (n as any)[0] === 'p')
43+
expect(
44+
paragraphs.every((p) => {
45+
const text = Array.isArray(p) && p.length > 2 ? String(p[2]) : ''
46+
return text !== 'This is the description paragraph.'
47+
})
48+
).toBe(true)
49+
})
50+
51+
it('does not set meta.title when no matching tag exists', async () => {
52+
const tree = await parse('Just a paragraph.\n', { plugins: [headings()] })
53+
54+
expect(tree.meta.title).toBeUndefined()
55+
expect(tree.meta.description).toBe('Just a paragraph.')
56+
})
57+
58+
it('uses custom titleTag and descriptionTag', async () => {
59+
const md = `## Custom Title
60+
61+
> Lead-in quote as description.
62+
63+
More content.
64+
`
65+
const tree = await parse(md, {
66+
plugins: [headings({ titleTag: 'h2', descriptionTag: 'blockquote' })],
67+
})
68+
69+
expect(tree.meta.title).toBe('Custom Title')
70+
expect(tree.meta.description).toBe('Lead-in quote as description.')
71+
})
72+
})

0 commit comments

Comments
 (0)