veslx 0.1.27 → 0.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/bin/lib/build.ts +2 -1
  2. package/bin/lib/export.ts +214 -0
  3. package/bin/lib/serve.ts +2 -1
  4. package/dist/client/components/front-matter.js +46 -1
  5. package/dist/client/components/front-matter.js.map +1 -1
  6. package/dist/client/components/header.js +2 -24
  7. package/dist/client/components/header.js.map +1 -1
  8. package/dist/client/components/mdx-components.js +2 -0
  9. package/dist/client/components/mdx-components.js.map +1 -1
  10. package/dist/client/components/post-list-item.js +43 -0
  11. package/dist/client/components/post-list-item.js.map +1 -0
  12. package/dist/client/components/post-list.js +54 -79
  13. package/dist/client/components/post-list.js.map +1 -1
  14. package/dist/client/hooks/use-mdx-content.js +64 -4
  15. package/dist/client/hooks/use-mdx-content.js.map +1 -1
  16. package/dist/client/lib/content-classification.js +1 -22
  17. package/dist/client/lib/content-classification.js.map +1 -1
  18. package/dist/client/pages/content-router.js +2 -10
  19. package/dist/client/pages/content-router.js.map +1 -1
  20. package/dist/client/pages/home.js +20 -23
  21. package/dist/client/pages/home.js.map +1 -1
  22. package/dist/client/pages/index-post.js +34 -0
  23. package/dist/client/pages/index-post.js.map +1 -0
  24. package/dist/client/pages/post.js +1 -3
  25. package/dist/client/pages/post.js.map +1 -1
  26. package/dist/client/pages/slides.js +4 -3
  27. package/dist/client/pages/slides.js.map +1 -1
  28. package/package.json +1 -1
  29. package/plugin/src/plugin.ts +127 -30
  30. package/plugin/src/types.ts +34 -4
  31. package/src/components/front-matter.tsx +60 -3
  32. package/src/components/header.tsx +2 -20
  33. package/src/components/mdx-components.tsx +3 -1
  34. package/src/components/post-list-item.tsx +54 -0
  35. package/src/components/post-list.tsx +74 -116
  36. package/src/components/welcome.tsx +2 -2
  37. package/src/hooks/use-mdx-content.ts +96 -7
  38. package/src/index.css +17 -0
  39. package/src/lib/content-classification.ts +0 -24
  40. package/src/pages/content-router.tsx +6 -17
  41. package/src/pages/home.tsx +26 -58
  42. package/src/pages/index-post.tsx +59 -0
  43. package/src/pages/post.tsx +1 -3
  44. package/src/pages/slides.tsx +5 -3
  45. package/src/vite-env.d.ts +11 -1
  46. package/vite.config.ts +4 -3
  47. package/dist/client/components/running-bar.js +0 -15
  48. package/dist/client/components/running-bar.js.map +0 -1
  49. package/src/components/content-tabs.tsx +0 -64
  50. package/src/components/running-bar.tsx +0 -21
package/bin/lib/build.ts CHANGED
@@ -84,7 +84,8 @@ export default async function buildApp(dir?: string) {
84
84
  site: {
85
85
  ...defaults.site,
86
86
  ...fileConfig?.site,
87
- }
87
+ },
88
+ slides: fileConfig?.slides,
88
89
  };
89
90
 
90
91
  const veslxRoot = new URL('../..', import.meta.url).pathname;
@@ -0,0 +1,214 @@
1
+ import { createServer, type ViteDevServer } from 'vite'
2
+ import { chromium, type Browser } from 'playwright'
3
+ import { execSync } from 'child_process'
4
+ import importConfig from "./import-config"
5
+ import veslxPlugin from '../../plugin/src/plugin'
6
+ import path from 'path'
7
+ import fs from 'fs'
8
+ import { log } from './log'
9
+
10
+ interface PackageJson {
11
+ name?: string;
12
+ description?: string;
13
+ }
14
+
15
+ interface ExportOptions {
16
+ timeout?: number;
17
+ }
18
+
19
+ async function readPackageJson(cwd: string): Promise<PackageJson | null> {
20
+ const file = Bun.file(path.join(cwd, 'package.json'));
21
+ if (!await file.exists()) return null;
22
+ try {
23
+ return await file.json();
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+
29
+ function getGitHubRepo(cwd: string): string {
30
+ try {
31
+ const remote = execSync('git remote get-url origin', { cwd, encoding: 'utf-8' }).trim();
32
+ const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
33
+ return match ? match[1] : '';
34
+ } catch {
35
+ return '';
36
+ }
37
+ }
38
+
39
+ async function getDefaultConfig(cwd: string) {
40
+ const pkg = await readPackageJson(cwd);
41
+ const folderName = path.basename(cwd);
42
+ const name = pkg?.name || folderName;
43
+
44
+ return {
45
+ dir: '.',
46
+ site: {
47
+ name,
48
+ description: pkg?.description || '',
49
+ github: getGitHubRepo(cwd),
50
+ }
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Detect if a path refers to slides based on file naming conventions
56
+ */
57
+ function isSlides(filePath: string): boolean {
58
+ const filename = path.basename(filePath).toLowerCase()
59
+ return (
60
+ filePath.endsWith('.slides.mdx') ||
61
+ filePath.endsWith('.slides.md') ||
62
+ filename === 'slides.mdx' ||
63
+ filename === 'slides.md'
64
+ )
65
+ }
66
+
67
+ /**
68
+ * Generate default output filename from input path
69
+ */
70
+ function getDefaultOutputPath(inputPath: string): string {
71
+ const baseName = inputPath
72
+ .replace(/\.(mdx|md)$/, '')
73
+ .replace(/\//g, '-')
74
+ .replace(/^-/, '')
75
+ return `${baseName}.pdf`
76
+ }
77
+
78
+ export default async function exportToPdf(
79
+ inputPath: string,
80
+ outputPath?: string,
81
+ options: ExportOptions = {}
82
+ ): Promise<void> {
83
+ const cwd = process.cwd()
84
+
85
+ // Validate input file exists
86
+ const fullInputPath = path.isAbsolute(inputPath)
87
+ ? inputPath
88
+ : path.resolve(cwd, inputPath)
89
+
90
+ if (!fs.existsSync(fullInputPath)) {
91
+ log.error(`File not found: ${inputPath}`)
92
+ process.exit(1)
93
+ }
94
+
95
+ // Validate it's an MDX or MD file
96
+ if (!inputPath.endsWith('.mdx') && !inputPath.endsWith('.md')) {
97
+ log.error('Only .mdx and .md files can be exported')
98
+ process.exit(1)
99
+ }
100
+
101
+ // Determine output path
102
+ const finalOutputPath = outputPath || getDefaultOutputPath(inputPath)
103
+ const fullOutputPath = path.isAbsolute(finalOutputPath)
104
+ ? finalOutputPath
105
+ : path.resolve(cwd, finalOutputPath)
106
+
107
+ // Determine content type
108
+ const contentIsSlides = isSlides(inputPath)
109
+
110
+ log.info(`Exporting ${contentIsSlides ? 'slides' : 'post'}: ${inputPath}`)
111
+
112
+ let server: ViteDevServer | null = null
113
+ let browser: Browser | null = null
114
+
115
+ try {
116
+ // Load config (same pattern as serve.ts)
117
+ const defaults = await getDefaultConfig(cwd)
118
+ let fileConfig = await importConfig(cwd)
119
+
120
+ const config = {
121
+ dir: fileConfig?.dir || defaults.dir,
122
+ site: {
123
+ ...defaults.site,
124
+ ...fileConfig?.site,
125
+ },
126
+ slides: fileConfig?.slides,
127
+ }
128
+
129
+ const veslxRoot = new URL('../..', import.meta.url).pathname
130
+ const configFile = new URL('../../vite.config.ts', import.meta.url).pathname
131
+
132
+ // Resolve content directory
133
+ const contentDir = path.isAbsolute(config.dir)
134
+ ? config.dir
135
+ : path.resolve(cwd, config.dir)
136
+
137
+ // Start dev server on random available port
138
+ server = await createServer({
139
+ root: veslxRoot,
140
+ configFile,
141
+ cacheDir: path.join(cwd, 'node_modules/.vite'),
142
+ plugins: [veslxPlugin(contentDir, config)],
143
+ server: {
144
+ port: 0, // Auto-select available port
145
+ },
146
+ logLevel: 'silent',
147
+ })
148
+
149
+ await server.listen()
150
+
151
+ const serverUrl = server.resolvedUrls?.local[0]
152
+ if (!serverUrl) {
153
+ throw new Error('Failed to get server URL')
154
+ }
155
+
156
+ // Build page URL from relative path
157
+ const relativePath = path.relative(contentDir, fullInputPath)
158
+ const pageUrl = `${serverUrl}${relativePath}`
159
+
160
+ // Launch browser and navigate
161
+ browser = await chromium.launch({ headless: true })
162
+ const page = await browser.newPage()
163
+
164
+ await page.goto(pageUrl, {
165
+ waitUntil: 'networkidle',
166
+ timeout: options.timeout || 30000
167
+ })
168
+
169
+ // Wait for content to render
170
+ await page.waitForTimeout(500)
171
+ try {
172
+ await page.waitForSelector('article, .slides-container', { timeout: 5000 })
173
+ } catch {
174
+ // Content selector not found, continue anyway
175
+ }
176
+
177
+ // Export to PDF with appropriate settings
178
+ const pdfOptions = contentIsSlides
179
+ ? {
180
+ path: fullOutputPath,
181
+ landscape: true,
182
+ printBackground: true,
183
+ preferCSSPageSize: true,
184
+ margin: { top: '0', right: '0', bottom: '0', left: '0' },
185
+ }
186
+ : {
187
+ path: fullOutputPath,
188
+ landscape: false,
189
+ printBackground: true,
190
+ preferCSSPageSize: true,
191
+ margin: { top: '1in', right: '1in', bottom: '1in', left: '1in' },
192
+ }
193
+
194
+ await page.pdf(pdfOptions)
195
+
196
+ log.success(finalOutputPath)
197
+
198
+ } catch (error) {
199
+ if (error instanceof Error) {
200
+ log.error(error.message)
201
+ } else {
202
+ log.error('Export failed')
203
+ }
204
+ process.exit(1)
205
+
206
+ } finally {
207
+ if (browser) {
208
+ await browser.close()
209
+ }
210
+ if (server) {
211
+ await server.close()
212
+ }
213
+ }
214
+ }
package/bin/lib/serve.ts CHANGED
@@ -79,7 +79,8 @@ export default async function serve(dir?: string) {
79
79
  site: {
80
80
  ...defaults.site,
81
81
  ...fileConfig?.site,
82
- }
82
+ },
83
+ slides: fileConfig?.slides,
83
84
  };
84
85
 
85
86
  const veslxRoot = new URL('../..', import.meta.url).pathname;
@@ -1,11 +1,56 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useLocation } from "react-router-dom";
2
3
  import { useFrontmatter } from "../lib/frontmatter-context.js";
3
4
  import { formatDate } from "../lib/format-date.js";
5
+ import veslxConfig from "virtual:veslx-config";
6
+ function convertToLlmsTxt(rawMdx, frontmatter) {
7
+ const contentWithoutFrontmatter = rawMdx.replace(/^---[\s\S]*?---\n*/, "");
8
+ const parts = [];
9
+ const title = (frontmatter == null ? void 0 : frontmatter.title) || "Untitled";
10
+ parts.push(`# ${title}`);
11
+ if (frontmatter == null ? void 0 : frontmatter.description) {
12
+ parts.push("");
13
+ parts.push(`> ${frontmatter.description}`);
14
+ }
15
+ if (contentWithoutFrontmatter.trim()) {
16
+ parts.push("");
17
+ parts.push(contentWithoutFrontmatter.trim());
18
+ }
19
+ return parts.join("\n");
20
+ }
4
21
  function FrontMatter() {
5
22
  const frontmatter = useFrontmatter();
23
+ const location = useLocation();
24
+ const config = veslxConfig.site;
25
+ const rawUrl = `/raw${location.pathname.replace(/^\//, "/")}`;
26
+ const handleLlmsTxt = async (e) => {
27
+ e.preventDefault();
28
+ try {
29
+ const res = await fetch(rawUrl);
30
+ if (!res.ok) throw new Error("Failed to fetch");
31
+ const rawMdx = await res.text();
32
+ const llmsTxt = convertToLlmsTxt(rawMdx, frontmatter);
33
+ const blob = new Blob([llmsTxt], { type: "text/plain" });
34
+ const url = URL.createObjectURL(blob);
35
+ window.location.href = url;
36
+ } catch {
37
+ console.error("Failed to load llms.txt");
38
+ }
39
+ };
6
40
  return /* @__PURE__ */ jsx("div", { children: (frontmatter == null ? void 0 : frontmatter.title) && /* @__PURE__ */ jsxs("header", { className: "not-prose flex flex-col gap-2 mb-8 pt-4", children: [
7
41
  /* @__PURE__ */ jsx("h1", { className: "text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3", children: frontmatter == null ? void 0 : frontmatter.title }),
8
- /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }) }),
42
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 text-muted-foreground", children: [
43
+ (frontmatter == null ? void 0 : frontmatter.date) && /* @__PURE__ */ jsx("time", { className: "font-mono text-xs bg-muted px-2 py-0.5 rounded", children: formatDate(new Date(frontmatter.date)) }),
44
+ config.llmsTxt && /* @__PURE__ */ jsx(
45
+ "a",
46
+ {
47
+ href: "#",
48
+ onClick: handleLlmsTxt,
49
+ className: "font-mono text-xs text-muted-foreground/70 hover:text-foreground underline underline-offset-2 transition-colors",
50
+ children: "llms.txt"
51
+ }
52
+ )
53
+ ] }),
9
54
  (frontmatter == null ? void 0 : frontmatter.description) && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap text-sm items-center gap-3 text-muted-foreground", children: frontmatter == null ? void 0 : frontmatter.description })
10
55
  ] }) });
11
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useFrontmatter } from \"@/lib/frontmatter-context\";\nimport { formatDate } from \"@/lib/format-date\"\n\nexport function FrontMatter(){\n const frontmatter = useFrontmatter();\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n )\n}\n"],"names":[],"mappings":";;;AAGO,SAAS,cAAa;AAC3B,QAAM,cAAc,eAAA;AAEpB,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,wBAGC,OAAA,EAAI,WAAU,2DACZ,WAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,qBAAW,IAAI,KAAK,YAAY,IAAc,CAAC,GAClD,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
1
+ {"version":3,"file":"front-matter.js","sources":["../../../src/components/front-matter.tsx"],"sourcesContent":["import { useLocation } from \"react-router-dom\";\nimport { useFrontmatter } from \"@/lib/frontmatter-context\";\nimport { formatDate } from \"@/lib/format-date\";\nimport veslxConfig from \"virtual:veslx-config\";\n\n/**\n * Convert MDX content to llms.txt format.\n */\nfunction convertToLlmsTxt(\n rawMdx: string,\n frontmatter?: { title?: string; description?: string }\n): string {\n const contentWithoutFrontmatter = rawMdx.replace(/^---[\\s\\S]*?---\\n*/, '')\n\n const parts: string[] = []\n\n const title = frontmatter?.title || 'Untitled'\n parts.push(`# ${title}`)\n\n if (frontmatter?.description) {\n parts.push('')\n parts.push(`> ${frontmatter.description}`)\n }\n\n if (contentWithoutFrontmatter.trim()) {\n parts.push('')\n parts.push(contentWithoutFrontmatter.trim())\n }\n\n return parts.join('\\n')\n}\n\nexport function FrontMatter() {\n const frontmatter = useFrontmatter();\n const location = useLocation();\n const config = veslxConfig.site;\n\n const rawUrl = `/raw${location.pathname.replace(/^\\//, '/')}`;\n\n const handleLlmsTxt = async (e: React.MouseEvent) => {\n e.preventDefault();\n try {\n const res = await fetch(rawUrl);\n if (!res.ok) throw new Error('Failed to fetch');\n const rawMdx = await res.text();\n const llmsTxt = convertToLlmsTxt(rawMdx, frontmatter);\n const blob = new Blob([llmsTxt], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n window.location.href = url;\n } catch {\n console.error('Failed to load llms.txt');\n }\n };\n\n return (\n <div>\n {frontmatter?.title && (\n <header className=\"not-prose flex flex-col gap-2 mb-8 pt-4\">\n <h1 className=\"text-2xl md:text-3xl font-semibold tracking-tight text-foreground mb-3\">\n {frontmatter?.title}\n </h1>\n\n {/* Meta line */}\n <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground\">\n {frontmatter?.date && (\n <time className=\"font-mono text-xs bg-muted px-2 py-0.5 rounded\">\n {formatDate(new Date(frontmatter.date as string))}\n </time>\n )}\n {config.llmsTxt && (\n <a\n href=\"#\"\n onClick={handleLlmsTxt}\n className=\"font-mono text-xs text-muted-foreground/70 hover:text-foreground underline underline-offset-2 transition-colors\"\n >\n llms.txt\n </a>\n )}\n </div>\n\n {frontmatter?.description && (\n <div className=\"flex flex-wrap text-sm items-center gap-3 text-muted-foreground\">\n {frontmatter?.description}\n </div>\n )}\n </header>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":";;;;;AAQA,SAAS,iBACP,QACA,aACQ;AACR,QAAM,4BAA4B,OAAO,QAAQ,sBAAsB,EAAE;AAEzE,QAAM,QAAkB,CAAA;AAExB,QAAM,SAAQ,2CAAa,UAAS;AACpC,QAAM,KAAK,KAAK,KAAK,EAAE;AAEvB,MAAI,2CAAa,aAAa;AAC5B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,YAAY,WAAW,EAAE;AAAA,EAC3C;AAEA,MAAI,0BAA0B,QAAQ;AACpC,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,0BAA0B,MAAM;AAAA,EAC7C;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,cAAc;AAC5B,QAAM,cAAc,eAAA;AACpB,QAAM,WAAW,YAAA;AACjB,QAAM,SAAS,YAAY;AAE3B,QAAM,SAAS,OAAO,SAAS,SAAS,QAAQ,OAAO,GAAG,CAAC;AAE3D,QAAM,gBAAgB,OAAO,MAAwB;AACnD,MAAE,eAAA;AACF,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,UAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,iBAAiB;AAC9C,YAAM,SAAS,MAAM,IAAI,KAAA;AACzB,YAAM,UAAU,iBAAiB,QAAQ,WAAW;AACpD,YAAM,OAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,cAAc;AACvD,YAAM,MAAM,IAAI,gBAAgB,IAAI;AACpC,aAAO,SAAS,OAAO;AAAA,IACzB,QAAQ;AACN,cAAQ,MAAM,yBAAyB;AAAA,IACzC;AAAA,EACF;AAEA,6BACG,OAAA,EACE,WAAA,2CAAa,UACZ,qBAAC,UAAA,EAAO,WAAU,2CAChB,UAAA;AAAA,IAAA,oBAAC,MAAA,EAAG,WAAU,0EACX,UAAA,2CAAa,OAChB;AAAA,IAGA,qBAAC,OAAA,EAAI,WAAU,2DACZ,UAAA;AAAA,OAAA,2CAAa,SACZ,oBAAC,QAAA,EAAK,WAAU,kDACb,UAAA,WAAW,IAAI,KAAK,YAAY,IAAc,CAAC,EAAA,CAClD;AAAA,MAED,OAAO,WACN;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACX,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IAED,GAEJ;AAAA,KAEC,2CAAa,gBACZ,oBAAC,SAAI,WAAU,mEACZ,qDAAa,YAAA,CAChB;AAAA,EAAA,EAAA,CAEJ,EAAA,CAEJ;AAEJ;"}
@@ -3,10 +3,10 @@ import { useParams, Link } from "react-router-dom";
3
3
  import { ModeToggle } from "./mode-toggle.js";
4
4
  import { SiGithub } from "@icons-pack/react-simple-icons";
5
5
  import { ChevronUp, ChevronDown } from "lucide-react";
6
- import siteConfig from "virtual:veslx-config";
6
+ import veslxConfig from "virtual:veslx-config";
7
7
  import { cn } from "../lib/utils.js";
8
8
  function Header({ slideControls } = {}) {
9
- const config = siteConfig;
9
+ const config = veslxConfig.site;
10
10
  const { "*": path } = useParams();
11
11
  return /* @__PURE__ */ jsx("header", { className: cn(
12
12
  "print:hidden",
@@ -50,28 +50,6 @@ function Header({ slideControls } = {}) {
50
50
  }
51
51
  )
52
52
  ] }),
53
- /* @__PURE__ */ jsx(
54
- Link,
55
- {
56
- to: `/posts`,
57
- className: cn(
58
- "font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
59
- (path == null ? void 0 : path.startsWith("posts")) && "font-semibold text-foreground"
60
- ),
61
- children: "Posts"
62
- }
63
- ),
64
- /* @__PURE__ */ jsx(
65
- Link,
66
- {
67
- to: `/docs`,
68
- className: cn(
69
- "font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300",
70
- (path == null ? void 0 : path.startsWith("docs")) && "font-semibold text-foreground"
71
- ),
72
- children: "Docs"
73
- }
74
- ),
75
53
  config.github && /* @__PURE__ */ jsx(
76
54
  Link,
77
55
  {
@@ -1 +1 @@
1
- {"version":3,"file":"header.js","sources":["../../../src/components/header.tsx"],"sourcesContent":["import { Link, useParams } from \"react-router-dom\";\nimport { ModeToggle } from \"./mode-toggle\";\nimport { SiGithub } from \"@icons-pack/react-simple-icons\";\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport siteConfig from \"virtual:veslx-config\";\nimport { cn } from \"@/lib/utils\";\n\ninterface HeaderProps {\n slideControls?: {\n current: number;\n total: number;\n onPrevious: () => void;\n onNext: () => void;\n };\n}\n\nexport function Header({ slideControls }: HeaderProps = {}) {\n const config = siteConfig;\n\n const { \"*\": path } = useParams()\n\n return (\n <header className={cn(\n \"print:hidden\",\n slideControls && \"fixed top-0 left-0 right-0 z-40\"\n )}>\n <div className={cn(\n \"mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4\",\n !slideControls && \"max-w-[var(--content-width)]\"\n )}>\n <nav className=\"flex items-center gap-1\">\n <Link\n to=\"/\"\n className=\"rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline\"\n >\n {config.name}\n </Link>\n </nav>\n\n <div className=\"flex-1\" />\n\n {/* Navigation */}\n <nav className=\"flex items-center gap-4\">\n {slideControls && (\n <>\n <button\n onClick={slideControls.onPrevious}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Previous slide (↑)\"\n >\n <ChevronUp className=\"h-4 w-4\" />\n </button>\n <span className=\"font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center\">\n {slideControls.current + 1}/{slideControls.total}\n </span>\n <button\n onClick={slideControls.onNext}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Next slide (↓)\"\n >\n <ChevronDown className=\"h-4 w-4\" />\n </button>\n </>\n )}\n <Link\n to={`/posts`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"posts\") && \"font-semibold text-foreground\",\n )}\n >\n Posts\n </Link>\n <Link\n to={`/docs`}\n className={cn(\n \"font-medium text-muted-foreground/70 hover:text-foreground transition-colors duration-300\",\n path?.startsWith(\"docs\") && \"font-semibold text-foreground\",\n )}\n >\n Docs\n </Link>\n {config.github && (\n <Link\n to={`https://github.com/${config.github}`}\n target=\"_blank\"\n className=\"text-muted-foreground/70 hover:text-foreground transition-colors duration-300\"\n aria-label=\"GitHub\"\n >\n <SiGithub className=\"h-4 w-4\" />\n </Link>\n )}\n <ModeToggle />\n </nav>\n </div>\n </header>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAgBO,SAAS,OAAO,EAAE,cAAA,IAA+B,IAAI;AAC1D,QAAM,SAAS;AAEf,QAAM,EAAE,KAAK,KAAA,IAAS,UAAA;AAEtB,SACE,oBAAC,YAAO,WAAW;AAAA,IACjB;AAAA,IACA,iBAAiB;AAAA,EAAA,GAEjB,UAAA,qBAAC,OAAA,EAAI,WAAW;AAAA,IACd;AAAA,IACA,CAAC,iBAAiB;AAAA,EAAA,GAElB,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,WAAU;AAAA,QAET,UAAA,OAAO;AAAA,MAAA;AAAA,IAAA,GAEZ;AAAA,IAEA,oBAAC,OAAA,EAAI,WAAU,SAAA,CAAS;AAAA,IAGxB,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,iBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEjC,qBAAC,QAAA,EAAK,WAAU,mFACb,UAAA;AAAA,UAAA,cAAc,UAAU;AAAA,UAAE;AAAA,UAAE,cAAc;AAAA,QAAA,GAC7C;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACnC,GACF;AAAA,MAEF;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,aAAY;AAAA,UAAA;AAAA,UAEhC,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGD;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,aACA,6BAAM,WAAW,YAAW;AAAA,UAAA;AAAA,UAE/B,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,MAGA,OAAO,UACN;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,sBAAsB,OAAO,MAAM;AAAA,UACvC,QAAO;AAAA,UACP,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA,oBAAC,UAAA,EAAS,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,0BAGjC,YAAA,CAAA,CAAW;AAAA,IAAA,EAAA,CACd;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
1
+ {"version":3,"file":"header.js","sources":["../../../src/components/header.tsx"],"sourcesContent":["import { Link, useParams } from \"react-router-dom\";\nimport { ModeToggle } from \"./mode-toggle\";\nimport { SiGithub } from \"@icons-pack/react-simple-icons\";\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport veslxConfig from \"virtual:veslx-config\";\nimport { cn } from \"@/lib/utils\";\n\ninterface HeaderProps {\n slideControls?: {\n current: number;\n total: number;\n onPrevious: () => void;\n onNext: () => void;\n };\n}\n\nexport function Header({ slideControls }: HeaderProps = {}) {\n const config = veslxConfig.site;\n\n const { \"*\": path } = useParams()\n\n return (\n <header className={cn(\n \"print:hidden\",\n slideControls && \"fixed top-0 left-0 right-0 z-40\"\n )}>\n <div className={cn(\n \"mx-auto w-full px-[var(--page-padding)] flex items-center gap-8 py-4\",\n !slideControls && \"max-w-[var(--content-width)]\"\n )}>\n <nav className=\"flex items-center gap-1\">\n <Link\n to=\"/\"\n className=\"rounded-lg font-mono py-1.5 text-sm font-medium text-muted-foreground hover:underline\"\n >\n {config.name}\n </Link>\n </nav>\n\n <div className=\"flex-1\" />\n\n {/* Navigation */}\n <nav className=\"flex items-center gap-4\">\n {slideControls && (\n <>\n <button\n onClick={slideControls.onPrevious}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Previous slide (↑)\"\n >\n <ChevronUp className=\"h-4 w-4\" />\n </button>\n <span className=\"font-mono text-xs text-muted-foreground/70 tabular-nums min-w-[3ch] text-center\">\n {slideControls.current + 1}/{slideControls.total}\n </span>\n <button\n onClick={slideControls.onNext}\n className=\"p-1.5 text-muted-foreground/70 hover:text-foreground transition-colors duration-200\"\n title=\"Next slide (↓)\"\n >\n <ChevronDown className=\"h-4 w-4\" />\n </button>\n </>\n )}\n {config.github && (\n <Link\n to={`https://github.com/${config.github}`}\n target=\"_blank\"\n className=\"text-muted-foreground/70 hover:text-foreground transition-colors duration-300\"\n aria-label=\"GitHub\"\n >\n <SiGithub className=\"h-4 w-4\" />\n </Link>\n )}\n <ModeToggle />\n </nav>\n </div>\n </header>\n );\n}\n"],"names":[],"mappings":";;;;;;;AAgBO,SAAS,OAAO,EAAE,cAAA,IAA+B,IAAI;AAC1D,QAAM,SAAS,YAAY;AAE3B,QAAM,EAAE,KAAK,KAAA,IAAS,UAAA;AAEtB,SACE,oBAAC,YAAO,WAAW;AAAA,IACjB;AAAA,IACA,iBAAiB;AAAA,EAAA,GAEjB,UAAA,qBAAC,OAAA,EAAI,WAAW;AAAA,IACd;AAAA,IACA,CAAC,iBAAiB;AAAA,EAAA,GAElB,UAAA;AAAA,IAAA,oBAAC,OAAA,EAAI,WAAU,2BACb,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,IAAG;AAAA,QACH,WAAU;AAAA,QAET,UAAA,OAAO;AAAA,MAAA;AAAA,IAAA,GAEZ;AAAA,IAEA,oBAAC,OAAA,EAAI,WAAU,SAAA,CAAS;AAAA,IAGxB,qBAAC,OAAA,EAAI,WAAU,2BACZ,UAAA;AAAA,MAAA,iBACC,qBAAA,UAAA,EACE,UAAA;AAAA,QAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,WAAA,EAAU,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,QAEjC,qBAAC,QAAA,EAAK,WAAU,mFACb,UAAA;AAAA,UAAA,cAAc,UAAU;AAAA,UAAE;AAAA,UAAE,cAAc;AAAA,QAAA,GAC7C;AAAA,QACA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,SAAS,cAAc;AAAA,YACvB,WAAU;AAAA,YACV,OAAM;AAAA,YAEN,UAAA,oBAAC,aAAA,EAAY,WAAU,UAAA,CAAU;AAAA,UAAA;AAAA,QAAA;AAAA,MACnC,GACF;AAAA,MAED,OAAO,UACN;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,IAAI,sBAAsB,OAAO,MAAM;AAAA,UACvC,QAAO;AAAA,UACP,WAAU;AAAA,UACV,cAAW;AAAA,UAEX,UAAA,oBAAC,UAAA,EAAS,WAAU,UAAA,CAAU;AAAA,QAAA;AAAA,MAAA;AAAA,0BAGjC,YAAA,CAAA,CAAW;AAAA,IAAA,EAAA,CACd;AAAA,EAAA,EAAA,CACF,EAAA,CACF;AAEJ;"}
@@ -8,6 +8,7 @@ import { HeroSlide } from "./slides/hero-slide.js";
8
8
  import { FigureSlide } from "./slides/figure-slide.js";
9
9
  import { TextSlide } from "./slides/text-slide.js";
10
10
  import { SlideOutline } from "./slides/slide-outline.js";
11
+ import { PostList } from "./post-list.js";
11
12
  function SmartLink({ href, children, ...props }) {
12
13
  const location = useLocation();
13
14
  const isExternal = (href == null ? void 0 : href.startsWith("http")) || (href == null ? void 0 : href.startsWith("mailto:")) || (href == null ? void 0 : href.startsWith("tel:"));
@@ -57,6 +58,7 @@ const mdxComponents = {
57
58
  FigureSlide,
58
59
  TextSlide,
59
60
  SlideOutline,
61
+ PostList,
60
62
  // Headings - clean sans-serif
61
63
  h1: (props) => {
62
64
  const id = generateId(props.children);
@@ -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 { 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'\n\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,\n\n Gallery,\n\n ParameterTable,\n\n ParameterBadge,\n\n HeroSlide,\n\n FigureSlide,\n\n TextSlide,\n\n SlideOutline,\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/50 border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: (props: React.HTMLAttributes<HTMLElement> & { className?: string }) => {\n const isInline = !props.className?.includes('language-')\n if (isInline) {\n return (\n <code\n className=\"font-mono text-[0.85em] bg-muted px-1.5 py-0.5 rounded text-primary\"\n {...props}\n />\n )\n }\n return <code {...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 { ParameterTable } from '@/components/parameter-table'\nimport { ParameterBadge } from '@/components/parameter-badge'\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'\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,\n\n Gallery,\n\n ParameterTable,\n\n ParameterBadge,\n\n HeroSlide,\n\n FigureSlide,\n\n TextSlide,\n\n SlideOutline,\n\n PostList,\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/50 border border-border rounded-md font-mono my-6\"\n {...props}\n />\n ),\n code: (props: React.HTMLAttributes<HTMLElement> & { className?: string }) => {\n const isInline = !props.className?.includes('language-')\n if (isInline) {\n return (\n <code\n className=\"font-mono text-[0.85em] bg-muted px-1.5 py-0.5 rounded text-primary\"\n {...props}\n />\n )\n }\n return <code {...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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"}
@@ -0,0 +1,43 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Link } from "react-router-dom";
3
+ import { cn } from "../lib/utils.js";
4
+ import { formatDate } from "../lib/format-date.js";
5
+ import { ArrowRight, Presentation } from "lucide-react";
6
+ function PostListItem({ title, description, date, linkPath, isSlides }) {
7
+ return /* @__PURE__ */ jsx(
8
+ Link,
9
+ {
10
+ to: linkPath,
11
+ className: cn(
12
+ "group block py-3 px-3 -mx-3 rounded-md",
13
+ "transition-colors duration-150"
14
+ ),
15
+ children: /* @__PURE__ */ jsxs("article", { className: "flex items-center gap-4", children: [
16
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
17
+ /* @__PURE__ */ jsxs("div", { className: cn(
18
+ "text-sm font-medium text-foreground",
19
+ "group-hover:underline",
20
+ "flex items-center gap-2"
21
+ ), children: [
22
+ /* @__PURE__ */ jsx("span", { children: title }),
23
+ /* @__PURE__ */ jsx(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" })
24
+ ] }),
25
+ description && /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground line-clamp-1 mt-0.5", children: description })
26
+ ] }),
27
+ isSlides && /* @__PURE__ */ jsx(Presentation, { className: "h-3 w-3 text-muted-foreground" }),
28
+ /* @__PURE__ */ jsx(
29
+ "time",
30
+ {
31
+ dateTime: date == null ? void 0 : date.toISOString(),
32
+ className: "font-mono text-xs text-muted-foreground tabular-nums w-20 flex-shrink-0",
33
+ children: date && formatDate(date)
34
+ }
35
+ )
36
+ ] })
37
+ }
38
+ );
39
+ }
40
+ export {
41
+ PostListItem
42
+ };
43
+ //# sourceMappingURL=post-list-item.js.map
@@ -0,0 +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 linkPath: string;\n isSlides?: boolean;\n}\n\nexport function PostListItem({ title, description, date, linkPath, isSlides }: PostListItemProps) {\n return (\n <Link\n to={linkPath}\n className={cn(\n \"group block py-3 px-3 -mx-3 rounded-md\",\n \"transition-colors duration-150\",\n )}\n >\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 </Link>\n );\n}\n"],"names":[],"mappings":";;;;;AAaO,SAAS,aAAa,EAAE,OAAO,aAAa,MAAM,UAAU,YAA+B;AAChG,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,IAAI;AAAA,MACJ,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAGF,UAAA,qBAAC,WAAA,EAAQ,WAAU,2BAEjB,UAAA;AAAA,QAAA,qBAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,UAAA,qBAAC,SAAI,WAAW;AAAA,YACd;AAAA,YACA;AAAA,YACA;AAAA,UAAA,GAEA,UAAA;AAAA,YAAA,oBAAC,UAAM,UAAA,MAAA,CAAM;AAAA,YACb,oBAAC,YAAA,EAAW,WAAU,8HAAA,CAA8H;AAAA,UAAA,GACtJ;AAAA,UAEC,eACC,oBAAC,OAAA,EAAI,WAAU,qDACZ,UAAA,YAAA,CACH;AAAA,QAAA,GAEJ;AAAA,QAEC,YACC,oBAAC,cAAA,EAAa,WAAU,gCAAA,CAAgC;AAAA,QAE1D;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,UAAU,6BAAM;AAAA,YAChB,WAAU;AAAA,YAET,UAAA,QAAQ,WAAW,IAAI;AAAA,UAAA;AAAA,QAAA;AAAA,MAC1B,EAAA,CACF;AAAA,IAAA;AAAA,EAAA;AAGN;"}