Skip to content

Commit e94d629

Browse files
feat(web): auto-link cross-references in statute text (#121) (#125)
Chapter pages: - "section 1114" text auto-linked to #section-1114 anchor - Only links when target section exists on the same page - Teal underline styling matches link convention Section detail pages: - "section N of this title" linked to chapter page with anchor - Links to /browse/title-N/chapter-M/#section-N Both use lightweight inline scripts that walk text nodes and replace "section N" mentions with anchor links. Skips headings and existing links to avoid nesting. Inspired by legislation.gov.uk's cross-reference linking pattern. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 362874d commit e94d629

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

apps/web/src/pages/browse/[title]/[chapter].astro

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,40 @@ const activeCount = renderedSections.filter(s => !s.isInactive).length;
148148
&larr; Back to Title {titleNum} table of contents
149149
</a>
150150
</div>
151+
152+
<!-- Auto-link cross-references: "section N" → #section-N anchor on this page -->
153+
<script is:inline>
154+
(function () {
155+
var articles = document.querySelectorAll('article .prose');
156+
var pattern = /\bsection (\d+[a-z]?)\b/gi;
157+
articles.forEach(function (article) {
158+
var walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT);
159+
var textNodes = [];
160+
while (walker.nextNode()) textNodes.push(walker.currentNode);
161+
textNodes.forEach(function (node) {
162+
if (!node.parentElement || node.parentElement.tagName === 'A' || node.parentElement.tagName === 'H2') return;
163+
var text = node.textContent || '';
164+
if (!pattern.test(text)) return;
165+
pattern.lastIndex = 0;
166+
var frag = document.createDocumentFragment();
167+
var lastIdx = 0;
168+
var m;
169+
while ((m = pattern.exec(text)) !== null) {
170+
var secNum = m[1];
171+
var target = document.getElementById('section-' + secNum);
172+
if (!target) continue;
173+
if (m.index > lastIdx) frag.appendChild(document.createTextNode(text.slice(lastIdx, m.index)));
174+
var a = document.createElement('a');
175+
a.href = '#section-' + secNum;
176+
a.textContent = m[0];
177+
a.className = 'text-teal underline underline-offset-2 decoration-teal/30 hover:decoration-teal';
178+
frag.appendChild(a);
179+
lastIdx = m.index + m[0].length;
180+
}
181+
if (lastIdx > 0 && lastIdx < text.length) frag.appendChild(document.createTextNode(text.slice(lastIdx)));
182+
if (lastIdx > 0) node.parentElement.replaceChild(frag, node);
183+
});
184+
});
185+
})();
186+
</script>
151187
</BaseLayout>

apps/web/src/pages/statute/[...slug].astro

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,39 @@ const readingTimeMin = Math.max(1, Math.round(wordCount / 200));
343343
</ul>
344344
</div>
345345
)}
346+
347+
<!-- Auto-link cross-references: "section N of this title" → chapter page anchor -->
348+
<script is:inline define:vars={{ base, usc_title, chapter }}>
349+
(function () {
350+
var prose = document.querySelector('.prose');
351+
if (!prose) return;
352+
var pattern = /\bsection (\d+[a-z]?)\b(?:\s+of this title)?/gi;
353+
var chapterUrl = base + 'browse/title-' + usc_title + '/chapter-' + chapter + '/';
354+
var walker = document.createTreeWalker(prose, NodeFilter.SHOW_TEXT);
355+
var textNodes = [];
356+
while (walker.nextNode()) textNodes.push(walker.currentNode);
357+
textNodes.forEach(function (node) {
358+
if (!node.parentElement || node.parentElement.tagName === 'A' || node.parentElement.closest('h1,h2')) return;
359+
var text = node.textContent || '';
360+
if (!pattern.test(text)) return;
361+
pattern.lastIndex = 0;
362+
var frag = document.createDocumentFragment();
363+
var lastIdx = 0;
364+
var m;
365+
while ((m = pattern.exec(text)) !== null) {
366+
var secNum = m[1];
367+
if (m.index > lastIdx) frag.appendChild(document.createTextNode(text.slice(lastIdx, m.index)));
368+
var a = document.createElement('a');
369+
a.href = chapterUrl + '#section-' + secNum;
370+
a.textContent = m[0];
371+
a.className = 'text-teal underline underline-offset-2 decoration-teal/30 hover:decoration-teal';
372+
a.title = 'View section ' + secNum;
373+
frag.appendChild(a);
374+
lastIdx = m.index + m[0].length;
375+
}
376+
if (lastIdx > 0 && lastIdx < text.length) frag.appendChild(document.createTextNode(text.slice(lastIdx)));
377+
if (lastIdx > 0 && node.parentElement) node.parentElement.replaceChild(frag, node);
378+
});
379+
})();
380+
</script>
346381
</BaseLayout>

0 commit comments

Comments
 (0)