diff --git a/docs/THIRDPARTY b/docs/THIRDPARTY index c36ed532ec..05ec1d8059 100644 --- a/docs/THIRDPARTY +++ b/docs/THIRDPARTY @@ -323,6 +323,10 @@ Source: https://lucide.dev/icons/undo-2 Copyright: 2013-2022 Cole Bemis License: ISC License +Files: web/shared/icons/folder.svg +Copyright: 2013-2023 Cole Bemis (https://lucide.dev/icons/folder) +License: ISC License + Files: web/third/bootstrap/css/bootstrap.app.css Copyright: 2012 Twitter, Inc. License: Apache-2.0 diff --git a/tools/test-js-with-node b/tools/test-js-with-node index 623e3e6a31..98e7065f72 100755 --- a/tools/test-js-with-node +++ b/tools/test-js-with-node @@ -222,6 +222,7 @@ EXEMPT_FILES = make_set( "web/src/settings_components.ts", "web/src/settings_emoji.ts", "web/src/settings_exports.ts", + "web/src/settings_folders.ts", "web/src/settings_invites.ts", "web/src/settings_linkifiers.ts", "web/src/settings_muted_users.ts", diff --git a/web/shared/icons/folder.svg b/web/shared/icons/folder.svg new file mode 100644 index 0000000000..82b158c203 --- /dev/null +++ b/web/shared/icons/folder.svg @@ -0,0 +1,8 @@ + + + diff --git a/web/src/channel_folders.ts b/web/src/channel_folders.ts index 97d81e85d8..ce54806e61 100644 --- a/web/src/channel_folders.ts +++ b/web/src/channel_folders.ts @@ -26,7 +26,7 @@ export function initialize(params: StateData["channel_folders"]): void { } 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); + const channel_folders = [...channel_folder_by_id_dict.values()]; return channel_folders.filter((channel_folder) => { if (!include_archived && channel_folder.is_archived) { return false; diff --git a/web/src/server_events_dispatch.js b/web/src/server_events_dispatch.js index 737e4cfd9d..9f19386d97 100644 --- a/web/src/server_events_dispatch.js +++ b/web/src/server_events_dispatch.js @@ -63,6 +63,7 @@ import * as settings_components from "./settings_components.ts"; import * as settings_config from "./settings_config.ts"; import * as settings_emoji from "./settings_emoji.ts"; import * as settings_exports from "./settings_exports.ts"; +import * as settings_folders from "./settings_folders.ts"; import * as settings_invites from "./settings_invites.ts"; import * as settings_linkifiers from "./settings_linkifiers.ts"; import * as settings_notifications from "./settings_notifications.ts"; @@ -122,6 +123,7 @@ export function dispatch_normal_event(event) { case "add": { channel_folders.add(event.channel_folder); inbox_ui.complete_rerender(); + settings_folders.populate_channel_folders(); break; } case "update": diff --git a/web/src/settings_folders.ts b/web/src/settings_folders.ts new file mode 100644 index 0000000000..118a0fc396 --- /dev/null +++ b/web/src/settings_folders.ts @@ -0,0 +1,99 @@ +import $ from "jquery"; +import SortableJS from "sortablejs"; + +import render_admin_channel_folder_list_item from "../templates/settings/admin_channel_folder_list_item.hbs"; + +import * as channel from "./channel.ts"; +import * as channel_folders from "./channel_folders.ts"; +import * as channel_folders_ui from "./channel_folders_ui.ts"; +import * as ListWidget from "./list_widget.ts"; +import * as settings_ui from "./settings_ui.ts"; +import {current_user} from "./state_data.ts"; +import * as util from "./util.ts"; + +const meta = { + loaded: false, +}; + +function update_folder_order(this: HTMLElement): void { + const order: number[] = []; + $(".channel-folder-row").each(function () { + order.push(Number.parseInt($(this).attr("data-channel-folder-id")!, 10)); + }); + settings_ui.do_settings_change( + channel.patch, + "/json/channel_folders", + {order: JSON.stringify(order)}, + $("#admin-channel-folder-status").expectOne(), + ); +} + +export function do_populate_channel_folders(): void { + const folders_data = channel_folders.get_channel_folders(); + + const $channel_folders_table = $("#admin_channel_folders_table").expectOne(); + + ListWidget.create($channel_folders_table, folders_data, { + name: "channel_folders_list", + get_item(folder) { + return folder; + }, + modifier_html(folder) { + return render_admin_channel_folder_list_item({ + folder_name: folder.name, + folder_description: folder.description, + id: folder.id, + is_admin: current_user.is_admin, + }); + }, + $parent_container: $("#channel-folder-settings").expectOne(), + $simplebar_container: $("#channel-folder-settings .progressive-table-wrapper"), + }); + + if (current_user.is_admin) { + const field_list = util.the($("#admin_channel_folders_table")); + SortableJS.create(field_list, { + onUpdate: update_folder_order, + filter: "input", + preventOnFilter: false, + }); + } +} + +export function populate_channel_folders(): void { + if (!meta.loaded) { + // If outside callers call us when we're not loaded, just + // exit and we'll draw the widgets again during set_up(). + return; + } + do_populate_channel_folders(); +} + +export function set_up(): void { + do_populate_channel_folders(); + meta.loaded = true; + + $("#channel-folder-settings").on( + "click", + ".add-channel-folder-button", + channel_folders_ui.add_channel_folder, + ); + $("#channel-folder-settings").on("click", ".edit-channel-folder-button", (e) => { + const folder_id = Number.parseInt( + $(e.target).closest(".channel-folder-row").attr("data-channel-folder-id")!, + 10, + ); + channel_folders_ui.handle_editing_channel_folder(folder_id); + }); + $("#channel-folder-settings").on("click", ".archive-channel-folder-button", (e) => { + const folder_id = Number.parseInt( + $(e.target).closest(".channel-folder-row").attr("data-channel-folder-id")!, + 10, + ); + channel_folders_ui.handle_archiving_channel_folder(folder_id); + }); +} + +export function reset(): void { + meta.loaded = false; +} diff --git a/web/src/settings_sections.ts b/web/src/settings_sections.ts index cc4f59760d..a631eb2967 100644 --- a/web/src/settings_sections.ts +++ b/web/src/settings_sections.ts @@ -7,6 +7,7 @@ import * as settings_account from "./settings_account.ts"; import * as settings_bots from "./settings_bots.ts"; import * as settings_emoji from "./settings_emoji.ts"; import * as settings_exports from "./settings_exports.ts"; +import * as settings_folders from "./settings_folders.ts"; import * as settings_invites from "./settings_invites.ts"; import * as settings_linkifiers from "./settings_linkifiers.ts"; import * as settings_muted_users from "./settings_muted_users.ts"; @@ -78,6 +79,7 @@ export function initialize(): void { "organization-level-user-defaults", settings_realm_user_settings_defaults.set_up, ); + load_func_dict.set("channel-folders", settings_folders.set_up); } export function load_settings_section(section: string): void { @@ -115,5 +117,6 @@ export function reset_sections(): void { settings_user_topics.reset(); settings_muted_users.reset(); alert_words_ui.reset(); + settings_folders.reset(); // settings_users doesn't need a reset() } diff --git a/web/styles/settings.css b/web/styles/settings.css index 63415b0864..271aaaf360 100644 --- a/web/styles/settings.css +++ b/web/styles/settings.css @@ -902,7 +902,8 @@ input[type="checkbox"] { .admin_profile_fields_table, .edit_profile_field_choices_container, .profile_field_choices_table, -.admin_linkifiers_table { +.admin_linkifiers_table, +.admin_channel_folders_table { .movable-row { .move-handle { cursor: move; diff --git a/web/templates/settings/admin_channel_folder_list_item.hbs b/web/templates/settings/admin_channel_folder_list_item.hbs new file mode 100644 index 0000000000..4c02bd7670 --- /dev/null +++ b/web/templates/settings/admin_channel_folder_list_item.hbs @@ -0,0 +1,31 @@ + + + {{#if is_admin}} + + + + + {{/if}} + {{folder_name}} + + + {{folder_description}} + + {{#if is_admin}} + + {{> ../components/icon_button + icon="edit" + intent="neutral" + custom_classes="tippy-zulip-delayed-tooltip edit-channel-folder-button" + data-tippy-content=(t "Edit") + }} + {{> ../components/icon_button + icon="trash" + intent="danger" + custom_classes="tippy-zulip-delayed-tooltip archive-channel-folder-button" + data-tippy-content=(t "Delete") + aria-label=(t "Delete") + }} + + {{/if}} + diff --git a/web/templates/settings/admin_channel_folders.hbs b/web/templates/settings/admin_channel_folders.hbs new file mode 100644 index 0000000000..05c82fea9e --- /dev/null +++ b/web/templates/settings/admin_channel_folders.hbs @@ -0,0 +1,28 @@ +
+
+

{{t "Channel folders"}}

+
+ {{#if is_admin}} + {{> ../components/action_button + custom_classes="add-channel-folder-button" + label=(t "Add a new channel folder") + attention="quiet" + intent="brand" + }} + {{/if}} +
+
+ + + + + + {{#if is_admin}} + + {{/if}} + + + +
{{t "Name" }}{{t "Description" }}{{t "Actions" }}
+
+
diff --git a/web/templates/settings/admin_tab.hbs b/web/templates/settings/admin_tab.hbs index 6c24f298f7..367050892a 100644 --- a/web/templates/settings/admin_tab.hbs +++ b/web/templates/settings/admin_tab.hbs @@ -17,6 +17,8 @@ {{> bot_list_admin . }} +{{> admin_channel_folders .}} + {{> default_streams_list_admin . }} {{> auth_methods_settings_admin . }} diff --git a/web/templates/settings_overlay.hbs b/web/templates/settings_overlay.hbs index b8d5ecbfb1..24d11e5805 100644 --- a/web/templates/settings_overlay.hbs +++ b/web/templates/settings_overlay.hbs @@ -109,6 +109,11 @@
{{t "Default user settings" }}
+ {{#unless is_guest}}