init
This commit is contained in:
79
desktop@marha.de/markdown.js
Normal file
79
desktop@marha.de/markdown.js
Normal file
@@ -0,0 +1,79 @@
|
||||
// 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);
|
||||
}
|
||||
Reference in New Issue
Block a user