This commit is contained in:
th committed 2026-04-06 20:27:19 +02:00
1 parent e85b5203cd
commit 0d4d369870
19 files changed
+2258 -1814

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
// ── Undo system ──
import { api } from './util.js';
const MAX_STACK = 30;
const stack = [];
export function pushUndo(label, restoreFn) {
stack.push({ label, restore: restoreFn, time: Date.now() });
if (stack.length > MAX_STACK) stack.shift();
showHint(label);
}
export async function undo() {
const entry = stack.pop();
if (!entry) return;
await entry.restore();
await window._reloadAll();
showHint(`undo: ${entry.label}`);
}
export function canUndo() { return stack.length > 0; }
export function lastLabel() { return stack.length ? stack[stack.length - 1].label : ''; }
// Snapshot helpers
export function snapshotCard(card) {
return { ...card };
}
export function snapshotCards(cards) {
return cards.map(c => ({ ...c }));
}
export function pushCardEdit(label, cardId, prevData) {
pushUndo(label, async () => {
await api(`api/cards/${cardId}`, { method: 'PATCH', body: prevData });
});
}
export function pushCardDelete(label, card) {
pushUndo(label, async () => {
await api('api/cards', { method: 'POST', body: {
title: card.title, body: card.body, color: card.color,
bucket_id: card.bucket_id, position: card.position,
}});
});
}
export function pushReorder(label, cards) {
const prev = cards.map(c => ({ id: c.id, position: c.position }));
pushUndo(label, async () => {
await Promise.all(prev.map(p => api(`api/cards/${p.id}`, { method: 'PATCH', body: { position: p.position } })));
});
}
export function pushBatch(label, patches) {
// patches = [{cardId, prevData}, ...]
pushUndo(label, async () => {
await Promise.all(patches.map(p => api(`api/cards/${p.cardId}`, { method: 'PATCH', body: p.prevData })));
});
}
// Toast hint
let hintTimer = null;
function showHint(text) {
let el = document.getElementById('undo-hint');
if (!el) {
el = document.createElement('div');
el.id = 'undo-hint';
el.className = 'fixed bottom-16 left-1/2 -translate-x-1/2 z-[95] bg-t-surface border border-t-border px-4 py-2 text-xs font-pixel text-t-muted shadow-lg transition-all';
document.body.appendChild(el);
}
el.textContent = text + ' (ctrl+z undo)';
el.style.opacity = '1';
el.style.transform = 'translateX(-50%) translateY(0)';
clearTimeout(hintTimer);
hintTimer = setTimeout(() => {
el.style.opacity = '0';
el.style.transform = 'translateX(-50%) translateY(8px)';
}, 2500);
}