collab attachments, multi-day events, calendar improvements
- Collab buckets sync attachments alongside cards (images/PDFs/audio). New /api/collab-attachments endpoint on relay, encrypted at rest, bucket-scoped for members. - CalDAV multi-day events via **Bis:** body field. Calendar renders event on every day in range with day counter (1/6, 2/6, ...). - Calendar preserves month across close/reopen (only jumps to today on first open). - Calendar title: centered nav group with [MM-DD] Mon Y [KW-KW] — dimmed brackets, fixed width so nothing wiggles. - Editor: scratchpad uses custom confirm, autobackup-draft dialog replaced with custom styled dialog. - Scanlines theme feature removed (performance issue). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
002562b1d3
commit
ca47052768
11 files changed
+254
-64
No files matched your search
+21
-11
@@ -4,6 +4,7 @@ import { $, esc, api, escRe, getCsrf, fuzzyMatch } from './util.js';
|
||||
import { state, getChildren, CARD_COLORS } from './state.js';
|
||||
import { renderMd, addCodeCopyButtons, formatTodoItems } from './markdown.js';
|
||||
import { pushCardEdit, snapshotCard } from './undo.js';
|
||||
import { showDialog } from './dialog.js';
|
||||
|
||||
const modal = $('modal');
|
||||
const body = $('editor-body');
|
||||
@@ -238,7 +239,7 @@ export function openModal(card = null) {
|
||||
updateActiveLine();
|
||||
|
||||
// Check for unsaved draft (newer than what we just loaded).
|
||||
// Skipped during test runs — tests would block on the confirm dialog.
|
||||
// Skipped during test runs — tests would block on the dialog.
|
||||
const draft = loadDraft(card?.id);
|
||||
if (draft && !window._testRunning) {
|
||||
const cardModified = card ? Date.parse(card.updated_at + 'Z') || 0 : 0;
|
||||
@@ -248,18 +249,27 @@ export function openModal(card = null) {
|
||||
if (draftIsNewer && draftHasContent && differsFromLoaded) {
|
||||
const age = Math.round((Date.now() - draft.savedAt) / 60000);
|
||||
const ageText = age < 1 ? 'just now' : age < 60 ? `${age} min ago` : `${Math.round(age / 60)} h ago`;
|
||||
const restore = window.confirm(`Unsaved draft from ${ageText} found.\n\nRestore it?`);
|
||||
if (restore) {
|
||||
inpTitle.value = draft.title || '';
|
||||
body.value = draft.body || '';
|
||||
if (scratchpadBody) scratchpadBody.value = draft.scratchpad || '';
|
||||
updatePreview();
|
||||
} else {
|
||||
clearDraft(card?.id);
|
||||
}
|
||||
const cardId = card?.id;
|
||||
// Async confirm — fires after openModal returns
|
||||
(async () => {
|
||||
const restore = await showDialog({
|
||||
title: 'unsaved draft',
|
||||
message: `Unsaved draft from ${ageText} found.\n\nRestore it?`,
|
||||
okLabel: 'Restore',
|
||||
cancelLabel: 'Discard',
|
||||
});
|
||||
if (!modal.classList.contains('open') || state.editingId != cardId) return;
|
||||
if (restore) {
|
||||
inpTitle.value = draft.title || '';
|
||||
body.value = draft.body || '';
|
||||
if (scratchpadBody) scratchpadBody.value = draft.scratchpad || '';
|
||||
updatePreview();
|
||||
} else {
|
||||
clearDraft(cardId);
|
||||
}
|
||||
})();
|
||||
}
|
||||
} else if (draft && window._testRunning) {
|
||||
// During tests, silently discard any draft so it doesn't accumulate
|
||||
clearDraft(card?.id);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user