Skip to content

Commit 1ed4c05

Browse files
committed
fix(site): clean up meander's annotation prose — stray mailto links + JSDoc tags
Two bugs in the prose column alongside each code chunk on part pages, where meander's client-side markdown renderer hydrates from .annotation-md-source textareas: 1. Meander's auto-linker treats `name@version` tokens (like `core@7.0.0`) as email addresses and wraps them in a mailto: <a>. False positive; these are npm package identifiers, not emails. After meander hydrates, unwrap any <a href="mailto:..."> in .annotation-md by replacing it with its text content. 2. JSDoc tags at the start of a line (@param, @returns, @throws, @example, @fileoverview, …) rendered as plain body text — they read as primary content instead of metadata markers. Wrap them in <span class="wt-jsdoc-tag"> after meander hydrates; CSS tints them muted in a monospace family so the @TagName token reads as quiet annotation, not prose. Timing: drag.js runs cleanupAnnotationProse once at ready(), then schedules a second pass via requestAnimationFrame so any hydration that landed later-same-tick gets swept too. rAF fires before the next paint, so the un-cleaned state is never visible. Tag list covers the common JSDoc vocabulary. Unknown @tokens (@scope npm names, email-likes) don't match — only the curated set gets wrapped.
1 parent aafecee commit 1ed4c05

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

drag.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,110 @@
960960
}, 1500)
961961
})
962962

963+
/* Tidy the prose that meander renders into .annotation-md at
964+
* page load. Two fixes:
965+
* 1. Meander's markdown auto-linker treats `name@x.y.z`
966+
* (e.g. `core@7.0.0`) as an email and wraps it in a
967+
* mailto: <a>. Unwrap those — they're package identifiers,
968+
* not addresses. Check the href pattern rather than the
969+
* text content so we don't accidentally un-link real
970+
* emails if any show up.
971+
* 2. JSDoc tags (@param, @returns, @throws, @example,
972+
* @fileoverview, etc.) at the start of a line are
973+
* metadata markers, not primary prose. Wrap them in a
974+
* <span class="wt-jsdoc-tag"> so CSS can tint them lighter
975+
* and set them apart. Matches only the `@tagname` token,
976+
* not `@scope` package names etc. */
977+
const JSDOC_TAGS = new Set([
978+
'param',
979+
'returns',
980+
'return',
981+
'throws',
982+
'throw',
983+
'example',
984+
'fileoverview',
985+
'see',
986+
'since',
987+
'deprecated',
988+
'default',
989+
'type',
990+
'typedef',
991+
'callback',
992+
'property',
993+
'prop',
994+
'template',
995+
'inheritdoc',
996+
'override',
997+
'private',
998+
'protected',
999+
'public',
1000+
'readonly',
1001+
'static',
1002+
'augments',
1003+
'extends',
1004+
'module',
1005+
'namespace',
1006+
'memberof',
1007+
'this',
1008+
])
1009+
const cleanupAnnotationProse = () => {
1010+
for (const container of document.querySelectorAll('.annotation-md')) {
1011+
/* 1. Unwrap spurious mailto: links (meander auto-linker
1012+
* misreading `name@version`). Replace the <a> node with
1013+
* its text content. */
1014+
for (const a of container.querySelectorAll('a[href^="mailto:"]')) {
1015+
a.replaceWith(document.createTextNode(a.textContent ?? ''))
1016+
}
1017+
/* 2. Wrap JSDoc tags. Walk text nodes only so we don't
1018+
* disturb existing <a>/<code>/<em> wrappers. A TreeWalker
1019+
* collects the candidates first so we can mutate without
1020+
* invalidating the iterator. */
1021+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT)
1022+
const textNodes = []
1023+
let n = walker.nextNode()
1024+
while (n) {
1025+
textNodes.push(n)
1026+
n = walker.nextNode()
1027+
}
1028+
const tagPattern = /@([A-Za-z]+)\b/g
1029+
for (const node of textNodes) {
1030+
const text = node.nodeValue ?? ''
1031+
if (!text.includes('@')) {
1032+
continue
1033+
}
1034+
const parts = []
1035+
let cursor = 0
1036+
let m
1037+
tagPattern.lastIndex = 0
1038+
while ((m = tagPattern.exec(text)) !== null) {
1039+
const tag = m[1].toLowerCase()
1040+
if (!JSDOC_TAGS.has(tag)) {
1041+
continue
1042+
}
1043+
if (m.index > cursor) {
1044+
parts.push(document.createTextNode(text.slice(cursor, m.index)))
1045+
}
1046+
const span = document.createElement('span')
1047+
span.className = 'wt-jsdoc-tag'
1048+
span.textContent = m[0]
1049+
parts.push(span)
1050+
cursor = m.index + m[0].length
1051+
}
1052+
if (parts.length === 0) {
1053+
continue
1054+
}
1055+
if (cursor < text.length) {
1056+
parts.push(document.createTextNode(text.slice(cursor)))
1057+
}
1058+
const frag = document.createDocumentFragment()
1059+
for (const p of parts) {
1060+
frag.appendChild(p)
1061+
}
1062+
node.parentNode?.replaceChild(frag, node)
1063+
}
1064+
}
1065+
}
1066+
9631067
const ready = async () => {
9641068
installAll()
9651069
installThemeToggle()
@@ -969,6 +1073,13 @@
9691073
// Source links run AFTER hljs — wrapping before would lose
9701074
// our <a>s the moment hljs re-tokenizes the text nodes.
9711075
installSourceLinks()
1076+
/* Fixup meander's annotation render. Run once now, then
1077+
* schedule a second pass via requestAnimationFrame so any
1078+
* meander hydration that landed later-in-the-same-tick gets
1079+
* swept too. rAF fires before the next paint, so neither
1080+
* the user nor layout ever sees the un-cleaned state. */
1081+
cleanupAnnotationProse()
1082+
requestAnimationFrame(cleanupAnnotationProse)
9721083
}
9731084
if (document.readyState === 'loading') {
9741085
document.addEventListener('DOMContentLoaded', ready)

overrides.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1684,6 +1684,18 @@ body.col-resizing * {
16841684
display: none;
16851685
}
16861686

1687+
/* JSDoc tag marker (@param, @returns, @throws, @example, …).
1688+
* Set in annotation prose by drag.js's cleanupAnnotationProse
1689+
* after meander hydrates. Reads as metadata — dimmer than body
1690+
* text, slightly larger tracking so the `@tagname` token has
1691+
* visual weight without demanding attention. */
1692+
.wt-jsdoc-tag {
1693+
color: var(--muted);
1694+
font-family: var(--font-mono);
1695+
font-size: 0.88em;
1696+
letter-spacing: 0.02em;
1697+
}
1698+
16871699
/* ── 8. Code / diff tables ───────────────────────────────────────── */
16881700

16891701
/* The code-section wraps a meander code-table. Darkened, rounded

0 commit comments

Comments
 (0)