configs/server-dockers/jellyfin-news/index.html
server-dockers/jellyfin-news/index.htmlRaw
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <title>Jellyfin updates</title>
7 <style>
8 body { max-width: 760px; margin: 2rem auto; padding: 0 1rem; font: 16px/1.5 system-ui, sans-serif; color: #202124; }
9 h1 { margin-bottom: .25rem; }
10 .controls { display: flex; flex-wrap: wrap; gap: .65rem; margin: 1.5rem 0; align-items: center; }
11 button, input { font: inherit; padding: .6rem .8rem; }
12 button { cursor: pointer; }
13 input { width: 5rem; }
14 #status { min-height: 1.5rem; color: #555; }
15 textarea { box-sizing: border-box; width: 100%; min-height: 22rem; padding: 1rem; font: 15px/1.5 ui-monospace, monospace; resize: vertical; }
16 .copy { margin-top: .65rem; }
17 </style>
18</head>
19<body>
20 <h1>Jellyfin updates</h1>
21 <p>Choose a period, then copy the generated recap.</p>
22
23 <div class="controls">
24 <button data-report="/api/current-week">Current week</button>
25 </div>
26
27 <div class="controls">
28 <label for="week-offset">Week offset:</label>
29 <input id="week-offset" type="number" min="-1200" max="-1" value="-1" required>
30 <button id="selected-week">Load selected week</button>
31 <span>−1 is last week, −2 is two weeks ago</span>
32 </div>
33
34 <div class="controls">
35 <label for="month-offset">Month offset:</label>
36 <input id="month-offset" type="number" min="-1200" max="-1" value="-1" required>
37 <button id="selected-month">Load selected month</button>
38 <span>−1 is last month, −2 is two months ago</span>
39 </div>
40
41 <p id="status" role="status"></p>
42 <textarea id="output" readonly aria-label="Jellyfin update output" placeholder="Your recap will appear here"></textarea>
43 <button id="copy" class="copy">Copy to clipboard</button>
44
45 <script>
46 const output = document.querySelector('#output');
47 const status = document.querySelector('#status');
48 const buttons = [...document.querySelectorAll('button')];
49
50 async function loadReport(url) {
51 buttons.forEach(button => button.disabled = true);
52 status.textContent = 'Loading…';
53 try {
54 const response = await fetch(url, { headers: { Accept: 'text/plain' } });
55 const text = await response.text();
56 if (!response.ok) throw new Error(text.trim() || `Request failed (${response.status})`);
57 output.value = text;
58 status.textContent = 'Recap ready.';
59 } catch (error) {
60 status.textContent = error.message;
61 } finally {
62 buttons.forEach(button => button.disabled = false);
63 }
64 }
65
66 document.querySelectorAll('[data-report]').forEach(button => {
67 button.addEventListener('click', () => loadReport(button.dataset.report));
68 });
69
70 document.querySelector('#selected-week').addEventListener('click', () => {
71 const offset = document.querySelector('#week-offset').value;
72 loadReport(`/api/week?offset=${encodeURIComponent(offset)}`);
73 });
74
75 document.querySelector('#selected-month').addEventListener('click', () => {
76 const offset = document.querySelector('#month-offset').value;
77 loadReport(`/api/month?offset=${encodeURIComponent(offset)}`);
78 });
79
80 document.querySelector('#copy').addEventListener('click', async () => {
81 if (!output.value) return;
82 try {
83 await navigator.clipboard.writeText(output.value);
84 } catch (_) {
85 output.select();
86 document.execCommand('copy');
87 output.setSelectionRange(0, 0);
88 }
89 status.textContent = 'Copied to clipboard.';
90 });
91 </script>
92</body>
93</html>