veslx 0.1.64 → 0.1.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -16
- package/bin/lib/serve.ts +46 -0
- package/dist/bin/lib/serve.js +36 -0
- package/dist/bin/veslx.js +0 -0
- package/dist/client/components/gallery/index.js +7 -23
- package/dist/client/components/gallery/index.js.map +1 -1
- package/dist/client/components/mdx-components.js +0 -14
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list-item.js +6 -5
- package/dist/client/components/post-list-item.js.map +1 -1
- package/dist/client/components/post-list.js +6 -1
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/lib/content-classification.js +11 -2
- package/dist/client/lib/content-classification.js.map +1 -1
- package/dist/client/plugin/src/client.js +7 -40
- package/dist/client/plugin/src/client.js.map +1 -1
- package/dist/plugin/src/client.js +7 -0
- package/dist/plugin/src/plugin.js +5 -2
- package/package.json +1 -1
- package/plugin/src/client.tsx +10 -0
- package/plugin/src/plugin.ts +5 -2
- package/src/components/gallery/index.tsx +8 -23
- package/src/components/index.ts +0 -3
- package/src/components/mdx-components.tsx +0 -21
- package/src/components/post-list-item.tsx +10 -6
- package/src/components/post-list.tsx +8 -2
- package/src/lib/content-classification.ts +12 -2
- package/dist/client/components/parameter-badge.js +0 -48
- package/dist/client/components/parameter-badge.js.map +0 -1
- package/dist/client/components/parameter-table.js +0 -216
- package/dist/client/components/parameter-table.js.map +0 -1
- package/dist/client/components/slides/figure-slide.js +0 -14
- package/dist/client/components/slides/figure-slide.js.map +0 -1
- package/dist/client/components/slides/hero-slide.js +0 -21
- package/dist/client/components/slides/hero-slide.js.map +0 -1
- package/dist/client/components/slides/slide-outline.js +0 -28
- package/dist/client/components/slides/slide-outline.js.map +0 -1
- package/dist/client/components/slides/text-slide.js +0 -18
- package/dist/client/components/slides/text-slide.js.map +0 -1
- package/dist/client/components/veslx-cat.js +0 -40
- package/dist/client/components/veslx-cat.js.map +0 -1
- package/dist/client/lib/parameter-utils.js +0 -108
- package/dist/client/lib/parameter-utils.js.map +0 -1
- package/src/components/parameter-badge.tsx +0 -78
- package/src/components/parameter-table.tsx +0 -369
- package/src/components/slides/figure-slide.tsx +0 -16
- package/src/components/slides/hero-slide.tsx +0 -34
- package/src/components/slides/slide-outline.tsx +0 -38
- package/src/components/slides/text-slide.tsx +0 -35
- package/src/components/veslx-cat.tsx +0 -73
package/README.md
CHANGED
|
@@ -156,22 +156,6 @@ Display images with titles, captions, and a fullscreen lightbox:
|
|
|
156
156
|
| `captionLabel` | `string` | Label prefix (e.g., "Figure 1") |
|
|
157
157
|
| `limit` | `number` | Max images to show |
|
|
158
158
|
|
|
159
|
-
### VeslxParameterTable
|
|
160
|
-
|
|
161
|
-
Display YAML or JSON configuration files with collapsible sections:
|
|
162
|
-
|
|
163
|
-
```mdx
|
|
164
|
-
<VeslxParameterTable
|
|
165
|
-
path="config.yaml"
|
|
166
|
-
keys={[".simulation.timestep", ".model.layers"]}
|
|
167
|
-
/>
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
| Prop | Type | Description |
|
|
171
|
-
|------|------|-------------|
|
|
172
|
-
| `path` | `string` | Path to YAML/JSON file |
|
|
173
|
-
| `keys` | `string[]` | jq-like paths to filter (optional) |
|
|
174
|
-
|
|
175
159
|
### Local Imports
|
|
176
160
|
|
|
177
161
|
Import React components directly from your content directory:
|
package/bin/lib/serve.ts
CHANGED
|
@@ -3,6 +3,7 @@ import importConfig from "./import-config.js";
|
|
|
3
3
|
import veslxPlugin from '../../plugin/src/plugin.js'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import fs from 'fs'
|
|
6
|
+
import net from 'net'
|
|
6
7
|
import { fileURLToPath } from 'url'
|
|
7
8
|
import { log } from './log.js'
|
|
8
9
|
|
|
@@ -49,6 +50,11 @@ async function listenWithFallback(server: Awaited<ReturnType<typeof createServer
|
|
|
49
50
|
|
|
50
51
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
51
52
|
const port = startPort + attempt;
|
|
53
|
+
if (await isPortOccupiedOnLoopback(port)) {
|
|
54
|
+
log.info(`busy :${port} → :${port + 1}`);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
52
58
|
try {
|
|
53
59
|
await server.listen(port);
|
|
54
60
|
const address = server.httpServer?.address();
|
|
@@ -67,6 +73,46 @@ async function listenWithFallback(server: Awaited<ReturnType<typeof createServer
|
|
|
67
73
|
process.exit(1);
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
async function isPortOccupiedOnLoopback(port: number): Promise<boolean> {
|
|
77
|
+
const hosts = ['127.0.0.1', '::1'];
|
|
78
|
+
|
|
79
|
+
for (const host of hosts) {
|
|
80
|
+
const canListen = await canListenOnHost(port, host);
|
|
81
|
+
if (!canListen) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function canListenOnHost(port: number, host: string): Promise<boolean> {
|
|
90
|
+
return await new Promise((resolve) => {
|
|
91
|
+
const probe = net.createServer();
|
|
92
|
+
|
|
93
|
+
probe.once('error', (err: NodeJS.ErrnoException) => {
|
|
94
|
+
// IPv6 may be unavailable on some systems; skip that host.
|
|
95
|
+
if (err.code === 'EAFNOSUPPORT' || err.code === 'EADDRNOTAVAIL') {
|
|
96
|
+
resolve(true);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (err.code === 'EADDRINUSE') {
|
|
101
|
+
resolve(false);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
resolve(true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
probe.once('listening', () => {
|
|
109
|
+
probe.close(() => resolve(true));
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
probe.listen(port, host);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
70
116
|
function resolveVeslxRoot() {
|
|
71
117
|
const candidates = [
|
|
72
118
|
new URL('../..', import.meta.url),
|
package/dist/bin/lib/serve.js
CHANGED
|
@@ -3,6 +3,7 @@ import importConfig from "./import-config.js";
|
|
|
3
3
|
import veslxPlugin from '../../plugin/src/plugin.js';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import fs from 'fs';
|
|
6
|
+
import net from 'net';
|
|
6
7
|
import { fileURLToPath } from 'url';
|
|
7
8
|
import { log } from './log.js';
|
|
8
9
|
async function readPackageJson(cwd) {
|
|
@@ -42,6 +43,10 @@ async function listenWithFallback(server) {
|
|
|
42
43
|
const maxAttempts = 50;
|
|
43
44
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
44
45
|
const port = startPort + attempt;
|
|
46
|
+
if (await isPortOccupiedOnLoopback(port)) {
|
|
47
|
+
log.info(`busy :${port} → :${port + 1}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
45
50
|
try {
|
|
46
51
|
await server.listen(port);
|
|
47
52
|
const address = server.httpServer?.address();
|
|
@@ -59,6 +64,37 @@ async function listenWithFallback(server) {
|
|
|
59
64
|
log.error(`no available ports from ${startPort} to ${startPort + maxAttempts - 1}`);
|
|
60
65
|
process.exit(1);
|
|
61
66
|
}
|
|
67
|
+
async function isPortOccupiedOnLoopback(port) {
|
|
68
|
+
const hosts = ['127.0.0.1', '::1'];
|
|
69
|
+
for (const host of hosts) {
|
|
70
|
+
const canListen = await canListenOnHost(port, host);
|
|
71
|
+
if (!canListen) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
async function canListenOnHost(port, host) {
|
|
78
|
+
return await new Promise((resolve) => {
|
|
79
|
+
const probe = net.createServer();
|
|
80
|
+
probe.once('error', (err) => {
|
|
81
|
+
// IPv6 may be unavailable on some systems; skip that host.
|
|
82
|
+
if (err.code === 'EAFNOSUPPORT' || err.code === 'EADDRNOTAVAIL') {
|
|
83
|
+
resolve(true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (err.code === 'EADDRINUSE') {
|
|
87
|
+
resolve(false);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
resolve(true);
|
|
91
|
+
});
|
|
92
|
+
probe.once('listening', () => {
|
|
93
|
+
probe.close(() => resolve(true));
|
|
94
|
+
});
|
|
95
|
+
probe.listen(port, host);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
62
98
|
function resolveVeslxRoot() {
|
|
63
99
|
const candidates = [
|
|
64
100
|
new URL('../..', import.meta.url),
|
package/dist/bin/veslx.js
CHANGED
|
File without changes
|
|
@@ -83,7 +83,7 @@ function Gallery({
|
|
|
83
83
|
return /* @__PURE__ */ jsx("figure", { className: "not-prose py-6 md:py-8", style: galleryStyle, children: /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx(
|
|
84
84
|
"div",
|
|
85
85
|
{
|
|
86
|
-
className: "
|
|
86
|
+
className: "h-40 rounded-sm bg-muted/20 relative overflow-hidden",
|
|
87
87
|
children: /* @__PURE__ */ jsx(
|
|
88
88
|
"div",
|
|
89
89
|
{
|
|
@@ -113,14 +113,14 @@ function Gallery({
|
|
|
113
113
|
"div",
|
|
114
114
|
{
|
|
115
115
|
title: img.label,
|
|
116
|
-
className: `
|
|
116
|
+
className: `overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ""}`,
|
|
117
117
|
onClick: () => lightbox.open(index),
|
|
118
118
|
children: /* @__PURE__ */ jsx(
|
|
119
119
|
LoadingImage,
|
|
120
120
|
{
|
|
121
121
|
src: img.src,
|
|
122
122
|
alt: img.label,
|
|
123
|
-
className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
123
|
+
className: "w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
124
124
|
}
|
|
125
125
|
)
|
|
126
126
|
},
|
|
@@ -149,29 +149,13 @@ function Gallery({
|
|
|
149
149
|
const offset = rowsToRender.slice(0, rowIndex).reduce((acc, row) => acc + row.length, 0);
|
|
150
150
|
const rowSubtitle = subtitles == null ? void 0 : subtitles[rowIndex];
|
|
151
151
|
const rowWrapperClass = "max-w-[var(--gallery-width)] w-full mx-auto";
|
|
152
|
-
const placeholders = Math.max(0, maxRowColumns - rowImages.length);
|
|
153
|
-
const rowCells = [
|
|
154
|
-
...rowImages.map((img, index) => ({ img, index })),
|
|
155
|
-
...Array.from({ length: placeholders }, () => ({ img: null, index: -1 }))
|
|
156
|
-
];
|
|
157
152
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
158
153
|
/* @__PURE__ */ jsx("div", { className: rowWrapperClass, children: /* @__PURE__ */ jsx(
|
|
159
154
|
"div",
|
|
160
155
|
{
|
|
161
156
|
className: "grid gap-3",
|
|
162
157
|
style: { gridTemplateColumns: `repeat(${Math.max(1, maxRowColumns)}, minmax(0, 1fr))` },
|
|
163
|
-
children:
|
|
164
|
-
if (!cell.img) {
|
|
165
|
-
return /* @__PURE__ */ jsx(
|
|
166
|
-
"div",
|
|
167
|
-
{
|
|
168
|
-
className: "aspect-square rounded-sm opacity-0 pointer-events-none"
|
|
169
|
-
},
|
|
170
|
-
`empty-${rowIndex}-${index}`
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
return imageElement(offset + cell.index, cell.img, "w-full");
|
|
174
|
-
})
|
|
158
|
+
children: rowImages.map((img, index) => imageElement(offset + index, img, "w-full"))
|
|
175
159
|
}
|
|
176
160
|
) }),
|
|
177
161
|
rowSubtitle && /* @__PURE__ */ jsx("div", { className: "max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]", children: /* @__PURE__ */ jsx(FigureHeader, { subtitle: rowSubtitle }) })
|
|
@@ -180,18 +164,18 @@ function Gallery({
|
|
|
180
164
|
/* @__PURE__ */ jsx(FigureHeader, { title, subtitle }),
|
|
181
165
|
imageElement(0, images[0]),
|
|
182
166
|
/* @__PURE__ */ jsx(FigureCaption, { caption, label: captionLabel })
|
|
183
|
-
] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4", children: images.map((img, index) => /* @__PURE__ */ jsx(
|
|
167
|
+
] }) : isCompact ? /* @__PURE__ */ jsx("div", { className: "flex items-start gap-3", children: images.map((img, index) => imageElement(index, img, "flex-1")) }) : /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "gallery-scroll-row flex items-start gap-3 overflow-x-auto overscroll-x-contain pb-4", children: images.map((img, index) => /* @__PURE__ */ jsx(
|
|
184
168
|
"div",
|
|
185
169
|
{
|
|
186
170
|
title: img.label,
|
|
187
|
-
className: "flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px]
|
|
171
|
+
className: "flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] overflow-hidden rounded-sm bg-muted/10 cursor-pointer group",
|
|
188
172
|
onClick: () => lightbox.open(index),
|
|
189
173
|
children: /* @__PURE__ */ jsx(
|
|
190
174
|
LoadingImage,
|
|
191
175
|
{
|
|
192
176
|
src: img.src,
|
|
193
177
|
alt: img.label,
|
|
194
|
-
className: "object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
178
|
+
className: "w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]"
|
|
195
179
|
}
|
|
196
180
|
)
|
|
197
181
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo, useRef, useEffect, type CSSProperties } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n * Captures wheel events and prevents default when at scroll boundaries.\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 // Only handle horizontal scrolling\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 // Prevent default if trying to scroll past boundaries\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\nfunction getImageLabel(path: string): string {\n const cleanPath = path.split(/[?#]/)[0];\n const filename = cleanPath.split('/').pop() || cleanPath;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction isAbsoluteUrl(path: string): boolean {\n return /^https?:\\/\\//i.test(path) || path.startsWith('//');\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const trimmedBase = baseUrl.replace(/\\/+$/, '');\n const trimmedPath = path.replace(/^\\/+/, '');\n return `${trimmedBase}/${trimmedPath}`;\n}\n\nfunction getImageUrl(path: string, baseUrl?: string): string {\n if (isAbsoluteUrl(path)) return path;\n if (baseUrl) return joinUrl(baseUrl, path);\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n baseUrl,\n caption,\n captionLabel,\n title,\n subtitle,\n subtitles,\n size = \"lg\",\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | string[][] | null;\n baseUrl?: string;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n subtitles?: string[];\n size?: \"sm\" | \"md\" | \"lg\";\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, rows, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n const galleryWidthMap: Record<\"sm\" | \"md\" | \"lg\", string> = {\n sm: \"min(80vw, 18rem)\",\n md: \"min(85vw, 30rem)\",\n lg: \"min(90vw, 48rem)\",\n };\n const galleryStyle = { \"--gallery-width\": galleryWidthMap[size] } as CSSProperties;\n\n const rowsToRender = rows.length > 0 ? rows : [paths];\n const flatPaths = rowsToRender.flat();\n const maxRowColumns = rowsToRender.reduce((max, row) => Math.max(max, row.length), 0);\n\n const images: LightboxImage[] = useMemo(\n () => flatPaths.map(p => ({ src: getImageUrl(p, baseUrl), label: getImageLabel(p) })),\n [flatPaths, baseUrl]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\" style={galleryStyle}>\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"aspect-square rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\" style={galleryStyle}>\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isSingle = images.length === 1;\n const isTwo = images.length === 2;\n const isCompact = images.length <= 3;\n const isGroupedRows = rowsToRender.length > 1;\n const isScrollRow = images.length > 3;\n const isSingleWithChildren = images.length === 1 && children;\n // 2+ images should break out of the content width\n const shouldBreakOut = images.length >= 2;\n const breakoutClass = shouldBreakOut\n ? isGroupedRows\n ? \"\"\n : isScrollRow\n ? \"gallery-breakout w-screen\"\n : \"gallery-breakout w-[var(--gallery-width)] max-w-none\"\n : \"\";\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n title={img.label}\n className={`aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure\n className={`not-prose relative py-6 md:py-8 ${breakoutClass}`}\n style={galleryStyle}\n >\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader title={title} subtitle={subtitle} />\n </div>\n )}\n\n {isSingleWithChildren ? (\n <div className={`grid items-start gap-12 md:gap-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] ${childAlign === 'left' ? '' : 'md:[&>div:first-child]:order-2'}`}>\n <div className=\"min-w-0 self-center text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5\">\n {children}\n </div>\n <div className=\"min-w-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isGroupedRows ? (\n <div className=\"space-y-6\">\n {rowsToRender.map((rowPaths, rowIndex) => {\n const rowImages = rowPaths.map((p) => ({\n src: getImageUrl(p, baseUrl),\n label: getImageLabel(p),\n }));\n const offset = rowsToRender.slice(0, rowIndex).reduce((acc, row) => acc + row.length, 0);\n const rowSubtitle = subtitles?.[rowIndex];\n const rowWrapperClass = \"max-w-[var(--gallery-width)] w-full mx-auto\";\n const placeholders = Math.max(0, maxRowColumns - rowImages.length);\n const rowCells = [\n ...rowImages.map((img, index) => ({ img, index })),\n ...Array.from({ length: placeholders }, () => ({ img: null, index: -1 })),\n ];\n\n return (\n <div key={`${rowIndex}-${rowPaths.join(\"|\")}`}>\n <div className={rowWrapperClass}>\n <div\n className=\"grid gap-3\"\n style={{ gridTemplateColumns: `repeat(${Math.max(1, maxRowColumns)}, minmax(0, 1fr))` }}\n >\n {rowCells.map((cell, index) => {\n if (!cell.img) {\n return (\n <div\n key={`empty-${rowIndex}-${index}`}\n className=\"aspect-square rounded-sm opacity-0 pointer-events-none\"\n />\n );\n }\n return imageElement(offset + cell.index, cell.img, 'w-full');\n })}\n </div>\n </div>\n\n {rowSubtitle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader subtitle={rowSubtitle} />\n </div>\n )}\n </div>\n );\n })}\n </div>\n ) : isSingle ? (\n <div className=\"max-w-[var(--gallery-width)] w-full mx-auto\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n ) : isCompact ? (\n <div className=\"flex gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <div ref={scrollRef} className=\"gallery-scroll-row flex gap-3 overflow-x-auto overscroll-x-contain pb-4\">\n {images.map((img, index) => (\n <div\n key={index}\n title={img.label}\n className=\"flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] aspect-square overflow-hidden rounded-sm bg-muted/10 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n )}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/components/gallery/index.tsx"],"sourcesContent":["import { useMemo, useRef, useEffect, type CSSProperties } from \"react\";\nimport { Image } from \"lucide-react\";\nimport { Lightbox, LightboxImage } from \"@/components/gallery/components/lightbox\";\nimport { useGalleryImages } from \"./hooks/use-gallery-images\";\nimport { useLightbox } from \"./hooks/use-lightbox\";\nimport { LoadingImage } from \"./components/loading-image\";\nimport { FigureHeader } from \"./components/figure-header\";\nimport { FigureCaption } from \"./components/figure-caption\";\n\n/**\n * Hook to prevent horizontal scroll from triggering browser back/forward gestures.\n * Captures wheel events and prevents default when at scroll boundaries.\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 // Only handle horizontal scrolling\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 // Prevent default if trying to scroll past boundaries\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\nfunction getImageLabel(path: string): string {\n const cleanPath = path.split(/[?#]/)[0];\n const filename = cleanPath.split('/').pop() || cleanPath;\n return filename\n .replace(/\\.(png|jpg|jpeg|gif|svg|webp)$/i, '')\n .replace(/[_-]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction isAbsoluteUrl(path: string): boolean {\n return /^https?:\\/\\//i.test(path) || path.startsWith('//');\n}\n\nfunction joinUrl(baseUrl: string, path: string): string {\n const trimmedBase = baseUrl.replace(/\\/+$/, '');\n const trimmedPath = path.replace(/^\\/+/, '');\n return `${trimmedBase}/${trimmedPath}`;\n}\n\nfunction getImageUrl(path: string, baseUrl?: string): string {\n if (isAbsoluteUrl(path)) return path;\n if (baseUrl) return joinUrl(baseUrl, path);\n return `/raw/${path}`;\n}\n\nexport default function Gallery({\n path,\n globs = null,\n baseUrl,\n caption,\n captionLabel,\n title,\n subtitle,\n subtitles,\n size = \"lg\",\n limit = null,\n page = 0,\n children,\n childAlign = \"right\",\n}: {\n path?: string;\n globs?: string[] | string[][] | null;\n baseUrl?: string;\n caption?: string;\n captionLabel?: string;\n title?: string;\n subtitle?: string;\n subtitles?: string[];\n size?: \"sm\" | \"md\" | \"lg\";\n limit?: number | null;\n page?: number;\n children?: React.ReactNode;\n childAlign?: \"left\" | \"right\";\n}) {\n const { paths, rows, isLoading, isEmpty } = useGalleryImages({\n path,\n globs,\n limit,\n page: page,\n });\n\n const lightbox = useLightbox(paths.length);\n const scrollRef = useRef<HTMLDivElement>(null);\n usePreventSwipeNavigation(scrollRef);\n\n const galleryWidthMap: Record<\"sm\" | \"md\" | \"lg\", string> = {\n sm: \"min(80vw, 18rem)\",\n md: \"min(85vw, 30rem)\",\n lg: \"min(90vw, 48rem)\",\n };\n const galleryStyle = { \"--gallery-width\": galleryWidthMap[size] } as CSSProperties;\n\n const rowsToRender = rows.length > 0 ? rows : [paths];\n const flatPaths = rowsToRender.flat();\n const maxRowColumns = rowsToRender.reduce((max, row) => Math.max(max, row.length), 0);\n\n const images: LightboxImage[] = useMemo(\n () => flatPaths.map(p => ({ src: getImageUrl(p, baseUrl), label: getImageLabel(p) })),\n [flatPaths, baseUrl]\n );\n\n if (isLoading) {\n return (\n <figure className=\"not-prose py-6 md:py-8\" style={galleryStyle}>\n <div className=\"grid grid-cols-3 gap-3 max-w-[var(--gallery-width)] mx-auto\">\n {[...Array(3)].map((_, i) => (\n <div\n key={i}\n className=\"h-40 rounded-sm bg-muted/20 relative overflow-hidden\"\n >\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-muted/30 to-transparent animate-shimmer\"\n style={{ animationDelay: `${i * 150}ms` }}\n />\n </div>\n ))}\n </div>\n </figure>\n );\n }\n\n if (isEmpty) {\n return (\n <figure className=\"not-prose py-12 text-center\" style={galleryStyle}>\n <div className=\"inline-flex items-center gap-2.5 text-muted-foreground/40\">\n <Image className=\"h-3.5 w-3.5\" strokeWidth={1.5} />\n <span className=\"font-mono text-xs uppercase tracking-widest\">No images</span>\n </div>\n </figure>\n );\n }\n\n const isSingle = images.length === 1;\n const isTwo = images.length === 2;\n const isCompact = images.length <= 3;\n const isGroupedRows = rowsToRender.length > 1;\n const isScrollRow = images.length > 3;\n const isSingleWithChildren = images.length === 1 && children;\n // 2+ images should break out of the content width\n const shouldBreakOut = images.length >= 2;\n const breakoutClass = shouldBreakOut\n ? isGroupedRows\n ? \"\"\n : isScrollRow\n ? \"gallery-breakout w-screen\"\n : \"gallery-breakout w-[var(--gallery-width)] max-w-none\"\n : \"\";\n\n const imageElement = (index: number, img: LightboxImage, className?: string) => (\n <div\n key={index}\n title={img.label}\n className={`overflow-hidden rounded-sm bg-muted/10 cursor-pointer group ${className || ''}`}\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n );\n\n return (\n <>\n <figure\n className={`not-prose relative py-6 md:py-8 ${breakoutClass}`}\n style={galleryStyle}\n >\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader title={title} subtitle={subtitle} />\n </div>\n )}\n\n {isSingleWithChildren ? (\n <div className={`grid items-start gap-12 md:gap-16 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] ${childAlign === 'left' ? '' : 'md:[&>div:first-child]:order-2'}`}>\n <div className=\"min-w-0 self-center text-sm leading-relaxed text-foreground/90 space-y-3 [&>ul]:space-y-1.5 [&>ul]:list-disc [&>ul]:pl-5 [&>ol]:space-y-1.5 [&>ol]:list-decimal [&>ol]:pl-5\">\n {children}\n </div>\n <div className=\"min-w-0\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n </div>\n ) : isGroupedRows ? (\n <div className=\"space-y-6\">\n {rowsToRender.map((rowPaths, rowIndex) => {\n const rowImages = rowPaths.map((p) => ({\n src: getImageUrl(p, baseUrl),\n label: getImageLabel(p),\n }));\n const offset = rowsToRender.slice(0, rowIndex).reduce((acc, row) => acc + row.length, 0);\n const rowSubtitle = subtitles?.[rowIndex];\n const rowWrapperClass = \"max-w-[var(--gallery-width)] w-full mx-auto\";\n\n return (\n <div key={`${rowIndex}-${rowPaths.join(\"|\")}`}>\n <div className={rowWrapperClass}>\n <div\n className=\"grid gap-3\"\n style={{ gridTemplateColumns: `repeat(${Math.max(1, maxRowColumns)}, minmax(0, 1fr))` }}\n >\n {rowImages.map((img, index) => imageElement(offset + index, img, 'w-full'))}\n </div>\n </div>\n\n {rowSubtitle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureHeader subtitle={rowSubtitle} />\n </div>\n )}\n </div>\n );\n })}\n </div>\n ) : isSingle ? (\n <div className=\"max-w-[var(--gallery-width)] w-full mx-auto\">\n <FigureHeader title={title} subtitle={subtitle} />\n {imageElement(0, images[0])}\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n ) : isCompact ? (\n <div className=\"flex items-start gap-3\">\n {images.map((img, index) => imageElement(index, img, 'flex-1'))}\n </div>\n ) : (\n <div ref={scrollRef} className=\"gallery-scroll-row flex items-start gap-3 overflow-x-auto overscroll-x-contain pb-4\">\n {images.map((img, index) => (\n <div\n key={index}\n title={img.label}\n className=\"flex-none w-[calc(var(--gallery-width)*0.3)] min-w-[250px] overflow-hidden rounded-sm bg-muted/10 cursor-pointer group\"\n onClick={() => lightbox.open(index)}\n >\n <LoadingImage\n src={img.src}\n alt={img.label}\n className=\"w-full h-auto object-contain transition-transform duration-500 ease-out group-hover:scale-[1.02]\"\n />\n </div>\n ))}\n </div>\n )}\n\n {!isSingleWithChildren && !isSingle && (\n <div className=\"max-w-[var(--content-width)] mx-auto px-[var(--page-padding)]\">\n <FigureCaption caption={caption} label={captionLabel} />\n </div>\n )}\n </figure>\n\n {lightbox.isOpen && lightbox.selectedIndex !== null && (\n <Lightbox\n images={images}\n selectedIndex={lightbox.selectedIndex}\n onClose={lightbox.close}\n onPrevious={lightbox.goToPrevious}\n onNext={lightbox.goToNext}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useLocation, Link } from "react-router-dom";
|
|
3
3
|
import Gallery from "./gallery/index.js";
|
|
4
|
-
import { ParameterTable } from "./parameter-table.js";
|
|
5
|
-
import { ParameterBadge } from "./parameter-badge.js";
|
|
6
|
-
import { VeslxCat } from "./veslx-cat.js";
|
|
7
4
|
import { VeslxTOC } from "./veslx-toc.js";
|
|
8
5
|
import { FrontMatter } from "./front-matter.js";
|
|
9
|
-
import { HeroSlide } from "./slides/hero-slide.js";
|
|
10
|
-
import { FigureSlide } from "./slides/figure-slide.js";
|
|
11
|
-
import { TextSlide } from "./slides/text-slide.js";
|
|
12
|
-
import { SlideOutline } from "./slides/slide-outline.js";
|
|
13
6
|
import { PostList } from "./post-list.js";
|
|
14
7
|
import { PostListItem } from "./post-list-item.js";
|
|
15
8
|
import { VeslxSearch } from "./veslx-search.js";
|
|
@@ -57,15 +50,8 @@ const mdxComponents = {
|
|
|
57
50
|
FrontMatter,
|
|
58
51
|
VeslxFrontMatter: FrontMatter,
|
|
59
52
|
VeslxGallery: Gallery,
|
|
60
|
-
VeslxParameterTable: ParameterTable,
|
|
61
|
-
VeslxParameterBadge: ParameterBadge,
|
|
62
|
-
VeslxHeroSlide: HeroSlide,
|
|
63
|
-
VeslxFigureSlide: FigureSlide,
|
|
64
|
-
VeslxTextSlide: TextSlide,
|
|
65
|
-
VeslxSlideOutline: SlideOutline,
|
|
66
53
|
VeslxPostList: PostList,
|
|
67
54
|
VeslxPostListItem: PostListItem,
|
|
68
|
-
VeslxCat,
|
|
69
55
|
VeslxTOC,
|
|
70
56
|
VeslxSearch,
|
|
71
57
|
// Headings - clean sans-serif
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { ParameterTable } from '@/components/parameter-table'\nimport { ParameterBadge } from '@/components/parameter-badge'\nimport { VeslxCat } from '@/components/veslx-cat'\nimport { VeslxTOC } from '@/components/veslx-toc'\nimport { FrontMatter } from './front-matter'\nimport { HeroSlide } from './slides/hero-slide'\nimport { FigureSlide } from './slides/figure-slide'\nimport { TextSlide } from './slides/text-slide'\nimport { SlideOutline } from './slides/slide-outline'\nimport { PostList } from '@/components/post-list'\nimport { PostListItem } from '@/components/post-list-item'\nimport { VeslxSearch } from '@/components/veslx-search'\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter: FrontMatter,\n\n VeslxFrontMatter: FrontMatter,\n\n VeslxGallery: Gallery,\n\n VeslxParameterTable: ParameterTable,\n\n VeslxParameterBadge: ParameterBadge,\n\n VeslxHeroSlide: HeroSlide,\n\n VeslxFigureSlide: FigureSlide,\n\n VeslxTextSlide: TextSlide,\n\n VeslxSlideOutline: SlideOutline,\n\n VeslxPostList: PostList,\n\n VeslxPostListItem: PostListItem,\n\n VeslxCat: VeslxCat,\n\n VeslxTOC: VeslxTOC,\n\n VeslxSearch: VeslxSearch,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (\n // Styling handled by CSS - inline code gets bg via :not(pre) > code selector\n <code className={className} {...props} />\n ),\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mdx-components.js","sources":["../../../src/components/mdx-components.tsx"],"sourcesContent":["import { Link, useLocation } from 'react-router-dom'\nimport Gallery from '@/components/gallery'\nimport { VeslxTOC } from '@/components/veslx-toc'\nimport { FrontMatter } from './front-matter'\nimport { PostList } from '@/components/post-list'\nimport { PostListItem } from '@/components/post-list-item'\nimport { VeslxSearch } from '@/components/veslx-search'\n/**\n * Smart link component that uses React Router for internal links\n * and regular anchor tags for external links.\n */\nfunction SmartLink({ href, children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) {\n const location = useLocation()\n\n // External links: absolute URLs, mailto, tel, etc.\n const isExternal = href?.startsWith('http') || href?.startsWith('mailto:') || href?.startsWith('tel:')\n\n // Hash-only links stay as anchors for in-page navigation\n const isHashOnly = href?.startsWith('#')\n\n if (isExternal || isHashOnly || !href) {\n return (\n <a\n href={href}\n className=\"text-primary hover:underline underline-offset-2\"\n {...(isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {})}\n {...props}\n >\n {children}\n </a>\n )\n }\n\n // Resolve relative paths (./foo.mdx, ../bar.mdx) against current location\n let resolvedHref = href\n if (href.startsWith('./') || href.startsWith('../')) {\n // Get current directory from pathname\n const currentPath = location.pathname\n const currentDir = currentPath.replace(/\\/[^/]+\\.mdx$/, '') || currentPath.replace(/\\/[^/]*$/, '') || '/'\n\n // Simple relative path resolution\n if (href.startsWith('./')) {\n resolvedHref = `${currentDir}/${href.slice(2)}`.replace(/\\/+/g, '/')\n } else if (href.startsWith('../')) {\n const parentDir = currentDir.replace(/\\/[^/]+$/, '') || '/'\n resolvedHref = `${parentDir}/${href.slice(3)}`.replace(/\\/+/g, '/')\n }\n }\n\n // Internal link - use React Router Link\n return (\n <Link\n to={resolvedHref}\n className=\"text-primary hover:underline underline-offset-2\"\n {...props}\n >\n {children}\n </Link>\n )\n}\n\nfunction generateId(children: unknown): string {\n return children\n ?.toString()\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-') ?? ''\n}\n\n// Shared MDX components - lab notebook / coder aesthetic\nexport const mdxComponents = {\n\n FrontMatter: FrontMatter,\n\n VeslxFrontMatter: FrontMatter,\n\n VeslxGallery: Gallery,\n\n VeslxPostList: PostList,\n\n VeslxPostListItem: PostListItem,\n\n VeslxTOC: VeslxTOC,\n\n VeslxSearch: VeslxSearch,\n\n // Headings - clean sans-serif\n h1: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h1\n id={id}\n className=\"text-2xl font-semibold tracking-tight mt-12 mb-4 first:mt-0\"\n {...props}\n />\n )\n },\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h2\n id={id}\n className=\"text-xl font-semibold tracking-tight mt-10 mb-3 pb-2 border-b border-border\"\n {...props}\n />\n )\n },\n h3: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h3\n id={id}\n className=\"text-lg font-medium tracking-tight mt-8 mb-2\"\n {...props}\n />\n )\n },\n h4: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h4\n id={id}\n className=\"text-base font-medium mt-6 mb-2\"\n {...props}\n />\n )\n },\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => {\n const id = generateId(props.children)\n return (\n <h5\n id={id}\n className=\"text-sm font-medium mt-4 mb-1\"\n {...props}\n />\n )\n },\n\n // Code blocks - IDE/terminal style\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => (\n <pre\n className=\"not-prose w-full overflow-x-auto p-4 text-sm bg-muted border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: ({ className, ...props }: React.HTMLAttributes<HTMLElement>) => (\n // Styling handled by CSS - inline code gets bg via :not(pre) > code selector\n <code className={className} {...props} />\n ),\n\n // Blockquote\n blockquote: (props: React.HTMLAttributes<HTMLQuoteElement>) => (\n <blockquote\n className=\"border-l-2 border-primary pl-4 my-6 text-muted-foreground\"\n {...props}\n />\n ),\n\n // Lists\n ul: (props: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"my-4 ml-6 list-disc marker:text-muted-foreground\" {...props} />\n ),\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => (\n <ol className=\"my-4 ml-6 list-decimal marker:text-muted-foreground\" {...props} />\n ),\n li: (props: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"mt-1.5\" {...props} />\n ),\n\n // Links - uses React Router for internal navigation\n a: SmartLink,\n\n // Tables\n table: (props: React.TableHTMLAttributes<HTMLTableElement>) => (\n <div className=\"not-prose my-6 overflow-x-auto border border-border rounded-md\">\n <table className=\"w-full text-sm border-collapse\" {...props} />\n </div>\n ),\n thead: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <thead className=\"bg-muted/50\" {...props} />\n ),\n tbody: (props: React.HTMLAttributes<HTMLTableSectionElement>) => (\n <tbody {...props} />\n ),\n tr: (props: React.HTMLAttributes<HTMLTableRowElement>) => (\n <tr className=\"border-b border-border last:border-b-0\" {...props} />\n ),\n th: (props: React.ThHTMLAttributes<HTMLTableCellElement>) => (\n <th\n className=\"px-4 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider\"\n {...props}\n />\n ),\n td: (props: React.TdHTMLAttributes<HTMLTableCellElement>) => (\n <td className=\"px-4 py-3 align-top\" {...props} />\n ),\n\n // Horizontal rule\n hr: (props: React.HTMLAttributes<HTMLHRElement>) => (\n <hr className=\"my-8 border-t border-border\" {...props} />\n ),\n\n // Paragraph\n p: (props: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"leading-relaxed mb-4 last:mb-0\" {...props} />\n ),\n\n // Strong/emphasis\n strong: (props: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"font-semibold\" {...props} />\n ),\n em: (props: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"italic\" {...props} />\n ),\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,8 @@ import { Link } from "react-router-dom";
|
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { formatDate } from "../lib/format-date.js";
|
|
5
5
|
import { ArrowRight, Presentation } from "lucide-react";
|
|
6
|
-
function PostListItem({ title, description, date, href, external, isSlides }) {
|
|
6
|
+
function PostListItem({ title, description, date, href, linkPath, external, openInNewTab = true, isSlides }) {
|
|
7
|
+
const resolvedHref = href || linkPath || "#";
|
|
7
8
|
const className = cn(
|
|
8
9
|
"group block py-3 px-3 -mx-3 rounded-md",
|
|
9
10
|
"transition-colors duration-150"
|
|
@@ -34,9 +35,9 @@ function PostListItem({ title, description, date, href, external, isSlides }) {
|
|
|
34
35
|
return /* @__PURE__ */ jsx(
|
|
35
36
|
"a",
|
|
36
37
|
{
|
|
37
|
-
href,
|
|
38
|
-
target: "_blank",
|
|
39
|
-
rel: "noopener noreferrer",
|
|
38
|
+
href: resolvedHref,
|
|
39
|
+
target: openInNewTab ? "_blank" : void 0,
|
|
40
|
+
rel: openInNewTab ? "noopener noreferrer" : void 0,
|
|
40
41
|
className,
|
|
41
42
|
children: content
|
|
42
43
|
}
|
|
@@ -45,7 +46,7 @@ function PostListItem({ title, description, date, href, external, isSlides }) {
|
|
|
45
46
|
return /* @__PURE__ */ jsx(
|
|
46
47
|
Link,
|
|
47
48
|
{
|
|
48
|
-
to:
|
|
49
|
+
to: resolvedHref,
|
|
49
50
|
className,
|
|
50
51
|
children: content
|
|
51
52
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-list-item.js","sources":["../../../src/components/post-list-item.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\n\ninterface PostListItemProps {\n title: string;\n description?: string;\n date?: Date;\n href
|
|
1
|
+
{"version":3,"file":"post-list-item.js","sources":["../../../src/components/post-list-item.tsx"],"sourcesContent":["import { Link } from \"react-router-dom\";\nimport { cn } from \"@/lib/utils\";\nimport { formatDate } from \"@/lib/format-date\";\nimport { ArrowRight, Presentation } from \"lucide-react\";\n\ninterface PostListItemProps {\n title: string;\n description?: string;\n date?: Date;\n href?: string;\n /** Alias for href (used in MDX as linkPath) */\n linkPath?: string;\n external?: boolean;\n openInNewTab?: boolean;\n isSlides?: boolean;\n}\n\nexport function PostListItem({ title, description, date, href, linkPath, external, openInNewTab = true, isSlides }: PostListItemProps) {\n const resolvedHref = href || linkPath || '#';\n const className = cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n );\n\n const content = (\n <article className=\"flex items-center gap-4\">\n {/* Main content */}\n <div className=\"flex-1 min-w-0\">\n <div className={cn(\n \"text-sm font-medium text-foreground\",\n \"group-hover:underline\",\n \"flex items-center gap-2\"\n )}>\n <span>{title}</span>\n <ArrowRight className=\"h-3 w-3 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200 text-primary\" />\n </div>\n\n {description && (\n <div className=\"text-sm text-muted-foreground line-clamp-1 mt-0.5\">\n {description}\n </div>\n )}\n </div>\n\n {isSlides && (\n <Presentation className=\"h-3 w-3 text-muted-foreground\" />\n )}\n <time\n dateTime={date?.toISOString()}\n className=\"font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0\"\n >\n {date && formatDate(date)}\n </time>\n </article>\n );\n\n if (external) {\n return (\n <a\n href={resolvedHref}\n target={openInNewTab ? \"_blank\" : undefined}\n rel={openInNewTab ? \"noopener noreferrer\" : undefined}\n className={className}\n >\n {content}\n </a>\n );\n }\n\n return (\n <Link\n to={resolvedHref}\n className={className}\n >\n {content}\n </Link>\n );\n}\n"],"names":[],"mappings":";;;;;AAiBO,SAAS,aAAa,EAAE,OAAO,aAAa,MAAM,MAAM,UAAU,UAAU,eAAe,MAAM,SAAA,GAA+B;AACrI,QAAM,eAAe,QAAQ,YAAY;AACzC,QAAM,YAAY;AAAA,IAChB;AAAA,IACA;AAAA,EAAA;AAGF,QAAM,UACJ,qBAAC,WAAA,EAAQ,WAAU,2BAEjB,UAAA;AAAA,IAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,MAAA,qBAAC,SAAI,WAAW;AAAA,QACd;AAAA,QACA;AAAA,QACA;AAAA,MAAA,GAEA,UAAA;AAAA,QAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,QACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,MAAA,GACtJ;AAAA,MAEC,eACC,oBAAC,OAAA,EAAI,WAAU,qDACZ,UAAA,YAAA,CACH;AAAA,IAAA,GAEJ;AAAA,IAEC,YACC,oBAAC,cAAA,EAAa,WAAU,gCAAA,CAAgC;AAAA,IAE1D;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,UAAU,6BAAM;AAAA,QAChB,WAAU;AAAA,QAET,UAAA,QAAQ,WAAW,IAAI;AAAA,MAAA;AAAA,IAAA;AAAA,EAC1B,GACF;AAGF,MAAI,UAAU;AACZ,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAM;AAAA,QACN,QAAQ,eAAe,WAAW;AAAA,QAClC,KAAK,eAAe,wBAAwB;AAAA,QAC5C;AAAA,QAEC,UAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AAEA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ;AAAA,MAEC,UAAA;AAAA,IAAA;AAAA,EAAA;AAGP;"}
|
|
@@ -16,6 +16,9 @@ function filePathToRoutePath(filePath) {
|
|
|
16
16
|
}
|
|
17
17
|
function getLinkPath(post) {
|
|
18
18
|
if (post.file) {
|
|
19
|
+
if (post.file.path.toLowerCase().endsWith(".pdf")) {
|
|
20
|
+
return `/raw/${post.file.path}`;
|
|
21
|
+
}
|
|
19
22
|
return filePathToRoutePath(post.file.path);
|
|
20
23
|
} else if (post.slides && !post.readme) {
|
|
21
24
|
return `/${post.slides.path}`;
|
|
@@ -26,7 +29,7 @@ function getLinkPath(post) {
|
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
function isRouterPath(href) {
|
|
29
|
-
return href.startsWith("/") && !href.startsWith("//");
|
|
32
|
+
return href.startsWith("/") && !href.startsWith("//") && !href.startsWith("/raw/");
|
|
30
33
|
}
|
|
31
34
|
function PostList({ globs = null }) {
|
|
32
35
|
var _a;
|
|
@@ -78,6 +81,7 @@ function PostList({ globs = null }) {
|
|
|
78
81
|
const frontmatterLink = typeof (frontmatter == null ? void 0 : frontmatter.link) === "string" ? frontmatter.link.trim() : "";
|
|
79
82
|
const href = frontmatterLink || internalLinkPath;
|
|
80
83
|
const external = !isRouterPath(href);
|
|
84
|
+
const openInNewTab = external && !href.startsWith("/raw/");
|
|
81
85
|
return /* @__PURE__ */ jsx(
|
|
82
86
|
PostListItem,
|
|
83
87
|
{
|
|
@@ -86,6 +90,7 @@ function PostList({ globs = null }) {
|
|
|
86
90
|
date,
|
|
87
91
|
href,
|
|
88
92
|
external,
|
|
93
|
+
openInNewTab,
|
|
89
94
|
isSlides
|
|
90
95
|
},
|
|
91
96
|
post.path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\nfunction filePathToRoutePath(filePath: string): string {\n // Routes without an extension are treated as directories; ContentRouter will\n // render index/readme files automatically via IndexPost.\n const normalized = filePath.replace(/^\\/+/, '');\n const stripped = normalized.replace(/(?:^|\\/)(?:index|readme)\\.mdx?$/i, '');\n return stripped ? `/${stripped}` : '/';\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone MDX file\n return filePathToRoutePath(post.file.path);\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return filePathToRoutePath(post.readme.path);\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nfunction isRouterPath(href: string): boolean {\n return href.startsWith(\"/\") && !href.startsWith(\"//\");\n}\n\nexport function PostList({ globs = null }: PostListProps) {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Filter by glob patterns if provided\n if (globs && globs.length > 0) {\n posts = posts.filter(post =>\n globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))\n );\n }\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const internalLinkPath = getLinkPath(post);\n const normalizedLink = internalLinkPath.toLowerCase();\n const isSlides =\n normalizedLink.endsWith('/slides.mdx') ||\n normalizedLink.endsWith('/slides.md') ||\n normalizedLink.endsWith('.slides.mdx') ||\n normalizedLink.endsWith('.slides.md');\n\n const frontmatterLink =\n typeof frontmatter?.link === \"string\" ? frontmatter.link.trim() : \"\";\n const href = frontmatterLink || internalLinkPath;\n const external = !isRouterPath(href);\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n href={href}\n external={external}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":";;;;;;;;AAoBA,SAAS,WAAW,MAAsB;AACxC,SAAO,KACJ,QAAQ,SAAS,EAAE,EACnB,QAAQ,MAAM,GAAG,EACjB,QAAQ,SAAS,CAAA,MAAK,EAAE,aAAa;AAC1C;AAEA,SAAS,oBAAoB,UAA0B;AAGrD,QAAM,aAAa,SAAS,QAAQ,QAAQ,EAAE;AAC9C,QAAM,WAAW,WAAW,QAAQ,oCAAoC,EAAE;AAC1E,SAAO,WAAW,IAAI,QAAQ,KAAK;AACrC;AAGA,SAAS,YAAY,MAAyB;AAC5C,MAAI,KAAK,MAAM;AAEb,WAAO,oBAAoB,KAAK,KAAK,IAAI;AAAA,EAC3C,WAAW,KAAK,UAAU,CAAC,KAAK,QAAQ;AAEtC,WAAO,IAAI,KAAK,OAAO,IAAI;AAAA,EAC7B,WAAW,KAAK,QAAQ;AAEtB,WAAO,oBAAoB,KAAK,OAAO,IAAI;AAAA,EAC7C,OAAO;AAEL,WAAO,IAAI,KAAK,IAAI;AAAA,EACtB;AACF;AAEA,SAAS,aAAa,MAAuB;AAC3C,SAAO,KAAK,WAAW,GAAG,KAAK,CAAC,KAAK,WAAW,IAAI;
|
|
1
|
+
{"version":3,"file":"post-list.js","sources":["../../../src/components/post-list.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { minimatch } from \"minimatch\";\nimport {\n type PostEntry,\n directoryToPostEntries,\n filterVisiblePosts,\n getFrontmatter,\n} from \"@/lib/content-classification\";\nimport { useDirectory } from \"../../plugin/src/client\";\nimport { ErrorDisplay } from \"./page-error\";\nimport Loading from \"./loading\";\nimport { PostListItem } from \"./post-list-item\";\nimport veslxConfig from \"virtual:veslx-config\";\n\ninterface PostListProps {\n /** Glob patterns to filter posts by name (e.g., [\"01-*\", \"getting-*\"]) */\n globs?: string[] | null;\n}\n\n// Helper to format name for display (e.g., \"01-getting-started\" → \"Getting Started\")\nfunction formatName(name: string): string {\n return name\n .replace(/^\\d+-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n}\n\nfunction filePathToRoutePath(filePath: string): string {\n // Routes without an extension are treated as directories; ContentRouter will\n // render index/readme files automatically via IndexPost.\n const normalized = filePath.replace(/^\\/+/, '');\n const stripped = normalized.replace(/(?:^|\\/)(?:index|readme)\\.mdx?$/i, '');\n return stripped ? `/${stripped}` : '/';\n}\n\n// Helper to get link path from post\nfunction getLinkPath(post: PostEntry): string {\n if (post.file) {\n // Standalone PDF file should open directly.\n if (post.file.path.toLowerCase().endsWith('.pdf')) {\n return `/raw/${post.file.path}`;\n }\n // Standalone MDX/TSX file\n return filePathToRoutePath(post.file.path);\n } else if (post.slides && !post.readme) {\n // Folder with only slides\n return `/${post.slides.path}`;\n } else if (post.readme) {\n // Folder with readme\n return filePathToRoutePath(post.readme.path);\n } else {\n // Fallback to folder path\n return `/${post.path}`;\n }\n}\n\nfunction isRouterPath(href: string): boolean {\n return href.startsWith(\"/\") && !href.startsWith(\"//\") && !href.startsWith(\"/raw/\");\n}\n\nexport function PostList({ globs = null }: PostListProps) {\n const { \"*\": path = \".\" } = useParams();\n\n const { directory, loading, error } = useDirectory(path)\n\n if (error) {\n return <ErrorDisplay error={error} path={path} />;\n }\n\n if (loading) {\n return (\n <Loading />\n )\n }\n\n if (!directory) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n let posts = directoryToPostEntries(directory);\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Filter out hidden and draft posts\n posts = filterVisiblePosts(posts);\n\n // Filter by glob patterns if provided\n if (globs && globs.length > 0) {\n posts = posts.filter(post =>\n globs.some(pattern => minimatch(post.name, pattern, { matchBase: true }))\n );\n }\n\n if (posts.length === 0) {\n return (\n <div className=\"py-24 text-center\">\n <p className=\"text-muted-foreground font-mono text-sm tracking-wide\">no entries</p>\n </div>\n );\n }\n\n // Sort based on config\n const sortMode = veslxConfig.posts?.sort ?? 'alpha';\n if (sortMode === 'date' || sortMode === 'date-asc') {\n const ascending = sortMode === 'date-asc';\n // Sort by date, posts without dates go to the top\n posts = posts.sort((a, b) => {\n const dateA = getFrontmatter(a)?.date;\n const dateB = getFrontmatter(b)?.date;\n if (!dateA && !dateB) return a.name.localeCompare(b.name);\n if (!dateA) return -1;\n if (!dateB) return 1;\n const diff = new Date(dateA as string).getTime() - new Date(dateB as string).getTime();\n return ascending ? diff : -diff;\n });\n } else {\n // Alphanumeric sorting by name\n posts = posts.sort((a, b) => a.name.localeCompare(b.name));\n }\n\n return (\n <div className=\"space-y-1 not-prose\">\n {posts.map((post) => {\n const frontmatter = getFrontmatter(post);\n const title = (frontmatter?.title as string) || formatName(post.name);\n const description = frontmatter?.description as string | undefined;\n const date = frontmatter?.date ? new Date(frontmatter.date as string) : undefined;\n const internalLinkPath = getLinkPath(post);\n const normalizedLink = internalLinkPath.toLowerCase();\n const isSlides =\n normalizedLink.endsWith('/slides.mdx') ||\n normalizedLink.endsWith('/slides.md') ||\n normalizedLink.endsWith('.slides.mdx') ||\n normalizedLink.endsWith('.slides.md');\n\n const frontmatterLink =\n typeof frontmatter?.link === \"string\" ? frontmatter.link.trim() : \"\";\n const href = frontmatterLink || internalLinkPath;\n const external = !isRouterPath(href);\n const openInNewTab = external && !href.startsWith(\"/raw/\");\n\n return (\n <PostListItem\n key={post.path}\n title={title}\n description={description}\n date={date}\n href={href}\n external={external}\n openInNewTab={openInNewTab}\n isSlides={isSlides}\n />\n );\n })}\n </div>\n );\n}\n"],"names":["_a"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findMdxFiles, findTsxFiles, findStandaloneSlides, findSlides, findReadme } from "../plugin/src/client.js";
|
|
1
|
+
import { findMdxFiles, findTsxFiles, findStandaloneSlides, findPdfFiles, findSlides, findReadme } from "../plugin/src/client.js";
|
|
2
2
|
function getFrontmatter(post) {
|
|
3
3
|
var _a, _b, _c;
|
|
4
4
|
return ((_a = post.readme) == null ? void 0 : _a.frontmatter) || ((_b = post.file) == null ? void 0 : _b.frontmatter) || ((_c = post.slides) == null ? void 0 : _c.frontmatter);
|
|
@@ -8,6 +8,7 @@ function directoryToPostEntries(directory) {
|
|
|
8
8
|
const standaloneFiles = findMdxFiles(directory);
|
|
9
9
|
const standaloneTsxFiles = findTsxFiles(directory);
|
|
10
10
|
const standaloneSlidesFiles = findStandaloneSlides(directory);
|
|
11
|
+
const standalonePdfFiles = findPdfFiles(directory);
|
|
11
12
|
const folderPosts = folders.map((folder) => ({
|
|
12
13
|
type: "folder",
|
|
13
14
|
name: folder.name,
|
|
@@ -40,7 +41,15 @@ function directoryToPostEntries(directory) {
|
|
|
40
41
|
slides: file,
|
|
41
42
|
file: null
|
|
42
43
|
}));
|
|
43
|
-
|
|
44
|
+
const pdfPosts = standalonePdfFiles.map((file) => ({
|
|
45
|
+
type: "file",
|
|
46
|
+
name: file.name.replace(/\.pdf$/i, ""),
|
|
47
|
+
path: file.path,
|
|
48
|
+
readme: null,
|
|
49
|
+
slides: null,
|
|
50
|
+
file
|
|
51
|
+
}));
|
|
52
|
+
return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts, ...pdfPosts];
|
|
44
53
|
}
|
|
45
54
|
function filterVisiblePosts(posts) {
|
|
46
55
|
return posts.filter((post) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type { DirectoryEntry, FileEntry } from \"../../plugin/src/lib\";\nimport { findReadme, findSlides, findMdxFiles, findStandaloneSlides, findTsxFiles } from \"../../plugin/src/client\";\n\nexport type PostEntry = {\n type: 'folder' | 'file';\n name: string;\n path: string;\n readme: FileEntry | null;\n slides: FileEntry | null;\n file: FileEntry | null;\n};\n\nexport function getFrontmatter(post: PostEntry) {\n return post.readme?.frontmatter || post.file?.frontmatter || post.slides?.frontmatter;\n}\n\nexport function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {\n const folders = directory.children.filter((c): c is DirectoryEntry => c.type === \"directory\");\n const standaloneFiles = findMdxFiles(directory);\n const standaloneTsxFiles = findTsxFiles(directory);\n const standaloneSlidesFiles = findStandaloneSlides(directory);\n\n const folderPosts: PostEntry[] = folders\n .map((folder) => ({\n type: 'folder' as const,\n name: folder.name,\n path: folder.path,\n readme: findReadme(folder),\n slides: findSlides(folder),\n file: null,\n }))\n .filter((post) => post.readme || post.slides); // Only include folders with content\n\n const filePosts: PostEntry[] = standaloneFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n const tsxPosts: PostEntry[] = standaloneTsxFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.tsx$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n // Standalone slides files (e.g., getting-started.slides.mdx)\n const slidesPosts: PostEntry[] = standaloneSlidesFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.slides\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: file,\n file: null,\n }));\n\n return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts];\n}\n\nexport function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {\n return posts.filter((post) => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.visibility !== \"hidden\" && frontmatter?.draft !== true;\n });\n}\n"],"names":[],"mappings":";AAYO,SAAS,eAAe,MAAiB;;AAC9C,WAAO,UAAK,WAAL,mBAAa,kBAAe,UAAK,SAAL,mBAAW,kBAAe,UAAK,WAAL,mBAAa;AAC5E;AAEO,SAAS,uBAAuB,WAAwC;AAC7E,QAAM,UAAU,UAAU,SAAS,OAAO,CAAC,MAA2B,EAAE,SAAS,WAAW;AAC5F,QAAM,kBAAkB,aAAa,SAAS;AAC9C,QAAM,qBAAqB,aAAa,SAAS;AACjD,QAAM,wBAAwB,qBAAqB,SAAS;
|
|
1
|
+
{"version":3,"file":"content-classification.js","sources":["../../../src/lib/content-classification.ts"],"sourcesContent":["import type { DirectoryEntry, FileEntry } from \"../../plugin/src/lib\";\nimport { findReadme, findSlides, findMdxFiles, findStandaloneSlides, findTsxFiles, findPdfFiles } from \"../../plugin/src/client\";\n\nexport type PostEntry = {\n type: 'folder' | 'file';\n name: string;\n path: string;\n readme: FileEntry | null;\n slides: FileEntry | null;\n file: FileEntry | null;\n};\n\nexport function getFrontmatter(post: PostEntry) {\n return post.readme?.frontmatter || post.file?.frontmatter || post.slides?.frontmatter;\n}\n\nexport function directoryToPostEntries(directory: DirectoryEntry): PostEntry[] {\n const folders = directory.children.filter((c): c is DirectoryEntry => c.type === \"directory\");\n const standaloneFiles = findMdxFiles(directory);\n const standaloneTsxFiles = findTsxFiles(directory);\n const standaloneSlidesFiles = findStandaloneSlides(directory);\n const standalonePdfFiles = findPdfFiles(directory);\n\n const folderPosts: PostEntry[] = folders\n .map((folder) => ({\n type: 'folder' as const,\n name: folder.name,\n path: folder.path,\n readme: findReadme(folder),\n slides: findSlides(folder),\n file: null,\n }))\n .filter((post) => post.readme || post.slides); // Only include folders with content\n\n const filePosts: PostEntry[] = standaloneFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n const tsxPosts: PostEntry[] = standaloneTsxFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.tsx$/, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n // Standalone slides files (e.g., getting-started.slides.mdx)\n const slidesPosts: PostEntry[] = standaloneSlidesFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.slides\\.mdx?$/, ''),\n path: file.path,\n readme: null,\n slides: file,\n file: null,\n }));\n\n const pdfPosts: PostEntry[] = standalonePdfFiles.map((file) => ({\n type: 'file' as const,\n name: file.name.replace(/\\.pdf$/i, ''),\n path: file.path,\n readme: null,\n slides: null,\n file,\n }));\n\n return [...folderPosts, ...filePosts, ...tsxPosts, ...slidesPosts, ...pdfPosts];\n}\n\nexport function filterVisiblePosts(posts: PostEntry[]): PostEntry[] {\n return posts.filter((post) => {\n const frontmatter = getFrontmatter(post);\n return frontmatter?.visibility !== \"hidden\" && frontmatter?.draft !== true;\n });\n}\n"],"names":[],"mappings":";AAYO,SAAS,eAAe,MAAiB;;AAC9C,WAAO,UAAK,WAAL,mBAAa,kBAAe,UAAK,SAAL,mBAAW,kBAAe,UAAK,WAAL,mBAAa;AAC5E;AAEO,SAAS,uBAAuB,WAAwC;AAC7E,QAAM,UAAU,UAAU,SAAS,OAAO,CAAC,MAA2B,EAAE,SAAS,WAAW;AAC5F,QAAM,kBAAkB,aAAa,SAAS;AAC9C,QAAM,qBAAqB,aAAa,SAAS;AACjD,QAAM,wBAAwB,qBAAqB,SAAS;AAC5D,QAAM,qBAAqB,aAAa,SAAS;AAEjD,QAAM,cAA2B,QAC9B,IAAI,CAAC,YAAY;AAAA,IAChB,MAAM;AAAA,IACN,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,QAAQ,WAAW,MAAM;AAAA,IACzB,QAAQ,WAAW,MAAM;AAAA,IACzB,MAAM;AAAA,EAAA,EACN,EACD,OAAO,CAAC,SAAS,KAAK,UAAU,KAAK,MAAM;AAE9C,QAAM,YAAyB,gBAAgB,IAAI,CAAC,UAAU;AAAA,IAC5D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAEF,QAAM,WAAwB,mBAAmB,IAAI,CAAC,UAAU;AAAA,IAC9D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,UAAU,EAAE;AAAA,IACpC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAGF,QAAM,cAA2B,sBAAsB,IAAI,CAAC,UAAU;AAAA,IACpE,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,mBAAmB,EAAE;AAAA,IAC7C,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA,EACN;AAEF,QAAM,WAAwB,mBAAmB,IAAI,CAAC,UAAU;AAAA,IAC9D,MAAM;AAAA,IACN,MAAM,KAAK,KAAK,QAAQ,WAAW,EAAE;AAAA,IACrC,MAAM,KAAK;AAAA,IACX,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EAAA,EACA;AAEF,SAAO,CAAC,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,aAAa,GAAG,QAAQ;AAChF;AAEO,SAAS,mBAAmB,OAAiC;AAClE,SAAO,MAAM,OAAO,CAAC,SAAS;AAC5B,UAAM,cAAc,eAAe,IAAI;AACvC,YAAO,2CAAa,gBAAe,aAAY,2CAAa,WAAU;AAAA,EACxE,CAAC;AACH;"}
|