docker.videokonverter/video-konverter/app/templates/tv/search.html
data 6d0b8936c5 feat: VideoKonverter v4.0 - Streaming-Client Ausbau
TV-App komplett ueberarbeitet: i18n (DE/EN), Multi-User Quick-Switch,
3 Themes (Dark/Medium/Light), 3 Ansichten (Grid/Liste/Detail),
Filter (Quellen/Genre/Rating/Sortierung), Merkliste, 5-Sterne-Bewertung,
Watch-Status, Player-Overlay (Audio/Untertitel/Qualitaet/Naechste Episode),
Episoden-Thumbnails, Suchverlauf, Queue-Bugfix (delete_source).

5 neue DB-Tabellen, 10+ neue API-Endpunkte, ~3800 neue Zeilen Code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 07:39:12 +01:00

133 lines
4.7 KiB
HTML

{% extends "tv/base.html" %}
{% block title %}{{ t('search.title') }} - VideoKonverter TV{% endblock %}
{% block content %}
<section class="tv-section">
<h1 class="tv-page-title">{{ t('search.title') }}</h1>
<form action="/tv/search" method="GET" class="tv-search-form" autocomplete="off">
<div class="tv-search-wrapper">
<input type="text" name="q" id="search-input" value="{{ query }}"
placeholder="{{ t('search.placeholder') }}"
class="tv-search-input" data-focusable autofocus>
<div class="tv-autocomplete" id="autocomplete" style="display:none"></div>
</div>
<button type="submit" class="tv-search-btn" data-focusable>{{ t('search.button') }}</button>
</form>
{% if query %}
<!-- Serien-Ergebnisse -->
{% if series %}
<h2 class="tv-section-title">{{ t('search.results_series') }} ({{ series|length }})</h2>
<div class="tv-grid">
{% for s in series %}
<a href="/tv/series/{{ s.id }}" class="tv-card" data-focusable>
{% if s.poster_url %}
<img src="{{ s.poster_url }}" alt="" class="tv-card-img" loading="lazy">
{% else %}
<div class="tv-card-placeholder">{{ s.title or s.folder_name }}</div>
{% endif %}
<div class="tv-card-info">
<span class="tv-card-title">{{ s.title or s.folder_name }}</span>
{% if s.genres %}
<span class="tv-card-meta">{{ s.genres }}</span>
{% endif %}
</div>
</a>
{% endfor %}
</div>
{% endif %}
<!-- Film-Ergebnisse -->
{% if movies %}
<h2 class="tv-section-title">{{ t('search.results_movies') }} ({{ movies|length }})</h2>
<div class="tv-grid">
{% for m in movies %}
<a href="/tv/movies/{{ m.id }}" class="tv-card" data-focusable>
{% if m.poster_url %}
<img src="{{ m.poster_url }}" alt="" class="tv-card-img" loading="lazy">
{% else %}
<div class="tv-card-placeholder">{{ m.title or m.folder_name }}</div>
{% endif %}
<div class="tv-card-info">
<span class="tv-card-title">{{ m.title or m.folder_name }}</span>
<span class="tv-card-meta">{{ m.year or "" }}</span>
</div>
</a>
{% endfor %}
</div>
{% endif %}
{% if not series and not movies %}
<div class="tv-empty">{{ t('search.no_results', query=query) }}</div>
{% endif %}
{% else %}
<!-- Such-History -->
{% if history %}
<div class="tv-search-history">
<div class="tv-search-history-header">
<h2 class="tv-section-title">{{ t('search.history') }}</h2>
<button class="tv-link-btn" onclick="clearHistory()" data-focusable>{{ t('search.clear_history') }}</button>
</div>
<div class="tv-search-history-list">
{% for h in history %}
<a href="/tv/search?q={{ h.query }}" class="tv-search-history-item" data-focusable>
<span class="tv-search-history-icon">&#128269;</span>
{{ h.query }}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
</section>
{% endblock %}
{% block scripts %}
<script>
// Autocomplete
const input = document.getElementById('search-input');
const acBox = document.getElementById('autocomplete');
let acTimer = null;
if (input) {
input.addEventListener('input', () => {
clearTimeout(acTimer);
const q = input.value.trim();
if (q.length < 2) { acBox.style.display = 'none'; return; }
acTimer = setTimeout(() => fetchSuggestions(q), 300);
});
input.addEventListener('blur', () => {
setTimeout(() => { acBox.style.display = 'none'; }, 200);
});
input.addEventListener('focus', () => {
if (acBox.children.length > 0) acBox.style.display = '';
});
}
function fetchSuggestions(q) {
fetch('/tv/api/search/suggest?q=' + encodeURIComponent(q))
.then(r => r.json())
.then(data => {
if (!data.suggestions || data.suggestions.length === 0) {
acBox.style.display = 'none';
return;
}
acBox.innerHTML = data.suggestions.map(s =>
`<a href="/tv/search?q=${encodeURIComponent(s)}" class="tv-ac-item">${s}</a>`
).join('');
acBox.style.display = '';
})
.catch(() => { acBox.style.display = 'none'; });
}
function clearHistory() {
fetch('/tv/api/search/history', { method: 'DELETE' })
.then(() => {
const hist = document.querySelector('.tv-search-history');
if (hist) hist.remove();
})
.catch(() => {});
}
</script>
{% endblock %}