context-menu: add Copy text action and always show Mark done
- New "Copy text" entry next to "Save .md": copies title + blank line + body to the clipboard, with fenced code blocks (incl. slide-image), markdown images / links / formatting, and inline #tags stripped. - "Mark done" is now visible on every card (used to require a due_date to even show up). On a card already tagged #done it flips to "Unmark done" and removes the tag. The original due-date-clearing behavior is preserved when transitioning a due card to done. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
26ffa0e71f
commit
ff3e0e8736
1 file changed
+24
-5
@@ -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) {
|
||||
<button class="ctx-item" data-action="duplicate"><i class="ph ph-copy text-sm"></i><span class="font-pixel text-[10px]">Duplicate</span></button>
|
||||
${/^\d{4}-\d{2}-\d{2}/.test(card.title) ? `<button class="ctx-item" data-action="move-date"><i class="ph ph-calendar-dots text-sm"></i><span class="font-pixel text-[10px]">Move to date</span></button>` : ''}
|
||||
<button class="ctx-item" data-action="due-date"><i class="ph ph-clock-countdown text-sm ${card.due_date ? 'text-t-accent' : ''}"></i><span class="font-pixel text-[10px]">${card.due_date ? 'Due: ' + card.due_date : 'Set due date'}</span></button>
|
||||
${card.due_date ? `<button class="ctx-item" data-action="due-done"><i class="ph ph-check-circle text-sm text-t-accent"></i><span class="font-pixel text-[10px]">Mark done</span></button>` : ''}
|
||||
<button class="ctx-item" data-action="due-done"><i class="ph ph-check-circle text-sm ${parseTags(card).includes('done') ? 'text-t-accent' : ''}"></i><span class="font-pixel text-[10px]">${parseTags(card).includes('done') ? 'Unmark done' : 'Mark done'}</span></button>
|
||||
<button class="ctx-item" data-action="save-md"><i class="ph ph-file-md text-sm"></i><span class="font-pixel text-[10px]">Save .md</span></button>
|
||||
<button class="ctx-item" data-action="copy-text"><i class="ph ph-clipboard-text text-sm"></i><span class="font-pixel text-[10px]">Copy text</span></button>
|
||||
<button class="ctx-item" data-action="push-relay"><i class="ph ph-broadcast text-sm"></i><span class="font-pixel text-[10px]">Push to relay</span><i class="ph ph-caret-right text-xs ml-auto"></i></button>
|
||||
<button class="ctx-item" data-action="favorite"><i class="ph${isFavorite(card.id) ? '-fill' : ''} ph-star text-sm ${isFavorite(card.id) ? 'text-amber-400' : ''}"></i><span class="font-pixel text-[10px]">${isFavorite(card.id) ? 'Unfavorite' : 'Favorite'}</span></button>
|
||||
<div class="ctx-sep"></div>
|
||||
@@ -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 = '<div class="px-3 py-2 text-[10px] text-t-dim font-pixel italic">no tenants</div>';
|
||||
relayTenants.innerHTML = '<div class="px-3 py-2 text-[10px] text-t-dim font-pixel italic cursor-blink">no tenants</div>';
|
||||
return;
|
||||
}
|
||||
// Self first, then others alphabetical
|
||||
|
||||
Reference in new issue
Block a user