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>
1049 lines
25 KiB
Python
1049 lines
25 KiB
Python
"""
|
||
BreakPilot Studio - Content Feed Modul
|
||
|
||
Funktionen:
|
||
- Educational Content durchsuchen & entdecken
|
||
- Matrix Feed Integration
|
||
- Search & Filter (Kategorie, Alter, Lizenz, Tags)
|
||
- Content Preview & Download
|
||
- Rating System (5 Sterne + Kommentare)
|
||
- Favoriten & Collections
|
||
"""
|
||
|
||
|
||
class ContentFeedModule:
|
||
"""Modul für Content Feed & Discovery."""
|
||
|
||
@staticmethod
|
||
def get_css() -> str:
|
||
"""CSS für das Content Feed Modul."""
|
||
return """
|
||
/* =============================================
|
||
CONTENT FEED MODULE
|
||
============================================= */
|
||
|
||
.panel-content-feed {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background: var(--bp-bg);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* Header with Search */
|
||
.content-feed-header {
|
||
padding: 24px 40px;
|
||
background: var(--bp-surface);
|
||
border-bottom: 1px solid var(--bp-border);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
.feed-header-top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.feed-header-top h2 {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--bp-text);
|
||
}
|
||
|
||
/* Search Bar */
|
||
.feed-search-bar {
|
||
position: relative;
|
||
}
|
||
|
||
.feed-search-input {
|
||
width: 100%;
|
||
padding: 14px 48px 14px 20px;
|
||
background: var(--bp-bg);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 999px;
|
||
color: var(--bp-text);
|
||
font-size: 15px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.feed-search-input:focus {
|
||
outline: none;
|
||
border-color: var(--bp-primary);
|
||
box-shadow: 0 0 0 3px var(--bp-primary-soft);
|
||
}
|
||
|
||
.feed-search-icon {
|
||
position: absolute;
|
||
right: 20px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
color: var(--bp-text-muted);
|
||
font-size: 18px;
|
||
}
|
||
|
||
/* Filters */
|
||
.feed-filters {
|
||
display: flex;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
margin-top: 16px;
|
||
}
|
||
|
||
.filter-group {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.filter-select {
|
||
padding: 8px 16px;
|
||
background: var(--bp-bg);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 8px;
|
||
color: var(--bp-text);
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.filter-select:hover {
|
||
border-color: var(--bp-primary);
|
||
}
|
||
|
||
.filter-chip {
|
||
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;
|
||
}
|
||
|
||
.filter-chip:hover {
|
||
background: var(--bp-primary-soft);
|
||
border-color: var(--bp-primary);
|
||
}
|
||
|
||
.filter-chip.active {
|
||
background: var(--bp-primary);
|
||
border-color: var(--bp-primary);
|
||
color: white;
|
||
}
|
||
|
||
/* View Toggle */
|
||
.view-toggle {
|
||
display: flex;
|
||
gap: 4px;
|
||
padding: 4px;
|
||
background: var(--bp-bg);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.view-btn {
|
||
padding: 8px 12px;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 6px;
|
||
color: var(--bp-text-muted);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.view-btn:hover {
|
||
background: var(--bp-surface-elevated);
|
||
}
|
||
|
||
.view-btn.active {
|
||
background: var(--bp-primary);
|
||
color: white;
|
||
}
|
||
|
||
/* Content Area */
|
||
.content-feed-content {
|
||
padding: 32px 40px;
|
||
flex: 1;
|
||
}
|
||
|
||
/* Feed Grid View */
|
||
.feed-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
||
gap: 24px;
|
||
}
|
||
|
||
/* Feed List View */
|
||
.feed-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
/* Feed Card (Grid View) */
|
||
.feed-card {
|
||
background: var(--bp-surface);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
transition: all 0.3s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.feed-card:hover {
|
||
transform: translateY(-4px);
|
||
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||
border-color: var(--bp-primary);
|
||
}
|
||
|
||
.feed-card-thumbnail {
|
||
width: 100%;
|
||
height: 200px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 64px;
|
||
color: white;
|
||
position: relative;
|
||
}
|
||
|
||
.feed-card-badge {
|
||
position: absolute;
|
||
top: 12px;
|
||
right: 12px;
|
||
padding: 6px 12px;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
backdrop-filter: blur(10px);
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: white;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.feed-card-body {
|
||
padding: 20px;
|
||
}
|
||
|
||
.feed-card-title {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
color: var(--bp-text);
|
||
margin-bottom: 8px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.feed-card-description {
|
||
font-size: 14px;
|
||
color: var(--bp-text-muted);
|
||
margin-bottom: 16px;
|
||
line-height: 1.5;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.feed-card-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.feed-meta-badge {
|
||
padding: 4px 10px;
|
||
background: var(--bp-bg);
|
||
border-radius: 4px;
|
||
font-size: 11px;
|
||
color: var(--bp-text-muted);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.feed-card-footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-top: 16px;
|
||
border-top: 1px solid var(--bp-border);
|
||
}
|
||
|
||
.feed-rating {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 13px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.feed-stars {
|
||
color: var(--bp-gold);
|
||
}
|
||
|
||
.feed-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.feed-action-btn {
|
||
padding: 8px 16px;
|
||
background: transparent;
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 6px;
|
||
color: var(--bp-text);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.feed-action-btn:hover {
|
||
background: var(--bp-primary);
|
||
border-color: var(--bp-primary);
|
||
color: white;
|
||
}
|
||
|
||
.feed-action-btn-primary {
|
||
background: var(--bp-primary);
|
||
border-color: var(--bp-primary);
|
||
color: white;
|
||
}
|
||
|
||
.feed-action-btn-primary:hover {
|
||
background: var(--bp-primary-hover);
|
||
}
|
||
|
||
/* Feed List Card */
|
||
.feed-list-card {
|
||
display: flex;
|
||
gap: 20px;
|
||
background: var(--bp-surface);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
transition: all 0.3s;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.feed-list-card:hover {
|
||
border-color: var(--bp-primary);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.feed-list-thumbnail {
|
||
width: 120px;
|
||
height: 120px;
|
||
flex-shrink: 0;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 48px;
|
||
color: white;
|
||
}
|
||
|
||
.feed-list-body {
|
||
flex: 1;
|
||
}
|
||
|
||
/* Content Modal */
|
||
.content-modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
backdrop-filter: blur(5px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 1000;
|
||
padding: 20px;
|
||
}
|
||
|
||
.content-modal {
|
||
background: var(--bp-surface);
|
||
border-radius: 16px;
|
||
max-width: 900px;
|
||
width: 100%;
|
||
max-height: 90vh;
|
||
overflow-y: auto;
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.content-modal-header {
|
||
padding: 32px;
|
||
border-bottom: 1px solid var(--bp-border);
|
||
position: sticky;
|
||
top: 0;
|
||
background: var(--bp-surface);
|
||
z-index: 1;
|
||
}
|
||
|
||
.modal-close {
|
||
float: right;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: transparent;
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 6px;
|
||
color: var(--bp-text-muted);
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.modal-close:hover {
|
||
background: var(--bp-danger);
|
||
border-color: var(--bp-danger);
|
||
color: white;
|
||
}
|
||
|
||
.content-modal-body {
|
||
padding: 32px;
|
||
}
|
||
|
||
.modal-content-preview {
|
||
width: 100%;
|
||
max-height: 500px;
|
||
background: var(--bp-bg);
|
||
border-radius: 12px;
|
||
margin-bottom: 24px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.modal-content-preview iframe,
|
||
.modal-content-preview video,
|
||
.modal-content-preview img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border: none;
|
||
}
|
||
|
||
.modal-content-info {
|
||
margin-top: 24px;
|
||
}
|
||
|
||
.modal-section-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--bp-text-muted);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.license-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 16px;
|
||
background: var(--bp-bg);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 8px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.license-badge-large {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.license-text {
|
||
flex: 1;
|
||
}
|
||
|
||
.license-name-large {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--bp-text);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.license-link {
|
||
font-size: 13px;
|
||
color: var(--bp-primary);
|
||
text-decoration: none;
|
||
}
|
||
|
||
.license-link:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
/* Rating Section */
|
||
.rating-section {
|
||
margin-top: 24px;
|
||
padding-top: 24px;
|
||
border-top: 1px solid var(--bp-border);
|
||
}
|
||
|
||
.rating-stars-interactive {
|
||
display: flex;
|
||
gap: 8px;
|
||
font-size: 32px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.star-btn {
|
||
background: none;
|
||
border: none;
|
||
color: var(--bp-border);
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
padding: 0;
|
||
}
|
||
|
||
.star-btn:hover,
|
||
.star-btn.active {
|
||
color: var(--bp-gold);
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.rating-comment {
|
||
width: 100%;
|
||
padding: 12px;
|
||
background: var(--bp-bg);
|
||
border: 1px solid var(--bp-border);
|
||
border-radius: 8px;
|
||
color: var(--bp-text);
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
resize: vertical;
|
||
min-height: 80px;
|
||
}
|
||
|
||
.rating-submit {
|
||
margin-top: 12px;
|
||
padding: 12px 24px;
|
||
background: var(--bp-primary);
|
||
border: none;
|
||
border-radius: 8px;
|
||
color: white;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.rating-submit:hover {
|
||
background: var(--bp-primary-hover);
|
||
}
|
||
|
||
/* Empty State */
|
||
.feed-empty {
|
||
text-align: center;
|
||
padding: 80px 20px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.feed-empty-icon {
|
||
font-size: 80px;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.feed-empty-title {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: var(--bp-text);
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.feed-empty-text {
|
||
font-size: 15px;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* Loading State */
|
||
.feed-loading {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: var(--bp-text-muted);
|
||
}
|
||
|
||
.spinner {
|
||
width: 48px;
|
||
height: 48px;
|
||
border: 4px solid var(--bp-border);
|
||
border-top-color: var(--bp-primary);
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin: 0 auto 16px;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
"""
|
||
|
||
@staticmethod
|
||
def get_html() -> str:
|
||
"""HTML für das Content Feed Modul."""
|
||
return """
|
||
<!-- Content Feed Panel -->
|
||
<div id="panel-content-feed" class="panel panel-content-feed" style="display: none;">
|
||
|
||
<!-- Header with Search -->
|
||
<div class="content-feed-header">
|
||
<div class="feed-header-top">
|
||
<h2>📚 Content Feed</h2>
|
||
<div class="view-toggle">
|
||
<button class="view-btn active" data-view="grid">⬜</button>
|
||
<button class="view-btn" data-view="list">☰</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="feed-search-bar">
|
||
<input type="text" class="feed-search-input" id="feed-search" placeholder="Suche nach Educational Content...">
|
||
<span class="feed-search-icon">🔍</span>
|
||
</div>
|
||
|
||
<div class="feed-filters">
|
||
<div class="filter-group">
|
||
<span class="filter-label">Kategorie:</span>
|
||
<select class="filter-select" id="filter-category">
|
||
<option value="">Alle</option>
|
||
<option value="movement">🏃 Bewegung</option>
|
||
<option value="math">🔢 Mathe</option>
|
||
<option value="steam">🔬 STEAM</option>
|
||
<option value="language">📖 Sprache</option>
|
||
<option value="arts">🎨 Kunst</option>
|
||
<option value="social">🤝 Sozial</option>
|
||
<option value="mindfulness">🧘 Achtsamkeit</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<span class="filter-label">Typ:</span>
|
||
<select class="filter-select" id="filter-type">
|
||
<option value="">Alle</option>
|
||
<option value="video">📹 Video</option>
|
||
<option value="pdf">📄 PDF</option>
|
||
<option value="h5p">🎓 H5P</option>
|
||
<option value="image_gallery">🖼️ Bilder</option>
|
||
<option value="audio">🎵 Audio</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-group">
|
||
<span class="filter-label">Alter:</span>
|
||
<select class="filter-select" id="filter-age">
|
||
<option value="">Alle</option>
|
||
<option value="3-6">3-6 Jahre</option>
|
||
<option value="6-10">6-10 Jahre</option>
|
||
<option value="10-14">10-14 Jahre</option>
|
||
<option value="14-18">14-18 Jahre</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="filter-chip active" data-filter="all">
|
||
Alle
|
||
</div>
|
||
<div class="filter-chip" data-filter="favorites">
|
||
⭐ Favoriten
|
||
</div>
|
||
<div class="filter-chip" data-filter="recent">
|
||
🆕 Neu
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content Area -->
|
||
<div class="content-feed-content">
|
||
|
||
<!-- Loading State -->
|
||
<div class="feed-loading" id="feed-loading">
|
||
<div class="spinner"></div>
|
||
<div>Content wird geladen...</div>
|
||
</div>
|
||
|
||
<!-- Grid View -->
|
||
<div class="feed-grid" id="feed-grid" style="display: none;">
|
||
<!-- Content cards will be inserted here -->
|
||
</div>
|
||
|
||
<!-- List View -->
|
||
<div class="feed-list" id="feed-list" style="display: none;">
|
||
<!-- Content list items will be inserted here -->
|
||
</div>
|
||
|
||
<!-- Empty State -->
|
||
<div class="feed-empty" id="feed-empty" style="display: none;">
|
||
<div class="feed-empty-icon">📦</div>
|
||
<div class="feed-empty-title">Kein Content gefunden</div>
|
||
<div class="feed-empty-text">
|
||
Versuchen Sie andere Suchbegriffe oder Filter.<br>
|
||
Oder erstellen Sie selbst Educational Content!
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Content Modal Template -->
|
||
<template id="content-modal-template">
|
||
<div class="content-modal-overlay">
|
||
<div class="content-modal">
|
||
<div class="content-modal-header">
|
||
<button class="modal-close">×</button>
|
||
<h2 class="modal-title">Content Title</h2>
|
||
</div>
|
||
<div class="content-modal-body">
|
||
<div class="modal-content-preview">
|
||
<!-- Preview will be inserted here -->
|
||
</div>
|
||
|
||
<div class="modal-content-info">
|
||
<p class="modal-description">Description...</p>
|
||
|
||
<div class="feed-card-meta" style="margin: 16px 0;">
|
||
<!-- Meta badges -->
|
||
</div>
|
||
|
||
<div class="modal-section-title">Creative Commons Lizenz</div>
|
||
<div class="license-info">
|
||
<div class="license-badge-large">⚖️</div>
|
||
<div class="license-text">
|
||
<div class="license-name-large">CC BY-SA 4.0</div>
|
||
<a href="#" class="license-link" target="_blank">Details anzeigen →</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rating-section">
|
||
<div class="modal-section-title">Bewerten Sie diesen Content</div>
|
||
<div class="rating-stars-interactive" data-rating="0">
|
||
<button class="star-btn" data-star="1">★</button>
|
||
<button class="star-btn" data-star="2">★</button>
|
||
<button class="star-btn" data-star="3">★</button>
|
||
<button class="star-btn" data-star="4">★</button>
|
||
<button class="star-btn" data-star="5">★</button>
|
||
</div>
|
||
<textarea class="rating-comment" placeholder="Teilen Sie Ihre Erfahrungen mit diesem Content..."></textarea>
|
||
<button class="rating-submit">Bewertung abgeben</button>
|
||
</div>
|
||
|
||
<div class="form-actions" style="margin-top: 24px;">
|
||
<button class="btn-primary">📥 Content herunterladen</button>
|
||
<button class="btn-secondary">⭐ Zu Favoriten</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
"""
|
||
|
||
@staticmethod
|
||
def get_js() -> str:
|
||
"""JavaScript für das Content Feed Modul."""
|
||
return """
|
||
// =============================================
|
||
// CONTENT FEED MODULE
|
||
// =============================================
|
||
|
||
(function() {
|
||
'use strict';
|
||
|
||
const ContentFeed = {
|
||
currentView: 'grid',
|
||
allContent: [],
|
||
filteredContent: [],
|
||
currentFilters: {
|
||
search: '',
|
||
category: '',
|
||
type: '',
|
||
age: '',
|
||
preset: 'all'
|
||
},
|
||
|
||
init() {
|
||
this.bindViewToggle();
|
||
this.bindSearch();
|
||
this.bindFilters();
|
||
this.loadContent();
|
||
},
|
||
|
||
bindViewToggle() {
|
||
document.querySelectorAll('.view-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const view = btn.dataset.view;
|
||
document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
|
||
this.currentView = view;
|
||
this.renderContent();
|
||
});
|
||
});
|
||
},
|
||
|
||
bindSearch() {
|
||
const searchInput = document.getElementById('feed-search');
|
||
let searchTimeout;
|
||
|
||
searchInput.addEventListener('input', (e) => {
|
||
clearTimeout(searchTimeout);
|
||
searchTimeout = setTimeout(() => {
|
||
this.currentFilters.search = e.target.value.toLowerCase();
|
||
this.filterContent();
|
||
}, 300);
|
||
});
|
||
},
|
||
|
||
bindFilters() {
|
||
// Select filters
|
||
['category', 'type', 'age'].forEach(filter => {
|
||
document.getElementById(`filter-${filter}`).addEventListener('change', (e) => {
|
||
this.currentFilters[filter] = e.target.value;
|
||
this.filterContent();
|
||
});
|
||
});
|
||
|
||
// Chip filters
|
||
document.querySelectorAll('.filter-chip').forEach(chip => {
|
||
chip.addEventListener('click', () => {
|
||
document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
||
chip.classList.add('active');
|
||
|
||
this.currentFilters.preset = chip.dataset.filter;
|
||
this.filterContent();
|
||
});
|
||
});
|
||
},
|
||
|
||
async loadContent() {
|
||
document.getElementById('feed-loading').style.display = 'block';
|
||
document.getElementById('feed-grid').style.display = 'none';
|
||
document.getElementById('feed-list').style.display = 'none';
|
||
document.getElementById('feed-empty').style.display = 'none';
|
||
|
||
try {
|
||
const response = await fetch('http://localhost:8002/api/v1/content?limit=50');
|
||
if (!response.ok) throw new Error('Failed to load content');
|
||
|
||
const data = await response.json();
|
||
this.allContent = data.content || [];
|
||
this.filteredContent = this.allContent;
|
||
|
||
this.renderContent();
|
||
} catch (error) {
|
||
console.error('Error loading content:', error);
|
||
document.getElementById('feed-loading').innerHTML = `
|
||
<div style="color: var(--bp-danger);">
|
||
❌ Fehler beim Laden des Contents<br>
|
||
<small>${error.message}</small>
|
||
</div>
|
||
`;
|
||
}
|
||
},
|
||
|
||
filterContent() {
|
||
let filtered = this.allContent;
|
||
|
||
// Search filter
|
||
if (this.currentFilters.search) {
|
||
filtered = filtered.filter(item =>
|
||
item.title.toLowerCase().includes(this.currentFilters.search) ||
|
||
(item.description && item.description.toLowerCase().includes(this.currentFilters.search))
|
||
);
|
||
}
|
||
|
||
// Category filter
|
||
if (this.currentFilters.category) {
|
||
filtered = filtered.filter(item => item.category === this.currentFilters.category);
|
||
}
|
||
|
||
// Type filter
|
||
if (this.currentFilters.type) {
|
||
filtered = filtered.filter(item => item.content_type === this.currentFilters.type);
|
||
}
|
||
|
||
// Age filter
|
||
if (this.currentFilters.age) {
|
||
const [min, max] = this.currentFilters.age.split('-').map(Number);
|
||
filtered = filtered.filter(item =>
|
||
item.age_min <= max && item.age_max >= min
|
||
);
|
||
}
|
||
|
||
// Preset filters
|
||
if (this.currentFilters.preset === 'favorites') {
|
||
// TODO: Filter favorites from localStorage
|
||
} else if (this.currentFilters.preset === 'recent') {
|
||
filtered = filtered.sort((a, b) =>
|
||
new Date(b.created_at) - new Date(a.created_at)
|
||
).slice(0, 20);
|
||
}
|
||
|
||
this.filteredContent = filtered;
|
||
this.renderContent();
|
||
},
|
||
|
||
renderContent() {
|
||
document.getElementById('feed-loading').style.display = 'none';
|
||
|
||
if (this.filteredContent.length === 0) {
|
||
document.getElementById('feed-grid').style.display = 'none';
|
||
document.getElementById('feed-list').style.display = 'none';
|
||
document.getElementById('feed-empty').style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('feed-empty').style.display = 'none';
|
||
|
||
if (this.currentView === 'grid') {
|
||
this.renderGridView();
|
||
} else {
|
||
this.renderListView();
|
||
}
|
||
},
|
||
|
||
renderGridView() {
|
||
const grid = document.getElementById('feed-grid');
|
||
grid.style.display = 'grid';
|
||
document.getElementById('feed-list').style.display = 'none';
|
||
|
||
grid.innerHTML = this.filteredContent.map(item => `
|
||
<div class="feed-card" data-id="${item.id}">
|
||
<div class="feed-card-thumbnail">
|
||
${this.getContentIcon(item.content_type)}
|
||
<div class="feed-card-badge">${item.content_type}</div>
|
||
</div>
|
||
<div class="feed-card-body">
|
||
<div class="feed-card-title">${item.title}</div>
|
||
<div class="feed-card-description">${item.description || 'Keine Beschreibung'}</div>
|
||
<div class="feed-card-meta">
|
||
<span class="feed-meta-badge">${this.getCategoryIcon(item.category)} ${this.getCategoryName(item.category)}</span>
|
||
<span class="feed-meta-badge">👥 ${item.age_min}-${item.age_max} Jahre</span>
|
||
<span class="feed-meta-badge">⚖️ ${item.license}</span>
|
||
</div>
|
||
<div class="feed-card-footer">
|
||
<div class="feed-rating">
|
||
<span class="feed-stars">⭐⭐⭐⭐⭐</span>
|
||
<span>${item.avg_rating || 0}/5</span>
|
||
</div>
|
||
<div class="feed-actions">
|
||
<button class="feed-action-btn feed-action-btn-primary" onclick="ContentFeed.openModal('${item.id}')">
|
||
Ansehen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
},
|
||
|
||
renderListView() {
|
||
const list = document.getElementById('feed-list');
|
||
list.style.display = 'flex';
|
||
document.getElementById('feed-grid').style.display = 'none';
|
||
|
||
list.innerHTML = this.filteredContent.map(item => `
|
||
<div class="feed-list-card" data-id="${item.id}" onclick="ContentFeed.openModal('${item.id}')">
|
||
<div class="feed-list-thumbnail">
|
||
${this.getContentIcon(item.content_type)}
|
||
</div>
|
||
<div class="feed-list-body">
|
||
<div class="feed-card-title">${item.title}</div>
|
||
<div class="feed-card-description">${item.description || 'Keine Beschreibung'}</div>
|
||
<div class="feed-card-meta">
|
||
<span class="feed-meta-badge">${this.getCategoryIcon(item.category)} ${this.getCategoryName(item.category)}</span>
|
||
<span class="feed-meta-badge">👥 ${item.age_min}-${item.age_max} Jahre</span>
|
||
<span class="feed-meta-badge">⚖️ ${item.license}</span>
|
||
<span class="feed-meta-badge">📥 ${item.downloads || 0} Downloads</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
},
|
||
|
||
openModal(contentId) {
|
||
const content = this.filteredContent.find(c => c.id === contentId);
|
||
if (!content) return;
|
||
|
||
const template = document.getElementById('content-modal-template');
|
||
const modal = template.content.cloneNode(true);
|
||
|
||
// Fill modal with content data
|
||
modal.querySelector('.modal-title').textContent = content.title;
|
||
modal.querySelector('.modal-description').textContent = content.description || 'Keine Beschreibung';
|
||
|
||
// Close button
|
||
modal.querySelector('.modal-close').addEventListener('click', (e) => {
|
||
e.target.closest('.content-modal-overlay').remove();
|
||
});
|
||
|
||
// Close on overlay click
|
||
modal.querySelector('.content-modal-overlay').addEventListener('click', (e) => {
|
||
if (e.target.classList.contains('content-modal-overlay')) {
|
||
e.target.remove();
|
||
}
|
||
});
|
||
|
||
// Add modal to body
|
||
document.body.appendChild(modal);
|
||
},
|
||
|
||
getContentIcon(type) {
|
||
const icons = {
|
||
video: '📹',
|
||
pdf: '📄',
|
||
h5p: '🎓',
|
||
image_gallery: '🖼️',
|
||
audio: '🎵',
|
||
markdown: '📝'
|
||
};
|
||
return icons[type] || '📦';
|
||
},
|
||
|
||
getCategoryIcon(category) {
|
||
const icons = {
|
||
movement: '🏃',
|
||
math: '🔢',
|
||
steam: '🔬',
|
||
language: '📖',
|
||
arts: '🎨',
|
||
social: '🤝',
|
||
mindfulness: '🧘'
|
||
};
|
||
return icons[category] || '📚';
|
||
},
|
||
|
||
getCategoryName(category) {
|
||
const names = {
|
||
movement: 'Bewegung',
|
||
math: 'Mathe',
|
||
steam: 'STEAM',
|
||
language: 'Sprache',
|
||
arts: 'Kunst',
|
||
social: 'Sozial',
|
||
mindfulness: 'Achtsamkeit'
|
||
};
|
||
return names[category] || category;
|
||
}
|
||
};
|
||
|
||
// Make ContentFeed globally accessible
|
||
window.ContentFeed = ContentFeed;
|
||
|
||
// Initialize when DOM is ready
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', () => ContentFeed.init());
|
||
} else {
|
||
ContentFeed.init();
|
||
}
|
||
})();
|
||
"""
|