@san-siva/blogkit 1.0.12 → 1.1.1
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 +64 -13
- package/dist/cjs/components/Blog.js +3 -118
- package/dist/cjs/components/Blog.js.map +1 -1
- package/dist/cjs/components/BlogHeader.js +4 -4
- package/dist/cjs/components/BlogHeader.js.map +1 -1
- package/dist/cjs/components/BlogLink.js +4 -23
- package/dist/cjs/components/BlogLink.js.map +1 -1
- package/dist/cjs/components/BlogSection.js +4 -29
- package/dist/cjs/components/BlogSection.js.map +1 -1
- package/dist/cjs/components/Callout.js +3 -4
- package/dist/cjs/components/Callout.js.map +1 -1
- package/dist/cjs/components/CodeBlock.js +3 -25
- package/dist/cjs/components/CodeBlock.js.map +1 -1
- package/dist/cjs/components/Mermaid.js +3 -46
- package/dist/cjs/components/Mermaid.js.map +1 -1
- package/dist/cjs/components/Table.js +2 -7
- package/dist/cjs/components/Table.js.map +1 -1
- package/dist/cjs/dynamicComponents/BlogDynamic.js +178 -0
- package/dist/cjs/dynamicComponents/BlogDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/BlogHeaderDynamic.js +13 -0
- package/dist/cjs/dynamicComponents/BlogHeaderDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/BlogLinkDynamic.js +35 -0
- package/dist/cjs/dynamicComponents/BlogLinkDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js +50 -0
- package/dist/cjs/dynamicComponents/BlogSectionDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/CalloutDynamic.js +14 -0
- package/dist/cjs/dynamicComponents/CalloutDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/CodeBlockDynamic.js +38 -0
- package/dist/cjs/dynamicComponents/CodeBlockDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/MermaidDynamic.js +59 -0
- package/dist/cjs/dynamicComponents/MermaidDynamic.js.map +1 -0
- package/dist/cjs/dynamicComponents/TableDynamic.js +18 -0
- package/dist/cjs/dynamicComponents/TableDynamic.js.map +1 -0
- package/dist/cjs/index.css +2 -0
- package/dist/cjs/index.css.map +1 -0
- package/dist/cjs/staticComponents/BlogLinkStatic.js +17 -0
- package/dist/cjs/staticComponents/BlogLinkStatic.js.map +1 -0
- package/dist/cjs/staticComponents/BlogSectionStatic.js +19 -0
- package/dist/cjs/staticComponents/BlogSectionStatic.js.map +1 -0
- package/dist/cjs/staticComponents/BlogStatic.js +13 -0
- package/dist/cjs/staticComponents/BlogStatic.js.map +1 -0
- package/dist/cjs/staticComponents/CodeBlockStatic.js +13 -0
- package/dist/cjs/staticComponents/CodeBlockStatic.js.map +1 -0
- package/dist/cjs/staticComponents/MermaidStatic.js +13 -0
- package/dist/cjs/staticComponents/MermaidStatic.js.map +1 -0
- package/dist/cjs/styles/Blog.module.scss.js +0 -4
- package/dist/cjs/styles/Blog.module.scss.js.map +1 -1
- package/dist/cjs/styles/BlogHeader.module.scss.js +0 -4
- package/dist/cjs/styles/BlogHeader.module.scss.js.map +1 -1
- package/dist/cjs/styles/BlogLink.module.scss.js +0 -4
- package/dist/cjs/styles/BlogLink.module.scss.js.map +1 -1
- package/dist/cjs/styles/BlogSection.module.scss.js +0 -4
- package/dist/cjs/styles/BlogSection.module.scss.js.map +1 -1
- package/dist/cjs/styles/Callout.module.scss.js +0 -4
- package/dist/cjs/styles/Callout.module.scss.js.map +1 -1
- package/dist/cjs/styles/CodeBlock.module.scss.js +1 -5
- package/dist/cjs/styles/CodeBlock.module.scss.js.map +1 -1
- package/dist/cjs/styles/Mermaid.module.scss.js +0 -4
- package/dist/cjs/styles/Mermaid.module.scss.js.map +1 -1
- package/dist/cjs/styles/Table.module.scss.js +0 -4
- package/dist/cjs/styles/Table.module.scss.js.map +1 -1
- package/dist/esm/components/Blog.js +5 -120
- package/dist/esm/components/Blog.js.map +1 -1
- package/dist/esm/components/BlogHeader.js +5 -5
- package/dist/esm/components/BlogHeader.js.map +1 -1
- package/dist/esm/components/BlogLink.js +6 -25
- package/dist/esm/components/BlogLink.js.map +1 -1
- package/dist/esm/components/BlogSection.js +6 -31
- package/dist/esm/components/BlogSection.js.map +1 -1
- package/dist/esm/components/Callout.js +4 -5
- package/dist/esm/components/Callout.js.map +1 -1
- package/dist/esm/components/CodeBlock.js +5 -27
- package/dist/esm/components/CodeBlock.js.map +1 -1
- package/dist/esm/components/Mermaid.js +5 -48
- package/dist/esm/components/Mermaid.js.map +1 -1
- package/dist/esm/components/Table.js +3 -8
- package/dist/esm/components/Table.js.map +1 -1
- package/dist/esm/dynamicComponents/BlogDynamic.js +174 -0
- package/dist/esm/dynamicComponents/BlogDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/BlogHeaderDynamic.js +9 -0
- package/dist/esm/dynamicComponents/BlogHeaderDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/BlogLinkDynamic.js +31 -0
- package/dist/esm/dynamicComponents/BlogLinkDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js +46 -0
- package/dist/esm/dynamicComponents/BlogSectionDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/CalloutDynamic.js +10 -0
- package/dist/esm/dynamicComponents/CalloutDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/CodeBlockDynamic.js +34 -0
- package/dist/esm/dynamicComponents/CodeBlockDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/MermaidDynamic.js +55 -0
- package/dist/esm/dynamicComponents/MermaidDynamic.js.map +1 -0
- package/dist/esm/dynamicComponents/TableDynamic.js +14 -0
- package/dist/esm/dynamicComponents/TableDynamic.js.map +1 -0
- package/dist/esm/index.css +2 -0
- package/dist/esm/index.css.map +1 -0
- package/dist/esm/staticComponents/BlogLinkStatic.js +13 -0
- package/dist/esm/staticComponents/BlogLinkStatic.js.map +1 -0
- package/dist/esm/staticComponents/BlogSectionStatic.js +15 -0
- package/dist/esm/staticComponents/BlogSectionStatic.js.map +1 -0
- package/dist/esm/staticComponents/BlogStatic.js +9 -0
- package/dist/esm/staticComponents/BlogStatic.js.map +1 -0
- package/dist/esm/staticComponents/CodeBlockStatic.js +9 -0
- package/dist/esm/staticComponents/CodeBlockStatic.js.map +1 -0
- package/dist/esm/staticComponents/MermaidStatic.js +9 -0
- package/dist/esm/staticComponents/MermaidStatic.js.map +1 -0
- package/dist/esm/styles/Blog.module.scss.js +0 -4
- package/dist/esm/styles/Blog.module.scss.js.map +1 -1
- package/dist/esm/styles/BlogHeader.module.scss.js +0 -4
- package/dist/esm/styles/BlogHeader.module.scss.js.map +1 -1
- package/dist/esm/styles/BlogLink.module.scss.js +0 -4
- package/dist/esm/styles/BlogLink.module.scss.js.map +1 -1
- package/dist/esm/styles/BlogSection.module.scss.js +0 -4
- package/dist/esm/styles/BlogSection.module.scss.js.map +1 -1
- package/dist/esm/styles/Callout.module.scss.js +0 -4
- package/dist/esm/styles/Callout.module.scss.js.map +1 -1
- package/dist/esm/styles/CodeBlock.module.scss.js +1 -5
- package/dist/esm/styles/CodeBlock.module.scss.js.map +1 -1
- package/dist/esm/styles/Mermaid.module.scss.js +0 -4
- package/dist/esm/styles/Mermaid.module.scss.js.map +1 -1
- package/dist/esm/styles/Table.module.scss.js +0 -4
- package/dist/esm/styles/Table.module.scss.js.map +1 -1
- package/dist/types/components/Blog.d.ts +0 -4
- package/dist/types/components/Blog.d.ts.map +1 -1
- package/dist/types/components/BlogHeader.d.ts +2 -2
- package/dist/types/components/BlogHeader.d.ts.map +1 -1
- package/dist/types/components/BlogLink.d.ts +2 -2
- package/dist/types/components/BlogLink.d.ts.map +1 -1
- package/dist/types/components/BlogSection.d.ts +5 -4
- package/dist/types/components/BlogSection.d.ts.map +1 -1
- package/dist/types/components/Callout.d.ts +1 -1
- package/dist/types/components/Callout.d.ts.map +1 -1
- package/dist/types/components/CodeBlock.d.ts +2 -3
- package/dist/types/components/CodeBlock.d.ts.map +1 -1
- package/dist/types/components/Mermaid.d.ts.map +1 -1
- package/dist/types/components/Table.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +0 -1
- package/dist/types/components/index.d.ts.map +1 -1
- package/dist/types/dynamicComponents/BlogDynamic.d.ts +12 -0
- package/dist/types/dynamicComponents/BlogDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/BlogHeaderDynamic.d.ts +8 -0
- package/dist/types/dynamicComponents/BlogHeaderDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/BlogLinkDynamic.d.ts +9 -0
- package/dist/types/dynamicComponents/BlogLinkDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts +11 -0
- package/dist/types/dynamicComponents/BlogSectionDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/CalloutDynamic.d.ts +10 -0
- package/dist/types/dynamicComponents/CalloutDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/CodeBlockDynamic.d.ts +10 -0
- package/dist/types/dynamicComponents/CodeBlockDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/MermaidDynamic.d.ts +9 -0
- package/dist/types/dynamicComponents/MermaidDynamic.d.ts.map +1 -0
- package/dist/types/dynamicComponents/TableDynamic.d.ts +10 -0
- package/dist/types/dynamicComponents/TableDynamic.d.ts.map +1 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/staticComponents/BlogLinkStatic.d.ts +9 -0
- package/dist/types/staticComponents/BlogLinkStatic.d.ts.map +1 -0
- package/dist/types/staticComponents/BlogSectionStatic.d.ts +10 -0
- package/dist/types/staticComponents/BlogSectionStatic.d.ts.map +1 -0
- package/dist/types/staticComponents/BlogStatic.d.ts +7 -0
- package/dist/types/staticComponents/BlogStatic.d.ts.map +1 -0
- package/dist/types/staticComponents/CodeBlockStatic.d.ts +9 -0
- package/dist/types/staticComponents/CodeBlockStatic.d.ts.map +1 -0
- package/dist/types/staticComponents/MermaidStatic.d.ts +9 -0
- package/dist/types/staticComponents/MermaidStatic.d.ts.map +1 -0
- package/package.json +8 -2
- package/src/components/Blog.tsx +7 -195
- package/src/components/BlogHeader.tsx +10 -23
- package/src/components/BlogLink.tsx +28 -58
- package/src/components/BlogSection.tsx +32 -57
- package/src/components/Callout.tsx +12 -11
- package/src/components/CodeBlock.tsx +23 -55
- package/src/components/Mermaid.tsx +21 -55
- package/src/components/Table.tsx +7 -34
- package/src/components/index.ts +0 -2
- package/src/dynamicComponents/BlogDynamic.tsx +264 -0
- package/src/dynamicComponents/BlogHeaderDynamic.tsx +32 -0
- package/src/dynamicComponents/BlogLinkDynamic.tsx +74 -0
- package/src/dynamicComponents/BlogSectionDynamic.tsx +90 -0
- package/src/dynamicComponents/CalloutDynamic.tsx +27 -0
- package/src/dynamicComponents/CodeBlockDynamic.tsx +76 -0
- package/src/dynamicComponents/MermaidDynamic.tsx +78 -0
- package/src/dynamicComponents/TableDynamic.tsx +54 -0
- package/src/index.ts +0 -3
- package/src/staticComponents/BlogLinkStatic.tsx +45 -0
- package/src/staticComponents/BlogSectionStatic.tsx +40 -0
- package/src/staticComponents/BlogStatic.tsx +16 -0
- package/src/staticComponents/CodeBlockStatic.tsx +34 -0
- package/src/staticComponents/MermaidStatic.tsx +26 -0
- package/src/styles/Blog.module.scss +19 -7
- package/src/styles/CodeBlock.module.scss +46 -41
- package/src/styles/CodeBlock.module.scss.d.ts +1 -0
- package/dist/cjs/node_modules/style-inject/dist/style-inject.es.js +0 -33
- package/dist/cjs/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
- package/dist/esm/node_modules/style-inject/dist/style-inject.es.js +0 -29
- package/dist/esm/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { lazy, Suspense } from 'react';
|
|
4
|
+
import MermaidStatic from '../staticComponents/MermaidStatic';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const MermaidDynamic = lazy(
|
|
7
|
+
() => import('../dynamicComponents/MermaidDynamic')
|
|
8
|
+
);
|
|
8
9
|
|
|
9
10
|
interface MermaidProperties {
|
|
10
11
|
code: string;
|
|
@@ -13,65 +14,30 @@ interface MermaidProperties {
|
|
|
13
14
|
hasMarginDown?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
mermaid.initialize({
|
|
17
|
-
startOnLoad: false,
|
|
18
|
-
theme: 'default',
|
|
19
|
-
timeline: {
|
|
20
|
-
useMaxWidth: true,
|
|
21
|
-
diagramMarginX: 0,
|
|
22
|
-
},
|
|
23
|
-
flowchart: {
|
|
24
|
-
useMaxWidth: true,
|
|
25
|
-
diagramPadding: 0,
|
|
26
|
-
},
|
|
27
|
-
sequence: {
|
|
28
|
-
useMaxWidth: true,
|
|
29
|
-
diagramMarginX: 0,
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
|
|
33
17
|
const Mermaid = ({
|
|
34
18
|
code = '',
|
|
35
19
|
id = '',
|
|
36
20
|
hasMarginUp = false,
|
|
37
21
|
hasMarginDown = false,
|
|
38
22
|
}: MermaidProperties) => {
|
|
39
|
-
const [enabled, setEnabled] = useState(false);
|
|
40
|
-
const mermaidReference = useRef<HTMLDivElement>(null);
|
|
41
|
-
|
|
42
|
-
const initializeMermaid = useCallback(async () => {
|
|
43
|
-
try {
|
|
44
|
-
if (!mermaidReference.current || !code) return;
|
|
45
|
-
const { svg, bindFunctions } = await mermaid.render(
|
|
46
|
-
`mermaid-diagram-${id}`,
|
|
47
|
-
code
|
|
48
|
-
);
|
|
49
|
-
if (!svg) return;
|
|
50
|
-
mermaidReference.current.innerHTML = svg || '';
|
|
51
|
-
bindFunctions?.(mermaidReference.current);
|
|
52
|
-
setEnabled(true);
|
|
53
|
-
} catch (error) {
|
|
54
|
-
console.error('Failed to render Mermaid diagram:', error);
|
|
55
|
-
}
|
|
56
|
-
}, [code, id]);
|
|
57
|
-
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (!code || !mermaidReference.current) return;
|
|
60
|
-
const timer = setTimeout(async () => {
|
|
61
|
-
await initializeMermaid();
|
|
62
|
-
}, 100);
|
|
63
|
-
return () => clearTimeout(timer);
|
|
64
|
-
}, [code, initializeMermaid]);
|
|
65
|
-
|
|
66
23
|
return (
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
24
|
+
<Suspense
|
|
25
|
+
fallback={
|
|
26
|
+
<MermaidStatic
|
|
27
|
+
code={code}
|
|
28
|
+
id={id}
|
|
29
|
+
hasMarginUp={hasMarginUp}
|
|
30
|
+
hasMarginDown={hasMarginDown}
|
|
31
|
+
/>
|
|
32
|
+
}
|
|
71
33
|
>
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
34
|
+
<MermaidDynamic
|
|
35
|
+
code={code}
|
|
36
|
+
id={id}
|
|
37
|
+
hasMarginUp={hasMarginUp}
|
|
38
|
+
hasMarginDown={hasMarginDown}
|
|
39
|
+
/>
|
|
40
|
+
</Suspense>
|
|
75
41
|
);
|
|
76
42
|
};
|
|
77
43
|
|
package/src/components/Table.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import
|
|
2
|
+
import TableDynamic from '../dynamicComponents/TableDynamic';
|
|
3
3
|
|
|
4
4
|
interface TableProperties {
|
|
5
5
|
rows?: ReactNode[][];
|
|
@@ -14,40 +14,13 @@ const Table = ({
|
|
|
14
14
|
hasMarginUp = false,
|
|
15
15
|
hasMarginDown = false,
|
|
16
16
|
}: TableProperties) => {
|
|
17
|
-
const columnCount = headers.length;
|
|
18
|
-
|
|
19
17
|
return (
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}}
|
|
27
|
-
>
|
|
28
|
-
<div className={`${styles['table__header']}`}>
|
|
29
|
-
{headers.map((header, index) => (
|
|
30
|
-
<div
|
|
31
|
-
key={typeof header === 'string' ? header : index}
|
|
32
|
-
className={`${styles['table__header__cell']}`}
|
|
33
|
-
>
|
|
34
|
-
{header}
|
|
35
|
-
</div>
|
|
36
|
-
))}
|
|
37
|
-
</div>
|
|
38
|
-
{rows.map((row, index) => (
|
|
39
|
-
<div key={index} className={`${styles['table__row']}`}>
|
|
40
|
-
{row.map((cell, cellIndex) => (
|
|
41
|
-
<div
|
|
42
|
-
key={typeof cell === 'string' ? cell : cellIndex}
|
|
43
|
-
className={`${styles['table__row__cell']}`}
|
|
44
|
-
>
|
|
45
|
-
{cell}
|
|
46
|
-
</div>
|
|
47
|
-
))}
|
|
48
|
-
</div>
|
|
49
|
-
))}
|
|
50
|
-
</div>
|
|
18
|
+
<TableDynamic
|
|
19
|
+
rows={rows}
|
|
20
|
+
headers={headers}
|
|
21
|
+
hasMarginUp={hasMarginUp}
|
|
22
|
+
hasMarginDown={hasMarginDown}
|
|
23
|
+
/>
|
|
51
24
|
);
|
|
52
25
|
};
|
|
53
26
|
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Children,
|
|
5
|
+
cloneElement,
|
|
6
|
+
isValidElement,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import { useSpring, animated } from '@react-spring/web';
|
|
13
|
+
|
|
14
|
+
import type { MouseEvent, ReactNode, RefAttributes } from 'react';
|
|
15
|
+
|
|
16
|
+
import styles from '../styles/Blog.module.scss';
|
|
17
|
+
|
|
18
|
+
interface BlogProperties {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
title?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ForwardedReference {
|
|
24
|
+
parentRef: HTMLDivElement;
|
|
25
|
+
childRefs: HTMLDivElement[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SectionReferenceValue {
|
|
29
|
+
el: HTMLElement;
|
|
30
|
+
title: string;
|
|
31
|
+
isSubSection: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type SectionReference = Map<string, SectionReferenceValue>;
|
|
35
|
+
|
|
36
|
+
interface CategoryTitleValue extends SectionReferenceValue {
|
|
37
|
+
lastUpdatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type CategoryTitle = Map<string, CategoryTitleValue>;
|
|
41
|
+
|
|
42
|
+
const Blog = ({ children, title = 'In this article' }: BlogProperties) => {
|
|
43
|
+
const sectionReferences = useRef<SectionReference>(new Map());
|
|
44
|
+
const [categoryTitles, setCategoryTitles] = useState<CategoryTitle>(
|
|
45
|
+
new Map()
|
|
46
|
+
);
|
|
47
|
+
const [visibleTitle, setVisibleTitle] = useState<string | null>(null);
|
|
48
|
+
const [showTOC, setShowTOC] = useState(false);
|
|
49
|
+
|
|
50
|
+
const updateTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
51
|
+
const showTOCTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
52
|
+
|
|
53
|
+
const sortByDomPosition = useCallback(
|
|
54
|
+
(
|
|
55
|
+
[, a]: [string, SectionReferenceValue],
|
|
56
|
+
[, b]: [string, SectionReferenceValue]
|
|
57
|
+
) => {
|
|
58
|
+
const position = a.el.compareDocumentPosition(b.el);
|
|
59
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
60
|
+
return -1; // a comes before b
|
|
61
|
+
} else if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
62
|
+
return 1; // b comes before a
|
|
63
|
+
}
|
|
64
|
+
return 0;
|
|
65
|
+
},
|
|
66
|
+
[]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const debounceShowTOC = useCallback(() => {
|
|
70
|
+
if (showTOC) return;
|
|
71
|
+
if (showTOCTimerRef.current) {
|
|
72
|
+
clearTimeout(showTOCTimerRef.current);
|
|
73
|
+
}
|
|
74
|
+
showTOCTimerRef.current = setTimeout(() => {
|
|
75
|
+
setShowTOC(true);
|
|
76
|
+
}, 200);
|
|
77
|
+
}, [showTOC]);
|
|
78
|
+
|
|
79
|
+
const updateCategoryTitles = useCallback(() => {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
const newCategoryTitles = new Map<string, CategoryTitleValue>();
|
|
82
|
+
|
|
83
|
+
// Sort sections by their DOM position to maintain correct order
|
|
84
|
+
const sectionsArray = Array.from(sectionReferences.current.entries());
|
|
85
|
+
sectionsArray.sort(sortByDomPosition);
|
|
86
|
+
|
|
87
|
+
let firstSectionId: string | null = null;
|
|
88
|
+
for (const [id, { title, el, isSubSection }] of sectionsArray) {
|
|
89
|
+
if (!firstSectionId) {
|
|
90
|
+
firstSectionId = id;
|
|
91
|
+
}
|
|
92
|
+
newCategoryTitles.set(id, {
|
|
93
|
+
el,
|
|
94
|
+
title,
|
|
95
|
+
lastUpdatedAt: now,
|
|
96
|
+
isSubSection,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (newCategoryTitles.size === 0) return;
|
|
101
|
+
|
|
102
|
+
setCategoryTitles(newCategoryTitles);
|
|
103
|
+
debounceShowTOC();
|
|
104
|
+
|
|
105
|
+
if (visibleTitle) return;
|
|
106
|
+
setVisibleTitle(firstSectionId);
|
|
107
|
+
}, [visibleTitle, sortByDomPosition]);
|
|
108
|
+
|
|
109
|
+
const debounceUpdateCategoryTitles = useCallback(() => {
|
|
110
|
+
// Clear existing timer and set a new one to batch updates
|
|
111
|
+
if (updateTimerRef.current) {
|
|
112
|
+
clearTimeout(updateTimerRef.current);
|
|
113
|
+
}
|
|
114
|
+
updateTimerRef.current = setTimeout(() => {
|
|
115
|
+
updateCategoryTitles();
|
|
116
|
+
}, 50);
|
|
117
|
+
}, [updateCategoryTitles]);
|
|
118
|
+
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
const observers = new Map<string, IntersectionObserver>();
|
|
121
|
+
for (const [id, { el }] of categoryTitles) {
|
|
122
|
+
const observer = new IntersectionObserver(
|
|
123
|
+
([entry]) => {
|
|
124
|
+
if (!entry.isIntersecting) return;
|
|
125
|
+
setVisibleTitle(visibleId => {
|
|
126
|
+
if (visibleId === id && !entry.isIntersecting) return null;
|
|
127
|
+
if (entry.isIntersecting) return id;
|
|
128
|
+
return visibleId;
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
{ threshold: 0.1 }
|
|
132
|
+
);
|
|
133
|
+
observers.set(id, observer);
|
|
134
|
+
observer.observe(el);
|
|
135
|
+
}
|
|
136
|
+
return () => {
|
|
137
|
+
for (const observer of observers.values()) {
|
|
138
|
+
observer.disconnect();
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}, [categoryTitles.size]);
|
|
142
|
+
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
return () => {
|
|
145
|
+
if (updateTimerRef.current) {
|
|
146
|
+
clearTimeout(updateTimerRef.current);
|
|
147
|
+
}
|
|
148
|
+
if (showTOCTimerRef.current) {
|
|
149
|
+
clearTimeout(showTOCTimerRef.current);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}, []);
|
|
153
|
+
|
|
154
|
+
const handleSectionReference = useCallback(
|
|
155
|
+
(element: ForwardedReference) => {
|
|
156
|
+
if (!element) return;
|
|
157
|
+
const { parentRef, childRefs } = element;
|
|
158
|
+
|
|
159
|
+
// Add parent section reference
|
|
160
|
+
if (parentRef) {
|
|
161
|
+
const id = parentRef.dataset.id;
|
|
162
|
+
const title = parentRef.dataset.title;
|
|
163
|
+
if (id && title) {
|
|
164
|
+
sectionReferences.current.set(id, {
|
|
165
|
+
el: parentRef,
|
|
166
|
+
title,
|
|
167
|
+
isSubSection: false,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add child section references
|
|
173
|
+
if (Array.isArray(childRefs)) {
|
|
174
|
+
for (const childRef of childRefs) {
|
|
175
|
+
if (!childRef) continue;
|
|
176
|
+
const id = childRef.dataset.id;
|
|
177
|
+
const title = childRef.dataset.title;
|
|
178
|
+
if (id && title) {
|
|
179
|
+
sectionReferences.current.set(id, {
|
|
180
|
+
el: childRef,
|
|
181
|
+
title,
|
|
182
|
+
isSubSection: true,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
debounceUpdateCategoryTitles();
|
|
189
|
+
},
|
|
190
|
+
[debounceUpdateCategoryTitles]
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const handleClickCategoryTitle = (
|
|
194
|
+
error: MouseEvent<HTMLParagraphElement>
|
|
195
|
+
) => {
|
|
196
|
+
const id = error.currentTarget.dataset.id;
|
|
197
|
+
const index = error.currentTarget.dataset.idx;
|
|
198
|
+
if (!id || !index) return;
|
|
199
|
+
|
|
200
|
+
const { el } = categoryTitles.get(id) || {};
|
|
201
|
+
if (!el) return;
|
|
202
|
+
|
|
203
|
+
const top = el.getBoundingClientRect().top + document.body.scrollTop - 100;
|
|
204
|
+
document.body.scrollTo({
|
|
205
|
+
top,
|
|
206
|
+
behavior: 'smooth',
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
setVisibleTitle(id);
|
|
211
|
+
clearTimeout(timer);
|
|
212
|
+
}, 1000);
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const sidebarStyle = useSpring({
|
|
216
|
+
opacity: showTOC ? 1 : 0,
|
|
217
|
+
transform: showTOC ? 'translateX(0)' : 'translateX(80px)',
|
|
218
|
+
config: { tension: 280, friction: 60 },
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div className={styles.blog}>
|
|
223
|
+
<div className={styles['blog__content']}>
|
|
224
|
+
{Children.map(children, child => {
|
|
225
|
+
if (!isValidElement(child)) return child;
|
|
226
|
+
return cloneElement(child, {
|
|
227
|
+
ref: handleSectionReference,
|
|
228
|
+
} as RefAttributes<ForwardedReference>);
|
|
229
|
+
})}
|
|
230
|
+
</div>
|
|
231
|
+
<animated.div className={styles['blog__sidebar']} style={sidebarStyle}>
|
|
232
|
+
<p
|
|
233
|
+
className={`${styles['margin-bottom--3']} ${styles['category__header']}`}
|
|
234
|
+
>
|
|
235
|
+
{title}
|
|
236
|
+
</p>
|
|
237
|
+
{[...categoryTitles].map(
|
|
238
|
+
([id, { title, isSubSection }], index, array) => {
|
|
239
|
+
const isNextSectionSubSection = array[index + 1]?.[1]?.isSubSection;
|
|
240
|
+
return (
|
|
241
|
+
<p
|
|
242
|
+
key={id}
|
|
243
|
+
data-idx={index}
|
|
244
|
+
data-id={id}
|
|
245
|
+
className={`${styles['category__title']} ${
|
|
246
|
+
id === visibleTitle ? styles['category__title--active'] : ''
|
|
247
|
+
} ${isSubSection ? styles['category__title--sub'] : ''} ${
|
|
248
|
+
isSubSection && !isNextSectionSubSection
|
|
249
|
+
? styles['margin-bottom-imp--2']
|
|
250
|
+
: ''
|
|
251
|
+
}`}
|
|
252
|
+
onClick={handleClickCategoryTitle}
|
|
253
|
+
>
|
|
254
|
+
{title}
|
|
255
|
+
</p>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
)}
|
|
259
|
+
</animated.div>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export default Blog;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Fragment } from 'react';
|
|
2
|
+
|
|
3
|
+
import styles from '../styles/BlogHeader.module.scss';
|
|
4
|
+
|
|
5
|
+
interface BlogProperties {
|
|
6
|
+
title: string[];
|
|
7
|
+
desc: string[];
|
|
8
|
+
isDescCite?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const renderLineBreaks = (array: string[]) =>
|
|
12
|
+
array.map((element, index, array) => (
|
|
13
|
+
<Fragment key={element}>
|
|
14
|
+
{element}
|
|
15
|
+
{index === array.length - 1 ? null : <br />}
|
|
16
|
+
</Fragment>
|
|
17
|
+
));
|
|
18
|
+
|
|
19
|
+
const BlogHeader = ({ title, desc, isDescCite = true }: BlogProperties) => (
|
|
20
|
+
<>
|
|
21
|
+
<h1 className={`${styles['blog-header']}`}>{renderLineBreaks(title)}</h1>
|
|
22
|
+
{isDescCite ? (
|
|
23
|
+
<cite className={`${styles['blog-date']}`}>
|
|
24
|
+
{renderLineBreaks(desc)}
|
|
25
|
+
</cite>
|
|
26
|
+
) : (
|
|
27
|
+
<p className={`${styles['blog-date']}`}>{renderLineBreaks(desc)}</p>
|
|
28
|
+
)}
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
export default BlogHeader;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
import { animated, useSpring } from '@react-spring/web';
|
|
6
|
+
|
|
7
|
+
import styles from '../styles/BlogLink.module.scss';
|
|
8
|
+
|
|
9
|
+
import { generateUrlForBlogTitle } from '../utils';
|
|
10
|
+
|
|
11
|
+
interface Properties {
|
|
12
|
+
title: string;
|
|
13
|
+
desc?: string;
|
|
14
|
+
isInProgress?: boolean;
|
|
15
|
+
href?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DEFAULT_LINE_END = 18;
|
|
19
|
+
const DEFAULT_POLYLINE_POINTS = '12 5, 19 12, 12 19';
|
|
20
|
+
const MOVED_POLYLINE_POINTS = '15 5, 22 12, 15 19';
|
|
21
|
+
|
|
22
|
+
const BlogLink = ({ title = '', desc = '', isInProgress = false, href }: Properties) => {
|
|
23
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
24
|
+
|
|
25
|
+
const link = href || `/blog/${generateUrlForBlogTitle(title)}`;
|
|
26
|
+
|
|
27
|
+
const svgColor = useSpring({
|
|
28
|
+
stroke: isHovered ? '#4242fa' : 'transparent',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const polyLine = useSpring({
|
|
32
|
+
points: isHovered ? MOVED_POLYLINE_POINTS : DEFAULT_POLYLINE_POINTS,
|
|
33
|
+
config: { duration: 200 },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const lineEnd = useSpring({
|
|
37
|
+
x2: isHovered ? `${DEFAULT_LINE_END + 2}` : `${DEFAULT_LINE_END}`,
|
|
38
|
+
config: { duration: 200 },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (isInProgress) return null;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<a
|
|
45
|
+
className={styles['blog-link']}
|
|
46
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
47
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
48
|
+
href={link}
|
|
49
|
+
rel="noopener noreferrer"
|
|
50
|
+
>
|
|
51
|
+
<h6 className={styles['blog-link__title']}>{title}</h6>
|
|
52
|
+
<p className={styles['blog-link__description']}>{desc}</p>
|
|
53
|
+
<div className={styles['blog-link__read-more']}>
|
|
54
|
+
<p>Read More</p>
|
|
55
|
+
<animated.svg
|
|
56
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
57
|
+
width="18px"
|
|
58
|
+
height="18px"
|
|
59
|
+
viewBox="0 0 24 24"
|
|
60
|
+
fill="none"
|
|
61
|
+
strokeWidth="2"
|
|
62
|
+
strokeLinecap="round"
|
|
63
|
+
strokeLinejoin="round"
|
|
64
|
+
style={svgColor as any}
|
|
65
|
+
>
|
|
66
|
+
<animated.line x1="5" y1="12" y2="12" x2={lineEnd.x2 as any}></animated.line>
|
|
67
|
+
<animated.polyline points={polyLine.points as any} />
|
|
68
|
+
</animated.svg>
|
|
69
|
+
</div>
|
|
70
|
+
</a>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default BlogLink;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
forwardRef,
|
|
5
|
+
isValidElement,
|
|
6
|
+
useImperativeHandle,
|
|
7
|
+
useRef,
|
|
8
|
+
} from 'react';
|
|
9
|
+
|
|
10
|
+
import type { ReactNode, RefAttributes } from 'react';
|
|
11
|
+
|
|
12
|
+
import styles from '../styles/BlogSection.module.scss';
|
|
13
|
+
|
|
14
|
+
import type { ForwardedReference } from './BlogDynamic';
|
|
15
|
+
import { generateIdForBlogTitle } from '../utils';
|
|
16
|
+
|
|
17
|
+
interface BlogProperties {
|
|
18
|
+
title?: string;
|
|
19
|
+
category?: string;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
increaseMarginBottom?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const BlogSection = forwardRef<ForwardedReference, BlogProperties>(
|
|
25
|
+
(
|
|
26
|
+
{
|
|
27
|
+
title = '',
|
|
28
|
+
category = '',
|
|
29
|
+
children = null,
|
|
30
|
+
increaseMarginBottom = false,
|
|
31
|
+
}: BlogProperties,
|
|
32
|
+
forwardedReference
|
|
33
|
+
) => {
|
|
34
|
+
const titleWithCategory = category ? `${category} - ${title}` : title;
|
|
35
|
+
const id = generateIdForBlogTitle(titleWithCategory);
|
|
36
|
+
|
|
37
|
+
const parentReference = useRef<ForwardedReference['parentRef']>(null);
|
|
38
|
+
const childReferences = useRef<ForwardedReference['childRefs']>([]);
|
|
39
|
+
const imperativeHandleRef = useRef<ForwardedReference | null>(null);
|
|
40
|
+
|
|
41
|
+
useImperativeHandle(forwardedReference, () => {
|
|
42
|
+
const handle = {
|
|
43
|
+
parentRef: parentReference.current!,
|
|
44
|
+
childRefs: childReferences.current!,
|
|
45
|
+
};
|
|
46
|
+
imperativeHandleRef.current = handle;
|
|
47
|
+
return handle;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const handleChildReferences = (element: ForwardedReference | null) => {
|
|
51
|
+
if (!element) return;
|
|
52
|
+
const { parentRef: subParentReference } = element;
|
|
53
|
+
if (!subParentReference) return;
|
|
54
|
+
childReferences.current.push(subParentReference);
|
|
55
|
+
|
|
56
|
+
// Re-trigger parent ref callback with updated children
|
|
57
|
+
if (typeof forwardedReference === 'function' && imperativeHandleRef.current) {
|
|
58
|
+
forwardedReference(imperativeHandleRef.current);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div
|
|
64
|
+
className={`${styles['blog-section']}
|
|
65
|
+
${
|
|
66
|
+
increaseMarginBottom
|
|
67
|
+
? styles['margin-bottom--9']
|
|
68
|
+
: styles['margin-bottom--6']
|
|
69
|
+
}`}
|
|
70
|
+
data-title={title}
|
|
71
|
+
data-id={id}
|
|
72
|
+
ref={parentReference}
|
|
73
|
+
>
|
|
74
|
+
{title ? (
|
|
75
|
+
<h4 className={styles['blog-section__title']}>{title}</h4>
|
|
76
|
+
) : null}
|
|
77
|
+
{Children.map(children, child => {
|
|
78
|
+
if (!isValidElement(child)) return child;
|
|
79
|
+
return cloneElement(child, {
|
|
80
|
+
ref: handleChildReferences,
|
|
81
|
+
} as RefAttributes<ForwardedReference>);
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
BlogSection.displayName = 'BlogSection';
|
|
89
|
+
|
|
90
|
+
export default BlogSection;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import styles from '../styles/Callout.module.scss';
|
|
4
|
+
|
|
5
|
+
interface CalloutProperties {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
type: 'info' | 'warning' | 'error' | 'success';
|
|
8
|
+
hasMarginUp?: boolean;
|
|
9
|
+
hasMarginDown?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Callout = ({ children, type = 'info',
|
|
13
|
+
hasMarginUp = false,
|
|
14
|
+
hasMarginDown = false
|
|
15
|
+
}: CalloutProperties) => {
|
|
16
|
+
const className = `${styles.callout} ${styles[`callout--${type}`]} ${
|
|
17
|
+
hasMarginUp ? styles['margin-top--1'] : ''
|
|
18
|
+
} ${hasMarginDown ? styles['margin-bottom--2'] : ''}`;
|
|
19
|
+
return (
|
|
20
|
+
<div className={className}>
|
|
21
|
+
<div className={styles.callout__icon}/>
|
|
22
|
+
<div className={styles.callout__wrapper}>{children}</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default Callout;
|