diff --git a/CHANGELOG.md b/CHANGELOG.md index def1113..e9a90c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- Renders ESV passage HTML through Obsidian's `sanitizeHTMLToDom()` and appends the resulting fragment, instead of assigning the raw API response to `innerHTML` + ## 0.13.0 - Brings the plugin in line with current Obsidian community standards diff --git a/FOLLOWUP.md b/FOLLOWUP.md index dfd77f0..cc9edf6 100644 --- a/FOLLOWUP.md +++ b/FOLLOWUP.md @@ -65,18 +65,20 @@ callbacks reuse the shared instance instead of constructing new ones. The file-l --- -## 3. Rendering ESV HTML via `innerHTML` +## 3. Rendering ESV HTML via `innerHTML` — ✅ RESOLVED **File:** `src/components/BibleReferenceRenderer.ts` (4 sites) -**What's wrong:** passage HTML returned by the ESV API is injected with +**What was wrong:** passage HTML returned by the ESV API was injected with `element.innerHTML = passage.html`. Flagged by `no-unsanitized/property` and -`@microsoft/sdl/no-inner-html`. The content is trusted (comes from the ESV API -over HTTPS), so this is currently accepted and disabled inline at each site. +`@microsoft/sdl/no-inner-html`. -**Suggested fix:** parse the HTML and rebuild it with Obsidian DOM helpers -(`createEl`/`createDiv`), or sanitize before insertion, so no raw HTML string is -written to the DOM. +**Fix applied:** all four sites now run the passage HTML through Obsidian's +`sanitizeHTMLToDom()` and append the returned `DocumentFragment`, so no raw HTML +string is written to the DOM. The verse-preview extraction (`showVersePreview`) +parses into the sanitized fragment and queries/clones paragraphs off of it +instead of a temporary `innerHTML` div. The inline `eslint-disable` comments at +each site were removed. --- diff --git a/src/components/BibleReferenceRenderer.ts b/src/components/BibleReferenceRenderer.ts index ff7f203..3f6c930 100644 --- a/src/components/BibleReferenceRenderer.ts +++ b/src/components/BibleReferenceRenderer.ts @@ -1,4 +1,4 @@ -import {Notice} from "obsidian"; +import {Notice, sanitizeHTMLToDom} from "obsidian"; import {BibleContentService} from "../services/BibleContentService"; import {BibleNavigation} from "./BibleNavigation"; import DisciplesJournalPlugin from "src/core/DisciplesJournalPlugin"; @@ -133,8 +133,7 @@ export class BibleReferenceRenderer { // Add verses const passageEl = el.doc.createElement('div'); passageEl.classList.add('bible-passage-text'); - // eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md - passageEl.innerHTML = response.passage.html; + passageEl.appendChild(sanitizeHTMLToDom(response.passage.html)); containerEl.appendChild(passageEl); el.appendChild(containerEl); @@ -187,18 +186,16 @@ export class BibleReferenceRenderer { // Use the HTML content directly, but try to extract just the portion we need // for the preview (to avoid showing footnotes, chapter headings, etc.) try { - // Create a temporary element to parse the HTML - const tempEl = element.doc.createElement('div'); - // eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md - tempEl.innerHTML = passage.html; + // Parse and sanitize the HTML into a detached fragment we can inspect + const parsed = sanitizeHTMLToDom(passage.html); // Remove footnote sections before extracting paragraphs if (this.plugin.settings.hideFootnotesInPreview) { - tempEl.querySelectorAll('.footnotes, .extra_text').forEach(el => el.remove()); + parsed.querySelectorAll('.footnotes, .extra_text').forEach(el => el.remove()); } // Find and extract the main verse content (paragraphs) - const paragraphs = tempEl.querySelectorAll('p:not(.extra_text)'); + const paragraphs = parsed.querySelectorAll('p:not(.extra_text)'); if (paragraphs.length > 0) { for (let i = 0; i < paragraphs.length; i++) { const cloned = paragraphs[i].cloneNode(true); @@ -213,13 +210,11 @@ export class BibleReferenceRenderer { } } else { // Fallback if we can't extract the verses properly - // eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md - contentEl.innerHTML = passage.html; + contentEl.appendChild(sanitizeHTMLToDom(passage.html)); } } catch (error) { console.error("Error extracting verse content from HTML:", error); - // eslint-disable-next-line no-unsanitized/property, @microsoft/sdl/no-inner-html -- deferred: ESV API returns trusted formatted HTML; see FOLLOWUP.md - contentEl.innerHTML = passage.html; + contentEl.appendChild(sanitizeHTMLToDom(passage.html)); } versePreviewEl.appendChild(contentEl);