veslx 0.1.22 → 0.1.23
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/dist/client/components/gallery/components/figure-caption.js +1 -1
- package/dist/client/components/gallery/components/figure-caption.js.map +1 -1
- package/dist/client/components/gallery/components/figure-header.js +1 -1
- package/dist/client/components/gallery/components/figure-header.js.map +1 -1
- package/dist/client/components/gallery/components/lightbox.js +1 -1
- package/dist/client/components/gallery/components/lightbox.js.map +1 -1
- package/dist/client/components/gallery/index.js +37 -7
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/components/header.js +45 -21
- package/dist/client/components/header.js.map +1 -1
- package/dist/client/components/mdx-components.js +8 -0
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list.js +13 -11
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/components/slides/figure-slide.js +14 -0
- package/dist/client/components/slides/figure-slide.js.map +1 -0
- package/dist/client/components/slides/hero-slide.js +21 -0
- package/dist/client/components/slides/hero-slide.js.map +1 -0
- package/dist/client/components/slides/slide-outline.js +28 -0
- package/dist/client/components/slides/slide-outline.js.map +1 -0
- package/dist/client/components/slides/text-slide.js +18 -0
- package/dist/client/components/slides/text-slide.js.map +1 -0
- package/dist/client/components/slides-renderer.js.map +1 -1
- package/dist/client/pages/home.js +2 -6
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/slides.js +7 -11
- package/dist/client/pages/slides.js.map +1 -1
- package/index.html +13 -0
- package/package.json +1 -1
- package/src/components/content-tabs.tsx +4 -4
- package/src/components/gallery/components/figure-caption.tsx +1 -1
- package/src/components/gallery/components/figure-header.tsx +1 -1
- package/src/components/gallery/components/lightbox.tsx +1 -1
- package/src/components/gallery/index.tsx +68 -29
- package/src/components/header.tsx +44 -25
- package/src/components/mdx-components.tsx +12 -0
- package/src/components/post-list.tsx +14 -10
- package/src/components/slides/figure-slide.tsx +16 -0
- package/src/components/slides/hero-slide.tsx +34 -0
- package/src/components/slides/slide-outline.tsx +38 -0
- package/src/components/slides/text-slide.tsx +35 -0
- package/src/components/slides-renderer.tsx +1 -1
- package/src/pages/home.tsx +3 -3
- package/src/pages/slides.tsx +7 -12
- package/dist/client/components/content-tabs.js +0 -50
- package/dist/client/components/content-tabs.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slides-renderer.js","sources":["../../../src/components/slides-renderer.tsx"],"sourcesContent":["import { ReactNode } from 'react'\nimport { mdxComponents } from '@/components/mdx-components'\nimport { Slide } from '@/components/slide'\n\n/**\n * MDX components for slides - includes the Slide component\n */\nexport const slidesMdxComponents = {\n ...mdxComponents,\n Slide,\n}\n\n/**\n * Renders a single slide's content\n */\nexport function SlideContent({ children }: { children: ReactNode }) {\n return (\n <div className=\"slide-content prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed
|
|
1
|
+
{"version":3,"file":"slides-renderer.js","sources":["../../../src/components/slides-renderer.tsx"],"sourcesContent":["import { ReactNode } from 'react'\nimport { mdxComponents } from '@/components/mdx-components'\nimport { Slide } from '@/components/slide'\n\n/**\n * MDX components for slides - includes the Slide component\n */\nexport const slidesMdxComponents = {\n ...mdxComponents,\n Slide,\n}\n\n/**\n * Renders a single slide's content\n */\nexport function SlideContent({ children }: { children: ReactNode }) {\n return (\n <div className=\"slide-content prose dark:prose-invert prose-headings:tracking-tight prose-p:leading-relaxed\">\n {children}\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAOO,MAAM,sBAAsB;AAAA,EACjC,GAAG;AAAA,EACH;AACF;"}
|
|
@@ -5,7 +5,6 @@ import PostList from "../components/post-list.js";
|
|
|
5
5
|
import { ErrorDisplay } from "../components/page-error.js";
|
|
6
6
|
import { RunningBar } from "../components/running-bar.js";
|
|
7
7
|
import { Header } from "../components/header.js";
|
|
8
|
-
import { ContentTabs } from "../components/content-tabs.js";
|
|
9
8
|
import { getViewCounts, filterVisiblePosts, directoryToPostEntries } from "../lib/content-classification.js";
|
|
10
9
|
import siteConfig from "virtual:veslx-config";
|
|
11
10
|
function Home({ view }) {
|
|
@@ -16,7 +15,7 @@ function Home({ view }) {
|
|
|
16
15
|
const { directory, error } = useDirectory(directoryPath);
|
|
17
16
|
const activeView = view ?? config.defaultView;
|
|
18
17
|
const isRoot = path === "." || path === "" || isViewRoute;
|
|
19
|
-
|
|
18
|
+
directory ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory))) : {};
|
|
20
19
|
if (error) {
|
|
21
20
|
return /* @__PURE__ */ jsx(ErrorDisplay, { error, path });
|
|
22
21
|
}
|
|
@@ -30,10 +29,7 @@ function Home({ view }) {
|
|
|
30
29
|
/* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground", children: config.name }),
|
|
31
30
|
config.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: config.description })
|
|
32
31
|
] }),
|
|
33
|
-
/* @__PURE__ */
|
|
34
|
-
isRoot && directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(ContentTabs, { value: activeView, counts }) }),
|
|
35
|
-
directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory, view: isRoot ? activeView : "all" }) })
|
|
36
|
-
] })
|
|
32
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory, view: isRoot ? activeView : "all" }) }) })
|
|
37
33
|
] })
|
|
38
34
|
] })
|
|
39
35
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { ContentTabs } from \"@/components/content-tabs\";\nimport {\n type ContentView,\n directoryToPostEntries,\n filterVisiblePosts,\n getViewCounts,\n} from \"@/lib/content-classification\";\nimport siteConfig from \"virtual:veslx-config\";\n\ninterface HomeProps {\n view?: ContentView;\n}\n\nexport function Home({ view }: HomeProps) {\n const { \"*\": path = \".\" } = useParams();\n const config = siteConfig;\n\n // Normalize path - \"posts\", \"docs\", and \"all\" are view routes, not directories\n const isViewRoute = path === \"posts\" || path === \"docs\" || path === \"all\";\n const directoryPath = isViewRoute ? \".\" : path;\n\n const { directory, loading, error } = useDirectory(directoryPath)\n\n // Use prop view, fallback to config default\n const activeView = view ?? config.defaultView;\n\n const isRoot = path === \".\" || path === \"\" || isViewRoute;\n\n // Calculate counts for tabs (only meaningful on root)\n const counts = directory\n ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory)))\n : { posts: 0, docs: 0, all: 0 };\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-32\">\n {isRoot && (\n <div className=\"animate-fade-in\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n )}\n\n <div className=\"\">\n {isRoot && directory && (\n <div className=\"animate-fade-in\">\n <ContentTabs value={activeView} counts={counts} />\n </div>\n )}\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory} view={isRoot ? activeView : 'all'} />\n </div>\n )}\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport PostList from \"@/components/post-list\";\nimport { ErrorDisplay } from \"@/components/page-error\";\nimport { RunningBar } from \"@/components/running-bar\";\nimport { Header } from \"@/components/header\";\nimport { ContentTabs } from \"@/components/content-tabs\";\nimport {\n type ContentView,\n directoryToPostEntries,\n filterVisiblePosts,\n getViewCounts,\n} from \"@/lib/content-classification\";\nimport siteConfig from \"virtual:veslx-config\";\n\ninterface HomeProps {\n view?: ContentView;\n}\n\nexport function Home({ view }: HomeProps) {\n const { \"*\": path = \".\" } = useParams();\n const config = siteConfig;\n\n // Normalize path - \"posts\", \"docs\", and \"all\" are view routes, not directories\n const isViewRoute = path === \"posts\" || path === \"docs\" || path === \"all\";\n const directoryPath = isViewRoute ? \".\" : path;\n\n const { directory, loading, error } = useDirectory(directoryPath)\n\n // Use prop view, fallback to config default\n const activeView = view ?? config.defaultView;\n\n const isRoot = path === \".\" || path === \"\" || isViewRoute;\n\n // Calculate counts for tabs (only meaningful on root)\n const counts = directory\n ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory)))\n : { posts: 0, docs: 0, all: 0 };\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <RunningBar />\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-32\">\n {isRoot && (\n <div className=\"animate-fade-in\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n {/* {isRoot && directory && (\n <div className=\"animate-fade-in\">\n <ContentTabs value={activeView} counts={counts} />\n </div>\n )} */}\n {directory && (\n <div className=\"animate-fade-in\">\n <PostList directory={directory} view={isRoot ? activeView : 'all'} />\n </div>\n )}\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBO,SAAS,KAAK,EAAE,QAAmB;AACxC,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,SAAS;AAGf,QAAM,cAAc,SAAS,WAAW,SAAS,UAAU,SAAS;AACpE,QAAM,gBAAgB,cAAc,MAAM;AAE1C,QAAM,EAAE,WAAoB,UAAU,aAAa,aAAa;AAGhE,QAAM,aAAa,QAAQ,OAAO;AAElC,QAAM,SAAS,SAAS,OAAO,SAAS,MAAM;AAG/B,cACX,cAAc,mBAAmB,uBAAuB,SAAS,CAAC,CAAC,IACnE,CAA4B;AAEhC,MAAI,OAAO;AACT,WAAO,oBAAC,cAAA,EAAa,OAAc,KAAA,CAAY;AAAA,EACjD;AAQA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW;AAAA,wBACX,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,mBAAS,OAAO,OAAO,GAAG,OAAO,IAAI,MAAM,IAAI,GAAA,CAAG;AAAA,MAC1D,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,QAAA,UACC,qBAAC,OAAA,EAAI,WAAU,mBACb,UAAA;AAAA,UAAA,oBAAC,MAAA,EAAG,WAAU,qEACX,UAAA,OAAO,MACV;AAAA,UACC,OAAO,eACN,oBAAC,OAAE,WAAU,8BACV,iBAAO,YAAA,CACV;AAAA,QAAA,GAEJ;AAAA,4BAGD,OAAA,EAAI,WAAU,uBAMZ,UAAA,iCACE,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,YAAS,WAAsB,MAAM,SAAS,aAAa,OAAO,GACrE,EAAA,CAEJ;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -13,14 +13,14 @@ function SlidesPage() {
|
|
|
13
13
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
14
14
|
const mdxPath = rawPath;
|
|
15
15
|
const { Content, frontmatter, slideCount, loading, error } = useMDXSlides(mdxPath);
|
|
16
|
-
const totalSlides =
|
|
16
|
+
const totalSlides = slideCount || 0;
|
|
17
17
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
18
18
|
const titleSlideRef = useRef(null);
|
|
19
19
|
const contentRef = useRef(null);
|
|
20
20
|
useEffect(() => {
|
|
21
21
|
const slideParam = parseInt(searchParams.get("slide") || "0", 10);
|
|
22
22
|
if (slideParam > 0 && contentRef.current) {
|
|
23
|
-
const slideEl = contentRef.current.querySelector(`[data-slide-index="${slideParam
|
|
23
|
+
const slideEl = contentRef.current.querySelector(`[data-slide-index="${slideParam}"]`);
|
|
24
24
|
if (slideEl) {
|
|
25
25
|
slideEl.scrollIntoView({ behavior: "auto" });
|
|
26
26
|
}
|
|
@@ -33,7 +33,7 @@ function SlidesPage() {
|
|
|
33
33
|
if (entry.isIntersecting) {
|
|
34
34
|
const index = entry.target.getAttribute("data-slide-index");
|
|
35
35
|
if (index !== null) {
|
|
36
|
-
const slideNum = index === "title" ? 0 : parseInt(index, 10)
|
|
36
|
+
const slideNum = index === "title" ? 0 : parseInt(index, 10);
|
|
37
37
|
setCurrentSlide(slideNum);
|
|
38
38
|
setSearchParams(slideNum > 0 ? { slide: String(slideNum) } : {}, { replace: true });
|
|
39
39
|
}
|
|
@@ -53,19 +53,15 @@ function SlidesPage() {
|
|
|
53
53
|
}, [Content, setSearchParams]);
|
|
54
54
|
const goToPrevious = useCallback(() => {
|
|
55
55
|
const prev = Math.max(0, currentSlide - 1);
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
} else if (contentRef.current) {
|
|
59
|
-
const slideEl = contentRef.current.querySelector(`[data-slide-index="${prev - 1}"]`);
|
|
56
|
+
if (contentRef.current) {
|
|
57
|
+
const slideEl = contentRef.current.querySelector(`[data-slide-index="${prev}"]`);
|
|
60
58
|
slideEl == null ? void 0 : slideEl.scrollIntoView({ behavior: "smooth" });
|
|
61
59
|
}
|
|
62
60
|
}, [currentSlide]);
|
|
63
61
|
const goToNext = useCallback(() => {
|
|
64
62
|
const next = Math.min(totalSlides - 1, currentSlide + 1);
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
} else if (contentRef.current) {
|
|
68
|
-
const slideEl = contentRef.current.querySelector(`[data-slide-index="${next - 1}"]`);
|
|
63
|
+
if (contentRef.current) {
|
|
64
|
+
const slideEl = contentRef.current.querySelector(`[data-slide-index="${next}"]`);
|
|
69
65
|
slideEl == null ? void 0 : slideEl.scrollIntoView({ behavior: "smooth" });
|
|
70
66
|
}
|
|
71
67
|
}, [currentSlide, totalSlides]);
|
|
@@ -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\";\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
|
|
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 const totalSlides = slideCount || 0;\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}\"]`);\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);\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 (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${prev}\"]`);\n slideEl?.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [currentSlide]);\n\n const goToNext = useCallback(() => {\n const next = Math.min(totalSlides - 1, currentSlide + 1);\n if (contentRef.current) {\n const slideEl = contentRef.current.querySelector(`[data-slide-index=\"${next}\"]`);\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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"}
|
package/index.html
CHANGED
|
@@ -5,6 +5,19 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/logo_dark.png" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>veslx</title>
|
|
8
|
+
<!-- Prevent FOUC: apply theme before render -->
|
|
9
|
+
<script>
|
|
10
|
+
(function() {
|
|
11
|
+
var theme = localStorage.getItem('theme');
|
|
12
|
+
var isDark = theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
13
|
+
if (isDark) document.documentElement.classList.add('dark');
|
|
14
|
+
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
|
|
15
|
+
})();
|
|
16
|
+
</script>
|
|
17
|
+
<style>
|
|
18
|
+
html { background: hsl(0 0% 100%); }
|
|
19
|
+
html.dark { background: hsl(0 0% 7%); }
|
|
20
|
+
</style>
|
|
8
21
|
<!-- Google Fonts: DM Sans + DM Mono -->
|
|
9
22
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
23
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
package/package.json
CHANGED
|
@@ -8,9 +8,9 @@ interface ContentTabsProps {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const views: { key: ContentView; label: string; path: string }[] = [
|
|
11
|
-
{ key: "posts", label: "
|
|
12
|
-
{ key: "docs", label: "
|
|
13
|
-
{ key: "all", label: "
|
|
11
|
+
{ key: "posts", label: "Posts", path: "/posts" },
|
|
12
|
+
{ key: "docs", label: "Docs", path: "/docs" },
|
|
13
|
+
// { key: "all", label: "All", path: "/all" },
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
export function ContentTabs({ value, counts }: ContentTabsProps) {
|
|
@@ -28,7 +28,7 @@ export function ContentTabs({ value, counts }: ContentTabsProps) {
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<nav className="flex
|
|
31
|
+
<nav className="flex items-center gap-3 font-mono font-medium text-xs text-muted-foreground">
|
|
32
32
|
{views.map((view) => {
|
|
33
33
|
const disabled = isDisabled(view.key);
|
|
34
34
|
|
|
@@ -4,7 +4,7 @@ export function FigureCaption({ caption, label }: { caption?: string; label?: st
|
|
|
4
4
|
if (!caption && !label) return null;
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<figcaption className="
|
|
7
|
+
<figcaption className="mt-4">
|
|
8
8
|
<p className="text-[13px] leading-[1.6] text-muted-foreground">
|
|
9
9
|
{label && (
|
|
10
10
|
<span className="font-semibold text-foreground tracking-tight">
|
|
@@ -4,7 +4,7 @@ export function FigureHeader({ title, subtitle }: { title?: string; subtitle?: s
|
|
|
4
4
|
if (!title && !subtitle) return null;
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<div className="
|
|
7
|
+
<div className="mb-4">
|
|
8
8
|
{title && (
|
|
9
9
|
<h3 className="text-[15px] font-medium tracking-[-0.01em] text-foreground">
|
|
10
10
|
{renderMathInText(title)}
|
|
@@ -31,7 +31,7 @@ export function Lightbox({
|
|
|
31
31
|
|
|
32
32
|
return createPortal(
|
|
33
33
|
<div
|
|
34
|
-
className="fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-fade-
|
|
34
|
+
className="fixed inset-0 z-[9999] bg-background/98 backdrop-blur-md animate-[fade-in_150ms_ease-out]"
|
|
35
35
|
onClick={onClose}
|
|
36
36
|
{...{ [FULLSCREEN_DATA_ATTR]: "true" }}
|
|
37
37
|
style={{ top: 0, left: 0, right: 0, bottom: 0 }}
|
|
@@ -29,13 +29,15 @@ function getImageUrl(path: string): string {
|
|
|
29
29
|
|
|
30
30
|
export default function Gallery({
|
|
31
31
|
path,
|
|
32
|
-
globs = null,
|
|
32
|
+
globs = null,
|
|
33
33
|
caption,
|
|
34
34
|
captionLabel,
|
|
35
35
|
title,
|
|
36
36
|
subtitle,
|
|
37
37
|
limit = null,
|
|
38
38
|
page = 0,
|
|
39
|
+
children,
|
|
40
|
+
childAlign = "right",
|
|
39
41
|
}: {
|
|
40
42
|
path?: string;
|
|
41
43
|
globs?: string[] | null;
|
|
@@ -45,6 +47,8 @@ export default function Gallery({
|
|
|
45
47
|
subtitle?: string;
|
|
46
48
|
limit?: number | null;
|
|
47
49
|
page?: number;
|
|
50
|
+
children?: React.ReactNode;
|
|
51
|
+
childAlign?: "left" | "right";
|
|
48
52
|
}) {
|
|
49
53
|
const { paths, isLoading, isEmpty } = useGalleryImages({
|
|
50
54
|
path,
|
|
@@ -91,39 +95,74 @@ export default function Gallery({
|
|
|
91
95
|
);
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
const isCompact = images.length <= 3;
|
|
99
|
+
const isSingleWithChildren = images.length === 1 && children;
|
|
100
|
+
|
|
101
|
+
const imageElement = (index: number, img: LightboxImage, className?: string) => (
|
|
102
|
+
<div
|
|
103
|
+
key={index}
|
|
104
|
+
className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}
|
|
105
|
+
onClick={() => lightbox.open(index)}
|
|
106
|
+
>
|
|
107
|
+
<LoadingImage
|
|
108
|
+
src={img.src}
|
|
109
|
+
alt={img.label}
|
|
110
|
+
className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
|
|
94
115
|
return (
|
|
95
116
|
<>
|
|
96
|
-
<figure className=
|
|
97
|
-
<FigureHeader title={title} subtitle={subtitle} />
|
|
117
|
+
<figure className={`not-prose relative py-6 md:py-8 ${isCompact ? '' : '-mx-[calc((var(--gallery-width)-var(--content-width))/2+var(--page-padding))] px-[calc((var(--gallery-width)-var(--content-width))/2)]'}`}>
|
|
118
|
+
{!isSingleWithChildren && <FigureHeader title={title} subtitle={subtitle} />}
|
|
98
119
|
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
{isSingleWithChildren ? (
|
|
121
|
+
<div className={`flex gap-6 ${childAlign === 'left' ? '' : 'flex-row-reverse'}`}>
|
|
122
|
+
<div className="flex-1 text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5 flex flex-col">
|
|
123
|
+
{(title || subtitle) && <div className="invisible"><FigureHeader title={title} subtitle={subtitle} /></div>}
|
|
124
|
+
<div>{children}</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="w-3/5 flex-shrink-0">
|
|
127
|
+
<FigureHeader title={title} subtitle={subtitle} />
|
|
128
|
+
{imageElement(0, images[0])}
|
|
129
|
+
<FigureCaption caption={caption} label={captionLabel} />
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
) : isCompact ? (
|
|
133
|
+
<div className="flex gap-3">
|
|
134
|
+
{images.map((img, index) => imageElement(index, img, 'flex-1'))}
|
|
135
|
+
</div>
|
|
136
|
+
) : (
|
|
137
|
+
<Carousel className="w-full">
|
|
138
|
+
<CarouselContent className="-ml-2 md:-ml-3">
|
|
139
|
+
{images.map((img, index) => (
|
|
140
|
+
<CarouselItem
|
|
141
|
+
key={index}
|
|
142
|
+
className="pl-2 md:pl-3 md:basis-1/2 lg:basis-1/3 cursor-pointer group"
|
|
143
|
+
onClick={() => lightbox.open(index)}
|
|
144
|
+
>
|
|
145
|
+
<div className="aspect-square overflow-hidden rounded-sm bg-muted/10">
|
|
146
|
+
<LoadingImage
|
|
147
|
+
src={img.src}
|
|
148
|
+
alt={img.label}
|
|
149
|
+
className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
150
|
+
/>
|
|
151
|
+
</div>
|
|
152
|
+
</CarouselItem>
|
|
153
|
+
))}
|
|
154
|
+
</CarouselContent>
|
|
117
155
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
{images.length > 3 && (
|
|
157
|
+
<>
|
|
158
|
+
<CarouselPrevious className="left-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" />
|
|
159
|
+
<CarouselNext className="right-4 bg-background/80 backdrop-blur-sm border-border/50 hover:bg-background hover:border-border" />
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
</Carousel>
|
|
163
|
+
)}
|
|
125
164
|
|
|
126
|
-
<FigureCaption caption={caption} label={captionLabel} />
|
|
165
|
+
{!isSingleWithChildren && <FigureCaption caption={caption} label={captionLabel} />}
|
|
127
166
|
</figure>
|
|
128
167
|
|
|
129
168
|
{lightbox.isOpen && lightbox.selectedIndex !== null && (
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Link } from "react-router-dom";
|
|
1
|
+
import { Link, useParams } from "react-router-dom";
|
|
2
2
|
import { ModeToggle } from "./mode-toggle";
|
|
3
3
|
import { SiGithub } from "@icons-pack/react-simple-icons";
|
|
4
4
|
import { ChevronUp, ChevronDown } from "lucide-react";
|
|
5
5
|
import siteConfig from "virtual:veslx-config";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
6
7
|
|
|
7
8
|
interface HeaderProps {
|
|
8
9
|
slideControls?: {
|
|
@@ -16,6 +17,8 @@ interface HeaderProps {
|
|
|
16
17
|
export function Header({ slideControls }: HeaderProps = {}) {
|
|
17
18
|
const config = siteConfig;
|
|
18
19
|
|
|
20
|
+
const { "*": path } = useParams()
|
|
21
|
+
|
|
19
22
|
return (
|
|
20
23
|
<header className="print:hidden fixed top-0 left-0 right-0 z-40">
|
|
21
24
|
<div className="mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4">
|
|
@@ -30,31 +33,47 @@ export function Header({ slideControls }: HeaderProps = {}) {
|
|
|
30
33
|
|
|
31
34
|
<div className="flex-1" />
|
|
32
35
|
|
|
33
|
-
{/* Slide navigation controls */}
|
|
34
|
-
{slideControls && (
|
|
35
|
-
<nav className="flex items-center gap-1">
|
|
36
|
-
<button
|
|
37
|
-
onClick={slideControls.onPrevious}
|
|
38
|
-
className="p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200"
|
|
39
|
-
title="Previous slide (↑)"
|
|
40
|
-
>
|
|
41
|
-
<ChevronUp className="h-4 w-4" />
|
|
42
|
-
</button>
|
|
43
|
-
<span className="font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center">
|
|
44
|
-
{slideControls.current + 1}/{slideControls.total}
|
|
45
|
-
</span>
|
|
46
|
-
<button
|
|
47
|
-
onClick={slideControls.onNext}
|
|
48
|
-
className="p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200"
|
|
49
|
-
title="Next slide (↓)"
|
|
50
|
-
>
|
|
51
|
-
<ChevronDown className="h-4 w-4" />
|
|
52
|
-
</button>
|
|
53
|
-
</nav>
|
|
54
|
-
)}
|
|
55
|
-
|
|
56
36
|
{/* Navigation */}
|
|
57
|
-
<nav className="flex items-center gap-
|
|
37
|
+
<nav className="flex items-center gap-4">
|
|
38
|
+
{slideControls && (
|
|
39
|
+
<>
|
|
40
|
+
<button
|
|
41
|
+
onClick={slideControls.onPrevious}
|
|
42
|
+
className="p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200"
|
|
43
|
+
title="Previous slide (↑)"
|
|
44
|
+
>
|
|
45
|
+
<ChevronUp className="h-4 w-4" />
|
|
46
|
+
</button>
|
|
47
|
+
<span className="font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center">
|
|
48
|
+
{slideControls.current + 1}/{slideControls.total}
|
|
49
|
+
</span>
|
|
50
|
+
<button
|
|
51
|
+
onClick={slideControls.onNext}
|
|
52
|
+
className="p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200"
|
|
53
|
+
title="Next slide (↓)"
|
|
54
|
+
>
|
|
55
|
+
<ChevronDown className="h-4 w-4" />
|
|
56
|
+
</button>
|
|
57
|
+
</>
|
|
58
|
+
)}
|
|
59
|
+
<Link
|
|
60
|
+
to={`/posts`}
|
|
61
|
+
className={cn(
|
|
62
|
+
"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
63
|
+
path?.startsWith("posts") && "font-semibold text-foreground",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
Posts
|
|
67
|
+
</Link>
|
|
68
|
+
<Link
|
|
69
|
+
to={`/docs`}
|
|
70
|
+
className={cn(
|
|
71
|
+
"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
72
|
+
path?.startsWith("docs") && "font-semibold text-foreground",
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
Docs
|
|
76
|
+
</Link>
|
|
58
77
|
{config.github && (
|
|
59
78
|
<Link
|
|
60
79
|
to={`https://github.com/${config.github}`}
|
|
@@ -3,6 +3,10 @@ import Gallery from '@/components/gallery'
|
|
|
3
3
|
import { ParameterTable } from '@/components/parameter-table'
|
|
4
4
|
import { ParameterBadge } from '@/components/parameter-badge'
|
|
5
5
|
import { FrontMatter } from './front-matter'
|
|
6
|
+
import { HeroSlide } from './slides/hero-slide'
|
|
7
|
+
import { FigureSlide } from './slides/figure-slide'
|
|
8
|
+
import { TextSlide } from './slides/text-slide'
|
|
9
|
+
import { SlideOutline } from './slides/slide-outline'
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Smart link component that uses React Router for internal links
|
|
@@ -77,6 +81,14 @@ export const mdxComponents = {
|
|
|
77
81
|
|
|
78
82
|
ParameterBadge,
|
|
79
83
|
|
|
84
|
+
HeroSlide,
|
|
85
|
+
|
|
86
|
+
FigureSlide,
|
|
87
|
+
|
|
88
|
+
TextSlide,
|
|
89
|
+
|
|
90
|
+
SlideOutline,
|
|
91
|
+
|
|
80
92
|
// Headings - clean sans-serif
|
|
81
93
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {
|
|
82
94
|
const id = generateId(props.children)
|
|
@@ -2,7 +2,7 @@ import { Link } from "react-router-dom";
|
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
import type { DirectoryEntry } from "../../plugin/src/lib";
|
|
4
4
|
import { formatDate } from "@/lib/format-date";
|
|
5
|
-
import { ArrowRight } from "lucide-react";
|
|
5
|
+
import { ArrowRight, Presentation } from "lucide-react";
|
|
6
6
|
import {
|
|
7
7
|
type ContentView,
|
|
8
8
|
type PostEntry,
|
|
@@ -117,6 +117,8 @@ export default function PostList({ directory, view = 'all' }: PostListProps) {
|
|
|
117
117
|
linkPath = `/${post.path}`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
const isSlides = linkPath.endsWith('SLIDES.mdx');
|
|
121
|
+
|
|
120
122
|
return (
|
|
121
123
|
<Link
|
|
122
124
|
key={post.path}
|
|
@@ -126,15 +128,7 @@ export default function PostList({ directory, view = 'all' }: PostListProps) {
|
|
|
126
128
|
"transition-colors duration-150",
|
|
127
129
|
)}
|
|
128
130
|
>
|
|
129
|
-
<article className="flex items-
|
|
130
|
-
{/* Date - left side, fixed width */}
|
|
131
|
-
<time
|
|
132
|
-
dateTime={date?.toISOString()}
|
|
133
|
-
className="font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0 pt-0.5"
|
|
134
|
-
>
|
|
135
|
-
{date ? formatDate(date) : <span className="text-muted-foreground/30">—</span>}
|
|
136
|
-
</time>
|
|
137
|
-
|
|
131
|
+
<article className="flex items-center gap-4">
|
|
138
132
|
{/* Main content */}
|
|
139
133
|
<div className="flex-1 min-w-0">
|
|
140
134
|
<h3 className={cn(
|
|
@@ -152,6 +146,16 @@ export default function PostList({ directory, view = 'all' }: PostListProps) {
|
|
|
152
146
|
</p>
|
|
153
147
|
)}
|
|
154
148
|
</div>
|
|
149
|
+
|
|
150
|
+
{isSlides && (
|
|
151
|
+
<Presentation className="h-3 w-3 text-muted-foreground" />
|
|
152
|
+
)}
|
|
153
|
+
<time
|
|
154
|
+
dateTime={date?.toISOString()}
|
|
155
|
+
className="font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0"
|
|
156
|
+
>
|
|
157
|
+
{date && formatDate(date)}
|
|
158
|
+
</time>
|
|
155
159
|
</article>
|
|
156
160
|
</Link>
|
|
157
161
|
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
export function HeroSlide({
|
|
3
|
+
title,
|
|
4
|
+
subtitle,
|
|
5
|
+
author,
|
|
6
|
+
date,
|
|
7
|
+
}: {
|
|
8
|
+
title: string;
|
|
9
|
+
subtitle?: string;
|
|
10
|
+
author?: string;
|
|
11
|
+
date?: string;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div>
|
|
15
|
+
<h1 className="text-[clamp(2.5rem,6vw,5rem)] font-semibold leading-[1.1] tracking-[-0.02em] text-foreground text-balance">
|
|
16
|
+
{title}
|
|
17
|
+
</h1>
|
|
18
|
+
|
|
19
|
+
{subtitle && (
|
|
20
|
+
<p className="text-[clamp(1rem,2vw,1.5rem)] text-muted-foreground max-w-[50ch] leading-relaxed">
|
|
21
|
+
{subtitle}
|
|
22
|
+
</p>
|
|
23
|
+
)}
|
|
24
|
+
|
|
25
|
+
{(author || date) && (
|
|
26
|
+
<div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground mt-4">
|
|
27
|
+
{author && <span>{author}</span>}
|
|
28
|
+
{author && date && <span className="text-border">·</span>}
|
|
29
|
+
{date && <span>{date}</span>}
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export function SlideOutline({
|
|
4
|
+
children,
|
|
5
|
+
className,
|
|
6
|
+
size="md"
|
|
7
|
+
}: {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
size?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "full";
|
|
11
|
+
}) {
|
|
12
|
+
|
|
13
|
+
const wClasses: Record<string, string> = {
|
|
14
|
+
sm: "max-w-lg",
|
|
15
|
+
md: "max-w-2xl",
|
|
16
|
+
lg: "max-w-5xl",
|
|
17
|
+
xl: "max-w-7xl",
|
|
18
|
+
full: "max-w-full",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const wClassName = `${wClasses[size]} ${className ?? ""}`;
|
|
22
|
+
|
|
23
|
+
const hClasses: Record<string, string> = {
|
|
24
|
+
sm: "min-h-[300px]",
|
|
25
|
+
md: "min-h-[400px]",
|
|
26
|
+
lg: "min-h-[500px]",
|
|
27
|
+
xl: "min-h-[600px]",
|
|
28
|
+
full: "min-h-[600px]",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const hClassName = `${hClasses[size]} ${className ?? ""}`;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={`border rounded relative left-1/2 -translate-x-1/2 w-screen ${wClassName} ${hClassName} ${className}`}>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|