feat: channel categories with drag-and-drop reordering
Add channel categories (named groups) with full CRUD, collapsible headers with unread indicators, and native HTML5 drag-and-drop for reordering channels within/between categories and reordering categories themselves. - Schema: channel_categories table, category_id column on channels - Server: category CRUD endpoints, batch channel-layout reorder endpoint - WebSocket: categories in ready payload, category_created/updated/deleted and channel_layout_updated events with per-user VIEW_CHANNEL filtering - Frontend: dynamic category-based sidebar layout replacing hardcoded Text/Voice sections, collapse state persisted to localStorage, category selector in CreateChannel modal, federation-aware store
This commit is contained in:
@@ -14,6 +14,7 @@ import type {
|
||||
UpdateMemberRequest,
|
||||
Space,
|
||||
Channel,
|
||||
ChannelCategory,
|
||||
MemberWithUser,
|
||||
SpaceWithChannelsAndMembers,
|
||||
Role,
|
||||
@@ -45,6 +46,7 @@ function rowToChannel(row: typeof schema.channels.$inferSelect): Channel {
|
||||
type: row.type as Channel['type'],
|
||||
topic: row.topic,
|
||||
position: row.position ?? 0,
|
||||
categoryId: row.categoryId ?? null,
|
||||
createdAt: row.createdAt,
|
||||
};
|
||||
}
|
||||
@@ -239,6 +241,19 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
})
|
||||
.filter((m): m is MemberWithUser => m !== null);
|
||||
|
||||
// Fetch categories for this space
|
||||
const categoryRows = db.select()
|
||||
.from(schema.channelCategories)
|
||||
.where(eq(schema.channelCategories.spaceId, id))
|
||||
.all();
|
||||
const categories: ChannelCategory[] = categoryRows.map(c => ({
|
||||
id: c.id,
|
||||
spaceId: c.spaceId,
|
||||
name: c.name,
|
||||
position: c.position ?? 0,
|
||||
createdAt: c.createdAt,
|
||||
}));
|
||||
|
||||
// Compute space-level permissions for the requesting user
|
||||
const spacePerms = computePermissions(request.userId, id);
|
||||
|
||||
@@ -259,6 +274,7 @@ export async function spaceRoutes(app: FastifyInstance): Promise<void> {
|
||||
const result: SpaceWithChannelsAndMembers = {
|
||||
...rowToSpace(server),
|
||||
channels: visibleChannels,
|
||||
categories,
|
||||
members,
|
||||
roles: roles.map(r => ({
|
||||
id: r.id,
|
||||
|
||||
Reference in New Issue
Block a user