Skip to content

Commit e5b3145

Browse files
authored
update markdown rendering approach (#1133)
1 parent 3007665 commit e5b3145

File tree

9 files changed

+240
-101
lines changed

9 files changed

+240
-101
lines changed

addon/components/docs-code-highlight/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import hljs from 'highlight.js/lib/core';
55
import javascript from 'highlight.js/lib/languages/javascript';
66
import css from 'highlight.js/lib/languages/css';
77
import handlebars from 'highlight.js/lib/languages/handlebars';
8-
import htmlbars from 'highlight.js/lib/languages/htmlbars';
98
import json from 'highlight.js/lib/languages/json';
109
import xml from 'highlight.js/lib/languages/xml';
1110
import diff from 'highlight.js/lib/languages/diff';
@@ -16,8 +15,8 @@ hljs.registerLanguage('javascript', javascript);
1615
hljs.registerLanguage('js', javascript);
1716
hljs.registerLanguage('css', css);
1817
hljs.registerLanguage('handlebars', handlebars);
19-
hljs.registerLanguage('htmlbars', htmlbars);
20-
hljs.registerLanguage('hbs', htmlbars);
18+
hljs.registerLanguage('htmlbars', handlebars);
19+
hljs.registerLanguage('hbs', handlebars);
2120
hljs.registerLanguage('json', json);
2221
hljs.registerLanguage('xml', xml);
2322
hljs.registerLanguage('diff', diff);
@@ -28,6 +27,6 @@ hljs.registerLanguage('ts', typescript);
2827

2928
export default class DocsCodeHighlight extends Component {
3029
setupElement(element) {
31-
hljs.highlightBlock(element);
30+
hljs.highlightElement(element);
3231
}
3332
}

addon/components/docs-demo/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default class DocsDemo extends Component {
8787
case 'hbs':
8888
case 'md':
8989
label = 'template.hbs';
90-
language = 'htmlbars';
90+
language = 'handlebars';
9191
break;
9292
default:
9393
label = 'script.js';

addon/utils/compile-markdown.js

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
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';
25

36
import hljs from 'highlight.js/lib/core';
47

58
// Installed languages
69
import javascript from 'highlight.js/lib/languages/javascript';
710
import css from 'highlight.js/lib/languages/css';
811
import handlebars from 'highlight.js/lib/languages/handlebars';
9-
import htmlbars from 'highlight.js/lib/languages/htmlbars';
1012
import json from 'highlight.js/lib/languages/json';
1113
import xml from 'highlight.js/lib/languages/xml';
1214
import diff from 'highlight.js/lib/languages/diff';
@@ -17,8 +19,8 @@ hljs.registerLanguage('javascript', javascript);
1719
hljs.registerLanguage('js', javascript);
1820
hljs.registerLanguage('css', css);
1921
hljs.registerLanguage('handlebars', handlebars);
20-
hljs.registerLanguage('htmlbars', htmlbars);
21-
hljs.registerLanguage('hbs', htmlbars);
22+
hljs.registerLanguage('hbs', handlebars);
23+
hljs.registerLanguage('htmlbars', handlebars);
2224
hljs.registerLanguage('json', json);
2325
hljs.registerLanguage('xml', xml);
2426
hljs.registerLanguage('diff', diff);
@@ -27,6 +29,90 @@ hljs.registerLanguage('sh', shell);
2729
hljs.registerLanguage('typescript', typescript);
2830
hljs.registerLanguage('ts', typescript);
2931

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-Za-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+
30116
/**
31117
This function is used when `compileMarkdown` encounters code blocks while
32118
rendering Markdown source.
@@ -98,63 +184,12 @@ export function highlightCode(code, language) {
98184
@param {object} options? Options. Pass `targetHandlebars: true` if turning MD into HBS
99185
*/
100186
export default function compileMarkdown(source, config) {
101-
let tokens = marked.lexer(source);
102187
let markedOptions = {
103188
highlight: highlightCode,
104189
renderer: new HBSRenderer(config),
105190
};
106191

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>`;
158193
}
159194

160195
class HBSRenderer extends marked.Renderer {
@@ -246,8 +281,8 @@ class HBSRenderer extends marked.Renderer {
246281
}
247282

248283
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
251286
? '<' +
252287
type +
253288
' align="' +
@@ -266,7 +301,7 @@ class HBSRenderer extends marked.Renderer {
266301
}
267302

268303
link(href, title, text) {
269-
const titleAttribute = title ? `title="${title}"` : '';
304+
let titleAttribute = title ? `title="${title}"` : '';
270305
return `<a href="${href}" ${titleAttribute} class="docs-md__a">${text}</a>`;
271306
}
272307
}

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@glimmer/component": "^1.0.4",
3737
"@glimmer/syntax": "^0.83.1",
3838
"@glimmer/tracking": "^1.0.4",
39+
"@handlebars/parser": "^2.1.0",
3940
"broccoli-bridge": "^1.0.0",
4041
"broccoli-caching-writer": "^3.0.3",
4142
"broccoli-filter": "^1.3.0",
@@ -74,14 +75,16 @@
7475
"execa": "5.1.1",
7576
"fs-extra": "^10.0.0",
7677
"git-repo-info": "^2.1.1",
77-
"highlight.js": "^10.7.2",
78+
"highlight.js": "^11.4.0",
7879
"hosted-git-info": "^4.0.2",
7980
"html-entities": "^2.3.2",
8081
"jsdom": "^19.0.0",
8182
"json-api-serializer": "^2.6.6",
83+
"line-column": "^1.0.2",
8284
"lodash": "^4.17.15",
8385
"lunr": "^2.3.7",
84-
"marked": "^0.8.2",
86+
"marked": "^4.0.12",
87+
"node-html-parser": "^5.2.0",
8588
"pad-start": "^1.0.2",
8689
"parse-git-config": "^3.0.0",
8790
"postcss": "^8.3.6",

0 commit comments

Comments
 (0)