Dev #4

Merged
sandrodach merged 12 commits from dev into main 2026-10-25 21:09:45 +01:00
Showing only changes of commit 634fdcac11 - Show all commits
@@ -246,9 +246,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}
};
const ensureLock = async (): Promise<boolean> => {
const ensureLock = async (force = false): Promise<boolean> => {
if (!selectedDoc || selectedDoc.permission === 'read') return false;
if (holdsLockRef.current) {
if (holdsLockRef.current && !force) {
return true;
}
try {
@@ -287,10 +287,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
if (idleUnlockTimerRef.current) {
clearTimeout(idleUnlockTimerRef.current);
}
// If a block is currently being dragged, do not schedule an unlock
if (isDraggingBlockRef.current) {
return;
}
// Short grace period: if user clicked into another block in the doc, focus will cancel this.
// If user clicked outside or stopped editing, save and release the lock immediately!
idleUnlockTimerRef.current = setTimeout(async () => {
if (!isEditorFocusedRef.current && holdsLockRef.current) {
if (!isEditorFocusedRef.current && !isDraggingBlockRef.current && holdsLockRef.current) {
await flushPendingSave();
await releaseMyLock();
}
@@ -523,13 +527,13 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
// Window blur / visibility change listener: release lock when user leaves tab/window
useEffect(() => {
const handleWindowBlur = () => {
if (!isEditorFocusedRef.current && holdsLockRef.current) {
if (!isEditorFocusedRef.current && !isDraggingBlockRef.current && holdsLockRef.current) {
flushPendingSave();
releaseMyLock();
}
};
const handleVisibilityChange = () => {
if (document.visibilityState === 'hidden') {
if (document.visibilityState === 'hidden' && !isDraggingBlockRef.current) {
isEditorFocusedRef.current = false;
flushPendingSave();
releaseMyLock();
@@ -1957,8 +1961,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
clearTimeout(idleUnlockTimerRef.current);
idleUnlockTimerRef.current = null;
}
// Activate write lock immediately upon dragging
ensureLock();
// Activate write lock immediately upon dragging (optimistic UI + backend broadcast)
setHoldsLock(true);
ensureLock(true);
e.dataTransfer.setData('application/mindsift-block-id', blockId);
e.dataTransfer.setData('text/plain', blockId);
@@ -1966,6 +1971,21 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
setDraggingBlockId(blockId);
};
const handleBlockDragEnd = () => {
isDraggingBlockRef.current = false;
setDraggingBlockId(null);
setDragOverBlockTarget(null);
if (idleUnlockTimerRef.current) {
clearTimeout(idleUnlockTimerRef.current);
}
idleUnlockTimerRef.current = setTimeout(async () => {
if (!isEditorFocusedRef.current && !isDraggingBlockRef.current && holdsLockRef.current) {
await flushPendingSave();
await releaseMyLock();
}
}, 2000);
};
const handleBlockDragOver = (e: React.DragEvent, targetBlockId: string) => {
e.preventDefault();
e.stopPropagation();
@@ -3596,25 +3616,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
clearTimeout(idleUnlockTimerRef.current);
idleUnlockTimerRef.current = null;
}
isDraggingBlockRef.current = true;
ensureLock();
ensureLock(true);
}
}}
onDragStart={(e) => !isReadOnly && handleBlockDragStart(e, block.id)}
onDragEnd={() => {
isDraggingBlockRef.current = false;
setDraggingBlockId(null);
setDragOverBlockTarget(null);
if (idleUnlockTimerRef.current) {
clearTimeout(idleUnlockTimerRef.current);
}
idleUnlockTimerRef.current = setTimeout(async () => {
if (!isEditorFocusedRef.current && !isDraggingBlockRef.current && holdsLockRef.current) {
await flushPendingSave();
await releaseMyLock();
}
}, 2500);
}}
onDragEnd={handleBlockDragEnd}
className="opacity-0 group-hover:opacity-100 flex items-center pt-1 text-faint hover:text-accent cursor-grab active:cursor-grabbing flex-shrink-0 -ml-1 mr-0.5"
title="Ziehen zum Verschieben"
>
@@ -3648,25 +3654,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
clearTimeout(idleUnlockTimerRef.current);
idleUnlockTimerRef.current = null;
}
isDraggingBlockRef.current = true;
ensureLock();
ensureLock(true);
}
}}
onDragStart={(e) => !isReadOnly && handleBlockDragStart(e, block.id)}
onDragEnd={() => {
isDraggingBlockRef.current = false;
setDraggingBlockId(null);
setDragOverBlockTarget(null);
if (idleUnlockTimerRef.current) {
clearTimeout(idleUnlockTimerRef.current);
}
idleUnlockTimerRef.current = setTimeout(async () => {
if (!isEditorFocusedRef.current && !isDraggingBlockRef.current && holdsLockRef.current) {
await flushPendingSave();
await releaseMyLock();
}
}, 2500);
}}
onDragEnd={handleBlockDragEnd}
onDragOver={(e) => {
if (!isReadOnly && draggingBlockId && draggingBlockId !== block.id) {
e.preventDefault();