From 7389d265206a095349c4512daad319635f4efb98 Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 29 Jul 2026 09:56:51 -0400 Subject: [PATCH] fix(markdown): convert single newlines to hard breaks matching Obsidian behavior Add _normalize_line_breaks() pre-processor that converts single \n to two-spaces+\n (hard break) before mistune rendering, while protecting fenced code blocks. This matches Obsidian's default 'Strict Line Breaks = off' behavior where standalone lines render on separate rows instead of merging into a single paragraph. --- backend/main.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/main.py b/backend/main.py index 6e0aabb..46eb98a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -908,6 +908,27 @@ def _convert_wikilinks(content: str, current_vault: str) -> str: return re.sub(pattern, _replace, content) +def _normalize_line_breaks(text: str) -> str: + """Convert single newlines to hard breaks (matching Obsidian default behavior). + + In standard Markdown, a single ``\\n`` is a "soft break" — it renders as a space, + not a visible line break. Obsidian defaults to treating single newlines as hard + breaks (equivalent to ``
``). This function pre-processes the Markdown source + so that mistune renders standalone lines on separate rows, while still honouring + blank lines as paragraph separators. + + Fenced code blocks (`` ``` ``) are left untouched so their internal newlines are + preserved verbatim. + """ + parts = re.split(r"(```[\s\S]*?```)", text) + for i, part in enumerate(parts): + if part.startswith("```"): + continue # Protect fenced code blocks + # Single \n (not preceded or followed by another \n) → two spaces + \n + parts[i] = re.sub(r"(? str: """Render a markdown string to HTML with wikilink and image support. @@ -936,6 +957,9 @@ def _render_markdown(raw_md: str, vault_name: str, current_file_path: Path | Non # Convert wikilinks converted = _convert_wikilinks(raw_md, vault_name) + # Normalize line breaks to match Obsidian behavior (single \n → hard break) + converted = _normalize_line_breaks(converted) + rendered = _markdown_renderer(converted) # Add heading IDs for TOC navigation