fix(markdown): convert single newlines to hard breaks matching Obsidian behavior
CI / lint (push) Failing after 36s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 14s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Failing after 36s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 14s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
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.
This commit is contained in:
@@ -908,6 +908,27 @@ def _convert_wikilinks(content: str, current_vault: str) -> str:
|
|||||||
return re.sub(pattern, _replace, content)
|
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 ``<br>``). 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"(?<!\n)\n(?!\n)", " \n", part)
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def _render_markdown(raw_md: str, vault_name: str, current_file_path: Path | None = None) -> str:
|
def _render_markdown(raw_md: str, vault_name: str, current_file_path: Path | None = None) -> str:
|
||||||
"""Render a markdown string to HTML with wikilink and image support.
|
"""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
|
# Convert wikilinks
|
||||||
converted = _convert_wikilinks(raw_md, vault_name)
|
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)
|
rendered = _markdown_renderer(converted)
|
||||||
|
|
||||||
# Add heading IDs for TOC navigation
|
# Add heading IDs for TOC navigation
|
||||||
|
|||||||
Reference in New Issue
Block a user