veslx 0.1.21 → 0.1.22

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 (34) hide show
  1. package/bin/lib/serve.ts +16 -2
  2. package/bin/lib/start.ts +12 -10
  3. package/bin/veslx.ts +1 -1
  4. package/dist/client/components/front-matter.js +2 -6
  5. package/dist/client/components/front-matter.js.map +1 -1
  6. package/dist/client/components/gallery/index.js +1 -1
  7. package/dist/client/components/gallery/index.js.map +1 -1
  8. package/dist/client/hooks/use-mdx-content.js +19 -6
  9. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  10. package/dist/client/lib/content-classification.js +11 -2
  11. package/dist/client/lib/content-classification.js.map +1 -1
  12. package/dist/client/lib/frontmatter-context.js +17 -0
  13. package/dist/client/lib/frontmatter-context.js.map +1 -0
  14. package/dist/client/pages/content-router.js +4 -1
  15. package/dist/client/pages/content-router.js.map +1 -1
  16. package/dist/client/pages/post.js +2 -9
  17. package/dist/client/pages/post.js.map +1 -1
  18. package/dist/client/pages/slides.js +2 -1
  19. package/dist/client/pages/slides.js.map +1 -1
  20. package/dist/client/plugin/src/client.js +20 -2
  21. package/dist/client/plugin/src/client.js.map +1 -1
  22. package/package.json +1 -1
  23. package/plugin/src/client.tsx +28 -2
  24. package/plugin/src/plugin.ts +49 -4
  25. package/src/components/front-matter.tsx +3 -8
  26. package/src/components/gallery/index.tsx +1 -1
  27. package/src/hooks/use-mdx-content.ts +27 -6
  28. package/src/index.css +1 -2
  29. package/src/lib/content-classification.ts +13 -2
  30. package/src/lib/frontmatter-context.tsx +29 -0
  31. package/src/pages/content-router.tsx +7 -1
  32. package/src/pages/post.tsx +6 -24
  33. package/src/pages/slides.tsx +7 -4
  34. package/vite.config.ts +4 -3
package/bin/lib/serve.ts CHANGED
@@ -57,7 +57,21 @@ export default async function serve(dir?: string) {
57
57
  // Get defaults first, then merge with config file if it exists
58
58
  // Look for config in content directory first, then fall back to cwd
59
59
  const defaults = await getDefaultConfig(contentDir);
60
- const fileConfig = await importConfig(contentDir) || await importConfig(cwd);
60
+
61
+ // Track which config file was found for hot reload
62
+ let configPath: string | undefined;
63
+ const contentConfigPath = path.join(contentDir, 'veslx.yaml');
64
+ const cwdConfigPath = path.join(cwd, 'veslx.yaml');
65
+
66
+ let fileConfig = await importConfig(contentDir);
67
+ if (fileConfig) {
68
+ configPath = contentConfigPath;
69
+ } else {
70
+ fileConfig = await importConfig(cwd);
71
+ if (fileConfig) {
72
+ configPath = cwdConfigPath;
73
+ }
74
+ }
61
75
 
62
76
  // CLI argument takes precedence over config file
63
77
  const config = {
@@ -82,7 +96,7 @@ export default async function serve(dir?: string) {
82
96
  // Cache in user's project so it persists across bunx runs
83
97
  cacheDir: path.join(cwd, 'node_modules/.vite'),
84
98
  plugins: [
85
- veslxPlugin(finalContentDir, config)
99
+ veslxPlugin(finalContentDir, config, { configPath })
86
100
  ],
87
101
  })
88
102
 
package/bin/lib/start.ts CHANGED
@@ -1,17 +1,19 @@
1
- import importConfig from "./import-config"
2
1
  import pm2 from "pm2";
2
+ import path from "path";
3
3
  import { log } from './log'
4
4
 
5
- export default async function start() {
6
- const config = await importConfig(process.cwd());
5
+ export default async function start(dir?: string) {
6
+ const cwd = process.cwd();
7
7
 
8
- if (!config) {
9
- log.error("veslx.yaml not found");
10
- return
11
- }
8
+ // Resolve content directory the same way as serve
9
+ const contentDir = dir
10
+ ? (path.isAbsolute(dir) ? dir : path.resolve(cwd, dir))
11
+ : cwd;
12
12
 
13
- const cwd = process.cwd();
14
- const name = `veslx-${cwd.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
13
+ const name = `veslx-${contentDir.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
14
+
15
+ // Build args for veslx serve
16
+ const args = dir ? ['veslx', 'serve', dir] : ['veslx', 'serve'];
15
17
 
16
18
  pm2.connect((err) => {
17
19
  if (err) {
@@ -22,7 +24,7 @@ export default async function start() {
22
24
  pm2.start({
23
25
  name: name,
24
26
  script: 'bunx',
25
- args: ['veslx', 'serve'],
27
+ args: args,
26
28
  cwd: cwd,
27
29
  autorestart: true,
28
30
  watch: false,
package/bin/veslx.ts CHANGED
@@ -22,7 +22,7 @@ cli
22
22
  .action(serve);
23
23
 
24
24
  cli
25
- .command("start", "Start the veslx server as a deamon")
25
+ .command("start [dir]", "Start the veslx server as a daemon")
26
26
  .action(start);
27
27
 
28
28
  cli
@@ -1,12 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useMDXContent, useMDXSlides } from "../hooks/use-mdx-content.js";
2
+ import { useFrontmatter } from "../lib/frontmatter-context.js";
3
3
  import { formatDate } from "../lib/format-date.js";
4
- import { useParams } from "react-router-dom";
5
4
  function FrontMatter() {
6
- const { "path": path = "." } = useParams();
7
- const { frontmatter: readmeFm } = useMDXContent(path);
8
- const { frontmatter: slidesFm } = useMDXSlides(path);
9
- let frontmatter = readmeFm || slidesFm;
5
+ const frontmatter = useFrontmatter();
10
6
  return /* @__PURE__ */ jsx("div", { children: (frontmatter == null ? void 0 : frontmatter.title) && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
11
7
  /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: frontmatter == null ? void 0 : frontmatter.title }),
12
8
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }) }),
@@ -1 +1 @@
1
- {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useMDXContent, useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { formatDate } from \"@/lib/format-date\"\nimport { useParams } from \"react-router-dom\"\n\nexport function FrontMatter(){\n const { \"path\": path = \".\" } = useParams();\n const { frontmatter: readmeFm } = useMDXContent(path);\n const { frontmatter: slidesFm } = useMDXSlides(path);\n\n let frontmatter = readmeFm || slidesFm;\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}"],"names":[],"mappings":";;;;AAIO,SAAS,cAAa;AAC3B,QAAM,EAAE,QAAQ,OAAO,IAAA,IAAQ,UAAA;AAC/B,QAAM,EAAE,aAAa,aAAa,cAAc,IAAI;AACpD,QAAM,EAAE,aAAa,aAAa,aAAa,IAAI;AAEnD,MAAI,cAAc,YAAY;AAE9B,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
1
+ {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useFrontmatter } from \"@/lib/frontmatter-context\";\nimport { formatDate } from \"@/lib/format-date\"\n\nexport function FrontMatter(){\n const frontmatter = useFrontmatter();\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAGO,SAAS,cAAa;AAC3B,QAAM,cAAc,eAAA;AAEpB,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
@@ -67,7 +67,7 @@ function Gallery({
67
67
  {
68
68
  className: `pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? "flex-none" : ""}`,
69
69
  onClick: () => lightbox.open(index),
70
- children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10", children: /* @__PURE__ */ jsx(
70
+ children: /* @__PURE__ */ jsx("div", { className: "aspect-square overflow-hidden rounded-sm bg-muted/10", children: /* @__PURE__ */ jsx(
71
71
  LoadingImage,
72
72
  {
73
73
  src: img.src,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/components/ui/carousel\"\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null, \n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n return (\n <>\n <figure className=\"not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel className=\"w-full\">\n <CarouselContent className={`-ml-2 md:-ml-3 ${images.length < 3 ? 'justify-center' : ''}`}>\n {images.map((img, index) => (\n <CarouselItem\n key={index}\n className={`pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? 'flex-none' : ''}`}\n onClick={() => lightbox.open(index)}\n >\n <div className=\"aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10\">\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious className=\"left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n <CarouselNext className=\"right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n </>\n )}\n </Carousel>\n\n <FigureCaption caption={caption} label={captionLabel} />\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\nimport {\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n} from \"@/components/ui/carousel\"\n\nfunction getImageLabel(path: string): string {\n const filename = path.split('/').pop() || path;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction getImageUrl(path: string): string {\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null, \n caption,\n captionLabel,\n title,\n subtitle,\n limit = null,\n page = 0,\n}: {\n path?: string;\n globs?: string[] | null;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n limit?: number | null;\n page?: number;\n}) {\n const { paths, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n\n const images: LightboxImage[] = useMemo(() =>\n paths.map(p => ({ src: getImageUrl(p), label: getImageLabel(p) })),\n [paths]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\">\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\">\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n return (\n <>\n <figure className=\"not-prose relative py-6 md:py-8 -mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))]\">\n <FigureHeader title={title} subtitle={subtitle} />\n\n <Carousel className=\"w-full\">\n <CarouselContent className={`-ml-2 md:-ml-3 ${images.length < 3 ? 'justify-center' : ''}`}>\n {images.map((img, index) => (\n <CarouselItem\n key={index}\n className={`pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? 'flex-none' : ''}`}\n onClick={() => lightbox.open(index)}\n >\n <div className=\"aspect-square overflow-hidden rounded-sm bg-muted/10\">\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n\n {images.length > 3 && (\n <>\n <CarouselPrevious className=\"left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n <CarouselNext className=\"right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border\" />\n </>\n )}\n </Carousel>\n\n <FigureCaption caption={caption} label={captionLabel} />\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -66,16 +66,29 @@ function useMDXContent(path) {
66
66
  }
67
67
  function findSlidesModule(modules, path) {
68
68
  const keys = Object.keys(modules);
69
- if (path.endsWith(".mdx")) {
70
- const matchingKey = keys.find((key) => key.endsWith(`/${path}`));
69
+ const normalizedPath = path.replace(/^\//, "");
70
+ if (normalizedPath.endsWith(".mdx")) {
71
+ const matchingKey = keys.find((key) => {
72
+ if (key.endsWith(`/${normalizedPath}`)) return true;
73
+ if (key === `@content/${normalizedPath}`) return true;
74
+ if (key === `/@content/${normalizedPath}`) return true;
75
+ if (key === normalizedPath) return true;
76
+ if (key === `/${normalizedPath}`) return true;
77
+ return false;
78
+ });
71
79
  return matchingKey ? modules[matchingKey] : null;
72
80
  }
73
81
  const candidates = [
74
- `/${path}/SLIDES.mdx`,
75
- `/${path}/index.slides.mdx`
82
+ `${normalizedPath}/SLIDES.mdx`,
83
+ `${normalizedPath}/index.slides.mdx`
76
84
  ];
77
- for (const suffix of candidates) {
78
- const matchingKey = keys.find((key) => key.endsWith(suffix));
85
+ for (const candidate of candidates) {
86
+ const matchingKey = keys.find((key) => {
87
+ if (key.endsWith(`/${candidate}`)) return true;
88
+ if (key === `@content/${candidate}`) return true;
89
+ if (key === candidate) return true;
90
+ return false;
91
+ });
79
92
  if (matchingKey) {
80
93
  return modules[matchingKey];
81
94
  }
@@ -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 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
+ {"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 // 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.slides.mdx matches docs/foo.slides.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:\n // 1. folder/SLIDES.mdx (current convention)\n // 2. folder/index.slides.mdx (alternative)\n const candidates = [\n `${normalizedPath}/SLIDES.mdx`,\n `${normalizedPath}/index.slides.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 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,4 +1,4 @@
1
- import { findMdxFiles, findSlides, findReadme } from "../plugin/src/client.js";
1
+ import { findMdxFiles, findStandaloneSlides, findSlides, findReadme } from "../plugin/src/client.js";
2
2
  function getFrontmatter(post) {
3
3
  var _a, _b, _c;
4
4
  return ((_a = post.readme) == null ? void 0 : _a.frontmatter) || ((_b = post.file) == null ? void 0 : _b.frontmatter) || ((_c = post.slides) == null ? void 0 : _c.frontmatter);
@@ -24,6 +24,7 @@ function getViewCounts(posts) {
24
24
  function directoryToPostEntries(directory) {
25
25
  const folders = directory.children.filter((c) => c.type === "directory");
26
26
  const standaloneFiles = findMdxFiles(directory);
27
+ const standaloneSlidesFiles = findStandaloneSlides(directory);
27
28
  const folderPosts = folders.map((folder) => ({
28
29
  type: "folder",
29
30
  name: folder.name,
@@ -40,7 +41,15 @@ function directoryToPostEntries(directory) {
40
41
  slides: null,
41
42
  file
42
43
  }));
43
- return [...folderPosts, ...filePosts];
44
+ const slidesPosts = standaloneSlidesFiles.map((file) => ({
45
+ type: "file",
46
+ name: file.name.replace(/\.slides\.mdx?$/, ""),
47
+ path: file.path,
48
+ readme: null,
49
+ slides: file,
50
+ file: null
51
+ }));
52
+ return [...folderPosts, ...filePosts, ...slidesPosts];
44
53
  }
45
54
  function filterVisiblePosts(posts) {
46
55
  return posts.filter((post) => {
@@ -1 +1 @@
1
- {"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type { ContentView } from \"../../plugin/src/types\";\nimport type { DirectoryEntry, FileEntry } from \"../../plugin/src/lib\";\nimport { findReadme, findSlides, findMdxFiles } from \"../../plugin/src/client\";\n\nexport type PostEntry = {\n type: 'folder' | 'file';\n name: string;\n path: string;\n readme: FileEntry | null;\n slides: FileEntry | null;\n file: FileEntry | null;\n};\n\nexport function getFrontmatter(post: PostEntry) {\n return post.readme?.frontmatter || post.file?.frontmatter || post.slides?.frontmatter;\n}\n\nexport function hasDate(post: PostEntry): boolean {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.date !== undefined && frontmatter.date !== null && frontmatter.date !== '';\n}\n\nexport function filterByView(posts: PostEntry[], view: ContentView): PostEntry[] {\n if (view === 'all') return posts;\n if (view === 'posts') return posts.filter(hasDate);\n if (view === 'docs') return posts.filter(post => !hasDate(post));\n return posts;\n}\n\nexport function getViewCounts(posts: PostEntry[]): { posts: number; docs: number; all: number } {\n const dated = posts.filter(hasDate).length;\n return {\n posts: dated,\n docs: posts.length - dated,\n all: posts.length,\n };\n}\n\nexport function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {\n const folders = directory.children.filter((c): c is DirectoryEntry => c.type === \"directory\");\n const standaloneFiles = findMdxFiles(directory);\n\n const folderPosts: PostEntry[] = folders\n .map((folder) => ({\n type: 'folder' as const,\n name: folder.name,\n path: folder.path,\n readme: findReadme(folder),\n slides: findSlides(folder),\n file: null,\n }))\n .filter((post) => post.readme || post.slides); // Only include folders with content\n\n const filePosts: PostEntry[] = standaloneFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n return [...folderPosts, ...filePosts];\n}\n\nexport function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {\n return posts.filter((post) => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.visibility !== \"hidden\" && frontmatter?.draft !== true;\n });\n}\n\nexport type { ContentView };\n"],"names":[],"mappings":";AAaO,SAAS,eAAe,MAAiB;;AAC9C,WAAO,UAAK,WAAL,mBAAa,kBAAe,UAAK,SAAL,mBAAW,kBAAe,UAAK,WAAL,mBAAa;AAC5E;AAEO,SAAS,QAAQ,MAA0B;AAChD,QAAM,cAAc,eAAe,IAAI;AACvC,UAAO,2CAAa,UAAS,UAAa,YAAY,SAAS,QAAQ,YAAY,SAAS;AAC9F;AAEO,SAAS,aAAa,OAAoB,MAAgC;AAC/E,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,SAAS,QAAS,QAAO,MAAM,OAAO,OAAO;AACjD,MAAI,SAAS,OAAQ,QAAO,MAAM,OAAO,CAAA,SAAQ,CAAC,QAAQ,IAAI,CAAC;AAC/D,SAAO;AACT;AAEO,SAAS,cAAc,OAAkE;AAC9F,QAAM,QAAQ,MAAM,OAAO,OAAO,EAAE;AACpC,SAAO;AAAA,IACL,OAAO;AAAA,IACP,MAAM,MAAM,SAAS;AAAA,IACrB,KAAK,MAAM;AAAA,EAAA;AAEf;AAEO,SAAS,uBAAuB,WAAwC;AAC7E,QAAM,UAAU,UAAU,SAAS,OAAO,CAAC,MAA2B,EAAE,SAAS,WAAW;AAC5F,QAAM,kBAAkB,aAAa,SAAS;AAE9C,QAAM,cAA2B,QAC9B,IAAI,CAAC,YAAY;AAAA,IAChB,MAAM;AAAA,IACN,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,QAAQ,WAAW,MAAM;AAAA,IACzB,QAAQ,WAAW,MAAM;AAAA,IACzB,MAAM;AAAA,EAAA,EACN,EACD,OAAO,CAAC,SAAS,KAAK,UAAU,KAAK,MAAM;AAE9C,QAAM,YAAyB,gBAAgB,IAAI,CAAC,UAAU;AAAA,IAC5D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAEF,SAAO,CAAC,GAAG,aAAa,GAAG,SAAS;AACtC;AAEO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,YAAO,2CAAa,gBAAe,aAAY,2CAAa,WAAU;AAAA,EACxE,CAAC;AACH;"}
1
+ {"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type { ContentView } from \"../../plugin/src/types\";\nimport type { DirectoryEntry, FileEntry } from \"../../plugin/src/lib\";\nimport { findReadme, findSlides, findMdxFiles, findStandaloneSlides } from \"../../plugin/src/client\";\n\nexport type PostEntry = {\n type: 'folder' | 'file';\n name: string;\n path: string;\n readme: FileEntry | null;\n slides: FileEntry | null;\n file: FileEntry | null;\n};\n\nexport function getFrontmatter(post: PostEntry) {\n return post.readme?.frontmatter || post.file?.frontmatter || post.slides?.frontmatter;\n}\n\nexport function hasDate(post: PostEntry): boolean {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.date !== undefined && frontmatter.date !== null && frontmatter.date !== '';\n}\n\nexport function filterByView(posts: PostEntry[], view: ContentView): PostEntry[] {\n if (view === 'all') return posts;\n if (view === 'posts') return posts.filter(hasDate);\n if (view === 'docs') return posts.filter(post => !hasDate(post));\n return posts;\n}\n\nexport function getViewCounts(posts: PostEntry[]): { posts: number; docs: number; all: number } {\n const dated = posts.filter(hasDate).length;\n return {\n posts: dated,\n docs: posts.length - dated,\n all: posts.length,\n };\n}\n\nexport function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {\n const folders = directory.children.filter((c): c is DirectoryEntry => c.type === \"directory\");\n const standaloneFiles = findMdxFiles(directory);\n const standaloneSlidesFiles = findStandaloneSlides(directory);\n\n const folderPosts: PostEntry[] = folders\n .map((folder) => ({\n type: 'folder' as const,\n name: folder.name,\n path: folder.path,\n readme: findReadme(folder),\n slides: findSlides(folder),\n file: null,\n }))\n .filter((post) => post.readme || post.slides); // Only include folders with content\n\n const filePosts: PostEntry[] = standaloneFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n // Standalone slides files (e.g., getting-started.slides.mdx)\n const slidesPosts: PostEntry[] = standaloneSlidesFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.slides\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: file,\n file: null,\n }));\n\n return [...folderPosts, ...filePosts, ...slidesPosts];\n}\n\nexport function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {\n return posts.filter((post) => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.visibility !== \"hidden\" && frontmatter?.draft !== true;\n });\n}\n\nexport type { ContentView };\n"],"names":[],"mappings":";AAaO,SAAS,eAAe,MAAiB;;AAC9C,WAAO,UAAK,WAAL,mBAAa,kBAAe,UAAK,SAAL,mBAAW,kBAAe,UAAK,WAAL,mBAAa;AAC5E;AAEO,SAAS,QAAQ,MAA0B;AAChD,QAAM,cAAc,eAAe,IAAI;AACvC,UAAO,2CAAa,UAAS,UAAa,YAAY,SAAS,QAAQ,YAAY,SAAS;AAC9F;AAEO,SAAS,aAAa,OAAoB,MAAgC;AAC/E,MAAI,SAAS,MAAO,QAAO;AAC3B,MAAI,SAAS,QAAS,QAAO,MAAM,OAAO,OAAO;AACjD,MAAI,SAAS,OAAQ,QAAO,MAAM,OAAO,CAAA,SAAQ,CAAC,QAAQ,IAAI,CAAC;AAC/D,SAAO;AACT;AAEO,SAAS,cAAc,OAAkE;AAC9F,QAAM,QAAQ,MAAM,OAAO,OAAO,EAAE;AACpC,SAAO;AAAA,IACL,OAAO;AAAA,IACP,MAAM,MAAM,SAAS;AAAA,IACrB,KAAK,MAAM;AAAA,EAAA;AAEf;AAEO,SAAS,uBAAuB,WAAwC;AAC7E,QAAM,UAAU,UAAU,SAAS,OAAO,CAAC,MAA2B,EAAE,SAAS,WAAW;AAC5F,QAAM,kBAAkB,aAAa,SAAS;AAC9C,QAAM,wBAAwB,qBAAqB,SAAS;AAE5D,QAAM,cAA2B,QAC9B,IAAI,CAAC,YAAY;AAAA,IAChB,MAAM;AAAA,IACN,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,QAAQ,WAAW,MAAM;AAAA,IACzB,QAAQ,WAAW,MAAM;AAAA,IACzB,MAAM;AAAA,EAAA,EACN,EACD,OAAO,CAAC,SAAS,KAAK,UAAU,KAAK,MAAM;AAE9C,QAAM,YAAyB,gBAAgB,IAAI,CAAC,UAAU;AAAA,IAC5D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAGF,QAAM,cAA2B,sBAAsB,IAAI,CAAC,UAAU;AAAA,IACpE,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,mBAAmB,EAAE;AAAA,IAC7C,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA,EACN;AAEF,SAAO,CAAC,GAAG,aAAa,GAAG,WAAW,GAAG,WAAW;AACtD;AAEO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,YAAO,2CAAa,gBAAe,aAAY,2CAAa,WAAU;AAAA,EACxE,CAAC;AACH;"}
@@ -0,0 +1,17 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const FrontmatterContext = createContext(void 0);
4
+ function FrontmatterProvider({
5
+ frontmatter,
6
+ children
7
+ }) {
8
+ return /* @__PURE__ */ jsx(FrontmatterContext.Provider, { value: frontmatter, children });
9
+ }
10
+ function useFrontmatter() {
11
+ return useContext(FrontmatterContext);
12
+ }
13
+ export {
14
+ FrontmatterProvider,
15
+ useFrontmatter
16
+ };
17
+ //# sourceMappingURL=frontmatter-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-context.js","sources":["../../../src/lib/frontmatter-context.tsx"],"sourcesContent":["import { createContext, useContext, ReactNode } from 'react';\n\nexport interface Frontmatter {\n title?: string;\n description?: string;\n date?: string;\n visibility?: string;\n draft?: boolean;\n}\n\nconst FrontmatterContext = createContext<Frontmatter | undefined>(undefined);\n\nexport function FrontmatterProvider({\n frontmatter,\n children\n}: {\n frontmatter: Frontmatter | undefined;\n children: ReactNode\n}) {\n return (\n <FrontmatterContext.Provider value={frontmatter}>\n {children}\n </FrontmatterContext.Provider>\n );\n}\n\nexport function useFrontmatter() {\n return useContext(FrontmatterContext);\n}\n"],"names":[],"mappings":";;AAUA,MAAM,qBAAqB,cAAuC,MAAS;AAEpE,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGG;AACD,6BACG,mBAAmB,UAAnB,EAA4B,OAAO,aACjC,UACH;AAEJ;AAEO,SAAS,iBAAiB;AAC/B,SAAO,WAAW,kBAAkB;AACtC;"}
@@ -4,6 +4,7 @@ import { Home } from "./home.js";
4
4
  import { Post } from "./post.js";
5
5
  import { SlidesPage } from "./slides.js";
6
6
  function ContentRouter() {
7
+ var _a;
7
8
  const { "*": path = "" } = useParams();
8
9
  if (path === "posts") {
9
10
  return /* @__PURE__ */ jsx(Home, { view: "posts" });
@@ -14,7 +15,9 @@ function ContentRouter() {
14
15
  if (path === "all") {
15
16
  return /* @__PURE__ */ jsx(Home, { view: "all" });
16
17
  }
17
- if (path.endsWith(".slides.mdx") || path.endsWith("SLIDES.mdx")) {
18
+ const filename = ((_a = path.split("/").pop()) == null ? void 0 : _a.toLowerCase()) || "";
19
+ const isSlides = path.endsWith(".slides.mdx") || path.endsWith(".slides.md") || filename === "slides.mdx" || filename === "slides.md";
20
+ if (isSlides) {
18
21
  return /* @__PURE__ */ jsx(SlidesPage, {});
19
22
  }
20
23
  if (path.endsWith(".mdx") || path.endsWith(".md")) {
@@ -1 +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 * - /posts → Home with posts view\n * - /docs → Home with docs view\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 for content view routes\n if (path === 'posts') {\n return <Home view=\"posts\" />\n }\n if (path === 'docs') {\n return <Home view=\"docs\" />\n }\n if (path === 'all') {\n return <Home view=\"all\" />\n }\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":";;;;;AAaO,SAAS,gBAAgB;AAC9B,QAAM,EAAE,KAAK,OAAO,GAAA,IAAO,UAAA;AAG3B,MAAI,SAAS,SAAS;AACpB,WAAO,oBAAC,MAAA,EAAK,MAAK,QAAA,CAAQ;AAAA,EAC5B;AACA,MAAI,SAAS,QAAQ;AACnB,WAAO,oBAAC,MAAA,EAAK,MAAK,OAAA,CAAO;AAAA,EAC3B;AACA,MAAI,SAAS,OAAO;AAClB,WAAO,oBAAC,MAAA,EAAK,MAAK,MAAA,CAAM;AAAA,EAC1B;AAGA,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
+ {"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 * - /posts → Home with posts view\n * - /docs → Home with docs view\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 for content view routes\n if (path === 'posts') {\n return <Home view=\"posts\" />\n }\n if (path === 'docs') {\n return <Home view=\"docs\" />\n }\n if (path === 'all') {\n return <Home view=\"all\" />\n }\n\n // Check if this is a slides file\n const filename = path.split('/').pop()?.toLowerCase() || ''\n const isSlides =\n path.endsWith('.slides.mdx') ||\n path.endsWith('.slides.md') ||\n filename === 'slides.mdx' ||\n filename === 'slides.md'\n if (isSlides) {\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":";;;;;AAaO,SAAS,gBAAgB;;AAC9B,QAAM,EAAE,KAAK,OAAO,GAAA,IAAO,UAAA;AAG3B,MAAI,SAAS,SAAS;AACpB,WAAO,oBAAC,MAAA,EAAK,MAAK,QAAA,CAAQ;AAAA,EAC5B;AACA,MAAI,SAAS,QAAQ;AACnB,WAAO,oBAAC,MAAA,EAAK,MAAK,OAAA,CAAO;AAAA,EAC3B;AACA,MAAI,SAAS,OAAO;AAClB,WAAO,oBAAC,MAAA,EAAK,MAAK,MAAA,CAAM;AAAA,EAC1B;AAGA,QAAM,aAAW,UAAK,MAAM,GAAG,EAAE,IAAA,MAAhB,mBAAuB,kBAAiB;AACzD,QAAM,WACJ,KAAK,SAAS,aAAa,KAC3B,KAAK,SAAS,YAAY,KAC1B,aAAa,gBACb,aAAa;AACf,MAAI,UAAU;AACZ,+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;"}
@@ -6,7 +6,7 @@ import { RunningBar } from "../components/running-bar.js";
6
6
  import { Header } from "../components/header.js";
7
7
  import { useMDXContent } from "../hooks/use-mdx-content.js";
8
8
  import { mdxComponents } from "../components/mdx-components.js";
9
- import { formatDate } from "../lib/format-date.js";
9
+ import { FrontmatterProvider } from "../lib/frontmatter-context.js";
10
10
  function Post() {
11
11
  const { "*": rawPath = "." } = useParams();
12
12
  const mdxPath = rawPath;
@@ -32,14 +32,7 @@ function Post() {
32
32
  /* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
33
33
  /* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
34
34
  ] }) }),
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
- (frontmatter == null ? void 0 : frontmatter.title) && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
37
- /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: frontmatter.title }),
38
- (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }) }),
39
- (frontmatter == null ? void 0 : frontmatter.description) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: frontmatter.description })
40
- ] }),
41
- /* @__PURE__ */ jsx(Content, { components: mdxComponents })
42
- ] })
35
+ Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__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 }) }) })
43
36
  ] })
44
37
  ] });
45
38
  }
@@ -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 { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { formatDate } from \"@/lib/format-date\";\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 {/* Render frontmatter header */}\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter.title}\n </h1>\n {frontmatter?.date && (\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n </div>\n )}\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter.description}\n </div>\n )}\n </header>\n )}\n <Content components={mdxComponents} />\n </article>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAUO,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,WACC,qBAAC,WAAA,EAAQ,WAAU,oMAEhB,UAAA;AAAA,SAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,YAAY,OACf;AAAA,WACC,2CAAa,SACZ,oBAAC,OAAA,EAAI,WAAU,2DACb,UAAA,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GACF;AAAA,WAED,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,sBAAY,YAAA,CACf;AAAA,QAAA,GAEJ;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\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\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 <FrontmatterProvider frontmatter={frontmatter}>\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 </FrontmatterProvider>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAUO,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,WACC,oBAAC,qBAAA,EAAoB,aACnB,UAAA,oBAAC,WAAA,EAAQ,WAAU,oMACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
@@ -7,6 +7,7 @@ import { RunningBar } from "../components/running-bar.js";
7
7
  import { Header } from "../components/header.js";
8
8
  import { useMDXSlides } from "../hooks/use-mdx-content.js";
9
9
  import { slidesMdxComponents } from "../components/slides-renderer.js";
10
+ import { FrontmatterProvider } from "../lib/frontmatter-context.js";
10
11
  function SlidesPage() {
11
12
  const { "*": rawPath = "." } = useParams();
12
13
  const [searchParams, setSearchParams] = useSearchParams();
@@ -104,7 +105,7 @@ function SlidesPage() {
104
105
  }
105
106
  }
106
107
  ),
107
- /* @__PURE__ */ jsx("div", { ...{ [FULLSCREEN_DATA_ATTR]: "true" }, children: /* @__PURE__ */ jsx("div", { ref: contentRef, children: /* @__PURE__ */ jsx(Content, { components: slidesMdxComponents }) }) })
108
+ /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("div", { ...{ [FULLSCREEN_DATA_ATTR]: "true" }, children: /* @__PURE__ */ jsx("div", { ref: contentRef, children: /* @__PURE__ */ jsx(Content, { components: slidesMdxComponents }) }) }) })
108
109
  ] });
109
110
  }
110
111
  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 { 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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"}
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\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\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 <FrontmatterProvider frontmatter={frontmatter}>\n <div {...{[FULLSCREEN_DATA_ATTR]: \"true\"}}>\n <div ref={contentRef}>\n <Content components={slidesMdxComponents} />\n </div>\n </div>\n </FrontmatterProvider>\n </main>\n )\n}\n"],"names":[],"mappings":"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"}
@@ -45,7 +45,7 @@ function findMdxFiles(directory) {
45
45
  );
46
46
  }
47
47
  function findSlides(directory) {
48
- const readme = directory.children.find(
48
+ const standardSlides = directory.children.find(
49
49
  (child) => child.type === "file" && [
50
50
  "SLIDES.md",
51
51
  "Slides.md",
@@ -55,7 +55,24 @@ function findSlides(directory) {
55
55
  "slides.mdx"
56
56
  ].includes(child.name)
57
57
  );
58
- return readme || null;
58
+ if (standardSlides) return standardSlides;
59
+ const dotSlides = directory.children.find(
60
+ (child) => child.type === "file" && (child.name.endsWith(".slides.mdx") || child.name.endsWith(".slides.md"))
61
+ );
62
+ return dotSlides || null;
63
+ }
64
+ function findStandaloneSlides(directory) {
65
+ const standardSlideFiles = [
66
+ "SLIDES.mdx",
67
+ "Slides.mdx",
68
+ "slides.mdx",
69
+ "SLIDES.md",
70
+ "Slides.md",
71
+ "slides.md"
72
+ ];
73
+ return directory.children.filter(
74
+ (child) => child.type === "file" && (child.name.endsWith(".slides.mdx") || child.name.endsWith(".slides.md")) && !standardSlideFiles.includes(child.name)
75
+ );
59
76
  }
60
77
  const directoryTree = buildDirectoryTree(Object.keys(files), frontmatters);
61
78
  function useDirectory(path = ".") {
@@ -140,6 +157,7 @@ export {
140
157
  findMdxFiles,
141
158
  findReadme,
142
159
  findSlides,
160
+ findStandaloneSlides,
143
161
  isSimulationRunning,
144
162
  useDirectory,
145
163
  useFileContent
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../../plugin/src/client.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { DirectoryEntry, FileEntry } from \"./lib\";\nimport { buildDirectoryTree, navigateToPath } from \"./directory-tree\";\n// @ts-ignore - virtual module\nimport { files, frontmatters } from \"virtual:content-modules\";\n\n/**\n * Find the main content file for a directory.\n * Supports (in order of preference):\n * - index.mdx / index.md (modern convention)\n * - README.mdx / README.md (traditional convention)\n */\nexport function findReadme(directory: DirectoryEntry): FileEntry | null {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n\n for (const filename of indexFiles) {\n const found = directory.children.find((child) =>\n child.type === \"file\" && child.name === filename\n ) as FileEntry | undefined;\n if (found) return found;\n }\n\n return null;\n}\n\n/**\n * Find all MDX files in a directory (excluding index/README and slides)\n */\nexport function findMdxFiles(directory: DirectoryEntry): FileEntry[] {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n const slideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n const excludeFiles = [...indexFiles, ...slideFiles];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.mdx') || child.name.endsWith('.md')) &&\n !excludeFiles.includes(child.name) &&\n !child.name.endsWith('.slides.mdx') &&\n !child.name.endsWith('.slides.md')\n );\n}\n\nexport function findSlides(directory: DirectoryEntry): FileEntry | null {\n const readme = directory.children.find((child) =>\n child.type === \"file\" &&\n [\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n return readme || null;\n}\n\n\nexport type DirectoryError =\n | { type: 'path_not_found'; message: string; status: 404 };\n\n// Build directory tree once from glob keys, with frontmatter metadata\nconst directoryTree = buildDirectoryTree(Object.keys(files), frontmatters as Record<string, FileEntry['frontmatter']>);\n\nexport function useDirectory(path: string = \".\") {\n const [error, setError] = useState<DirectoryError | null>(null);\n\n const result = useMemo(() => {\n try {\n const { directory, file } = navigateToPath(directoryTree, path);\n return { directory, file };\n } catch {\n setError({ type: 'path_not_found', message: `Path not found: ${path}`, status: 404 });\n return { directory: null, file: null };\n }\n }, [path]);\n\n return {\n directory: result.directory,\n file: result.file,\n loading: false, // No async loading needed\n error\n };\n}\n\nexport function useFileContent(path: string) {\n const [blob, setBlob] = useState<Blob | null>(null);\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const res = await fetch(`/raw/${path}`, {\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);\n }\n\n const fetchedBlob = await res.blob();\n setBlob(fetchedBlob);\n\n // Try to read as text - some binary files may fail\n try {\n const text = await fetchedBlob.text();\n setContent(text);\n } catch {\n // Binary file - text content not available\n setContent(null);\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return; // Ignore abort errors\n }\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n })();\n\n return () => controller.abort();\n }, [path]);\n\n return { blob, content, loading, error };\n}\n\nexport function isSimulationRunning() {\n const [running, setRunning] = useState<boolean>(false);\n\n useEffect(() => {\n let interval: ReturnType<typeof setInterval>;\n\n const fetchStatus = async () => {\n const response = await fetch(`/raw/.running`);\n\n // this is an elaborate workaround to stop devtools logging errors on 404s\n const text = await response.text()\n if (text === \"\") {\n setRunning(true);\n } else {\n setRunning(false);\n }\n };\n\n // Initial fetch\n fetchStatus();\n\n // Poll every second\n interval = setInterval(fetchStatus, 1000);\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return running;\n}"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sources":["../../../../plugin/src/client.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { DirectoryEntry, FileEntry } from \"./lib\";\nimport { buildDirectoryTree, navigateToPath } from \"./directory-tree\";\n// @ts-ignore - virtual module\nimport { files, frontmatters } from \"virtual:content-modules\";\n\n/**\n * Find the main content file for a directory.\n * Supports (in order of preference):\n * - index.mdx / index.md (modern convention)\n * - README.mdx / README.md (traditional convention)\n */\nexport function findReadme(directory: DirectoryEntry): FileEntry | null {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n\n for (const filename of indexFiles) {\n const found = directory.children.find((child) =>\n child.type === \"file\" && child.name === filename\n ) as FileEntry | undefined;\n if (found) return found;\n }\n\n return null;\n}\n\n/**\n * Find all MDX files in a directory (excluding index/README and slides)\n */\nexport function findMdxFiles(directory: DirectoryEntry): FileEntry[] {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n const slideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n const excludeFiles = [...indexFiles, ...slideFiles];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.mdx') || child.name.endsWith('.md')) &&\n !excludeFiles.includes(child.name) &&\n !child.name.endsWith('.slides.mdx') &&\n !child.name.endsWith('.slides.md')\n );\n}\n\nexport function findSlides(directory: DirectoryEntry): FileEntry | null {\n // First check for standard SLIDES.mdx files\n const standardSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n [\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n if (standardSlides) return standardSlides;\n\n // Then check for *.slides.mdx files\n const dotSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md'))\n ) as FileEntry | undefined;\n\n return dotSlides || null;\n}\n\n/**\n * Find all standalone slides files in a directory (*.slides.mdx, *.slides.md)\n * These are slides files that aren't part of a folder (like getting-started.slides.mdx)\n */\nexport function findStandaloneSlides(directory: DirectoryEntry): FileEntry[] {\n const standardSlideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md')) &&\n !standardSlideFiles.includes(child.name)\n );\n}\n\n\nexport type DirectoryError =\n | { type: 'path_not_found'; message: string; status: 404 };\n\n// Build directory tree once from glob keys, with frontmatter metadata\nconst directoryTree = buildDirectoryTree(Object.keys(files), frontmatters as Record<string, FileEntry['frontmatter']>);\n\nexport function useDirectory(path: string = \".\") {\n const [error, setError] = useState<DirectoryError | null>(null);\n\n const result = useMemo(() => {\n try {\n const { directory, file } = navigateToPath(directoryTree, path);\n return { directory, file };\n } catch {\n setError({ type: 'path_not_found', message: `Path not found: ${path}`, status: 404 });\n return { directory: null, file: null };\n }\n }, [path]);\n\n return {\n directory: result.directory,\n file: result.file,\n loading: false, // No async loading needed\n error\n };\n}\n\nexport function useFileContent(path: string) {\n const [blob, setBlob] = useState<Blob | null>(null);\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const res = await fetch(`/raw/${path}`, {\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);\n }\n\n const fetchedBlob = await res.blob();\n setBlob(fetchedBlob);\n\n // Try to read as text - some binary files may fail\n try {\n const text = await fetchedBlob.text();\n setContent(text);\n } catch {\n // Binary file - text content not available\n setContent(null);\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return; // Ignore abort errors\n }\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n })();\n\n return () => controller.abort();\n }, [path]);\n\n return { blob, content, loading, error };\n}\n\nexport function isSimulationRunning() {\n const [running, setRunning] = useState<boolean>(false);\n\n useEffect(() => {\n let interval: ReturnType<typeof setInterval>;\n\n const fetchStatus = async () => {\n const response = await fetch(`/raw/.running`);\n\n // this is an elaborate workaround to stop devtools logging errors on 404s\n const text = await response.text()\n if (text === \"\") {\n setRunning(true);\n } else {\n setRunning(false);\n }\n };\n\n // Initial fetch\n fetchStatus();\n\n // Poll every second\n interval = setInterval(fetchStatus, 1000);\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return running;\n}"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,7 +52,8 @@ export function findMdxFiles(directory: DirectoryEntry): FileEntry[] {
52
52
  }
53
53
 
54
54
  export function findSlides(directory: DirectoryEntry): FileEntry | null {
55
- const readme = directory.children.find((child) =>
55
+ // First check for standard SLIDES.mdx files
56
+ const standardSlides = directory.children.find((child) =>
56
57
  child.type === "file" &&
57
58
  [
58
59
  "SLIDES.md", "Slides.md", "slides.md",
@@ -60,7 +61,32 @@ export function findSlides(directory: DirectoryEntry): FileEntry | null {
60
61
  ].includes(child.name)
61
62
  ) as FileEntry | undefined;
62
63
 
63
- return readme || null;
64
+ if (standardSlides) return standardSlides;
65
+
66
+ // Then check for *.slides.mdx files
67
+ const dotSlides = directory.children.find((child) =>
68
+ child.type === "file" &&
69
+ (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md'))
70
+ ) as FileEntry | undefined;
71
+
72
+ return dotSlides || null;
73
+ }
74
+
75
+ /**
76
+ * Find all standalone slides files in a directory (*.slides.mdx, *.slides.md)
77
+ * These are slides files that aren't part of a folder (like getting-started.slides.mdx)
78
+ */
79
+ export function findStandaloneSlides(directory: DirectoryEntry): FileEntry[] {
80
+ const standardSlideFiles = [
81
+ "SLIDES.mdx", "Slides.mdx", "slides.mdx",
82
+ "SLIDES.md", "Slides.md", "slides.md",
83
+ ];
84
+
85
+ return directory.children.filter((child): child is FileEntry =>
86
+ child.type === "file" &&
87
+ (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md')) &&
88
+ !standardSlideFiles.includes(child.name)
89
+ );
64
90
  }
65
91
 
66
92
 
@@ -1,6 +1,7 @@
1
- import { type Plugin, type Connect } from 'vite'
1
+ import { type Plugin, type Connect, type ViteDevServer } from 'vite'
2
2
  import path from 'path'
3
3
  import fs from 'fs'
4
+ import yaml from 'js-yaml'
4
5
  import type { IncomingMessage, ServerResponse } from 'http'
5
6
  import { type VeslxConfig, type ResolvedSiteConfig, DEFAULT_SITE_CONFIG } from './types'
6
7
  import matter from 'gray-matter'
@@ -71,7 +72,11 @@ function copyDirSync(src: string, dest: string) {
71
72
  }
72
73
  }
73
74
 
74
- export default function contentPlugin(contentDir: string, config?: VeslxConfig): Plugin {
75
+ interface PluginOptions {
76
+ configPath?: string
77
+ }
78
+
79
+ export default function contentPlugin(contentDir: string, config?: VeslxConfig, options?: PluginOptions): Plugin {
75
80
 
76
81
  if (!contentDir) {
77
82
  throw new Error('Content directory must be specified.')
@@ -82,13 +87,31 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig):
82
87
  }
83
88
 
84
89
  const dir = contentDir
90
+ const configPath = options?.configPath
85
91
 
86
- // Resolve site config with defaults
87
- const siteConfig: ResolvedSiteConfig = {
92
+ // Mutable site config that can be updated on hot reload
93
+ let siteConfig: ResolvedSiteConfig = {
88
94
  ...DEFAULT_SITE_CONFIG,
89
95
  ...config?.site,
90
96
  }
91
97
 
98
+ // Helper to reload config from file
99
+ function reloadConfig(): boolean {
100
+ if (!configPath || !fs.existsSync(configPath)) return false
101
+ try {
102
+ const content = fs.readFileSync(configPath, 'utf-8')
103
+ const parsed = yaml.load(content) as VeslxConfig
104
+ siteConfig = {
105
+ ...DEFAULT_SITE_CONFIG,
106
+ ...parsed?.site,
107
+ }
108
+ return true
109
+ } catch (e) {
110
+ console.error('[veslx] Failed to reload config:', e)
111
+ return false
112
+ }
113
+ }
114
+
92
115
  // Server middleware for serving content files
93
116
  const urlToDir = new Map<string, string>()
94
117
 
@@ -209,6 +232,28 @@ export const modules = import.meta.glob('@content/**/*.mdx');
209
232
  configureServer(server) {
210
233
  // Add middleware for serving content files
211
234
  server.middlewares.use(middleware)
235
+
236
+ // Watch config file for hot reload
237
+ if (configPath && fs.existsSync(configPath)) {
238
+ server.watcher.add(configPath)
239
+ }
240
+ },
241
+
242
+ handleHotUpdate({ file, server }) {
243
+ // Check if the changed file is our config
244
+ if (configPath && file === configPath) {
245
+ console.log('[veslx] Config changed, reloading...')
246
+ if (reloadConfig()) {
247
+ // Invalidate the virtual config module
248
+ const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_CONFIG_ID)
249
+ if (mod) {
250
+ server.moduleGraph.invalidateModule(mod)
251
+ }
252
+ // Full reload since config affects the entire app
253
+ server.ws.send({ type: 'full-reload' })
254
+ return [] // Prevent default HMR handling
255
+ }
256
+ }
212
257
  },
213
258
  configurePreviewServer(server) {
214
259
  // Add middleware for preview server too
@@ -1,13 +1,8 @@
1
- import { useMDXContent, useMDXSlides } from "@/hooks/use-mdx-content";
1
+ import { useFrontmatter } from "@/lib/frontmatter-context";
2
2
  import { formatDate } from "@/lib/format-date"
3
- import { useParams } from "react-router-dom"
4
3
 
5
4
  export function FrontMatter(){
6
- const { "path": path = "." } = useParams();
7
- const { frontmatter: readmeFm } = useMDXContent(path);
8
- const { frontmatter: slidesFm } = useMDXSlides(path);
9
-
10
- let frontmatter = readmeFm || slidesFm;
5
+ const frontmatter = useFrontmatter();
11
6
 
12
7
  return (
13
8
  <div>
@@ -35,4 +30,4 @@ export function FrontMatter(){
35
30
  )}
36
31
  </div>
37
32
  )
38
- }
33
+ }
@@ -104,7 +104,7 @@ export default function Gallery({
104
104
  className={`pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group ${images.length < 3 ? 'flex-none' : ''}`}
105
105
  onClick={() => lightbox.open(index)}
106
106
  >
107
- <div className="aspect-square overflow-hidden rounded-sm ring-1 ring-border/50 transition-all duration-300 group-hover:ring-border group-hover:shadow-lg bg-muted/10">
107
+ <div className="aspect-square overflow-hidden rounded-sm bg-muted/10">
108
108
  <LoadingImage
109
109
  src={img.src}
110
110
  alt={img.label}
@@ -123,9 +123,25 @@ export function useMDXContent(path: string) {
123
123
  function findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null {
124
124
  const keys = Object.keys(modules)
125
125
 
126
+ // Normalize path - remove leading slash if present
127
+ const normalizedPath = path.replace(/^\//, '')
128
+
126
129
  // If path already ends with .mdx, match exactly
127
- if (path.endsWith('.mdx')) {
128
- const matchingKey = keys.find(key => key.endsWith(`/${path}`))
130
+ if (normalizedPath.endsWith('.mdx')) {
131
+ // Try multiple matching strategies for different Vite glob formats
132
+ const matchingKey = keys.find(key => {
133
+ // Strategy 1: Key ends with /path (e.g., @content/docs/foo.slides.mdx matches docs/foo.slides.mdx)
134
+ if (key.endsWith(`/${normalizedPath}`)) return true
135
+ // Strategy 2: Key equals @content/path (alias form)
136
+ if (key === `@content/${normalizedPath}`) return true
137
+ // Strategy 3: Key equals /@content/path (with leading slash)
138
+ if (key === `/@content/${normalizedPath}`) return true
139
+ // Strategy 4: Key equals path directly
140
+ if (key === normalizedPath) return true
141
+ // Strategy 5: Key equals /path (with leading slash)
142
+ if (key === `/${normalizedPath}`) return true
143
+ return false
144
+ })
129
145
  return matchingKey ? modules[matchingKey] : null
130
146
  }
131
147
 
@@ -133,12 +149,17 @@ function findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null
133
149
  // 1. folder/SLIDES.mdx (current convention)
134
150
  // 2. folder/index.slides.mdx (alternative)
135
151
  const candidates = [
136
- `/${path}/SLIDES.mdx`,
137
- `/${path}/index.slides.mdx`,
152
+ `${normalizedPath}/SLIDES.mdx`,
153
+ `${normalizedPath}/index.slides.mdx`,
138
154
  ]
139
155
 
140
- for (const suffix of candidates) {
141
- const matchingKey = keys.find(key => key.endsWith(suffix))
156
+ for (const candidate of candidates) {
157
+ const matchingKey = keys.find(key => {
158
+ if (key.endsWith(`/${candidate}`)) return true
159
+ if (key === `@content/${candidate}`) return true
160
+ if (key === candidate) return true
161
+ return false
162
+ })
142
163
  if (matchingKey) {
143
164
  return modules[matchingKey]
144
165
  }
package/src/index.css CHANGED
@@ -325,8 +325,7 @@
325
325
  .slides-container > .running-bar,
326
326
  .slides-container hr,
327
327
  .slides-container > title,
328
- header.print\\:hidden,
329
- [class*="print:hidden"] {
328
+ body > header {
330
329
  display: none !important;
331
330
  height: 0 !important;
332
331
  width: 0 !important;
@@ -1,6 +1,6 @@
1
1
  import type { ContentView } from "../../plugin/src/types";
2
2
  import type { DirectoryEntry, FileEntry } from "../../plugin/src/lib";
3
- import { findReadme, findSlides, findMdxFiles } from "../../plugin/src/client";
3
+ import { findReadme, findSlides, findMdxFiles, findStandaloneSlides } from "../../plugin/src/client";
4
4
 
5
5
  export type PostEntry = {
6
6
  type: 'folder' | 'file';
@@ -39,6 +39,7 @@ export function getViewCounts(posts: PostEntry[]): { posts: number; docs: number
39
39
  export function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {
40
40
  const folders = directory.children.filter((c): c is DirectoryEntry => c.type === "directory");
41
41
  const standaloneFiles = findMdxFiles(directory);
42
+ const standaloneSlidesFiles = findStandaloneSlides(directory);
42
43
 
43
44
  const folderPosts: PostEntry[] = folders
44
45
  .map((folder) => ({
@@ -60,7 +61,17 @@ export function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {
60
61
  file,
61
62
  }));
62
63
 
63
- return [...folderPosts, ...filePosts];
64
+ // Standalone slides files (e.g., getting-started.slides.mdx)
65
+ const slidesPosts: PostEntry[] = standaloneSlidesFiles.map((file) => ({
66
+ type: 'file' as const,
67
+ name: file.name.replace(/\.slides\.mdx?$/, ''),
68
+ path: file.path,
69
+ readme: null,
70
+ slides: file,
71
+ file: null,
72
+ }));
73
+
74
+ return [...folderPosts, ...filePosts, ...slidesPosts];
64
75
  }
65
76
 
66
77
  export function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {
@@ -0,0 +1,29 @@
1
+ import { createContext, useContext, ReactNode } from 'react';
2
+
3
+ export interface Frontmatter {
4
+ title?: string;
5
+ description?: string;
6
+ date?: string;
7
+ visibility?: string;
8
+ draft?: boolean;
9
+ }
10
+
11
+ const FrontmatterContext = createContext<Frontmatter | undefined>(undefined);
12
+
13
+ export function FrontmatterProvider({
14
+ frontmatter,
15
+ children
16
+ }: {
17
+ frontmatter: Frontmatter | undefined;
18
+ children: ReactNode
19
+ }) {
20
+ return (
21
+ <FrontmatterContext.Provider value={frontmatter}>
22
+ {children}
23
+ </FrontmatterContext.Provider>
24
+ );
25
+ }
26
+
27
+ export function useFrontmatter() {
28
+ return useContext(FrontmatterContext);
29
+ }
@@ -26,7 +26,13 @@ export function ContentRouter() {
26
26
  }
27
27
 
28
28
  // Check if this is a slides file
29
- if (path.endsWith('.slides.mdx') || path.endsWith('SLIDES.mdx')) {
29
+ const filename = path.split('/').pop()?.toLowerCase() || ''
30
+ const isSlides =
31
+ path.endsWith('.slides.mdx') ||
32
+ path.endsWith('.slides.md') ||
33
+ filename === 'slides.mdx' ||
34
+ filename === 'slides.md'
35
+ if (isSlides) {
30
36
  return <SlidesPage />
31
37
  }
32
38
 
@@ -6,7 +6,7 @@ import { RunningBar } from "@/components/running-bar";
6
6
  import { Header } from "@/components/header";
7
7
  import { useMDXContent } from "@/hooks/use-mdx-content";
8
8
  import { mdxComponents } from "@/components/mdx-components";
9
- import { formatDate } from "@/lib/format-date";
9
+ import { FrontmatterProvider } from "@/lib/frontmatter-context";
10
10
 
11
11
  export function Post() {
12
12
  const { "*": rawPath = "." } = useParams();
@@ -55,29 +55,11 @@ export function Post() {
55
55
  )}
56
56
 
57
57
  {Content && (
58
- <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">
59
- {/* Render frontmatter header */}
60
- {frontmatter?.title && (
61
- <header className="not-prose flex flex-col gap-2 mb-8 pt-4">
62
- <h1 className="text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3">
63
- {frontmatter.title}
64
- </h1>
65
- {frontmatter?.date && (
66
- <div className="flex flex-wrap items-center gap-3 text-muted-foreground">
67
- <time className="font-mono text-xs bg-muted px-2 py-0.5 rounded">
68
- {formatDate(new Date(frontmatter.date as string))}
69
- </time>
70
- </div>
71
- )}
72
- {frontmatter?.description && (
73
- <div className="flex flex-wrap text-sm items-center gap-3 text-muted-foreground">
74
- {frontmatter.description}
75
- </div>
76
- )}
77
- </header>
78
- )}
79
- <Content components={mdxComponents} />
80
- </article>
58
+ <FrontmatterProvider frontmatter={frontmatter}>
59
+ <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">
60
+ <Content components={mdxComponents} />
61
+ </article>
62
+ </FrontmatterProvider>
81
63
  )}
82
64
  </main>
83
65
  </div>
@@ -6,6 +6,7 @@ import { RunningBar } from "@/components/running-bar";
6
6
  import { Header } from "@/components/header";
7
7
  import { useMDXSlides } from "@/hooks/use-mdx-content";
8
8
  import { slidesMdxComponents } from "@/components/slides-renderer";
9
+ import { FrontmatterProvider } from "@/lib/frontmatter-context";
9
10
 
10
11
 
11
12
  export function SlidesPage() {
@@ -137,11 +138,13 @@ export function SlidesPage() {
137
138
  onNext: goToNext,
138
139
  }}
139
140
  />
140
- <div {...{[FULLSCREEN_DATA_ATTR]: "true"}}>
141
- <div ref={contentRef}>
142
- <Content components={slidesMdxComponents} />
141
+ <FrontmatterProvider frontmatter={frontmatter}>
142
+ <div {...{[FULLSCREEN_DATA_ATTR]: "true"}}>
143
+ <div ref={contentRef}>
144
+ <Content components={slidesMdxComponents} />
145
+ </div>
143
146
  </div>
144
- </div>
147
+ </FrontmatterProvider>
145
148
  </main>
146
149
  )
147
150
  }
package/vite.config.ts CHANGED
@@ -80,10 +80,11 @@ export default defineConfig(({ command }) => {
80
80
  reactResolverPlugin(),
81
81
  tailwindcss(),
82
82
  // MDX for slides - splits at --- into <Slide> components
83
+ // Matches: SLIDES.mdx, slides.mdx, *.slides.mdx
83
84
  {
84
85
  enforce: 'pre',
85
86
  ...mdx({
86
- include: /SLIDES\.mdx$/,
87
+ include: /SLIDES\.mdx$|slides\.mdx$/i,
87
88
  remarkPlugins: [
88
89
  ...commonRemarkPlugins,
89
90
  remarkSlides, // Transform --- into <Slide> wrappers
@@ -92,11 +93,11 @@ export default defineConfig(({ command }) => {
92
93
  providerImportSource: '@mdx-js/react',
93
94
  }),
94
95
  },
95
- // MDX for regular posts
96
+ // MDX for regular posts (excludes all slides files)
96
97
  {
97
98
  enforce: 'pre',
98
99
  ...mdx({
99
- exclude: /SLIDES\.mdx$/,
100
+ exclude: /SLIDES\.mdx$|slides\.mdx$/i,
100
101
  remarkPlugins: commonRemarkPlugins,
101
102
  rehypePlugins: [rehypeKatex],
102
103
  providerImportSource: '@mdx-js/react',