veslx 0.1.5 → 0.1.15

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.
Files changed (97) hide show
  1. package/README.md +262 -55
  2. package/bin/lib/build.ts +65 -13
  3. package/bin/lib/import-config.ts +10 -9
  4. package/bin/lib/init.ts +21 -22
  5. package/bin/lib/serve.ts +66 -12
  6. package/bin/veslx.ts +2 -2
  7. package/dist/client/App.js +3 -9
  8. package/dist/client/App.js.map +1 -1
  9. package/dist/client/components/front-matter.js +11 -25
  10. package/dist/client/components/front-matter.js.map +1 -1
  11. package/dist/client/components/gallery/components/figure-caption.js +6 -4
  12. package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
  13. package/dist/client/components/gallery/components/figure-header.js +3 -3
  14. package/dist/client/components/gallery/components/figure-header.js.map +1 -1
  15. package/dist/client/components/gallery/components/lightbox.js +13 -13
  16. package/dist/client/components/gallery/components/lightbox.js.map +1 -1
  17. package/dist/client/components/gallery/components/loading-image.js +11 -10
  18. package/dist/client/components/gallery/components/loading-image.js.map +1 -1
  19. package/dist/client/components/gallery/hooks/use-gallery-images.js +31 -7
  20. package/dist/client/components/gallery/hooks/use-gallery-images.js.map +1 -1
  21. package/dist/client/components/gallery/index.js +22 -15
  22. package/dist/client/components/gallery/index.js.map +1 -1
  23. package/dist/client/components/header.js +5 -3
  24. package/dist/client/components/header.js.map +1 -1
  25. package/dist/client/components/mdx-components.js +42 -8
  26. package/dist/client/components/mdx-components.js.map +1 -1
  27. package/dist/client/components/post-list.js +97 -90
  28. package/dist/client/components/post-list.js.map +1 -1
  29. package/dist/client/components/running-bar.js +1 -1
  30. package/dist/client/components/running-bar.js.map +1 -1
  31. package/dist/client/components/slide.js +18 -0
  32. package/dist/client/components/slide.js.map +1 -0
  33. package/dist/client/components/slides-renderer.js +7 -71
  34. package/dist/client/components/slides-renderer.js.map +1 -1
  35. package/dist/client/hooks/use-mdx-content.js +55 -9
  36. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  37. package/dist/client/main.js +1 -0
  38. package/dist/client/main.js.map +1 -1
  39. package/dist/client/pages/content-router.js +19 -0
  40. package/dist/client/pages/content-router.js.map +1 -0
  41. package/dist/client/pages/home.js +11 -7
  42. package/dist/client/pages/home.js.map +1 -1
  43. package/dist/client/pages/post.js +8 -20
  44. package/dist/client/pages/post.js.map +1 -1
  45. package/dist/client/pages/slides.js +62 -86
  46. package/dist/client/pages/slides.js.map +1 -1
  47. package/dist/client/plugin/src/client.js +58 -96
  48. package/dist/client/plugin/src/client.js.map +1 -1
  49. package/dist/client/plugin/src/directory-tree.js +111 -0
  50. package/dist/client/plugin/src/directory-tree.js.map +1 -0
  51. package/index.html +1 -1
  52. package/package.json +34 -15
  53. package/plugin/src/client.tsx +64 -116
  54. package/plugin/src/directory-tree.ts +171 -0
  55. package/plugin/src/lib.ts +6 -249
  56. package/plugin/src/plugin.ts +93 -50
  57. package/plugin/src/remark-slides.ts +100 -0
  58. package/plugin/src/types.ts +22 -0
  59. package/src/App.tsx +3 -6
  60. package/src/components/front-matter.tsx +14 -29
  61. package/src/components/gallery/components/figure-caption.tsx +15 -7
  62. package/src/components/gallery/components/figure-header.tsx +3 -3
  63. package/src/components/gallery/components/lightbox.tsx +15 -13
  64. package/src/components/gallery/components/loading-image.tsx +15 -12
  65. package/src/components/gallery/hooks/use-gallery-images.ts +51 -10
  66. package/src/components/gallery/index.tsx +32 -26
  67. package/src/components/header.tsx +14 -9
  68. package/src/components/mdx-components.tsx +61 -8
  69. package/src/components/post-list.tsx +149 -115
  70. package/src/components/running-bar.tsx +1 -1
  71. package/src/components/slide.tsx +22 -5
  72. package/src/components/slides-renderer.tsx +7 -115
  73. package/src/components/welcome.tsx +11 -14
  74. package/src/hooks/use-mdx-content.ts +94 -9
  75. package/src/index.css +159 -0
  76. package/src/main.tsx +1 -0
  77. package/src/pages/content-router.tsx +27 -0
  78. package/src/pages/home.tsx +16 -2
  79. package/src/pages/post.tsx +10 -13
  80. package/src/pages/slides.tsx +75 -88
  81. package/src/vite-env.d.ts +7 -17
  82. package/vite.config.ts +25 -6
  83. package/dist/assets/README-NSyLDlyP.js +0 -7
  84. package/dist/assets/SLIDES-C12TOqNU.js +0 -10
  85. package/dist/assets/_virtual_content-modules-DK3Yb9K2.js +0 -2
  86. package/dist/assets/index-BUMwRZ7d.js +0 -468
  87. package/dist/assets/index-C8sJQuOZ.js +0 -1
  88. package/dist/assets/index-PspMxLnH.css +0 -1
  89. package/dist/index.html +0 -18
  90. package/dist/logo_dark.png +0 -0
  91. package/dist/logo_light.png +0 -0
  92. package/dist/raw/.veslx.json +0 -61
  93. package/dist/raw/README.md +0 -33
  94. package/dist/raw/test-post/Chart.tsx +0 -16
  95. package/dist/raw/test-post/README.mdx +0 -21
  96. package/dist/raw/test-slides/Counter.tsx +0 -25
  97. package/dist/raw/test-slides/SLIDES.mdx +0 -27
@@ -1,11 +1,28 @@
1
+ import { ReactNode } from 'react'
1
2
 
3
+ interface SlideProps {
4
+ index: number
5
+ children: ReactNode
6
+ }
2
7
 
3
- export function Slide({ children }: { children: React.ReactNode }) {
8
+ /**
9
+ * Slide component - wraps slide content for stacked scrollable display.
10
+ * Each slide takes full viewport height.
11
+ */
12
+ export function Slide({ index, children }: SlideProps) {
4
13
  return (
5
- <div className="w-screen h-screen bg-red-500 flex items-center justify-center p-8">
6
- <div className="max-w-5xl w-full h-full">
7
- {children}
14
+ <>
15
+ {index > 0 && (
16
+ <hr className="border-t border-border w-full" />
17
+ )}
18
+ <div
19
+ className="slide-section min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4"
20
+ data-slide-index={index}
21
+ >
22
+ <div className="slide-content prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed max-w-[var(--content-width)] w-full">
23
+ {children}
24
+ </div>
8
25
  </div>
9
- </div>
26
+ </>
10
27
  )
11
28
  }
@@ -1,127 +1,19 @@
1
- import React, { Children, isValidElement, ReactNode, useMemo } from 'react'
1
+ import { ReactNode } from 'react'
2
2
  import { mdxComponents } from '@/components/mdx-components'
3
-
4
- interface SlidesRendererProps {
5
- Content: React.ComponentType<{ components?: Record<string, React.ComponentType> }> | null
6
- frontmatter?: {
7
- title?: string
8
- description?: string
9
- date?: string
10
- }
11
- }
12
-
13
- /**
14
- * Splits MDX content into slides by <hr> elements.
15
- * The MDX is compiled at build time, so imports work.
16
- * Slide boundaries are marked with --- in MDX (which becomes <hr>).
17
- */
18
- export function useSlidesFromMDX({ Content, frontmatter }: SlidesRendererProps) {
19
- const slides = useMemo(() => {
20
- // Handle null Content (loading state)
21
- if (!Content) {
22
- return []
23
- }
24
-
25
- // Create a custom hr component that we can detect
26
- const SlideBreak = () => <hr data-slide-break="true" />
27
-
28
- // Render the MDX with our custom hr
29
- const componentsWithBreak = {
30
- ...mdxComponents,
31
- hr: SlideBreak,
32
- }
33
-
34
- // Render to get the element tree
35
- const rendered = <Content components={componentsWithBreak} />
36
-
37
- // Split children by hr elements
38
- return splitByHr(rendered)
39
- }, [Content])
40
-
41
- return { slides, frontmatter }
42
- }
3
+ import { Slide } from '@/components/slide'
43
4
 
44
5
  /**
45
- * Recursively traverses React element tree and splits by <hr> elements
6
+ * MDX components for slides - includes the Slide component
46
7
  */
47
- function splitByHr(element: ReactNode): ReactNode[][] {
48
- const slides: ReactNode[][] = [[]]
49
-
50
- function traverse(node: ReactNode) {
51
- if (!node) return
52
-
53
- if (Array.isArray(node)) {
54
- node.forEach(traverse)
55
- return
56
- }
57
-
58
- if (isValidElement(node)) {
59
- // Check if this is our slide break marker
60
- if (node.type === 'hr' || (node.props && node.props['data-slide-break'] === 'true')) {
61
- // Start a new slide
62
- slides.push([])
63
- return
64
- }
65
-
66
- // Check if it's an hr component from mdxComponents
67
- const nodeType = node.type as any
68
- if (typeof nodeType === 'function' && nodeType.name === 'SlideBreak') {
69
- slides.push([])
70
- return
71
- }
72
-
73
- // For fragments or elements with children, we need to check children
74
- if (node.props?.children) {
75
- const children = Children.toArray(node.props.children)
76
-
77
- // Check if any child is an hr - if so, we need to split this element
78
- const hasHrChild = children.some(child =>
79
- isValidElement(child) && (
80
- child.type === 'hr' ||
81
- (child.props && child.props['data-slide-break'] === 'true')
82
- )
83
- )
84
-
85
- if (hasHrChild) {
86
- // Split the children
87
- children.forEach(child => {
88
- if (isValidElement(child) && (
89
- child.type === 'hr' ||
90
- (child.props && child.props['data-slide-break'] === 'true')
91
- )) {
92
- slides.push([])
93
- } else {
94
- slides[slides.length - 1].push(child)
95
- }
96
- })
97
- } else {
98
- // No hr children, add the whole element
99
- slides[slides.length - 1].push(node)
100
- }
101
- return
102
- }
103
- }
104
-
105
- // Add to current slide
106
- slides[slides.length - 1].push(node)
107
- }
108
-
109
- // Get the children of the rendered element
110
- if (isValidElement(element) && element.props?.children) {
111
- const children = Children.toArray(element.props.children)
112
- children.forEach(traverse)
113
- } else {
114
- traverse(element)
115
- }
116
-
117
- // Filter out empty slides
118
- return slides.filter(slide => slide.length > 0)
8
+ export const slidesMdxComponents = {
9
+ ...mdxComponents,
10
+ Slide,
119
11
  }
120
12
 
121
13
  /**
122
14
  * Renders a single slide's content
123
15
  */
124
- export function SlideContent({ children }: { children: ReactNode[] }) {
16
+ export function SlideContent({ children }: { children: ReactNode }) {
125
17
  return (
126
18
  <div className="slide-content prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed max-w-xl">
127
19
  {children}
@@ -1,21 +1,18 @@
1
+ import siteConfig from "virtual:veslx-config";
1
2
 
2
3
  export function Welcome() {
4
+ const config = siteConfig;
5
+
3
6
  return (
4
7
  <div className="text-muted-foreground">
5
- <pre
6
- className="not-prose text-xs md:text-sm rounded"
7
- >
8
- {/* Rubifont on figlet */}
9
- {`
10
- ▗▄▄▖▗▄▄▄▖▗▖ ▗▖ ▗▄▄▖▗▖ ▗▄▖ ▗▄▄▖
11
- ▐▌ ▐▌ █ ▐▛▚▖▐▌▐▌ ▐▌ ▐▌ ▐▌▐▌ ▐▌
12
- ▐▛▀▘ █ ▐▌ ▝▜▌▐▌▝▜▌▐▌ ▐▛▀▜▌▐▛▀▚▖
13
- ▐▌ ▗▄█▄▖▐▌ ▐▌▝▚▄▞▘▐▙▄▄▖▐▌ ▐▌▐▙▄▞▘
14
- `}
15
- </pre>
16
- <div className="text-xs mt-2 font-mono">
17
- PingLab is a repository for running experiments on PING Spiking Neural Networks.
18
- </div>
8
+ <h1 className="text-xl md:text-2xl font-semibold tracking-tight text-foreground mb-2">
9
+ {config.name}
10
+ </h1>
11
+ {config.description && (
12
+ <p className="text-sm text-muted-foreground/80 font-mono">
13
+ {config.description}
14
+ </p>
15
+ )}
19
16
  </div>
20
17
  )
21
18
  }
@@ -8,12 +8,69 @@ interface MDXModule {
8
8
  description?: string
9
9
  date?: string
10
10
  visibility?: string
11
+ draft?: boolean
11
12
  }
13
+ slideCount?: number // Exported by remark-slides plugin for SLIDES.mdx files
12
14
  }
13
15
 
14
16
  type ModuleLoader = () => Promise<MDXModule>
15
17
  type ModuleMap = Record<string, ModuleLoader>
16
18
 
19
+ /**
20
+ * Find MDX module by path. Supports:
21
+ * - Full path: "docs/intro.mdx" -> matches exactly
22
+ * - Folder path: "docs" -> matches "docs/index.mdx", "docs/README.mdx", or "docs.mdx"
23
+ */
24
+ function findMdxModule(modules: ModuleMap, path: string): ModuleLoader | null {
25
+ const keys = Object.keys(modules)
26
+
27
+ // Normalize path - remove leading slash if present
28
+ const normalizedPath = path.replace(/^\//, '')
29
+
30
+ // If path already ends with .mdx, match exactly
31
+ if (normalizedPath.endsWith('.mdx')) {
32
+ // Try multiple matching strategies for different Vite glob formats
33
+ const matchingKey = keys.find(key => {
34
+ // Strategy 1: Key ends with /path (e.g., @content/docs/foo.mdx matches docs/foo.mdx)
35
+ if (key.endsWith(`/${normalizedPath}`)) return true
36
+ // Strategy 2: Key equals @content/path (alias form)
37
+ if (key === `@content/${normalizedPath}`) return true
38
+ // Strategy 3: Key equals /@content/path (with leading slash)
39
+ if (key === `/@content/${normalizedPath}`) return true
40
+ // Strategy 4: Key equals path directly
41
+ if (key === normalizedPath) return true
42
+ // Strategy 5: Key equals /path (with leading slash)
43
+ if (key === `/${normalizedPath}`) return true
44
+ return false
45
+ })
46
+ return matchingKey ? modules[matchingKey] : null
47
+ }
48
+
49
+ // Otherwise, try folder conventions in order of preference:
50
+ // 1. folder/index.mdx (modern convention)
51
+ // 2. folder/README.mdx (current convention)
52
+ // 3. folder.mdx (file alongside folders)
53
+ const candidates = [
54
+ `${normalizedPath}/index.mdx`,
55
+ `${normalizedPath}/README.mdx`,
56
+ `${normalizedPath}.mdx`,
57
+ ]
58
+
59
+ for (const candidate of candidates) {
60
+ const matchingKey = keys.find(key => {
61
+ if (key.endsWith(`/${candidate}`)) return true
62
+ if (key === `@content/${candidate}`) return true
63
+ if (key === candidate) return true
64
+ return false
65
+ })
66
+ if (matchingKey) {
67
+ return modules[matchingKey]
68
+ }
69
+ }
70
+
71
+ return null
72
+ }
73
+
17
74
  export function useMDXContent(path: string) {
18
75
  const [Content, setContent] = useState<MDXModule['default'] | null>(null)
19
76
  const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)
@@ -28,10 +85,7 @@ export function useMDXContent(path: string) {
28
85
  // Dynamic import to avoid pre-bundling issues
29
86
  import('virtual:content-modules')
30
87
  .then(({ modules }) => {
31
- const matchingKey = Object.keys(modules).find(key =>
32
- key.endsWith(`/${path}/README.mdx`)
33
- )
34
- const loader = matchingKey ? (modules as ModuleMap)[matchingKey] : null
88
+ const loader = findMdxModule(modules as ModuleMap, path)
35
89
 
36
90
  if (!loader) {
37
91
  throw new Error(`MDX module not found for path: ${path}`)
@@ -61,9 +115,42 @@ export function useMDXContent(path: string) {
61
115
  return { Content, frontmatter, loading, error }
62
116
  }
63
117
 
118
+ /**
119
+ * Find slides module by path. Supports:
120
+ * - Full path: "docs/intro.slides.mdx" or "docs/SLIDES.mdx" -> matches exactly
121
+ * - Folder path: "docs" -> matches "docs/SLIDES.mdx" or "docs/index.slides.mdx"
122
+ */
123
+ function findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null {
124
+ const keys = Object.keys(modules)
125
+
126
+ // If path already ends with .mdx, match exactly
127
+ if (path.endsWith('.mdx')) {
128
+ const matchingKey = keys.find(key => key.endsWith(`/${path}`))
129
+ return matchingKey ? modules[matchingKey] : null
130
+ }
131
+
132
+ // Otherwise, try folder conventions:
133
+ // 1. folder/SLIDES.mdx (current convention)
134
+ // 2. folder/index.slides.mdx (alternative)
135
+ const candidates = [
136
+ `/${path}/SLIDES.mdx`,
137
+ `/${path}/index.slides.mdx`,
138
+ ]
139
+
140
+ for (const suffix of candidates) {
141
+ const matchingKey = keys.find(key => key.endsWith(suffix))
142
+ if (matchingKey) {
143
+ return modules[matchingKey]
144
+ }
145
+ }
146
+
147
+ return null
148
+ }
149
+
64
150
  export function useMDXSlides(path: string) {
65
151
  const [Content, setContent] = useState<MDXModule['default'] | null>(null)
66
152
  const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)
153
+ const [slideCount, setSlideCount] = useState<number | undefined>(undefined)
67
154
  const [loading, setLoading] = useState(true)
68
155
  const [error, setError] = useState<Error | null>(null)
69
156
 
@@ -75,10 +162,7 @@ export function useMDXSlides(path: string) {
75
162
  // Dynamic import to avoid pre-bundling issues
76
163
  import('virtual:content-modules')
77
164
  .then(({ slides }) => {
78
- const matchingKey = Object.keys(slides).find(key =>
79
- key.endsWith(`/${path}/SLIDES.mdx`)
80
- )
81
- const loader = matchingKey ? (slides as ModuleMap)[matchingKey] : null
165
+ const loader = findSlidesModule(slides as ModuleMap, path)
82
166
 
83
167
  if (!loader) {
84
168
  throw new Error(`Slides module not found for path: ${path}`)
@@ -90,6 +174,7 @@ export function useMDXSlides(path: string) {
90
174
  if (!cancelled) {
91
175
  setContent(() => mod.default)
92
176
  setFrontmatter(mod.frontmatter)
177
+ setSlideCount(mod.slideCount)
93
178
  setLoading(false)
94
179
  }
95
180
  })
@@ -105,5 +190,5 @@ export function useMDXSlides(path: string) {
105
190
  }
106
191
  }, [path])
107
192
 
108
- return { Content, frontmatter, loading, error }
193
+ return { Content, frontmatter, slideCount, loading, error }
109
194
  }
package/src/index.css CHANGED
@@ -277,3 +277,162 @@
277
277
  .animate-slide-up {
278
278
  animation: slide-up 0.5s ease-out forwards;
279
279
  }
280
+
281
+ /* Shimmer loading animation for skeletons */
282
+ @keyframes shimmer {
283
+ 0% {
284
+ transform: translateX(-100%);
285
+ }
286
+ 100% {
287
+ transform: translateX(100%);
288
+ }
289
+ }
290
+
291
+ .animate-shimmer {
292
+ animation: shimmer 1.5s ease-in-out infinite;
293
+ }
294
+
295
+ /* Print styles - slides landscape, posts portrait */
296
+ @media print {
297
+ /* Default page is portrait for posts */
298
+ @page {
299
+ size: portrait;
300
+ margin: 1in;
301
+ }
302
+
303
+ /* Named page for slides - landscape */
304
+ @page slides {
305
+ size: landscape;
306
+ margin: 0;
307
+ }
308
+
309
+ /* Slides container uses the slides page */
310
+ .slides-container {
311
+ page: slides;
312
+ }
313
+
314
+ /* Reset everything */
315
+ html, body {
316
+ height: auto !important;
317
+ overflow: visible !important;
318
+ background: white !important;
319
+ margin: 0 !important;
320
+ padding: 0 !important;
321
+ }
322
+
323
+ /* Hide ALL UI elements aggressively */
324
+ .slides-container > header,
325
+ .slides-container > .running-bar,
326
+ .slides-container hr,
327
+ .slides-container > title,
328
+ header.print\\:hidden,
329
+ [class*="print:hidden"] {
330
+ display: none !important;
331
+ height: 0 !important;
332
+ width: 0 !important;
333
+ overflow: hidden !important;
334
+ position: absolute !important;
335
+ left: -9999px !important;
336
+ }
337
+
338
+ /* Main container - no space, no pages */
339
+ .slides-container {
340
+ display: block !important;
341
+ position: static !important;
342
+ margin: 0 !important;
343
+ padding: 0 !important;
344
+ }
345
+
346
+ /* All wrapper divs - collapse completely */
347
+ .slides-container > div:not(.slide-section),
348
+ .slides-container > div > div:not(.slide-section) {
349
+ display: contents !important;
350
+ }
351
+
352
+ /* Each slide is one page */
353
+ .slide-section {
354
+ display: flex !important;
355
+ align-items: center !important;
356
+ justify-content: center !important;
357
+ width: 100vw !important;
358
+ height: 100vh !important;
359
+ min-height: 100vh !important;
360
+ max-height: 100vh !important;
361
+ padding: 5vh 8vw !important;
362
+ margin: 0 !important;
363
+ page-break-after: always !important;
364
+ break-after: page !important;
365
+ page-break-inside: avoid !important;
366
+ break-inside: avoid !important;
367
+ box-sizing: border-box !important;
368
+ overflow: hidden !important;
369
+ }
370
+
371
+ /* Last slide shouldn't add extra page */
372
+ .slide-section:last-of-type {
373
+ page-break-after: auto !important;
374
+ break-after: auto !important;
375
+ }
376
+
377
+ /* Slide content fills available space */
378
+ .slide-content {
379
+ max-width: 90% !important;
380
+ max-height: 90vh !important;
381
+ width: auto !important;
382
+ margin: 0 auto !important;
383
+ }
384
+
385
+ /* Scale images appropriately for print */
386
+ .slide-content img {
387
+ max-height: 60vh !important;
388
+ width: auto !important;
389
+ height: auto !important;
390
+ object-fit: contain !important;
391
+ }
392
+
393
+ /* Gallery adjustments for print */
394
+ .slide-content figure {
395
+ margin: 0 !important;
396
+ padding: 1rem 0 !important;
397
+ }
398
+
399
+ /* Ensure text is black for print */
400
+ .slide-content,
401
+ .slide-content * {
402
+ color: black !important;
403
+ -webkit-print-color-adjust: exact !important;
404
+ print-color-adjust: exact !important;
405
+ }
406
+
407
+ /* Hide carousel navigation in print */
408
+ .slide-content button[class*="Carousel"] {
409
+ display: none !important;
410
+ }
411
+
412
+ /* === Post print styles === */
413
+
414
+ /* Hide carousel navigation buttons in posts */
415
+ figure button {
416
+ display: none !important;
417
+ }
418
+
419
+ /* Make carousel show all items in grid for print */
420
+ [data-slot="carousel-content"] {
421
+ display: grid !important;
422
+ grid-template-columns: repeat(3, 1fr) !important;
423
+ gap: 0.5rem !important;
424
+ transform: none !important;
425
+ }
426
+
427
+ [data-slot="carousel-item"] {
428
+ flex: none !important;
429
+ width: 100% !important;
430
+ padding: 0 !important;
431
+ margin: 0 !important;
432
+ }
433
+
434
+ /* Ensure gallery images print well */
435
+ figure img {
436
+ break-inside: avoid !important;
437
+ }
438
+ }
package/src/main.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import { StrictMode } from 'react'
2
2
  import { createRoot } from 'react-dom/client'
3
+ import 'katex/dist/katex.min.css'
3
4
  import './index.css'
4
5
  import App from '@/App'
5
6
 
@@ -0,0 +1,27 @@
1
+ import { useParams } from "react-router-dom"
2
+ import { Home } from "./home"
3
+ import { Post } from "./post"
4
+ import { SlidesPage } from "./slides"
5
+
6
+ /**
7
+ * Routes to the appropriate page based on the URL path:
8
+ * - *.slides.mdx or *SLIDES.mdx → SlidesPage
9
+ * - *.mdx → Post
10
+ * - everything else → Home (directory listing)
11
+ */
12
+ export function ContentRouter() {
13
+ const { "*": path = "" } = useParams()
14
+
15
+ // Check if this is a slides file
16
+ if (path.endsWith('.slides.mdx') || path.endsWith('SLIDES.mdx')) {
17
+ return <SlidesPage />
18
+ }
19
+
20
+ // Check if this is any MDX file
21
+ if (path.endsWith('.mdx') || path.endsWith('.md')) {
22
+ return <Post />
23
+ }
24
+
25
+ // Otherwise show directory listing
26
+ return <Home />
27
+ }
@@ -5,10 +5,12 @@ import PostList from "@/components/post-list";
5
5
  import { ErrorDisplay } from "@/components/page-error";
6
6
  import { RunningBar } from "@/components/running-bar";
7
7
  import { Header } from "@/components/header";
8
+ import siteConfig from "virtual:veslx-config";
8
9
 
9
10
  export function Home() {
10
11
  const { "*": path = "." } = useParams();
11
12
  const { directory, loading, error } = useDirectory(path)
13
+ const config = siteConfig;
12
14
 
13
15
  if (error) {
14
16
  return <ErrorDisplay error={error} path={path} />;
@@ -25,8 +27,20 @@ export function Home() {
25
27
  <RunningBar />
26
28
  <Header />
27
29
  <main className="flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]">
28
- <title>{`Pinglab ${path}`}</title>
29
- <main className="flex flex-col gap-6 mb-32 mt-32">
30
+ <title>{(path === "." || path === "") ? config.name : `${config.name} - ${path}`}</title>
31
+ <main className="flex flex-col gap-8 mb-32 mt-32">
32
+ {(path === "." || path === "") && (
33
+ <div className="animate-fade-in">
34
+ <h1 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground">
35
+ {config.name}
36
+ </h1>
37
+ {config.description && (
38
+ <p className="mt-2 text-muted-foreground">
39
+ {config.description}
40
+ </p>
41
+ )}
42
+ </div>
43
+ )}
30
44
  {directory && (
31
45
  <div className="animate-fade-in">
32
46
  <PostList directory={directory}/>
@@ -2,18 +2,22 @@ import { useParams } from "react-router-dom";
2
2
  import { findSlides, isSimulationRunning, useDirectory } from "../../plugin/src/client";
3
3
  import Loading from "@/components/loading";
4
4
  import { FileEntry } from "plugin/src/lib";
5
- import { FrontMatter } from "@/components/front-matter";
6
5
  import { RunningBar } from "@/components/running-bar";
7
6
  import { Header } from "@/components/header";
8
7
  import { useMDXContent } from "@/hooks/use-mdx-content";
9
8
  import { mdxComponents } from "@/components/mdx-components";
10
9
 
11
-
12
10
  export function Post() {
13
- const { "path": path = "." } = useParams();
14
- const filePath = `${path}/README.mdx`
15
- const { directory, loading: dirLoading } = useDirectory(filePath)
16
- const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(path);
11
+ const { "*": rawPath = "." } = useParams();
12
+
13
+ // The path includes the .mdx extension from the route
14
+ const mdxPath = rawPath;
15
+
16
+ // Extract directory path for finding sibling files (slides, etc.)
17
+ const dirPath = mdxPath.replace(/\/[^/]+\.mdx$/, '') || '.';
18
+
19
+ const { directory, loading: dirLoading } = useDirectory(dirPath)
20
+ const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);
17
21
  const isRunning = isSimulationRunning();
18
22
 
19
23
  let slides: FileEntry | null = null;
@@ -39,7 +43,6 @@ export function Post() {
39
43
  <RunningBar />
40
44
  <Header />
41
45
  <main className="flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]">
42
-
43
46
  {isRunning && (
44
47
  <div className="sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide">
45
48
  <span className="inline-flex items-center gap-3">
@@ -52,12 +55,6 @@ export function Post() {
52
55
 
53
56
  {Content && (
54
57
  <article className="my-24 prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed prose-a:text-primary prose-a:no-underline hover:prose-a:underline max-w-[var(--prose-width)] animate-fade-in">
55
- <FrontMatter
56
- title={frontmatter?.title}
57
- date={frontmatter?.date}
58
- description={frontmatter?.description}
59
- slides={slides}
60
- />
61
58
  <Content components={mdxComponents} />
62
59
  </article>
63
60
  )}