veslx 0.0.1
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 +3 -0
- package/bin/lib/import-config.ts +13 -0
- package/bin/lib/init.ts +31 -0
- package/bin/lib/serve.ts +35 -0
- package/bin/lib/start.ts +40 -0
- package/bin/lib/stop.ts +24 -0
- package/bin/vesl.ts +41 -0
- package/components.json +20 -0
- package/eslint.config.js +23 -0
- package/index.html +17 -0
- package/package.json +89 -0
- package/plugin/README.md +21 -0
- package/plugin/package.json +26 -0
- package/plugin/src/cli.ts +30 -0
- package/plugin/src/client.tsx +224 -0
- package/plugin/src/lib.ts +268 -0
- package/plugin/src/plugin.ts +109 -0
- package/postcss.config.js +5 -0
- package/public/logo_dark.png +0 -0
- package/public/logo_light.png +0 -0
- package/src/App.tsx +21 -0
- package/src/components/front-matter.tsx +53 -0
- package/src/components/gallery/components/figure-caption.tsx +15 -0
- package/src/components/gallery/components/figure-header.tsx +20 -0
- package/src/components/gallery/components/lightbox.tsx +106 -0
- package/src/components/gallery/components/loading-image.tsx +48 -0
- package/src/components/gallery/hooks/use-gallery-images.ts +103 -0
- package/src/components/gallery/hooks/use-lightbox.ts +40 -0
- package/src/components/gallery/index.tsx +134 -0
- package/src/components/gallery/lib/render-math-in-text.tsx +47 -0
- package/src/components/header.tsx +68 -0
- package/src/components/index.ts +5 -0
- package/src/components/loading.tsx +16 -0
- package/src/components/mdx-components.tsx +163 -0
- package/src/components/mode-toggle.tsx +44 -0
- package/src/components/page-error.tsx +59 -0
- package/src/components/parameter-badge.tsx +78 -0
- package/src/components/parameter-table.tsx +420 -0
- package/src/components/post-list.tsx +148 -0
- package/src/components/running-bar.tsx +21 -0
- package/src/components/runtime-mdx.tsx +82 -0
- package/src/components/slide.tsx +11 -0
- package/src/components/theme-provider.tsx +6 -0
- package/src/components/ui/badge.tsx +36 -0
- package/src/components/ui/breadcrumb.tsx +115 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.tsx +79 -0
- package/src/components/ui/carousel.tsx +260 -0
- package/src/components/ui/dropdown-menu.tsx +198 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/kbd.tsx +22 -0
- package/src/components/ui/select.tsx +158 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/shadcn-io/code-block/index.tsx +620 -0
- package/src/components/ui/shadcn-io/code-block/server.tsx +63 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/components/welcome.tsx +21 -0
- package/src/hooks/use-key-bindings.ts +72 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.css +279 -0
- package/src/lib/constants.ts +10 -0
- package/src/lib/format-date.tsx +6 -0
- package/src/lib/format-file-size.ts +10 -0
- package/src/lib/parameter-utils.ts +134 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +10 -0
- package/src/pages/home.tsx +39 -0
- package/src/pages/post.tsx +65 -0
- package/src/pages/slides.tsx +173 -0
- package/tailwind.config.js +136 -0
- package/test-content/.vesl.json +49 -0
- package/test-content/README.md +33 -0
- package/test-content/test-post/README.mdx +7 -0
- package/test-content/test-slides/SLIDES.mdx +8 -0
- package/tsconfig.app.json +32 -0
- package/tsconfig.json +15 -0
- package/tsconfig.node.json +25 -0
- package/vesl.config.ts +4 -0
- package/vite.config.ts +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
export default async function importConfig(root: string) {
|
|
4
|
+
const file = Bun.file(`${root}/veslx.config.ts`);
|
|
5
|
+
|
|
6
|
+
if (!await file.exists()) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const module = await import(`file://${root}/veslx.config.ts`);
|
|
11
|
+
const config = module.default;
|
|
12
|
+
return config
|
|
13
|
+
}
|
package/bin/lib/init.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createPrompt } from "bun-promptx";
|
|
2
|
+
|
|
3
|
+
export default async function createNewConfig() {
|
|
4
|
+
|
|
5
|
+
const path = "veslx.config.ts"
|
|
6
|
+
|
|
7
|
+
if (await Bun.file(path).exists()) {
|
|
8
|
+
console.error(`Configuration file '${path}' already exists in the current directory.`);
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
console.log("Initializing a new veslx project...");
|
|
13
|
+
|
|
14
|
+
const dir = createPrompt("Enter dir: ");
|
|
15
|
+
if (dir.error) {
|
|
16
|
+
console.error("Failed to read dir");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log("You entered:", dir.value);
|
|
21
|
+
|
|
22
|
+
const configStr = `
|
|
23
|
+
export default {
|
|
24
|
+
dir: '${dir.value}',
|
|
25
|
+
}`
|
|
26
|
+
|
|
27
|
+
await Bun.write(path, configStr.trim());
|
|
28
|
+
|
|
29
|
+
console.log("Created 'veslx.config.ts' in the current directory.");
|
|
30
|
+
console.log("You can now run 'veslx serve' to start the development server.");
|
|
31
|
+
}
|
package/bin/lib/serve.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
|
|
2
|
+
import { createServer } from 'vite'
|
|
3
|
+
import importConfig from "./import-config";
|
|
4
|
+
import veslxPlugin from '../../plugin/src/plugin'
|
|
5
|
+
|
|
6
|
+
export default async function startServer() {
|
|
7
|
+
const cwd = process.cwd()
|
|
8
|
+
|
|
9
|
+
console.log(`Starting veslx server in ${cwd}`);
|
|
10
|
+
|
|
11
|
+
const config = await importConfig(cwd);
|
|
12
|
+
|
|
13
|
+
if (!config) {
|
|
14
|
+
console.error("Configuration file 'veslx.config.ts' not found in the current directory.");
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const veslxRoot = new URL('../..', import.meta.url).pathname;
|
|
19
|
+
|
|
20
|
+
const server = await createServer({
|
|
21
|
+
root: veslxRoot,
|
|
22
|
+
configFile: new URL('../../vite.config.ts', import.meta.url).pathname,
|
|
23
|
+
plugins: [
|
|
24
|
+
veslxPlugin(config.dir)
|
|
25
|
+
],
|
|
26
|
+
env: {
|
|
27
|
+
cwd,
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
await server.listen()
|
|
32
|
+
|
|
33
|
+
server.printUrls()
|
|
34
|
+
server.bindCLIShortcuts({ print: true })
|
|
35
|
+
}
|
package/bin/lib/start.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import importConfig from "./import-config"
|
|
2
|
+
import pm2 from "pm2";
|
|
3
|
+
|
|
4
|
+
export default async function start() {
|
|
5
|
+
const config = await importConfig(process.cwd());
|
|
6
|
+
|
|
7
|
+
if (!config) {
|
|
8
|
+
console.error("Configuration file 'veslx.config.ts' not found in the current directory.");
|
|
9
|
+
return
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const name = `veslx-${cwd.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
|
|
14
|
+
|
|
15
|
+
pm2.connect((err) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
console.error('PM2 connect error:', err);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
pm2.start({
|
|
22
|
+
name: name,
|
|
23
|
+
script: 'bunx',
|
|
24
|
+
args: ['veslx', 'serve'],
|
|
25
|
+
cwd: cwd,
|
|
26
|
+
autorestart: true,
|
|
27
|
+
watch: false,
|
|
28
|
+
max_memory_restart: '200M'
|
|
29
|
+
}, (err, apps) => {
|
|
30
|
+
pm2.disconnect(); // Disconnects from PM2
|
|
31
|
+
|
|
32
|
+
if (err) {
|
|
33
|
+
console.error('Failed to start daemon:', err);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`veslx daemon started in ${cwd}`);
|
|
38
|
+
});
|
|
39
|
+
})
|
|
40
|
+
}
|
package/bin/lib/stop.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import pm2 from "pm2";
|
|
2
|
+
|
|
3
|
+
export default async function start() {
|
|
4
|
+
const cwd = process.cwd();
|
|
5
|
+
const name = `veslx-${cwd.replace(/\//g, '-').replace(/^-/, '')}`.toLowerCase();
|
|
6
|
+
|
|
7
|
+
pm2.connect((err) => {
|
|
8
|
+
if (err) {
|
|
9
|
+
console.error('PM2 connect error:', err);
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
pm2.stop(name, (err, apps) => {
|
|
14
|
+
pm2.disconnect(); // Disconnects from PM2
|
|
15
|
+
|
|
16
|
+
if (err) {
|
|
17
|
+
console.error('Failed to stop daemon:', err);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`veslx daemon stopped as "${name}" in ${cwd}`);
|
|
22
|
+
});
|
|
23
|
+
})
|
|
24
|
+
}
|
package/bin/vesl.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { cac } from "cac";
|
|
4
|
+
import pkg from "../package.json" assert { type: "json" };
|
|
5
|
+
import init from "./lib/init";
|
|
6
|
+
import serve from "./lib/serve";
|
|
7
|
+
import start from "./lib/start";
|
|
8
|
+
import stop from "./lib/stop";
|
|
9
|
+
|
|
10
|
+
const cli = cac("veslx");
|
|
11
|
+
|
|
12
|
+
console.log(`▗▖ ▗▖▗▄▄▄▖ ▗▄▄▖▗▖
|
|
13
|
+
▐▌ ▐▌▐▌ ▐▌ ▐▌
|
|
14
|
+
▐▌ ▐▌▐▛▀▀▘ ▝▀▚▖▐▌
|
|
15
|
+
▝▚▞▘ ▐▙▄▄▖▗▄▄▞▘▐▙▄▄▖
|
|
16
|
+
`);
|
|
17
|
+
|
|
18
|
+
cli
|
|
19
|
+
.command("init", "Initialize a new veslx project")
|
|
20
|
+
.action(init)
|
|
21
|
+
|
|
22
|
+
cli
|
|
23
|
+
.command("serve", "Start the veslx server")
|
|
24
|
+
.action(serve);
|
|
25
|
+
|
|
26
|
+
cli
|
|
27
|
+
.command("start", "Start the veslx server as a deamon")
|
|
28
|
+
.action(start);
|
|
29
|
+
|
|
30
|
+
cli
|
|
31
|
+
.command("stop", "Stop the veslx deamon")
|
|
32
|
+
.action(stop);
|
|
33
|
+
|
|
34
|
+
cli.help();
|
|
35
|
+
cli.version(pkg.version);
|
|
36
|
+
cli.parse();
|
|
37
|
+
|
|
38
|
+
if (!cli.matchedCommand && process.argv.length <= 2) {
|
|
39
|
+
cli.outputHelp();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
package/components.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "default",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "tailwind.config.js",
|
|
8
|
+
"css": "src/index.css",
|
|
9
|
+
"baseColor": "slate",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "src/components",
|
|
15
|
+
"utils": "src/lib/utils",
|
|
16
|
+
"ui": "src/components/ui",
|
|
17
|
+
"lib": "src/lib",
|
|
18
|
+
"hooks": "src/hooks"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
5
|
+
import tseslint from 'typescript-eslint'
|
|
6
|
+
import { defineConfig, globalIgnores } from 'eslint/config'
|
|
7
|
+
|
|
8
|
+
export default defineConfig([
|
|
9
|
+
globalIgnores(['dist']),
|
|
10
|
+
{
|
|
11
|
+
files: ['**/*.{ts,tsx}'],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs.flat.recommended,
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
])
|
package/index.html
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/logo_dark.png" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Pinglab</title>
|
|
8
|
+
<!-- Google Fonts: DM Sans + DM Mono -->
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&display=swap" rel="stylesheet">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "veslx",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"veslx": "bin/veslx.ts"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "vite",
|
|
10
|
+
"build": "vite build",
|
|
11
|
+
"typecheck": "tsc -b",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"preview": "vite preview",
|
|
14
|
+
"start": "vite preview"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@icons-pack/react-simple-icons": "^13.8.0",
|
|
18
|
+
"@mdx-js/react": "^3.1.1",
|
|
19
|
+
"@radix-ui/react-collapsible": "^1.1.12",
|
|
20
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
21
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
22
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
23
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
24
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
25
|
+
"@radix-ui/react-separator": "^1.1.8",
|
|
26
|
+
"@radix-ui/react-slider": "^1.3.6",
|
|
27
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
28
|
+
"@radix-ui/react-tooltip": "^1.2.8",
|
|
29
|
+
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
|
30
|
+
"@tailwindcss/vite": "^4.1.17",
|
|
31
|
+
"@types/bun": "^1.3.4",
|
|
32
|
+
"@types/js-yaml": "^4.0.9",
|
|
33
|
+
"@visx/axis": "^3.12.0",
|
|
34
|
+
"@visx/grid": "^3.12.0",
|
|
35
|
+
"@visx/group": "^3.12.0",
|
|
36
|
+
"@visx/scale": "^3.12.0",
|
|
37
|
+
"@visx/shape": "^3.12.0",
|
|
38
|
+
"bun-promptx": "^0.2.0",
|
|
39
|
+
"cac": "^6.7.14",
|
|
40
|
+
"date-fns": "^4.1.0",
|
|
41
|
+
"embla-carousel-react": "^8.6.0",
|
|
42
|
+
"install": "^0.13.0",
|
|
43
|
+
"js-yaml": "^4.1.1",
|
|
44
|
+
"katex": "^0.16.25",
|
|
45
|
+
"minimatch": "^10.1.1",
|
|
46
|
+
"next-themes": "^0.4.6",
|
|
47
|
+
"pm2": "^6.0.14",
|
|
48
|
+
"react": "^19.2.0",
|
|
49
|
+
"react-dom": "^19.2.0",
|
|
50
|
+
"react-router-dom": "^7.9.5",
|
|
51
|
+
"react-use": "^17.6.0",
|
|
52
|
+
"rehype-katex": "^7.0.1",
|
|
53
|
+
"remark-frontmatter": "^5.0.0",
|
|
54
|
+
"remark-gfm": "^4.0.1",
|
|
55
|
+
"remark-math": "^6.0.0",
|
|
56
|
+
"remark-mdx-frontmatter": "^5.2.0",
|
|
57
|
+
"shiki": "^3.15.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@eslint/js": "^9.39.1",
|
|
61
|
+
"@mdx-js/rollup": "^3.1.1",
|
|
62
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
63
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
64
|
+
"@types/node": "^25.0.0",
|
|
65
|
+
"@types/react": "^19.2.2",
|
|
66
|
+
"@types/react-dom": "^19.2.2",
|
|
67
|
+
"@vitejs/plugin-react": "^5.1.0",
|
|
68
|
+
"autoprefixer": "^10.4.22",
|
|
69
|
+
"chokidar": "^4.0.3",
|
|
70
|
+
"class-variance-authority": "^0.7.1",
|
|
71
|
+
"clsx": "^2.1.1",
|
|
72
|
+
"eslint": "^9.39.1",
|
|
73
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
74
|
+
"eslint-plugin-react-refresh": "^0.4.24",
|
|
75
|
+
"globals": "^16.5.0",
|
|
76
|
+
"gray-matter": "^4.0.3",
|
|
77
|
+
"lucide-react": "^0.554.0",
|
|
78
|
+
"postcss": "^8.5.6",
|
|
79
|
+
"tailwind-merge": "^3.4.0",
|
|
80
|
+
"tailwindcss": "^4.1.17",
|
|
81
|
+
"tailwindcss-animate": "^1.0.7",
|
|
82
|
+
"typescript": "~5.9.3",
|
|
83
|
+
"typescript-eslint": "^8.46.3",
|
|
84
|
+
"vite": "npm:rolldown-vite@7.2.2"
|
|
85
|
+
},
|
|
86
|
+
"overrides": {
|
|
87
|
+
"vite": "npm:rolldown-vite@7.2.2"
|
|
88
|
+
}
|
|
89
|
+
}
|
package/plugin/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# bun-react-template
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To start a development server:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
To run for production:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun start
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This project was created using `bun init` in bun v1.3.2. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veslx/vite-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/plugin.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/plugin.ts",
|
|
9
|
+
"./lib": "./src/lib.ts"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "bun --hot src/index.ts",
|
|
13
|
+
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
|
|
14
|
+
"start": "NODE_ENV=production bun src/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"gray-matter": "^4.0.3",
|
|
18
|
+
"react": "^19",
|
|
19
|
+
"react-dom": "^19"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/react": "^19",
|
|
23
|
+
"@types/react-dom": "^19",
|
|
24
|
+
"@types/bun": "latest"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { parseArgs } from "util";
|
|
2
|
+
import { buildAll } from "./lib";
|
|
3
|
+
|
|
4
|
+
async function cli(): Promise<void> {
|
|
5
|
+
|
|
6
|
+
const { values } = parseArgs({
|
|
7
|
+
args: Bun.argv,
|
|
8
|
+
options: {
|
|
9
|
+
dir: {
|
|
10
|
+
type: "string",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
strict: true,
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (!values.dir) {
|
|
18
|
+
throw new Error("Content directory is required. Use --dir to specify.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
await buildAll([values.dir]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Run if executed directly
|
|
26
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
27
|
+
cli()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { cli };
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { DirectoryEntry, FileEntry } from "./lib";
|
|
3
|
+
|
|
4
|
+
async function parsePath(directory: DirectoryEntry, path: string): Promise<{ directory: DirectoryEntry; file: FileEntry | null }> {
|
|
5
|
+
const parts = path === "." ? [] : path.split("/").filter(Boolean);
|
|
6
|
+
|
|
7
|
+
let file = null;
|
|
8
|
+
let currentDir = directory;
|
|
9
|
+
|
|
10
|
+
for (let i = 0; i < parts.length; i++) {
|
|
11
|
+
const part = parts[i];
|
|
12
|
+
const isLastPart = i === parts.length - 1;
|
|
13
|
+
|
|
14
|
+
// Check if this part matches a file (only on last part)
|
|
15
|
+
if (isLastPart) {
|
|
16
|
+
const matchedFile = currentDir.children.find(
|
|
17
|
+
(child) => child.type === "file" && child.name === part
|
|
18
|
+
) as FileEntry | undefined;
|
|
19
|
+
|
|
20
|
+
if (matchedFile) {
|
|
21
|
+
file = matchedFile;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Otherwise, look for a directory
|
|
27
|
+
const nextDir = currentDir.children.find(
|
|
28
|
+
(child) => child.type === "directory" && child.name === part
|
|
29
|
+
) as DirectoryEntry | undefined;
|
|
30
|
+
|
|
31
|
+
if (!nextDir) {
|
|
32
|
+
throw new Error(`Path not found: ${path}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
currentDir = nextDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { directory: currentDir, file };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function findReadme(directory: DirectoryEntry): FileEntry | null {
|
|
42
|
+
const readme = directory.children.find((child) =>
|
|
43
|
+
child.type === "file" &&
|
|
44
|
+
[
|
|
45
|
+
"README.md", "Readme.md", "readme.md",
|
|
46
|
+
"README.mdx", "Readme.mdx", "readme.mdx"
|
|
47
|
+
].includes(child.name)
|
|
48
|
+
) as FileEntry | undefined;
|
|
49
|
+
|
|
50
|
+
return readme || null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function findSlides(directory: DirectoryEntry): FileEntry | null {
|
|
54
|
+
const readme = directory.children.find((child) =>
|
|
55
|
+
child.type === "file" &&
|
|
56
|
+
[
|
|
57
|
+
"SLIDES.md", "Slides.md", "slides.md",
|
|
58
|
+
"SLIDES.mdx", "Slides.mdx", "slides.mdx"
|
|
59
|
+
].includes(child.name)
|
|
60
|
+
) as FileEntry | undefined;
|
|
61
|
+
|
|
62
|
+
return readme || null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
export type DirectoryError =
|
|
67
|
+
| { type: 'config_not_found'; message: string }
|
|
68
|
+
| { type: 'path_not_found'; message: string; status: 404 }
|
|
69
|
+
| { type: 'fetch_error'; message: string }
|
|
70
|
+
| { type: 'parse_error'; message: string };
|
|
71
|
+
|
|
72
|
+
export function useDirectory(path: string = ".") {
|
|
73
|
+
const [directory, setDirectory] = useState<DirectoryEntry | null>(null);
|
|
74
|
+
const [file, setFile] = useState<FileEntry | null>(null);
|
|
75
|
+
const [loading, setLoading] = useState(true);
|
|
76
|
+
const [error, setError] = useState<DirectoryError | null>(null);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
setLoading(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
|
|
82
|
+
(async () => {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(`/raw/.veslx.json`);
|
|
85
|
+
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
if (response.status === 404) {
|
|
88
|
+
setError({ type: 'config_not_found', message: '.veslx.json not found' });
|
|
89
|
+
} else {
|
|
90
|
+
setError({ type: 'fetch_error', message: `Failed to fetch: ${response.status} ${response.statusText}` });
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let json;
|
|
96
|
+
try {
|
|
97
|
+
json = await response.json();
|
|
98
|
+
} catch {
|
|
99
|
+
setError({ type: 'parse_error', message: 'Failed to parse .veslx.json' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let parsed: { directory: DirectoryEntry; file: FileEntry | null };
|
|
104
|
+
try {
|
|
105
|
+
parsed = await parsePath(json, path);
|
|
106
|
+
} catch {
|
|
107
|
+
setError({ type: 'path_not_found', message: `Path not found: ${path}`, status: 404 });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
parsed.directory.children.sort((a: any, b: any) => {
|
|
112
|
+
let aDate, bDate;
|
|
113
|
+
if (a.children) {
|
|
114
|
+
const readme = findReadme(a);
|
|
115
|
+
if (readme && readme.frontmatter && readme.frontmatter.date) {
|
|
116
|
+
aDate = new Date(readme.frontmatter.date as string | number | Date)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (b.children) {
|
|
120
|
+
const readme = findReadme(b);
|
|
121
|
+
if (readme && readme.frontmatter && readme.frontmatter.date) {
|
|
122
|
+
bDate = new Date(readme.frontmatter.date as string | number | Date)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (aDate && bDate) {
|
|
126
|
+
return bDate.getTime() - aDate.getTime()
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
setDirectory(parsed.directory);
|
|
132
|
+
setFile(parsed.file);
|
|
133
|
+
} catch (err: any) {
|
|
134
|
+
setError({ type: 'fetch_error', message: err.message || 'Unknown error' });
|
|
135
|
+
} finally {
|
|
136
|
+
setLoading(false);
|
|
137
|
+
}
|
|
138
|
+
})();
|
|
139
|
+
|
|
140
|
+
return () => {};
|
|
141
|
+
}, [path]);
|
|
142
|
+
|
|
143
|
+
return { directory, file, loading, error };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function useFileContent(path: string) {
|
|
147
|
+
const [blob, setBlob] = useState<Blob | null>(null);
|
|
148
|
+
const [content, setContent] = useState<string | null>(null);
|
|
149
|
+
const [loading, setLoading] = useState(true);
|
|
150
|
+
const [error, setError] = useState<string | null>(null);
|
|
151
|
+
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const controller = new AbortController();
|
|
154
|
+
setLoading(true);
|
|
155
|
+
setError(null);
|
|
156
|
+
|
|
157
|
+
(async () => {
|
|
158
|
+
try {
|
|
159
|
+
const res = await fetch(`/raw/${path}`, {
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
if (!res.ok) {
|
|
164
|
+
throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const fetchedBlob = await res.blob();
|
|
168
|
+
setBlob(fetchedBlob);
|
|
169
|
+
|
|
170
|
+
// Try to read as text - some binary files may fail
|
|
171
|
+
try {
|
|
172
|
+
const text = await fetchedBlob.text();
|
|
173
|
+
setContent(text);
|
|
174
|
+
} catch {
|
|
175
|
+
// Binary file - text content not available
|
|
176
|
+
setContent(null);
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
180
|
+
return; // Ignore abort errors
|
|
181
|
+
}
|
|
182
|
+
setError(err instanceof Error ? err : new Error('Unknown error'));
|
|
183
|
+
} finally {
|
|
184
|
+
setLoading(false);
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
|
|
188
|
+
return () => controller.abort();
|
|
189
|
+
}, [path]);
|
|
190
|
+
|
|
191
|
+
return { blob, content, loading, error };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function isSimulationRunning() {
|
|
195
|
+
const [running, setRunning] = useState<boolean>(false);
|
|
196
|
+
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
let interval: ReturnType<typeof setInterval>;
|
|
199
|
+
|
|
200
|
+
const fetchStatus = async () => {
|
|
201
|
+
const response = await fetch(`/raw/.running`);
|
|
202
|
+
|
|
203
|
+
// this is an elaborate workaround to stop devtools logging errors on 404s
|
|
204
|
+
const text = await response.text()
|
|
205
|
+
if (text === "") {
|
|
206
|
+
setRunning(true);
|
|
207
|
+
} else {
|
|
208
|
+
setRunning(false);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Initial fetch
|
|
213
|
+
fetchStatus();
|
|
214
|
+
|
|
215
|
+
// Poll every second
|
|
216
|
+
interval = setInterval(fetchStatus, 1000);
|
|
217
|
+
|
|
218
|
+
return () => {
|
|
219
|
+
clearInterval(interval);
|
|
220
|
+
};
|
|
221
|
+
}, []);
|
|
222
|
+
|
|
223
|
+
return running;
|
|
224
|
+
}
|