From 9b27907b0439f98b197d99b7180b17c063561792 Mon Sep 17 00:00:00 2001 From: simonpipe Date: Tue, 7 Jul 2026 21:16:38 +0200 Subject: [PATCH] Update Core.js --- Core.js | 150 +++++++++++++++++++++----------------------------------- 1 file changed, 56 insertions(+), 94 deletions(-) diff --git a/Core.js b/Core.js index 9017b11..700c49e 100644 --- a/Core.js +++ b/Core.js @@ -11,37 +11,28 @@ class SimonUp { /** * Registriert eine Erweiterung (Plugin) - * @param {Object} plugin - Ein Objekt mit optionalen 'before'- und 'after'-Hooks */ use(plugin) { this.plugins.push(plugin); - return this; // Ermöglicht Method Chaining + return this; } /** * Hauptfunktion: Wandelt SimonUp-Text (.up) in valides HTML um - * @param {string} text - Der rohe SimonUp-Text - * @returns {string} Das gerenderte HTML */ parse(text) { - // Zustand bei jedem Parse-Vorgang zurücksetzen this.headings = []; this.counters = [0, 0, 0, 0, 0, 0]; let html = text; - // 1. Vorverarbeitung durch Plugins for (const plugin of this.plugins) { if (plugin.before) html = plugin.before(html); } - // 2. Block-Parsing (Strukturelle Blöcke wie &t, &toc, &-, etc.) html = this._parseBlocks(html); - - // 3. Nachverarbeitung des Gesamtergebnisses & TOC-Injektion html = this._injectTOC(html); - // 4. Nachverarbeitung durch Plugins for (const plugin of this.plugins) { if (plugin.after) html = plugin.after(html); } @@ -53,18 +44,16 @@ class SimonUp { * INTERN: Verarbeitet strukturelle Blöcke zeilenweise */ _parseBlocks(text) { - // Normalisiere Zeilenumbrüche und teile den Text auf const lines = text.replace(/\r\n/g, '\n').split('\n'); let result = []; let inBlock = false; let blockType = null; - let blockMeta = ""; // Für zusätzliche Infos in der Startzeile (z.B. "3x" oder "Titel") + let blockMeta = ""; let blockBuffer = []; for (let i = 0; i < lines.length; i++) { let line = lines[i]; - // Block-Start erkennen (Zeile beginnt mit '&' und hat danach Inhalt) if (!inBlock && line.startsWith('&') && line.trim().length > 1) { inBlock = true; const fullHeader = line.substring(1).trim(); @@ -81,7 +70,6 @@ class SimonUp { continue; } - // Block-Ende erkennen (Ein einzelnes '&' auf einer eigenen Zeile) if (inBlock && line.trim() === '&') { result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); inBlock = false; @@ -90,22 +78,17 @@ class SimonUp { continue; } - // Inhalt sammeln oder normalen Fließtext verarbeiten if (inBlock) { blockBuffer.push(line); } else { - // Horizontale Linie als blockfreie Ausnahme abfangen if (line.trim() === '___') { result.push('
'); } else { - // Normaler Fließtext außerhalb von Blöcken wandert in den Inline-Parser result.push(this._parseInline(line)); } } } - // Am Ende leere Zeilen (Soft Breaks vs. Hard Breaks) korrekt verknüpfen - // Zwei aufeinanderfolgende leere Absätze erzeugen

, einfache Zeilenumbrüche
return this._processParagraphs(result); } @@ -117,46 +100,42 @@ class SimonUp { switch (type) { case 'toc': - // Unser neuer, intelligenter TOC-Block let tocHtml = `

\n`; if (meta) tocHtml += `

${meta}

\n`; if (content.trim()) { const intro = lines.map(l => this._parseInline(l)).join('
'); tocHtml += `

${intro}

\n`; } - // Ein eindeutiger HTML-Kommentar als Platzhalter für die spätere Injektion tocHtml += `\n
`; return tocHtml; - case '-': // Unsortierte Liste + case '-': return ''; - case '+': // Nummerierte Liste + case '+': return '
    \n' + lines.filter(l => l.trim()).map(l => `
  1. ${this._parseInline(l)}
  2. `).join('\n') + '\n
'; - case 'c': // Code-Block (Ignoriert Fluss-Formatierungen) + case 'c': const langClass = meta ? ` class="language-${meta}"` : ''; - // Escaping für HTML-Sonderzeichen im Code-Block - const escapedCode = content.replace(/&/g, '&').replace(//g, '>'); - return `
${escapedCode}
`; + const poolCode = content.replace(/&/g, '&').replace(//g, '>'); + return `
${poolCode}
`; - case 'q': // Zitatblock + case 'q': return `
${lines.map(l => this._parseInline(l)).join('
')}
`; - case 'b': // Bibeltext + case 'b': return `
${lines.map(l => this._parseInline(l)).join('
')}
`; - case 'f': // Spoiler / Fold + case 'f': return `
\n${meta || 'Details'}\n

${lines.map(l => this._parseInline(l)).join('
')}

\n
`; - case 'i': // Infobox + case 'i': return `
\n${lines.map(l => this._parseInline(l)).join('
')}\n
`; - case 't': // Komplexe Multi-line-Tabellen-Logik & Galerien + case 't': return this._renderTable(meta, lines); default: - // Fallback für unbekannte Blocktypen return `
${lines.map(l => this._parseInline(l)).join('
')}
`; } } @@ -166,23 +145,16 @@ class SimonUp { */ _renderTable(meta, lines) { let tableHtml = '\n'; - // Einfache Breiten-Zuweisung über CSS (z.B. "3x" oder Prozentangaben) im Stylesheet spiegeln - // Der Kürze halber generieren wir hier das Grundgerüst: - let inRow = false; let currentCells = []; let cellBuffer = []; for (let line of lines) { if (line.trim() === '') continue; - // Zerlege die Zeile anhand des Spaltenrenners '|' - // Achtung: Ein escaped '|' (falls vorhanden) müsste beachtet werden let parts = line.split('|'); - for (let i = 0; i < parts.length; i++) { let part = parts[i]; if (i === parts.length - 1 && part.trim() === '') { - // Zeile wurde mit einem | sauber beendet if (cellBuffer.length > 0 || part !== '') { cellBuffer.push(part); } @@ -190,7 +162,6 @@ class SimonUp { currentCells.push(cellBuffer.join('\n')); cellBuffer = []; } - // Zeile ist komplett! tableHtml += '\n' + currentCells.map(c => ``).join('\n') + '\n\n'; currentCells = []; } else { @@ -213,112 +184,105 @@ class SimonUp { if (!text) return ''; let html = text; - // 1. ESCAPE: Temporär schützen, damit Formatierungen sie nicht überschreiben + // 1. ESCAPE: Temporär schützen html = html.replace(/\\(.)/g, (match, char) => ``); - // 2. ÜBERSCHRIFTEN (Exakt nach unseren neuen Regeln verarbeitet) + // 2. ÜBERSCHRIFTEN (Flusssicher angepasst) - // Regel A: Automatische Nummerierung (3#x Titel 3#x) - html = html.replace(/([1-6])#x\s*(.*?)\s*\1#x/g, (match, level, title) => { + // Regel A: Automatische Nummerierung (3#x Titel 3#x Restlicher Text) + html = html.replace(/([1-6])#x\s*([^#]+?)\s*\1#x(.*)/g, (match, level, title, rest) => { const lvl = parseInt(level); const numStr = this._getAutoNumber(lvl); - const fullTitle = `${numStr} ${title}`; + const fullTitle = `${numStr} ${title.trim()}`; const id = this._slugify(fullTitle); - // Für das TOC erfassen this.headings.push({ level: lvl, text: fullTitle, id: id }); - return `${fullTitle}`; + return `${fullTitle}${rest}`; }); - // Regel B: Vom TOC ausgeschlossene Titel (3#- Titel 3#-) - html = html.replace(/([1-6])#-\s*(.*?)\s*\1#-/g, (match, level, title) => { + // Regel B: Vom TOC ausgeschlossene Titel (3#- Titel 3#- Restlicher Text) + html = html.replace(/([1-6])#-\s*([^#]+?)\s*\1#-(.*)/g, (match, level, title, rest) => { const lvl = parseInt(level); - return `${title}`; // Wird NICHT in this.headings gespeichert + return `${title.trim()}${rest}`; }); - // Regel C: Manuelle Nummerierung (3# Titel 3#) - html = html.replace(/([1-6])#\s*(.*?)\s*\1#/g, (match, level, title) => { + // Regel C: Manuelle Nummerierung (3# Titel 3# Restlicher Text) + html = html.replace(/([1-6])#\s*([^#]+?)\s*\1#(.*)/g, (match, level, title, rest) => { const lvl = parseInt(level); - const id = this._slugify(title); + const id = this._slugify(title.trim()); - this.headings.push({ level: lvl, text: title, id: id }); - return `${title}`; + this.headings.push({ level: lvl, text: title.trim(), id: id }); + return `${title.trim()}${rest}`; }); - // Regel D: Das schnelle, solitäre # für H1 (# Titel #) - html = html.replace(/#([^#]+)#/g, (match, title) => { - const id = this._slugify(title); - this.headings.push({ level: 1, text: title, id: id }); - return `

${title}

`; + // Regel D: Das schnelle, solitäre # für H1 (# Titel # Restlicher Text) + html = html.replace(/#([^#]+)#(.*)/g, (match, title, rest) => { + const id = this._slugify(title.trim()); + this.headings.push({ level: 1, text: title.trim(), id: id }); + return `

${title.trim()}

${rest}`; }); - // 3. STANDARD FLUSS-FORMATIERUNGEN + // 3. STANDARD FLUSS-FORMATIERUNGEN (In priorisierter Reihenfolge) html = html.replace(/\*\*([^*]+)\*\*/g, '$1'); // Fett - html = html.replace(/\*([^*]+)\*/g, '$1'); // Medium - html = html.replace(/\/([^/]+)\//g, '$1'); // Kursiv - html = html.replace(/_([^_]+)_/g, '$1'); // Unterstrichen - html = html.replace(/~([^~]+)~/g, '$1'); // Durchgestrichen - html = html.replace(/=([^=]+)=/g, '$1'); // Highlight - html = html.replace(/\^([^^]+)\^/g, '$1'); // Hochgestellt - html = html.replace(/°([^°]+)°/g, '$1'); // Tiefgestellt - html = html.replace(/`([^`]+)`/g, '$1'); // Inline-Code + html = html.replace(/\/([^/]+)\//g, '$1'); // Kursiv + html = html.replace(/_([^_]+)_/g, '$1'); // Unterstrichen + html = html.replace(/~([^~]+)~/g, '$1'); // Durchgestrichen + html = html.replace(/=([^=]+)=/g, '$1'); // Highlight + html = html.replace(/\^([^^]+)\^/g, '$1'); // Hochgestellt + html = html.replace(/°([^°]+)°/g, '$1'); // Tiefgestellt + html = html.replace(/`([^`]+)`/g, '$1'); // Inline-Code + + // 3.1 MEDIUM-FORMATIERUNG (Sucht nach *, ignoriert aber bereits generiertes HTML) + html = html.replace(/(?]*)\*([^*<\/>]+)\*(?![^<]*>)/g, '$1'); // 4. AUSRICHTUNG & EINRÜCKUNG (Basiszeichen '>') - html = html.replace(/>l ([^>]+)>/g, '
$1
'); - html = html.replace(/>r ([^>]+)>/g, '
$1
'); - html = html.replace(/>c ([^>]+)>/g, '
$1
'); - html = html.replace(/>j ([^>]+)>/g, '
$1
'); - html = html.replace(/>tt ([^>]+)>/g, '
$1
'); - html = html.replace(/>t ([^>]+)>/g, '
$1
'); + html = html.replace(/>l ([^>]+)>/g, '
$1
'); + html = html.replace(/>r ([^>]+)>/g, '
$1
'); + html = html.replace(/>c ([^>]+)>/g, '
$1
'); + html = html.replace(/>j ([^>]+)>/g, '
$1
'); + html = html.replace(/>tt ([^>]+)>/g, '
$1
'); + html = html.replace(/>t ([^>]+)>/g, '
$1
'); // 5. LINKS & MEDIEN - // Bilder mit definierter Breite: ![Alt w:300](URL) html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); - // Standard Bilder: ![Alt](URL) html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - // Komfort-Links (Neuer Tab): [Text]{URL} html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); - // Standard-Links: [Text](URL) html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); - // 6. FUSSNOTEN (Manuell ^1[...]^ und Automatisch ^x[...]^) - html = html.replace(/\^x\[([^\]]+)\]\^/g, '[*]'); // Logik für Fußnoten-Endleiste optional + // 6. FUSSNOTEN + html = html.replace(/\^x\[([^\]]+)\]\^/g, '[*]'); html = html.replace(/\^(\d+)\[([^\]]+)\]\^/g, '[$1]'); // 7. ESCAPE ZURÜCKWANDELN - html = html.replace(/``/g, (match, code) => String.fromCharCode(parseInt(code))); + html = html.replace(/<\!--SU_ESC_(\d+)-->/g, (match, code) => String.fromCharCode(parseInt(code))); return html; } /** - * INTERN: Errechnet die nächste fortlaufende Kapitelnummer (z.B. "1.2.1.") + * INTERN: Errechnet die nächste fortlaufende Kapitelnummer */ _getAutoNumber(level) { let idx = level - 1; this.counters[idx]++; - // Alle untergeordneten Ebenen radikal auf 0 zurücksetzen for (let i = idx + 1; i < 6; i++) this.counters[i] = 0; - - // Array filtern, um nur die bis hierhin aktiven Nummernzähler zu extrahieren let activeParts = this.counters.slice(0, level); return activeParts.join('.') + '.'; } /** - * INTERN: Macht aus Text eine URL-konforme ID (Anker für das TOC) + * INTERN: Macht aus Text eine URL-konforme ID */ _slugify(text) { return text.toLowerCase() - .replace(/[^a-z0-9\s-]/g, '') // Entferne Sonderzeichen - .replace(/\s+/g, '-'); // Ersetze Leerzeichen durch Bindestriche + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-'); } /** * INTERN: Erzeugt die fertige HTML-Liste des TOC und injiziert sie in den Platzhalter */ _injectTOC(html) { - // Falls kein TOC-Block deklariert wurde oder keine Überschriften da sind, Platzhalter entfernen if (this.headings.length === 0) { return html.replace('', ''); } @@ -353,15 +317,13 @@ class SimonUp { let paragraphBuffer = []; for (let element of elements) { - // Wenn es ein fertiger HTML-Block (z.B. Table, List, H1) ist, schließe offenen Absatz ab - if (element.startsWith('<') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith('') && !element.startsWith(' 0) { processed.push(`

${paragraphBuffer.join('
')}

`); paragraphBuffer = []; } processed.push(element); } else if (element.trim() === '') { - // Bei einer echten Leerzeile (2x Enter) wird der Absatz gebunden if (paragraphBuffer.length > 0) { processed.push(`

${paragraphBuffer.join('
')}

`); paragraphBuffer = [];
${this._parseInline(c.trim())}