From 403b73e1a63a22b507a157e9220431d8c7bfcdfe Mon Sep 17 00:00:00 2001 From: Sahil Batra Date: Thu, 29 May 2025 12:37:51 +0530 Subject: [PATCH] channel_folders: Add support for channel folders in webapp. This commit adds code to handle channel folders data in webapp. --- web/src/channel_folders.ts | 34 +++++++++++++++++ web/src/state_data.ts | 14 +++++++ web/src/ui_init.js | 4 ++ web/tests/channel_folders.test.cjs | 59 ++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 web/src/channel_folders.ts create mode 100644 web/tests/channel_folders.test.cjs diff --git a/web/src/channel_folders.ts b/web/src/channel_folders.ts new file mode 100644 index 0000000000..0e55a011dc --- /dev/null +++ b/web/src/channel_folders.ts @@ -0,0 +1,34 @@ +import type {z} from "zod"; + +import {FoldDict} from "./fold_dict.ts"; +import type {StateData, channel_folder_schema} from "./state_data.ts"; + +export type ChannelFolder = z.infer; + +let channel_folder_name_dict: FoldDict; +let channel_folder_by_id_dict: Map; + +export function add(channel_folder: ChannelFolder): void { + channel_folder_name_dict.set(channel_folder.name, channel_folder); + channel_folder_by_id_dict.set(channel_folder.id, channel_folder); +} + +export function initialize(params: StateData["channel_folders"]): void { + channel_folder_name_dict = new FoldDict(); + channel_folder_by_id_dict = new Map(); + + for (const channel_folder of params.channel_folders) { + add(channel_folder); + } +} + +export function get_channel_folders(include_archived = false): ChannelFolder[] { + const channel_folders = [...channel_folder_by_id_dict.values()].sort((a, b) => a.id - b.id); + return channel_folders.filter((channel_folder) => { + if (!include_archived && channel_folder.is_archived) { + return false; + } + + return true; + }); +} diff --git a/web/src/state_data.ts b/web/src/state_data.ts index 8d040fad0f..e78cbc6d2c 100644 --- a/web/src/state_data.ts +++ b/web/src/state_data.ts @@ -141,6 +141,15 @@ export const raw_user_group_schema = z.object({ deactivated: z.boolean(), }); +export const channel_folder_schema = z.object({ + id: z.number(), + name: z.string(), + rendered_description: z.string(), + creator_id: z.number().nullable(), + date_created: z.number(), + is_archived: z.boolean(), +}); + export const user_topic_schema = z.object({ stream_id: z.number(), topic_name: z.string(), @@ -499,6 +508,11 @@ export const state_data_schema = z .object({realm_user_groups: z.array(raw_user_group_schema)}) .transform((user_groups) => ({user_groups})), ) + .and( + z + .object({channel_folders: z.array(channel_folder_schema)}) + .transform((channel_folders) => ({channel_folders})), + ) .and( z .object({ diff --git a/web/src/ui_init.js b/web/src/ui_init.js index 76b7d0d409..531e3d528c 100644 --- a/web/src/ui_init.js +++ b/web/src/ui_init.js @@ -21,6 +21,7 @@ import * as banners from "./banners.ts"; import * as blueslip from "./blueslip.ts"; import * as bot_data from "./bot_data.ts"; import * as channel from "./channel.ts"; +import * as channel_folders from "./channel_folders.ts"; import * as click_handlers from "./click_handlers.ts"; import * as color_picker_popover from "./color_picker_popover.ts"; import * as common from "./common.ts"; @@ -475,6 +476,9 @@ export async function initialize_everything(state_data) { // user_group whose members are allowed to create multiuse invite. user_groups.initialize(state_data.user_groups); + // Channel folders data must be initialized before left sidebar. + channel_folders.initialize(state_data.channel_folders); + // These components must be initialized early, because other // modules' initialization has not been audited for whether they // expect DOM elements to always exist (As that did before these diff --git a/web/tests/channel_folders.test.cjs b/web/tests/channel_folders.test.cjs new file mode 100644 index 0000000000..41c6e38a8e --- /dev/null +++ b/web/tests/channel_folders.test.cjs @@ -0,0 +1,59 @@ +"use strict"; + +const assert = require("node:assert/strict"); + +const {zrequire} = require("./lib/namespace.cjs"); +const {run_test} = require("./lib/test.cjs"); + +const channel_folders = zrequire("channel_folders"); + +run_test("basics", () => { + const params = {}; + const frontend_folder = { + name: "Frontend", + description: "Channels for frontend discussions", + rendered_description: "

Channels for frontend discussions

", + creator_id: null, + date_created: 1596710000, + id: 1, + is_archived: false, + }; + const backend_folder = { + name: "Backend", + description: "Channels for backend discussions", + rendered_description: "

Channels for backend discussions

", + creator_id: null, + date_created: 1596720000, + id: 2, + is_archived: false, + }; + params.channel_folders = [frontend_folder, backend_folder]; + channel_folders.initialize(params); + + assert.deepEqual(channel_folders.get_channel_folders(), [frontend_folder, backend_folder]); + + const devops_folder = { + name: "Devops", + description: "", + rendered_description: "", + creator_id: 1, + date_created: 1596810000, + id: 3, + is_archived: false, + }; + channel_folders.add(devops_folder); + assert.deepEqual(channel_folders.get_channel_folders(), [ + frontend_folder, + backend_folder, + devops_folder, + ]); + + devops_folder.is_archived = true; + assert.deepEqual(channel_folders.get_channel_folders(), [frontend_folder, backend_folder]); + + assert.deepEqual(channel_folders.get_channel_folders(true), [ + frontend_folder, + backend_folder, + devops_folder, + ]); +});