veslx 0.1.27 → 0.1.28
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/build.ts +2 -1
- package/bin/lib/serve.ts +2 -1
- package/dist/client/components/header.js +2 -24
- package/dist/client/components/header.js.map +1 -1
- package/dist/client/components/mdx-components.js +2 -0
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list-item.js +43 -0
- package/dist/client/components/post-list-item.js.map +1 -0
- package/dist/client/components/post-list.js +39 -79
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +64 -4
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/lib/content-classification.js +1 -22
- package/dist/client/lib/content-classification.js.map +1 -1
- package/dist/client/pages/content-router.js +2 -10
- package/dist/client/pages/content-router.js.map +1 -1
- package/dist/client/pages/home.js +7 -20
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/index-post.js +34 -0
- package/dist/client/pages/index-post.js.map +1 -0
- package/dist/client/pages/post.js +1 -3
- package/dist/client/pages/post.js.map +1 -1
- package/dist/client/pages/slides.js +4 -3
- package/dist/client/pages/slides.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/plugin.ts +18 -8
- package/plugin/src/types.ts +17 -4
- package/src/components/header.tsx +2 -20
- package/src/components/mdx-components.tsx +3 -1
- package/src/components/post-list-item.tsx +54 -0
- package/src/components/post-list.tsx +58 -115
- package/src/components/welcome.tsx +2 -2
- package/src/hooks/use-mdx-content.ts +96 -7
- package/src/index.css +12 -0
- package/src/lib/content-classification.ts +0 -24
- package/src/pages/content-router.tsx +6 -17
- package/src/pages/home.tsx +8 -50
- package/src/pages/index-post.tsx +59 -0
- package/src/pages/post.tsx +1 -3
- package/src/pages/slides.tsx +5 -3
- package/src/vite-env.d.ts +11 -1
- package/vite.config.ts +4 -3
- package/dist/client/components/running-bar.js +0 -15
- package/dist/client/components/running-bar.js.map +0 -1
- package/src/components/content-tabs.tsx +0 -64
- package/src/components/running-bar.tsx +0 -21
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import 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 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"],"names":[],"mappings":";AAYO,SAAS,eAAe,MAAiB;;AAC9C,WAAO,UAAK,WAAL,mBAAa,kBAAe,UAAK,SAAL,mBAAW,kBAAe,UAAK,WAAL,mBAAa;AAC5E;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;"}
|
|
@@ -3,18 +3,10 @@ import { useParams } from "react-router-dom";
|
|
|
3
3
|
import { Home } from "./home.js";
|
|
4
4
|
import { Post } from "./post.js";
|
|
5
5
|
import { SlidesPage } from "./slides.js";
|
|
6
|
+
import { IndexPost } from "./index-post.js";
|
|
6
7
|
function ContentRouter() {
|
|
7
8
|
var _a;
|
|
8
9
|
const { "*": path = "" } = useParams();
|
|
9
|
-
if (path === "posts") {
|
|
10
|
-
return /* @__PURE__ */ jsx(Home, { view: "posts" });
|
|
11
|
-
}
|
|
12
|
-
if (path === "docs") {
|
|
13
|
-
return /* @__PURE__ */ jsx(Home, { view: "docs" });
|
|
14
|
-
}
|
|
15
|
-
if (path === "all") {
|
|
16
|
-
return /* @__PURE__ */ jsx(Home, { view: "all" });
|
|
17
|
-
}
|
|
18
10
|
const filename = ((_a = path.split("/").pop()) == null ? void 0 : _a.toLowerCase()) || "";
|
|
19
11
|
const isSlides = path.endsWith(".slides.mdx") || path.endsWith(".slides.md") || filename === "slides.mdx" || filename === "slides.md";
|
|
20
12
|
if (isSlides) {
|
|
@@ -23,7 +15,7 @@ function ContentRouter() {
|
|
|
23
15
|
if (path.endsWith(".mdx") || path.endsWith(".md")) {
|
|
24
16
|
return /* @__PURE__ */ jsx(Post, {});
|
|
25
17
|
}
|
|
26
|
-
return /* @__PURE__ */ jsx(Home, {});
|
|
18
|
+
return /* @__PURE__ */ jsx(IndexPost, { fallback: /* @__PURE__ */ jsx(Home, {}) });
|
|
27
19
|
}
|
|
28
20
|
export {
|
|
29
21
|
ContentRouter
|
|
@@ -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 * -
|
|
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\"\nimport { IndexPost } from \"./index-post\"\n\n/**\n * Routes to the appropriate page based on the URL path:\n * - *.slides.mdx or *SLIDES.mdx → SlidesPage\n * - *.mdx or *.md → Post\n * - directory with index.mdx/index.md → IndexPost (renders index file)\n * - everything else → Home (directory listing)\n */\nexport function ContentRouter() {\n const { \"*\": path = \"\" } = useParams()\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/MD file\n if (path.endsWith('.mdx') || path.endsWith('.md')) {\n return <Post />\n }\n\n // For directories, try to render index.mdx/index.md, fallback to Home\n return <IndexPost fallback={<Home />} />\n}\n"],"names":[],"mappings":";;;;;;AAaO,SAAS,gBAAgB;;AAC9B,QAAM,EAAE,KAAK,OAAO,GAAA,IAAO,UAAA;AAG3B,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,SAAO,oBAAC,WAAA,EAAU,UAAU,oBAAC,QAAK,GAAI;AACxC;"}
|
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
|
-
import {
|
|
4
|
-
import PostList from "../components/post-list.js";
|
|
5
|
-
import { ErrorDisplay } from "../components/page-error.js";
|
|
6
|
-
import { RunningBar } from "../components/running-bar.js";
|
|
3
|
+
import { PostList } from "../components/post-list.js";
|
|
7
4
|
import { Header } from "../components/header.js";
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
function Home({ view }) {
|
|
5
|
+
import veslxConfig from "virtual:veslx-config";
|
|
6
|
+
function Home() {
|
|
11
7
|
const { "*": path = "." } = useParams();
|
|
12
|
-
const config =
|
|
13
|
-
const
|
|
14
|
-
const directoryPath = isViewRoute ? "." : path;
|
|
15
|
-
const { directory, error } = useDirectory(directoryPath);
|
|
16
|
-
const activeView = view ?? config.defaultView;
|
|
17
|
-
const isRoot = path === "." || path === "" || isViewRoute;
|
|
18
|
-
directory ? getViewCounts(filterVisiblePosts(directoryToPostEntries(directory))) : {};
|
|
19
|
-
if (error) {
|
|
20
|
-
return /* @__PURE__ */ jsx(ErrorDisplay, { error, path });
|
|
21
|
-
}
|
|
8
|
+
const config = veslxConfig.site;
|
|
9
|
+
const isRoot = path === "." || path === "";
|
|
22
10
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
|
|
23
|
-
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
24
11
|
/* @__PURE__ */ jsx(Header, {}),
|
|
25
12
|
/* @__PURE__ */ jsxs("main", { className: "flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]", children: [
|
|
26
13
|
/* @__PURE__ */ jsx("title", { children: isRoot ? config.name : `${config.name} - ${path}` }),
|
|
@@ -29,7 +16,7 @@ function Home({ view }) {
|
|
|
29
16
|
/* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground", children: config.name }),
|
|
30
17
|
config.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-muted-foreground", children: config.description })
|
|
31
18
|
] }),
|
|
32
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children:
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, {}) }) })
|
|
33
20
|
] })
|
|
34
21
|
] })
|
|
35
22
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport {
|
|
1
|
+
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { PostList } from \"@/components/post-list\";\nimport { Header } from \"@/components/header\";\nimport veslxConfig from \"virtual:veslx-config\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const config = veslxConfig.site;\n\n const isRoot = path === \".\" || path === \"\";\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\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-12\">\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 <div className=\"animate-fade-in\">\n <PostList />\n </div>\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;AAKO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,SAAS,YAAY;AAE3B,QAAM,SAAS,SAAS,OAAO,SAAS;AAExC,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,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,QAGF,oBAAC,OAAA,EAAI,WAAU,uBACb,UAAA,oBAAC,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,UAAA,CAAA,CAAS,EAAA,CACZ,EAAA,CACF;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx, Fragment, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useParams } from "react-router-dom";
|
|
3
|
+
import { isSimulationRunning } from "../plugin/src/client.js";
|
|
4
|
+
import Loading from "../components/loading.js";
|
|
5
|
+
import { Header } from "../components/header.js";
|
|
6
|
+
import { useIndexContent } from "../hooks/use-mdx-content.js";
|
|
7
|
+
import { mdxComponents } from "../components/mdx-components.js";
|
|
8
|
+
import { FrontmatterProvider } from "../lib/frontmatter-context.js";
|
|
9
|
+
function IndexPost({ fallback }) {
|
|
10
|
+
const { "*": rawPath = "." } = useParams();
|
|
11
|
+
const dirPath = rawPath || ".";
|
|
12
|
+
const { Content, frontmatter, loading, notFound } = useIndexContent(dirPath);
|
|
13
|
+
const isRunning = isSimulationRunning();
|
|
14
|
+
if (loading) return /* @__PURE__ */ jsx(Loading, {});
|
|
15
|
+
if (notFound) {
|
|
16
|
+
return /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
17
|
+
}
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
|
|
19
|
+
/* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
20
|
+
/* @__PURE__ */ jsx(Header, {}),
|
|
21
|
+
/* @__PURE__ */ jsxs("main", { className: "flex-1 w-full overflow-x-clip", children: [
|
|
22
|
+
isRunning && /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-3", children: [
|
|
23
|
+
/* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-current animate-pulse" }),
|
|
24
|
+
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
|
|
25
|
+
/* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
|
|
26
|
+
] }) }),
|
|
27
|
+
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "my-12 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
|
|
28
|
+
] })
|
|
29
|
+
] });
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
IndexPost
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=index-post.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-post.js","sources":["../../../src/pages/index-post.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { isSimulationRunning } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { Header } from \"@/components/header\";\nimport { useIndexContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\ninterface IndexPostProps {\n fallback: ReactNode;\n}\n\n/**\n * Attempts to render an index.mdx or index.md file for a directory.\n * Falls back to the provided component if no index file exists.\n */\nexport function IndexPost({ fallback }: IndexPostProps) {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // Normalize path for index lookup\n const dirPath = rawPath || \".\";\n\n const { Content, frontmatter, loading, notFound } = useIndexContent(dirPath);\n const isRunning = isSimulationRunning();\n\n if (loading) return <Loading />\n\n // No index file found - render fallback (usually Home)\n if (notFound) {\n return <>{fallback}</>;\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\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-12 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AAiBO,SAAS,UAAU,EAAE,YAA4B;AACtD,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU,WAAW;AAE3B,QAAM,EAAE,SAAS,aAAa,SAAS,SAAA,IAAa,gBAAgB,OAAO;AAC3E,QAAM,YAAY,oBAAA;AAElB,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAG7B,MAAI,UAAU;AACZ,2CAAU,UAAA,SAAA,CAAS;AAAA,EACrB;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,iCACb,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,uFACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -2,7 +2,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
3
|
import { useDirectory, isSimulationRunning, findSlides } from "../plugin/src/client.js";
|
|
4
4
|
import Loading from "../components/loading.js";
|
|
5
|
-
import { RunningBar } from "../components/running-bar.js";
|
|
6
5
|
import { Header } from "../components/header.js";
|
|
7
6
|
import { useMDXContent } from "../hooks/use-mdx-content.js";
|
|
8
7
|
import { mdxComponents } from "../components/mdx-components.js";
|
|
@@ -24,7 +23,6 @@ function Post() {
|
|
|
24
23
|
}
|
|
25
24
|
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col bg-background noise-overlay", children: [
|
|
26
25
|
/* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
27
|
-
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
28
26
|
/* @__PURE__ */ jsx(Header, {}),
|
|
29
27
|
/* @__PURE__ */ jsxs("main", { className: "flex-1 w-full overflow-x-clip", children: [
|
|
30
28
|
isRunning && /* @__PURE__ */ jsx("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", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-3", children: [
|
|
@@ -32,7 +30,7 @@ function Post() {
|
|
|
32
30
|
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
|
|
33
31
|
/* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
|
|
34
32
|
] }) }),
|
|
35
|
-
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "
|
|
33
|
+
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "mt-12 mb-64 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
|
|
36
34
|
] })
|
|
37
35
|
] });
|
|
38
36
|
}
|
|
@@ -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 {
|
|
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 { 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 <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\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=\"mt-12 mb-64 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;AASO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU;AAGhB,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,EAAE,KAAK;AAExD,QAAM,EAAE,UAA+B,IAAI,aAAa,OAAO;AAC/D,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,OAAO;AAClF,QAAM,YAAY,oBAAA;AAGlB,MAAI,WAAW;AACJ,eAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAwB;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,iCACb,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,6FACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -3,11 +3,12 @@ import { useState, useRef, useEffect, useCallback } from "react";
|
|
|
3
3
|
import { FULLSCREEN_DATA_ATTR } from "../lib/constants.js";
|
|
4
4
|
import { useParams, useSearchParams } from "react-router-dom";
|
|
5
5
|
import Loading from "../components/loading.js";
|
|
6
|
-
import { RunningBar } from "../components/running-bar.js";
|
|
7
6
|
import { Header } from "../components/header.js";
|
|
8
7
|
import { useMDXSlides } from "../hooks/use-mdx-content.js";
|
|
9
8
|
import { slidesMdxComponents } from "../components/slides-renderer.js";
|
|
10
9
|
import { FrontmatterProvider } from "../lib/frontmatter-context.js";
|
|
10
|
+
import veslxConfig from "virtual:veslx-config";
|
|
11
|
+
import { cn } from "../lib/utils.js";
|
|
11
12
|
function SlidesPage() {
|
|
12
13
|
const { "*": rawPath = "." } = useParams();
|
|
13
14
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
@@ -87,9 +88,9 @@ function SlidesPage() {
|
|
|
87
88
|
if (!Content) {
|
|
88
89
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-12 text-muted-foreground font-mono text-sm", children: 'no slides found — use "---" to separate slides' });
|
|
89
90
|
}
|
|
90
|
-
|
|
91
|
+
const scrollSnap = veslxConfig.slides.scrollSnap;
|
|
92
|
+
return /* @__PURE__ */ jsxs("main", { className: cn("slides-container", scrollSnap && "slides-scroll-snap"), children: [
|
|
91
93
|
/* @__PURE__ */ jsx("title", { children: frontmatter == null ? void 0 : frontmatter.title }),
|
|
92
|
-
/* @__PURE__ */ jsx(RunningBar, {}),
|
|
93
94
|
/* @__PURE__ */ jsx(
|
|
94
95
|
Header,
|
|
95
96
|
{
|
|
@@ -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 {
|
|
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 { Header } from \"@/components/header\";\nimport { useMDXSlides } from \"@/hooks/use-mdx-content\";\nimport { slidesMdxComponents } from \"@/components/slides-renderer\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\nimport veslxConfig from \"virtual:veslx-config\";\nimport { cn } from \"@/lib/utils\";\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 (up/down only - left/right reserved for horizontal scrolling)\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n goToPrevious();\n } else if (e.key === \"ArrowDown\") {\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 const scrollSnap = veslxConfig.slides.scrollSnap;\n\n return (\n <main className={cn(\"slides-container\", scrollSnap && \"slides-scroll-snap\")}>\n <title>{frontmatter?.title}</title>\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/package.json
CHANGED
package/plugin/src/plugin.ts
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'path'
|
|
|
3
3
|
import fs from 'fs'
|
|
4
4
|
import yaml from 'js-yaml'
|
|
5
5
|
import type { IncomingMessage, ServerResponse } from 'http'
|
|
6
|
-
import { type VeslxConfig, type ResolvedSiteConfig, DEFAULT_SITE_CONFIG } from './types'
|
|
6
|
+
import { type VeslxConfig, type ResolvedSiteConfig, type ResolvedSlidesConfig, type ResolvedConfig, DEFAULT_SITE_CONFIG, DEFAULT_SLIDES_CONFIG } from './types'
|
|
7
7
|
import matter from 'gray-matter'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -89,12 +89,17 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
89
89
|
const dir = contentDir
|
|
90
90
|
const configPath = options?.configPath
|
|
91
91
|
|
|
92
|
-
// Mutable
|
|
92
|
+
// Mutable config that can be updated on hot reload
|
|
93
93
|
let siteConfig: ResolvedSiteConfig = {
|
|
94
94
|
...DEFAULT_SITE_CONFIG,
|
|
95
95
|
...config?.site,
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
let slidesConfig: ResolvedSlidesConfig = {
|
|
99
|
+
...DEFAULT_SLIDES_CONFIG,
|
|
100
|
+
...config?.slides,
|
|
101
|
+
}
|
|
102
|
+
|
|
98
103
|
// Helper to reload config from file
|
|
99
104
|
function reloadConfig(): boolean {
|
|
100
105
|
if (!configPath || !fs.existsSync(configPath)) return false
|
|
@@ -105,6 +110,10 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
105
110
|
...DEFAULT_SITE_CONFIG,
|
|
106
111
|
...parsed?.site,
|
|
107
112
|
}
|
|
113
|
+
slidesConfig = {
|
|
114
|
+
...DEFAULT_SLIDES_CONFIG,
|
|
115
|
+
...parsed?.slides,
|
|
116
|
+
}
|
|
108
117
|
return true
|
|
109
118
|
} catch (e) {
|
|
110
119
|
console.error('[veslx] Failed to reload config:', e)
|
|
@@ -252,12 +261,12 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
252
261
|
|
|
253
262
|
// Generate virtual module with import.meta.glob for MDX files
|
|
254
263
|
return `
|
|
255
|
-
export const posts = import.meta.glob('@content/**/*.mdx', {
|
|
264
|
+
export const posts = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md'], {
|
|
256
265
|
import: 'default',
|
|
257
266
|
query: { skipSlides: true }
|
|
258
267
|
});
|
|
259
|
-
export const allMdx = import.meta.glob('@content/**/*.mdx');
|
|
260
|
-
export const slides = import.meta.glob(['@content/**/SLIDES.mdx', '@content/**/*.slides.mdx']);
|
|
268
|
+
export const allMdx = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md']);
|
|
269
|
+
export const slides = import.meta.glob(['@content/**/SLIDES.mdx', '@content/**/SLIDES.md', '@content/**/*.slides.mdx', '@content/**/*.slides.md']);
|
|
261
270
|
|
|
262
271
|
// All files for directory tree building (web-compatible files only)
|
|
263
272
|
export const files = import.meta.glob([
|
|
@@ -280,12 +289,13 @@ export const files = import.meta.glob([
|
|
|
280
289
|
export const frontmatters = ${JSON.stringify(frontmatterData)};
|
|
281
290
|
|
|
282
291
|
// Legacy aliases for backwards compatibility
|
|
283
|
-
export const modules = import.meta.glob('@content/**/*.mdx');
|
|
292
|
+
export const modules = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md']);
|
|
284
293
|
`
|
|
285
294
|
}
|
|
286
295
|
if (id === RESOLVED_VIRTUAL_CONFIG_ID) {
|
|
287
|
-
// Generate virtual module with
|
|
288
|
-
|
|
296
|
+
// Generate virtual module with full config
|
|
297
|
+
const fullConfig: ResolvedConfig = { site: siteConfig, slides: slidesConfig }
|
|
298
|
+
return `export default ${JSON.stringify(fullConfig)};`
|
|
289
299
|
}
|
|
290
300
|
},
|
|
291
301
|
|
package/plugin/src/types.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface SlidesConfig {
|
|
2
|
+
scrollSnap?: boolean;
|
|
3
|
+
}
|
|
2
4
|
|
|
3
5
|
export interface SiteConfig {
|
|
4
6
|
name?: string;
|
|
5
7
|
description?: string;
|
|
6
8
|
github?: string;
|
|
7
9
|
homepage?: string;
|
|
8
|
-
defaultView?: ContentView;
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export interface VeslxConfig {
|
|
12
13
|
dir?: string;
|
|
13
14
|
site?: SiteConfig;
|
|
15
|
+
slides?: SlidesConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResolvedSlidesConfig {
|
|
19
|
+
scrollSnap: boolean;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export interface ResolvedSiteConfig {
|
|
@@ -18,7 +24,11 @@ export interface ResolvedSiteConfig {
|
|
|
18
24
|
description: string;
|
|
19
25
|
github: string;
|
|
20
26
|
homepage: string;
|
|
21
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ResolvedConfig {
|
|
30
|
+
site: ResolvedSiteConfig;
|
|
31
|
+
slides: ResolvedSlidesConfig;
|
|
22
32
|
}
|
|
23
33
|
|
|
24
34
|
export const DEFAULT_SITE_CONFIG: ResolvedSiteConfig = {
|
|
@@ -26,5 +36,8 @@ export const DEFAULT_SITE_CONFIG: ResolvedSiteConfig = {
|
|
|
26
36
|
description: '',
|
|
27
37
|
github: '',
|
|
28
38
|
homepage: '',
|
|
29
|
-
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_SLIDES_CONFIG: ResolvedSlidesConfig = {
|
|
42
|
+
scrollSnap: true,
|
|
30
43
|
};
|
|
@@ -2,7 +2,7 @@ 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
|
-
import
|
|
5
|
+
import veslxConfig from "virtual:veslx-config";
|
|
6
6
|
import { cn } from "@/lib/utils";
|
|
7
7
|
|
|
8
8
|
interface HeaderProps {
|
|
@@ -15,7 +15,7 @@ interface HeaderProps {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export function Header({ slideControls }: HeaderProps = {}) {
|
|
18
|
-
const config =
|
|
18
|
+
const config = veslxConfig.site;
|
|
19
19
|
|
|
20
20
|
const { "*": path } = useParams()
|
|
21
21
|
|
|
@@ -62,24 +62,6 @@ export function Header({ slideControls }: HeaderProps = {}) {
|
|
|
62
62
|
</button>
|
|
63
63
|
</>
|
|
64
64
|
)}
|
|
65
|
-
<Link
|
|
66
|
-
to={`/posts`}
|
|
67
|
-
className={cn(
|
|
68
|
-
"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
69
|
-
path?.startsWith("posts") && "font-semibold text-foreground",
|
|
70
|
-
)}
|
|
71
|
-
>
|
|
72
|
-
Posts
|
|
73
|
-
</Link>
|
|
74
|
-
<Link
|
|
75
|
-
to={`/docs`}
|
|
76
|
-
className={cn(
|
|
77
|
-
"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
|
|
78
|
-
path?.startsWith("docs") && "font-semibold text-foreground",
|
|
79
|
-
)}
|
|
80
|
-
>
|
|
81
|
-
Docs
|
|
82
|
-
</Link>
|
|
83
65
|
{config.github && (
|
|
84
66
|
<Link
|
|
85
67
|
to={`https://github.com/${config.github}`}
|
|
@@ -7,7 +7,7 @@ import { HeroSlide } from './slides/hero-slide'
|
|
|
7
7
|
import { FigureSlide } from './slides/figure-slide'
|
|
8
8
|
import { TextSlide } from './slides/text-slide'
|
|
9
9
|
import { SlideOutline } from './slides/slide-outline'
|
|
10
|
-
|
|
10
|
+
import { PostList } from '@/components/post-list'
|
|
11
11
|
/**
|
|
12
12
|
* Smart link component that uses React Router for internal links
|
|
13
13
|
* and regular anchor tags for external links.
|
|
@@ -89,6 +89,8 @@ export const mdxComponents = {
|
|
|
89
89
|
|
|
90
90
|
SlideOutline,
|
|
91
91
|
|
|
92
|
+
PostList,
|
|
93
|
+
|
|
92
94
|
// Headings - clean sans-serif
|
|
93
95
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {
|
|
94
96
|
const id = generateId(props.children)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Link } from "react-router-dom";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { formatDate } from "@/lib/format-date";
|
|
4
|
+
import { ArrowRight, Presentation } from "lucide-react";
|
|
5
|
+
|
|
6
|
+
interface PostListItemProps {
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
date?: Date;
|
|
10
|
+
linkPath: string;
|
|
11
|
+
isSlides?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function PostListItem({ title, description, date, linkPath, isSlides }: PostListItemProps) {
|
|
15
|
+
return (
|
|
16
|
+
<Link
|
|
17
|
+
to={linkPath}
|
|
18
|
+
className={cn(
|
|
19
|
+
"group block py-3 px-3 -mx-3 rounded-md",
|
|
20
|
+
"transition-colors duration-150",
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
<article className="flex items-center gap-4">
|
|
24
|
+
{/* Main content */}
|
|
25
|
+
<div className="flex-1 min-w-0">
|
|
26
|
+
<div className={cn(
|
|
27
|
+
"text-sm font-medium text-foreground",
|
|
28
|
+
"group-hover:underline",
|
|
29
|
+
"flex items-center gap-2"
|
|
30
|
+
)}>
|
|
31
|
+
<span>{title}</span>
|
|
32
|
+
<ArrowRight className="h-3 w-3 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200 text-primary" />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
{description && (
|
|
36
|
+
<div className="text-sm text-muted-foreground line-clamp-1 mt-0.5">
|
|
37
|
+
{description}
|
|
38
|
+
</div>
|
|
39
|
+
)}
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{isSlides && (
|
|
43
|
+
<Presentation className="h-3 w-3 text-muted-foreground" />
|
|
44
|
+
)}
|
|
45
|
+
<time
|
|
46
|
+
dateTime={date?.toISOString()}
|
|
47
|
+
className="font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0"
|
|
48
|
+
>
|
|
49
|
+
{date && formatDate(date)}
|
|
50
|
+
</time>
|
|
51
|
+
</article>
|
|
52
|
+
</Link>
|
|
53
|
+
);
|
|
54
|
+
}
|