feat(workspace): per-user note pinning, real-time unlock synchronization, and icon-only share button
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 56s

This commit is contained in:
sandrodach committed 2026-10-25 10:39:52 +01:00
1 parent ec79a2493e
commit af7a9eb200
5 files changed
+183 -47

No files matched your search

@@ -43,6 +43,7 @@ import {
Lock,
Unlock,
Eye,
Share,
Share2,
} from 'lucide-react';
import { BlockType, Document, DocumentBlock, DocLockInfo, DocShare, User } from '../../lib/types';
@@ -373,15 +374,28 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
};
}, [selectedDoc?.id]);
// Flush pending save before tab closes or unmounts
// Flush pending save and release lock before tab closes or unmounts
useEffect(() => {
const handleBeforeUnload = () => {
flushPendingSave();
if (currentDocIdRef.current && holdsLockRef.current) {
try {
const token = localStorage.getItem('mindsift_token') || sessionStorage.getItem('mindsift_token');
fetch(`/api/v1/documents/${currentDocIdRef.current}/lock`, {
method: 'DELETE',
headers: token ? { Authorization: `Bearer ${token}` } : {},
keepalive: true,
}).catch(() => {});
} catch {}
}
};
window.addEventListener('beforeunload', handleBeforeUnload);
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
flushPendingSave();
if (currentDocIdRef.current && holdsLockRef.current) {
releaseMyLock(currentDocIdRef.current);
}
};
}, []);
@@ -399,8 +413,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}
}
} else if (msg.event === 'DOC_UNLOCKED') {
const payload = msg.payload as { doc_id: string };
if (payload && payload.doc_id === selectedDoc?.id) {
const payload = msg.payload as { docId?: string; doc_id?: string; id?: string };
const unlockedDocId = payload?.docId || payload?.doc_id || payload?.id;
if (unlockedDocId && unlockedDocId === selectedDoc?.id) {
setActiveLock(null);
}
} else if (msg.event === 'DOC_CREATED') {
@@ -413,11 +428,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
const updated = msg.payload as Document;
if (updated && updated.id) {
setDocs((prev) =>
prev.map((d) => (d.id === updated.id ? { ...d, ...updated } : d))
prev.map((d) => (d.id === updated.id ? { ...d, ...updated, isPinned: d.isPinned } : d))
);
if (selectedDoc?.id === updated.id) {
if (!holdsLockRef.current) {
setSelectedDoc((prev) => (prev ? { ...prev, ...updated } : updated));
setSelectedDoc((prev) => (prev ? { ...prev, ...updated, isPinned: prev.isPinned } : updated));
}
}
onStatsChange();
@@ -1915,16 +1930,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
<button
type="button"
onClick={openShareModal}
className="btn-icon flex items-center gap-1 px-2 text-xs"
className="btn-icon"
title="Notiz teilen und Berechtigungen"
>
<Share2 className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Teilen</span>
{selectedDoc.sharedWith && selectedDoc.sharedWith.length > 0 && (
<span className="ml-0.5 px-1 rounded-full bg-accent/20 text-accent text-[10px] font-bold">
{selectedDoc.sharedWith.length}
</span>
)}
<Share className="w-3.5 h-3.5" />
</button>
<button