From 898b70975cd42549197c8fd5cce8636a03313b32 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 28 Jul 2026 16:19:01 +0200 Subject: [PATCH] =?UTF-8?q?Playground=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Playground/Core.js | 444 ++++++++++++++++++++++++++++++++++++++++++ Playground/index.html | 215 ++++++++++++++++++++ 2 files changed, 659 insertions(+) create mode 100644 Playground/Core.js create mode 100644 Playground/index.html diff --git a/Playground/Core.js b/Playground/Core.js new file mode 100644 index 0000000..37c99e7 --- /dev/null +++ b/Playground/Core.js @@ -0,0 +1,444 @@ +/** + * SimonUp Core Compiler (Core.js) - Version 1.2b.005 + * Eine moderne, fluss- und blockorientierte Auszeichnungssprache. + */ +class SimonUp { + constructor() { + this.plugins = []; + this.headings = []; + this.counters = [0, 0, 0, 0, 0, 0]; + this.currentFootnotes = []; // Sammelt Fußnoten für den aktuellen Abschnitt + } + + use(plugin) { + this.plugins.push(plugin); + return this; + } + + parse(text) { + this.headings = []; + this.counters = [0, 0, 0, 0, 0, 0]; + this.currentFootnotes = []; + + let html = text; + + if (this.plugins && this.plugins.length > 0) { + for (let i = 0; i < this.plugins.length; i++) { + if (this.plugins[i].before) { + html = this.plugins[i].before(html); + } + } + } + + // Phase 1: Strukturelle Blöcke isolieren + html = this._parseBlocks(html); + + // Phase 2: Das Inhaltsverzeichnis nachträglich injizieren + html = this._injectTOC(html); + + if (this.plugins && this.plugins.length > 0) { + for (let i = 0; i < this.plugins.length; i++) { + if (this.plugins[i].after) { + html = this.plugins[i].after(html); + } + } + } + + return html; + } + + _parseBlocks(text) { + const lines = text.replace(/\r\n/g, '\n').split('\n'); + let result = []; + let inBlock = false; + let blockType = null; + let blockMeta = ""; + let blockBuffer = []; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + let trimmed = line.trim(); + + // Block-Ende erkennen mit /& + if (inBlock && trimmed.replace(/\u00A0/g, ' ') === '/&') { + result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); + inBlock = false; + blockType = null; + blockMeta = ""; + continue; + } + + if (inBlock) { + blockBuffer.push(line); + continue; + } + + // Block-Start erkennen + if (line.startsWith('&') && trimmed.length > 1) { + inBlock = true; + const fullHeader = line.substring(1).trim(); + const spaceIndex = fullHeader.indexOf(' '); + + if (spaceIndex !== -1) { + blockType = fullHeader.substring(0, spaceIndex).toLowerCase(); + blockMeta = fullHeader.substring(spaceIndex).trim(); + } else { + blockType = fullHeader.toLowerCase(); + blockMeta = ""; + } + blockBuffer = []; + continue; + } + + // Überschriften abfangen, um vorherige Fußnoten zu flushen + if (/^([1-6])(x|-)?#\s*/.test(trimmed)) { + let footnoteBlock = this._flushFootnotes(); + if (footnoteBlock) result.push(footnoteBlock); + } + + // Freie Linien und Listen-Strukturen + if (trimmed === '___') { + result.push('
'); + } else if (trimmed.startsWith('- ')) { + let insideUl = this._parseInline(line.replace(/^\s*-\s+/, '')); + result.push(`${insideUl}`); + } else if (/^\d+\.\s+/.test(trimmed)) { + let cleanLine = line.replace(/^\s*\d+\.\s+/, ''); + let insideOl = this._parseInline(cleanLine); + result.push(`${insideOl}`); + } else { + result.push(this._parseInline(line)); + } + } + + if (inBlock && blockBuffer.length > 0) { + result.push(this._renderBlock(blockType, blockMeta, blockBuffer)); + } + + // Letzte Fußnoten am Ende des Dokuments anfügen + let finalFootnotes = this._flushFootnotes(); + if (finalFootnotes) result.push(finalFootnotes); + + return this._processParagraphsAndLists(result); + } + + _renderBlock(type, meta, lines) { + const content = lines.join('\n'); + let self = this; + + switch (type) { + case 'toc': + let tocWrapper = '
\n'; + if (meta) { + tocWrapper += `

${meta}

\n`; + } + if (content.trim()) { + const intro = lines.map(function(l) { return self._parseInline(l); }).join('
'); + tocWrapper += `

${intro}

\n`; + } + tocWrapper += 'STRENG_GEHEIMER_TOC_PLATZHALTER\n'; + tocWrapper += '
'; + return tocWrapper; + + case 'c': + const langClass = meta ? ` class="language-${meta}"` : ''; + const escapedCode = content.replace(/&/g, '&').replace(//g, '>'); + return `
${escapedCode}
`; + + case 'q': + const qContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); + return `
${qContent}
`; + + case 'b': + const bContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); + return `
${bContent}
`; + + case 'f': + const fSummary = meta || 'Details'; + const fContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); + return `
\n${fSummary}\n

${fContent}

\n
`; + + case 'i': + const iColor = meta || '#ff6347'; + const iContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); + return `
\n${iContent}\n
`; + + case 't': + return this._renderTable(meta, lines); + + default: + const defContent = lines.map(function(l) { return self._parseInline(l); }).join('
'); + return `
${defContent}
`; + } + } + + _renderTable(meta, lines) { + let tableHtml = '\n'; + let colCount = 0; + if (meta) { + let metaParts = meta.trim().split(/\s+/); + if (metaParts.length === 1 && metaParts[0].endsWith('x')) { + colCount = parseInt(metaParts[0]); + } else { + colCount = metaParts.length; + } + } + + let processedLines = lines + .map(l => l.trim()) + .filter(l => l.length > 0) + .map(l => { + if (l.startsWith('|')) l = l.substring(1); + if (l.endsWith('|')) l = l.slice(0, -1); + return l; + }); + + let fullContent = processedLines.join('|'); + let rawCells = fullContent.split('|'); + let currentConfiguredCols = colCount || 1; + let cellIndex = 0; + + tableHtml += '\n'; + for (let i = 0; i < rawCells.length; i++) { + let cellText = rawCells[i].trim(); + let parsedCell = this._parseInline(cellText); + tableHtml += ` \n`; + cellIndex++; + if (cellIndex === currentConfiguredCols && i < rawCells.length - 1) { + tableHtml += '\n\n'; + cellIndex = 0; + } + } + tableHtml += '\n
${parsedCell}
'; + return tableHtml; + } + + _parseInline(text) { + if (!text) return ''; + let html = text; + + const escapes = []; + html = html.replace(/\\(.)/g, function(match, char) { + escapes.push(char); + return `__ESCAPED_CHAR_${escapes.length - 1}__`; + }); + + // Überschriften parsen + html = html.replace(/^([1-6])x#\s*(.*)/gm, (match, level, titleAndRest) => { + let { title, rest } = this._extractHeadingRest(titleAndRest); + const numStr = this._getAutoNumber(parseInt(level)); + const fullTitle = `${numStr} ${title}`; + const id = this._slugify(fullTitle); + this.headings.push({ level: parseInt(level), text: title, id: id }); + return `${fullTitle}${rest}`; + }); + + html = html.replace(/^([1-6])-#\s*(.*)/gm, (match, level, titleAndRest) => { + let { title, rest } = this._extractHeadingRest(titleAndRest); + return `${title}${rest}`; + }); + + html = html.replace(/^([1-6])#\s*(.*)/gm, (match, level, titleAndRest) => { + let { title, rest } = this._extractHeadingRest(titleAndRest); + const id = this._slugify(title); + this.headings.push({ level: parseInt(level), text: title, id: id }); + return `${title}${rest}`; + }); + + // Medien & Links + html = html.replace(/!\[([^\]]+)\s+w:(\d+)\]\(([^)]+)\)/g, '$1'); + html = html.replace(/!\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + html = html.replace(/\[([^\]]+)\]\{([^}]+)\}/g, '$1'); + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + const replaceOutsideTags = (regex, replacement) => { + const masterRegex = new RegExp(/(<[^>]+>)/g.source + '|' + regex.source, 'g'); + html = html.replace(masterRegex, (match, p1) => { + if (p1) return p1; + const innerMatch = regex.exec(match); + if (innerMatch) { + if (typeof replacement === 'function') return replacement(...innerMatch); + let res = replacement; + for (let idx = 1; idx < innerMatch.length; idx++) { + res = res.replace(new RegExp('\\$' + idx, 'g'), innerMatch[idx]); + } + return res; + } + return match; + }); + }; + + // Exakte Trennung für Fett und Medium (Verhindert das Überschneiden) + replaceOutsideTags(/\*\*([^*]+?)(?:\/\*\*|\/|\*\*|$)/, '$1'); + replaceOutsideTags(/\*([^*]+?)(?:\/\*|\/|\*|$)/, '$1'); + + const inlineRules = [ + { tag: 'em', symbol: '%' }, + { tag: 'u', symbol: '_' }, + { tag: 'del', symbol: '~' }, + { tag: 'mark', symbol: '=' }, + { tag: 'sup', symbol: '\\^' }, + { tag: 'sub', symbol: '°' }, + { tag: 'code', symbol: '`' } + ]; + + for (let k = 0; k < inlineRules.length; k++) { + let rule = inlineRules[k]; + let tagSimple = rule.tag.split(' ')[0]; + replaceOutsideTags(new RegExp(rule.symbol + '(.+?)(?:\\/' + rule.symbol + '|\\/|' + rule.symbol + '|$)'), `<${rule.tag}>$1`); + } + + // Fußnoten abfangen und registrieren + let self = this; + html = html.replace(/\^x\[([^\]]+)\]\^x/g, function(m, content) { + self.currentFootnotes.push(content); + const num = self.currentFootnotes.length; + return `[${num}]`; + }); + + html = html.replace(/\^(\d+)\[([^\]]+)\]\^x/g, function(m, num, content) { + self.currentFootnotes.push(`${num}: ${content}`); + return `[${num}]`; + }); + + const alignMap = { 'l': 'left', 'r': 'right', 'c': 'center', 'j': 'justify' }; + html = html.replace(/>([lrcj])\s+(.+?)\s*\/>/g, function(m, dir, content) { + return `
${content}
`; + }); + + html = html.replace(/__ESCAPED_CHAR_(\d+)__/g, function(match, idx) { + return escapes[idx]; + }); + + return html; + } + + // Hilfsfunktion: Gibt gesammelte Fußnoten als HTML-Block aus und leert den Speicher + _flushFootnotes() { + if (this.currentFootnotes.length === 0) return null; + let html = '
\n'; + for (let i = 0; i < this.currentFootnotes.length; i++) { + html += `
${this.currentFootnotes[i]}
\n`; + } + html += '
'; + this.currentFootnotes = []; // Zurücksetzen fürs nächste Kapitel + return html; + } + + _extractHeadingRest(text) { + let title = text; + let rest = ""; + const closeIdx = text.indexOf('/#'); + if (closeIdx !== -1) { + title = text.substring(0, closeIdx); + rest = text.substring(closeIdx + 2); + } + return { title: title.trim(), rest: rest }; + } + + _getAutoNumber(level) { + let idx = level - 1; + this.counters[idx]++; + for (let i = idx + 1; i < 6; i++) { + this.counters[i] = 0; + } + let activeParts = this.counters.slice(0, level); + return activeParts.join('.') + '.'; + } + + _slugify(text) { + return text.toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-'); + } + + _injectTOC(html) { + const placeholder = 'STRENG_GEHEIMER_TOC_PLATZHALTER'; + if (!html.includes(placeholder)) return html; + if (this.headings.length === 0) return html.replace(placeholder, ''); + + let tocListHtml = '
\n\n'.repeat(diffFinal); + } + tocListHtml += '\n
'; + + return html.replace(placeholder, tocListHtml); + } + + _processParagraphsAndLists(elements) { + let processed = []; + let paragraphBuffer = []; + let listBuffer = []; + let currentListType = null; + + function flushList() { + if (listBuffer.length > 0) { + let items = listBuffer.map(function(item) { return "
  • " + item + "
  • "; }).join("\n"); + processed.push("<" + currentListType + " style='margin-bottom: 12px; margin-left: 20px;'>\n" + items + "\n"); + listBuffer = []; + currentListType = null; + } + } + + function flushParagraph() { + if (paragraphBuffer.length > 0) { + let pContent = paragraphBuffer.join("
    "); + processed.push("

    " + pContent + "

    "); + paragraphBuffer = []; + } + } + + for (let i = 0; i < elements.length; i++) { + let element = elements[i]; + if (!element) continue; + + let isUlItem = element.startsWith(""); + let isOlItem = element.startsWith(""); + + if (isUlItem || isOlItem) { + flushParagraph(); + let type = isUlItem ? "ul" : "ol"; + let content = isUlItem ? element.replace(/<\/?ul-item>/g, "") : element.replace(/<\/?ol-item>/g, ""); + + if (currentListType && currentListType !== type) { + flushList(); + } + currentListType = type; + listBuffer.push(content); + } else if (element.startsWith("<") && !/^(|||||||||||| + + + + + SimonUp – Live Playground + + + + +
    +
    +

    SimonUp Playground

    +

    Die moderne Fluss- & Block-Auszeichnungssprache

    +
    +
    + +
    +
    +
    SimonUp Input (.up)
    + +
    + +
    +
    HTML Vorschau (Live)
    +
    +
    +
    + + + + + \ No newline at end of file