feat: content import system - Markdown parser, importer, 7 decks + 3 tutorials + 5 cheatsheets

Dieser Commit ist enthalten in:
hafroese 2026-04-02 23:09:27 +02:00
Ursprung 1c59b667f2
Commit 6a504254b0
17 geänderte Dateien mit 2658 neuen und 0 gelöschten Zeilen

56
edu/js/markdown.js Normale Datei
Datei anzeigen

@ -0,0 +1,56 @@
'use strict';
var Markdown = {
render: function(md) {
if (!md) return '';
var html = md;
// Code blocks
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, function(_, lang, code) {
return '<pre><code class="lang-' + lang + '">' + Markdown.esc(code.trim()) + '</code></pre>';
});
// Inline code
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
// Bold
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
// Italic
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
// Headings
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>');
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>');
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>');
// HR
html = html.replace(/^---$/gm, '<hr>');
// Tables
html = html.replace(/^\|(.+)\|\s*\n\|[-| :]+\|\s*\n((?:\|.+\|\s*\n?)*)/gm, function(_, header, rows) {
var ths = header.split('|').map(function(h) { return '<th>' + h.trim() + '</th>'; }).join('');
var trs = rows.trim().split('\n').map(function(row) {
var tds = row.replace(/^\||\|$/g, '').split('|').map(function(c) { return '<td>' + c.trim() + '</td>'; }).join('');
return '<tr>' + tds + '</tr>';
}).join('');
return '<table><thead><tr>' + ths + '</tr></thead><tbody>' + trs + '</tbody></table>';
});
// Lists
html = html.replace(/^- (.+)$/gm, '<li>$1</li>');
html = html.replace(/(<li>[\s\S]*?<\/li>)/g, function(match) {
if (match.indexOf('<ul>') === -1) return '<ul>' + match + '</ul>';
return match;
});
// Paragraphs
html = html.replace(/^(?!<[huptlo]|<\/|<hr|<pre|<li|<ul)(.+)$/gm, '<p>$1</p>');
return html;
},
esc: function(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
};