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