260720
This commit is contained in:
125
utils/markdown.js
Normal file
125
utils/markdown.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* Lightweight markdown to HTML converter for uni-app rich-text component.
|
||||
* Supports: headings, bold, italic, inline code, code blocks,
|
||||
* unordered lists, ordered lists, blockquotes, horizontal rules, line breaks.
|
||||
*/
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert markdown string to HTML string.
|
||||
* @param {string} text
|
||||
* @returns {string}
|
||||
*/
|
||||
export function renderMarkdown(text) {
|
||||
if (!text) return ''
|
||||
|
||||
// 1. Protect code blocks first (```)
|
||||
const codeBlocks = []
|
||||
let html = text.replace(/```([\w]*)\n?([\s\S]*?)```/g, (_, lang, code) => {
|
||||
const idx = codeBlocks.length
|
||||
const escaped = escapeHtml(code.replace(/^\n/, '').replace(/\n$/, ''))
|
||||
const langAttr = lang ? ` class="language-${lang}"` : ''
|
||||
codeBlocks.push(`<pre><code${langAttr}>${escaped}</code></pre>`)
|
||||
return `\x00CODE${idx}\x00`
|
||||
})
|
||||
|
||||
// 2. Escape remaining HTML to prevent injection
|
||||
html = html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
|
||||
// 3. Protect inline code (`)
|
||||
const inlineCodes = []
|
||||
html = html.replace(/`([^`\n]+)`/g, (_, code) => {
|
||||
const idx = inlineCodes.length
|
||||
inlineCodes.push(`<code>${escapeHtml(code)}</code>`)
|
||||
return `\x00INLINE${idx}\x00`
|
||||
})
|
||||
|
||||
// 4. Headings (must be at line start)
|
||||
html = html.replace(/^#{4,6} (.+)$/gm, '<h6>$1</h6>')
|
||||
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
|
||||
// 5. Horizontal rules
|
||||
html = html.replace(/^[ \t]*(?:---+|===+|\*\*\*+)[ \t]*$/gm, '<hr/>')
|
||||
|
||||
// 6. Blockquotes
|
||||
html = html.replace(/^> ?(.*)$/gm, '<blockquote>$1</blockquote>')
|
||||
|
||||
// 7. Bold + Italic
|
||||
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>')
|
||||
html = html.replace(/___(.+?)___/g, '<strong><em>$1</em></strong>')
|
||||
|
||||
// 8. Bold
|
||||
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>')
|
||||
|
||||
// 9. Italic
|
||||
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
html = html.replace(/_(.+?)_/g, '<em>$1</em>')
|
||||
|
||||
// 10. Lists — collect consecutive list lines into <ul> or <ol>
|
||||
const lines = html.split('\n')
|
||||
const result = []
|
||||
let inUl = false
|
||||
let inOl = false
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i]
|
||||
const ulMatch = line.match(/^[ \t]*[-*+] (.+)$/)
|
||||
const olMatch = line.match(/^[ \t]*\d+\. (.+)$/)
|
||||
|
||||
if (ulMatch) {
|
||||
if (!inUl) {
|
||||
if (inOl) { result.push('</ol>'); inOl = false }
|
||||
result.push('<ul>')
|
||||
inUl = true
|
||||
}
|
||||
result.push(`<li>${ulMatch[1]}</li>`)
|
||||
} else if (olMatch) {
|
||||
if (!inOl) {
|
||||
if (inUl) { result.push('</ul>'); inUl = false }
|
||||
result.push('<ol>')
|
||||
inOl = true
|
||||
}
|
||||
result.push(`<li>${olMatch[1]}</li>`)
|
||||
} else {
|
||||
if (inUl) { result.push('</ul>'); inUl = false }
|
||||
if (inOl) { result.push('</ol>'); inOl = false }
|
||||
result.push(line)
|
||||
}
|
||||
}
|
||||
if (inUl) result.push('</ul>')
|
||||
if (inOl) result.push('</ol>')
|
||||
|
||||
html = result.join('\n')
|
||||
|
||||
// 11. Paragraphs — split by blank lines, wrap plain text blocks in <p>
|
||||
const blocks = html.split(/\n{2,}/)
|
||||
html = blocks.map(block => {
|
||||
block = block.trim()
|
||||
if (!block) return ''
|
||||
// Already wrapped in a block-level tag — leave as-is
|
||||
if (/^<(h[1-6]|ul|ol|li|pre|blockquote|hr)[\s/>]/.test(block)) return block
|
||||
// Has multiple lines → join with <br/>
|
||||
block = block.replace(/\n/g, '<br/>')
|
||||
return `<p>${block}</p>`
|
||||
}).join('\n')
|
||||
|
||||
// 12. Restore inline codes and code blocks
|
||||
inlineCodes.forEach((code, idx) => {
|
||||
html = html.replace(`\x00INLINE${idx}\x00`, code)
|
||||
})
|
||||
codeBlocks.forEach((code, idx) => {
|
||||
html = html.replace(`\x00CODE${idx}\x00`, code)
|
||||
})
|
||||
|
||||
return html
|
||||
}
|
||||
Reference in New Issue
Block a user