File size: 1,800 Bytes
309320b |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
// 一个小型且安全的 Markdown -> HTML 解析器(支持标题、段落、链接、粗体、斜体、行内代码)
function escapeHtml(str) {
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
}
function parseInline(md) {
// code
md = md.replace(/`([^`]+)`/g, '<code>$1</code>')
// bold **text** or __text__
md = md.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
md = md.replace(/__([^_]+)__/g, '<strong>$1</strong>')
// italic *text* or _text_
md = md.replace(/\*([^*]+)\*/g, '<em>$1</em>')
md = md.replace(/_([^_]+)_/g, '<em>$1</em>')
// links [text](url)
md = md.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (m, text, url) => {
const safeUrl = url.replace(/\"/g, '%22')
return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer" class="text-blue-600 underline dark:text-blue-400">${text}</a>`
})
return md
}
function parsedHeaderHtml(headerMarkdown){
const raw = headerMarkdown || ''
// split into lines and paragraphs
const blocks = raw.split(/\n{2,}/g)
const html = blocks.map(block => {
const line = block.trim()
if (!line) return ''
// heading
const hMatch = line.match(/^(#{1,6})\s+(.*)$/)
if (hMatch) {
const level = Math.min(6, hMatch[1].length)
const content = parseInline(escapeHtml(hMatch[2]))
// 增加块间距(mb-2)以扩大行之间的视觉间隔
return `<h${level} class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-2">${content}</h${level}>`
}
// normal paragraph
// 使用 leading-relaxed 增加行高,并用 mb-2 增加段落间距
return `<p class="text-sm text-gray-700 dark:text-gray-200 leading-relaxed mb-2">${parseInline(escapeHtml(line))}</p>`
}).join('\n')
return html
}
export { parsedHeaderHtml } |