feat(workspace): synchronize folders with server and add folder deletion
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s

This commit is contained in:
sandrodach committed 2026-10-25 11:50:34 +01:00
1 parent 1be0909344
commit c4b2a00348
5 files changed
+382 -49

No files matched your search

@@ -101,15 +101,13 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
const [showIconPicker, setShowIconPicker] = useState(false);
const [showWikiLinkMenu, setShowWikiLinkMenu] = useState(false);
// Folder state
const [customFolders, setCustomFolders] = useState<string[]>(() => {
// Folder state (synchronized with server)
const [customFolders, setCustomFolders] = useState<string[]>([]);
useEffect(() => {
try {
const stored = localStorage.getItem('mindsift_workspace_folders');
return stored ? JSON.parse(stored) : [];
} catch {
return [];
}
});
localStorage.removeItem('mindsift_workspace_folders');
} catch {}
}, []);
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
const [newFolderParent, setNewFolderParent] = useState<string | null>(null);
@@ -344,20 +342,28 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
};
const loadDocs = async (selectId?: string | null) => {
const list = await api.getDocuments();
setDocs(list);
if (selectId) {
const found = list.find((d) => d.id === selectId);
if (found) {
setSelectedDoc(found);
return;
try {
const [list, serverFolders] = await Promise.all([
api.getDocuments(),
api.getFolders(),
]);
setDocs(list);
setCustomFolders(serverFolders);
if (selectId) {
const found = list.find((d) => d.id === selectId);
if (found) {
setSelectedDoc(found);
return;
}
}
}
if (!selectedDoc && list.length > 0) {
setSelectedDoc(list[0]);
} else if (selectedDoc) {
const updated = list.find((d) => d.id === selectedDoc.id);
if (updated) setSelectedDoc(updated);
if (!selectedDoc && list.length > 0) {
setSelectedDoc(list[0]);
} else if (selectedDoc) {
const updated = list.find((d) => d.id === selectedDoc.id);
if (updated) setSelectedDoc(updated);
}
} catch (err) {
console.error('Error loading documents or folders:', err);
}
};
@@ -504,6 +510,8 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}
onStatsChange();
}
} else if (msg.event === 'WORKSPACE_FOLDERS_UPDATED') {
api.getFolders().then(setCustomFolders).catch(() => {});
}
};
@@ -817,25 +825,13 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
.catch((err) => console.error('Failed to update doc folder', err));
}
// 2. Update customFolders in state & localStorage
const updatedCustomFolders = customFolders.map((p) => {
if (p === cleanSource) return newFolderPath;
if (p.startsWith(cleanSource + '/')) {
return `${newFolderPath}${p.slice(cleanSource.length)}`;
}
return p;
});
if (!updatedCustomFolders.includes(newFolderPath)) {
updatedCustomFolders.push(newFolderPath);
}
const sortedCustom = Array.from(new Set(updatedCustomFolders)).sort((a, b) =>
a.localeCompare(b)
);
setCustomFolders(sortedCustom);
try {
localStorage.setItem('mindsift_workspace_folders', JSON.stringify(sortedCustom));
} catch {}
// 2. Sync folder structure with server
api
.createFolder(newFolderPath)
.then(() => api.deleteFolder(cleanSource))
.then(() => api.getFolders())
.then(setCustomFolders)
.catch((err) => console.error('Error syncing moved folders with server', err));
// Expand the new folder path and its parent
setExpandedFolders((prev) => {
@@ -881,20 +877,21 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
}
};
const handleConfirmCreateFolder = () => {
const handleConfirmCreateFolder = async () => {
const trimmed = newFolderName.trim();
if (!trimmed) {
setIsCreatingFolder(false);
return;
}
const fullPath = newFolderParent ? `${newFolderParent}/${trimmed}` : trimmed;
if (!customFolders.includes(fullPath)) {
const next = [...customFolders, fullPath].sort((a, b) => a.localeCompare(b));
setCustomFolders(next);
try {
localStorage.setItem('mindsift_workspace_folders', JSON.stringify(next));
} catch {
// ignore
try {
await api.createFolder(fullPath);
const serverFolders = await api.getFolders();
setCustomFolders(serverFolders);
} catch (err) {
console.error('Failed to create folder on server', err);
if (!customFolders.includes(fullPath)) {
setCustomFolders((prev) => [...prev, fullPath].sort((a, b) => a.localeCompare(b)));
}
}
setExpandedFolders((prev) => new Set([...prev, fullPath]));
@@ -903,6 +900,40 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
setNewFolderParent(null);
};
const handleDeleteFolder = async (folderPath: string, folderName: string) => {
// Find all documents in this folder and subfolders
const docsInFolder = docs.filter((d) => {
const f = getDocFolder(d);
return f === folderPath || f.startsWith(`${folderPath}/`);
});
const confirmMsg =
docsInFolder.length > 0
? `Möchtest du den Ordner "${folderName}" wirklich löschen? Alle ${docsInFolder.length} darin enthaltenen Notizen werden unwiderruflich gelöscht!`
: `Möchtest du den leeren Ordner "${folderName}" wirklich löschen?`;
if (!window.confirm(confirmMsg)) return;
try {
const res = await api.deleteFolder(folderPath);
const deletedIds = new Set(res.deletedDocIds || docsInFolder.map((d) => d.id));
const nextDocs = docs.filter((d) => !deletedIds.has(d.id));
setDocs(nextDocs);
if (selectedDoc && deletedIds.has(selectedDoc.id)) {
setSelectedDoc(nextDocs.length > 0 ? nextDocs[0] : null);
}
const serverFolders = await api.getFolders();
setCustomFolders(serverFolders);
onStatsChange();
} catch (err) {
console.error('Failed to delete folder', err);
alert('Fehler beim Löschen des Ordners');
}
};
const toggleFolder = (folderPath: string) => {
setExpandedFolders((prev) => {
const next = new Set(prev);
@@ -1688,6 +1719,17 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
>
<FolderPlus className="w-3 h-3" />
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
handleDeleteFolder(node.path, node.name);
}}
className="p-1 hover:bg-red-500/10 text-muted hover:text-red-400 rounded-btn transition-colors"
title="Ordner löschen"
>
<Trash2 className="w-3 h-3" />
</button>
</div>
</div>