Skip to content

Commit 28cf213

Browse files
authored
Merge pull request #544 from raifdmueller/fix/modal-entity-decode
fix: decode HTML entities in anchor modal title
2 parents 34ff458 + 89d3d52 commit 28cf213

3 files changed

Lines changed: 20 additions & 3 deletions

File tree

website/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const browserGlobals = {
1818
setInterval: 'readonly',
1919
clearInterval: 'readonly',
2020
HTMLElement: 'readonly',
21+
DOMParser: 'readonly',
2122
Event: 'readonly',
2223
CustomEvent: 'readonly',
2324
KeyboardEvent: 'readonly',

website/src/components/anchor-modal.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,12 @@ export async function loadAnchorContent(anchorId) {
238238
},
239239
})
240240

241-
// Extract title from HTML or use anchor ID
242-
const titleMatch = htmlContent.match(/<h1[^>]*>([^<]+)<\/h1>/)
243-
const title = titleMatch ? titleMatch[1] : anchorId
241+
// Extract title from HTML or use anchor ID.
242+
// Parse via DOMParser so entities like &amp; decode to their characters —
243+
// setting textContent on a regex-captured string would render
244+
// "Strunk &amp; White" literally (regressed twice; keep this DOM round-trip).
245+
const h1El = new DOMParser().parseFromString(htmlContent, 'text/html').querySelector('h1')
246+
const title = h1El ? h1El.textContent.trim() : anchorId
244247

245248
titleEl.textContent = title
246249
// Safe: htmlContent is generated by asciidoctor from .adoc files served from our own origin

website/src/components/anchor-modal.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,19 @@ describe('anchor-modal', () => {
129129
expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('test-anchor.adoc'))
130130
})
131131

132+
it('should decode HTML entities in the modal title (regression)', async () => {
133+
global.fetch.mockResolvedValue({
134+
ok: true,
135+
text: async () => '= Plain English according to Strunk & White\n\nBody.',
136+
})
137+
138+
await showAnchorDetails('plain-english-strunk-white')
139+
140+
const title = document.getElementById('modal-title').textContent
141+
expect(title).toBe('Plain English according to Strunk & White')
142+
expect(title).not.toContain('&amp;')
143+
})
144+
132145
it('should handle fetch errors gracefully', async () => {
133146
global.fetch.mockRejectedValue(new Error('Network error'))
134147

0 commit comments

Comments
 (0)