From 9fe34cc743a4c086a0aee597000fd2e9a32f19b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lohrer?= Date: Wed, 1 Oct 2025 15:53:36 +0200 Subject: [PATCH] docs: add interactive playground and quick start guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - playground.js: Interaktives Script zum Ausprobieren aller Features - Forgejo API Client Demo - Post-Listing (53 Posts) - Vollständiges Parsing-Beispiel - AMB-Metadaten-Analyse mit Validierung - Content-Statistiken (Überschriften, Links, Bilder) - AST-Struktur-Visualisierung - JSON-Export-Vorschau - Farbige Console-Ausgabe - QUICKSTART.md: Schnelleinstieg für Entwickler - Playground-Anleitung - Code-Beispiele für alle Use Cases - Alle verfügbaren Posts aufgelistet - Erweiterte Optionen - Links zur weiteren Dokumentation Ready to use! 🚀 --- QUICKSTART.md | 280 ++++++++++++++++++++++++++++++++++++++++++++++++++ playground.js | 246 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 526 insertions(+) create mode 100644 QUICKSTART.md create mode 100644 playground.js diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..6b776f7 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,280 @@ +# 🚀 Quick Start Guide + +## Installation & Setup + +```bash +# 1. Dependencies installieren (schon erledigt ✅) +npm install + +# 2. .env ist schon konfiguriert mit deinem Token ✅ +``` + +## 🎮 Interaktives Ausprobieren + +### Playground starten +```bash +node playground.js +``` + +Das Playground-Script zeigt dir: +- ✅ Verbindung zur Forgejo API +- ✅ Alle 53 Posts im Repository +- ✅ Vollständiges Parsing eines Posts +- ✅ AMB-Metadaten-Analyse +- ✅ Content-Statistiken (Überschriften, Links, Bilder) +- ✅ AST-Struktur +- ✅ JSON-Export + +### Anderen Post wählen + +Öffne `playground.js` und ändere Zeile 64: + +```javascript +// Standard: +const selectedPost = '2025-04-20-OER-und-Symbole' + +// Andere Posts (siehe Liste im Output): +const selectedPost = '2024-08-05-hello-world' +const selectedPost = '2024-09-06-OERcamp-Hamburg' +const selectedPost = '2025-07-02-nostr-schrein' +``` + +## 📚 Weitere Beispiele + +### 1. Einzelnen Post von Forgejo parsen +```bash +node examples/parse-forgejo.js +``` + +### 2. Alle Posts auflisten +```bash +node examples/list-all-posts.js +``` + +### 3. Lokale Datei parsen +```bash +node examples/parse-local.js +``` + +## 💻 Im eigenen Code verwenden + +### Einfaches Beispiel + +```javascript +import { parseMarkdownFile } from './src/index.js' + +const result = await parseMarkdownFile('./test/fixtures/example.md') + +console.log(result.metadata.name) // Titel +console.log(result.metadata.creator) // Autoren +console.log(result.content.length) // Content-Länge +``` + +### Forgejo API nutzen + +```javascript +import { createForgejoClient, parseMarkdownString } from './src/index.js' + +// Client erstellen +const client = createForgejoClient() + +// Alle Posts auflisten +const posts = await client.listPosts() +console.log(`${posts.length} Posts gefunden`) + +// Einen Post abrufen und parsen +const markdown = await client.getPostContent('2024-08-05-hello-world') +const result = await parseMarkdownString(markdown) + +console.log(result.metadata) +``` + +### Metadaten extrahieren + +```javascript +import { extractAMBMetadata, validateAMBMetadata } from './src/index.js' + +// AMB-Metadaten aus YAML extrahieren +const metadata = extractAMBMetadata(result.yaml) + +// Validieren +const validation = validateAMBMetadata(metadata) +console.log(`Gültig: ${validation.valid}`) +console.log(`Fehler: ${validation.errors.length}`) +console.log(`Warnings: ${validation.warnings.length}`) +``` + +### Content analysieren + +```javascript +import { + extractHeadings, + extractLinks, + extractImages +} from './src/index.js' + +// Überschriften +const headings = extractHeadings(result.ast) +headings.forEach(h => { + console.log(`H${h.level}: ${h.text}`) +}) + +// Links +const links = extractLinks(result.ast) +links.forEach(link => { + console.log(`${link.text} → ${link.url}`) +}) + +// Bilder +const images = extractImages(result.ast) +images.forEach(img => { + console.log(`${img.alt} → ${img.url}`) +}) +``` + +## 🧪 Tests ausführen + +```bash +npm test +``` + +Output: +``` +✅ Alle Tests erfolgreich! +✔ 11 Tests passing +``` + +## 📁 Verfügbare Posts im Repository + +Die ersten 10 von 53 Posts: +1. `2025-07-02-nostr-schrein` +2. `2024-08-05-hello-world` +3. `2024-08-09-sdg-logos` +4. `2024-08-15-OER-im-Blick` +5. `2024-09-02-blog_its_jointly_2024` +6. `2024-09-03-OER-Fachtag-Orca.NRW` +7. `2024-09-04-OER-erklaert` +8. `2024-09-06-OERcamp-Hamburg` +9. `2024-09-11-OER-Brownbag` +10. `2024-09-15-pirner-oer-youtube` + +## 🎯 Typische Use Cases + +### 1. Alle Posts mit bestimmtem Tag finden + +```javascript +const client = createForgejoClient() +const allPosts = await client.getAllPosts() + +const oerPosts = [] +for (const post of allPosts) { + const result = await parseMarkdownString(post.content) + if (result.metadata._tags?.includes('OER')) { + oerPosts.push(result) + } +} + +console.log(`${oerPosts.length} Posts mit Tag "OER"`) +``` + +### 2. Statistiken über alle Posts + +```javascript +const client = createForgejoClient() +const posts = await client.listPosts() + +const stats = { + total: posts.length, + withAuthors: 0, + withLicense: 0, + avgContentLength: 0 +} + +let totalLength = 0 + +for (const post of posts.slice(0, 10)) { + const markdown = await client.getPostContent(post.name) + const result = await parseMarkdownString(markdown) + + if (result.metadata?.creator) stats.withAuthors++ + if (result.metadata?.license) stats.withLicense++ + totalLength += result.content.length +} + +stats.avgContentLength = Math.round(totalLength / 10) + +console.log(stats) +``` + +### 3. Export als JSON + +```javascript +import { writeFile } from 'fs/promises' + +const client = createForgejoClient() +const markdown = await client.getPostContent('2024-08-05-hello-world') +const result = await parseMarkdownString(markdown) + +// Nur Metadaten exportieren +const exportData = { + metadata: result.metadata, + stats: { + contentLength: result.content.length, + headings: extractHeadings(result.ast).length, + links: extractLinks(result.ast).length + } +} + +await writeFile( + 'export.json', + JSON.stringify(exportData, null, 2) +) +``` + +## 🔧 Erweiterte Optionen + +### Parser-Optionen anpassen + +```javascript +const result = await parseMarkdownString(markdown, { + extractYaml: true, // YAML Front Matter extrahieren + parseGfm: true, // GitHub Flavored Markdown + extractAMB: true // AMB-Metadaten extrahieren +}) +``` + +### Eigene Forgejo-Instanz + +```javascript +const client = new ForgejoClient({ + baseUrl: 'https://deine-instanz.de/api/v1', + owner: 'dein-username', + repo: 'dein-repo', + branch: 'main', + token: 'dein-token' +}) +``` + +## 📖 Weitere Dokumentation + +- **README.md** - Vollständige Projekt-Dokumentation +- **docs/ARCHITECTURE.md** - Technische Architektur +- **docs/DECISIONS.md** - Design-Entscheidungen +- **test/parser.test.js** - Test-Beispiele + +## 💡 Nächste Schritte + +1. ✅ Parser ausprobieren (du bist hier!) +2. 🔜 WordPress Transformer (Phase 2) +3. 🔜 Nostr Transformer (Phase 2) +4. 🔜 Batch-Processing +5. 🔜 Browser-Build + +## 🆘 Hilfe + +Bei Fragen oder Problemen: +- Schaue in die Tests: `test/parser.test.js` +- Lese die Beispiele: `examples/*.js` +- Prüfe die API-Docs: `README.md` + +Viel Erfolg! 🚀 diff --git a/playground.js b/playground.js new file mode 100644 index 0000000..cffe05a --- /dev/null +++ b/playground.js @@ -0,0 +1,246 @@ +/** + * Interaktives Playground zum Ausprobieren des Parsers + * + * Verwendung: + * node playground.js + */ + +import { createForgejoClient } from './src/forgejo-client.js' +import { parseMarkdownString, extractHeadings, extractLinks, extractImages } from './src/parser.js' +import { validateAMBMetadata } from './src/extractors/amb-extractor.js' + +// Farben für Console-Output +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m' +} + +function print(text, color = 'reset') { + console.log(`${colors[color]}${text}${colors.reset}`) +} + +function section(title) { + console.log('\n' + '='.repeat(70)) + print(title, 'bright') + console.log('='.repeat(70) + '\n') +} + +async function playground() { + section('🎮 MDParser Playground') + + print('Dieses Script zeigt die verschiedenen Funktionen des Parsers.', 'cyan') + print('Du kannst den Code in playground.js anpassen, um zu experimentieren.\n', 'cyan') + + // 1. Forgejo Client Setup + section('1️⃣ Forgejo API Client') + + const client = createForgejoClient() + print(`✓ Client erstellt`, 'green') + print(` Repository: ${client.owner}/${client.repo}`, 'blue') + print(` Branch: ${client.branch}`, 'blue') + + // Repository-Info + const repo = await client.getRepository() + print(` Beschreibung: ${repo.description}`, 'blue') + + // 2. Posts auflisten + section('2️⃣ Posts auflisten') + + const posts = await client.listPosts() + print(`✓ ${posts.length} Posts gefunden`, 'green') + + // Die ersten 10 Posts anzeigen + print('\nErste 10 Posts:', 'yellow') + posts.slice(0, 10).forEach((post, i) => { + console.log(` ${i + 1}. ${post.name}`) + }) + + // 3. Einen Post parsen + section('3️⃣ Post parsen') + + // Du kannst hier einen anderen Post wählen! + const selectedPost = '2025-04-20-OER-und-Symbole' + print(`📄 Parse: ${selectedPost}`, 'yellow') + + const markdown = await client.getPostContent(selectedPost) + print(`✓ Markdown geladen: ${markdown.length} Zeichen`, 'green') + + const result = await parseMarkdownString(markdown) + print(`✓ Erfolgreich geparst`, 'green') + + // 4. Metadaten analysieren + section('4️⃣ AMB-Metadaten') + + if (result.metadata) { + const meta = result.metadata + + console.log('\n📋 Basis-Informationen:') + console.log(` Titel: ${meta.name || 'N/A'}`) + console.log(` Typ: ${meta.type}`) + console.log(` Lizenz: ${meta.license || 'N/A'}`) + console.log(` Datum: ${meta.datePublished || 'N/A'}`) + console.log(` ID: ${meta.id || 'N/A'}`) + console.log(` Sprache: ${meta.inLanguage ? meta.inLanguage.join(', ') : 'N/A'}`) + + if (meta.creator && meta.creator.length > 0) { + console.log('\n👥 Autoren:') + meta.creator.forEach((creator, i) => { + const name = creator.name || `${creator.givenName} ${creator.familyName}` + console.log(` ${i + 1}. ${name}`) + if (creator.id) console.log(` └─ ID: ${creator.id}`) + if (creator.affiliation) { + console.log(` └─ Affiliation: ${creator.affiliation.name}`) + } + }) + } + + if (meta.about && meta.about.length > 0) { + console.log('\n🏷️ Themen:') + meta.about.forEach(topic => console.log(` - ${topic}`)) + } + + if (meta.learningResourceType && meta.learningResourceType.length > 0) { + console.log('\n📚 Lernressourcen-Typen:') + meta.learningResourceType.forEach(type => console.log(` - ${type}`)) + } + + // Validierung + const validation = validateAMBMetadata(meta) + console.log('\n✓ Validierung:') + console.log(` Status: ${validation.valid ? '✅ Gültig' : '⚠️ Unvollständig'}`) + if (validation.errors.length > 0) { + console.log(` Fehler: ${validation.errors.length}`) + validation.errors.forEach(err => console.log(` - ${err}`)) + } + if (validation.warnings.length > 0) { + console.log(` Warnings: ${validation.warnings.length}`) + validation.warnings.slice(0, 3).forEach(warn => console.log(` - ${warn}`)) + if (validation.warnings.length > 3) { + console.log(` ... und ${validation.warnings.length - 3} weitere`) + } + } + + if (meta._warnings && meta._warnings.length > 0) { + console.log('\n⚠️ Parser-Warnings:') + meta._warnings.forEach(w => console.log(` - ${w}`)) + } + } else { + print('⚠️ Keine Metadaten gefunden', 'yellow') + } + + // 5. Content-Analyse + section('5️⃣ Content-Analyse') + + console.log('📝 Content-Statistik:') + console.log(` Länge: ${result.content.length} Zeichen`) + console.log(` Zeilen: ${result.content.split('\n').length}`) + console.log(` Wörter (ca.): ${result.content.split(/\s+/).length}`) + + // Überschriften + const headings = extractHeadings(result.ast) + console.log(`\n📑 Überschriften: ${headings.length}`) + headings.forEach(h => { + const indent = ' ' + ' '.repeat(h.level - 2) + console.log(`${indent}H${h.level}: ${h.text}`) + }) + + // Links + const links = extractLinks(result.ast) + if (links.length > 0) { + console.log(`\n🔗 Links: ${links.length}`) + links.slice(0, 5).forEach(link => { + console.log(` - ${link.text || 'Kein Text'}`) + console.log(` → ${link.url}`) + }) + if (links.length > 5) { + console.log(` ... und ${links.length - 5} weitere`) + } + } + + // Bilder + const images = extractImages(result.ast) + if (images.length > 0) { + console.log(`\n🖼️ Bilder: ${images.length}`) + images.forEach(img => { + console.log(` - ${img.alt || 'Kein Alt-Text'}`) + console.log(` → ${img.url}`) + }) + } + + // 6. AST-Struktur + section('6️⃣ AST-Struktur (Auszug)') + + console.log('🌲 Root-Node:') + console.log(` Type: ${result.ast.type}`) + console.log(` Children: ${result.ast.children?.length || 0}`) + + if (result.ast.children && result.ast.children.length > 0) { + console.log('\n Erste 5 Child-Nodes:') + result.ast.children.slice(0, 5).forEach((child, i) => { + console.log(` ${i + 1}. ${child.type}`) + if (child.depth) console.log(` └─ depth: ${child.depth}`) + if (child.value) { + const preview = child.value.substring(0, 50) + console.log(` └─ value: "${preview}${child.value.length > 50 ? '...' : ''}"`) + } + }) + if (result.ast.children.length > 5) { + console.log(` ... und ${result.ast.children.length - 5} weitere`) + } + } + + // 7. Rohdaten-Export + section('7️⃣ JSON-Export') + + const exportData = { + metadata: result.metadata, + content: result.content.substring(0, 500) + '...', + stats: { + chars: result.content.length, + lines: result.content.split('\n').length, + headings: headings.length, + links: links.length, + images: images.length + } + } + + console.log('📦 Export-Vorschau (erste 500 Zeichen Content):') + console.log(JSON.stringify(exportData, null, 2)) + + // 8. Tipps + section('💡 Tipps zum Experimentieren') + + print('Du kannst dieses Script anpassen:', 'cyan') + console.log('\n1. Anderen Post wählen:') + console.log(' const selectedPost = "2024-08-05-hello-world"') + + console.log('\n2. Mehrere Posts analysieren:') + console.log(' for (const post of posts.slice(0, 5)) { ... }') + + console.log('\n3. Parser-Optionen anpassen:') + console.log(' const result = await parseMarkdownString(markdown, {') + console.log(' extractYaml: true,') + console.log(' parseGfm: true,') + console.log(' extractAMB: true') + console.log(' })') + + console.log('\n4. Weitere Funktionen ausprobieren:') + console.log(' import { extractYAML, hasYAML } from "./src/index.js"') + + section('✅ Fertig!') + + print('Viel Spaß beim Experimentieren! 🎉', 'green') + print('Für mehr Infos: README.md oder docs/ARCHITECTURE.md\n', 'cyan') +} + +// Fehlerbehandlung +playground().catch(error => { + console.error('\n❌ Fehler:', error.message) + console.error(error.stack) + process.exit(1) +})