Skip to content

Commit b38ada6

Browse files
committed
Automation of AnchorHeading & StarlightPage headings
1 parent bcfc7fb commit b38ada6

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-0
lines changed

web/package-lock.json

Lines changed: 115 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"glob": "^11.0.2",
1919
"js-yaml": "^4.1.0",
2020
"marked": "^15.0.6",
21+
"node-html-parser": "^7.0.1",
2122
"pagefind": "^1.3.0",
2223
"sharp": "^0.32.5",
2324
"tm-themes": "^1.10.6"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
3+
import { parse } from 'node-html-parser';
4+
5+
const { frontmatter = {} } = Astro.props;
6+
7+
let slot = '';
8+
if (Astro.slots.has('default')) {
9+
slot = await Astro.slots.render('default');
10+
}
11+
12+
const root = parse(slot ?? '', { lowerCaseTagName: false });
13+
14+
const headings = [];
15+
16+
// AnchorHeading component source
17+
function renderAnchorHeading(level, id, text) {
18+
const accessibleLabel = `Section ${text}`;
19+
20+
return `
21+
<div class="sl-heading-wrapper level-h${level}">
22+
<h${level} id="${id}">${text}</h${level}>
23+
<a class="sl-anchor-link" href="#${id}">
24+
<span aria-hidden="true" class="sl-anchor-icon">
25+
<svg width="16" height="16" viewBox="0 0 24 24">
26+
<path fill="currentcolor" d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"></path>
27+
</svg>
28+
</span>
29+
<span class="sr-only">${accessibleLabel}</span>
30+
</a>
31+
</div>
32+
`;
33+
}
34+
35+
function escapeHtml(str: string) {
36+
return str
37+
.replace(/&/g, '&amp;')
38+
.replace(/</g, '&lt;')
39+
.replace(/>/g, '&gt;');
40+
}
41+
42+
root.querySelectorAll('h2, h3').forEach((el) => {
43+
const level = parseInt(el.tagName[1]);
44+
const text = el.text.trim();
45+
const slug = text
46+
.toLowerCase()
47+
.replace(/[^\w\s-]/g, '')
48+
.replace(/\s+/g, '-');
49+
50+
headings.push({ depth: level, slug, text });
51+
52+
el.replaceWith(renderAnchorHeading(level, slug, escapeHtml(text)));
53+
});
54+
55+
const finalHtml = root.toString();
56+
---
57+
58+
<StarlightPage frontmatter={frontmatter} headings={headings}>
59+
<div id="_top" />
60+
<Fragment set:html={finalHtml} />
61+
</StarlightPage>

0 commit comments

Comments
 (0)