fix: consistent handling of markdown between post preview and preview server
This commit is contained in:
@@ -19,6 +19,7 @@ import { AutoSaveManager, getContrastColor } from '../../utils';
|
|||||||
import { parseMacros, getMacro } from '../../macros/registry';
|
import { parseMacros, getMacro } from '../../macros/registry';
|
||||||
import { InsertModal } from '../InsertModal';
|
import { InsertModal } from '../InsertModal';
|
||||||
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
||||||
|
import { marked } from 'marked';
|
||||||
import './Editor.css';
|
import './Editor.css';
|
||||||
|
|
||||||
/** Get display name for media: prefer title over originalName */
|
/** Get display name for media: prefer title over originalName */
|
||||||
@@ -134,7 +135,7 @@ const renderMacroSync = (name: string, params: Record<string, string>, postId?:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Simple markdown to HTML converter for preview
|
// 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
|
// First, render macros
|
||||||
const macros = parseMacros(markdown);
|
const macros = parseMacros(markdown);
|
||||||
let result = markdown;
|
let result = markdown;
|
||||||
@@ -145,33 +146,12 @@ const markdownToHtml = (markdown: string, postId?: string): string => {
|
|||||||
const rendered = renderMacroSync(macro.name, macro.params, postId);
|
const rendered = renderMacroSync(macro.name, macro.params, postId);
|
||||||
result = result.slice(0, macro.start) + rendered + result.slice(macro.end);
|
result = result.slice(0, macro.start) + rendered + result.slice(macro.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return marked.parse(result, {
|
||||||
// Escape HTML (but not our rendered macros - they're already safe)
|
gfm: true,
|
||||||
// We need to be careful here - macro output contains HTML
|
breaks: false,
|
||||||
// For safety, we skip escaping since we control the macro output
|
async: false,
|
||||||
// Headers
|
}) as string;
|
||||||
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
||||||
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
||||||
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
||||||
// Bold
|
|
||||||
.replace(/\*\*(.*?)\*\*/gim, '<strong>$1</strong>')
|
|
||||||
// Italic
|
|
||||||
.replace(/\*(.*?)\*/gim, '<em>$1</em>')
|
|
||||||
// Images
|
|
||||||
.replace(/!\[(.*?)\]\((.*?)\)/gim, '<img alt="$1" src="$2" style="max-width: 100%;" />')
|
|
||||||
// Links
|
|
||||||
.replace(/\[(.*?)\]\((.*?)\)/gim, '<a href="$2" target="_blank">$1</a>')
|
|
||||||
// Code blocks
|
|
||||||
.replace(/```([\s\S]*?)```/gim, '<pre><code>$1</code></pre>')
|
|
||||||
// Inline code
|
|
||||||
.replace(/`(.*?)`/gim, '<code>$1</code>')
|
|
||||||
// Blockquotes
|
|
||||||
.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>')
|
|
||||||
// Horizontal rules
|
|
||||||
.replace(/^---$/gim, '<hr />')
|
|
||||||
// Line breaks
|
|
||||||
.replace(/\n/g, '<br />');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
21
tests/renderer/components/EditorMarkdownPreview.test.ts
Normal file
21
tests/renderer/components/EditorMarkdownPreview.test.ts
Normal file
@@ -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(/<blockquote>/g) ?? []).length).toBe(1);
|
||||||
|
expect((html.match(/<p>/g) ?? []).length).toBe(1);
|
||||||
|
expect(html).not.toContain('<br');
|
||||||
|
expect(html).toContain('Georg Bauer');
|
||||||
|
expect(html).toContain('eMail: gb at rfc1437.de');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user