Dev #8
No files matched your search
@@ -1072,22 +1072,60 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
||||
);
|
||||
|
||||
const handleMoveBlock = useCallback(
|
||||
(blockId: string, newIndex: number, level?: number) => {
|
||||
(blockId: string, targetIndex: number, newLevel?: number) => {
|
||||
if (!effectiveCanEdit) return;
|
||||
setBlocks((prev) => {
|
||||
const fromIdx = prev.findIndex((b) => b.id === blockId);
|
||||
if (fromIdx === -1) return prev;
|
||||
|
||||
const parentBlock = prev[fromIdx];
|
||||
const parentLevel = Math.max(0, parentBlock.level || 0);
|
||||
|
||||
// Calculate full subtree count (dragged block + all contiguous descendants)
|
||||
let count = 1;
|
||||
for (let i = fromIdx + 1; i < prev.length; i++) {
|
||||
if (Math.max(0, prev[i].level || 0) > parentLevel) {
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent dropping inside its own subtree
|
||||
if (targetIndex >= fromIdx && targetIndex < fromIdx + count) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const copy = [...prev];
|
||||
const [moving] = copy.splice(fromIdx, 1);
|
||||
const updatedMoving: DocumentBlock = {
|
||||
...moving,
|
||||
level: level !== undefined ? level : moving.level,
|
||||
};
|
||||
const boundedIdx = Math.max(0, Math.min(newIndex, copy.length));
|
||||
copy.splice(boundedIdx, 0, updatedMoving);
|
||||
const subtree = copy.splice(fromIdx, count);
|
||||
|
||||
// Adjust insertion index because removing elements from before shifts target index
|
||||
let insertIndex = targetIndex;
|
||||
if (fromIdx < targetIndex) {
|
||||
insertIndex = targetIndex - count;
|
||||
}
|
||||
insertIndex = Math.max(0, Math.min(insertIndex, copy.length));
|
||||
|
||||
// Calculate level adjustments if moving to a constrained position
|
||||
const prevBlockInPosition = insertIndex > 0 ? copy[insertIndex - 1] : null;
|
||||
const maxAllowed = prevBlockInPosition
|
||||
? Math.max(0, prevBlockInPosition.level || 0) + 1
|
||||
: 0;
|
||||
|
||||
const targetLevel = newLevel !== undefined ? newLevel : parentLevel;
|
||||
const clampedLevel = Math.min(targetLevel, maxAllowed);
|
||||
const levelDelta = clampedLevel - parentLevel;
|
||||
|
||||
const updatedSubtree = subtree.map((b) => ({
|
||||
...b,
|
||||
level: Math.max(0, (b.level || 0) + levelDelta),
|
||||
}));
|
||||
|
||||
copy.splice(insertIndex, 0, ...updatedSubtree);
|
||||
const normalized = ensureNonEmptyBlocks(docId, copy);
|
||||
|
||||
if (isLiveEnabled) {
|
||||
sendBlockMoved(blockId, boundedIdx, updatedMoving.level, normalized);
|
||||
sendBlockMoved(blockId, insertIndex, updatedSubtree[0].level, normalized);
|
||||
}
|
||||
return normalized;
|
||||
});
|
||||
|
||||
@@ -282,10 +282,29 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
|
||||
setDraggedIndex(index);
|
||||
}, []);
|
||||
|
||||
const handleDragOverBlock = useCallback((e: React.DragEvent, index: number) => {
|
||||
e.preventDefault();
|
||||
setDragOverIndex(index);
|
||||
}, []);
|
||||
const handleDragOverBlock = useCallback(
|
||||
(e: React.DragEvent, index: number) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex !== null) {
|
||||
const parentLevel = Math.max(0, blocks[draggedIndex]?.level || 0);
|
||||
let count = 1;
|
||||
for (let i = draggedIndex + 1; i < blocks.length; i++) {
|
||||
if (Math.max(0, blocks[i].level || 0) > parentLevel) {
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Do not allow dropping inside its own subtree (self or descendants)
|
||||
if (index >= draggedIndex && index < draggedIndex + count) {
|
||||
setDragOverIndex(null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
setDragOverIndex(index);
|
||||
},
|
||||
[blocks, draggedIndex]
|
||||
);
|
||||
|
||||
const handleDropBlock = useCallback(
|
||||
(targetIndex: number) => {
|
||||
@@ -294,6 +313,20 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
|
||||
setDragOverIndex(null);
|
||||
return;
|
||||
}
|
||||
const parentLevel = Math.max(0, blocks[draggedIndex]?.level || 0);
|
||||
let count = 1;
|
||||
for (let i = draggedIndex + 1; i < blocks.length; i++) {
|
||||
if (Math.max(0, blocks[i].level || 0) > parentLevel) {
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetIndex >= draggedIndex && targetIndex < draggedIndex + count) {
|
||||
setDraggedIndex(null);
|
||||
setDragOverIndex(null);
|
||||
return;
|
||||
}
|
||||
const movingBlock = blocks[draggedIndex];
|
||||
if (movingBlock) {
|
||||
onMoveBlock(movingBlock.id, targetIndex, movingBlock.level);
|
||||
@@ -372,7 +405,18 @@ export const OutlinerTree: React.FC<OutlinerTreeProps> = ({
|
||||
})}
|
||||
|
||||
{canEdit && (
|
||||
<div className="pt-2 pl-2">
|
||||
<div
|
||||
className={`pt-2 pl-2 transition-colors ${
|
||||
dragOverIndex === blocks.length ? 'border-t-2 border-accent bg-accent/10 rounded' : ''
|
||||
}`}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
if (draggedIndex !== null) {
|
||||
setDragOverIndex(blocks.length);
|
||||
}
|
||||
}}
|
||||
onDrop={() => handleDropBlock(blocks.length)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
|
||||
Reference in new issue
Block a user