diff --git a/public/js/modules/context-menu.js b/public/js/modules/context-menu.js index 8058ee8..ac99d40 100644 --- a/public/js/modules/context-menu.js +++ b/public/js/modules/context-menu.js @@ -4,6 +4,7 @@ import { $, esc, api } from './util.js'; import { state, parseTags, getChildren, isFavorite, toggleFavorite, CARD_COLORS } from './state.js'; import { showDialog } from './dialog.js'; import { pushCardEdit, pushCardDelete, pushBatch, snapshotCard } from './undo.js'; +import { stripMd } from './markdown.js'; const menu = $('ctx-menu'); const items = $('ctx-items'); @@ -42,8 +43,9 @@ export function showContextMenu(x, y, card) { ${/^\d{4}-\d{2}-\d{2}/.test(card.title) ? `` : ''} - ${card.due_date ? `` : ''} + +
@@ -202,9 +204,16 @@ export function showContextMenu(x, y, card) { } else if (a === 'due-done') { hideContextMenu(); - // Clear due date and mark as done - const bodyWithDone = (card.body || '').includes('#done') ? card.body : (card.body || '').trimEnd() + ' #done'; - await api(`api/cards/${card.id}`, { method: 'PATCH', body: { due_date: null, body: bodyWithDone } }); + const isDone = parseTags(card).includes('done'); + let newBody; + if (isDone) { + newBody = (card.body || '').replace(/(^|\s)#done\b/g, '$1').replace(/[ \t]+$/gm, '').replace(/\n{3,}/g, '\n\n'); + } else { + newBody = (card.body || '').trimEnd() + ((card.body || '').trimEnd() ? ' ' : '') + '#done'; + } + const patch = { body: newBody }; + if (!isDone && card.due_date) patch.due_date = null; + await api(`api/cards/${card.id}`, { method: 'PATCH', body: patch }); await window._reloadAll(); } else if (a === 'due-date') { @@ -215,6 +224,16 @@ export function showContextMenu(x, y, card) { await window._reloadAll(); } else if (a === 'save-md') { hideContextMenu(); window.location.href = `api/export-md?ids=${card.id}`; } + else if (a === 'copy-text') { + hideContextMenu(); + let body = card.body || ''; + // Drop fenced code blocks (incl. slide-image), then markdown structure, then hashtags + body = body.replace(/```[\s\S]*?```/g, ''); + body = stripMd(body); + body = body.replace(/(^|\s)#[\w/-]+/g, '$1').replace(/[ \t]+$/gm, '').replace(/\n{2,}/g, '\n\n').trim(); + const text = (card.title || '') + '\n\n' + body; + try { await navigator.clipboard.writeText(text); } catch {} + } else if (a === 'push-relay') { await openRelayTenantSubmenu(card); } @@ -284,7 +303,7 @@ function renderRelayTenants(card) { const list = cachedRelayTenants?.tenants || []; const self = cachedRelayTenants?.self || ''; if (!list.length) { - relayTenants.innerHTML = '
no tenants
'; + relayTenants.innerHTML = ''; return; } // Self first, then others alphabetical