mitai-jinkendo/frontend/src/utils/Markdown.jsx
Lars Stommer 89b6c0b072
Some checks are pending
Deploy to Raspberry Pi / deploy (push) Waiting to run
Build Test / build-frontend (push) Waiting to run
Build Test / lint-backend (push) Waiting to run
feat: initial commit – Mitai Jinkendo v9a
2026-03-16 13:35:11 +01:00

134 lines
3.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Lightweight Markdown renderer handles the subset used by the AI:
// ## Headings, **bold**, bullet lists, numbered lists, line breaks
export default function Markdown({ text }) {
if (!text) return null
const lines = text.split('\n')
const elements = []
let i = 0
const parseLine = (line) => {
// Parse inline **bold** and *italic*
const parts = []
let remaining = line
let key = 0
while (remaining.length > 0) {
const boldMatch = remaining.match(/^(.*?)\*\*(.*?)\*\*(.*)$/)
if (boldMatch) {
if (boldMatch[1]) parts.push(<span key={key++}>{boldMatch[1]}</span>)
parts.push(<strong key={key++}>{boldMatch[2]}</strong>)
remaining = boldMatch[3]
continue
}
const italicMatch = remaining.match(/^(.*?)\*(.*?)\*(.*)$/)
if (italicMatch) {
if (italicMatch[1]) parts.push(<span key={key++}>{italicMatch[1]}</span>)
parts.push(<em key={key++}>{italicMatch[2]}</em>)
remaining = italicMatch[3]
continue
}
parts.push(<span key={key++}>{remaining}</span>)
break
}
return parts.length === 1 && typeof parts[0].props?.children === 'string'
? parts[0].props.children
: parts
}
while (i < lines.length) {
const line = lines[i]
// Skip empty lines (add spacing)
if (line.trim() === '') {
elements.push(<div key={i} style={{ height: 8 }} />)
i++; continue
}
// H1
if (line.startsWith('# ')) {
elements.push(
<h1 key={i} style={{ fontSize: 18, fontWeight: 700, margin: '16px 0 8px', color: 'var(--text1)' }}>
{parseLine(line.slice(2))}
</h1>
)
i++; continue
}
// H2
if (line.startsWith('## ')) {
elements.push(
<h2 key={i} style={{ fontSize: 15, fontWeight: 700, margin: '14px 0 6px', color: 'var(--text1)',
display: 'flex', alignItems: 'center', gap: 6 }}>
{parseLine(line.slice(3))}
</h2>
)
i++; continue
}
// H3
if (line.startsWith('### ')) {
elements.push(
<h3 key={i} style={{ fontSize: 14, fontWeight: 600, margin: '10px 0 4px', color: 'var(--text1)' }}>
{parseLine(line.slice(4))}
</h3>
)
i++; continue
}
// Unordered list item
if (line.match(/^[-*] /)) {
const listItems = []
while (i < lines.length && lines[i].match(/^[-*] /)) {
listItems.push(
<li key={i} style={{ fontSize: 13, lineHeight: 1.65, color: 'var(--text2)', marginBottom: 4 }}>
{parseLine(lines[i].slice(2))}
</li>
)
i++
}
elements.push(
<ul key={`ul-${i}`} style={{ paddingLeft: 20, margin: '6px 0' }}>
{listItems}
</ul>
)
continue
}
// Numbered list item
if (line.match(/^\d+\. /)) {
const listItems = []
while (i < lines.length && lines[i].match(/^\d+\. /)) {
listItems.push(
<li key={i} style={{ fontSize: 13, lineHeight: 1.65, color: 'var(--text2)', marginBottom: 4 }}>
{parseLine(lines[i].replace(/^\d+\. /, ''))}
</li>
)
i++
}
elements.push(
<ol key={`ol-${i}`} style={{ paddingLeft: 20, margin: '6px 0' }}>
{listItems}
</ol>
)
continue
}
// Horizontal rule
if (line.match(/^---+$/)) {
elements.push(<hr key={i} style={{ border: 'none', borderTop: '1px solid var(--border)', margin: '12px 0' }} />)
i++; continue
}
// Normal paragraph
elements.push(
<p key={i} style={{ fontSize: 13, lineHeight: 1.7, color: 'var(--text2)', margin: '4px 0' }}>
{parseLine(line)}
</p>
)
i++
}
return <div>{elements}</div>
}