Skip to content

Commit 80e5d06

Browse files
committed
many changes
1 parent 0f194f8 commit 80e5d06

25 files changed

+1050
-308
lines changed

astro.config.mjs

-10
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,5 @@ export default defineConfig({
3939
wrap: true
4040
}
4141
},
42-
server: {
43-
headers: {
44-
'Content-Security-Policy':
45-
"default-src 'self'; " +
46-
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://challenges.cloudflare.com; " +
47-
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; " +
48-
"font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com; " +
49-
"frame-src https://challenges.cloudflare.com;"
50-
}
51-
}
5242
});
5343

package-lock.json

+315-79
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,18 @@
99
"astro": "astro"
1010
},
1111
"dependencies": {
12-
"@astrojs/markdown-remark": "^6.0.2",
13-
"@astrojs/mdx": "^4.0.7",
12+
"@astrojs/markdown-remark": "^6.1.0",
13+
"@astrojs/mdx": "^4.0.8",
1414
"@astrojs/rss": "^4.0.11",
15-
"@astrojs/tailwind": "^5.1.5",
15+
"@astrojs/tailwind": "^6.0.0",
1616
"@fontsource/ibm-plex-serif": "^5.1.1",
1717
"@tailwindcss/typography": "^0.5.16",
1818
"apexcharts": "^4.4.0",
19-
"astro": "^5.1.9",
19+
"astro": "^5.2.5",
2020
"katex": "^0.16.21",
2121
"markdown-it": "^14.1.0",
2222
"mermaid": "^11.4.1",
23+
"recharts": "^2.15.1",
2324
"rehype-katex": "^7.0.1",
2425
"rehype-pretty-code": "^0.14.0",
2526
"remark-math": "^6.0.0",

src/components/Chart.astro

-103
This file was deleted.

src/components/TableOfContents.astro

+136-43
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,52 @@ interface Props {
55
slug: string;
66
text: string;
77
}[];
8+
maxDepth?: number;
9+
className?: string;
810
}
911
10-
const { headings } = Astro.props;
12+
const {
13+
headings,
14+
maxDepth = 3,
15+
className = ''
16+
} = Astro.props;
1117
12-
const filteredHeadings = headings.filter(heading => heading.depth <= 3);
18+
const filteredHeadings = headings.filter(heading => heading.depth <= maxDepth);
19+
20+
const depthClasses = {
21+
1: 'pl-0',
22+
2: 'pl-4',
23+
3: 'pl-8',
24+
4: 'pl-12'
25+
};
1326
---
1427

15-
<nav class="toc hidden lg:block" aria-label="Table of Contents">
16-
<div class="sticky top-8 max-h-[calc(100vh-4rem)] overflow-y-auto">
17-
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">Table of Contents</h2>
28+
<nav
29+
class:list={[
30+
'toc hidden lg:block',
31+
className
32+
]}
33+
aria-label="Table of Contents"
34+
>
35+
<div class="sticky top-8 bottom-16 max-h-[calc(100vh-8rem)] overflow-y-auto pb-8 pr-2">
36+
<h2 class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-4">
37+
Table of Contents
38+
</h2>
1839
<ul class="space-y-2 text-sm">
1940
{filteredHeadings.map(heading => (
20-
<li class={`pl-${(heading.depth - 1) * 4}`}>
21-
<a
41+
<li class={depthClasses[heading.depth]}>
42+
<a
2243
href={`#${heading.slug}`}
23-
class={`
24-
block py-1 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200
25-
${heading.depth === 1 ? 'font-medium' : 'font-normal'}
26-
`}
44+
class:list={[
45+
'toc-item block py-1 transition-colors duration-150',
46+
'text-gray-600 dark:text-gray-400',
47+
'hover:text-gray-900 dark:hover:text-gray-200',
48+
{
49+
'font-medium': heading.depth === 1,
50+
'font-normal': heading.depth > 1
51+
}
52+
]}
53+
data-heading={heading.slug}
2754
>
2855
{heading.text}
2956
</a>
@@ -35,50 +62,116 @@ const filteredHeadings = headings.filter(heading => heading.depth <= 3);
3562

3663
<style>
3764
.toc {
38-
width: 16rem;
39-
position: fixed;
40-
left: max(2rem, calc((100vw - 60ch - 28rem) / 2));
41-
transform: translateX(-100%);
42-
padding-right: 2rem;
65+
width: clamp(14rem, 16rem, 20vw);
66+
position: fixed;
67+
left: max(2rem, calc((100vw - 60ch - 28rem) / 2));
68+
transform: translateX(-100%);
69+
padding-right: 2rem;
4370
}
4471

4572
@media (max-width: 1300px) {
46-
.toc {
47-
left: 2rem;
48-
transform: none;
49-
padding-right: 1rem;
50-
width: 14rem;
51-
}
73+
.toc {
74+
left: 2rem;
75+
transform: none;
76+
padding-right: 1rem;
77+
width: 14rem;
78+
}
5279
}
5380

5481
@media (max-width: 1024px) {
55-
.toc {
56-
display: none;
57-
}
82+
.toc {
83+
display: none;
84+
}
85+
}
86+
87+
.toc div::-webkit-scrollbar {
88+
width: 6px;
89+
}
90+
91+
.toc div::-webkit-scrollbar-track {
92+
background: transparent;
93+
}
94+
95+
.toc div::-webkit-scrollbar-thumb {
96+
background-color: rgb(156 163 175 / 0.2);
97+
border-radius: 3px;
98+
}
99+
100+
:global(.dark) .toc div::-webkit-scrollbar-thumb {
101+
background-color: rgb(75 85 99 / 0.3);
102+
}
103+
104+
.toc div::-webkit-scrollbar-thumb:hover {
105+
background-color: rgb(156 163 175 / 0.3);
106+
}
107+
108+
:global(.dark) .toc div::-webkit-scrollbar-thumb:hover {
109+
background-color: rgb(75 85 99 / 0.4);
110+
}
111+
112+
/* Active state styling */
113+
.toc-item.active {
114+
@apply text-blue-600 dark:text-blue-400 font-medium;
58115
}
59116
</style>
60117

61118
<script>
62-
const observerOptions = {
63-
root: null,
64-
rootMargin: '0px',
65-
threshold: 1.0
66-
};
119+
function updateActiveHeading() {
120+
const headings = Array.from(document.querySelectorAll('h1[id], h2[id], h3[id]'));
121+
const tocItems = document.querySelectorAll('.toc-item');
67122

68-
const observer = new IntersectionObserver(entries => {
69-
entries.forEach(entry => {
70-
const id = entry.target.getAttribute('id');
71-
if (entry.isIntersecting) {
72-
document.querySelector(`nav.toc a[href="#${id}"]`)
73-
?.classList.add('text-blue-600', 'dark:text-blue-400');
74-
} else {
75-
document.querySelector(`nav.toc a[href="#${id}"]`)
76-
?.classList.remove('text-blue-600', 'dark:text-blue-400');
123+
const observer = new IntersectionObserver(
124+
(entries) => {
125+
const visibleHeadings = entries
126+
.filter(entry => entry.isIntersecting)
127+
.sort((a, b) => {
128+
return a.target.getBoundingClientRect().top - b.target.getBoundingClientRect().top;
129+
});
130+
131+
if (visibleHeadings.length > 0) {
132+
const activeHeading = visibleHeadings[0];
133+
tocItems.forEach(item => item.classList.remove('active'));
134+
135+
const activeItem = document.querySelector(
136+
`.toc-item[data-heading="${activeHeading.target.id}"]`
137+
);
138+
if (activeItem) {
139+
activeItem.classList.add('active');
140+
}
141+
}
142+
},
143+
{
144+
rootMargin: '-64px 0px -70% 0px',
145+
threshold: [0, 1]
77146
}
78-
});
79-
}, observerOptions);
147+
);
80148

81-
document.querySelectorAll('h1[id], h2[id], h3[id]').forEach((header) => {
82-
observer.observe(header);
149+
headings.forEach(heading => observer.observe(heading));
150+
return () => observer.disconnect();
151+
}
152+
153+
updateActiveHeading();
154+
document.addEventListener('astro:after-swap', updateActiveHeading);
155+
156+
document.querySelectorAll('.toc-item').forEach(link => {
157+
link.addEventListener('click', (e) => {
158+
e.preventDefault();
159+
const targetId = link.getAttribute('href')?.slice(1);
160+
if (targetId) {
161+
const targetElement = document.getElementById(targetId);
162+
if (targetElement) {
163+
const headerOffset = 32;
164+
const elementPosition = targetElement.getBoundingClientRect().top;
165+
const offsetPosition = elementPosition + window.pageYOffset - headerOffset;
166+
167+
window.scrollTo({
168+
top: offsetPosition,
169+
behavior: 'smooth'
170+
});
171+
172+
history.pushState(null, '', `#${targetId}`);
173+
}
174+
}
175+
});
83176
});
84177
</script>

0 commit comments

Comments
 (0)