Skip to content

Commit 885496e

Browse files
authored
feat: support writing HTML(Vue) anywhere in the header. (vuejs#711)
1. You can write HTML(Vue) anywhere in the header as long as it is not wrapped by code(`). 2. The HTML wrapped by code will be shown as it is. 3. A good practice when using HTML in a header is to leave a space between plain text and HTML.
1 parent fa8b055 commit 885496e

File tree

2 files changed

+108
-17
lines changed

2 files changed

+108
-17
lines changed

lib/util/parseHeaders.js

+28-9
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
// Since VuePress needs to extract the header from the markdown source
2+
// file and display it in the sidebar or title (#238), this file simply
3+
// removes some unnecessary elements to make header displays well at
4+
// sidebar or title.
5+
//
6+
// But header's parsing in the markdown content is done by the markdown
7+
// loader based on markdown-it. markdown-it parser will will always keep
8+
// HTML in headers, so in VuePress, after being parsed by the markdiwn
9+
// loader, the raw HTML in headers will finally be parsed by Vue-loader.
10+
// so that we can write HTML/Vue in the header. One exception is the HTML
11+
// wrapped by <code>(markdown token: '`') tag.
12+
113
const { compose } = require('./shared')
214

315
const parseEmojis = str => {
@@ -12,26 +24,33 @@ const unescapeHtml = html => String(html)
1224
.replace(/&lt;/g, '<')
1325
.replace(/&gt;/g, '>')
1426

15-
const removeMarkdownToken = str => String(str)
27+
const removeMarkdownTokens = str => String(str)
1628
.replace(/\[(.*)\]\(.*\)/, '$1') // []()
1729
.replace(/(`|\*{1,3}|_)(.*?[^\\])\1/g, '$2') // `{t}` | *{t}* | **{t}** | ***{t}*** | _{t}_
1830
.replace(/(\\)(\*|_|`)/g, '$2') // remove escape char '\'
1931

20-
exports.removeTailHtml = (str) => {
21-
return String(str).replace(/\s*?<.*>\s*$/g, '')
32+
const trim = str => str.trim()
33+
34+
// This method remove the raw HTML but reserve the HTML wrapped by `<code>`.
35+
// e.g.
36+
// Input: "<a> b", Output: "b"
37+
// Input: "`<a>` b", Output: "`<a>` b"
38+
exports.removeNonCodeWrappedHTML = (str) => {
39+
return String(str).replace(/(^|[^><`])<.*>([^><`]|$)/g, '$1$2')
2240
}
2341

24-
// Only remove some md tokens.
42+
// Unescape html, parse emojis and remove some md tokens.
2543
exports.parseHeaders = compose(
2644
unescapeHtml,
2745
parseEmojis,
28-
removeMarkdownToken
46+
removeMarkdownTokens,
47+
trim
2948
)
3049

31-
// Also clean the tail html in headers.
32-
// Since we want to support tailed badge in headers.
33-
// See: https://vuepress.vuejs.org/guide/using-vue.html#badge
50+
// Also clean the html that isn't wrapped by code.
51+
// Because we want to support using VUE components in headers.
52+
// e.g. https://vuepress.vuejs.org/guide/using-vue.html#badge
3453
exports.deeplyParseHeaders = compose(
35-
exports.removeTailHtml,
54+
exports.removeNonCodeWrappedHTML,
3655
exports.parseHeaders,
3756
)

test/util/parseHeaders.spec.js

+80-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
parseHeaders,
3-
removeTailHtml,
3+
removeNonCodeWrappedHTML,
44
deeplyParseHeaders
55
} from '@/util/parseHeaders'
66

@@ -32,15 +32,87 @@ describe('parseHeaders', () => {
3232
})
3333
})
3434

35-
test('should remove tail html correctly', () => {
36-
expect(removeTailHtml('# H1 <Comp></Comp>')).toBe('# H1')
37-
expect(removeTailHtml('# H1 <Comp a="b"></Comp>')).toBe('# H1')
38-
expect(removeTailHtml('# H1 <Comp/>')).toBe('# H1')
39-
expect(removeTailHtml('# H1 <Comp a="b"/>')).toBe('# H1')
35+
test('should remove non-code-wrapped html correctly', () => {
36+
const asserts = {
37+
// Remove tail html
38+
'# H1 <Comp></Comp>': '# H1 ',
39+
'# H1<Comp></Comp>': '# H1',
40+
'# H1 <Comp a="b"></Comp>': '# H1 ',
41+
'# H1<Comp a="b"></Comp>': '# H1',
42+
'# H1 <Comp/>': '# H1 ',
43+
'# H1<Comp/>': '# H1',
44+
'# H1 <Comp a="b"/>': '# H1 ',
45+
'# H1<Comp a="b"/>': '# H1',
46+
47+
// Reserve code-wrapped tail html
48+
'# H1 `<Comp></Comp>`': '# H1 `<Comp></Comp>`',
49+
'# H1 `<Comp a="b"></Comp>`': '# H1 `<Comp a="b"></Comp>`',
50+
'# H1 `<Comp/>`': '# H1 `<Comp/>`',
51+
'# H1 `<Comp a="b"/>`': '# H1 `<Comp a="b"/>`',
52+
53+
// Remove leading html
54+
'# <Comp></Comp> H1': '# H1',
55+
'# <Comp></Comp>H1': '# H1',
56+
'# <Comp a="b"></Comp> H1': '# H1',
57+
'# <Comp a="b"></Comp>H1': '# H1',
58+
'# <Comp/> H1': '# H1',
59+
'# <Comp/>H1': '# H1',
60+
'# <Comp a="b"/> H1': '# H1',
61+
'# <Comp a="b"/>H1': '# H1',
62+
63+
// Reserve code-wrapped leading html
64+
'# `<Comp></Comp>` H1': '# `<Comp></Comp>` H1',
65+
'# `<Comp a="b"></Comp>` H1': '# `<Comp a="b"></Comp>` H1',
66+
'# `<Comp/>` H1': '# `<Comp/>` H1',
67+
'# `<Comp a="b"/>` H1': '# `<Comp a="b"/>` H1',
68+
69+
// Remove middle html
70+
'# H1 <Comp></Comp> H2': '# H1 H2',
71+
'# H1 <Comp a="b"></Comp> H2': '# H1 H2',
72+
'# H1 <Comp/> H2': '# H1 H2',
73+
'# H1 <Comp a="b"/> H2': '# H1 H2',
74+
75+
// Reserve code-wrapped middle html
76+
'# H1 `<Comp></Comp>` H2': '# H1 `<Comp></Comp>` H2',
77+
'# H1 `<Comp a="b"></Comp>` H2': '# H1 `<Comp a="b"></Comp>` H2',
78+
'# H1 `<Comp/>` H2': '# H1 `<Comp/>` H2',
79+
'# H1 `<Comp a="b"/>` H2': '# H1 `<Comp a="b"/>` H2'
80+
}
81+
82+
Object.keys(asserts).forEach(input => {
83+
expect(removeNonCodeWrappedHTML(input)).toBe(asserts[input])
84+
})
4085
})
4186

4287
test('should deeplyParseHeaders transformed as expected', () => {
43-
expect(deeplyParseHeaders('# `H1` <Comp></Comp>')).toBe('# H1')
44-
expect(deeplyParseHeaders('# *H1* <Comp/>')).toBe('# H1')
88+
const asserts = {
89+
// Remove tail html
90+
'# `H1` <Comp></Comp>': '# H1',
91+
'# *H1* <Comp/>': '# H1',
92+
93+
// Reserve code-wrapped tail html
94+
'# `H1` `<Comp></Comp>`': '# H1 <Comp></Comp>',
95+
'# *H1* `<Comp/>`': '# H1 <Comp/>',
96+
97+
// Remove leading html
98+
'# <Comp></Comp> `H1`': '# H1',
99+
'# <Comp/> *H1*': '# H1',
100+
101+
// Reserve code-wrapped leading html
102+
'# `<Comp></Comp>` `H1`': '# <Comp></Comp> H1',
103+
'# `<Comp/>` *H1*': '# <Comp/> H1',
104+
105+
// Remove middle html
106+
'# `H1` <Comp></Comp> `H2`': '# H1 H2',
107+
'# `H1` <Comp/> `H2`': '# H1 H2',
108+
109+
// Reserve middle html
110+
'# `H1` `<Comp></Comp>` `H2`': '# H1 <Comp></Comp> H2',
111+
'# `H1` `<Comp/>` `H2`': '# H1 <Comp/> H2'
112+
}
113+
114+
Object.keys(asserts).forEach(input => {
115+
expect(deeplyParseHeaders(input)).toBe(asserts[input])
116+
})
45117
})
46118
})

0 commit comments

Comments
 (0)