This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/backend/frontend/modules/content_creator.py
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

1135 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
BreakPilot Studio - Content Creator Modul
Funktionen:
- Educational Content erstellen & verwalten
- H5P Interactive Content Editor Integration
- Creative Commons Lizenzierung
- Upload von Videos, PDFs, Bildern, Audio
- Publishing zu Matrix Feed
- Analytics & Impact Scoring
"""
class ContentCreatorModule:
"""Modul für Content Creation."""
@staticmethod
def get_css() -> str:
"""CSS für das Content Creator Modul."""
return """
/* =============================================
CONTENT CREATOR MODULE
============================================= */
.panel-content-creator {
display: flex;
flex-direction: column;
height: 100%;
background: var(--bp-bg);
overflow-y: auto;
}
/* Header */
.content-creator-header {
padding: 32px 40px 24px;
background: var(--bp-surface);
border-bottom: 1px solid var(--bp-border);
}
.content-creator-header h2 {
font-size: 24px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 8px;
}
.content-creator-header p {
font-size: 14px;
color: var(--bp-text-muted);
}
/* Navigation Tabs */
.content-creator-tabs {
display: flex;
gap: 8px;
margin-top: 16px;
}
.creator-tab {
padding: 10px 20px;
background: transparent;
border: 1px solid var(--bp-border);
border-radius: 8px;
color: var(--bp-text-muted);
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.creator-tab:hover {
background: var(--bp-surface-elevated);
border-color: var(--bp-primary);
}
.creator-tab.active {
background: var(--bp-primary);
border-color: var(--bp-primary);
color: white;
}
/* Content Area */
.content-creator-content {
padding: 32px 40px;
flex: 1;
}
/* Tab Panels */
.creator-tab-panel {
display: none;
}
.creator-tab-panel.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Create Content Form */
.create-content-form {
max-width: 800px;
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 32px;
}
.form-section {
margin-bottom: 32px;
}
.form-section-title {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid var(--bp-border);
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-size: 14px;
font-weight: 500;
color: var(--bp-text);
margin-bottom: 8px;
}
.form-label-required::after {
content: ' *';
color: var(--bp-danger);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: 12px 16px;
background: var(--bp-bg);
border: 1px solid var(--bp-border);
border-radius: 8px;
color: var(--bp-text);
font-size: 14px;
font-family: inherit;
transition: all 0.2s;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--bp-primary);
box-shadow: 0 0 0 3px var(--bp-primary-soft);
}
.form-textarea {
min-height: 100px;
resize: vertical;
}
.form-hint {
font-size: 12px;
color: var(--bp-text-muted);
margin-top: 6px;
}
/* File Upload Area */
.file-upload-area {
border: 2px dashed var(--bp-border);
border-radius: 12px;
padding: 40px;
text-align: center;
background: var(--bp-bg);
cursor: pointer;
transition: all 0.3s;
}
.file-upload-area:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.file-upload-area.dragover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
transform: scale(1.02);
}
.upload-icon {
font-size: 48px;
color: var(--bp-text-muted);
margin-bottom: 16px;
}
.upload-text {
font-size: 16px;
color: var(--bp-text);
font-weight: 500;
margin-bottom: 8px;
}
.upload-hint {
font-size: 13px;
color: var(--bp-text-muted);
}
.file-input-hidden {
display: none;
}
/* Uploaded Files List */
.uploaded-files {
margin-top: 16px;
}
.uploaded-file {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 8px;
margin-bottom: 8px;
}
.file-icon {
font-size: 24px;
color: var(--bp-primary);
}
.file-info {
flex: 1;
}
.file-name {
font-size: 14px;
font-weight: 500;
color: var(--bp-text);
}
.file-size {
font-size: 12px;
color: var(--bp-text-muted);
}
.file-remove {
padding: 6px 12px;
background: transparent;
border: none;
color: var(--bp-danger);
cursor: pointer;
font-size: 12px;
transition: opacity 0.2s;
}
.file-remove:hover {
opacity: 0.7;
}
/* License Selector */
.license-options {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.license-option {
padding: 16px;
background: var(--bp-bg);
border: 2px solid var(--bp-border);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
text-align: center;
}
.license-option:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.license-option.selected {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.license-badge {
font-size: 24px;
margin-bottom: 8px;
}
.license-name {
font-size: 13px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 4px;
}
.license-desc {
font-size: 11px;
color: var(--bp-text-muted);
}
/* Category Pills */
.category-pills {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.category-pill {
padding: 8px 16px;
background: var(--bp-bg);
border: 1px solid var(--bp-border);
border-radius: 999px;
font-size: 13px;
color: var(--bp-text);
cursor: pointer;
transition: all 0.2s;
}
.category-pill:hover {
border-color: var(--bp-primary);
background: var(--bp-primary-soft);
}
.category-pill.selected {
background: var(--bp-primary);
border-color: var(--bp-primary);
color: white;
}
/* Tags Input */
.tags-container {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px;
background: var(--bp-bg);
border: 1px solid var(--bp-border);
border-radius: 8px;
min-height: 42px;
}
.tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
background: var(--bp-primary-soft);
border: 1px solid var(--bp-primary);
border-radius: 4px;
font-size: 12px;
color: var(--bp-primary);
}
.tag-remove {
cursor: pointer;
font-weight: 700;
opacity: 0.7;
}
.tag-remove:hover {
opacity: 1;
}
.tag-input {
flex: 1;
min-width: 150px;
border: none;
background: transparent;
color: var(--bp-text);
font-size: 14px;
outline: none;
}
/* H5P Integration */
.h5p-editor-container {
margin-top: 16px;
border: 1px solid var(--bp-border);
border-radius: 12px;
overflow: hidden;
background: white;
}
.h5p-editor-frame {
width: 100%;
min-height: 600px;
border: none;
}
/* My Content Grid */
.my-content-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
}
.content-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
overflow: hidden;
transition: all 0.3s;
}
.content-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.content-thumbnail {
width: 100%;
height: 180px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: white;
}
.content-body {
padding: 20px;
}
.content-title {
font-size: 16px;
font-weight: 600;
color: var(--bp-text);
margin-bottom: 8px;
}
.content-meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.meta-badge {
padding: 4px 10px;
background: var(--bp-bg);
border-radius: 4px;
font-size: 11px;
color: var(--bp-text-muted);
}
.content-stats {
display: flex;
gap: 16px;
padding-top: 12px;
border-top: 1px solid var(--bp-border);
font-size: 12px;
color: var(--bp-text-muted);
}
.stat {
display: flex;
align-items: center;
gap: 4px;
}
.content-actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.btn-icon {
padding: 8px;
background: transparent;
border: 1px solid var(--bp-border);
border-radius: 6px;
color: var(--bp-text-muted);
cursor: pointer;
transition: all 0.2s;
}
.btn-icon:hover {
background: var(--bp-primary);
border-color: var(--bp-primary);
color: white;
}
/* Analytics Dashboard */
.analytics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 24px;
margin-bottom: 32px;
}
.analytics-card {
background: var(--bp-surface);
border: 1px solid var(--bp-border);
border-radius: 12px;
padding: 24px;
}
.analytics-label {
font-size: 13px;
color: var(--bp-text-muted);
margin-bottom: 8px;
}
.analytics-value {
font-size: 32px;
font-weight: 700;
color: var(--bp-text);
margin-bottom: 4px;
}
.analytics-change {
font-size: 12px;
color: var(--bp-success);
}
.analytics-change.negative {
color: var(--bp-danger);
}
/* Action Buttons */
.form-actions {
display: flex;
gap: 12px;
margin-top: 32px;
padding-top: 24px;
border-top: 1px solid var(--bp-border);
}
.btn-primary {
flex: 1;
padding: 14px 24px;
background: var(--bp-primary);
border: none;
border-radius: 8px;
color: white;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary:hover {
background: var(--bp-primary-hover);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(108, 27, 27, 0.3);
}
.btn-secondary {
padding: 14px 24px;
background: transparent;
border: 1px solid var(--bp-border);
border-radius: 8px;
color: var(--bp-text);
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover {
background: var(--bp-surface-elevated);
border-color: var(--bp-primary);
}
/* Status Badge */
.status-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
}
.status-draft {
background: rgba(100, 116, 139, 0.15);
color: #64748b;
}
.status-published {
background: rgba(34, 197, 94, 0.15);
color: #22c55e;
}
.status-review {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
}
"""
@staticmethod
def get_html() -> str:
"""HTML für das Content Creator Modul."""
return """
<!-- Content Creator Panel -->
<div id="panel-content-creator" class="panel panel-content-creator" style="display: none;">
<!-- Header -->
<div class="content-creator-header">
<h2>🎓 Content Creator</h2>
<p>Erstellen Sie Educational Content mit Creative Commons Lizenzierung</p>
<!-- Tabs -->
<div class="content-creator-tabs">
<button class="creator-tab active" data-tab="create">
✨ Neuer Content
</button>
<button class="creator-tab" data-tab="my-content">
📚 Mein Content
</button>
<button class="creator-tab" data-tab="analytics">
📊 Analytics
</button>
</div>
</div>
<!-- Content Area -->
<div class="content-creator-content">
<!-- Tab: Create Content -->
<div id="tab-create" class="creator-tab-panel active">
<form class="create-content-form" id="create-content-form">
<!-- Basic Info -->
<div class="form-section">
<div class="form-section-title">📝 Grundinformationen</div>
<div class="form-group">
<label class="form-label form-label-required">Titel</label>
<input type="text" class="form-input" id="content-title" placeholder="z.B. 5-Minuten Yoga für Grundschule" required>
</div>
<div class="form-group">
<label class="form-label form-label-required">Beschreibung</label>
<textarea class="form-textarea" id="content-description" placeholder="Beschreiben Sie Ihren Content..." required></textarea>
<div class="form-hint">Was lernen Schüler mit diesem Content?</div>
</div>
<div class="form-group">
<label class="form-label form-label-required">Content-Typ</label>
<select class="form-select" id="content-type" required>
<option value="">Bitte wählen...</option>
<option value="video">📹 Video</option>
<option value="pdf">📄 PDF Dokument</option>
<option value="image_gallery">🖼️ Bildergalerie</option>
<option value="audio">🎵 Audio</option>
<option value="markdown">📝 Markdown</option>
<option value="h5p">🎓 H5P Interactive</option>
</select>
</div>
</div>
<!-- File Upload -->
<div class="form-section" id="file-upload-section">
<div class="form-section-title">📁 Dateien hochladen</div>
<div class="file-upload-area" id="file-upload-area">
<div class="upload-icon">☁️</div>
<div class="upload-text">Dateien hierher ziehen oder klicken zum Auswählen</div>
<div class="upload-hint">Unterstützt: MP4, PDF, JPG, PNG, MP3 (max. 100MB)</div>
<input type="file" class="file-input-hidden" id="file-input" multiple>
</div>
<div class="uploaded-files" id="uploaded-files"></div>
</div>
<!-- H5P Editor (shown only when content-type = h5p) -->
<div class="form-section" id="h5p-section" style="display: none;">
<div class="form-section-title">🎓 H5P Interactive Content</div>
<div class="h5p-editor-container">
<iframe class="h5p-editor-frame" id="h5p-editor" src="http://localhost:8003/h5p/editor/new"></iframe>
</div>
</div>
<!-- Category & Tags -->
<div class="form-section">
<div class="form-section-title">🏷️ Kategorisierung</div>
<div class="form-group">
<label class="form-label form-label-required">Kategorie</label>
<div class="category-pills" id="category-pills">
<div class="category-pill" data-category="movement">🏃 Bewegung</div>
<div class="category-pill" data-category="math">🔢 Mathe</div>
<div class="category-pill" data-category="steam">🔬 STEAM</div>
<div class="category-pill" data-category="language">📖 Sprache</div>
<div class="category-pill" data-category="arts">🎨 Kunst</div>
<div class="category-pill" data-category="social">🤝 Sozial</div>
<div class="category-pill" data-category="mindfulness">🧘 Achtsamkeit</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Altersgruppe</label>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px;">
<div>
<label class="form-label" style="font-size: 12px;">Von (Jahre)</label>
<input type="number" class="form-input" id="age-min" value="6" min="3" max="18">
</div>
<div>
<label class="form-label" style="font-size: 12px;">Bis (Jahre)</label>
<input type="number" class="form-input" id="age-max" value="10" min="3" max="18">
</div>
</div>
</div>
<div class="form-group">
<label class="form-label">Tags</label>
<div class="tags-container" id="tags-container">
<input type="text" class="tag-input" id="tag-input" placeholder="Tag hinzufügen...">
</div>
<div class="form-hint">Enter drücken um Tags hinzuzufügen</div>
</div>
</div>
<!-- License -->
<div class="form-section">
<div class="form-section-title">⚖️ Creative Commons Lizenz</div>
<div class="license-options" id="license-options">
<div class="license-option" data-license="CC-BY-SA-4.0">
<div class="license-badge">🔄</div>
<div class="license-name">CC BY-SA</div>
<div class="license-desc">Empfohlen</div>
</div>
<div class="license-option" data-license="CC-BY-4.0">
<div class="license-badge">✅</div>
<div class="license-name">CC BY</div>
<div class="license-desc">Attribution</div>
</div>
<div class="license-option" data-license="CC-BY-NC-4.0">
<div class="license-badge">🚫</div>
<div class="license-name">CC BY-NC</div>
<div class="license-desc">Nicht-kommerziell</div>
</div>
<div class="license-option" data-license="CC0-1.0">
<div class="license-badge">🌍</div>
<div class="license-name">CC0</div>
<div class="license-desc">Public Domain</div>
</div>
</div>
</div>
<!-- Actions -->
<div class="form-actions">
<button type="button" class="btn-secondary" id="btn-save-draft">💾 Als Entwurf speichern</button>
<button type="submit" class="btn-primary">🚀 Content veröffentlichen</button>
</div>
</form>
</div>
<!-- Tab: My Content -->
<div id="tab-my-content" class="creator-tab-panel">
<div class="my-content-grid" id="my-content-grid">
<!-- Content wird hier dynamisch eingefügt -->
<div style="grid-column: 1/-1; text-align: center; padding: 60px; color: var(--bp-text-muted);">
<div style="font-size: 64px; margin-bottom: 16px;">📦</div>
<div style="font-size: 16px; margin-bottom: 8px;">Noch kein Content erstellt</div>
<div style="font-size: 14px;">Erstellen Sie Ihren ersten Educational Content!</div>
</div>
</div>
</div>
<!-- Tab: Analytics -->
<div id="tab-analytics" class="creator-tab-panel">
<div class="analytics-grid">
<div class="analytics-card">
<div class="analytics-label">Gesamt Downloads</div>
<div class="analytics-value" id="total-downloads">0</div>
<div class="analytics-change">+0% diese Woche</div>
</div>
<div class="analytics-card">
<div class="analytics-label">Durchschn. Rating</div>
<div class="analytics-value" id="avg-rating">0.0</div>
<div class="analytics-change">⭐⭐⭐⭐⭐</div>
</div>
<div class="analytics-card">
<div class="analytics-label">Impact Score</div>
<div class="analytics-value" id="impact-score">0</div>
<div class="analytics-change">+0 Punkte</div>
</div>
<div class="analytics-card">
<div class="analytics-label">Veröffentlicht</div>
<div class="analytics-value" id="published-count">0</div>
<div class="analytics-change">Content-Stücke</div>
</div>
</div>
<div style="text-align: center; padding: 60px; color: var(--bp-text-muted);">
<div style="font-size: 48px; margin-bottom: 16px;">📊</div>
<div style="font-size: 16px;">Detaillierte Analytics kommen bald!</div>
</div>
</div>
</div>
</div>
"""
@staticmethod
def get_js() -> str:
"""JavaScript für das Content Creator Modul."""
return """
// =============================================
// CONTENT CREATOR MODULE
// =============================================
(function() {
'use strict';
const ContentCreator = {
selectedFiles: [],
selectedCategory: null,
selectedLicense: 'CC-BY-SA-4.0',
tags: [],
init() {
this.bindTabSwitching();
this.bindFileUpload();
this.bindCategorySelection();
this.bindLicenseSelection();
this.bindTagsInput();
this.bindContentTypeChange();
this.bindFormSubmit();
// Set default license
document.querySelector(`[data-license="${this.selectedLicense}"]`)?.classList.add('selected');
},
bindTabSwitching() {
const tabs = document.querySelectorAll('.creator-tab');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
// Update tabs
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Update panels
document.querySelectorAll('.creator-tab-panel').forEach(panel => {
panel.classList.remove('active');
});
document.getElementById(`tab-${tabName}`)?.classList.add('active');
// Load content if switching to my-content
if (tabName === 'my-content') {
this.loadMyContent();
} else if (tabName === 'analytics') {
this.loadAnalytics();
}
});
});
},
bindFileUpload() {
const uploadArea = document.getElementById('file-upload-area');
const fileInput = document.getElementById('file-input');
uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
this.handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', (e) => {
this.handleFiles(e.target.files);
});
},
handleFiles(files) {
Array.from(files).forEach(file => {
if (file.size > 100 * 1024 * 1024) {
alert(`Datei ${file.name} ist zu groß (max. 100MB)`);
return;
}
this.selectedFiles.push(file);
this.renderUploadedFiles();
});
},
renderUploadedFiles() {
const container = document.getElementById('uploaded-files');
container.innerHTML = '';
this.selectedFiles.forEach((file, index) => {
const fileEl = document.createElement('div');
fileEl.className = 'uploaded-file';
fileEl.innerHTML = `
<div class="file-icon">${this.getFileIcon(file.type)}</div>
<div class="file-info">
<div class="file-name">${file.name}</div>
<div class="file-size">${this.formatFileSize(file.size)}</div>
</div>
<button type="button" class="file-remove" data-index="${index}">Entfernen</button>
`;
fileEl.querySelector('.file-remove').addEventListener('click', () => {
this.selectedFiles.splice(index, 1);
this.renderUploadedFiles();
});
container.appendChild(fileEl);
});
},
getFileIcon(type) {
if (type.startsWith('video/')) return '📹';
if (type.startsWith('image/')) return '🖼️';
if (type.startsWith('audio/')) return '🎵';
if (type === 'application/pdf') return '📄';
return '📁';
},
formatFileSize(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
},
bindCategorySelection() {
document.querySelectorAll('.category-pill').forEach(pill => {
pill.addEventListener('click', () => {
document.querySelectorAll('.category-pill').forEach(p => p.classList.remove('selected'));
pill.classList.add('selected');
this.selectedCategory = pill.dataset.category;
});
});
},
bindLicenseSelection() {
document.querySelectorAll('.license-option').forEach(option => {
option.addEventListener('click', () => {
document.querySelectorAll('.license-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.selectedLicense = option.dataset.license;
});
});
},
bindTagsInput() {
const input = document.getElementById('tag-input');
const container = document.getElementById('tags-container');
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && input.value.trim()) {
e.preventDefault();
const tag = input.value.trim();
if (!this.tags.includes(tag)) {
this.tags.push(tag);
this.renderTags();
}
input.value = '';
}
});
},
renderTags() {
const container = document.getElementById('tags-container');
const input = document.getElementById('tag-input');
// Remove old tags
container.querySelectorAll('.tag').forEach(el => el.remove());
// Add new tags
this.tags.forEach((tag, index) => {
const tagEl = document.createElement('div');
tagEl.className = 'tag';
tagEl.innerHTML = `
${tag}
<span class="tag-remove" data-index="${index}">×</span>
`;
tagEl.querySelector('.tag-remove').addEventListener('click', () => {
this.tags.splice(index, 1);
this.renderTags();
});
container.insertBefore(tagEl, input);
});
},
bindContentTypeChange() {
const typeSelect = document.getElementById('content-type');
typeSelect.addEventListener('change', () => {
const h5pSection = document.getElementById('h5p-section');
const fileSection = document.getElementById('file-upload-section');
if (typeSelect.value === 'h5p') {
h5pSection.style.display = 'block';
fileSection.style.display = 'none';
} else {
h5pSection.style.display = 'none';
fileSection.style.display = 'block';
}
});
},
bindFormSubmit() {
const form = document.getElementById('create-content-form');
form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.publishContent();
});
document.getElementById('btn-save-draft').addEventListener('click', () => {
this.saveDraft();
});
},
async publishContent() {
if (!this.selectedCategory) {
alert('Bitte wählen Sie eine Kategorie aus.');
return;
}
const data = {
title: document.getElementById('content-title').value,
description: document.getElementById('content-description').value,
content_type: document.getElementById('content-type').value,
category: this.selectedCategory,
license: this.selectedLicense,
age_min: parseInt(document.getElementById('age-min').value),
age_max: parseInt(document.getElementById('age-max').value),
tags: this.tags,
status: 'published'
};
try {
// Create content
const response = await fetch('http://localhost:8002/api/v1/content', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) throw new Error('Failed to create content');
const content = await response.json();
// Upload files if any
if (this.selectedFiles.length > 0) {
await this.uploadFiles(content.id);
}
alert('✅ Content erfolgreich veröffentlicht und zu Matrix Feed hinzugefügt!');
this.resetForm();
// Switch to my-content tab
document.querySelector('[data-tab="my-content"]').click();
} catch (error) {
console.error('Error:', error);
alert('❌ Fehler beim Veröffentlichen: ' + error.message);
}
},
async uploadFiles(contentId) {
for (const file of this.selectedFiles) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('http://localhost:8002/api/v1/upload', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('File upload failed');
const { file_url } = await response.json();
// Attach file to content
await fetch(`http://localhost:8002/api/v1/content/${contentId}/files`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file_urls: [file_url] })
});
}
},
saveDraft() {
alert('💾 Als Entwurf gespeichert (Feature kommt bald)');
},
resetForm() {
document.getElementById('create-content-form').reset();
this.selectedFiles = [];
this.selectedCategory = null;
this.tags = [];
this.renderUploadedFiles();
this.renderTags();
document.querySelectorAll('.category-pill').forEach(p => p.classList.remove('selected'));
},
async loadMyContent() {
// TODO: Load user's content from API
console.log('Loading my content...');
},
async loadAnalytics() {
// TODO: Load analytics from API
console.log('Loading analytics...');
}
};
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => ContentCreator.init());
} else {
ContentCreator.init();
}
})();
"""