From 0affb827a42575efbe8648d6c7bde97dec94cbe3 Mon Sep 17 00:00:00 2001 From: Sandro Date: Sun, 25 Oct 2026 11:26:36 +0100 Subject: [PATCH] feat(workspace): confirm deletion of notes and bullets with nested subpoints --- .../src/features/workspace/WorkspaceView.tsx | 120 ++++++++++++++---- 1 file changed, 94 insertions(+), 26 deletions(-) diff --git a/frontend/src/features/workspace/WorkspaceView.tsx b/frontend/src/features/workspace/WorkspaceView.tsx index 1169067..d77f00c 100644 --- a/frontend/src/features/workspace/WorkspaceView.tsx +++ b/frontend/src/features/workspace/WorkspaceView.tsx @@ -611,12 +611,18 @@ export const WorkspaceView: React.FC = ({ const handleLeaveShare = async () => { if (!selectedDoc || !currentUser) return; - if ( - !window.confirm( - 'Möchtest du diese freigegebene Notiz wirklich aus deinem Workspace entfernen?' - ) - ) - return; + const totalBlocks = selectedDoc.blocks?.length || 0; + const subpointsCount = (selectedDoc.blocks || []).filter((b) => (b.level || 0) > 0).length; + const title = selectedDoc.title || 'Geteilte Notiz'; + + let confirmMsg = 'Möchtest du diese freigegebene Notiz wirklich aus deinem Workspace entfernen?'; + if (totalBlocks > 0 && subpointsCount > 0) { + confirmMsg = `Möchtest du die geteilte Notiz "${title}" mit allen ${totalBlocks} Punkten (inklusive ${subpointsCount} Unterpunkten) wirklich aus deinem Workspace entfernen?`; + } else if (totalBlocks > 0) { + confirmMsg = `Möchtest du die geteilte Notiz "${title}" wirklich aus deinem Workspace entfernen?`; + } + + if (!window.confirm(confirmMsg)) return; try { await api.deleteDocument(selectedDoc.id); setShowShareModal(false); @@ -691,7 +697,21 @@ export const WorkspaceView: React.FC = ({ }; const handleDeleteDoc = async (id: string) => { - if (!window.confirm('Dokument wirklich löschen?')) return; + const docToDelete = docs.find((d) => d.id === id) || (selectedDoc?.id === id ? selectedDoc : null); + const title = docToDelete?.title || selectedDoc?.title || 'Notiz'; + const totalBlocks = docToDelete?.blocks?.length || 0; + const subpointsCount = (docToDelete?.blocks || []).filter((b) => (b.level || 0) > 0).length; + + let confirmMsg = `Möchtest du die Notiz "${title}" wirklich löschen?`; + if (totalBlocks > 0) { + if (subpointsCount > 0) { + confirmMsg = `Möchtest du die Notiz "${title}" mit allen ${totalBlocks} Aufzählungspunkten (inklusive ${subpointsCount} Unterpunkten) wirklich unwiderruflich löschen?`; + } else { + confirmMsg = `Möchtest du die Notiz "${title}" mit allen ${totalBlocks} Aufzählungspunkten wirklich löschen?`; + } + } + + if (!window.confirm(confirmMsg)) return; triggerHaptic(20); await api.deleteDocument(id); const remaining = docs.filter((d) => d.id !== id); @@ -953,25 +973,79 @@ export const WorkspaceView: React.FC = ({ } }; - // Delete block and focus the preceding one - const deleteBlockAndFocusPrev = (index: number) => { - if (isReadOnly || !selectedDoc || selectedDoc.blocks.length <= 1) return; - const prevBlock = selectedDoc.blocks[index - 1] || selectedDoc.blocks[index + 1]; - const targetId = selectedDoc.blocks[index].id; - removeBlock(targetId); + // Delete block and all its descendant sub-bullets with confirmation + const removeBlock = (blockId: string): boolean => { + if (isReadOnly || !selectedDoc) return false; + const blocks = selectedDoc.blocks; + const index = blocks.findIndex((b) => b.id === blockId); + if (index === -1) return false; + + const targetBlock = blocks[index]; + const targetLevel = targetBlock.level || 0; + + // Find all descendant sub-bullets directly or indirectly nested under this block + const descendantIds: string[] = []; + for (let i = index + 1; i < blocks.length; i++) { + const cur = blocks[i]; + const curLevel = cur.level || 0; + if (curLevel <= targetLevel) break; + descendantIds.push(cur.id); + } + + if (descendantIds.length > 0) { + const count = descendantIds.length; + const snippet = targetBlock.content?.trim() ? ` "${targetBlock.content.trim().slice(0, 30)}..."` : ''; + const confirmText = + count === 1 + ? `Dieser Aufzählungspunkt${snippet} enthält 1 Unterpunkt. Möchtest du ihn mitsamt dem Unterpunkt wirklich löschen?` + : `Dieser Aufzählungspunkt${snippet} enthält ${count} Unterpunkte. Möchtest du ihn mitsamt allen ${count} Unterpunkten wirklich löschen?`; + if (!window.confirm(confirmText)) { + return false; + } + } + + triggerHaptic(15); + const idsToDelete = new Set([blockId, ...descendantIds]); + let nextBlocks = blocks.filter((b) => !idsToDelete.has(b.id)); + + if (nextBlocks.length === 0) { + nextBlocks = [ + { + id: `blk_${Date.now()}_1`, + type: 'bullet', + content: '', + level: 0, + }, + ]; + } + + // Determine fallback block to focus + const prevBlock = index > 0 ? blocks[index - 1] : nextBlocks[0]; + + persistDocument({ ...selectedDoc, blocks: nextBlocks }); + if (prevBlock) { setLastFocusedBlockId(prevBlock.id); setTimeout(() => { const el = blockInputRefs.current[prevBlock.id]; if (el) { el.focus(); - // Put cursor at end of text - if ('setSelectionRange' in el) { + if ('setSelectionRange' in el && typeof el.value === 'string') { el.setSelectionRange(el.value.length, el.value.length); } } - }, 20); + }, 30); } + + return true; + }; + + // Delete block and focus the preceding one + const deleteBlockAndFocusPrev = (index: number) => { + if (isReadOnly || !selectedDoc) return; + const targetBlock = selectedDoc.blocks[index]; + if (!targetBlock) return; + removeBlock(targetBlock.id); }; const updateBlock = (blockId: string, patch: Partial) => { @@ -982,12 +1056,6 @@ export const WorkspaceView: React.FC = ({ persistDocument({ ...selectedDoc, blocks: nextBlocks }); }; - const removeBlock = (blockId: string) => { - if (isReadOnly || !selectedDoc || selectedDoc.blocks.length <= 1) return; - const nextBlocks = selectedDoc.blocks.filter((b) => b.id !== blockId); - persistDocument({ ...selectedDoc, blocks: nextBlocks }); - }; - const toggleCollapse = (blockId: string) => { if (!selectedDoc) return; const block = selectedDoc.blocks.find((b) => b.id === blockId); @@ -1050,13 +1118,13 @@ export const WorkspaceView: React.FC = ({ indentBlock(index); } } else if (e.key === 'Backspace' && (!block.content || block.content === '')) { - if (selectedDoc?.blocks && selectedDoc.blocks.length > 1) { + if ((block.level || 0) > 0) { + e.preventDefault(); + outdentBlock(block.id); + } else if (selectedDoc?.blocks && selectedDoc.blocks.length > 1) { e.preventDefault(); setSlashBlockId(null); deleteBlockAndFocusPrev(index); - } else if ((block.level || 0) > 0) { - e.preventDefault(); - outdentBlock(block.id); } } };