/
index.js
104 lines (94 loc) · 3.14 KB
/
index.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
/**
* @typedef {import('mdast').Root} MdastRoot
* @typedef {import('hast').Root} HastRoot
* @typedef {import('hast').Element} HastElement
* @typedef {HastRoot|HastRoot['children'][number]} HastNode
* @typedef {import('virtual-dom').VNode} VNode
* @typedef {import('virtual-dom').VChild} VChild
* @typedef {import('virtual-dom').h} H
* @typedef {import('hast-util-sanitize').Schema} Schema
*
* @callback Component
* @param {string} name
* @param {Record<string, unknown>} props
* @param {VNode[]} children
* @returns {VNode|VNode[]}
*
* @typedef Options
* Configuration.
* @property {boolean|Schema|null|undefined} [sanitize]
* How to sanitize the output.
*
* Sanitation is done by `hast-util-sanitize`, except when `false` is
* given.
* If an object is passed in, it’s given as a schema to `sanitize`.
* By default, input is sanitized according to GitHub’s sanitation rules.
*
* Embedded HTML is **always** stripped.
* @property {string|null|undefined} [prefix='h-']
* Optimization hint.
* @property {H|null|undefined} [h]
* Hyperscript to use.
* @property {Record<string, Component>} [components={}]
* Map of tag names to custom components.
* That component is invoked with `tagName`, `props`, and `children`.
* It can return any VDOM compatible value (such as `VNode`, `VText`,
* `Widget`).
*/
import {toHast} from 'mdast-util-to-hast'
import {sanitize} from 'hast-util-sanitize'
import {toH} from 'hast-to-hyperscript'
import {h as defaultH} from 'virtual-dom'
const own = {}.hasOwnProperty
/**
* Plugin to compile Markdown to Virtual DOM.
*
* @type {import('unified').Plugin<[Options?]|void[], MdastRoot, VNode>}
*/
export default function remarkVdom(options = {}) {
const info = options.sanitize
const clean = info !== false
const schema = info && typeof info === 'object' ? info : null
const components = options.components || {}
const h = options.h || defaultH
Object.assign(this, {Compiler: compiler})
/** @type {import('unified').CompilerFunction<MdastRoot, VNode>} */
function compiler(node) {
/** @type {HastNode} */
// @ts-expect-error: assume a root w/o doctypes.
let hast = div(toHast(node).children)
if (clean) {
hast = sanitize(hast, schema || undefined)
// If `div` is removed by sanitation, add it back.
if (hast.type === 'root') {
// @ts-expect-error: assume a root w/o doctypes.
hast = div(hast.children)
}
}
// @ts-expect-error: assume no doctypes.
return toH(w, hast, options.prefix)
}
/**
* Wrapper around `h` to pass components in.
*
* @param {string} name
* @param {object} props
* @param {VChild[]|undefined} children
* @returns {VNode|VNode[]}
*/
function w(name, props, children) {
const id = name.toLowerCase()
const fn = own.call(components, id) ? components[id] : h
// @ts-expect-error: vdom types vague.
return fn(name, props, children || [])
}
/**
* Wrap `children` in a hast div.
*
* @param {HastElement['children']} children
* @returns {HastElement}
*/
function div(children) {
return {type: 'element', tagName: 'div', properties: {}, children}
}
}