feat(workspace): align workspace UI with tasks and lists, add top tabs and collapsible sidebar
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
This commit is contained in:
1 parent
7baab8faa2
commit
59ddca91f3
2 files changed
+252
-362
No files matched your search
@@ -4,7 +4,7 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
Trash2,
|
Trash2,
|
||||||
Download,
|
Download,
|
||||||
Folder as FolderIcon,
|
FileText,
|
||||||
Radio,
|
Radio,
|
||||||
Loader2,
|
Loader2,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
@@ -162,7 +162,6 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
const [loading, setLoading] = useState<boolean>(!initialDoc);
|
const [loading, setLoading] = useState<boolean>(!initialDoc);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
|
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
|
||||||
const [showFolderMenu, setShowFolderMenu] = useState<boolean>(false);
|
|
||||||
const [zoomedBlockId, setZoomedBlockId] = useState<string | null>(null);
|
const [zoomedBlockId, setZoomedBlockId] = useState<string | null>(null);
|
||||||
|
|
||||||
const titleInputRef = useRef<HTMLInputElement | null>(null);
|
const titleInputRef = useRef<HTMLInputElement | null>(null);
|
||||||
@@ -543,18 +542,6 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMoveToFolder = async (folderPath: string) => {
|
|
||||||
setShowFolderMenu(false);
|
|
||||||
if (!doc || !canEdit) return;
|
|
||||||
try {
|
|
||||||
const updated = await api.moveDocument(doc.id, folderPath);
|
|
||||||
setDoc(updated);
|
|
||||||
onDocMetadataChange(updated);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExportMarkdown = () => {
|
const handleExportMarkdown = () => {
|
||||||
const lines: string[] = [`# ${title || 'Unbenannte Notiz'}`, ''];
|
const lines: string[] = [`# ${title || 'Unbenannte Notiz'}`, ''];
|
||||||
for (const b of blocks) {
|
for (const b of blocks) {
|
||||||
@@ -626,46 +613,58 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Folder Selector */}
|
{/* Breadcrumbs Navigation (Fokus-Modus) or Doc Indicator */}
|
||||||
<div className="relative">
|
{zoomedBlock && breadcrumbs.length > 0 ? (
|
||||||
<button
|
<nav
|
||||||
type="button"
|
aria-label="Breadcrumb Navigation"
|
||||||
onClick={() => canEdit && setShowFolderMenu((prev) => !prev)}
|
className="flex items-center flex-wrap gap-1 text-xs text-muted"
|
||||||
className="btn-action inline-flex items-center gap-1.5 px-2 py-1 text-xs text-muted hover:text-main"
|
|
||||||
title="Ordner wechseln"
|
|
||||||
>
|
>
|
||||||
<FolderIcon className="w-3.5 h-3.5 text-accent" />
|
<button
|
||||||
<span className="truncate max-w-[140px]">
|
type="button"
|
||||||
{doc?.folderPath ? doc.folderPath : 'Hauptebene'}
|
onClick={() => setZoomedBlockId(null)}
|
||||||
</span>
|
className="hover:text-accent inline-flex items-center gap-1 font-medium transition-colors cursor-pointer"
|
||||||
</button>
|
title="Zurück zum Hauptdokument"
|
||||||
|
>
|
||||||
|
<Home className="w-3.5 h-3.5 text-accent shrink-0" />
|
||||||
|
<span className="truncate max-w-[130px]">
|
||||||
|
{title || doc?.title || 'Dokument'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
{showFolderMenu && canEdit && (
|
{breadcrumbs.slice(1).map((crumb, idx) => {
|
||||||
<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">
|
const isCurrent = idx === breadcrumbs.length - 2;
|
||||||
<button
|
return (
|
||||||
type="button"
|
<React.Fragment key={crumb.id || idx}>
|
||||||
onClick={() => handleMoveToFolder('')}
|
<ChevronRight className="w-3 h-3 text-faint shrink-0" />
|
||||||
className={`w-full text-left px-3 py-1.5 text-xs hover:bg-panel-elevated ${
|
{isCurrent ? (
|
||||||
!doc?.folderPath ? 'font-bold text-accent' : 'text-main'
|
<span
|
||||||
}`}
|
className="font-bold text-accent truncate max-w-[160px]"
|
||||||
>
|
title={crumb.title}
|
||||||
/ (Hauptebene)
|
>
|
||||||
</button>
|
{crumb.title || 'Punkt'}
|
||||||
{availableFolders.map((folder) => (
|
</span>
|
||||||
<button
|
) : (
|
||||||
key={folder}
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleMoveToFolder(folder)}
|
onClick={() => setZoomedBlockId(crumb.id)}
|
||||||
className={`w-full text-left px-3 py-1.5 text-xs hover:bg-panel-elevated truncate ${
|
className="hover:text-main truncate max-w-[120px] transition-colors cursor-pointer"
|
||||||
doc?.folderPath === folder ? 'font-bold text-accent' : 'text-main'
|
title={`Zoom zu "${crumb.title}"`}
|
||||||
}`}
|
>
|
||||||
>
|
{crumb.title || 'Punkt'}
|
||||||
📁 {folder}
|
</button>
|
||||||
</button>
|
)}
|
||||||
))}
|
</React.Fragment>
|
||||||
</div>
|
);
|
||||||
)}
|
})}
|
||||||
</div>
|
</nav>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center gap-1.5 text-xs text-muted">
|
||||||
|
<FileText className="w-3.5 h-3.5 text-accent shrink-0" />
|
||||||
|
<span className="truncate max-w-[180px] font-medium text-main">
|
||||||
|
{title || doc?.title || 'Dokument'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Read-only indicator */}
|
{/* Read-only indicator */}
|
||||||
{!canEdit && (
|
{!canEdit && (
|
||||||
@@ -821,52 +820,6 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
|
|||||||
FOKUS-MODUS & ZOOM-IN (HOISTED SUBTREE VIEW)
|
FOKUS-MODUS & ZOOM-IN (HOISTED SUBTREE VIEW)
|
||||||
========================================================================= */
|
========================================================================= */
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{/* Clickable Breadcrumb Bar */}
|
|
||||||
{breadcrumbs.length > 0 && (
|
|
||||||
<nav
|
|
||||||
aria-label="Breadcrumb Navigation"
|
|
||||||
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
|
|
||||||
type="button"
|
|
||||||
onClick={() => setZoomedBlockId(null)}
|
|
||||||
className="hover:text-accent inline-flex items-center gap-1 font-medium transition-colors"
|
|
||||||
title="Zurück zum Hauptdokument"
|
|
||||||
>
|
|
||||||
<Home className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span className="truncate max-w-[120px]">
|
|
||||||
{title || doc?.title || 'Dokument'}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{breadcrumbs.slice(1).map((crumb, idx) => {
|
|
||||||
const isCurrent = idx === breadcrumbs.length - 2;
|
|
||||||
return (
|
|
||||||
<React.Fragment key={crumb.id || idx}>
|
|
||||||
<ChevronRight className="w-3 h-3 text-faint shrink-0" />
|
|
||||||
{isCurrent ? (
|
|
||||||
<span
|
|
||||||
className="font-bold text-accent truncate max-w-[200px]"
|
|
||||||
title={crumb.title}
|
|
||||||
>
|
|
||||||
{crumb.title || 'Punkt'}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setZoomedBlockId(crumb.id)}
|
|
||||||
className="hover:text-main truncate max-w-[140px] transition-colors"
|
|
||||||
title={`Zoom zu "${crumb.title}"`}
|
|
||||||
>
|
|
||||||
{crumb.title || 'Punkt'}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Prominent Zoomed Ast-Heading */}
|
{/* Prominent Zoomed Ast-Heading */}
|
||||||
<div className="flex items-start justify-between gap-3 pb-4 mb-2 border-b border-border">
|
<div className="flex items-start justify-between gap-3 pb-4 mb-2 border-b border-border">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
CheckSquare,
|
CheckSquare,
|
||||||
|
PanelLeftClose,
|
||||||
|
PanelLeftOpen,
|
||||||
|
ChevronLeft,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Document,
|
Document,
|
||||||
@@ -268,6 +271,23 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
const [newFolderName, setNewFolderName] = useState<string>('');
|
const [newFolderName, setNewFolderName] = useState<string>('');
|
||||||
const [newFolderIsShared, setNewFolderIsShared] = useState<boolean>(false);
|
const [newFolderIsShared, setNewFolderIsShared] = useState<boolean>(false);
|
||||||
const [mobileShowDetail, setMobileShowDetail] = useState<boolean>(Boolean(initialDocId));
|
const [mobileShowDetail, setMobileShowDetail] = useState<boolean>(Boolean(initialDocId));
|
||||||
|
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState<boolean>(() => {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem('mindsift:workspace_sidebar_collapsed') === 'true';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const toggleSidebar = useCallback(() => {
|
||||||
|
setIsSidebarCollapsed((prev) => {
|
||||||
|
const next = !prev;
|
||||||
|
try {
|
||||||
|
localStorage.setItem('mindsift:workspace_sidebar_collapsed', String(next));
|
||||||
|
} catch {}
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Drag and Drop state
|
// Drag and Drop state
|
||||||
const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
|
const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
|
||||||
@@ -1003,117 +1023,155 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}, [tagsSummary, tagSearchQuery, tagSortBy]);
|
}, [tagsSummary, tagSearchQuery, tagSortBy]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[calc(100vh-4rem)] bg-canvas rounded-card border border-border overflow-hidden shadow-panel">
|
<div className="w-full space-y-3">
|
||||||
{/* LEFT MASTER SIDEBAR */}
|
{/* Top Header: Workspace Tabs & Actions */}
|
||||||
<aside
|
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
|
||||||
className={`${
|
{/* Workspace selector tabs */}
|
||||||
mobileShowDetail ? 'hidden lg:flex' : 'flex'
|
<div className="flex flex-wrap items-center gap-1">
|
||||||
} w-full lg:w-72 xl:w-80 shrink-0 flex-col bg-canvas border-r border-border`}
|
<button
|
||||||
>
|
type="button"
|
||||||
{/* Sidebar Header & Actions */}
|
onClick={() => setSubView('editor')}
|
||||||
<div className="p-3 border-b border-border space-y-2">
|
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||||
<div className="flex items-center justify-between">
|
subView === 'editor'
|
||||||
<h2 className="text-xs font-bold text-main uppercase tracking-wider">
|
? 'bg-panel border-accent text-main'
|
||||||
Notizen & Outliner
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
</h2>
|
}`}
|
||||||
<div className="flex items-center gap-1">
|
>
|
||||||
<button
|
<FileText className="w-3.5 h-3.5 text-accent" />
|
||||||
type="button"
|
<span>Notizen</span>
|
||||||
onClick={() => setShowNewFolderModal(true)}
|
{docs.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{docs.length}</span>}
|
||||||
className="btn-action p-1 text-muted hover:text-main"
|
</button>
|
||||||
title="Neuer Ordner"
|
|
||||||
>
|
<button
|
||||||
<FolderPlus className="w-4 h-4" />
|
type="button"
|
||||||
</button>
|
onClick={() => {
|
||||||
<button
|
setSubView('todos');
|
||||||
type="button"
|
setMobileShowDetail(true);
|
||||||
onClick={() => handleCreateDocument('')}
|
}}
|
||||||
className="h-7 px-2.5 bg-canvas hover:bg-panel border border-border border-dashed rounded-btn text-xs text-muted hover:text-main inline-flex items-center gap-1 transition-colors cursor-pointer"
|
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||||
title="Neue Notiz anlegen"
|
subView === 'todos'
|
||||||
>
|
? 'bg-panel border-accent text-main'
|
||||||
<Plus className="w-3.5 h-3.5" />
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
<span>Notiz</span>
|
}`}
|
||||||
</button>
|
>
|
||||||
|
<Check className="w-3.5 h-3.5 text-status-ok" />
|
||||||
|
<span>To-Dos</span>
|
||||||
|
{totalOpenTodos > 0 && <span className="ml-1 badge-neutral text-[10px]">{totalOpenTodos}</span>}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setSubView('tags');
|
||||||
|
setMobileShowDetail(true);
|
||||||
|
}}
|
||||||
|
className={`h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer ${
|
||||||
|
subView === 'tags' || subView === 'tag_detail'
|
||||||
|
? 'bg-panel border-accent text-main'
|
||||||
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Hash className="w-3.5 h-3.5 text-accent" />
|
||||||
|
<span>Hashtags</span>
|
||||||
|
{tagsSummary.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{tagsSummary.length}</span>}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Action buttons */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{subView === 'editor' && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={toggleSidebar}
|
||||||
|
className={`hidden sm:flex h-8 px-2.5 text-xs font-medium border rounded-btn transition-colors items-center gap-1.5 cursor-pointer ${
|
||||||
|
isSidebarCollapsed
|
||||||
|
? 'bg-panel border-accent text-accent'
|
||||||
|
: 'bg-canvas border-border text-muted hover:text-main'
|
||||||
|
}`}
|
||||||
|
title={isSidebarCollapsed ? 'Ordnerleiste einblenden' : 'Ordnerleiste einklappen'}
|
||||||
|
>
|
||||||
|
{isSidebarCollapsed ? (
|
||||||
|
<PanelLeftOpen className="w-3.5 h-3.5" />
|
||||||
|
) : (
|
||||||
|
<PanelLeftClose className="w-3.5 h-3.5" />
|
||||||
|
)}
|
||||||
|
<span>{isSidebarCollapsed ? 'Ordner anzeigen' : 'Ordner einklappen'}</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCreateDocument('')}
|
||||||
|
className="h-8 px-2.5 bg-canvas hover:bg-panel border border-border border-dashed rounded-btn text-xs text-muted hover:text-main flex items-center gap-1 transition-colors cursor-pointer"
|
||||||
|
title="Neue Notiz anlegen"
|
||||||
|
>
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
<span>Neue Notiz</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Workspace Split Pane */}
|
||||||
|
<div className="flex h-[calc(100vh-7.5rem)] bg-canvas rounded-card border border-border overflow-hidden shadow-panel">
|
||||||
|
{/* LEFT MASTER SIDEBAR */}
|
||||||
|
<aside
|
||||||
|
className={`${
|
||||||
|
isSidebarCollapsed
|
||||||
|
? 'hidden'
|
||||||
|
: mobileShowDetail
|
||||||
|
? 'hidden lg:flex'
|
||||||
|
: 'flex'
|
||||||
|
} w-full lg:w-72 xl:w-80 shrink-0 flex-col bg-canvas border-r border-border transition-all duration-200`}
|
||||||
|
>
|
||||||
|
{/* Sidebar Header & Actions */}
|
||||||
|
<div className="p-3 border-b border-border space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h2 className="text-xs font-bold text-main uppercase tracking-wider">
|
||||||
|
Notizen & Outliner
|
||||||
|
</h2>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowNewFolderModal(true)}
|
||||||
|
className="btn-action p-1 text-muted hover:text-main"
|
||||||
|
title="Neuer Ordner"
|
||||||
|
>
|
||||||
|
<FolderPlus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCreateDocument('')}
|
||||||
|
className="h-7 px-2.5 bg-canvas hover:bg-panel border border-border border-dashed rounded-btn text-xs text-muted hover:text-main inline-flex items-center gap-1 transition-colors cursor-pointer"
|
||||||
|
title="Neue Notiz anlegen"
|
||||||
|
>
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
<span>Notiz</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Input */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="w-3.5 h-3.5 text-faint absolute left-2.5 top-1/2 -translate-y-1/2" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
placeholder="Notizen oder #Tags suchen…"
|
||||||
|
className="input-field w-full pl-8 pr-6 py-1 text-xs"
|
||||||
|
/>
|
||||||
|
{searchQuery && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setSearchQuery('')}
|
||||||
|
className="absolute right-2 top-1/2 -translate-y-1/2 text-faint hover:text-main"
|
||||||
|
>
|
||||||
|
<X className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search Input */}
|
|
||||||
<div className="relative">
|
|
||||||
<Search className="w-3.5 h-3.5 text-faint absolute left-2.5 top-1/2 -translate-y-1/2" />
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={searchQuery}
|
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
|
||||||
placeholder="Notizen oder #Tags suchen…"
|
|
||||||
className="input-field w-full pl-8 pr-6 py-1 text-xs"
|
|
||||||
/>
|
|
||||||
{searchQuery && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSearchQuery('')}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-faint hover:text-main"
|
|
||||||
>
|
|
||||||
<X className="w-3.5 h-3.5" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Sub-view Switcher: Notizen vs. To-Dos vs. Hashtags */}
|
|
||||||
<div className="grid grid-cols-3 gap-1 p-0.5 rounded-btn bg-canvas border border-border text-xs">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
|
||||||
subView === 'editor'
|
|
||||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
|
||||||
: 'text-muted hover:text-main'
|
|
||||||
}`}
|
|
||||||
title="Notizen & Dokumente"
|
|
||||||
>
|
|
||||||
<FileText className="w-3.5 h-3.5 text-accent shrink-0" />
|
|
||||||
<span className="truncate">Notizen</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setSubView('todos');
|
|
||||||
setMobileShowDetail(true);
|
|
||||||
}}
|
|
||||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
|
||||||
subView === 'todos'
|
|
||||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
|
||||||
: 'text-muted hover:text-main'
|
|
||||||
}`}
|
|
||||||
title="To-Dos Rollup"
|
|
||||||
>
|
|
||||||
<Check className="w-3.5 h-3.5 text-status-ok shrink-0" />
|
|
||||||
<span className="truncate">To-Dos</span>
|
|
||||||
{totalOpenTodos > 0 && (
|
|
||||||
<span className="badge-neutral text-[9px] px-1 py-0">{totalOpenTodos}</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setSubView('tags');
|
|
||||||
setMobileShowDetail(true);
|
|
||||||
}}
|
|
||||||
className={`py-1.5 px-1 rounded-btn font-medium transition-colors flex items-center justify-center gap-1 cursor-pointer truncate ${
|
|
||||||
subView === 'tags' || subView === 'tag_detail'
|
|
||||||
? 'bg-panel-elevated text-main font-semibold shadow-xs'
|
|
||||||
: 'text-muted hover:text-main'
|
|
||||||
}`}
|
|
||||||
title="Hashtag Kacheln & Übersicht"
|
|
||||||
>
|
|
||||||
<Hash className="w-3.5 h-3.5 text-accent shrink-0" />
|
|
||||||
<span className="truncate">Hashtags</span>
|
|
||||||
{tagsSummary.length > 0 && (
|
|
||||||
<span className="badge-neutral text-[9px] px-1 py-0">{tagsSummary.length}</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Scrollable Navigation Tree */}
|
{/* Scrollable Navigation Tree */}
|
||||||
<div className="flex-1 overflow-y-auto p-2 space-y-3">
|
<div className="flex-1 overflow-y-auto p-2 space-y-3">
|
||||||
{/* Drop target for moving docs or folders to root */}
|
{/* Drop target for moving docs or folders to root */}
|
||||||
@@ -1273,7 +1331,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
{/* RIGHT DETAIL AREA (STRICTLY ISOLATED BY KEY) */}
|
{/* RIGHT DETAIL AREA (STRICTLY ISOLATED BY KEY) */}
|
||||||
<main
|
<main
|
||||||
className={`${
|
className={`${
|
||||||
mobileShowDetail ? 'flex' : 'hidden lg:flex'
|
mobileShowDetail ? 'flex' : isSidebarCollapsed ? 'flex' : 'hidden lg:flex'
|
||||||
} flex-1 flex-col min-w-0 h-full overflow-hidden bg-canvas`}
|
} flex-1 flex-col min-w-0 h-full overflow-hidden bg-canvas`}
|
||||||
>
|
>
|
||||||
{subView === 'editor' && selectedDocId ? (
|
{subView === 'editor' && selectedDocId ? (
|
||||||
@@ -1298,49 +1356,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
========================================================================= */
|
========================================================================= */
|
||||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||||
<div className="max-w-4xl mx-auto space-y-5">
|
<div className="max-w-4xl mx-auto space-y-5">
|
||||||
{/* Top Navigation Tabs */}
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
|
||||||
>
|
|
||||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span>Notizen</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setSubView('todos');
|
|
||||||
setMobileShowDetail(true);
|
|
||||||
}}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
|
||||||
>
|
|
||||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
|
||||||
<span>To-Dos</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
|
||||||
>
|
|
||||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span>Hashtags</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
|
||||||
>
|
|
||||||
Zum Editor
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Header with Title and Search Controls */}
|
{/* Header with Title and Search Controls */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
@@ -1507,58 +1522,16 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
) : subView === 'tag_detail' && selectedTag ? (
|
) : subView === 'tag_detail' && selectedTag ? (
|
||||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||||
<div className="max-w-3xl mx-auto space-y-4">
|
<div className="max-w-3xl mx-auto space-y-4">
|
||||||
{/* Top Navigation Tabs */}
|
{/* Back to all tags */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
<div className="flex items-center justify-between border-b border-border pb-3">
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
onClick={() => setSubView('tags')}
|
||||||
onClick={() => setSubView('editor')}
|
className="btn-action inline-flex items-center gap-1.5 px-2.5 py-1 text-xs text-muted hover:text-main cursor-pointer"
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
>
|
||||||
>
|
<ArrowLeft className="w-3.5 h-3.5" />
|
||||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
<span>Alle Hashtag-Kacheln</span>
|
||||||
<span>Notizen</span>
|
</button>
|
||||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setSubView('todos');
|
|
||||||
setMobileShowDetail(true);
|
|
||||||
}}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
|
||||||
>
|
|
||||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
|
||||||
<span>To-Dos</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('tags')}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
|
||||||
>
|
|
||||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span>Hashtags</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('tags')}
|
|
||||||
className="btn-action inline-flex items-center gap-1 px-2.5 py-1 text-xs text-muted hover:text-main cursor-pointer"
|
|
||||||
>
|
|
||||||
<ArrowLeft className="w-3.5 h-3.5" />
|
|
||||||
<span>Alle Kacheln</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
|
||||||
>
|
|
||||||
Zum Editor
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tag Context Header */}
|
{/* Tag Context Header */}
|
||||||
@@ -1755,49 +1728,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
) : subView === 'todos' ? (
|
) : subView === 'todos' ? (
|
||||||
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
<div className="flex-1 overflow-y-auto p-4 sm:p-8 bg-canvas">
|
||||||
<div className="max-w-3xl mx-auto space-y-4">
|
<div className="max-w-3xl mx-auto space-y-4">
|
||||||
{/* Top Navigation Tabs */}
|
|
||||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-3">
|
|
||||||
<div className="flex flex-wrap items-center gap-1.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
|
||||||
>
|
|
||||||
<FileText className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span>Notizen</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{docs.length}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-panel border-accent text-main font-semibold shadow-xs"
|
|
||||||
>
|
|
||||||
<Check className="w-3.5 h-3.5 text-status-ok" />
|
|
||||||
<span>To-Dos</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{totalOpenTodos}</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setSubView('tags');
|
|
||||||
setMobileShowDetail(true);
|
|
||||||
}}
|
|
||||||
className="h-8 px-3 text-xs font-medium border rounded-btn transition-colors flex items-center gap-1.5 cursor-pointer bg-canvas border-border text-muted hover:text-main"
|
|
||||||
>
|
|
||||||
<Hash className="w-3.5 h-3.5 text-accent" />
|
|
||||||
<span>Hashtags</span>
|
|
||||||
<span className="badge-neutral text-[10px]">{tagsSummary.length}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
|
||||||
>
|
|
||||||
Zum Editor
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between border-b border-border pb-3 gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between border-b border-border pb-3 gap-2">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-lg font-bold text-main">
|
<h2 className="text-lg font-bold text-main">
|
||||||
@@ -1865,14 +1795,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
<span>Ordner</span>
|
<span>Ordner</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setSubView('editor')}
|
|
||||||
className="btn-action px-3 py-1 text-xs text-main cursor-pointer"
|
|
||||||
>
|
|
||||||
Zum Editor
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2272,16 +2194,30 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
Keine Notiz ausgewählt
|
Keine Notiz ausgewählt
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-muted max-w-sm mb-3">
|
<p className="text-xs text-muted max-w-sm mb-3">
|
||||||
Wähle links in der Seitenleiste eine bestehende Notiz aus oder erstelle eine neue.
|
{isSidebarCollapsed
|
||||||
|
? 'Die Ordnerleiste ist eingeklappt. Blende sie ein oder erstelle eine neue Notiz.'
|
||||||
|
: 'Wähle links in der Seitenleiste eine bestehende Notiz aus oder erstelle eine neue.'}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<div className="flex items-center gap-2">
|
||||||
type="button"
|
{isSidebarCollapsed && (
|
||||||
onClick={() => handleCreateDocument('')}
|
<button
|
||||||
className="btn-primary inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold"
|
type="button"
|
||||||
>
|
onClick={toggleSidebar}
|
||||||
<Plus className="w-3.5 h-3.5" />
|
className="btn-action inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold cursor-pointer"
|
||||||
<span>Notiz erstellen</span>
|
>
|
||||||
</button>
|
<PanelLeftOpen className="w-3.5 h-3.5" />
|
||||||
|
<span>Ordner anzeigen</span>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCreateDocument('')}
|
||||||
|
className="btn-primary inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold"
|
||||||
|
>
|
||||||
|
<Plus className="w-3.5 h-3.5" />
|
||||||
|
<span>Notiz erstellen</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
@@ -2402,6 +2338,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
Reference in new issue
Block a user