fix(collab): fix self-lockout, stuck live sessions, block locking propagation and reactive hashtags
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s

This commit is contained in:
sandrodach committed 2026-10-27 14:57:30 +01:00
1 parent 0e06cc5577
commit 996f27133c
6 files changed
+303 -89

No files matched your search

@@ -267,7 +267,29 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
const [docs, setDocs] = useState<Document[]>([]);
const [folders, setFolders] = useState<string[]>([]);
const [sharedFolders, setSharedFolders] = useState<SharedFolder[]>([]);
const [tagsSummary, setTagsSummary] = useState<TagSummary[]>([]);
const [serverTagsSummary, setServerTagsSummary] = useState<TagSummary[]>([]);
// Dynamically compute hashtag counts directly from in-memory documents
const tagsSummary = useMemo(() => {
if (docs.length === 0) return serverTagsSummary;
const tagMap = new Map<string, number>();
for (const d of docs) {
for (const b of d.blocks || []) {
const matches = b.content?.match(/#([a-zA-Z0-9äöüÄÖÜß_-]+)/g);
if (matches) {
for (const m of matches) {
const clean = m.slice(1).toLowerCase();
tagMap.set(clean, (tagMap.get(clean) || 0) + 1);
}
}
}
}
const list: TagSummary[] = [];
tagMap.forEach((count, tag) => {
list.push({ tag, count });
});
return list.sort((a, b) => b.count - a.count || a.tag.localeCompare(b.tag));
}, [docs, serverTagsSummary]);
const [selectedDocId, setSelectedDocId] = useState<string | null>(initialDocId || null);
const [targetBlockId, setTargetBlockId] = useState<string | null>(null);
@@ -356,7 +378,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
setDocs(docsRes);
setFolders(foldersRes);
setSharedFolders(sharedFoldersRes);
setTagsSummary(tagsRes);
setServerTagsSummary(tagsRes);
setSelectedDocId((prevId) => {
if (prevId && docsRes.some((d) => d.id === prevId)) {
@@ -1196,32 +1218,64 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
{/* Interactive Live Collaboration Button (replaces passive radio icon) */}
{docLiveState === 'locked_by_other' ? (
<div
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-status-warn/15 border border-status-warn/40 text-status-warn select-none"
title={`Live-Zusammenarbeit aktiv durch ${docLiveInitiator || 'anderer Nutzer'}`}
>
<Lock className="w-3.5 h-3.5 shrink-0" />
<span className="truncate max-w-[120px]">Live: {docLiveInitiator || 'Gesperrt'}</span>
<div className="inline-flex items-center gap-1.5">
<div
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-status-warn/15 border border-status-warn/40 text-status-warn select-none"
title={`Live-Zusammenarbeit aktiv durch ${docLiveInitiator || 'anderer Nutzer'}`}
>
<Lock className="w-3.5 h-3.5 shrink-0" />
<span className="truncate max-w-[120px]">Live: {docLiveInitiator || 'Gesperrt'}</span>
</div>
{(selectedDocObj?.isOwner || selectedDocObj?.permission === 'owner') && (
<button
type="button"
onClick={() => liveToggleRef.current?.()}
className="px-2 py-1 rounded-btn text-xs font-semibold bg-status-warn/20 border border-status-warn/40 text-status-warn hover:bg-status-warn/30 transition-colors cursor-pointer"
title="Als Notiz-Besitzer Live-Sitzung beenden und Dokument freigeben"
>
Freigeben
</button>
)}
</div>
) : docLiveState === 'blocked' ? (
<button
type="button"
onClick={() => setShowWsBlockedModal(true)}
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-status-err/15 border border-status-err/40 text-status-err hover:bg-status-err/25 transition-colors cursor-pointer"
title="WebSockets im Firmennetzwerk blockiert – Hier klicken für Infos"
>
<AlertTriangle className="w-3.5 h-3.5 text-status-err animate-pulse shrink-0" />
<span>Live blockiert</span>
</button>
<div className="inline-flex items-center gap-1.5">
<button
type="button"
onClick={() => setShowWsBlockedModal(true)}
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-status-err/15 border border-status-err/40 text-status-err hover:bg-status-err/25 transition-colors cursor-pointer"
title="WebSockets im Firmennetzwerk blockiert – Hier klicken für Infos"
>
<AlertTriangle className="w-3.5 h-3.5 text-status-err animate-pulse shrink-0" />
<span>Live blockiert</span>
</button>
<button
type="button"
onClick={() => liveToggleRef.current?.()}
className="px-2 py-1 rounded-btn text-xs font-medium bg-canvas border border-border text-muted hover:text-status-err hover:border-status-err/50 transition-colors cursor-pointer"
title="Live-Sitzung beenden"
>
Beenden
</button>
</div>
) : docLiveState === 'connecting' ? (
<button
type="button"
disabled
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-amber-500/15 border border-amber-500/40 text-amber-500 opacity-80 cursor-wait"
>
<Loader2 className="w-3.5 h-3.5 animate-spin text-amber-500 shrink-0" />
<span>Verbinde…</span>
</button>
<div className="inline-flex items-center gap-1.5">
<button
type="button"
disabled
className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-btn text-xs font-medium bg-amber-500/15 border border-amber-500/40 text-amber-500 opacity-80 cursor-wait"
>
<Loader2 className="w-3.5 h-3.5 animate-spin text-amber-500 shrink-0" />
<span>Verbinde…</span>
</button>
<button
type="button"
onClick={() => liveToggleRef.current?.()}
className="px-2 py-1 rounded-btn text-xs font-medium bg-canvas border border-border text-muted hover:text-status-err hover:border-status-err/50 transition-colors cursor-pointer"
title="Verbindungsaufbau abbrechen"
>
Abbrechen
</button>
</div>
) : docLiveState === 'active' ? (
<button
type="button"
@@ -2547,7 +2601,17 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
</p>
</div>
<div className="flex justify-end pt-2">
<div className="flex justify-between items-center pt-2">
<button
type="button"
onClick={() => {
liveToggleRef.current?.();
setShowWsBlockedModal(false);
}}
className="px-3 py-1.5 text-xs font-medium bg-status-err/15 border border-status-err/40 text-status-err hover:bg-status-err/25 rounded-btn transition-colors cursor-pointer"
>
Live-Sitzung beenden
</button>
<button
type="button"
onClick={() => setShowWsBlockedModal(false)}