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