Dev #8

Merged
sandrodach merged 30 commits from dev into main 2026-10-27 22:02:54 +01:00
Showing only changes of commit 183199401d - Show all commits
@@ -197,6 +197,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
currentTitleRef.current = title;
const currentBlocksRef = useRef<DocumentBlock[]>(blocks);
currentBlocksRef.current = blocks;
const lastKnownUpdatedAtRef = useRef<string | null>(initialDoc?.updatedAt || null);
useEffect(() => {
if (!loading && (autoFocusTitle || (doc?.title === 'Neue Notiz' && (!doc?.blocks || doc?.blocks.length <= 1)))) {
@@ -434,6 +435,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
});
isSavingRef.current = false;
lastSavedSnapshotRef.current = snapshot;
lastKnownUpdatedAtRef.current = updated.updatedAt || null;
setSaveStatus('saved');
setSaveErrorMessage(null);
onSaveStatusChange?.('saved');
@@ -646,6 +648,9 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
});
lastSavedSnapshotRef.current = newSnapshot;
currentSnapshotRef.current = newSnapshot;
if (remoteDoc.updatedAt) {
lastKnownUpdatedAtRef.current = remoteDoc.updatedAt;
}
if (docRef.current) {
docRef.current = { ...docRef.current, ...remoteDoc };
@@ -715,32 +720,61 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
[docId]
);
// Periodic poll for locks & updates (guarantees synchronization even across flaky networks)
// Sync when initialDoc prop changes externally
useEffect(() => {
if (initialDoc && initialDoc.id === docId && initialDoc.updatedAt && initialDoc.updatedAt !== lastKnownUpdatedAtRef.current) {
lastKnownUpdatedAtRef.current = initialDoc.updatedAt;
handleRemoteDocUpdated(initialDoc);
}
}, [initialDoc, docId, handleRemoteDocUpdated]);
// Periodic poll for locks & document text (guarantees text synchronization even without WebSockets)
useEffect(() => {
if (!docId) return;
const syncLocks = () => {
api.getDocLocks(docId)
.then((res) => {
if (Array.isArray(res.blockLocks)) {
const map: Record<string, BlockLockInfo> = {};
for (const l of res.blockLocks) {
map[l.blockId] = l;
}
setActiveBlockLocks(map);
let isCancelled = false;
const syncLocksAndDoc = async () => {
try {
// 1. Sync locks
const locksRes = await api.getDocLocks(docId);
if (isCancelled) return;
if (Array.isArray(locksRes.blockLocks)) {
const map: Record<string, BlockLockInfo> = {};
for (const l of locksRes.blockLocks) {
map[l.blockId] = l;
}
if (res.liveSession) {
setLiveSession(res.liveSession);
} else {
setLiveSession(null);
setActiveBlockLocks(map);
}
if (locksRes.liveSession) {
setLiveSession(locksRes.liveSession);
} else {
setLiveSession(null);
}
// 2. Sync document content if not currently actively saving
if (!isSavingRef.current) {
const remoteDoc = await api.getDocument(docId);
if (isCancelled || !remoteDoc) return;
// Check if server has newer or different state
if (remoteDoc.updatedAt && remoteDoc.updatedAt !== lastKnownUpdatedAtRef.current) {
console.log(`[DocumentEditor] Polling: Neuer Text-Stand vom Server (${remoteDoc.updatedAt}). Aktualisiere nicht-fokussierte Blöcke...`);
lastKnownUpdatedAtRef.current = remoteDoc.updatedAt;
handleRemoteDocUpdated(remoteDoc);
}
})
.catch(() => {});
}
} catch (err) {
// ignore poll errors
}
};
syncLocks();
const interval = window.setInterval(syncLocks, 3000);
return () => window.clearInterval(interval);
}, [docId]);
syncLocksAndDoc();
const interval = window.setInterval(syncLocksAndDoc, 2000);
return () => {
isCancelled = true;
window.clearInterval(interval);
};
}, [docId, handleRemoteDocUpdated]);
// Subscribe to realtime lock & live session events
useEffect(() => {
@@ -842,7 +876,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
focusedBlockIdRef.current = null;
}
api.unlockBlock(docId, blockId).catch(() => {});
executeAutoSave();
executeAutoSave(true);
},
[docId, executeAutoSave]
);