-
-
Notifications
You must be signed in to change notification settings - Fork 94
/
renderer.js
132 lines (114 loc) · 3.46 KB
/
renderer.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
'use strict';
const { promisify } = require('util');
const marked = promisify(require('marked'));
const stripIndent = require('strip-indent');
const { encodeURL, highlight, slugize, stripHTML, url_for, isExternalLink } = require('hexo-util');
const MarkedRenderer = marked.Renderer;
const { parse } = require('url');
const anchorId = (str, transformOption) => {
return slugize(str.trim(), {transform: transformOption});
};
class Renderer extends MarkedRenderer {
constructor() {
super();
this._headingId = {};
}
// Add id attribute to headings
heading(text, level) {
if (!this.options.headerIds) {
return `<h${level}>${text}</h${level}>`;
}
const transformOption = this.options.modifyAnchors;
let id = anchorId(stripHTML(text), transformOption);
const headingId = this._headingId;
// Add a number after id if repeated
if (headingId[id]) {
id += `-${headingId[id]++}`;
} else {
headingId[id] = 1;
}
// add headerlink
return `<h${level} id="${id}"><a href="#${id}" class="headerlink" title="${stripHTML(text)}"></a>${text}</h${level}>`;
}
// Support AutoLink option
link(href, title, text) {
const { options } = this;
const { external_link } = options;
if (options.sanitizeUrl) {
if (href.startsWith('javascript:') || href.startsWith('vbscript:') || href.startsWith('data:')) {
href = '';
}
}
if (!options.autolink && href === text && title == null) {
return href;
}
let out = `<a href="${encodeURL(href)}"`;
if (title) {
out += ` title="${title}"`;
}
if (external_link) {
const target = ' target="_blank"';
const noopener = ' rel="noopener"';
const nofollowTag = ' rel="noopener external nofollow noreferrer"';
if (isExternalLink(href, options.config.url, external_link.exclude)) {
if (external_link.enable && external_link.nofollow) {
out += target + nofollowTag;
} else if (external_link.enable) {
out += target + noopener;
} else if (external_link.nofollow) {
out += nofollowTag;
}
}
}
out += `>${text}</a>`;
return out;
}
// Support Basic Description Lists
paragraph(text) {
const dlTest = /(?:^|\s)(\S.+)<br>:\s+(\S.+)/;
const dl = '<dl><dt>$1</dt><dd>$2</dd></dl>';
if (dlTest.test(text)) {
return text.replace(dlTest, dl);
}
return `<p>${text}</p>\n`;
}
// Prepend root to image path
image(href, title, text) {
const { options } = this;
if (!parse(href).hostname && !options.config.relative_link
&& options.prependRoot) {
href = url_for.call(options, href);
}
let out = `<img src="${encodeURL(href)}"`;
if (text) out += ` alt="${text}"`;
if (title) out += ` title="${title}"`;
out += '>';
return out;
}
}
marked.setOptions({
langPrefix: '',
highlight(code, lang) {
return highlight(stripIndent(code), {
lang,
gutter: false,
wrap: false
});
}
});
module.exports = async function(data, options) {
const siteCfg = Object.assign({}, {
config: {
url: this.config.url,
root: this.config.root,
relative_link: this.config.relative_link
}
});
// exec filter to extend renderer.
const renderer = new Renderer();
await this.execFilter('marked:renderer', renderer, {context: this});
const result = await marked(data.text, Object.assign({
renderer
}, this.config.marked, options, siteCfg));
return result;
};