From 201a74f447ca2f42c33dfc06d4e481257ef065e2 Mon Sep 17 00:00:00 2001 From: hugo Date: Mon, 16 Feb 2026 22:04:55 +0100 Subject: [PATCH] fix: consistent handling of markdown between post preview and preview server --- src/renderer/components/Editor/Editor.tsx | 36 +++++-------------- .../components/EditorMarkdownPreview.test.ts | 21 +++++++++++ 2 files changed, 29 insertions(+), 28 deletions(-) create mode 100644 tests/renderer/components/EditorMarkdownPreview.test.ts diff --git a/src/renderer/components/Editor/Editor.tsx b/src/renderer/components/Editor/Editor.tsx index 1fa6154..7fe9bff 100644 --- a/src/renderer/components/Editor/Editor.tsx +++ b/src/renderer/components/Editor/Editor.tsx @@ -19,6 +19,7 @@ import { AutoSaveManager, getContrastColor } from '../../utils'; import { parseMacros, getMacro } from '../../macros/registry'; import { InsertModal } from '../InsertModal'; import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal'; +import { marked } from 'marked'; import './Editor.css'; /** Get display name for media: prefer title over originalName */ @@ -134,7 +135,7 @@ const renderMacroSync = (name: string, params: Record, postId?: }; // Simple markdown to HTML converter for preview -const markdownToHtml = (markdown: string, postId?: string): string => { +export const markdownToHtml = (markdown: string, postId?: string): string => { // First, render macros const macros = parseMacros(markdown); let result = markdown; @@ -145,33 +146,12 @@ const markdownToHtml = (markdown: string, postId?: string): string => { const rendered = renderMacroSync(macro.name, macro.params, postId); result = result.slice(0, macro.start) + rendered + result.slice(macro.end); } - - return result - // Escape HTML (but not our rendered macros - they're already safe) - // We need to be careful here - macro output contains HTML - // For safety, we skip escaping since we control the macro output - // Headers - .replace(/^### (.*$)/gim, '

$1

') - .replace(/^## (.*$)/gim, '

$1

') - .replace(/^# (.*$)/gim, '

$1

') - // Bold - .replace(/\*\*(.*?)\*\*/gim, '$1') - // Italic - .replace(/\*(.*?)\*/gim, '$1') - // Images - .replace(/!\[(.*?)\]\((.*?)\)/gim, '$1') - // Links - .replace(/\[(.*?)\]\((.*?)\)/gim, '$1') - // Code blocks - .replace(/```([\s\S]*?)```/gim, '
$1
') - // Inline code - .replace(/`(.*?)`/gim, '$1') - // Blockquotes - .replace(/^\> (.*$)/gim, '
$1
') - // Horizontal rules - .replace(/^---$/gim, '
') - // Line breaks - .replace(/\n/g, '
'); + + return marked.parse(result, { + gfm: true, + breaks: false, + async: false, + }) as string; }; /** diff --git a/tests/renderer/components/EditorMarkdownPreview.test.ts b/tests/renderer/components/EditorMarkdownPreview.test.ts new file mode 100644 index 0000000..a589afd --- /dev/null +++ b/tests/renderer/components/EditorMarkdownPreview.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest'; +import { markdownToHtml } from '../../../src/renderer/components/Editor/Editor'; + +describe('Editor markdown preview rendering', () => { + it('renders continuous blockquote lines as a single blockquote paragraph (CommonMark softbreak behavior)', () => { + const markdown = [ + '> Georg Bauer', + '> Am Krug 40', + '> 48151 Münster', + '> eMail: gb at rfc1437.de', + ].join('\n'); + + const html = markdownToHtml(markdown, 'post-1'); + + expect((html.match(/
/g) ?? []).length).toBe(1); + expect((html.match(/

/g) ?? []).length).toBe(1); + expect(html).not.toContain('