-
${escapeHtml(m.sender)}${m.fetched ? '' : ' new'}
- ${m.title ? `
${escapeHtml(m.title)}
` : ''}
-
${escapeHtml(m.body)}
+
${escapeHtml(m.sender)}${m.fetched ? ' synced' : ' new'}
+
${m.title ? escapeHtml(m.title) : 'No preview'}
${escapeHtml(formatRelTime(m.created_at))}
@@ -364,19 +408,56 @@ async function loadInbox() {
}
async function deleteMessage(id, btn) {
- if (!confirm('Delete this message?')) return;
- const res = await api('api/messages/' + id, { method: 'DELETE' });
- if (res?.ok) {
- btn.closest('.inbox-item')?.remove();
- loadInbox();
- refreshMe();
- toast('message deleted', 'ok');
- } else {
- toast(res?.error || 'delete failed', 'err');
- }
+ // Inline confirmation: replace the trash button with yes/no.
+ // Event listeners are wired via JS (not inline onclick) to avoid
+ // passing escaped HTML through attributes.
+ const row = btn.closest('.inbox-item');
+ if (!row) return;
+ const origHtml = btn.outerHTML;
+ const wrap = document.createElement('span');
+ wrap.className = 'del-confirm';
+ wrap.style.cssText = 'display:inline-flex;align-items:center;gap:4px;font-family:var(--pixel);font-size:9px;';
+ wrap.innerHTML = `
+
delete?
+
+
+ `;
+ btn.replaceWith(wrap);
+ wrap.querySelector('.del-yes').addEventListener('click', async () => {
+ const res = await api('api/messages/' + id, { method: 'DELETE' });
+ if (res?.ok) {
+ if (row) { row.style.transition = 'opacity .2s'; row.style.opacity = '0'; setTimeout(() => row.remove(), 220); }
+ loadInbox();
+ refreshMe();
+ toast('message deleted', 'ok');
+ } else {
+ toast(res?.error || 'delete failed', 'err');
+ }
+ });
+ wrap.querySelector('.del-no').addEventListener('click', () => {
+ const temp = document.createElement('div');
+ temp.innerHTML = origHtml;
+ wrap.replaceWith(temp.firstChild);
+ });
}
window.deleteMessage = deleteMessage;
+async function deleteAllMessages() {
+ const res = await api('api/messages/inbox');
+ const messages = res?.messages || [];
+ if (!messages.length) return;
+ if (!await relayConfirm(`Delete all ${messages.length} message${messages.length !== 1 ? 's' : ''}? This cannot be undone.`, { okLabel: 'Delete all' })) return;
+ let ok = 0;
+ for (const m of messages) {
+ const r = await api('api/messages/' + m.id, { method: 'DELETE' });
+ if (r?.ok) ok++;
+ }
+ toast(`${ok} message${ok !== 1 ? 's' : ''} deleted`, 'ok');
+ loadInbox();
+ refreshMe();
+}
+window.deleteAllMessages = deleteAllMessages;
+
/** Lightweight relative-time formatter for the inbox. SQLite gives us
* `YYYY-MM-DD HH:MM:SS` (UTC). Returns "2 min ago", "today 14:32",
* or a full date for older messages. */
@@ -466,7 +547,7 @@ async function resetUserPassword(id, username) {
}
async function deleteUser(id, username) {
- if (!confirm(`Delete user "${username}"? All their messages and attachments will be removed.`)) return;
+ if (!await relayConfirm(`Delete user "${username}"? All their messages and attachments will be removed.`, { okLabel: 'Delete user' })) return;
const res = await api('api/users/' + id, { method: 'DELETE' });
if (res.ok) { toast('user deleted'); loadUsers(); }
else toast(res.error || 'failed', true);
@@ -533,7 +614,7 @@ async function saveAccountSecret() {
async function generateAccountSecret() {
if (currentAccountSecret) {
- if (!confirm('Replace your current secret? All your hyprcrd clients will need the new value to connect.')) return;
+ if (!await relayConfirm('Replace your current secret? All your hyprcrd clients will need the new value to connect.', { okLabel: 'Replace', danger: false })) return;
}
setAccountStatus('generating...');
const res = await api('api/account/secret', { method: 'POST', body: { generate: true } });
@@ -550,7 +631,7 @@ async function generateAccountSecret() {
}
async function clearAccountSecret() {
- if (!confirm('Clear your client secret? All your hyprcrd clients will be disconnected and will need a new secret to reconnect.')) return;
+ if (!await relayConfirm('Clear your client secret? All your hyprcrd clients will be disconnected and will need a new secret to reconnect.', { okLabel: 'Clear secret' })) return;
const res = await api('api/account/secret', { method: 'DELETE' });
if (res.ok) {
setAccountStatus('cleared', 'ok');
diff --git a/relay/ui/layout.php b/relay/ui/layout.php
index ddaa2d8..dc69585 100644
--- a/relay/ui/layout.php
+++ b/relay/ui/layout.php
@@ -74,7 +74,14 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
compose
@@ -92,7 +99,10 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }