veslx 0.1.65 → 0.1.66

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 (44) hide show
  1. package/README.md +0 -16
  2. package/dist/client/components/mdx-components.js +0 -14
  3. package/dist/client/components/mdx-components.js.map +1 -1
  4. package/dist/client/components/post-list-item.js +6 -5
  5. package/dist/client/components/post-list-item.js.map +1 -1
  6. package/dist/client/components/post-list.js +6 -1
  7. package/dist/client/components/post-list.js.map +1 -1
  8. package/dist/client/lib/content-classification.js +11 -2
  9. package/dist/client/lib/content-classification.js.map +1 -1
  10. package/dist/client/plugin/src/client.js +7 -40
  11. package/dist/client/plugin/src/client.js.map +1 -1
  12. package/dist/plugin/src/client.js +7 -0
  13. package/dist/plugin/src/plugin.js +5 -2
  14. package/package.json +1 -1
  15. package/plugin/src/client.tsx +10 -0
  16. package/plugin/src/plugin.ts +5 -2
  17. package/src/components/index.ts +0 -3
  18. package/src/components/mdx-components.tsx +0 -21
  19. package/src/components/post-list-item.tsx +10 -6
  20. package/src/components/post-list.tsx +8 -2
  21. package/src/lib/content-classification.ts +12 -2
  22. package/dist/client/components/parameter-badge.js +0 -48
  23. package/dist/client/components/parameter-badge.js.map +0 -1
  24. package/dist/client/components/parameter-table.js +0 -216
  25. package/dist/client/components/parameter-table.js.map +0 -1
  26. package/dist/client/components/slides/figure-slide.js +0 -14
  27. package/dist/client/components/slides/figure-slide.js.map +0 -1
  28. package/dist/client/components/slides/hero-slide.js +0 -21
  29. package/dist/client/components/slides/hero-slide.js.map +0 -1
  30. package/dist/client/components/slides/slide-outline.js +0 -28
  31. package/dist/client/components/slides/slide-outline.js.map +0 -1
  32. package/dist/client/components/slides/text-slide.js +0 -18
  33. package/dist/client/components/slides/text-slide.js.map +0 -1
  34. package/dist/client/components/veslx-cat.js +0 -40
  35. package/dist/client/components/veslx-cat.js.map +0 -1
  36. package/dist/client/lib/parameter-utils.js +0 -108
  37. package/dist/client/lib/parameter-utils.js.map +0 -1
  38. package/src/components/parameter-badge.tsx +0 -78
  39. package/src/components/parameter-table.tsx +0 -369
  40. package/src/components/slides/figure-slide.tsx +0 -16
  41. package/src/components/slides/hero-slide.tsx +0 -34
  42. package/src/components/slides/slide-outline.tsx +0 -38
  43. package/src/components/slides/text-slide.tsx +0 -35
  44. package/src/components/veslx-cat.tsx +0 -73
package/README.md CHANGED
@@ -156,22 +156,6 @@ Display images with titles, captions, and a fullscreen lightbox:
156
156
  | `captionLabel` | `string` | Label prefix (e.g., "Figure 1") |
157
157
  | `limit` | `number` | Max images to show |
158
158
 
159
- ### VeslxParameterTable
160
-
161
- Display YAML or JSON configuration files with collapsible sections:
162
-
163
- ```mdx
164
- <VeslxParameterTable
165
- path="config.yaml"
166
- keys={[".simulation.timestep", ".model.layers"]}
167
- />
168
- ```
169
-
170
- | Prop | Type | Description |
171
- |------|------|-------------|
172
- | `path` | `string` | Path to YAML/JSON file |
173
- | `keys` | `string[]` | jq-like paths to filter (optional) |
174
-
175
159
  ### Local Imports
176
160
 
177
161
  Import React components directly from your content directory:
@@ -1,15 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useLocation, Link } from "react-router-dom";
3
3
  import Gallery from "./gallery/index.js";
4
- import { ParameterTable } from "./parameter-table.js";
5
- import { ParameterBadge } from "./parameter-badge.js";
6
- import { VeslxCat } from "./veslx-cat.js";
7
4
  import { VeslxTOC } from "./veslx-toc.js";
8
5
  import { FrontMatter } from "./front-matter.js";
9
- import { HeroSlide } from "./slides/hero-slide.js";
10
- import { FigureSlide } from "./slides/figure-slide.js";
11
- import { TextSlide } from "./slides/text-slide.js";
12
- import { SlideOutline } from "./slides/slide-outline.js";
13
6
  import { PostList } from "./post-list.js";
14
7
  import { PostListItem } from "./post-list-item.js";
15
8
  import { VeslxSearch } from "./veslx-search.js";
@@ -57,15 +50,8 @@ const mdxComponents = {
57
50
  FrontMatter,
58
51
  VeslxFrontMatter: FrontMatter,
59
52
  VeslxGallery: Gallery,
60
- VeslxParameterTable: ParameterTable,
61
- VeslxParameterBadge: ParameterBadge,
62
- VeslxHeroSlide: HeroSlide,
63
- VeslxFigureSlide: FigureSlide,
64
- VeslxTextSlide: TextSlide,
65
- VeslxSlideOutline: SlideOutline,
66
53
  VeslxPostList: PostList,
67
54
  VeslxPostListItem: PostListItem,
68
- VeslxCat,
69
55
  VeslxTOC,
70
56
  VeslxSearch,
71
57
  // Headings - clean sans-serif
@@ -1 +1 @@
1
- {"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { ParameterTable } from '@/components/parameter-table'\nimport { ParameterBadge } from '@/components/parameter-badge'\nimport { VeslxCat } from '@/components/veslx-cat'\nimport { VeslxTOC } from '@/components/veslx-toc'\nimport { FrontMatter } from './front-matter'\nimport { HeroSlide } from './slides/hero-slide'\nimport { FigureSlide } from './slides/figure-slide'\nimport { TextSlide } from './slides/text-slide'\nimport { SlideOutline } from './slides/slide-outline'\nimport { PostList } from '@/components/post-list'\nimport { PostListItem } from '@/components/post-list-item'\nimport { VeslxSearch } from '@/components/veslx-search'\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter: FrontMatter,\n\n VeslxFrontMatter: FrontMatter,\n\n VeslxGallery: Gallery,\n\n VeslxParameterTable: ParameterTable,\n\n VeslxParameterBadge: ParameterBadge,\n\n VeslxHeroSlide: HeroSlide,\n\n VeslxFigureSlide: FigureSlide,\n\n VeslxTextSlide: TextSlide,\n\n VeslxSlideOutline: SlideOutline,\n\n VeslxPostList: PostList,\n\n VeslxPostListItem: PostListItem,\n\n VeslxCat: VeslxCat,\n\n VeslxTOC: VeslxTOC,\n\n VeslxSearch: VeslxSearch,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (\n // Styling handled by CSS - inline code gets bg via :not(pre) > code selector\n <code className={className} {...props} />\n ),\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { VeslxTOC } from '@/components/veslx-toc'\nimport { FrontMatter } from './front-matter'\nimport { PostList } from '@/components/post-list'\nimport { PostListItem } from '@/components/post-list-item'\nimport { VeslxSearch } from '@/components/veslx-search'\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter: FrontMatter,\n\n VeslxFrontMatter: FrontMatter,\n\n VeslxGallery: Gallery,\n\n VeslxPostList: PostList,\n\n VeslxPostListItem: PostListItem,\n\n VeslxTOC: VeslxTOC,\n\n VeslxSearch: VeslxSearch,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (\n // Styling handled by CSS - inline code gets bg via :not(pre) > code selector\n <code className={className} {...props} />\n ),\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":";;;;;;;;AAWA,SAAS,UAAU,EAAE,MAAM,UAAU,GAAG,SAAwD;AAC9F,QAAM,WAAW,YAAA;AAGjB,QAAM,cAAa,6BAAM,WAAW,aAAW,6BAAM,WAAW,gBAAc,6BAAM,WAAW;AAG/F,QAAM,aAAa,6BAAM,WAAW;AAEpC,MAAI,cAAc,cAAc,CAAC,MAAM;AACrC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAI,aAAa,EAAE,QAAQ,UAAU,KAAK,sBAAA,IAA0B,CAAA;AAAA,QACpE,GAAG;AAAA,QAEH;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAGA,MAAI,eAAe;AACnB,MAAI,KAAK,WAAW,IAAI,KAAK,KAAK,WAAW,KAAK,GAAG;AAEnD,UAAM,cAAc,SAAS;AAC7B,UAAM,aAAa,YAAY,QAAQ,iBAAiB,EAAE,KAAK,YAAY,QAAQ,YAAY,EAAE,KAAK;AAGtG,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,qBAAe,GAAG,UAAU,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,QAAQ,QAAQ,GAAG;AAAA,IACrE,WAAW,KAAK,WAAW,KAAK,GAAG;AACjC,YAAM,YAAY,WAAW,QAAQ,YAAY,EAAE,KAAK;AACxD,qBAAe,GAAG,SAAS,IAAI,KAAK,MAAM,CAAC,CAAC,GAAG,QAAQ,QAAQ,GAAG;AAAA,IACpE;AAAA,EACF;AAGA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ,WAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA,IAAA;AAAA,EAAA;AAGP;AAEA,SAAS,WAAW,UAA2B;AAC7C,UAAO,qCACH,WACD,cACA,QAAQ,iBAAiB,IACzB,QAAQ,QAAQ,SAAQ;AAC7B;AAGO,MAAM,gBAAgB;AAAA,EAE3B;AAAA,EAEA,kBAAkB;AAAA,EAElB,cAAc;AAAA,EAEd,eAAe;AAAA,EAEf,mBAAmB;AAAA,EAEnB;AAAA,EAEA;AAAA;AAAA,EAGA,IAAI,CAAC,UAAoD;AACvD,UAAM,KAAK,WAAW,MAAM,QAAQ;AACpC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAAA,EACA,IAAI,CAAC,UAAoD;AACvD,UAAM,KAAK,WAAW,MAAM,QAAQ;AACpC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAAA,EACA,IAAI,CAAC,UAAoD;AACvD,UAAM,KAAK,WAAW,MAAM,QAAQ;AACpC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAAA,EACA,IAAI,CAAC,UAAoD;AACvD,UAAM,KAAK,WAAW,MAAM,QAAQ;AACpC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAAA,EACA,IAAI,CAAC,UAAoD;AACvD,UAAM,KAAK,WAAW,MAAM,QAAQ;AACpC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,WAAU;AAAA,QACT,GAAG;AAAA,MAAA;AAAA,IAAA;AAAA,EAGV;AAAA;AAAA,EAGA,KAAK,CAAC,UACJ;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACT,GAAG;AAAA,IAAA;AAAA,EAAA;AAAA,EAGR,MAAM,CAAC,EAAE,WAAW,GAAG,MAAA;AAAA;AAAA,IAErB,oBAAC,QAAA,EAAK,WAAuB,GAAG,MAAA,CAAO;AAAA;AAAA;AAAA,EAIzC,YAAY,CAAC,UACX;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACT,GAAG;AAAA,IAAA;AAAA,EAAA;AAAA;AAAA,EAKR,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,oDAAoD,GAAG,OAAO;AAAA,EAE9E,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,uDAAuD,GAAG,OAAO;AAAA,EAEjF,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,UAAU,GAAG,OAAO;AAAA;AAAA,EAIpC,GAAG;AAAA;AAAA,EAGH,OAAO,CAAC,UACN,oBAAC,OAAA,EAAI,WAAU,kEACb,UAAA,oBAAC,SAAA,EAAM,WAAU,kCAAkC,GAAG,OAAO,GAC/D;AAAA,EAEF,OAAO,CAAC,UACN,oBAAC,WAAM,WAAU,eAAe,GAAG,OAAO;AAAA,EAE5C,OAAO,CAAC,UACN,oBAAC,SAAA,EAAO,GAAG,OAAO;AAAA,EAEpB,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,0CAA0C,GAAG,OAAO;AAAA,EAEpE,IAAI,CAAC,UACH;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAU;AAAA,MACT,GAAG;AAAA,IAAA;AAAA,EAAA;AAAA,EAGR,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,uBAAuB,GAAG,OAAO;AAAA;AAAA,EAIjD,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,+BAA+B,GAAG,OAAO;AAAA;AAAA,EAIzD,GAAG,CAAC,UACF,oBAAC,OAAE,WAAU,kCAAkC,GAAG,OAAO;AAAA;AAAA,EAI3D,QAAQ,CAAC,UACP,oBAAC,YAAO,WAAU,iBAAiB,GAAG,OAAO;AAAA,EAE/C,IAAI,CAAC,UACH,oBAAC,QAAG,WAAU,UAAU,GAAG,MAAA,CAAO;AAEtC;"}
@@ -3,7 +3,8 @@ import { Link } from "react-router-dom";
3
3
  import { cn } from "../lib/utils.js";
4
4
  import { formatDate } from "../lib/format-date.js";
5
5
  import { ArrowRight, Presentation } from "lucide-react";
6
- function PostListItem({ title, description, date, href, external, isSlides }) {
6
+ function PostListItem({ title, description, date, href, linkPath, external, openInNewTab = true, isSlides }) {
7
+ const resolvedHref = href || linkPath || "#";
7
8
  const className = cn(
8
9
  "group block py-3 px-3 -mx-3 rounded-md",
9
10
  "transition-colors duration-150"
@@ -34,9 +35,9 @@ function PostListItem({ title, description, date, href, external, isSlides }) {
34
35
  return /* @__PURE__ */ jsx(
35
36
  "a",
36
37
  {
37
- href,
38
- target: "_blank",
39
- rel: "noopener noreferrer",
38
+ href: resolvedHref,
39
+ target: openInNewTab ? "_blank" : void 0,
40
+ rel: openInNewTab ? "noopener noreferrer" : void 0,
40
41
  className,
41
42
  children: content
42
43
  }
@@ -45,7 +46,7 @@ function PostListItem({ title, description, date, href, external, isSlides }) {
45
46
  return /* @__PURE__ */ jsx(
46
47
  Link,
47
48
  {
48
- to: href,
49
+ to: resolvedHref,
49
50
  className,
50
51
  children: content
51
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"post-list-item.js","sources":["../../../src/components/post-list-item.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\n\ninterface PostListItemProps {\n title: string;\n description?: string;\n date?: Date;\n href: string;\n external?: boolean;\n isSlides?: boolean;\n}\n\nexport function PostListItem({ title, description, date, href, external, isSlides }: PostListItemProps) {\n const className = cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n );\n\n const content = (\n <article className=\"flex items-center gap-4\">\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <div className={cn(\n \"text-sm font-medium text-foreground\",\n \"group-hover:underline\",\n \"flex items-center gap-2\"\n )}>\n <span>{title}</span>\n <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\" />\n </div>\n\n {description && (\n <div className=\"text-sm text-muted-foreground line-clamp-1 mt-0.5\">\n {description}\n </div>\n )}\n </div>\n\n {isSlides && (\n <Presentation className=\"h-3 w-3 text-muted-foreground\" />\n )}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0\"\n >\n {date && formatDate(date)}\n </time>\n </article>\n );\n\n if (external) {\n return (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={className}\n >\n {content}\n </a>\n );\n }\n\n return (\n <Link\n to={href}\n className={className}\n >\n {content}\n </Link>\n );\n}\n"],"names":[],"mappings":";;;;;AAcO,SAAS,aAAa,EAAE,OAAO,aAAa,MAAM,MAAM,UAAU,YAA+B;AACtG,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,UACJ,qBAAC,WAAA,EAAQ,WAAU,2BAEjB,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,MAAA,qBAAC,SAAI,WAAW;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,MAAA,GAEA,UAAA;AAAA,QAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,QACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,MAAA,GACtJ;AAAA,MAEC,eACC,oBAAC,OAAA,EAAI,WAAU,qDACZ,UAAA,YAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEC,YACC,oBAAC,cAAA,EAAa,WAAU,gCAAA,CAAgC;AAAA,IAE1D;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,UAAU,6BAAM;AAAA,QAChB,WAAU;AAAA,QAET,UAAA,QAAQ,WAAW,IAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EAC1B,GACF;AAGF,MAAI,UAAU;AACZ,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,QAAO;AAAA,QACP,KAAI;AAAA,QACJ;AAAA,QAEC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MAEC,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"post-list-item.js","sources":["../../../src/components/post-list-item.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\n\ninterface PostListItemProps {\n title: string;\n description?: string;\n date?: Date;\n href?: string;\n /** Alias for href (used in MDX as linkPath) */\n linkPath?: string;\n external?: boolean;\n openInNewTab?: boolean;\n isSlides?: boolean;\n}\n\nexport function PostListItem({ title, description, date, href, linkPath, external, openInNewTab = true, isSlides }: PostListItemProps) {\n const resolvedHref = href || linkPath || '#';\n const className = cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n );\n\n const content = (\n <article className=\"flex items-center gap-4\">\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <div className={cn(\n \"text-sm font-medium text-foreground\",\n \"group-hover:underline\",\n \"flex items-center gap-2\"\n )}>\n <span>{title}</span>\n <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\" />\n </div>\n\n {description && (\n <div className=\"text-sm text-muted-foreground line-clamp-1 mt-0.5\">\n {description}\n </div>\n )}\n </div>\n\n {isSlides && (\n <Presentation className=\"h-3 w-3 text-muted-foreground\" />\n )}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0\"\n >\n {date && formatDate(date)}\n </time>\n </article>\n );\n\n if (external) {\n return (\n <a\n href={resolvedHref}\n target={openInNewTab ? \"_blank\" : undefined}\n rel={openInNewTab ? \"noopener noreferrer\" : undefined}\n className={className}\n >\n {content}\n </a>\n );\n }\n\n return (\n <Link\n to={resolvedHref}\n className={className}\n >\n {content}\n </Link>\n );\n}\n"],"names":[],"mappings":";;;;;AAiBO,SAAS,aAAa,EAAE,OAAO,aAAa,MAAM,MAAM,UAAU,UAAU,eAAe,MAAM,SAAA,GAA+B;AACrI,QAAM,eAAe,QAAQ,YAAY;AACzC,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,UACJ,qBAAC,WAAA,EAAQ,WAAU,2BAEjB,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,MAAA,qBAAC,SAAI,WAAW;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,MAAA,GAEA,UAAA;AAAA,QAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,QACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,MAAA,GACtJ;AAAA,MAEC,eACC,oBAAC,OAAA,EAAI,WAAU,qDACZ,UAAA,YAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEC,YACC,oBAAC,cAAA,EAAa,WAAU,gCAAA,CAAgC;AAAA,IAE1D;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,UAAU,6BAAM;AAAA,QAChB,WAAU;AAAA,QAET,UAAA,QAAQ,WAAW,IAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EAC1B,GACF;AAGF,MAAI,UAAU;AACZ,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM;AAAA,QACN,QAAQ,eAAe,WAAW;AAAA,QAClC,KAAK,eAAe,wBAAwB;AAAA,QAC5C;AAAA,QAEC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MAEC,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -16,6 +16,9 @@ function filePathToRoutePath(filePath) {
16
16
  }
17
17
  function getLinkPath(post) {
18
18
  if (post.file) {
19
+ if (post.file.path.toLowerCase().endsWith(".pdf")) {
20
+ return `/raw/${post.file.path}`;
21
+ }
19
22
  return filePathToRoutePath(post.file.path);
20
23
  } else if (post.slides && !post.readme) {
21
24
  return `/${post.slides.path}`;
@@ -26,7 +29,7 @@ function getLinkPath(post) {
26
29
  }
27
30
  }
28
31
  function isRouterPath(href) {
29
- return href.startsWith("/") && !href.startsWith("//");
32
+ return href.startsWith("/") && !href.startsWith("//") && !href.startsWith("/raw/");
30
33
  }
31
34
  function PostList({ globs = null }) {
32
35
  var _a;
@@ -78,6 +81,7 @@ function PostList({ globs = null }) {
78
81
  const frontmatterLink = typeof (frontmatter == null ? void 0 : frontmatter.link) === "string" ? frontmatter.link.trim() : "";
79
82
  const href = frontmatterLink || internalLinkPath;
80
83
  const external = !isRouterPath(href);
84
+ const openInNewTab = external && !href.startsWith("/raw/");
81
85
  return /* @__PURE__ */ jsx(
82
86
  PostListItem,
83
87
  {
@@ -86,6 +90,7 @@ function PostList({ globs = null }) {
86
90
  date,
87
91
  href,
88
92
  external,
93
+ openInNewTab,
89
94
  isSlides
90
95
  },
91
96
  post.path
@@ -1 +1 @@
1
- {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\nfunction filePathToRoutePath(filePath: string): string {\n // Routes without an extension are treated as directories; ContentRouter will\n // render index/readme files automatically via IndexPost.\n const normalized = filePath.replace(/^\\/+/, '');\n const stripped = normalized.replace(/(?:^|\\/)(?:index|readme)\\.mdx?$/i, '');\n return stripped ? `/${stripped}` : '/';\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return filePathToRoutePath(post.file.path);\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return filePathToRoutePath(post.readme.path);\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nfunction isRouterPath(href: string): boolean {\n return href.startsWith(\"/\") && !href.startsWith(\"//\");\n}\n\nexport function PostList({ globs = null }: PostListProps) {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Filter by glob patterns if provided\n if (globs && globs.length > 0) {\n posts = posts.filter(post =>\n globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))\n );\n }\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const internalLinkPath = getLinkPath(post);\n const normalizedLink = internalLinkPath.toLowerCase();\n const isSlides =\n normalizedLink.endsWith('/slides.mdx') ||\n normalizedLink.endsWith('/slides.md') ||\n normalizedLink.endsWith('.slides.mdx') ||\n normalizedLink.endsWith('.slides.md');\n\n const frontmatterLink =\n typeof frontmatter?.link === \"string\" ? frontmatter.link.trim() : \"\";\n const href = frontmatterLink || internalLinkPath;\n const external = !isRouterPath(href);\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n href={href}\n external={external}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
1
+ {"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\nfunction filePathToRoutePath(filePath: string): string {\n // Routes without an extension are treated as directories; ContentRouter will\n // render index/readme files automatically via IndexPost.\n const normalized = filePath.replace(/^\\/+/, '');\n const stripped = normalized.replace(/(?:^|\\/)(?:index|readme)\\.mdx?$/i, '');\n return stripped ? `/${stripped}` : '/';\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone PDF file should open directly.\n if (post.file.path.toLowerCase().endsWith('.pdf')) {\n return `/raw/${post.file.path}`;\n }\n // Standalone MDX/TSX file\n return filePathToRoutePath(post.file.path);\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return filePathToRoutePath(post.readme.path);\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nfunction isRouterPath(href: string): boolean {\n return href.startsWith(\"/\") && !href.startsWith(\"//\") && !href.startsWith(\"/raw/\");\n}\n\nexport function PostList({ globs = null }: PostListProps) {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Filter by glob patterns if provided\n if (globs && globs.length > 0) {\n posts = posts.filter(post =>\n globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))\n );\n }\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const internalLinkPath = getLinkPath(post);\n const normalizedLink = internalLinkPath.toLowerCase();\n const isSlides =\n normalizedLink.endsWith('/slides.mdx') ||\n normalizedLink.endsWith('/slides.md') ||\n normalizedLink.endsWith('.slides.mdx') ||\n normalizedLink.endsWith('.slides.md');\n\n const frontmatterLink =\n typeof frontmatter?.link === \"string\" ? frontmatter.link.trim() : \"\";\n const href = frontmatterLink || internalLinkPath;\n const external = !isRouterPath(href);\n const openInNewTab = external && !href.startsWith(\"/raw/\");\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n href={href}\n external={external}\n openInNewTab={openInNewTab}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { findMdxFiles, findTsxFiles, findStandaloneSlides, findSlides, findReadme } from "../plugin/src/client.js";
1
+ import { findMdxFiles, findTsxFiles, findStandaloneSlides, findPdfFiles, findSlides, findReadme } from "../plugin/src/client.js";
2
2
  function getFrontmatter(post) {
3
3
  var _a, _b, _c;
4
4
  return ((_a = post.readme) == null ? void 0 : _a.frontmatter) || ((_b = post.file) == null ? void 0 : _b.frontmatter) || ((_c = post.slides) == null ? void 0 : _c.frontmatter);
@@ -8,6 +8,7 @@ function directoryToPostEntries(directory) {
8
8
  const standaloneFiles = findMdxFiles(directory);
9
9
  const standaloneTsxFiles = findTsxFiles(directory);
10
10
  const standaloneSlidesFiles = findStandaloneSlides(directory);
11
+ const standalonePdfFiles = findPdfFiles(directory);
11
12
  const folderPosts = folders.map((folder) => ({
12
13
  type: "folder",
13
14
  name: folder.name,
@@ -40,7 +41,15 @@ function directoryToPostEntries(directory) {
40
41
  slides: file,
41
42
  file: null
42
43
  }));
43
- return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts];
44
+ const pdfPosts = standalonePdfFiles.map((file) => ({
45
+ type: "file",
46
+ name: file.name.replace(/\.pdf$/i, ""),
47
+ path: file.path,
48
+ readme: null,
49
+ slides: null,
50
+ file
51
+ }));
52
+ return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts, ...pdfPosts];
44
53
  }
45
54
  function filterVisiblePosts(posts) {
46
55
  return posts.filter((post) => {
@@ -1 +1 @@
1
- {"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type { DirectoryEntry, FileEntry } from \"../../plugin/src/lib\";\nimport { findReadme, findSlides, findMdxFiles, findStandaloneSlides, findTsxFiles } 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 standaloneTsxFiles = findTsxFiles(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 const tsxPosts: PostEntry[] = standaloneTsxFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.tsx$/, ''),\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, ...tsxPosts, ...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,qBAAqB,aAAa,SAAS;AACjD,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;AAEF,QAAM,WAAwB,mBAAmB,IAAI,CAAC,UAAU;AAAA,IAC9D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,UAAU,EAAE;AAAA,IACpC,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,UAAU,GAAG,WAAW;AACnE;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, findTsxFiles, findPdfFiles } 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 standaloneTsxFiles = findTsxFiles(directory);\n const standaloneSlidesFiles = findStandaloneSlides(directory);\n const standalonePdfFiles = findPdfFiles(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 const tsxPosts: PostEntry[] = standaloneTsxFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.tsx$/, ''),\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 const pdfPosts: PostEntry[] = standalonePdfFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.pdf$/i, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts, ...pdfPosts];\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,qBAAqB,aAAa,SAAS;AACjD,QAAM,wBAAwB,qBAAqB,SAAS;AAC5D,QAAM,qBAAqB,aAAa,SAAS;AAEjD,QAAM,cAA2B,QAC9B,IAAI,CAAC,YAAY;AAAA,IAChB,MAAM;AAAA,IACN,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,QAAQ,WAAW,MAAM;AAAA,IACzB,QAAQ,WAAW,MAAM;AAAA,IACzB,MAAM;AAAA,EAAA,EACN,EACD,OAAO,CAAC,SAAS,KAAK,UAAU,KAAK,MAAM;AAE9C,QAAM,YAAyB,gBAAgB,IAAI,CAAC,UAAU;AAAA,IAC5D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAEF,QAAM,WAAwB,mBAAmB,IAAI,CAAC,UAAU;AAAA,IAC9D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,UAAU,EAAE;AAAA,IACpC,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,QAAM,WAAwB,mBAAmB,IAAI,CAAC,UAAU;AAAA,IAC9D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAEF,SAAO,CAAC,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ;AAChF;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;"}
@@ -49,6 +49,11 @@ function findTsxFiles(directory) {
49
49
  (child) => child.type === "file" && child.name.endsWith(".tsx") && !child.name.endsWith(".d.ts") && child.frontmatter !== void 0
50
50
  );
51
51
  }
52
+ function findPdfFiles(directory) {
53
+ return directory.children.filter(
54
+ (child) => child.type === "file" && child.name.toLowerCase().endsWith(".pdf")
55
+ );
56
+ }
52
57
  function findSlides(directory) {
53
58
  const standardSlides = directory.children.find(
54
59
  (child) => child.type === "file" && [
@@ -97,44 +102,6 @@ function useDirectory(path = ".") {
97
102
  error: result.error
98
103
  };
99
104
  }
100
- function useFileContent(path) {
101
- const [blob, setBlob] = useState(null);
102
- const [content, setContent] = useState(null);
103
- const [loading, setLoading] = useState(true);
104
- const [error, setError] = useState(null);
105
- useEffect(() => {
106
- const controller = new AbortController();
107
- setLoading(true);
108
- setError(null);
109
- (async () => {
110
- try {
111
- const res = await fetch(`/raw/${path}`, {
112
- signal: controller.signal
113
- });
114
- if (!res.ok) {
115
- throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
116
- }
117
- const fetchedBlob = await res.blob();
118
- setBlob(fetchedBlob);
119
- try {
120
- const text = await fetchedBlob.text();
121
- setContent(text);
122
- } catch {
123
- setContent(null);
124
- }
125
- } catch (err) {
126
- if (err instanceof Error && err.name === "AbortError") {
127
- return;
128
- }
129
- setError(err instanceof Error ? err.message : "Unknown error");
130
- } finally {
131
- setLoading(false);
132
- }
133
- })();
134
- return () => controller.abort();
135
- }, [path]);
136
- return { blob, content, loading, error };
137
- }
138
105
  function isSimulationRunning() {
139
106
  const [running, setRunning] = useState(false);
140
107
  useEffect(() => {
@@ -162,12 +129,12 @@ function isSimulationRunning() {
162
129
  }
163
130
  export {
164
131
  findMdxFiles,
132
+ findPdfFiles,
165
133
  findReadme,
166
134
  findSlides,
167
135
  findStandaloneSlides,
168
136
  findTsxFiles,
169
137
  isSimulationRunning,
170
- useDirectory,
171
- useFileContent
138
+ useDirectory
172
139
  };
173
140
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sources":["../../../../plugin/src/client.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { DirectoryEntry, FileEntry } from \"./lib.js\";\nimport { buildDirectoryTree, navigateToPath } from \"./directory-tree.js\";\n// @ts-ignore - virtual module\nimport { files, frontmatters } from \"virtual:content-modules\";\n\n/**\n * Find the main content file for a directory.\n * Supports (in order of preference):\n * - index.mdx / index.md (modern convention)\n * - README.mdx / README.md (traditional convention)\n */\nexport function findReadme(directory: DirectoryEntry): FileEntry | null {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n\n for (const filename of indexFiles) {\n const found = directory.children.find((child) =>\n child.type === \"file\" && child.name === filename\n ) as FileEntry | undefined;\n if (found) return found;\n }\n\n return null;\n}\n\n/**\n * Find all MDX files in a directory (excluding index/README and slides)\n */\nexport function findMdxFiles(directory: DirectoryEntry): FileEntry[] {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n const slideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n const excludeFiles = [...indexFiles, ...slideFiles];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.mdx') || child.name.endsWith('.md')) &&\n !excludeFiles.includes(child.name) &&\n !child.name.endsWith('.slides.mdx') &&\n !child.name.endsWith('.slides.md')\n );\n}\n\n/**\n * Find TSX pages in a directory (requires frontmatter export).\n */\nexport function findTsxFiles(directory: DirectoryEntry): FileEntry[] {\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n child.name.endsWith('.tsx') &&\n !child.name.endsWith('.d.ts') &&\n child.frontmatter !== undefined\n );\n}\n\nexport function findSlides(directory: DirectoryEntry): FileEntry | null {\n // First check for standard SLIDES.mdx files\n const standardSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n [\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n if (standardSlides) return standardSlides;\n\n // Then check for *.slides.mdx files\n const dotSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md'))\n ) as FileEntry | undefined;\n\n return dotSlides || null;\n}\n\n/**\n * Find all standalone slides files in a directory (*.slides.mdx, *.slides.md)\n * These are slides files that aren't part of a folder (like getting-started.slides.mdx)\n */\nexport function findStandaloneSlides(directory: DirectoryEntry): FileEntry[] {\n const standardSlideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md')) &&\n !standardSlideFiles.includes(child.name)\n );\n}\n\n\nexport type DirectoryError =\n | { type: 'path_not_found'; message: string; status: 404 };\n\n// Build directory tree once from glob keys, with frontmatter metadata\nconst directoryTree = buildDirectoryTree(Object.keys(files), frontmatters as Record<string, FileEntry['frontmatter']>);\n\nexport function useDirectory(path: string = \".\") {\n const result = useMemo(() => {\n try {\n const { directory, file } = navigateToPath(directoryTree, path);\n return { directory, file, error: null as DirectoryError | null };\n } catch {\n return { directory: null, file: null, error: { type: 'path_not_found', message: `Path not found: ${path}`, status: 404 } };\n }\n }, [path]);\n\n return {\n directory: result.directory,\n file: result.file,\n loading: false, // No async loading needed\n error: result.error\n };\n}\n\nexport function useFileContent(path: string) {\n const [blob, setBlob] = useState<Blob | null>(null);\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const res = await fetch(`/raw/${path}`, {\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);\n }\n\n const fetchedBlob = await res.blob();\n setBlob(fetchedBlob);\n\n // Try to read as text - some binary files may fail\n try {\n const text = await fetchedBlob.text();\n setContent(text);\n } catch {\n // Binary file - text content not available\n setContent(null);\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return; // Ignore abort errors\n }\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n })();\n\n return () => controller.abort();\n }, [path]);\n\n return { blob, content, loading, error };\n}\n\nexport function isSimulationRunning() {\n const [running, setRunning] = useState<boolean>(false);\n\n useEffect(() => {\n let interval: ReturnType<typeof setInterval>;\n\n const fetchStatus = async () => {\n try {\n const response = await fetch(`/raw/.running`);\n\n // this is an elaborate workaround to stop devtools logging errors on 404s\n const text = await response.text()\n if (text === \"\") {\n setRunning(true);\n } else {\n setRunning(false);\n }\n } catch {\n setRunning(false);\n }\n };\n\n // Initial fetch\n fetchStatus();\n\n // Poll every second\n interval = setInterval(fetchStatus, 1000);\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return running;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sources":["../../../../plugin/src/client.tsx"],"sourcesContent":["import { useState, useEffect, useMemo } from \"react\";\nimport { DirectoryEntry, FileEntry } from \"./lib.js\";\nimport { buildDirectoryTree, navigateToPath } from \"./directory-tree.js\";\n// @ts-ignore - virtual module\nimport { files, frontmatters } from \"virtual:content-modules\";\n\n/**\n * Find the main content file for a directory.\n * Supports (in order of preference):\n * - index.mdx / index.md (modern convention)\n * - README.mdx / README.md (traditional convention)\n */\nexport function findReadme(directory: DirectoryEntry): FileEntry | null {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n\n for (const filename of indexFiles) {\n const found = directory.children.find((child) =>\n child.type === \"file\" && child.name === filename\n ) as FileEntry | undefined;\n if (found) return found;\n }\n\n return null;\n}\n\n/**\n * Find all MDX files in a directory (excluding index/README and slides)\n */\nexport function findMdxFiles(directory: DirectoryEntry): FileEntry[] {\n const indexFiles = [\n \"index.mdx\", \"index.md\",\n \"README.mdx\", \"Readme.mdx\", \"readme.mdx\",\n \"README.md\", \"Readme.md\", \"readme.md\",\n ];\n const slideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n const excludeFiles = [...indexFiles, ...slideFiles];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.mdx') || child.name.endsWith('.md')) &&\n !excludeFiles.includes(child.name) &&\n !child.name.endsWith('.slides.mdx') &&\n !child.name.endsWith('.slides.md')\n );\n}\n\n/**\n * Find TSX pages in a directory (requires frontmatter export).\n */\nexport function findTsxFiles(directory: DirectoryEntry): FileEntry[] {\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n child.name.endsWith('.tsx') &&\n !child.name.endsWith('.d.ts') &&\n child.frontmatter !== undefined\n );\n}\n\n/**\n * Find standalone PDF files in a directory.\n */\nexport function findPdfFiles(directory: DirectoryEntry): FileEntry[] {\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n child.name.toLowerCase().endsWith('.pdf')\n );\n}\n\nexport function findSlides(directory: DirectoryEntry): FileEntry | null {\n // First check for standard SLIDES.mdx files\n const standardSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n [\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\"\n ].includes(child.name)\n ) as FileEntry | undefined;\n\n if (standardSlides) return standardSlides;\n\n // Then check for *.slides.mdx files\n const dotSlides = directory.children.find((child) =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md'))\n ) as FileEntry | undefined;\n\n return dotSlides || null;\n}\n\n/**\n * Find all standalone slides files in a directory (*.slides.mdx, *.slides.md)\n * These are slides files that aren't part of a folder (like getting-started.slides.mdx)\n */\nexport function findStandaloneSlides(directory: DirectoryEntry): FileEntry[] {\n const standardSlideFiles = [\n \"SLIDES.mdx\", \"Slides.mdx\", \"slides.mdx\",\n \"SLIDES.md\", \"Slides.md\", \"slides.md\",\n ];\n\n return directory.children.filter((child): child is FileEntry =>\n child.type === \"file\" &&\n (child.name.endsWith('.slides.mdx') || child.name.endsWith('.slides.md')) &&\n !standardSlideFiles.includes(child.name)\n );\n}\n\n\nexport type DirectoryError =\n | { type: 'path_not_found'; message: string; status: 404 };\n\n// Build directory tree once from glob keys, with frontmatter metadata\nconst directoryTree = buildDirectoryTree(Object.keys(files), frontmatters as Record<string, FileEntry['frontmatter']>);\n\nexport function useDirectory(path: string = \".\") {\n const result = useMemo(() => {\n try {\n const { directory, file } = navigateToPath(directoryTree, path);\n return { directory, file, error: null as DirectoryError | null };\n } catch {\n return { directory: null, file: null, error: { type: 'path_not_found', message: `Path not found: ${path}`, status: 404 } };\n }\n }, [path]);\n\n return {\n directory: result.directory,\n file: result.file,\n loading: false, // No async loading needed\n error: result.error\n };\n}\n\nexport function useFileContent(path: string) {\n const [blob, setBlob] = useState<Blob | null>(null);\n const [content, setContent] = useState<string | null>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n const controller = new AbortController();\n setLoading(true);\n setError(null);\n\n (async () => {\n try {\n const res = await fetch(`/raw/${path}`, {\n signal: controller.signal,\n });\n\n if (!res.ok) {\n throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);\n }\n\n const fetchedBlob = await res.blob();\n setBlob(fetchedBlob);\n\n // Try to read as text - some binary files may fail\n try {\n const text = await fetchedBlob.text();\n setContent(text);\n } catch {\n // Binary file - text content not available\n setContent(null);\n }\n } catch (err) {\n if (err instanceof Error && err.name === 'AbortError') {\n return; // Ignore abort errors\n }\n setError(err instanceof Error ? err.message : 'Unknown error');\n } finally {\n setLoading(false);\n }\n })();\n\n return () => controller.abort();\n }, [path]);\n\n return { blob, content, loading, error };\n}\n\nexport function isSimulationRunning() {\n const [running, setRunning] = useState<boolean>(false);\n\n useEffect(() => {\n let interval: ReturnType<typeof setInterval>;\n\n const fetchStatus = async () => {\n try {\n const response = await fetch(`/raw/.running`);\n\n // this is an elaborate workaround to stop devtools logging errors on 404s\n const text = await response.text()\n if (text === \"\") {\n setRunning(true);\n } else {\n setRunning(false);\n }\n } catch {\n setRunning(false);\n }\n };\n\n // Initial fetch\n fetchStatus();\n\n // Poll every second\n interval = setInterval(fetchStatus, 1000);\n\n return () => {\n clearInterval(interval);\n };\n }, []);\n\n return running;\n}\n"],"names":[],"mappings":"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"}
@@ -50,6 +50,13 @@ export function findTsxFiles(directory) {
50
50
  !child.name.endsWith('.d.ts') &&
51
51
  child.frontmatter !== undefined);
52
52
  }
53
+ /**
54
+ * Find standalone PDF files in a directory.
55
+ */
56
+ export function findPdfFiles(directory) {
57
+ return directory.children.filter((child) => child.type === "file" &&
58
+ child.name.toLowerCase().endsWith('.pdf'));
59
+ }
53
60
  export function findSlides(directory) {
54
61
  // First check for standard SLIDES.mdx files
55
62
  const standardSlides = directory.children.find((child) => child.type === "file" &&
@@ -514,6 +514,7 @@ export default function contentPlugin(contentDir, config, options) {
514
514
  '.jpeg': 'image/jpeg',
515
515
  '.gif': 'image/gif',
516
516
  '.svg': 'image/svg+xml',
517
+ '.pdf': 'application/pdf',
517
518
  '.json': 'application/json',
518
519
  '.md': 'text/markdown',
519
520
  '.yaml': 'text/yaml',
@@ -612,6 +613,7 @@ export const files = import.meta.glob([
612
613
  '@content/*.gif',
613
614
  '@content/*.svg',
614
615
  '@content/*.webp',
616
+ '@content/*.pdf',
615
617
  '@content/*.css',
616
618
  '@content/*.yaml',
617
619
  '@content/*.yml',
@@ -628,6 +630,7 @@ export const files = import.meta.glob([
628
630
  '@content/**/*.gif',
629
631
  '@content/**/*.svg',
630
632
  '@content/**/*.webp',
633
+ '@content/**/*.pdf',
631
634
  '@content/**/*.css',
632
635
  '@content/**/*.yaml',
633
636
  '@content/**/*.yml',
@@ -659,7 +662,7 @@ export const modules = import.meta.glob(['@content/*.mdx', '@content/*.md', '@co
659
662
  // Watch content directory for all file changes (add, delete, change)
660
663
  server.watcher.add(dir);
661
664
  // File extensions that should trigger a full reload
662
- const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.tsx', '.ts', '.jsx', '.js', '.css'];
665
+ const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf', '.tsx', '.ts', '.jsx', '.js', '.css'];
663
666
  // Debounce reload to avoid rapid-fire refreshes when multiple files change
664
667
  let reloadTimeout = null;
665
668
  const pendingChanges = [];
@@ -719,7 +722,7 @@ export const modules = import.meta.glob(['@content/*.mdx', '@content/*.md', '@co
719
722
  // Check if the changed file is in the content directory
720
723
  // Return empty array to prevent default HMR - we handle it in configureServer
721
724
  if (file.startsWith(dir)) {
722
- const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.tsx', '.ts', '.jsx', '.js', '.css'];
725
+ const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf', '.tsx', '.ts', '.jsx', '.js', '.css'];
723
726
  const ext = path.extname(file).toLowerCase();
724
727
  if (watchedExtensions.includes(ext)) {
725
728
  return []; // Prevent default HMR, we already handle this via watcher events
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veslx",
3
- "version": "0.1.65",
3
+ "version": "0.1.66",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -63,6 +63,16 @@ export function findTsxFiles(directory: DirectoryEntry): FileEntry[] {
63
63
  );
64
64
  }
65
65
 
66
+ /**
67
+ * Find standalone PDF files in a directory.
68
+ */
69
+ export function findPdfFiles(directory: DirectoryEntry): FileEntry[] {
70
+ return directory.children.filter((child): child is FileEntry =>
71
+ child.type === "file" &&
72
+ child.name.toLowerCase().endsWith('.pdf')
73
+ );
74
+ }
75
+
66
76
  export function findSlides(directory: DirectoryEntry): FileEntry | null {
67
77
  // First check for standard SLIDES.mdx files
68
78
  const standardSlides = directory.children.find((child) =>
@@ -580,6 +580,7 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
580
580
  '.jpeg': 'image/jpeg',
581
581
  '.gif': 'image/gif',
582
582
  '.svg': 'image/svg+xml',
583
+ '.pdf': 'application/pdf',
583
584
  '.json': 'application/json',
584
585
  '.md': 'text/markdown',
585
586
  '.yaml': 'text/yaml',
@@ -692,6 +693,7 @@ export const files = import.meta.glob([
692
693
  '@content/*.gif',
693
694
  '@content/*.svg',
694
695
  '@content/*.webp',
696
+ '@content/*.pdf',
695
697
  '@content/*.css',
696
698
  '@content/*.yaml',
697
699
  '@content/*.yml',
@@ -708,6 +710,7 @@ export const files = import.meta.glob([
708
710
  '@content/**/*.gif',
709
711
  '@content/**/*.svg',
710
712
  '@content/**/*.webp',
713
+ '@content/**/*.pdf',
711
714
  '@content/**/*.css',
712
715
  '@content/**/*.yaml',
713
716
  '@content/**/*.yml',
@@ -743,7 +746,7 @@ export const modules = import.meta.glob(['@content/*.mdx', '@content/*.md', '@co
743
746
  server.watcher.add(dir)
744
747
 
745
748
  // File extensions that should trigger a full reload
746
- const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.tsx', '.ts', '.jsx', '.js', '.css']
749
+ const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf', '.tsx', '.ts', '.jsx', '.js', '.css']
747
750
 
748
751
  // Debounce reload to avoid rapid-fire refreshes when multiple files change
749
752
  let reloadTimeout: ReturnType<typeof setTimeout> | null = null
@@ -813,7 +816,7 @@ export const modules = import.meta.glob(['@content/*.mdx', '@content/*.md', '@co
813
816
  // Check if the changed file is in the content directory
814
817
  // Return empty array to prevent default HMR - we handle it in configureServer
815
818
  if (file.startsWith(dir)) {
816
- const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.tsx', '.ts', '.jsx', '.js', '.css']
819
+ const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.pdf', '.tsx', '.ts', '.jsx', '.js', '.css']
817
820
  const ext = path.extname(file).toLowerCase()
818
821
  if (watchedExtensions.includes(ext)) {
819
822
  return [] // Prevent default HMR, we already handle this via watcher events
@@ -1,7 +1,4 @@
1
1
 
2
2
  export { default as Gallery } from './gallery'
3
- export { ParameterTable } from './parameter-table'
4
- export { ParameterBadge } from './parameter-badge'
5
3
  export { Slide } from './slide'
6
- export { VeslxCat } from './veslx-cat'
7
4
  export { VeslxTOC } from './veslx-toc'
@@ -1,14 +1,7 @@
1
1
  import { Link, useLocation } from 'react-router-dom'
2
2
  import Gallery from '@/components/gallery'
3
- import { ParameterTable } from '@/components/parameter-table'
4
- import { ParameterBadge } from '@/components/parameter-badge'
5
- import { VeslxCat } from '@/components/veslx-cat'
6
3
  import { VeslxTOC } from '@/components/veslx-toc'
7
4
  import { FrontMatter } from './front-matter'
8
- import { HeroSlide } from './slides/hero-slide'
9
- import { FigureSlide } from './slides/figure-slide'
10
- import { TextSlide } from './slides/text-slide'
11
- import { SlideOutline } from './slides/slide-outline'
12
5
  import { PostList } from '@/components/post-list'
13
6
  import { PostListItem } from '@/components/post-list-item'
14
7
  import { VeslxSearch } from '@/components/veslx-search'
@@ -83,24 +76,10 @@ export const mdxComponents = {
83
76
 
84
77
  VeslxGallery: Gallery,
85
78
 
86
- VeslxParameterTable: ParameterTable,
87
-
88
- VeslxParameterBadge: ParameterBadge,
89
-
90
- VeslxHeroSlide: HeroSlide,
91
-
92
- VeslxFigureSlide: FigureSlide,
93
-
94
- VeslxTextSlide: TextSlide,
95
-
96
- VeslxSlideOutline: SlideOutline,
97
-
98
79
  VeslxPostList: PostList,
99
80
 
100
81
  VeslxPostListItem: PostListItem,
101
82
 
102
- VeslxCat: VeslxCat,
103
-
104
83
  VeslxTOC: VeslxTOC,
105
84
 
106
85
  VeslxSearch: VeslxSearch,