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
@@ -2,60 +2,100 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { FULLSCREEN_DATA_ATTR } from "@/lib/constants";
3
3
  import { useParams, useSearchParams } from "react-router-dom"
4
4
  import Loading from "@/components/loading";
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 { useMDXSlides } from "@/hooks/use-mdx-content";
9
- import { useSlidesFromMDX, SlideContent } from "@/components/slides-renderer";
8
+ import { slidesMdxComponents } from "@/components/slides-renderer";
10
9
 
11
10
 
12
11
  export function SlidesPage() {
13
- const { "path": path = "." } = useParams();
12
+ const { "*": rawPath = "." } = useParams();
14
13
  const [searchParams, setSearchParams] = useSearchParams();
15
14
 
16
- // Load the compiled MDX module
17
- const { Content, frontmatter, loading, error } = useMDXSlides(path);
15
+ // The path includes the .mdx extension from the route
16
+ const mdxPath = rawPath;
18
17
 
19
- // Split the MDX content into slides by <hr> elements
20
- const { slides } = useSlidesFromMDX({ Content, frontmatter });
18
+ // Load the compiled MDX module (now includes slideCount export)
19
+ const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);
21
20
 
22
21
  // Total slides = 1 (title) + content slides
23
- const totalSlides = slides.length + 1;
24
-
25
- // Get initial slide from query param
26
- const initialSlide = Math.max(0, Math.min(
27
- parseInt(searchParams.get("slide") || "0", 10),
28
- totalSlides - 1
29
- ));
30
-
31
- const [currentSlide, setCurrentSlide] = useState(initialSlide);
32
- const slideRefs = useRef<(HTMLDivElement | null)[]>([]);
33
-
34
- // Scroll to slide and update query param
35
- const goToSlide = useCallback((index: number) => {
36
- const clampedIndex = Math.max(0, Math.min(index, totalSlides - 1));
37
- const target = slideRefs.current[clampedIndex];
38
- if (target) {
39
- const targetTop = target.getBoundingClientRect().top + window.scrollY;
40
- window.scrollTo({ top: targetTop, behavior: "smooth" });
22
+ const totalSlides = (slideCount || 0) + 1;
23
+
24
+ const [currentSlide, setCurrentSlide] = useState(0);
25
+ const titleSlideRef = useRef<HTMLDivElement>(null);
26
+ const contentRef = useRef<HTMLDivElement>(null);
27
+
28
+ // Scroll to slide on initial load if query param is set
29
+ useEffect(() => {
30
+ const slideParam = parseInt(searchParams.get("slide") || "0", 10);
31
+ if (slideParam > 0 && contentRef.current) {
32
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${slideParam - 1}"]`);
33
+ if (slideEl) {
34
+ slideEl.scrollIntoView({ behavior: "auto" });
35
+ }
36
+ }
37
+ }, [searchParams, Content]);
38
+
39
+ // Track current slide based on scroll position
40
+ useEffect(() => {
41
+ const observer = new IntersectionObserver(
42
+ (entries) => {
43
+ for (const entry of entries) {
44
+ if (entry.isIntersecting) {
45
+ const index = entry.target.getAttribute("data-slide-index");
46
+ if (index !== null) {
47
+ const slideNum = index === "title" ? 0 : parseInt(index, 10) + 1;
48
+ setCurrentSlide(slideNum);
49
+ setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });
50
+ }
51
+ }
52
+ }
53
+ },
54
+ { threshold: 0.5 }
55
+ );
56
+
57
+ // Observe title slide
58
+ if (titleSlideRef.current) {
59
+ observer.observe(titleSlideRef.current);
41
60
  }
42
- }, [totalSlides]);
43
61
 
62
+ // Observe content slides
63
+ if (contentRef.current) {
64
+ const slides = contentRef.current.querySelectorAll("[data-slide-index]");
65
+ slides.forEach((slide) => observer.observe(slide));
66
+ }
67
+
68
+ return () => observer.disconnect();
69
+ }, [Content, setSearchParams]);
70
+
71
+ // Keyboard/scroll navigation helpers
44
72
  const goToPrevious = useCallback(() => {
45
- goToSlide(currentSlide - 1);
46
- }, [currentSlide, goToSlide]);
73
+ const prev = Math.max(0, currentSlide - 1);
74
+ if (prev === 0 && titleSlideRef.current) {
75
+ titleSlideRef.current.scrollIntoView({ behavior: "smooth" });
76
+ } else if (contentRef.current) {
77
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${prev - 1}"]`);
78
+ slideEl?.scrollIntoView({ behavior: "smooth" });
79
+ }
80
+ }, [currentSlide]);
47
81
 
48
82
  const goToNext = useCallback(() => {
49
- goToSlide(currentSlide + 1);
50
- }, [currentSlide, goToSlide]);
83
+ const next = Math.min(totalSlides - 1, currentSlide + 1);
84
+ if (next === 0 && titleSlideRef.current) {
85
+ titleSlideRef.current.scrollIntoView({ behavior: "smooth" });
86
+ } else if (contentRef.current) {
87
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${next - 1}"]`);
88
+ slideEl?.scrollIntoView({ behavior: "smooth" });
89
+ }
90
+ }, [currentSlide, totalSlides]);
51
91
 
52
92
  // Keyboard navigation
53
93
  useEffect(() => {
54
94
  const handleKeyDown = (e: KeyboardEvent) => {
55
- if (e.key === "ArrowUp" || e.key === "k") {
95
+ if (e.key === "ArrowUp" || e.key === "ArrowLeft" || e.key === "k") {
56
96
  e.preventDefault();
57
97
  goToPrevious();
58
- } else if (e.key === "ArrowDown" || e.key === "j") {
98
+ } else if (e.key === "ArrowDown" || e.key === "ArrowRight" || e.key === "j") {
59
99
  e.preventDefault();
60
100
  goToNext();
61
101
  }
@@ -65,37 +105,6 @@ export function SlidesPage() {
65
105
  return () => window.removeEventListener("keydown", handleKeyDown);
66
106
  }, [goToPrevious, goToNext]);
67
107
 
68
- // Update query param on scroll (delayed to avoid interference on load)
69
- useEffect(() => {
70
- let observer: IntersectionObserver | null = null;
71
-
72
- const timeoutId = setTimeout(() => {
73
- observer = new IntersectionObserver(
74
- (entries) => {
75
- for (const entry of entries) {
76
- if (entry.isIntersecting) {
77
- const index = slideRefs.current.findIndex((ref) => ref === entry.target);
78
- if (index !== -1) {
79
- setCurrentSlide(index);
80
- setSearchParams(index > 0 ? { slide: String(index) } : {}, { replace: true });
81
- }
82
- }
83
- }
84
- },
85
- { threshold: 0.5 }
86
- );
87
-
88
- slideRefs.current.forEach((ref) => {
89
- if (ref) observer!.observe(ref);
90
- });
91
- }, 100);
92
-
93
- return () => {
94
- clearTimeout(timeoutId);
95
- observer?.disconnect();
96
- };
97
- }, [slides.length, setSearchParams]);
98
-
99
108
  if (loading) {
100
109
  return <Loading />
101
110
  }
@@ -108,7 +117,7 @@ export function SlidesPage() {
108
117
  )
109
118
  }
110
119
 
111
- if (slides.length === 0) {
120
+ if (!Content) {
112
121
  return (
113
122
  <div className="flex items-center justify-center p-12 text-muted-foreground font-mono text-sm">
114
123
  no slides found — use "---" to separate slides
@@ -129,31 +138,9 @@ export function SlidesPage() {
129
138
  }}
130
139
  />
131
140
  <div {...{[FULLSCREEN_DATA_ATTR]: "true"}}>
132
- {/* Title slide */}
133
- <div
134
- ref={(el) => { slideRefs.current[0] = el; }}
135
- className="slide-page max-w-xl min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto"
136
- >
137
- <FrontMatter
138
- title={frontmatter?.title}
139
- date={frontmatter?.date}
140
- description={frontmatter?.description}
141
- />
141
+ <div ref={contentRef}>
142
+ <Content components={slidesMdxComponents} />
142
143
  </div>
143
- <hr className="print:hidden" />
144
-
145
- {/* Content slides */}
146
- {slides.map((slideContent, index) => (
147
- <div key={index}>
148
- <div
149
- ref={(el) => { slideRefs.current[index + 1] = el; }}
150
- className="slide-page min-h-[50vh] sm:min-h-[70vh] md:min-h-screen flex items-center justify-center py-8 sm:py-12 md:py-16 px-4 mx-auto"
151
- >
152
- <SlideContent>{slideContent}</SlideContent>
153
- </div>
154
- {index < slides.length - 1 && <hr className="print:hidden" />}
155
- </div>
156
- ))}
157
144
  </div>
158
145
  </main>
159
146
  )
package/src/vite-env.d.ts CHANGED
@@ -1,21 +1,11 @@
1
1
  /// <reference types="vite/client" />
2
2
 
3
- declare module 'virtual:content-modules' {
4
- import type { ComponentType } from 'react'
5
-
6
- interface MDXModule {
7
- default: ComponentType<{ components?: Record<string, ComponentType> }>
8
- frontmatter?: {
9
- title?: string
10
- description?: string
11
- date?: string
12
- visibility?: string
13
- }
3
+ declare module 'virtual:veslx-config' {
4
+ interface SiteConfig {
5
+ name: string;
6
+ description: string;
7
+ github: string;
14
8
  }
15
-
16
- type ModuleLoader = () => Promise<MDXModule>
17
-
18
- export const modules: Record<string, ModuleLoader>
19
- export const slides: Record<string, ModuleLoader>
20
- export const index: Record<string, { default: unknown }>
9
+ const config: SiteConfig;
10
+ export default config;
21
11
  }
package/vite.config.ts CHANGED
@@ -7,6 +7,7 @@ import rehypeKatex from 'rehype-katex'
7
7
  import remarkFrontmatter from 'remark-frontmatter'
8
8
  import remarkGfm from 'remark-gfm'
9
9
  import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
10
+ import { remarkSlides } from './plugin/src/remark-slides'
10
11
  import { fileURLToPath } from 'url'
11
12
  import path from 'path'
12
13
  import fs from 'fs'
@@ -17,10 +18,19 @@ const distClientPath = path.join(__dirname, 'dist/client')
17
18
  const srcPath = path.join(__dirname, 'src')
18
19
  const hasPrebuilt = fs.existsSync(path.join(distClientPath, 'main.js'))
19
20
 
21
+ // Common remark plugins
22
+ const commonRemarkPlugins = [
23
+ remarkGfm,
24
+ remarkMath,
25
+ remarkFrontmatter,
26
+ [remarkMdxFrontmatter, { name: 'frontmatter' }],
27
+ ]
28
+
20
29
  export default defineConfig(({ command }) => {
21
30
  // Only use pre-built files for dev server, not production build
22
31
  // Pre-built files have externalized React which breaks production bundles
23
- const usePrebuilt = command === 'serve' && hasPrebuilt
32
+ // VESLX_DEV=1 forces using src for live reload during development
33
+ const usePrebuilt = command === 'serve' && hasPrebuilt && !process.env.VESLX_DEV
24
34
  const clientPath = usePrebuilt ? distClientPath : srcPath
25
35
 
26
36
  return {
@@ -29,20 +39,29 @@ export default defineConfig(({ command }) => {
29
39
  publicDir: path.join(__dirname, 'public'),
30
40
  plugins: [
31
41
  tailwindcss(),
32
- // MDX must run before Vite's default transforms
42
+ // MDX for slides - splits at --- into <Slide> components
33
43
  {
34
44
  enforce: 'pre',
35
45
  ...mdx({
46
+ include: /SLIDES\.mdx$/,
36
47
  remarkPlugins: [
37
- remarkGfm,
38
- remarkMath,
39
- remarkFrontmatter,
40
- [remarkMdxFrontmatter, { name: 'frontmatter' }],
48
+ ...commonRemarkPlugins,
49
+ remarkSlides, // Transform --- into <Slide> wrappers
41
50
  ],
42
51
  rehypePlugins: [rehypeKatex],
43
52
  providerImportSource: '@mdx-js/react',
44
53
  }),
45
54
  },
55
+ // MDX for regular posts
56
+ {
57
+ enforce: 'pre',
58
+ ...mdx({
59
+ exclude: /SLIDES\.mdx$/,
60
+ remarkPlugins: commonRemarkPlugins,
61
+ rehypePlugins: [rehypeKatex],
62
+ providerImportSource: '@mdx-js/react',
63
+ }),
64
+ },
46
65
  react({ include: /\.(jsx|js|mdx|md|tsx|ts)$/ }),
47
66
  ],
48
67
  resolve: {
@@ -1,7 +0,0 @@
1
- import{j as t}from"./index-BUMwRZ7d.js";import{u as r}from"./index-C8sJQuOZ.js";function a({data:n=[30,60,45,80,55]}){const e=Math.max(...n);return t.jsx("div",{className:"flex items-end gap-2 h-32 p-4 bg-muted rounded-lg",children:n.map((s,i)=>t.jsx("div",{className:"flex-1 bg-primary rounded-t transition-all hover:bg-primary/80",style:{height:`${s/e*100}%`},title:`Value: ${s}`},i))})}const m={title:"Test Post",description:"This is a test post for testing purposes.",date:"2024-06-15"};function o(n){const e={code:"code",h1:"h1",h2:"h2",p:"p",...r(),...n.components};return t.jsxs(t.Fragment,{children:[t.jsx(e.h1,{children:"Test Post"}),`
2
- `,t.jsx(e.p,{children:"This post demonstrates local imports in MDX."}),`
3
- `,t.jsx(e.h2,{children:"Interactive Chart"}),`
4
- `,t.jsx(e.p,{children:"Here's a chart component imported from a local file:"}),`
5
- `,t.jsx(a,{data:[25,50,75,40,90]}),`
6
- `,t.jsx(e.h2,{children:"How it works"}),`
7
- `,t.jsxs(e.p,{children:["The MDX is compiled at build time by Vite, so ",t.jsx(e.code,{children:"import Chart from './Chart.tsx'"})," works natively. No runtime compilation needed!"]})]})}function h(n={}){const{wrapper:e}={...r(),...n.components};return e?t.jsx(e,{...n,children:t.jsx(o,{...n})}):o(n)}export{h as default,m as frontmatter};
@@ -1,10 +0,0 @@
1
- import{r as i,j as e}from"./index-BUMwRZ7d.js";import{u as o}from"./index-C8sJQuOZ.js";function c(){const[t,n]=i.useState(0);return e.jsxs("div",{className:"flex flex-col items-center gap-4 p-4 border rounded-lg",children:[e.jsx("p",{className:"text-2xl font-bold",children:t}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{onClick:()=>n(s=>s-1),className:"px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600",children:"-"}),e.jsx("button",{onClick:()=>n(s=>s+1),className:"px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600",children:"+"})]})]})}const h={title:"Test Slides",description:"These are test slides for testing purposes.",date:"2024-06-15"};function r(t){const n={h1:"h1",hr:"hr",p:"p",...o(),...t.components};return e.jsxs(e.Fragment,{children:[e.jsx(n.h1,{children:"Slide 1"}),`
2
- `,e.jsx(n.p,{children:"This is the first slide with some text."}),`
3
- `,e.jsx(n.hr,{}),`
4
- `,e.jsx(n.h1,{children:"Slide 2: Interactive Component"}),`
5
- `,e.jsx(n.p,{children:"Here's an interactive counter component:"}),`
6
- `,e.jsx(c,{}),`
7
- `,e.jsx(n.hr,{}),`
8
- `,e.jsx(n.h1,{children:"Slide 3"}),`
9
- `,e.jsx(n.p,{children:"This is the final slide."}),`
10
- `,e.jsx(n.p,{children:"Local imports work in slides now!"})]})}function x(t={}){const{wrapper:n}={...o(),...t.components};return n?e.jsx(n,{...t,children:e.jsx(r,{...t})}):r(t)}export{x as default,h as frontmatter};
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/README-NSyLDlyP.js","assets/index-BUMwRZ7d.js","assets/index-PspMxLnH.css","assets/index-C8sJQuOZ.js","assets/SLIDES-C12TOqNU.js"])))=>i.map(i=>d[i]);
2
- import{_ as t}from"./index-BUMwRZ7d.js";const e=Object.assign({"/test-content/test-post/README.mdx":()=>t(()=>import("./README-NSyLDlyP.js"),__vite__mapDeps([0,1,2,3]))}),o=Object.assign({"/test-content/test-slides/SLIDES.mdx":()=>t(()=>import("./SLIDES-C12TOqNU.js"),__vite__mapDeps([4,1,2,3]))});export{e as modules,o as slides};