Interface web complète : formulaire d'ajout (URL directe ou recherche de chaîne) et panneau de file d'attente avec sondage de la progression. - views/downloadForm/ : sélecteur de mode (vidéo/audio/miniature) partagé, ajout direct par URL, recherche de chaîne (GET /api/channel) avec ajout par résultat. Construction DOM via createElement/textContent (jamais d'innerHTML avec du texte venant de YouTube), pas juste par prudence : ça évite aussi tout risque d'injection depuis un titre de vidéo. - views/queuePanel/ : sonde GET /api/queue toutes les 1,5s, affiche statut/ progression/erreur par élément, annulation et lien de téléchargement natif (<a download>, pas de fetch+blob). - api/ : clients fetch typés (channelApi, queueApi), chaque await dans son propre try/catch avec message d'erreur adapté (réseau vs HTTP vs JSON). - QueueItem gagne un champ title optionnel (purement informatif, jamais utilisé pour construire la commande yt-dlp) pour un affichage plus parlant qu'une URL brute. - client/tsconfig.json + build.mjs : cible ES2022 (nécessaire pour Error.cause, jusque-là ES2020). - Vitest ajouté côté client (fonctions pures de src/format.ts ; le câblage DOM est vérifié manuellement, pas de jsdom pour l'instant). Deux bugs réels trouvés et corrigés en testant dans un vrai navigateur (pas seulement en lisant le code) : - CSS : un attribut `hidden` sur un conteneur `display: flex` se faisait écraser par cette règle (même spécificité, la feuille de l'auteur gagne) - les deux panneaux d'options restaient visibles en même temps. - server/src/routes/channel.ts : ne mappait vers 502 que les YtDlpError (code de sortie non nul), pas les erreurs brutes de spawn (ex. binaire introuvable - ENOENT), qui remontaient donc en 500 générique. Corrigé + test de non-régression. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
59 lines
2 KiB
HTML
59 lines
2 KiB
HTML
<div class="download-form">
|
|
<h2>Ajouter des vidéos</h2>
|
|
|
|
<fieldset class="download-form__mode">
|
|
<legend>Mode</legend>
|
|
<label><input type="radio" name="mode" value="video" checked /> Vidéo</label>
|
|
<label><input type="radio" name="mode" value="audio" /> Audio</label>
|
|
<label><input type="radio" name="mode" value="thumbnail" /> Miniature seule</label>
|
|
</fieldset>
|
|
|
|
<div class="download-form__options" data-mode-options="video">
|
|
<label><input type="checkbox" data-field="subtitles" /> Sous-titres (fr/en)</label>
|
|
<label><input type="checkbox" data-field="embedThumbnail" /> Miniature embarquée</label>
|
|
</div>
|
|
<div class="download-form__options" data-mode-options="audio" hidden>
|
|
<label>
|
|
Format
|
|
<select data-field="audioFormat">
|
|
<option value="mp3">MP3</option>
|
|
<option value="m4a">M4A</option>
|
|
<option value="opus">Opus</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<form class="download-form__form" data-form="url">
|
|
<label class="download-form__field">
|
|
URL directe
|
|
<input type="url" name="url" placeholder="https://www.youtube.com/watch?v=..." required />
|
|
</label>
|
|
<button type="submit">Ajouter à la file</button>
|
|
</form>
|
|
<p class="download-form__error" data-role="url-error" hidden></p>
|
|
|
|
<hr />
|
|
|
|
<form class="download-form__form" data-form="channel">
|
|
<label class="download-form__field">
|
|
Chaîne (@handle ou URL)
|
|
<input type="text" name="channel" placeholder="@worldclimbing" required />
|
|
</label>
|
|
<label class="download-form__field">
|
|
Section
|
|
<select name="section">
|
|
<option value="videos">Vidéos</option>
|
|
<option value="streams">Streams</option>
|
|
</select>
|
|
</label>
|
|
<label class="download-form__field">
|
|
Mots-clés (optionnel, séparés par des virgules)
|
|
<input type="text" name="keywords" placeholder="speed, 2026" />
|
|
</label>
|
|
<button type="submit">Rechercher</button>
|
|
</form>
|
|
<p class="download-form__error" data-role="channel-error" hidden></p>
|
|
<p class="download-form__status" data-role="channel-status" hidden></p>
|
|
|
|
<ul class="download-form__results" data-role="results"></ul>
|
|
</div>
|