Skip to content

Commit cede0ff

Browse files
committed
Add Mermaid to Unified stack
1 parent 54f64c5 commit cede0ff

10 files changed

Lines changed: 187 additions & 44 deletions

File tree

.cache/pages.json

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,10 @@
2222
"contact",
2323
"offline",
2424
{
25-
"privacy": [
26-
"my-data"
27-
]
25+
"privacy": ["my-data"]
2826
},
2927
{
30-
"services": [
31-
"consulting",
32-
"overview",
33-
"web-development"
34-
]
28+
"services": ["consulting", "overview", "web-development"]
3529
},
3630
{
3731
"tags": [
@@ -46,4 +40,4 @@
4640
"typescript"
4741
]
4842
}
49-
]
43+
]

_TODO.md

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -163,25 +163,6 @@ Needs to add real API key and test
163163
- (Optional) Set up Bridgy for social media
164164
- Test with sample webmentions
165165

166-
## "Add to Calendar" button
167-
168-
Google Calendar, Apple Calendar, Yahoo Calender, Microsoft 365, Outlook, and Teams, and generate iCal/ics files (for all other calendars and cases).
169-
170-
`https://github.com/add2cal/add-to-calendar-button`
171-
`https://add-to-calendar-button.com/`
172-
173-
## Astro wrapper for the `@github/clipboard-copy-element` web component. Copies element text content or input values to the clipboard
174-
175-
[`clipboard-copy`](https://github.com/BryceRussell/astro-github-elements/tree/main/packages/clipboard-copy#astro-github-elementsclipboard-copy)
176-
177-
## Astro wrapper for GitHub's relative time web component. Translates dates to past or future time phrases, like "*4 hours from now*" or "*20 days ago*"
178-
179-
[Relative Time](https://github.com/BryceRussell/astro-github-elements/tree/main/packages/time#readme)
180-
181-
## Display text in a circular layout
182-
183-
[TextCircle](https://github.com/LoStisWorld/astro-textcircle#astro-textcircle)
184-
185166
## Custom Directives
186167

187168
[`astro-directives`](https://github.com/QuentinDutot/astro-directives)
@@ -229,6 +210,27 @@ You can then opt-out of prefetching for individual links by setting data-astro-p
229210
<a href="/about" data-astro-prefetch="false">About</a>
230211
```
231212

213+
## Astro Components to Add
214+
215+
### "Add to Calendar" button
216+
217+
Google Calendar, Apple Calendar, Yahoo Calender, Microsoft 365, Outlook, and Teams, and generate iCal/ics files (for all other calendars and cases).
218+
219+
`https://github.com/add2cal/add-to-calendar-button`
220+
`https://add-to-calendar-button.com/`
221+
222+
### Astro wrapper for the `@github/clipboard-copy-element` web component. Copies element text content or input values to the clipboard
223+
224+
[`clipboard-copy`](https://github.com/BryceRussell/astro-github-elements/tree/main/packages/clipboard-copy#astro-github-elementsclipboard-copy)
225+
226+
### Astro wrapper for GitHub's relative time web component. Translates dates to past or future time phrases, like "*4 hours from now*" or "*20 days ago*"
227+
228+
[Relative Time](https://github.com/BryceRussell/astro-github-elements/tree/main/packages/time#readme)
229+
230+
### Display text in a circular layout
231+
232+
[TextCircle](https://github.com/LoStisWorld/astro-textcircle#astro-textcircle)
233+
232234
## Markdown
233235

234236
## Code Block and Highlighting
@@ -275,19 +277,3 @@ const markdownCodeCopyConfig = {
275277
attachText: ``,
276278
}
277279
```
278-
279-
Next I'd like to setup the rehype-mermaid package. I've installed it already.
280-
1. Use the 'inline-svg' strategy
281-
2. Point the 'css' option key to a file src/styles/vendor/mermaid.css
282-
3. In the 'errorFallback' add a function that rethrows the error as a BuildError with descriptive text that Mermaid has encountered something it can't graph
283-
4. Evaluate whether we can set fontFamily in the mermaidConfig option property to use one of our fonts in public/fonts. The docs mention "You need to call mermaid.initialize() manually", I'm not sure if we can do this.
284-
285-
### rehype-mermaid
286-
287-
```mermaid
288-
graph TD
289-
A[Start] --> B{Is it working?}
290-
B -->|Yes| C[Great!]
291-
B -->|No| D[Debug]
292-
D --> B
293-
```

src/content/articles/demo/index.mdx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,26 @@ There is a doc listing all available emojis in the /docs directory.
529529

530530
:rocket:
531531

532+
### rehype-mermaid
533+
534+
With Mermaid markup like this in a code block with "mermaid" set as the language:
535+
536+
```text
537+
graph TD
538+
A[Start&nbsp;] --> B{Is it working?&nbsp;}
539+
B -->|Yes&nbsp;| C[Great!]
540+
B -->|No&nbsp;| D[Debug&nbsp;]
541+
D --> B
542+
```
543+
544+
```mermaid
545+
graph TD
546+
A[Start&nbsp;] --> B{Is it working?&nbsp;}
547+
B -->|Yes&nbsp;| C[Great!&nbsp;]
548+
B -->|No&nbsp;| D[Debug&nbsp;]
549+
D --> B
550+
```
551+
532552
### remark-align
533553

534554
This custom plugin parses custom Markdown syntax to center- or right-align elements. Alignment is done by wrapping something in shortcode-style tags, applying Tailwind `text-right` or `text-center` classes:

src/content/test-fixtures/markdown/index.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,16 @@ $$
110110
E = mc^2
111111
$$
112112

113+
## Mermaid (rehype-mermaid)
114+
115+
```mermaid
116+
graph TD
117+
A --> B
118+
A --> C
119+
B --> D
120+
C --> D
121+
```
122+
113123
## Captions (remark-captions)
114124

115125
> Do it or do it not, there is no try

src/lib/config/markdown.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Object.defineProperty(rehypeAccessibleEmojis, 'name', { value: 'rehypeAccessible
2828
import rehypeMathjax from 'rehype-mathjax'
2929
Object.defineProperty(rehypeMathjax, 'name', { value: 'rehypeMathjax' })
3030

31+
import rehypeMermaid from 'rehype-mermaid'
32+
Object.defineProperty(rehypeMermaid, 'name', { value: 'rehypeMermaid' })
33+
3134
import type { Options as RehypeAutolinkHeadingsOptions } from 'rehype-autolink-headings'
3235
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
3336
Object.defineProperty(rehypeAutolinkHeadings, 'name', { value: 'rehypeAutolinkHeadings' })
@@ -123,6 +126,8 @@ Object.defineProperty(remarkSmartypants, 'name', { value: 'remarkSmartypants' })
123126
import { rehypeTailwindClasses } from '../markdown/plugins/rehype-tailwind'
124127
Object.defineProperty(rehypeTailwindClasses, 'name', { value: 'rehypeTailwindClasses' })
125128

129+
import { BuildError } from '../errors/BuildError'
130+
126131
/**
127132
* ==============================================================
128133
*
@@ -146,6 +151,25 @@ Object.defineProperty(remarkLinkifyRegexUrls, 'name', { value: 'remarkLinkifyReg
146151
/** remark-attributes plugin */
147152
export const remarkAttributesConfig = { scope: 'permissive' } as const
148153

154+
/** rehype-mermaid plugin */
155+
export const rehypeMermaidConfig = {
156+
strategy: 'inline-svg',
157+
css: new URL('../../styles/vendor/mermaid.css', import.meta.url),
158+
mermaidConfig: {
159+
fontFamily: '"Onest Regular", arial, sans-serif',
160+
},
161+
errorFallback: (_element: unknown, _diagram: string, error: unknown, file: unknown) => {
162+
const filePath =
163+
typeof (file as { path?: unknown } | null)?.path === 'string' ? ((file as { path: string }).path as string) : undefined
164+
165+
throw new BuildError(new Error("Mermaid couldn't graph this diagram.", { cause: error }), {
166+
phase: 'compilation',
167+
tool: 'mermaid',
168+
filePath,
169+
})
170+
},
171+
} as const
172+
149173
/** rehype-autolink-headings plugin */
150174
export const rehypeAutolinkHeadingsConfig: RehypeAutolinkHeadingsOptions = {
151175
content: {
@@ -401,6 +425,8 @@ export const markdownConfig: Partial<MdxOptions> = {
401425
* replaced with <mjx-container> before Tailwind class injection.
402426
*/
403427
rehypeMathjax,
428+
/** Render Mermaid diagrams to inline SVG at build-time */
429+
[rehypeMermaid, rehypeMermaidConfig],
404430
/**
405431
* Automatically add Tailwind classes to markdown elements as
406432
* specified in src/lib/markdown/rehype-tailwind-classes.ts
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @vitest-environment node
2+
3+
import { describe, it, expect, beforeAll } from 'vitest'
4+
5+
import { processWithFullPipeline } from '@lib/markdown/helpers/processors'
6+
7+
let html: string
8+
9+
beforeAll(async () => {
10+
const markdown = ['```mermaid', 'graph TD;', ' A-->B;', '```'].join('\n')
11+
html = await processWithFullPipeline(markdown)
12+
})
13+
14+
describe('Layer 3: E2E - rehypeMermaid', () => {
15+
it('should render Mermaid diagrams as inline SVG', () => {
16+
expect(html).toContain('<svg')
17+
expect(html).toMatch(/id="mermaid-\d+"/)
18+
expect(html).not.toContain('language-mermaid')
19+
})
20+
})
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it, expect } from 'vitest'
2+
import rehypeMermaid from 'rehype-mermaid'
3+
4+
import { rehypeMermaidConfig } from '@lib/config/markdown'
5+
import { processWithAstroSettings } from '@lib/markdown/helpers/processors'
6+
7+
describe('rehype-mermaid (Layer 2: With Astro Pipeline)', () => {
8+
it('should render Mermaid code blocks to inline SVG', async () => {
9+
const markdown = ['```mermaid', 'graph TD;', ' A-->B;', '```'].join('\n')
10+
11+
const html = await processWithAstroSettings({
12+
markdown,
13+
plugin: rehypeMermaid,
14+
pluginOptions: rehypeMermaidConfig,
15+
stage: 'rehype',
16+
})
17+
18+
expect(html).toContain('<svg')
19+
expect(html).toMatch(/id="mermaid-\d+"/)
20+
expect(html).not.toContain('language-mermaid')
21+
})
22+
})
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, it, expect } from 'vitest'
2+
import rehypeMermaid from 'rehype-mermaid'
3+
4+
import { BuildError } from '@lib/errors/BuildError'
5+
import { rehypeMermaidConfig } from '@lib/config/markdown'
6+
import { processIsolated } from '@lib/markdown/helpers/processors'
7+
8+
describe('rehype-mermaid (Layer 1: Isolated)', () => {
9+
it('should use inline-svg strategy and a local CSS file', () => {
10+
expect(rehypeMermaidConfig.strategy).toBe('inline-svg')
11+
expect(String(rehypeMermaidConfig.css)).toContain('/src/styles/vendor/mermaid.css')
12+
})
13+
14+
it('should throw BuildError from errorFallback', () => {
15+
const element = { type: 'element', tagName: 'code', properties: {}, children: [] }
16+
const diagram = 'graph TD\n A --> B\n'
17+
const error = new Error('boom')
18+
const file = { path: '/fake/file.mdx' }
19+
20+
expect(() => rehypeMermaidConfig.errorFallback(element, diagram, error, file)).toThrow(BuildError)
21+
})
22+
23+
it('should render Mermaid code blocks to inline SVG', async () => {
24+
const markdown = ['```mermaid', 'graph TD;', ' A-->B;', '```'].join('\n')
25+
26+
const html = await processIsolated({
27+
markdown,
28+
plugin: rehypeMermaid,
29+
pluginOptions: rehypeMermaidConfig,
30+
stage: 'rehype',
31+
})
32+
33+
expect(html).toContain('<svg')
34+
expect(html).toMatch(/id="mermaid-\d+"/)
35+
expect(html).not.toContain('language-mermaid')
36+
})
37+
})

src/styles/vendor/mermaid.css

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,19 @@
22
* Styles for Playwright to use when rendering Mermaid diagrams in Markdown files.
33
* This file is only used during the build process and is not included in the final site CSS.
44
*/
5+
6+
@font-face {
7+
font-display: swap;
8+
font-family: "Onest Regular";
9+
font-style: normal;
10+
font-weight: normal;
11+
src: url("../../../public/fonts/OnestRegular1602-hint.woff2") format("woff2");
12+
}
13+
14+
body {
15+
font-family: "Onest Regular", arial, sans-serif;
16+
}
17+
18+
svg {
19+
font-family: "Onest Regular", arial, sans-serif;
20+
}

test/e2e/specs/04-components/markdown.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ test.describe('Markdown (MDX) fixture page', () => {
216216
})
217217
})
218218

219+
test.describe('Mermaid (rehype-mermaid)', () => {
220+
test('renders Mermaid as inline SVG at build-time', async () => {
221+
await expect(markdownPage.heading('Mermaid (rehype-mermaid)', 2)).toBeVisible()
222+
223+
const diagramSvg = markdownPage.prose.locator('svg[id^="mermaid-"]').first()
224+
await expect(diagramSvg).toBeVisible()
225+
226+
await expect(markdownPage.prose.locator('code.language-mermaid')).toHaveCount(0)
227+
await expect(markdownPage.prose.locator('pre.mermaid')).toHaveCount(0)
228+
})
229+
})
230+
219231
test.describe('Math (remark-math + rehype-mathjax)', () => {
220232
test('renders TeX as SVG at build-time (no client-side MathJax)', async ({ page }) => {
221233
await expect(markdownPage.heading('Math (remark-math + rehype-mathjax)', 2)).toBeVisible()

0 commit comments

Comments
 (0)