@san-siva/blogkit 1.1.20 → 1.1.22
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/dist/cjs/components/BlogSection.js +2 -2
- package/dist/cjs/components/BlogSection.js.map +1 -1
- package/dist/cjs/dynamicComponents/BlogDynamic.js +31 -181
- package/dist/cjs/dynamicComponents/BlogDynamic.js.map +1 -1
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js +16 -8
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js.map +1 -1
- package/dist/cjs/hooks/useCategoryTitles.js +104 -0
- package/dist/cjs/hooks/useCategoryTitles.js.map +1 -0
- package/dist/cjs/hooks/useSectionObserver.js +89 -0
- package/dist/cjs/hooks/useSectionObserver.js.map +1 -0
- package/dist/cjs/index.css +1 -1
- package/dist/cjs/index.css.map +1 -1
- package/dist/cjs/staticComponents/BlogSectionStatic.js +2 -5
- package/dist/cjs/staticComponents/BlogSectionStatic.js.map +1 -1
- package/dist/cjs/staticComponents/TocNodeStatic.js +16 -0
- package/dist/cjs/staticComponents/TocNodeStatic.js.map +1 -0
- package/dist/cjs/styles/Blog.module.scss.js +1 -1
- package/dist/cjs/styles/BlogSection.module.scss.js +1 -1
- package/dist/cjs/styles/Callout.module.scss.js +1 -1
- package/dist/cjs/styles/TocNode.module.scss.js +8 -0
- package/dist/cjs/styles/TocNode.module.scss.js.map +1 -0
- package/dist/esm/components/BlogSection.js +2 -2
- package/dist/esm/components/BlogSection.js.map +1 -1
- package/dist/esm/dynamicComponents/BlogDynamic.js +32 -182
- package/dist/esm/dynamicComponents/BlogDynamic.js.map +1 -1
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js +17 -9
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js.map +1 -1
- package/dist/esm/hooks/useCategoryTitles.js +102 -0
- package/dist/esm/hooks/useCategoryTitles.js.map +1 -0
- package/dist/esm/hooks/useSectionObserver.js +87 -0
- package/dist/esm/hooks/useSectionObserver.js.map +1 -0
- package/dist/esm/index.css +1 -1
- package/dist/esm/index.css.map +1 -1
- package/dist/esm/staticComponents/BlogSectionStatic.js +2 -5
- package/dist/esm/staticComponents/BlogSectionStatic.js.map +1 -1
- package/dist/esm/staticComponents/TocNodeStatic.js +12 -0
- package/dist/esm/staticComponents/TocNodeStatic.js.map +1 -0
- package/dist/esm/styles/Blog.module.scss.js +1 -1
- package/dist/esm/styles/BlogSection.module.scss.js +1 -1
- package/dist/esm/styles/Callout.module.scss.js +1 -1
- package/dist/esm/styles/TocNode.module.scss.js +4 -0
- package/dist/esm/styles/TocNode.module.scss.js.map +1 -0
- package/dist/types/components/BlogSection.d.ts +0 -1
- package/dist/types/components/BlogSection.d.ts.map +1 -1
- package/dist/types/dynamicComponents/BlogDynamic.d.ts +1 -1
- package/dist/types/dynamicComponents/BlogDynamic.d.ts.map +1 -1
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts +0 -1
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts.map +1 -1
- package/dist/types/hooks/useCategoryTitles.d.ts +22 -0
- package/dist/types/hooks/useCategoryTitles.d.ts.map +1 -0
- package/dist/types/hooks/useSectionObserver.d.ts +11 -0
- package/dist/types/hooks/useSectionObserver.d.ts.map +1 -0
- package/dist/types/staticComponents/BlogSectionStatic.d.ts +1 -2
- package/dist/types/staticComponents/BlogSectionStatic.d.ts.map +1 -1
- package/dist/types/staticComponents/TocNodeStatic.d.ts +16 -0
- package/dist/types/staticComponents/TocNodeStatic.d.ts.map +1 -0
- package/package.json +5 -3
- package/src/components/BlogSection.tsx +0 -4
- package/src/dynamicComponents/BlogDynamic.tsx +42 -253
- package/src/dynamicComponents/BlogSectionDynamic.tsx +38 -21
- package/src/hooks/useCategoryTitles.ts +148 -0
- package/src/hooks/useSectionObserver.ts +102 -0
- package/src/staticComponents/BlogSectionStatic.tsx +2 -13
- package/src/staticComponents/TocNodeStatic.tsx +52 -0
- package/src/styles/Blog.module.scss +0 -30
- package/src/styles/Blog.module.scss.d.ts +0 -4
- package/src/styles/BlogHeader.module.scss +6 -5
- package/src/styles/BlogLink.module.scss +1 -1
- package/src/styles/BlogSection.module.scss +26 -21
- package/src/styles/BlogSection.module.scss.d.ts +1 -2
- package/src/styles/CodeBlock.module.scss +2 -2
- package/src/styles/Table.module.scss +1 -1
- package/src/styles/TocNode.module.scss +49 -0
- package/src/styles/TocNode.module.scss.d.ts +11 -0
|
@@ -8,8 +8,8 @@ var react = require('react');
|
|
|
8
8
|
var BlogSectionStatic = require('../staticComponents/BlogSectionStatic.js');
|
|
9
9
|
|
|
10
10
|
const BlogSectionDynamic = react.lazy(() => Promise.resolve().then(function () { return require('../dynamicComponents/BlogSectionDynamic.js'); }));
|
|
11
|
-
const BlogSection = react.forwardRef(({ title = '', category = '', children = null,
|
|
12
|
-
return (jsxRuntime.jsx(react.Suspense, { fallback: jsxRuntime.jsx(BlogSectionStatic.default, { title: title, category: category,
|
|
11
|
+
const BlogSection = react.forwardRef(({ title = '', category = '', children = null, }, ref) => {
|
|
12
|
+
return (jsxRuntime.jsx(react.Suspense, { fallback: jsxRuntime.jsx(BlogSectionStatic.default, { title: title, category: category, children: children }), children: jsxRuntime.jsx(BlogSectionDynamic, { ref: ref, title: title, category: category, children: children }) }));
|
|
13
13
|
});
|
|
14
14
|
BlogSection.displayName = 'BlogSection';
|
|
15
15
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlogSection.js","sources":["../../../src/components/BlogSection.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, lazy, Suspense } from 'react';\nimport type { ReactNode } from 'react';\nimport BlogSectionStatic from '../staticComponents/BlogSectionStatic';\nimport type { ForwardedReference } from '../dynamicComponents/BlogDynamic';\nexport type { ForwardedReference };\n\nconst BlogSectionDynamic = lazy(\n\t() => import('../dynamicComponents/BlogSectionDynamic')\n);\n\ninterface BlogSectionProperties {\n\ttitle?: string;\n\tcategory?: string;\n\tchildren?: ReactNode;\n
|
|
1
|
+
{"version":3,"file":"BlogSection.js","sources":["../../../src/components/BlogSection.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, lazy, Suspense } from 'react';\nimport type { ReactNode } from 'react';\nimport BlogSectionStatic from '../staticComponents/BlogSectionStatic';\nimport type { ForwardedReference } from '../dynamicComponents/BlogDynamic';\nexport type { ForwardedReference };\n\nconst BlogSectionDynamic = lazy(\n\t() => import('../dynamicComponents/BlogSectionDynamic')\n);\n\ninterface BlogSectionProperties {\n\ttitle?: string;\n\tcategory?: string;\n\tchildren?: ReactNode;\n}\n\nconst BlogSection = forwardRef<ForwardedReference, BlogSectionProperties>(\n\t(\n\t\t{\n\t\t\ttitle = '',\n\t\t\tcategory = '',\n\t\t\tchildren = null,\n\t\t},\n\t\tref\n\t) => {\n\t\treturn (\n\t\t\t<Suspense\n\t\t\t\tfallback={\n\t\t\t\t\t<BlogSectionStatic\n\t\t\t\t\t\ttitle={title}\n\t\t\t\t\t\tcategory={category}\n\t\t\t\t\t>\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</BlogSectionStatic>\n\t\t\t\t}\n\t\t\t>\n\t\t\t\t<BlogSectionDynamic\n\t\t\t\t\tref={ref}\n\t\t\t\t\ttitle={title}\n\t\t\t\t\tcategory={category}\n\t\t\t\t>\n\t\t\t\t\t{children}\n\t\t\t\t</BlogSectionDynamic>\n\t\t\t</Suspense>\n\t\t);\n\t}\n);\n\nBlogSection.displayName = 'BlogSection';\n\nexport default BlogSection;\n"],"names":["lazy","forwardRef","_jsx","Suspense","BlogSectionStatic"],"mappings":";;;;;;;;AAQA,MAAM,kBAAkB,GAAGA,UAAI,CAC9B,MAAM,oDAAO,4CAAyC,KAAC,CACvD;AAQD,MAAM,WAAW,GAAGC,gBAAU,CAC7B,CACC,EACC,KAAK,GAAG,EAAE,EACV,QAAQ,GAAG,EAAE,EACb,QAAQ,GAAG,IAAI,GACf,EACD,GAAG,KACA;AACH,IAAA,QACCC,cAAA,CAACC,cAAQ,EAAA,EACR,QAAQ,EACPD,cAAA,CAACE,yBAAiB,EAAA,EACjB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,YAEjB,QAAQ,EAAA,CACU,EAAA,QAAA,EAGrBF,cAAA,CAAC,kBAAkB,EAAA,EAClB,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAAA,QAAA,EAEjB,QAAQ,EAAA,CACW,EAAA,CACX;AAEb,CAAC;AAGF,WAAW,CAAC,WAAW,GAAG,aAAa;;;;"}
|
|
@@ -7,185 +7,40 @@ var jsxRuntime = require('react/jsx-runtime');
|
|
|
7
7
|
var react = require('react');
|
|
8
8
|
var web = require('@react-spring/web');
|
|
9
9
|
var Blog_module = require('../styles/Blog.module.scss.js');
|
|
10
|
-
var
|
|
10
|
+
var useCategoryTitles = require('../hooks/useCategoryTitles.js');
|
|
11
|
+
var useSectionObserver = require('../hooks/useSectionObserver.js');
|
|
12
|
+
var TocNodeStatic = require('../staticComponents/TocNodeStatic.js');
|
|
11
13
|
|
|
14
|
+
const buildTocTree = (entries) => {
|
|
15
|
+
const roots = [];
|
|
16
|
+
const stack = [];
|
|
17
|
+
for (const [id, { title, depth }] of entries) {
|
|
18
|
+
const node = { id, title, depth, children: [] };
|
|
19
|
+
while (stack.length > 0 && stack[stack.length - 1].depth >= depth) {
|
|
20
|
+
stack.pop();
|
|
21
|
+
}
|
|
22
|
+
if (stack.length === 0) {
|
|
23
|
+
roots.push(node);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
stack[stack.length - 1].children.push(node);
|
|
27
|
+
}
|
|
28
|
+
stack.push(node);
|
|
29
|
+
}
|
|
30
|
+
return roots;
|
|
31
|
+
};
|
|
12
32
|
const Blog = ({ children, title = 'In this article', jsonLd, }) => {
|
|
13
|
-
const sectionReferences = react.useRef(new Map());
|
|
14
|
-
const [categoryTitles, setCategoryTitles] = react.useState(new Map());
|
|
15
33
|
const [visibleTitle, setVisibleTitle] = react.useState(null);
|
|
16
34
|
const [showTOC, setShowTOC] = react.useState(false);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
28
|
-
return 1; // b comes before a
|
|
29
|
-
}
|
|
30
|
-
return 0;
|
|
31
|
-
}, []);
|
|
32
|
-
const updateCategoryTitles = react.useCallback(() => {
|
|
33
|
-
const now = Date.now();
|
|
34
|
-
const newCategoryTitles = new Map();
|
|
35
|
-
// Sort sections by their DOM position to maintain correct order
|
|
36
|
-
const sectionsArray = Array.from(sectionReferences.current.entries());
|
|
37
|
-
sectionsArray.sort(sortByDomPosition);
|
|
38
|
-
let firstSectionId = null;
|
|
39
|
-
for (const [id, { title, el, isSubSection }] of sectionsArray) {
|
|
40
|
-
if (!firstSectionId) {
|
|
41
|
-
firstSectionId = id;
|
|
42
|
-
}
|
|
43
|
-
newCategoryTitles.set(id, {
|
|
44
|
-
el,
|
|
45
|
-
title,
|
|
46
|
-
lastUpdatedAt: now,
|
|
47
|
-
isSubSection,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
if (newCategoryTitles.size === 0)
|
|
51
|
-
return;
|
|
52
|
-
setCategoryTitles(newCategoryTitles);
|
|
53
|
-
if (!showTOC)
|
|
54
|
-
setShowTOC(true);
|
|
55
|
-
if (visibleTitle)
|
|
56
|
-
return;
|
|
57
|
-
setVisibleTitle(firstSectionId);
|
|
58
|
-
}, [visibleTitle, sortByDomPosition, showTOC, setShowTOC]);
|
|
59
|
-
const debounceUpdateCategoryTitles = react.useCallback(() => {
|
|
60
|
-
// Clear existing timer and set a new one to batch updates
|
|
61
|
-
if (updateTimerRef.current) {
|
|
62
|
-
clearTimeout(updateTimerRef.current);
|
|
63
|
-
}
|
|
64
|
-
updateTimerRef.current = setTimeout(() => {
|
|
65
|
-
updateCategoryTitles();
|
|
66
|
-
}, 200);
|
|
67
|
-
}, [updateCategoryTitles]);
|
|
68
|
-
react.useEffect(() => {
|
|
69
|
-
for (const [id, { el }] of categoryTitles) {
|
|
70
|
-
const observer = new IntersectionObserver(([entry]) => {
|
|
71
|
-
if (!entry.isIntersecting)
|
|
72
|
-
return;
|
|
73
|
-
if (isClickScrolling.current)
|
|
74
|
-
return;
|
|
75
|
-
if (document.body.scrollTop === 0)
|
|
76
|
-
return;
|
|
77
|
-
setVisibleTitle(visibleId => {
|
|
78
|
-
if (visibleId === id && !entry.isIntersecting)
|
|
79
|
-
return null;
|
|
80
|
-
if (entry.isIntersecting)
|
|
81
|
-
return id;
|
|
82
|
-
return visibleId;
|
|
83
|
-
});
|
|
84
|
-
const url = new URL(window.location.href);
|
|
85
|
-
url.searchParams.set('section', id);
|
|
86
|
-
window.history.replaceState({}, '', url.toString());
|
|
87
|
-
}, { threshold: 0.1 });
|
|
88
|
-
intersectionObserversRef.current.set(id, observer);
|
|
89
|
-
observer.observe(el);
|
|
90
|
-
}
|
|
91
|
-
}, [categoryTitles.size]);
|
|
92
|
-
react.useEffect(() => {
|
|
93
|
-
return () => {
|
|
94
|
-
if (updateTimerRef.current) {
|
|
95
|
-
clearTimeout(updateTimerRef.current);
|
|
96
|
-
}
|
|
97
|
-
if (showTOCTimerRef.current) {
|
|
98
|
-
clearTimeout(showTOCTimerRef.current);
|
|
99
|
-
}
|
|
100
|
-
if (scrollEndHandlerRef.current) {
|
|
101
|
-
document.body.removeEventListener('scrollend', scrollEndHandlerRef.current);
|
|
102
|
-
}
|
|
103
|
-
if (intersectionObserversRef.current) {
|
|
104
|
-
for (const observer of intersectionObserversRef.current.values()) {
|
|
105
|
-
observer.disconnect();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
}, []);
|
|
110
|
-
const getSectionFromUrl = () => {
|
|
111
|
-
const url = new URL(window.location.href);
|
|
112
|
-
const section = url.searchParams.get('section');
|
|
113
|
-
if (!section)
|
|
114
|
-
return null;
|
|
115
|
-
return section;
|
|
116
|
-
};
|
|
117
|
-
const updateUrl = (id) => {
|
|
118
|
-
const url = new URL(window.location.href);
|
|
119
|
-
url.searchParams.set('section', id);
|
|
120
|
-
window.history.replaceState({}, '', url.toString());
|
|
121
|
-
};
|
|
122
|
-
const scrollIntoView = (element) => {
|
|
123
|
-
if (!element)
|
|
124
|
-
return;
|
|
125
|
-
const top = element.getBoundingClientRect().top + document.body.scrollTop - 100;
|
|
126
|
-
document.body.scrollTo({ top, behavior: 'smooth' });
|
|
127
|
-
};
|
|
128
|
-
// On initial load, scroll to section specified in URL
|
|
129
|
-
react.useEffect(() => {
|
|
130
|
-
if (categoryTitles.size === 0)
|
|
131
|
-
return;
|
|
132
|
-
const section = getSectionFromUrl();
|
|
133
|
-
if (!section) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const entry = categoryTitles.get(section);
|
|
137
|
-
if (!entry) {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
scrollIntoView(entry.el);
|
|
141
|
-
lockScrollUpdates.default(section, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
142
|
-
}, [categoryTitles.size]);
|
|
143
|
-
const handleSectionReference = react.useCallback((element) => {
|
|
144
|
-
if (!element)
|
|
145
|
-
return;
|
|
146
|
-
const { parentRef, childRefs } = element;
|
|
147
|
-
// Add parent section reference
|
|
148
|
-
if (parentRef) {
|
|
149
|
-
const id = parentRef.dataset.id;
|
|
150
|
-
const title = parentRef.dataset.title;
|
|
151
|
-
if (id && title) {
|
|
152
|
-
sectionReferences.current.set(id, {
|
|
153
|
-
el: parentRef,
|
|
154
|
-
title,
|
|
155
|
-
isSubSection: false,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// Add child section references
|
|
160
|
-
if (Array.isArray(childRefs)) {
|
|
161
|
-
for (const childRef of childRefs) {
|
|
162
|
-
if (!childRef)
|
|
163
|
-
continue;
|
|
164
|
-
const id = childRef.dataset.id;
|
|
165
|
-
const title = childRef.dataset.title;
|
|
166
|
-
if (id && title) {
|
|
167
|
-
sectionReferences.current.set(id, {
|
|
168
|
-
el: childRef,
|
|
169
|
-
title,
|
|
170
|
-
isSubSection: true,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
debounceUpdateCategoryTitles();
|
|
176
|
-
}, [debounceUpdateCategoryTitles]);
|
|
177
|
-
const handleClickCategoryTitle = (error) => {
|
|
178
|
-
const id = error.currentTarget.dataset.id;
|
|
179
|
-
const index = error.currentTarget.dataset.idx;
|
|
180
|
-
if (!id || !index)
|
|
181
|
-
return;
|
|
182
|
-
const { el } = categoryTitles.get(id) || {};
|
|
183
|
-
if (!el)
|
|
184
|
-
return;
|
|
185
|
-
updateUrl(id);
|
|
186
|
-
scrollIntoView(el);
|
|
187
|
-
lockScrollUpdates.default(id, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
188
|
-
};
|
|
35
|
+
const { categoryTitles, handleSectionReference } = useCategoryTitles.useCategoryTitles({
|
|
36
|
+
visibleTitle,
|
|
37
|
+
setVisibleTitle,
|
|
38
|
+
setShowTOC,
|
|
39
|
+
});
|
|
40
|
+
const { handleClickCategoryTitle } = useSectionObserver.useSectionObserver({
|
|
41
|
+
categoryTitles,
|
|
42
|
+
setVisibleTitle,
|
|
43
|
+
});
|
|
189
44
|
const sidebarStyle = web.useSpring({
|
|
190
45
|
opacity: showTOC ? 1 : 0,
|
|
191
46
|
transform: showTOC ? 'translateX(0)' : 'translateX(40px)',
|
|
@@ -197,12 +52,7 @@ const Blog = ({ children, title = 'In this article', jsonLd, }) => {
|
|
|
197
52
|
return react.cloneElement(child, {
|
|
198
53
|
ref: handleSectionReference,
|
|
199
54
|
});
|
|
200
|
-
}) }), jsxRuntime.jsxs(web.animated.div, { className: Blog_module.default['blog__sidebar'], style: sidebarStyle, children: [jsxRuntime.jsx("p", { className: `${Blog_module.default['margin-bottom--3']} ${Blog_module.default['category__header']}`, children: title }), [...categoryTitles].map((
|
|
201
|
-
const isNextSectionSubSection = array[index + 1]?.[1]?.isSubSection;
|
|
202
|
-
return (jsxRuntime.jsx("p", { "data-idx": index, "data-id": id, className: `${Blog_module.default['category__title']} ${id === visibleTitle ? Blog_module.default['category__title--active'] : ''} ${isSubSection ? Blog_module.default['category__title--sub'] : ''} ${isSubSection && !isNextSectionSubSection
|
|
203
|
-
? Blog_module.default['margin-bottom-imp--2']
|
|
204
|
-
: ''}`, onClick: handleClickCategoryTitle, children: title }, id));
|
|
205
|
-
})] })] }));
|
|
55
|
+
}) }), jsxRuntime.jsxs(web.animated.div, { className: Blog_module.default['blog__sidebar'], style: sidebarStyle, children: [jsxRuntime.jsx("p", { className: `${Blog_module.default['margin-bottom--3']} ${Blog_module.default['category__header']}`, children: title }), buildTocTree([...categoryTitles]).map((node, i) => (jsxRuntime.jsx(TocNodeStatic.default, { node: node, index: i, visibleTitle: visibleTitle, onClick: handleClickCategoryTitle }, node.id)))] })] }));
|
|
206
56
|
};
|
|
207
57
|
|
|
208
58
|
exports.default = Blog;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlogDynamic.js","sources":["../../../src/dynamicComponents/BlogDynamic.tsx"],"sourcesContent":["'use client';\n\nimport {\n\tChildren,\n\tcloneElement,\n\tisValidElement,\n\tuseCallback,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from 'react';\nimport { useSpring, animated, config } from '@react-spring/web';\n\nimport type { MouseEvent, ReactNode, RefAttributes } from 'react';\nimport type { Thing, WithContext } from 'schema-dts';\n\nimport styles from '../styles/Blog.module.scss';\nimport lockScrollUpdates from '../utils/lockScrollUpdates';\n\ninterface BlogProperties {\n\tchildren: ReactNode;\n\ttitle?: string;\n\tjsonLd?: WithContext<Thing>;\n}\n\nexport interface ForwardedReference {\n\tparentRef: HTMLDivElement;\n\tchildRefs: HTMLDivElement[];\n}\n\ninterface SectionReferenceValue {\n\tel: HTMLElement;\n\ttitle: string;\n\tisSubSection: boolean;\n}\n\ntype SectionReference = Map<string, SectionReferenceValue>;\n\ninterface CategoryTitleValue extends SectionReferenceValue {\n\tlastUpdatedAt: number;\n}\n\ntype CategoryTitle = Map<string, CategoryTitleValue>;\n\nconst Blog = ({\n\tchildren,\n\ttitle = 'In this article',\n\tjsonLd,\n}: BlogProperties) => {\n\tconst sectionReferences = useRef<SectionReference>(new Map());\n\tconst [categoryTitles, setCategoryTitles] = useState<CategoryTitle>(\n\t\tnew Map()\n\t);\n\tconst [visibleTitle, setVisibleTitle] = useState<string | null>(null);\n\tconst [showTOC, setShowTOC] = useState(false);\n\n\tconst updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\tconst showTOCTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\tconst isClickScrolling = useRef(false);\n\tconst scrollEndHandlerRef = useRef<(() => void) | null>(null);\n\tconst intersectionObserversRef = useRef<Map<string, IntersectionObserver>>(\n\t\tnew Map()\n\t);\n\n\tconst sortByDomPosition = useCallback(\n\t\t(\n\t\t\t[, a]: [string, SectionReferenceValue],\n\t\t\t[, b]: [string, SectionReferenceValue]\n\t\t) => {\n\t\t\tconst position = a.el.compareDocumentPosition(b.el);\n\t\t\tif (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n\t\t\t\treturn -1; // a comes before b\n\t\t\t} else if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n\t\t\t\treturn 1; // b comes before a\n\t\t\t}\n\t\t\treturn 0;\n\t\t},\n\t\t[]\n\t);\n\n\tconst updateCategoryTitles = useCallback(() => {\n\t\tconst now = Date.now();\n\t\tconst newCategoryTitles = new Map<string, CategoryTitleValue>();\n\n\t\t// Sort sections by their DOM position to maintain correct order\n\t\tconst sectionsArray = Array.from(sectionReferences.current.entries());\n\t\tsectionsArray.sort(sortByDomPosition);\n\n\t\tlet firstSectionId: string | null = null;\n\t\tfor (const [id, { title, el, isSubSection }] of sectionsArray) {\n\t\t\tif (!firstSectionId) {\n\t\t\t\tfirstSectionId = id;\n\t\t\t}\n\t\t\tnewCategoryTitles.set(id, {\n\t\t\t\tel,\n\t\t\t\ttitle,\n\t\t\t\tlastUpdatedAt: now,\n\t\t\t\tisSubSection,\n\t\t\t});\n\t\t}\n\n\t\tif (newCategoryTitles.size === 0) return;\n\n\t\tsetCategoryTitles(newCategoryTitles);\n\t\tif (!showTOC) setShowTOC(true);\n\n\t\tif (visibleTitle) return;\n\t\tsetVisibleTitle(firstSectionId);\n\t}, [visibleTitle, sortByDomPosition, showTOC, setShowTOC]);\n\n\tconst debounceUpdateCategoryTitles = useCallback(() => {\n\t\t// Clear existing timer and set a new one to batch updates\n\t\tif (updateTimerRef.current) {\n\t\t\tclearTimeout(updateTimerRef.current);\n\t\t}\n\t\tupdateTimerRef.current = setTimeout(() => {\n\t\t\tupdateCategoryTitles();\n\t\t}, 200);\n\t}, [updateCategoryTitles]);\n\n\tuseEffect(() => {\n\t\tfor (const [id, { el }] of categoryTitles) {\n\t\t\tconst observer = new IntersectionObserver(\n\t\t\t\t([entry]) => {\n\t\t\t\t\tif (!entry.isIntersecting) return;\n\t\t\t\t\tif (isClickScrolling.current) return;\n\t\t\t\t\tif (document.body.scrollTop === 0) return;\n\t\t\t\t\tsetVisibleTitle(visibleId => {\n\t\t\t\t\t\tif (visibleId === id && !entry.isIntersecting) return null;\n\t\t\t\t\t\tif (entry.isIntersecting) return id;\n\t\t\t\t\t\treturn visibleId;\n\t\t\t\t\t});\n\t\t\t\t\tconst url = new URL(window.location.href);\n\t\t\t\t\turl.searchParams.set('section', id);\n\t\t\t\t\twindow.history.replaceState({}, '', url.toString());\n\t\t\t\t},\n\t\t\t\t{ threshold: 0.1 }\n\t\t\t);\n\t\t\tintersectionObserversRef.current.set(id, observer);\n\t\t\tobserver.observe(el as HTMLElement);\n\t\t}\n\t}, [categoryTitles.size]);\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (updateTimerRef.current) {\n\t\t\t\tclearTimeout(updateTimerRef.current);\n\t\t\t}\n\t\t\tif (showTOCTimerRef.current) {\n\t\t\t\tclearTimeout(showTOCTimerRef.current);\n\t\t\t}\n\t\t\tif (scrollEndHandlerRef.current) {\n\t\t\t\tdocument.body.removeEventListener(\n\t\t\t\t\t'scrollend',\n\t\t\t\t\tscrollEndHandlerRef.current\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (intersectionObserversRef.current) {\n\t\t\t\tfor (const observer of intersectionObserversRef.current.values()) {\n\t\t\t\t\tobserver.disconnect();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}, []);\n\n\tconst getSectionFromUrl = () => {\n\t\tconst url = new URL(window.location.href);\n\t\tconst section = url.searchParams.get('section');\n\t\tif (!section) return null;\n\t\treturn section;\n\t};\n\n\tconst updateUrl = (id: string) => {\n\t\tconst url = new URL(window.location.href);\n\t\turl.searchParams.set('section', id);\n\t\twindow.history.replaceState({}, '', url.toString());\n\t};\n\n\tconst scrollIntoView = (element: HTMLElement) => {\n\t\tif (!element) return;\n\t\tconst top =\n\t\t\telement.getBoundingClientRect().top + document.body.scrollTop - 100;\n\t\tdocument.body.scrollTo({ top, behavior: 'smooth' });\n\t};\n\n\t// On initial load, scroll to section specified in URL\n\tuseEffect(() => {\n\t\tif (categoryTitles.size === 0) return;\n\t\tconst section = getSectionFromUrl();\n\t\tif (!section) {\n\t\t\treturn;\n\t\t}\n\t\tconst entry = categoryTitles.get(section);\n\t\tif (!entry) {\n\t\t\treturn;\n\t\t}\n\t\tscrollIntoView(entry.el);\n\t\tlockScrollUpdates(\n\t\t\tsection,\n\t\t\tisClickScrolling,\n\t\t\tscrollEndHandlerRef,\n\t\t\tsetVisibleTitle\n\t\t);\n\t}, [categoryTitles.size]);\n\n\tconst handleSectionReference = useCallback(\n\t\t(element: ForwardedReference) => {\n\t\t\tif (!element) return;\n\t\t\tconst { parentRef, childRefs } = element;\n\n\t\t\t// Add parent section reference\n\t\t\tif (parentRef) {\n\t\t\t\tconst id = parentRef.dataset.id;\n\t\t\t\tconst title = parentRef.dataset.title;\n\t\t\t\tif (id && title) {\n\t\t\t\t\tsectionReferences.current.set(id, {\n\t\t\t\t\t\tel: parentRef,\n\t\t\t\t\t\ttitle,\n\t\t\t\t\t\tisSubSection: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add child section references\n\t\t\tif (Array.isArray(childRefs)) {\n\t\t\t\tfor (const childRef of childRefs) {\n\t\t\t\t\tif (!childRef) continue;\n\t\t\t\t\tconst id = childRef.dataset.id;\n\t\t\t\t\tconst title = childRef.dataset.title;\n\t\t\t\t\tif (id && title) {\n\t\t\t\t\t\tsectionReferences.current.set(id, {\n\t\t\t\t\t\t\tel: childRef,\n\t\t\t\t\t\t\ttitle,\n\t\t\t\t\t\t\tisSubSection: true,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdebounceUpdateCategoryTitles();\n\t\t},\n\t\t[debounceUpdateCategoryTitles]\n\t);\n\n\tconst handleClickCategoryTitle = (\n\t\terror: MouseEvent<HTMLParagraphElement>\n\t) => {\n\t\tconst id = error.currentTarget.dataset.id;\n\t\tconst index = error.currentTarget.dataset.idx;\n\t\tif (!id || !index) return;\n\n\t\tconst { el } = categoryTitles.get(id) || {};\n\t\tif (!el) return;\n\n\t\tupdateUrl(id);\n\n\t\tscrollIntoView(el);\n\n\t\tlockScrollUpdates(\n\t\t\tid,\n\t\t\tisClickScrolling,\n\t\t\tscrollEndHandlerRef,\n\t\t\tsetVisibleTitle\n\t\t);\n\t};\n\n\tconst sidebarStyle = useSpring({\n\t\topacity: showTOC ? 1 : 0,\n\t\ttransform: showTOC ? 'translateX(0)' : 'translateX(40px)',\n\t\tconfig: config.gentle,\n\t});\n\n\treturn (\n\t\t<div className={styles.blog}>\n\t\t\t{jsonLd && (\n\t\t\t\t<script\n\t\t\t\t\ttype=\"application/ld+json\"\n\t\t\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<div className={styles['blog__content']}>\n\t\t\t\t{Children.map(children, child => {\n\t\t\t\t\tif (!isValidElement(child)) return child;\n\t\t\t\t\treturn cloneElement(child, {\n\t\t\t\t\t\tref: handleSectionReference,\n\t\t\t\t\t} as RefAttributes<ForwardedReference>);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t\t<animated.div className={styles['blog__sidebar']} style={sidebarStyle}>\n\t\t\t\t<p\n\t\t\t\t\tclassName={`${styles['margin-bottom--3']} ${styles['category__header']}`}\n\t\t\t\t>\n\t\t\t\t\t{title}\n\t\t\t\t</p>\n\t\t\t\t{[...categoryTitles].map(\n\t\t\t\t\t([id, { title, isSubSection }], index, array) => {\n\t\t\t\t\t\tconst isNextSectionSubSection = array[index + 1]?.[1]?.isSubSection;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<p\n\t\t\t\t\t\t\t\tkey={id}\n\t\t\t\t\t\t\t\tdata-idx={index}\n\t\t\t\t\t\t\t\tdata-id={id}\n\t\t\t\t\t\t\t\tclassName={`${styles['category__title']} ${\n\t\t\t\t\t\t\t\t\tid === visibleTitle ? styles['category__title--active'] : ''\n\t\t\t\t\t\t\t\t} ${isSubSection ? styles['category__title--sub'] : ''} ${\n\t\t\t\t\t\t\t\t\tisSubSection && !isNextSectionSubSection\n\t\t\t\t\t\t\t\t\t\t? styles['margin-bottom-imp--2']\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t}`}\n\t\t\t\t\t\t\t\tonClick={handleClickCategoryTitle}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t\t</p>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t)}\n\t\t\t</animated.div>\n\t\t</div>\n\t);\n};\n\nexport default Blog;\n"],"names":["useRef","useState","useCallback","useEffect","lockScrollUpdates","useSpring","config","_jsxs","styles","_jsx","Children","isValidElement","cloneElement","animated"],"mappings":";;;;;;;;;;AA4CA,MAAM,IAAI,GAAG,CAAC,EACb,QAAQ,EACR,KAAK,GAAG,iBAAiB,EACzB,MAAM,GACU,KAAI;IACpB,MAAM,iBAAiB,GAAGA,YAAM,CAAmB,IAAI,GAAG,EAAE,CAAC;AAC7D,IAAA,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAGC,cAAQ,CACnD,IAAI,GAAG,EAAE,CACT;IACD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAGA,cAAQ,CAAgB,IAAI,CAAC;IACrE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAGA,cAAQ,CAAC,KAAK,CAAC;AAE7C,IAAA,MAAM,cAAc,GAAGD,YAAM,CAAuC,IAAI,CAAC;AACzE,IAAA,MAAM,eAAe,GAAGA,YAAM,CAAuC,IAAI,CAAC;AAC1E,IAAA,MAAM,gBAAgB,GAAGA,YAAM,CAAC,KAAK,CAAC;AACtC,IAAA,MAAM,mBAAmB,GAAGA,YAAM,CAAsB,IAAI,CAAC;IAC7D,MAAM,wBAAwB,GAAGA,YAAM,CACtC,IAAI,GAAG,EAAE,CACT;AAED,IAAA,MAAM,iBAAiB,GAAGE,iBAAW,CACpC,CACC,GAAG,CAAC,CAAkC,EACtC,GAAG,CAAC,CAAkC,KACnC;AACH,QAAA,MAAM,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE,CAAC;AACnD,QAAA,IAAI,QAAQ,GAAG,IAAI,CAAC,2BAA2B,EAAE;AAChD,YAAA,OAAO,EAAE,CAAC;QACX;AAAO,aAAA,IAAI,QAAQ,GAAG,IAAI,CAAC,2BAA2B,EAAE;YACvD,OAAO,CAAC,CAAC;QACV;AACA,QAAA,OAAO,CAAC;IACT,CAAC,EACD,EAAE,CACF;AAED,IAAA,MAAM,oBAAoB,GAAGA,iBAAW,CAAC,MAAK;AAC7C,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AACtB,QAAA,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA8B;;AAG/D,QAAA,MAAM,aAAa,GAAG,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;AACrE,QAAA,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC;QAErC,IAAI,cAAc,GAAkB,IAAI;AACxC,QAAA,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,IAAI,aAAa,EAAE;YAC9D,IAAI,CAAC,cAAc,EAAE;gBACpB,cAAc,GAAG,EAAE;YACpB;AACA,YAAA,iBAAiB,CAAC,GAAG,CAAC,EAAE,EAAE;gBACzB,EAAE;gBACF,KAAK;AACL,gBAAA,aAAa,EAAE,GAAG;gBAClB,YAAY;AACZ,aAAA,CAAC;QACH;AAEA,QAAA,IAAI,iBAAiB,CAAC,IAAI,KAAK,CAAC;YAAE;QAElC,iBAAiB,CAAC,iBAAiB,CAAC;AACpC,QAAA,IAAI,CAAC,OAAO;YAAE,UAAU,CAAC,IAAI,CAAC;AAE9B,QAAA,IAAI,YAAY;YAAE;QAClB,eAAe,CAAC,cAAc,CAAC;IAChC,CAAC,EAAE,CAAC,YAAY,EAAE,iBAAiB,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC;AAE1D,IAAA,MAAM,4BAA4B,GAAGA,iBAAW,CAAC,MAAK;;AAErD,QAAA,IAAI,cAAc,CAAC,OAAO,EAAE;AAC3B,YAAA,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC;QACrC;AACA,QAAA,cAAc,CAAC,OAAO,GAAG,UAAU,CAAC,MAAK;AACxC,YAAA,oBAAoB,EAAE;QACvB,CAAC,EAAE,GAAG,CAAC;AACR,IAAA,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC;IAE1BC,eAAS,CAAC,MAAK;QACd,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,cAAc,EAAE;YAC1C,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CACxC,CAAC,CAAC,KAAK,CAAC,KAAI;gBACX,IAAI,CAAC,KAAK,CAAC,cAAc;oBAAE;gBAC3B,IAAI,gBAAgB,CAAC,OAAO;oBAAE;AAC9B,gBAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC;oBAAE;gBACnC,eAAe,CAAC,SAAS,IAAG;AAC3B,oBAAA,IAAI,SAAS,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc;AAAE,wBAAA,OAAO,IAAI;oBAC1D,IAAI,KAAK,CAAC,cAAc;AAAE,wBAAA,OAAO,EAAE;AACnC,oBAAA,OAAO,SAAS;AACjB,gBAAA,CAAC,CAAC;gBACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;AACnC,gBAAA,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;AACpD,YAAA,CAAC,EACD,EAAE,SAAS,EAAE,GAAG,EAAE,CAClB;YACD,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC;AAClD,YAAA,QAAQ,CAAC,OAAO,CAAC,EAAiB,CAAC;QACpC;AACD,IAAA,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAEzBA,eAAS,CAAC,MAAK;AACd,QAAA,OAAO,MAAK;AACX,YAAA,IAAI,cAAc,CAAC,OAAO,EAAE;AAC3B,gBAAA,YAAY,CAAC,cAAc,CAAC,OAAO,CAAC;YACrC;AACA,YAAA,IAAI,eAAe,CAAC,OAAO,EAAE;AAC5B,gBAAA,YAAY,CAAC,eAAe,CAAC,OAAO,CAAC;YACtC;AACA,YAAA,IAAI,mBAAmB,CAAC,OAAO,EAAE;gBAChC,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAChC,WAAW,EACX,mBAAmB,CAAC,OAAO,CAC3B;YACF;AACA,YAAA,IAAI,wBAAwB,CAAC,OAAO,EAAE;gBACrC,KAAK,MAAM,QAAQ,IAAI,wBAAwB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE;oBACjE,QAAQ,CAAC,UAAU,EAAE;gBACtB;YACD;AACD,QAAA,CAAC;IACF,CAAC,EAAE,EAAE,CAAC;IAEN,MAAM,iBAAiB,GAAG,MAAK;QAC9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACzC,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;AAC/C,QAAA,IAAI,CAAC,OAAO;AAAE,YAAA,OAAO,IAAI;AACzB,QAAA,OAAO,OAAO;AACf,IAAA,CAAC;AAED,IAAA,MAAM,SAAS,GAAG,CAAC,EAAU,KAAI;QAChC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;AACnC,QAAA,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;AACpD,IAAA,CAAC;AAED,IAAA,MAAM,cAAc,GAAG,CAAC,OAAoB,KAAI;AAC/C,QAAA,IAAI,CAAC,OAAO;YAAE;AACd,QAAA,MAAM,GAAG,GACR,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG;AACpE,QAAA,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACpD,IAAA,CAAC;;IAGDA,eAAS,CAAC,MAAK;AACd,QAAA,IAAI,cAAc,CAAC,IAAI,KAAK,CAAC;YAAE;AAC/B,QAAA,MAAM,OAAO,GAAG,iBAAiB,EAAE;QACnC,IAAI,CAAC,OAAO,EAAE;YACb;QACD;QACA,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;QACzC,IAAI,CAAC,KAAK,EAAE;YACX;QACD;AACA,QAAA,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACxBC,yBAAiB,CAChB,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,CACf;AACF,IAAA,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AAEzB,IAAA,MAAM,sBAAsB,GAAGF,iBAAW,CACzC,CAAC,OAA2B,KAAI;AAC/B,QAAA,IAAI,CAAC,OAAO;YAAE;AACd,QAAA,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,OAAO;;QAGxC,IAAI,SAAS,EAAE;AACd,YAAA,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,EAAE;AAC/B,YAAA,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK;AACrC,YAAA,IAAI,EAAE,IAAI,KAAK,EAAE;AAChB,gBAAA,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE;AACjC,oBAAA,EAAE,EAAE,SAAS;oBACb,KAAK;AACL,oBAAA,YAAY,EAAE,KAAK;AACnB,iBAAA,CAAC;YACH;QACD;;AAGA,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;AAC7B,YAAA,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE;AACjC,gBAAA,IAAI,CAAC,QAAQ;oBAAE;AACf,gBAAA,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,EAAE;AAC9B,gBAAA,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK;AACpC,gBAAA,IAAI,EAAE,IAAI,KAAK,EAAE;AAChB,oBAAA,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE;AACjC,wBAAA,EAAE,EAAE,QAAQ;wBACZ,KAAK;AACL,wBAAA,YAAY,EAAE,IAAI;AAClB,qBAAA,CAAC;gBACH;YACD;QACD;AAEA,QAAA,4BAA4B,EAAE;AAC/B,IAAA,CAAC,EACD,CAAC,4BAA4B,CAAC,CAC9B;AAED,IAAA,MAAM,wBAAwB,GAAG,CAChC,KAAuC,KACpC;QACH,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG;AAC7C,QAAA,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK;YAAE;AAEnB,QAAA,MAAM,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;AAC3C,QAAA,IAAI,CAAC,EAAE;YAAE;QAET,SAAS,CAAC,EAAE,CAAC;QAEb,cAAc,CAAC,EAAE,CAAC;QAElBE,yBAAiB,CAChB,EAAE,EACF,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,CACf;AACF,IAAA,CAAC;IAED,MAAM,YAAY,GAAGC,aAAS,CAAC;QAC9B,OAAO,EAAE,OAAO,GAAG,CAAC,GAAG,CAAC;QACxB,SAAS,EAAE,OAAO,GAAG,eAAe,GAAG,kBAAkB;QACzD,MAAM,EAAEC,UAAM,CAAC,MAAM;AACrB,KAAA,CAAC;IAEF,QACCC,yBAAK,SAAS,EAAEC,mBAAM,CAAC,IAAI,aACzB,MAAM,KACNC,cAAA,CAAA,QAAA,EAAA,EACC,IAAI,EAAC,qBAAqB,EAC1B,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,EAAA,CAC1D,CACF,EACDA,cAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAED,mBAAM,CAAC,eAAe,CAAC,EAAA,QAAA,EACrCE,cAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,IAAG;AAC/B,oBAAA,IAAI,CAACC,oBAAc,CAAC,KAAK,CAAC;AAAE,wBAAA,OAAO,KAAK;oBACxC,OAAOC,kBAAY,CAAC,KAAK,EAAE;AAC1B,wBAAA,GAAG,EAAE,sBAAsB;AACU,qBAAA,CAAC;gBACxC,CAAC,CAAC,EAAA,CACG,EACNL,eAAA,CAACM,YAAQ,CAAC,GAAG,EAAA,EAAC,SAAS,EAAEL,mBAAM,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,YAAY,EAAA,QAAA,EAAA,CACpEC,cAAA,CAAA,GAAA,EAAA,EACC,SAAS,EAAE,CAAA,EAAGD,mBAAM,CAAC,kBAAkB,CAAC,CAAA,CAAA,EAAIA,mBAAM,CAAC,kBAAkB,CAAC,CAAA,CAAE,EAAA,QAAA,EAEvE,KAAK,EAAA,CACH,EACH,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,CACvB,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,KAAI;AAC/C,wBAAA,MAAM,uBAAuB,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY;wBACnE,QACCC,kCAEW,KAAK,EAAA,SAAA,EACN,EAAE,EACX,SAAS,EAAE,CAAA,EAAGD,mBAAM,CAAC,iBAAiB,CAAC,IACtC,EAAE,KAAK,YAAY,GAAGA,mBAAM,CAAC,yBAAyB,CAAC,GAAG,EAC3D,IAAI,YAAY,GAAGA,mBAAM,CAAC,sBAAsB,CAAC,GAAG,EAAE,CAAA,CAAA,EACrD,YAAY,IAAI,CAAC;AAChB,kCAAEA,mBAAM,CAAC,sBAAsB;AAC/B,kCAAE,EACJ,CAAA,CAAE,EACF,OAAO,EAAE,wBAAwB,EAAA,QAAA,EAEhC,KAAK,EAAA,EAZD,EAAE,CAaJ;AAEN,oBAAA,CAAC,CACD,CAAA,EAAA,CACa,CAAA,EAAA,CACV;AAER;;;;"}
|
|
1
|
+
{"version":3,"file":"BlogDynamic.js","sources":["../../../src/dynamicComponents/BlogDynamic.tsx"],"sourcesContent":["'use client';\n\nimport {\n\tChildren,\n\tcloneElement,\n\tisValidElement,\n\tuseState,\n} from 'react';\nimport { useSpring, animated, config } from '@react-spring/web';\n\nimport type { ReactNode, RefAttributes } from 'react';\nimport type { Thing, WithContext } from 'schema-dts';\n\nimport styles from '../styles/Blog.module.scss';\nimport { useCategoryTitles } from '../hooks/useCategoryTitles';\nimport { useSectionObserver } from '../hooks/useSectionObserver';\nimport type { CategoryTitleValue } from '../hooks/useCategoryTitles';\nimport TocNodeStatic from '../staticComponents/TocNodeStatic';\nimport type { TocNode } from '../staticComponents/TocNodeStatic';\n\ninterface BlogProperties {\n\tchildren: ReactNode;\n\ttitle?: string;\n\tjsonLd?: WithContext<Thing>;\n}\n\nexport interface ForwardedReference {\n\tparentRef: HTMLDivElement;\n\tchildRefs: ForwardedReference[];\n}\n\nconst buildTocTree = (entries: [string, CategoryTitleValue][]): TocNode[] => {\n\tconst roots: TocNode[] = [];\n\tconst stack: TocNode[] = [];\n\tfor (const [id, { title, depth }] of entries) {\n\t\tconst node: TocNode = { id, title, depth, children: [] };\n\t\twhile (stack.length > 0 && stack[stack.length - 1].depth >= depth) {\n\t\t\tstack.pop();\n\t\t}\n\t\tif (stack.length === 0) {\n\t\t\troots.push(node);\n\t\t} else {\n\t\t\tstack[stack.length - 1].children.push(node);\n\t\t}\n\t\tstack.push(node);\n\t}\n\treturn roots;\n};\n\nconst Blog = ({\n\tchildren,\n\ttitle = 'In this article',\n\tjsonLd,\n}: BlogProperties) => {\n\tconst [visibleTitle, setVisibleTitle] = useState<string | null>(null);\n\tconst [showTOC, setShowTOC] = useState(false);\n\n\tconst { categoryTitles, handleSectionReference } = useCategoryTitles({\n\t\tvisibleTitle,\n\t\tsetVisibleTitle,\n\t\tsetShowTOC,\n\t});\n\n\tconst { handleClickCategoryTitle } = useSectionObserver({\n\t\tcategoryTitles,\n\t\tsetVisibleTitle,\n\t});\n\n\tconst sidebarStyle = useSpring({\n\t\topacity: showTOC ? 1 : 0,\n\t\ttransform: showTOC ? 'translateX(0)' : 'translateX(40px)',\n\t\tconfig: config.gentle,\n\t});\n\n\treturn (\n\t\t<div className={styles.blog}>\n\t\t\t{jsonLd && (\n\t\t\t\t<script\n\t\t\t\t\ttype=\"application/ld+json\"\n\t\t\t\t\tdangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}\n\t\t\t\t/>\n\t\t\t)}\n\t\t\t<div className={styles['blog__content']}>\n\t\t\t\t{Children.map(children, child => {\n\t\t\t\t\tif (!isValidElement(child)) return child;\n\t\t\t\t\treturn cloneElement(child, {\n\t\t\t\t\t\tref: handleSectionReference,\n\t\t\t\t\t} as RefAttributes<ForwardedReference>);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t\t<animated.div className={styles['blog__sidebar']} style={sidebarStyle}>\n\t\t\t\t<p\n\t\t\t\t\tclassName={`${styles['margin-bottom--3']} ${styles['category__header']}`}\n\t\t\t\t>\n\t\t\t\t\t{title}\n\t\t\t\t</p>\n\t\t\t\t{buildTocTree([...categoryTitles]).map((node, i) => (\n\t\t\t\t\t<TocNodeStatic\n\t\t\t\t\t\tkey={node.id}\n\t\t\t\t\t\tnode={node}\n\t\t\t\t\t\tindex={i}\n\t\t\t\t\t\tvisibleTitle={visibleTitle}\n\t\t\t\t\t\tonClick={handleClickCategoryTitle}\n\t\t\t\t\t/>\n\t\t\t\t))}\n\t\t\t</animated.div>\n\t\t</div>\n\t);\n};\n\nexport default Blog;\n"],"names":["useState","useCategoryTitles","useSectionObserver","useSpring","config","_jsxs","styles","_jsx","Children","isValidElement","cloneElement","animated","TocNodeStatic"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ var react = require('react');
|
|
|
8
8
|
var BlogSection_module = require('../styles/BlogSection.module.scss.js');
|
|
9
9
|
var index = require('../utils/index.js');
|
|
10
10
|
|
|
11
|
-
const BlogSection = react.forwardRef(({ title = '', category = '', children = null
|
|
11
|
+
const BlogSection = react.forwardRef(({ title = '', category = '', children = null }, forwardedReference) => {
|
|
12
12
|
const titleWithCategory = category ? `${category} - ${title}` : title;
|
|
13
13
|
const id = index.generateIdForBlogTitle(titleWithCategory);
|
|
14
14
|
const parentReference = react.useRef(null);
|
|
@@ -22,22 +22,30 @@ const BlogSection = react.forwardRef(({ title = '', category = '', children = nu
|
|
|
22
22
|
imperativeHandleRef.current = handle;
|
|
23
23
|
return handle;
|
|
24
24
|
});
|
|
25
|
+
// Re-register when title or category changes so the TOC reflects the updated heading
|
|
26
|
+
react.useEffect(() => {
|
|
27
|
+
if (typeof forwardedReference === 'function' &&
|
|
28
|
+
imperativeHandleRef.current) {
|
|
29
|
+
forwardedReference(imperativeHandleRef.current);
|
|
30
|
+
}
|
|
31
|
+
}, [title, category]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
25
32
|
const handleChildReferences = (element) => {
|
|
26
33
|
if (!element)
|
|
27
34
|
return;
|
|
28
35
|
const { parentRef: subParentReference } = element;
|
|
29
36
|
if (!subParentReference)
|
|
30
37
|
return;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (
|
|
38
|
+
// Avoid registering the same child section twice
|
|
39
|
+
const alreadyRegistered = childReferences.current.some(ref => ref.parentRef === subParentReference);
|
|
40
|
+
if (!alreadyRegistered) {
|
|
41
|
+
childReferences.current.push(element);
|
|
42
|
+
}
|
|
43
|
+
if (typeof forwardedReference === 'function' &&
|
|
44
|
+
imperativeHandleRef.current) {
|
|
34
45
|
forwardedReference(imperativeHandleRef.current);
|
|
35
46
|
}
|
|
36
47
|
};
|
|
37
|
-
return (jsxRuntime.jsxs("div", { className: `${BlogSection_module.default['blog-
|
|
38
|
-
${increaseMarginBottom
|
|
39
|
-
? BlogSection_module.default['margin-bottom--9']
|
|
40
|
-
: BlogSection_module.default['margin-bottom--6']}`, "data-title": title, "data-id": id, ref: parentReference, children: [title ? (jsxRuntime.jsx("h4", { className: BlogSection_module.default['blog-section__title'], children: jsxRuntime.jsx("a", { href: index.generateSectionHref(id), className: BlogSection_module.default['blog-section__title-link'], onClick: e => e.preventDefault(), children: title }) })) : null, react.Children.map(children, child => {
|
|
48
|
+
return (jsxRuntime.jsxs("div", { className: BlogSection_module.default['blog-section'], "data-title": title, "data-id": id, ref: parentReference, children: [jsxRuntime.jsx("h3", { className: `${BlogSection_module.default['blog-section__title']} ${title ? '' : BlogSection_module.default['blog-section__title--empty']}`, children: title ? (jsxRuntime.jsx("a", { href: index.generateSectionHref(id), className: BlogSection_module.default['blog-section__title-link'], onClick: e => e.preventDefault(), children: title })) : (jsxRuntime.jsx("p", { children: "No title" })) }), react.Children.map(children, child => {
|
|
41
49
|
if (!react.isValidElement(child))
|
|
42
50
|
return child;
|
|
43
51
|
return react.cloneElement(child, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BlogSectionDynamic.js","sources":["../../../src/dynamicComponents/BlogSectionDynamic.tsx"],"sourcesContent":["'use client';\n\nimport {\n\tChildren,\n\tcloneElement,\n\tforwardRef,\n\tisValidElement,\n\tuseImperativeHandle,\n\tuseRef,\n} from 'react';\n\nimport type { ReactNode, RefAttributes } from 'react';\n\nimport styles from '../styles/BlogSection.module.scss';\n\nimport type { ForwardedReference } from './BlogDynamic';\nimport { generateIdForBlogTitle, generateSectionHref } from '../utils';\n\ninterface BlogProperties {\n\ttitle?: string;\n\tcategory?: string;\n\tchildren?: ReactNode;\n
|
|
1
|
+
{"version":3,"file":"BlogSectionDynamic.js","sources":["../../../src/dynamicComponents/BlogSectionDynamic.tsx"],"sourcesContent":["'use client';\n\nimport {\n\tChildren,\n\tcloneElement,\n\tforwardRef,\n\tisValidElement,\n\tuseEffect,\n\tuseImperativeHandle,\n\tuseRef,\n} from 'react';\n\nimport type { ReactNode, RefAttributes } from 'react';\n\nimport styles from '../styles/BlogSection.module.scss';\n\nimport type { ForwardedReference } from './BlogDynamic';\nimport { generateIdForBlogTitle, generateSectionHref } from '../utils';\n\ninterface BlogProperties {\n\ttitle?: string;\n\tcategory?: string;\n\tchildren?: ReactNode;\n}\n\nconst BlogSection = forwardRef<ForwardedReference, BlogProperties>(\n\t(\n\t\t{ title = '', category = '', children = null }: BlogProperties,\n\t\tforwardedReference\n\t) => {\n\t\tconst titleWithCategory = category ? `${category} - ${title}` : title;\n\t\tconst id = generateIdForBlogTitle(titleWithCategory);\n\n\t\tconst parentReference = useRef<ForwardedReference['parentRef']>(null);\n\t\tconst childReferences = useRef<ForwardedReference['childRefs']>([]);\n\t\tconst imperativeHandleRef = useRef<ForwardedReference | null>(null);\n\n\t\tuseImperativeHandle(forwardedReference, () => {\n\t\t\tconst handle = {\n\t\t\t\tparentRef: parentReference.current!,\n\t\t\t\tchildRefs: childReferences.current!,\n\t\t\t};\n\t\t\timperativeHandleRef.current = handle;\n\t\t\treturn handle;\n\t\t});\n\n\t\t// Re-register when title or category changes so the TOC reflects the updated heading\n\t\tuseEffect(() => {\n\t\t\tif (\n\t\t\t\ttypeof forwardedReference === 'function' &&\n\t\t\t\timperativeHandleRef.current\n\t\t\t) {\n\t\t\t\tforwardedReference(imperativeHandleRef.current);\n\t\t\t}\n\t\t}, [title, category]); // eslint-disable-line react-hooks/exhaustive-deps\n\n\t\tconst handleChildReferences = (element: ForwardedReference | null) => {\n\t\t\tif (!element) return;\n\t\t\tconst { parentRef: subParentReference } = element;\n\t\t\tif (!subParentReference) return;\n\n\t\t\t// Avoid registering the same child section twice\n\t\t\tconst alreadyRegistered = childReferences.current.some(\n\t\t\t\tref => ref.parentRef === subParentReference\n\t\t\t);\n\t\t\tif (!alreadyRegistered) {\n\t\t\t\tchildReferences.current.push(element);\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\ttypeof forwardedReference === 'function' &&\n\t\t\t\timperativeHandleRef.current\n\t\t\t) {\n\t\t\t\tforwardedReference(imperativeHandleRef.current);\n\t\t\t}\n\t\t};\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\tclassName={styles['blog-section']}\n\t\t\t\tdata-title={title}\n\t\t\t\tdata-id={id}\n\t\t\t\tref={parentReference}\n\t\t\t>\n\t\t\t\t<h3\n\t\t\t\t\tclassName={`${styles['blog-section__title']} ${title ? '' : styles['blog-section__title--empty']}`}\n\t\t\t\t>\n\t\t\t\t\t{title ? (\n\t\t\t\t\t\t<a\n\t\t\t\t\t\t\thref={generateSectionHref(id)}\n\t\t\t\t\t\t\tclassName={styles['blog-section__title-link']}\n\t\t\t\t\t\t\tonClick={e => e.preventDefault()}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<p>No title</p>\n\t\t\t\t\t)}\n\t\t\t\t</h3>\n\t\t\t\t{Children.map(children, child => {\n\t\t\t\t\tif (!isValidElement(child)) return child;\n\t\t\t\t\treturn cloneElement(child, {\n\t\t\t\t\t\tref: handleChildReferences,\n\t\t\t\t\t} as RefAttributes<ForwardedReference>);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t);\n\t}\n);\n\nBlogSection.displayName = 'BlogSection';\n\nexport default BlogSection;\n"],"names":["forwardRef","generateIdForBlogTitle","useRef","useImperativeHandle","useEffect","_jsxs","styles","_jsx","generateSectionHref","Children","isValidElement","cloneElement"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
function useCategoryTitles({ visibleTitle, setVisibleTitle, setShowTOC, }) {
|
|
7
|
+
const sectionReferences = react.useRef(new Map());
|
|
8
|
+
const [categoryTitles, setCategoryTitles] = react.useState(new Map());
|
|
9
|
+
const updateTimerRef = react.useRef(null);
|
|
10
|
+
const sortByDomPosition = react.useCallback(([, a], [, b]) => {
|
|
11
|
+
const position = a.el.compareDocumentPosition(b.el);
|
|
12
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
13
|
+
return -1;
|
|
14
|
+
}
|
|
15
|
+
else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}, []);
|
|
20
|
+
const updateCategoryTitles = react.useCallback(() => {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
const newCategoryTitles = new Map();
|
|
23
|
+
const sectionsArray = Array.from(sectionReferences.current.entries());
|
|
24
|
+
sectionsArray.sort(sortByDomPosition);
|
|
25
|
+
let firstSectionId = null;
|
|
26
|
+
for (const [id, { title, el, depth }] of sectionsArray) {
|
|
27
|
+
if (!firstSectionId) {
|
|
28
|
+
firstSectionId = id;
|
|
29
|
+
}
|
|
30
|
+
newCategoryTitles.set(id, {
|
|
31
|
+
el,
|
|
32
|
+
title,
|
|
33
|
+
lastUpdatedAt: now,
|
|
34
|
+
depth,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (newCategoryTitles.size === 0)
|
|
38
|
+
return;
|
|
39
|
+
setCategoryTitles(newCategoryTitles);
|
|
40
|
+
setShowTOC(true);
|
|
41
|
+
if (visibleTitle)
|
|
42
|
+
return;
|
|
43
|
+
setVisibleTitle(firstSectionId);
|
|
44
|
+
}, [visibleTitle, sortByDomPosition, setShowTOC, setVisibleTitle]);
|
|
45
|
+
const debounceUpdateCategoryTitles = react.useCallback(() => {
|
|
46
|
+
if (updateTimerRef.current) {
|
|
47
|
+
clearTimeout(updateTimerRef.current);
|
|
48
|
+
}
|
|
49
|
+
updateTimerRef.current = setTimeout(() => {
|
|
50
|
+
updateCategoryTitles();
|
|
51
|
+
}, 200);
|
|
52
|
+
}, [updateCategoryTitles]);
|
|
53
|
+
const removeStaleRefs = (ref) => {
|
|
54
|
+
for (const [existingId, { el }] of sectionReferences.current) {
|
|
55
|
+
if (el !== ref) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
sectionReferences.current.delete(existingId);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const handleCategoryTitle = (ref) => {
|
|
63
|
+
const id = ref.dataset.id;
|
|
64
|
+
const title = ref.dataset.title;
|
|
65
|
+
if (!id || !title)
|
|
66
|
+
return;
|
|
67
|
+
let depth = 0;
|
|
68
|
+
let parent = ref.parentElement;
|
|
69
|
+
while (parent) {
|
|
70
|
+
if (parent.hasAttribute('data-id'))
|
|
71
|
+
depth++;
|
|
72
|
+
parent = parent.parentElement;
|
|
73
|
+
}
|
|
74
|
+
removeStaleRefs(ref);
|
|
75
|
+
sectionReferences.current.set(id, { el: ref, title, depth });
|
|
76
|
+
};
|
|
77
|
+
const processSection = (element) => {
|
|
78
|
+
const { parentRef, childRefs } = element;
|
|
79
|
+
if (parentRef)
|
|
80
|
+
handleCategoryTitle(parentRef);
|
|
81
|
+
if (Array.isArray(childRefs)) {
|
|
82
|
+
for (const childRef of childRefs) {
|
|
83
|
+
processSection(childRef);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const handleSectionReference = react.useCallback((element) => {
|
|
88
|
+
if (!element)
|
|
89
|
+
return;
|
|
90
|
+
processSection(element);
|
|
91
|
+
debounceUpdateCategoryTitles();
|
|
92
|
+
}, [debounceUpdateCategoryTitles]);
|
|
93
|
+
react.useEffect(() => {
|
|
94
|
+
return () => {
|
|
95
|
+
if (updateTimerRef.current) {
|
|
96
|
+
clearTimeout(updateTimerRef.current);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
return { categoryTitles, handleSectionReference };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
exports.useCategoryTitles = useCategoryTitles;
|
|
104
|
+
//# sourceMappingURL=useCategoryTitles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCategoryTitles.js","sources":["../../../src/hooks/useCategoryTitles.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { Dispatch, SetStateAction } from 'react';\n\nimport type { ForwardedReference } from '../dynamicComponents/BlogDynamic';\n\nexport interface SectionReferenceValue {\n\tel: HTMLElement;\n\ttitle: string;\n\tdepth: number;\n}\n\nexport interface CategoryTitleValue extends SectionReferenceValue {\n\tlastUpdatedAt: number;\n}\n\nexport type CategoryTitle = Map<string, CategoryTitleValue>;\n\ntype SectionReference = Map<string, SectionReferenceValue>;\n\ninterface Options {\n\tvisibleTitle: string | null;\n\tsetVisibleTitle: Dispatch<SetStateAction<string | null>>;\n\tsetShowTOC: Dispatch<SetStateAction<boolean>>;\n}\n\nexport function useCategoryTitles({\n\tvisibleTitle,\n\tsetVisibleTitle,\n\tsetShowTOC,\n}: Options) {\n\tconst sectionReferences = useRef<SectionReference>(new Map());\n\tconst [categoryTitles, setCategoryTitles] = useState<CategoryTitle>(\n\t\tnew Map()\n\t);\n\tconst updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n\tconst sortByDomPosition = useCallback(\n\t\t(\n\t\t\t[, a]: [string, SectionReferenceValue],\n\t\t\t[, b]: [string, SectionReferenceValue]\n\t\t) => {\n\t\t\tconst position = a.el.compareDocumentPosition(b.el);\n\t\t\tif (position & Node.DOCUMENT_POSITION_FOLLOWING) {\n\t\t\t\treturn -1;\n\t\t\t} else if (position & Node.DOCUMENT_POSITION_PRECEDING) {\n\t\t\t\treturn 1;\n\t\t\t}\n\t\t\treturn 0;\n\t\t},\n\t\t[]\n\t);\n\n\tconst updateCategoryTitles = useCallback(() => {\n\t\tconst now = Date.now();\n\t\tconst newCategoryTitles = new Map<string, CategoryTitleValue>();\n\n\t\tconst sectionsArray = Array.from(sectionReferences.current.entries());\n\t\tsectionsArray.sort(sortByDomPosition);\n\n\t\tlet firstSectionId: string | null = null;\n\t\tfor (const [id, { title, el, depth }] of sectionsArray) {\n\t\t\tif (!firstSectionId) {\n\t\t\t\tfirstSectionId = id;\n\t\t\t}\n\t\t\tnewCategoryTitles.set(id, {\n\t\t\t\tel,\n\t\t\t\ttitle,\n\t\t\t\tlastUpdatedAt: now,\n\t\t\t\tdepth,\n\t\t\t});\n\t\t}\n\n\t\tif (newCategoryTitles.size === 0) return;\n\n\t\tsetCategoryTitles(newCategoryTitles);\n\t\tsetShowTOC(true);\n\n\t\tif (visibleTitle) return;\n\t\tsetVisibleTitle(firstSectionId);\n\t}, [visibleTitle, sortByDomPosition, setShowTOC, setVisibleTitle]);\n\n\tconst debounceUpdateCategoryTitles = useCallback(() => {\n\t\tif (updateTimerRef.current) {\n\t\t\tclearTimeout(updateTimerRef.current);\n\t\t}\n\t\tupdateTimerRef.current = setTimeout(() => {\n\t\t\tupdateCategoryTitles();\n\t\t}, 200);\n\t}, [updateCategoryTitles]);\n\n\tconst removeStaleRefs = (ref: HTMLDivElement) => {\n\t\tfor (const [existingId, { el }] of sectionReferences.current) {\n\t\t\tif (el !== ref) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tsectionReferences.current.delete(existingId);\n\t\t\tbreak;\n\t\t}\n\t};\n\n\tconst handleCategoryTitle = (ref: HTMLDivElement) => {\n\t\tconst id = ref.dataset.id;\n\t\tconst title = ref.dataset.title;\n\t\tif (!id || !title) return;\n\n\t\tlet depth = 0;\n\t\tlet parent = ref.parentElement;\n\t\twhile (parent) {\n\t\t\tif (parent.hasAttribute('data-id')) depth++;\n\t\t\tparent = parent.parentElement;\n\t\t}\n\n\t\tremoveStaleRefs(ref);\n\t\tsectionReferences.current.set(id, { el: ref, title, depth });\n\t};\n\n\tconst processSection = (element: ForwardedReference) => {\n\t\tconst { parentRef, childRefs } = element;\n\t\tif (parentRef) handleCategoryTitle(parentRef);\n\t\tif (Array.isArray(childRefs)) {\n\t\t\tfor (const childRef of childRefs) {\n\t\t\t\tprocessSection(childRef);\n\t\t\t}\n\t\t}\n\t};\n\n\tconst handleSectionReference = useCallback(\n\t\t(element: ForwardedReference) => {\n\t\t\tif (!element) return;\n\t\t\tprocessSection(element);\n\t\t\tdebounceUpdateCategoryTitles();\n\t\t},\n\t\t[debounceUpdateCategoryTitles]\n\t);\n\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (updateTimerRef.current) {\n\t\t\t\tclearTimeout(updateTimerRef.current);\n\t\t\t}\n\t\t};\n\t}, []);\n\n\treturn { categoryTitles, handleSectionReference };\n}\n"],"names":["useRef","useState","useCallback","useEffect"],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var lockScrollUpdates = require('../utils/lockScrollUpdates.js');
|
|
6
|
+
|
|
7
|
+
const getSectionFromUrl = () => {
|
|
8
|
+
const url = new URL(window.location.href);
|
|
9
|
+
return url.searchParams.get('section');
|
|
10
|
+
};
|
|
11
|
+
const updateUrl = (id) => {
|
|
12
|
+
const url = new URL(window.location.href);
|
|
13
|
+
url.searchParams.set('section', id);
|
|
14
|
+
window.history.replaceState({}, '', url.toString());
|
|
15
|
+
};
|
|
16
|
+
const scrollIntoView = (element) => {
|
|
17
|
+
if (!element)
|
|
18
|
+
return;
|
|
19
|
+
const top = element.getBoundingClientRect().top + document.body.scrollTop - 100;
|
|
20
|
+
document.body.scrollTo({ top, behavior: 'smooth' });
|
|
21
|
+
};
|
|
22
|
+
function useSectionObserver({ categoryTitles, setVisibleTitle }) {
|
|
23
|
+
const isClickScrolling = react.useRef(false);
|
|
24
|
+
const scrollEndHandlerRef = react.useRef(null);
|
|
25
|
+
const intersectionObserversRef = react.useRef(new Map());
|
|
26
|
+
// Set up IntersectionObservers whenever the set of sections changes
|
|
27
|
+
react.useEffect(() => {
|
|
28
|
+
for (const [id, { el }] of categoryTitles) {
|
|
29
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
30
|
+
if (!entry.isIntersecting)
|
|
31
|
+
return;
|
|
32
|
+
if (isClickScrolling.current)
|
|
33
|
+
return;
|
|
34
|
+
if (document.body.scrollTop === 0)
|
|
35
|
+
return;
|
|
36
|
+
setVisibleTitle(visibleId => {
|
|
37
|
+
if (visibleId === id && !entry.isIntersecting)
|
|
38
|
+
return null;
|
|
39
|
+
if (entry.isIntersecting)
|
|
40
|
+
return id;
|
|
41
|
+
return visibleId;
|
|
42
|
+
});
|
|
43
|
+
updateUrl(id);
|
|
44
|
+
}, { threshold: 0.1 });
|
|
45
|
+
intersectionObserversRef.current.set(id, observer);
|
|
46
|
+
observer.observe(el);
|
|
47
|
+
}
|
|
48
|
+
}, [categoryTitles.size, setVisibleTitle]);
|
|
49
|
+
// On initial load, scroll to section specified in URL
|
|
50
|
+
react.useEffect(() => {
|
|
51
|
+
if (categoryTitles.size === 0)
|
|
52
|
+
return;
|
|
53
|
+
const section = getSectionFromUrl();
|
|
54
|
+
if (!section)
|
|
55
|
+
return;
|
|
56
|
+
const entry = categoryTitles.get(section);
|
|
57
|
+
if (!entry)
|
|
58
|
+
return;
|
|
59
|
+
scrollIntoView(entry.el);
|
|
60
|
+
lockScrollUpdates.default(section, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
61
|
+
}, [categoryTitles.size, setVisibleTitle]);
|
|
62
|
+
// Cleanup observers on unmount
|
|
63
|
+
react.useEffect(() => {
|
|
64
|
+
return () => {
|
|
65
|
+
if (scrollEndHandlerRef.current) {
|
|
66
|
+
document.body.removeEventListener('scrollend', scrollEndHandlerRef.current);
|
|
67
|
+
}
|
|
68
|
+
for (const observer of intersectionObserversRef.current.values()) {
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
const handleClickCategoryTitle = react.useCallback((event) => {
|
|
74
|
+
const id = event.currentTarget.dataset.id;
|
|
75
|
+
const index = event.currentTarget.dataset.idx;
|
|
76
|
+
if (!id || !index)
|
|
77
|
+
return;
|
|
78
|
+
const { el } = categoryTitles.get(id) || {};
|
|
79
|
+
if (!el)
|
|
80
|
+
return;
|
|
81
|
+
updateUrl(id);
|
|
82
|
+
scrollIntoView(el);
|
|
83
|
+
lockScrollUpdates.default(id, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);
|
|
84
|
+
}, [categoryTitles, setVisibleTitle]);
|
|
85
|
+
return { handleClickCategoryTitle };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.useSectionObserver = useSectionObserver;
|
|
89
|
+
//# sourceMappingURL=useSectionObserver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSectionObserver.js","sources":["../../../src/hooks/useSectionObserver.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef } from 'react';\n\nimport type { Dispatch, MouseEvent, SetStateAction } from 'react';\n\nimport lockScrollUpdates from '../utils/lockScrollUpdates';\nimport type { CategoryTitle } from './useCategoryTitles';\n\nconst getSectionFromUrl = () => {\n\tconst url = new URL(window.location.href);\n\treturn url.searchParams.get('section');\n};\n\nconst updateUrl = (id: string) => {\n\tconst url = new URL(window.location.href);\n\turl.searchParams.set('section', id);\n\twindow.history.replaceState({}, '', url.toString());\n};\n\nconst scrollIntoView = (element: HTMLElement) => {\n\tif (!element) return;\n\tconst top =\n\t\telement.getBoundingClientRect().top + document.body.scrollTop - 100;\n\tdocument.body.scrollTo({ top, behavior: 'smooth' });\n};\n\ninterface Options {\n\tcategoryTitles: CategoryTitle;\n\tsetVisibleTitle: Dispatch<SetStateAction<string | null>>;\n}\n\nexport function useSectionObserver({ categoryTitles, setVisibleTitle }: Options) {\n\tconst isClickScrolling = useRef(false);\n\tconst scrollEndHandlerRef = useRef<(() => void) | null>(null);\n\tconst intersectionObserversRef = useRef<Map<string, IntersectionObserver>>(\n\t\tnew Map()\n\t);\n\n\t// Set up IntersectionObservers whenever the set of sections changes\n\tuseEffect(() => {\n\t\tfor (const [id, { el }] of categoryTitles) {\n\t\t\tconst observer = new IntersectionObserver(\n\t\t\t\t([entry]) => {\n\t\t\t\t\tif (!entry.isIntersecting) return;\n\t\t\t\t\tif (isClickScrolling.current) return;\n\t\t\t\t\tif (document.body.scrollTop === 0) return;\n\t\t\t\t\tsetVisibleTitle(visibleId => {\n\t\t\t\t\t\tif (visibleId === id && !entry.isIntersecting) return null;\n\t\t\t\t\t\tif (entry.isIntersecting) return id;\n\t\t\t\t\t\treturn visibleId;\n\t\t\t\t\t});\n\t\t\t\t\tupdateUrl(id);\n\t\t\t\t},\n\t\t\t\t{ threshold: 0.1 }\n\t\t\t);\n\t\t\tintersectionObserversRef.current.set(id, observer);\n\t\t\tobserver.observe(el as HTMLElement);\n\t\t}\n\t}, [categoryTitles.size, setVisibleTitle]);\n\n\t// On initial load, scroll to section specified in URL\n\tuseEffect(() => {\n\t\tif (categoryTitles.size === 0) return;\n\t\tconst section = getSectionFromUrl();\n\t\tif (!section) return;\n\t\tconst entry = categoryTitles.get(section);\n\t\tif (!entry) return;\n\t\tscrollIntoView(entry.el);\n\t\tlockScrollUpdates(section, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);\n\t}, [categoryTitles.size, setVisibleTitle]);\n\n\t// Cleanup observers on unmount\n\tuseEffect(() => {\n\t\treturn () => {\n\t\t\tif (scrollEndHandlerRef.current) {\n\t\t\t\tdocument.body.removeEventListener('scrollend', scrollEndHandlerRef.current);\n\t\t\t}\n\t\t\tfor (const observer of intersectionObserversRef.current.values()) {\n\t\t\t\tobserver.disconnect();\n\t\t\t}\n\t\t};\n\t}, []);\n\n\tconst handleClickCategoryTitle = useCallback(\n\t\t(event: MouseEvent<HTMLParagraphElement>) => {\n\t\t\tconst id = event.currentTarget.dataset.id;\n\t\t\tconst index = event.currentTarget.dataset.idx;\n\t\t\tif (!id || !index) return;\n\n\t\t\tconst { el } = categoryTitles.get(id) || {};\n\t\t\tif (!el) return;\n\n\t\t\tupdateUrl(id);\n\t\t\tscrollIntoView(el);\n\t\t\tlockScrollUpdates(id, isClickScrolling, scrollEndHandlerRef, setVisibleTitle);\n\t\t},\n\t\t[categoryTitles, setVisibleTitle]\n\t);\n\n\treturn { handleClickCategoryTitle };\n}\n"],"names":["useRef","useEffect","lockScrollUpdates","useCallback"],"mappings":";;;;;AASA,MAAM,iBAAiB,GAAG,MAAK;IAC9B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IACzC,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC;AACvC,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,EAAU,KAAI;IAChC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;IACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;AACnC,IAAA,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;AACpD,CAAC;AAED,MAAM,cAAc,GAAG,CAAC,OAAoB,KAAI;AAC/C,IAAA,IAAI,CAAC,OAAO;QAAE;AACd,IAAA,MAAM,GAAG,GACR,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG;AACpE,IAAA,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACpD,CAAC;SAOe,kBAAkB,CAAC,EAAE,cAAc,EAAE,eAAe,EAAW,EAAA;AAC9E,IAAA,MAAM,gBAAgB,GAAGA,YAAM,CAAC,KAAK,CAAC;AACtC,IAAA,MAAM,mBAAmB,GAAGA,YAAM,CAAsB,IAAI,CAAC;IAC7D,MAAM,wBAAwB,GAAGA,YAAM,CACtC,IAAI,GAAG,EAAE,CACT;;IAGDC,eAAS,CAAC,MAAK;QACd,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,cAAc,EAAE;YAC1C,MAAM,QAAQ,GAAG,IAAI,oBAAoB,CACxC,CAAC,CAAC,KAAK,CAAC,KAAI;gBACX,IAAI,CAAC,KAAK,CAAC,cAAc;oBAAE;gBAC3B,IAAI,gBAAgB,CAAC,OAAO;oBAAE;AAC9B,gBAAA,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,KAAK,CAAC;oBAAE;gBACnC,eAAe,CAAC,SAAS,IAAG;AAC3B,oBAAA,IAAI,SAAS,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc;AAAE,wBAAA,OAAO,IAAI;oBAC1D,IAAI,KAAK,CAAC,cAAc;AAAE,wBAAA,OAAO,EAAE;AACnC,oBAAA,OAAO,SAAS;AACjB,gBAAA,CAAC,CAAC;gBACF,SAAS,CAAC,EAAE,CAAC;AACd,YAAA,CAAC,EACD,EAAE,SAAS,EAAE,GAAG,EAAE,CAClB;YACD,wBAAwB,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,CAAC;AAClD,YAAA,QAAQ,CAAC,OAAO,CAAC,EAAiB,CAAC;QACpC;IACD,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;;IAG1CA,eAAS,CAAC,MAAK;AACd,QAAA,IAAI,cAAc,CAAC,IAAI,KAAK,CAAC;YAAE;AAC/B,QAAA,MAAM,OAAO,GAAG,iBAAiB,EAAE;AACnC,QAAA,IAAI,CAAC,OAAO;YAAE;QACd,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC;AACzC,QAAA,IAAI,CAAC,KAAK;YAAE;AACZ,QAAA,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QACxBC,yBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,eAAe,CAAC;IACnF,CAAC,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;;IAG1CD,eAAS,CAAC,MAAK;AACd,QAAA,OAAO,MAAK;AACX,YAAA,IAAI,mBAAmB,CAAC,OAAO,EAAE;gBAChC,QAAQ,CAAC,IAAI,CAAC,mBAAmB,CAAC,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC;YAC5E;YACA,KAAK,MAAM,QAAQ,IAAI,wBAAwB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE;gBACjE,QAAQ,CAAC,UAAU,EAAE;YACtB;AACD,QAAA,CAAC;IACF,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,wBAAwB,GAAGE,iBAAW,CAC3C,CAAC,KAAuC,KAAI;QAC3C,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;QACzC,MAAM,KAAK,GAAG,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG;AAC7C,QAAA,IAAI,CAAC,EAAE,IAAI,CAAC,KAAK;YAAE;AAEnB,QAAA,MAAM,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE;AAC3C,QAAA,IAAI,CAAC,EAAE;YAAE;QAET,SAAS,CAAC,EAAE,CAAC;QACb,cAAc,CAAC,EAAE,CAAC;QAClBD,yBAAiB,CAAC,EAAE,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,eAAe,CAAC;AAC9E,IAAA,CAAC,EACD,CAAC,cAAc,EAAE,eAAe,CAAC,CACjC;IAED,OAAO,EAAE,wBAAwB,EAAE;AACpC;;;;"}
|