Skip to content

Commit

Permalink
feat: support writing HTML(Vue) anywhere in the header. (#711)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ulivz authored Aug 8, 2018
1 parent fa8b055 commit 885496e
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 17 deletions.
37 changes: 28 additions & 9 deletions lib/util/parseHeaders.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
// Since VuePress needs to extract the header from the markdown source
// file and display it in the sidebar or title (#238), this file simply
// removes some unnecessary elements to make header displays well at
// sidebar or title.
//
// But header's parsing in the markdown content is done by the markdown
// loader based on markdown-it. markdown-it parser will will always keep
// HTML in headers, so in VuePress, after being parsed by the markdiwn
// loader, the raw HTML in headers will finally be parsed by Vue-loader.
// so that we can write HTML/Vue in the header. One exception is the HTML
// wrapped by <code>(markdown token: '`') tag.

const { compose } = require('./shared')

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

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

exports.removeTailHtml = (str) => {
return String(str).replace(/\s*?<.*>\s*$/g, '')
const trim = str => str.trim()

// This method remove the raw HTML but reserve the HTML wrapped by `<code>`.
// e.g.
// Input: "<a> b", Output: "b"
// Input: "`<a>` b", Output: "`<a>` b"
exports.removeNonCodeWrappedHTML = (str) => {
return String(str).replace(/(^|[^><`])<.*>([^><`]|$)/g, '$1$2')
}

// Only remove some md tokens.
// Unescape html, parse emojis and remove some md tokens.
exports.parseHeaders = compose(
unescapeHtml,
parseEmojis,
removeMarkdownToken
removeMarkdownTokens,
trim
)

// Also clean the tail html in headers.
// Since we want to support tailed badge in headers.
// See: https://vuepress.vuejs.org/guide/using-vue.html#badge
// Also clean the html that isn't wrapped by code.
// Because we want to support using VUE components in headers.
// e.g. https://vuepress.vuejs.org/guide/using-vue.html#badge
exports.deeplyParseHeaders = compose(
exports.removeTailHtml,
exports.removeNonCodeWrappedHTML,
exports.parseHeaders,
)
88 changes: 80 additions & 8 deletions test/util/parseHeaders.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
parseHeaders,
removeTailHtml,
removeNonCodeWrappedHTML,
deeplyParseHeaders
} from '@/util/parseHeaders'

Expand Down Expand Up @@ -32,15 +32,87 @@ describe('parseHeaders', () => {
})
})

test('should remove tail html correctly', () => {
expect(removeTailHtml('# H1 <Comp></Comp>')).toBe('# H1')
expect(removeTailHtml('# H1 <Comp a="b"></Comp>')).toBe('# H1')
expect(removeTailHtml('# H1 <Comp/>')).toBe('# H1')
expect(removeTailHtml('# H1 <Comp a="b"/>')).toBe('# H1')
test('should remove non-code-wrapped html correctly', () => {
const asserts = {
// Remove tail html
'# H1 <Comp></Comp>': '# H1 ',
'# H1<Comp></Comp>': '# H1',
'# H1 <Comp a="b"></Comp>': '# H1 ',
'# H1<Comp a="b"></Comp>': '# H1',
'# H1 <Comp/>': '# H1 ',
'# H1<Comp/>': '# H1',
'# H1 <Comp a="b"/>': '# H1 ',
'# H1<Comp a="b"/>': '# H1',

// Reserve code-wrapped tail html
'# H1 `<Comp></Comp>`': '# H1 `<Comp></Comp>`',
'# H1 `<Comp a="b"></Comp>`': '# H1 `<Comp a="b"></Comp>`',
'# H1 `<Comp/>`': '# H1 `<Comp/>`',
'# H1 `<Comp a="b"/>`': '# H1 `<Comp a="b"/>`',

// Remove leading html
'# <Comp></Comp> H1': '# H1',
'# <Comp></Comp>H1': '# H1',
'# <Comp a="b"></Comp> H1': '# H1',
'# <Comp a="b"></Comp>H1': '# H1',
'# <Comp/> H1': '# H1',
'# <Comp/>H1': '# H1',
'# <Comp a="b"/> H1': '# H1',
'# <Comp a="b"/>H1': '# H1',

// Reserve code-wrapped leading html
'# `<Comp></Comp>` H1': '# `<Comp></Comp>` H1',
'# `<Comp a="b"></Comp>` H1': '# `<Comp a="b"></Comp>` H1',
'# `<Comp/>` H1': '# `<Comp/>` H1',
'# `<Comp a="b"/>` H1': '# `<Comp a="b"/>` H1',

// Remove middle html
'# H1 <Comp></Comp> H2': '# H1 H2',
'# H1 <Comp a="b"></Comp> H2': '# H1 H2',
'# H1 <Comp/> H2': '# H1 H2',
'# H1 <Comp a="b"/> H2': '# H1 H2',

// Reserve code-wrapped middle html
'# H1 `<Comp></Comp>` H2': '# H1 `<Comp></Comp>` H2',
'# H1 `<Comp a="b"></Comp>` H2': '# H1 `<Comp a="b"></Comp>` H2',
'# H1 `<Comp/>` H2': '# H1 `<Comp/>` H2',
'# H1 `<Comp a="b"/>` H2': '# H1 `<Comp a="b"/>` H2'
}

Object.keys(asserts).forEach(input => {
expect(removeNonCodeWrappedHTML(input)).toBe(asserts[input])
})
})

test('should deeplyParseHeaders transformed as expected', () => {
expect(deeplyParseHeaders('# `H1` <Comp></Comp>')).toBe('# H1')
expect(deeplyParseHeaders('# *H1* <Comp/>')).toBe('# H1')
const asserts = {
// Remove tail html
'# `H1` <Comp></Comp>': '# H1',
'# *H1* <Comp/>': '# H1',

// Reserve code-wrapped tail html
'# `H1` `<Comp></Comp>`': '# H1 <Comp></Comp>',
'# *H1* `<Comp/>`': '# H1 <Comp/>',

// Remove leading html
'# <Comp></Comp> `H1`': '# H1',
'# <Comp/> *H1*': '# H1',

// Reserve code-wrapped leading html
'# `<Comp></Comp>` `H1`': '# <Comp></Comp> H1',
'# `<Comp/>` *H1*': '# <Comp/> H1',

// Remove middle html
'# `H1` <Comp></Comp> `H2`': '# H1 H2',
'# `H1` <Comp/> `H2`': '# H1 H2',

// Reserve middle html
'# `H1` `<Comp></Comp>` `H2`': '# H1 <Comp></Comp> H2',
'# `H1` `<Comp/>` `H2`': '# H1 <Comp/> H2'
}

Object.keys(asserts).forEach(input => {
expect(deeplyParseHeaders(input)).toBe(asserts[input])
})
})
})

0 comments on commit 885496e

Please sign in to comment.