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.
- package/bin/lib/build.ts +2 -1
- package/bin/lib/export.ts +214 -0
- package/bin/lib/serve.ts +2 -1
- package/dist/client/components/front-matter.js +46 -1
- package/dist/client/components/front-matter.js.map +1 -1
- package/dist/client/components/header.js +2 -24
- package/dist/client/components/header.js.map +1 -1
- package/dist/client/components/mdx-components.js +2 -0
- package/dist/client/components/mdx-components.js.map +1 -1
- package/dist/client/components/post-list-item.js +43 -0
- package/dist/client/components/post-list-item.js.map +1 -0
- package/dist/client/components/post-list.js +54 -79
- package/dist/client/components/post-list.js.map +1 -1
- package/dist/client/hooks/use-mdx-content.js +64 -4
- package/dist/client/hooks/use-mdx-content.js.map +1 -1
- package/dist/client/lib/content-classification.js +1 -22
- package/dist/client/lib/content-classification.js.map +1 -1
- package/dist/client/pages/content-router.js +2 -10
- package/dist/client/pages/content-router.js.map +1 -1
- package/dist/client/pages/home.js +20 -23
- package/dist/client/pages/home.js.map +1 -1
- package/dist/client/pages/index-post.js +34 -0
- package/dist/client/pages/index-post.js.map +1 -0
- package/dist/client/pages/post.js +1 -3
- package/dist/client/pages/post.js.map +1 -1
- package/dist/client/pages/slides.js +4 -3
- package/dist/client/pages/slides.js.map +1 -1
- package/package.json +1 -1
- package/plugin/src/plugin.ts +127 -30
- package/plugin/src/types.ts +34 -4
- package/src/components/front-matter.tsx +60 -3
- package/src/components/header.tsx +2 -20
- package/src/components/mdx-components.tsx +3 -1
- package/src/components/post-list-item.tsx +54 -0
- package/src/components/post-list.tsx +74 -116
- package/src/components/welcome.tsx +2 -2
- package/src/hooks/use-mdx-content.ts +96 -7
- package/src/index.css +17 -0
- package/src/lib/content-classification.ts +0 -24
- package/src/pages/content-router.tsx +6 -17
- package/src/pages/home.tsx +26 -58
- package/src/pages/index-post.tsx +59 -0
- package/src/pages/post.tsx +1 -3
- package/src/pages/slides.tsx +5 -3
- package/src/vite-env.d.ts +11 -1
- package/vite.config.ts +4 -3
- package/dist/client/components/running-bar.js +0 -15
- package/dist/client/components/running-bar.js.map +0 -1
- package/src/components/content-tabs.tsx +0 -64
- package/src/components/running-bar.tsx +0 -21
package/bin/lib/build.ts
CHANGED
|
@@ -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
|
@@ -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__ */
|
|
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 )
|
|
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
|
|
6
|
+
import veslxConfig from "virtual:veslx-config";
|
|
7
7
|
import { cn } from "../lib/utils.js";
|
|
8
8
|
function Header({ slideControls } = {}) {
|
|
9
|
-
const config =
|
|
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
|
|
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;"}
|