veslx 0.1.35 → 0.1.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/lib/build.ts +0 -12
- package/bin/lib/export.ts +0 -12
- package/bin/lib/serve.ts +9 -14
- package/dist/client/components/footer.js +21 -0
- package/dist/client/components/footer.js.map +1 -0
- package/dist/client/components/gallery/index.js +21 -2
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/components/mdx-components.js +2 -0
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/parameter-table.js +163 -23
- package/dist/client/components/parameter-table.js.map +1 -1
- package/dist/client/components/post-list.js +7 -1
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +29 -3
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/lib/parameter-utils.js +1 -1
- package/dist/client/lib/parameter-utils.js.map +1 -1
- package/dist/client/package.json.js +9 -0
- package/dist/client/package.json.js.map +1 -0
- package/dist/client/pages/home.js +3 -1
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/index-post.js +3 -1
- package/dist/client/pages/index-post.js.map +1 -1
- package/dist/client/pages/post.js +3 -1
- package/dist/client/pages/post.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/plugin.ts +70 -39
- package/src/components/footer.tsx +18 -0
- package/src/components/gallery/index.tsx +32 -2
- package/src/components/mdx-components.tsx +3 -0
- package/src/components/parameter-table.tsx +233 -14
- package/src/components/post-list.tsx +14 -1
- package/src/hooks/use-mdx-content.ts +54 -14
- package/src/lib/parameter-utils.ts +1 -1
- package/src/pages/home.tsx +2 -0
- package/src/pages/index-post.tsx +5 -2
- package/src/pages/post.tsx +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parameter-table.js","sources":["../../../src/components/parameter-table.tsx"],"sourcesContent":["import { useFileContent } from \"../../plugin/src/client\";\nimport { useMemo, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n type ParameterValue,\n extractPath,\n getValueType,\n formatValue,\n parseConfigFile,\n} from \"@/lib/parameter-utils\";\n\n/**\n * Build a filtered data object from an array of jq-like paths.\n * Each path extracts data and places it in the result under the final key name.\n */\nfunction filterData(\n data: Record<string, ParameterValue>,\n keys: string[]\n): Record<string, ParameterValue> {\n const result: Record<string, ParameterValue> = {};\n\n for (const keyPath of keys) {\n const extracted = extractPath(data, keyPath);\n if (extracted === undefined) continue;\n\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n\n // For simple paths like .base.N_E, use \"N_E\" as key\n // For paths with [], preserve more context\n let keyName: string;\n if (cleanPath.includes(\"[\")) {\n keyName = cleanPath.replace(/\\[\\]/g, \"\").replace(/\\[(\\d+)\\]/g, \"_$1\");\n } else {\n const parts = cleanPath.split(\".\");\n keyName = parts[parts.length - 1];\n }\n\n result[keyName] = extracted;\n }\n\n return result;\n}\n\n// Renders a flat section of key-value pairs in a dense grid\nfunction ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {\n if (entries.length === 0) return null;\n\n return (\n <div className=\"grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-x-6 gap-y-px\">\n {entries.map(([key, value]) => {\n const type = getValueType(value);\n return (\n <div\n key={key}\n className=\"flex items-baseline justify-between gap-2 py-1 group hover:bg-muted/30 -mx-1.5 px-1.5 rounded-sm transition-colors\"\n >\n <span className=\"text-[11px] text-muted-foreground font-mono truncate\">\n {key}\n </span>\n <span\n className={cn(\n \"text-[11px] font-mono tabular-nums font-medium shrink-0\",\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 )}\n >\n {type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n </span>\n </div>\n );\n })}\n </div>\n );\n}\n\n// Renders a nested section with its own header\nfunction ParameterSection({\n name,\n data,\n depth = 0\n}: {\n name: string;\n data: Record<string, ParameterValue>;\n depth?: number;\n}) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n\n const entries = Object.entries(data);\n const leafEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const nestedEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n return (\n <div className={cn(depth === 0 && \"mb-4 last:mb-0\")}>\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className={cn(\n \"flex items-center gap-2 w-full text-left group mb-1.5\",\n depth === 0 && \"pb-1 border-b border-border/50\"\n )}\n >\n <span className={cn(\n \"text-[10px] text-muted-foreground/60 transition-transform duration-150 select-none\",\n isCollapsed && \"-rotate-90\"\n )}>\n {isCollapsed ? \"+\" : \"-\"}\n </span>\n\n <span className={cn(\n \"font-mono text-[11px] uppercase tracking-widest\",\n depth === 0\n ? \"text-foreground/80 font-semibold\"\n : \"text-muted-foreground/70\"\n )}>\n {name.replace(/_/g, \" \")}\n </span>\n\n <span className=\"text-[9px] font-mono text-muted-foreground/40 ml-auto\">\n {entries.length}\n </span>\n </button>\n\n {!isCollapsed && (\n <div className={cn(\n depth > 0 && \"pl-3 ml-1 border-l border-border/40\"\n )}>\n {leafEntries.length > 0 && (\n <div className={cn(nestedEntries.length > 0 && \"mb-3\")}>\n <ParameterGrid entries={leafEntries} />\n </div>\n )}\n\n {nestedEntries.map(([key, value]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-2 last:mb-0\">\n <div className=\"text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1\">\n {key} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n * Examples:\n * - [\".base.N_E\", \".base.N_I\"] → show only N_E and N_I from base\n * - [\".base\"] → show entire base section\n * - [\".default_inputs\", \".base.dt\"] → show default_inputs section and dt from base\n */\n keys?: string[];\n}\n\n/**\n * Estimate the height contribution of a data structure.\n */\nfunction estimateHeight(data: Record<string, ParameterValue>, depth = 0): number {\n const entries = Object.entries(data);\n let height = 0;\n\n for (const [, value] of entries) {\n const type = getValueType(value);\n if (type === \"object\") {\n height += 28 + estimateHeight(value as Record<string, ParameterValue>, depth + 1);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n height += 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n height += 24 + estimateHeight(item as Record<string, ParameterValue>, depth + 1);\n } else {\n height += 24;\n }\n }\n } else {\n height += 24;\n }\n }\n\n return height;\n}\n\n/**\n * Split entries into balanced columns based on estimated height.\n */\nfunction splitIntoColumns<T extends [string, ParameterValue]>(\n entries: T[],\n numColumns: number\n): T[][] {\n if (numColumns <= 1) return [entries];\n\n const entryHeights = entries.map(([, value]) => {\n const type = getValueType(value);\n if (type === \"object\") {\n return 28 + estimateHeight(value as Record<string, ParameterValue>);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n let h = 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n h += 24 + estimateHeight(item as Record<string, ParameterValue>);\n } else {\n h += 24;\n }\n }\n return h;\n }\n return 24;\n });\n\n const totalHeight = entryHeights.reduce((a, b) => a + b, 0);\n const targetPerColumn = totalHeight / numColumns;\n\n const columns: T[][] = [];\n let currentColumn: T[] = [];\n let currentHeight = 0;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const entryHeight = entryHeights[i];\n\n if (currentHeight >= targetPerColumn && columns.length < numColumns - 1 && currentColumn.length > 0) {\n columns.push(currentColumn);\n currentColumn = [];\n currentHeight = 0;\n }\n\n currentColumn.push(entry);\n currentHeight += entryHeight;\n }\n\n if (currentColumn.length > 0) {\n columns.push(currentColumn);\n }\n\n return columns;\n}\n\nexport function ParameterTable({ path, keys }: ParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n const parsed = useMemo(() => {\n if (!content) return null;\n\n const data = parseConfigFile(content, path);\n if (!data) return null;\n\n if (keys && keys.length > 0) {\n return filterData(data, keys);\n }\n\n return data;\n }, [content, path, keys]);\n\n if (loading) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"my-6 p-3 rounded border border-destructive/30 bg-destructive/5\">\n <p className=\"text-[11px] font-mono text-destructive\">{error}</p>\n </div>\n );\n }\n\n if (!parsed) {\n return (\n <div className=\"my-6 p-3 rounded border border-border/50 bg-card/30\">\n <p className=\"text-[11px] font-mono text-muted-foreground\">unable to parse config</p>\n </div>\n );\n }\n\n const entries = Object.entries(parsed);\n\n const topLeaves = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const topNested = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n const estHeight = estimateHeight(parsed);\n const HEIGHT_THRESHOLD = 500;\n const numColumns = estHeight > HEIGHT_THRESHOLD ? Math.min(Math.ceil(estHeight / HEIGHT_THRESHOLD), 3) : 1;\n const useColumns = numColumns > 1 && topNested.length > 1;\n\n const columns = useColumns\n ? splitIntoColumns(topNested as [string, ParameterValue][], numColumns)\n : [topNested];\n\n const filename = path.split(\"/\").pop() || path;\n\n const renderNestedEntry = ([key, value]: [string, ParameterValue]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-4 last:mb-0\">\n <div className=\"text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50\">\n {key.replace(/_/g, \" \")} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={0}\n />\n );\n };\n\n return (\n <div className=\"my-6 not-prose\">\n <div className=\"rounded border border-border/60 bg-card/20 p-3 overflow-hidden\">\n {topLeaves.length > 0 && (\n <div className={cn(topNested.length > 0 && \"mb-4 pb-3 border-b border-border/30\")}>\n <ParameterGrid entries={topLeaves} />\n </div>\n )}\n\n {useColumns ? (\n <div\n className=\"grid gap-6\"\n style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}\n >\n {columns.map((columnEntries, colIndex) => (\n <div key={colIndex} className={cn(\n colIndex > 0 && \"border-l border-border/30 pl-6\"\n )}>\n {columnEntries.map(renderNestedEntry)}\n </div>\n ))}\n </div>\n ) : (\n topNested.map(renderNestedEntry)\n )}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"parameter-table.js","sources":["../../../src/components/parameter-table.tsx"],"sourcesContent":["import { useFileContent, useDirectory } from \"../../plugin/src/client\";\nimport { useMemo, useState, useRef, useEffect } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { minimatch } from \"minimatch\";\nimport {\n type ParameterValue,\n extractPath,\n getValueType,\n formatValue,\n parseConfigFile,\n} from \"@/lib/parameter-utils\";\nimport { FileEntry, DirectoryEntry } from \"../../plugin/src/lib\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n */\nfunction usePreventSwipeNavigation(ref: React.RefObject<HTMLElement | null>) {\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const handleWheel = (e: WheelEvent) => {\n if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;\n\n const { scrollLeft, scrollWidth, clientWidth } = el;\n const atLeftEdge = scrollLeft <= 0;\n const atRightEdge = scrollLeft + clientWidth >= scrollWidth - 1;\n\n if ((atLeftEdge && e.deltaX < 0) || (atRightEdge && e.deltaX > 0)) {\n e.preventDefault();\n }\n };\n\n el.addEventListener('wheel', handleWheel, { passive: false });\n return () => el.removeEventListener('wheel', handleWheel);\n }, [ref]);\n}\n\n// Check if a path contains glob patterns\nfunction isGlobPattern(path: string): boolean {\n return path.includes('*') || path.includes('?') || path.includes('[');\n}\n\n// Recursively collect all config files from a directory tree\nfunction collectAllConfigFiles(entry: DirectoryEntry | FileEntry): FileEntry[] {\n if (entry.type === \"file\") {\n if (entry.name.match(/\\.(yaml|yml|json)$/i)) {\n return [entry];\n }\n return [];\n }\n const files: FileEntry[] = [];\n for (const child of entry.children || []) {\n files.push(...collectAllConfigFiles(child));\n }\n return files;\n}\n\n// Sort paths numerically\nfunction sortPathsNumerically(paths: string[]): void {\n paths.sort((a, b) => {\n const nums = (s: string) => (s.match(/\\d+/g) || []).map(Number);\n const na = nums(a);\n const nb = nums(b);\n const len = Math.max(na.length, nb.length);\n for (let i = 0; i < len; i++) {\n const diff = (na[i] ?? 0) - (nb[i] ?? 0);\n if (diff !== 0) return diff;\n }\n return a.localeCompare(b);\n });\n}\n\n/**\n * Build a filtered data object from an array of jq-like paths.\n * Each path extracts data and places it in the result under the final key name.\n */\nfunction filterData(\n data: Record<string, ParameterValue>,\n keys: string[]\n): Record<string, ParameterValue> {\n const result: Record<string, ParameterValue> = {};\n\n for (const keyPath of keys) {\n const extracted = extractPath(data, keyPath);\n if (extracted === undefined) continue;\n\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n\n // For simple paths like .base.N_E, use \"N_E\" as key\n // For paths with [], preserve more context\n let keyName: string;\n if (cleanPath.includes(\"[\")) {\n keyName = cleanPath.replace(/\\[\\]/g, \"\").replace(/\\[(\\d+)\\]/g, \"_$1\");\n } else {\n const parts = cleanPath.split(\".\");\n keyName = parts[parts.length - 1];\n }\n\n result[keyName] = extracted;\n }\n\n return result;\n}\n\n// Renders a flat section of key-value pairs in a dense grid\nfunction ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {\n if (entries.length === 0) return null;\n\n return (\n <div className=\"grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-x-6 gap-y-px\">\n {entries.map(([key, value]) => {\n const type = getValueType(value);\n return (\n <div\n key={key}\n className=\"flex items-baseline justify-between gap-2 py-1 group hover:bg-muted/30 -mx-1.5 px-1.5 rounded-sm transition-colors\"\n >\n <span className=\"text-[11px] text-muted-foreground font-mono truncate\">\n {key}\n </span>\n <span\n className={cn(\n \"text-[11px] font-mono tabular-nums font-medium truncate max-w-[120px]\",\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 )}\n title={type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n >\n {type === \"string\" ? `\"${formatValue(value)}\"` : formatValue(value)}\n </span>\n </div>\n );\n })}\n </div>\n );\n}\n\n// Renders a nested section with its own header\nfunction ParameterSection({\n name,\n data,\n depth = 0\n}: {\n name: string;\n data: Record<string, ParameterValue>;\n depth?: number;\n}) {\n const [isCollapsed, setIsCollapsed] = useState(false);\n\n const entries = Object.entries(data);\n const leafEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const nestedEntries = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n return (\n <div className={cn(depth === 0 && \"mb-4 last:mb-0\")}>\n <button\n onClick={() => setIsCollapsed(!isCollapsed)}\n className={cn(\n \"flex items-center gap-2 w-full text-left group mb-1.5\",\n depth === 0 && \"pb-1 border-b border-border/50\"\n )}\n >\n <span className={cn(\n \"text-[10px] text-muted-foreground/60 transition-transform duration-150 select-none\",\n isCollapsed && \"-rotate-90\"\n )}>\n {isCollapsed ? \"+\" : \"-\"}\n </span>\n\n <span className={cn(\n \"font-mono text-[11px] uppercase tracking-widest\",\n depth === 0\n ? \"text-foreground/80 font-semibold\"\n : \"text-muted-foreground/70\"\n )}>\n {name.replace(/_/g, \" \")}\n </span>\n\n <span className=\"text-[9px] font-mono text-muted-foreground/40 ml-auto\">\n {entries.length}\n </span>\n </button>\n\n {!isCollapsed && (\n <div className={cn(\n depth > 0 && \"pl-3 ml-1 border-l border-border/40\"\n )}>\n {leafEntries.length > 0 && (\n <div className={cn(nestedEntries.length > 0 && \"mb-3\")}>\n <ParameterGrid entries={leafEntries} />\n </div>\n )}\n\n {nestedEntries.map(([key, value]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-2 last:mb-0\">\n <div className=\"text-[10px] font-mono text-muted-foreground/60 uppercase tracking-wider mb-1\">\n {key} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={depth + 1}\n />\n );\n })}\n </div>\n )}\n </div>\n );\n}\n\ninterface SingleParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n * Examples:\n * - [\".base.N_E\", \".base.N_I\"] → show only N_E and N_I from base\n * - [\".base\"] → show entire base section\n * - [\".default_inputs\", \".base.dt\"] → show default_inputs section and dt from base\n */\n keys?: string[];\n /** Optional label to show above the table */\n label?: string;\n /** Whether to include vertical margin (default true) */\n withMargin?: boolean;\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file, supports glob patterns like \"*.yaml\" */\n path: string;\n /**\n * Optional array of jq-like paths to filter which parameters to show.\n */\n keys?: string[];\n}\n\n/**\n * Estimate the height contribution of a data structure.\n */\nfunction estimateHeight(data: Record<string, ParameterValue>, depth = 0): number {\n const entries = Object.entries(data);\n let height = 0;\n\n for (const [, value] of entries) {\n const type = getValueType(value);\n if (type === \"object\") {\n height += 28 + estimateHeight(value as Record<string, ParameterValue>, depth + 1);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n height += 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n height += 24 + estimateHeight(item as Record<string, ParameterValue>, depth + 1);\n } else {\n height += 24;\n }\n }\n } else {\n height += 24;\n }\n }\n\n return height;\n}\n\n/**\n * Split entries into balanced columns based on estimated height.\n */\nfunction splitIntoColumns<T extends [string, ParameterValue]>(\n entries: T[],\n numColumns: number\n): T[][] {\n if (numColumns <= 1) return [entries];\n\n const entryHeights = entries.map(([, value]) => {\n const type = getValueType(value);\n if (type === \"object\") {\n return 28 + estimateHeight(value as Record<string, ParameterValue>);\n } else if (type === \"array\") {\n const arr = value as ParameterValue[];\n let h = 28;\n for (const item of arr) {\n if (getValueType(item) === \"object\") {\n h += 24 + estimateHeight(item as Record<string, ParameterValue>);\n } else {\n h += 24;\n }\n }\n return h;\n }\n return 24;\n });\n\n const totalHeight = entryHeights.reduce((a, b) => a + b, 0);\n const targetPerColumn = totalHeight / numColumns;\n\n const columns: T[][] = [];\n let currentColumn: T[] = [];\n let currentHeight = 0;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n const entryHeight = entryHeights[i];\n\n if (currentHeight >= targetPerColumn && columns.length < numColumns - 1 && currentColumn.length > 0) {\n columns.push(currentColumn);\n currentColumn = [];\n currentHeight = 0;\n }\n\n currentColumn.push(entry);\n currentHeight += entryHeight;\n }\n\n if (currentColumn.length > 0) {\n columns.push(currentColumn);\n }\n\n return columns;\n}\n\nfunction SingleParameterTable({ path, keys, label, withMargin = true }: SingleParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n const { parsed, parseError } = useMemo(() => {\n if (!content) return { parsed: null, parseError: 'no content' };\n\n const data = parseConfigFile(content, path);\n if (!data) {\n // Check why parsing failed\n if (!path.match(/\\.(yaml|yml|json)$/i)) {\n return { parsed: null, parseError: `unsupported file type` };\n }\n // Check if content looks like HTML (404 page)\n if (content.trim().startsWith('<!') || content.trim().startsWith('<html')) {\n return { parsed: null, parseError: `file not found` };\n }\n return { parsed: null, parseError: `invalid ${path.split('.').pop()} syntax` };\n }\n\n if (keys && keys.length > 0) {\n const filtered = filterData(data, keys);\n if (Object.keys(filtered).length === 0) {\n return { parsed: null, parseError: `keys not found: ${keys.join(', ')}` };\n }\n return { parsed: filtered, parseError: null };\n }\n\n return { parsed: data, parseError: null };\n }, [content, path, keys]);\n\n if (loading) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"my-6 p-3 rounded border border-destructive/30 bg-destructive/5\">\n <p className=\"text-[11px] font-mono text-destructive\">{error}</p>\n </div>\n );\n }\n\n if (!parsed) {\n return (\n <div className={cn(\"p-3 rounded border border-border/50 bg-card/30\", withMargin && \"my-6\")}>\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n {label && <span className=\"text-foreground/60\">{label}: </span>}\n {parseError || 'unable to parse'}\n </p>\n </div>\n );\n }\n\n const entries = Object.entries(parsed);\n\n const topLeaves = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t !== \"object\" && t !== \"array\";\n });\n const topNested = entries.filter(([, v]) => {\n const t = getValueType(v);\n return t === \"object\" || t === \"array\";\n });\n\n const estHeight = estimateHeight(parsed);\n const HEIGHT_THRESHOLD = 500;\n const numColumns = estHeight > HEIGHT_THRESHOLD ? Math.min(Math.ceil(estHeight / HEIGHT_THRESHOLD), 3) : 1;\n const useColumns = numColumns > 1 && topNested.length > 1;\n\n const columns = useColumns\n ? splitIntoColumns(topNested as [string, ParameterValue][], numColumns)\n : [topNested];\n\n const filename = path.split(\"/\").pop() || path;\n\n const renderNestedEntry = ([key, value]: [string, ParameterValue]) => {\n const type = getValueType(value);\n if (type === \"array\") {\n const arr = value as ParameterValue[];\n return (\n <div key={key} className=\"mb-4 last:mb-0\">\n <div className=\"text-[11px] font-mono text-foreground/80 uppercase tracking-widest font-semibold mb-1.5 pb-1 border-b border-border/50\">\n {key.replace(/_/g, \" \")} [{arr.length}]\n </div>\n <div className=\"pl-3 ml-1 border-l border-border/40\">\n {arr.map((item, i) => {\n const itemType = getValueType(item);\n if (itemType === \"object\") {\n return (\n <ParameterSection\n key={i}\n name={`${i}`}\n data={item as Record<string, ParameterValue>}\n depth={1}\n />\n );\n }\n return (\n <div key={i} className=\"text-[11px] font-mono text-foreground py-0.5\">\n [{i}] {formatValue(item)}\n </div>\n );\n })}\n </div>\n </div>\n );\n }\n return (\n <ParameterSection\n key={key}\n name={key}\n data={value as Record<string, ParameterValue>}\n depth={0}\n />\n );\n };\n\n return (\n <div className={cn(\"not-prose\", withMargin && \"my-6\")}>\n {label && (\n <div className=\"text-[11px] font-mono text-muted-foreground mb-1.5 truncate\" title={label}>\n {label}\n </div>\n )}\n <div className=\"rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full\">\n {topLeaves.length > 0 && (\n <div className={cn(topNested.length > 0 && \"mb-4 pb-3 border-b border-border/30\")}>\n <ParameterGrid entries={topLeaves} />\n </div>\n )}\n\n {useColumns ? (\n <div\n className=\"grid gap-6\"\n style={{ gridTemplateColumns: `repeat(${columns.length}, 1fr)` }}\n >\n {columns.map((columnEntries, colIndex) => (\n <div key={colIndex} className={cn(\n colIndex > 0 && \"border-l border-border/30 pl-6\"\n )}>\n {columnEntries.map(renderNestedEntry)}\n </div>\n ))}\n </div>\n ) : (\n topNested.map(renderNestedEntry)\n )}\n </div>\n </div>\n );\n}\n\n/**\n * ParameterTable component that displays YAML/JSON config files.\n * Supports glob patterns in the path prop to show multiple files.\n */\nexport function ParameterTable({ path, keys }: ParameterTableProps) {\n const { \"*\": routePath = \"\" } = useParams();\n\n // Get current directory from route\n const currentDir = routePath\n .replace(/\\/?[^/]+\\.mdx$/i, \"\")\n .replace(/\\/$/, \"\")\n || \".\";\n\n // Resolve relative paths\n let resolvedPath = path;\n if (path?.startsWith(\"./\")) {\n const relativePart = path.slice(2);\n resolvedPath = currentDir === \".\" ? relativePart : `${currentDir}/${relativePart}`;\n } else if (path && !path.startsWith(\"/\") && !path.includes(\"/\") && !isGlobPattern(path)) {\n resolvedPath = currentDir === \".\" ? path : `${currentDir}/${path}`;\n }\n\n // Check if this is a glob pattern\n const hasGlob = isGlobPattern(resolvedPath);\n\n // For glob patterns, get the base directory (directory containing the glob pattern)\n const baseDir = useMemo(() => {\n if (!hasGlob) return null;\n // Get everything before the first glob character\n const beforeGlob = resolvedPath.split(/[*?\\[]/, 1)[0];\n // Extract directory portion (everything up to the last slash)\n const lastSlash = beforeGlob.lastIndexOf('/');\n if (lastSlash === -1) return \".\";\n return beforeGlob.slice(0, lastSlash) || \".\";\n }, [hasGlob, resolvedPath]);\n\n const { directory } = useDirectory(baseDir || \".\");\n\n // Find matching files for glob patterns\n const matchingPaths = useMemo(() => {\n if (!hasGlob || !directory) return [];\n\n const allFiles = collectAllConfigFiles(directory);\n const paths = allFiles\n .map(f => f.path)\n .filter(p => minimatch(p, resolvedPath, { matchBase: true }));\n\n sortPathsNumerically(paths);\n\n // Debug logging\n console.log('[ParameterTable]', {\n original: path,\n resolved: resolvedPath,\n baseDir,\n allConfigFiles: allFiles.map(f => f.path),\n matched: paths\n });\n\n return paths;\n }, [hasGlob, directory, resolvedPath, path, baseDir]);\n\n // If not a glob pattern, just render the single table\n if (!hasGlob) {\n return <SingleParameterTable path={resolvedPath} keys={keys} />;\n }\n\n // Loading state for glob patterns\n if (!directory) {\n return (\n <div className=\"my-6 p-4 rounded border border-border/50 bg-card/30\">\n <div className=\"flex items-center gap-2 text-muted-foreground/60\">\n <div className=\"w-3 h-3 border border-current border-t-transparent rounded-full animate-spin\" />\n <span className=\"text-[11px] font-mono\">loading parameters...</span>\n </div>\n </div>\n );\n }\n\n // No matches\n if (matchingPaths.length === 0) {\n return (\n <div className=\"my-6 p-3 rounded border border-border/50 bg-card/30\">\n <p className=\"text-[11px] font-mono text-muted-foreground\">\n no files matching: {resolvedPath}\n <br />\n <span className=\"text-muted-foreground/50\">(base dir: {baseDir}, original: {path})</span>\n </p>\n </div>\n );\n }\n\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n // Breakout width based on count\n const count = matchingPaths.length;\n const breakoutClass = count >= 4\n ? 'w-[96vw] ml-[calc(-48vw+50%)]'\n : count >= 2\n ? 'w-[75vw] ml-[calc(-37.5vw+50%)]'\n : '';\n\n return (\n <div className={`my-6 ${breakoutClass}`}>\n <div ref={scrollRef} className=\"flex gap-4 overflow-x-auto overscroll-x-contain pb-2\">\n {matchingPaths.map((filePath) => (\n <div key={filePath} className=\"flex-none w-[280px]\">\n <SingleParameterTable\n path={filePath}\n keys={keys}\n label={filePath.split('/').pop() || filePath}\n withMargin={false}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
|
+
import { minimatch } from "minimatch";
|
|
3
4
|
import { directoryToPostEntries, filterVisiblePosts, getFrontmatter } from "../lib/content-classification.js";
|
|
4
5
|
import { useDirectory } from "../plugin/src/client.js";
|
|
5
6
|
import { ErrorDisplay } from "./page-error.js";
|
|
@@ -19,7 +20,7 @@ function getLinkPath(post) {
|
|
|
19
20
|
return `/${post.path}`;
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
|
-
function PostList() {
|
|
23
|
+
function PostList({ globs = null }) {
|
|
23
24
|
var _a;
|
|
24
25
|
const { "*": path = "." } = useParams();
|
|
25
26
|
const { directory, error } = useDirectory(path);
|
|
@@ -34,6 +35,11 @@ function PostList() {
|
|
|
34
35
|
return /* @__PURE__ */ jsx("div", { className: "py-24 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground font-mono text-sm tracking-wide", children: "no entries" }) });
|
|
35
36
|
}
|
|
36
37
|
posts = filterVisiblePosts(posts);
|
|
38
|
+
if (globs && globs.length > 0) {
|
|
39
|
+
posts = posts.filter(
|
|
40
|
+
(post) => globs.some((pattern) => minimatch(post.name, pattern, { matchBase: true }))
|
|
41
|
+
);
|
|
42
|
+
}
|
|
37
43
|
if (posts.length === 0) {
|
|
38
44
|
return /* @__PURE__ */ jsx("div", { className: "py-24 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground font-mono text-sm tracking-wide", children: "no entries" }) });
|
|
39
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\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\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\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return `/${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 `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nexport function PostList() {\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 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 linkPath = getLinkPath(post);\n const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n linkPath={linkPath}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"
|
|
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\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return `/${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 `/${post.readme.path}`;\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\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 linkPath = getLinkPath(post);\n const isSlides = linkPath.endsWith('SLIDES.mdx') || linkPath.endsWith('.slides.mdx');\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n linkPath={linkPath}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ function findMdxModule(modules, path) {
|
|
|
5
5
|
if (normalizedPath.endsWith(".mdx") || normalizedPath.endsWith(".md")) {
|
|
6
6
|
const matchingKey = keys.find((key) => {
|
|
7
7
|
if (key.endsWith(`/${normalizedPath}`)) return true;
|
|
8
|
-
if (key === `@content/${normalizedPath}`) return true;
|
|
9
8
|
if (key === `/@content/${normalizedPath}`) return true;
|
|
9
|
+
if (key === `@content/${normalizedPath}`) return true;
|
|
10
10
|
if (key === normalizedPath) return true;
|
|
11
11
|
if (key === `/${normalizedPath}`) return true;
|
|
12
12
|
return false;
|
|
@@ -24,6 +24,7 @@ function findMdxModule(modules, path) {
|
|
|
24
24
|
for (const candidate of candidates) {
|
|
25
25
|
const matchingKey = keys.find((key) => {
|
|
26
26
|
if (key.endsWith(`/${candidate}`)) return true;
|
|
27
|
+
if (key === `/@content/${candidate}`) return true;
|
|
27
28
|
if (key === `@content/${candidate}`) return true;
|
|
28
29
|
if (key === candidate) return true;
|
|
29
30
|
return false;
|
|
@@ -73,8 +74,8 @@ function findSlidesModule(modules, path) {
|
|
|
73
74
|
if (normalizedPath.endsWith(".mdx") || normalizedPath.endsWith(".md")) {
|
|
74
75
|
const matchingKey = keys.find((key) => {
|
|
75
76
|
if (key.endsWith(`/${normalizedPath}`)) return true;
|
|
76
|
-
if (key === `@content/${normalizedPath}`) return true;
|
|
77
77
|
if (key === `/@content/${normalizedPath}`) return true;
|
|
78
|
+
if (key === `@content/${normalizedPath}`) return true;
|
|
78
79
|
if (key === normalizedPath) return true;
|
|
79
80
|
if (key === `/${normalizedPath}`) return true;
|
|
80
81
|
return false;
|
|
@@ -90,6 +91,7 @@ function findSlidesModule(modules, path) {
|
|
|
90
91
|
for (const candidate of candidates) {
|
|
91
92
|
const matchingKey = keys.find((key) => {
|
|
92
93
|
if (key.endsWith(`/${candidate}`)) return true;
|
|
94
|
+
if (key === `/@content/${candidate}`) return true;
|
|
93
95
|
if (key === `@content/${candidate}`) return true;
|
|
94
96
|
if (key === candidate) return true;
|
|
95
97
|
return false;
|
|
@@ -138,11 +140,35 @@ function useMDXSlides(path) {
|
|
|
138
140
|
function findIndexModule(modules, path) {
|
|
139
141
|
const keys = Object.keys(modules);
|
|
140
142
|
const normalizedPath = path.replace(/^\//, "") || ".";
|
|
141
|
-
const
|
|
143
|
+
const isRoot = normalizedPath === "." || normalizedPath === "";
|
|
144
|
+
console.log("[findIndexModule] path:", path, "normalizedPath:", normalizedPath, "isRoot:", isRoot);
|
|
145
|
+
console.log("[findIndexModule] ALL keys:", keys);
|
|
146
|
+
const candidates = isRoot ? ["index.mdx", "index.md", "README.mdx", "README.md"] : [
|
|
147
|
+
`${normalizedPath}/index.mdx`,
|
|
148
|
+
`${normalizedPath}/index.md`,
|
|
149
|
+
`${normalizedPath}/README.mdx`,
|
|
150
|
+
`${normalizedPath}/README.md`
|
|
151
|
+
];
|
|
142
152
|
for (const candidate of candidates) {
|
|
143
153
|
const matchingKey = keys.find((key) => {
|
|
154
|
+
if (isRoot) {
|
|
155
|
+
const keyFilename = key.split("/").pop();
|
|
156
|
+
if (keyFilename === candidate) {
|
|
157
|
+
const parts = key.split("/");
|
|
158
|
+
const contentIdx = parts.findIndex((p) => p === "content" || p === "@content");
|
|
159
|
+
if (contentIdx !== -1) {
|
|
160
|
+
const afterContent = parts.slice(contentIdx + 1);
|
|
161
|
+
if (afterContent.length === 1) return true;
|
|
162
|
+
}
|
|
163
|
+
if (key === `/@content/${candidate}`) return true;
|
|
164
|
+
if (key === `@content/${candidate}`) return true;
|
|
165
|
+
}
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
144
168
|
if (key.endsWith(`/${candidate}`)) return true;
|
|
169
|
+
if (key === `/@content/${candidate}`) return true;
|
|
145
170
|
if (key === `@content/${candidate}`) return true;
|
|
171
|
+
if (key === `/content/${candidate}`) return true;
|
|
146
172
|
if (key === candidate) return true;
|
|
147
173
|
return false;
|
|
148
174
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-mdx-content.js","sources":["../../../src/hooks/use-mdx-content.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport type { ComponentType } from 'react'\n\ninterface MDXModule {\n default: ComponentType<{ components?: Record<string, ComponentType> }>\n frontmatter?: {\n title?: string\n description?: string\n date?: string\n visibility?: string\n draft?: boolean\n }\n slideCount?: number // Exported by remark-slides plugin for SLIDES.mdx files\n}\n\ntype ModuleLoader = () => Promise<MDXModule>\ntype ModuleMap = Record<string, ModuleLoader>\n\n/**\n * Find MDX/MD module by path. Supports:\n * - Full path: \"docs/intro.mdx\" or \"docs/intro.md\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/index.mdx\", \"docs/README.mdx\", or \"docs.mdx\" (and .md variants)\n */\nfunction findMdxModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash if present\n const normalizedPath = path.replace(/^\\//, '')\n\n // If path already ends with .mdx or .md, match exactly\n if (normalizedPath.endsWith('.mdx') || normalizedPath.endsWith('.md')) {\n // Try multiple matching strategies for different Vite glob formats\n const matchingKey = keys.find(key => {\n // Strategy 1: Key ends with /path (e.g., @content/docs/foo.mdx matches docs/foo.mdx)\n if (key.endsWith(`/${normalizedPath}`)) return true\n // Strategy 2: Key equals @content/path (alias form)\n if (key === `@content/${normalizedPath}`) return true\n // Strategy 3: Key equals /@content/path (with leading slash)\n if (key === `/@content/${normalizedPath}`) return true\n // Strategy 4: Key equals path directly\n if (key === normalizedPath) return true\n // Strategy 5: Key equals /path (with leading slash)\n if (key === `/${normalizedPath}`) return true\n return false\n })\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions in order of preference:\n // 1. folder/index.mdx (modern convention)\n // 2. folder/README.mdx (current convention)\n // 3. folder.mdx (file alongside folders)\n // Also try .md variants\n const candidates = [\n `${normalizedPath}/index.mdx`,\n `${normalizedPath}/index.md`,\n `${normalizedPath}/README.mdx`,\n `${normalizedPath}/README.md`,\n `${normalizedPath}.mdx`,\n `${normalizedPath}.md`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ modules }) => {\n const loader = findMdxModule(modules as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`MDX module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, error }\n}\n\n/**\n * Find slides module by path. Supports:\n * - Full path: \"docs/intro.slides.mdx\" or \"docs/SLIDES.mdx\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/SLIDES.mdx\" or \"docs/index.slides.mdx\"\n */\nfunction findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash if present\n const normalizedPath = path.replace(/^\\//, '')\n\n // If path already ends with .mdx or .md, match exactly\n if (normalizedPath.endsWith('.mdx') || normalizedPath.endsWith('.md')) {\n // Try multiple matching strategies for different Vite glob formats\n const matchingKey = keys.find(key => {\n // Strategy 1: Key ends with /path (e.g., @content/docs/foo.slides.mdx matches docs/foo.slides.mdx)\n if (key.endsWith(`/${normalizedPath}`)) return true\n // Strategy 2: Key equals @content/path (alias form)\n if (key === `@content/${normalizedPath}`) return true\n // Strategy 3: Key equals /@content/path (with leading slash)\n if (key === `/@content/${normalizedPath}`) return true\n // Strategy 4: Key equals path directly\n if (key === normalizedPath) return true\n // Strategy 5: Key equals /path (with leading slash)\n if (key === `/${normalizedPath}`) return true\n return false\n })\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions:\n // 1. folder/SLIDES.mdx (current convention)\n // 2. folder/index.slides.mdx (alternative)\n // Also try .md variants\n const candidates = [\n `${normalizedPath}/SLIDES.mdx`,\n `${normalizedPath}/SLIDES.md`,\n `${normalizedPath}/index.slides.mdx`,\n `${normalizedPath}/index.slides.md`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXSlides(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [slideCount, setSlideCount] = useState<number | undefined>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ slides }) => {\n const loader = findSlidesModule(slides as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`Slides module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setSlideCount(mod.slideCount)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, slideCount, loading, error }\n}\n\n/**\n * Find only index.mdx or index.md in a directory (not README or other conventions)\n */\nfunction findIndexModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash, handle root\n const normalizedPath = path.replace(/^\\//, '') || '.'\n\n // Only look for index.mdx or index.md\n const candidates = normalizedPath === '.'\n ? ['index.mdx', 'index.md']\n : [`${normalizedPath}/index.mdx`, `${normalizedPath}/index.md`]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\n/**\n * Hook for loading index.mdx/index.md content only.\n * Returns notFound: true if no index file exists (instead of throwing an error).\n */\nexport function useIndexContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [notFound, setNotFound] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setNotFound(false)\n\n import('virtual:content-modules')\n .then(({ modules }) => {\n const loader = findIndexModule(modules as ModuleMap, path)\n\n if (!loader) {\n // No index file - this is not an error, just means fallback to directory listing\n if (!cancelled) {\n setNotFound(true)\n setLoading(false)\n }\n return null\n }\n\n return loader()\n })\n .then((mod) => {\n if (mod && !cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch(() => {\n // Treat load errors as not found\n if (!cancelled) {\n setNotFound(true)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, notFound }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-mdx-content.js","sources":["../../../src/hooks/use-mdx-content.ts"],"sourcesContent":["import { useState, useEffect } from 'react'\nimport type { ComponentType } from 'react'\n\ninterface MDXModule {\n default: ComponentType<{ components?: Record<string, ComponentType> }>\n frontmatter?: {\n title?: string\n description?: string\n date?: string\n visibility?: string\n draft?: boolean\n }\n slideCount?: number // Exported by remark-slides plugin for SLIDES.mdx files\n}\n\ntype ModuleLoader = () => Promise<MDXModule>\ntype ModuleMap = Record<string, ModuleLoader>\n\n/**\n * Find MDX/MD module by path. Supports:\n * - Full path: \"docs/intro.mdx\" or \"docs/intro.md\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/index.mdx\", \"docs/README.mdx\", or \"docs.mdx\" (and .md variants)\n */\nfunction findMdxModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash if present\n const normalizedPath = path.replace(/^\\//, '')\n\n // If path already ends with .mdx or .md, match exactly\n if (normalizedPath.endsWith('.mdx') || normalizedPath.endsWith('.md')) {\n // Try multiple matching strategies for different Vite glob formats\n const matchingKey = keys.find(key => {\n // Strategy 1: Key ends with /path (e.g., @content/docs/foo.mdx matches docs/foo.mdx)\n if (key.endsWith(`/${normalizedPath}`)) return true\n // Strategy 2: Key equals /@content/path (Vite alias resolution)\n if (key === `/@content/${normalizedPath}`) return true\n // Strategy 3: Key equals @content/path (alias form)\n if (key === `@content/${normalizedPath}`) return true\n // Strategy 4: Key equals path directly\n if (key === normalizedPath) return true\n // Strategy 5: Key equals /path (with leading slash)\n if (key === `/${normalizedPath}`) return true\n return false\n })\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions in order of preference:\n // 1. folder/index.mdx (modern convention)\n // 2. folder/README.mdx (current convention)\n // 3. folder.mdx (file alongside folders)\n // Also try .md variants\n const candidates = [\n `${normalizedPath}/index.mdx`,\n `${normalizedPath}/index.md`,\n `${normalizedPath}/README.mdx`,\n `${normalizedPath}/README.md`,\n `${normalizedPath}.mdx`,\n `${normalizedPath}.md`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `/@content/${candidate}`) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ modules }) => {\n const loader = findMdxModule(modules as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`MDX module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, error }\n}\n\n/**\n * Find slides module by path. Supports:\n * - Full path: \"docs/intro.slides.mdx\" or \"docs/SLIDES.mdx\" -> matches exactly\n * - Folder path: \"docs\" -> matches \"docs/SLIDES.mdx\" or \"docs/index.slides.mdx\"\n */\nfunction findSlidesModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash if present\n const normalizedPath = path.replace(/^\\//, '')\n\n // If path already ends with .mdx or .md, match exactly\n if (normalizedPath.endsWith('.mdx') || normalizedPath.endsWith('.md')) {\n // Try multiple matching strategies for different Vite glob formats\n const matchingKey = keys.find(key => {\n // Strategy 1: Key ends with /path (e.g., @content/docs/foo.slides.mdx matches docs/foo.slides.mdx)\n if (key.endsWith(`/${normalizedPath}`)) return true\n // Strategy 2: Key equals /@content/path (Vite alias resolution)\n if (key === `/@content/${normalizedPath}`) return true\n // Strategy 3: Key equals @content/path (alias form)\n if (key === `@content/${normalizedPath}`) return true\n // Strategy 4: Key equals path directly\n if (key === normalizedPath) return true\n // Strategy 5: Key equals /path (with leading slash)\n if (key === `/${normalizedPath}`) return true\n return false\n })\n return matchingKey ? modules[matchingKey] : null\n }\n\n // Otherwise, try folder conventions:\n // 1. folder/SLIDES.mdx (current convention)\n // 2. folder/index.slides.mdx (alternative)\n // Also try .md variants\n const candidates = [\n `${normalizedPath}/SLIDES.mdx`,\n `${normalizedPath}/SLIDES.md`,\n `${normalizedPath}/index.slides.mdx`,\n `${normalizedPath}/index.slides.md`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `/@content/${candidate}`) return true\n if (key === `@content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\nexport function useMDXSlides(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [slideCount, setSlideCount] = useState<number | undefined>(undefined)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setError(null)\n\n // Dynamic import to avoid pre-bundling issues\n import('virtual:content-modules')\n .then(({ slides }) => {\n const loader = findSlidesModule(slides as ModuleMap, path)\n\n if (!loader) {\n throw new Error(`Slides module not found for path: ${path}`)\n }\n\n return loader()\n })\n .then((mod) => {\n if (!cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setSlideCount(mod.slideCount)\n setLoading(false)\n }\n })\n .catch((err) => {\n if (!cancelled) {\n setError(err)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, slideCount, loading, error }\n}\n\n/**\n * Find index.mdx, index.md, README.mdx, or README.md in a directory.\n * Checks index files first, then README files.\n */\nfunction findIndexModule(modules: ModuleMap, path: string): ModuleLoader | null {\n const keys = Object.keys(modules)\n\n // Normalize path - remove leading slash, handle root\n const normalizedPath = path.replace(/^\\//, '') || '.'\n const isRoot = normalizedPath === '.' || normalizedPath === ''\n\n // Debug: log keys and path\n console.log('[findIndexModule] path:', path, 'normalizedPath:', normalizedPath, 'isRoot:', isRoot)\n console.log('[findIndexModule] ALL keys:', keys)\n\n // Look for index files first, then README files\n const candidates = isRoot\n ? ['index.mdx', 'index.md', 'README.mdx', 'README.md']\n : [\n `${normalizedPath}/index.mdx`,\n `${normalizedPath}/index.md`,\n `${normalizedPath}/README.mdx`,\n `${normalizedPath}/README.md`,\n ]\n\n for (const candidate of candidates) {\n const matchingKey = keys.find(key => {\n // For root-level files, match keys that end with /candidate but DON'T have additional path segments\n // e.g., for README.mdx: match \"/../content/README.mdx\" but not \"/../content/subdir/README.mdx\"\n if (isRoot) {\n // Key must end with /candidate and have no additional path segments before the filename\n // Extract the filename from the key and compare\n const keyFilename = key.split('/').pop()\n if (keyFilename === candidate) {\n // Make sure it's in the root of content dir, not a subdirectory\n // Count path segments after the content directory marker\n // Keys look like: /../pinglab/content/README.mdx or @content/README.mdx\n const parts = key.split('/')\n const contentIdx = parts.findIndex(p => p === 'content' || p === '@content')\n if (contentIdx !== -1) {\n // If there's only one segment after \"content\", it's a root file\n const afterContent = parts.slice(contentIdx + 1)\n if (afterContent.length === 1) return true\n }\n // Also try @content prefix matching\n if (key === `/@content/${candidate}`) return true\n if (key === `@content/${candidate}`) return true\n }\n return false\n }\n // For subdirectories, allow endsWith matching\n if (key.endsWith(`/${candidate}`)) return true\n if (key === `/@content/${candidate}`) return true\n if (key === `@content/${candidate}`) return true\n if (key === `/content/${candidate}`) return true\n if (key === candidate) return true\n return false\n })\n if (matchingKey) {\n return modules[matchingKey]\n }\n }\n\n return null\n}\n\n/**\n * Hook for loading index.mdx/index.md or README.mdx/README.md content.\n * Checks for index files first, then README files.\n * Returns notFound: true if no matching file exists (instead of throwing an error).\n */\nexport function useIndexContent(path: string) {\n const [Content, setContent] = useState<MDXModule['default'] | null>(null)\n const [frontmatter, setFrontmatter] = useState<MDXModule['frontmatter']>(undefined)\n const [loading, setLoading] = useState(true)\n const [notFound, setNotFound] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n setLoading(true)\n setNotFound(false)\n\n import('virtual:content-modules')\n .then(({ modules }) => {\n const loader = findIndexModule(modules as ModuleMap, path)\n\n if (!loader) {\n // No index file - this is not an error, just means fallback to directory listing\n if (!cancelled) {\n setNotFound(true)\n setLoading(false)\n }\n return null\n }\n\n return loader()\n })\n .then((mod) => {\n if (mod && !cancelled) {\n setContent(() => mod.default)\n setFrontmatter(mod.frontmatter)\n setLoading(false)\n }\n })\n .catch(() => {\n // Treat load errors as not found\n if (!cancelled) {\n setNotFound(true)\n setLoading(false)\n }\n })\n\n return () => {\n cancelled = true\n }\n }, [path])\n\n return { Content, frontmatter, loading, notFound }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -65,7 +65,7 @@ function formatNumber(value) {
|
|
|
65
65
|
if (Math.abs(value) < 1e-4 && value !== 0) return value.toExponential(1);
|
|
66
66
|
if (Math.abs(value) >= 1e4) return value.toExponential(1);
|
|
67
67
|
if (Number.isInteger(value)) return value.toString();
|
|
68
|
-
return value.toFixed(
|
|
68
|
+
return value.toFixed(3);
|
|
69
69
|
}
|
|
70
70
|
function formatValue(value) {
|
|
71
71
|
if (value === null) return "null";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parameter-utils.js","sources":["../../../src/lib/parameter-utils.ts"],"sourcesContent":["import { load } from \"js-yaml\";\n\nexport type ParameterValue = string | number | boolean | null | ParameterValue[] | { [key: string]: ParameterValue };\n\n/**\n * Extract a value from nested data using a jq-like path.\n * Supports:\n * - .foo.bar → nested keys\n * - .foo[0] → array index\n * - .foo[] → all array elements (returns array)\n */\nexport function extractPath(data: ParameterValue, path: string): ParameterValue | undefined {\n if (!path || path === \".\") return data;\n\n const cleanPath = path.startsWith(\".\") ? path.slice(1) : path;\n if (!cleanPath) return data;\n\n const segments: Array<{ type: \"key\" | \"index\" | \"all\"; value: string | number }> = [];\n let current = \"\";\n let i = 0;\n\n while (i < cleanPath.length) {\n const char = cleanPath[i];\n\n if (char === \".\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n i++;\n } else if (char === \"[\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n const closeIdx = cleanPath.indexOf(\"]\", i);\n if (closeIdx === -1) return undefined;\n const inner = cleanPath.slice(i + 1, closeIdx);\n if (inner === \"\") {\n segments.push({ type: \"all\", value: 0 });\n } else {\n const idx = parseInt(inner, 10);\n if (isNaN(idx)) return undefined;\n segments.push({ type: \"index\", value: idx });\n }\n i = closeIdx + 1;\n } else {\n current += char;\n i++;\n }\n }\n if (current) {\n segments.push({ type: \"key\", value: current });\n }\n\n let result: ParameterValue | undefined = data;\n\n for (const seg of segments) {\n if (result === null || result === undefined) return undefined;\n\n if (seg.type === \"key\") {\n if (typeof result !== \"object\" || Array.isArray(result)) return undefined;\n result = (result as Record<string, ParameterValue>)[seg.value as string];\n } else if (seg.type === \"index\") {\n if (!Array.isArray(result)) return undefined;\n result = result[seg.value as number];\n } else if (seg.type === \"all\") {\n if (!Array.isArray(result)) return undefined;\n // Return the array itself for further processing\n }\n }\n\n return result;\n}\n\nexport function getValueType(value: ParameterValue): \"string\" | \"number\" | \"boolean\" | \"null\" | \"array\" | \"object\" {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"object\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"number\") return \"number\";\n return \"string\";\n}\n\nexport function formatNumber(value: number): string {\n if (Math.abs(value) < 0.0001 && value !== 0) return value.toExponential(1);\n if (Math.abs(value) >= 10000) return value.toExponential(1);\n if (Number.isInteger(value)) return value.toString();\n return value.toFixed(
|
|
1
|
+
{"version":3,"file":"parameter-utils.js","sources":["../../../src/lib/parameter-utils.ts"],"sourcesContent":["import { load } from \"js-yaml\";\n\nexport type ParameterValue = string | number | boolean | null | ParameterValue[] | { [key: string]: ParameterValue };\n\n/**\n * Extract a value from nested data using a jq-like path.\n * Supports:\n * - .foo.bar → nested keys\n * - .foo[0] → array index\n * - .foo[] → all array elements (returns array)\n */\nexport function extractPath(data: ParameterValue, path: string): ParameterValue | undefined {\n if (!path || path === \".\") return data;\n\n const cleanPath = path.startsWith(\".\") ? path.slice(1) : path;\n if (!cleanPath) return data;\n\n const segments: Array<{ type: \"key\" | \"index\" | \"all\"; value: string | number }> = [];\n let current = \"\";\n let i = 0;\n\n while (i < cleanPath.length) {\n const char = cleanPath[i];\n\n if (char === \".\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n i++;\n } else if (char === \"[\") {\n if (current) {\n segments.push({ type: \"key\", value: current });\n current = \"\";\n }\n const closeIdx = cleanPath.indexOf(\"]\", i);\n if (closeIdx === -1) return undefined;\n const inner = cleanPath.slice(i + 1, closeIdx);\n if (inner === \"\") {\n segments.push({ type: \"all\", value: 0 });\n } else {\n const idx = parseInt(inner, 10);\n if (isNaN(idx)) return undefined;\n segments.push({ type: \"index\", value: idx });\n }\n i = closeIdx + 1;\n } else {\n current += char;\n i++;\n }\n }\n if (current) {\n segments.push({ type: \"key\", value: current });\n }\n\n let result: ParameterValue | undefined = data;\n\n for (const seg of segments) {\n if (result === null || result === undefined) return undefined;\n\n if (seg.type === \"key\") {\n if (typeof result !== \"object\" || Array.isArray(result)) return undefined;\n result = (result as Record<string, ParameterValue>)[seg.value as string];\n } else if (seg.type === \"index\") {\n if (!Array.isArray(result)) return undefined;\n result = result[seg.value as number];\n } else if (seg.type === \"all\") {\n if (!Array.isArray(result)) return undefined;\n // Return the array itself for further processing\n }\n }\n\n return result;\n}\n\nexport function getValueType(value: ParameterValue): \"string\" | \"number\" | \"boolean\" | \"null\" | \"array\" | \"object\" {\n if (value === null) return \"null\";\n if (Array.isArray(value)) return \"array\";\n if (typeof value === \"object\") return \"object\";\n if (typeof value === \"boolean\") return \"boolean\";\n if (typeof value === \"number\") return \"number\";\n return \"string\";\n}\n\nexport function formatNumber(value: number): string {\n if (Math.abs(value) < 0.0001 && value !== 0) return value.toExponential(1);\n if (Math.abs(value) >= 10000) return value.toExponential(1);\n if (Number.isInteger(value)) return value.toString();\n return value.toFixed(3);\n}\n\nexport function formatValue(value: ParameterValue): string {\n if (value === null) return \"null\";\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (typeof value === \"number\") return formatNumber(value);\n if (Array.isArray(value)) return `[${value.length}]`;\n if (typeof value === \"object\") return \"{...}\";\n return String(value);\n}\n\n/**\n * Parse YAML or JSON content based on file extension.\n */\nexport function parseConfigFile(content: string, path: string): Record<string, ParameterValue> | null {\n if (path.endsWith(\".yaml\") || path.endsWith(\".yml\")) {\n try {\n return load(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n if (path.endsWith(\".json\")) {\n try {\n return JSON.parse(content) as Record<string, ParameterValue>;\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\n/**\n * Derive a label from a jq-like keyPath.\n * E.g., \".base.N_E\" → \"N_E\"\n */\nexport function deriveLabelFromPath(keyPath: string): string {\n const cleanPath = keyPath.startsWith(\".\") ? keyPath.slice(1) : keyPath;\n const parts = cleanPath.split(\".\");\n return parts[parts.length - 1].replace(/\\[\\d+\\]/g, \"\");\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.json.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
|
@@ -2,6 +2,7 @@ import { jsxs, jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
3
|
import { PostList } from "../components/post-list.js";
|
|
4
4
|
import { Header } from "../components/header.js";
|
|
5
|
+
import { Footer } from "../components/footer.js";
|
|
5
6
|
import veslxConfig from "virtual:veslx-config";
|
|
6
7
|
function Home() {
|
|
7
8
|
const { "*": path = "." } = useParams();
|
|
@@ -27,7 +28,8 @@ function Home() {
|
|
|
27
28
|
)
|
|
28
29
|
] }),
|
|
29
30
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: /* @__PURE__ */ jsx("div", { className: "animate-fade-in", children: /* @__PURE__ */ jsx(PostList, {}) }) })
|
|
30
|
-
] })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx(Footer, {})
|
|
31
33
|
] })
|
|
32
34
|
] });
|
|
33
35
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { PostList } from \"@/components/post-list\";\nimport { Header } from \"@/components/header\";\nimport veslxConfig from \"virtual:veslx-config\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const config = veslxConfig.site;\n\n const isRoot = path === \".\" || path === \"\";\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-12\">\n {isRoot && (\n <div className=\"animate-fade-in flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n {config.llmsTxt && (\n <a\n href=\"/llms-full.txt\"\n className=\"font-mono text-xs text-muted-foreground/70 hover:text-foreground underline underline-offset-2 transition-colors shrink-0\"\n >\n llms.txt\n </a>\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n <div className=\"animate-fade-in\">\n <PostList />\n </div>\n </div>\n </main>\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"home.js","sources":["../../../src/pages/home.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\"\nimport { PostList } from \"@/components/post-list\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport veslxConfig from \"virtual:veslx-config\";\n\nexport function Home() {\n const { \"*\": path = \".\" } = useParams();\n const config = veslxConfig.site;\n\n const isRoot = path === \".\" || path === \"\";\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <Header />\n <main className=\"flex-1 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]\">\n <title>{isRoot ? config.name : `${config.name} - ${path}`}</title>\n <main className=\"flex flex-col gap-8 mb-32 mt-12\">\n {isRoot && (\n <div className=\"animate-fade-in flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground\">\n {config.name}\n </h1>\n {config.description && (\n <p className=\"mt-2 text-muted-foreground\">\n {config.description}\n </p>\n )}\n </div>\n {config.llmsTxt && (\n <a\n href=\"/llms-full.txt\"\n className=\"font-mono text-xs text-muted-foreground/70 hover:text-foreground underline underline-offset-2 transition-colors shrink-0\"\n >\n llms.txt\n </a>\n )}\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n <div className=\"animate-fade-in\">\n <PostList />\n </div>\n </div>\n </main>\n <Footer />\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;AAMO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,OAAO,IAAA,IAAQ,UAAA;AAC5B,QAAM,SAAS,YAAY;AAE3B,QAAM,SAAS,SAAS,OAAO,SAAS;AAExC,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,+EACd,UAAA;AAAA,MAAA,oBAAC,SAAA,EAAO,mBAAS,OAAO,OAAO,GAAG,OAAO,IAAI,MAAM,IAAI,GAAA,CAAG;AAAA,MAC1D,qBAAC,QAAA,EAAK,WAAU,mCACb,UAAA;AAAA,QAAA,UACC,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,UAAA,qBAAC,OAAA,EACC,UAAA;AAAA,YAAA,oBAAC,MAAA,EAAG,WAAU,qEACX,UAAA,OAAO,MACV;AAAA,YACC,OAAO,eACN,oBAAC,OAAE,WAAU,8BACV,iBAAO,YAAA,CACV;AAAA,UAAA,GAEJ;AAAA,UACC,OAAO,WACN;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACX,UAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QAED,GAEJ;AAAA,QAGF,oBAAC,OAAA,EAAI,WAAU,uBACb,UAAA,oBAAC,OAAA,EAAI,WAAU,mBACb,UAAA,oBAAC,UAAA,CAAA,CAAS,EAAA,CACZ,EAAA,CACF;AAAA,MAAA,GACF;AAAA,0BACC,QAAA,CAAA,CAAO;AAAA,IAAA,EAAA,CACV;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
|
|
|
3
3
|
import { isSimulationRunning } from "../plugin/src/client.js";
|
|
4
4
|
import Loading from "../components/loading.js";
|
|
5
5
|
import { Header } from "../components/header.js";
|
|
6
|
+
import { Footer } from "../components/footer.js";
|
|
6
7
|
import { useIndexContent } from "../hooks/use-mdx-content.js";
|
|
7
8
|
import { mdxComponents } from "../components/mdx-components.js";
|
|
8
9
|
import { FrontmatterProvider } from "../lib/frontmatter-context.js";
|
|
@@ -24,7 +25,8 @@ function IndexPost({ fallback }) {
|
|
|
24
25
|
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
|
|
25
26
|
/* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
|
|
26
27
|
] }) }),
|
|
27
|
-
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "my-12 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) })
|
|
28
|
+
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "my-12 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) }),
|
|
29
|
+
/* @__PURE__ */ jsx(Footer, {})
|
|
28
30
|
] })
|
|
29
31
|
] });
|
|
30
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-post.js","sources":["../../../src/pages/index-post.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { isSimulationRunning } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { Header } from \"@/components/header\";\nimport { useIndexContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\ninterface IndexPostProps {\n fallback: ReactNode;\n}\n\n/**\n * Attempts to render an index.mdx or
|
|
1
|
+
{"version":3,"file":"index-post.js","sources":["../../../src/pages/index-post.tsx"],"sourcesContent":["import { ReactNode } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { isSimulationRunning } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport { useIndexContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\ninterface IndexPostProps {\n fallback: ReactNode;\n}\n\n/**\n * Attempts to render an index.mdx, index.md, README.mdx, or README.md file for a directory.\n * Checks for index files first, then README files.\n * Falls back to the provided component if no matching file exists.\n */\nexport function IndexPost({ fallback }: IndexPostProps) {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // Normalize path for index lookup\n const dirPath = rawPath || \".\";\n\n const { Content, frontmatter, loading, notFound } = useIndexContent(dirPath);\n const isRunning = isSimulationRunning();\n\n if (loading) return <Loading />\n\n // No index file found - render fallback (usually Home)\n if (notFound) {\n return <>{fallback}</>;\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <FrontmatterProvider frontmatter={frontmatter}>\n <article className=\"my-12 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n <Footer />\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAmBO,SAAS,UAAU,EAAE,YAA4B;AACtD,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU,WAAW;AAE3B,QAAM,EAAE,SAAS,aAAa,SAAS,SAAA,IAAa,gBAAgB,OAAO;AAC3E,QAAM,YAAY,oBAAA;AAElB,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAG7B,MAAI,UAAU;AACZ,2CAAU,UAAA,SAAA,CAAS;AAAA,EACrB;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,iCACb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,oBAAC,qBAAA,EAAoB,aACnB,UAAA,oBAAC,WAAA,EAAQ,WAAU,uFACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,GACF;AAAA,0BAED,QAAA,CAAA,CAAO;AAAA,IAAA,EAAA,CACV;AAAA,EAAA,GACF;AAEJ;"}
|
|
@@ -3,6 +3,7 @@ import { useParams } from "react-router-dom";
|
|
|
3
3
|
import { useDirectory, isSimulationRunning, findSlides } from "../plugin/src/client.js";
|
|
4
4
|
import Loading from "../components/loading.js";
|
|
5
5
|
import { Header } from "../components/header.js";
|
|
6
|
+
import { Footer } from "../components/footer.js";
|
|
6
7
|
import { useMDXContent } from "../hooks/use-mdx-content.js";
|
|
7
8
|
import { mdxComponents } from "../components/mdx-components.js";
|
|
8
9
|
import { FrontmatterProvider } from "../lib/frontmatter-context.js";
|
|
@@ -30,7 +31,8 @@ function Post() {
|
|
|
30
31
|
/* @__PURE__ */ jsx("span", { className: "uppercase tracking-widest", children: "simulation running" }),
|
|
31
32
|
/* @__PURE__ */ jsx("span", { className: "text-primary-foreground/60", children: "Page will auto-refresh on completion" })
|
|
32
33
|
] }) }),
|
|
33
|
-
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "mt-12 mb-
|
|
34
|
+
Content && /* @__PURE__ */ jsx(FrontmatterProvider, { frontmatter, children: /* @__PURE__ */ jsx("article", { className: "mt-12 mb-32 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in", children: /* @__PURE__ */ jsx(Content, { components: mdxComponents }) }) }),
|
|
35
|
+
/* @__PURE__ */ jsx(Footer, {})
|
|
34
36
|
] })
|
|
35
37
|
] });
|
|
36
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { Header } from \"@/components/header\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\nexport function Post() {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Extract directory path for finding sibling files (slides, etc.)\n const dirPath = mdxPath.replace(/\\/[^/]+\\.mdx$/, '') || '.';\n\n const { directory, loading: dirLoading } = useDirectory(dirPath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <FrontmatterProvider frontmatter={frontmatter}>\n <article className=\"mt-12 mb-
|
|
1
|
+
{"version":3,"file":"post.js","sources":["../../../src/pages/post.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { findSlides, isSimulationRunning, useDirectory } from \"../../plugin/src/client\";\nimport Loading from \"@/components/loading\";\nimport { FileEntry } from \"plugin/src/lib\";\nimport { Header } from \"@/components/header\";\nimport { Footer } from \"@/components/footer\";\nimport { useMDXContent } from \"@/hooks/use-mdx-content\";\nimport { mdxComponents } from \"@/components/mdx-components\";\nimport { FrontmatterProvider } from \"@/lib/frontmatter-context\";\n\nexport function Post() {\n const { \"*\": rawPath = \".\" } = useParams();\n\n // The path includes the .mdx extension from the route\n const mdxPath = rawPath;\n\n // Extract directory path for finding sibling files (slides, etc.)\n const dirPath = mdxPath.replace(/\\/[^/]+\\.mdx$/, '') || '.';\n\n const { directory, loading: dirLoading } = useDirectory(dirPath)\n const { Content, frontmatter, loading: mdxLoading, error } = useMDXContent(mdxPath);\n const isRunning = isSimulationRunning();\n\n let slides: FileEntry | null = null;\n if (directory) {\n slides = findSlides(directory);\n }\n\n const loading = dirLoading || mdxLoading;\n\n if (loading) return <Loading />\n\n if (error) {\n return (\n <main className=\"min-h-screen bg-background container mx-auto max-w-4xl py-12\">\n <p className=\"text-center text-red-600\">{error.message}</p>\n </main>\n )\n }\n\n return (\n <div className=\"flex min-h-screen flex-col bg-background noise-overlay\">\n <title>{frontmatter?.title}</title>\n <Header />\n <main className=\"flex-1 w-full overflow-x-clip\">\n {isRunning && (\n <div className=\"sticky top-0 z-50 px-[var(--page-padding)] py-2 bg-red-500 text-primary-foreground font-mono text-xs text-center tracking-wide\">\n <span className=\"inline-flex items-center gap-3\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current animate-pulse\" />\n <span className=\"uppercase tracking-widest\">simulation running</span>\n <span className=\"text-primary-foreground/60\">Page will auto-refresh on completion</span>\n </span>\n </div>\n )}\n\n {Content && (\n <FrontmatterProvider frontmatter={frontmatter}>\n <article className=\"mt-12 mb-32 mx-auto px-[var(--page-padding)] max-w-[var(--content-width)] animate-fade-in\">\n <Content components={mdxComponents} />\n </article>\n </FrontmatterProvider>\n )}\n <Footer />\n </main>\n </div>\n )\n}\n"],"names":[],"mappings":";;;;;;;;;AAUO,SAAS,OAAO;AACrB,QAAM,EAAE,KAAK,UAAU,IAAA,IAAQ,UAAA;AAG/B,QAAM,UAAU;AAGhB,QAAM,UAAU,QAAQ,QAAQ,iBAAiB,EAAE,KAAK;AAExD,QAAM,EAAE,UAA+B,IAAI,aAAa,OAAO;AAC/D,QAAM,EAAE,SAAS,aAAa,SAAS,YAAY,MAAA,IAAU,cAAc,OAAO;AAClF,QAAM,YAAY,oBAAA;AAGlB,MAAI,WAAW;AACJ,eAAW,SAAS;AAAA,EAC/B;AAEA,QAAM,UAAwB;AAE9B,MAAI,QAAS,QAAO,oBAAC,SAAA,CAAA,CAAQ;AAE7B,MAAI,OAAO;AACT,WACE,oBAAC,QAAA,EAAK,WAAU,gEACd,UAAA,oBAAC,OAAE,WAAU,4BAA4B,UAAA,MAAM,QAAA,CAAQ,GACzD;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAA,EAAI,WAAU,0DACb,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAO,qDAAa,MAAA,CAAM;AAAA,wBAC1B,QAAA,EAAO;AAAA,IACR,qBAAC,QAAA,EAAK,WAAU,iCACb,UAAA;AAAA,MAAA,iCACE,OAAA,EAAI,WAAU,kIACb,UAAA,qBAAC,QAAA,EAAK,WAAU,kCACd,UAAA;AAAA,QAAA,oBAAC,QAAA,EAAK,WAAU,oDAAA,CAAoD;AAAA,QACpE,oBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA,sBAAkB;AAAA,QAC9D,oBAAC,QAAA,EAAK,WAAU,8BAA6B,UAAA,uCAAA,CAAoC;AAAA,MAAA,EAAA,CACnF,EAAA,CACF;AAAA,MAGD,WACC,oBAAC,qBAAA,EAAoB,aACnB,UAAA,oBAAC,WAAA,EAAQ,WAAU,6FACjB,UAAA,oBAAC,SAAA,EAAQ,YAAY,cAAA,CAAe,GACtC,GACF;AAAA,0BAED,QAAA,CAAA,CAAO;AAAA,IAAA,EAAA,CACV;AAAA,EAAA,GACF;AAEJ;"}
|