veslx 0.1.63 → 0.1.64
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/dist/bin/veslx.js +0 -0
- package/dist/client/components/parameter-table.js +15 -4
- package/dist/client/components/parameter-table.js.map +1 -1
- package/dist/client/components/post-list.js +7 -2
- package/dist/client/components/post-list.js.map +1 -1
- package/package.json +1 -1
- package/src/components/parameter-table.tsx +23 -5
- package/src/components/post-list.tsx +10 -2
package/dist/bin/veslx.js
CHANGED
|
File without changes
|
|
@@ -51,7 +51,14 @@ function sortPathsNumerically(paths) {
|
|
|
51
51
|
return a.localeCompare(b);
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
function SingleParameterTable({
|
|
54
|
+
function SingleParameterTable({
|
|
55
|
+
path,
|
|
56
|
+
pairs,
|
|
57
|
+
label,
|
|
58
|
+
withMargin = true,
|
|
59
|
+
scrollable = true,
|
|
60
|
+
compact = false
|
|
61
|
+
}) {
|
|
55
62
|
const { content, loading, error } = useFileContent(path);
|
|
56
63
|
if (!pairs || pairs.length === 0) {
|
|
57
64
|
return /* @__PURE__ */ jsx("div", { className: cn("p-3 rounded border border-border/50 bg-card/30", withMargin && "my-6"), children: /* @__PURE__ */ jsx("p", { className: "text-[11px] font-mono text-muted-foreground", children: "pairs is required" }) });
|
|
@@ -89,7 +96,10 @@ function SingleParameterTable({ path, pairs, label, withMargin = true, compact =
|
|
|
89
96
|
] }) });
|
|
90
97
|
}
|
|
91
98
|
const renderPairsTable = () => {
|
|
92
|
-
return /* @__PURE__ */ jsx("div", { className:
|
|
99
|
+
return /* @__PURE__ */ jsx("div", { className: cn(
|
|
100
|
+
"border border-border rounded-md",
|
|
101
|
+
scrollable ? "overflow-x-auto" : "overflow-x-hidden"
|
|
102
|
+
), children: /* @__PURE__ */ jsxs("table", { className: cn(
|
|
93
103
|
"w-full border-collapse",
|
|
94
104
|
compact ? "text-[11px]" : "text-sm"
|
|
95
105
|
), children: [
|
|
@@ -187,14 +197,15 @@ function ParameterTable({ path, pairs, compact = false }) {
|
|
|
187
197
|
const scrollRef = useRef(null);
|
|
188
198
|
usePreventSwipeNavigation(scrollRef);
|
|
189
199
|
const count = matchingPaths.length;
|
|
190
|
-
const breakoutClass = count >= 4 ? "w-[96vw]
|
|
191
|
-
return /* @__PURE__ */ jsx("div", { className: `my-6 ${breakoutClass}`, children: /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-4 overflow-x-auto overscroll-x-contain pb-2", children: matchingPaths.map((filePath) => /* @__PURE__ */ jsx("div", { className: "flex-none w-[280px]", children: /* @__PURE__ */ jsx(
|
|
200
|
+
const breakoutClass = count >= 4 ? "relative left-1/2 w-[96vw] -translate-x-1/2" : count >= 2 ? "relative left-1/2 w-[75vw] -translate-x-1/2" : "";
|
|
201
|
+
return /* @__PURE__ */ jsx("div", { className: `my-6 ${breakoutClass} overflow-x-hidden`, children: /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex gap-4 overflow-x-auto overscroll-x-contain pb-2", children: matchingPaths.map((filePath) => /* @__PURE__ */ jsx("div", { className: "flex-none w-[280px]", children: /* @__PURE__ */ jsx(
|
|
192
202
|
SingleParameterTable,
|
|
193
203
|
{
|
|
194
204
|
path: filePath,
|
|
195
205
|
pairs,
|
|
196
206
|
label: filePath.split("/").pop() || filePath,
|
|
197
207
|
withMargin: false,
|
|
208
|
+
scrollable: false,
|
|
198
209
|
compact
|
|
199
210
|
}
|
|
200
211
|
) }, filePath)) }) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parameter-table.js","sources":["../../../src/components/parameter-table.tsx"],"sourcesContent":["import { useFileContent, useDirectory } from \"../../plugin/src/client\";\nimport { useMemo, 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\ninterface SingleParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Required array of key/label pairs to render as a markdown-style table.\n * Each key is a jq-like path (e.g., \".base.dt\").\n */\n pairs: Array<{ key: string; label?: string }>;\n /** Optional label to show above the table */\n label?: string;\n /** Whether to include vertical margin (default true) */\n withMargin?: boolean;\n /** Compact mode for dense rendering */\n compact?: boolean;\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file, supports glob patterns like \"*.yaml\" */\n path: string;\n /**\n * Required array of key/label pairs to render Complete table.\n */\n pairs: Array<{ key: string; label?: string }>;\n /** Compact mode for dense rendering */\n compact?: boolean;\n}\n\nfunction SingleParameterTable({ path, pairs, label, withMargin = true, compact = false }: SingleParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n if (!pairs || pairs.length === 0) {\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 pairs is required\n </p>\n </div>\n );\n }\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 return { parsed: data, parseError: null };\n }, [content, path]);\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 renderPairsTable = () => {\n return (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className={cn(\n \"w-full border-collapse\",\n compact ? \"text-[11px]\" : \"text-sm\"\n )}>\n <thead className=\"bg-muted/50\">\n <tr className=\"border-b border-border last:border-b-0\">\n <th className={cn(\n \"text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>\n Parameter\n </th>\n <th className={cn(\n \"text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>\n Value\n </th>\n </tr>\n </thead>\n <tbody>\n {pairs.map(({ key, label: rowLabel }) => {\n const value = extractPath(parsed, key);\n const type = value === undefined ? \"missing\" : getValueType(value);\n const displayValue = value === undefined\n ? \"—\"\n : type === \"string\"\n ? `\"${formatValue(value)}\"`\n : formatValue(value);\n\n return (\n <tr key={key} className=\"border-b border-border last:border-b-0\">\n <td className={cn(\n \"align-top\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>{rowLabel || key}</td>\n <td\n className={cn(\n \"align-top\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\",\n type === \"missing\" && \"text-muted-foreground\"\n )}\n >\n {displayValue}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\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 {renderPairsTable()}\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, pairs, compact = false }: ParameterTableProps) {\n const { \"*\": routePath = \"\" } = useParams();\n\n if (!pairs || pairs.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\">pairs is required</p>\n </div>\n );\n }\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 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} pairs={pairs} compact={compact} />;\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 pairs={pairs}\n label={filePath.split('/').pop() || filePath}\n withMargin={false}\n compact={compact}\n />\n </div>\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, 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\ninterface SingleParameterTableProps {\n /** Path to the YAML or JSON file */\n path: string;\n /**\n * Required array of key/label pairs to render as a markdown-style table.\n * Each key is a jq-like path (e.g., \".base.dt\").\n */\n pairs: Array<{ key: string; label?: 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 * Whether this table should manage its own horizontal scrolling.\n * Set false when the parent already provides a single shared scrollbar.\n */\n scrollable?: boolean;\n /** Compact mode for dense rendering */\n compact?: boolean;\n}\n\ninterface ParameterTableProps {\n /** Path to the YAML or JSON file, supports glob patterns like \"*.yaml\" */\n path: string;\n /**\n * Required array of key/label pairs to render Complete table.\n */\n pairs: Array<{ key: string; label?: string }>;\n /** Compact mode for dense rendering */\n compact?: boolean;\n}\n\nfunction SingleParameterTable({\n path,\n pairs,\n label,\n withMargin = true,\n scrollable = true,\n compact = false,\n}: SingleParameterTableProps) {\n const { content, loading, error } = useFileContent(path);\n\n if (!pairs || pairs.length === 0) {\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 pairs is required\n </p>\n </div>\n );\n }\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 return { parsed: data, parseError: null };\n }, [content, path]);\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 renderPairsTable = () => {\n return (\n <div className={cn(\n \"border border-border rounded-md\",\n scrollable ? \"overflow-x-auto\" : \"overflow-x-hidden\"\n )}>\n <table className={cn(\n \"w-full border-collapse\",\n compact ? \"text-[11px]\" : \"text-sm\"\n )}>\n <thead className=\"bg-muted/50\">\n <tr className=\"border-b border-border last:border-b-0\">\n <th className={cn(\n \"text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>\n Parameter\n </th>\n <th className={cn(\n \"text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>\n Value\n </th>\n </tr>\n </thead>\n <tbody>\n {pairs.map(({ key, label: rowLabel }) => {\n const value = extractPath(parsed, key);\n const type = value === undefined ? \"missing\" : getValueType(value);\n const displayValue = value === undefined\n ? \"—\"\n : type === \"string\"\n ? `\"${formatValue(value)}\"`\n : formatValue(value);\n\n return (\n <tr key={key} className=\"border-b border-border last:border-b-0\">\n <td className={cn(\n \"align-top\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\"\n )}>{rowLabel || key}</td>\n <td\n className={cn(\n \"align-top\",\n compact ? \"px-3 py-2\" : \"px-4 py-3\",\n type === \"missing\" && \"text-muted-foreground\"\n )}\n >\n {displayValue}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\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 {renderPairsTable()}\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, pairs, compact = false }: ParameterTableProps) {\n const { \"*\": routePath = \"\" } = useParams();\n\n if (!pairs || pairs.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\">pairs is required</p>\n </div>\n );\n }\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 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} pairs={pairs} compact={compact} />;\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 // Use translate centering (instead of negative margins) to avoid creating a tiny\n // page-level horizontal scrollbar while still \"breaking out\" of prose width.\n const breakoutClass = count >= 4\n ? 'relative left-1/2 w-[96vw] -translate-x-1/2'\n : count >= 2\n ? 'relative left-1/2 w-[75vw] -translate-x-1/2'\n : '';\n\n return (\n <div className={`my-6 ${breakoutClass} overflow-x-hidden`}>\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 pairs={pairs}\n label={filePath.split('/').pop() || filePath}\n withMargin={false}\n scrollable={false}\n compact={compact}\n />\n </div>\n ))}\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -9,13 +9,18 @@ import veslxConfig from "virtual:veslx-config";
|
|
|
9
9
|
function formatName(name) {
|
|
10
10
|
return name.replace(/^\d+-/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
11
11
|
}
|
|
12
|
+
function filePathToRoutePath(filePath) {
|
|
13
|
+
const normalized = filePath.replace(/^\/+/, "");
|
|
14
|
+
const stripped = normalized.replace(/(?:^|\/)(?:index|readme)\.mdx?$/i, "");
|
|
15
|
+
return stripped ? `/${stripped}` : "/";
|
|
16
|
+
}
|
|
12
17
|
function getLinkPath(post) {
|
|
13
18
|
if (post.file) {
|
|
14
|
-
return
|
|
19
|
+
return filePathToRoutePath(post.file.path);
|
|
15
20
|
} else if (post.slides && !post.readme) {
|
|
16
21
|
return `/${post.slides.path}`;
|
|
17
22
|
} else if (post.readme) {
|
|
18
|
-
return
|
|
23
|
+
return filePathToRoutePath(post.readme.path);
|
|
19
24
|
} else {
|
|
20
25
|
return `/${post.path}`;
|
|
21
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return
|
|
1
|
+
{"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\nfunction filePathToRoutePath(filePath: string): string {\n // Routes without an extension are treated as directories; ContentRouter will\n // render index/readme files automatically via IndexPost.\n const normalized = filePath.replace(/^\\/+/, '');\n const stripped = normalized.replace(/(?:^|\\/)(?:index|readme)\\.mdx?$/i, '');\n return stripped ? `/${stripped}` : '/';\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return filePathToRoutePath(post.file.path);\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return filePathToRoutePath(post.readme.path);\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nfunction isRouterPath(href: string): boolean {\n return href.startsWith(\"/\") && !href.startsWith(\"//\");\n}\n\nexport function PostList({ globs = null }: PostListProps) {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Filter by glob patterns if provided\n if (globs && globs.length > 0) {\n posts = posts.filter(post =>\n globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))\n );\n }\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const internalLinkPath = getLinkPath(post);\n const normalizedLink = internalLinkPath.toLowerCase();\n const isSlides =\n normalizedLink.endsWith('/slides.mdx') ||\n normalizedLink.endsWith('/slides.md') ||\n normalizedLink.endsWith('.slides.mdx') ||\n normalizedLink.endsWith('.slides.md');\n\n const frontmatterLink =\n typeof frontmatter?.link === \"string\" ? frontmatter.link.trim() : \"\";\n const href = frontmatterLink || internalLinkPath;\n const external = !isRouterPath(href);\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n href={href}\n external={external}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -84,6 +84,11 @@ interface SingleParameterTableProps {
|
|
|
84
84
|
label?: string;
|
|
85
85
|
/** Whether to include vertical margin (default true) */
|
|
86
86
|
withMargin?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Whether this table should manage its own horizontal scrolling.
|
|
89
|
+
* Set false when the parent already provides a single shared scrollbar.
|
|
90
|
+
*/
|
|
91
|
+
scrollable?: boolean;
|
|
87
92
|
/** Compact mode for dense rendering */
|
|
88
93
|
compact?: boolean;
|
|
89
94
|
}
|
|
@@ -99,7 +104,14 @@ interface ParameterTableProps {
|
|
|
99
104
|
compact?: boolean;
|
|
100
105
|
}
|
|
101
106
|
|
|
102
|
-
function SingleParameterTable({
|
|
107
|
+
function SingleParameterTable({
|
|
108
|
+
path,
|
|
109
|
+
pairs,
|
|
110
|
+
label,
|
|
111
|
+
withMargin = true,
|
|
112
|
+
scrollable = true,
|
|
113
|
+
compact = false,
|
|
114
|
+
}: SingleParameterTableProps) {
|
|
103
115
|
const { content, loading, error } = useFileContent(path);
|
|
104
116
|
|
|
105
117
|
if (!pairs || pairs.length === 0) {
|
|
@@ -163,7 +175,10 @@ function SingleParameterTable({ path, pairs, label, withMargin = true, compact =
|
|
|
163
175
|
|
|
164
176
|
const renderPairsTable = () => {
|
|
165
177
|
return (
|
|
166
|
-
<div className=
|
|
178
|
+
<div className={cn(
|
|
179
|
+
"border border-border rounded-md",
|
|
180
|
+
scrollable ? "overflow-x-auto" : "overflow-x-hidden"
|
|
181
|
+
)}>
|
|
167
182
|
<table className={cn(
|
|
168
183
|
"w-full border-collapse",
|
|
169
184
|
compact ? "text-[11px]" : "text-sm"
|
|
@@ -325,14 +340,16 @@ export function ParameterTable({ path, pairs, compact = false }: ParameterTableP
|
|
|
325
340
|
|
|
326
341
|
// Breakout width based on count
|
|
327
342
|
const count = matchingPaths.length;
|
|
343
|
+
// Use translate centering (instead of negative margins) to avoid creating a tiny
|
|
344
|
+
// page-level horizontal scrollbar while still "breaking out" of prose width.
|
|
328
345
|
const breakoutClass = count >= 4
|
|
329
|
-
? 'w-[96vw]
|
|
346
|
+
? 'relative left-1/2 w-[96vw] -translate-x-1/2'
|
|
330
347
|
: count >= 2
|
|
331
|
-
? 'w-[75vw]
|
|
348
|
+
? 'relative left-1/2 w-[75vw] -translate-x-1/2'
|
|
332
349
|
: '';
|
|
333
350
|
|
|
334
351
|
return (
|
|
335
|
-
<div className={`my-6 ${breakoutClass}`}>
|
|
352
|
+
<div className={`my-6 ${breakoutClass} overflow-x-hidden`}>
|
|
336
353
|
<div ref={scrollRef} className="flex gap-4 overflow-x-auto overscroll-x-contain pb-2">
|
|
337
354
|
{matchingPaths.map((filePath) => (
|
|
338
355
|
<div key={filePath} className="flex-none w-[280px]">
|
|
@@ -341,6 +358,7 @@ export function ParameterTable({ path, pairs, compact = false }: ParameterTableP
|
|
|
341
358
|
pairs={pairs}
|
|
342
359
|
label={filePath.split('/').pop() || filePath}
|
|
343
360
|
withMargin={false}
|
|
361
|
+
scrollable={false}
|
|
344
362
|
compact={compact}
|
|
345
363
|
/>
|
|
346
364
|
</div>
|
|
@@ -25,17 +25,25 @@ function formatName(name: string): string {
|
|
|
25
25
|
.replace(/\b\w/g, c => c.toUpperCase());
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
function filePathToRoutePath(filePath: string): string {
|
|
29
|
+
// Routes without an extension are treated as directories; ContentRouter will
|
|
30
|
+
// render index/readme files automatically via IndexPost.
|
|
31
|
+
const normalized = filePath.replace(/^\/+/, '');
|
|
32
|
+
const stripped = normalized.replace(/(?:^|\/)(?:index|readme)\.mdx?$/i, '');
|
|
33
|
+
return stripped ? `/${stripped}` : '/';
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
// Helper to get link path from post
|
|
29
37
|
function getLinkPath(post: PostEntry): string {
|
|
30
38
|
if (post.file) {
|
|
31
39
|
// Standalone MDX file
|
|
32
|
-
return
|
|
40
|
+
return filePathToRoutePath(post.file.path);
|
|
33
41
|
} else if (post.slides && !post.readme) {
|
|
34
42
|
// Folder with only slides
|
|
35
43
|
return `/${post.slides.path}`;
|
|
36
44
|
} else if (post.readme) {
|
|
37
45
|
// Folder with readme
|
|
38
|
-
return
|
|
46
|
+
return filePathToRoutePath(post.readme.path);
|
|
39
47
|
} else {
|
|
40
48
|
// Fallback to folder path
|
|
41
49
|
return `/${post.path}`;
|