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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s
This commit is contained in:
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;
|
||||||
true
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[blocks, canEdit, onUpdateBlock]
|
[blocks, canEdit, levelOffset, onUpdateBlock, onUpdateBlocks]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNavigateFocus = useCallback(
|
const handleNavigateFocus = useCallback(
|
||||||
|
|||||||
Reference in new issue
Block a user