Markdown-Renderer: Bullet-Listen, ---Trenner und ##-Überschriften

- Devlogs im strukturierten Stil (Sektionen, Listen, Icons) rendern jetzt sauber
  auf der Webseite: Listen mit Neon-Markern, Trenner als Gradient-Linie,
  Überschriften im Mono-Tag-Stil
- Weiterhin ohne Library, Input wird nie als HTML interpretiert

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 10:49:42 +02:00
co-authored by Claude Opus 4.8
parent 3c8f442d6c
commit 4fb3a6d02c
2 changed files with 108 additions and 12 deletions
+75 -12
View File
@@ -1,5 +1,6 @@
// Mini-Markdown-Renderer für Devlog-Prosa (Discord-Stil): **fett**, *kursiv*,
// `code`, [Links](url). Bewusst ohne Library — Input wird escaped, kein HTML-Injection.
// `code`, [Links](url), * Bullet-Listen, --- Trenner, ## Überschriften.
// Bewusst ohne Library — Input wird nie als HTML interpretiert, kein Injection.
const TOKEN = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^\s)]+\))/g;
function renderInline(text) {
@@ -26,16 +27,78 @@ function renderInline(text) {
});
}
/** Devlog-Text als React-Elemente (Absätze + Inline-Formatierung) */
/** Devlog-Text als React-Elemente: Absätze, Listen, Trenner, Überschriften */
export function Markdown({ text }) {
return text.split(/\n{2,}/).map((block, i) => (
<p key={i}>
{block.split('\n').map((line, j, arr) => (
<span key={j}>
{renderInline(line)}
{j < arr.length - 1 && <br />}
</span>
))}
</p>
));
const out = [];
let para = [];
let list = [];
let key = 0;
const flushPara = () => {
if (para.length === 0) return;
out.push(
<p key={key++}>
{para.map((line, j) => (
<span key={j}>
{renderInline(line)}
{j < para.length - 1 && <br />}
</span>
))}
</p>
);
para = [];
};
const flushList = () => {
if (list.length === 0) return;
out.push(
<ul key={key++} className="md-list">
{list.map((item, j) => (
<li key={j}>{renderInline(item)}</li>
))}
</ul>
);
list = [];
};
for (const raw of text.split('\n')) {
const line = raw.trim();
if (!line) {
flushPara();
flushList();
continue;
}
// --- → Trenner
if (/^-{3,}$/.test(line)) {
flushPara();
flushList();
out.push(<hr key={key++} className="md-hr" />);
continue;
}
// * Punkt / - Punkt → Liste (aber nicht *kursiv am Zeilenanfang*)
const li = line.match(/^[*-]\s+(.*)$/);
if (li) {
flushPara();
list.push(li[1]);
continue;
}
// ## Überschrift (1-3 Raute-Ebenen)
const heading = line.match(/^(#{1,3})\s+(.*)$/);
if (heading) {
flushPara();
flushList();
out.push(
<h3 key={key++} className={`md-h md-h${heading[1].length}`}>
{renderInline(heading[2])}
</h3>
);
continue;
}
flushList();
para.push(raw);
}
flushPara();
flushList();
return out;
}