Dev #7
No files matched your search
@@ -5,7 +5,6 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
Download,
|
Download,
|
||||||
FileText,
|
FileText,
|
||||||
Radio,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Hash,
|
Hash,
|
||||||
@@ -18,7 +17,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Document, DocumentBlock, User } from '../../lib/types';
|
import { Document, DocumentBlock, User } from '../../lib/types';
|
||||||
import { api } from '../../lib/api';
|
import { api } from '../../lib/api';
|
||||||
import { useDocumentSync, DocSyncEvent, RemoteCursor } from '../../lib/useDocumentSync';
|
import { useDocumentSync, DocSyncEvent, RemoteCursor, DocSyncStatus } from '../../lib/useDocumentSync';
|
||||||
import { OutlinerTree } from './OutlinerTree';
|
import { OutlinerTree } from './OutlinerTree';
|
||||||
|
|
||||||
export interface DocumentEditorProps {
|
export interface DocumentEditorProps {
|
||||||
@@ -34,6 +33,7 @@ export interface DocumentEditorProps {
|
|||||||
onSelectTag: (tag: string) => void;
|
onSelectTag: (tag: string) => void;
|
||||||
onWikiLinkClick?: (pageTitle: string) => void;
|
onWikiLinkClick?: (pageTitle: string) => void;
|
||||||
onBackMobile?: () => void;
|
onBackMobile?: () => void;
|
||||||
|
onSyncStatusChange?: (status: DocSyncStatus) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒'];
|
const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒'];
|
||||||
@@ -153,6 +153,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
onSelectTag,
|
onSelectTag,
|
||||||
onWikiLinkClick,
|
onWikiLinkClick,
|
||||||
onBackMobile,
|
onBackMobile,
|
||||||
|
onSyncStatusChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [doc, setDoc] = useState<Document | null>(initialDoc || null);
|
const [doc, setDoc] = useState<Document | null>(initialDoc || null);
|
||||||
const [title, setTitle] = useState<string>(initialDoc?.title || '');
|
const [title, setTitle] = useState<string>(initialDoc?.title || '');
|
||||||
@@ -344,6 +345,10 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
sendTitleUpdated,
|
sendTitleUpdated,
|
||||||
} = useDocumentSync(docId, handleRemoteEvent, canEdit, currentUser);
|
} = useDocumentSync(docId, handleRemoteEvent, canEdit, currentUser);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onSyncStatusChange?.(syncStatus);
|
||||||
|
}, [syncStatus, onSyncStatusChange]);
|
||||||
|
|
||||||
// Fetch isolated document state on mount
|
// Fetch isolated document state on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
@@ -750,34 +755,6 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
{/* Live Sync Status (1:1 styling from SharedListsView) */}
|
|
||||||
<div
|
|
||||||
className={`h-8 w-8 flex items-center justify-center rounded-btn border transition-colors ${
|
|
||||||
syncStatus === 'connected'
|
|
||||||
? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-500'
|
|
||||||
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
|
||||||
? 'bg-amber-500/10 border-amber-500/30 text-amber-500'
|
|
||||||
: 'bg-canvas border-border text-muted'
|
|
||||||
}`}
|
|
||||||
title={
|
|
||||||
syncStatus === 'connected'
|
|
||||||
? 'Live synchronisiert (WebSocket online)'
|
|
||||||
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
|
||||||
? 'Verbindung wird aufgebaut…'
|
|
||||||
: 'Offline'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Radio
|
|
||||||
className={`w-3.5 h-3.5 ${
|
|
||||||
syncStatus === 'connected'
|
|
||||||
? 'animate-pulse'
|
|
||||||
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
|
||||||
? 'animate-spin'
|
|
||||||
: ''
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleTogglePin}
|
onClick={handleTogglePin}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
PanelLeftClose,
|
PanelLeftClose,
|
||||||
PanelLeftOpen,
|
PanelLeftOpen,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
|
Radio,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
@@ -34,7 +35,16 @@ import {
|
|||||||
UnifiedShare,
|
UnifiedShare,
|
||||||
User,
|
User,
|
||||||
} from '../../lib/types';
|
} from '../../lib/types';
|
||||||
import { api, onWebSocketMessage, triggerHaptic } from '../../lib/api';
|
import {
|
||||||
|
api,
|
||||||
|
onWebSocketMessage,
|
||||||
|
triggerHaptic,
|
||||||
|
onRealtimeStatus,
|
||||||
|
isRealtimeConnected,
|
||||||
|
getRealtimeTransport,
|
||||||
|
RealtimeTransport,
|
||||||
|
} from '../../lib/api';
|
||||||
|
import { DocSyncStatus } from '../../lib/useDocumentSync';
|
||||||
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
|
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
|
||||||
import { useMobileActions } from '../../context/MobileActionContext';
|
import { useMobileActions } from '../../context/MobileActionContext';
|
||||||
import { DocumentEditor } from './DocumentEditor';
|
import { DocumentEditor } from './DocumentEditor';
|
||||||
@@ -289,6 +299,19 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Realtime connection status for top header indicator (identical to SharedListsView)
|
||||||
|
const [realtimeConnected, setRealtimeConnected] = useState<boolean>(() => isRealtimeConnected());
|
||||||
|
const [realtimeTransport, setRealtimeTransport] = useState<RealtimeTransport>(() => getRealtimeTransport());
|
||||||
|
const [activeDocSyncStatus, setActiveDocSyncStatus] = useState<DocSyncStatus>('connected');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsub = onRealtimeStatus((connected, transport) => {
|
||||||
|
setRealtimeConnected(connected);
|
||||||
|
setRealtimeTransport(transport);
|
||||||
|
});
|
||||||
|
return unsub;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Drag and Drop state
|
// Drag and Drop state
|
||||||
const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
|
const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
|
||||||
const [draggingFolderPath, setDraggingFolderPath] = useState<string | null>(null);
|
const [draggingFolderPath, setDraggingFolderPath] = useState<string | null>(null);
|
||||||
@@ -1096,6 +1119,47 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{tagsSummary.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{tagsSummary.length}</span>}
|
{tagsSummary.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{tagsSummary.length}</span>}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Right Header Section: Live Sync Status (identical to SharedListsView) */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{(() => {
|
||||||
|
const isDocActive = subView === 'editor' && Boolean(selectedDocId);
|
||||||
|
const isConnected = isDocActive
|
||||||
|
? activeDocSyncStatus === 'connected'
|
||||||
|
: realtimeConnected;
|
||||||
|
const isConnecting = isDocActive && (activeDocSyncStatus === 'connecting' || activeDocSyncStatus === 'reconnecting');
|
||||||
|
const isSSE = !isDocActive && realtimeTransport === 'sse';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`h-8 w-8 flex items-center justify-center rounded-btn border transition-colors ${
|
||||||
|
isConnected
|
||||||
|
? isSSE
|
||||||
|
? 'bg-teal-500/10 border-teal-500/30 text-teal-400'
|
||||||
|
: 'bg-emerald-500/10 border-emerald-500/30 text-emerald-500'
|
||||||
|
: isConnecting
|
||||||
|
? 'bg-amber-500/10 border-amber-500/30 text-amber-500'
|
||||||
|
: 'bg-canvas border-border text-muted'
|
||||||
|
}`}
|
||||||
|
title={
|
||||||
|
isConnected
|
||||||
|
? isSSE
|
||||||
|
? 'Live synchronisiert (Server-Sent Events / Proxy-Stream)'
|
||||||
|
: 'Live synchronisiert (WebSocket online)'
|
||||||
|
: isConnecting
|
||||||
|
? 'Verbindung wird aufgebaut…'
|
||||||
|
: 'Offline'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Radio
|
||||||
|
className={`w-3.5 h-3.5 ${
|
||||||
|
isConnected ? 'animate-pulse' : isConnecting ? 'animate-spin' : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main Workspace Split Pane */}
|
{/* Main Workspace Split Pane */}
|
||||||
@@ -1336,6 +1400,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
onSelectTag={handleSelectTag}
|
onSelectTag={handleSelectTag}
|
||||||
onWikiLinkClick={handleWikiLinkClick}
|
onWikiLinkClick={handleWikiLinkClick}
|
||||||
onBackMobile={() => setMobileShowDetail(false)}
|
onBackMobile={() => setMobileShowDetail(false)}
|
||||||
|
onSyncStatusChange={setActiveDocSyncStatus}
|
||||||
/>
|
/>
|
||||||
) : subView === 'tags' ? (
|
) : subView === 'tags' ? (
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
|
|||||||
Reference in new issue
Block a user