feat: complete workspace, triage, outliner and sharing overhaul (tasks 1-26)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
1 parent
df2f2bac67
commit
d39d1d1122
17 files changed
+1173
-501
No files matched your search
@@ -55,6 +55,7 @@ import { DocumentEditor, DocSaveStatus } from './DocumentEditor';
|
||||
interface WorkspaceViewProps {
|
||||
currentUser?: User | null;
|
||||
initialDocId?: string | null;
|
||||
initialBlockId?: string | null;
|
||||
onStatsChange: () => void;
|
||||
}
|
||||
|
||||
@@ -81,6 +82,7 @@ interface FolderTreeItemProps {
|
||||
handleFolderDragStart: (e: React.DragEvent, path: string) => void;
|
||||
handleDocDragStart: (e: React.DragEvent, id: string) => void;
|
||||
handleCreateDocument: (folderPath?: string) => void;
|
||||
handleOpenNewFolder: (parentPath?: string, e?: React.MouseEvent) => void;
|
||||
handleShareFolderClick: (folderPath: string, existingSf?: SharedFolder) => void;
|
||||
handleDeleteFolder: (folderPath: string) => void;
|
||||
onSelectDoc: (id: string) => void;
|
||||
@@ -99,13 +101,14 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
handleFolderDragStart,
|
||||
handleDocDragStart,
|
||||
handleCreateDocument,
|
||||
handleOpenNewFolder,
|
||||
handleShareFolderClick,
|
||||
handleDeleteFolder,
|
||||
onSelectDoc,
|
||||
}) => {
|
||||
const isExpanded = Boolean(expandedFolders[node.fullPath]);
|
||||
const isDragOver = dragOverFolder === node.fullPath;
|
||||
const hasContent = node.subfolders.length > 0 || node.docs.length > 0;
|
||||
const isExpanded = Boolean(hasContent && expandedFolders[node.fullPath]);
|
||||
const isDragOver = dragOverFolder === node.fullPath;
|
||||
|
||||
return (
|
||||
<div className="space-y-0.5">
|
||||
@@ -132,6 +135,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
hasContent &&
|
||||
setExpandedFolders((prev) => ({
|
||||
...prev,
|
||||
[node.fullPath]: !prev[node.fullPath],
|
||||
@@ -145,9 +149,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
) : (
|
||||
<ChevronRight className="w-3.5 h-3.5 text-faint shrink-0" />
|
||||
)
|
||||
) : (
|
||||
<span className="w-3.5 shrink-0" />
|
||||
)}
|
||||
) : null}
|
||||
{isExpanded ? (
|
||||
<FolderOpen className="w-3.5 h-3.5 text-accent shrink-0" />
|
||||
) : (
|
||||
@@ -157,9 +159,6 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
{node.sharedFolderObj && (
|
||||
<Users className="w-3 h-3 text-status-ok shrink-0" />
|
||||
)}
|
||||
<span className="text-[10px] text-faint ml-auto pr-1">
|
||||
{node.docs.length}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 pointer-events-none group-hover:pointer-events-auto transition-opacity shrink-0">
|
||||
@@ -169,18 +168,29 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
e.stopPropagation();
|
||||
handleCreateDocument(node.fullPath);
|
||||
}}
|
||||
className="p-1 h-6 w-6 flex items-center justify-center rounded-btn hover:bg-canvas text-muted hover:text-main cursor-pointer"
|
||||
className="p-1 h-6 w-6 flex items-center justify-center text-muted hover:text-main transition-colors cursor-pointer"
|
||||
title="Notiz in diesem Ordner erstellen"
|
||||
>
|
||||
<Plus className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOpenNewFolder(node.fullPath, e);
|
||||
}}
|
||||
className="p-1 h-6 w-6 flex items-center justify-center text-muted hover:text-main transition-colors cursor-pointer"
|
||||
title="Neuen Unterordner erstellen"
|
||||
>
|
||||
<FolderPlus className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleShareFolderClick(node.fullPath, node.sharedFolderObj);
|
||||
}}
|
||||
className={`p-1 h-6 w-6 flex items-center justify-center rounded-btn hover:bg-canvas cursor-pointer ${
|
||||
className={`p-1 h-6 w-6 flex items-center justify-center transition-colors cursor-pointer ${
|
||||
node.sharedFolderObj ? 'text-accent' : 'text-faint hover:text-accent'
|
||||
}`}
|
||||
title="Ordner teilen und Berechtigungen"
|
||||
@@ -193,7 +203,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
e.stopPropagation();
|
||||
handleDeleteFolder(node.fullPath);
|
||||
}}
|
||||
className="p-1 h-6 w-6 flex items-center justify-center rounded-btn hover:bg-status-err/20 text-faint hover:text-status-err cursor-pointer"
|
||||
className="p-1 h-6 w-6 flex items-center justify-center text-faint hover:text-status-err transition-colors cursor-pointer"
|
||||
title="Ordner löschen"
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
@@ -219,6 +229,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
handleFolderDragStart={handleFolderDragStart}
|
||||
handleDocDragStart={handleDocDragStart}
|
||||
handleCreateDocument={handleCreateDocument}
|
||||
handleOpenNewFolder={handleOpenNewFolder}
|
||||
handleShareFolderClick={handleShareFolderClick}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
onSelectDoc={onSelectDoc}
|
||||
@@ -235,7 +246,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
style={{ paddingLeft: `${(depth + 1) * 12 + 16}px` }}
|
||||
className={`w-full flex items-center gap-2 pr-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
||||
selectedDocId === d.id && subView === 'editor'
|
||||
? 'bg-panel border-accent text-main font-semibold shadow-xs'
|
||||
? 'bg-panel-elevated text-main font-semibold border-border/80'
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
@@ -261,6 +272,7 @@ const FolderTreeItem: React.FC<FolderTreeItemProps> = ({
|
||||
export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
currentUser,
|
||||
initialDocId,
|
||||
initialBlockId,
|
||||
onStatsChange,
|
||||
}) => {
|
||||
const [docs, setDocs] = useState<Document[]>([]);
|
||||
@@ -291,7 +303,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}, [docs, serverTagsSummary]);
|
||||
|
||||
const [selectedDocId, setSelectedDocId] = useState<string | null>(initialDocId || null);
|
||||
const [targetBlockId, setTargetBlockId] = useState<string | null>(null);
|
||||
const [targetBlockId, setTargetBlockId] = useState<string | null>(initialBlockId || null);
|
||||
const [newlyCreatedDocId, setNewlyCreatedDocId] = useState<string | null>(null);
|
||||
const [subView, setSubView] = useState<WorkspaceSubView>('editor');
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
||||
@@ -301,10 +313,40 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
const [tagSortBy, setTagSortBy] = useState<'count' | 'name'>('count');
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [expandedFolders, setExpandedFolders] = useState<Record<string, boolean>>({});
|
||||
const [expandedFolders, setExpandedFolders] = useState<Record<string, boolean>>(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem('mindsift:workspace_expanded_folders');
|
||||
return saved ? JSON.parse(saved) : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
localStorage.setItem('mindsift:workspace_expanded_folders', JSON.stringify(expandedFolders));
|
||||
} catch {}
|
||||
}, [expandedFolders]);
|
||||
|
||||
const [showNewFolderModal, setShowNewFolderModal] = useState<boolean>(false);
|
||||
const [newFolderCoords, setNewFolderCoords] = useState<{ x: number; y: number } | null>(null);
|
||||
const [newFolderParentPath, setNewFolderParentPath] = useState<string>('');
|
||||
const [newFolderName, setNewFolderName] = useState<string>('');
|
||||
const [newFolderIsShared, setNewFolderIsShared] = useState<boolean>(false);
|
||||
|
||||
const handleOpenNewFolder = (parentPath = '', e?: React.MouseEvent) => {
|
||||
setNewFolderParentPath(parentPath);
|
||||
setNewFolderName('');
|
||||
if (e) {
|
||||
setNewFolderCoords({
|
||||
x: Math.min(e.clientX, window.innerWidth - 270),
|
||||
y: Math.min(e.clientY, window.innerHeight - 130),
|
||||
});
|
||||
} else {
|
||||
setNewFolderCoords({ x: 100, y: 150 });
|
||||
}
|
||||
setShowNewFolderModal(true);
|
||||
};
|
||||
const [mobileShowDetail, setMobileShowDetail] = useState<boolean>(Boolean(initialDocId));
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState<boolean>(() => {
|
||||
try {
|
||||
@@ -345,8 +387,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
|
||||
// Rollup drilldown & View Mode
|
||||
const [todoFolderDrilldown, setTodoFolderDrilldown] = useState<string | null>(null);
|
||||
const [todoGrouping, setTodoGrouping] = useState<'folder_note' | 'note' | 'folder'>(() => {
|
||||
return (localStorage.getItem('mindsift_todo_grouping') as any) || 'folder_note';
|
||||
const [todoGrouping, setTodoGrouping] = useState<'grouped' | 'list'>(() => {
|
||||
const saved = localStorage.getItem('mindsift_todo_grouping');
|
||||
return saved === 'list' ? 'list' : 'grouped';
|
||||
});
|
||||
const [collapsedTodoFolders, setCollapsedTodoFolders] = useState<Set<string>>(new Set());
|
||||
const [collapsedTodoNotes, setCollapsedTodoNotes] = useState<Set<string>>(new Set());
|
||||
@@ -536,14 +579,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
|
||||
const handleCreateFolderSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const clean = newFolderName.trim().replace(/^\/+|\/+$/g, '');
|
||||
if (!clean) return;
|
||||
const cleanName = newFolderName.trim().replace(/^\/+|\/+$/g, '');
|
||||
if (!cleanName) return;
|
||||
const cleanPath = newFolderParentPath ? `${newFolderParentPath}/${cleanName}` : cleanName;
|
||||
try {
|
||||
if (newFolderIsShared) {
|
||||
const sf = await api.createSharedFolder({ name: clean, mountPath: clean });
|
||||
setSharedFolders((prev) => [...prev, sf]);
|
||||
}
|
||||
const res = await api.createFolder(clean);
|
||||
const res = await api.createFolder(cleanPath);
|
||||
setFolders((prev) => (prev.includes(res.path) ? prev : [...prev, res.path].sort()));
|
||||
const parts = res.path.split('/');
|
||||
let acc = '';
|
||||
@@ -554,10 +594,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}
|
||||
setExpandedFolders((prev) => ({ ...prev, ...expansions }));
|
||||
setNewFolderName('');
|
||||
setNewFolderIsShared(false);
|
||||
setNewFolderParentPath('');
|
||||
setShowNewFolderModal(false);
|
||||
} catch {
|
||||
// ignore
|
||||
} catch (err: any) {
|
||||
alert(err.message || 'Fehler beim Erstellen des Ordners');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1208,40 +1248,12 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
/* Standalone Realtime Status Indicator for Todos / Hashtags */
|
||||
(() => {
|
||||
const isConnected = realtimeConnected;
|
||||
const isSSE = 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'
|
||||
: 'bg-canvas border-border text-muted'
|
||||
}`}
|
||||
title={
|
||||
isConnected
|
||||
? isSSE
|
||||
? 'Synchronisiert (Server-Sent Events / HTTPS)'
|
||||
: 'Synchronisiert (WebSocket online)'
|
||||
: 'Offline'
|
||||
}
|
||||
>
|
||||
<Radio
|
||||
className={`w-3.5 h-3.5 ${isConnected ? 'animate-pulse' : ''}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})()
|
||||
)}
|
||||
) : null}
|
||||
</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">
|
||||
<div className="flex h-[calc(100vh-7.5rem)] bg-canvas rounded-card border border-border overflow-hidden">
|
||||
{/* LEFT MASTER SIDEBAR */}
|
||||
<aside
|
||||
className={`${
|
||||
@@ -1250,7 +1262,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
: 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`}
|
||||
} w-full lg:w-72 xl:w-80 shrink-0 flex-col bg-panel border-r border-border transition-all duration-200`}
|
||||
>
|
||||
{/* Sidebar Header & Actions */}
|
||||
<div className="p-3 border-b border-border space-y-2">
|
||||
@@ -1261,7 +1273,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewFolderModal(true)}
|
||||
onClick={(e) => handleOpenNewFolder('', e)}
|
||||
className="btn-action p-1 text-muted hover:text-main"
|
||||
title="Neuer Ordner"
|
||||
>
|
||||
@@ -1303,23 +1315,19 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
|
||||
{/* Scrollable Navigation Tree */}
|
||||
<div className="flex-1 overflow-y-auto p-2 space-y-3">
|
||||
{/* Drop target for moving docs or folders to root */}
|
||||
{/* Top drop target for moving to root */}
|
||||
{(draggingDocId || draggingFolderPath) && (
|
||||
<div
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setDragOverFolder('__root__');
|
||||
setDragOverFolder('__root_top__');
|
||||
}}
|
||||
onDragLeave={() => setDragOverFolder(null)}
|
||||
onDrop={(e) => handleDropOnFolder('', e)}
|
||||
className={`p-2 border-2 border-dashed rounded-btn text-center text-xs transition-colors ${
|
||||
dragOverFolder === '__root__'
|
||||
? 'border-accent bg-accent/20 text-main font-semibold'
|
||||
: 'border-border text-muted bg-canvas'
|
||||
className={`h-2.5 -mx-1 my-0.5 rounded transition-all ${
|
||||
dragOverFolder === '__root_top__' ? 'bg-accent/40 ring-1 ring-accent h-4' : 'opacity-0'
|
||||
}`}
|
||||
>
|
||||
📁 In Hauptebene ablegen
|
||||
</div>
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Pinned Notes */}
|
||||
@@ -1342,7 +1350,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
||||
selectedDocId === d.id && subView === 'editor'
|
||||
? 'bg-panel border-accent text-main font-semibold shadow-xs'
|
||||
? 'bg-panel-elevated text-main font-semibold border-border/80'
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
@@ -1376,6 +1384,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
handleFolderDragStart={handleFolderDragStart}
|
||||
handleDocDragStart={handleDocDragStart}
|
||||
handleCreateDocument={handleCreateDocument}
|
||||
handleOpenNewFolder={handleOpenNewFolder}
|
||||
handleShareFolderClick={handleShareFolderClick}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
onSelectDoc={(id) => {
|
||||
@@ -1414,7 +1423,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
}}
|
||||
className={`w-full flex items-center gap-2 px-2 py-1.5 rounded-btn text-left text-xs cursor-pointer border transition-colors ${
|
||||
selectedDocId === d.id && subView === 'editor'
|
||||
? 'bg-panel border-accent text-main font-semibold shadow-xs'
|
||||
? 'bg-panel-elevated text-main font-semibold border-border/80'
|
||||
: 'border-transparent text-main hover:bg-panel-elevated'
|
||||
}`}
|
||||
>
|
||||
@@ -1429,6 +1438,21 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Bottom drop target for moving to root */}
|
||||
{(draggingDocId || draggingFolderPath) && (
|
||||
<div
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setDragOverFolder('__root_bottom__');
|
||||
}}
|
||||
onDragLeave={() => setDragOverFolder(null)}
|
||||
onDrop={(e) => handleDropOnFolder('', e)}
|
||||
className={`min-h-[2.5rem] flex-1 rounded transition-all ${
|
||||
dragOverFolder === '__root_bottom__' ? 'bg-accent/20 ring-1 ring-accent' : ''
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tags Explorer */}
|
||||
{tagsSummary.length > 0 && (
|
||||
<div>
|
||||
@@ -1586,7 +1610,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<div
|
||||
key={t.tag}
|
||||
onClick={() => handleSelectTag(t.tag)}
|
||||
className="p-4 rounded-card border border-border bg-canvas hover:border-accent hover:shadow-panel transition-all cursor-pointer group flex flex-col justify-between space-y-3"
|
||||
className="p-4 rounded-card border border-border bg-panel hover:border-accent hover:bg-panel-elevated transition-all cursor-pointer group flex flex-col justify-between space-y-3"
|
||||
>
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
@@ -1595,35 +1619,25 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
#
|
||||
</span>
|
||||
<h3 className="font-bold text-sm text-main group-hover:text-accent transition-colors truncate">
|
||||
#{t.tag}
|
||||
{t.tag.replace(/^#/, '')}
|
||||
</h3>
|
||||
</div>
|
||||
<span className="badge-neutral font-mono text-[10px] shrink-0">
|
||||
{t.count} {t.count === 1 ? 'Treffer' : 'Treffer'}
|
||||
{t.count} Treffer
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{meta && meta.docs.length > 0 && (
|
||||
<div className="space-y-1">
|
||||
<span className="text-[10px] font-semibold text-faint uppercase tracking-wider block">
|
||||
In {meta.docs.length} {meta.docs.length === 1 ? 'Notiz' : 'Notizen'}:
|
||||
Aktuellste Notiz:
|
||||
</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{meta.docs.slice(0, 3).map((d) => (
|
||||
<span
|
||||
key={d.id}
|
||||
className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-btn bg-panel-elevated text-muted truncate max-w-[140px]"
|
||||
title={d.title}
|
||||
>
|
||||
<span>{d.icon || '📝'}</span>
|
||||
<span className="truncate">{d.title}</span>
|
||||
</span>
|
||||
))}
|
||||
{meta.docs.length > 3 && (
|
||||
<span className="text-[10px] text-faint self-center">
|
||||
+{meta.docs.length - 3}
|
||||
</span>
|
||||
)}
|
||||
<div
|
||||
className="inline-flex items-center gap-1.5 text-[11px] px-2 py-0.5 rounded-btn bg-panel-elevated text-main truncate max-w-full"
|
||||
title={meta.docs[0].title}
|
||||
>
|
||||
<span>{meta.docs[0].icon || '📝'}</span>
|
||||
<span className="truncate">{meta.docs[0].title || 'Unbenannte Notiz'}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -1631,13 +1645,11 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
|
||||
<div className="flex items-center justify-between pt-2.5 border-t border-border/50 text-[11px]">
|
||||
<div>
|
||||
{meta && meta.openTodosCount > 0 ? (
|
||||
{meta && meta.openTodosCount > 0 && (
|
||||
<span className="text-status-warn text-[10px] flex items-center gap-1 font-medium">
|
||||
<Check className="w-2.5 h-2.5" />
|
||||
{meta.openTodosCount} offene To-Dos
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-faint text-[10px]">Blöcke & Childs anzeigen</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-accent text-xs font-semibold group-hover:translate-x-0.5 transition-transform flex items-center gap-0.5">
|
||||
@@ -1863,8 +1875,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<div className="w-full space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between border-b border-border pb-3 gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-main">
|
||||
Globaler To-Do Rollup ({totalOpenTodos} offen)
|
||||
<h2 className="text-base sm:text-lg font-bold text-main">
|
||||
To-Dos aus Notizen ({totalOpenTodos} offen)
|
||||
</h2>
|
||||
<p className="text-xs text-muted">
|
||||
Alle Aufgaben aus deinen Notizen im gesamten Workspace
|
||||
@@ -1872,60 +1884,37 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{/* View Grouping Selector */}
|
||||
<div className="flex items-center rounded-btn bg-panel p-0.5 border border-border shadow-xs">
|
||||
<span className="text-[10px] uppercase font-bold text-faint px-1.5 hidden sm:inline">
|
||||
Ansicht:
|
||||
</span>
|
||||
{/* View Grouping Selector (matching hashtag switcher style) */}
|
||||
<div className="flex items-center bg-canvas border border-border rounded-btn p-0.5 text-[11px]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTodoGrouping('folder_note');
|
||||
localStorage.setItem('mindsift_todo_grouping', 'folder_note');
|
||||
setTodoGrouping('grouped');
|
||||
localStorage.setItem('mindsift_todo_grouping', 'grouped');
|
||||
}}
|
||||
className={`px-2 py-1 text-[11px] font-semibold rounded-btn transition-colors cursor-pointer flex items-center gap-1 ${
|
||||
todoGrouping === 'folder_note'
|
||||
className={`px-2.5 py-1 rounded-btn font-semibold transition-colors cursor-pointer ${
|
||||
todoGrouping === 'grouped'
|
||||
? 'bg-panel-elevated text-main shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Erst nach Ordner, dann nach Notiz gruppieren"
|
||||
title="Sortiert nach Ordner und Notiz"
|
||||
>
|
||||
<Folder className="w-3 h-3 text-accent" />
|
||||
<span>+</span>
|
||||
<FileText className="w-3 h-3 text-accent" />
|
||||
<span>Ordner & Notiz</span>
|
||||
Gruppiert
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTodoGrouping('note');
|
||||
localStorage.setItem('mindsift_todo_grouping', 'note');
|
||||
setTodoGrouping('list');
|
||||
localStorage.setItem('mindsift_todo_grouping', 'list');
|
||||
}}
|
||||
className={`px-2 py-1 text-[11px] font-semibold rounded-btn transition-colors cursor-pointer flex items-center gap-1 ${
|
||||
todoGrouping === 'note'
|
||||
className={`px-2.5 py-1 rounded-btn font-semibold transition-colors cursor-pointer ${
|
||||
todoGrouping === 'list'
|
||||
? 'bg-panel-elevated text-main shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Direkt nach Notiz gruppieren"
|
||||
title="Alle To-Dos als einfache Liste"
|
||||
>
|
||||
<FileText className="w-3 h-3 text-accent" />
|
||||
<span>Notiz</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTodoGrouping('folder');
|
||||
localStorage.setItem('mindsift_todo_grouping', 'folder');
|
||||
}}
|
||||
className={`px-2 py-1 text-[11px] font-semibold rounded-btn transition-colors cursor-pointer flex items-center gap-1 ${
|
||||
todoGrouping === 'folder'
|
||||
? 'bg-panel-elevated text-main shadow-xs'
|
||||
: 'text-muted hover:text-main'
|
||||
}`}
|
||||
title="Nur nach Ordner gruppieren"
|
||||
>
|
||||
<Folder className="w-3 h-3 text-accent" />
|
||||
<span>Ordner</span>
|
||||
Liste
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1949,113 +1938,72 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
|
||||
{/* Grouped Folders & To-Dos */}
|
||||
{allRollupTodos.length === 0 ? (
|
||||
<div className="p-8 text-center text-muted text-xs space-y-1">
|
||||
<div className="p-8 text-center text-muted text-xs space-y-1 border border-border rounded-card bg-panel">
|
||||
<Sparkles className="w-6 h-6 text-accent mx-auto mb-1" />
|
||||
<p className="font-semibold text-main">Keine To-Dos in Notizen gefunden!</p>
|
||||
<p className="text-faint">
|
||||
Tippe in einem beliebigen Dokument <code>/todo</code> oder <code>[] </code> ein.
|
||||
</p>
|
||||
</div>
|
||||
) : todoGrouping === 'note' ? (
|
||||
/* Grouped by Note directly */
|
||||
<div className="space-y-3">
|
||||
{Array.from(rollupTodosByNote.entries()).map(([docId, { doc, items }]) => {
|
||||
const isCollapsed = collapsedTodoNotes.has(docId);
|
||||
const openCount = items.filter((i) => !i.checked).length;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={docId}
|
||||
className="border border-border rounded-card bg-canvas overflow-hidden"
|
||||
>
|
||||
<div className="p-2 bg-canvas border-b border-border flex items-center justify-between">
|
||||
<div
|
||||
onClick={() => {
|
||||
setCollapsedTodoNotes((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(docId)) next.delete(docId);
|
||||
else next.add(docId);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className="flex items-center gap-2 cursor-pointer select-none"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="w-3.5 h-3.5 text-faint" />
|
||||
) : (
|
||||
<ChevronDown className="w-3.5 h-3.5 text-accent" />
|
||||
)}
|
||||
<span className="text-sm">{doc.icon || '📝'}</span>
|
||||
<span className="font-bold text-xs text-main">{doc.title}</span>
|
||||
{doc.folderPath && (
|
||||
<span className="badge-neutral text-[10px]">📁 {doc.folderPath}</span>
|
||||
)}
|
||||
<span className="badge-neutral text-[10px] font-mono">
|
||||
{openCount} offen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleJumpToDocBlock(doc.id)}
|
||||
className="text-[10px] text-accent hover:underline px-1.5 py-0.5 rounded-btn hover:bg-canvas cursor-pointer flex items-center gap-1"
|
||||
title="Zur Notiz springen"
|
||||
>
|
||||
<span>Notiz öffnen</span>
|
||||
<ExternalLink className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!isCollapsed && (
|
||||
<div className="p-2 space-y-1">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={`${item.doc.id}-${item.blockId}`}
|
||||
className="flex items-center justify-between gap-2 p-1.5 rounded-btn hover:bg-panel-elevated text-xs transition-colors group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleRollupTodo(item.doc, item.blockId, item.checked);
|
||||
}}
|
||||
className={`w-3.5 h-3.5 checkbox-square flex items-center justify-center transition-colors border cursor-pointer ${
|
||||
item.checked
|
||||
? 'bg-status-ok border-status-ok text-on-accent'
|
||||
: 'border-strong hover:border-accent text-transparent'
|
||||
}`}
|
||||
>
|
||||
<Check className="w-2.5 h-2.5 stroke-[3]" />
|
||||
</button>
|
||||
<span
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className={`truncate flex-1 ${
|
||||
item.checked
|
||||
? 'line-through text-faint'
|
||||
: 'text-main font-medium group-hover:text-accent'
|
||||
}`}
|
||||
>
|
||||
{item.content}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className="text-faint hover:text-accent opacity-0 group-hover:opacity-100 transition-opacity p-1"
|
||||
title="Zu dieser Zeile springen"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
) : todoGrouping === 'list' ? (
|
||||
/* Flat list of all todos across notes */
|
||||
<div className="border border-border rounded-card bg-panel p-2 space-y-1">
|
||||
{allRollupTodos.map((item) => (
|
||||
<div
|
||||
key={`${item.doc.id}-${item.blockId}`}
|
||||
className="flex items-center justify-between gap-2 p-2 rounded-btn hover:bg-panel-elevated text-xs transition-colors group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleRollupTodo(item.doc, item.blockId, item.checked);
|
||||
}}
|
||||
className={`w-3.5 h-3.5 checkbox-square flex items-center justify-center transition-colors border cursor-pointer ${
|
||||
item.checked
|
||||
? 'bg-status-ok border-status-ok text-on-accent'
|
||||
: 'border-strong hover:border-accent text-transparent'
|
||||
}`}
|
||||
>
|
||||
<Check className="w-2.5 h-2.5 stroke-[3]" />
|
||||
</button>
|
||||
<span
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className={`truncate flex-1 ${
|
||||
item.checked
|
||||
? 'line-through text-faint'
|
||||
: 'text-main font-medium group-hover:text-accent'
|
||||
}`}
|
||||
>
|
||||
{item.content}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<span
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className="inline-flex items-center gap-1 text-[11px] px-2 py-0.5 rounded-btn bg-panel-elevated text-muted hover:text-main"
|
||||
title={item.doc.title}
|
||||
>
|
||||
<span>{item.doc.icon || '📝'}</span>
|
||||
<span className="truncate max-w-[120px]">{item.doc.title}</span>
|
||||
{item.doc.folderPath && (
|
||||
<span className="text-faint text-[10px]">({item.doc.folderPath})</span>
|
||||
)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className="text-faint hover:text-accent opacity-0 group-hover:opacity-100 transition-opacity p-1"
|
||||
title="Zu dieser Zeile springen"
|
||||
>
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : todoGrouping === 'folder_note' ? (
|
||||
) : (
|
||||
/* Grouped by Folder -> grouped by Note */
|
||||
<div className="space-y-3">
|
||||
{Array.from(rollupTodosByFolder.entries()).map(([folderName, items]) => {
|
||||
@@ -2073,10 +2021,10 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={folderName}
|
||||
className="border border-border rounded-card bg-canvas overflow-hidden"
|
||||
className="border border-border rounded-card bg-panel overflow-hidden"
|
||||
>
|
||||
{/* Folder Header */}
|
||||
<div className="p-2 bg-canvas border-b border-border flex items-center justify-between">
|
||||
<div className="p-2 bg-panel border-b border-border flex items-center justify-between">
|
||||
<div
|
||||
onClick={() => {
|
||||
setCollapsedTodoFolders((prev) => {
|
||||
@@ -2103,7 +2051,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTodoFolderDrilldown(folderName)}
|
||||
className="text-[10px] text-muted hover:text-main px-1.5 py-0.5 rounded-btn hover:bg-canvas cursor-pointer"
|
||||
className="text-[10px] text-muted hover:text-main px-1.5 py-0.5 rounded-btn hover:bg-panel-elevated cursor-pointer"
|
||||
title="Diesen Ordner isolieren"
|
||||
>
|
||||
Nur diesen Ordner
|
||||
@@ -2119,7 +2067,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={doc.id}
|
||||
className="border border-border/60 rounded-btn p-2 bg-panel-elevated/30 space-y-1.5"
|
||||
className="border border-border/60 rounded-btn p-2 bg-panel-elevated/40 space-y-1.5"
|
||||
>
|
||||
<div className="flex items-center justify-between pb-1 border-b border-border/40">
|
||||
<div
|
||||
@@ -2148,7 +2096,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleJumpToDocBlock(doc.id)}
|
||||
className="text-[10px] text-accent hover:underline px-1.5 py-0.5 rounded-btn hover:bg-canvas cursor-pointer flex items-center gap-1"
|
||||
className="text-[10px] text-accent hover:underline px-1.5 py-0.5 rounded-btn hover:bg-panel cursor-pointer flex items-center gap-1"
|
||||
title="Notiz öffnen"
|
||||
>
|
||||
<span>Notiz öffnen</span>
|
||||
@@ -2161,7 +2109,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
{docItems.map((item) => (
|
||||
<div
|
||||
key={`${item.doc.id}-${item.blockId}`}
|
||||
className="flex items-center justify-between gap-2 p-1 rounded-btn hover:bg-canvas text-xs transition-colors group cursor-pointer"
|
||||
className="flex items-center justify-between gap-2 p-1 rounded-btn hover:bg-panel-elevated text-xs transition-colors group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
@@ -2218,105 +2166,6 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
/* Grouped by Folder (Flat tasks inside folder) */
|
||||
<div className="space-y-3">
|
||||
{Array.from(rollupTodosByFolder.entries()).map(([folderName, items]) => {
|
||||
const isCollapsed = collapsedTodoFolders.has(folderName);
|
||||
const openCount = items.filter((i) => !i.checked).length;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={folderName}
|
||||
className="border border-border rounded-card bg-canvas overflow-hidden"
|
||||
>
|
||||
{/* Folder Header */}
|
||||
<div className="p-2 bg-canvas border-b border-border flex items-center justify-between">
|
||||
<div
|
||||
onClick={() => {
|
||||
setCollapsedTodoFolders((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(folderName)) next.delete(folderName);
|
||||
else next.add(folderName);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
className="flex items-center gap-2 cursor-pointer select-none"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="w-3.5 h-3.5 text-faint" />
|
||||
) : (
|
||||
<ChevronDown className="w-3.5 h-3.5 text-accent" />
|
||||
)}
|
||||
<Folder className="w-3.5 h-3.5 text-accent" />
|
||||
<span className="font-bold text-xs text-main">{folderName}</span>
|
||||
<span className="badge-neutral text-[10px] font-mono">
|
||||
{openCount} offen
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTodoFolderDrilldown(folderName)}
|
||||
className="text-[10px] text-muted hover:text-main px-1.5 py-0.5 rounded-btn hover:bg-canvas cursor-pointer"
|
||||
title="Diesen Ordner isolieren"
|
||||
>
|
||||
Nur diesen Ordner
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Folder Tasks */}
|
||||
{!isCollapsed && (
|
||||
<div className="p-2 space-y-1">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={`${item.doc.id}-${item.blockId}`}
|
||||
className="flex items-center justify-between gap-2 p-1.5 rounded-btn hover:bg-panel-elevated text-xs transition-colors group cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleToggleRollupTodo(item.doc, item.blockId, item.checked);
|
||||
}}
|
||||
className={`w-3.5 h-3.5 checkbox-square flex items-center justify-center transition-colors border cursor-pointer ${
|
||||
item.checked
|
||||
? 'bg-status-ok border-status-ok text-on-accent'
|
||||
: 'border-strong hover:border-accent text-transparent'
|
||||
}`}
|
||||
>
|
||||
<Check className="w-2.5 h-2.5 stroke-[3]" />
|
||||
</button>
|
||||
<span
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className={`truncate flex-1 ${
|
||||
item.checked
|
||||
? 'line-through text-faint'
|
||||
: 'text-main font-medium group-hover:text-accent'
|
||||
}`}
|
||||
>
|
||||
{item.content}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleJumpToDocBlock(item.doc.id, item.blockId)}
|
||||
className="text-[11px] text-accent hover:underline flex items-center gap-1 shrink-0 cursor-pointer"
|
||||
title="Zu dieser Zeile springen"
|
||||
>
|
||||
<span>{item.doc.icon || '📝'}</span>
|
||||
<span className="truncate max-w-[120px]">{item.doc.title}</span>
|
||||
<ExternalLink className="w-2.5 h-2.5 opacity-60" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2355,55 +2204,45 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* New Folder Modal */}
|
||||
{/* New Folder Popover (small & positioned near click, asks only for name & OK) */}
|
||||
{showNewFolderModal && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-overlay backdrop-blur-sm p-4">
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-black/20"
|
||||
onClick={() => setShowNewFolderModal(false)}
|
||||
>
|
||||
<form
|
||||
onSubmit={handleCreateFolderSubmit}
|
||||
className="w-full max-w-md panel-surface shadow-panel border border-border rounded-card p-4 space-y-3"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={
|
||||
newFolderCoords
|
||||
? { top: `${newFolderCoords.y}px`, left: `${newFolderCoords.x}px` }
|
||||
: undefined
|
||||
}
|
||||
className="fixed w-64 bg-panel border border-border rounded-card p-3 shadow-lg space-y-2.5 z-50 text-xs"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-bold text-main">
|
||||
Neuen Ordner anlegen
|
||||
</h3>
|
||||
<div className="font-semibold text-main text-xs flex items-center justify-between">
|
||||
<span className="truncate">{newFolderParentPath ? `Unterordner in "${newFolderParentPath}"` : 'Neuer Ordner'}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewFolderModal(false)}
|
||||
className="p-1 rounded-btn text-faint hover:text-main"
|
||||
className="text-faint hover:text-main cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-[11px] font-medium text-muted">
|
||||
Ordnerpfad (z.B. <code>Projekte/2026</code>)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={newFolderName}
|
||||
onChange={(e) => setNewFolderName(e.target.value)}
|
||||
placeholder="Projekte"
|
||||
className="input-field w-full px-2.5 py-1.5 text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-xs text-muted cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={newFolderIsShared}
|
||||
onChange={(e) => setNewFolderIsShared(e.target.checked)}
|
||||
className="checkbox-square"
|
||||
/>
|
||||
<span>Als geteilten Team-Ordner freigeben</span>
|
||||
</label>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-1">
|
||||
<input
|
||||
type="text"
|
||||
autoFocus
|
||||
value={newFolderName}
|
||||
onChange={(e) => setNewFolderName(e.target.value)}
|
||||
placeholder="Ordnername"
|
||||
className="input-field w-full px-2 py-1 text-xs"
|
||||
/>
|
||||
<div className="flex justify-end gap-1.5 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowNewFolderModal(false)}
|
||||
className="btn-action px-3 py-1 text-xs"
|
||||
className="btn-action px-2.5 py-1 text-xs"
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
@@ -2411,14 +2250,14 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
type="submit"
|
||||
className="btn-primary px-3 py-1 text-xs font-semibold"
|
||||
>
|
||||
Erstellen
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Unified Sharing Modal */}
|
||||
{/* Unified Sharing Modal (Strictly Read-Only for Docs and Folders) */}
|
||||
{(shareModalDoc || shareModalFolder) && (
|
||||
<UnifiedShareModal
|
||||
isOpen={true}
|
||||
@@ -2437,9 +2276,9 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
shares={unifiedShares}
|
||||
teams={teams}
|
||||
collaborators={collaborators}
|
||||
allowRoleSelection={!shareModalDoc}
|
||||
onAddShare={async (targetType, targetId, permission) => {
|
||||
const perm = (!shareModalDoc && permission === 'editor') ? 'write' : 'read';
|
||||
allowRoleSelection={false}
|
||||
fixedPermission="viewer"
|
||||
onAddShare={async (targetType, targetId) => {
|
||||
if (shareModalDoc) {
|
||||
await api.createDocShare(
|
||||
shareModalDoc.id,
|
||||
@@ -2453,7 +2292,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
|
||||
await api.createFolderShare(shareModalFolder.id, {
|
||||
targetType,
|
||||
targetId,
|
||||
permission: perm,
|
||||
permission: 'read',
|
||||
});
|
||||
const updatedFolder = await api.getSharedFolder(shareModalFolder.id);
|
||||
setShareModalFolder(updatedFolder);
|
||||
|
||||
Reference in new issue
Block a user