diff --git a/internal/markdown/codeblock.go b/internal/markdown/codeblock.go index efa7399..76ac922 100644 --- a/internal/markdown/codeblock.go +++ b/internal/markdown/codeblock.go @@ -24,6 +24,10 @@ func PreprocessCodeBlocks(source []byte, plantumlServer string) []byte { switch lang { case "svg": + // Remove blank lines from SVG content to prevent goldmark from + // splitting the HTML block (CommonMark HTML block type 6 ends at + // a blank line). + code = removeBlankLines(code) return []byte(fmt.Sprintf("\n
\n%s\n", code)) @@ -43,6 +47,18 @@ func PreprocessCodeBlocks(source []byte, plantumlServer string) []byte { }) } +// removeBlankLines removes blank lines (empty or whitespace-only) from the text. +func removeBlankLines(s string) string { + var b strings.Builder + for _, line := range strings.Split(s, "\n") { + if strings.TrimSpace(line) != "" { + b.WriteString(line) + b.WriteByte('\n') + } + } + return b.String() +} + // encodePlantUML encodes PlantUML text for the PlantUML server URL. // Uses the ~h (hex encoding) format: each byte is converted to its 2-digit hex representation. func encodePlantUML(text string) string { diff --git a/internal/markdown/codeblock_test.go b/internal/markdown/codeblock_test.go index 5b2ddac..8a0ca6f 100644 --- a/internal/markdown/codeblock_test.go +++ b/internal/markdown/codeblock_test.go @@ -53,6 +53,25 @@ func TestPreprocessCodeBlocks_SVG(t *testing.T) { } } +func TestPreprocessCodeBlocks_SVGWithBlankLines(t *testing.T) { + input := []byte("```svg\n\n```\n") + result := PreprocessCodeBlocks(input, "") + s := string(result) + + if !strings.Contains(s, "svg-container") { + t.Error("should render SVG container") + } + if strings.Contains(s, "\n\n") { + t.Error("should not contain blank lines in SVG output (would break goldmark HTML block parsing)") + } + if !strings.Contains(s, "