Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
820 changes: 820 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion website/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createRequire } from 'node:module'
import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { defineConfig, type SiteConfig } from 'vitepress'
import { mermaidFences } from './mermaid.mts'
import { repositoryLinks } from './repository-links.mts'
import { docsSidebar, legacyRedirects, pageTitle, readDocsJson, repoRoot } from './docs-nav.mts'

Expand Down Expand Up @@ -120,7 +121,10 @@ export default defineConfig({
markdown: {
theme: { light: 'github-light', dark: 'github-dark' },
languageAlias: { caddyfile: 'nginx' },
config: (md) => repositoryLinks(md, { repoRoot, blobBase: `${repo}/blob/main`, treeBase: `${repo}/tree/main` }),
config: (md) => {
repositoryLinks(md, { repoRoot, blobBase: `${repo}/blob/main`, treeBase: `${repo}/tree/main` })
mermaidFences(md)
},
},

vite: {
Expand Down
12 changes: 12 additions & 0 deletions website/.vitepress/mermaid.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Turns ```mermaid fences into the client-side <Mermaid> component. The
// diagram source is URI-encoded so Vue's template compiler sees plain text.
import type MarkdownIt from 'markdown-it'

export function mermaidFences(md: MarkdownIt) {
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (tokens, index, options, env, self) => {
const token = tokens[index]
if (token.info.trim() !== 'mermaid') return fence(tokens, index, options, env, self)
return `<Mermaid code="${encodeURIComponent(token.content)}" />\n`
}
}
85 changes: 85 additions & 0 deletions website/.vitepress/theme/components/Mermaid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
// Renders a Mermaid diagram in the browser. The source is shown until the
// diagram is ready, so the page works without JavaScript, and the diagram is
// redrawn when the colour theme changes.
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'

const props = defineProps<{ code: string }>()

const source = computed(() => decodeURIComponent(props.code))
const svg = ref('')
const failed = ref(false)
let themeObserver: MutationObserver | undefined
let renderCount = 0

async function render() {
const { default: mermaid } = await import('mermaid')
// Mermaid measures label text, so the web font must be ready first.
await document.fonts?.ready
const dark = document.documentElement.classList.contains('dark')
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
theme: dark ? 'dark' : 'neutral',
fontFamily: "'Inter Variable', Inter, system-ui, sans-serif",
themeVariables: dark
? { primaryColor: '#121817', primaryBorderColor: '#4ade80', lineColor: '#7f8f85', clusterBkg: '#0b100e', clusterBorder: '#2f7a4c', edgeLabelBackground: '#121817' }
: { primaryColor: '#f2f3f0', primaryBorderColor: '#15803d', lineColor: '#5d6b62', clusterBkg: '#fafaf9', clusterBorder: '#86b89a', edgeLabelBackground: '#f2f3f0' },
})
const id = `oac-mermaid-${Math.random().toString(36).slice(2)}-${renderCount++}`
try {
svg.value = (await mermaid.render(id, source.value)).svg
failed.value = false
} catch (error) {
console.error('Mermaid diagram failed to render', error)
failed.value = true
document.getElementById(`d${id}`)?.remove()
}
}

onMounted(() => {
render()
themeObserver = new MutationObserver(() => render())
themeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] })
})

onBeforeUnmount(() => themeObserver?.disconnect())
</script>

<template>
<div class="oac-mermaid" :class="{ failed }">
<div v-if="svg && !failed" class="oac-mermaid-diagram" v-html="svg" />
<pre v-else class="oac-mermaid-source"><code>{{ source }}</code></pre>
</div>
</template>

<style scoped>
.oac-mermaid {
margin: 16px 0;
padding: 16px;
overflow-x: auto;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
}
.oac-mermaid-diagram :deep(svg) {
display: block;
max-width: 100%;
height: auto;
margin: 0 auto;
}
/* Documentation paragraph styles must not reach Mermaid's HTML labels, whose
size Mermaid measures before the page styles apply. */
.oac-mermaid-diagram :deep(foreignObject p),
.oac-mermaid-diagram :deep(foreignObject div),
.oac-mermaid-diagram :deep(foreignObject span) {
margin: 0;
line-height: 1.5;
}
.oac-mermaid-source {
margin: 0;
font: 13px/1.6 var(--vp-font-family-mono);
white-space: pre;
color: var(--vp-c-text-2);
}
</style>
2 changes: 2 additions & 0 deletions website/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import '@fontsource-variable/inter'
import '@fontsource-variable/geist-mono'
import Layout from './Layout.vue'
import Landing from './components/Landing.vue'
import Mermaid from './components/Mermaid.vue'
import './style.css'

export default {
extends: DefaultTheme,
Layout,
enhanceApp({ app }) {
app.component('Landing', Landing)
app.component('Mermaid', Mermaid)
},
} satisfies Theme
3 changes: 2 additions & 1 deletion website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"dependencies": {
"@fontsource-variable/geist-mono": "^5.3.0",
"@fontsource-variable/inter": "^5.3.0"
"@fontsource-variable/inter": "^5.3.0",
"mermaid": "^11.17.2"
},
"devDependencies": {
"vitepress": "^1.6.4",
Expand Down
6 changes: 6 additions & 0 deletions website/tests/dist.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,9 @@ test('llms.txt lists every page', () => {
const llms = readFileSync(resolve(dist, 'llms.txt'), 'utf8')
for (const page of pages) assert.ok(llms.includes(`${page}.md)`), `${page} in llms.txt`)
})

test('Mermaid fences become diagrams, not code blocks', () => {
const source = readFileSync(html('docs/architecture'), 'utf8')
assert.match(source, /class="oac-mermaid/)
assert.doesNotMatch(source, /language-mermaid/)
})
Loading