Skip to content

Commit 81b1336

Browse files
committed
fix(markdown): do not escape extracted headers (close #117)
1 parent 8b9ac6c commit 81b1336

File tree

5 files changed

+153
-12
lines changed

5 files changed

+153
-12
lines changed

Diff for: packages/@vuepress/markdown/__tests__/plugins/extractHeadersPlugin.spec.ts

+75-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import * as MarkdownIt from 'markdown-it'
2-
import { extractHeadersPlugin } from '@vuepress/markdown'
3-
import type { MarkdownEnv } from '@vuepress/markdown'
2+
import { anchorPlugin, extractHeadersPlugin, slugify } from '@vuepress/markdown'
3+
import type { MarkdownEnv, MarkdownHeader } from '@vuepress/markdown'
44

55
const fixtures = {
66
simpleTree: `\
7-
# h1
8-
## h2
9-
### h3
10-
#### h4
11-
##### h5
12-
###### h6
7+
# h1
8+
## h2
9+
### h3
10+
#### h4
11+
##### h5
12+
###### h6
1313
`,
1414
complexTree: `\
1515
# s1
@@ -76,4 +76,71 @@ describe('@vuepress/markdown > plugins > extractHeadersPlugin', () => {
7676
})
7777
})
7878
})
79+
80+
describe('should not include html elements and should not escape texts', () => {
81+
const md = MarkdownIt({
82+
html: true,
83+
})
84+
.use(anchorPlugin, { slugify })
85+
.use(extractHeadersPlugin, { slugify })
86+
87+
const testCases: [string, MarkdownHeader[]][] = [
88+
// html element should be ignored
89+
[
90+
'## foo <bar />',
91+
[
92+
{
93+
level: 2,
94+
title: 'foo',
95+
slug: 'foo',
96+
children: [],
97+
},
98+
],
99+
],
100+
// inline code should not be escaped
101+
[
102+
'## foo <bar/> `<code />`',
103+
[
104+
{
105+
level: 2,
106+
title: 'foo <code />',
107+
slug: 'foo-code',
108+
children: [],
109+
},
110+
],
111+
],
112+
// text should not be escaped
113+
[
114+
'## foo <bar/> "baz"',
115+
[
116+
{
117+
level: 2,
118+
title: 'foo "baz"',
119+
slug: 'foo-baz',
120+
children: [],
121+
},
122+
],
123+
],
124+
// text should not be escaped
125+
[
126+
'## < test >',
127+
[
128+
{
129+
level: 2,
130+
title: '< test >',
131+
slug: 'test',
132+
children: [],
133+
},
134+
],
135+
],
136+
]
137+
138+
testCases.forEach(([source, expected], i) =>
139+
it(`case ${i}`, () => {
140+
const env: MarkdownEnv = {}
141+
md.render(source, env)
142+
expect(env.headers).toEqual(expected)
143+
})
144+
)
145+
})
79146
})

Diff for: packages/@vuepress/markdown/__tests__/plugins/tocPlugin.spec.ts

+69-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as MarkdownIt from 'markdown-it'
2-
import { tocPlugin } from '@vuepress/markdown'
2+
import { anchorPlugin, tocPlugin, slugify } from '@vuepress/markdown'
33

44
const fixtures = {
55
simpleTree: `\
@@ -75,4 +75,72 @@ describe('@vuepress/markdown > plugins > tocPlugin', () => {
7575
})
7676
})
7777
})
78+
79+
describe('should include html elements and should escape texts', () => {
80+
const md = MarkdownIt({
81+
html: true,
82+
})
83+
.use(anchorPlugin, { slugify })
84+
.use(tocPlugin, { slugify })
85+
86+
const testCases: [string, { slug: string; title: string; h2: string }][] = [
87+
// html element should be kept as is
88+
[
89+
`\
90+
[[toc]]
91+
## foo <bar />
92+
`,
93+
{
94+
slug: 'foo',
95+
title: 'foo <bar />',
96+
h2: 'foo <bar />',
97+
},
98+
],
99+
// inline code should be escaped
100+
[
101+
`\
102+
[[toc]]
103+
## foo <bar /> \`<code />\`
104+
`,
105+
{
106+
slug: 'foo-code',
107+
title: 'foo <bar /> &lt;code /&gt;',
108+
h2: 'foo <bar /> <code>&lt;code /&gt;</code>',
109+
},
110+
],
111+
// text should be escaped
112+
[
113+
`\
114+
[[toc]]
115+
## foo <bar/> "baz"
116+
`,
117+
{
118+
slug: 'foo-baz',
119+
title: 'foo <bar/> &quot;baz&quot;',
120+
h2: 'foo <bar/> &quot;baz&quot;',
121+
},
122+
],
123+
// text should be escaped
124+
[
125+
`\
126+
[[toc]]
127+
## < test >
128+
`,
129+
{
130+
slug: 'test',
131+
title: '&lt; test &gt;',
132+
h2: '&lt; test &gt;',
133+
},
134+
],
135+
]
136+
137+
testCases.forEach(([source, expected], i) =>
138+
it(`case ${i}`, () => {
139+
expect(md.render(source)).toEqual(`\
140+
<nav class="table-of-contents"><ul><li><a href="#${expected.slug}">${expected.title}</a></li></ul></nav>
141+
<h2 id="${expected.slug}">${expected.h2}</h2>
142+
`)
143+
})
144+
)
145+
})
78146
})

Diff for: packages/@vuepress/markdown/src/plugins/extractHeadersPlugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const extractHeadersPlugin: PluginWithOptions<ExtractHeadersPluginOptions
4646
headers = resolveHeadersFromTokens(state.tokens, {
4747
level,
4848
allowHtml: false,
49+
escapeText: false,
4950
slugify,
5051
format,
5152
})

Diff for: packages/@vuepress/markdown/src/plugins/tocPlugin/tocPlugin.ts

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export const tocPlugin: PluginWithOptions<TocPluginOptions> = (
105105
headers = resolveHeadersFromTokens(state.tokens, {
106106
level,
107107
allowHtml: true,
108+
escapeText: true,
108109
slugify,
109110
format,
110111
})

Diff for: packages/@vuepress/markdown/src/utils/resolveHeadersFromTokens.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ export const resolveHeadersFromTokens = (
1010
{
1111
level,
1212
allowHtml,
13+
escapeText,
1314
slugify,
1415
format,
1516
}: {
1617
level: number[]
1718
allowHtml: boolean
19+
escapeText: boolean
1820
slugify: (str: string) => string
1921
format?: (str: string) => string
2022
}
@@ -84,9 +86,11 @@ export const resolveHeadersFromTokens = (
8486
// get title from tokens
8587
const title = titleTokens
8688
.reduce((result, item) => {
87-
// escape content of 'code_inline' and 'text'
88-
if (item.type === 'code_inline' || item.type === 'text') {
89-
return `${result}${htmlEscape(item.content)}`
89+
if (escapeText) {
90+
// escape the content of 'code_inline' and 'text'
91+
if (item.type === 'code_inline' || item.type === 'text') {
92+
return `${result}${htmlEscape(item.content)}`
93+
}
9094
}
9195

9296
// keep the content of 'emoji' and 'html_inline'

0 commit comments

Comments
 (0)