Video server and timestamp #45

Merged
quoije merged 8 commits from cursor/video-server-and-timestamp-fc01 into 2026-video.backend 2025-12-22 01:50:21 -05:00
2 changed files with 62 additions and 5 deletions
Showing only changes of commit 2025f0231c - Show all commits
+10 -1
View File
@@ -418,6 +418,7 @@
<thead> <thead>
<tr> <tr>
<th style="width: 1%;">Sel.</th> <th style="width: 1%;">Sel.</th>
<th style="width: 1%;">ID</th>
<th>Titre</th> <th>Titre</th>
<th style="width: 12%;">Player</th> <th style="width: 12%;">Player</th>
<th style="width: 20%;">Catégorie</th> <th style="width: 20%;">Catégorie</th>
@@ -426,7 +427,7 @@
</tr> </tr>
</thead> </thead>
<tbody id="admin-movies-body"> <tbody id="admin-movies-body">
<tr><td colspan="6" class="text-muted">Charge la liste…</td></tr> <tr><td colspan="7" class="text-muted">Charge la liste…</td></tr>
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -679,6 +680,14 @@
<div class="modal-body"> <div class="modal-body">
<input type="hidden" id="edit_movie_id"> <input type="hidden" id="edit_movie_id">
<div class="mb-3">
<div class="text-muted small mb-1">Movie ID</div>
<div class="d-flex gap-2 align-items-center">
<code class="flex-grow-1" id="edit_movie_id_label">—</code>
<button type="button" class="btn btn-outline-secondary btn-sm" id="copy-movie-id">Copier</button>
</div>
</div>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label for="edit_imdb_id" class="form-label">IMDB ID (ex: tt1234567)</label> <label for="edit_imdb_id" class="form-label">IMDB ID (ex: tt1234567)</label>
+52 -4
View File
@@ -98,6 +98,8 @@ window.onload = async function () {
const saveMovieChangesBtn = document.getElementById('save-movie-changes'); const saveMovieChangesBtn = document.getElementById('save-movie-changes');
const editMovieIdEl = document.getElementById('edit_movie_id'); const editMovieIdEl = document.getElementById('edit_movie_id');
const editMovieIdLabelEl = document.getElementById('edit_movie_id_label');
const copyMovieIdBtn = document.getElementById('copy-movie-id');
const editImdbIdEl = document.getElementById('edit_imdb_id'); const editImdbIdEl = document.getElementById('edit_imdb_id');
const editYearEl = document.getElementById('edit_year'); const editYearEl = document.getElementById('edit_year');
const editCategoryEl = document.getElementById('edit_category'); const editCategoryEl = document.getElementById('edit_category');
@@ -145,6 +147,17 @@ window.onload = async function () {
.replace(/'/g, '&#039;'); .replace(/'/g, '&#039;');
} }
async function copyToClipboard(text) {
const t = String(text || '').trim();
if (!t) return false;
try {
await navigator.clipboard.writeText(t);
return true;
} catch (_) {
return false;
}
}
function sanitizeHtml(raw) { function sanitizeHtml(raw) {
const html = String(raw || ''); const html = String(raw || '');
const withoutScripts = html.replace(/<script\b[\s\S]*?<\/script>/gi, ''); const withoutScripts = html.replace(/<script\b[\s\S]*?<\/script>/gi, '');
@@ -2021,6 +2034,7 @@ window.onload = async function () {
if (!movie) return; if (!movie) return;
editMovieIdEl.value = movie._id || ''; editMovieIdEl.value = movie._id || '';
if (editMovieIdLabelEl) editMovieIdLabelEl.textContent = movie._id || '—';
editImdbIdEl.value = movie.imdb_id || ''; editImdbIdEl.value = movie.imdb_id || '';
editYearEl.value = movie.year ? String(movie.year) : ''; editYearEl.value = movie.year ? String(movie.year) : '';
editCategoryEl.value = movie.category || ''; editCategoryEl.value = movie.category || '';
@@ -2147,12 +2161,12 @@ window.onload = async function () {
async function loadMoviesForManagement() { async function loadMoviesForManagement() {
const year = manageYearSelect.value; const year = manageYearSelect.value;
const url = year ? `/api/movies?year=${encodeURIComponent(year)}` : '/api/movies'; const url = year ? `/api/movies?year=${encodeURIComponent(year)}` : '/api/movies';
adminMoviesBody.innerHTML = `<tr><td colspan="6" class="text-muted">Chargement…</td></tr>`; adminMoviesBody.innerHTML = `<tr><td colspan="7" class="text-muted">Chargement…</td></tr>`;
try { try {
const res = await fetch(url, { method: 'GET' }); const res = await fetch(url, { method: 'GET' });
if (!res.ok) { if (!res.ok) {
adminMoviesBody.innerHTML = `<tr><td colspan="6" class="text-danger">Erreur lors du chargement (${res.status})</td></tr>`; adminMoviesBody.innerHTML = `<tr><td colspan="7" class="text-danger">Erreur lors du chargement (${res.status})</td></tr>`;
return; return;
} }
const movies = await res.json(); const movies = await res.json();
@@ -2160,7 +2174,7 @@ window.onload = async function () {
moviesById = new Map(movies.map((m) => [m._id, m])); moviesById = new Map(movies.map((m) => [m._id, m]));
if (!movies.length) { if (!movies.length) {
adminMoviesBody.innerHTML = `<tr><td colspan="6" class="text-muted">Aucun film.</td></tr>`; adminMoviesBody.innerHTML = `<tr><td colspan="7" class="text-muted">Aucun film.</td></tr>`;
updateSelectionUI(); updateSelectionUI();
return; return;
} }
@@ -2195,11 +2209,19 @@ window.onload = async function () {
.map((b) => `<span class="badge ${b.cls} me-1 mb-1">${b.text}</span>`) .map((b) => `<span class="badge ${b.cls} me-1 mb-1">${b.text}</span>`)
.join(''); .join('');
const movieId = String(m._id || '');
const shortId = movieId ? `${movieId.slice(0, 6)}…${movieId.slice(-4)}` : '—';
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.innerHTML = ` tr.innerHTML = `
<td> <td>
<input type="checkbox" class="form-check-input" data-movie-id="${m._id}"> <input type="checkbox" class="form-check-input" data-movie-id="${m._id}">
</td> </td>
<td class="text-nowrap">
<button type="button" class="btn btn-sm btn-outline-secondary font-monospace" data-copy-movie-id="${escapeHtml(movieId)}" title="${escapeHtml(movieId)}">
${escapeHtml(shortId)}
</button>
</td>
<td> <td>
<div class="fw-semibold">${m.title || '(sans titre)'}</div> <div class="fw-semibold">${m.title || '(sans titre)'}</div>
<div class="text-muted small">${m.imdb_id || ''}</div> <div class="text-muted small">${m.imdb_id || ''}</div>
@@ -2224,9 +2246,22 @@ window.onload = async function () {
btn.addEventListener('click', () => openEditModal(btn.getAttribute('data-edit-movie-id'))); btn.addEventListener('click', () => openEditModal(btn.getAttribute('data-edit-movie-id')));
}); });
adminMoviesBody.querySelectorAll('button[data-copy-movie-id]').forEach((btn) => {
btn.addEventListener('click', async () => {
const id = btn.getAttribute('data-copy-movie-id');
if (!id) return;
const ok = await copyToClipboard(id);
if (ok) {
showResponse('success', 'Movie ID copié.');
} else {
showResponse('warning', 'Impossible de copier automatiquement. Copie manuellement.');
}
});
});
updateSelectionUI(); updateSelectionUI();
} catch (err) { } catch (err) {
adminMoviesBody.innerHTML = `<tr><td colspan="6" class="text-danger">${err.message || 'Erreur réseau'}</td></tr>`; adminMoviesBody.innerHTML = `<tr><td colspan="7" class="text-danger">${err.message || 'Erreur réseau'}</td></tr>`;
} }
} }
@@ -2281,6 +2316,19 @@ window.onload = async function () {
saveMovieChangesBtn.addEventListener('click', saveMovieChanges); saveMovieChangesBtn.addEventListener('click', saveMovieChanges);
} }
if (copyMovieIdBtn) {
copyMovieIdBtn.addEventListener('click', async () => {
const id = String(editMovieIdEl?.value || '').trim();
if (!id) return;
const ok = await copyToClipboard(id);
if (ok) {
showResponse('success', 'Movie ID copié.');
} else {
showResponse('warning', 'Impossible de copier automatiquement. Copie manuellement.');
}
});
}
// Initial load // Initial load
activeYear = await fetchActiveYear(); activeYear = await fetchActiveYear();
if (activeYear) { if (activeYear) {