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:
Jannis Braun
2026-03-12 01:19:06 +01:00
parent b6d44f1568
commit c51d6b1a0e
11 changed files with 1045 additions and 121 deletions
+9
View File
@@ -42,6 +42,14 @@ export const spaceMembers = sqliteTable('space_members', {
pk: primaryKey({ columns: [table.spaceId, table.userId] }),
}));
export const channelCategories = sqliteTable('channel_categories', {
id: text('id').primaryKey(),
spaceId: text('space_id').notNull().references(() => spaces.id, { onDelete: 'cascade' }),
name: text('name').notNull(),
position: integer('position').default(0),
createdAt: integer('created_at').notNull(),
});
export const channels = sqliteTable('channels', {
id: text('id').primaryKey(),
spaceId: text('space_id').notNull().references(() => spaces.id, { onDelete: 'cascade' }),
@@ -49,6 +57,7 @@ export const channels = sqliteTable('channels', {
type: text('type').notNull(),
topic: text('topic'),
position: integer('position').default(0),
categoryId: text('category_id'),
createdAt: integer('created_at').notNull(),
});