| |
| function escapeHtml(str) { |
| return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') |
| } |
|
|
| function parseInline(md) { |
| |
| md = md.replace(/`([^`]+)`/g, '<code>$1</code>') |
| |
| md = md.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>') |
| md = md.replace(/__([^_]+)__/g, '<strong>$1</strong>') |
| |
| md = md.replace(/\*([^*]+)\*/g, '<em>$1</em>') |
| md = md.replace(/_([^_]+)_/g, '<em>$1</em>') |
| |
| 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 || '' |
| |
| const blocks = raw.split(/\n{2,}/g) |
| const html = blocks.map(block => { |
| const line = block.trim() |
| if (!line) return '' |
| |
| const hMatch = line.match(/^(#{1,6})\s+(.*)$/) |
| if (hMatch) { |
| const level = Math.min(6, hMatch[1].length) |
| const content = parseInline(escapeHtml(hMatch[2])) |
| |
| return `<h${level} class="text-lg font-semibold text-gray-800 dark:text-gray-100 mb-2">${content}</h${level}>` |
| } |
| |
| |
| 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 } |