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 '\n' + lines.filter(l => l.trim()).map(l => `- ${this._parseInline(l)}
`).join('\n') + '\n
';
- case '+': // Nummerierte Liste
+ case '+':
return '\n' + lines.filter(l => l.trim()).map(l => `- ${this._parseInline(l)}
`).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 => `| ${this._parseInline(c.trim())} | `).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: 
html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '
');
- // Standard Bilder: 
html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '
');
- // 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, '');
// 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 = [];