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.
Files changed (46) hide show
  1. package/bin/lib/build.ts +2 -1
  2. package/bin/lib/serve.ts +2 -1
  3. package/dist/client/components/header.js +2 -24
  4. package/dist/client/components/header.js.map +1 -1
  5. package/dist/client/components/mdx-components.js +2 -0
  6. package/dist/client/components/mdx-components.js.map +1 -1
  7. package/dist/client/components/post-list-item.js +43 -0
  8. package/dist/client/components/post-list-item.js.map +1 -0
  9. package/dist/client/components/post-list.js +39 -79
  10. package/dist/client/components/post-list.js.map +1 -1
  11. package/dist/client/hooks/use-mdx-content.js +64 -4
  12. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  13. package/dist/client/lib/content-classification.js +1 -22
  14. package/dist/client/lib/content-classification.js.map +1 -1
  15. package/dist/client/pages/content-router.js +2 -10
  16. package/dist/client/pages/content-router.js.map +1 -1
  17. package/dist/client/pages/home.js +7 -20
  18. package/dist/client/pages/home.js.map +1 -1
  19. package/dist/client/pages/index-post.js +34 -0
  20. package/dist/client/pages/index-post.js.map +1 -0
  21. package/dist/client/pages/post.js +1 -3
  22. package/dist/client/pages/post.js.map +1 -1
  23. package/dist/client/pages/slides.js +4 -3
  24. package/dist/client/pages/slides.js.map +1 -1
  25. package/package.json +1 -1
  26. package/plugin/src/plugin.ts +18 -8
  27. package/plugin/src/types.ts +17 -4
  28. package/src/components/header.tsx +2 -20
  29. package/src/components/mdx-components.tsx +3 -1
  30. package/src/components/post-list-item.tsx +54 -0
  31. package/src/components/post-list.tsx +58 -115
  32. package/src/components/welcome.tsx +2 -2
  33. package/src/hooks/use-mdx-content.ts +96 -7
  34. package/src/index.css +12 -0
  35. package/src/lib/content-classification.ts +0 -24
  36. package/src/pages/content-router.tsx +6 -17
  37. package/src/pages/home.tsx +8 -50
  38. package/src/pages/index-post.tsx +59 -0
  39. package/src/pages/post.tsx +1 -3
  40. package/src/pages/slides.tsx +5 -3
  41. package/src/vite-env.d.ts +11 -1
  42. package/vite.config.ts +4 -3
  43. package/dist/client/components/running-bar.js +0 -15
  44. package/dist/client/components/running-bar.js.map +0 -1
  45. package/src/components/content-tabs.tsx +0 -64
  46. 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 { 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;"}
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 * - /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;"}
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.mdIndexPost (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 { jsx, jsxs } from "react/jsx-runtime";
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { useParams } from "react-router-dom";
3
- import { useDirectory } from "../plugin/src/client.js";
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 { getViewCounts, filterVisiblePosts, directoryToPostEntries } from "../lib/content-classification.js";
9
- import siteConfig from "virtual:veslx-config";
10
- function Home({ view }) {
5
+ import veslxConfig from "virtual:veslx-config";
6
+ function Home() {
11
7
  const { "*": path = "." } = useParams();
12
- const config = siteConfig;
13
- const isViewRoute = path === "posts" || path === "docs" || path === "all";
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: directory && /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, { directory, view: isRoot ? activeView : "all" }) }) })
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 { 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-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 {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,uBACZ,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;"}
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: "my-12 mx-auto px-[var(--page-padding)] 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(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
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 { 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 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)] 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(--content-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,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,uOACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,GACF;AAEJ;"}
1
+ {"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { 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
- return /* @__PURE__ */ jsxs("main", { className: "slides-container", children: [
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 { 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 (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 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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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.27",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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 site config that can be updated on hot reload
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 site config
288
- return `export default ${JSON.stringify(siteConfig)};`
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
 
@@ -1,16 +1,22 @@
1
- export type ContentView = 'posts' | 'docs' | 'all';
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
- defaultView: ContentView;
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
- defaultView: 'all',
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 siteConfig from "virtual:veslx-config";
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 = siteConfig;
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
+ }