-
-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathindex.js
111 lines (94 loc) · 3.09 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
105
106
107
108
109
110
111
/**
* @import {Options} from 'hast-util-to-mdast'
* @import {Nodes} from 'hast'
* @import {Nodes as MdastNodes, RootContent as MdastRootContent} from 'mdast'
*/
import structuredClone from '@ungap/structured-clone'
import rehypeMinifyWhitespace from 'rehype-minify-whitespace'
import {visit} from 'unist-util-visit'
import {createState} from './state.js'
/** @type {Readonly<Options>} */
const emptyOptions = {}
/**
* Transform hast to mdast.
*
* @param {Readonly<Nodes>} tree
* hast tree to transform.
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns {MdastNodes}
* mdast tree.
*/
export function toMdast(tree, options) {
// We have to clone, cause we’ll use `rehype-minify-whitespace` on the tree,
// which modifies.
const cleanTree = structuredClone(tree)
const settings = options || emptyOptions
const transformWhitespace = rehypeMinifyWhitespace({
newlines: settings.newlines === true
})
const state = createState(settings)
/** @type {MdastNodes} */
let mdast
// @ts-expect-error: fine to pass an arbitrary node.
transformWhitespace(cleanTree)
visit(cleanTree, function (node) {
if (node && node.type === 'element' && node.properties) {
const id = String(node.properties.id || '') || undefined
if (id && !state.elementById.has(id)) {
state.elementById.set(id, node)
}
}
})
const result = state.one(cleanTree, undefined)
if (!result) {
mdast = {type: 'root', children: []}
} else if (Array.isArray(result)) {
// Assume content.
const children = /** @type {Array<MdastRootContent>} */ (result)
mdast = {type: 'root', children}
} else {
mdast = result
}
// Collapse text nodes, and fix whitespace.
//
// Most of this is taken care of by `rehype-minify-whitespace`, but
// we’re generating some whitespace too, and some nodes are in the end
// ignored.
// So clean up.
visit(mdast, function (node, index, parent) {
if (node.type === 'text' && index !== undefined && parent) {
const previous = parent.children[index - 1]
if (previous && previous.type === node.type) {
previous.value += node.value
parent.children.splice(index, 1)
if (previous.position && node.position) {
previous.position.end = node.position.end
}
// Iterate over the previous node again, to handle its total value.
return index - 1
}
node.value = node.value.replace(/[\t ]*(\r?\n|\r)[\t ]*/, '$1')
// We don’t care about other phrasing nodes in between (e.g., `[ asd ]()`),
// as there the whitespace matters.
if (
parent &&
(parent.type === 'heading' ||
parent.type === 'paragraph' ||
parent.type === 'root')
) {
if (!index) {
node.value = node.value.replace(/^[\t ]+/, '')
}
if (index === parent.children.length - 1) {
node.value = node.value.replace(/[\t ]+$/, '')
}
}
if (!node.value) {
parent.children.splice(index, 1)
return index
}
}
})
return mdast
}