summaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/js/admin.js23
-rw-r--r--web/js/app.js3
-rw-r--r--web/js/dom.js24
-rw-r--r--web/js/lightbox.js4
-rw-r--r--web/js/podcasts.js23
-rw-r--r--web/js/utils.js21
-rw-r--r--web/js/views/media-actions.js2
-rw-r--r--web/js/views/media-grid.js3
-rw-r--r--web/js/views/media-info.js3
-rw-r--r--web/js/views/sets.js2
-rw-r--r--web/js/views/shares.js3
-rw-r--r--web/js/views/tags.js2
-rw-r--r--web/js/views/upload.js2
-rw-r--r--web/sw.js3
14 files changed, 45 insertions, 73 deletions
diff --git a/web/js/admin.js b/web/js/admin.js
index bc2913a..86ad471 100644
--- a/web/js/admin.js
+++ b/web/js/admin.js
@@ -1,4 +1,5 @@
import { API } from './api.js';
+import { escapeHtml, toast } from './utils.js';
export function initAdmin() {
const btn = document.getElementById('admin-toggle');
@@ -54,16 +55,12 @@ async function refreshAdmin() {
} catch (err) { toast(err.message, 'error'); }
}
-function esc(s) {
- return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]));
-}
-
function renderUsers(users) {
const el = document.getElementById('admin-users');
if (!el) return;
el.innerHTML = `<ul class="admin-list">
${users.map((u) => `<li class="${u.is_admin ? 'is-admin' : ''}">
- <span>${esc(u.username)}${u.is_admin ? ' <span class="badge">admin</span>' : ''}</span>
+ <span>${escapeHtml(u.username)}${u.is_admin ? ' <span class="badge">admin</span>' : ''}</span>
<button class="btn btn-danger btn-sm" data-id="${u.id}">Remove</button>
</li>`).join('')}
</ul>`;
@@ -102,12 +99,12 @@ function renderPermissions(data) {
let html = '<table class="admin-table"><thead><tr><th>Set</th>';
data.users.forEach((u) => {
- html += `<th>${esc(u.username)}</th>`;
+ html += `<th>${escapeHtml(u.username)}</th>`;
});
html += '</tr></thead><tbody>';
data.sets.forEach((s) => {
- html += `<tr><td>${esc(s.name)}</td>`;
+ html += `<tr><td>${escapeHtml(s.name)}</td>`;
data.users.forEach((u) => {
const role = roleMap[s.id]?.[u.id] || '';
const selectId = `perm-${s.id}-${u.id}`;
@@ -152,8 +149,8 @@ function renderTrash(data) {
}
el.innerHTML = `<h4 class="text-90">Trash</h4><ul class="admin-list">
${data.map((m) => `<li>
- <span>${esc(m.file_name)}</span>
- <span class="text-muted text-75">${esc(m.deleted_at || '')}</span>
+ <span>${escapeHtml(m.file_name)}</span>
+ <span class="text-muted text-75">${escapeHtml(m.deleted_at || '')}</span>
<button class="btn btn-primary btn-sm" data-id="${m.id}">Restore</button>
</li>`).join('')}
</ul>`;
@@ -169,11 +166,3 @@ function renderTrash(data) {
});
});
}
-
-function toast(msg, type = 'info') {
- const t = document.getElementById('toast');
- if (!t) return;
- t.textContent = msg;
- t.className = 'toast show ' + type;
- setTimeout(() => t.classList.remove('show'), 2800);
-}
diff --git a/web/js/app.js b/web/js/app.js
index 81799c6..ee515af 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -39,7 +39,8 @@ import {
zoomOut as lightboxZoomOut,
toggleSlideshow as lightboxToggleSlideshow,
} from './lightbox.js';
-import { closeAllModals, toast } from './dom.js';
+import { closeAllModals } from './dom.js';
+import { toast } from './utils.js';
import { showAdmin } from './views/admin-status.js';
import { initHelp, showSearch, toggleHelp, toggleSidebar } from './views/help.js';
import {
diff --git a/web/js/dom.js b/web/js/dom.js
index 3d78bb4..1ea5840 100644
--- a/web/js/dom.js
+++ b/web/js/dom.js
@@ -1,3 +1,5 @@
+export { escapeHtml, fmtDur, toast } from './utils.js';
+
export function fmtDate(d) {
if (!d) return '';
const dt = typeof d === 'string' ? new Date(d) : d;
@@ -11,16 +13,6 @@ export function fmtDateTime(d) {
return dt.toLocaleString();
}
-export function fmtDur(s) {
- if (!s || s < 0) return '0:00';
- const h = Math.floor(s / 3600);
- const m = Math.floor((s % 3600) / 60);
- const sec = Math.floor(s % 60);
- const mm = String(m).padStart(2, '0');
- const ss = String(sec).padStart(2, '0');
- return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
-}
-
export function fmtSize(bytes) {
if (!bytes || bytes <= 0) return '';
const kb = bytes / 1024;
@@ -30,18 +22,6 @@ export function fmtSize(bytes) {
return Math.round((mb / 1024) * 10) / 10 + ' GB';
}
-export function escapeHtml(s) {
- return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
-}
-
-export function toast(msg, type = 'info') {
- const t = document.getElementById('toast');
- if (!t) return;
- t.textContent = msg;
- t.className = 'toast show ' + type;
- setTimeout(() => t.classList.remove('show'), 2800);
-}
-
export function closeAllModals() {
document.querySelectorAll('.modal-overlay.open').forEach((m) => m.classList.remove('open'));
}
diff --git a/web/js/lightbox.js b/web/js/lightbox.js
index 876f456..b8cda54 100644
--- a/web/js/lightbox.js
+++ b/web/js/lightbox.js
@@ -175,7 +175,3 @@ function fmtSize(bytes) {
if (mb < 1024) return Math.round(mb * 10) / 10 + ' MB';
return Math.round((mb / 1024) * 10) / 10 + ' GB';
}
-
-function escapeHtml(s) {
- return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
-}
diff --git a/web/js/podcasts.js b/web/js/podcasts.js
index 690b24e..1883e63 100644
--- a/web/js/podcasts.js
+++ b/web/js/podcasts.js
@@ -1,5 +1,6 @@
// podcastUI.js — Podcast feed manager + episode rendering.
import { API } from './api.js';
+import { escapeHtml, fmtDur, toast } from './utils.js';
export function initPodcasts() {
const modal = document.getElementById('podcast-modal');
@@ -88,7 +89,7 @@ export function renderPodcastEpisodes(grid, episodes) {
function renderEpisodeHtml(ep) {
const completed = ep.is_completed;
const dateStr = ep.published_at ? new Date(ep.published_at).toLocaleDateString() : '';
- const duration = ep.duration_seconds ? fmtDuration(ep.duration_seconds) : '';
+ const duration = ep.duration_seconds ? fmtDur(ep.duration_seconds) : '';
return `
<div class="thumb-wrap">
<span class="placeholder">🎙️</span>
@@ -104,23 +105,3 @@ function renderEpisodeHtml(ep) {
</div>
`;
}
-
-function fmtDuration(s) {
- const m = Math.floor(s / 60);
- const h = Math.floor(m / 60);
- if (h > 0) return `${h}h ${m % 60}m`;
- return `${m}m`;
-}
-
-function escapeHtml(str) {
- if (!str) return '';
- return str.replace(/[&<>"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
-}
-
-function toast(msg, type = 'info') {
- const t = document.getElementById('toast');
- if (!t) return;
- t.textContent = msg;
- t.className = 'toast show ' + type;
- setTimeout(() => t.classList.remove('show'), 2800);
-}
diff --git a/web/js/utils.js b/web/js/utils.js
new file mode 100644
index 0000000..2851211
--- /dev/null
+++ b/web/js/utils.js
@@ -0,0 +1,21 @@
+export function fmtDur(s) {
+ if (!s || s < 0) return '0:00';
+ const h = Math.floor(s / 3600);
+ const m = Math.floor((s % 3600) / 60);
+ const sec = Math.floor(s % 60);
+ const mm = String(m).padStart(2, '0');
+ const ss = String(sec).padStart(2, '0');
+ return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`;
+}
+
+export function escapeHtml(s) {
+ return (s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
+}
+
+export function toast(msg, type = 'info') {
+ const t = document.getElementById('toast');
+ if (!t) return;
+ t.textContent = msg;
+ t.className = 'toast show ' + type;
+ setTimeout(() => t.classList.remove('show'), 2800);
+}
diff --git a/web/js/views/media-actions.js b/web/js/views/media-actions.js
index 7dab090..2367241 100644
--- a/web/js/views/media-actions.js
+++ b/web/js/views/media-actions.js
@@ -2,7 +2,7 @@ import { API } from '../api.js';
import { currentElement } from '../selection.js';
import { currentMediaId } from '../player.js';
import { open as openNotes } from '../notes.js';
-import { toast } from '../dom.js';
+import { toast } from '../utils.js';
export async function shareSelected() {
const el = currentElement();
diff --git a/web/js/views/media-grid.js b/web/js/views/media-grid.js
index f773f37..5c16de0 100644
--- a/web/js/views/media-grid.js
+++ b/web/js/views/media-grid.js
@@ -1,7 +1,8 @@
import { API } from '../api.js';
import { state, setMedia } from '../state.js';
import { clearSelection, selectByElement } from '../selection.js';
-import { escapeHtml, fmtDur, fmtSize, toast } from '../dom.js';
+import { fmtSize } from '../dom.js';
+import { escapeHtml, fmtDur, toast } from '../utils.js';
let callbacks = {};
diff --git a/web/js/views/media-info.js b/web/js/views/media-info.js
index 06d88ad..51e576c 100644
--- a/web/js/views/media-info.js
+++ b/web/js/views/media-info.js
@@ -1,5 +1,6 @@
import { API } from '../api.js';
-import { escapeHtml, fmtDateTime, fmtDur, fmtSize, toast } from '../dom.js';
+import { fmtDateTime, fmtSize } from '../dom.js';
+import { escapeHtml, fmtDur, toast } from '../utils.js';
import { selectedMediaId } from './media-actions.js';
export function initMediaInfo() {
diff --git a/web/js/views/sets.js b/web/js/views/sets.js
index 9bb6db3..6badcb3 100644
--- a/web/js/views/sets.js
+++ b/web/js/views/sets.js
@@ -1,6 +1,6 @@
import { API } from '../api.js';
import { state } from '../state.js';
-import { escapeHtml, toast } from '../dom.js';
+import { escapeHtml, toast } from '../utils.js';
let loadMediaCallback = () => {};
diff --git a/web/js/views/shares.js b/web/js/views/shares.js
index 12f717c..3cbf6b5 100644
--- a/web/js/views/shares.js
+++ b/web/js/views/shares.js
@@ -1,6 +1,7 @@
import { API } from '../api.js';
import { state } from '../state.js';
-import { escapeHtml, fmtDate, toast } from '../dom.js';
+import { fmtDate } from '../dom.js';
+import { escapeHtml, toast } from '../utils.js';
export function initShares() {
const modal = document.getElementById('shares-modal');
diff --git a/web/js/views/tags.js b/web/js/views/tags.js
index 21186bf..7d3ceba 100644
--- a/web/js/views/tags.js
+++ b/web/js/views/tags.js
@@ -1,6 +1,6 @@
import { API } from '../api.js';
import { currentElement } from '../selection.js';
-import { escapeHtml, toast } from '../dom.js';
+import { escapeHtml, toast } from '../utils.js';
let tagsCurrentMediaId = null;
diff --git a/web/js/views/upload.js b/web/js/views/upload.js
index 19ceb87..de88ba5 100644
--- a/web/js/views/upload.js
+++ b/web/js/views/upload.js
@@ -1,6 +1,6 @@
import { API } from '../api.js';
import { state } from '../state.js';
-import { toast } from '../dom.js';
+import { toast } from '../utils.js';
let loadMediaCallback = () => {};
diff --git a/web/sw.js b/web/sw.js
index f9ddecd..12e8900 100644
--- a/web/sw.js
+++ b/web/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'kiss-v7';
+const CACHE = 'kiss-v8';
const ASSETS = [
'/',
'/index.html',
@@ -22,6 +22,7 @@ const ASSETS = [
'/js/notes.js',
'/js/admin.js',
'/js/dom.js',
+ '/js/utils.js',
'/js/podcasts.js',
'/js/pwa.js',
'/js/lightbox.js',