-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathplugin.ts
98 lines (84 loc) · 2.83 KB
/
plugin.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
import { retext } from "retext";
import { visit } from "unist-util-visit";
import smartypants, { type Options } from "retext-smartypants";
import type { Plugin } from "unified";
import type { Test } from "unist-util-is";
import type { Node } from "unist";
const VISITED_NODES = new Set(["text", "inlineCode", "paragraph"]);
const IGNORED_HTML_ELEMENTS = new Set(["style", "script"]);
const check: Test = (node, index, parent) => {
return (
parent &&
(parent.type !== "mdxJsxTextElement" ||
("name" in parent &&
typeof parent.name === "string" &&
!IGNORED_HTML_ELEMENTS.has(parent.name))) &&
VISITED_NODES.has(node.type) &&
(isLiteral(node) || isParagraph(node))
);
};
/**
* remark plugin to implement SmartyPants.
*/
const remarkSmartypants: Plugin<[Options?]> = (options) => {
const processor = retext().use(smartypants, {
...options,
// Do not replace ellipses, dashes, backticks because they change string
// length, and we couldn't guarantee right splice of text in second visit of
// tree
ellipses: false,
dashes: false,
backticks: false,
});
const processor2 = retext().use(smartypants, {
...options,
// Do not replace quotes because they are already replaced in the first
// processor
quotes: false,
});
return (tree) => {
let allText = "";
let startIndex = 0;
const nodes: (Literal | Paragraph)[] = [];
visit(tree, check, (node) => {
if (isLiteral(node)) {
allText +=
node.type === "text" ? node.value : "A".repeat(node.value.length);
} else if (isParagraph(node)) {
// Inject a "fake" space because otherwise, when concatenated below,
// smartypants will fail to recognize opening quotes at the start of
// paragraphs
allText += " ";
}
nodes.push(node);
});
// Concat all text into one string, to properly replace quotes around links
// and bold text
allText = processor.processSync(allText).toString();
for (const node of nodes) {
if (isLiteral(node)) {
const endIndex = startIndex + node.value.length;
if (node.type === "text") {
const processedText = allText.slice(startIndex, endIndex);
node.value = processor2.processSync(processedText).toString();
}
startIndex = endIndex;
} else if (isParagraph(node)) {
// Skip over the space we added above
startIndex += 1;
}
}
};
};
// the Literal interface from @types/unist has unknown value, we want string
interface Literal extends Node {
value: string;
}
function isLiteral(node: Node): node is Literal {
return "value" in node && typeof node.value === "string";
}
interface Paragraph extends Node {}
function isParagraph(node: Node): node is Paragraph {
return node.type === "paragraph";
}
export default remarkSmartypants;