1
- import marked from 'marked' ;
1
+ import { marked } from 'marked' ;
2
+ import { parse as htmlParse } from 'node-html-parser' ;
3
+ import { parse as hbsParse } from '@handlebars/parser' ;
4
+ import lineColumn from 'line-column' ;
2
5
3
6
import hljs from 'highlight.js/lib/core' ;
4
7
5
8
// Installed languages
6
9
import javascript from 'highlight.js/lib/languages/javascript' ;
7
10
import css from 'highlight.js/lib/languages/css' ;
8
11
import handlebars from 'highlight.js/lib/languages/handlebars' ;
9
- import htmlbars from 'highlight.js/lib/languages/htmlbars' ;
10
12
import json from 'highlight.js/lib/languages/json' ;
11
13
import xml from 'highlight.js/lib/languages/xml' ;
12
14
import diff from 'highlight.js/lib/languages/diff' ;
@@ -17,8 +19,8 @@ hljs.registerLanguage('javascript', javascript);
17
19
hljs . registerLanguage ( 'js' , javascript ) ;
18
20
hljs . registerLanguage ( 'css' , css ) ;
19
21
hljs . registerLanguage ( 'handlebars' , handlebars ) ;
20
- hljs . registerLanguage ( 'htmlbars ' , htmlbars ) ;
21
- hljs . registerLanguage ( 'hbs ' , htmlbars ) ;
22
+ hljs . registerLanguage ( 'hbs ' , handlebars ) ;
23
+ hljs . registerLanguage ( 'htmlbars ' , handlebars ) ;
22
24
hljs . registerLanguage ( 'json' , json ) ;
23
25
hljs . registerLanguage ( 'xml' , xml ) ;
24
26
hljs . registerLanguage ( 'diff' , diff ) ;
@@ -27,6 +29,90 @@ hljs.registerLanguage('sh', shell);
27
29
hljs . registerLanguage ( 'typescript' , typescript ) ;
28
30
hljs . registerLanguage ( 'ts' , typescript ) ;
29
31
32
+ const htmlComponent = {
33
+ name : 'htmlComponent' ,
34
+ level : 'block' ,
35
+ start ( src ) {
36
+ // stop text tokenizer at the next potential match.
37
+ // we're only interested in html blocks that begin in a new line
38
+ let match = src . match ( / \n < [ ^ / ^ \s > ] / ) ;
39
+ return match && match . index ;
40
+ } ,
41
+ tokenizer ( src ) {
42
+ let openingRule = / ^ < ( [ ^ / ^ \s > ] + ) \s ? [ \s \S ] * ?> / ;
43
+ let openingMatch = openingRule . exec ( src ) ;
44
+
45
+ if ( openingMatch ) {
46
+ let openingTag = openingMatch [ 1 ] ;
47
+
48
+ let root = htmlParse ( src ) ;
49
+
50
+ for ( let el of root . childNodes ) {
51
+ if ( el . rawTagName === openingTag ) {
52
+ let finalMatch = src . substring ( el . range [ 0 ] , el . range [ 1 ] ) ;
53
+
54
+ return {
55
+ type : 'htmlComponent' ,
56
+ raw : finalMatch ,
57
+ text : finalMatch ,
58
+ tokens : [ ] ,
59
+ } ;
60
+ }
61
+ }
62
+ }
63
+ } ,
64
+ renderer ( token ) {
65
+ return `\n<p>${ token . text } </p>\n` ;
66
+ } ,
67
+ } ;
68
+
69
+ const hbsComponent = {
70
+ name : 'hbsComponent' ,
71
+ level : 'block' ,
72
+ start ( src ) {
73
+ // stop text tokenizer at the next potential match.
74
+ // we're only interested in hbs blocks that begin in a new line
75
+ let match = src . match ( / \n { { # \S / ) ;
76
+ return match && match . index ;
77
+ } ,
78
+ tokenizer ( src ) {
79
+ let openingRule = / ^ { { # ( [ A - Z a - z - ] + ) [ \S \s ] + ?} } / ;
80
+ let openingMatch = openingRule . exec ( src ) ;
81
+
82
+ if ( openingMatch ) {
83
+ let openingTag = openingMatch [ 1 ] ;
84
+
85
+ let root = hbsParse ( src ) ;
86
+
87
+ for ( let el of root . body ) {
88
+ if ( el . path && el . path . original === openingTag ) {
89
+ let start = lineColumn ( src ) . toIndex ( [
90
+ el . loc . start . line ,
91
+ el . loc . start . column ,
92
+ ] ) ;
93
+ let end = lineColumn ( src ) . toIndex ( [
94
+ el . loc . end . line ,
95
+ el . loc . end . column ,
96
+ ] ) ;
97
+ let finalMatch = src . substring ( start , end + 1 ) ;
98
+
99
+ return {
100
+ type : 'hbsComponent' ,
101
+ raw : finalMatch ,
102
+ text : finalMatch ,
103
+ tokens : [ ] ,
104
+ } ;
105
+ }
106
+ }
107
+ }
108
+ } ,
109
+ renderer ( token ) {
110
+ return `\n<p>${ token . text } </p>\n` ;
111
+ } ,
112
+ } ;
113
+
114
+ marked . use ( { extensions : [ htmlComponent , hbsComponent ] } ) ;
115
+
30
116
/**
31
117
This function is used when `compileMarkdown` encounters code blocks while
32
118
rendering Markdown source.
@@ -98,63 +184,12 @@ export function highlightCode(code, language) {
98
184
@param {object } options? Options. Pass `targetHandlebars: true` if turning MD into HBS
99
185
*/
100
186
export default function compileMarkdown ( source , config ) {
101
- let tokens = marked . lexer ( source ) ;
102
187
let markedOptions = {
103
188
highlight : highlightCode ,
104
189
renderer : new HBSRenderer ( config ) ,
105
190
} ;
106
191
107
- if ( config && config . targetHandlebars ) {
108
- tokens = compactParagraphs ( tokens ) ;
109
- }
110
-
111
- return `<div class="docs-md">${ marked
112
- . parser ( tokens , markedOptions )
113
- . trim ( ) } </div>`;
114
- }
115
-
116
- // Whitespace can imply paragraphs in Markdown, which can result
117
- // in interleaving between <p> tags and block component invocations,
118
- // so this scans the Marked tokens to turn things like this:
119
- // <p>{{#my-component}}<p>
120
- // <p>{{/my-component}}</p>
121
- // Into this:
122
- // <p>{{#my-component}} {{/my-component}}</p>
123
- function compactParagraphs ( tokens ) {
124
- let compacted = [ ] ;
125
-
126
- compacted . links = tokens . links ;
127
-
128
- let balance = 0 ;
129
- for ( let token of tokens ) {
130
- if ( balance === 0 ) {
131
- compacted . push ( token ) ;
132
- } else if ( token . text ) {
133
- let last = compacted [ compacted . length - 1 ] ;
134
- last . text = `${ last . text } ${ token . text } ` ;
135
- }
136
-
137
- let tokenText = token . text || '' ;
138
- let textWithoutCode = tokenText . replace ( / ` [ \s \S ] * ?` / g, '' ) ;
139
-
140
- if ( token . type === 'code' ) {
141
- textWithoutCode = '' ;
142
- }
143
-
144
- balance += count ( / { { # / g, textWithoutCode ) ;
145
- balance += count ( / < [ A - Z ] / g, textWithoutCode ) ;
146
- balance -= count ( / [ A - Z ] [ ^ < > ] + \/ > / g, textWithoutCode ) ;
147
- balance -= count ( / { { \/ / g, textWithoutCode ) ;
148
- balance -= count ( / < \/ [ A - Z ] / g, textWithoutCode ) ;
149
- }
150
-
151
- return compacted ;
152
- }
153
-
154
- function count ( regex , string ) {
155
- let total = 0 ;
156
- while ( regex . exec ( string ) ) total ++ ;
157
- return total ;
192
+ return `<div class="docs-md">${ marked . parse ( source , markedOptions ) } </div>` ;
158
193
}
159
194
160
195
class HBSRenderer extends marked . Renderer {
@@ -246,8 +281,8 @@ class HBSRenderer extends marked.Renderer {
246
281
}
247
282
248
283
tablecell ( content , flags ) {
249
- const type = flags . header ? 'th' : 'td' ;
250
- const tag = flags . align
284
+ let type = flags . header ? 'th' : 'td' ;
285
+ let tag = flags . align
251
286
? '<' +
252
287
type +
253
288
' align="' +
@@ -266,7 +301,7 @@ class HBSRenderer extends marked.Renderer {
266
301
}
267
302
268
303
link ( href , title , text ) {
269
- const titleAttribute = title ? `title="${ title } "` : '' ;
304
+ let titleAttribute = title ? `title="${ title } "` : '' ;
270
305
return `<a href="${ href } " ${ titleAttribute } class="docs-md__a">${ text } </a>` ;
271
306
}
272
307
}
0 commit comments