diff --git a/src/main/resources/static/js/copyright-notices.js b/src/main/resources/static/js/copyright-notices.js new file mode 100644 index 0000000..ac189ba --- /dev/null +++ b/src/main/resources/static/js/copyright-notices.js @@ -0,0 +1,137 @@ +/** + * Copyright notices panel for the broadcaster dashboard. + * + * On load: fetches any pending (NOTIFIED) copyright notices and renders them. + * Dismiss button: acknowledges the notice via API (→ RESOLVED) and removes it. + * WebSocket: subscribes to the channel topic and refreshes on COPYRIGHT_WARNING. + */ +(function () { + const panel = document.getElementById("copyright-notices-panel"); + const list = document.getElementById("copyright-notices-list"); + + if (!panel || !list || typeof broadcaster === "undefined") return; + + // ── Helpers ─────────────────────────────────────────────────────────────── + function csrfHeaders() { + const token = document.querySelector("meta[name='_csrf']")?.content ?? ""; + const header = document.querySelector("meta[name='_csrf_header']")?.content ?? "X-XSRF-TOKEN"; + return { [header]: token }; + } + + function escHtml(str) { + if (!str) return ""; + return String(str) + .replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); + } + + function formatDate(iso) { + if (!iso) return ""; + try { return new Date(iso).toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric" }); } + catch { return iso; } + } + + // ── Render ──────────────────────────────────────────────────────────────── + function renderNotices(notices) { + if (!notices || notices.length === 0) { + panel.classList.add("hidden"); + return; + } + panel.classList.remove("hidden"); + list.innerHTML = ""; + for (const notice of notices) { + list.appendChild(buildNoticeItem(notice)); + } + } + + function buildNoticeItem(notice) { + const li = document.createElement("li"); + li.className = "copyright-notice-item"; + li.dataset.reportId = notice.id; + li.innerHTML = ` + + + `; + li.querySelector("[data-dismiss]").addEventListener("click", () => dismissNotice(notice.id, li)); + return li; + } + + // ── API calls ───────────────────────────────────────────────────────────── + async function loadNotices() { + try { + const resp = await fetch(`/api/channels/${encodeURIComponent(broadcaster)}/copyright-notices`); + if (!resp.ok) return; + renderNotices(await resp.json()); + } catch (_) { /* non-critical — silently skip */ } + } + + async function dismissNotice(reportId, li) { + const btn = li.querySelector("[data-dismiss]"); + if (btn) btn.disabled = true; + try { + const resp = await fetch( + `/api/channels/${encodeURIComponent(broadcaster)}/copyright-notices/${encodeURIComponent(reportId)}/dismiss`, + { method: "POST", headers: csrfHeaders() } + ); + if (!resp.ok) { + if (btn) btn.disabled = false; + return; + } + li.classList.add("copyright-notice-dismissed"); + li.addEventListener("transitionend", () => { + li.remove(); + if (list.children.length === 0) panel.classList.add("hidden"); + }, { once: true }); + // Fallback in case transition doesn't fire + setTimeout(() => { + if (li.parentNode) { + li.remove(); + if (list.children.length === 0) panel.classList.add("hidden"); + } + }, 400); + } catch (_) { + if (btn) btn.disabled = false; + } + } + + // ── WebSocket — refresh when a new COPYRIGHT_WARNING arrives ────────────── + function connectWebSocket() { + if (typeof SockJS === "undefined" || typeof Stomp === "undefined") return; + try { + const socket = new SockJS("/ws"); + const stomp = Stomp.over(socket); + stomp.debug = () => {}; + stomp.connect({}, () => { + stomp.subscribe(`/topic/channel/${broadcaster}`, (frame) => { + try { + const msg = JSON.parse(frame.body); + if (msg.type === "COPYRIGHT_WARNING") { + loadNotices(); + } + } catch (_) {} + }); + }); + } catch (_) {} + } + + // ── Init ────────────────────────────────────────────────────────────────── + loadNotices(); + connectWebSocket(); +})(); diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index 5545345..37dc55e 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -14,6 +14,8 @@ crossorigin="anonymous" referrerpolicy="no-referrer" /> + +
@@ -37,6 +39,21 @@ + + +
@@ -192,5 +209,6 @@ const broadcaster = /*[[${channel}]]*/ ""; +