* version: every span that shows v<APP_VERSION> in the page chrome now carries data-app-version, and checkAuth() patches them in from the /api/auth/status response so the GUI version doesn't go stale when PHP is cached behind anything that revalidates lazily. * undo: tailwind v4 compiles `-translate-x-1/2` to the *new* CSS `translate` property, which then stacks with the inline `transform` the toast set — the box ended up shifted ~ -100% and looked left- anchored. Drop the tailwind utility, set `translate` directly inline so there's one source of truth for the offset. * editor: ctrl/cmd+enter while the editor modal is open now invokes saveCard() (= save + close), pairing with the existing ctrl+s (save only) and ctrl+shift+s (save+close) shortcuts. * collab buckets: drop the inset left edge from .bucket-tab-collab and the matching pulse-keyframe shadow — the bar tab now reads as a normal bucket tab, just with the unread dot + 3-cycle pulse on update. * collab create dialog: drop the box border / background from .collab-toggle-row and .collab-members so the toggle and the user list sit directly on the modal background instead of looking like nested input boxes. * test progress strip: ensureProgressBar() now also pushes the body down by 30px AND shrinks it via height: calc(100vh - 30px) so the fixed-top bar doesn't clip the footer (body is h-screen + overflow-hidden in the core layout). hideProgress() clears both inline styles when the run finishes. chore: bump version to 0.9.2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
3.1 KiB
JavaScript
95 lines
3.1 KiB
JavaScript
// ── 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 () => {
|
|
const created = await api('api/cards', { method: 'POST', body: {
|
|
title: card.title, body: card.body, color: card.color,
|
|
bucket_id: card.bucket_id, parent_id: card.parent_id, position: card.position,
|
|
}});
|
|
// Restore fields that POST doesn't accept
|
|
if (created?.id && card.due_date) {
|
|
await api(`api/cards/${created.id}`, { method: 'PATCH', body: { due_date: card.due_date } });
|
|
}
|
|
});
|
|
}
|
|
|
|
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
|
|
//
|
|
// Centering note: tailwind v4 compiles `-translate-x-1/2` to the *new*
|
|
// CSS `translate` property, which then stacks with any inline `transform`
|
|
// we set — the box ended up shifted ~ -100% (utility translate + JS
|
|
// transform translate both applied) and looked left-anchored. Fix: drop
|
|
// the tailwind translate utility and centre purely via the modern
|
|
// `translate` property in inline styles, which is one source of truth.
|
|
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 z-[95] bg-t-surface border border-t-border px-4 py-2 text-xs font-pixel text-t-muted shadow-lg transition-all';
|
|
el.style.translate = '-50% 0';
|
|
document.body.appendChild(el);
|
|
}
|
|
el.textContent = text + ' (ctrl+z undo)';
|
|
el.style.opacity = '1';
|
|
el.style.translate = '-50% 0';
|
|
clearTimeout(hintTimer);
|
|
hintTimer = setTimeout(() => {
|
|
el.style.opacity = '0';
|
|
el.style.translate = '-50% 8px';
|
|
}, 2500);
|
|
}
|