Migrate documentation site to Astro
This commit is contained in:
parent
4020b7c2d5
commit
0e873fbd51
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -6,3 +6,6 @@
|
|||
node_modules
|
||||
/dist
|
||||
site/decodal-site/dist
|
||||
|
||||
# Astro
|
||||
site/decodal-site/.astro
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
|||
|
||||
## Web site and playground
|
||||
|
||||
The Svelte documentation site and browser playground are kept in:
|
||||
The Astro documentation site and browser playground are kept in:
|
||||
|
||||
```text
|
||||
site/decodal-site/
|
||||
|
|
@ -34,8 +34,9 @@ The playground loads `decodal-wasm` and evaluates DCDL entirely in the browser.
|
|||
Important files:
|
||||
|
||||
```text
|
||||
site/decodal-site/src/App.svelte
|
||||
site/decodal-site/src/Playground.svelte
|
||||
site/decodal-site/src/pages/docs/[...slug].astro
|
||||
site/decodal-site/src/pages/playground.astro
|
||||
site/decodal-site/src/layouts/ManualLayout.astro
|
||||
site/decodal-site/src/lib/docs.js
|
||||
crates/decodal-wasm/src/lib.rs
|
||||
```
|
||||
|
|
|
|||
5
site/decodal-site/astro.config.mjs
Normal file
5
site/decodal-site/astro.config.mjs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
export default defineConfig({
|
||||
output: 'static',
|
||||
});
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Decodal</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
5460
site/decodal-site/package-lock.json
generated
5460
site/decodal-site/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -4,16 +4,14 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --host 0.0.0.0",
|
||||
"dev": "astro dev --host 0.0.0.0",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview --host 0.0.0.0",
|
||||
"build:wasm": "wasm-pack build ../../crates/decodal-wasm --target web --out-dir ../../site/decodal-site/src/wasm --release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"marked": "^12.0.2",
|
||||
"svelte": "^4.2.18",
|
||||
"vite": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"astro": "^4.16.18",
|
||||
"marked": "^12.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import NavTree from './NavTree.svelte';
|
||||
import Playground from './Playground.svelte';
|
||||
import { nav, renderMarkdown } from './lib/docs.js';
|
||||
|
||||
let route = parseRoute(location.hash);
|
||||
|
||||
onMount(() => {
|
||||
const onHashChange = () => (route = parseRoute(location.hash));
|
||||
addEventListener('hashchange', onHashChange);
|
||||
return () => removeEventListener('hashchange', onHashChange);
|
||||
});
|
||||
|
||||
$: isPlayground = route.kind === 'playground';
|
||||
$: currentSlug = route.slug ?? 'introduction';
|
||||
$: html = isPlayground ? '' : renderMarkdown(currentSlug);
|
||||
|
||||
function parseRoute(hash) {
|
||||
const raw = hash.replace(/^#\/?/, '');
|
||||
if (raw === 'playground') return { kind: 'playground' };
|
||||
if (raw.startsWith('docs/')) {
|
||||
return { kind: 'docs', slug: raw.slice('docs/'.length) || 'introduction' };
|
||||
}
|
||||
return { kind: 'docs', slug: 'introduction' };
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="topbar">
|
||||
<a class="brand" href="#/docs/introduction">Decodal</a>
|
||||
<nav class="topnav">
|
||||
<a href="#/docs/introduction">Docs</a>
|
||||
<a href="#/playground">Playground</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<a class="sidebar-title" href="#/docs/index">Manual</a>
|
||||
<NavTree items={nav} active={currentSlug} />
|
||||
</aside>
|
||||
|
||||
<main class:playground={isPlayground}>
|
||||
{#if isPlayground}
|
||||
<Playground />
|
||||
{:else}
|
||||
<article class="markdown">
|
||||
{@html html}
|
||||
</article>
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<script>
|
||||
export let items = [];
|
||||
export let active = '';
|
||||
</script>
|
||||
|
||||
<ul class="nav-tree">
|
||||
{#each items as item}
|
||||
<li>
|
||||
<a class:active={active === item.slug} href={`#/docs/${item.slug}`}>{item.title}</a>
|
||||
{#if item.children}
|
||||
<svelte:self items={item.children} {active} />
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const starter = `let
|
||||
Service = {
|
||||
name = String;
|
||||
port = Int & > 443 default 8443;
|
||||
feature.enable = Bool default true;
|
||||
};
|
||||
in
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
`;
|
||||
|
||||
let source = starter;
|
||||
let output = '';
|
||||
let error = '';
|
||||
let ready = false;
|
||||
let loading = true;
|
||||
let evaluate;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const wasm = await import('./wasm/decodal_wasm.js');
|
||||
await wasm.default();
|
||||
evaluate = wasm.evaluate;
|
||||
ready = true;
|
||||
run();
|
||||
} catch (err) {
|
||||
error = `Failed to load WASM playground: ${err.message ?? err}`;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
function run() {
|
||||
if (!evaluate) return;
|
||||
const result = JSON.parse(evaluate(source));
|
||||
if (result.ok) {
|
||||
output = result.output;
|
||||
error = '';
|
||||
} else {
|
||||
output = '';
|
||||
error = result.error;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="playground-page">
|
||||
<div class="playground-header">
|
||||
<div>
|
||||
<h1>Playground</h1>
|
||||
<p>Evaluate Decodal directly in your browser through WebAssembly.</p>
|
||||
</div>
|
||||
<button on:click={run} disabled={!ready}>Run</button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<p class="status">Loading WASM...</p>
|
||||
{/if}
|
||||
|
||||
<div class="playground-grid">
|
||||
<label class="pane">
|
||||
<span>Input</span>
|
||||
<textarea bind:value={source} spellcheck="false" on:keydown={(event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') run();
|
||||
}} />
|
||||
</label>
|
||||
|
||||
<section class="pane output-pane">
|
||||
<span>Output</span>
|
||||
{#if error}
|
||||
<pre class="error">{error}</pre>
|
||||
{:else}
|
||||
<pre>{output}</pre>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
1
site/decodal-site/src/env.d.ts
vendored
Normal file
1
site/decodal-site/src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
43
site/decodal-site/src/layouts/ManualLayout.astro
Normal file
43
site/decodal-site/src/layouts/ManualLayout.astro
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
import { nav } from '../lib/docs.js';
|
||||
import '../style.css';
|
||||
|
||||
const { title = 'Decodal', active = '' } = Astro.props;
|
||||
|
||||
function renderNav(items) {
|
||||
return `<ul class="nav-tree">${items
|
||||
.map((item) => {
|
||||
const href = `/docs/${item.slug}/`;
|
||||
const activeClass = active === item.slug ? ' class="active"' : '';
|
||||
const children = item.children ? renderNav(item.children) : '';
|
||||
return `<li><a${activeClass} href="${href}">${item.title}</a>${children}</li>`;
|
||||
})
|
||||
.join('')}</ul>`;
|
||||
}
|
||||
---
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<a class="brand" href="/docs/introduction/">Decodal</a>
|
||||
<nav class="topnav">
|
||||
<a href="/docs/introduction/">Docs</a>
|
||||
<a href="/playground/">Playground</a>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<a class="sidebar-title" href="/docs/">Manual</a>
|
||||
<nav set:html={renderNav(nav)} />
|
||||
</aside>
|
||||
<main class={Astro.props.playground ? 'playground' : ''}>
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -79,18 +79,18 @@ export const nav = [
|
|||
{ title: 'Open Issues', slug: 'open-issues' },
|
||||
];
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
mangle: false,
|
||||
headerIds: true,
|
||||
});
|
||||
marked.setOptions({ gfm: true });
|
||||
|
||||
export function allDocSlugs() {
|
||||
return Object.keys(docs).filter((slug) => slug !== 'index');
|
||||
}
|
||||
|
||||
export function renderMarkdown(slug) {
|
||||
const source = docs[slug] ?? docs.index;
|
||||
const html = marked.parse(source ?? '# Not found\n');
|
||||
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
|
||||
const target = normalizeDocLink(slug, href);
|
||||
return `href="#/docs/${target}${hash}"`;
|
||||
return `href="/docs/${target}/${hash}"`;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
import App from './App.svelte';
|
||||
import './style.css';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app'),
|
||||
});
|
||||
|
||||
export default app;
|
||||
17
site/decodal-site/src/pages/docs/[...slug].astro
Normal file
17
site/decodal-site/src/pages/docs/[...slug].astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
import ManualLayout from '../../layouts/ManualLayout.astro';
|
||||
import { allDocSlugs, renderMarkdown } from '../../lib/docs.js';
|
||||
|
||||
export function getStaticPaths() {
|
||||
return allDocSlugs().map((slug) => ({
|
||||
params: { slug },
|
||||
props: { slug },
|
||||
}));
|
||||
}
|
||||
|
||||
const { slug } = Astro.props;
|
||||
const html = renderMarkdown(slug);
|
||||
---
|
||||
<ManualLayout title={`Decodal - ${slug}`} active={slug}>
|
||||
<article class="markdown" set:html={html} />
|
||||
</ManualLayout>
|
||||
3
site/decodal-site/src/pages/docs/index.astro
Normal file
3
site/decodal-site/src/pages/docs/index.astro
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
return Astro.redirect('/docs/introduction/');
|
||||
---
|
||||
3
site/decodal-site/src/pages/index.astro
Normal file
3
site/decodal-site/src/pages/index.astro
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
return Astro.redirect('/docs/introduction/');
|
||||
---
|
||||
26
site/decodal-site/src/pages/playground.astro
Normal file
26
site/decodal-site/src/pages/playground.astro
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
import ManualLayout from '../layouts/ManualLayout.astro';
|
||||
---
|
||||
<ManualLayout title="Decodal Playground" playground>
|
||||
<section class="playground-page">
|
||||
<div class="playground-header">
|
||||
<div>
|
||||
<h1>Playground</h1>
|
||||
<p>Evaluate Decodal directly in your browser through WebAssembly.</p>
|
||||
</div>
|
||||
<button id="run" disabled>Run</button>
|
||||
</div>
|
||||
<p id="status" class="status">Loading WASM...</p>
|
||||
<div class="playground-grid">
|
||||
<label class="pane">
|
||||
<span>Input</span>
|
||||
<textarea id="source" spellcheck="false"></textarea>
|
||||
</label>
|
||||
<section class="pane output-pane">
|
||||
<span>Output</span>
|
||||
<pre id="output"></pre>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
<script type="module" src="../scripts/playground.js"></script>
|
||||
</ManualLayout>
|
||||
41
site/decodal-site/src/scripts/playground.js
Normal file
41
site/decodal-site/src/scripts/playground.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import init, { evaluate } from '../wasm/decodal_wasm.js';
|
||||
|
||||
const starter = `let
|
||||
Service = {
|
||||
name = String;
|
||||
port = Int & > 443 default 8443;
|
||||
feature.enable = Bool default true;
|
||||
};
|
||||
in
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
`;
|
||||
|
||||
const source = document.getElementById('source');
|
||||
const output = document.getElementById('output');
|
||||
const run = document.getElementById('run');
|
||||
const status = document.getElementById('status');
|
||||
|
||||
source.value = starter;
|
||||
|
||||
function execute() {
|
||||
const result = JSON.parse(evaluate(source.value));
|
||||
output.textContent = result.ok ? result.output : result.error;
|
||||
output.classList.toggle('error', !result.ok);
|
||||
}
|
||||
|
||||
try {
|
||||
await init();
|
||||
run.disabled = false;
|
||||
status.textContent = '';
|
||||
execute();
|
||||
} catch (error) {
|
||||
status.textContent = `Failed to load WASM: ${error?.message ?? error}`;
|
||||
}
|
||||
|
||||
run.addEventListener('click', execute);
|
||||
source.addEventListener('keydown', (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') execute();
|
||||
});
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import { defineConfig } from 'vite';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../..', import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
fs: {
|
||||
allow: [repoRoot],
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user