Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support writing HTML(Vue) anywhere in the header. #711

Merged
merged 1 commit into from
Aug 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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])
})
})
})