@@ -1645,7 +1860,7 @@ if (str_starts_with($route, '/api/')) {
diff --git a/public/js/app.js b/public/js/app.js
index bb6b21f..f3c441d 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -126,10 +126,10 @@ importFile?.addEventListener('change', async e => {
// Check if it's a selective export (merge) or ask for full replace
const form = new FormData(); form.append('file', file);
- const mode = await showDialog({ title: 'import_data', message: 'How do you want to import?', okLabel: 'merge (add to existing)', cancelLabel: 'replace (wipe all)' });
+ const mode = await showDialog({ title: 'import_data', message: 'How do you want to import?', okLabel: 'Merge (add to existing)', cancelLabel: 'Replace (wipe all)' });
// mode: true = merge, null = replace
if (mode === null) {
- const sure = await showDialog({ title: 'replace_all', message: 'All existing data will be replaced!', danger: true, okLabel: 'replace' });
+ const sure = await showDialog({ title: 'replace_all', message: 'All existing data will be replaced!', danger: true, okLabel: 'Replace' });
if (!sure) { e.target.value = ''; return; }
}
try {
@@ -145,7 +145,7 @@ importFile?.addEventListener('change', async e => {
// ── Auth ──
document.getElementById('logout-btn')?.addEventListener('click', async () => {
- const ok = await showDialog({ title: 'logout', message: 'Log out?', okLabel: 'logout', danger: true });
+ const ok = await showDialog({ title: 'logout', message: 'Log out?', okLabel: 'Logout', danger: true });
if (!ok) return;
await fetch('api/auth/logout', { method: 'POST' });
location.reload();
@@ -159,7 +159,7 @@ async function checkAuth() {
if (data.setup) {
let pw = null;
while (!pw || pw.length < 4) {
- pw = await showDialog({ title: 'setup', message: 'Welcome to hyprcrd. Set a password:', input: 'password', placeholder: '> password (min. 4 chars)...', okLabel: 'set', cancelLabel: '' });
+ pw = await showDialog({ title: 'setup', message: 'Welcome to hyprcrd. Set a password:', input: 'password', placeholder: '> password (min. 4 chars)...', okLabel: 'Set', cancelLabel: '' });
if (pw && pw.length < 4) pw = null;
}
const r = await fetch('api/auth/setup', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }) });
@@ -169,7 +169,7 @@ async function checkAuth() {
} else if (!data.logged_in) {
let ok = false;
while (!ok) {
- const pw = await showDialog({ title: 'login', message: '', input: 'password', placeholder: '> password...', okLabel: 'login', cancelLabel: '' });
+ const pw = await showDialog({ title: 'login', message: '', input: 'password', placeholder: '> password...', okLabel: 'Login', cancelLabel: '' });
if (!pw) continue;
const r = await fetch('api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: pw }) });
const d = await r.json();
@@ -204,7 +204,15 @@ document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
const qc = document.getElementById('quick-capture');
if (!qc.classList.contains('hidden')) { qc.classList.add('hidden'); document.getElementById('quick-capture-input').value = ''; return; }
- if (detail.classList.contains('open')) closeDetail();
+ if (detail.classList.contains('open')) {
+ // If the user chose "exit zen first", ESC leaves zen mode before closing the card.
+ const escAction = localStorage.getItem('hyprcrd-esc-action') || 'close';
+ if (escAction === 'exitZen' && detail.classList.contains('zen')) {
+ document.getElementById('detail-zen')?.click();
+ return;
+ }
+ closeDetail();
+ }
else if (modal.classList.contains('open')) closeModal();
else if (search.classList.contains('open')) closeSearch();
else if (tasks?.classList.contains('open')) closeTasks();
@@ -236,14 +244,23 @@ document.getElementById('quick-capture-input')?.addEventListener('keydown', asyn
return;
}
if (e.key !== 'Enter') return;
- const title = e.target.value.trim();
- if (!title) return;
+ const raw = e.target.value.trim();
+ if (!raw) return;
e.preventDefault();
- // Split: first line = title, rest = body
- const lines = title.split(/\n/);
- const cardTitle = lines[0];
- let body = lines.slice(1).join('\n').trim();
+ // Split on first "|" — title before, body after.
+ // Also accept newlines as a fallback (first line = title, rest = body).
+ let cardTitle, body;
+ const pipeIdx = raw.indexOf('|');
+ if (pipeIdx >= 0) {
+ cardTitle = raw.slice(0, pipeIdx).trim();
+ body = raw.slice(pipeIdx + 1).trim();
+ } else {
+ const lines = raw.split(/\n/);
+ cardTitle = lines[0].trim();
+ body = lines.slice(1).join('\n').trim();
+ }
+ if (!cardTitle) return;
if (!body.includes('#inbox')) body = (body ? body + '\n\n' : '') + '#inbox';
await api('api/cards', { method: 'POST', body: { title: cardTitle, body, color: '#38bdf8', position: -1 } });
diff --git a/public/js/hyprcrd.js b/public/js/hyprcrd.js
index c7cab7e..52a9a38 100644
--- a/public/js/hyprcrd.js
+++ b/public/js/hyprcrd.js
@@ -217,8 +217,8 @@
message,
input: false,
danger: opts.danger ?? true,
- okLabel: opts.okLabel || 'ok',
- cancelLabel: opts.cancelLabel || 'cancel',
+ okLabel: opts.okLabel || 'Ok',
+ cancelLabel: opts.cancelLabel || 'Cancel',
});
},
};
diff --git a/public/js/modules/board.js b/public/js/modules/board.js
index 89cdd4f..9c0b7b3 100644
--- a/public/js/modules/board.js
+++ b/public/js/modules/board.js
@@ -78,7 +78,7 @@ document.addEventListener('keydown', async e => {
e.preventDefault();
if (localStorage.getItem('hyprcrd-confirm-delete') !== '0') {
const { showDialog } = await import('./dialog.js');
- const ok = await showDialog({ title: 'delete cards', message: `Delete ${selectedIds.size} card${selectedIds.size > 1 ? 's' : ''}?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete cards', message: `Delete ${selectedIds.size} card${selectedIds.size > 1 ? 's' : ''}?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
for (const id of selectedIds) await api(`api/cards/${id}`, { method: 'DELETE' });
@@ -176,7 +176,7 @@ $('select-bucket')?.addEventListener('click', () => {
$('select-color')?.addEventListener('click', async () => {
if (!selectedIds.size) return;
const { showDialog } = await import('./dialog.js');
- const color = await showDialog({ title: 'set color', input: true, placeholder: '#38bdf8', okLabel: 'apply' });
+ const color = await showDialog({ title: 'set color', input: true, placeholder: '#38bdf8', okLabel: 'Apply' });
if (!color || !/^#[0-9a-fA-F]{6}$/.test(color.trim())) return;
for (const id of selectedIds) {
await api(`api/cards/${id}`, { method: 'PATCH', body: { color: color.trim() } });
@@ -188,7 +188,7 @@ $('select-color')?.addEventListener('click', async () => {
$('select-tag')?.addEventListener('click', async () => {
if (!selectedIds.size) return;
const { showDialog } = await import('./dialog.js');
- const tag = await showDialog({ title: 'add tag', input: true, placeholder: '#tagname', okLabel: 'add' });
+ const tag = await showDialog({ title: 'add tag', input: true, placeholder: '#tagname', okLabel: 'Add' });
if (!tag) return;
const cleanTag = tag.trim().replace(/^#/, '');
if (!cleanTag) return;
@@ -215,7 +215,7 @@ $('select-delete')?.addEventListener('click', async () => {
if (!selectedIds.size) return;
if (localStorage.getItem('hyprcrd-confirm-delete') !== '0') {
const { showDialog } = await import('./dialog.js');
- const ok = await showDialog({ title: 'delete cards', message: `Delete ${selectedIds.size} card${selectedIds.size > 1 ? 's' : ''}?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete cards', message: `Delete ${selectedIds.size} card${selectedIds.size > 1 ? 's' : ''}?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
for (const id of selectedIds) {
@@ -418,7 +418,7 @@ export function renderCardHtml(c, inGroup = false, recent = []) {
${loaded
? (localStorage.getItem('hyprcrd-body-lines') === '0' ? '' : `
${esc(stripMd(c.body))}
`)
: `
`}
- ${tags.length && localStorage.getItem('hyprcrd-show-tags') !== '0' ? `
${tags.slice(0, 5).map(t => tagChipHtml(t)).join('')}${tags.length > 5 ? `+${tags.length - 5}` : ''}
` : ''}
+ ${tags.length && localStorage.getItem('hyprcrd-show-tags') !== '0' ? `
${tags.slice(0, 5).map(t => tagChipHtml(t)).join('')}${tags.length > 5 ? `+${tags.length - 5}` : ''}
` : ''}
${c.due_date ? `
${c.due_date}` : `
${(c.updated_at || c.created_at || '').substring(0, 10)}`}
${c.bucket_name ? `
${esc(c.bucket_name)}` : ''}
@@ -774,7 +774,7 @@ function renderGroupBar(cards) {
bar.dataset.groupId = String(state.activeGroup);
bar.innerHTML = `
${esc(parent?.title || 'group')}
diff --git a/public/js/modules/buckets.js b/public/js/modules/buckets.js
index 52cb910..6cd44a0 100644
--- a/public/js/modules/buckets.js
+++ b/public/js/modules/buckets.js
@@ -25,16 +25,16 @@ async function showBucketCtx(x, y, bucket) {
const hasCards = state.cards.some(c => c.bucket_id == bucket.id);
ctxMenu.innerHTML = `
-
-
+
+
-
-
+
+
${hasCards
- ? `
-
`
- : `
`
+ ? `
+
`
+ : `
`
}
@@ -76,7 +76,7 @@ async function showBucketCtx(x, y, bucket) {
hideBucketCtx();
const cards = state.cards.filter(c => c.bucket_id == bucket.id);
if (cards.length > 0 || localStorage.getItem('hyprcrd-confirm-delete') !== '0') {
- const ok = await showDialog({ title: 'delete_bucket', message: cards.length ? `Delete "${bucket.name}"? ${cards.length} card(s) will be kept.` : `Delete "${bucket.name}"?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete_bucket', message: cards.length ? `Delete "${bucket.name}"? ${cards.length} card(s) will be kept.` : `Delete "${bucket.name}"?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
if (state.activeBucket == bucket.id) state.activeBucket = 'all';
@@ -86,7 +86,7 @@ async function showBucketCtx(x, y, bucket) {
else if (a === 'delete-all') {
hideBucketCtx();
const cards = state.cards.filter(c => c.bucket_id == bucket.id);
- const ok = await showDialog({ title: 'delete_bucket', message: `Delete "${bucket.name}" and ${cards.length} card(s)?`, danger: true, okLabel: 'delete all' });
+ const ok = await showDialog({ title: 'delete_bucket', message: `Delete "${bucket.name}" and ${cards.length} card(s)?`, danger: true, okLabel: 'Delete all' });
if (!ok) return;
for (const c of cards) await api(`api/cards/${c.id}`, { method: 'DELETE' });
if (state.activeBucket == bucket.id) state.activeBucket = 'all';
@@ -165,7 +165,7 @@ function renderBar() {
const active = state.activeBucket == b.id;
return `
`;
}).join('')}
-
`;
+
`;
updateHeaderTint();
diff --git a/public/js/modules/context-menu.js b/public/js/modules/context-menu.js
index ee2e158..8058ee8 100644
--- a/public/js/modules/context-menu.js
+++ b/public/js/modules/context-menu.js
@@ -37,28 +37,28 @@ export function showContextMenu(x, y, card) {
const isChild = !!card.parent_id;
items.innerHTML = `
-
-
-
- ${/^\d{4}-\d{2}-\d{2}/.test(card.title) ? `
` : ''}
-
- ${card.due_date ? `
` : ''}
-
-
-
+
+
+
+ ${/^\d{4}-\d{2}-\d{2}/.test(card.title) ? `
` : ''}
+
+ ${card.due_date ? `
` : ''}
+
+
+
-
-
-
+
+
+
${isParent ? `
-
-
-
` : ''}
+
+
+
` : ''}
${isChild ? `
-
-
` : ''}
+
+
` : ''}
-
`;
+
`;
// Color sub-panel
const grid = $('ctx-color-grid');
@@ -77,7 +77,7 @@ export function showContextMenu(x, y, card) {
members.classList.add('hidden');
// Bucket sub-panel
- buckets.innerHTML = `
` +
+ buckets.innerHTML = `
` +
state.buckets.map(b => `
`).join('');
buckets.classList.add('hidden');
@@ -97,7 +97,7 @@ export function showContextMenu(x, y, card) {
#${esc(t)}
`).join('') +
(f && !allTags.includes(f) ? `
` : '');
tagList.querySelectorAll('[data-ctx-tag]').forEach(btn => btn.addEventListener('click', async e => {
@@ -181,7 +181,7 @@ export function showContextMenu(x, y, card) {
const hasDate = /^\d{4}-\d{2}-\d{2}/.test(card.title);
if (hasDate) {
const curDate = card.title.substring(0, 10);
- const newDate = await showDialog({ title: 'duplicate_to_date', input: 'date', defaultValue: curDate, okLabel: 'duplicate' });
+ const newDate = await showDialog({ title: 'duplicate_to_date', input: 'date', defaultValue: curDate, okLabel: 'Duplicate' });
if (!newDate) return;
const newTitle = card.title.replace(/^\d{4}-\d{2}-\d{2}/, newDate.trim());
await api('api/cards', { method: 'POST', body: { title: newTitle, body: card.body, color: card.color, bucket_id: card.bucket_id, position: -1 } });
@@ -193,7 +193,7 @@ export function showContextMenu(x, y, card) {
else if (a === 'move-date') {
hideContextMenu();
const curDate = card.title.substring(0, 10);
- const newDate = await showDialog({ title: 'move_to_date', input: 'date', defaultValue: curDate, okLabel: 'move' });
+ const newDate = await showDialog({ title: 'move_to_date', input: 'date', defaultValue: curDate, okLabel: 'Move' });
if (!newDate || newDate === curDate) return;
const newTitle = card.title.replace(/^\d{4}-\d{2}-\d{2}/, newDate.trim());
await api(`api/cards/${card.id}`, { method: 'PATCH', body: { title: newTitle } });
@@ -209,7 +209,7 @@ export function showContextMenu(x, y, card) {
}
else if (a === 'due-date') {
hideContextMenu();
- const newDate = await showDialog({ title: card.due_date ? 'change due date' : 'set due date', input: 'date', defaultValue: card.due_date || new Date().toISOString().substring(0, 10), okLabel: card.due_date ? 'update' : 'set' });
+ const newDate = await showDialog({ title: card.due_date ? 'change due date' : 'set due date', input: 'date', defaultValue: card.due_date || new Date().toISOString().substring(0, 10), okLabel: card.due_date ? 'Update' : 'Set' });
if (newDate === null) return;
await api(`api/cards/${card.id}`, { method: 'PATCH', body: { due_date: newDate || null } });
await window._reloadAll();
@@ -226,7 +226,7 @@ export function showContextMenu(x, y, card) {
else if (a === 'expand-group') { hideContextMenu(); state.activeGroup = card.id; window._renderBoard(); }
else if (a === 'go-to-group') { hideContextMenu(); const p = state.cards.find(c => c.id == card.parent_id); if (p) { state.activeGroup = p.id; window._renderBoard(); } }
else if (a === 'dissolve') {
- hideContextMenu(); const ok = await showDialog({ title: 'dissolve_group', message: `Dissolve group "${card.title}"?`, danger: true, okLabel: 'dissolve' });
+ hideContextMenu(); const ok = await showDialog({ title: 'dissolve_group', message: `Dissolve group "${card.title}"?`, danger: true, okLabel: 'Dissolve' });
if (ok) { const ch = getChildren(card.id); pushBatch('dissolve', ch.map(c => ({ cardId: c.id, prevData: { parent_id: card.id } }))); await Promise.all(ch.map(c => api(`api/cards/${c.id}`, { method: 'PATCH', body: { parent_id: null } }))); await window._reloadAll(); }
}
else if (a === 'ungroup') { hideContextMenu(); pushCardEdit('ungroup', card.id, { parent_id: card.parent_id }); await api(`api/cards/${card.id}`, { method: 'PATCH', body: { parent_id: null } }); await window._reloadAll(); }
@@ -234,7 +234,7 @@ export function showContextMenu(x, y, card) {
hideContextMenu();
const needConfirm = localStorage.getItem('hyprcrd-confirm-delete') !== '0';
if (needConfirm) {
- const ok = await showDialog({ title: 'delete_card', message: `Really delete "${card.title}"?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete_card', message: `Really delete "${card.title}"?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
const wasSync = !!card.caldav_uid;
diff --git a/public/js/modules/detail.js b/public/js/modules/detail.js
index c01586f..18e0c1b 100644
--- a/public/js/modules/detail.js
+++ b/public/js/modules/detail.js
@@ -417,6 +417,7 @@ function renderSidebar(card) {
if (groupCards && groupCards.length > 1) {
sidebar.classList.remove('hidden');
+ sidebar.dataset.groupId = String(groupCards[0].id);
$('detail-sidebar-count').textContent = groupCards.length;
list.innerHTML = groupCards.map(gc => {
const active = gc.id == card.id;
@@ -438,6 +439,7 @@ function renderSidebar(card) {
});
} else {
sidebar.classList.add('hidden');
+ delete sidebar.dataset.groupId;
}
}
@@ -451,7 +453,7 @@ function updateDoneButton(card) {
btn.classList.toggle('text-t-accent', isDone);
btn.classList.toggle('border-t-accent', isDone);
}
- if (label) label.textContent = isDone ? 'undone' : 'done';
+ if (label) label.textContent = isDone ? 'Undone' : 'Done';
}
function showContent(card) {
@@ -892,7 +894,7 @@ function loadSplitCard(id) {
${splitLastQuery ? 'results' : 'related cards'}
`;
splitPicks.querySelector('#split-back').addEventListener('click', restoreSplitList);
@@ -1015,7 +1017,7 @@ $('detail-done')?.addEventListener('click', async () => {
$('detail-delete')?.addEventListener('click', async () => {
if (!state.viewingCard) return;
if (localStorage.getItem('hyprcrd-confirm-delete') !== '0') {
- const ok = await showDialog({ title: 'delete_card', message: `Really delete "${state.viewingCard.title}"?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete_card', message: `Really delete "${state.viewingCard.title}"?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
pushCardDelete('delete', snapshotCard(state.viewingCard));
diff --git a/public/js/modules/dialog.js b/public/js/modules/dialog.js
index 3fc3f65..2ddf0db 100644
--- a/public/js/modules/dialog.js
+++ b/public/js/modules/dialog.js
@@ -110,7 +110,7 @@ function renderDatepicker() {
});
}
-export function showDialog({ title, message = '', input = false, placeholder = '', defaultValue = '', danger = false, okLabel = 'ok', cancelLabel = 'cancel' }) {
+export function showDialog({ title, message = '', input = false, placeholder = '', defaultValue = '', danger = false, okLabel = 'Ok', cancelLabel = 'Cancel' }) {
return new Promise(res => {
resolve = res;
titleEl.textContent = title;
diff --git a/public/js/modules/editor.js b/public/js/modules/editor.js
index f815aa2..6add5d4 100644
--- a/public/js/modules/editor.js
+++ b/public/js/modules/editor.js
@@ -116,7 +116,7 @@ const inpDueBtn = $('inp-due-date');
function setDueDate(date) {
if (inpDueValue) inpDueValue.value = date || '';
- if (inpDueLabel) inpDueLabel.textContent = date || 'no due';
+ if (inpDueLabel) inpDueLabel.textContent = date || 'No due';
if (inpDueBtn) {
inpDueBtn.classList.toggle('text-t-accent', !!date);
inpDueBtn.classList.toggle('border-t-accent', !!date);
@@ -131,7 +131,7 @@ inpDueBtn?.addEventListener('click', async () => {
title: current ? 'change due date' : 'set due date',
input: 'date',
defaultValue: current || new Date().toISOString().substring(0, 10),
- okLabel: current ? 'update' : 'set',
+ okLabel: current ? 'Update' : 'Set',
});
if (result === null) return;
setDueDate(result || '');
@@ -147,7 +147,7 @@ function updateDoneToggle() {
inpDoneToggle.classList.toggle('border-t-accent', isDone);
inpDoneToggle.classList.toggle('text-t-dim', !isDone);
}
- if (inpDoneLabel) inpDoneLabel.textContent = isDone ? 'undone' : 'done';
+ if (inpDoneLabel) inpDoneLabel.textContent = isDone ? 'Undone' : 'Done';
}
inpDoneToggle?.addEventListener('click', () => {
diff --git a/public/js/modules/gallery.js b/public/js/modules/gallery.js
index d05b2eb..3334e51 100644
--- a/public/js/modules/gallery.js
+++ b/public/js/modules/gallery.js
@@ -156,7 +156,7 @@ export async function openGallery() {
e.stopPropagation();
const name = btn.dataset.delOrphan;
if (localStorage.getItem('hyprcrd-confirm-delete') !== '0') {
- const ok = await showDialog({ title: 'delete_image', message: `Delete "${name}"?`, danger: true, okLabel: 'delete' });
+ const ok = await showDialog({ title: 'delete_image', message: `Delete "${name}"?`, danger: true, okLabel: 'Delete' });
if (!ok) return;
}
await fetch(`api/uploads/${name}`, { method: 'DELETE', headers: { 'X-CSRF-Token': getCsrf() } });
@@ -179,13 +179,13 @@ function openOrphanLightbox(file) {
diff --git a/public/plugins/chat/chat.js b/public/plugins/chat/chat.js
index 9598896..ea29a10 100644
--- a/public/plugins/chat/chat.js
+++ b/public/plugins/chat/chat.js
@@ -235,14 +235,31 @@ async function sendMessage(e) {
body: { peer: currentPeer, body },
});
if (!res.ok) {
- alert('Send failed: ' + (res.error || 'unknown'));
+ const detail = [
+ res.error || 'unknown',
+ res.url ? `URL: ${res.url}` : null,
+ res.response_body ? `Body: ${res.response_body}` : null,
+ ].filter(Boolean).join('\n');
+ await hyprcrd.dialog.show({
+ title: 'send failed',
+ message: detail,
+ danger: true,
+ okLabel: 'Ok',
+ cancelLabel: '',
+ });
return;
}
input.value = '';
await loadThread(currentPeer);
await loadConversations();
} catch (err) {
- alert('Send error: ' + err.message);
+ await hyprcrd.dialog.show({
+ title: 'send error',
+ message: String(err?.message || err),
+ danger: true,
+ okLabel: 'Ok',
+ cancelLabel: '',
+ });
} finally {
sendBtn.disabled = false;
input.disabled = false;
diff --git a/public/plugins/email/email.html b/public/plugins/email/email.html
index 6a4ede2..8d738a6 100644
--- a/public/plugins/email/email.html
+++ b/public/plugins/email/email.html
@@ -10,8 +10,8 @@
-
accounts
-
close
+
Accounts
+
Close
@@ -27,7 +27,7 @@
@@ -90,10 +90,10 @@
date:
-
- view as html
- view as text
- load images (0)
+
+ View as html
+ View as text
+ Load images (0)
diff --git a/public/plugins/email/email.js b/public/plugins/email/email.js
index 70446fb..5acdaaa 100644
--- a/public/plugins/email/email.js
+++ b/public/plugins/email/email.js
@@ -165,9 +165,9 @@ function buildSettingsPanel(panelEl) {
- save
- test
- cancel
+ Save
+ Test
+ Cancel
@@ -297,7 +297,7 @@ async function deleteAccount(id) {
if (!acc) return;
const ok = await hyprcrd.dialog.confirm(
`Delete email account "${acc.name}"? Stored credentials will be removed but no mail is deleted from the server.`,
- { title: 'delete account', okLabel: 'delete', cancelLabel: 'cancel', danger: true }
+ { title: 'delete account', okLabel: 'Delete', cancelLabel: 'Cancel', danger: true }
);
if (!ok) return;
await hyprcrd.api(`api/email/accounts/${id}`, { method: 'DELETE' });
@@ -573,7 +573,7 @@ async function markSelectedAs(action) {
const verb = action === 'spam' ? 'as spam' : 'as not spam';
const ok = await hyprcrd.dialog.confirm(
`Mark ${n} sender${n === 1 ? '' : 's'} ${verb}? This adds ${action === 'spam' ? 'block' : 'allow'} rules that affect all future messages from the same senders.`,
- { title: `mark ${verb}`, okLabel: 'mark', cancelLabel: 'cancel', danger: action === 'spam' }
+ { title: `mark ${verb}`, okLabel: 'Mark', cancelLabel: 'Cancel', danger: action === 'spam' }
);
if (!ok) return;
@@ -609,7 +609,7 @@ async function deleteSelected({ skipConfirm = false } = {}) {
if (!skipConfirm) {
const ok = await hyprcrd.dialog.confirm(
`Permanently delete ${n} message${n === 1 ? '' : 's'} from the server? This cannot be undone.`,
- { title: 'delete mail', okLabel: 'delete', cancelLabel: 'cancel', danger: true }
+ { title: 'delete mail', okLabel: 'Delete', cancelLabel: 'Cancel', danger: true }
);
if (!ok) return;
}
@@ -861,7 +861,7 @@ function injectHeaderButton() {
const btn = document.createElement('button');
btn.id = 'email-btn';
btn.className = 'flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors';
- btn.innerHTML = 'mail';
+ btn.innerHTML = 'Mail';
btn.addEventListener('click', openEmail);
const addBtn = document.getElementById('add-btn');
if (addBtn) header.insertBefore(btn, addBtn);
diff --git a/public/plugins/gitea/gitea.html b/public/plugins/gitea/gitea.html
index 4f2869c..f9c260d 100644
--- a/public/plugins/gitea/gitea.html
+++ b/public/plugins/gitea/gitea.html
@@ -9,8 +9,8 @@
- accounts
- close
+ Accounts
+ Close
@@ -26,7 +26,7 @@
- configure accounts
+ Configure accounts
settings → gitea
@@ -126,8 +126,8 @@
project: not supported by gitea api on issue create — set it manually on gitea after creation
- create issue
- cancel
+ Create issue
+ Cancel
@@ -164,8 +164,8 @@
- save
- cancel
+ Save
+ Cancel
@@ -198,14 +198,14 @@
- save
- cancel
+ Save
+ Cancel
@@ -218,8 +218,8 @@
- save
- cancel
+ Save
+ Cancel
diff --git a/public/plugins/gitea/gitea.js b/public/plugins/gitea/gitea.js
index eca1b16..1dce55b 100644
--- a/public/plugins/gitea/gitea.js
+++ b/public/plugins/gitea/gitea.js
@@ -114,9 +114,9 @@ function buildSettingsPanel(panelEl) {
- save
- test
- cancel
+ Save
+ Test
+ Cancel
@@ -224,7 +224,7 @@ async function deleteAccount(id) {
if (!acc) return;
const ok = await hyprcrd.dialog.confirm(
`Delete gitea account "${acc.name}"? Stored token will be removed but no issues are deleted on the server.`,
- { title: 'delete account', okLabel: 'delete', cancelLabel: 'cancel', danger: true }
+ { title: 'delete account', okLabel: 'Delete', cancelLabel: 'Cancel', danger: true }
);
if (!ok) return;
await hyprcrd.api(`api/gitea/accounts/${id}`, { method: 'DELETE' });
@@ -1169,9 +1169,9 @@ function renderStateToggleButton() {
const btn = $('gitea-state-toggle');
if (!btn || !currentIssue) return;
if (currentIssue.state === 'open') {
- btn.innerHTML = 'close';
+ btn.innerHTML = 'Close';
} else {
- btn.innerHTML = 'reopen';
+ btn.innerHTML = 'Reopen';
}
}
@@ -1447,7 +1447,7 @@ function injectHeaderButton() {
const btn = document.createElement('button');
btn.id = 'gitea-btn';
btn.className = 'flex items-center gap-2 px-2.5 py-1.5 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors';
- btn.innerHTML = 'gitea';
+ btn.innerHTML = 'Gitea';
btn.addEventListener('click', openGitea);
const addBtn = document.getElementById('add-btn');
if (addBtn) header.insertBefore(btn, addBtn);
diff --git a/public/plugins/logseq-import/logseq-import.js b/public/plugins/logseq-import/logseq-import.js
index b90e692..13bbfe7 100644
--- a/public/plugins/logseq-import/logseq-import.js
+++ b/public/plugins/logseq-import/logseq-import.js
@@ -26,7 +26,7 @@ function injectFooterButton() {
const btn = document.createElement('button');
btn.id = 'logseq-import-btn';
btn.className = 'flex items-center gap-1.5 px-2.5 py-1 text-xs border border-t-border text-t-muted hover:border-t-accent hover:text-t-accent transition-colors';
- btn.innerHTML = 'logseq import';
+ btn.innerHTML = 'Logseq import';
group.insertBefore(btn, settingsBtn || null);
btn.addEventListener('click', () => fileInput.click());
@@ -47,14 +47,14 @@ function injectFooterButton() {
await hyprcrd.dialog.show({
title: 'logseq import',
message: `${data.imported} cards, ${data.assets} images imported`,
- okLabel: 'ok',
+ okLabel: 'Ok',
});
} catch (err) {
await hyprcrd.dialog.show({
title: 'logseq import failed',
message: String(err?.message || err) || 'unknown error',
danger: true,
- okLabel: 'ok',
+ okLabel: 'Ok',
});
}
e.target.value = '';
diff --git a/public/plugins/slideshow/slideshow.css b/public/plugins/slideshow/slideshow.css
index d470ce0..da1a6dc 100644
--- a/public/plugins/slideshow/slideshow.css
+++ b/public/plugins/slideshow/slideshow.css
@@ -164,6 +164,25 @@
border-color: var(--t-accent);
}
+/* ── Detail-sidebar button (icon-only, sits next to the "group" label) ── */
+#detail-sidebar .slideshow-present-btn-sidebar {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 20px;
+ height: 20px;
+ margin-left: 4px;
+ color: var(--t-muted);
+ border: 1px solid var(--t-border);
+ background: transparent;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+#detail-sidebar .slideshow-present-btn-sidebar:hover {
+ color: var(--t-accent);
+ border-color: var(--t-accent);
+}
+
/* ── Hotspot editor ── */
#slideshow-editor.hidden { display: none; }
#slideshow-editor[disabled], #slideshow-editor [disabled] { opacity: 0.4; pointer-events: none; }
diff --git a/public/plugins/slideshow/slideshow.html b/public/plugins/slideshow/slideshow.html
index d38f79b..8ce0db4 100644
--- a/public/plugins/slideshow/slideshow.html
+++ b/public/plugins/slideshow/slideshow.html
@@ -60,11 +60,11 @@
- upload
+ Upload
- cancel
+ Cancel
@@ -79,9 +79,9 @@
hotspot target
- next slide
- previous slide
- first slide
+ Next slide
+ Previous slide
+ First slide
diff --git a/public/plugins/slideshow/slideshow.js b/public/plugins/slideshow/slideshow.js
index 6c2b501..b4a193a 100644
--- a/public/plugins/slideshow/slideshow.js
+++ b/public/plugins/slideshow/slideshow.js
@@ -306,6 +306,31 @@ if (groupBar) {
injectGroupBarButton();
}
+// ── Detail-view sidebar button (shown when browsing a group card) ──
+
+function injectDetailSidebarButton() {
+ const sidebar = $('detail-sidebar');
+ if (!sidebar) return;
+ if (sidebar.querySelector('.slideshow-present-btn')) return;
+ // Sidebar header is the first direct child div
+ const header = sidebar.firstElementChild;
+ if (!header) return;
+ const btn = document.createElement('button');
+ btn.className = 'slideshow-present-btn slideshow-present-btn-sidebar';
+ btn.type = 'button';
+ btn.title = 'present this group';
+ btn.innerHTML = '';
+ btn.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ const pid = sidebar.dataset.groupId;
+ if (pid) await startPresentationForGroup(pid);
+ });
+ header.appendChild(btn);
+}
+
+if ($('detail-sidebar')) injectDetailSidebarButton();
+else document.addEventListener('DOMContentLoaded', injectDetailSidebarButton, { once: true });
+
// ── Hotspot editor ───────────────────────────────────────────────
const ed = {