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
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 56s
This commit is contained in:
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
|
||||
|
||||
Reference in new issue
Block a user