93 lines
4.1 KiB
JavaScript
93 lines
4.1 KiB
JavaScript
// 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, '<').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(`<span font_family="monospace" background="${CODE_BG}">${code}</span>`);
|
|
return `${HOLE}${codes.length - 1}${HOLE}`;
|
|
});
|
|
out = out
|
|
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '[Bild: $1]')
|
|
.replace(/\[([^\]]+)\]\([^)]*\)/g, `<u><span foreground="${LINK_COLOR}">$1</span></u>`)
|
|
.replace(/\[\[([^\]]+)\]\]/g, `<u><span foreground="${LINK_COLOR}">$1</span></u>`)
|
|
.replace(/\*\*(.+?)\*\*/g, '<b>$1</b>')
|
|
.replace(/__(.+?)__/g, '<b>$1</b>')
|
|
.replace(/~~(.+?)~~/g, '<s>$1</s>')
|
|
.replace(/(^|[^*\w])\*([^*\s](?:[^*]*[^*\s])?)\*(?!\w)/g, '$1<i>$2</i>')
|
|
.replace(/(^|[^_\w])_([^_\s](?:[^_]*[^_\s])?)_(?!\w)/g, '$1<i>$2</i>');
|
|
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(`<span font_family="monospace" background="${CODE_BG}">${escapeMarkup(line) || ' '}</span>`);
|
|
continue;
|
|
}
|
|
|
|
let match;
|
|
if ((match = line.match(/^(#{1,6})\s+(.*)$/))) {
|
|
const level = match[1].length;
|
|
out.push(`<span size="${HEADING_SIZE[level - 1]}" weight="bold">${inline(match[2])}</span>`);
|
|
} else if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
|
|
out.push('<span foreground="#666666">────────────────────────────</span>');
|
|
} else if ((match = line.match(/^(\s*)[-*+]\s+\[([ xX])\]\s+(.*)$/))) {
|
|
const box = match[2] === ' ' ? '☐' : '☑';
|
|
const body = match[2] === ' ' ? inline(match[3]) : `<span foreground="#888888"><s>${inline(match[3])}</s></span>`;
|
|
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(`<span foreground="${QUOTE_COLOR}">▎ <i>${inline(match[1])}</i></span>`);
|
|
} 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);
|
|
}
|
|
|
|
// Was nach Enter am Ende dieser Zeile in der nächsten stehen soll.
|
|
// null: keine Liste. '': leerer Listenpunkt, der Marker soll weg. Sonst der Marker für den nächsten Punkt.
|
|
export function continueList(line) {
|
|
let m;
|
|
if ((m = line.match(/^(\s*)([-*+])\s+\[[ xX]\]\s+(.*)$/)))
|
|
return m[3].trim() ? `${m[1]}${m[2]} [ ] ` : '';
|
|
if ((m = line.match(/^(\s*)([-*+])\s+(.*)$/)))
|
|
return m[3].trim() ? `${m[1]}${m[2]} ` : '';
|
|
if ((m = line.match(/^(\s*)(\d+)([.)])\s+(.*)$/)))
|
|
return m[4].trim() ? `${m[1]}${Number(m[2]) + 1}${m[3]} ` : '';
|
|
return null;
|
|
}
|