@readme/markdown 13.8.0 → 13.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -1
- package/components/TableOfContents/index.tsx +82 -6
- package/dist/main.js +90 -18
- package/dist/main.node.js +100 -28
- package/dist/main.node.js.map +1 -1
- package/dist/processor/transform/mdxish/variables-text.d.ts +0 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -125,9 +125,29 @@ To make changes to the RDMD engine locally, run the local development server. Cl
|
|
|
125
125
|
|
|
126
126
|
If you make changes to the docs or how the markdown is rendered, you may need to update the visual regression snapshots by running `make updateSnapshot`. Running these browser tests requires `docker`. Follow the docker [install instructions for mac](https://docs.docker.com/docker-for-mac/install/). You may want to increase the [memory usage](https://docs.docker.com/docker-for-mac/#resources). If you have not already, you'll need to create an account for `docker hub` and [sign-in locally](https://docs.docker.com/docker-for-mac/#docker-hub).
|
|
127
127
|
|
|
128
|
+
### Linking Changes to the Main Repo
|
|
129
|
+
|
|
130
|
+
To test local changes in the main readme app, use `npm pack` (not `npm link`) and clear the webpack cache:
|
|
131
|
+
|
|
132
|
+
**In this repo, run:**
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
npm run build && npm pack
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**In the main app repo, run:**
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
npm install ../markdown/readme-markdown-<version>.tgz
|
|
142
|
+
rm -rf webpack-cache
|
|
143
|
+
make start
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
> **Note:** You must clear `webpack-cache` after every reinstall or the app will serve the old version.
|
|
147
|
+
|
|
128
148
|
### Linking Changes to Storybook
|
|
129
149
|
|
|
130
|
-
|
|
150
|
+
`npm link` works with the storybook app:
|
|
131
151
|
|
|
132
152
|
**In this repo, run:**
|
|
133
153
|
|
|
@@ -17,6 +17,26 @@ function buildLinkMap(nav: HTMLElement) {
|
|
|
17
17
|
return map;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const VISIBLE_RATIO = 0.4;
|
|
21
|
+
/** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
|
|
22
|
+
const SCROLL_BOTTOM_TOLERANCE = 1;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Walk up the DOM to find the nearest scrollable ancestor.
|
|
26
|
+
* Falls back to `window` when the page itself scrolls.
|
|
27
|
+
*/
|
|
28
|
+
function getScrollParent(el: HTMLElement): HTMLElement | Window {
|
|
29
|
+
let parent = el.parentElement;
|
|
30
|
+
while (parent) {
|
|
31
|
+
const { overflow, overflowY } = getComputedStyle(parent);
|
|
32
|
+
if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
|
|
33
|
+
return parent;
|
|
34
|
+
}
|
|
35
|
+
parent = parent.parentElement;
|
|
36
|
+
}
|
|
37
|
+
return window;
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
/**
|
|
21
41
|
* Watches headings in the viewport and toggles `active` on the
|
|
22
42
|
* corresponding TOC links so the reader always knows where they are.
|
|
@@ -44,7 +64,16 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
|
|
|
44
64
|
if (headings.length === 0) return undefined;
|
|
45
65
|
|
|
46
66
|
let activeId: string | null = null;
|
|
67
|
+
let clickLocked = false;
|
|
47
68
|
const visible = new Set<string>();
|
|
69
|
+
const scrollParent = getScrollParent(headings[0]);
|
|
70
|
+
|
|
71
|
+
const isAtBottom = () => {
|
|
72
|
+
if (scrollParent instanceof Window) {
|
|
73
|
+
return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
74
|
+
}
|
|
75
|
+
return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
76
|
+
};
|
|
48
77
|
|
|
49
78
|
const activate = (id: string | null) => {
|
|
50
79
|
if (id === activeId) return;
|
|
@@ -66,22 +95,69 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
|
|
|
66
95
|
}
|
|
67
96
|
};
|
|
68
97
|
|
|
98
|
+
const updateActive = () => {
|
|
99
|
+
if (clickLocked) return;
|
|
100
|
+
|
|
101
|
+
if (isAtBottom()) {
|
|
102
|
+
activate(headings[headings.length - 1].id);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const topmost = headings.find(el => visible.has(el.id));
|
|
107
|
+
if (topmost) activate(topmost.id);
|
|
108
|
+
};
|
|
109
|
+
|
|
69
110
|
const observer = new IntersectionObserver(
|
|
70
111
|
entries => {
|
|
71
112
|
entries.forEach(e => {
|
|
72
113
|
if (e.isIntersecting) visible.add(e.target.id);
|
|
73
114
|
else visible.delete(e.target.id);
|
|
74
115
|
});
|
|
75
|
-
|
|
76
|
-
// keep the last active one so the user still has context.
|
|
77
|
-
const topmost = headings.find(el => visible.has(el.id));
|
|
78
|
-
if (topmost) activate(topmost.id);
|
|
116
|
+
updateActive();
|
|
79
117
|
},
|
|
80
|
-
{ rootMargin:
|
|
118
|
+
{ rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 },
|
|
81
119
|
);
|
|
82
120
|
|
|
121
|
+
// Check on scroll so bottom-of-page detection works even when
|
|
122
|
+
// no headings are crossing the intersection boundary.
|
|
123
|
+
const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
|
|
124
|
+
const onScroll = () => { updateActive(); };
|
|
125
|
+
|
|
126
|
+
// Click a ToC link → immediately activate it, suppress the observer
|
|
127
|
+
// until the smooth scroll finishes, then hand control back.
|
|
128
|
+
const onClick = (e: MouseEvent) => {
|
|
129
|
+
const anchor = (e.target as HTMLElement).closest?.('a[href^="#"]');
|
|
130
|
+
if (!anchor) return;
|
|
131
|
+
const id = decodeURIComponent(anchor.getAttribute('href')!.slice(1));
|
|
132
|
+
if (!linkMap.has(id)) return;
|
|
133
|
+
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
activate(id);
|
|
136
|
+
clickLocked = true;
|
|
137
|
+
|
|
138
|
+
const unlock = () => { clickLocked = false; };
|
|
139
|
+
scrollTarget.addEventListener('scrollend', unlock, { once: true });
|
|
140
|
+
|
|
141
|
+
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
|
142
|
+
};
|
|
143
|
+
|
|
83
144
|
headings.forEach(el => { observer.observe(el); });
|
|
84
|
-
|
|
145
|
+
scrollTarget.addEventListener('scroll', onScroll, { passive: true });
|
|
146
|
+
nav.addEventListener('click', onClick);
|
|
147
|
+
|
|
148
|
+
// Set initial active state for the first heading visible in the viewport,
|
|
149
|
+
// falling back to the first heading if none is in the observation zone yet.
|
|
150
|
+
const initialHeading = headings.find(el => {
|
|
151
|
+
const rect = el.getBoundingClientRect();
|
|
152
|
+
return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
|
|
153
|
+
}) || headings[0];
|
|
154
|
+
activate(initialHeading.id);
|
|
155
|
+
|
|
156
|
+
return () => {
|
|
157
|
+
observer.disconnect();
|
|
158
|
+
scrollTarget.removeEventListener('scroll', onScroll);
|
|
159
|
+
nav.removeEventListener('click', onClick);
|
|
160
|
+
};
|
|
85
161
|
}, [navRef, linkCount]);
|
|
86
162
|
}
|
|
87
163
|
|
package/dist/main.js
CHANGED
|
@@ -12087,6 +12087,24 @@ function buildLinkMap(nav) {
|
|
|
12087
12087
|
});
|
|
12088
12088
|
return map;
|
|
12089
12089
|
}
|
|
12090
|
+
const VISIBLE_RATIO = 0.4;
|
|
12091
|
+
/** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
|
|
12092
|
+
const SCROLL_BOTTOM_TOLERANCE = 1;
|
|
12093
|
+
/**
|
|
12094
|
+
* Walk up the DOM to find the nearest scrollable ancestor.
|
|
12095
|
+
* Falls back to `window` when the page itself scrolls.
|
|
12096
|
+
*/
|
|
12097
|
+
function getScrollParent(el) {
|
|
12098
|
+
let parent = el.parentElement;
|
|
12099
|
+
while (parent) {
|
|
12100
|
+
const { overflow, overflowY } = getComputedStyle(parent);
|
|
12101
|
+
if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
|
|
12102
|
+
return parent;
|
|
12103
|
+
}
|
|
12104
|
+
parent = parent.parentElement;
|
|
12105
|
+
}
|
|
12106
|
+
return window;
|
|
12107
|
+
}
|
|
12090
12108
|
/**
|
|
12091
12109
|
* Watches headings in the viewport and toggles `active` on the
|
|
12092
12110
|
* corresponding TOC links so the reader always knows where they are.
|
|
@@ -12113,7 +12131,15 @@ function useScrollHighlight(navRef) {
|
|
|
12113
12131
|
if (headings.length === 0)
|
|
12114
12132
|
return undefined;
|
|
12115
12133
|
let activeId = null;
|
|
12134
|
+
let clickLocked = false;
|
|
12116
12135
|
const visible = new Set();
|
|
12136
|
+
const scrollParent = getScrollParent(headings[0]);
|
|
12137
|
+
const isAtBottom = () => {
|
|
12138
|
+
if (scrollParent instanceof Window) {
|
|
12139
|
+
return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
12140
|
+
}
|
|
12141
|
+
return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
12142
|
+
};
|
|
12117
12143
|
const activate = (id) => {
|
|
12118
12144
|
if (id === activeId)
|
|
12119
12145
|
return;
|
|
@@ -12133,6 +12159,17 @@ function useScrollHighlight(navRef) {
|
|
|
12133
12159
|
}
|
|
12134
12160
|
}
|
|
12135
12161
|
};
|
|
12162
|
+
const updateActive = () => {
|
|
12163
|
+
if (clickLocked)
|
|
12164
|
+
return;
|
|
12165
|
+
if (isAtBottom()) {
|
|
12166
|
+
activate(headings[headings.length - 1].id);
|
|
12167
|
+
return;
|
|
12168
|
+
}
|
|
12169
|
+
const topmost = headings.find(el => visible.has(el.id));
|
|
12170
|
+
if (topmost)
|
|
12171
|
+
activate(topmost.id);
|
|
12172
|
+
};
|
|
12136
12173
|
const observer = new IntersectionObserver(entries => {
|
|
12137
12174
|
entries.forEach(e => {
|
|
12138
12175
|
if (e.isIntersecting)
|
|
@@ -12140,14 +12177,43 @@ function useScrollHighlight(navRef) {
|
|
|
12140
12177
|
else
|
|
12141
12178
|
visible.delete(e.target.id);
|
|
12142
12179
|
});
|
|
12143
|
-
|
|
12144
|
-
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12180
|
+
updateActive();
|
|
12181
|
+
}, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
|
|
12182
|
+
// Check on scroll so bottom-of-page detection works even when
|
|
12183
|
+
// no headings are crossing the intersection boundary.
|
|
12184
|
+
const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
|
|
12185
|
+
const onScroll = () => { updateActive(); };
|
|
12186
|
+
// Click a ToC link → immediately activate it, suppress the observer
|
|
12187
|
+
// until the smooth scroll finishes, then hand control back.
|
|
12188
|
+
const onClick = (e) => {
|
|
12189
|
+
const anchor = e.target.closest?.('a[href^="#"]');
|
|
12190
|
+
if (!anchor)
|
|
12191
|
+
return;
|
|
12192
|
+
const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
|
|
12193
|
+
if (!linkMap.has(id))
|
|
12194
|
+
return;
|
|
12195
|
+
e.preventDefault();
|
|
12196
|
+
activate(id);
|
|
12197
|
+
clickLocked = true;
|
|
12198
|
+
const unlock = () => { clickLocked = false; };
|
|
12199
|
+
scrollTarget.addEventListener('scrollend', unlock, { once: true });
|
|
12200
|
+
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
|
12201
|
+
};
|
|
12149
12202
|
headings.forEach(el => { observer.observe(el); });
|
|
12150
|
-
|
|
12203
|
+
scrollTarget.addEventListener('scroll', onScroll, { passive: true });
|
|
12204
|
+
nav.addEventListener('click', onClick);
|
|
12205
|
+
// Set initial active state for the first heading visible in the viewport,
|
|
12206
|
+
// falling back to the first heading if none is in the observation zone yet.
|
|
12207
|
+
const initialHeading = headings.find(el => {
|
|
12208
|
+
const rect = el.getBoundingClientRect();
|
|
12209
|
+
return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
|
|
12210
|
+
}) || headings[0];
|
|
12211
|
+
activate(initialHeading.id);
|
|
12212
|
+
return () => {
|
|
12213
|
+
observer.disconnect();
|
|
12214
|
+
scrollTarget.removeEventListener('scroll', onScroll);
|
|
12215
|
+
nav.removeEventListener('click', onClick);
|
|
12216
|
+
};
|
|
12151
12217
|
}, [navRef, linkCount]);
|
|
12152
12218
|
}
|
|
12153
12219
|
function TableOfContents({ children }) {
|
|
@@ -98719,25 +98785,31 @@ function makeVariableNode(varName, rawValue) {
|
|
|
98719
98785
|
}
|
|
98720
98786
|
/**
|
|
98721
98787
|
* A remark plugin that parses {user.<field>} patterns from text nodes and
|
|
98722
|
-
*
|
|
98788
|
+
* mdx expression nodes, creating Variable nodes for runtime resolution.
|
|
98723
98789
|
*
|
|
98724
|
-
* Handles
|
|
98790
|
+
* Handles:
|
|
98725
98791
|
* - `text` nodes: when safeMode is true or after expression evaluation
|
|
98726
98792
|
* - `mdxTextExpression` nodes: when mdxExpression has parsed {user.*} before evaluation
|
|
98793
|
+
* - `mdxFlowExpression` nodes: when {user.*} appears on its own line (e.g. inside JSX table cells)
|
|
98727
98794
|
*
|
|
98728
98795
|
* Supports any user field: name, email, email_verified, exp, iat, etc.
|
|
98729
98796
|
*/
|
|
98797
|
+
function visitExpressionNode(node, index, parent) {
|
|
98798
|
+
if (index === undefined || !parent)
|
|
98799
|
+
return;
|
|
98800
|
+
const wrapped = `{${(node.value ?? '').trim()}}`;
|
|
98801
|
+
const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
|
|
98802
|
+
if (matches.length !== 1)
|
|
98803
|
+
return;
|
|
98804
|
+
const varName = matches[0][1] || matches[0][2];
|
|
98805
|
+
parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
|
|
98806
|
+
}
|
|
98730
98807
|
const variablesTextTransformer = () => tree => {
|
|
98731
|
-
// Handle mdxTextExpression nodes (e.g. {user.name} parsed by mdxExpression)
|
|
98732
98808
|
visit(tree, 'mdxTextExpression', (node, index, parent) => {
|
|
98733
|
-
|
|
98734
|
-
|
|
98735
|
-
|
|
98736
|
-
|
|
98737
|
-
if (matches.length !== 1)
|
|
98738
|
-
return;
|
|
98739
|
-
const varName = matches[0][1] || matches[0][2];
|
|
98740
|
-
parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
|
|
98809
|
+
visitExpressionNode(node, index, parent);
|
|
98810
|
+
});
|
|
98811
|
+
visit(tree, 'mdxFlowExpression', (node, index, parent) => {
|
|
98812
|
+
visitExpressionNode(node, index, parent);
|
|
98741
98813
|
});
|
|
98742
98814
|
visit(tree, 'text', (node, index, parent) => {
|
|
98743
98815
|
if (index === undefined || !parent)
|
package/dist/main.node.js
CHANGED
|
@@ -19670,14 +19670,14 @@ function getWindowScrollBarX(element) {
|
|
|
19670
19670
|
}
|
|
19671
19671
|
;// ./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js
|
|
19672
19672
|
|
|
19673
|
-
function
|
|
19673
|
+
function getComputedStyle_getComputedStyle(element) {
|
|
19674
19674
|
return getWindow(element).getComputedStyle(element);
|
|
19675
19675
|
}
|
|
19676
19676
|
;// ./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js
|
|
19677
19677
|
|
|
19678
19678
|
function isScrollParent(element) {
|
|
19679
19679
|
// Firefox wants us to check `-x` and `-y` variations as well
|
|
19680
|
-
var _getComputedStyle =
|
|
19680
|
+
var _getComputedStyle = getComputedStyle_getComputedStyle(element),
|
|
19681
19681
|
overflow = _getComputedStyle.overflow,
|
|
19682
19682
|
overflowX = _getComputedStyle.overflowX,
|
|
19683
19683
|
overflowY = _getComputedStyle.overflowY;
|
|
@@ -19849,7 +19849,7 @@ function isTableElement(element) {
|
|
|
19849
19849
|
|
|
19850
19850
|
function getTrueOffsetParent(element) {
|
|
19851
19851
|
if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837
|
|
19852
|
-
|
|
19852
|
+
getComputedStyle_getComputedStyle(element).position === 'fixed') {
|
|
19853
19853
|
return null;
|
|
19854
19854
|
}
|
|
19855
19855
|
|
|
@@ -19864,7 +19864,7 @@ function getContainingBlock(element) {
|
|
|
19864
19864
|
|
|
19865
19865
|
if (isIE && isHTMLElement(element)) {
|
|
19866
19866
|
// In IE 9, 10 and 11 fixed elements containing block is always established by the viewport
|
|
19867
|
-
var elementCss =
|
|
19867
|
+
var elementCss = getComputedStyle_getComputedStyle(element);
|
|
19868
19868
|
|
|
19869
19869
|
if (elementCss.position === 'fixed') {
|
|
19870
19870
|
return null;
|
|
@@ -19878,7 +19878,7 @@ function getContainingBlock(element) {
|
|
|
19878
19878
|
}
|
|
19879
19879
|
|
|
19880
19880
|
while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {
|
|
19881
|
-
var css =
|
|
19881
|
+
var css = getComputedStyle_getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that
|
|
19882
19882
|
// create a containing block.
|
|
19883
19883
|
// https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
|
|
19884
19884
|
|
|
@@ -19898,11 +19898,11 @@ function getOffsetParent(element) {
|
|
|
19898
19898
|
var window = getWindow(element);
|
|
19899
19899
|
var offsetParent = getTrueOffsetParent(element);
|
|
19900
19900
|
|
|
19901
|
-
while (offsetParent && isTableElement(offsetParent) &&
|
|
19901
|
+
while (offsetParent && isTableElement(offsetParent) && getComputedStyle_getComputedStyle(offsetParent).position === 'static') {
|
|
19902
19902
|
offsetParent = getTrueOffsetParent(offsetParent);
|
|
19903
19903
|
}
|
|
19904
19904
|
|
|
19905
|
-
if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' &&
|
|
19905
|
+
if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle_getComputedStyle(offsetParent).position === 'static')) {
|
|
19906
19906
|
return window;
|
|
19907
19907
|
}
|
|
19908
19908
|
|
|
@@ -20447,7 +20447,7 @@ function mapToStyles(_ref2) {
|
|
|
20447
20447
|
if (offsetParent === getWindow(popper)) {
|
|
20448
20448
|
offsetParent = getDocumentElement(popper);
|
|
20449
20449
|
|
|
20450
|
-
if (
|
|
20450
|
+
if (getComputedStyle_getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {
|
|
20451
20451
|
heightProp = 'scrollHeight';
|
|
20452
20452
|
widthProp = 'scrollWidth';
|
|
20453
20453
|
}
|
|
@@ -20759,7 +20759,7 @@ function getDocumentRect(element) {
|
|
|
20759
20759
|
var x = -winScroll.scrollLeft + getWindowScrollBarX(element);
|
|
20760
20760
|
var y = -winScroll.scrollTop;
|
|
20761
20761
|
|
|
20762
|
-
if (
|
|
20762
|
+
if (getComputedStyle_getComputedStyle(body || html).direction === 'rtl') {
|
|
20763
20763
|
x += math_max(html.clientWidth, body ? body.clientWidth : 0) - width;
|
|
20764
20764
|
}
|
|
20765
20765
|
|
|
@@ -20841,7 +20841,7 @@ function getClientRectFromMixedType(element, clippingParent, strategy) {
|
|
|
20841
20841
|
|
|
20842
20842
|
function getClippingParents(element) {
|
|
20843
20843
|
var clippingParents = listScrollParents(getParentNode(element));
|
|
20844
|
-
var canEscapeClipping = ['absolute', 'fixed'].indexOf(
|
|
20844
|
+
var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle_getComputedStyle(element).position) >= 0;
|
|
20845
20845
|
var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;
|
|
20846
20846
|
|
|
20847
20847
|
if (!isElement(clipperElement)) {
|
|
@@ -24683,6 +24683,24 @@ function buildLinkMap(nav) {
|
|
|
24683
24683
|
});
|
|
24684
24684
|
return map;
|
|
24685
24685
|
}
|
|
24686
|
+
const VISIBLE_RATIO = 0.4;
|
|
24687
|
+
/** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
|
|
24688
|
+
const SCROLL_BOTTOM_TOLERANCE = 1;
|
|
24689
|
+
/**
|
|
24690
|
+
* Walk up the DOM to find the nearest scrollable ancestor.
|
|
24691
|
+
* Falls back to `window` when the page itself scrolls.
|
|
24692
|
+
*/
|
|
24693
|
+
function TableOfContents_getScrollParent(el) {
|
|
24694
|
+
let parent = el.parentElement;
|
|
24695
|
+
while (parent) {
|
|
24696
|
+
const { overflow, overflowY } = getComputedStyle(parent);
|
|
24697
|
+
if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
|
|
24698
|
+
return parent;
|
|
24699
|
+
}
|
|
24700
|
+
parent = parent.parentElement;
|
|
24701
|
+
}
|
|
24702
|
+
return window;
|
|
24703
|
+
}
|
|
24686
24704
|
/**
|
|
24687
24705
|
* Watches headings in the viewport and toggles `active` on the
|
|
24688
24706
|
* corresponding TOC links so the reader always knows where they are.
|
|
@@ -24709,7 +24727,15 @@ function useScrollHighlight(navRef) {
|
|
|
24709
24727
|
if (headings.length === 0)
|
|
24710
24728
|
return undefined;
|
|
24711
24729
|
let activeId = null;
|
|
24730
|
+
let clickLocked = false;
|
|
24712
24731
|
const visible = new Set();
|
|
24732
|
+
const scrollParent = TableOfContents_getScrollParent(headings[0]);
|
|
24733
|
+
const isAtBottom = () => {
|
|
24734
|
+
if (scrollParent instanceof Window) {
|
|
24735
|
+
return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
24736
|
+
}
|
|
24737
|
+
return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
|
|
24738
|
+
};
|
|
24713
24739
|
const activate = (id) => {
|
|
24714
24740
|
if (id === activeId)
|
|
24715
24741
|
return;
|
|
@@ -24729,6 +24755,17 @@ function useScrollHighlight(navRef) {
|
|
|
24729
24755
|
}
|
|
24730
24756
|
}
|
|
24731
24757
|
};
|
|
24758
|
+
const updateActive = () => {
|
|
24759
|
+
if (clickLocked)
|
|
24760
|
+
return;
|
|
24761
|
+
if (isAtBottom()) {
|
|
24762
|
+
activate(headings[headings.length - 1].id);
|
|
24763
|
+
return;
|
|
24764
|
+
}
|
|
24765
|
+
const topmost = headings.find(el => visible.has(el.id));
|
|
24766
|
+
if (topmost)
|
|
24767
|
+
activate(topmost.id);
|
|
24768
|
+
};
|
|
24732
24769
|
const observer = new IntersectionObserver(entries => {
|
|
24733
24770
|
entries.forEach(e => {
|
|
24734
24771
|
if (e.isIntersecting)
|
|
@@ -24736,14 +24773,43 @@ function useScrollHighlight(navRef) {
|
|
|
24736
24773
|
else
|
|
24737
24774
|
visible.delete(e.target.id);
|
|
24738
24775
|
});
|
|
24739
|
-
|
|
24740
|
-
|
|
24741
|
-
|
|
24742
|
-
|
|
24743
|
-
|
|
24744
|
-
|
|
24776
|
+
updateActive();
|
|
24777
|
+
}, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
|
|
24778
|
+
// Check on scroll so bottom-of-page detection works even when
|
|
24779
|
+
// no headings are crossing the intersection boundary.
|
|
24780
|
+
const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
|
|
24781
|
+
const onScroll = () => { updateActive(); };
|
|
24782
|
+
// Click a ToC link → immediately activate it, suppress the observer
|
|
24783
|
+
// until the smooth scroll finishes, then hand control back.
|
|
24784
|
+
const onClick = (e) => {
|
|
24785
|
+
const anchor = e.target.closest?.('a[href^="#"]');
|
|
24786
|
+
if (!anchor)
|
|
24787
|
+
return;
|
|
24788
|
+
const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
|
|
24789
|
+
if (!linkMap.has(id))
|
|
24790
|
+
return;
|
|
24791
|
+
e.preventDefault();
|
|
24792
|
+
activate(id);
|
|
24793
|
+
clickLocked = true;
|
|
24794
|
+
const unlock = () => { clickLocked = false; };
|
|
24795
|
+
scrollTarget.addEventListener('scrollend', unlock, { once: true });
|
|
24796
|
+
document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
|
|
24797
|
+
};
|
|
24745
24798
|
headings.forEach(el => { observer.observe(el); });
|
|
24746
|
-
|
|
24799
|
+
scrollTarget.addEventListener('scroll', onScroll, { passive: true });
|
|
24800
|
+
nav.addEventListener('click', onClick);
|
|
24801
|
+
// Set initial active state for the first heading visible in the viewport,
|
|
24802
|
+
// falling back to the first heading if none is in the observation zone yet.
|
|
24803
|
+
const initialHeading = headings.find(el => {
|
|
24804
|
+
const rect = el.getBoundingClientRect();
|
|
24805
|
+
return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
|
|
24806
|
+
}) || headings[0];
|
|
24807
|
+
activate(initialHeading.id);
|
|
24808
|
+
return () => {
|
|
24809
|
+
observer.disconnect();
|
|
24810
|
+
scrollTarget.removeEventListener('scroll', onScroll);
|
|
24811
|
+
nav.removeEventListener('click', onClick);
|
|
24812
|
+
};
|
|
24747
24813
|
}, [navRef, linkCount]);
|
|
24748
24814
|
}
|
|
24749
24815
|
function TableOfContents({ children }) {
|
|
@@ -118913,25 +118979,31 @@ function makeVariableNode(varName, rawValue) {
|
|
|
118913
118979
|
}
|
|
118914
118980
|
/**
|
|
118915
118981
|
* A remark plugin that parses {user.<field>} patterns from text nodes and
|
|
118916
|
-
*
|
|
118982
|
+
* mdx expression nodes, creating Variable nodes for runtime resolution.
|
|
118917
118983
|
*
|
|
118918
|
-
* Handles
|
|
118984
|
+
* Handles:
|
|
118919
118985
|
* - `text` nodes: when safeMode is true or after expression evaluation
|
|
118920
118986
|
* - `mdxTextExpression` nodes: when mdxExpression has parsed {user.*} before evaluation
|
|
118987
|
+
* - `mdxFlowExpression` nodes: when {user.*} appears on its own line (e.g. inside JSX table cells)
|
|
118921
118988
|
*
|
|
118922
118989
|
* Supports any user field: name, email, email_verified, exp, iat, etc.
|
|
118923
118990
|
*/
|
|
118991
|
+
function visitExpressionNode(node, index, parent) {
|
|
118992
|
+
if (index === undefined || !parent)
|
|
118993
|
+
return;
|
|
118994
|
+
const wrapped = `{${(node.value ?? '').trim()}}`;
|
|
118995
|
+
const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
|
|
118996
|
+
if (matches.length !== 1)
|
|
118997
|
+
return;
|
|
118998
|
+
const varName = matches[0][1] || matches[0][2];
|
|
118999
|
+
parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
|
|
119000
|
+
}
|
|
118924
119001
|
const variablesTextTransformer = () => tree => {
|
|
118925
|
-
// Handle mdxTextExpression nodes (e.g. {user.name} parsed by mdxExpression)
|
|
118926
119002
|
visit(tree, 'mdxTextExpression', (node, index, parent) => {
|
|
118927
|
-
|
|
118928
|
-
|
|
118929
|
-
|
|
118930
|
-
|
|
118931
|
-
if (matches.length !== 1)
|
|
118932
|
-
return;
|
|
118933
|
-
const varName = matches[0][1] || matches[0][2];
|
|
118934
|
-
parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
|
|
119003
|
+
visitExpressionNode(node, index, parent);
|
|
119004
|
+
});
|
|
119005
|
+
visit(tree, 'mdxFlowExpression', (node, index, parent) => {
|
|
119006
|
+
visitExpressionNode(node, index, parent);
|
|
118935
119007
|
});
|
|
118936
119008
|
visit(tree, 'text', (node, index, parent) => {
|
|
118937
119009
|
if (index === undefined || !parent)
|