Files
Roman 41566ab5fb v0.1.0 — Réécriture complète de OhmStreaming
Nouvelle version réécrite de zéro : recherche multi-sources (Vostfree,
French-Manga), extraction 2 niveaux, proxy vidéo intégré, streaming/téléchargement
HLS, métadonnées Kitsu, bibliothèque locale, comptes JWT + administration,
découverte fusionnée, indexeur Torznab (Sonarr/Prowlarr).
2026-09-22 10:05:47 +00:00

67 lines
2.5 KiB
HTML

{% extends "base.html" %}
{% set active = 'search' %}
{% block title %}Recherche — Ohm Stream{% endblock %}
{% block content %}
<h1 class="page-title">Recherche unifiée</h1>
<p class="page-sub">Une seule requête interroge toutes tes sources activées.</p>
<div x-data="searchPage()">
<form class="search-bar" @submit.prevent="run">
<input type="search" x-model="query" placeholder="Naruto, One Piece, Frieren…" autofocus>
<button class="btn" type="submit" :disabled="loading">Rechercher</button>
</form>
<div class="spinner" x-show="loading"></div>
<template x-if="error">
<div class="login-error" x-text="error"></div>
</template>
<template x-if="!loading && searched && results.length === 0">
<div class="empty-state"><div class="big">🏜️</div>Aucun résultat pour cette recherche.</div>
</template>
<div class="grid" x-show="results.length > 0">
<template x-for="r in results" :key="r.source + r.source_id">
<a class="card" :href="`/title/${r.source}/${encodeURIComponent(r.source_id)}`">
<img class="card-poster" :src="r.image_url || '/static/img/placeholder.svg'" :alt="r.title" loading="lazy"
onerror="this.src='/static/img/placeholder.svg'">
<span class="card-chip" x-text="r.source"></span>
<div class="card-overlay">
<div class="card-title" x-text="r.title"></div>
<div class="card-meta">
<span class="badge badge-type" x-text="r.media_type"></span>
</div>
</div>
</a>
</template>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function searchPage() {
return {
query: new URLSearchParams(location.search).get('q') || '',
results: [], loading: false, searched: false, error: null,
init() { if (this.query) this.run(); },
async run() {
if (this.query.trim().length < 2) return;
this.loading = true; this.error = null; this.searched = false;
history.replaceState(null, '', '?q=' + encodeURIComponent(this.query));
try {
const res = await fetch('/api/search?q=' + encodeURIComponent(this.query.trim()));
if (!res.ok) throw new Error('Erreur ' + res.status);
const data = await res.json();
this.results = data.results;
if (data.failed_sources.length) this.error = 'Sources en échec : ' + data.failed_sources.join(', ');
} catch (e) { this.error = e.message; }
finally { this.loading = false; this.searched = true; }
}
};
}
</script>
{% endblock %}