Skip to content

Commit f25dfbb

Browse files
committed
fix(mdviewer): code block line annotation on first load, disable spellcheck
Fix _annotateCodeBlockLines to work even when morphdom strips data-source-line from <pre> on first render — falls back to estimating source line from nearest preceding sibling. Skip already-annotated blocks to avoid double processing. Disable spellcheck, autocorrect, autocomplete, and autocapitalize on all code blocks in the viewer. Remove debug exports and restore sandbox.
1 parent 5c018a7 commit f25dfbb

2 files changed

Lines changed: 33 additions & 23 deletions

File tree

src-mdviewer/src/bridge.js

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { setLocale } from "./core/i18n.js";
99
import { marked } from "marked";
1010
import * as docCache from "./core/doc-cache.js";
1111
import { broadcastSelectionStateSync } from "./components/editor.js";
12-
import { renderAfterHTML } from "./components/viewer.js";
1312

1413
let _syncId = 0;
1514
let _lastReceivedSyncId = -1;
@@ -572,16 +571,11 @@ function handleSetContent(data) {
572571
docCache.switchTo(filePath);
573572
}
574573

575-
// Run post-render processing (Prism highlighting, code block line annotation)
576-
const content = document.getElementById("viewer-content");
577-
if (content) {
578-
renderAfterHTML(content, parseResult);
579-
}
580-
581574
setState({
582575
currentContent: markdown,
583576
parseResult: parseResult
584577
});
578+
// file:rendered triggers viewer.js handler which does morphdom + renderAfterHTML
585579
emit("file:rendered", parseResult);
586580
_suppressContentChange = false;
587581
}
@@ -675,13 +669,6 @@ function handleSwitchFile(data) {
675669
// Cache hit, content unchanged — instant switch
676670
docCache.switchTo(filePath);
677671

678-
// Ensure code blocks are highlighted and line-annotated (may be missing on first load)
679-
const cachedContent = document.getElementById("viewer-content");
680-
if (cachedContent && !cachedContent.querySelector("pre code span[data-source-line]") &&
681-
cachedContent.querySelector("pre[data-source-line]")) {
682-
renderAfterHTML(cachedContent, existing.parseResult);
683-
}
684-
685672
setState({
686673
currentContent: markdown,
687674
parseResult: existing.parseResult
@@ -706,12 +693,6 @@ function handleSwitchFile(data) {
706693
docCache.createEntry(filePath, markdown, parseResult);
707694
docCache.switchTo(filePath);
708695

709-
// Run post-render processing on newly created entry
710-
const newContent = document.getElementById("viewer-content");
711-
if (newContent) {
712-
renderAfterHTML(newContent, parseResult);
713-
}
714-
715696
// Restore scroll position and edit mode from reload if applicable
716697
if (_pendingReloadScroll && _pendingReloadScroll.filePath === filePath) {
717698
const entry = docCache.getEntry(filePath);

src-mdviewer/src/components/viewer.js

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,14 @@ export function highlightCode() {
183183
Prism.highlightElement(block);
184184
});
185185

186+
// Disable spellcheck and autocomplete inside code blocks
187+
document.querySelectorAll("#viewer-content pre").forEach((pre) => {
188+
pre.spellcheck = false;
189+
pre.setAttribute("autocorrect", "off");
190+
pre.setAttribute("autocomplete", "off");
191+
pre.setAttribute("autocapitalize", "off");
192+
});
193+
186194
// After Prism highlighting, add per-line data-source-line spans inside code blocks
187195
_annotateCodeBlockLines();
188196
}
@@ -193,12 +201,33 @@ export function highlightCode() {
193201
* Must run AFTER Prism highlighting since Prism replaces innerHTML.
194202
*/
195203
function _annotateCodeBlockLines() {
196-
const pres = document.querySelectorAll("#viewer-content pre[data-source-line]");
204+
// Process all pre elements, not just those with data-source-line
205+
// (morphdom may strip the attr on first render)
206+
const pres = document.querySelectorAll("#viewer-content pre");
197207
pres.forEach((pre) => {
198208
const code = pre.querySelector("code");
199209
if (!code) return;
200-
const preSourceLine = parseInt(pre.getAttribute("data-source-line"), 10);
201-
if (isNaN(preSourceLine)) return;
210+
// Already annotated?
211+
if (code.querySelector("span[data-source-line]")) return;
212+
213+
let preSourceLine = parseInt(pre.getAttribute("data-source-line"), 10);
214+
if (isNaN(preSourceLine)) {
215+
// Fallback: find the nearest preceding sibling with data-source-line
216+
// and estimate this pre's line from it
217+
let prev = pre.previousElementSibling;
218+
while (prev && !prev.hasAttribute("data-source-line")) {
219+
prev = prev.previousElementSibling;
220+
}
221+
if (prev) {
222+
const prevLine = parseInt(prev.getAttribute("data-source-line"), 10);
223+
// Rough estimate: count text lines in the previous element
224+
const prevText = prev.textContent || "";
225+
const prevLines = (prevText.match(/\n/g) || []).length + 1;
226+
preSourceLine = prevLine + prevLines + 1; // +1 for blank line between
227+
} else {
228+
return; // Can't determine line, skip
229+
}
230+
}
202231
// Code content starts after the ``` line
203232
const codeStartLine = preSourceLine + 1;
204233

0 commit comments

Comments
 (0)