stream-chat-react 14.4.1 → 14.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/dist/cjs/{ReactPlayerWrapper.16cd6fed.js → ReactPlayerWrapper.30240f76.js} +2 -2
  2. package/dist/cjs/{ReactPlayerWrapper.16cd6fed.js.map → ReactPlayerWrapper.30240f76.js.map} +1 -1
  3. package/dist/cjs/channel-detail.js +3007 -0
  4. package/dist/cjs/channel-detail.js.map +1 -0
  5. package/dist/cjs/emojis.js +5 -4
  6. package/dist/cjs/emojis.js.map +1 -1
  7. package/dist/cjs/index.js +2273 -4163
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/useChannelHeaderOnlineStatus.6546ac83.js +4143 -0
  10. package/dist/cjs/useChannelHeaderOnlineStatus.6546ac83.js.map +1 -0
  11. package/dist/cjs/useMessageComposerController.c0dad9bc.js +99 -0
  12. package/dist/cjs/useMessageComposerController.c0dad9bc.js.map +1 -0
  13. package/dist/cjs/{useNotificationApi.9ffe5761.js → useNotificationApi.eb753f31.js} +156 -168
  14. package/dist/cjs/useNotificationApi.eb753f31.js.map +1 -0
  15. package/dist/css/channel-detail.css +825 -0
  16. package/dist/css/channel-detail.css.map +1 -0
  17. package/dist/css/index.css +297 -31
  18. package/dist/css/index.css.map +1 -1
  19. package/dist/es/channel-detail.mjs +2950 -0
  20. package/dist/es/channel-detail.mjs.map +1 -0
  21. package/dist/es/emojis.mjs +2 -1
  22. package/dist/es/emojis.mjs.map +1 -1
  23. package/dist/es/index.mjs +1859 -3774
  24. package/dist/es/index.mjs.map +1 -1
  25. package/dist/es/useChannelHeaderOnlineStatus.c5215b13.mjs +3600 -0
  26. package/dist/es/useChannelHeaderOnlineStatus.c5215b13.mjs.map +1 -0
  27. package/dist/es/useMessageComposerController.29f189b4.mjs +69 -0
  28. package/dist/es/useMessageComposerController.29f189b4.mjs.map +1 -0
  29. package/dist/es/{useNotificationApi.88c33caa.mjs → useNotificationApi.fa5cddf9.mjs} +123 -141
  30. package/dist/es/useNotificationApi.fa5cddf9.mjs.map +1 -0
  31. package/dist/types/components/AudioPlayback/components/index.d.ts +1 -0
  32. package/dist/types/components/AudioPlayback/components/index.d.ts.map +1 -1
  33. package/dist/types/components/Avatar/Avatar.d.ts +4 -2
  34. package/dist/types/components/Avatar/Avatar.d.ts.map +1 -1
  35. package/dist/types/components/Avatar/ChannelAvatar.d.ts.map +1 -1
  36. package/dist/types/components/Channel/hooks/useMentionsHandlers.d.ts +8 -3
  37. package/dist/types/components/Channel/hooks/useMentionsHandlers.d.ts.map +1 -1
  38. package/dist/types/components/ChannelHeader/hooks/useChannelHasMembersOnline.d.ts +7 -0
  39. package/dist/types/components/ChannelHeader/hooks/useChannelHasMembersOnline.d.ts.map +1 -0
  40. package/dist/types/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.d.ts +6 -1
  41. package/dist/types/components/ChannelHeader/hooks/useChannelHeaderOnlineStatus.d.ts.map +1 -1
  42. package/dist/types/components/ChannelListItem/hooks/index.d.ts +1 -0
  43. package/dist/types/components/ChannelListItem/hooks/index.d.ts.map +1 -1
  44. package/dist/types/components/ChannelListItem/hooks/useChannelPreviewInfo.d.ts.map +1 -1
  45. package/dist/types/components/ChannelListItem/hooks/useIsUserMuted.d.ts +2 -0
  46. package/dist/types/components/ChannelListItem/hooks/useIsUserMuted.d.ts.map +1 -0
  47. package/dist/types/components/Chat/Chat.d.ts.map +1 -1
  48. package/dist/types/components/Dialog/components/Prompt.d.ts +7 -4
  49. package/dist/types/components/Dialog/components/Prompt.d.ts.map +1 -1
  50. package/dist/types/components/Dialog/service/DialogPortal.d.ts +5 -1
  51. package/dist/types/components/Dialog/service/DialogPortal.d.ts.map +1 -1
  52. package/dist/types/components/FileIcon/iconMap.d.ts.map +1 -1
  53. package/dist/types/components/Form/Checkbox.d.ts +8 -0
  54. package/dist/types/components/Form/Checkbox.d.ts.map +1 -0
  55. package/dist/types/components/Form/SwitchField.d.ts +6 -0
  56. package/dist/types/components/Form/SwitchField.d.ts.map +1 -1
  57. package/dist/types/components/Form/index.d.ts +1 -0
  58. package/dist/types/components/Form/index.d.ts.map +1 -1
  59. package/dist/types/components/Icons/icons.d.ts +24 -0
  60. package/dist/types/components/Icons/icons.d.ts.map +1 -1
  61. package/dist/types/components/InfiniteScrollPaginator/index.d.ts +1 -0
  62. package/dist/types/components/InfiniteScrollPaginator/index.d.ts.map +1 -1
  63. package/dist/types/components/ListItemLayout/ListItemLayout.d.ts +34 -0
  64. package/dist/types/components/ListItemLayout/ListItemLayout.d.ts.map +1 -0
  65. package/dist/types/components/ListItemLayout/index.d.ts +2 -0
  66. package/dist/types/components/ListItemLayout/index.d.ts.map +1 -0
  67. package/dist/types/components/MediaRecorder/classes/MediaRecorderController.d.ts.map +1 -1
  68. package/dist/types/components/Message/MessageText.d.ts.map +1 -1
  69. package/dist/types/components/Message/hooks/useMentionsHandler.d.ts +6 -1
  70. package/dist/types/components/Message/hooks/useMentionsHandler.d.ts.map +1 -1
  71. package/dist/types/components/Message/hooks/useMessageTextStreaming.d.ts +1 -1
  72. package/dist/types/components/Message/hooks/useReactionsFetcher.d.ts +1 -1
  73. package/dist/types/components/Message/hooks/useReactionsFetcher.d.ts.map +1 -1
  74. package/dist/types/components/Message/renderText/componentRenderers/Mention.d.ts +22 -4
  75. package/dist/types/components/Message/renderText/componentRenderers/Mention.d.ts.map +1 -1
  76. package/dist/types/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.d.ts +44 -3
  77. package/dist/types/components/Message/renderText/rehypePlugins/mentionsMarkdownPlugin.d.ts.map +1 -1
  78. package/dist/types/components/Message/renderText/renderText.d.ts +25 -3
  79. package/dist/types/components/Message/renderText/renderText.d.ts.map +1 -1
  80. package/dist/types/components/Message/types.d.ts +2 -3
  81. package/dist/types/components/Message/types.d.ts.map +1 -1
  82. package/dist/types/components/MessageComposer/QuotedMessagePreview.d.ts.map +1 -1
  83. package/dist/types/components/MessageComposer/hooks/useMessageComposerController.d.ts.map +1 -1
  84. package/dist/types/components/Modal/GlobalModal.d.ts +3 -1
  85. package/dist/types/components/Modal/GlobalModal.d.ts.map +1 -1
  86. package/dist/types/components/Notifications/hooks/useNotificationApi.d.ts.map +1 -1
  87. package/dist/types/components/Poll/PollCreationDialog/MultipleAnswersField.d.ts.map +1 -1
  88. package/dist/types/components/Poll/PollOptionSelector.d.ts +0 -4
  89. package/dist/types/components/Poll/PollOptionSelector.d.ts.map +1 -1
  90. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/BroadcastMentionItem.d.ts +5 -0
  91. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/BroadcastMentionItem.d.ts.map +1 -0
  92. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/MentionItem.d.ts +17 -0
  93. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/MentionItem.d.ts.map +1 -0
  94. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/MentionSuggestionTitle.d.ts +6 -0
  95. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/MentionSuggestionTitle.d.ts.map +1 -0
  96. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/RoleItem.d.ts +5 -0
  97. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/RoleItem.d.ts.map +1 -0
  98. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/SpecialMentionItem.d.ts +10 -0
  99. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/SpecialMentionItem.d.ts.map +1 -0
  100. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.d.ts +5 -0
  101. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/UserGroupItem.d.ts.map +1 -0
  102. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/UserItem.d.ts +17 -0
  103. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/UserItem.d.ts.map +1 -0
  104. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/index.d.ts +8 -0
  105. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/index.d.ts.map +1 -0
  106. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/types.d.ts +6 -0
  107. package/dist/types/components/TextareaComposer/SuggestionList/MentionItem/types.d.ts.map +1 -0
  108. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionList.d.ts.map +1 -1
  109. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionListItem.d.ts +2 -2
  110. package/dist/types/components/TextareaComposer/SuggestionList/SuggestionListItem.d.ts.map +1 -1
  111. package/dist/types/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.d.ts +9 -0
  112. package/dist/types/components/TextareaComposer/SuggestionList/TokenizedSuggestionParts.d.ts.map +1 -0
  113. package/dist/types/components/TextareaComposer/SuggestionList/index.d.ts +2 -1
  114. package/dist/types/components/TextareaComposer/SuggestionList/index.d.ts.map +1 -1
  115. package/dist/types/components/TextareaComposer/TextareaComposer.d.ts.map +1 -1
  116. package/dist/types/components/index.d.ts +1 -0
  117. package/dist/types/components/index.d.ts.map +1 -1
  118. package/dist/types/context/DialogManagerContext.d.ts +3 -1
  119. package/dist/types/context/DialogManagerContext.d.ts.map +1 -1
  120. package/dist/types/context/MessageContext.d.ts +3 -3
  121. package/dist/types/context/MessageContext.d.ts.map +1 -1
  122. package/dist/types/i18n/Streami18n.d.ts +105 -1
  123. package/dist/types/i18n/Streami18n.d.ts.map +1 -1
  124. package/dist/types/plugins/ChannelDetail/AvatarWithChannelDetail.d.ts +9 -0
  125. package/dist/types/plugins/ChannelDetail/AvatarWithChannelDetail.d.ts.map +1 -0
  126. package/dist/types/plugins/ChannelDetail/ChannelDetail.d.ts +14 -0
  127. package/dist/types/plugins/ChannelDetail/ChannelDetail.d.ts.map +1 -0
  128. package/dist/types/plugins/ChannelDetail/ChannelDetailContext.d.ts +11 -0
  129. package/dist/types/plugins/ChannelDetail/ChannelDetailContext.d.ts.map +1 -0
  130. package/dist/types/plugins/ChannelDetail/ChannelDetailEmptyList.d.ts +3 -0
  131. package/dist/types/plugins/ChannelDetail/ChannelDetailEmptyList.d.ts.map +1 -0
  132. package/dist/types/plugins/ChannelDetail/ChannelDetailListLoadingIndicator.d.ts +6 -0
  133. package/dist/types/plugins/ChannelDetail/ChannelDetailListLoadingIndicator.d.ts.map +1 -0
  134. package/dist/types/plugins/ChannelDetail/ChannelDetailNavButton.d.ts +15 -0
  135. package/dist/types/plugins/ChannelDetail/ChannelDetailNavButton.d.ts.map +1 -0
  136. package/dist/types/plugins/ChannelDetail/ChannelDetailSearchInput.d.ts +8 -0
  137. package/dist/types/plugins/ChannelDetail/ChannelDetailSearchInput.d.ts.map +1 -0
  138. package/dist/types/plugins/ChannelDetail/SectionNavigator/SectionNavigator.d.ts +52 -0
  139. package/dist/types/plugins/ChannelDetail/SectionNavigator/SectionNavigator.d.ts.map +1 -0
  140. package/dist/types/plugins/ChannelDetail/SectionNavigator/SectionNavigatorHeader.d.ts +11 -0
  141. package/dist/types/plugins/ChannelDetail/SectionNavigator/SectionNavigatorHeader.d.ts.map +1 -0
  142. package/dist/types/plugins/ChannelDetail/SectionNavigator/index.d.ts +3 -0
  143. package/dist/types/plugins/ChannelDetail/SectionNavigator/index.d.ts.map +1 -0
  144. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesEmptyList.d.ts +2 -0
  145. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesEmptyList.d.ts.map +1 -0
  146. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.d.ts +5 -0
  147. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.d.ts.map +1 -0
  148. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.d.ts +40 -0
  149. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.d.ts.map +1 -0
  150. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/index.d.ts +5 -0
  151. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/index.d.ts.map +1 -0
  152. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.d.ts +10 -0
  153. package/dist/types/plugins/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.d.ts.map +1 -0
  154. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.d.ts +16 -0
  155. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.d.ts.map +1 -0
  156. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.d.ts +20 -0
  157. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.d.ts.map +1 -0
  158. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/index.d.ts +3 -0
  159. package/dist/types/plugins/ChannelDetail/Views/ChannelManagementView/index.d.ts.map +1 -0
  160. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaEmptyList.d.ts +2 -0
  161. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaEmptyList.d.ts.map +1 -0
  162. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.d.ts +8 -0
  163. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.d.ts.map +1 -0
  164. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.utils.d.ts +22 -0
  165. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.utils.d.ts.map +1 -0
  166. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/index.d.ts +5 -0
  167. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/index.d.ts.map +1 -0
  168. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.d.ts +9 -0
  169. package/dist/types/plugins/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.d.ts.map +1 -0
  170. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.d.ts +26 -0
  171. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.d.ts.map +1 -0
  172. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberDetail.d.ts +12 -0
  173. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberDetail.d.ts.map +1 -0
  174. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/index.d.ts +3 -0
  175. package/dist/types/plugins/ChannelDetail/Views/ChannelMemberDetailView/index.d.ts.map +1 -0
  176. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.d.ts +7 -0
  177. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.d.ts.map +1 -0
  178. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.d.ts +6 -0
  179. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.d.ts.map +1 -0
  180. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.d.ts +45 -0
  181. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.d.ts.map +1 -0
  182. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.d.ts +46 -0
  183. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.d.ts.map +1 -0
  184. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.d.ts +8 -0
  185. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.d.ts.map +1 -0
  186. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/index.d.ts +6 -0
  187. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/index.d.ts.map +1 -0
  188. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberCount.d.ts +4 -0
  189. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberCount.d.ts.map +1 -0
  190. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberIds.d.ts +4 -0
  191. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberIds.d.ts.map +1 -0
  192. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.d.ts +12 -0
  193. package/dist/types/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.d.ts.map +1 -0
  194. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesEmptyList.d.ts +2 -0
  195. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesEmptyList.d.ts.map +1 -0
  196. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesView.d.ts +9 -0
  197. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesView.d.ts.map +1 -0
  198. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/index.d.ts +4 -0
  199. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/index.d.ts.map +1 -0
  200. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesCount.d.ts +4 -0
  201. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesCount.d.ts.map +1 -0
  202. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.d.ts +16 -0
  203. package/dist/types/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.d.ts.map +1 -0
  204. package/dist/types/plugins/ChannelDetail/VirtualizedList/VirtualizedList.d.ts +28 -0
  205. package/dist/types/plugins/ChannelDetail/VirtualizedList/VirtualizedList.d.ts.map +1 -0
  206. package/dist/types/plugins/ChannelDetail/VirtualizedList/index.d.ts +2 -0
  207. package/dist/types/plugins/ChannelDetail/VirtualizedList/index.d.ts.map +1 -0
  208. package/dist/types/plugins/ChannelDetail/index.d.ts +16 -0
  209. package/dist/types/plugins/ChannelDetail/index.d.ts.map +1 -0
  210. package/dist/types/utils/index.d.ts +2 -0
  211. package/dist/types/utils/index.d.ts.map +1 -1
  212. package/dist/types/utils/isDmChannel.d.ts +6 -0
  213. package/dist/types/utils/isDmChannel.d.ts.map +1 -0
  214. package/package.json +13 -4
  215. package/dist/cjs/useNotificationApi.9ffe5761.js.map +0 -1
  216. package/dist/es/useNotificationApi.88c33caa.mjs.map +0 -1
  217. package/dist/types/components/TextareaComposer/SuggestionList/UserItem.d.ts +0 -25
  218. package/dist/types/components/TextareaComposer/SuggestionList/UserItem.d.ts.map +0 -1
@@ -0,0 +1,2950 @@
1
+ import { $r as useChannelActionContext, Ar as useDialogIsOpen, Bn as IconUserAdd, Bt as IconDelete, Ft as IconChevronLeft, Gn as IconVideoFill, Hn as IconUserRemove, It as IconChevronRight, Lr as useStateStore, Mr as useDialogOnNearestManager, Qt as IconFolder, Vr as useComponentContext, Wr as useChatContext, Xr as useChannelListContext, Zn as Button, cn as IconMessageBubble, en as IconImage, gn as IconNoSign, hn as IconMute, jt as IconCheckmark, kn as IconSearch, mr as isDate, nn as IconLeave, pr as getDateString, qr as useChannelStateContext, r as useNotificationApi, sn as IconMenu, tn as IconInfo, ur as useTranslationContext, wt as IconAudio, yn as IconPin, zn as IconUser } from "./useNotificationApi.fa5cddf9.mjs";
2
+ import { A as GalleryUI, B as FileIcon, Ct as LoadingIndicator, I as toBaseImageDescriptors, L as BaseImage, Q as ContextMenu, S as Checkbox, St as FileSizeIndicator, X as Prompt, Y as GlobalModal, _t as Alert, c as useIsUserMuted, dt as ChannelAvatar, f as isDmChannel, ft as Badge, k as Gallery, kt as useModalContext, l as useIsChannelMuted, mt as Avatar, n as useChannelHasMembersOnline, p as useChannelPreviewInfo, t as useChannelHeaderOnlineStatus, tt as ContextMenuButton, u as useChannelMembershipState, w as Switch, x as TextInput, xt as useStableCallback, y as ListItemLayout, z as formatTime } from "./useChannelHeaderOnlineStatus.c5215b13.mjs";
3
+ import React, { createContext, forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
4
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
+ import { ChannelMemberSearchSource, MessageSearchSource, UserSearchSource, isImageAttachment, isScrapedContent, isVideoAttachment } from "stream-chat";
6
+ import clsx from "clsx";
7
+ import debounce from "lodash.debounce";
8
+ import uniqBy from "lodash.uniqby";
9
+ import { GroupedVirtuoso, Virtuoso } from "react-virtuoso";
10
+ //#region src/plugins/ChannelDetail/SectionNavigator/SectionNavigator.tsx
11
+ var SECTION_NAVIGATOR_LAYOUT = {
12
+ inline: "inline",
13
+ tabs: "tabs"
14
+ };
15
+ var DEFAULT_TABS_LAYOUT_MIN_WIDTH = 640;
16
+ var defaultCreateLayoutObserver = ({ element, setLayout, tabsLayoutMinWidth }) => {
17
+ if (typeof ResizeObserver === "undefined") return;
18
+ const observedElement = element.parentElement ?? element;
19
+ const updateLayout = (width) => {
20
+ if (width <= 0) return;
21
+ setLayout(width < tabsLayoutMinWidth ? SECTION_NAVIGATOR_LAYOUT.inline : SECTION_NAVIGATOR_LAYOUT.tabs);
22
+ };
23
+ const observer = new ResizeObserver(([entry]) => {
24
+ updateLayout(entry.contentRect.width);
25
+ });
26
+ updateLayout(observedElement.getBoundingClientRect().width);
27
+ observer.observe(observedElement);
28
+ return () => observer.disconnect();
29
+ };
30
+ var SectionNavigatorContext = createContext({
31
+ closeNavigation: () => void 0,
32
+ history: [],
33
+ historyPop: () => void 0,
34
+ historyPush: () => void 0,
35
+ isNavigationOpen: false,
36
+ layout: SECTION_NAVIGATOR_LAYOUT.tabs,
37
+ openNavigation: () => void 0
38
+ });
39
+ var useSectionNavigatorContext = () => useContext(SectionNavigatorContext);
40
+ var getCurrentRoute = (history) => history[history.length - 1];
41
+ var SectionNavigator = ({ className, createLayoutObserver = defaultCreateLayoutObserver, defaultLayout = SECTION_NAVIGATOR_LAYOUT.tabs, initialHistory, layout: controlledLayout, onLayoutChange, sections, tabsLayoutMinWidth = DEFAULT_TABS_LAYOUT_MIN_WIDTH, ...props }) => {
42
+ const rootRef = useRef(null);
43
+ const [internalLayout, setInternalLayout] = useState(defaultLayout);
44
+ const [history, setHistory] = useState(() => initialHistory ?? (sections[0] ? [{ id: sections[0].id }] : []));
45
+ const [isNavigationOpen, setIsNavigationOpen] = useState(false);
46
+ const layout = controlledLayout ?? internalLayout;
47
+ const currentRoute = getCurrentRoute(history);
48
+ const currentSection = sections.find((section) => section.id === currentRoute?.id);
49
+ const activeSection = currentSection ?? sections[0];
50
+ const isInlineLayout = layout === SECTION_NAVIGATOR_LAYOUT.inline;
51
+ const showDockedNavigation = !isInlineLayout || !currentSection;
52
+ const openNavigation = useCallback(() => setIsNavigationOpen(true), []);
53
+ const closeNavigation = useCallback(() => setIsNavigationOpen(false), []);
54
+ const historyPush = useCallback((route) => {
55
+ setHistory((history) => {
56
+ if (getCurrentRoute(history)?.id === route.id) return history;
57
+ if (layout === SECTION_NAVIGATOR_LAYOUT.tabs) return [route];
58
+ return [...history, route];
59
+ });
60
+ }, [layout]);
61
+ const historyPop = useCallback(() => {
62
+ setHistory((history) => history.length > 1 ? history.slice(0, -1) : history);
63
+ }, []);
64
+ useEffect(() => {
65
+ if (!isInlineLayout) setIsNavigationOpen(false);
66
+ }, [isInlineLayout]);
67
+ useEffect(() => {
68
+ if (!isNavigationOpen) return;
69
+ const handleKeyDown = (event) => {
70
+ if (event.key === "Escape") closeNavigation();
71
+ };
72
+ document.addEventListener("keydown", handleKeyDown);
73
+ return () => document.removeEventListener("keydown", handleKeyDown);
74
+ }, [closeNavigation, isNavigationOpen]);
75
+ useEffect(() => {
76
+ onLayoutChange?.(layout);
77
+ }, [layout, onLayoutChange]);
78
+ useEffect(() => {
79
+ if (controlledLayout) return;
80
+ if (!rootRef.current) return;
81
+ return createLayoutObserver({
82
+ element: rootRef.current,
83
+ setLayout: setInternalLayout,
84
+ tabsLayoutMinWidth
85
+ });
86
+ }, [
87
+ controlledLayout,
88
+ createLayoutObserver,
89
+ tabsLayoutMinWidth
90
+ ]);
91
+ useEffect(() => {
92
+ setHistory((history) => {
93
+ const currentRoute = getCurrentRoute(history);
94
+ const currentRouteHasSection = sections.some((section) => section.id === currentRoute?.id);
95
+ if (!currentRoute) return sections[0] ? [{ id: sections[0].id }] : [];
96
+ if (currentRouteHasSection) return history;
97
+ return sections[0] ? [{ id: sections[0].id }] : [];
98
+ });
99
+ }, [sections]);
100
+ const contextValue = useMemo(() => ({
101
+ closeNavigation,
102
+ history,
103
+ historyPop,
104
+ historyPush,
105
+ isNavigationOpen,
106
+ layout,
107
+ openNavigation
108
+ }), [
109
+ closeNavigation,
110
+ history,
111
+ historyPop,
112
+ historyPush,
113
+ isNavigationOpen,
114
+ layout,
115
+ openNavigation
116
+ ]);
117
+ const Content = activeSection?.SectionContent;
118
+ const navigation = /* @__PURE__ */ jsx("div", {
119
+ className: "str-chat__section-navigator__navigation",
120
+ children: sections.map((section) => {
121
+ const NavButton = section.NavButton;
122
+ const selected = activeSection?.id === section.id;
123
+ return /* @__PURE__ */ jsx("div", {
124
+ className: "str-chat__section-navigator__navigation-item",
125
+ children: /* @__PURE__ */ jsx(NavButton, {
126
+ className: "str-chat__section-navigator__navigation-item__nav-button",
127
+ sectionId: section.id,
128
+ select: () => {
129
+ historyPush({ id: section.id });
130
+ closeNavigation();
131
+ },
132
+ selected
133
+ })
134
+ }, section.id);
135
+ })
136
+ });
137
+ return /* @__PURE__ */ jsx(SectionNavigatorContext.Provider, {
138
+ value: contextValue,
139
+ children: /* @__PURE__ */ jsxs("div", {
140
+ className: clsx("str-chat__section-navigator", className, { "str-chat__section-navigator--inline": isInlineLayout }),
141
+ "data-layout": layout,
142
+ ref: rootRef,
143
+ ...props,
144
+ children: [
145
+ showDockedNavigation && navigation,
146
+ Content && /* @__PURE__ */ jsx("div", {
147
+ className: "str-chat__section-navigator__content",
148
+ children: /* @__PURE__ */ jsx(Content, { layout })
149
+ }),
150
+ isInlineLayout && /* @__PURE__ */ jsxs("div", {
151
+ className: clsx("str-chat__section-navigator__navigation-overlay", { "str-chat__section-navigator__navigation-overlay--open": isNavigationOpen }),
152
+ children: [/* @__PURE__ */ jsx("button", {
153
+ "aria-hidden": true,
154
+ className: "str-chat__section-navigator__navigation-scrim",
155
+ onClick: closeNavigation,
156
+ tabIndex: -1,
157
+ type: "button"
158
+ }), /* @__PURE__ */ jsx("div", {
159
+ className: "str-chat__section-navigator__navigation-drawer",
160
+ children: navigation
161
+ })]
162
+ })
163
+ ]
164
+ })
165
+ });
166
+ };
167
+ //#endregion
168
+ //#region src/plugins/ChannelDetail/SectionNavigator/SectionNavigatorHeader.tsx
169
+ /**
170
+ * Generic header for content rendered inside a `SectionNavigator`. It renders a
171
+ * `Prompt.Header` and, in the inline layout (where the navigation sidebar is not
172
+ * shown), prepends a hamburger button that opens the navigation drawer. The
173
+ * hamburger is omitted on nested views that already show a back button
174
+ * (`goBack`), where it would compete with the back affordance.
175
+ */
176
+ var SectionNavigatorHeader = (props) => {
177
+ const { t } = useTranslationContext("SectionNavigatorHeader");
178
+ const { layout, openNavigation } = useSectionNavigatorContext();
179
+ const MenuButton = useMemo(() => {
180
+ if (layout !== SECTION_NAVIGATOR_LAYOUT.inline) return void 0;
181
+ if (props.goBack) return void 0;
182
+ return function SectionNavigatorHeaderMenuButton() {
183
+ return /* @__PURE__ */ jsx(Button, {
184
+ appearance: "ghost",
185
+ "aria-label": t("Open menu"),
186
+ circular: true,
187
+ className: "str-chat__section-navigator__header-menu-button",
188
+ onClick: openNavigation,
189
+ size: "md",
190
+ variant: "secondary",
191
+ children: /* @__PURE__ */ jsx(IconMenu, {})
192
+ });
193
+ };
194
+ }, [
195
+ layout,
196
+ openNavigation,
197
+ props.goBack,
198
+ t
199
+ ]);
200
+ return /* @__PURE__ */ jsx(Prompt.Header, {
201
+ ...props,
202
+ LeadingContent: MenuButton
203
+ });
204
+ };
205
+ //#endregion
206
+ //#region src/plugins/ChannelDetail/ChannelDetailNavButton.tsx
207
+ /**
208
+ * Underlying button shared by all ChannelDetail section nav buttons. Renders a
209
+ * `ListItemLayout` as a `<button>` and wires the SectionNavigator selection
210
+ * state into `aria-current` and the click handler.
211
+ */
212
+ var ChannelDetailNavButton = ({ className, LeadingIcon, sectionId: _sectionId, select, selected, title, ...props }) => /* @__PURE__ */ jsx(ListItemLayout, {
213
+ LeadingIcon,
214
+ RootElement: "button",
215
+ rootProps: {
216
+ ...props,
217
+ "aria-current": selected ? "page" : void 0,
218
+ className: clsx("str-chat__channel-detail__nav-button", className),
219
+ onClick: select
220
+ },
221
+ selected,
222
+ title
223
+ });
224
+ //#endregion
225
+ //#region src/plugins/ChannelDetail/ChannelDetailContext.tsx
226
+ var ChannelDetailContext = React.createContext(void 0);
227
+ var ChannelDetailProvider = ({ channel, children }) => {
228
+ const value = useMemo(() => ({ channel }), [channel]);
229
+ return /* @__PURE__ */ jsx(ChannelDetailContext.Provider, {
230
+ value,
231
+ children
232
+ });
233
+ };
234
+ var useChannelDetailContext = () => {
235
+ const contextValue = useContext(ChannelDetailContext);
236
+ if (!contextValue) throw new Error("The useChannelDetailContext hook was called outside of ChannelDetailProvider.");
237
+ return contextValue;
238
+ };
239
+ //#endregion
240
+ //#region src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesEmptyList.tsx
241
+ var ChannelFilesEmptyList = () => {
242
+ const { t } = useTranslationContext("ChannelFilesEmptyList");
243
+ return /* @__PURE__ */ jsxs("div", {
244
+ className: "str-chat__channel-detail__files-view__empty-state",
245
+ children: [/* @__PURE__ */ jsx(IconFolder, { className: "str-chat__channel-detail__files-view__empty-state__icon" }), /* @__PURE__ */ jsxs("div", {
246
+ className: "str-chat__channel-detail__files-view__empty-state__content",
247
+ children: [/* @__PURE__ */ jsx("p", {
248
+ className: "str-chat__channel-detail__files-view__empty-state__title",
249
+ children: t("No files")
250
+ }), /* @__PURE__ */ jsx("p", {
251
+ className: "str-chat__channel-detail__files-view__empty-state__description",
252
+ children: t("Share a file to see it here")
253
+ })]
254
+ })]
255
+ });
256
+ };
257
+ //#endregion
258
+ //#region src/plugins/ChannelDetail/ChannelDetailListLoadingIndicator.tsx
259
+ var searchSourceFooterStateSelector = (state) => ({
260
+ hasNextPage: state.hasNext,
261
+ isLoading: state.isLoading
262
+ });
263
+ var ChannelDetailListLoadingIndicator = ({ searchSource }) => {
264
+ const { hasNextPage, isLoading } = useStateStore(searchSource.state, searchSourceFooterStateSelector);
265
+ if (!hasNextPage || !isLoading) return null;
266
+ return /* @__PURE__ */ jsx("div", {
267
+ className: "str-chat__loading-indicator-placeholder",
268
+ children: isLoading && /* @__PURE__ */ jsx(LoadingIndicator, {})
269
+ });
270
+ };
271
+ //#endregion
272
+ //#region src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.utils.ts
273
+ /** Attachment types listed by the files view (everything that is not an image/video). */
274
+ var FILE_ATTACHMENT_TYPES = ["file", "audio"];
275
+ var FILE_ATTACHMENT_TYPE_SET = new Set(FILE_ATTACHMENT_TYPES);
276
+ var normalizeTimestamp$1 = (timestamp) => {
277
+ if (!timestamp) return void 0;
278
+ return isDate(timestamp) ? timestamp.toISOString() : timestamp;
279
+ };
280
+ var isChannelFileAttachment = (attachment) => !isScrapedContent(attachment) && !!attachment.type && FILE_ATTACHMENT_TYPE_SET.has(attachment.type);
281
+ var byCreatedAtDesc = (a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? "");
282
+ /**
283
+ * Flattens messages into file/audio attachment items organized into descending
284
+ * month/year sections (newest first), in a single pass over the attachments.
285
+ *
286
+ * The result is shaped for GroupedVirtuoso: a single flat `items` array (items
287
+ * grouped contiguously by section) plus `groupCounts`/`sections` aligned with
288
+ * it, so the view never has to re-flatten. The raw attachment is kept
289
+ * untransformed; only the carrying message timestamp is captured for headers.
290
+ */
291
+ var toChannelFileSections = (messages) => {
292
+ const groups = [];
293
+ const groupIndexByKey = /* @__PURE__ */ new Map();
294
+ messages.forEach((message) => {
295
+ const createdAt = normalizeTimestamp$1(message.created_at);
296
+ const key = createdAt ? createdAt.slice(0, 7) : "unknown";
297
+ message.attachments?.forEach((attachment, index) => {
298
+ if (!isChannelFileAttachment(attachment)) return;
299
+ const item = {
300
+ attachment,
301
+ createdAt,
302
+ id: `${message.id}-${index}`
303
+ };
304
+ const existingIndex = groupIndexByKey.get(key);
305
+ if (existingIndex === void 0) {
306
+ groupIndexByKey.set(key, groups.length);
307
+ groups.push({
308
+ items: [item],
309
+ key,
310
+ timestamp: createdAt
311
+ });
312
+ } else groups[existingIndex].items.push(item);
313
+ });
314
+ });
315
+ groups.forEach((group) => {
316
+ group.items.sort(byCreatedAtDesc);
317
+ group.timestamp = group.items[0]?.createdAt;
318
+ });
319
+ groups.sort((a, b) => (b.timestamp ?? "").localeCompare(a.timestamp ?? ""));
320
+ return {
321
+ groupCounts: groups.map((group) => group.items.length),
322
+ items: groups.flatMap((group) => group.items),
323
+ sections: groups.map(({ key, timestamp }) => ({
324
+ key,
325
+ timestamp
326
+ }))
327
+ };
328
+ };
329
+ //#endregion
330
+ //#region src/plugins/ChannelDetail/Views/ChannelFilesView/useChannelFilesSearch.ts
331
+ var CHANNEL_FILES_SEARCH_PAGE_SIZE = 30;
332
+ var channelFilesSearchSourceStateSelector = (state) => ({
333
+ isLoading: state.isLoading,
334
+ messages: state.items
335
+ });
336
+ var useChannelFilesSearch = () => {
337
+ const { client } = useChatContext();
338
+ const { channel } = useChannelDetailContext();
339
+ const channelFilesSearchSource = useMemo(() => {
340
+ const source = new MessageSearchSource(client, {
341
+ allowEmptySearchString: true,
342
+ pageSize: CHANNEL_FILES_SEARCH_PAGE_SIZE,
343
+ resetOnNewSearchQuery: false
344
+ });
345
+ source.messageSearchChannelFilters = { cid: channel.cid };
346
+ source.messageSearchFilters = { "attachments.type": { $in: [...FILE_ATTACHMENT_TYPES] } };
347
+ return source;
348
+ }, [channel.cid, client]);
349
+ const { isLoading, messages } = useStateStore(channelFilesSearchSource.state, channelFilesSearchSourceStateSelector);
350
+ const { groupCounts, items, sections } = useMemo(() => toChannelFileSections(messages ?? []), [messages]);
351
+ useEffect(() => {
352
+ channelFilesSearchSource.activate();
353
+ channelFilesSearchSource.search("");
354
+ return () => {
355
+ channelFilesSearchSource.cancelScheduledQuery();
356
+ };
357
+ }, [channelFilesSearchSource]);
358
+ return {
359
+ channelFilesSearchSource,
360
+ fileItems: items,
361
+ groupCounts,
362
+ hasResultsLoaded: Array.isArray(messages),
363
+ isLoading,
364
+ sections
365
+ };
366
+ };
367
+ //#endregion
368
+ //#region src/plugins/ChannelDetail/Views/ChannelFilesView/ChannelFilesView.tsx
369
+ var ChannelFilesItem = forwardRef(function ChannelFilesItem({ className, ...props }, ref) {
370
+ return /* @__PURE__ */ jsx("div", {
371
+ ...props,
372
+ className: clsx("str-chat__channel-detail__files-view__item", className),
373
+ ref
374
+ });
375
+ });
376
+ var ChannelFilesGroup = forwardRef(function ChannelFilesGroup({ className, ...props }, ref) {
377
+ return /* @__PURE__ */ jsx("div", {
378
+ ...props,
379
+ className: clsx("str-chat__channel-detail__files-view__group", className),
380
+ ref
381
+ });
382
+ });
383
+ var ChannelFilesSectionHeader = ({ timestamp }) => {
384
+ const { t, tDateTimeParser } = useTranslationContext("ChannelFilesView");
385
+ const label = getDateString({
386
+ format: "MMMM YYYY",
387
+ messageCreatedAt: timestamp,
388
+ t,
389
+ tDateTimeParser
390
+ });
391
+ if (!label) return null;
392
+ return /* @__PURE__ */ jsx("div", {
393
+ className: "str-chat__channel-detail__files-view__section-header",
394
+ children: label
395
+ });
396
+ };
397
+ var getAttachmentFileName = (attachment) => attachment.title || attachment.fallback || "";
398
+ var ChannelFileListItem = ({ item }) => {
399
+ const { attachment } = item;
400
+ const fileName = getAttachmentFileName(attachment);
401
+ const assetUrl = attachment.asset_url;
402
+ const LeadingSlot = useMemo(() => function FileListItemIcon() {
403
+ return /* @__PURE__ */ jsx(FileIcon, {
404
+ className: "str-chat__channel-detail__files-view__list-item__icon",
405
+ fileName,
406
+ mimeType: attachment.mime_type,
407
+ size: "md"
408
+ });
409
+ }, [attachment.mime_type, fileName]);
410
+ const sharedProps = useMemo(() => ({
411
+ LeadingSlot,
412
+ subtitle: /* @__PURE__ */ jsx(FileSizeIndicator, { fileSize: attachment.file_size }),
413
+ subtitleClassName: "str-chat__channel-detail__files-view__list-item__size",
414
+ title: fileName,
415
+ titleClassName: "str-chat__channel-detail__files-view__list-item__name"
416
+ }), [
417
+ attachment.file_size,
418
+ fileName,
419
+ LeadingSlot
420
+ ]);
421
+ const linkRootProps = useMemo(() => ({
422
+ className: "str-chat__channel-detail__files-view__list-item",
423
+ download: fileName || void 0,
424
+ href: assetUrl,
425
+ rel: "noopener noreferrer",
426
+ target: "_blank"
427
+ }), [assetUrl, fileName]);
428
+ const divRootProps = useMemo(() => ({ className: "str-chat__channel-detail__files-view__list-item" }), []);
429
+ if (assetUrl) return /* @__PURE__ */ jsx(ListItemLayout, {
430
+ ...sharedProps,
431
+ RootElement: "a",
432
+ rootProps: linkRootProps
433
+ });
434
+ return /* @__PURE__ */ jsx(ListItemLayout, {
435
+ ...sharedProps,
436
+ RootElement: "div",
437
+ rootProps: divRootProps
438
+ });
439
+ };
440
+ var ChannelFilesView = () => {
441
+ const { t } = useTranslationContext();
442
+ const { close } = useModalContext();
443
+ const { channelFilesSearchSource, fileItems, groupCounts, hasResultsLoaded, sections } = useChannelFilesSearch();
444
+ const groupContent = useCallback((groupIndex) => /* @__PURE__ */ jsx(ChannelFilesSectionHeader, { timestamp: sections[groupIndex]?.timestamp }), [sections]);
445
+ const itemContent = useCallback((index) => /* @__PURE__ */ jsx(ChannelFileListItem, { item: fileItems[index] }), [fileItems]);
446
+ const atBottomStateChange = useCallback((atBottom) => {
447
+ if (atBottom) channelFilesSearchSource.search();
448
+ }, [channelFilesSearchSource]);
449
+ const EmptyPlaceholder = useMemo(() => function ChannelFilesEmptyPlaceholder() {
450
+ return hasResultsLoaded ? /* @__PURE__ */ jsx(ChannelFilesEmptyList, {}) : null;
451
+ }, [hasResultsLoaded]);
452
+ const Footer = useMemo(() => function ChannelFilesListFooter() {
453
+ return /* @__PURE__ */ jsx(ChannelDetailListLoadingIndicator, { searchSource: channelFilesSearchSource });
454
+ }, [channelFilesSearchSource]);
455
+ const components = useMemo(() => ({
456
+ EmptyPlaceholder,
457
+ Footer,
458
+ Group: ChannelFilesGroup,
459
+ Item: ChannelFilesItem
460
+ }), [EmptyPlaceholder, Footer]);
461
+ return /* @__PURE__ */ jsxs("div", {
462
+ className: "str-chat__channel-detail__files-view",
463
+ children: [/* @__PURE__ */ jsx(SectionNavigatorHeader, {
464
+ close,
465
+ title: t("Files")
466
+ }), /* @__PURE__ */ jsx(Prompt.Body, {
467
+ className: "str-chat__channel-detail__files-view__body",
468
+ children: /* @__PURE__ */ jsx(GroupedVirtuoso, {
469
+ atBottomStateChange,
470
+ className: "str-chat__virtualized-list str-chat__channel-detail__files-view__list",
471
+ components,
472
+ groupContent,
473
+ groupCounts,
474
+ itemContent
475
+ })
476
+ })]
477
+ });
478
+ };
479
+ //#endregion
480
+ //#region src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementActions.defaults.tsx
481
+ var toError$1 = (error) => error instanceof Error ? error : /* @__PURE__ */ new Error("An unknown error occurred");
482
+ var getDisplayName = (name, fallback) => name || fallback || "";
483
+ var BlockUserActionIcon$1 = () => /* @__PURE__ */ jsx(IconNoSign, { className: "str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user" });
484
+ var DeleteChatActionIcon = () => /* @__PURE__ */ jsx(IconDelete, { className: "str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--delete-chat" });
485
+ var MuteActionIcon = () => /* @__PURE__ */ jsx(IconMute, { className: "str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--mute" });
486
+ var MutedActionIcon = () => /* @__PURE__ */ jsx(IconAudio, { className: "str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--unmute" });
487
+ var LeaveChannelActionIcon = () => /* @__PURE__ */ jsx(IconLeave, { className: "str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--leave-channel" });
488
+ var channelManagementViewActionClassName = "str-chat__channel-management-view-action";
489
+ var blockedUsersSelector$1 = ({ userIds }) => ({ userIds });
490
+ var ChannelManagementConfirmationAlert = ({ action, cancelLabel, confirmLabel, description, isSubmitting, onCancel, onConfirm, testId, title }) => /* @__PURE__ */ jsxs(Alert.Root, {
491
+ className: clsx("str-chat__channel-management-confirmation-alert", { [`str-chat__channel-management-confirmation-alert--${action}`]: action }),
492
+ "data-testid": testId,
493
+ children: [/* @__PURE__ */ jsx(Alert.Header, {
494
+ description,
495
+ title
496
+ }), /* @__PURE__ */ jsxs(Alert.Actions, { children: [/* @__PURE__ */ jsx(Button, {
497
+ appearance: "solid",
498
+ "data-testid": `${testId}-confirm-button`,
499
+ disabled: isSubmitting,
500
+ onClick: onConfirm,
501
+ size: "md",
502
+ variant: "danger",
503
+ children: confirmLabel
504
+ }), /* @__PURE__ */ jsx(Button, {
505
+ appearance: "outline",
506
+ autoFocus: true,
507
+ "data-testid": `${testId}-cancel-button`,
508
+ disabled: isSubmitting,
509
+ onClick: onCancel,
510
+ size: "md",
511
+ variant: "secondary",
512
+ children: cancelLabel
513
+ })] })]
514
+ });
515
+ var useOtherMember = () => {
516
+ const { client } = useChatContext();
517
+ const { channel } = useChannelDetailContext();
518
+ return useMemo(() => {
519
+ const stateMembers = Object.values(channel.state?.members ?? {});
520
+ return (stateMembers.length ? stateMembers : channel.data?.members ?? []).find((member) => member.user?.id && member.user.id !== client.user?.id);
521
+ }, [channel, client.user?.id]);
522
+ };
523
+ var useChannelManagementActionFilterState = () => {
524
+ const { client } = useChatContext();
525
+ const { channel } = useChannelDetailContext();
526
+ const otherMember = useOtherMember();
527
+ const resolvedIsDmChannel = isDmChannel({
528
+ channel,
529
+ ownUserId: client.user?.id
530
+ });
531
+ const isGroupChannel = !resolvedIsDmChannel;
532
+ const ownCapabilities = channel.data?.own_capabilities;
533
+ const isDmChannelWithOtherUser = resolvedIsDmChannel && !!otherMember;
534
+ return {
535
+ canBlockUser: isDmChannelWithOtherUser && ownCapabilities?.includes("ban-channel-members"),
536
+ canDeleteChat: ownCapabilities?.includes("delete-channel"),
537
+ canLeaveChannel: isGroupChannel && ownCapabilities?.includes("leave-channel"),
538
+ canMuteChannel: ownCapabilities?.includes("mute-channel"),
539
+ canMuteUser: isDmChannelWithOtherUser
540
+ };
541
+ };
542
+ var useBaseChannelManagementActionSetFilter = (channelManagementActionSet) => {
543
+ const { canBlockUser, canDeleteChat, canLeaveChannel, canMuteChannel, canMuteUser } = useChannelManagementActionFilterState();
544
+ return useMemo(() => channelManagementActionSet.filter((action) => {
545
+ switch (action.type) {
546
+ case "blockUser": return canBlockUser;
547
+ case "deleteChat": return canDeleteChat;
548
+ case "muteChannel": return canMuteChannel;
549
+ case "muteUser": return canMuteUser;
550
+ case "leaveChannel": return canLeaveChannel;
551
+ default: return true;
552
+ }
553
+ }), [
554
+ canBlockUser,
555
+ canDeleteChat,
556
+ canLeaveChannel,
557
+ canMuteChannel,
558
+ canMuteUser,
559
+ channelManagementActionSet
560
+ ]);
561
+ };
562
+ var ChannelMuteAction = () => {
563
+ const { channel } = useChannelDetailContext();
564
+ const { addNotification } = useNotificationApi();
565
+ const { t } = useTranslationContext();
566
+ const { muted: channelMuted } = useIsChannelMuted(channel);
567
+ const [optimisticChannelMuted, setOptimisticChannelMuted] = useState(channelMuted);
568
+ useEffect(() => {
569
+ setOptimisticChannelMuted(channelMuted);
570
+ }, [channelMuted]);
571
+ const toggleChannelMuteRequest = useStableCallback((nextMuted, targetChannel) => {
572
+ if (!nextMuted) return targetChannel.unmute().then(() => addNotification({
573
+ context: { channel: targetChannel },
574
+ emitter: "ChannelManagementView",
575
+ message: t("Channel unmuted"),
576
+ severity: "success",
577
+ type: "api:channel:unmute:success"
578
+ })).catch((error) => {
579
+ setOptimisticChannelMuted(channelMuted);
580
+ return addNotification({
581
+ context: { channel: targetChannel },
582
+ emitter: "ChannelManagementView",
583
+ error: toError$1(error),
584
+ message: t("Error unmuting channel"),
585
+ severity: "error",
586
+ type: "api:channel:unmute:failed"
587
+ });
588
+ });
589
+ return targetChannel.mute().then(() => addNotification({
590
+ context: { channel: targetChannel },
591
+ emitter: "ChannelManagementView",
592
+ message: t("Channel muted"),
593
+ severity: "success",
594
+ type: "api:channel:mute:success"
595
+ })).catch((error) => {
596
+ setOptimisticChannelMuted(channelMuted);
597
+ return addNotification({
598
+ context: { channel: targetChannel },
599
+ emitter: "ChannelManagementView",
600
+ error: toError$1(error),
601
+ message: t("Error muting channel"),
602
+ severity: "error",
603
+ type: "api:channel:mute:failed"
604
+ });
605
+ });
606
+ });
607
+ const toggleChannelMute = useMemo(() => debounce(toggleChannelMuteRequest, 1e3), [toggleChannelMuteRequest]);
608
+ useEffect(() => () => {
609
+ toggleChannelMute.cancel();
610
+ }, [toggleChannelMute]);
611
+ const toggleOptimisticChannelMute = useCallback(() => {
612
+ const nextMuted = !optimisticChannelMuted;
613
+ setOptimisticChannelMuted(nextMuted);
614
+ toggleChannelMute(nextMuted, channel);
615
+ }, [
616
+ channel,
617
+ optimisticChannelMuted,
618
+ toggleChannelMute
619
+ ]);
620
+ const rootProps = useMemo(() => ({
621
+ "aria-pressed": optimisticChannelMuted,
622
+ className: clsx("str-chat__form__switch-field", channelManagementViewActionClassName),
623
+ onClick: toggleOptimisticChannelMute
624
+ }), [optimisticChannelMuted, toggleOptimisticChannelMute]);
625
+ const TrailingSlot = useMemo(() => {
626
+ function ChannelMuteSwitch() {
627
+ return /* @__PURE__ */ jsx(Switch, {
628
+ on: optimisticChannelMuted,
629
+ presentation: true
630
+ });
631
+ }
632
+ return ChannelMuteSwitch;
633
+ }, [optimisticChannelMuted]);
634
+ return /* @__PURE__ */ jsx(ListItemLayout, {
635
+ LeadingIcon: optimisticChannelMuted ? MutedActionIcon : MuteActionIcon,
636
+ RootElement: "button",
637
+ rootProps,
638
+ title: optimisticChannelMuted ? t("Unmute chat") : t("Mute chat"),
639
+ TrailingSlot
640
+ });
641
+ };
642
+ var UserMuteAction$1 = () => {
643
+ const { client, mutes } = useChatContext();
644
+ const { channel } = useChannelDetailContext();
645
+ const { addNotification } = useNotificationApi();
646
+ const { t } = useTranslationContext();
647
+ const otherMember = useOtherMember();
648
+ const userMuted = !!mutes.find((mute) => mute.target.id === otherMember?.user?.id);
649
+ const [optimisticUserMuted, setOptimisticUserMuted] = useState(userMuted);
650
+ useEffect(() => {
651
+ setOptimisticUserMuted(userMuted);
652
+ }, [userMuted]);
653
+ const otherMemberUserId = otherMember?.user?.id;
654
+ const toggleUserMuteRequest = useStableCallback((nextMuted, targetUserId) => {
655
+ if (!targetUserId) return;
656
+ if (!nextMuted) return client.unmuteUser(targetUserId).then(() => addNotification({
657
+ context: { channel },
658
+ emitter: "ChannelManagementView",
659
+ message: t("User unmuted"),
660
+ severity: "success",
661
+ type: "api:user:unmute:success"
662
+ })).catch((error) => {
663
+ setOptimisticUserMuted(userMuted);
664
+ return addNotification({
665
+ context: { channel },
666
+ emitter: "ChannelManagementView",
667
+ error: toError$1(error),
668
+ message: t("Error unmuting user"),
669
+ severity: "error",
670
+ type: "api:user:unmute:failed"
671
+ });
672
+ });
673
+ return client.muteUser(targetUserId).then(() => addNotification({
674
+ context: { channel },
675
+ emitter: "ChannelManagementView",
676
+ message: t("User muted"),
677
+ severity: "success",
678
+ type: "api:user:mute:success"
679
+ })).catch((error) => {
680
+ setOptimisticUserMuted(userMuted);
681
+ return addNotification({
682
+ context: { channel },
683
+ emitter: "ChannelManagementView",
684
+ error: toError$1(error),
685
+ message: t("Error muting user"),
686
+ severity: "error",
687
+ type: "api:user:mute:failed"
688
+ });
689
+ });
690
+ });
691
+ const toggleUserMute = useMemo(() => debounce(toggleUserMuteRequest, 1e3), [toggleUserMuteRequest]);
692
+ useEffect(() => () => {
693
+ toggleUserMute.cancel();
694
+ }, [toggleUserMute]);
695
+ const toggleOptimisticUserMute = useCallback(() => {
696
+ const nextMuted = !optimisticUserMuted;
697
+ setOptimisticUserMuted(nextMuted);
698
+ toggleUserMute(nextMuted, otherMemberUserId);
699
+ }, [
700
+ optimisticUserMuted,
701
+ otherMemberUserId,
702
+ toggleUserMute
703
+ ]);
704
+ const rootProps = useMemo(() => ({
705
+ "aria-pressed": optimisticUserMuted,
706
+ className: clsx("str-chat__form__switch-field", channelManagementViewActionClassName),
707
+ onClick: toggleOptimisticUserMute
708
+ }), [optimisticUserMuted, toggleOptimisticUserMute]);
709
+ const TrailingSlot = useMemo(() => {
710
+ function UserMuteSwitch() {
711
+ return /* @__PURE__ */ jsx(Switch, {
712
+ on: optimisticUserMuted,
713
+ presentation: true
714
+ });
715
+ }
716
+ return UserMuteSwitch;
717
+ }, [optimisticUserMuted]);
718
+ return /* @__PURE__ */ jsx(ListItemLayout, {
719
+ LeadingIcon: optimisticUserMuted ? MutedActionIcon : MuteActionIcon,
720
+ RootElement: "button",
721
+ rootProps,
722
+ title: optimisticUserMuted ? t("Unmute user") : t("Mute user"),
723
+ TrailingSlot
724
+ });
725
+ };
726
+ var BlockUserAction$1 = () => {
727
+ const { client } = useChatContext();
728
+ const { Modal = GlobalModal } = useComponentContext();
729
+ const { channel } = useChannelDetailContext();
730
+ const { addNotification } = useNotificationApi();
731
+ const { t } = useTranslationContext();
732
+ const targetUserId = useOtherMember()?.user?.id;
733
+ const { userIds: blockedUserIds } = useStateStore(client.blockedUsers, blockedUsersSelector$1);
734
+ const isBlocked = useMemo(() => !!targetUserId && new Set(blockedUserIds).has(targetUserId), [blockedUserIds, targetUserId]);
735
+ const [alertOpen, setAlertOpen] = useState(false);
736
+ const [userBlockInProgress, setUserBlockInProgress] = useState(false);
737
+ const closeBlockUserAlert = useCallback(() => {
738
+ setAlertOpen(false);
739
+ }, []);
740
+ const openBlockUserAlert = useCallback(() => {
741
+ setAlertOpen(true);
742
+ }, []);
743
+ const unblockUser = useCallback(async () => {
744
+ if (!targetUserId) return;
745
+ try {
746
+ setUserBlockInProgress(true);
747
+ await client.unBlockUser(targetUserId);
748
+ addNotification({
749
+ context: { channel },
750
+ emitter: "ChannelManagementView",
751
+ message: t("User unblocked"),
752
+ severity: "success",
753
+ type: "api:user:unblock:success"
754
+ });
755
+ } catch (error) {
756
+ addNotification({
757
+ context: { channel },
758
+ emitter: "ChannelManagementView",
759
+ error: toError$1(error),
760
+ message: t("Error unblocking user"),
761
+ severity: "error",
762
+ type: "api:user:unblock:failed"
763
+ });
764
+ } finally {
765
+ setAlertOpen(false);
766
+ setUserBlockInProgress(false);
767
+ }
768
+ }, [
769
+ addNotification,
770
+ channel,
771
+ client,
772
+ targetUserId,
773
+ t
774
+ ]);
775
+ const blockUser = useCallback(async () => {
776
+ if (!targetUserId) return;
777
+ try {
778
+ setUserBlockInProgress(true);
779
+ await client.blockUser(targetUserId);
780
+ addNotification({
781
+ context: { channel },
782
+ emitter: "ChannelManagementView",
783
+ message: t("User blocked"),
784
+ severity: "success",
785
+ type: "api:user:block:success"
786
+ });
787
+ } catch (error) {
788
+ addNotification({
789
+ context: { channel },
790
+ emitter: "ChannelManagementView",
791
+ error: toError$1(error),
792
+ message: t("Error blocking user"),
793
+ severity: "error",
794
+ type: "api:user:block:failed"
795
+ });
796
+ } finally {
797
+ setAlertOpen(false);
798
+ setUserBlockInProgress(false);
799
+ }
800
+ }, [
801
+ addNotification,
802
+ channel,
803
+ client,
804
+ targetUserId,
805
+ t
806
+ ]);
807
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ListItemLayout, {
808
+ destructive: true,
809
+ LeadingIcon: BlockUserActionIcon$1,
810
+ RootElement: "button",
811
+ rootProps: useMemo(() => ({
812
+ className: channelManagementViewActionClassName,
813
+ disabled: userBlockInProgress,
814
+ onClick: openBlockUserAlert
815
+ }), [openBlockUserAlert, userBlockInProgress]),
816
+ title: isBlocked ? t("Unblock") : t("Block user")
817
+ }), /* @__PURE__ */ jsx(Modal, {
818
+ open: alertOpen,
819
+ role: "alertdialog",
820
+ children: /* @__PURE__ */ jsx(ChannelManagementConfirmationAlert, {
821
+ action: "blockUser",
822
+ cancelLabel: t("Cancel"),
823
+ confirmLabel: isBlocked ? t("Unblock") : t("Block User"),
824
+ description: isBlocked ? t("This user will be able to message you again.") : t("This user won't be able to message you anymore. You can unblock them anytime."),
825
+ isSubmitting: userBlockInProgress,
826
+ onCancel: closeBlockUserAlert,
827
+ onConfirm: isBlocked ? unblockUser : blockUser,
828
+ testId: "channel-detail-block-user-alert",
829
+ title: isBlocked ? t("Unblock") : t("Block User")
830
+ })
831
+ })] });
832
+ };
833
+ var LeaveChannelAction = () => {
834
+ const { client } = useChatContext();
835
+ const { channel } = useChannelDetailContext();
836
+ const { Modal = GlobalModal } = useComponentContext();
837
+ const { close } = useModalContext();
838
+ const { addNotification } = useNotificationApi();
839
+ const { t } = useTranslationContext();
840
+ const [alertOpen, setAlertOpen] = useState(false);
841
+ const [leaveChannelInProgress, setLeaveChannelInProgress] = useState(false);
842
+ const closeLeaveChannelAlert = useCallback(() => {
843
+ setAlertOpen(false);
844
+ }, []);
845
+ const openLeaveChannelAlert = useCallback(() => {
846
+ setAlertOpen(true);
847
+ }, []);
848
+ const leaveChannel = useCallback(async () => {
849
+ if (!client.userID) return;
850
+ try {
851
+ setLeaveChannelInProgress(true);
852
+ await channel.removeMembers([client.userID]);
853
+ addNotification({
854
+ context: { channel },
855
+ emitter: "ChannelManagementView",
856
+ message: t("Left channel"),
857
+ severity: "success",
858
+ type: "api:channel:leave:success"
859
+ });
860
+ setAlertOpen(false);
861
+ close();
862
+ } catch (error) {
863
+ addNotification({
864
+ context: { channel },
865
+ emitter: "ChannelManagementView",
866
+ error: toError$1(error),
867
+ message: t("Failed to leave channel"),
868
+ severity: "error",
869
+ type: "api:channel:leave:failed"
870
+ });
871
+ } finally {
872
+ setLeaveChannelInProgress(false);
873
+ }
874
+ }, [
875
+ addNotification,
876
+ channel,
877
+ client.userID,
878
+ close,
879
+ t
880
+ ]);
881
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ListItemLayout, {
882
+ destructive: true,
883
+ LeadingIcon: LeaveChannelActionIcon,
884
+ RootElement: "button",
885
+ rootProps: useMemo(() => ({
886
+ className: channelManagementViewActionClassName,
887
+ disabled: leaveChannelInProgress,
888
+ onClick: openLeaveChannelAlert
889
+ }), [leaveChannelInProgress, openLeaveChannelAlert]),
890
+ title: t("Leave chat")
891
+ }), /* @__PURE__ */ jsx(Modal, {
892
+ open: alertOpen,
893
+ role: "alertdialog",
894
+ children: /* @__PURE__ */ jsx(ChannelManagementConfirmationAlert, {
895
+ action: "leaveChannel",
896
+ cancelLabel: t("Cancel"),
897
+ confirmLabel: t("Leave chat"),
898
+ description: t("Are you sure you want to leave this channel?"),
899
+ isSubmitting: leaveChannelInProgress,
900
+ onCancel: closeLeaveChannelAlert,
901
+ onConfirm: leaveChannel,
902
+ testId: "channel-detail-leave-channel-alert",
903
+ title: t("Leave chat")
904
+ })
905
+ })] });
906
+ };
907
+ var DeleteChatAction = () => {
908
+ const { channel } = useChannelDetailContext();
909
+ const { Modal = GlobalModal } = useComponentContext();
910
+ const { close: closeChannelDetail } = useModalContext();
911
+ const { addNotification } = useNotificationApi();
912
+ const { t } = useTranslationContext();
913
+ const otherMember = useOtherMember();
914
+ const [alertOpen, setAlertOpen] = useState(false);
915
+ const [deleteChatInProgress, setDeleteChatInProgress] = useState(false);
916
+ const userName = getDisplayName(otherMember?.user?.name, otherMember?.user?.id);
917
+ const closeDeleteChatAlert = useCallback(() => {
918
+ setAlertOpen(false);
919
+ }, []);
920
+ const openDeleteChatAlert = useCallback(() => {
921
+ setAlertOpen(true);
922
+ }, []);
923
+ const deleteChat = useCallback(async () => {
924
+ try {
925
+ setDeleteChatInProgress(true);
926
+ await channel.delete();
927
+ addNotification({
928
+ context: { channel },
929
+ emitter: "ChannelManagementView",
930
+ message: t("Chat deleted"),
931
+ severity: "success",
932
+ type: "api:channel:delete:success"
933
+ });
934
+ setAlertOpen(false);
935
+ closeChannelDetail();
936
+ } catch (error) {
937
+ addNotification({
938
+ context: { channel },
939
+ emitter: "ChannelManagementView",
940
+ error: toError$1(error),
941
+ message: t("Error deleting chat"),
942
+ severity: "error",
943
+ type: "api:channel:delete:failed"
944
+ });
945
+ } finally {
946
+ setDeleteChatInProgress(false);
947
+ }
948
+ }, [
949
+ addNotification,
950
+ channel,
951
+ closeChannelDetail,
952
+ t
953
+ ]);
954
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ListItemLayout, {
955
+ destructive: true,
956
+ LeadingIcon: DeleteChatActionIcon,
957
+ RootElement: "button",
958
+ rootProps: useMemo(() => ({
959
+ className: channelManagementViewActionClassName,
960
+ disabled: deleteChatInProgress,
961
+ onClick: openDeleteChatAlert
962
+ }), [deleteChatInProgress, openDeleteChatAlert]),
963
+ title: t("Delete chat")
964
+ }), /* @__PURE__ */ jsx(Modal, {
965
+ open: alertOpen,
966
+ role: "alertdialog",
967
+ children: /* @__PURE__ */ jsx(ChannelManagementConfirmationAlert, {
968
+ action: "deleteChat",
969
+ cancelLabel: t("Cancel"),
970
+ confirmLabel: t("Delete chat"),
971
+ description: t("This permanently deletes your message history with {{ user }}. This can't be undone.", { user: userName }),
972
+ isSubmitting: deleteChatInProgress,
973
+ onCancel: closeDeleteChatAlert,
974
+ onConfirm: deleteChat,
975
+ testId: "channel-detail-delete-chat-alert",
976
+ title: t("Delete chat")
977
+ })
978
+ })] });
979
+ };
980
+ var DefaultChannelManagementActions = {
981
+ BlockUser: BlockUserAction$1,
982
+ DeleteChat: DeleteChatAction,
983
+ LeaveChannel: LeaveChannelAction,
984
+ MuteChannel: ChannelMuteAction,
985
+ MuteUser: UserMuteAction$1
986
+ };
987
+ var defaultChannelManagementActionSet = [
988
+ {
989
+ Component: DefaultChannelManagementActions.MuteChannel,
990
+ type: "muteChannel"
991
+ },
992
+ {
993
+ Component: DefaultChannelManagementActions.MuteUser,
994
+ type: "muteUser"
995
+ },
996
+ {
997
+ Component: DefaultChannelManagementActions.BlockUser,
998
+ type: "blockUser"
999
+ },
1000
+ {
1001
+ Component: DefaultChannelManagementActions.LeaveChannel,
1002
+ type: "leaveChannel"
1003
+ },
1004
+ {
1005
+ Component: DefaultChannelManagementActions.DeleteChat,
1006
+ type: "deleteChat"
1007
+ }
1008
+ ];
1009
+ //#endregion
1010
+ //#region src/plugins/ChannelDetail/Views/ChannelManagementView/ChannelManagementView.tsx
1011
+ var ChannelManagementInfoBody = ({ actions }) => {
1012
+ const { client } = useChatContext();
1013
+ const { channel } = useChannelDetailContext();
1014
+ const { Avatar = ChannelAvatar } = useComponentContext();
1015
+ const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({ channel });
1016
+ const resolvedIsDmChannel = isDmChannel({
1017
+ channel,
1018
+ ownUserId: client.user?.id
1019
+ });
1020
+ const otherMemberUserId = useMemo(() => {
1021
+ if (!resolvedIsDmChannel) return;
1022
+ return Object.values(channel.state?.members ?? {}).find((member) => member.user?.id && member.user.id !== client.user?.id)?.user?.id;
1023
+ }, [
1024
+ channel,
1025
+ client.user?.id,
1026
+ resolvedIsDmChannel
1027
+ ]);
1028
+ const isOnline = useChannelHasMembersOnline({ channel });
1029
+ const { muted: channelMuted } = useIsChannelMuted(channel);
1030
+ const userMuted = useIsUserMuted(otherMemberUserId);
1031
+ const membership = useChannelMembershipState(channel);
1032
+ const onlineStatusText = useChannelHeaderOnlineStatus({ channel });
1033
+ const pinned = !!membership.pinned_at;
1034
+ return /* @__PURE__ */ jsxs(Prompt.Body, {
1035
+ className: "str-chat__channel-detail__channel-management-view__body",
1036
+ children: [/* @__PURE__ */ jsxs("div", {
1037
+ className: "str-chat__channel-detail__channel-management-view__profile",
1038
+ children: [/* @__PURE__ */ jsx(Avatar, {
1039
+ displayMembers: groupChannelDisplayInfo.members,
1040
+ imageUrl: displayImage,
1041
+ isOnline: resolvedIsDmChannel ? isOnline : void 0,
1042
+ size: "2xl",
1043
+ userName: displayTitle
1044
+ }), /* @__PURE__ */ jsxs("div", {
1045
+ className: "str-chat__channel-detail__channel-management-view__profile__details",
1046
+ children: [/* @__PURE__ */ jsxs("div", {
1047
+ className: "str-chat__channel-detail__channel-management-view__profile__details__title",
1048
+ children: [
1049
+ displayTitle && /* @__PURE__ */ jsx("span", { children: displayTitle }),
1050
+ pinned && /* @__PURE__ */ jsx(IconPin, {}),
1051
+ resolvedIsDmChannel && userMuted || !resolvedIsDmChannel && channelMuted ? /* @__PURE__ */ jsx(IconMute, {}) : null
1052
+ ]
1053
+ }), onlineStatusText && /* @__PURE__ */ jsx("div", {
1054
+ className: "str-chat__channel-detail__channel-management-view__profile__details__connection-status",
1055
+ children: onlineStatusText
1056
+ })]
1057
+ })]
1058
+ }), /* @__PURE__ */ jsx("div", {
1059
+ className: "str-chat__channel-detail__channel-management-view__actions str-chat__form__switch-fieldset",
1060
+ children: actions.map(({ Component, type }) => /* @__PURE__ */ jsx(Component, {}, type))
1061
+ })]
1062
+ });
1063
+ };
1064
+ var EDIT_BODY_EMITTER = "ChannelManagementEditBody";
1065
+ /**
1066
+ * Assembles the argument for `channel.updatePartial` from the pending edits,
1067
+ * or returns `null` when there is nothing to persist. `image` is a tri-state:
1068
+ * a string sets a new avatar, `null` clears it, `undefined` leaves it untouched.
1069
+ */
1070
+ var buildChannelUpdatePayload = ({ image, name }) => {
1071
+ const payload = {};
1072
+ const set = {};
1073
+ if (name !== void 0) set.name = name;
1074
+ if (typeof image === "string") set.image = image;
1075
+ if (Object.keys(set).length > 0) payload.set = set;
1076
+ if (image === null) payload.unset = ["image"];
1077
+ return Object.keys(payload).length > 0 ? payload : null;
1078
+ };
1079
+ /**
1080
+ * Owns the channel-edit form: field state, the local image preview lifecycle,
1081
+ * the derived "can save" flags, and the save orchestration (upload → persist →
1082
+ * notify). The component is left to render the values this returns.
1083
+ */
1084
+ var useChannelManagementEditForm = ({ uploadImage }) => {
1085
+ const { t } = useTranslationContext();
1086
+ const { client } = useChatContext();
1087
+ const { channel } = useChannelDetailContext();
1088
+ const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({ channel });
1089
+ const { addNotification } = useNotificationApi();
1090
+ const resolvedIsDmChannel = isDmChannel({
1091
+ channel,
1092
+ ownUserId: client.user?.id
1093
+ });
1094
+ const hasMembersOnline = useChannelHasMembersOnline({ channel });
1095
+ const isOnline = resolvedIsDmChannel ? hasMembersOnline : void 0;
1096
+ const nameLabel = resolvedIsDmChannel ? t("Contact name") : t("Group name");
1097
+ const [baselineName, setBaselineName] = useState(channel.data?.name ?? "");
1098
+ const [name, setName] = useState(baselineName);
1099
+ const [imageEdit, setImageEdit] = useState(null);
1100
+ const [isSaving, setIsSaving] = useState(false);
1101
+ const fileInputRef = useRef(null);
1102
+ const pickedFile = imageEdit instanceof File ? imageEdit : null;
1103
+ const objectUrl = useMemo(() => pickedFile ? URL.createObjectURL(pickedFile) : null, [pickedFile]);
1104
+ useEffect(() => () => {
1105
+ if (objectUrl) URL.revokeObjectURL(objectUrl);
1106
+ }, [objectUrl]);
1107
+ const previewImageUrl = objectUrl ?? (imageEdit === "removed" ? void 0 : displayImage);
1108
+ const trimmedName = name.trim();
1109
+ const nameChanged = trimmedName !== baselineName.trim();
1110
+ const imageChanged = imageEdit !== null;
1111
+ const hasChanges = trimmedName.length > 0 && nameChanged || imageChanged;
1112
+ const canSubmit = trimmedName.length > 0 && !isSaving && hasChanges;
1113
+ const handleOpenFilePicker = useCallback(() => {
1114
+ fileInputRef.current?.click();
1115
+ }, []);
1116
+ const handleFileChange = useCallback((event) => {
1117
+ const file = event.target.files?.[0];
1118
+ if (file) setImageEdit(file);
1119
+ event.target.value = "";
1120
+ }, []);
1121
+ const handleDeleteImage = useCallback(() => setImageEdit("removed"), []);
1122
+ const resolveImageUrl = useCallback(async (file) => {
1123
+ const url = uploadImage ? await uploadImage(file) : (await channel.sendImage(file)).file;
1124
+ if (!url) throw new Error("Image upload did not return a URL");
1125
+ return url;
1126
+ }, [channel, uploadImage]);
1127
+ return {
1128
+ canSubmit,
1129
+ displayTitle,
1130
+ fileInputRef,
1131
+ groupChannelDisplayInfo,
1132
+ handleDeleteImage,
1133
+ handleFileChange,
1134
+ handleOpenFilePicker,
1135
+ handleSubmit: useCallback(async (event) => {
1136
+ event.preventDefault();
1137
+ if (!canSubmit) return;
1138
+ setIsSaving(true);
1139
+ try {
1140
+ let image;
1141
+ if (pickedFile) image = await resolveImageUrl(pickedFile);
1142
+ else if (imageEdit === "removed") image = null;
1143
+ const payload = buildChannelUpdatePayload({
1144
+ image,
1145
+ name: nameChanged ? trimmedName : void 0
1146
+ });
1147
+ if (payload) await channel.updatePartial(payload);
1148
+ setImageEdit(null);
1149
+ setBaselineName(trimmedName);
1150
+ setName(trimmedName);
1151
+ addNotification({
1152
+ duration: 3e3,
1153
+ emitter: EDIT_BODY_EMITTER,
1154
+ incident: {
1155
+ domain: "channel",
1156
+ entity: "channel",
1157
+ operation: "update",
1158
+ status: "success"
1159
+ },
1160
+ message: t("Changes saved"),
1161
+ severity: "success"
1162
+ });
1163
+ } catch (error) {
1164
+ addNotification({
1165
+ emitter: EDIT_BODY_EMITTER,
1166
+ error: error instanceof Error ? error : void 0,
1167
+ incident: {
1168
+ domain: "api",
1169
+ entity: "channel",
1170
+ operation: "update",
1171
+ status: "failed"
1172
+ },
1173
+ message: t("Failed to save changes"),
1174
+ severity: "error"
1175
+ });
1176
+ } finally {
1177
+ setIsSaving(false);
1178
+ }
1179
+ }, [
1180
+ addNotification,
1181
+ canSubmit,
1182
+ channel,
1183
+ imageEdit,
1184
+ nameChanged,
1185
+ pickedFile,
1186
+ resolveImageUrl,
1187
+ t,
1188
+ trimmedName
1189
+ ]),
1190
+ hasAvatarImage: !!previewImageUrl,
1191
+ isOnline,
1192
+ name,
1193
+ nameLabel,
1194
+ previewImageUrl,
1195
+ setName,
1196
+ t,
1197
+ trimmedName
1198
+ };
1199
+ };
1200
+ var ChannelManagementEditBody = (props) => {
1201
+ const { Avatar = ChannelAvatar } = useComponentContext();
1202
+ const { canSubmit, displayTitle, fileInputRef, groupChannelDisplayInfo, handleDeleteImage, handleFileChange, handleOpenFilePicker, handleSubmit, hasAvatarImage, isOnline, name, nameLabel, previewImageUrl, setName, t, trimmedName } = useChannelManagementEditForm(props);
1203
+ return /* @__PURE__ */ jsxs("form", {
1204
+ className: "str-chat__channel-detail__channel-management-view__form",
1205
+ onSubmit: handleSubmit,
1206
+ children: [/* @__PURE__ */ jsxs(Prompt.Body, {
1207
+ className: "str-chat__channel-detail__channel-management-view__body",
1208
+ children: [/* @__PURE__ */ jsxs("div", {
1209
+ className: "str-chat__channel-detail__channel-management-view__avatar-row",
1210
+ children: [/* @__PURE__ */ jsx(Avatar, {
1211
+ displayMembers: groupChannelDisplayInfo.members,
1212
+ imageUrl: previewImageUrl,
1213
+ isOnline,
1214
+ size: "2xl",
1215
+ userName: trimmedName || displayTitle
1216
+ }), /* @__PURE__ */ jsxs("div", {
1217
+ className: "str-chat__channel-detail__channel-management-view__avatar-row__actions",
1218
+ children: [
1219
+ /* @__PURE__ */ jsx(Button, {
1220
+ appearance: "outline",
1221
+ onClick: handleOpenFilePicker,
1222
+ size: "sm",
1223
+ type: "button",
1224
+ variant: "secondary",
1225
+ children: t("Upload Picture")
1226
+ }),
1227
+ hasAvatarImage && /* @__PURE__ */ jsx(Button, {
1228
+ appearance: "outline",
1229
+ onClick: handleDeleteImage,
1230
+ size: "sm",
1231
+ type: "button",
1232
+ variant: "secondary",
1233
+ children: t("Delete")
1234
+ }),
1235
+ /* @__PURE__ */ jsx("input", {
1236
+ accept: "image/*",
1237
+ "aria-hidden": true,
1238
+ className: "str-chat__channel-detail__channel-management-view__file-input",
1239
+ onChange: handleFileChange,
1240
+ ref: fileInputRef,
1241
+ tabIndex: -1,
1242
+ type: "file"
1243
+ })
1244
+ ]
1245
+ })]
1246
+ }), /* @__PURE__ */ jsx(TextInput, {
1247
+ "aria-label": nameLabel,
1248
+ autoFocus: true,
1249
+ className: "str-chat__channel-detail__channel-management-view__name-input",
1250
+ maxLength: 255,
1251
+ onChange: (event) => setName(event.target.value),
1252
+ placeholder: nameLabel,
1253
+ value: name
1254
+ })]
1255
+ }), /* @__PURE__ */ jsx(Prompt.Footer, {
1256
+ className: "str-chat__channel-detail__channel-management-view__footer",
1257
+ children: /* @__PURE__ */ jsx(Prompt.FooterControls, { children: canSubmit && /* @__PURE__ */ jsxs(Prompt.FooterControlsButtonPrimary, {
1258
+ className: "str-chat__channel-detail__channel-management-view__footer__save-button",
1259
+ type: "submit",
1260
+ children: [/* @__PURE__ */ jsx(IconCheckmark, {}), t("Save")]
1261
+ }) })
1262
+ })]
1263
+ });
1264
+ };
1265
+ var ChannelManagementView = ({ channelManagementActionSet = defaultChannelManagementActionSet, EditModeComponent = ChannelManagementEditBody, uploadImage, ViewModeComponent = ChannelManagementInfoBody }) => {
1266
+ const { t } = useTranslationContext();
1267
+ const { client } = useChatContext();
1268
+ const { channel } = useChannelDetailContext();
1269
+ const { close } = useModalContext();
1270
+ const resolvedIsDmChannel = isDmChannel({
1271
+ channel,
1272
+ ownUserId: client.user?.id
1273
+ });
1274
+ const actions = useBaseChannelManagementActionSetFilter(channelManagementActionSet);
1275
+ const [isEditing, setIsEditing] = useState(false);
1276
+ const canEditChannel = channel.data?.own_capabilities?.includes("update-channel");
1277
+ const isEditMode = isEditing && canEditChannel;
1278
+ useEffect(() => {
1279
+ setIsEditing(false);
1280
+ }, [channel.cid]);
1281
+ const EditChannelButton = useMemo(() => function EditChannelButton() {
1282
+ return /* @__PURE__ */ jsx(Button, {
1283
+ appearance: "outline",
1284
+ "aria-label": t("Edit chat data"),
1285
+ className: "str-chat__channel-detail__channel-management-view__edit-button",
1286
+ onClick: () => {
1287
+ setIsEditing(true);
1288
+ },
1289
+ size: "md",
1290
+ variant: "secondary",
1291
+ children: t("Edit")
1292
+ });
1293
+ }, [t]);
1294
+ const headerTitle = isEditMode ? resolvedIsDmChannel ? t("Edit contact") : t("Edit group") : resolvedIsDmChannel ? t("Contact info") : t("Group info");
1295
+ return /* @__PURE__ */ jsxs("div", {
1296
+ className: "str-chat__channel-detail__channel-management-view",
1297
+ children: [/* @__PURE__ */ jsx(SectionNavigatorHeader, {
1298
+ close,
1299
+ description: isEditMode ? void 0 : t("Manage channel"),
1300
+ goBack: isEditMode ? () => setIsEditing(false) : void 0,
1301
+ title: headerTitle,
1302
+ TrailingContent: !isEditMode && canEditChannel ? EditChannelButton : void 0
1303
+ }), isEditMode ? /* @__PURE__ */ jsx(EditModeComponent, { uploadImage }) : /* @__PURE__ */ jsx(ViewModeComponent, { actions })]
1304
+ });
1305
+ };
1306
+ //#endregion
1307
+ //#region src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaEmptyList.tsx
1308
+ var ChannelMediaEmptyList = () => {
1309
+ const { t } = useTranslationContext("ChannelMediaEmptyList");
1310
+ return /* @__PURE__ */ jsxs("div", {
1311
+ className: "str-chat__channel-detail__media-view__empty-state",
1312
+ children: [/* @__PURE__ */ jsx(IconImage, { className: "str-chat__channel-detail__media-view__empty-state__icon" }), /* @__PURE__ */ jsxs("div", {
1313
+ className: "str-chat__channel-detail__media-view__empty-state__content",
1314
+ children: [/* @__PURE__ */ jsx("p", {
1315
+ className: "str-chat__channel-detail__media-view__empty-state__title",
1316
+ children: t("No photos or videos")
1317
+ }), /* @__PURE__ */ jsx("p", {
1318
+ className: "str-chat__channel-detail__media-view__empty-state__description",
1319
+ children: t("Share a photo or video to see it here")
1320
+ })]
1321
+ })]
1322
+ });
1323
+ };
1324
+ //#endregion
1325
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.utils.ts
1326
+ var getMemberDisplayName = (member) => getUserDisplayName(member.user) || member.user_id || "";
1327
+ var getMemberUserId = (member) => member.user?.id || member.user_id;
1328
+ var getUserDisplayName = (user) => user?.name || user?.username || user?.id || "";
1329
+ var getChannelMemberUserIds = (channel) => Object.values(channel.state?.members ?? {}).map((member) => member.user?.id || member.user_id).filter((userId) => !!userId);
1330
+ var canUpdateChannelMembers = (channel) => channel.data?.own_capabilities?.includes("update-channel-members") ?? false;
1331
+ //#endregion
1332
+ //#region src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.utils.ts
1333
+ /** Attachment types rendered by the media gallery. */
1334
+ var MEDIA_ATTACHMENT_TYPES = ["image", "video"];
1335
+ var getMediaAttachmentType = (attachment) => {
1336
+ if (isVideoAttachment(attachment)) return "video";
1337
+ if (isImageAttachment(attachment)) return "image";
1338
+ };
1339
+ /**
1340
+ * Flattens messages into one renderable media item per image/video attachment,
1341
+ * carrying over the gallery descriptor, posting user, and video duration.
1342
+ */
1343
+ var toChannelMediaItems = (messages) => {
1344
+ const items = [];
1345
+ messages.forEach((message) => {
1346
+ message.attachments?.forEach((attachment, index) => {
1347
+ const type = getMediaAttachmentType(attachment);
1348
+ if (!type) return;
1349
+ const descriptor = toBaseImageDescriptors(attachment);
1350
+ if (!descriptor) return;
1351
+ if (!(type === "video" ? Boolean(descriptor.videoThumbnailUrl || descriptor.videoUrl) : Boolean(descriptor.imageUrl))) return;
1352
+ items.push({
1353
+ durationSeconds: typeof attachment.duration === "number" ? attachment.duration : void 0,
1354
+ galleryItem: descriptor,
1355
+ id: `${message.id}-${index}`,
1356
+ type,
1357
+ user: message.user ?? void 0
1358
+ });
1359
+ });
1360
+ });
1361
+ return items;
1362
+ };
1363
+ //#endregion
1364
+ //#region src/plugins/ChannelDetail/Views/ChannelMediaView/useChannelMediaSearch.ts
1365
+ var CHANNEL_MEDIA_SEARCH_PAGE_SIZE = 30;
1366
+ var channelMediaSearchSourceItemsStateSelector = (state) => ({
1367
+ hasNext: state.hasNext,
1368
+ isLoading: state.isLoading,
1369
+ messages: state.items
1370
+ });
1371
+ var useChannelMediaSearch = () => {
1372
+ const { client } = useChatContext();
1373
+ const { channel } = useChannelDetailContext();
1374
+ const channelMediaSearchSource = useMemo(() => {
1375
+ const source = new MessageSearchSource(client, {
1376
+ allowEmptySearchString: true,
1377
+ pageSize: CHANNEL_MEDIA_SEARCH_PAGE_SIZE,
1378
+ resetOnNewSearchQuery: false
1379
+ });
1380
+ source.messageSearchChannelFilters = { cid: channel.cid };
1381
+ source.messageSearchFilters = { "attachments.type": { $in: [...MEDIA_ATTACHMENT_TYPES] } };
1382
+ return source;
1383
+ }, [channel.cid, client]);
1384
+ const { hasNext, isLoading, messages } = useStateStore(channelMediaSearchSource.state, channelMediaSearchSourceItemsStateSelector);
1385
+ const mediaItems = useMemo(() => toChannelMediaItems(messages ?? []), [messages]);
1386
+ useEffect(() => {
1387
+ channelMediaSearchSource.activate();
1388
+ channelMediaSearchSource.search("");
1389
+ return () => {
1390
+ channelMediaSearchSource.cancelScheduledQuery();
1391
+ };
1392
+ }, [channelMediaSearchSource]);
1393
+ return {
1394
+ channelMediaSearchSource,
1395
+ hasNext: Boolean(hasNext),
1396
+ hasResultsLoaded: Array.isArray(messages),
1397
+ isLoading,
1398
+ mediaItems
1399
+ };
1400
+ };
1401
+ //#endregion
1402
+ //#region src/plugins/ChannelDetail/Views/ChannelMediaView/ChannelMediaView.tsx
1403
+ var DEFAULT_CHANNEL_MEDIA_ITEMS_PER_PAGE = 30;
1404
+ var ChannelMediaGridItem = ({ BaseImage, item, onClick }) => {
1405
+ const { t } = useTranslationContext("ChannelMediaView");
1406
+ const displayName = getUserDisplayName(item.user);
1407
+ const mediaSrc = item.type === "video" ? item.galleryItem.videoThumbnailUrl : item.galleryItem.imageUrl;
1408
+ const durationLabel = formatTime(item.durationSeconds, "floor");
1409
+ return /* @__PURE__ */ jsxs("button", {
1410
+ "aria-label": item.type === "video" ? t("aria/Open video shared by {{ name }}", { name: displayName }) : t("aria/Open image shared by {{ name }}", { name: displayName }),
1411
+ className: "str-chat__channel-detail__media-view__item",
1412
+ onClick,
1413
+ type: "button",
1414
+ children: [
1415
+ mediaSrc ? /* @__PURE__ */ jsx(BaseImage, {
1416
+ alt: item.galleryItem.alt ?? item.galleryItem.title ?? "",
1417
+ className: "str-chat__channel-detail__media-view__item__media",
1418
+ src: mediaSrc
1419
+ }) : /* @__PURE__ */ jsx("div", {
1420
+ "aria-hidden": "true",
1421
+ className: "str-chat__channel-detail__media-view__item__placeholder",
1422
+ children: /* @__PURE__ */ jsx(IconImage, {})
1423
+ }),
1424
+ /* @__PURE__ */ jsx(Avatar, {
1425
+ "aria-hidden": "true",
1426
+ className: "str-chat__channel-detail__media-view__item__avatar",
1427
+ imageUrl: item.user?.image,
1428
+ size: "sm",
1429
+ userName: displayName
1430
+ }),
1431
+ item.type === "video" && /* @__PURE__ */ jsxs(Badge, {
1432
+ className: "str-chat__channel-detail__media-view__item__duration",
1433
+ size: "sm",
1434
+ variant: "inverse",
1435
+ children: [/* @__PURE__ */ jsx(IconVideoFill, { className: "str-chat__channel-detail__media-view__item__duration-icon" }), durationLabel ? /* @__PURE__ */ jsx("span", { children: durationLabel }) : null]
1436
+ })
1437
+ ]
1438
+ });
1439
+ };
1440
+ var ChannelMediaPagination = ({ nextDisabled, onNext, onPrevious, previousDisabled }) => {
1441
+ const { t } = useTranslationContext("ChannelMediaView");
1442
+ return /* @__PURE__ */ jsxs("div", {
1443
+ className: "str-chat__channel-detail__media-view__pagination",
1444
+ children: [/* @__PURE__ */ jsxs(Button, {
1445
+ appearance: "outline",
1446
+ "aria-label": t("aria/Previous page"),
1447
+ className: "str-chat__channel-detail__media-view__pagination__button str-chat__channel-detail__media-view__pagination__button--previous",
1448
+ disabled: previousDisabled,
1449
+ onClick: onPrevious,
1450
+ size: "md",
1451
+ variant: "secondary",
1452
+ children: [/* @__PURE__ */ jsx(IconChevronLeft, {}), t("Previous")]
1453
+ }), /* @__PURE__ */ jsxs(Button, {
1454
+ appearance: "outline",
1455
+ "aria-label": t("aria/Next page"),
1456
+ className: "str-chat__channel-detail__media-view__pagination__button str-chat__channel-detail__media-view__pagination__button--next",
1457
+ disabled: nextDisabled,
1458
+ onClick: onNext,
1459
+ size: "md",
1460
+ variant: "secondary",
1461
+ children: [t("Next"), /* @__PURE__ */ jsx(IconChevronRight, {})]
1462
+ })]
1463
+ });
1464
+ };
1465
+ var ChannelMediaView = ({ itemsPerPage = DEFAULT_CHANNEL_MEDIA_ITEMS_PER_PAGE }) => {
1466
+ const { t } = useTranslationContext();
1467
+ const { close } = useModalContext();
1468
+ const { BaseImage: BaseImage$1 = BaseImage, Gallery: Gallery$1 = Gallery, Modal = GlobalModal } = useComponentContext();
1469
+ const { channelMediaSearchSource, hasNext, hasResultsLoaded, isLoading, mediaItems } = useChannelMediaSearch();
1470
+ const [viewerOpen, setViewerOpen] = useState(false);
1471
+ const [selectedIndex, setSelectedIndex] = useState(0);
1472
+ const [page, setPage] = useState(0);
1473
+ const gridRef = useRef(null);
1474
+ const pendingNextRef = useRef(false);
1475
+ const galleryItems = useMemo(() => mediaItems.map((item) => item.galleryItem), [mediaItems]);
1476
+ const pageStart = page * itemsPerPage;
1477
+ const pageItems = useMemo(() => mediaItems.slice(pageStart, pageStart + itemsPerPage), [
1478
+ mediaItems,
1479
+ itemsPerPage,
1480
+ pageStart
1481
+ ]);
1482
+ const canGoNext = mediaItems.length > pageStart + itemsPerPage || hasNext;
1483
+ const showPagination = mediaItems.length > itemsPerPage || mediaItems.length > 0 && hasNext;
1484
+ const openViewer = useCallback((index) => {
1485
+ setSelectedIndex(index);
1486
+ setViewerOpen(true);
1487
+ }, []);
1488
+ const closeViewer = useCallback(() => {
1489
+ setViewerOpen(false);
1490
+ }, []);
1491
+ const goToPreviousPage = useCallback(() => {
1492
+ pendingNextRef.current = false;
1493
+ setPage((current) => Math.max(0, current - 1));
1494
+ }, []);
1495
+ const goToNextPage = useCallback(() => {
1496
+ const nextPageStart = (page + 1) * itemsPerPage;
1497
+ if (mediaItems.length > nextPageStart) setPage(page + 1);
1498
+ else if (hasNext) {
1499
+ pendingNextRef.current = true;
1500
+ channelMediaSearchSource.search();
1501
+ }
1502
+ }, [
1503
+ channelMediaSearchSource,
1504
+ hasNext,
1505
+ itemsPerPage,
1506
+ mediaItems.length,
1507
+ page
1508
+ ]);
1509
+ useEffect(() => {
1510
+ if (isLoading) return;
1511
+ if (pendingNextRef.current) {
1512
+ const nextPageStart = (page + 1) * itemsPerPage;
1513
+ if (mediaItems.length > nextPageStart) {
1514
+ pendingNextRef.current = false;
1515
+ setPage((current) => current + 1);
1516
+ return;
1517
+ }
1518
+ if (!hasNext) {
1519
+ pendingNextRef.current = false;
1520
+ return;
1521
+ }
1522
+ channelMediaSearchSource.search();
1523
+ return;
1524
+ }
1525
+ const need = pageStart + itemsPerPage;
1526
+ if (mediaItems.length < need && hasNext) channelMediaSearchSource.search();
1527
+ }, [
1528
+ channelMediaSearchSource,
1529
+ hasNext,
1530
+ isLoading,
1531
+ itemsPerPage,
1532
+ mediaItems.length,
1533
+ page,
1534
+ pageStart
1535
+ ]);
1536
+ useEffect(() => {
1537
+ const grid = gridRef.current;
1538
+ if (typeof grid?.scrollTo === "function") grid.scrollTo({ top: 0 });
1539
+ else if (grid) grid.scrollTop = 0;
1540
+ }, [page]);
1541
+ return /* @__PURE__ */ jsxs("div", {
1542
+ className: "str-chat__channel-detail__media-view",
1543
+ children: [
1544
+ /* @__PURE__ */ jsx(SectionNavigatorHeader, {
1545
+ close,
1546
+ title: t("Photos & videos")
1547
+ }),
1548
+ /* @__PURE__ */ jsx(Prompt.Body, {
1549
+ className: "str-chat__channel-detail__media-view__body",
1550
+ children: /* @__PURE__ */ jsx("div", {
1551
+ className: "str-chat__channel-detail__media-view__grid",
1552
+ ref: gridRef,
1553
+ children: /* @__PURE__ */ jsxs("div", {
1554
+ className: "str-chat__channel-detail__media-view__grid__content",
1555
+ children: [
1556
+ pageItems.length > 0 ? /* @__PURE__ */ jsx("div", {
1557
+ className: "str-chat__channel-detail__media-view__grid__items",
1558
+ children: pageItems.map((item, index) => /* @__PURE__ */ jsx(ChannelMediaGridItem, {
1559
+ BaseImage: BaseImage$1,
1560
+ item,
1561
+ onClick: () => openViewer(pageStart + index)
1562
+ }, item.id))
1563
+ }) : hasResultsLoaded ? /* @__PURE__ */ jsx(ChannelMediaEmptyList, {}) : null,
1564
+ /* @__PURE__ */ jsx(ChannelDetailListLoadingIndicator, { searchSource: channelMediaSearchSource }),
1565
+ showPagination && /* @__PURE__ */ jsx(ChannelMediaPagination, {
1566
+ nextDisabled: !canGoNext || isLoading,
1567
+ onNext: goToNextPage,
1568
+ onPrevious: goToPreviousPage,
1569
+ previousDisabled: page === 0
1570
+ })
1571
+ ]
1572
+ })
1573
+ })
1574
+ }),
1575
+ /* @__PURE__ */ jsx(Modal, {
1576
+ className: clsx("str-chat__gallery-modal", "str-chat__channel-detail__media-view__viewer"),
1577
+ onClose: closeViewer,
1578
+ open: viewerOpen,
1579
+ children: /* @__PURE__ */ jsx(Gallery$1, {
1580
+ GalleryUI,
1581
+ initialIndex: selectedIndex,
1582
+ items: galleryItems,
1583
+ onRequestClose: closeViewer
1584
+ })
1585
+ })
1586
+ ]
1587
+ });
1588
+ };
1589
+ //#endregion
1590
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersHeaderActions.defaults.tsx
1591
+ /**
1592
+ * First-pass filter applied by {@link DefaultHeaderActions}. The SDK's own
1593
+ * actions are gated internally by capability — `addMembers` requires
1594
+ * `update-channel-members`; any other action is shown by default. App-defined
1595
+ * actions may further narrow visibility via their own `filter` predicate (which
1596
+ * is what an app should use to gate, e.g., a custom member-removal action).
1597
+ */
1598
+ var useBaseChannelMembersHeaderActionSetFilter = (channelMembersHeaderActionSet) => {
1599
+ const { channel } = useChannelDetailContext();
1600
+ const canManageChannelMembers = canUpdateChannelMembers(channel);
1601
+ return useMemo(() => channelMembersHeaderActionSet.filter((action) => {
1602
+ return (action.type !== "addMembers" || canManageChannelMembers) && (action.filter?.({ channel }) ?? true);
1603
+ }), [
1604
+ canManageChannelMembers,
1605
+ channel,
1606
+ channelMembersHeaderActionSet
1607
+ ]);
1608
+ };
1609
+ var AddMembersHeaderAction = ({ modeController }) => {
1610
+ const { t } = useTranslationContext();
1611
+ if (modeController.mode !== "browse") return null;
1612
+ return /* @__PURE__ */ jsx(Button, {
1613
+ appearance: "outline",
1614
+ "aria-label": t("Add channel members"),
1615
+ className: "str-chat__channel-detail__channel-members-view__add-button",
1616
+ onClick: () => modeController.setMode("add"),
1617
+ size: "md",
1618
+ variant: "secondary",
1619
+ children: t("Add")
1620
+ });
1621
+ };
1622
+ var AddMembersMenuAction = ({ closeMenu, modeController }) => {
1623
+ const { t } = useTranslationContext();
1624
+ if (modeController.mode !== "browse") return null;
1625
+ return /* @__PURE__ */ jsx(ContextMenuButton, {
1626
+ "aria-label": t("Add channel members"),
1627
+ Icon: IconUserAdd,
1628
+ onClick: () => {
1629
+ modeController.setMode("add");
1630
+ closeMenu?.();
1631
+ },
1632
+ children: t("Add")
1633
+ });
1634
+ };
1635
+ var DefaultChannelMembersHeaderActions = {
1636
+ AddMembers: AddMembersHeaderAction,
1637
+ AddMembersMenu: AddMembersMenuAction
1638
+ };
1639
+ var defaultChannelMembersHeaderActionSet = [{
1640
+ component: DefaultChannelMembersHeaderActions.AddMembers,
1641
+ placement: "quick",
1642
+ type: "addMembers"
1643
+ }];
1644
+ var DefaultHeaderActionsMenuTrigger = ({ referenceRef, ...props }) => {
1645
+ const { t } = useTranslationContext();
1646
+ return /* @__PURE__ */ jsx(Button, {
1647
+ appearance: "outline",
1648
+ "aria-label": t("Open members actions"),
1649
+ className: "str-chat__channel-detail__channel-members-view__actions-button",
1650
+ ref: referenceRef,
1651
+ size: "md",
1652
+ variant: "secondary",
1653
+ ...props,
1654
+ children: t("Actions")
1655
+ });
1656
+ };
1657
+ var getHeaderActionsDialogId = (channelId) => `channel-members-header-actions-${channelId ?? "unknown"}`;
1658
+ var DefaultHeaderActions = ({ headerActionSet, HeaderActionsMenuTrigger = DefaultHeaderActionsMenuTrigger, modeController }) => {
1659
+ const { ContextMenu: ContextMenuComponent = ContextMenu } = useComponentContext();
1660
+ const { channel } = useChannelDetailContext();
1661
+ const actions = useBaseChannelMembersHeaderActionSetFilter(headerActionSet);
1662
+ const [referenceElement, setReferenceElement] = useState(null);
1663
+ const dialogId = getHeaderActionsDialogId(channel.id);
1664
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId });
1665
+ const dialogManagerId = dialogManager?.id;
1666
+ const dialogIsOpen = useDialogIsOpen(dialogId, dialogManagerId);
1667
+ if (!actions.length) return null;
1668
+ const quickActions = actions.filter((action) => action.placement === "quick");
1669
+ const menuActions = actions.filter((action) => action.placement === "menu");
1670
+ return /* @__PURE__ */ jsxs("div", {
1671
+ className: "str-chat__channel-detail__channel-members-view__header-actions",
1672
+ children: [quickActions.map(({ component: QuickComponent, type }) => /* @__PURE__ */ jsx(QuickComponent, { modeController }, type)), menuActions.length > 0 && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(HeaderActionsMenuTrigger, {
1673
+ "aria-expanded": dialogIsOpen,
1674
+ onClick: () => {
1675
+ dialog.toggle();
1676
+ },
1677
+ referenceRef: setReferenceElement
1678
+ }), /* @__PURE__ */ jsx(ContextMenuComponent, {
1679
+ "aria-label": "Members actions",
1680
+ className: "str-chat__channel-detail__channel-members-view__header-actions-menu",
1681
+ dialogManagerId,
1682
+ id: dialog.id,
1683
+ onClose: () => dialog.close(),
1684
+ placement: "bottom-start",
1685
+ referenceElement,
1686
+ tabIndex: -1,
1687
+ trapFocus: true,
1688
+ children: menuActions.map(({ component: MenuComponent, type }) => /* @__PURE__ */ jsx(MenuComponent, {
1689
+ closeMenu: () => dialog.close(),
1690
+ modeController
1691
+ }, type))
1692
+ })] })]
1693
+ });
1694
+ };
1695
+ //#endregion
1696
+ //#region src/plugins/ChannelDetail/VirtualizedList/VirtualizedList.tsx
1697
+ var VirtualizedListContent = forwardRef(function VirtualizedListContent({ className, ...props }, ref) {
1698
+ return /* @__PURE__ */ jsx("div", {
1699
+ ...props,
1700
+ className: clsx("str-chat__virtualized-list__content", className),
1701
+ ref
1702
+ });
1703
+ });
1704
+ /**
1705
+ * Thin wrapper around `react-virtuoso`'s `Virtuoso` for the simple,
1706
+ * append-only, flat lists used across the ChannelDetail views. It keeps the
1707
+ * call sites declarative (data + itemContent + empty/footer slots) and hides
1708
+ * the infinite-scroll wiring.
1709
+ */
1710
+ var VirtualizedList = ({ className, computeItemKey, data, EmptyPlaceholder, Footer, itemContent, loadNext, virtuosoProps, ...rest }) => {
1711
+ const atBottomStateChange = useCallback((atBottom) => {
1712
+ if (atBottom) loadNext?.();
1713
+ }, [loadNext]);
1714
+ const components = useMemo(() => ({
1715
+ EmptyPlaceholder,
1716
+ Footer,
1717
+ List: VirtualizedListContent
1718
+ }), [EmptyPlaceholder, Footer]);
1719
+ return /* @__PURE__ */ jsx(Virtuoso, {
1720
+ atBottomStateChange,
1721
+ className: clsx("str-chat__virtualized-list", className),
1722
+ components,
1723
+ computeItemKey,
1724
+ data,
1725
+ itemContent,
1726
+ ...rest,
1727
+ ...virtuosoProps
1728
+ });
1729
+ };
1730
+ //#endregion
1731
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberIds.ts
1732
+ var MEMBER_IDS_EVENTS = [
1733
+ "member.added",
1734
+ "member.removed",
1735
+ "member.updated"
1736
+ ];
1737
+ /** Reactive set of channel member user ids derived from real channel state. */
1738
+ var useChannelMemberIds = (channel) => {
1739
+ const [memberIds, setMemberIds] = useState(() => getChannelMemberUserIds(channel));
1740
+ useEffect(() => {
1741
+ const syncMemberIds = () => setMemberIds(getChannelMemberUserIds(channel));
1742
+ syncMemberIds();
1743
+ const subscriptions = MEMBER_IDS_EVENTS.map((event) => channel.on(event, syncMemberIds));
1744
+ return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
1745
+ }, [channel]);
1746
+ return memberIds;
1747
+ };
1748
+ //#endregion
1749
+ //#region src/plugins/ChannelDetail/ChannelDetailSearchInput.tsx
1750
+ var ChannelDetailSearchInput = React.memo(({ autoFocus, onSearchChange, resetKey }) => {
1751
+ const { t } = useTranslationContext();
1752
+ const [searchInput, setSearchInput] = useState("");
1753
+ useEffect(() => {
1754
+ setSearchInput("");
1755
+ }, [resetKey]);
1756
+ const handleSearchChange = useCallback((event) => {
1757
+ const { value } = event.target;
1758
+ setSearchInput(value);
1759
+ onSearchChange(value);
1760
+ }, [onSearchChange]);
1761
+ return /* @__PURE__ */ jsx(TextInput, {
1762
+ "aria-label": t("Search"),
1763
+ autoComplete: "off",
1764
+ autoFocus,
1765
+ className: "str-chat__channel-detail__search-input",
1766
+ leading: /* @__PURE__ */ jsx(IconSearch, {}),
1767
+ onChange: handleSearchChange,
1768
+ placeholder: t("Search"),
1769
+ type: "search",
1770
+ value: searchInput
1771
+ });
1772
+ });
1773
+ ChannelDetailSearchInput.displayName = "ChannelDetailSearchInput";
1774
+ //#endregion
1775
+ //#region src/plugins/ChannelDetail/ChannelDetailEmptyList.tsx
1776
+ var ChannelDetailEmptyList = ({ children }) => /* @__PURE__ */ jsxs("div", {
1777
+ className: "str-chat__channel-detail__channel-members-view__empty-state",
1778
+ children: [/* @__PURE__ */ jsx(IconSearch, {}), /* @__PURE__ */ jsx("div", { children })]
1779
+ });
1780
+ //#endregion
1781
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersAddView.tsx
1782
+ var USER_SEARCH_PAGE_SIZE = 30;
1783
+ var searchSourceItemsStateSelector = (state) => ({
1784
+ isLoading: state.isLoading,
1785
+ users: state.items
1786
+ });
1787
+ var EMPTY_USERS = [];
1788
+ var computeUserItemKey = (_, user) => user.id;
1789
+ var MuteIndicator = () => /* @__PURE__ */ jsx(IconMute, { className: "str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--mute" });
1790
+ var readOnlyRootProps = { className: "str-chat__channel-detail__channel-members-view__list-item" };
1791
+ var ChannelMembersAddViewItem = ({ canManageChannelMembers, isMember, isMuted, isSelected, toggleSelectedUser, user }) => {
1792
+ const { t } = useTranslationContext();
1793
+ const displayName = getUserDisplayName(user);
1794
+ const LeadingSlot = useMemo(() => function MemberAvatar() {
1795
+ return /* @__PURE__ */ jsx(Avatar, {
1796
+ imageUrl: user.image,
1797
+ isOnline: user.online,
1798
+ size: "md",
1799
+ userName: displayName
1800
+ });
1801
+ }, [
1802
+ displayName,
1803
+ user.image,
1804
+ user.online
1805
+ ]);
1806
+ const SelectableTrailingSlot = useMemo(() => function SelectCheckbox() {
1807
+ return /* @__PURE__ */ jsx(Checkbox, {
1808
+ "aria-hidden": true,
1809
+ checked: isSelected
1810
+ });
1811
+ }, [isSelected]);
1812
+ const selectableRootProps = useMemo(() => ({
1813
+ "aria-pressed": isSelected,
1814
+ className: "str-chat__channel-detail__channel-members-view__list-item",
1815
+ onClick: () => toggleSelectedUser(user.id)
1816
+ }), [
1817
+ isSelected,
1818
+ toggleSelectedUser,
1819
+ user.id
1820
+ ]);
1821
+ if (canManageChannelMembers && !isMember) return /* @__PURE__ */ jsx(ListItemLayout, {
1822
+ LeadingSlot,
1823
+ RootElement: "button",
1824
+ rootProps: selectableRootProps,
1825
+ title: displayName,
1826
+ TrailingSlot: SelectableTrailingSlot
1827
+ });
1828
+ return /* @__PURE__ */ jsx(ListItemLayout, {
1829
+ LeadingSlot,
1830
+ rootProps: readOnlyRootProps,
1831
+ subtitle: isMember ? t("Already a member") : void 0,
1832
+ title: displayName,
1833
+ TrailingSlot: isMuted ? MuteIndicator : void 0
1834
+ });
1835
+ };
1836
+ var ChannelMembersAddView = ({ modeController, searchSource }) => {
1837
+ const { client, mutes } = useChatContext();
1838
+ const { t } = useTranslationContext();
1839
+ const { channel } = useChannelDetailContext();
1840
+ const canManageChannelMembers = canUpdateChannelMembers(channel);
1841
+ const { addNotification } = useNotificationApi();
1842
+ const memberUserIds = useChannelMemberIds(channel);
1843
+ const memberIdSet = useMemo(() => new Set(memberUserIds), [memberUserIds]);
1844
+ const userSearchSource = useMemo(() => searchSource ?? new UserSearchSource(client, {
1845
+ allowEmptySearchString: true,
1846
+ pageSize: USER_SEARCH_PAGE_SIZE,
1847
+ resetOnNewSearchQuery: false
1848
+ }), [client, searchSource]);
1849
+ const { isLoading, users } = useStateStore(userSearchSource.state, searchSourceItemsStateSelector);
1850
+ const [isSaving, setIsSaving] = useState(false);
1851
+ const [selectedUserIds, setSelectedUserIds] = useState([]);
1852
+ useEffect(() => {
1853
+ userSearchSource.activate();
1854
+ userSearchSource.search("");
1855
+ return () => userSearchSource.cancelScheduledQuery();
1856
+ }, [userSearchSource]);
1857
+ const selectedUserIdSet = useMemo(() => new Set(selectedUserIds), [selectedUserIds]);
1858
+ const mutedUserIdSet = useMemo(() => new Set(mutes.map((mute) => mute.target.id)), [mutes]);
1859
+ const handleSearchChange = useCallback((query) => {
1860
+ userSearchSource.search(query);
1861
+ }, [userSearchSource]);
1862
+ const toggleSelectedUser = useCallback((userId) => setSelectedUserIds((currentSelectedUserIds) => currentSelectedUserIds.includes(userId) ? currentSelectedUserIds.filter((id) => id !== userId) : [...currentSelectedUserIds, userId]), []);
1863
+ const renderItem = useCallback((_, user) => /* @__PURE__ */ jsx(ChannelMembersAddViewItem, {
1864
+ canManageChannelMembers,
1865
+ isMember: memberIdSet.has(user.id),
1866
+ isMuted: mutedUserIdSet.has(user.id),
1867
+ isSelected: selectedUserIdSet.has(user.id),
1868
+ toggleSelectedUser,
1869
+ user
1870
+ }), [
1871
+ canManageChannelMembers,
1872
+ memberIdSet,
1873
+ mutedUserIdSet,
1874
+ selectedUserIdSet,
1875
+ toggleSelectedUser
1876
+ ]);
1877
+ const EmptyPlaceholder = useMemo(() => function ChannelMembersAddEmptyPlaceholder() {
1878
+ if (isLoading || !users) return null;
1879
+ return /* @__PURE__ */ jsx(ChannelDetailEmptyList, { children: t("No user found") });
1880
+ }, [
1881
+ isLoading,
1882
+ t,
1883
+ users
1884
+ ]);
1885
+ const Footer = useMemo(() => function ChannelMembersAddListFooter() {
1886
+ return /* @__PURE__ */ jsx(ChannelDetailListLoadingIndicator, { searchSource: userSearchSource });
1887
+ }, [userSearchSource]);
1888
+ const handleSave = async () => {
1889
+ if (!canManageChannelMembers || !selectedUserIds.length || isSaving) return;
1890
+ setIsSaving(true);
1891
+ try {
1892
+ await channel.addMembers(selectedUserIds);
1893
+ addNotification({
1894
+ context: { channel },
1895
+ emitter: "ChannelMembersView",
1896
+ message: t("{{ count }} members added", { count: selectedUserIds.length }),
1897
+ severity: "success",
1898
+ type: "api:channel:addMembers:success"
1899
+ });
1900
+ setSelectedUserIds([]);
1901
+ setIsSaving(false);
1902
+ modeController.setMode("browse");
1903
+ } catch (error) {
1904
+ setIsSaving(false);
1905
+ addNotification({
1906
+ context: { channel },
1907
+ emitter: "ChannelMembersView",
1908
+ error,
1909
+ message: t("Error adding members"),
1910
+ severity: "error",
1911
+ type: "api:channel:addMembers:failed"
1912
+ });
1913
+ }
1914
+ };
1915
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Prompt.Body, {
1916
+ className: "str-chat__channel-members-view__body",
1917
+ children: [/* @__PURE__ */ jsx(ChannelDetailSearchInput, {
1918
+ autoFocus: true,
1919
+ onSearchChange: handleSearchChange
1920
+ }), /* @__PURE__ */ jsx(VirtualizedList, {
1921
+ className: "str-chat__channel-detail__channel-members-view__list",
1922
+ computeItemKey: computeUserItemKey,
1923
+ data: users ?? EMPTY_USERS,
1924
+ EmptyPlaceholder,
1925
+ Footer,
1926
+ itemContent: renderItem,
1927
+ loadNext: userSearchSource.search
1928
+ })]
1929
+ }), canManageChannelMembers && selectedUserIds.length > 0 && /* @__PURE__ */ jsx(Prompt.Footer, { children: /* @__PURE__ */ jsx(Prompt.FooterControls, { children: /* @__PURE__ */ jsx(Prompt.FooterControlsButtonPrimary, {
1930
+ disabled: isSaving,
1931
+ onClick: handleSave,
1932
+ children: t("Add {{ count }} members", { count: selectedUserIds.length })
1933
+ }) }) })] });
1934
+ };
1935
+ //#endregion
1936
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMemberCount.ts
1937
+ var MEMBER_COUNT_EVENTS = [
1938
+ "member.added",
1939
+ "member.removed",
1940
+ "channel.updated"
1941
+ ];
1942
+ /** Reactive channel member count derived from real channel state. */
1943
+ var useChannelMemberCount = (channel) => {
1944
+ const [memberCount, setMemberCount] = useState(channel.data?.member_count ?? 0);
1945
+ useEffect(() => {
1946
+ const syncMemberCount = () => setMemberCount(channel.data?.member_count ?? 0);
1947
+ syncMemberCount();
1948
+ const subscriptions = MEMBER_COUNT_EVENTS.map((event) => channel.on(event, syncMemberCount));
1949
+ return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
1950
+ }, [channel]);
1951
+ return memberCount;
1952
+ };
1953
+ //#endregion
1954
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/useChannelMembersSearch.ts
1955
+ var MEMBERS_SEARCH_DEBOUNCE_MS = 300;
1956
+ var membersSearchSourceItemsStateSelector = (state) => ({ members: state.items });
1957
+ var useChannelMembersSearch = () => {
1958
+ const { channel } = useChannelDetailContext();
1959
+ const fallbackMembers = useMemo(() => Object.values(channel.state?.members ?? {}), [channel]);
1960
+ const memberCount = useChannelMemberCount(channel);
1961
+ const hasMembers = channel.data?.member_count === void 0 || memberCount !== 0;
1962
+ const membersSearchSource = useMemo(() => new ChannelMemberSearchSource(channel, {
1963
+ allowEmptySearchString: true,
1964
+ debounceMs: MEMBERS_SEARCH_DEBOUNCE_MS,
1965
+ pageSize: 100,
1966
+ resetOnNewSearchQuery: false
1967
+ }), [channel]);
1968
+ const { members } = useStateStore(membersSearchSource.state, membersSearchSourceItemsStateSelector);
1969
+ const [searchInputResetKey, setSearchInputResetKey] = useState(0);
1970
+ const resetMembersSearch = useCallback(() => {
1971
+ membersSearchSource.cancelScheduledQuery();
1972
+ setSearchInputResetKey((currentResetKey) => currentResetKey + 1);
1973
+ membersSearchSource.resetState();
1974
+ membersSearchSource.activate();
1975
+ membersSearchSource.search("");
1976
+ }, [membersSearchSource]);
1977
+ const handleSearchChange = useCallback((query) => {
1978
+ membersSearchSource.search(query.trim());
1979
+ }, [membersSearchSource]);
1980
+ useEffect(() => {
1981
+ if (!hasMembers) return;
1982
+ membersSearchSource.activate();
1983
+ membersSearchSource.search("");
1984
+ }, [hasMembers, membersSearchSource]);
1985
+ useEffect(() => () => {
1986
+ membersSearchSource.cancelScheduledQuery();
1987
+ }, [membersSearchSource]);
1988
+ return {
1989
+ displayedMembers: members ?? fallbackMembers,
1990
+ handleSearchChange,
1991
+ hasMembers,
1992
+ membersSearchSource,
1993
+ resetMembersSearch,
1994
+ searchInputResetKey
1995
+ };
1996
+ };
1997
+ //#endregion
1998
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersBrowseView.tsx
1999
+ var getMemberRoleTranslation = (member, t) => {
2000
+ if ([member.user?.role, member.channel_role].includes("admin")) return t("Admin");
2001
+ if (member.channel_role === "channel_moderator" || member.channel_role === "moderator") return t("Moderator");
2002
+ if (member.role === "owner") return t("Owner");
2003
+ };
2004
+ var getPresenceStatusText$1 = (user, t) => {
2005
+ if (user?.online) return t("Online");
2006
+ if (user?.last_active) return t("Last seen {{ timestamp }}", { timestamp: t("timestamp/ChannelMembersLastActive", { timestamp: user.last_active }) });
2007
+ return t("Offline");
2008
+ };
2009
+ var ChannelMembersBrowseViewItem = ({ isMuted, member, onMemberSelect }) => {
2010
+ const { t } = useTranslationContext();
2011
+ const user = member.user;
2012
+ const displayName = getMemberDisplayName(member);
2013
+ const roleTranslation = getMemberRoleTranslation(member, t);
2014
+ const LeadingSlot = useMemo(() => function MemberAvatar() {
2015
+ return /* @__PURE__ */ jsx(Avatar, {
2016
+ imageUrl: user?.image,
2017
+ isOnline: user?.online,
2018
+ size: "md",
2019
+ userName: getUserDisplayName(user)
2020
+ });
2021
+ }, [user]);
2022
+ const TrailingSlot = useMemo(() => function MemberTrailingSlot() {
2023
+ return /* @__PURE__ */ jsxs("div", {
2024
+ className: "str-chat__channel-detail__channel-members-view__list-item__trailing-slot",
2025
+ children: [roleTranslation ? /* @__PURE__ */ jsx("span", {
2026
+ className: "str-chat__channel-detail__channel-members-view__role-label",
2027
+ children: roleTranslation
2028
+ }) : null, isMuted ? /* @__PURE__ */ jsx(IconMute, { className: "str-chat__channel-detail__channel-members-view__list-item__indicator-icon str-chat__channel-detail__channel-members-view__list-item__indicator-icon--mute" }) : null]
2029
+ });
2030
+ }, [isMuted, roleTranslation]);
2031
+ return /* @__PURE__ */ jsx(ListItemLayout, {
2032
+ LeadingSlot,
2033
+ RootElement: "button",
2034
+ rootProps: useMemo(() => ({
2035
+ "aria-label": t("View member details for {{ member }}", { member: displayName }),
2036
+ className: "str-chat__channel-detail__channel-members-view__list-item",
2037
+ onClick: () => onMemberSelect?.(member)
2038
+ }), [
2039
+ displayName,
2040
+ member,
2041
+ onMemberSelect,
2042
+ t
2043
+ ]),
2044
+ subtitle: getPresenceStatusText$1(user, t),
2045
+ title: displayName,
2046
+ TrailingSlot
2047
+ });
2048
+ };
2049
+ var computeMemberItemKey = (_, member) => getMemberUserId(member);
2050
+ var ChannelMembersBrowseView = ({ onMemberSelect }) => {
2051
+ const { mutes } = useChatContext();
2052
+ const { t } = useTranslationContext();
2053
+ const { displayedMembers, handleSearchChange, hasMembers, membersSearchSource, searchInputResetKey } = useChannelMembersSearch();
2054
+ const mutedUserIdSet = useMemo(() => new Set(mutes.map((mute) => mute.target.id)), [mutes]);
2055
+ const renderableMembers = useMemo(() => displayedMembers.filter((member) => getMemberUserId(member)), [displayedMembers]);
2056
+ const renderItem = useCallback((_, member) => /* @__PURE__ */ jsx(ChannelMembersBrowseViewItem, {
2057
+ isMuted: mutedUserIdSet.has(getMemberUserId(member)),
2058
+ member,
2059
+ onMemberSelect
2060
+ }), [mutedUserIdSet, onMemberSelect]);
2061
+ const EmptyPlaceholder = useMemo(() => function ChannelMembersEmptyPlaceholder() {
2062
+ return /* @__PURE__ */ jsx(ChannelDetailEmptyList, { children: t("No member found") });
2063
+ }, [t]);
2064
+ const Footer = useMemo(() => function ChannelMembersListFooter() {
2065
+ return /* @__PURE__ */ jsx(ChannelDetailListLoadingIndicator, { searchSource: membersSearchSource });
2066
+ }, [membersSearchSource]);
2067
+ return /* @__PURE__ */ jsxs(Prompt.Body, {
2068
+ className: "str-chat__channel-members-view__body",
2069
+ children: [hasMembers && /* @__PURE__ */ jsx(ChannelDetailSearchInput, {
2070
+ onSearchChange: handleSearchChange,
2071
+ resetKey: searchInputResetKey
2072
+ }), /* @__PURE__ */ jsx(VirtualizedList, {
2073
+ className: "str-chat__channel-detail__channel-members-view__list",
2074
+ computeItemKey: computeMemberItemKey,
2075
+ data: renderableMembers,
2076
+ EmptyPlaceholder,
2077
+ Footer,
2078
+ itemContent: renderItem,
2079
+ loadNext: hasMembers ? membersSearchSource.search : void 0
2080
+ })]
2081
+ });
2082
+ };
2083
+ //#endregion
2084
+ //#region src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberActions.defaults.tsx
2085
+ var ChannelMemberActionContext = createContext(void 0);
2086
+ var ChannelMemberActionProvider = ({ children, value }) => /* @__PURE__ */ jsx(ChannelMemberActionContext.Provider, {
2087
+ value,
2088
+ children
2089
+ });
2090
+ var useChannelMemberActionContext = () => {
2091
+ const contextValue = useContext(ChannelMemberActionContext);
2092
+ if (!contextValue) throw new Error("The useChannelMemberActionContext hook was called outside of ChannelMemberActionProvider.");
2093
+ return contextValue;
2094
+ };
2095
+ var toError = (error) => error instanceof Error ? error : /* @__PURE__ */ new Error("An unknown error occurred");
2096
+ var MemberMuteActionIcon = () => /* @__PURE__ */ jsx(IconMute, { className: "str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--mute" });
2097
+ var MemberUnmuteActionIcon = () => /* @__PURE__ */ jsx(IconAudio, { className: "str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--unmute" });
2098
+ var SendDirectMessageActionIcon = () => /* @__PURE__ */ jsx(IconMessageBubble, { className: "str-chat__channel-detail__action-icon" });
2099
+ var BlockUserActionIcon = () => /* @__PURE__ */ jsx(IconNoSign, { className: "str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--block-user" });
2100
+ var RemoveUserActionIcon = () => /* @__PURE__ */ jsx(IconUserRemove, { className: "str-chat__icon--destructive str-chat__channel-detail__action-icon str-chat__channel-detail__action-icon--remove-user" });
2101
+ var channelMemberDetailActionClassName = "str-chat__channel-member-detail-action";
2102
+ var blockedUsersSelector = ({ userIds }) => ({ userIds });
2103
+ var ChannelMemberConfirmationAlert = ({ action, cancelLabel, confirmLabel, description, isSubmitting, onCancel, onConfirm, testId, title }) => /* @__PURE__ */ jsxs(Alert.Root, {
2104
+ className: clsx("str-chat__channel-member-confirmation-alert", { [`str-chat__channel-member-confirmation-alert--${action}`]: action }),
2105
+ "data-testid": testId,
2106
+ children: [/* @__PURE__ */ jsx(Alert.Header, {
2107
+ description,
2108
+ title
2109
+ }), /* @__PURE__ */ jsxs(Alert.Actions, { children: [/* @__PURE__ */ jsx(Button, {
2110
+ appearance: "solid",
2111
+ "data-testid": `${testId}-confirm-button`,
2112
+ disabled: isSubmitting,
2113
+ onClick: onConfirm,
2114
+ size: "md",
2115
+ variant: "danger",
2116
+ children: confirmLabel
2117
+ }), /* @__PURE__ */ jsx(Button, {
2118
+ appearance: "outline",
2119
+ autoFocus: true,
2120
+ "data-testid": `${testId}-cancel-button`,
2121
+ disabled: isSubmitting,
2122
+ onClick: onCancel,
2123
+ size: "md",
2124
+ variant: "secondary",
2125
+ children: cancelLabel
2126
+ })] })]
2127
+ });
2128
+ var useChannelMemberActionFilterState = () => {
2129
+ const { client } = useChatContext();
2130
+ const { channel } = useChannelDetailContext();
2131
+ const { targetUserId } = useChannelMemberActionContext();
2132
+ const ownCapabilities = channel.data?.own_capabilities;
2133
+ const isCurrentUser = targetUserId === client.user?.id;
2134
+ return {
2135
+ canBlockUser: !isCurrentUser && !!targetUserId,
2136
+ canMuteUser: !isCurrentUser && !!targetUserId,
2137
+ canRemoveUser: !isCurrentUser && !!targetUserId && ownCapabilities?.includes("update-channel-members"),
2138
+ canSendMessage: !isCurrentUser && !!targetUserId
2139
+ };
2140
+ };
2141
+ var useBaseChannelMemberActionSetFilter = (channelMemberActionSet) => {
2142
+ const { canBlockUser, canMuteUser, canRemoveUser, canSendMessage } = useChannelMemberActionFilterState();
2143
+ return useMemo(() => channelMemberActionSet.filter((action) => {
2144
+ switch (action.type) {
2145
+ case "blockUser": return canBlockUser;
2146
+ case "muteUser": return canMuteUser;
2147
+ case "removeUser": return canRemoveUser;
2148
+ case "sendMessage": return canSendMessage;
2149
+ default: return true;
2150
+ }
2151
+ }), [
2152
+ canBlockUser,
2153
+ canMuteUser,
2154
+ canRemoveUser,
2155
+ canSendMessage,
2156
+ channelMemberActionSet
2157
+ ]);
2158
+ };
2159
+ var SendDirectMessageAction = () => {
2160
+ const { client, setActiveChannel } = useChatContext();
2161
+ const { setChannels } = useChannelListContext();
2162
+ const { close } = useModalContext();
2163
+ const { channel } = useChannelDetailContext();
2164
+ const { addNotification } = useNotificationApi();
2165
+ const { t } = useTranslationContext();
2166
+ const { targetUserId } = useChannelMemberActionContext();
2167
+ const [isSending, setIsSending] = useState(false);
2168
+ const openDirectMessage = useCallback(async () => {
2169
+ if (!client.userID || !targetUserId || isSending) return;
2170
+ setIsSending(true);
2171
+ try {
2172
+ const directMessageChannel = client.channel(channel.type, { members: [client.userID, targetUserId] });
2173
+ await directMessageChannel.watch();
2174
+ setActiveChannel(directMessageChannel);
2175
+ setChannels?.((channels) => uniqBy([directMessageChannel, ...channels], "cid"));
2176
+ close();
2177
+ } catch (error) {
2178
+ addNotification({
2179
+ context: { channel },
2180
+ emitter: "ChannelMemberDetail",
2181
+ error: toError(error),
2182
+ message: t("Error opening direct message"),
2183
+ severity: "error",
2184
+ type: "api:channel:watch:failed"
2185
+ });
2186
+ } finally {
2187
+ setIsSending(false);
2188
+ }
2189
+ }, [
2190
+ addNotification,
2191
+ channel,
2192
+ client,
2193
+ close,
2194
+ isSending,
2195
+ setActiveChannel,
2196
+ setChannels,
2197
+ t,
2198
+ targetUserId
2199
+ ]);
2200
+ return /* @__PURE__ */ jsx(ListItemLayout, {
2201
+ LeadingIcon: SendDirectMessageActionIcon,
2202
+ RootElement: "button",
2203
+ rootProps: useMemo(() => ({
2204
+ className: channelMemberDetailActionClassName,
2205
+ disabled: isSending,
2206
+ onClick: openDirectMessage
2207
+ }), [isSending, openDirectMessage]),
2208
+ title: t("Send direct message")
2209
+ });
2210
+ };
2211
+ var UserMuteAction = () => {
2212
+ const { channel } = useChannelDetailContext();
2213
+ const { client, mutes } = useChatContext();
2214
+ const { addNotification } = useNotificationApi();
2215
+ const { t } = useTranslationContext();
2216
+ const { targetUserId } = useChannelMemberActionContext();
2217
+ const userMuted = !!targetUserId && mutes.some((mute) => mute.target.id === targetUserId);
2218
+ const [optimisticUserMuted, setOptimisticUserMuted] = useState(userMuted);
2219
+ useEffect(() => {
2220
+ setOptimisticUserMuted(userMuted);
2221
+ }, [userMuted]);
2222
+ const toggleUserMuteRequest = useStableCallback((nextMuted, userId) => {
2223
+ if (!userId) return;
2224
+ if (!nextMuted) return client.unmuteUser(userId).then(() => addNotification({
2225
+ context: { channel },
2226
+ emitter: "ChannelMemberDetail",
2227
+ message: t("User unmuted"),
2228
+ severity: "success",
2229
+ type: "api:user:unmute:success"
2230
+ })).catch((error) => {
2231
+ setOptimisticUserMuted(userMuted);
2232
+ return addNotification({
2233
+ context: { channel },
2234
+ emitter: "ChannelMemberDetail",
2235
+ error: toError(error),
2236
+ message: t("Error unmuting user"),
2237
+ severity: "error",
2238
+ type: "api:user:unmute:failed"
2239
+ });
2240
+ });
2241
+ return client.muteUser(userId).then(() => addNotification({
2242
+ context: { channel },
2243
+ emitter: "ChannelMemberDetail",
2244
+ message: t("User muted"),
2245
+ severity: "success",
2246
+ type: "api:user:mute:success"
2247
+ })).catch((error) => {
2248
+ setOptimisticUserMuted(userMuted);
2249
+ return addNotification({
2250
+ context: { channel },
2251
+ emitter: "ChannelMemberDetail",
2252
+ error: toError(error),
2253
+ message: t("Error muting user"),
2254
+ severity: "error",
2255
+ type: "api:user:mute:failed"
2256
+ });
2257
+ });
2258
+ });
2259
+ const toggleUserMute = useMemo(() => debounce(toggleUserMuteRequest, 1e3), [toggleUserMuteRequest]);
2260
+ useEffect(() => () => {
2261
+ toggleUserMute.cancel();
2262
+ }, [toggleUserMute]);
2263
+ const toggleOptimisticUserMute = useCallback(() => {
2264
+ const nextMuted = !optimisticUserMuted;
2265
+ setOptimisticUserMuted(nextMuted);
2266
+ toggleUserMute(nextMuted, targetUserId);
2267
+ }, [
2268
+ optimisticUserMuted,
2269
+ targetUserId,
2270
+ toggleUserMute
2271
+ ]);
2272
+ const rootProps = useMemo(() => ({
2273
+ "aria-pressed": optimisticUserMuted,
2274
+ className: clsx("str-chat__form__switch-field", channelMemberDetailActionClassName),
2275
+ onClick: toggleOptimisticUserMute
2276
+ }), [optimisticUserMuted, toggleOptimisticUserMute]);
2277
+ const TrailingSlot = useMemo(() => {
2278
+ function UserMuteSwitch() {
2279
+ return /* @__PURE__ */ jsx(Switch, {
2280
+ on: optimisticUserMuted,
2281
+ presentation: true
2282
+ });
2283
+ }
2284
+ return UserMuteSwitch;
2285
+ }, [optimisticUserMuted]);
2286
+ return /* @__PURE__ */ jsx(ListItemLayout, {
2287
+ LeadingIcon: optimisticUserMuted ? MemberUnmuteActionIcon : MemberMuteActionIcon,
2288
+ RootElement: "button",
2289
+ rootProps,
2290
+ title: optimisticUserMuted ? t("Unmute user") : t("Mute user"),
2291
+ TrailingSlot
2292
+ });
2293
+ };
2294
+ var BlockUserAction = () => {
2295
+ const { client } = useChatContext();
2296
+ const { Modal = GlobalModal } = useComponentContext();
2297
+ const { channel } = useChannelDetailContext();
2298
+ const { addNotification } = useNotificationApi();
2299
+ const { t } = useTranslationContext();
2300
+ const { memberDisplayName, targetUserId } = useChannelMemberActionContext();
2301
+ const { userIds: blockedUserIds } = useStateStore(client.blockedUsers, blockedUsersSelector);
2302
+ const isBlocked = !!targetUserId && new Set(blockedUserIds).has(targetUserId);
2303
+ const [alertOpen, setAlertOpen] = useState(false);
2304
+ const [userBlockInProgress, setUserBlockInProgress] = useState(false);
2305
+ const closeBlockUserAlert = useCallback(() => {
2306
+ setAlertOpen(false);
2307
+ }, []);
2308
+ const openBlockUserAlert = useCallback(() => {
2309
+ setAlertOpen(true);
2310
+ }, []);
2311
+ const unblockUser = useCallback(async () => {
2312
+ if (!targetUserId) return;
2313
+ try {
2314
+ setUserBlockInProgress(true);
2315
+ await client.unBlockUser(targetUserId);
2316
+ addNotification({
2317
+ context: { channel },
2318
+ emitter: "ChannelMemberDetail",
2319
+ message: t("User unblocked"),
2320
+ severity: "success",
2321
+ type: "api:user:unblock:success"
2322
+ });
2323
+ } catch (error) {
2324
+ addNotification({
2325
+ context: { channel },
2326
+ emitter: "ChannelMemberDetail",
2327
+ error: toError(error),
2328
+ message: t("Error unblocking user"),
2329
+ severity: "error",
2330
+ type: "api:user:unblock:failed"
2331
+ });
2332
+ } finally {
2333
+ setAlertOpen(false);
2334
+ setUserBlockInProgress(false);
2335
+ }
2336
+ }, [
2337
+ addNotification,
2338
+ channel,
2339
+ client,
2340
+ t,
2341
+ targetUserId
2342
+ ]);
2343
+ const blockUser = useCallback(async () => {
2344
+ if (!targetUserId) return;
2345
+ try {
2346
+ setUserBlockInProgress(true);
2347
+ await client.blockUser(targetUserId);
2348
+ addNotification({
2349
+ context: { channel },
2350
+ emitter: "ChannelMemberDetail",
2351
+ message: t("User blocked"),
2352
+ severity: "success",
2353
+ type: "api:user:block:success"
2354
+ });
2355
+ } catch (error) {
2356
+ addNotification({
2357
+ context: { channel },
2358
+ emitter: "ChannelMemberDetail",
2359
+ error: toError(error),
2360
+ message: t("Error blocking user"),
2361
+ severity: "error",
2362
+ type: "api:user:block:failed"
2363
+ });
2364
+ } finally {
2365
+ setAlertOpen(false);
2366
+ setUserBlockInProgress(false);
2367
+ }
2368
+ }, [
2369
+ addNotification,
2370
+ channel,
2371
+ client,
2372
+ t,
2373
+ targetUserId
2374
+ ]);
2375
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ListItemLayout, {
2376
+ destructive: true,
2377
+ LeadingIcon: BlockUserActionIcon,
2378
+ RootElement: "button",
2379
+ rootProps: useMemo(() => ({
2380
+ className: channelMemberDetailActionClassName,
2381
+ disabled: userBlockInProgress,
2382
+ onClick: openBlockUserAlert
2383
+ }), [openBlockUserAlert, userBlockInProgress]),
2384
+ title: isBlocked ? t("Unblock user") : t("Block user")
2385
+ }), /* @__PURE__ */ jsx(Modal, {
2386
+ open: alertOpen,
2387
+ role: "alertdialog",
2388
+ children: /* @__PURE__ */ jsx(ChannelMemberConfirmationAlert, {
2389
+ action: "blockUser",
2390
+ cancelLabel: t("Cancel"),
2391
+ confirmLabel: isBlocked ? t("Unblock user") : t("Block user"),
2392
+ description: isBlocked ? t("{{ member }} will be able to message you again.", { member: memberDisplayName }) : t("{{ member }} won't be able to message you anymore.", { member: memberDisplayName }),
2393
+ isSubmitting: userBlockInProgress,
2394
+ onCancel: closeBlockUserAlert,
2395
+ onConfirm: isBlocked ? unblockUser : blockUser,
2396
+ testId: "channel-detail-block-member-alert",
2397
+ title: isBlocked ? t("Unblock user") : t("Block user")
2398
+ })
2399
+ })] });
2400
+ };
2401
+ var RemoveUserAction = () => {
2402
+ const { Modal = GlobalModal } = useComponentContext();
2403
+ const { channel } = useChannelDetailContext();
2404
+ const { addNotification } = useNotificationApi();
2405
+ const { t } = useTranslationContext();
2406
+ const { memberDisplayName, targetUserId } = useChannelMemberActionContext();
2407
+ const [alertOpen, setAlertOpen] = useState(false);
2408
+ const [removeMemberInProgress, setRemoveMemberInProgress] = useState(false);
2409
+ const closeRemoveUserAlert = useCallback(() => {
2410
+ setAlertOpen(false);
2411
+ }, []);
2412
+ const openRemoveUserAlert = useCallback(() => {
2413
+ setAlertOpen(true);
2414
+ }, []);
2415
+ const removeUser = useCallback(async () => {
2416
+ if (!targetUserId) return;
2417
+ try {
2418
+ setRemoveMemberInProgress(true);
2419
+ await channel.removeMembers([targetUserId]);
2420
+ addNotification({
2421
+ context: { channel },
2422
+ emitter: "ChannelMemberDetail",
2423
+ message: t("User removed"),
2424
+ severity: "success",
2425
+ type: "api:channel:remove-members:success"
2426
+ });
2427
+ setAlertOpen(false);
2428
+ } catch (error) {
2429
+ addNotification({
2430
+ context: { channel },
2431
+ emitter: "ChannelMemberDetail",
2432
+ error: toError(error),
2433
+ message: t("Error removing user"),
2434
+ severity: "error",
2435
+ type: "api:channel:remove-members:failed"
2436
+ });
2437
+ } finally {
2438
+ setRemoveMemberInProgress(false);
2439
+ }
2440
+ }, [
2441
+ addNotification,
2442
+ channel,
2443
+ t,
2444
+ targetUserId
2445
+ ]);
2446
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ListItemLayout, {
2447
+ destructive: true,
2448
+ LeadingIcon: RemoveUserActionIcon,
2449
+ RootElement: "button",
2450
+ rootProps: useMemo(() => ({
2451
+ className: channelMemberDetailActionClassName,
2452
+ disabled: removeMemberInProgress,
2453
+ onClick: openRemoveUserAlert
2454
+ }), [openRemoveUserAlert, removeMemberInProgress]),
2455
+ title: t("Remove user")
2456
+ }), /* @__PURE__ */ jsx(Modal, {
2457
+ open: alertOpen,
2458
+ role: "alertdialog",
2459
+ children: /* @__PURE__ */ jsx(ChannelMemberConfirmationAlert, {
2460
+ action: "removeUser",
2461
+ cancelLabel: t("Cancel"),
2462
+ confirmLabel: t("Remove user"),
2463
+ description: t("Remove {{ member }} from this channel?", { member: memberDisplayName }),
2464
+ isSubmitting: removeMemberInProgress,
2465
+ onCancel: closeRemoveUserAlert,
2466
+ onConfirm: removeUser,
2467
+ testId: "channel-detail-remove-member-alert",
2468
+ title: t("Remove user")
2469
+ })
2470
+ })] });
2471
+ };
2472
+ var DefaultChannelMemberActions = {
2473
+ BlockUser: BlockUserAction,
2474
+ MuteUser: UserMuteAction,
2475
+ RemoveUser: RemoveUserAction,
2476
+ SendDirectMessage: SendDirectMessageAction
2477
+ };
2478
+ var defaultChannelMemberActionSet = [
2479
+ {
2480
+ Component: DefaultChannelMemberActions.SendDirectMessage,
2481
+ type: "sendMessage"
2482
+ },
2483
+ {
2484
+ Component: DefaultChannelMemberActions.MuteUser,
2485
+ type: "muteUser"
2486
+ },
2487
+ {
2488
+ Component: DefaultChannelMemberActions.BlockUser,
2489
+ type: "blockUser"
2490
+ },
2491
+ {
2492
+ Component: DefaultChannelMemberActions.RemoveUser,
2493
+ type: "removeUser"
2494
+ }
2495
+ ];
2496
+ //#endregion
2497
+ //#region src/plugins/ChannelDetail/Views/ChannelMemberDetailView/ChannelMemberDetail.tsx
2498
+ var getPresenceStatusText = (user, t) => {
2499
+ if (user?.online) return t("Online");
2500
+ if (user?.last_active) return t("Last seen {{ timestamp }}", { timestamp: t("timestamp/ChannelMembersLastActive", { timestamp: user.last_active }) });
2501
+ return t("Offline");
2502
+ };
2503
+ var ChannelMemberDetail = ({ channelMemberActionSet = defaultChannelMemberActionSet, member, onBack }) => {
2504
+ const { t } = useTranslationContext();
2505
+ const memberDisplayName = getMemberDisplayName(member);
2506
+ const memberStatusText = getPresenceStatusText(member.user, t);
2507
+ return /* @__PURE__ */ jsx(ChannelMemberActionProvider, {
2508
+ value: useMemo(() => ({
2509
+ member,
2510
+ memberDisplayName,
2511
+ targetUserId: member.user?.id || member.user_id
2512
+ }), [member, memberDisplayName]),
2513
+ children: /* @__PURE__ */ jsx(ChannelMemberDetailContent, {
2514
+ channelMemberActionSet,
2515
+ memberDisplayName,
2516
+ memberStatusText,
2517
+ onBack
2518
+ })
2519
+ });
2520
+ };
2521
+ var ChannelMemberDetailContent = ({ channelMemberActionSet, memberDisplayName, memberStatusText, onBack }) => {
2522
+ const { close } = useModalContext();
2523
+ const { t } = useTranslationContext();
2524
+ const { Avatar = ChannelAvatar } = useComponentContext();
2525
+ const { member } = useChannelMemberActionContext();
2526
+ const filteredActions = useBaseChannelMemberActionSetFilter(channelMemberActionSet);
2527
+ return /* @__PURE__ */ jsxs("div", {
2528
+ className: "str-chat__channel-detail__channel-member-detail-view",
2529
+ children: [/* @__PURE__ */ jsx(SectionNavigatorHeader, {
2530
+ close,
2531
+ goBack: onBack,
2532
+ title: t("Member detail")
2533
+ }), /* @__PURE__ */ jsxs(Prompt.Body, {
2534
+ className: "str-chat__channel-detail__channel-member-detail-view__body",
2535
+ children: [/* @__PURE__ */ jsxs("div", {
2536
+ className: "str-chat__channel-detail__channel-member-detail-view__profile",
2537
+ children: [/* @__PURE__ */ jsx(Avatar, {
2538
+ imageUrl: member.user?.image,
2539
+ isOnline: member.user?.online,
2540
+ size: "2xl",
2541
+ userName: memberDisplayName
2542
+ }), /* @__PURE__ */ jsxs("div", {
2543
+ className: "str-chat__channel-detail__channel-member-detail-view__profile__details",
2544
+ children: [/* @__PURE__ */ jsx("div", {
2545
+ className: "str-chat__channel-detail__channel-member-detail-view__profile__details__title",
2546
+ children: memberDisplayName
2547
+ }), /* @__PURE__ */ jsx("div", {
2548
+ className: "str-chat__channel-detail__channel-member-detail-view__profile__details__connection-status",
2549
+ children: memberStatusText
2550
+ })]
2551
+ })]
2552
+ }), /* @__PURE__ */ jsx("div", {
2553
+ className: "str-chat__channel-detail__channel-member-detail-view__actions str-chat__form__switch-fieldset",
2554
+ children: filteredActions.map(({ Component, type }) => /* @__PURE__ */ jsx(Component, {}, type))
2555
+ })]
2556
+ })]
2557
+ });
2558
+ };
2559
+ //#endregion
2560
+ //#region src/plugins/ChannelDetail/Views/ChannelMembersView/ChannelMembersView.tsx
2561
+ var RESERVED_MODES = ["browse", "memberDetail"];
2562
+ var isReservedMode = (mode) => RESERVED_MODES.includes(mode);
2563
+ var AddMembersModeTitle = () => {
2564
+ const { t } = useTranslationContext();
2565
+ return /* @__PURE__ */ jsx(Fragment$1, { children: t("Add members") });
2566
+ };
2567
+ /** Built-in mode descriptors. Merged with (and overridable by) the `modeViews` prop. */
2568
+ var defaultChannelMembersModeViews = { add: {
2569
+ Body: ChannelMembersAddView,
2570
+ Title: AddMembersModeTitle
2571
+ } };
2572
+ var ChannelMembersView = ({ HeaderActions = DefaultHeaderActions, headerActionSet = defaultChannelMembersHeaderActionSet, HeaderActionsMenuTrigger, layout, modeViews: customModeViews }) => {
2573
+ const { t } = useTranslationContext();
2574
+ const { channel } = useChannelDetailContext();
2575
+ const { close } = useModalContext();
2576
+ const [mode, setMode] = useState("browse");
2577
+ const [selectedMember, setSelectedMember] = useState();
2578
+ const memberCount = useChannelMemberCount(channel);
2579
+ const modeViews = useMemo(() => ({
2580
+ ...defaultChannelMembersModeViews,
2581
+ ...customModeViews
2582
+ }), [customModeViews]);
2583
+ const activeModeDescriptor = isReservedMode(mode) ? void 0 : modeViews[mode];
2584
+ const isViewingMemberDetail = mode === "memberDetail";
2585
+ const setViewMode = useCallback((nextMode) => {
2586
+ setMode(nextMode);
2587
+ if (nextMode !== "memberDetail") setSelectedMember(void 0);
2588
+ }, []);
2589
+ useEffect(() => {
2590
+ if (!isReservedMode(mode) && !modeViews[mode]) setViewMode("browse");
2591
+ }, [
2592
+ mode,
2593
+ modeViews,
2594
+ setViewMode
2595
+ ]);
2596
+ const goBack = useCallback(() => setViewMode("browse"), [setViewMode]);
2597
+ const modeController = useMemo(() => ({
2598
+ mode,
2599
+ setMode: setViewMode
2600
+ }), [mode, setViewMode]);
2601
+ const HeaderTrailingActions = useMemo(() => function HeaderTrailingActions() {
2602
+ if (mode !== "browse") return null;
2603
+ return /* @__PURE__ */ jsx(HeaderActions, {
2604
+ headerActionSet,
2605
+ HeaderActionsMenuTrigger,
2606
+ modeController
2607
+ });
2608
+ }, [
2609
+ HeaderActions,
2610
+ HeaderActionsMenuTrigger,
2611
+ modeController,
2612
+ headerActionSet,
2613
+ mode
2614
+ ]);
2615
+ if (isViewingMemberDetail && selectedMember) return /* @__PURE__ */ jsx(ChannelMemberDetail, {
2616
+ layout,
2617
+ member: selectedMember,
2618
+ onBack: goBack
2619
+ });
2620
+ const ActiveModeTitle = activeModeDescriptor?.Title;
2621
+ const ActiveModeBody = activeModeDescriptor?.Body;
2622
+ return /* @__PURE__ */ jsxs("div", {
2623
+ className: "str-chat__channel-detail__channel-members-view",
2624
+ children: [/* @__PURE__ */ jsx(SectionNavigatorHeader, {
2625
+ close,
2626
+ description: activeModeDescriptor ? void 0 : t("Browse channel members"),
2627
+ goBack: activeModeDescriptor ? goBack : void 0,
2628
+ title: ActiveModeTitle ? /* @__PURE__ */ jsx(ActiveModeTitle, {}) : t("{{ count }} members", { count: memberCount }),
2629
+ TrailingContent: HeaderTrailingActions
2630
+ }), ActiveModeBody ? /* @__PURE__ */ jsx(ActiveModeBody, { modeController }) : /* @__PURE__ */ jsx(ChannelMembersBrowseView, { onMemberSelect: (member) => {
2631
+ setSelectedMember(member);
2632
+ setViewMode("memberDetail");
2633
+ } })]
2634
+ });
2635
+ };
2636
+ //#endregion
2637
+ //#region src/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesEmptyList.tsx
2638
+ var PinnedMessagesEmptyList = () => {
2639
+ const { t } = useTranslationContext();
2640
+ return /* @__PURE__ */ jsxs("div", {
2641
+ className: "str-chat__channel-detail__pinned-messages-view__empty-state",
2642
+ children: [/* @__PURE__ */ jsx(IconPin, { className: "str-chat__channel-detail__pinned-messages-view__empty-state__icon" }), /* @__PURE__ */ jsxs("div", {
2643
+ className: "str-chat__channel-detail__pinned-messages-view__empty-state__content",
2644
+ children: [/* @__PURE__ */ jsx("p", {
2645
+ className: "str-chat__channel-detail__pinned-messages-view__empty-state__title",
2646
+ children: t("No pinned messages")
2647
+ }), /* @__PURE__ */ jsx("p", {
2648
+ className: "str-chat__channel-detail__pinned-messages-view__empty-state__description",
2649
+ children: t("Pin a message to see it here")
2650
+ })]
2651
+ })]
2652
+ });
2653
+ };
2654
+ //#endregion
2655
+ //#region src/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesCount.ts
2656
+ var PINNED_MESSAGES_EVENTS = [
2657
+ "message.new",
2658
+ "message.updated",
2659
+ "message.deleted",
2660
+ "message.undeleted"
2661
+ ];
2662
+ var getPinnedMessagesCount = (channel) => channel.state?.pinnedMessages?.length ?? 0;
2663
+ /** Reactive count of the channel's pinned messages derived from channel state. */
2664
+ var usePinnedMessagesCount = (channel) => {
2665
+ const [pinnedMessagesCount, setPinnedMessagesCount] = useState(() => getPinnedMessagesCount(channel));
2666
+ useEffect(() => {
2667
+ const syncPinnedMessagesCount = () => setPinnedMessagesCount(getPinnedMessagesCount(channel));
2668
+ syncPinnedMessagesCount();
2669
+ const subscriptions = PINNED_MESSAGES_EVENTS.map((event) => channel.on(event, syncPinnedMessagesCount));
2670
+ return () => subscriptions.forEach((subscription) => subscription.unsubscribe());
2671
+ }, [channel]);
2672
+ return pinnedMessagesCount;
2673
+ };
2674
+ //#endregion
2675
+ //#region src/plugins/ChannelDetail/Views/PinnedMessagesView/usePinnedMessagesSearch.ts
2676
+ var PINNED_MESSAGES_SEARCH_PAGE_SIZE = 30;
2677
+ var PINNED_MESSAGES_SEARCH_DEBOUNCE_MS = 300;
2678
+ var pinnedMessagesSearchSourceItemsStateSelector = (state) => ({ messages: state.items });
2679
+ var usePinnedMessagesSearch = ({ searchSource } = {}) => {
2680
+ const { client } = useChatContext();
2681
+ const { channel } = useChannelDetailContext();
2682
+ const pinnedMessagesSearchSource = useMemo(() => {
2683
+ const source = searchSource ?? new MessageSearchSource(client, {
2684
+ allowEmptySearchString: true,
2685
+ debounceMs: PINNED_MESSAGES_SEARCH_DEBOUNCE_MS,
2686
+ pageSize: PINNED_MESSAGES_SEARCH_PAGE_SIZE,
2687
+ resetOnNewSearchQuery: false
2688
+ }, { messageSearch: { initialFilterConfig: { text: {
2689
+ enabled: true,
2690
+ generate: ({ searchQuery }) => searchQuery ? { text: { $autocomplete: searchQuery } } : null
2691
+ } } } });
2692
+ if (!searchSource) {
2693
+ source.messageSearchChannelFilters = { cid: channel.cid };
2694
+ source.messageSearchFilters = { pinned: true };
2695
+ }
2696
+ return source;
2697
+ }, [
2698
+ channel.cid,
2699
+ client,
2700
+ searchSource
2701
+ ]);
2702
+ const { messages } = useStateStore(pinnedMessagesSearchSource.state, pinnedMessagesSearchSourceItemsStateSelector);
2703
+ useEffect(() => {
2704
+ pinnedMessagesSearchSource.activate();
2705
+ return () => {
2706
+ pinnedMessagesSearchSource.cancelScheduledQuery();
2707
+ };
2708
+ }, [pinnedMessagesSearchSource]);
2709
+ const hasPinnedMessages = usePinnedMessagesCount(channel) > 0;
2710
+ useEffect(() => {
2711
+ if (!hasPinnedMessages) return;
2712
+ pinnedMessagesSearchSource.search("");
2713
+ }, [hasPinnedMessages, pinnedMessagesSearchSource]);
2714
+ const handleSearchChange = useCallback((query) => {
2715
+ const trimmedQuery = query.trim();
2716
+ if (!trimmedQuery) {
2717
+ pinnedMessagesSearchSource.cancelScheduledQuery();
2718
+ pinnedMessagesSearchSource.resetState();
2719
+ pinnedMessagesSearchSource.activate();
2720
+ pinnedMessagesSearchSource.search("");
2721
+ return;
2722
+ }
2723
+ pinnedMessagesSearchSource.search(trimmedQuery);
2724
+ }, [pinnedMessagesSearchSource]);
2725
+ return {
2726
+ displayedMessages: messages ?? [],
2727
+ handleSearchChange,
2728
+ hasPinnedMessages,
2729
+ hasSearchResultsLoaded: Array.isArray(messages),
2730
+ pinnedMessagesSearchSource
2731
+ };
2732
+ };
2733
+ //#endregion
2734
+ //#region src/plugins/ChannelDetail/Views/PinnedMessagesView/PinnedMessagesView.tsx
2735
+ var computeItemKey = (_, message) => message.id;
2736
+ var normalizeTimestamp = (timestamp) => {
2737
+ if (!timestamp) return void 0;
2738
+ return isDate(timestamp) ? timestamp.toISOString() : timestamp;
2739
+ };
2740
+ var getPinnedMessagePreview = (message, t) => {
2741
+ const text = message.text?.trim();
2742
+ if (text) return text;
2743
+ const attachment = message.attachments?.[0];
2744
+ return attachment?.title || attachment?.text || attachment?.fallback || attachment?.type || t("Pinned message");
2745
+ };
2746
+ var PinnedMessageDate = ({ message }) => {
2747
+ const { t, tDateTimeParser } = useTranslationContext("PinnedMessageDate");
2748
+ const normalizedTimestamp = normalizeTimestamp(message.created_at);
2749
+ const when = useMemo(() => getDateString({
2750
+ messageCreatedAt: normalizedTimestamp,
2751
+ t,
2752
+ tDateTimeParser,
2753
+ timestampTranslationKey: "timestamp/ChannelDetailPinnedMessageTimestamp"
2754
+ }), [
2755
+ normalizedTimestamp,
2756
+ t,
2757
+ tDateTimeParser
2758
+ ]);
2759
+ if (!when) return null;
2760
+ return /* @__PURE__ */ jsx("time", {
2761
+ className: "str-chat__channel-detail__pinned-messages-view__list-item__date",
2762
+ dateTime: normalizedTimestamp,
2763
+ children: when
2764
+ });
2765
+ };
2766
+ var PinnedMessagesViewItem = ({ message, onSelect }) => {
2767
+ const { t } = useTranslationContext();
2768
+ const displayName = getUserDisplayName(message.user ?? void 0);
2769
+ const LeadingSlot = useMemo(() => function MessageAuthorAvatar() {
2770
+ return /* @__PURE__ */ jsx(Avatar, {
2771
+ imageUrl: message.user?.image,
2772
+ size: "md",
2773
+ userName: displayName
2774
+ });
2775
+ }, [displayName, message.user?.image]);
2776
+ const TrailingSlot = useMemo(() => function MessageDate() {
2777
+ return /* @__PURE__ */ jsx(PinnedMessageDate, { message });
2778
+ }, [message]);
2779
+ return /* @__PURE__ */ jsx(ListItemLayout, {
2780
+ LeadingSlot,
2781
+ RootElement: "button",
2782
+ rootProps: useMemo(() => ({
2783
+ className: "str-chat__channel-detail__pinned-messages-view__list-item",
2784
+ onClick: () => onSelect(message)
2785
+ }), [message, onSelect]),
2786
+ subtitle: getPinnedMessagePreview(message, t),
2787
+ subtitleClassName: "str-chat__channel-detail__pinned-messages-view__list-item__message-preview",
2788
+ title: displayName,
2789
+ TrailingSlot
2790
+ });
2791
+ };
2792
+ var PinnedMessagesView = ({ searchSource }) => {
2793
+ const { setActiveChannel } = useChatContext();
2794
+ const { t } = useTranslationContext();
2795
+ const { close } = useModalContext();
2796
+ const { jumpToMessage } = useChannelActionContext();
2797
+ const { channel } = useChannelDetailContext();
2798
+ const { displayedMessages, handleSearchChange, hasPinnedMessages, hasSearchResultsLoaded, pinnedMessagesSearchSource } = usePinnedMessagesSearch({ searchSource });
2799
+ const handleSelectMessage = useCallback((message) => {
2800
+ setActiveChannel(channel);
2801
+ jumpToMessage(message.id);
2802
+ close();
2803
+ }, [
2804
+ channel,
2805
+ close,
2806
+ jumpToMessage,
2807
+ setActiveChannel
2808
+ ]);
2809
+ const renderItem = useCallback((_, message) => /* @__PURE__ */ jsx(PinnedMessagesViewItem, {
2810
+ message,
2811
+ onSelect: handleSelectMessage
2812
+ }), [handleSelectMessage]);
2813
+ const EmptyPlaceholder = useMemo(() => function PinnedMessagesEmptyPlaceholder() {
2814
+ if (!hasPinnedMessages) return /* @__PURE__ */ jsx(PinnedMessagesEmptyList, {});
2815
+ if (hasSearchResultsLoaded) return /* @__PURE__ */ jsx(ChannelDetailEmptyList, { children: t("No messages found") });
2816
+ return null;
2817
+ }, [
2818
+ hasPinnedMessages,
2819
+ hasSearchResultsLoaded,
2820
+ t
2821
+ ]);
2822
+ const Footer = useMemo(() => function PinnedMessagesListFooter() {
2823
+ return /* @__PURE__ */ jsx(ChannelDetailListLoadingIndicator, { searchSource: pinnedMessagesSearchSource });
2824
+ }, [pinnedMessagesSearchSource]);
2825
+ return /* @__PURE__ */ jsxs("div", {
2826
+ className: "str-chat__channel-detail__pinned-messages-view",
2827
+ children: [/* @__PURE__ */ jsx(SectionNavigatorHeader, {
2828
+ close,
2829
+ description: t("Browse pinned messages"),
2830
+ title: t("Pinned messages")
2831
+ }), /* @__PURE__ */ jsxs(Prompt.Body, {
2832
+ className: "str-chat__channel-detail__pinned-messages-view__body",
2833
+ children: [hasPinnedMessages && /* @__PURE__ */ jsx(ChannelDetailSearchInput, { onSearchChange: handleSearchChange }), /* @__PURE__ */ jsx(VirtualizedList, {
2834
+ className: "str-chat__channel-detail__pinned-messages-view__list",
2835
+ computeItemKey,
2836
+ data: displayedMessages,
2837
+ EmptyPlaceholder,
2838
+ Footer,
2839
+ itemContent: renderItem,
2840
+ loadNext: hasPinnedMessages ? pinnedMessagesSearchSource.search : void 0
2841
+ })]
2842
+ })]
2843
+ });
2844
+ };
2845
+ //#endregion
2846
+ //#region src/plugins/ChannelDetail/ChannelDetail.tsx
2847
+ var ChannelManagementNavButtonIcon = () => /* @__PURE__ */ jsx(IconInfo, { className: "str-chat__channel-detail__action-icon" });
2848
+ var ChannelMembersNavButtonIcon = () => /* @__PURE__ */ jsx(IconUser, { className: "str-chat__channel-detail__action-icon" });
2849
+ var PinnedMessagesNavButtonIcon = () => /* @__PURE__ */ jsx(IconPin, { className: "str-chat__channel-detail__action-icon" });
2850
+ var ChannelMediaNavButtonIcon = () => /* @__PURE__ */ jsx(IconImage, { className: "str-chat__channel-detail__action-icon" });
2851
+ var ChannelFilesNavButtonIcon = () => /* @__PURE__ */ jsx(IconFolder, { className: "str-chat__channel-detail__action-icon" });
2852
+ var ChannelManagementNavButton = (props) => /* @__PURE__ */ jsx(ChannelDetailNavButton, {
2853
+ ...props,
2854
+ LeadingIcon: ChannelManagementNavButtonIcon,
2855
+ title: "Channel info"
2856
+ });
2857
+ var ChannelMembersNavButton = (props) => /* @__PURE__ */ jsx(ChannelDetailNavButton, {
2858
+ ...props,
2859
+ LeadingIcon: ChannelMembersNavButtonIcon,
2860
+ title: "Members"
2861
+ });
2862
+ var PinnedMessagesNavButton = (props) => /* @__PURE__ */ jsx(ChannelDetailNavButton, {
2863
+ ...props,
2864
+ LeadingIcon: PinnedMessagesNavButtonIcon,
2865
+ title: "Pinned messages"
2866
+ });
2867
+ var ChannelMediaNavButton = (props) => /* @__PURE__ */ jsx(ChannelDetailNavButton, {
2868
+ ...props,
2869
+ LeadingIcon: ChannelMediaNavButtonIcon,
2870
+ title: "Photos & videos"
2871
+ });
2872
+ var ChannelFilesNavButton = (props) => /* @__PURE__ */ jsx(ChannelDetailNavButton, {
2873
+ ...props,
2874
+ LeadingIcon: ChannelFilesNavButtonIcon,
2875
+ title: "Files"
2876
+ });
2877
+ var defaultChannelDetailSections = [
2878
+ {
2879
+ id: "channel-info",
2880
+ NavButton: ChannelManagementNavButton,
2881
+ SectionContent: ChannelManagementView
2882
+ },
2883
+ {
2884
+ id: "channel-members",
2885
+ NavButton: ChannelMembersNavButton,
2886
+ SectionContent: ChannelMembersView
2887
+ },
2888
+ {
2889
+ id: "pinned-messages",
2890
+ NavButton: PinnedMessagesNavButton,
2891
+ SectionContent: PinnedMessagesView
2892
+ },
2893
+ {
2894
+ id: "channel-media",
2895
+ NavButton: ChannelMediaNavButton,
2896
+ SectionContent: ChannelMediaView
2897
+ },
2898
+ {
2899
+ id: "channel-files",
2900
+ NavButton: ChannelFilesNavButton,
2901
+ SectionContent: ChannelFilesView
2902
+ }
2903
+ ];
2904
+ var ChannelDetail = ({ channel, className, defaultLayout = SECTION_NAVIGATOR_LAYOUT.tabs, sections = defaultChannelDetailSections, ...props }) => {
2905
+ const [layout, setLayout] = useState(defaultLayout);
2906
+ return /* @__PURE__ */ jsx(ChannelDetailProvider, {
2907
+ channel,
2908
+ children: /* @__PURE__ */ jsx(Prompt.Root, {
2909
+ className: clsx("str-chat__channel-detail", { "str-chat__channel-detail--inline": layout === SECTION_NAVIGATOR_LAYOUT.inline }, className),
2910
+ children: /* @__PURE__ */ jsx(SectionNavigator, {
2911
+ ...props,
2912
+ defaultLayout,
2913
+ onLayoutChange: setLayout,
2914
+ sections
2915
+ })
2916
+ })
2917
+ });
2918
+ };
2919
+ //#endregion
2920
+ //#region src/plugins/ChannelDetail/AvatarWithChannelDetail.tsx
2921
+ var avatarWithChannelDetailDialogRootProps = { className: "str-chat__channel-detail-modal" };
2922
+ var AvatarWithChannelDetail = ({ Avatar, ChannelDetail: ChannelDetail$1 = ChannelDetail, className, ...avatarProps }) => {
2923
+ const { t } = useTranslationContext();
2924
+ const { channel } = useChannelStateContext();
2925
+ const { Avatar: ContextAvatar, Modal = GlobalModal } = useComponentContext();
2926
+ const [isModalOpen, setIsModalOpen] = useState(false);
2927
+ const openModal = useCallback(() => setIsModalOpen(true), []);
2928
+ const closeModal = useCallback(() => setIsModalOpen(false), []);
2929
+ const AvatarComponent = Avatar ?? (ContextAvatar === AvatarWithChannelDetail ? void 0 : ContextAvatar) ?? ChannelAvatar;
2930
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("button", {
2931
+ "aria-label": t("aria/Open channel details"),
2932
+ className: "str-chat__avatar-with-channel-detail-button",
2933
+ onClick: openModal,
2934
+ type: "button",
2935
+ children: /* @__PURE__ */ jsx(AvatarComponent, {
2936
+ ...avatarProps,
2937
+ className: clsx("str-chat__avatar-with-channel-detail-button__avatar", className)
2938
+ })
2939
+ }), /* @__PURE__ */ jsx(Modal, {
2940
+ "aria-label": t("aria/Channel details"),
2941
+ dialogRootProps: avatarWithChannelDetailDialogRootProps,
2942
+ onClose: closeModal,
2943
+ open: isModalOpen,
2944
+ children: /* @__PURE__ */ jsx(ChannelDetail$1, { channel })
2945
+ })] });
2946
+ };
2947
+ //#endregion
2948
+ export { AvatarWithChannelDetail, ChannelDetail, ChannelDetailEmptyList, ChannelDetailListLoadingIndicator, ChannelDetailNavButton, ChannelDetailProvider, ChannelDetailSearchInput, ChannelFilesEmptyList, ChannelFilesNavButton, ChannelFilesView, ChannelManagementEditBody, ChannelManagementInfoBody, ChannelManagementNavButton, ChannelManagementView, ChannelMediaEmptyList, ChannelMediaNavButton, ChannelMediaView, ChannelMemberActionProvider, ChannelMemberDetail, ChannelMembersAddView, ChannelMembersBrowseView, ChannelMembersNavButton, ChannelMembersView, DefaultChannelManagementActions, DefaultChannelMemberActions, DefaultChannelMembersHeaderActions, DefaultHeaderActions, DefaultHeaderActionsMenuTrigger, FILE_ATTACHMENT_TYPES, MEDIA_ATTACHMENT_TYPES, PinnedMessagesEmptyList, PinnedMessagesNavButton, PinnedMessagesView, SECTION_NAVIGATOR_LAYOUT, SectionNavigator, SectionNavigatorHeader, defaultChannelDetailSections, defaultChannelManagementActionSet, defaultChannelMemberActionSet, defaultChannelMembersHeaderActionSet, defaultChannelMembersModeViews, toChannelFileSections, toChannelMediaItems, useBaseChannelManagementActionSetFilter, useBaseChannelMemberActionSetFilter, useBaseChannelMembersHeaderActionSetFilter, useChannelDetailContext, useChannelFilesSearch, useChannelMediaSearch, useChannelMemberActionContext, useChannelMembersSearch, usePinnedMessagesSearch, useSectionNavigatorContext };
2949
+
2950
+ //# sourceMappingURL=channel-detail.mjs.map