veslx 0.1.36 → 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 +0 -13
- package/dist/client/components/footer.js +21 -0
- package/dist/client/components/footer.js.map +1 -0
- 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 +14 -7
- 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 +67 -39
- package/src/components/footer.tsx +18 -0
- package/src/components/mdx-components.tsx +3 -0
- package/src/components/parameter-table.tsx +33 -25
- 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":"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":";AAuBA,SAAS,cAAc,SAAoB,MAAmC;AAC5E,QAAM,OAAO,OAAO,KAAK,OAAO;AAGhC,QAAM,iBAAiB,KAAK,QAAQ,OAAO,EAAE;AAG7C,MAAI,eAAe,SAAS,MAAM,KAAK,eAAe,SAAS,KAAK,GAAG;AAErE,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AAEnC,UAAI,IAAI,SAAS,IAAI,cAAc,EAAE,EAAG,QAAO;AAE/C,UAAI,QAAQ,aAAa,cAAc,GAAI,QAAO;AAElD,UAAI,QAAQ,YAAY,cAAc,GAAI,QAAO;AAEjD,UAAI,QAAQ,eAAgB,QAAO;AAEnC,UAAI,QAAQ,IAAI,cAAc,GAAI,QAAO;AACzC,aAAO;AAAA,IACT,CAAC;AACD,WAAO,cAAc,QAAQ,WAAW,IAAI;AAAA,EAC9C;AAOA,QAAM,aAAa;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,EAAA;AAGnB,aAAW,aAAa,YAAY;AAClC,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AACnC,UAAI,IAAI,SAAS,IAAI,SAAS,EAAE,EAAG,QAAO;AAC1C,UAAI,QAAQ,aAAa,SAAS,GAAI,QAAO;AAC7C,UAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAC5C,UAAI,QAAQ,UAAW,QAAO;AAC9B,aAAO;AAAA,IACT,CAAC;AACD,QAAI,aAAa;AACf,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,cAAc,MAAc;AAC1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,cAAc;AACrB,YAAM,SAAS,cAAc,SAAsB,IAAI;AAEvD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,kCAAkC,IAAI,EAAE;AAAA,MAC1D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,SAAS,MAAA;AAC1C;AAOA,SAAS,iBAAiB,SAAoB,MAAmC;AAC/E,QAAM,OAAO,OAAO,KAAK,OAAO;AAGhC,QAAM,iBAAiB,KAAK,QAAQ,OAAO,EAAE;AAG7C,MAAI,eAAe,SAAS,MAAM,KAAK,eAAe,SAAS,KAAK,GAAG;AAErE,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AAEnC,UAAI,IAAI,SAAS,IAAI,cAAc,EAAE,EAAG,QAAO;AAE/C,UAAI,QAAQ,aAAa,cAAc,GAAI,QAAO;AAElD,UAAI,QAAQ,YAAY,cAAc,GAAI,QAAO;AAEjD,UAAI,QAAQ,eAAgB,QAAO;AAEnC,UAAI,QAAQ,IAAI,cAAc,GAAI,QAAO;AACzC,aAAO;AAAA,IACT,CAAC;AACD,WAAO,cAAc,QAAQ,WAAW,IAAI;AAAA,EAC9C;AAMA,QAAM,aAAa;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,EAAA;AAGnB,aAAW,aAAa,YAAY;AAClC,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AACnC,UAAI,IAAI,SAAS,IAAI,SAAS,EAAE,EAAG,QAAO;AAC1C,UAAI,QAAQ,aAAa,SAAS,GAAI,QAAO;AAC7C,UAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAC5C,UAAI,QAAQ,UAAW,QAAO;AAC9B,aAAO;AAAA,IACT,CAAC;AACD,QAAI,aAAa;AACf,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,aAAa,MAAc;AACzC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,YAAY,aAAa,IAAI,SAA6B,MAAS;AAC1E,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAuB,IAAI;AAErD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,aAAS,IAAI;AAGb,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,aAAa;AACpB,YAAM,SAAS,iBAAiB,QAAqB,IAAI;AAEzD,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,qCAAqC,IAAI,EAAE;AAAA,MAC7D;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,CAAC,WAAW;AACd,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,sBAAc,IAAI,UAAU;AAC5B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,UAAI,CAAC,WAAW;AACd,iBAAS,GAAG;AACZ,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,YAAY,SAAS,MAAA;AACtD;AAMA,SAAS,gBAAgB,SAAoB,MAAmC;AAC9E,QAAM,OAAO,OAAO,KAAK,OAAO;AAGhC,QAAM,iBAAiB,KAAK,QAAQ,OAAO,EAAE,KAAK;AAClD,QAAM,SAAS,mBAAmB,OAAO,mBAAmB;AAG5D,UAAQ,IAAI,2BAA2B,MAAM,mBAAmB,gBAAgB,WAAW,MAAM;AACjG,UAAQ,IAAI,+BAA+B,IAAI;AAG/C,QAAM,aAAa,SACf,CAAC,aAAa,YAAY,cAAc,WAAW,IACnD;AAAA,IACE,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,IACjB,GAAG,cAAc;AAAA,EAAA;AAGvB,aAAW,aAAa,YAAY;AAClC,UAAM,cAAc,KAAK,KAAK,CAAA,QAAO;AAGnC,UAAI,QAAQ;AAGV,cAAM,cAAc,IAAI,MAAM,GAAG,EAAE,IAAA;AACnC,YAAI,gBAAgB,WAAW;AAI7B,gBAAM,QAAQ,IAAI,MAAM,GAAG;AAC3B,gBAAM,aAAa,MAAM,UAAU,OAAK,MAAM,aAAa,MAAM,UAAU;AAC3E,cAAI,eAAe,IAAI;AAErB,kBAAM,eAAe,MAAM,MAAM,aAAa,CAAC;AAC/C,gBAAI,aAAa,WAAW,EAAG,QAAO;AAAA,UACxC;AAEA,cAAI,QAAQ,aAAa,SAAS,GAAI,QAAO;AAC7C,cAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAAA,QAC9C;AACA,eAAO;AAAA,MACT;AAEA,UAAI,IAAI,SAAS,IAAI,SAAS,EAAE,EAAG,QAAO;AAC1C,UAAI,QAAQ,aAAa,SAAS,GAAI,QAAO;AAC7C,UAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAC5C,UAAI,QAAQ,YAAY,SAAS,GAAI,QAAO;AAC5C,UAAI,QAAQ,UAAW,QAAO;AAC9B,aAAO;AAAA,IACT,CAAC;AACD,QAAI,aAAa;AACf,aAAO,QAAQ,WAAW;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AACT;AAOO,SAAS,gBAAgB,MAAc;AAC5C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAsC,IAAI;AACxE,QAAM,CAAC,aAAa,cAAc,IAAI,SAAmC,MAAS;AAClF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAE9C,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,eAAW,IAAI;AACf,gBAAY,KAAK;AAEjB,WAAO,yBAAyB,EAC7B,KAAK,CAAC,EAAE,cAAc;AACrB,YAAM,SAAS,gBAAgB,SAAsB,IAAI;AAEzD,UAAI,CAAC,QAAQ;AAEX,YAAI,CAAC,WAAW;AACd,sBAAY,IAAI;AAChB,qBAAW,KAAK;AAAA,QAClB;AACA,eAAO;AAAA,MACT;AAEA,aAAO,OAAA;AAAA,IACT,CAAC,EACA,KAAK,CAAC,QAAQ;AACb,UAAI,OAAO,CAAC,WAAW;AACrB,mBAAW,MAAM,IAAI,OAAO;AAC5B,uBAAe,IAAI,WAAW;AAC9B,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,MAAM;AAEX,UAAI,CAAC,WAAW;AACd,oBAAY,IAAI;AAChB,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,SAAS,aAAa,SAAS,SAAA;AAC1C;"}
|
|
@@ -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;"}
|
package/package.json
CHANGED
package/plugin/src/plugin.ts
CHANGED
|
@@ -310,9 +310,6 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
310
310
|
next()
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
-
// Virtual module ID for the modified CSS
|
|
314
|
-
const VIRTUAL_CSS_MODULE = '\0veslx:index.css'
|
|
315
|
-
|
|
316
313
|
return {
|
|
317
314
|
name: 'content',
|
|
318
315
|
enforce: 'pre',
|
|
@@ -336,17 +333,8 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
336
333
|
}
|
|
337
334
|
},
|
|
338
335
|
|
|
339
|
-
// Intercept
|
|
340
|
-
resolveId(id
|
|
341
|
-
// Intercept index.css imported from main.tsx and redirect to our virtual module
|
|
342
|
-
// This allows us to inject @source directive for Tailwind to scan user content
|
|
343
|
-
if (id === './index.css' && importer?.endsWith('/src/main.tsx')) {
|
|
344
|
-
return VIRTUAL_CSS_MODULE
|
|
345
|
-
}
|
|
346
|
-
// Also catch the resolved path
|
|
347
|
-
if (id.endsWith('/src/index.css') && !id.startsWith('\0')) {
|
|
348
|
-
return VIRTUAL_CSS_MODULE
|
|
349
|
-
}
|
|
336
|
+
// Intercept virtual module imports
|
|
337
|
+
resolveId(id) {
|
|
350
338
|
// Virtual modules for content
|
|
351
339
|
if (id === VIRTUAL_MODULE_ID) {
|
|
352
340
|
return RESOLVED_VIRTUAL_MODULE_ID
|
|
@@ -356,28 +344,25 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
356
344
|
}
|
|
357
345
|
},
|
|
358
346
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (id
|
|
363
|
-
// Read the original CSS
|
|
364
|
-
const veslxRoot = path.dirname(path.dirname(__dirname))
|
|
365
|
-
const cssPath = path.join(veslxRoot, 'src/index.css')
|
|
366
|
-
const cssContent = fs.readFileSync(cssPath, 'utf-8')
|
|
367
|
-
|
|
347
|
+
// Transform CSS to inject @source directive for Tailwind
|
|
348
|
+
// This enables Tailwind v4 to scan the user's content directory for classes
|
|
349
|
+
transform(code, id) {
|
|
350
|
+
if (id.endsWith('/src/index.css') && code.includes("@import 'tailwindcss'")) {
|
|
368
351
|
// Use absolute path for @source directive
|
|
369
352
|
const absoluteContentDir = dir.replace(/\\/g, '/')
|
|
353
|
+
const sourceDirective = `@source "${absoluteContentDir}";`
|
|
370
354
|
|
|
371
355
|
// Inject @source directive after the tailwindcss import
|
|
372
|
-
const
|
|
373
|
-
const modified = cssContent.replace(
|
|
356
|
+
const modified = code.replace(
|
|
374
357
|
/(@import\s+["']tailwindcss["'];?)/,
|
|
375
358
|
`$1\n${sourceDirective}`
|
|
376
359
|
)
|
|
377
360
|
|
|
378
|
-
return modified
|
|
361
|
+
return { code: modified, map: null }
|
|
379
362
|
}
|
|
363
|
+
},
|
|
380
364
|
|
|
365
|
+
load(id) {
|
|
381
366
|
// Virtual module for content
|
|
382
367
|
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
383
368
|
// Extract frontmatter from MDX files at build time (avoids MDX hook issues)
|
|
@@ -385,15 +370,32 @@ export default function contentPlugin(contentDir: string, config?: VeslxConfig,
|
|
|
385
370
|
|
|
386
371
|
// Generate virtual module with import.meta.glob for MDX files
|
|
387
372
|
return `
|
|
388
|
-
export const posts = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md'], {
|
|
373
|
+
export const posts = import.meta.glob(['@content/*.mdx', '@content/*.md', '@content/**/*.mdx', '@content/**/*.md'], {
|
|
389
374
|
import: 'default',
|
|
390
375
|
query: { skipSlides: true }
|
|
391
376
|
});
|
|
392
|
-
export const allMdx = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md']);
|
|
393
|
-
export const slides = import.meta.glob(['@content/**/SLIDES.mdx', '@content/**/SLIDES.md', '@content/**/*.slides.mdx', '@content/**/*.slides.md']);
|
|
377
|
+
export const allMdx = import.meta.glob(['@content/*.mdx', '@content/*.md', '@content/**/*.mdx', '@content/**/*.md']);
|
|
378
|
+
export const slides = import.meta.glob(['@content/SLIDES.mdx', '@content/SLIDES.md', '@content/*.slides.mdx', '@content/*.slides.md', '@content/**/SLIDES.mdx', '@content/**/SLIDES.md', '@content/**/*.slides.mdx', '@content/**/*.slides.md']);
|
|
394
379
|
|
|
395
|
-
// All files for directory tree building
|
|
380
|
+
// All files for directory tree building (using ?url to avoid parsing non-JS files)
|
|
381
|
+
// Exclude veslx.yaml config files from bundling
|
|
396
382
|
export const files = import.meta.glob([
|
|
383
|
+
'@content/*.mdx',
|
|
384
|
+
'@content/*.md',
|
|
385
|
+
'@content/*.tsx',
|
|
386
|
+
'@content/*.ts',
|
|
387
|
+
'@content/*.jsx',
|
|
388
|
+
'@content/*.js',
|
|
389
|
+
'@content/*.png',
|
|
390
|
+
'@content/*.jpg',
|
|
391
|
+
'@content/*.jpeg',
|
|
392
|
+
'@content/*.gif',
|
|
393
|
+
'@content/*.svg',
|
|
394
|
+
'@content/*.webp',
|
|
395
|
+
'@content/*.css',
|
|
396
|
+
'@content/*.yaml',
|
|
397
|
+
'@content/*.yml',
|
|
398
|
+
'@content/*.json',
|
|
397
399
|
'@content/**/*.mdx',
|
|
398
400
|
'@content/**/*.md',
|
|
399
401
|
'@content/**/*.tsx',
|
|
@@ -410,13 +412,15 @@ export const files = import.meta.glob([
|
|
|
410
412
|
'@content/**/*.yaml',
|
|
411
413
|
'@content/**/*.yml',
|
|
412
414
|
'@content/**/*.json',
|
|
413
|
-
|
|
415
|
+
'!@content/veslx.yaml',
|
|
416
|
+
'!@content/**/veslx.yaml',
|
|
417
|
+
], { eager: false, query: '?url', import: 'default' });
|
|
414
418
|
|
|
415
419
|
// Frontmatter extracted at build time (no MDX execution required)
|
|
416
420
|
export const frontmatters = ${JSON.stringify(frontmatterData)};
|
|
417
421
|
|
|
418
422
|
// Legacy aliases for backwards compatibility
|
|
419
|
-
export const modules = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md']);
|
|
423
|
+
export const modules = import.meta.glob(['@content/*.mdx', '@content/*.md', '@content/**/*.mdx', '@content/**/*.md']);
|
|
420
424
|
`
|
|
421
425
|
}
|
|
422
426
|
if (id === RESOLVED_VIRTUAL_CONFIG_ID) {
|
|
@@ -441,15 +445,21 @@ export const modules = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md'
|
|
|
441
445
|
// File extensions that should trigger a full reload
|
|
442
446
|
const watchedExtensions = ['.mdx', '.md', '.yaml', '.yml', '.json', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.tsx', '.ts', '.jsx', '.js', '.css']
|
|
443
447
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
448
|
+
// Debounce reload to avoid rapid-fire refreshes when multiple files change
|
|
449
|
+
let reloadTimeout: ReturnType<typeof setTimeout> | null = null
|
|
450
|
+
const pendingChanges: Array<{ filePath: string; event: 'add' | 'unlink' | 'change' }> = []
|
|
451
|
+
const DEBOUNCE_MS = 1000
|
|
447
452
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (!watchedExtensions.includes(ext)) return
|
|
453
|
+
const flushReload = () => {
|
|
454
|
+
if (pendingChanges.length === 0) return
|
|
451
455
|
|
|
452
|
-
|
|
456
|
+
// Log all pending changes
|
|
457
|
+
for (const { filePath, event } of pendingChanges) {
|
|
458
|
+
console.log(`[veslx] Content ${event}: ${path.relative(dir, filePath)}`)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Clear pending changes
|
|
462
|
+
pendingChanges.length = 0
|
|
453
463
|
|
|
454
464
|
// Invalidate the virtual content module so frontmatters are re-extracted
|
|
455
465
|
const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID)
|
|
@@ -461,6 +471,24 @@ export const modules = import.meta.glob(['@content/**/*.mdx', '@content/**/*.md'
|
|
|
461
471
|
server.ws.send({ type: 'full-reload' })
|
|
462
472
|
}
|
|
463
473
|
|
|
474
|
+
const handleContentChange = (filePath: string, event: 'add' | 'unlink' | 'change') => {
|
|
475
|
+
// Check if the file is in the content directory
|
|
476
|
+
if (!filePath.startsWith(dir)) return
|
|
477
|
+
|
|
478
|
+
// Check if it's a watched file type
|
|
479
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
480
|
+
if (!watchedExtensions.includes(ext)) return
|
|
481
|
+
|
|
482
|
+
// Queue this change
|
|
483
|
+
pendingChanges.push({ filePath, event })
|
|
484
|
+
|
|
485
|
+
// Debounce: clear existing timeout and set a new one
|
|
486
|
+
if (reloadTimeout) {
|
|
487
|
+
clearTimeout(reloadTimeout)
|
|
488
|
+
}
|
|
489
|
+
reloadTimeout = setTimeout(flushReload, DEBOUNCE_MS)
|
|
490
|
+
}
|
|
491
|
+
|
|
464
492
|
server.watcher.on('add', (filePath) => handleContentChange(filePath, 'add'))
|
|
465
493
|
server.watcher.on('unlink', (filePath) => handleContentChange(filePath, 'unlink'))
|
|
466
494
|
server.watcher.on('change', (filePath) => handleContentChange(filePath, 'change'))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import packageJson from "../../package.json";
|
|
2
|
+
|
|
3
|
+
export function Footer() {
|
|
4
|
+
return (
|
|
5
|
+
<footer className="py-4 mx-auto w-full max-w-[var(--content-width)] px-[var(--page-padding)]">
|
|
6
|
+
<div className="flex justify-end">
|
|
7
|
+
<a
|
|
8
|
+
href="https://github.com/eoinmurray/veslx"
|
|
9
|
+
target="_blank"
|
|
10
|
+
rel="noopener noreferrer"
|
|
11
|
+
className="font-mono text-xs text-muted-foreground/50 hover:text-muted-foreground transition-colors"
|
|
12
|
+
>
|
|
13
|
+
veslx v{packageJson.version}
|
|
14
|
+
</a>
|
|
15
|
+
</div>
|
|
16
|
+
</footer>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -8,6 +8,7 @@ import { FigureSlide } from './slides/figure-slide'
|
|
|
8
8
|
import { TextSlide } from './slides/text-slide'
|
|
9
9
|
import { SlideOutline } from './slides/slide-outline'
|
|
10
10
|
import { PostList } from '@/components/post-list'
|
|
11
|
+
import { PostListItem } from '@/components/post-list-item'
|
|
11
12
|
/**
|
|
12
13
|
* Smart link component that uses React Router for internal links
|
|
13
14
|
* and regular anchor tags for external links.
|
|
@@ -91,6 +92,8 @@ export const mdxComponents = {
|
|
|
91
92
|
|
|
92
93
|
PostList,
|
|
93
94
|
|
|
95
|
+
PostListItem,
|
|
96
|
+
|
|
94
97
|
// Headings - clean sans-serif
|
|
95
98
|
h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {
|
|
96
99
|
const id = generateId(props.children)
|
|
@@ -122,12 +122,13 @@ function ParameterGrid({ entries }: { entries: [string, ParameterValue][] }) {
|
|
|
122
122
|
</span>
|
|
123
123
|
<span
|
|
124
124
|
className={cn(
|
|
125
|
-
"text-[11px] font-mono tabular-nums font-medium
|
|
125
|
+
"text-[11px] font-mono tabular-nums font-medium truncate max-w-[120px]",
|
|
126
126
|
type === "number" && "text-foreground",
|
|
127
127
|
type === "string" && "text-amber-600 dark:text-amber-500",
|
|
128
128
|
type === "boolean" && "text-cyan-600 dark:text-cyan-500",
|
|
129
129
|
type === "null" && "text-muted-foreground/50"
|
|
130
130
|
)}
|
|
131
|
+
title={type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
|
|
131
132
|
>
|
|
132
133
|
{type === "string" ? `"${formatValue(value)}"` : formatValue(value)}
|
|
133
134
|
</span>
|
|
@@ -489,7 +490,7 @@ function SingleParameterTable({ path, keys, label, withMargin = true }: SinglePa
|
|
|
489
490
|
{label}
|
|
490
491
|
</div>
|
|
491
492
|
)}
|
|
492
|
-
<div className="rounded border border-border/60 bg-card/20 p-3 overflow-hidden">
|
|
493
|
+
<div className="rounded border border-border/60 bg-card/20 p-3 overflow-hidden max-w-full">
|
|
493
494
|
{topLeaves.length > 0 && (
|
|
494
495
|
<div className={cn(topNested.length > 0 && "mb-4 pb-3 border-b border-border/30")}>
|
|
495
496
|
<ParameterGrid entries={topLeaves} />
|
|
@@ -542,10 +543,16 @@ export function ParameterTable({ path, keys }: ParameterTableProps) {
|
|
|
542
543
|
// Check if this is a glob pattern
|
|
543
544
|
const hasGlob = isGlobPattern(resolvedPath);
|
|
544
545
|
|
|
545
|
-
// For glob patterns, get the base directory (
|
|
546
|
-
const baseDir =
|
|
547
|
-
|
|
548
|
-
|
|
546
|
+
// For glob patterns, get the base directory (directory containing the glob pattern)
|
|
547
|
+
const baseDir = useMemo(() => {
|
|
548
|
+
if (!hasGlob) return null;
|
|
549
|
+
// Get everything before the first glob character
|
|
550
|
+
const beforeGlob = resolvedPath.split(/[*?\[]/, 1)[0];
|
|
551
|
+
// Extract directory portion (everything up to the last slash)
|
|
552
|
+
const lastSlash = beforeGlob.lastIndexOf('/');
|
|
553
|
+
if (lastSlash === -1) return ".";
|
|
554
|
+
return beforeGlob.slice(0, lastSlash) || ".";
|
|
555
|
+
}, [hasGlob, resolvedPath]);
|
|
549
556
|
|
|
550
557
|
const { directory } = useDirectory(baseDir || ".");
|
|
551
558
|
|
|
@@ -602,30 +609,31 @@ export function ParameterTable({ path, keys }: ParameterTableProps) {
|
|
|
602
609
|
);
|
|
603
610
|
}
|
|
604
611
|
|
|
605
|
-
|
|
606
|
-
|
|
612
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
613
|
+
usePreventSwipeNavigation(scrollRef);
|
|
614
|
+
|
|
615
|
+
// Breakout width based on count
|
|
607
616
|
const count = matchingPaths.length;
|
|
608
|
-
const breakoutClass = count >=
|
|
609
|
-
? 'w-[
|
|
610
|
-
: count
|
|
617
|
+
const breakoutClass = count >= 4
|
|
618
|
+
? 'w-[96vw] ml-[calc(-48vw+50%)]'
|
|
619
|
+
: count >= 2
|
|
611
620
|
? 'w-[75vw] ml-[calc(-37.5vw+50%)]'
|
|
612
621
|
: '';
|
|
613
622
|
|
|
614
|
-
const scrollRef = useRef<HTMLDivElement>(null);
|
|
615
|
-
usePreventSwipeNavigation(scrollRef);
|
|
616
|
-
|
|
617
623
|
return (
|
|
618
|
-
<div
|
|
619
|
-
{
|
|
620
|
-
|
|
621
|
-
<
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
624
|
+
<div className={`my-6 ${breakoutClass}`}>
|
|
625
|
+
<div ref={scrollRef} className="flex gap-4 overflow-x-auto overscroll-x-contain pb-2">
|
|
626
|
+
{matchingPaths.map((filePath) => (
|
|
627
|
+
<div key={filePath} className="flex-none w-[280px]">
|
|
628
|
+
<SingleParameterTable
|
|
629
|
+
path={filePath}
|
|
630
|
+
keys={keys}
|
|
631
|
+
label={filePath.split('/').pop() || filePath}
|
|
632
|
+
withMargin={false}
|
|
633
|
+
/>
|
|
634
|
+
</div>
|
|
635
|
+
))}
|
|
636
|
+
</div>
|
|
629
637
|
</div>
|
|
630
638
|
);
|
|
631
639
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useParams } from "react-router-dom";
|
|
2
|
+
import { minimatch } from "minimatch";
|
|
2
3
|
import {
|
|
3
4
|
type PostEntry,
|
|
4
5
|
directoryToPostEntries,
|
|
@@ -11,6 +12,11 @@ import Loading from "./loading";
|
|
|
11
12
|
import { PostListItem } from "./post-list-item";
|
|
12
13
|
import veslxConfig from "virtual:veslx-config";
|
|
13
14
|
|
|
15
|
+
interface PostListProps {
|
|
16
|
+
/** Glob patterns to filter posts by name (e.g., ["01-*", "getting-*"]) */
|
|
17
|
+
globs?: string[] | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
// Helper to format name for display (e.g., "01-getting-started" → "Getting Started")
|
|
15
21
|
function formatName(name: string): string {
|
|
16
22
|
return name
|
|
@@ -36,7 +42,7 @@ function getLinkPath(post: PostEntry): string {
|
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
export function PostList() {
|
|
45
|
+
export function PostList({ globs = null }: PostListProps) {
|
|
40
46
|
const { "*": path = "." } = useParams();
|
|
41
47
|
|
|
42
48
|
const { directory, loading, error } = useDirectory(path)
|
|
@@ -72,6 +78,13 @@ export function PostList() {
|
|
|
72
78
|
// Filter out hidden and draft posts
|
|
73
79
|
posts = filterVisiblePosts(posts);
|
|
74
80
|
|
|
81
|
+
// Filter by glob patterns if provided
|
|
82
|
+
if (globs && globs.length > 0) {
|
|
83
|
+
posts = posts.filter(post =>
|
|
84
|
+
globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
if (posts.length === 0) {
|
|
76
89
|
return (
|
|
77
90
|
<div className="py-24 text-center">
|