// Wandelt Markdown in Pango-Markup für St.Label um. // Unterstützt: Überschriften, Fett, Kursiv, Durchgestrichen, Code (inline und Block), // Listen, Checkboxen, Zitate, Links, Trennlinien. const CODE_BG = '#2b2b2b'; const LINK_COLOR = '#8ab4f8'; const QUOTE_COLOR = '#b0b0b0'; // Platzhalter für herausgenommene Code-Spans (Private-Use-Zeichen, kommt in Text nicht vor). const HOLE = ''; export function escapeMarkup(text) { return text.replace(/&/g, '&').replace(//g, '>'); } function inline(text) { // Inline-Code zuerst herausnehmen, damit Sternchen darin unberührt bleiben. const codes = []; let out = escapeMarkup(text).replace(/`([^`]+)`/g, (_, code) => { codes.push(`${code}`); return `${HOLE}${codes.length - 1}${HOLE}`; }); out = out .replace(/!\[([^\]]*)\]\([^)]*\)/g, '[Bild: $1]') .replace(/\[([^\]]+)\]\([^)]*\)/g, `$1`) .replace(/\[\[([^\]]+)\]\]/g, `$1`) .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/__(.+?)__/g, '$1') .replace(/~~(.+?)~~/g, '$1') .replace(/(^|[^*\w])\*([^*\s](?:[^*]*[^*\s])?)\*(?!\w)/g, '$1$2') .replace(/(^|[^_\w])_([^_\s](?:[^_]*[^_\s])?)_(?!\w)/g, '$1$2'); return out.replace(new RegExp(`${HOLE}(\\d+)${HOLE}`, 'g'), (_, i) => codes[Number(i)]); } // Absolute Punktgrößen: Schlüsselwörter wie xx-large skalieren im ClutterText den ganzen Text. const HEADING_SIZE = ['18pt', '15pt', '12.5pt', '11pt', '11pt', '11pt']; export function renderMarkdown(text) { const lines = text.replace(/\r\n/g, '\n').split('\n'); const out = []; let inCode = false; for (const line of lines) { if (/^\s*```/.test(line)) { inCode = !inCode; continue; } if (inCode) { out.push(`${escapeMarkup(line) || ' '}`); continue; } let match; if ((match = line.match(/^(#{1,6})\s+(.*)$/))) { const level = match[1].length; out.push(`${inline(match[2])}`); } else if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) { out.push('────────────────────────────'); } else if ((match = line.match(/^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/))) { const box = match[2] === ' ' ? '☐' : '☑'; const body = match[2] === ' ' ? inline(match[3]) : `${inline(match[3])}`; out.push(`${indent(match[1])}${box} ${body}`); } else if ((match = line.match(/^(\s*)[-*+]\s+(.*)$/))) { out.push(`${indent(match[1])}• ${inline(match[2])}`); } else if ((match = line.match(/^(\s*)(\d+)[.)]\s+(.*)$/))) { out.push(`${indent(match[1])}${match[2]}. ${inline(match[3])}`); } else if ((match = line.match(/^>\s?(.*)$/))) { out.push(`▎ ${inline(match[1])}`); } else { out.push(inline(line)); } } return out.join('\n'); } // Verschachtelte Listen: je 2 Leerzeichen oder 1 Tab eine Ebene tiefer. function indent(spaces) { const level = spaces.replace(/\t/g, ' ').length / 2; return ' '.repeat(Math.floor(level) + 1); }