Skip to content

Commit 22738a4

Browse files
fix(markdown-preview): remove excessive line space in rendering
1 parent 1b8d03c commit 22738a4

2 files changed

Lines changed: 112 additions & 29 deletions

File tree

client/src/components/tools/markdown-preview.tsx

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,78 @@ export default function MarkdownPreview() {
1818
};
1919

2020
const convertMarkdownToHTML = (markdown: string): string => {
21-
return markdown
22-
// Headers
23-
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
24-
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
25-
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
26-
// Bold
27-
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
28-
.replace(/__(.*?)__/g, '<strong>$1</strong>')
29-
// Italic
30-
.replace(/\*(.*?)\*/g, '<em>$1</em>')
31-
.replace(/_(.*?)_/g, '<em>$1</em>')
32-
// Code inline
33-
.replace(/`(.*?)`/g, '<code>$1</code>')
34-
// Code blocks
35-
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
36-
// Links
37-
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
38-
// Images
39-
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%; height: auto;" />')
40-
// Lists
41-
.replace(/^\* (.*$)/gim, '<ul><li>$1</li></ul>')
42-
.replace(/^\- (.*$)/gim, '<ul><li>$1</li></ul>')
43-
.replace(/^\d+\. (.*$)/gim, '<ol><li>$1</li></ol>')
44-
// Blockquotes
45-
.replace(/^> (.*$)/gim, '<blockquote>$1</blockquote>')
46-
// Horizontal rules
47-
.replace(/^---$/gim, '<hr>')
48-
// Line breaks
49-
.replace(/\n/g, '<br>');
21+
// Split into lines for better processing
22+
const lines = markdown.split('\n');
23+
const processedLines: string[] = [];
24+
25+
for (let i = 0; i < lines.length; i++) {
26+
const line = lines[i];
27+
let processedLine = line;
28+
29+
// Process inline formatting first
30+
processedLine = processedLine
31+
// Bold
32+
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
33+
.replace(/__(.*?)__/g, '<strong>$1</strong>')
34+
// Italic
35+
.replace(/\*(.*?)\*/g, '<em>$1</em>')
36+
.replace(/_(.*?)_/g, '<em>$1</em>')
37+
// Code inline
38+
.replace(/`(.*?)`/g, '<code>$1</code>')
39+
// Links
40+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
41+
// Images
42+
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%; height: auto;" />');
43+
44+
// Process block elements
45+
if (line.match(/^### (.*$)/)) {
46+
processedLine = `<h3>${line.replace(/^### /, '')}</h3>`;
47+
} else if (line.match(/^## (.*$)/)) {
48+
processedLine = `<h2>${line.replace(/^## /, '')}</h2>`;
49+
} else if (line.match(/^# (.*$)/)) {
50+
processedLine = `<h1>${line.replace(/^# /, '')}</h1>`;
51+
} else if (line.match(/^> (.*$)/)) {
52+
processedLine = `<blockquote>${line.replace(/^> /, '')}</blockquote>`;
53+
} else if (line.match(/^---$/)) {
54+
processedLine = '<hr>';
55+
} else if (line.match(/^\* (.*$)/) || line.match(/^\- (.*$)/)) {
56+
processedLine = `<li>${line.replace(/^[\*\-] /, '')}</li>`;
57+
} else if (line.match(/^\d+\. (.*$)/)) {
58+
processedLine = `<li>${line.replace(/^\d+\. /, '')}</li>`;
59+
} else if (line.trim() === '') {
60+
// Empty lines become paragraph breaks
61+
processedLine = '</p><p>';
62+
} else {
63+
// Regular text lines
64+
processedLine = processedLine;
65+
}
66+
67+
processedLines.push(processedLine);
68+
}
69+
70+
// Join lines and handle special cases
71+
let result = processedLines.join('\n');
72+
73+
// Handle code blocks (multi-line)
74+
result = result.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>');
75+
76+
// Handle lists properly
77+
result = result.replace(/(<li>.*<\/li>)/g, (match) => {
78+
// Check if it's a numbered list or bullet list
79+
const isNumbered = match.includes('<li>') && /^\d+\./.test(match);
80+
const listType = isNumbered ? 'ol' : 'ul';
81+
return `<${listType}>${match}</${listType}>`;
82+
});
83+
84+
// Wrap in paragraphs and clean up
85+
result = result
86+
.replace(/^(?!<[hou][1-6lr]|<p>|<blockquote>|<pre>|<hr>)(.+)$/gm, '<p>$1</p>')
87+
.replace(/<\/p>\s*<p>/g, '</p><p>')
88+
.replace(/<p><\/p>/g, '') // Remove empty paragraphs
89+
.replace(/<p>(<[hou][1-6lr]|<blockquote>|<pre>|<hr>)/g, '$1') // Remove p tags around block elements
90+
.replace(/(<\/[hou][1-6lr]|<\/blockquote>|<\/pre>|<\/hr>)<\/p>/g, '$1'); // Remove closing p tags around block elements
91+
92+
return result;
5093
};
5194

5295
const clearAll = () => {

client/src/index.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,44 @@
9191
.regex-match {
9292
@apply bg-yellow-200 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-200;
9393
}
94+
95+
/* Custom prose styling for markdown preview */
96+
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
97+
margin-top: 1.5em;
98+
margin-bottom: 0.5em;
99+
}
100+
101+
.prose h1:first-child, .prose h2:first-child, .prose h3:first-child,
102+
.prose h4:first-child, .prose h5:first-child, .prose h6:first-child {
103+
margin-top: 0;
104+
}
105+
106+
.prose p {
107+
margin-top: 0.5em;
108+
margin-bottom: 0.5em;
109+
}
110+
111+
.prose p:first-child {
112+
margin-top: 0;
113+
}
114+
115+
.prose ul, .prose ol {
116+
margin-top: 0.5em;
117+
margin-bottom: 0.5em;
118+
}
119+
120+
.prose blockquote {
121+
margin-top: 0.5em;
122+
margin-bottom: 0.5em;
123+
}
124+
125+
.prose pre {
126+
margin-top: 0.5em;
127+
margin-bottom: 0.5em;
128+
}
129+
130+
.prose hr {
131+
margin-top: 1em;
132+
margin-bottom: 1em;
133+
}
94134
}

0 commit comments

Comments
 (0)