veslx 0.1.14 → 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 (82) 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 +27 -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
@@ -1,4 +1,36 @@
1
1
  import { useState, useEffect } from "react";
2
+ function findMdxModule(modules, path) {
3
+ const keys = Object.keys(modules);
4
+ const normalizedPath = path.replace(/^\//, "");
5
+ if (normalizedPath.endsWith(".mdx")) {
6
+ const matchingKey = keys.find((key) => {
7
+ if (key.endsWith(`/${normalizedPath}`)) return true;
8
+ if (key === `@content/${normalizedPath}`) return true;
9
+ if (key === `/@content/${normalizedPath}`) return true;
10
+ if (key === normalizedPath) return true;
11
+ if (key === `/${normalizedPath}`) return true;
12
+ return false;
13
+ });
14
+ return matchingKey ? modules[matchingKey] : null;
15
+ }
16
+ const candidates = [
17
+ `${normalizedPath}/index.mdx`,
18
+ `${normalizedPath}/README.mdx`,
19
+ `${normalizedPath}.mdx`
20
+ ];
21
+ for (const candidate of candidates) {
22
+ const matchingKey = keys.find((key) => {
23
+ if (key.endsWith(`/${candidate}`)) return true;
24
+ if (key === `@content/${candidate}`) return true;
25
+ if (key === candidate) return true;
26
+ return false;
27
+ });
28
+ if (matchingKey) {
29
+ return modules[matchingKey];
30
+ }
31
+ }
32
+ return null;
33
+ }
2
34
  function useMDXContent(path) {
3
35
  const [Content, setContent] = useState(null);
4
36
  const [frontmatter, setFrontmatter] = useState(void 0);
@@ -9,10 +41,7 @@ function useMDXContent(path) {
9
41
  setLoading(true);
10
42
  setError(null);
11
43
  import("virtual:content-modules").then(({ modules }) => {
12
- const matchingKey = Object.keys(modules).find(
13
- (key) => key.endsWith(`/${path}/README.mdx`)
14
- );
15
- const loader = matchingKey ? modules[matchingKey] : null;
44
+ const loader = findMdxModule(modules, path);
16
45
  if (!loader) {
17
46
  throw new Error(`MDX module not found for path: ${path}`);
18
47
  }
@@ -35,9 +64,28 @@ function useMDXContent(path) {
35
64
  }, [path]);
36
65
  return { Content, frontmatter, loading, error };
37
66
  }
67
+ function findSlidesModule(modules, path) {
68
+ const keys = Object.keys(modules);
69
+ if (path.endsWith(".mdx")) {
70
+ const matchingKey = keys.find((key) => key.endsWith(`/${path}`));
71
+ return matchingKey ? modules[matchingKey] : null;
72
+ }
73
+ const candidates = [
74
+ `/${path}/SLIDES.mdx`,
75
+ `/${path}/index.slides.mdx`
76
+ ];
77
+ for (const suffix of candidates) {
78
+ const matchingKey = keys.find((key) => key.endsWith(suffix));
79
+ if (matchingKey) {
80
+ return modules[matchingKey];
81
+ }
82
+ }
83
+ return null;
84
+ }
38
85
  function useMDXSlides(path) {
39
86
  const [Content, setContent] = useState(null);
40
87
  const [frontmatter, setFrontmatter] = useState(void 0);
88
+ const [slideCount, setSlideCount] = useState(void 0);
41
89
  const [loading, setLoading] = useState(true);
42
90
  const [error, setError] = useState(null);
43
91
  useEffect(() => {
@@ -45,10 +93,7 @@ function useMDXSlides(path) {
45
93
  setLoading(true);
46
94
  setError(null);
47
95
  import("virtual:content-modules").then(({ slides }) => {
48
- const matchingKey = Object.keys(slides).find(
49
- (key) => key.endsWith(`/${path}/SLIDES.mdx`)
50
- );
51
- const loader = matchingKey ? slides[matchingKey] : null;
96
+ const loader = findSlidesModule(slides, path);
52
97
  if (!loader) {
53
98
  throw new Error(`Slides module not found for path: ${path}`);
54
99
  }
@@ -57,6 +102,7 @@ function useMDXSlides(path) {
57
102
  if (!cancelled) {
58
103
  setContent(() => mod.default);
59
104
  setFrontmatter(mod.frontmatter);
105
+ setSlideCount(mod.slideCount);
60
106
  setLoading(false);
61
107
  }
62
108
  }).catch((err) => {
@@ -69,7 +115,7 @@ function useMDXSlides(path) {
69
115
  cancelled = true;
70
116
  };
71
117
  }, [path]);
72
- return { Content, frontmatter, loading, error };
118
+ return { Content, frontmatter, slideCount, loading, error };
73
119
  }
74
120
  export {
75
121
  useMDXContent,
@@ -1 +1 @@
1
- {"version":3,"file":"use-mdx-content.js","sources":["../../../src/hooks/use-mdx-content.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport type { ComponentType } from 'react'\n\ninterface MDXModule {\n default: ComponentType<{ components?: Record<string, ComponentType> }>\n frontmatter?: {\n title?: string\n description?: string\n date?: string\n visibility?: string\n }\n}\n\ntype ModuleLoader = () => Promise<MDXModule>\ntype ModuleMap = Record<string, ModuleLoader>\n\nexport function useMDXContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ modules }) => {\n const matchingKey = Object.keys(modules).find(key =>\n key.endsWith(`/${path}/README.mdx`)\n )\n const loader = matchingKey ? (modules as ModuleMap)[matchingKey] : null\n\n if (!loader) {\n throw new Error(`MDX module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, error }\n}\n\nexport function useMDXSlides(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ slides }) => {\n const matchingKey = Object.keys(slides).find(key =>\n key.endsWith(`/${path}/SLIDES.mdx`)\n )\n const loader = matchingKey ? (slides as ModuleMap)[matchingKey] : null\n\n if (!loader) {\n throw new Error(`Slides module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, error }\n}\n"],"names":[],"mappings":";AAgBO,SAAS,cAAc,MAAc;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,cAAc;AACrB,YAAM,cAAc,OAAO,KAAK,OAAO,EAAE;AAAA,QAAK,CAAA,QAC5C,IAAI,SAAS,IAAI,IAAI,aAAa;AAAA,MAAA;AAEpC,YAAM,SAAS,cAAe,QAAsB,WAAW,IAAI;AAEnE,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,MAC1D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,SAAS,MAAA;AAC1C;AAEO,SAAS,aAAa,MAAc;AACzC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,aAAa;AACpB,YAAM,cAAc,OAAO,KAAK,MAAM,EAAE;AAAA,QAAK,CAAA,QAC3C,IAAI,SAAS,IAAI,IAAI,aAAa;AAAA,MAAA;AAEpC,YAAM,SAAS,cAAe,OAAqB,WAAW,IAAI;AAElE,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AAAA,MAC7D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,SAAS,MAAA;AAC1C;"}
1
+ {"version":3,"file":"use-mdx-content.js","sources":["../../../src/hooks/use-mdx-content.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport type { ComponentType } from 'react'\n\ninterface MDXModule {\n default: ComponentType<{ components?: Record<string, ComponentType> }>\n frontmatter?: {\n title?: string\n description?: string\n date?: string\n visibility?: string\n draft?: boolean\n }\n slideCount?: number // Exported by remark-slides plugin for SLIDES.mdx files\n}\n\ntype ModuleLoader = () => Promise<MDXModule>\ntype ModuleMap = Record<string, ModuleLoader>\n\n/**\n * Find MDX module by path. Supports:\n * - Full path: \"docs/intro.mdx\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/index.mdx\", \"docs/README.mdx\", or \"docs.mdx\"\n */\nfunction findMdxModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash if present\n const normalizedPath = path.replace(/^\\//, '')\n\n // If path already ends with .mdx, match exactly\n if (normalizedPath.endsWith('.mdx')) {\n // Try multiple matching strategies for different Vite glob formats\n const matchingKey = keys.find(key => {\n // Strategy 1: Key ends with /path (e.g., @content/docs/foo.mdx matches docs/foo.mdx)\n if (key.endsWith(`/${normalizedPath}`)) return true\n // Strategy 2: Key equals @content/path (alias form)\n if (key === `@content/${normalizedPath}`) return true\n // Strategy 3: Key equals /@content/path (with leading slash)\n if (key === `/@content/${normalizedPath}`) return true\n // Strategy 4: Key equals path directly\n if (key === normalizedPath) return true\n // Strategy 5: Key equals /path (with leading slash)\n if (key === `/${normalizedPath}`) return true\n return false\n })\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions in order of preference:\n // 1. folder/index.mdx (modern convention)\n // 2. folder/README.mdx (current convention)\n // 3. folder.mdx (file alongside folders)\n const candidates = [\n `${normalizedPath}/index.mdx`,\n `${normalizedPath}/README.mdx`,\n `${normalizedPath}.mdx`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ modules }) => {\n const loader = findMdxModule(modules as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`MDX module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, error }\n}\n\n/**\n * Find slides module by path. Supports:\n * - Full path: \"docs/intro.slides.mdx\" or \"docs/SLIDES.mdx\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/SLIDES.mdx\" or \"docs/index.slides.mdx\"\n */\nfunction findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // If path already ends with .mdx, match exactly\n if (path.endsWith('.mdx')) {\n const matchingKey = keys.find(key => key.endsWith(`/${path}`))\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions:\n // 1. folder/SLIDES.mdx (current convention)\n // 2. folder/index.slides.mdx (alternative)\n const candidates = [\n `/${path}/SLIDES.mdx`,\n `/${path}/index.slides.mdx`,\n ]\n\n for (const suffix of candidates) {\n const matchingKey = keys.find(key => key.endsWith(suffix))\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXSlides(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [slideCount, setSlideCount] = useState<number | undefined>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ slides }) => {\n const loader = findSlidesModule(slides as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`Slides module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setSlideCount(mod.slideCount)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, slideCount, loading, error }\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { StrictMode } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
+ import "katex/dist/katex.min.css";
4
5
  import "./src/index.css";
5
6
  import App from "./App.js";
6
7
  createRoot(document.getElementById("root")).render(
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sources":["../../src/main.tsx"],"sourcesContent":["import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport './index.css'\nimport App from '@/App'\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n)\n"],"names":[],"mappings":";;;;;AAKA,WAAW,SAAS,eAAe,MAAM,CAAE,EAAE;AAAA,EAC3C,oBAAC,YAAA,EACC,UAAA,oBAAC,KAAA,CAAA,CAAI,EAAA,CACP;AACF;"}
1
+ {"version":3,"file":"main.js","sources":["../../src/main.tsx"],"sourcesContent":["import { StrictMode } from 'react'\nimport { createRoot } from 'react-dom/client'\nimport 'katex/dist/katex.min.css'\nimport './index.css'\nimport App from '@/App'\n\ncreateRoot(document.getElementById('root')!).render(\n <StrictMode>\n <App />\n </StrictMode>,\n)\n"],"names":[],"mappings":";;;;;;AAMA,WAAW,SAAS,eAAe,MAAM,CAAE,EAAE;AAAA,EAC3C,oBAAC,YAAA,EACC,UAAA,oBAAC,KAAA,CAAA,CAAI,EAAA,CACP;AACF;"}
@@ -0,0 +1,19 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { useParams } from "react-router-dom";
3
+ import { Home } from "./home.js";
4
+ import { Post } from "./post.js";
5
+ import { SlidesPage } from "./slides.js";
6
+ function ContentRouter() {
7
+ const { "*": path = "" } = useParams();
8
+ if (path.endsWith(".slides.mdx") || path.endsWith("SLIDES.mdx")) {
9
+ return /* @__PURE__ */ jsx(SlidesPage, {});
10
+ }
11
+ if (path.endsWith(".mdx") || path.endsWith(".md")) {
12
+ return /* @__PURE__ */ jsx(Post, {});
13
+ }
14
+ return /* @__PURE__ */ jsx(Home, {});
15
+ }
16
+ export {
17
+ ContentRouter
18
+ };
19
+ //# sourceMappingURL=content-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content-router.js","sources":["../../../src/pages/content-router.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { Home } from \"./home\"\nimport { Post } from \"./post\"\nimport { SlidesPage } from \"./slides\"\n\n/**\n * Routes to the appropriate page based on the URL path:\n * - *.slides.mdx or *SLIDES.mdx → SlidesPage\n * - *.mdx → Post\n * - everything else → Home (directory listing)\n */\nexport function ContentRouter() {\n const { \"*\": path = \"\" } = useParams()\n\n // Check if this is a slides file\n if (path.endsWith('.slides.mdx') || path.endsWith('SLIDES.mdx')) {\n return <SlidesPage />\n }\n\n // Check if this is any MDX file\n if (path.endsWith('.mdx') || path.endsWith('.md')) {\n return <Post />\n }\n\n // Otherwise show directory listing\n return <Home />\n}\n"],"names":[],"mappings":";;;;;AAWO,SAAS,gBAAgB;AAC9B,QAAM,EAAE,KAAK,OAAO,GAAA,IAAO,UAAA;AAG3B,MAAI,KAAK,SAAS,aAAa,KAAK,KAAK,SAAS,YAAY,GAAG;AAC/D,+BAAQ,YAAA,EAAW;AAAA,EACrB;AAGA,MAAI,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,KAAK,GAAG;AACjD,+BAAQ,MAAA,EAAK;AAAA,EACf;AAGA,6BAAQ,MAAA,EAAK;AACf;"}
@@ -1,26 +1,30 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useParams } from "react-router-dom";
3
3
  import { useDirectory } from "../plugin/src/client.js";
4
- import Loading from "../components/loading.js";
5
4
  import PostList from "../components/post-list.js";
6
5
  import { ErrorDisplay } from "../components/page-error.js";
7
6
  import { RunningBar } from "../components/running-bar.js";
8
7
  import { Header } from "../components/header.js";
8
+ import siteConfig from "virtual:veslx-config";
9
9
  function Home() {
10
10
  const { "*": path = "." } = useParams();
11
- const { directory, loading, error } = useDirectory(path);
11
+ const { directory, error } = useDirectory(path);
12
+ const config = siteConfig;
12
13
  if (error) {
13
14
  return /* @__PURE__ */ jsx(ErrorDisplay, { error, path });
14
15
  }
15
- if (loading) {
16
- return /* @__PURE__ */ jsx(Loading, {});
17
- }
18
16
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
19
17
  /* @__PURE__ */ jsx(RunningBar, {}),
20
18
  /* @__PURE__ */ jsx(Header, {}),
21
19
  /* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
22
- /* @__PURE__ */ jsx("title", { children: `Pinglab ${path}` }),
23
- /* @__PURE__ */ jsx("main", { className: "flex flex-col gap-6 mb-32 mt-32", children: directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory }) }) })
20
+ /* @__PURE__ */ jsx("title", { children: path === "." || path === "" ? config.name : `${config.name} - ${path}` }),
21
+ /* @__PURE__ */ jsxs("main", { className: "flex flex-col gap-8 mb-32 mt-32", children: [
22
+ (path === "." || path === "") && /* @__PURE__ */ jsxs("div", { className: "animate-fade-in", children: [
23
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground", children: config.name }),
24
+ config.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: config.description })
25
+ ] }),
26
+ directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory }) })
27
+ ] })
24
28
  ] })
25
29
  ] });
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{`Pinglab ${path}`}</title>\n <main className=\"flex flex-col gap-6 mb-32 mt-32\">\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory}/>\n </div>\n )}\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AAQO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,EAAE,WAAW,SAAS,MAAA,IAAU,aAAa,IAAI;AAEvD,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAEA,MAAI,SAAS;AACX,+BACG,SAAA,EAAQ;AAAA,EAEb;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,UAAA,WAAW,IAAI,IAAG;AAAA,MAC1B,oBAAC,QAAA,EAAK,WAAU,mCACb,UAAA,aACC,oBAAC,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,UAAA,EAAS,UAAA,CAAqB,GACjC,EAAA,CAEJ;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport siteConfig from \"virtual:veslx-config\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const { directory, loading, error } = useDirectory(path)\n const config = siteConfig;\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{(path === \".\" || path === \"\") ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-32\">\n {(path === \".\" || path === \"\") && (\n <div className=\"animate-fade-in\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n )}\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory}/>\n </div>\n )}\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AASO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,EAAE,WAAoB,UAAU,aAAa,IAAI;AACvD,QAAM,SAAS;AAEf,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAQ,UAAA,SAAS,OAAO,SAAS,KAAM,OAAO,OAAO,GAAG,OAAO,IAAI,MAAM,IAAI,IAAG;AAAA,MACjF,qBAAC,QAAA,EAAK,WAAU,mCACZ,UAAA;AAAA,SAAA,SAAS,OAAO,SAAS,OACzB,qBAAC,OAAA,EAAI,WAAU,mBACb,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,qEACX,UAAA,OAAO,MACV;AAAA,UACC,OAAO,eACN,oBAAC,OAAE,WAAU,8BACV,iBAAO,YAAA,CACV;AAAA,QAAA,GAEJ;AAAA,QAED,iCACE,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,UAAA,EAAS,WAAqB,EAAA,CACjC;AAAA,MAAA,EAAA,CAEJ;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
@@ -2,22 +2,21 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useParams } from "react-router-dom";
3
3
  import { useDirectory, isSimulationRunning, findSlides } from "../plugin/src/client.js";
4
4
  import Loading from "../components/loading.js";
5
- import { FrontMatter } from "../components/front-matter.js";
6
5
  import { RunningBar } from "../components/running-bar.js";
7
6
  import { Header } from "../components/header.js";
8
7
  import { useMDXContent } from "../hooks/use-mdx-content.js";
9
8
  import { mdxComponents } from "../components/mdx-components.js";
10
9
  function Post() {
11
- const { "path": path = "." } = useParams();
12
- const filePath = `${path}/README.mdx`;
13
- const { directory, loading: dirLoading } = useDirectory(filePath);
14
- const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(path);
10
+ const { "*": rawPath = "." } = useParams();
11
+ const mdxPath = rawPath;
12
+ const dirPath = mdxPath.replace(/\/[^/]+\.mdx$/, "") || ".";
13
+ const { directory } = useDirectory(dirPath);
14
+ const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);
15
15
  const isRunning = isSimulationRunning();
16
- let slides = null;
17
16
  if (directory) {
18
- slides = findSlides(directory);
17
+ findSlides(directory);
19
18
  }
20
- const loading = dirLoading || mdxLoading;
19
+ const loading = mdxLoading;
21
20
  if (loading) return /* @__PURE__ */ jsx(Loading, {});
22
21
  if (error) {
23
22
  return /* @__PURE__ */ jsx("main", { className: "min-h-screen bg-background container mx-auto max-w-4xl py-12", children: /* @__PURE__ */ jsx("p", { className: "text-center text-red-600", children: error.message }) });
@@ -32,18 +31,7 @@ function Post() {
32
31
  /* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
33
32
  /* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
34
33
  ] }) }),
35
- Content && /* @__PURE__ */ jsxs("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", children: [
36
- /* @__PURE__ */ jsx(
37
- FrontMatter,
38
- {
39
- title: frontmatter == null ? void 0 : frontmatter.title,
40
- date: frontmatter == null ? void 0 : frontmatter.date,
41
- description: frontmatter == null ? void 0 : frontmatter.description,
42
- slides
43
- }
44
- ),
45
- /* @__PURE__ */ jsx(Content, { components: mdxComponents })
46
- ] })
34
+ Content && /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) })
47
35
  ] })
48
36
  ] });
49
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { FrontMatter } from \"@/components/front-matter\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\n\n\nexport function Post() {\n const { \"path\": path = \".\" } = useParams();\n const filePath = `${path}/README.mdx`\n const { directory, loading: dirLoading } = useDirectory(filePath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(path);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n\n {isRunning && (\n <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\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <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\">\n <FrontMatter\n title={frontmatter?.title}\n date={frontmatter?.date}\n description={frontmatter?.description}\n slides={slides}\n />\n <Content components={mdxComponents} />\n </article>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAWO,SAAS,OAAO;AACrB,QAAM,EAAE,QAAQ,OAAO,IAAA,IAAQ,UAAA;AAC/B,QAAM,WAAW,GAAG,IAAI;AACxB,QAAM,EAAE,WAAW,SAAS,WAAA,IAAe,aAAa,QAAQ;AAChE,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,IAAI;AAC/E,QAAM,YAAY,oBAAA;AAElB,MAAI,SAA2B;AAC/B,MAAI,WAAW;AACb,aAAS,WAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAU,cAAc;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EAEb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,qBAAC,WAAA,EAAQ,WAAU,oMACjB,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,OAAO,2CAAa;AAAA,YACpB,MAAM,2CAAa;AAAA,YACnB,aAAa,2CAAa;AAAA,YAC1B;AAAA,UAAA;AAAA,QAAA;AAAA,QAEF,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe;AAAA,MAAA,EAAA,CACtC;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\n\nexport function Post() {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Extract directory path for finding sibling files (slides, etc.)\n const dirPath = mdxPath.replace(/\\/[^/]+\\.mdx$/, '') || '.';\n\n const { directory, loading: dirLoading } = useDirectory(dirPath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n {isRunning && (\n <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\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <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\">\n <Content components={mdxComponents} />\n </article>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AASO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU;AAGhB,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,EAAE,KAAK;AAExD,QAAM,EAAE,UAA+B,IAAI,aAAa,OAAO;AAC/D,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,OAAO;AAClF,QAAM,YAAY,oBAAA;AAGlB,MAAI,WAAW;AACJ,eAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAwB;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,+BACE,WAAA,EAAQ,WAAU,oMACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,EAAA,CACtC;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
@@ -1,45 +1,79 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useState, useRef, useCallback, useEffect } from "react";
2
+ import { useState, useRef, useEffect, useCallback } from "react";
3
3
  import { FULLSCREEN_DATA_ATTR } from "../lib/constants.js";
4
4
  import { useParams, useSearchParams } from "react-router-dom";
5
5
  import Loading from "../components/loading.js";
6
- import { FrontMatter } from "../components/front-matter.js";
7
6
  import { RunningBar } from "../components/running-bar.js";
8
7
  import { Header } from "../components/header.js";
9
8
  import { useMDXSlides } from "../hooks/use-mdx-content.js";
10
- import { useSlidesFromMDX, SlideContent } from "../components/slides-renderer.js";
9
+ import { slidesMdxComponents } from "../components/slides-renderer.js";
11
10
  function SlidesPage() {
12
- const { "path": path = "." } = useParams();
11
+ const { "*": rawPath = "." } = useParams();
13
12
  const [searchParams, setSearchParams] = useSearchParams();
14
- const { Content, frontmatter, loading, error } = useMDXSlides(path);
15
- const { slides } = useSlidesFromMDX({ Content, frontmatter });
16
- const totalSlides = slides.length + 1;
17
- const initialSlide = Math.max(0, Math.min(
18
- parseInt(searchParams.get("slide") || "0", 10),
19
- totalSlides - 1
20
- ));
21
- const [currentSlide, setCurrentSlide] = useState(initialSlide);
22
- const slideRefs = useRef([]);
23
- const goToSlide = useCallback((index) => {
24
- const clampedIndex = Math.max(0, Math.min(index, totalSlides - 1));
25
- const target = slideRefs.current[clampedIndex];
26
- if (target) {
27
- const targetTop = target.getBoundingClientRect().top + window.scrollY;
28
- window.scrollTo({ top: targetTop, behavior: "smooth" });
13
+ const mdxPath = rawPath;
14
+ const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);
15
+ const totalSlides = (slideCount || 0) + 1;
16
+ const [currentSlide, setCurrentSlide] = useState(0);
17
+ const titleSlideRef = useRef(null);
18
+ const contentRef = useRef(null);
19
+ useEffect(() => {
20
+ const slideParam = parseInt(searchParams.get("slide") || "0", 10);
21
+ if (slideParam > 0 && contentRef.current) {
22
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${slideParam - 1}"]`);
23
+ if (slideEl) {
24
+ slideEl.scrollIntoView({ behavior: "auto" });
25
+ }
29
26
  }
30
- }, [totalSlides]);
27
+ }, [searchParams, Content]);
28
+ useEffect(() => {
29
+ const observer = new IntersectionObserver(
30
+ (entries) => {
31
+ for (const entry of entries) {
32
+ if (entry.isIntersecting) {
33
+ const index = entry.target.getAttribute("data-slide-index");
34
+ if (index !== null) {
35
+ const slideNum = index === "title" ? 0 : parseInt(index, 10) + 1;
36
+ setCurrentSlide(slideNum);
37
+ setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });
38
+ }
39
+ }
40
+ }
41
+ },
42
+ { threshold: 0.5 }
43
+ );
44
+ if (titleSlideRef.current) {
45
+ observer.observe(titleSlideRef.current);
46
+ }
47
+ if (contentRef.current) {
48
+ const slides = contentRef.current.querySelectorAll("[data-slide-index]");
49
+ slides.forEach((slide) => observer.observe(slide));
50
+ }
51
+ return () => observer.disconnect();
52
+ }, [Content, setSearchParams]);
31
53
  const goToPrevious = useCallback(() => {
32
- goToSlide(currentSlide - 1);
33
- }, [currentSlide, goToSlide]);
54
+ const prev = Math.max(0, currentSlide - 1);
55
+ if (prev === 0 && titleSlideRef.current) {
56
+ titleSlideRef.current.scrollIntoView({ behavior: "smooth" });
57
+ } else if (contentRef.current) {
58
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${prev - 1}"]`);
59
+ slideEl == null ? void 0 : slideEl.scrollIntoView({ behavior: "smooth" });
60
+ }
61
+ }, [currentSlide]);
34
62
  const goToNext = useCallback(() => {
35
- goToSlide(currentSlide + 1);
36
- }, [currentSlide, goToSlide]);
63
+ const next = Math.min(totalSlides - 1, currentSlide + 1);
64
+ if (next === 0 && titleSlideRef.current) {
65
+ titleSlideRef.current.scrollIntoView({ behavior: "smooth" });
66
+ } else if (contentRef.current) {
67
+ const slideEl = contentRef.current.querySelector(`[data-slide-index="${next - 1}"]`);
68
+ slideEl == null ? void 0 : slideEl.scrollIntoView({ behavior: "smooth" });
69
+ }
70
+ }, [currentSlide, totalSlides]);
37
71
  useEffect(() => {
38
72
  const handleKeyDown = (e) => {
39
- if (e.key === "ArrowUp" || e.key === "k") {
73
+ if (e.key === "ArrowUp" || e.key === "ArrowLeft" || e.key === "k") {
40
74
  e.preventDefault();
41
75
  goToPrevious();
42
- } else if (e.key === "ArrowDown" || e.key === "j") {
76
+ } else if (e.key === "ArrowDown" || e.key === "ArrowRight" || e.key === "j") {
43
77
  e.preventDefault();
44
78
  goToNext();
45
79
  }
@@ -47,39 +81,13 @@ function SlidesPage() {
47
81
  window.addEventListener("keydown", handleKeyDown);
48
82
  return () => window.removeEventListener("keydown", handleKeyDown);
49
83
  }, [goToPrevious, goToNext]);
50
- useEffect(() => {
51
- let observer = null;
52
- const timeoutId = setTimeout(() => {
53
- observer = new IntersectionObserver(
54
- (entries) => {
55
- for (const entry of entries) {
56
- if (entry.isIntersecting) {
57
- const index = slideRefs.current.findIndex((ref) => ref === entry.target);
58
- if (index !== -1) {
59
- setCurrentSlide(index);
60
- setSearchParams(index > 0 ? { slide: String(index) } : {}, { replace: true });
61
- }
62
- }
63
- }
64
- },
65
- { threshold: 0.5 }
66
- );
67
- slideRefs.current.forEach((ref) => {
68
- if (ref) observer.observe(ref);
69
- });
70
- }, 100);
71
- return () => {
72
- clearTimeout(timeoutId);
73
- observer == null ? void 0 : observer.disconnect();
74
- };
75
- }, [slides.length, setSearchParams]);
76
84
  if (loading) {
77
85
  return /* @__PURE__ */ jsx(Loading, {});
78
86
  }
79
87
  if (error) {
80
88
  return /* @__PURE__ */ jsx("main", { className: "min-h-screen bg-background container mx-auto max-w-4xl py-12", children: /* @__PURE__ */ jsx("p", { className: "text-center text-red-600", children: error.message }) });
81
89
  }
82
- if (slides.length === 0) {
90
+ if (!Content) {
83
91
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-12 text-muted-foreground font-mono text-sm", children: 'no slides found — use "---" to separate slides' });
84
92
  }
85
93
  return /* @__PURE__ */ jsxs("main", { className: "slides-container", children: [
@@ -96,39 +104,7 @@ function SlidesPage() {
96
104
  }
97
105
  }
98
106
  ),
99
- /* @__PURE__ */ jsxs("div", { ...{ [FULLSCREEN_DATA_ATTR]: "true" }, children: [
100
- /* @__PURE__ */ jsx(
101
- "div",
102
- {
103
- ref: (el) => {
104
- slideRefs.current[0] = el;
105
- },
106
- 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",
107
- children: /* @__PURE__ */ jsx(
108
- FrontMatter,
109
- {
110
- title: frontmatter == null ? void 0 : frontmatter.title,
111
- date: frontmatter == null ? void 0 : frontmatter.date,
112
- description: frontmatter == null ? void 0 : frontmatter.description
113
- }
114
- )
115
- }
116
- ),
117
- /* @__PURE__ */ jsx("hr", { className: "print:hidden" }),
118
- slides.map((slideContent, index) => /* @__PURE__ */ jsxs("div", { children: [
119
- /* @__PURE__ */ jsx(
120
- "div",
121
- {
122
- ref: (el) => {
123
- slideRefs.current[index + 1] = el;
124
- },
125
- 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",
126
- children: /* @__PURE__ */ jsx(SlideContent, { children: slideContent })
127
- }
128
- ),
129
- index < slides.length - 1 && /* @__PURE__ */ jsx("hr", { className: "print:hidden" })
130
- ] }, index))
131
- ] })
107
+ /* @__PURE__ */ jsx("div", { ...{ [FULLSCREEN_DATA_ATTR]: "true" }, children: /* @__PURE__ */ jsx("div", { ref: contentRef, children: /* @__PURE__ */ jsx(Content, { components: slidesMdxComponents }) }) })
132
108
  ] });
133
109
  }
134
110
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"slides.js","sources":["../../../src/pages/slides.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\nimport { useParams, useSearchParams } from \"react-router-dom\"\nimport Loading from \"@/components/loading\";\nimport { FrontMatter } from \"@/components/front-matter\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { useSlidesFromMDX, SlideContent } from \"@/components/slides-renderer\";\n\n\nexport function SlidesPage() {\n const { \"path\": path = \".\" } = useParams();\n const [searchParams, setSearchParams] = useSearchParams();\n\n // Load the compiled MDX module\n const { Content, frontmatter, loading, error } = useMDXSlides(path);\n\n // Split the MDX content into slides by <hr> elements\n const { slides } = useSlidesFromMDX({ Content, frontmatter });\n\n // Total slides = 1 (title) + content slides\n const totalSlides = slides.length + 1;\n\n // Get initial slide from query param\n const initialSlide = Math.max(0, Math.min(\n parseInt(searchParams.get(\"slide\") || \"0\", 10),\n totalSlides - 1\n ));\n\n const [currentSlide, setCurrentSlide] = useState(initialSlide);\n const slideRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n // Scroll to slide and update query param\n const goToSlide = useCallback((index: number) => {\n const clampedIndex = Math.max(0, Math.min(index, totalSlides - 1));\n const target = slideRefs.current[clampedIndex];\n if (target) {\n const targetTop = target.getBoundingClientRect().top + window.scrollY;\n window.scrollTo({ top: targetTop, behavior: \"smooth\" });\n }\n }, [totalSlides]);\n\n const goToPrevious = useCallback(() => {\n goToSlide(currentSlide - 1);\n }, [currentSlide, goToSlide]);\n\n const goToNext = useCallback(() => {\n goToSlide(currentSlide + 1);\n }, [currentSlide, goToSlide]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\" || e.key === \"k\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\" || e.key === \"j\") {\n e.preventDefault();\n goToNext();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [goToPrevious, goToNext]);\n\n // Update query param on scroll (delayed to avoid interference on load)\n useEffect(() => {\n let observer: IntersectionObserver | null = null;\n\n const timeoutId = setTimeout(() => {\n observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const index = slideRefs.current.findIndex((ref) => ref === entry.target);\n if (index !== -1) {\n setCurrentSlide(index);\n setSearchParams(index > 0 ? { slide: String(index) } : {}, { replace: true });\n }\n }\n }\n },\n { threshold: 0.5 }\n );\n\n slideRefs.current.forEach((ref) => {\n if (ref) observer!.observe(ref);\n });\n }, 100);\n\n return () => {\n clearTimeout(timeoutId);\n observer?.disconnect();\n };\n }, [slides.length, setSearchParams]);\n\n if (loading) {\n return <Loading />\n }\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n if (slides.length === 0) {\n return (\n <div className=\"flex items-center justify-center p-12 text-muted-foreground font-mono text-sm\">\n no slides found — use \"---\" to separate slides\n </div>\n );\n }\n\n return (\n <main className=\"slides-container\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header\n slideControls={{\n current: currentSlide,\n total: totalSlides,\n onPrevious: goToPrevious,\n onNext: goToNext,\n }}\n />\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n {/* Title slide */}\n <div\n ref={(el) => { slideRefs.current[0] = el; }}\n 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\"\n >\n <FrontMatter\n title={frontmatter?.title}\n date={frontmatter?.date}\n description={frontmatter?.description}\n />\n </div>\n <hr className=\"print:hidden\" />\n\n {/* Content slides */}\n {slides.map((slideContent, index) => (\n <div key={index}>\n <div\n ref={(el) => { slideRefs.current[index + 1] = el; }}\n 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\"\n >\n <SlideContent>{slideContent}</SlideContent>\n </div>\n {index < slides.length - 1 && <hr className=\"print:hidden\" />}\n </div>\n ))}\n </div>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slides.js","sources":["../../../src/pages/slides.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { FULLSCREEN_DATA_ATTR } from \"@/lib/constants\";\nimport { useParams, useSearchParams } from \"react-router-dom\"\nimport Loading from \"@/components/loading\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { slidesMdxComponents } from \"@/components/slides-renderer\";\n\n\nexport function SlidesPage() {\n const { \"*\": rawPath = \".\" } = useParams();\n const [searchParams, setSearchParams] = useSearchParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Load the compiled MDX module (now includes slideCount export)\n const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);\n\n // Total slides = 1 (title) + content slides\n const totalSlides = (slideCount || 0) + 1;\n\n const [currentSlide, setCurrentSlide] = useState(0);\n const titleSlideRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n\n // Scroll to slide on initial load if query param is set\n useEffect(() => {\n const slideParam = parseInt(searchParams.get(\"slide\") || \"0\", 10);\n if (slideParam > 0 && contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${slideParam - 1}\"]`);\n if (slideEl) {\n slideEl.scrollIntoView({ behavior: \"auto\" });\n }\n }\n }, [searchParams, Content]);\n\n // Track current slide based on scroll position\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n const index = entry.target.getAttribute(\"data-slide-index\");\n if (index !== null) {\n const slideNum = index === \"title\" ? 0 : parseInt(index, 10) + 1;\n setCurrentSlide(slideNum);\n setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });\n }\n }\n }\n },\n { threshold: 0.5 }\n );\n\n // Observe title slide\n if (titleSlideRef.current) {\n observer.observe(titleSlideRef.current);\n }\n\n // Observe content slides\n if (contentRef.current) {\n const slides = contentRef.current.querySelectorAll(\"[data-slide-index]\");\n slides.forEach((slide) => observer.observe(slide));\n }\n\n return () => observer.disconnect();\n }, [Content, setSearchParams]);\n\n // Keyboard/scroll navigation helpers\n const goToPrevious = useCallback(() => {\n const prev = Math.max(0, currentSlide - 1);\n if (prev === 0 && titleSlideRef.current) {\n titleSlideRef.current.scrollIntoView({ behavior: \"smooth\" });\n } else if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${prev - 1}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide]);\n\n const goToNext = useCallback(() => {\n const next = Math.min(totalSlides - 1, currentSlide + 1);\n if (next === 0 && titleSlideRef.current) {\n titleSlideRef.current.scrollIntoView({ behavior: \"smooth\" });\n } else if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${next - 1}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide, totalSlides]);\n\n // Keyboard navigation\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\" || e.key === \"k\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\" || e.key === \"j\") {\n e.preventDefault();\n goToNext();\n }\n };\n\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [goToPrevious, goToNext]);\n\n if (loading) {\n return <Loading />\n }\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n if (!Content) {\n return (\n <div className=\"flex items-center justify-center p-12 text-muted-foreground font-mono text-sm\">\n no slides found — use \"---\" to separate slides\n </div>\n );\n }\n\n return (\n <main className=\"slides-container\">\n <title>{frontmatter?.title}</title>\n <RunningBar />\n <Header\n slideControls={{\n current: currentSlide,\n total: totalSlides,\n onPrevious: goToPrevious,\n onNext: goToNext,\n }}\n />\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n <div ref={contentRef}>\n <Content components={slidesMdxComponents} />\n </div>\n </div>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}