/
remark.ts
132 lines (105 loc) Β· 3.29 KB
/
remark.ts
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
import loader from "../core/loaders/text.ts";
import { merge } from "../core/utils.ts";
import {
rehypeRaw,
rehypeSanitize,
rehypeStringify,
remarkGfm,
remarkParse,
remarkRehype,
unified,
} from "../deps/remark.ts";
import type { Data, Engine, Helper, Site } from "../core.ts";
export interface Options {
/** List of extensions this plugin applies to */
extensions: string[];
/** List of remark plugins to use */
remarkPlugins?: unknown[];
/** List of rehype plugins to use */
rehypePlugins?: unknown[];
/** Flag to turn on HTML sanitization to prevent XSS */
sanitize?: boolean;
/** Flag to override the default plugins */
overrideDefaultPlugins?: boolean;
}
// Default options
export const defaults: Options = {
extensions: [".md"],
// By default, GitHub-flavored markdown is enabled
remarkPlugins: [remarkGfm],
sanitize: false,
};
/** Template engine to render Markdown files with Remark */
export class MarkdownEngine implements Engine {
engine: unified.Processor;
constructor(engine: unified.Processor) {
this.engine = engine;
}
deleteCache() {}
async render(
content: string,
data?: Data,
filename?: string,
): Promise<string> {
return (await this.engine.process({
value: content,
data: data?.page?.data,
path: filename,
})).toString();
}
renderSync(content: string, data?: Data, filename?: string): string {
return this.engine.processSync({
value: content,
data: data?.page?.data,
path: filename,
}).toString();
}
addHelper() {}
}
/** Register the plugin to support Markdown */
export default function (userOptions?: Partial<Options>) {
const options = merge(defaults, userOptions);
return function (site: Site) {
// @ts-ignore: This expression is not callable
const engine = unified.unified();
const plugins = [];
// Add remark-parse to generate MDAST
plugins.push(remarkParse);
if (!options.overrideDefaultPlugins) {
// Add default remark plugins
defaults.remarkPlugins?.forEach((defaultPlugin) =>
plugins.push(defaultPlugin)
);
}
// Add remark plugins
options.remarkPlugins?.forEach((plugin) => plugins.push(plugin));
// Add remark-rehype to generate HAST
plugins.push([remarkRehype, { allowDangerousHtml: true }]);
if (options.sanitize) {
// Add rehype-raw to convert raw HTML to HAST
plugins.push(rehypeRaw);
}
// Add rehype plugins
options.rehypePlugins?.forEach((plugin) => plugins.push(plugin));
if (options.sanitize) {
// Add rehype-sanitize to make sure HTML is safe
plugins.push(rehypeSanitize);
// Add rehype-stringify to output HTML ignoring raw HTML nodes
plugins.push(rehypeStringify);
} else {
// Add rehype-stringify to output HTML
plugins.push([rehypeStringify, { allowDangerousHtml: true }]);
}
// Register all plugins
// @ts-ignore: let unified take care of loading all the plugins
engine.use(plugins);
// Load the pages
const remarkEngine = new MarkdownEngine(engine);
site.loadPages(options.extensions, loader, remarkEngine);
// Register the filter
site.filter("md", filter as Helper);
function filter(content: string): string {
return remarkEngine.renderSync(content).trim();
}
};
}