feat: JMAP file/folder sharing in Files app #408

This commit is contained in:
Linus Rath
2026-06-12 00:02:45 +02:00
parent 20fd9ff4de
commit a4f476945d
29 changed files with 549 additions and 22 deletions
+39
View File
@@ -8,6 +8,7 @@ import {
ChevronRight,
ChevronDown,
Home,
Share2,
} from "lucide-react";
import { cn } from "@/lib/utils";
import { useFileStore, type FileResource } from "@/stores/file-store";
@@ -29,6 +30,8 @@ interface FolderTreeSidebarProps {
export function FolderTreeSidebar({ currentPath, onNavigate, listByParentId, width = 256, isResizing }: FolderTreeSidebarProps) {
const t = useTranslations("files");
const client = useFileStore(s => s.client);
const sharedRoots = useFileStore(s => s.sharedRoots);
const loadSharedRoots = useFileStore(s => s.loadSharedRoots);
const [rootChildren, setRootChildren] = useState<FolderNode[] | null>(null);
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set(["root"]));
const [loadingIds, setLoadingIds] = useState<Set<string>>(new Set());
@@ -81,6 +84,8 @@ export function FolderTreeSidebar({ currentPath, onNavigate, listByParentId, wid
useEffect(() => {
if (client) {
loadChildren(null, "/");
// Discover folders shared with the user by other principals.
loadSharedRoots();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [client]);
@@ -180,6 +185,40 @@ export function FolderTreeSidebar({ currentPath, onNavigate, listByParentId, wid
/>
))
)}
{/* Shared with me: folders another principal has shared with the user */}
{sharedRoots.filter(r => r.isDirectory).length > 0 && (
<div className="mt-2 pt-2 border-t border-border/60">
<div className="px-3 py-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground flex items-center gap-1.5">
<Share2 className="w-3 h-3" />
<span className="truncate">{t("shared_with_me")}</span>
</div>
{sharedRoots.filter(r => r.isDirectory).map(r => {
const path = `/${r.name}`;
const isSelected = currentPath === path;
return (
<div
key={r.id}
style={{ paddingBlock: "var(--density-sidebar-py)" }}
className={cn(
"group w-full flex items-center max-lg:min-h-[44px] text-sm transition-all duration-200 px-2",
isSelected ? "bg-accent text-accent-foreground" : "hover:bg-muted text-foreground"
)}
>
<button
onClick={() => handleFolderClick(path, r.id)}
className="flex items-center px-1 rounded transition-colors duration-150 flex-1 text-left min-w-0"
style={{ paddingBlock: "var(--density-sidebar-py)", paddingLeft: "24px" }}
title={r.ownerName ? t("shared_by", { name: r.ownerName }) : r.name}
>
<Folder className="w-4 h-4 flex-shrink-0 mr-2 text-primary" />
<span className="truncate">{r.name}</span>
</button>
</div>
);
})}
</div>
)}
</div>
</div>
);