Fix spacing on MD upload

This commit is contained in:
adilallo
2025-09-07 12:24:04 -06:00
parent b85b4248c0
commit d22f10af0d
3 changed files with 154 additions and 47 deletions
+2 -9
View File
@@ -105,18 +105,11 @@ export default async function BlogPostPage({ params }) {
})}
</time>
</div>
<p className="text-xl text-gray-700 leading-relaxed">
{post.frontmatter.description}
</p>
</header>
{/* Article Content */}
<div className="prose prose-lg max-w-none">
<div
dangerouslySetInnerHTML={{ __html: post.htmlContent }}
className="text-gray-800 leading-relaxed"
/>
<div className="post-body max-w-none text-gray-800 leading-relaxed text-lg">
<div dangerouslySetInnerHTML={{ __html: post.htmlContent }} />
</div>
</article>
+39
View File
@@ -1068,4 +1068,43 @@
text-indent: 0px;
margin-bottom: 0px;
}
/* Blog post body styling with semantic spacing */
.post-body p {
/* Scales with font size - uses logical properties for better writing mode support */
margin-block: 1em;
}
/* Extra blank lines from markdown -> visible gaps that scale with font size */
.post-body .md-gap {
/* Each "extra blank line" is one em; scales with font size */
block-size: calc(1em * var(--gap, 1));
margin: 0; /* no extra margins around the gap */
line-height: 1; /* prevent tall line-height from compounding */
}
/* Heading rhythm for better typography */
.post-body h1 {
margin-block: 1.5em 0.6em;
}
.post-body h2 {
margin-block: 1.4em 0.6em;
}
.post-body h3 {
margin-block: 1.2em 0.5em;
}
.post-body h4 {
margin-block: 1.1em 0.5em;
}
.post-body h5 {
margin-block: 1em 0.4em;
}
.post-body h6 {
margin-block: 1em 0.4em;
}
/* Ensure line breaks are visible */
.post-body br {
display: block;
}
}
+113 -38
View File
@@ -135,58 +135,133 @@ function generateHeadingId(text) {
/**
* Convert markdown to HTML with enhanced formatting
* - Preserves extra blank lines between paragraphs as visible gaps
* (each extra blank line becomes <p class="md-gap">&nbsp;</p>)
* @param {string} markdown - Raw markdown content
* @returns {string} HTML content
*/
function markdownToHtml(markdown) {
if (!markdown) return "";
// Normalize line endings
const GAP_TOKEN = "<GAP/>";
const src = String(markdown).replace(/\r\n?/g, "\n");
// For 3+ consecutive newlines, keep 2 for the paragraph break and
// emit a counted gap token for additional blank lines to preserve spacing.
const withGaps = src.replace(/\n{3,}/g, (m) => {
const extra = m.length - 2;
return `\n\n<GAP:${extra}/>`;
});
return (
markdown
withGaps
// Headers with IDs
.replace(/^### (.*$)/gim, (match, text) => {
const id = generateHeadingId(text);
return `<h3 id="${id}">${text}</h3>`;
})
.replace(/^## (.*$)/gim, (match, text) => {
const id = generateHeadingId(text);
return `<h2 id="${id}">${text}</h2>`;
})
.replace(/^# (.*$)/gim, (match, text) => {
const id = generateHeadingId(text);
return `<h1 id="${id}">${text}</h1>`;
})
// Bold and italic
.replace(/\*\*(.*?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.*?)\*/g, "<em>$1</em>")
// Code blocks
.replace(
/^###### (.*$)/gim,
(m, t) => `<h6 id="${generateHeadingId(t)}">${t}</h6>`
)
.replace(
/^##### (.*$)/gim,
(m, t) => `<h5 id="${generateHeadingId(t)}">${t}</h5>`
)
.replace(
/^#### (.*$)/gim,
(m, t) => `<h4 id="${generateHeadingId(t)}">${t}</h4>`
)
.replace(
/^### (.*$)/gim,
(m, t) => `<h3 id="${generateHeadingId(t)}">${t}</h3>`
)
.replace(
/^## (.*$)/gim,
(m, t) => `<h2 id="${generateHeadingId(t)}">${t}</h2>`
)
.replace(
/^# (.*$)/gim,
(m, t) => `<h1 id="${generateHeadingId(t)}">${t}</h1>`
)
// Code fences (block) and inline code
.replace(
/```(\w+)?\n([\s\S]*?)\n```/g,
'<pre><code class="language-$1">$2</code></pre>'
(m, lang = "", code) =>
`<pre><code class="language-${lang}">${code}</code></pre>`
)
.replace(/`([^`]+)`/g, "<code>$1</code>")
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>')
// Lists
.replace(/^\* (.*$)/gim, "<li>$1</li>")
.replace(/^- (.*$)/gim, "<li>$1</li>")
.replace(/(<li>.*<\/li>)/gim, "<ul>$1</ul>")
// Bold and italic (strong before em to avoid overlap issues)
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
.replace(/\*(.+?)\*/g, "<em>$1</em>")
// Links and images
.replace(
/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g,
(m, alt, src, title = "") =>
`<img alt="${alt}" src="${src}"${title ? ` title="${title}"` : ""}>`
)
.replace(
/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g,
(m, text, href, title = "") =>
`<a href="${href}"${title ? ` title="${title}"` : ""}>${text}</a>`
)
// Blockquotes
.replace(/^> (.*$)/gim, "<blockquote><p>$1</p></blockquote>")
// Horizontal rules
.replace(/^---$/gm, "<hr>")
.replace(/^\*\*\*$/gm, "<hr>")
// Paragraphs
.replace(/\n\n/g, "</p><p>")
.replace(/^(?!<[h|u|li|blockquote|hr|pre])(.*$)/gim, "<p>$1</p>")
// Clean up empty paragraphs and fix list wrapping
.replace(/<p><\/p>/g, "")
.replace(/<p>(.*?)<\/p>/g, (match, content) => {
return content.trim() ? match : "";
.replace(/^(>\s?.+)(\n(>\s?.+))*$/gim, (m) => {
const inner = m.replace(/^>\s?/gm, "");
return `<blockquote><p>${inner.replace(
/\n{2,}/g,
"</p><p>"
)}</p></blockquote>`;
})
.replace(/<\/ul>\s*<ul>/g, "") // Merge consecutive ul elements
.replace(/<ul>\s*<\/ul>/g, "")
); // Remove empty ul elements
// Lists (ul/ol)
.replace(/^(\s*[-*]\s.+(?:\n\s*[-*]\s.+)*)/gim, (m) => {
const items = m
.trim()
.split(/\n/)
.map((l) => l.replace(/^\s*[-*]\s+/, ""))
.map((t) => `<li>${t}</li>`)
.join("");
return `<ul>${items}</ul>`;
})
.replace(/^(\s*\d+\.\s.+(?:\n\s*\d+\.\s.+)*)/gim, (m) => {
const items = m
.trim()
.split(/\n/)
.map((l) => l.replace(/^\s*\d+\.\s+/, ""))
.map((t) => `<li>${t}</li>`)
.join("");
return `<ol>${items}</ol>`;
})
// Horizontal rules
.replace(/^\s*(?:-{3,}|\*{3,})\s*$/gm, "<hr>")
// Paragraphs:
// 1) Convert double newlines to paragraph boundaries
.replace(/\n\n/g, "</p><p>")
// 2) Convert single line breaks to <br> tags within paragraphs
.replace(/(?<!\n)\n(?!\n)/g, "<br>")
// 3) Wrap remaining bare lines that are not already block-level elements.
// (Also skip our GAP_TOKEN so we can turn it into gap paragraphs later.)
.replace(
/^(?!\s*<(h[1-6]|ul|ol|li|blockquote|hr|pre|code|table|img)\b)(?!\s*<\/)(?!\s*<GAP\/>)(.+)$/gim,
"<p>$2</p>"
)
// Clean up truly empty paragraphs but keep &nbsp; gap paragraphs
.replace(/<p>\s*<\/p>/g, "")
// Turn counted GAP tokens into explicit, styleable gap elements
.replace(
/<GAP:(\d+)\/>/g,
(m, n) =>
`<div class="md-gap" style="--gap:${Number(
n
)}" aria-hidden="true"></div>`
)
);
}
/**