-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
120 lines (106 loc) · 2.86 KB
/
index.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
import { type Element, type Root } from "hast";
import { type Plugin } from "unified";
export type RehypeScrollToLink = {
ariaLabel: string;
classes: string;
disabled: boolean;
id: string;
text: string;
};
export type RehypeScrollToTopOptions = {
topLink?: Partial<RehypeScrollToLink>;
bottomLink?: Partial<RehypeScrollToLink>;
};
export type RehypeScrollToTopOptionsRequired = {
topLink: RehypeScrollToLink;
bottomLink: RehypeScrollToLink;
};
const defaultOptions: RehypeScrollToTopOptionsRequired = {
topLink: {
ariaLabel: "Scroll to bottom",
classes: "",
disabled: false,
id: "top",
text: "Scroll to bottom",
},
bottomLink: {
ariaLabel: "Scroll to top",
classes: "",
disabled: false,
id: "bottom",
text: "Scroll to top",
},
};
const baseClass = "scroll-to";
const bottomClass = "scroll-to--bottom";
const topClass = "scroll-to--top";
export const rehypeScrollToTop: Plugin<[RehypeScrollToTopOptions?], Root> = (options = {}) => {
const options_ = mergeOptions(options);
return (tree) => {
const newChildren = [];
const { topLink, bottomLink } = options_;
if (!topLink.disabled) {
const { ariaLabel, id, text, classes, disabled } = topLink;
const topLinkElement = createLinkElement({
ariaLabel,
classes: `${classes} ${baseClass} ${topClass}`,
destinationId: bottomLink.id,
disabled,
id,
text,
});
newChildren.push(topLinkElement);
}
// Append original children
newChildren.push(...tree.children);
if (!bottomLink.disabled) {
const { ariaLabel, id, text, classes, disabled } = bottomLink;
const bottomLinkElement = createLinkElement({
ariaLabel,
classes: `${classes} ${baseClass} ${bottomClass}`,
destinationId: topLink.id,
disabled,
id,
text,
});
newChildren.push(bottomLinkElement);
}
tree.children = newChildren;
return tree;
};
};
export default rehypeScrollToTop;
function mergeOptions(userOptions: Partial<RehypeScrollToTopOptions> = {}): RehypeScrollToTopOptionsRequired {
return {
bottomLink: {
...defaultOptions.bottomLink,
...userOptions.bottomLink,
},
topLink: {
...defaultOptions.topLink,
...userOptions.topLink,
},
};
}
function createLinkElement(props: RehypeScrollToLink & { destinationId: string }): Element {
const { ariaLabel, classes, disabled, id, text, destinationId } = props;
const linkElement: Element = {
type: "element",
tagName: "a",
properties: {
"aria-label": ariaLabel,
"href": `#${destinationId}`,
id,
"className": [classes],
},
children: [{ type: "text", value: text }],
};
return {
type: "element",
tagName: "div",
properties: {
className: ["scroll-to-wrapper"],
},
children: [linkElement],
};
}