Dev #2

Merged
sandrodach merged 37 commits from dev into main 2026-10-25 12:14:23 +01:00
Showing only changes of commit 0affb827a4 - Show all commits
@@ -611,12 +611,18 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
const handleLeaveShare = async () => { const handleLeaveShare = async () => {
if (!selectedDoc || !currentUser) return; if (!selectedDoc || !currentUser) return;
if ( const totalBlocks = selectedDoc.blocks?.length || 0;
!window.confirm( const subpointsCount = (selectedDoc.blocks || []).filter((b) => (b.level || 0) > 0).length;
'Möchtest du diese freigegebene Notiz wirklich aus deinem Workspace entfernen?' const title = selectedDoc.title || 'Geteilte Notiz';
)
) let confirmMsg = 'Möchtest du diese freigegebene Notiz wirklich aus deinem Workspace entfernen?';
return; 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 { try {
await api.deleteDocument(selectedDoc.id); await api.deleteDocument(selectedDoc.id);
setShowShareModal(false); setShowShareModal(false);
@@ -691,7 +697,21 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}; };
const handleDeleteDoc = async (id: string) => { 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); triggerHaptic(20);
await api.deleteDocument(id); await api.deleteDocument(id);
const remaining = docs.filter((d) => d.id !== id); const remaining = docs.filter((d) => d.id !== id);
@@ -953,25 +973,79 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
} }
}; };
// Delete block and focus the preceding one // Delete block and all its descendant sub-bullets with confirmation
const deleteBlockAndFocusPrev = (index: number) => { const removeBlock = (blockId: string): boolean => {
if (isReadOnly || !selectedDoc || selectedDoc.blocks.length <= 1) return; if (isReadOnly || !selectedDoc) return false;
const prevBlock = selectedDoc.blocks[index - 1] || selectedDoc.blocks[index + 1]; const blocks = selectedDoc.blocks;
const targetId = selectedDoc.blocks[index].id; const index = blocks.findIndex((b) => b.id === blockId);
removeBlock(targetId); 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) { if (prevBlock) {
setLastFocusedBlockId(prevBlock.id); setLastFocusedBlockId(prevBlock.id);
setTimeout(() => { setTimeout(() => {
const el = blockInputRefs.current[prevBlock.id]; const el = blockInputRefs.current[prevBlock.id];
if (el) { if (el) {
el.focus(); el.focus();
// Put cursor at end of text if ('setSelectionRange' in el && typeof el.value === 'string') {
if ('setSelectionRange' in el) {
el.setSelectionRange(el.value.length, el.value.length); 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<DocumentBlock>) => { const updateBlock = (blockId: string, patch: Partial<DocumentBlock>) => {
@@ -982,12 +1056,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
persistDocument({ ...selectedDoc, blocks: nextBlocks }); 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) => { const toggleCollapse = (blockId: string) => {
if (!selectedDoc) return; if (!selectedDoc) return;
const block = selectedDoc.blocks.find((b) => b.id === blockId); const block = selectedDoc.blocks.find((b) => b.id === blockId);
@@ -1050,13 +1118,13 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
indentBlock(index); indentBlock(index);
} }
} else if (e.key === 'Backspace' && (!block.content || block.content === '')) { } 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(); e.preventDefault();
setSlashBlockId(null); setSlashBlockId(null);
deleteBlockAndFocusPrev(index); deleteBlockAndFocusPrev(index);
} else if ((block.level || 0) > 0) {
e.preventDefault();
outdentBlock(block.id);
} }
} }
}; };