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.
- package/bin/lib/serve.ts +16 -2
- package/bin/lib/start.ts +12 -10
- package/bin/veslx.ts +1 -1
- package/dist/client/components/front-matter.js +2 -6
- package/dist/client/components/front-matter.js.map +1 -1
- package/dist/client/components/gallery/index.js +1 -1
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +19 -6
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/lib/content-classification.js +11 -2
- package/dist/client/lib/content-classification.js.map +1 -1
- package/dist/client/lib/frontmatter-context.js +17 -0
- package/dist/client/lib/frontmatter-context.js.map +1 -0
- package/dist/client/pages/content-router.js +4 -1
- package/dist/client/pages/content-router.js.map +1 -1
- package/dist/client/pages/post.js +2 -9
- package/dist/client/pages/post.js.map +1 -1
- package/dist/client/pages/slides.js +2 -1
- package/dist/client/pages/slides.js.map +1 -1
- package/dist/client/plugin/src/client.js +20 -2
- package/dist/client/plugin/src/client.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/client.tsx +28 -2
- package/plugin/src/plugin.ts +49 -4
- package/src/components/front-matter.tsx +3 -8
- package/src/components/gallery/index.tsx +1 -1
- package/src/hooks/use-mdx-content.ts +27 -6
- package/src/index.css +1 -2
- package/src/lib/content-classification.ts +13 -2
- package/src/lib/frontmatter-context.tsx +29 -0
- package/src/pages/content-router.tsx +7 -1
- package/src/pages/post.tsx +6 -24
- package/src/pages/slides.tsx +7 -4
- 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
|
-
|
|
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
|
|
5
|
+
export default async function start(dir?: string) {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
14
|
-
|
|
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:
|
|
27
|
+
args: args,
|
|
26
28
|
cwd: cwd,
|
|
27
29
|
autorestart: true,
|
|
28
30
|
watch: false,
|
package/bin/veslx.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
82
|
+
`${normalizedPath}/SLIDES.mdx`,
|
|
83
|
+
`${normalizedPath}/index.slides.mdx`
|
|
76
84
|
];
|
|
77
|
-
for (const
|
|
78
|
-
const matchingKey = keys.find((key) =>
|
|
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":";AAuBA,SAAS,cAAc,SAAoB,MAAmC;AAC5E,QAAM,OAAO,OAAO,KAAK,OAAO;AAGhC,QAAM,iBAAiB,KAAK,QAAQ,OAAO,EAAE;AAG7C,MAAI,eAAe,SAAS,MAAM,GAAG;AAEnC,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AAEnC,UAAI,IAAI,SAAS,IAAI,cAAc,EAAE,EAAG,QAAO;AAE/C,UAAI,QAAQ,YAAY,cAAc,GAAI,QAAO;AAEjD,UAAI,QAAQ,aAAa,cAAc,GAAI,QAAO;AAElD,UAAI,QAAQ,eAAgB,QAAO;AAEnC,UAAI,QAAQ,IAAI,cAAc,GAAI,QAAO;AACzC,aAAO;AAAA,IACT,CAAC;AACD,WAAO,cAAc,QAAQ,WAAW,IAAI;AAAA,EAC9C;AAMA,QAAM,aAAa;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,EAAA;AAGnB,aAAW,aAAa,YAAY;AAClC,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AACnC,UAAI,IAAI,SAAS,IAAI,SAAS,EAAE,EAAG,QAAO;AAC1C,UAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAC5C,UAAI,QAAQ,UAAW,QAAO;AAC9B,aAAO;AAAA,IACT,CAAC;AACD,QAAI,aAAa;AACf,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,MAAc;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,cAAc;AACrB,YAAM,SAAS,cAAc,SAAsB,IAAI;AAEvD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,MAC1D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,SAAS,MAAA;AAC1C;AAOA,SAAS,iBAAiB,SAAoB,MAAmC;AAC/E,QAAM,OAAO,OAAO,KAAK,OAAO;AAGhC,MAAI,KAAK,SAAS,MAAM,GAAG;AACzB,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;AAC7D,WAAO,cAAc,QAAQ,WAAW,IAAI;AAAA,EAC9C;AAKA,QAAM,aAAa;AAAA,IACjB,IAAI,IAAI;AAAA,IACR,IAAI,IAAI;AAAA,EAAA;AAGV,aAAW,UAAU,YAAY;AAC/B,UAAM,cAAc,KAAK,KAAK,SAAO,IAAI,SAAS,MAAM,CAAC;AACzD,QAAI,aAAa;AACf,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,aAAa,MAAc;AACzC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,YAAY,aAAa,IAAI,SAA6B,MAAS;AAC1E,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,aAAa;AACpB,YAAM,SAAS,iBAAiB,QAAqB,IAAI;AAEzD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AAAA,MAC7D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,sBAAc,IAAI,UAAU;AAC5B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,YAAY,SAAS,MAAA;AACtD;"}
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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__ */
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
package/plugin/src/client.tsx
CHANGED
|
@@ -52,7 +52,8 @@ export function findMdxFiles(directory: DirectoryEntry): FileEntry[] {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export function findSlides(directory: DirectoryEntry): FileEntry | null {
|
|
55
|
-
|
|
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
|
-
|
|
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
|
|
package/plugin/src/plugin.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
87
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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 (
|
|
128
|
-
|
|
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
|
-
|
|
137
|
-
|
|
152
|
+
`${normalizedPath}/SLIDES.mdx`,
|
|
153
|
+
`${normalizedPath}/index.slides.mdx`,
|
|
138
154
|
]
|
|
139
155
|
|
|
140
|
-
for (const
|
|
141
|
-
const matchingKey = keys.find(key =>
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
package/src/pages/post.tsx
CHANGED
|
@@ -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 {
|
|
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
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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>
|
package/src/pages/slides.tsx
CHANGED
|
@@ -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
|
-
<
|
|
141
|
-
<div
|
|
142
|
-
<
|
|
141
|
+
<FrontmatterProvider frontmatter={frontmatter}>
|
|
142
|
+
<div {...{[FULLSCREEN_DATA_ATTR]: "true"}}>
|
|
143
|
+
<div ref={contentRef}>
|
|
144
|
+
<Content components={slidesMdxComponents} />
|
|
145
|
+
</div>
|
|
143
146
|
</div>
|
|
144
|
-
</
|
|
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',
|