-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
126 lines (112 loc) · 3.46 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
121
122
123
124
125
126
import { Plugin } from "unified";
import { Element, Root } from "hast";
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";
const rehypeScrollToTop: Plugin<[RehypeScrollToTopOptions?], Root> = (options = {}) => {
const opts = mergeOptions(options);
return (tree) => {
const newChildren = [];
const { topLink, bottomLink } = opts;
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: {
ariaLabel: userOptions.bottomLink?.ariaLabel ?? defaultOptions.bottomLink.ariaLabel,
classes: userOptions.bottomLink?.classes ?? defaultOptions.bottomLink.classes,
disabled: userOptions.bottomLink?.disabled ?? defaultOptions.bottomLink.disabled,
id: userOptions.bottomLink?.id ?? defaultOptions.bottomLink.id,
text: userOptions.bottomLink?.text ?? defaultOptions.bottomLink.text,
},
topLink: {
ariaLabel: userOptions.topLink?.ariaLabel ?? defaultOptions.topLink.ariaLabel,
classes: userOptions.topLink?.classes ?? defaultOptions.topLink.classes,
disabled: userOptions.topLink?.disabled ?? defaultOptions.topLink.disabled,
id: userOptions.topLink?.id ?? defaultOptions.topLink.id,
text: userOptions.topLink?.text ?? defaultOptions.topLink.text,
},
};
}
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],
};
}