feat: space sidebar drag-and-drop reordering with folder system

Add user_space_layout table and PUT /api/users/@me/space-layout endpoint
for persisting per-user sidebar ordering. Spaces can be freely reordered
via drag-and-drop, folders created by dragging one space onto another,
and folders auto-dissolve when they have fewer than 2 members. Includes
folder context menu (rename, color, ungroup), collapsed folder mini-grid
icons, multi-tab sync via WebSocket, and localStorage collapse state.
Removes the rigid native/federated split — federated spaces now intermix
freely while keeping their globe badge.
This commit is contained in:
Jannis Braun
2026-03-12 03:38:22 +01:00
parent 39174e5454
commit dbd964d40c
12 changed files with 1006 additions and 93 deletions
+15
View File
@@ -68,6 +68,19 @@ function buildFullSpace(spaceId: string, forUserId: string): SpaceWithChannelsAn
.where(eq(schema.channels.spaceId, spaceId))
.all();
const categories = db.select()
.from(schema.channelCategories)
.where(eq(schema.channelCategories.spaceId, spaceId))
.orderBy(schema.channelCategories.position)
.all()
.map(c => ({
id: c.id,
spaceId: c.spaceId,
name: c.name,
position: c.position ?? 0,
createdAt: c.createdAt,
}));
const roles = db.select()
.from(schema.roles)
.where(eq(schema.roles.spaceId, spaceId))
@@ -135,6 +148,7 @@ function buildFullSpace(spaceId: string, forUserId: string): SpaceWithChannelsAn
type: ch.type as Channel['type'],
topic: ch.topic,
position: ch.position ?? 0,
categoryId: ch.categoryId ?? null,
createdAt: ch.createdAt,
myPermissions: permissionsToString(chPerms),
});
@@ -153,6 +167,7 @@ function buildFullSpace(spaceId: string, forUserId: string): SpaceWithChannelsAn
description: space.description ?? null,
createdAt: space.createdAt,
channels: visibleChannels,
categories,
members,
roles: roles.map(r => ({
id: r.id,