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:
+75
-12
@@ -1,5 +1,6 @@
|
|||||||
// Mini-Markdown-Renderer für Devlog-Prosa (Discord-Stil): **fett**, *kursiv*,
|
// 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;
|
const TOKEN = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`|\[[^\]]+\]\(https?:\/\/[^\s)]+\))/g;
|
||||||
|
|
||||||
function renderInline(text) {
|
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 }) {
|
export function Markdown({ text }) {
|
||||||
return text.split(/\n{2,}/).map((block, i) => (
|
const out = [];
|
||||||
<p key={i}>
|
let para = [];
|
||||||
{block.split('\n').map((line, j, arr) => (
|
let list = [];
|
||||||
<span key={j}>
|
let key = 0;
|
||||||
{renderInline(line)}
|
|
||||||
{j < arr.length - 1 && <br />}
|
const flushPara = () => {
|
||||||
</span>
|
if (para.length === 0) return;
|
||||||
))}
|
out.push(
|
||||||
</p>
|
<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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,6 +300,39 @@ body::after {
|
|||||||
padding: .06rem .4rem;
|
padding: .06rem .4rem;
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
}
|
}
|
||||||
|
.card-body .md-list {
|
||||||
|
margin: .55rem 0;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.card-body .md-list li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: .2rem;
|
||||||
|
margin: .25rem 0;
|
||||||
|
}
|
||||||
|
.card-body .md-list li::before {
|
||||||
|
content: '▪';
|
||||||
|
position: absolute;
|
||||||
|
left: -1.1rem;
|
||||||
|
color: var(--neon);
|
||||||
|
font-size: .8em;
|
||||||
|
top: .15em;
|
||||||
|
}
|
||||||
|
.card-body .md-hr {
|
||||||
|
border: none;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(to right, var(--border), transparent);
|
||||||
|
margin: 1.1rem 0;
|
||||||
|
}
|
||||||
|
.card-body .md-h {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: .78rem;
|
||||||
|
letter-spacing: .2em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--neon);
|
||||||
|
margin: 1rem 0 .4rem;
|
||||||
|
}
|
||||||
|
.card-body .md-h2, .card-body .md-h3 { color: var(--muted); }
|
||||||
|
|
||||||
/* Bildergalerie (heruntergeladene Devlog-Screenshots) */
|
/* Bildergalerie (heruntergeladene Devlog-Screenshots) */
|
||||||
.card-images {
|
.card-images {
|
||||||
|
|||||||
Reference in New Issue
Block a user