Skip to content

Implement multi-select chats with bulk actions in sidebar#639

Open
igitur wants to merge 2 commits into
morethanwords:masterfrom
igitur:select-multiple-chats
Open

Implement multi-select chats with bulk actions in sidebar#639
igitur wants to merge 2 commits into
morethanwords:masterfrom
igitur:select-multiple-chats

Conversation

@igitur

@igitur igitur commented May 7, 2026

Copy link
Copy Markdown

This pull request introduces multi-select (bulk selection) functionality for dialogs (chats) in the sidebar, enabling users to select multiple chats and perform bulk actions such as marking as unread, muting, archiving, or deleting. It includes UI state management, context menu adjustments, and new popup dialogs for bulk actions.

Bulk selection and context menu enhancements:

  • Added multi-select state management to AppDialogsManager, including methods to enter, update, toggle, and exit chat selection mode, and to track selected chats (selectedPeerIds, isSelectingChats). Ctrl/Cmd+Click now toggles selection, and the UI updates accordingly. [1] [2] [3]
  • Updated DialogsContextMenu to detect when bulk selection is active and display a special context menu with bulk actions (Mark as Unread, Mute, Archive, Delete) when right-clicking a selected chat. [1] [2] [3] [4]

Bulk action implementations:

  • Implemented PopupBulkMute popup for muting multiple chats at once, allowing the user to select mute duration.
  • Implemented PopupDeleteDialogs popup for confirming deletion of multiple chats, including an option to also delete messages for contacts.
  • Added logic in context menu to handle bulk actions and clean up selection state after actions complete.

UI and language support:

  • Added new language strings for bulk chat deletion confirmation and options.
  • Stubbed sidebar methods for entering, updating, and exiting chat selection mode, to be implemented for visual feedback.

Supporting utilities and imports:

  • Added imports for new popups and async array filtering utility. [1] [2]

These changes collectively enable efficient management of multiple chats, improving usability for users handling large numbers of dialogs.

igitur added 2 commits May 7, 2026 14:30
- Ctrl+Click a chat to enter selection mode; additional Ctrl+Clicks toggle more chats
- The currently open chat is automatically included when entering selection mode
- Plain click while in selection mode exits selection and opens the clicked chat
- Selected chats use the same visual style as the active (open) chat
- Right-clicking a selected chat while multiple are selected shows a bulk Delete option
- Bulk delete confirmation popup mirrors the single-chat UX including an 'Also delete for contacts' checkbox when any selected chat is a private user conversation
- Escape / back button exits selection mode
…ntext menu

- Mark as Unread: loops markDialogUnread for all selected peers
- Mute: opens a new PopupBulkMute (mirrors PopupMute's time-picker UI) and applies the chosen duration to all selected peers
- Archive: calls editPeerFolders with all selected peerIds in one API call
@morethanwords

Copy link
Copy Markdown
Owner

Thanks for this — the selection state machine and the context-menu swap are a solid foundation. My feedback splits into two parts: the interaction/UX model (the main thing), and a recap of the correctness notes from my earlier review.

UX & interaction model

The client already ships a selection system, used both for messages in chat and for shared-media/search results: AppSelection and its SearchSelection subclass in src/components/chat/selection.ts, wired into appSearchSuper.ts. The current implementation introduces a parallel, simpler mechanism that diverges from it in three user-visible ways:

1. Selection should be shown with checkboxes, not a fill highlight.
Selected rows currently reuse the "active/open chat" background (setDialogActiveStatus via the is-selected-chat class, which has no CSS of its own). So a selected chat looks identical to the currently-open one, which is confusing. The established pattern appends a real CheckboxField to each item (AppSelection.appendCheckbox) — dialog rows should do the same.

2. Selection should be initiated from the menu, not Ctrl/Cmd+click.
Two issues here: Ctrl/Cmd+click is undiscoverable, and it overrides an existing gesture — the dialog click handler in appDialogsManager previously used Ctrl/Cmd+click to open a chat in a new tab (openDialogInNewTab), and the PR repurposes it for selection on regular chats. The conventional entry point is a "Select" item in the context menu (plus long-press on touch), matching how message selection is entered everywhere else. Ctrl+click is fine to keep as an extra power-user shortcut, but it shouldn't replace the new-tab gesture or be the only way in.

3. There's no selection action bar.
enterChatSelectionMode / updateChatSelectionCount / exitChatSelectionMode on AppSidebarLeft are empty stubs, so there's no header showing the count or the actions — the only way to act on a selection is to right-click a selected row. The reference is the toolbar that SearchSelection builds: a …-container with a live count element ("N selected") plus the bulk-action buttons and a cancel/Done control. That bar is what makes the feature both discoverable and complete.

Net: AppSelection already gives you checkboxes, the count bar, and clean enter/cancel. Extending or mirroring it for the dialog list would deliver all three of the above for free and keep behavior consistent with the rest of the client, rather than maintaining a second selection mechanism.

Correctness & code quality (recap of my earlier review)

Bringing these forward so everything is in one place:

  • 🔴 Bulk delete silently no-ops for channels and groups. PopupDeleteDialogs does flushHistory(...).catch(() => leave(...)), assuming flushHistory throws for groups/channels. It doesn't — for a channel it takes the channels.deleteHistory branch, for a basic group messages.deleteHistory; both resolve successfully (they just clear history), so the .catch → leave fallback never fires and the chat stays in the list. Only private chats are actually removed. Reuse the per-type logic already in PopupDeleteDialog (getDialogType → leave / appChatsManager.delete / flushHistory, chosen by hasRights('delete_chat')).
  • 🔴 Errors are swallowed (.catch(() => {})), so any real failure is invisible — no toast. Combined with the above, a user can "delete N chats" and have most of them remain with zero feedback.
  • 🟡 type: 'chat-multiselect' as any — add the literal to the NavigationItem union in appNavigationController.ts instead of casting the type away (there's already a 'multiselect' member there).
  • 🟡 PopupBulkMute is a near-verbatim copy of PopupMute (the entire times table is duplicated) and loosens langPackKey: LangPackKey to any. Prefer parameterizing PopupMute to accept PeerId | PeerId[] and keep the LangPackKey type.
  • 🟡 deleteDialogs.ts is missing the standard GPL license header that every other file has (including the sibling bulkMute.ts).
  • 🟡 The _sidebar.scss change is just two blank lines (no actual styles) — drop it, or replace it with the real .is-selected-chat style once checkboxes land.
  • 🟢 Minor: the bulk archive/mute actions don't exclude Saved Messages (myId) the way the single-item versions do; mark-unread/mute fire N un-awaited per-peer calls (archive correctly batches via editPeerFolders); and there are no tests around the delete dispatch logic.

Happy to help with any of these — especially the bulk-delete fix and extending AppSelection for the dialog list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants