feat(outliner): indent and outdent entire subtree with all children on Tab and Shift+Tab
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s

This commit is contained in:
sandrodach committed 2026-10-27 16:06:31 +01:00
1 parent 1bdd7d245d
commit aea05e868a
2 files changed
+57 -11

No files matched your search

@@ -1029,6 +1029,25 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
[effectiveCanEdit, isLiveEnabled, sendBlockUpdated] [effectiveCanEdit, isLiveEnabled, sendBlockUpdated]
); );
const handleUpdateBlocks = useCallback(
(updatedBlocks: DocumentBlock[]) => {
if (!effectiveCanEdit || updatedBlocks.length === 0) return;
const map = new Map<string, DocumentBlock>();
for (const b of updatedBlocks) {
map.set(b.id, b);
}
setBlocks((prev) =>
prev.map((b) => map.get(b.id) || b)
);
if (isLiveEnabled) {
for (const b of updatedBlocks) {
sendBlockUpdated(b);
}
}
},
[effectiveCanEdit, isLiveEnabled, sendBlockUpdated]
);
const handleCreateBlock = useCallback( const handleCreateBlock = useCallback(
(newBlock: DocumentBlock, afterBlockId?: string) => { (newBlock: DocumentBlock, afterBlockId?: string) => {
if (!effectiveCanEdit) return; if (!effectiveCanEdit) return;
@@ -1531,6 +1550,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
remoteCursors={remoteCursors} remoteCursors={remoteCursors}
sendCursorMoved={sendCursorMoved} sendCursorMoved={sendCursorMoved}
onUpdateBlock={handleUpdateBlock} onUpdateBlock={handleUpdateBlock}
onUpdateBlocks={handleUpdateBlocks}
onCreateBlock={handleCreateBlock} onCreateBlock={handleCreateBlock}
onDeleteBlock={handleDeleteBlock} onDeleteBlock={handleDeleteBlock}
onMoveBlock={handleMoveBlock} onMoveBlock={handleMoveBlock}
@@ -1599,6 +1619,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
remoteCursors={remoteCursors} remoteCursors={remoteCursors}
sendCursorMoved={sendCursorMoved} sendCursorMoved={sendCursorMoved}
onUpdateBlock={handleUpdateBlock} onUpdateBlock={handleUpdateBlock}
onUpdateBlocks={handleUpdateBlocks}
onCreateBlock={handleCreateBlock} onCreateBlock={handleCreateBlock}
onDeleteBlock={handleDeleteBlock} onDeleteBlock={handleDeleteBlock}
onMoveBlock={handleMoveBlock} onMoveBlock={handleMoveBlock}
@@ -13,6 +13,7 @@ export interface OutlinerTreeProps {
remoteCursors?: Record<string, RemoteCursor>; remoteCursors?: Record<string, RemoteCursor>;
sendCursorMoved?: (blockId: string | null, selectionStart?: number, selectionEnd?: number) => void; sendCursorMoved?: (blockId: string | null, selectionStart?: number, selectionEnd?: number) => void;
onUpdateBlock: (updated: DocumentBlock, immediate?: boolean) => void; onUpdateBlock: (updated: DocumentBlock, immediate?: boolean) => void;
onUpdateBlocks?: (updated: DocumentBlock[]) => void;
onCreateBlock: (newBlock: DocumentBlock, afterBlockId?: string) => void; onCreateBlock: (newBlock: DocumentBlock, afterBlockId?: string) => void;
onDeleteBlock: (blockId: string) => void; onDeleteBlock: (blockId: string) => void;
onMoveBlock: (blockId: string, newIndex: number, level?: number) => void; onMoveBlock: (blockId: string, newIndex: number, level?: number) => void;
@@ -34,6 +35,7 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
remoteCursors, remoteCursors,
sendCursorMoved, sendCursorMoved,
onUpdateBlock, onUpdateBlock,
onUpdateBlocks,
onCreateBlock, onCreateBlock,
onDeleteBlock, onDeleteBlock,
onMoveBlock, onMoveBlock,
@@ -172,7 +174,7 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
} }
return result; return result;
}, [blocks]); }, [blocks, levelOffset]);
const handleCreateBlockBelow = useCallback( const handleCreateBlockBelow = useCallback(
(afterBlockId: string, _currentContent: string) => { (afterBlockId: string, _currentContent: string) => {
@@ -241,9 +243,9 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
const current = blocks[idx]; const current = blocks[idx];
const currentLevel = Math.max(0, current.level || 0); const currentLevel = Math.max(0, current.level || 0);
const prevLevel = idx > 0 ? Math.max(0, blocks[idx - 1].level || 0) : 0; const prevLevel = idx > 0 ? Math.max(0, blocks[idx - 1].level || 0) : 0;
const maxLevel = idx === 0 ? 0 : prevLevel + 1;
const minLevel = levelOffset || 0; const minLevel = levelOffset || 0;
const maxLevel = idx === 0 ? minLevel : prevLevel + 1;
const nextLevel = const nextLevel =
delta === 1 delta === 1
? Math.min(maxLevel, currentLevel + 1) ? Math.min(maxLevel, currentLevel + 1)
@@ -251,15 +253,38 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
if (nextLevel === currentLevel) return; if (nextLevel === currentLevel) return;
onUpdateBlock( const actualDelta = nextLevel - currentLevel;
{
...current, // Find all contiguous children / descendants:
level: nextLevel, // contiguous blocks with level > currentLevel
let count = 1;
for (let i = idx + 1; i < blocks.length; i++) {
if (Math.max(0, blocks[i].level || 0) > currentLevel) {
count++;
} else {
break;
}
}
const updatedBlocks: DocumentBlock[] = [];
for (let i = idx; i < idx + count; i++) {
const b = blocks[i];
const newLvl = Math.max(minLevel, (b.level || 0) + actualDelta);
updatedBlocks.push({
...b,
level: newLvl,
});
}
if (onUpdateBlocks) {
onUpdateBlocks(updatedBlocks);
} else {
for (const b of updatedBlocks) {
onUpdateBlock(b, true);
}
}
}, },
true [blocks, canEdit, levelOffset, onUpdateBlock, onUpdateBlocks]
);
},
[blocks, canEdit, onUpdateBlock]
); );
const handleNavigateFocus = useCallback( const handleNavigateFocus = useCallback(