forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
renderContent.js
109 lines (90 loc) · 3.65 KB
/
renderContent.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import liquid from './liquid.js'
import cheerio from 'cheerio'
import { encode, decode } from 'html-entities'
import stripHtmlComments from 'strip-html-comments'
import createProcessor from './create-processor.js'
// used below to remove extra newlines in TOC lists
const endLine = '</a>\r?\n'
const blankLine = '\\s*?[\r\n]*'
const startNextLine = '[^\\S\r\n]*?[-\\*] <a'
const blankLineInList = new RegExp(`(${endLine})${blankLine}(${startNextLine})`, 'mg')
// used below to remove unwanted newlines from inline tags in tables
const inlineTags = ['a', 'code', 'em']
const inlineTagString = `(?:${inlineTags.join('|')})`
const inlineTagRegex = new RegExp(`\n?(</?${inlineTagString}>?)\n?`, 'gm')
// parse multiple times because some templates contain more templates. :]
async function renderContent(template = '', context = {}, options = {}) {
// If called with a falsy template, it can't ever become something
// when rendered. We can exit early to save some pointless work.
if (!template) return template
try {
// remove any newlines that precede html comments, then remove the comments
if (template) {
template = stripHtmlComments(template.replace(/\n<!--/g, '<!--'))
}
template = await liquid.parseAndRender(template, context)
// this workaround loses syntax highlighting but correctly handles tags like <em> and entities like <
template = template.replace(
/``` ?shell\r?\n\s*?(\S[\s\S]*?)\r?\n.*?```/gm,
'<pre><code class="hljs language-shell">$1</code></pre>'
)
// clean up empty lines in TOC lists left by unrendered list items (due to productVersions)
// for example, remove the blank line here:
// - <a>foo</a>
//
// - <a>bar</a>
if (template.includes('</a>')) {
template = template.replace(blankLineInList, '$1$2')
}
// this removes any extra newlines left by (now resolved) liquid
// statements so that extra space doesn't mess with list numbering
template = template.replace(/(\r?\n){3}/g, '\n\n')
const processor = createProcessor(context)
const vFile = await processor.process(template)
let html = vFile.toString()
// Remove unwanted newlines (which appear as spaces) from inline tags inside tables
if (html.includes('<table>')) {
html = removeNewlinesFromInlineTags(html)
}
if (options.textOnly) {
html = fastTextOnly(html)
}
if (options.cheerioObject) {
return cheerio.load(html, { xmlMode: true })
}
if (options.encodeEntities) {
html = encode(html)
}
return html.trim()
} catch (error) {
if (options.filename) {
console.error(`renderContent failed on file: ${options.filename}`)
}
throw error
}
}
function removeNewlinesFromInlineTags(html) {
const $ = cheerio.load(html)
// see https://cheerio.js.org/#html-htmlstring-
$(inlineTags.join(','))
.parents('td')
.get()
.map((tag) => $(tag).html($(tag).html().replace(inlineTagRegex, '$1')))
return $('body').html()
}
// Given a piece of HTML return it without HTML. E.g.
// `<p>Foo & bar</p>` becomes `Foo & bar`
// and `A <a href="">link</a> and <code>code</code>` becomes `A link and code`.
// Take advantage of the subtle fact that a lot of the times, the html value
// we get here is a single line that starts with `<p>` and ends with `</p>`
// and contains no longer HTML tags.
function fastTextOnly(html) {
if (!html) return ''
if (html.startsWith('<p>') && html.endsWith('</p>')) {
const middle = html.slice(3, -4)
if (!middle.includes('<')) return decode(middle.trim())
}
return cheerio.load(html, { xmlMode: true }).text().trim()
}
renderContent.liquid = liquid
export default renderContent