From 5034f156ee42bf1352abc5086831b472e7e1d58f Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 10 Sep 2026 13:12:12 -0500 Subject: [PATCH] Fix #205: strip whitespace-only text nodes between codeline blocks paste-lines.js rebuilt the codebody with innerHTML but relied on the server-side '\n' delimiters disappearing between the per-line blocks. Under white-space: pre any whitespace-only text node that survives (cached HTML, template drift) renders as a phantom row and drifts the gutter numbers off their lines. splitLines() now strips whitespace-only text nodes after rebuilding, keeping renumber() geometry intact. --- internal/api/ratelimit_test.go | 7 +++++-- internal/lang/highlight.go | 4 ++++ internal/web/static/paste-lines.js | 11 ++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/internal/api/ratelimit_test.go b/internal/api/ratelimit_test.go index 731a879..544373d 100644 --- a/internal/api/ratelimit_test.go +++ b/internal/api/ratelimit_test.go @@ -166,8 +166,11 @@ func TestHighlightCode(t *testing.T) { if plain != "<b>x</b>" { t.Fatalf("plain escaping wrong: %q", plain) } - // line count preserved (gutter alignment) - if got := len(splitLines(lang.HighlightCode("a\nb\nc", "go"))); got != 3 { + // #205: newline join preserved as the delimiter paste-lines.js splits on; + // per-line segments survive and the client joins with '' so no newline + // text node reaches the rendered DOM. + hl := lang.HighlightCode("a\nb\nc", "go") + if got := len(splitLines(hl)); got != 3 { t.Fatalf("want 3 lines, got %d", got) } } diff --git a/internal/lang/highlight.go b/internal/lang/highlight.go index 1aaad90..975829f 100644 --- a/internal/lang/highlight.go +++ b/internal/lang/highlight.go @@ -146,5 +146,9 @@ func HighlightCode(content, langID string) string { for i, line := range lines { out[i] = highlightLine(line, h, langID) } + // Note: lines are joined with "\n" on purpose — paste-lines.js splits on + // the newline to build its per-line .codeline blocks, then joins those + // with "" and strips whitespace-only text nodes (#205), so no newline + // text node ever reaches the rendered DOM. return strings.Join(out, "\n") } diff --git a/internal/web/static/paste-lines.js b/internal/web/static/paste-lines.js index 41537f9..e385e9d 100644 --- a/internal/web/static/paste-lines.js +++ b/internal/web/static/paste-lines.js @@ -17,7 +17,10 @@ // because HighlightCode highlights per line) in a .codeline block. The // spans are display:block, so they are joined with '' — a '\n' join leaves // newline text nodes between blocks that pre-wrap renders as an extra line - // box per line, which would shift every following number down one row (#167). + // box per line, which would shift every following number down one row (#167, + // #205). Whitespace-only text nodes are also stripped defensively below: any + // that reach the DOM (older cached HTML, other templates) render as phantom + // rows under white-space: pre and drift the gutter. function splitLines() { var html = body.innerHTML; var parts = html.split('\n'); @@ -26,6 +29,12 @@ out.push('' + parts[i] + ''); } body.innerHTML = out.join(''); + // #205: strip whitespace-only text nodes between the .codeline blocks. + var ws = []; + for (var n = body.firstChild; n; n = n.nextSibling) { + if (n.nodeType === 3 && !/\S/.test(n.nodeValue)) ws.push(n); + } + for (var w = 0; w < ws.length; w++) body.removeChild(ws[w]); } // One .gutline block per visual row. The gutter must reproduce the code -- 2.54.0