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:
@@ -6,6 +6,7 @@ import type {
|
||||
Space,
|
||||
SpaceWithChannelsAndMembers,
|
||||
Channel,
|
||||
ChannelCategory,
|
||||
MessageWithUser,
|
||||
MemberWithUser,
|
||||
Attachment,
|
||||
@@ -92,6 +93,13 @@ export class BackspaceApiClient {
|
||||
getOverrides: (channelId: string) => Promise<{ channelId: string; targetType: string; targetId: string; allow: string; deny: string }[]>;
|
||||
putOverride: (channelId: string, data: { targetType: string; targetId: string; allow: string; deny: string }) => Promise<{ success: boolean }>;
|
||||
deleteOverride: (channelId: string, targetType: string, targetId: string) => Promise<{ success: boolean }>;
|
||||
updateLayout: (spaceId: string, data: { channels: Array<{ id: string; position: number; categoryId: string | null }>; categories: Array<{ id: string; position: number }> }) => Promise<{ success: boolean }>;
|
||||
};
|
||||
|
||||
readonly categories: {
|
||||
create: (spaceId: string, name: string) => Promise<ChannelCategory>;
|
||||
update: (id: string, data: { name?: string; position?: number }) => Promise<ChannelCategory>;
|
||||
delete: (id: string) => Promise<{ success: boolean }>;
|
||||
};
|
||||
|
||||
readonly messages: {
|
||||
@@ -313,6 +321,17 @@ export class BackspaceApiClient {
|
||||
request<{ success: boolean }>('PUT', `/channels/${channelId}/overrides`, data),
|
||||
deleteOverride: (channelId: string, targetType: string, targetId: string) =>
|
||||
request<{ success: boolean }>('DELETE', `/channels/${channelId}/overrides/${targetType}/${targetId}`),
|
||||
updateLayout: (spaceId: string, data: { channels: Array<{ id: string; position: number; categoryId: string | null }>; categories: Array<{ id: string; position: number }> }) =>
|
||||
request<{ success: boolean }>('PATCH', `/spaces/${spaceId}/channel-layout`, data),
|
||||
};
|
||||
|
||||
this.categories = {
|
||||
create: (spaceId: string, name: string) =>
|
||||
request<ChannelCategory>('POST', `/spaces/${spaceId}/categories`, { name }),
|
||||
update: (id: string, data: { name?: string; position?: number }) =>
|
||||
request<ChannelCategory>('PATCH', `/categories/${id}`, data),
|
||||
delete: (id: string) =>
|
||||
request<{ success: boolean }>('DELETE', `/categories/${id}`),
|
||||
};
|
||||
|
||||
this.messages = {
|
||||
|
||||
Reference in New Issue
Block a user