veslx 0.1.64 → 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 (50) hide show
  1. package/README.md +0 -16
  2. package/bin/lib/serve.ts +46 -0
  3. package/dist/bin/lib/serve.js +36 -0
  4. package/dist/bin/veslx.js +0 -0
  5. package/dist/client/components/gallery/index.js +7 -23
  6. package/dist/client/components/gallery/index.js.map +1 -1
  7. package/dist/client/components/mdx-components.js +0 -14
  8. package/dist/client/components/mdx-components.js.map +1 -1
  9. package/dist/client/components/post-list-item.js +6 -5
  10. package/dist/client/components/post-list-item.js.map +1 -1
  11. package/dist/client/components/post-list.js +6 -1
  12. package/dist/client/components/post-list.js.map +1 -1
  13. package/dist/client/lib/content-classification.js +11 -2
  14. package/dist/client/lib/content-classification.js.map +1 -1
  15. package/dist/client/plugin/src/client.js +7 -40
  16. package/dist/client/plugin/src/client.js.map +1 -1
  17. package/dist/plugin/src/client.js +7 -0
  18. package/dist/plugin/src/plugin.js +5 -2
  19. package/package.json +1 -1
  20. package/plugin/src/client.tsx +10 -0
  21. package/plugin/src/plugin.ts +5 -2
  22. package/src/components/gallery/index.tsx +8 -23
  23. package/src/components/index.ts +0 -3
  24. package/src/components/mdx-components.tsx +0 -21
  25. package/src/components/post-list-item.tsx +10 -6
  26. package/src/components/post-list.tsx +8 -2
  27. package/src/lib/content-classification.ts +12 -2
  28. package/dist/client/components/parameter-badge.js +0 -48
  29. package/dist/client/components/parameter-badge.js.map +0 -1
  30. package/dist/client/components/parameter-table.js +0 -216
  31. package/dist/client/components/parameter-table.js.map +0 -1
  32. package/dist/client/components/slides/figure-slide.js +0 -14
  33. package/dist/client/components/slides/figure-slide.js.map +0 -1
  34. package/dist/client/components/slides/hero-slide.js +0 -21
  35. package/dist/client/components/slides/hero-slide.js.map +0 -1
  36. package/dist/client/components/slides/slide-outline.js +0 -28
  37. package/dist/client/components/slides/slide-outline.js.map +0 -1
  38. package/dist/client/components/slides/text-slide.js +0 -18
  39. package/dist/client/components/slides/text-slide.js.map +0 -1
  40. package/dist/client/components/veslx-cat.js +0 -40
  41. package/dist/client/components/veslx-cat.js.map +0 -1
  42. package/dist/client/lib/parameter-utils.js +0 -108
  43. package/dist/client/lib/parameter-utils.js.map +0 -1
  44. package/src/components/parameter-badge.tsx +0 -78
  45. package/src/components/parameter-table.tsx +0 -369
  46. package/src/components/slides/figure-slide.tsx +0 -16
  47. package/src/components/slides/hero-slide.tsx +0 -34
  48. package/src/components/slides/slide-outline.tsx +0 -38
  49. package/src/components/slides/text-slide.tsx +0 -35
  50. package/src/components/veslx-cat.tsx +0 -73
@@ -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.64",
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
@@ -124,7 +124,7 @@ export default function Gallery({
124
124
  {[...Array(3)].map((_, i) => (
125
125
  <div
126
126
  key={i}
127
- className="aspect-square rounded-sm bg-muted/20 relative overflow-hidden"
127
+ className="h-40 rounded-sm bg-muted/20 relative overflow-hidden"
128
128
  >
129
129
  <div
130
130
  className="absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer"
@@ -168,13 +168,13 @@ export default function Gallery({
168
168
  <div
169
169
  key={index}
170
170
  title={img.label}
171
- className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}
171
+ className={`overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}
172
172
  onClick={() => lightbox.open(index)}
173
173
  >
174
174
  <LoadingImage
175
175
  src={img.src}
176
176
  alt={img.label}
177
- className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
177
+ className="w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
178
178
  />
179
179
  </div>
180
180
  );
@@ -212,11 +212,6 @@ export default function Gallery({
212
212
  const offset = rowsToRender.slice(0, rowIndex).reduce((acc, row) => acc + row.length, 0);
213
213
  const rowSubtitle = subtitles?.[rowIndex];
214
214
  const rowWrapperClass = "max-w-[var(--gallery-width)] w-full mx-auto";
215
- const placeholders = Math.max(0, maxRowColumns - rowImages.length);
216
- const rowCells = [
217
- ...rowImages.map((img, index) => ({ img, index })),
218
- ...Array.from({ length: placeholders }, () => ({ img: null, index: -1 })),
219
- ];
220
215
 
221
216
  return (
222
217
  <div key={`${rowIndex}-${rowPaths.join("|")}`}>
@@ -225,17 +220,7 @@ export default function Gallery({
225
220
  className="grid gap-3"
226
221
  style={{ gridTemplateColumns: `repeat(${Math.max(1, maxRowColumns)}, minmax(0, 1fr))` }}
227
222
  >
228
- {rowCells.map((cell, index) => {
229
- if (!cell.img) {
230
- return (
231
- <div
232
- key={`empty-${rowIndex}-${index}`}
233
- className="aspect-square rounded-sm opacity-0 pointer-events-none"
234
- />
235
- );
236
- }
237
- return imageElement(offset + cell.index, cell.img, 'w-full');
238
- })}
223
+ {rowImages.map((img, index) => imageElement(offset + index, img, 'w-full'))}
239
224
  </div>
240
225
  </div>
241
226
 
@@ -255,22 +240,22 @@ export default function Gallery({
255
240
  <FigureCaption caption={caption} label={captionLabel} />
256
241
  </div>
257
242
  ) : isCompact ? (
258
- <div className="flex gap-3">
243
+ <div className="flex items-start gap-3">
259
244
  {images.map((img, index) => imageElement(index, img, 'flex-1'))}
260
245
  </div>
261
246
  ) : (
262
- <div ref={scrollRef} className="gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4">
247
+ <div ref={scrollRef} className="gallery-scroll-row flex items-start gap-3 overflow-x-auto overscroll-x-contain pb-4">
263
248
  {images.map((img, index) => (
264
249
  <div
265
250
  key={index}
266
251
  title={img.label}
267
- className="flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group"
252
+ className="flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] overflow-hidden rounded-sm bg-muted/10 cursor-pointer group"
268
253
  onClick={() => lightbox.open(index)}
269
254
  >
270
255
  <LoadingImage
271
256
  src={img.src}
272
257
  alt={img.label}
273
- className="object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
258
+ className="w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
274
259
  />
275
260
  </div>
276
261
  ))}
@@ -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,
@@ -7,12 +7,16 @@ interface PostListItemProps {
7
7
  title: string;
8
8
  description?: string;
9
9
  date?: Date;
10
- href: string;
10
+ href?: string;
11
+ /** Alias for href (used in MDX as linkPath) */
12
+ linkPath?: string;
11
13
  external?: boolean;
14
+ openInNewTab?: boolean;
12
15
  isSlides?: boolean;
13
16
  }
14
17
 
15
- export function PostListItem({ title, description, date, href, external, isSlides }: PostListItemProps) {
18
+ export function PostListItem({ title, description, date, href, linkPath, external, openInNewTab = true, isSlides }: PostListItemProps) {
19
+ const resolvedHref = href || linkPath || '#';
16
20
  const className = cn(
17
21
  "group block py-3 px-3 -mx-3 rounded-md",
18
22
  "transition-colors duration-150",
@@ -53,9 +57,9 @@ export function PostListItem({ title, description, date, href, external, isSlide
53
57
  if (external) {
54
58
  return (
55
59
  <a
56
- href={href}
57
- target="_blank"
58
- rel="noopener noreferrer"
60
+ href={resolvedHref}
61
+ target={openInNewTab ? "_blank" : undefined}
62
+ rel={openInNewTab ? "noopener noreferrer" : undefined}
59
63
  className={className}
60
64
  >
61
65
  {content}
@@ -65,7 +69,7 @@ export function PostListItem({ title, description, date, href, external, isSlide
65
69
 
66
70
  return (
67
71
  <Link
68
- to={href}
72
+ to={resolvedHref}
69
73
  className={className}
70
74
  >
71
75
  {content}
@@ -36,7 +36,11 @@ function filePathToRoutePath(filePath: string): string {
36
36
  // Helper to get link path from post
37
37
  function getLinkPath(post: PostEntry): string {
38
38
  if (post.file) {
39
- // Standalone MDX file
39
+ // Standalone PDF file should open directly.
40
+ if (post.file.path.toLowerCase().endsWith('.pdf')) {
41
+ return `/raw/${post.file.path}`;
42
+ }
43
+ // Standalone MDX/TSX file
40
44
  return filePathToRoutePath(post.file.path);
41
45
  } else if (post.slides && !post.readme) {
42
46
  // Folder with only slides
@@ -51,7 +55,7 @@ function getLinkPath(post: PostEntry): string {
51
55
  }
52
56
 
53
57
  function isRouterPath(href: string): boolean {
54
- return href.startsWith("/") && !href.startsWith("//");
58
+ return href.startsWith("/") && !href.startsWith("//") && !href.startsWith("/raw/");
55
59
  }
56
60
 
57
61
  export function PostList({ globs = null }: PostListProps) {
@@ -143,6 +147,7 @@ export function PostList({ globs = null }: PostListProps) {
143
147
  typeof frontmatter?.link === "string" ? frontmatter.link.trim() : "";
144
148
  const href = frontmatterLink || internalLinkPath;
145
149
  const external = !isRouterPath(href);
150
+ const openInNewTab = external && !href.startsWith("/raw/");
146
151
 
147
152
  return (
148
153
  <PostListItem
@@ -152,6 +157,7 @@ export function PostList({ globs = null }: PostListProps) {
152
157
  date={date}
153
158
  href={href}
154
159
  external={external}
160
+ openInNewTab={openInNewTab}
155
161
  isSlides={isSlides}
156
162
  />
157
163
  );
@@ -1,5 +1,5 @@
1
1
  import type { DirectoryEntry, FileEntry } from "../../plugin/src/lib";
2
- import { findReadme, findSlides, findMdxFiles, findStandaloneSlides, findTsxFiles } from "../../plugin/src/client";
2
+ import { findReadme, findSlides, findMdxFiles, findStandaloneSlides, findTsxFiles, findPdfFiles } from "../../plugin/src/client";
3
3
 
4
4
  export type PostEntry = {
5
5
  type: 'folder' | 'file';
@@ -19,6 +19,7 @@ export function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {
19
19
  const standaloneFiles = findMdxFiles(directory);
20
20
  const standaloneTsxFiles = findTsxFiles(directory);
21
21
  const standaloneSlidesFiles = findStandaloneSlides(directory);
22
+ const standalonePdfFiles = findPdfFiles(directory);
22
23
 
23
24
  const folderPosts: PostEntry[] = folders
24
25
  .map((folder) => ({
@@ -59,7 +60,16 @@ export function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {
59
60
  file: null,
60
61
  }));
61
62
 
62
- return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts];
63
+ const pdfPosts: PostEntry[] = standalonePdfFiles.map((file) => ({
64
+ type: 'file' as const,
65
+ name: file.name.replace(/\.pdf$/i, ''),
66
+ path: file.path,
67
+ readme: null,
68
+ slides: null,
69
+ file,
70
+ }));
71
+
72
+ return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts, ...pdfPosts];
63
73
  }
64
74
 
65
75
  export function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {
@@ -1,48 +0,0 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useFileContent } from "../plugin/src/client.js";
3
- import { useMemo } from "react";
4
- import { cn } from "../lib/utils.js";
5
- import { parseConfigFile, extractPath, deriveLabelFromPath, formatValue, getValueType } from "../lib/parameter-utils.js";
6
- function ParameterBadge({ path, keyPath, label, unit }) {
7
- const { content, loading, error } = useFileContent(path);
8
- const { value, displayLabel } = useMemo(() => {
9
- if (!content) return { value: void 0, displayLabel: "" };
10
- const data = parseConfigFile(content, path);
11
- if (!data) return { value: void 0, displayLabel: "" };
12
- const extracted = extractPath(data, keyPath);
13
- const derivedLabel = label || deriveLabelFromPath(keyPath);
14
- return { value: extracted, displayLabel: derivedLabel };
15
- }, [content, path, keyPath, label]);
16
- if (loading) {
17
- return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-muted/50 border border-border/50", children: /* @__PURE__ */ jsx("span", { className: "w-2 h-2 border border-muted-foreground/40 border-t-transparent rounded-full animate-spin" }) });
18
- }
19
- if (error || value === void 0) {
20
- return /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-destructive/10 border border-destructive/30", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-destructive", children: "—" }) });
21
- }
22
- const type = getValueType(value);
23
- const formattedValue = formatValue(value);
24
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md font-mono text-[11px]", children: [
25
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: displayLabel }),
26
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/40", children: "=" }),
27
- /* @__PURE__ */ jsx(
28
- "span",
29
- {
30
- className: cn(
31
- "font-medium tabular-nums",
32
- type === "number" && "text-foreground",
33
- type === "string" && "text-amber-600 dark:text-amber-500",
34
- type === "boolean" && "text-cyan-600 dark:text-cyan-500",
35
- type === "null" && "text-muted-foreground/50",
36
- type === "array" && "text-purple-600 dark:text-purple-400",
37
- type === "object" && "text-purple-600 dark:text-purple-400"
38
- ),
39
- children: type === "string" ? `"${formattedValue}"` : formattedValue
40
- }
41
- ),
42
- unit && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60", children: unit })
43
- ] });
44
- }
45
- export {
46
- ParameterBadge
47
- };
48
- //# sourceMappingURL=parameter-badge.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"parameter-badge.js","sources":["../../../src/components/parameter-badge.tsx"],"sourcesContent":["import { useFileContent } from \"../../plugin/src/client\";\nimport { useMemo } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n type ParameterValue,\n extractPath,\n getValueType,\n formatValue,\n parseConfigFile,\n deriveLabelFromPath,\n} from \"@/lib/parameter-utils\";\n\ninterface ParameterBadgeProps {\n /** Path to the YAML or JSON file */\n path: string;\n /** jq-like path to the value (e.g., \".base.N_E\") */\n keyPath: string;\n /** Optional label override (defaults to last segment of keyPath) */\n label?: string;\n /** Optional unit suffix (e.g., \"ms\", \"Hz\") */\n unit?: string;\n}\n\nexport function ParameterBadge({ path, keyPath, label, unit }: ParameterBadgeProps) {\n const { content, loading, error } = useFileContent(path);\n\n const { value, displayLabel } = useMemo(() => {\n if (!content) return { value: undefined, displayLabel: \"\" };\n\n const data = parseConfigFile(content, path);\n if (!data) return { value: undefined, displayLabel: \"\" };\n\n const extracted = extractPath(data, keyPath);\n const derivedLabel = label || deriveLabelFromPath(keyPath);\n\n return { value: extracted, displayLabel: derivedLabel };\n }, [content, path, keyPath, label]);\n\n if (loading) {\n return (\n <span className=\"inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-muted/50 border border-border/50\">\n <span className=\"w-2 h-2 border border-muted-foreground/40 border-t-transparent rounded-full animate-spin\" />\n </span>\n );\n }\n\n if (error || value === undefined) {\n return (\n <span className=\"inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-destructive/10 border border-destructive/30\">\n <span className=\"text-[10px] font-mono text-destructive\">—</span>\n </span>\n );\n }\n\n const type = getValueType(value);\n const formattedValue = formatValue(value);\n\n return (\n <span className=\"inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md font-mono text-[11px]\">\n <span className=\"text-muted-foreground\">{displayLabel}</span>\n <span className=\"text-muted-foreground/40\">=</span>\n <span\n className={cn(\n \"font-medium tabular-nums\",\n type === \"number\" && \"text-foreground\",\n type === \"string\" && \"text-amber-600 dark:text-amber-500\",\n type === \"boolean\" && \"text-cyan-600 dark:text-cyan-500\",\n type === \"null\" && \"text-muted-foreground/50\",\n type === \"array\" && \"text-purple-600 dark:text-purple-400\",\n type === \"object\" && \"text-purple-600 dark:text-purple-400\"\n )}\n >\n {type === \"string\" ? `\"${formattedValue}\"` : formattedValue}\n </span>\n {unit && <span className=\"text-muted-foreground/60\">{unit}</span>}\n </span>\n );\n}\n"],"names":[],"mappings":";;;;;AAuBO,SAAS,eAAe,EAAE,MAAM,SAAS,OAAO,QAA6B;AAClF,QAAM,EAAE,SAAS,SAAS,MAAA,IAAU,eAAe,IAAI;AAEvD,QAAM,EAAE,OAAO,aAAA,IAAiB,QAAQ,MAAM;AAC5C,QAAI,CAAC,QAAS,QAAO,EAAE,OAAO,QAAW,cAAc,GAAA;AAEvD,UAAM,OAAO,gBAAgB,SAAS,IAAI;AAC1C,QAAI,CAAC,KAAM,QAAO,EAAE,OAAO,QAAW,cAAc,GAAA;AAEpD,UAAM,YAAY,YAAY,MAAM,OAAO;AAC3C,UAAM,eAAe,SAAS,oBAAoB,OAAO;AAEzD,WAAO,EAAE,OAAO,WAAW,cAAc,aAAA;AAAA,EAC3C,GAAG,CAAC,SAAS,MAAM,SAAS,KAAK,CAAC;AAElC,MAAI,SAAS;AACX,WACE,oBAAC,UAAK,WAAU,+FACd,8BAAC,QAAA,EAAK,WAAU,4FAA2F,EAAA,CAC7G;AAAA,EAEJ;AAEA,MAAI,SAAS,UAAU,QAAW;AAChC,WACE,oBAAC,UAAK,WAAU,0GACd,8BAAC,QAAA,EAAK,WAAU,0CAAyC,UAAA,IAAA,CAAC,EAAA,CAC5D;AAAA,EAEJ;AAEA,QAAM,OAAO,aAAa,KAAK;AAC/B,QAAM,iBAAiB,YAAY,KAAK;AAExC,SACE,qBAAC,QAAA,EAAK,WAAU,iFACd,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAK,WAAU,yBAAyB,UAAA,cAAa;AAAA,IACtD,oBAAC,QAAA,EAAK,WAAU,4BAA2B,UAAA,KAAC;AAAA,IAC5C;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,SAAS,YAAY;AAAA,UACrB,SAAS,YAAY;AAAA,UACrB,SAAS,aAAa;AAAA,UACtB,SAAS,UAAU;AAAA,UACnB,SAAS,WAAW;AAAA,UACpB,SAAS,YAAY;AAAA,QAAA;AAAA,QAGtB,UAAA,SAAS,WAAW,IAAI,cAAc,MAAM;AAAA,MAAA;AAAA,IAAA;AAAA,IAE9C,QAAQ,oBAAC,QAAA,EAAK,WAAU,4BAA4B,UAAA,KAAA,CAAK;AAAA,EAAA,GAC5D;AAEJ;"}