Dev #7
No files matched your search
@@ -1,12 +1,11 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Pin,
|
Pin,
|
||||||
Share2,
|
Users,
|
||||||
Trash2,
|
Trash2,
|
||||||
Download,
|
Download,
|
||||||
Folder as FolderIcon,
|
Folder as FolderIcon,
|
||||||
Wifi,
|
Radio,
|
||||||
WifiOff,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
Hash,
|
Hash,
|
||||||
@@ -590,16 +589,16 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col h-full min-h-0 bg-canvas overflow-y-auto">
|
<div className="flex-1 flex flex-col h-full min-h-0 bg-canvas overflow-y-auto">
|
||||||
{/* Top Document Action Bar */}
|
{/* Top Document Action Bar */}
|
||||||
<div className="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-2 px-4 sm:px-6 py-2.5 bg-panel border-b border-border shadow-sm">
|
<div className="sticky top-0 z-20 flex flex-wrap items-center justify-between gap-2 px-4 sm:px-6 py-2 bg-canvas border-b border-border">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
{onBackMobile && (
|
{onBackMobile && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onBackMobile}
|
onClick={onBackMobile}
|
||||||
className="lg:hidden p-1.5 rounded-btn text-muted hover:text-main hover:bg-panel-elevated"
|
className="lg:hidden btn-icon"
|
||||||
title="Zurück zur Seitenleiste"
|
title="Zurück zur Seitenleiste"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-4 h-4" />
|
<ArrowLeft className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -618,7 +617,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showFolderMenu && canEdit && (
|
{showFolderMenu && canEdit && (
|
||||||
<div className="absolute left-0 mt-1 w-52 panel-surface shadow-panel border border-border rounded-card py-1 z-30 max-h-60 overflow-y-auto">
|
<div className="absolute left-0 mt-1 w-52 bg-canvas shadow-panel border border-border rounded-card py-1 z-30 max-h-60 overflow-y-auto">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleMoveToFolder('')}
|
onClick={() => handleMoveToFolder('')}
|
||||||
@@ -654,8 +653,8 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Right Status & Action Buttons */}
|
{/* Right Status & Action Buttons */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1.5">
|
||||||
{/* Active Presence Avatars */}
|
{/* Active Presence Avatars (Immer rund: rounded-full mit Überlappung) */}
|
||||||
{activeUsers.length > 0 && (
|
{activeUsers.length > 0 && (
|
||||||
<div className="flex items-center -space-x-1.5 mr-1" title="Aktive Bearbeiter im Dokument">
|
<div className="flex items-center -space-x-1.5 mr-1" title="Aktive Bearbeiter im Dokument">
|
||||||
{activeUsers.map((u) => {
|
{activeUsers.map((u) => {
|
||||||
@@ -668,13 +667,13 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div key={u.clientId} className="relative group/avatar cursor-default">
|
<div key={u.clientId} className="relative group/avatar cursor-default">
|
||||||
<div
|
<div
|
||||||
className="w-5 h-5 rounded-btn flex items-center justify-center text-[9px] font-bold text-white shadow-sm ring-1 ring-panel"
|
className="w-6 h-6 rounded-full flex items-center justify-center text-[10px] font-bold text-white shadow-sm ring-2 ring-canvas"
|
||||||
style={{ backgroundColor: u.userColor }}
|
style={{ backgroundColor: u.userColor }}
|
||||||
>
|
>
|
||||||
{initials}
|
{initials}
|
||||||
</div>
|
</div>
|
||||||
{/* Tooltip */}
|
{/* Tooltip */}
|
||||||
<div className="pointer-events-none absolute bottom-full mb-1 left-1/2 -translate-x-1/2 opacity-0 group-hover/avatar:opacity-100 transition-opacity whitespace-nowrap bg-panel-elevated text-main text-[10px] px-1.5 py-0.5 rounded-btn border border-border shadow-md z-30">
|
<div className="pointer-events-none absolute bottom-full mb-1 left-1/2 -translate-x-1/2 opacity-0 group-hover/avatar:opacity-100 transition-opacity whitespace-nowrap bg-canvas text-main text-[10px] px-1.5 py-0.5 rounded-btn border border-border shadow-md z-30">
|
||||||
{u.userName}
|
{u.userName}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -683,80 +682,109 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sync Status Badge */}
|
{/* Sharing Section (1:1 styling from SharedListsView) */}
|
||||||
<span
|
{doc && (() => {
|
||||||
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded-btn text-[10px] font-mono font-medium ${
|
const isOwner = doc.isOwner !== false && (doc.userId === currentUser?.id || !doc.userId || currentUser?.role === 'admin');
|
||||||
|
const sharesCount = doc.sharedWith?.length || 0;
|
||||||
|
const isShared = sharesCount > 0 || !isOwner || Boolean(doc.sharedFolderId);
|
||||||
|
const ownerDisplayName = doc.ownerName || 'Eigentümer';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
{isShared && (
|
||||||
|
isOwner ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onOpenShareModal(doc)}
|
||||||
|
className="px-2 py-0.5 rounded text-[10px] font-semibold bg-accent/15 text-accent border border-accent/30 flex items-center gap-1 hover:bg-accent/25 hover:border-accent/50 transition-colors cursor-pointer"
|
||||||
|
title="Teilen-Menü öffnen: Geteilt mit Nutzern/Gruppen anzeigen & verwalten"
|
||||||
|
>
|
||||||
|
<Users className="w-2.5 h-2.5" />
|
||||||
|
<span>Geteilt{sharesCount > 0 ? ` (${sharesCount})` : ''}</span>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onOpenShareModal(doc)}
|
||||||
|
className="px-2 py-0.5 rounded text-[10px] font-medium border border-border text-muted flex items-center gap-1 bg-surface-subtle hover:bg-panel-elevated hover:text-main transition-colors cursor-pointer"
|
||||||
|
title="Teilen & Berechtigungen öffnen"
|
||||||
|
>
|
||||||
|
<Users className="w-2.5 h-2.5" />
|
||||||
|
<span>Geteilt von {ownerDisplayName}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onOpenShareModal(doc)}
|
||||||
|
className="btn-icon"
|
||||||
|
title="Notiz teilen und Berechtigungen"
|
||||||
|
>
|
||||||
|
<Users className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
|
||||||
|
{/* 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'
|
syncStatus === 'connected'
|
||||||
? 'bg-status-ok/10 text-status-ok border border-status-ok/30'
|
? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-500'
|
||||||
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
||||||
? 'bg-status-warn/10 text-status-warn border border-status-warn/30'
|
? 'bg-amber-500/10 border-amber-500/30 text-amber-500'
|
||||||
: 'bg-panel-elevated text-muted border border-border'
|
: 'bg-canvas border-border text-muted'
|
||||||
}`}
|
}`}
|
||||||
title={
|
title={
|
||||||
syncStatus === 'connected'
|
syncStatus === 'connected'
|
||||||
? 'Echtzeit-Synchronisation aktiv'
|
? 'Live synchronisiert (WebSocket online)'
|
||||||
: 'Verbindung wird hergestellt…'
|
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
||||||
|
? 'Verbindung wird aufgebaut…'
|
||||||
|
: 'Offline'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{syncStatus === 'connected' ? (
|
<Radio
|
||||||
<>
|
className={`w-3.5 h-3.5 ${
|
||||||
<Wifi className="w-2.5 h-2.5" />
|
syncStatus === 'connected'
|
||||||
<span className="hidden sm:inline">LIVE</span>
|
? 'animate-pulse'
|
||||||
</>
|
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
|
||||||
) : syncStatus === 'connecting' || syncStatus === 'reconnecting' ? (
|
? 'animate-spin'
|
||||||
<>
|
: ''
|
||||||
<Loader2 className="w-2.5 h-2.5 animate-spin" />
|
}`}
|
||||||
<span className="hidden sm:inline">SYNC…</span>
|
/>
|
||||||
</>
|
</div>
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<WifiOff className="w-2.5 h-2.5" />
|
|
||||||
<span className="hidden sm:inline">OFFLINE</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleTogglePin}
|
onClick={handleTogglePin}
|
||||||
className={`p-1.5 rounded-btn transition-colors ${
|
className={`btn-icon ${
|
||||||
doc?.isPinned
|
doc?.isPinned
|
||||||
? 'text-status-warn bg-status-warn/10'
|
? 'text-status-warn bg-status-warn/10 border-status-warn/30'
|
||||||
: 'text-muted hover:text-main hover:bg-panel-elevated'
|
: ''
|
||||||
}`}
|
}`}
|
||||||
title={doc?.isPinned ? 'Anheftung lösen' : 'Notiz anheften'}
|
title={doc?.isPinned ? 'Anheftung lösen' : 'Notiz anheften'}
|
||||||
>
|
>
|
||||||
<Pin className="w-4 h-4" />
|
<Pin className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{doc && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onOpenShareModal(doc)}
|
|
||||||
className="p-1.5 rounded-btn text-muted hover:text-accent hover:bg-panel-elevated transition-colors"
|
|
||||||
title="Notiz teilen / Berechtigungen"
|
|
||||||
>
|
|
||||||
<Share2 className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleExportMarkdown}
|
onClick={handleExportMarkdown}
|
||||||
className="p-1.5 rounded-btn text-muted hover:text-main hover:bg-panel-elevated transition-colors"
|
className="btn-icon"
|
||||||
title="Als Markdown (.md) exportieren"
|
title="Als Markdown (.md) exportieren"
|
||||||
>
|
>
|
||||||
<Download className="w-4 h-4" />
|
<Download className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{canEdit && (doc?.isOwner || doc?.permission === 'owner') && (
|
{canEdit && (doc?.isOwner || doc?.permission === 'owner') && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onDeleteDoc(docId)}
|
onClick={() => onDeleteDoc(docId)}
|
||||||
className="p-1.5 rounded-btn text-muted hover:text-status-err hover:bg-status-err/10 transition-colors"
|
className="btn-icon-danger"
|
||||||
title="Notiz löschen"
|
title="Notiz löschen"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -773,7 +801,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
{breadcrumbs.length > 0 && (
|
{breadcrumbs.length > 0 && (
|
||||||
<nav
|
<nav
|
||||||
aria-label="Breadcrumb Navigation"
|
aria-label="Breadcrumb Navigation"
|
||||||
className="flex items-center flex-wrap gap-1.5 py-1.5 px-3 bg-panel-elevated/70 border border-border rounded-card text-xs text-muted"
|
className="flex items-center flex-wrap gap-1.5 py-1.5 px-3 bg-canvas border border-border rounded-btn text-xs text-muted"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -918,7 +946,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => canEdit && setShowEmojiPicker((prev) => !prev)}
|
onClick={() => canEdit && setShowEmojiPicker((prev) => !prev)}
|
||||||
className="w-10 h-10 rounded-btn bg-panel-elevated border border-border hover:border-strong flex items-center justify-center text-xl transition-colors"
|
className="w-10 h-10 rounded-btn bg-canvas border border-border hover:border-strong flex items-center justify-center text-xl transition-colors"
|
||||||
title="Icon ändern"
|
title="Icon ändern"
|
||||||
>
|
>
|
||||||
{doc?.icon || '📝'}
|
{doc?.icon || '📝'}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Plus,
|
Plus,
|
||||||
X,
|
X,
|
||||||
Share2,
|
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@@ -514,12 +513,12 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}, [activeDocShares, shareModalDoc, shareModalFolder]);
|
}, [activeDocShares, shareModalDoc, shareModalFolder]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[calc(100vh-4rem)] panel-surface rounded-card border border-border overflow-hidden shadow-panel">
|
<div className="flex h-[calc(100vh-4rem)] bg-canvas rounded-card border border-border overflow-hidden shadow-panel">
|
||||||
{/* LEFT MASTER SIDEBAR */}
|
{/* LEFT MASTER SIDEBAR */}
|
||||||
<aside
|
<aside
|
||||||
className={`${
|
className={`${
|
||||||
mobileShowDetail ? 'hidden lg:flex' : 'flex'
|
mobileShowDetail ? 'hidden lg:flex' : 'flex'
|
||||||
} w-full lg:w-72 xl:w-80 shrink-0 flex-col bg-panel border-r border-border`}
|
} w-full lg:w-72 xl:w-80 shrink-0 flex-col bg-canvas border-r border-border`}
|
||||||
>
|
>
|
||||||
{/* Sidebar Header & Actions */}
|
{/* Sidebar Header & Actions */}
|
||||||
<div className="p-3 border-b border-border space-y-2">
|
<div className="p-3 border-b border-border space-y-2">
|
||||||
@@ -733,9 +732,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => openFolderShareModal(sharedFolderObj)}
|
onClick={() => openFolderShareModal(sharedFolderObj)}
|
||||||
className="p-1 rounded-btn hover:bg-canvas text-accent"
|
className="p-1 rounded-btn hover:bg-canvas text-accent"
|
||||||
title="Ordner-Freigabe"
|
title="Ordner teilen und Berechtigungen"
|
||||||
>
|
>
|
||||||
<Share2 className="w-3 h-3" />
|
<Users className="w-3 h-3" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
@@ -916,7 +915,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{tagDetails?.documents.map((docGroup) => (
|
{tagDetails?.documents.map((docGroup) => (
|
||||||
<div
|
<div
|
||||||
key={docGroup.docId}
|
key={docGroup.docId}
|
||||||
className="p-3 rounded-card border border-border bg-panel space-y-1.5"
|
className="p-3 rounded-card border border-border bg-canvas space-y-1.5"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -1001,10 +1000,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={folderName}
|
key={folderName}
|
||||||
className="border border-border rounded-card bg-panel overflow-hidden"
|
className="border border-border rounded-card bg-canvas overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* Folder Header */}
|
{/* Folder Header */}
|
||||||
<div className="p-2 bg-panel-elevated border-b border-border flex items-center justify-between">
|
<div className="p-2 bg-canvas border-b border-border flex items-center justify-between">
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCollapsedTodoFolders((prev) => {
|
setCollapsedTodoFolders((prev) => {
|
||||||
|
|||||||
Reference in new issue
Block a user