stream-chat-react 14.0.0-beta.7 → 14.0.0-beta.8

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 (42) hide show
  1. package/dist/cjs/{WithAudioPlayback.83ba0e35.js → WithAudioPlayback.58b0b39b.js} +4 -2
  2. package/dist/cjs/WithAudioPlayback.58b0b39b.js.map +1 -0
  3. package/dist/cjs/emojis.js +1 -1
  4. package/dist/cjs/index.js +1732 -1588
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/css/index.css +1346 -1115
  7. package/dist/css/index.css.map +1 -1
  8. package/dist/es/{WithAudioPlayback.21b7f35a.mjs → WithAudioPlayback.2ffdc4c5.mjs} +169 -167
  9. package/dist/es/WithAudioPlayback.2ffdc4c5.mjs.map +1 -0
  10. package/dist/es/emojis.mjs +1 -1
  11. package/dist/es/index.mjs +1783 -1639
  12. package/dist/es/index.mjs.map +1 -1
  13. package/dist/types/components/ChannelListItem/ChannelListItemActionButtons.defaults.d.ts.map +1 -1
  14. package/dist/types/components/ChannelListItem/utils.d.ts.map +1 -1
  15. package/dist/types/components/Chat/Chat.d.ts.map +1 -1
  16. package/dist/types/components/Dialog/components/ContextMenu.d.ts +3 -1
  17. package/dist/types/components/Dialog/components/ContextMenu.d.ts.map +1 -1
  18. package/dist/types/components/Dialog/service/DialogPortal.d.ts.map +1 -1
  19. package/dist/types/components/Form/NumericInput.d.ts.map +1 -1
  20. package/dist/types/components/Form/TextInput.d.ts +15 -8
  21. package/dist/types/components/Form/TextInput.d.ts.map +1 -1
  22. package/dist/types/components/Form/index.d.ts +1 -0
  23. package/dist/types/components/Form/index.d.ts.map +1 -1
  24. package/dist/types/components/Message/MessageUI.d.ts.map +1 -1
  25. package/dist/types/components/Message/utils.d.ts +1 -0
  26. package/dist/types/components/Message/utils.d.ts.map +1 -1
  27. package/dist/types/components/MessageActions/MessageActions.d.ts.map +1 -1
  28. package/dist/types/components/MessageActions/MessageActions.defaults.d.ts.map +1 -1
  29. package/dist/types/components/MessageActions/hooks/useBaseMessageActionSetFilter.d.ts +1 -1
  30. package/dist/types/components/MessageActions/hooks/useBaseMessageActionSetFilter.d.ts.map +1 -1
  31. package/dist/types/components/MessageList/hooks/MessageList/useFloatingDateSeparatorMessageList.d.ts +2 -2
  32. package/dist/types/components/MessageList/hooks/MessageList/useFloatingDateSeparatorMessageList.d.ts.map +1 -1
  33. package/dist/types/components/MessageList/hooks/VirtualizedMessageList/useFloatingDateSeparator.d.ts.map +1 -1
  34. package/dist/types/components/Poll/PollCreationDialog/OptionFieldSet.d.ts.map +1 -1
  35. package/dist/types/components/Reactions/ReactionSelector.d.ts +2 -0
  36. package/dist/types/components/Reactions/ReactionSelector.d.ts.map +1 -1
  37. package/dist/types/components/SummarizedMessagePreview/hooks/useLatestMessagePreview.d.ts.map +1 -1
  38. package/dist/types/components/Tooltip/Tooltip.d.ts +4 -2
  39. package/dist/types/components/Tooltip/Tooltip.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/dist/cjs/WithAudioPlayback.83ba0e35.js.map +0 -1
  42. package/dist/es/WithAudioPlayback.21b7f35a.mjs.map +0 -1
package/dist/cjs/index.js CHANGED
@@ -5,7 +5,7 @@ const clsx = require("clsx");
5
5
  const nanoid = require("nanoid");
6
6
  const React = require("react");
7
7
  const audioProcessing = require("./audioProcessing.56e5db9d.js");
8
- const WithAudioPlayback = require("./WithAudioPlayback.83ba0e35.js");
8
+ const WithAudioPlayback = require("./WithAudioPlayback.58b0b39b.js");
9
9
  const streamChat = require("stream-chat");
10
10
  const throttle = require("lodash.throttle");
11
11
  const linkify = require("linkifyjs");
@@ -308,6 +308,10 @@ const DialogPortalDestination = () => {
308
308
  if (!destinationRoot || !dialogManager) return;
309
309
  const handleDocumentClick = (event) => {
310
310
  const target = event.target;
311
+ const clickedOverlay = target.closest?.(
312
+ "[data-str-chat__portal-id]"
313
+ );
314
+ if (clickedOverlay && clickedOverlay !== destinationRoot) return;
311
315
  if (target !== destinationRoot && destinationRoot.contains(target)) return;
312
316
  setTimeout(() => {
313
317
  Object.values(dialogManager.state.getLatestValue().dialogsById).forEach(
@@ -9859,6 +9863,7 @@ const ContextMenuContext = React.createContext(
9859
9863
  );
9860
9864
  const useContextMenuContext = () => React.useContext(ContextMenuContext);
9861
9865
  function ContextMenuContent({
9866
+ anchorReferenceElement,
9862
9867
  backLabel = "Back",
9863
9868
  children,
9864
9869
  className,
@@ -9922,31 +9927,37 @@ function ContextMenuContent({
9922
9927
  if (!transitionDirection) return;
9923
9928
  setMenuBodyAnimationKey((value) => value + 1);
9924
9929
  }, [transitionDirection, menuStack.length]);
9925
- return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuContext.Provider, { value: { closeMenu, openSubmenu, returnToParentMenu }, children: /* @__PURE__ */ jsxRuntime.jsxs(
9926
- ContextMenuRoot,
9930
+ return /* @__PURE__ */ jsxRuntime.jsx(
9931
+ ContextMenuContext.Provider,
9927
9932
  {
9928
- className: clsx(className, activeMenu.menuClassName),
9929
- "data-str-chat-enable-animations": enableAnimations,
9930
- ...props,
9931
- children: [
9932
- activeMenu.Header ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9933
- /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
9934
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: backLabel })
9935
- ] }) }) : null,
9936
- /* @__PURE__ */ jsxRuntime.jsx(
9937
- ContextMenuBody,
9938
- {
9939
- className: clsx({
9940
- "str-chat__context-menu__body--submenu-backward": transitionDirection === "backward",
9941
- "str-chat__context-menu__body--submenu-forward": transitionDirection === "forward"
9942
- }),
9943
- children: activeMenu.Submenu ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsxRuntime.jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsxRuntime.jsx(Item2, {}, `context-menu-item-${index}`)) })
9944
- },
9945
- `context-menu-body-${menuStack.length}-${menuBodyAnimationKey}`
9946
- )
9947
- ]
9933
+ value: { anchorReferenceElement, closeMenu, openSubmenu, returnToParentMenu },
9934
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
9935
+ ContextMenuRoot,
9936
+ {
9937
+ className: clsx(className, activeMenu.menuClassName),
9938
+ "data-str-chat-enable-animations": enableAnimations,
9939
+ ...props,
9940
+ children: [
9941
+ activeMenu.Header ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Header, {}) : menuStack.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
9942
+ /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
9943
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: backLabel })
9944
+ ] }) }) : null,
9945
+ /* @__PURE__ */ jsxRuntime.jsx(
9946
+ ContextMenuBody,
9947
+ {
9948
+ className: clsx({
9949
+ "str-chat__context-menu__body--submenu-backward": transitionDirection === "backward",
9950
+ "str-chat__context-menu__body--submenu-forward": transitionDirection === "forward"
9951
+ }),
9952
+ children: activeMenu.Submenu ? /* @__PURE__ */ jsxRuntime.jsx(activeMenu.Submenu, {}) : /* @__PURE__ */ jsxRuntime.jsx(ActiveMenuItemsWrapper, { children: typeof children !== "undefined" ? children : activeMenu.items?.map((Item2, index) => /* @__PURE__ */ jsxRuntime.jsx(Item2, {}, `context-menu-item-${index}`)) })
9953
+ },
9954
+ `context-menu-body-${menuStack.length}-${menuBodyAnimationKey}`
9955
+ )
9956
+ ]
9957
+ }
9958
+ )
9948
9959
  }
9949
- ) });
9960
+ );
9950
9961
  }
9951
9962
  const ContextMenu = (props) => {
9952
9963
  const { ContextMenuContent: ContextMenuContentComponent = ContextMenuContent } = WithAudioPlayback.useComponentContext();
@@ -10019,6 +10030,7 @@ const ContextMenu = (props) => {
10019
10030
  const content = /* @__PURE__ */ React.createElement(
10020
10031
  ContextMenuContentComponent,
10021
10032
  {
10033
+ anchorReferenceElement: isAnchored ? referenceElement : void 0,
10022
10034
  ...menuProps,
10023
10035
  key: `context-menu-content-${contentResetToken}`,
10024
10036
  onMenuLevelChange: handleMenuLevelChange,
@@ -10360,6 +10372,7 @@ const MessageBlocked = () => {
10360
10372
  const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10361
10373
  const { initialMessage: isInitialMessage, message } = useMessageContext();
10362
10374
  const { channelConfig } = WithAudioPlayback.useChannelStateContext();
10375
+ const messageIsDeleted = WithAudioPlayback.isMessageDeleted(message);
10363
10376
  const {
10364
10377
  canBlockUser,
10365
10378
  canDelete,
@@ -10387,9 +10400,9 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10387
10400
  if (WithAudioPlayback.ACTIONS_NOT_WORKING_IN_THREAD.includes(type) && isMessageThreadReply)
10388
10401
  return false;
10389
10402
  if (message.error) {
10390
- return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && (isBounced && canEdit || hasNetworkSendFailure) || type === "delete" && (isBounced && canDelete || hasNetworkSendFailure);
10403
+ return type === "resendMessage" && canSendMessage && (allowRetry || isBounced) || type === "edit" && (isBounced && canEdit || hasNetworkSendFailure) || type === "delete" && !messageIsDeleted && (isBounced && canDelete || hasNetworkSendFailure);
10391
10404
  }
10392
- if (type === "resendMessage" || type === "blockUser" && !canBlockUser || type === "copyMessageText" && !message.text || type === "delete" && !canDelete || type === "edit" && !canEdit || type === "flag" && !canFlag || type === "markUnread" && !canMarkUnread || type === "mute" && !canMute || type === "quote" && !canQuote || type === "react" && !canReact || type === "reply" && !canReply || type === "remindMe" && !channelConfig?.["user_message_reminders"] || type === "saveForLater" && !channelConfig?.["user_message_reminders"])
10405
+ if (type === "resendMessage" || type === "blockUser" && !canBlockUser || type === "copyMessageText" && !message.text || type === "delete" && (!canDelete || messageIsDeleted) || type === "edit" && !canEdit || type === "flag" && !canFlag || type === "markUnread" && !canMarkUnread || type === "mute" && !canMute || type === "quote" && !canQuote || type === "react" && !canReact || type === "reply" && !canReply || type === "remindMe" && !channelConfig?.["user_message_reminders"] || type === "saveForLater" && !channelConfig?.["user_message_reminders"])
10393
10406
  return false;
10394
10407
  return true;
10395
10408
  });
@@ -10408,6 +10421,7 @@ const useBaseMessageActionSetFilter = (messageActionSet, disable = false) => {
10408
10421
  channelConfig,
10409
10422
  isBounced,
10410
10423
  isInitialMessage,
10424
+ messageIsDeleted,
10411
10425
  isMessageThreadReply,
10412
10426
  message.error,
10413
10427
  message.status,
@@ -10539,9 +10553,10 @@ const PinIndicator = ({ message }) => {
10539
10553
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
10540
10554
  ] }) });
10541
10555
  };
10542
- const Tooltip = ({ children, ...rest }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__tooltip", ...rest, children });
10556
+ const Tooltip = ({ children, className, ...rest }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("str-chat__tooltip", className), ...rest, children });
10543
10557
  const PopperTooltip = ({
10544
10558
  children,
10559
+ className,
10545
10560
  offset = [0, 10],
10546
10561
  placement = "top",
10547
10562
  referenceElement,
@@ -10568,7 +10583,7 @@ const PopperTooltip = ({
10568
10583
  return /* @__PURE__ */ jsxRuntime.jsx(
10569
10584
  "div",
10570
10585
  {
10571
- className: "str-chat__tooltip",
10586
+ className: clsx("str-chat__tooltip", className),
10572
10587
  "data-placement": resolvedPlacement,
10573
10588
  ref: setPopperElement,
10574
10589
  style: { left: x ?? 0, position: strategy, top: y ?? 0 },
@@ -14181,8 +14196,9 @@ const NumericInput = React.forwardRef(
14181
14196
  ),
14182
14197
  disabled: disabled || atMin,
14183
14198
  onClick: handleDecrement,
14199
+ size: "xs",
14184
14200
  variant: "secondary",
14185
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-numeric-input__stepper-icon", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMinus, {}) })
14201
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMinus, { className: "str-chat__form-numeric-input__stepper-icon" })
14186
14202
  }
14187
14203
  ),
14188
14204
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -14211,7 +14227,7 @@ const NumericInput = React.forwardRef(
14211
14227
  ),
14212
14228
  disabled: disabled || atMax,
14213
14229
  onClick: handleIncrement,
14214
- size: "sm",
14230
+ size: "xs",
14215
14231
  variant: "secondary",
14216
14232
  children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlusSmall, { className: "str-chat__form-numeric-input__stepper-icon" })
14217
14233
  }
@@ -14222,12 +14238,72 @@ const NumericInput = React.forwardRef(
14222
14238
  );
14223
14239
  }
14224
14240
  );
14241
+ const TextInputIconMessageLine = ({ icon, text: text2 }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14242
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__message-icon", children: icon }),
14243
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__form-text-input__message-text", children: text2 })
14244
+ ] });
14245
+ const TextInputFieldMessage = (props) => {
14246
+ if (props.kind === "neutral") {
14247
+ return /* @__PURE__ */ jsxRuntime.jsx(
14248
+ "div",
14249
+ {
14250
+ className: clsx(
14251
+ "str-chat__form-text-input__message",
14252
+ props.insidePlacement && "str-chat__form-text-input__message--field-message-inside"
14253
+ ),
14254
+ id: props.id,
14255
+ children: props.text
14256
+ }
14257
+ );
14258
+ } else if (props.kind === "success") {
14259
+ return /* @__PURE__ */ jsxRuntime.jsx(
14260
+ "div",
14261
+ {
14262
+ className: clsx(
14263
+ "str-chat__form-text-input__message",
14264
+ "str-chat__form-text-input__message--success",
14265
+ props.insidePlacement && "str-chat__form-text-input__message--field-message-inside"
14266
+ ),
14267
+ id: props.id,
14268
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14269
+ TextInputIconMessageLine,
14270
+ {
14271
+ icon: props.successMessageIcon ?? /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconCheckmark, {}),
14272
+ text: props.text
14273
+ }
14274
+ )
14275
+ }
14276
+ );
14277
+ } else if (props.kind === "error") {
14278
+ return /* @__PURE__ */ jsxRuntime.jsx(
14279
+ "div",
14280
+ {
14281
+ className: clsx(
14282
+ "str-chat__form-text-input__message",
14283
+ "str-chat__form-field-error",
14284
+ props.insidePlacement && "str-chat__form-text-input__message--field-message-inside"
14285
+ ),
14286
+ id: props.id,
14287
+ role: "alert",
14288
+ children: /* @__PURE__ */ jsxRuntime.jsx(
14289
+ TextInputIconMessageLine,
14290
+ {
14291
+ icon: props.errorMessageIcon ?? /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationMark, {}),
14292
+ text: props.text
14293
+ }
14294
+ )
14295
+ }
14296
+ );
14297
+ }
14298
+ return null;
14299
+ };
14225
14300
  const TextInput = React.forwardRef(function TextInput2({
14226
14301
  className,
14227
14302
  disabled,
14228
14303
  error = false,
14229
14304
  errorMessage,
14230
14305
  errorMessageIcon,
14306
+ fieldMessagePlacement = "outside",
14231
14307
  id: idProp,
14232
14308
  label,
14233
14309
  leading,
@@ -14239,71 +14315,86 @@ const TextInput = React.forwardRef(function TextInput2({
14239
14315
  variant = "outline",
14240
14316
  ...inputProps
14241
14317
  }, ref) {
14242
- const generatedId = useStableId();
14243
- const id = idProp ?? generatedId;
14244
- const displayError = error && (errorMessage != null || message != null);
14245
- const displaySuccess = successMessage != null;
14246
- const displayNeutralMessage = message != null && !error;
14247
- const displayMessage = displayError || displaySuccess || displayNeutralMessage;
14248
- const messageId = displayMessage ? `${id}-message` : void 0;
14249
- const messageContent = displayError ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14250
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__message-icon", children: errorMessageIcon ?? /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconExclamationMark, {}) }),
14251
- errorMessage ?? message
14252
- ] }) : displaySuccess ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
14253
- /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__message-icon", children: successMessageIcon ?? /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconCheckmark, {}) }),
14254
- successMessage
14255
- ] }) : displayNeutralMessage ? message : null;
14318
+ const autoId = useStableId();
14319
+ const id = idProp ?? autoId;
14320
+ const hasError = error && (errorMessage != null || message != null);
14321
+ const showSuccess = !hasError && successMessage != null;
14322
+ const showNeutral = !hasError && !showSuccess && message != null;
14323
+ const hasFeedback = hasError || showSuccess || showNeutral;
14324
+ const messageInside = fieldMessagePlacement === "inside" && hasFeedback;
14325
+ const messageId = hasError ? `${id}-field-error` : showSuccess || showNeutral ? `${id}-message` : void 0;
14326
+ const describedBy = messageId;
14327
+ const fieldMessage = hasError ? /* @__PURE__ */ jsxRuntime.jsx(
14328
+ TextInputFieldMessage,
14329
+ {
14330
+ errorMessageIcon,
14331
+ id: messageId,
14332
+ insidePlacement: messageInside,
14333
+ kind: "error",
14334
+ text: errorMessage ?? message
14335
+ }
14336
+ ) : showSuccess ? /* @__PURE__ */ jsxRuntime.jsx(
14337
+ TextInputFieldMessage,
14338
+ {
14339
+ id: messageId,
14340
+ insidePlacement: messageInside,
14341
+ kind: "success",
14342
+ successMessageIcon,
14343
+ text: successMessage
14344
+ }
14345
+ ) : showNeutral ? /* @__PURE__ */ jsxRuntime.jsx(
14346
+ TextInputFieldMessage,
14347
+ {
14348
+ id: messageId,
14349
+ insidePlacement: messageInside,
14350
+ kind: "neutral",
14351
+ text: message
14352
+ }
14353
+ ) : null;
14256
14354
  return /* @__PURE__ */ jsxRuntime.jsxs(
14257
14355
  "div",
14258
14356
  {
14259
14357
  className: clsx(
14260
14358
  "str-chat__form-text-input",
14261
14359
  error && "str-chat__form-text-input--error",
14262
- displaySuccess && "str-chat__form-text-input--success",
14360
+ showSuccess && "str-chat__form-text-input--success",
14263
14361
  disabled && "str-chat__form-text-input--disabled",
14362
+ messageInside && "str-chat__form-text-input--field-message-inside",
14264
14363
  className
14265
14364
  ),
14266
14365
  children: [
14267
- !!label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "str-chat__form-text-input__label", htmlFor: id, children: label }),
14366
+ label ? /* @__PURE__ */ jsxRuntime.jsx("label", { className: "str-chat__form-text-input__label", htmlFor: id, children: label }) : null,
14268
14367
  /* @__PURE__ */ jsxRuntime.jsxs(
14269
14368
  "div",
14270
14369
  {
14271
14370
  className: clsx(
14272
14371
  "str-chat__form-text-input__wrapper",
14273
- `str-chat__form-text-input__wrapper--${variant}`
14372
+ `str-chat__form-text-input__wrapper--${variant}`,
14373
+ messageInside && "str-chat__form-text-input__wrapper--field-message-inside"
14274
14374
  ),
14275
14375
  children: [
14276
- !!leading && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__leading", children: leading }),
14277
- /* @__PURE__ */ jsxRuntime.jsx(
14278
- "input",
14279
- {
14280
- "aria-describedby": messageId,
14281
- "aria-invalid": error,
14282
- className: "str-chat__form-text-input__input",
14283
- disabled,
14284
- id,
14285
- ref,
14286
- ...inputProps
14287
- }
14288
- ),
14289
- trailingText != null && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__suffix", children: trailingText }),
14290
- !!trailing && /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__trailing", children: trailing })
14376
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__form-text-input__control-row", children: [
14377
+ leading ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__leading", children: leading }) : null,
14378
+ /* @__PURE__ */ jsxRuntime.jsx(
14379
+ "input",
14380
+ {
14381
+ "aria-describedby": describedBy,
14382
+ "aria-invalid": error,
14383
+ className: "str-chat__form-text-input__input",
14384
+ disabled,
14385
+ id,
14386
+ ref,
14387
+ ...inputProps
14388
+ }
14389
+ ),
14390
+ trailingText != null ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__suffix", children: trailingText }) : null,
14391
+ trailing ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "str-chat__form-text-input__trailing", children: trailing }) : null
14392
+ ] }),
14393
+ messageInside ? fieldMessage : null
14291
14394
  ]
14292
14395
  }
14293
14396
  ),
14294
- messageContent != null && /* @__PURE__ */ jsxRuntime.jsx(
14295
- "div",
14296
- {
14297
- className: clsx(
14298
- "str-chat__form-text-input__message",
14299
- displayError && "str-chat__form-field-error",
14300
- displaySuccess && "str-chat__form-text-input__message--success"
14301
- ),
14302
- id: messageId,
14303
- role: error ? "alert" : void 0,
14304
- children: messageContent
14305
- }
14306
- )
14397
+ messageInside ? null : fieldMessage
14307
14398
  ]
14308
14399
  }
14309
14400
  );
@@ -15688,6 +15779,7 @@ const OptionFieldSet = () => {
15688
15779
  {
15689
15780
  className: "str-chat__form__input-field__value",
15690
15781
  error: !!error,
15782
+ fieldMessagePlacement: "inside",
15691
15783
  id: option.id,
15692
15784
  leading: draggable ? /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconReorder, { className: "str-chat__drag-handle" }) : void 0,
15693
15785
  message: error ? /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "poll-option-input-field-error", children: knownValidationErrors[error] ?? t("Error") }) : void 0,
@@ -15725,7 +15817,7 @@ const RemoveOptionButton = ({ className, ...props }) => /* @__PURE__ */ jsxRunti
15725
15817
  appearance: "ghost",
15726
15818
  circular: true,
15727
15819
  className: clsx("str-chat__form__remove-option-button", className),
15728
- size: "sm",
15820
+ size: "xs",
15729
15821
  variant: "secondary",
15730
15822
  ...props,
15731
15823
  children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMinusCircle, {})
@@ -17341,7 +17433,7 @@ const GalleryUI = () => {
17341
17433
  children: /* @__PURE__ */ jsxRuntime.jsx(
17342
17434
  "div",
17343
17435
  {
17344
- className: clsx({
17436
+ className: clsx("str-chat__gallery__media-container", {
17345
17437
  "str-chat__gallery__media--dragging": isDragging,
17346
17438
  "str-chat__gallery__media--slide-backward": !isDragging && slideDirection === "backward",
17347
17439
  "str-chat__gallery__media--slide-forward": !isDragging && slideDirection === "forward"
@@ -18246,7 +18338,7 @@ const textComposerStateSelector$3 = ({ selection, suggestions }) => ({
18246
18338
  const searchSourceStateSelector$5 = (nextValue) => ({
18247
18339
  items: nextValue.items ?? []
18248
18340
  });
18249
- const defaultComponents = {
18341
+ const defaultComponents$1 = {
18250
18342
  "/": (props) => /* @__PURE__ */ jsxRuntime.jsx(CommandItem, { ...props, entity: props.entity }),
18251
18343
  ":": (props) => /* @__PURE__ */ jsxRuntime.jsx(EmoticonItem, { ...props, entity: props.entity }),
18252
18344
  "@": (props) => /* @__PURE__ */ jsxRuntime.jsx(UserItem, { ...props, entity: props.entity })
@@ -18257,7 +18349,7 @@ const SuggestionList = ({
18257
18349
  containerClassName,
18258
18350
  focusedItemIndex,
18259
18351
  setFocusedItemIndex,
18260
- suggestionItemComponents = defaultComponents
18352
+ suggestionItemComponents = defaultComponents$1
18261
18353
  }) => {
18262
18354
  const {
18263
18355
  AutocompleteSuggestionItem = SuggestionListItem,
@@ -19111,7 +19203,7 @@ const getLatestMessagePreview = (channel, t, userLanguage = "en", isMessageAIGen
19111
19203
  if (!latestMessage) {
19112
19204
  return t("Nothing yet...");
19113
19205
  }
19114
- if (latestMessage.deleted_at) {
19206
+ if (WithAudioPlayback.isMessageDeleted(latestMessage)) {
19115
19207
  return t("Message deleted");
19116
19208
  }
19117
19209
  if (poll) {
@@ -19649,7 +19741,11 @@ const defaultReactionOptions = {
19649
19741
  };
19650
19742
  const stableOwnReactions = [];
19651
19743
  const ReactionSelector = (props) => {
19652
- const { handleReaction: propHandleReaction, own_reactions: propOwnReactions } = props;
19744
+ const {
19745
+ dialogId: propDialogId,
19746
+ handleReaction: propHandleReaction,
19747
+ own_reactions: propOwnReactions
19748
+ } = props;
19653
19749
  const [extendedListOpen, setExtendedListOpen] = React.useState(false);
19654
19750
  const {
19655
19751
  reactionOptions = defaultReactionOptions,
@@ -19661,7 +19757,7 @@ const ReactionSelector = (props) => {
19661
19757
  message,
19662
19758
  threadList
19663
19759
  } = useMessageContext();
19664
- const dialogId2 = ReactionSelector.getDialogId({
19760
+ const dialogId2 = propDialogId ?? ReactionSelector.getDialogId({
19665
19761
  messageId: message.id,
19666
19762
  threadList
19667
19763
  });
@@ -19687,50 +19783,61 @@ const ReactionSelector = (props) => {
19687
19783
  })
19688
19784
  );
19689
19785
  }, [reactionOptions]);
19690
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__reaction-selector", "data-testid": "reaction-selector", children: [
19691
- !extendedListOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19692
- /* @__PURE__ */ jsxRuntime.jsx(
19693
- "ul",
19694
- {
19695
- className: "str-chat__reaction-selector-list",
19696
- "data-testid": "reaction-selector-list",
19697
- children: adjustedQuickReactionOptions.map(
19698
- ({ Component, name: reactionName, type: reactionType }) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__reaction-list-selector__item", children: /* @__PURE__ */ jsxRuntime.jsx(
19699
- "button",
19700
- {
19701
- "aria-label": `Select Reaction: ${reactionName || reactionType}`,
19702
- "aria-pressed": typeof ownReactionByType[reactionType] !== "undefined",
19703
- className: clsx("str-chat__reaction-selector-list__item-button"),
19704
- "data-testid": "select-reaction-button",
19705
- "data-text": reactionType,
19706
- onClick: (event) => {
19707
- handleReaction(reactionType, event);
19708
- if (closeReactionSelectorOnClick) {
19709
- dialog.close();
19710
- }
19711
- },
19712
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__reaction-icon", children: /* @__PURE__ */ jsxRuntime.jsx(Component, {}) })
19713
- }
19714
- ) }, reactionType)
19715
- )
19716
- }
19717
- ),
19718
- /* @__PURE__ */ jsxRuntime.jsx(
19719
- WithAudioPlayback.Button,
19720
- {
19721
- appearance: "outline",
19722
- circular: true,
19723
- className: "str-chat__reaction-selector__add-button",
19724
- "data-testid": "reaction-selector-add-button",
19725
- onClick: () => setExtendedListOpen(true),
19726
- size: "sm",
19727
- variant: "secondary",
19728
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlus, {})
19786
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__reaction-selector", "data-testid": "reaction-selector", children: !extendedListOpen ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19787
+ /* @__PURE__ */ jsxRuntime.jsx(
19788
+ "ul",
19789
+ {
19790
+ className: "str-chat__reaction-selector-list",
19791
+ "data-testid": "reaction-selector-list",
19792
+ children: adjustedQuickReactionOptions.map(
19793
+ ({ Component, name: reactionName, type: reactionType }) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__reaction-list-selector__item", children: /* @__PURE__ */ jsxRuntime.jsx(
19794
+ "button",
19795
+ {
19796
+ "aria-label": `Select Reaction: ${reactionName || reactionType}`,
19797
+ "aria-pressed": typeof ownReactionByType[reactionType] !== "undefined",
19798
+ className: clsx("str-chat__reaction-selector-list__item-button"),
19799
+ "data-testid": "select-reaction-button",
19800
+ "data-text": reactionType,
19801
+ onClick: (event) => {
19802
+ handleReaction(reactionType, event);
19803
+ if (closeReactionSelectorOnClick) {
19804
+ dialog.close();
19805
+ }
19806
+ },
19807
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__reaction-icon", children: /* @__PURE__ */ jsxRuntime.jsx(Component, {}) })
19808
+ }
19809
+ ) }, reactionType)
19810
+ )
19811
+ }
19812
+ ),
19813
+ /* @__PURE__ */ jsxRuntime.jsx(
19814
+ WithAudioPlayback.Button,
19815
+ {
19816
+ appearance: "outline",
19817
+ circular: true,
19818
+ className: "str-chat__reaction-selector__add-button",
19819
+ "data-testid": "reaction-selector-add-button",
19820
+ onClick: () => {
19821
+ setExtendedListOpen(true);
19822
+ },
19823
+ size: "sm",
19824
+ variant: "secondary",
19825
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconPlus, {})
19826
+ }
19827
+ )
19828
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
19829
+ ReactionSelectorExtendedList2,
19830
+ {
19831
+ ...props,
19832
+ dialogId: dialogId2,
19833
+ handleReaction: async (reactionType, event) => {
19834
+ await handleReaction(reactionType, event);
19835
+ if (closeReactionSelectorOnClick) {
19836
+ dialog.close();
19729
19837
  }
19730
- )
19731
- ] }),
19732
- extendedListOpen && /* @__PURE__ */ jsxRuntime.jsx(ReactionSelectorExtendedList2, { ...props, dialogId: dialogId2 })
19733
- ] });
19838
+ }
19839
+ }
19840
+ ) });
19734
19841
  };
19735
19842
  ReactionSelector.getDialogId = ({ messageId, threadList }) => {
19736
19843
  const dialogIdNamespace = threadList ? "-thread" : "";
@@ -19790,521 +19897,682 @@ ReactionSelector.ExtendedList = function ReactionSelectorExtendedList({
19790
19897
  }
19791
19898
  );
19792
19899
  };
19793
- const ReactionSelectorWithButton = ({
19794
- ReactionIcon
19900
+ function useFetchReactions(options) {
19901
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
19902
+ const { handleFetchReactions: contextHandleFetchReactions } = useMessageContext();
19903
+ const { t } = WithAudioPlayback.useTranslationContext("useFetchReactions");
19904
+ const [reactions, setReactions] = React.useState([]);
19905
+ const {
19906
+ handleFetchReactions: propHandleFetchReactions,
19907
+ reactionType,
19908
+ shouldFetch,
19909
+ sort
19910
+ } = options;
19911
+ const [isLoading, setIsLoading] = React.useState(shouldFetch);
19912
+ const handleFetchReactions = propHandleFetchReactions ?? contextHandleFetchReactions;
19913
+ const [refetchNonce, setRefetchNonce] = React.useState(null);
19914
+ React.useEffect(() => {
19915
+ if (!shouldFetch) {
19916
+ return;
19917
+ }
19918
+ let cancel = false;
19919
+ (async () => {
19920
+ try {
19921
+ setIsLoading(true);
19922
+ const reactions2 = await handleFetchReactions(reactionType ?? void 0, sort);
19923
+ if (!cancel) {
19924
+ setReactions(reactions2);
19925
+ }
19926
+ } catch (e) {
19927
+ if (!cancel) {
19928
+ setReactions([]);
19929
+ addNotification({
19930
+ emitter: "Reactions",
19931
+ error: e instanceof Error ? e : void 0,
19932
+ message: t("Error fetching reactions"),
19933
+ severity: "error",
19934
+ type: "api:message:reactions:fetch:failed"
19935
+ });
19936
+ }
19937
+ } finally {
19938
+ if (!cancel) {
19939
+ setIsLoading(false);
19940
+ }
19941
+ }
19942
+ })();
19943
+ return () => {
19944
+ cancel = true;
19945
+ };
19946
+ }, [
19947
+ addNotification,
19948
+ handleFetchReactions,
19949
+ reactionType,
19950
+ refetchNonce,
19951
+ shouldFetch,
19952
+ sort,
19953
+ t
19954
+ ]);
19955
+ const refetch = React.useCallback(() => {
19956
+ setRefetchNonce(Math.random());
19957
+ }, []);
19958
+ return { isLoading, reactions, refetch };
19959
+ }
19960
+ const defaultReactionDetailsSort = { created_at: -1 };
19961
+ const MessageReactionsDetailLoadingIndicator = () => {
19962
+ const elements = React.useMemo(
19963
+ () => Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
19964
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
19965
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
19966
+ ] }, index)),
19967
+ []
19968
+ );
19969
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: elements });
19970
+ };
19971
+ const MessageReactionsDetail = ({
19972
+ handleFetchReactions,
19973
+ handleReaction,
19974
+ onSelectedReactionTypeChange,
19975
+ own_reactions,
19976
+ reactionDetailsSort: propReactionDetailsSort,
19977
+ reactionGroups,
19978
+ reactions,
19979
+ selectedReactionType,
19980
+ totalReactionCount
19795
19981
  }) => {
19796
- const { t } = WithAudioPlayback.useTranslationContext("ReactionSelectorWithButton");
19797
- const { isMyMessage, message, threadList } = useMessageContext();
19798
- const { ReactionSelector: ReactionSelector$1 = ReactionSelector } = WithAudioPlayback.useComponentContext();
19799
- const buttonRef = React.useRef(null);
19800
- const dialogId2 = ReactionSelector.getDialogId({
19801
- messageId: message.id,
19802
- threadList
19982
+ const [extendedReactionListOpen, setExtendedReactionListOpen] = React.useState(false);
19983
+ const { client } = WithAudioPlayback.useChatContext();
19984
+ const {
19985
+ Avatar: Avatar$1 = Avatar,
19986
+ LoadingIndicator: LoadingIndicator2 = MessageReactionsDetailLoadingIndicator,
19987
+ reactionOptions = defaultReactionOptions,
19988
+ ReactionSelectorExtendedList: ReactionSelectorExtendedList2 = ReactionSelector.ExtendedList
19989
+ } = WithAudioPlayback.useComponentContext(MessageReactionsDetail.name);
19990
+ const { t } = WithAudioPlayback.useTranslationContext();
19991
+ const {
19992
+ handleReaction: contextHandleReaction,
19993
+ message,
19994
+ reactionDetailsSort: contextReactionDetailsSort
19995
+ } = useMessageContext(MessageReactionsDetail.name);
19996
+ const reactionDetailsSort = propReactionDetailsSort ?? contextReactionDetailsSort ?? defaultReactionDetailsSort;
19997
+ const {
19998
+ isLoading: areReactionsLoading,
19999
+ reactions: reactionDetails,
20000
+ refetch
20001
+ } = useFetchReactions({
20002
+ handleFetchReactions,
20003
+ reactionType: selectedReactionType,
20004
+ shouldFetch: true,
20005
+ sort: reactionDetailsSort
19803
20006
  });
19804
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
19805
- const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
19806
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
19807
- /* @__PURE__ */ jsxRuntime.jsx(
19808
- DialogAnchor,
20007
+ if (extendedReactionListOpen) {
20008
+ return /* @__PURE__ */ jsxRuntime.jsx(
20009
+ "div",
19809
20010
  {
19810
- dialogManagerId: dialogManager?.id,
19811
- id: dialogId2,
19812
- placement: isMyMessage() ? "top-end" : "top-start",
19813
- referenceElement: buttonRef.current,
19814
- trapFocus: true,
19815
- updatePositionOnContentResize: true,
19816
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactionSelector$1, {})
19817
- }
19818
- ),
19819
- /* @__PURE__ */ jsxRuntime.jsx(
19820
- QuickMessageActionsButton,
19821
- {
19822
- "aria-expanded": dialogIsOpen,
19823
- "aria-label": t("aria/Open Reaction Selector"),
19824
- className: "str-chat__message-reactions-button",
19825
- "data-testid": "message-reaction-action",
19826
- onClick: () => dialog?.toggle(),
19827
- ref: buttonRef,
19828
- children: /* @__PURE__ */ jsxRuntime.jsx(ReactionIcon, { className: "str-chat__message-action-icon" })
20011
+ className: "str-chat__message-reactions-detail",
20012
+ "data-testid": "message-reactions-detail",
20013
+ children: /* @__PURE__ */ jsxRuntime.jsx(
20014
+ ReactionSelectorExtendedList2,
20015
+ {
20016
+ dialogId: MessageReactionsDetail.getDialogId({ messageId: message.id }),
20017
+ handleReaction,
20018
+ own_reactions
20019
+ }
20020
+ )
19829
20021
  }
19830
- )
19831
- ] });
19832
- };
19833
- const getErrorMessage$1 = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
19834
- const getNotificationError$1 = (error) => {
19835
- if (error instanceof Error) return error;
19836
- if (typeof error === "string") return new Error(error);
19837
- if (error && typeof error === "object" && "message" in error) {
19838
- const message = error.message;
19839
- if (typeof message === "string") return new Error(message);
20022
+ );
19840
20023
  }
19841
- return void 0;
19842
- };
19843
- const RemindMeSubmenuHeader = () => {
19844
- const { t } = WithAudioPlayback.useTranslationContext();
19845
- const { returnToParentMenu } = useContextMenuContext();
19846
- return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
19847
- /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
19848
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("Remind Me") })
19849
- ] }) });
19850
- };
19851
- const RemindMeSubmenu = () => {
19852
- const { t } = WithAudioPlayback.useTranslationContext();
19853
- const { client } = WithAudioPlayback.useChatContext();
19854
- const { message } = useMessageContext();
19855
- const { closeMenu } = useContextMenuContext();
19856
- const { addNotification } = WithAudioPlayback.useNotificationApi();
19857
- return /* @__PURE__ */ jsxRuntime.jsx(
20024
+ return /* @__PURE__ */ jsxRuntime.jsxs(
19858
20025
  "div",
19859
20026
  {
19860
- "aria-label": t("aria/Remind Me Options"),
19861
- className: "str-chat__message-actions-box__submenu",
19862
- role: "listbox",
19863
- children: client.reminders.scheduledOffsetsMs.map((offsetMs) => /* @__PURE__ */ jsxRuntime.jsx(
19864
- ContextMenuButton,
19865
- {
19866
- className: "str-chat__message-actions-list-item-button",
19867
- onClick: async () => {
19868
- try {
19869
- await client.reminders.upsertReminder({
19870
- messageId: message.id,
19871
- remind_at: new Date((/* @__PURE__ */ new Date()).getTime() + offsetMs).toISOString()
19872
- });
19873
- addNotification({
19874
- context: {
19875
- message
19876
- },
19877
- emitter: "MessageActions",
19878
- message: t("Reminder set"),
19879
- severity: "success",
19880
- type: "api:message:reminder:set:success"
19881
- });
19882
- } catch (error) {
19883
- addNotification({
19884
- context: {
19885
- message
19886
- },
19887
- emitter: "MessageActions",
19888
- error: getNotificationError$1(error),
19889
- message: getErrorMessage$1(error, "Error setting reminder"),
19890
- severity: "error",
19891
- type: "api:message:reminder:set:failed"
19892
- });
19893
- } finally {
19894
- closeMenu();
19895
- }
19896
- },
19897
- children: t("duration/Remind Me", { milliseconds: offsetMs })
19898
- },
19899
- `reminder-offset-option--${offsetMs}`
19900
- ))
20027
+ className: "str-chat__message-reactions-detail",
20028
+ "data-testid": "message-reactions-detail",
20029
+ children: [
20030
+ typeof totalReactionCount === "number" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__total-count", children: t("{{ count }} reactions", { count: totalReactionCount }) }),
20031
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__reaction-type-list-container", children: /* @__PURE__ */ jsxRuntime.jsxs(
20032
+ "ul",
20033
+ {
20034
+ className: "str-chat__message-reactions-detail__reaction-type-list",
20035
+ "data-testid": "reaction-type-list",
20036
+ children: [
20037
+ /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__message-reactions-detail__reaction-type-list-item", children: /* @__PURE__ */ jsxRuntime.jsx(
20038
+ "button",
20039
+ {
20040
+ "aria-label": t("Add reaction"),
20041
+ className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20042
+ "data-testid": "add-reaction-button",
20043
+ onClick: () => setExtendedReactionListOpen(true),
20044
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconEmojiAdd, {}) })
20045
+ }
20046
+ ) }),
20047
+ reactions.map(
20048
+ ({ EmojiComponent, reactionCount, reactionType }) => EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(
20049
+ "li",
20050
+ {
20051
+ className: "str-chat__message-reactions-detail__reaction-type-list-item",
20052
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
20053
+ "button",
20054
+ {
20055
+ "aria-pressed": reactionType === selectedReactionType,
20056
+ className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20057
+ onClick: () => onSelectedReactionTypeChange?.(
20058
+ selectedReactionType === reactionType ? null : reactionType
20059
+ ),
20060
+ children: [
20061
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) }),
20062
+ /* @__PURE__ */ jsxRuntime.jsx(
20063
+ "span",
20064
+ {
20065
+ className: "str-chat__message-reactions-detail__reaction-type-list-item-count",
20066
+ "data-testid": "reaction-type-count",
20067
+ children: reactionCount
20068
+ }
20069
+ )
20070
+ ]
20071
+ }
20072
+ )
20073
+ },
20074
+ reactionType
20075
+ )
20076
+ )
20077
+ ]
20078
+ }
20079
+ ) }),
20080
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__user-list-container", children: /* @__PURE__ */ jsxRuntime.jsxs(
20081
+ "div",
20082
+ {
20083
+ className: "str-chat__message-reactions-detail__user-list",
20084
+ "data-testid": "all-reacting-users",
20085
+ children: [
20086
+ areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator2, {}),
20087
+ !areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reactionDetails.map(({ type, user }) => {
20088
+ const belongsToCurrentUser = client.user?.id === user?.id;
20089
+ const EmojiComponent = Array.isArray(reactionOptions) ? void 0 : reactionOptions.quick[type]?.Component ?? reactionOptions.extended?.[type]?.Component;
20090
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20091
+ "div",
20092
+ {
20093
+ className: "str-chat__message-reactions-detail__user-list-item",
20094
+ children: [
20095
+ /* @__PURE__ */ jsxRuntime.jsx(
20096
+ Avatar$1,
20097
+ {
20098
+ className: "str-chat__avatar--with-border",
20099
+ "data-testid": "avatar",
20100
+ imageUrl: user?.image,
20101
+ size: "md",
20102
+ userName: user?.name || user?.id
20103
+ }
20104
+ ),
20105
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__user-list-item-info", children: [
20106
+ /* @__PURE__ */ jsxRuntime.jsx(
20107
+ "span",
20108
+ {
20109
+ className: "str-chat__message-reactions-detail__user-list-item-username",
20110
+ "data-testid": "reaction-user-username",
20111
+ children: belongsToCurrentUser ? t("You") : user?.name || user?.id
20112
+ }
20113
+ ),
20114
+ belongsToCurrentUser && /* @__PURE__ */ jsxRuntime.jsx(
20115
+ "button",
20116
+ {
20117
+ className: "str-chat__message-reactions-detail__user-list-item-button",
20118
+ "data-testid": "remove-reaction-button",
20119
+ onClick: async (e) => {
20120
+ const reactionCountBeforeRemoval = reactionGroups?.[type]?.count ?? 0;
20121
+ await contextHandleReaction(type, e);
20122
+ if (selectedReactionType !== null && reactionCountBeforeRemoval <= 1) {
20123
+ onSelectedReactionTypeChange?.(null);
20124
+ } else {
20125
+ refetch();
20126
+ }
20127
+ },
20128
+ children: t("Tap to remove")
20129
+ }
20130
+ )
20131
+ ] }),
20132
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__user-list-item-icon", children: !selectedReactionType && EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) })
20133
+ ]
20134
+ },
20135
+ `${user?.id}-${type}`
20136
+ );
20137
+ }) })
20138
+ ]
20139
+ }
20140
+ ) })
20141
+ ]
19901
20142
  }
19902
20143
  );
19903
20144
  };
19904
- const QuickMessageActionsButton = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
19905
- WithAudioPlayback.Button,
19906
- {
19907
- appearance: "ghost",
19908
- circular: true,
19909
- className: clsx("str-chat__message-actions-box-button", className),
19910
- size: "sm",
19911
- variant: "secondary",
19912
- ...props
20145
+ MessageReactionsDetail.displayName = "MessageReactionsDetail";
20146
+ MessageReactionsDetail.getDialogId = ({ messageId }) => `message-reactions-detail-${messageId}`;
20147
+ const defaultReactionsSort = (a, b) => {
20148
+ if (a.firstReactionAt && b.firstReactionAt) {
20149
+ return +a.firstReactionAt - +b.firstReactionAt;
19913
20150
  }
19914
- );
19915
- const DeleteMessageAlert = ({ onCancel, onDelete }) => {
19916
- const { t } = WithAudioPlayback.useTranslationContext();
19917
- const { close } = useModalContext();
19918
- return /* @__PURE__ */ jsxRuntime.jsxs(
19919
- Alert.Root,
19920
- {
19921
- className: "str-chat__delete-message-alert",
19922
- "data-testid": "message-delete-alert",
19923
- children: [
19924
- /* @__PURE__ */ jsxRuntime.jsx(
19925
- Alert.Header,
19926
- {
19927
- description: t("Are you sure you want to delete this message?"),
19928
- title: t("Delete message")
19929
- }
19930
- ),
19931
- /* @__PURE__ */ jsxRuntime.jsxs(Alert.Actions, { children: [
19932
- /* @__PURE__ */ jsxRuntime.jsx(
19933
- WithAudioPlayback.Button,
19934
- {
19935
- appearance: "outline",
19936
- className: "str-chat__delete-message-alert__delete-button",
19937
- "data-testid": "delete-message-alert-delete-button",
19938
- onClick: onDelete,
19939
- size: "md",
19940
- variant: "danger",
19941
- children: t("Delete message")
19942
- }
19943
- ),
19944
- /* @__PURE__ */ jsxRuntime.jsx(
19945
- WithAudioPlayback.Button,
19946
- {
19947
- appearance: "outline",
19948
- className: "str-chat__delete-message-alert__cancel-button",
19949
- "data-testid": "delete-message-alert-cancel-button",
19950
- onClick: () => {
19951
- onCancel();
19952
- close();
19953
- },
19954
- size: "md",
19955
- variant: "secondary",
19956
- children: t("Cancel")
19957
- }
19958
- )
19959
- ] })
19960
- ]
20151
+ return a.reactionType.localeCompare(b.reactionType, "en");
20152
+ };
20153
+ const useProcessReactions = (params) => {
20154
+ const {
20155
+ own_reactions: propOwnReactions,
20156
+ reaction_groups: propReactionGroups,
20157
+ reactions: propReactions,
20158
+ sortReactions: propSortReactions
20159
+ } = params;
20160
+ const { message, sortReactions: contextSortReactions } = useMessageContext();
20161
+ const { reactionOptions = defaultReactionOptions } = WithAudioPlayback.useComponentContext();
20162
+ const sortReactions = propSortReactions ?? contextSortReactions ?? defaultReactionsSort;
20163
+ const latestReactions = propReactions ?? message.latest_reactions;
20164
+ const ownReactions = propOwnReactions ?? message?.own_reactions;
20165
+ const reactionGroups = propReactionGroups ?? message?.reaction_groups ?? void 0;
20166
+ const isOwnReaction = React.useCallback(
20167
+ (reactionType) => ownReactions?.some((reaction) => reaction.type === reactionType) ?? false,
20168
+ [ownReactions]
20169
+ );
20170
+ const getEmojiByReactionType = React.useCallback(
20171
+ (reactionType) => {
20172
+ if (Array.isArray(reactionOptions)) {
20173
+ return reactionOptions.find(({ type }) => type === reactionType)?.Component ?? null;
20174
+ }
20175
+ return reactionOptions.quick[reactionType]?.Component ?? reactionOptions.extended?.[reactionType]?.Component ?? null;
20176
+ },
20177
+ [reactionOptions]
20178
+ );
20179
+ const isSupportedReaction = React.useCallback(
20180
+ (reactionType) => {
20181
+ if (Array.isArray(reactionOptions)) {
20182
+ return reactionOptions.some(
20183
+ (reactionOption) => reactionOption.type === reactionType
20184
+ );
20185
+ }
20186
+ return typeof reactionOptions.quick[reactionType] !== "undefined" || typeof reactionOptions.extended?.[reactionType] !== "undefined";
20187
+ },
20188
+ [reactionOptions]
20189
+ );
20190
+ const uniqueReactionTypeCount = React.useMemo(() => {
20191
+ if (!reactionGroups) {
20192
+ return 0;
20193
+ }
20194
+ return Object.keys(reactionGroups).filter(
20195
+ (reactionType) => isSupportedReaction(reactionType)
20196
+ ).length;
20197
+ }, [isSupportedReaction, reactionGroups]);
20198
+ const getLatestReactedUserNames = React.useCallback(
20199
+ (reactionType) => latestReactions?.flatMap((reaction) => {
20200
+ if (reactionType && reactionType === reaction.type) {
20201
+ const username = reaction.user?.name || reaction.user?.id;
20202
+ return username ? [username] : [];
20203
+ }
20204
+ return [];
20205
+ }) ?? [],
20206
+ [latestReactions]
20207
+ );
20208
+ const existingReactions = React.useMemo(() => {
20209
+ if (!reactionGroups) {
20210
+ return [];
19961
20211
  }
20212
+ const unsortedReactions = Object.entries(reactionGroups).flatMap(
20213
+ ([reactionType, { count, first_reaction_at, last_reaction_at }]) => {
20214
+ if (count === 0 || !isSupportedReaction(reactionType)) {
20215
+ return [];
20216
+ }
20217
+ const latestReactedUserNames = getLatestReactedUserNames(reactionType);
20218
+ return [
20219
+ {
20220
+ EmojiComponent: getEmojiByReactionType(reactionType),
20221
+ firstReactionAt: first_reaction_at ? new Date(first_reaction_at) : null,
20222
+ isOwnReaction: isOwnReaction(reactionType),
20223
+ lastReactionAt: last_reaction_at ? new Date(last_reaction_at) : null,
20224
+ latestReactedUserNames,
20225
+ reactionCount: count,
20226
+ reactionType,
20227
+ unlistedReactedUserCount: count - latestReactedUserNames.length
20228
+ }
20229
+ ];
20230
+ }
20231
+ );
20232
+ return unsortedReactions.sort(sortReactions);
20233
+ }, [
20234
+ getEmojiByReactionType,
20235
+ getLatestReactedUserNames,
20236
+ isOwnReaction,
20237
+ isSupportedReaction,
20238
+ reactionGroups,
20239
+ sortReactions
20240
+ ]);
20241
+ const hasReactions = existingReactions.length > 0;
20242
+ const totalReactionCount = React.useMemo(
20243
+ () => Object.values(reactionGroups ?? {}).reduce((total, { count }) => total + count, 0),
20244
+ [reactionGroups]
19962
20245
  );
20246
+ return {
20247
+ existingReactions,
20248
+ hasReactions,
20249
+ reactionGroups,
20250
+ totalReactionCount,
20251
+ uniqueReactionTypeCount
20252
+ };
19963
20253
  };
19964
- const msgActionsBoxButtonClassName = "str-chat__message-actions-list-item-button";
19965
- const getErrorMessage = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
19966
- const getNotificationError = (error) => {
19967
- if (error instanceof Error) return error;
19968
- if (typeof error === "string") return new Error(error);
19969
- if (error && typeof error === "object" && "message" in error) {
19970
- const message = error.message;
19971
- if (typeof message === "string") return new Error(message);
20254
+ const FragmentOrButton = ({
20255
+ buttonIf: renderButton = false,
20256
+ children,
20257
+ ...props
20258
+ }) => {
20259
+ if (renderButton) {
20260
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { ...props, children });
19972
20261
  }
19973
- return void 0;
20262
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
19974
20263
  };
19975
- const DefaultMessageActionComponents = {
19976
- dropdown: {
19977
- ThreadReply() {
19978
- const { closeMenu } = useContextMenuContext();
19979
- const { handleOpenThread } = useMessageContext();
19980
- const { t } = WithAudioPlayback.useTranslationContext();
19981
- return /* @__PURE__ */ jsxRuntime.jsx(
19982
- ContextMenuButton,
19983
- {
19984
- "aria-label": t("aria/Open Thread"),
19985
- className: msgActionsBoxButtonClassName,
19986
- "data-testid": "thread-action",
19987
- Icon: WithAudioPlayback.IconThread,
19988
- onClick: (e) => {
19989
- handleOpenThread(e);
19990
- closeMenu();
19991
- },
19992
- children: t("Thread Reply")
19993
- }
19994
- );
19995
- },
19996
- Quote() {
19997
- const { closeMenu } = useContextMenuContext();
19998
- const { message } = useMessageContext();
19999
- const { t } = WithAudioPlayback.useTranslationContext();
20000
- const messageComposer = WithAudioPlayback.useMessageComposerController();
20001
- const handleQuote = () => {
20002
- messageComposer.setQuotedMessage(message);
20003
- const elements = message.parent_id ? document.querySelectorAll(".str-chat__thread .str-chat__textarea__textarea") : document.getElementsByClassName("str-chat__textarea__textarea");
20004
- const textarea = elements.item(0);
20005
- if (textarea instanceof HTMLTextAreaElement) {
20006
- textarea.focus();
20007
- }
20008
- };
20009
- return /* @__PURE__ */ jsxRuntime.jsx(
20010
- ContextMenuButton,
20011
- {
20012
- "aria-label": t("aria/Quote Message"),
20013
- className: msgActionsBoxButtonClassName,
20014
- Icon: WithAudioPlayback.IconQuote,
20015
- onClick: () => {
20016
- handleQuote();
20017
- closeMenu();
20018
- },
20019
- children: t("Quote Reply")
20020
- }
20021
- );
20022
- },
20023
- Pin() {
20024
- const { closeMenu } = useContextMenuContext();
20025
- const { handlePin, message } = useMessageContext();
20026
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20027
- const { t } = WithAudioPlayback.useTranslationContext();
20028
- const isPinned = !!message.pinned;
20029
- return /* @__PURE__ */ jsxRuntime.jsx(
20030
- ContextMenuButton,
20031
- {
20032
- "aria-label": isPinned ? t("aria/Unpin Message") : t("aria/Pin Message"),
20033
- className: msgActionsBoxButtonClassName,
20034
- Icon: isPinned ? WithAudioPlayback.IconUnpin : WithAudioPlayback.IconPin,
20035
- onClick: async (event) => {
20036
- try {
20037
- await handlePin(event);
20038
- addNotification({
20039
- context: {
20040
- message
20041
- },
20042
- emitter: "MessageActions",
20043
- message: isPinned ? t("Message unpinned") : t("Message pinned"),
20044
- severity: "success",
20045
- type: isPinned ? "api:message:unpin:success" : "api:message:pin:success"
20046
- });
20047
- } catch (error) {
20048
- addNotification({
20049
- context: {
20050
- message
20051
- },
20052
- emitter: "MessageActions",
20053
- error: getNotificationError(error),
20054
- message: getErrorMessage(
20055
- error,
20056
- isPinned ? t("Error removing message pin") : t("Error pinning message")
20057
- ),
20058
- severity: "error",
20059
- type: isPinned ? "api:message:unpin:failed" : "api:message:pin:failed"
20060
- });
20061
- }
20062
- closeMenu();
20063
- },
20064
- children: isPinned ? t("Unpin") : t("Pin")
20065
- }
20066
- );
20067
- },
20068
- CopyMessageText() {
20069
- const { closeMenu } = useContextMenuContext();
20070
- const { message } = useMessageContext();
20071
- const { t } = WithAudioPlayback.useTranslationContext();
20072
- return /* @__PURE__ */ jsxRuntime.jsx(
20073
- ContextMenuButton,
20074
- {
20075
- "aria-label": t("aria/Copy Message Text"),
20076
- className: msgActionsBoxButtonClassName,
20077
- Icon: WithAudioPlayback.IconCopy,
20078
- onClick: () => {
20079
- if (message.text) navigator.clipboard.writeText(message.text);
20080
- closeMenu();
20081
- },
20082
- children: t("Copy Message")
20083
- }
20084
- );
20085
- },
20086
- Resend() {
20087
- const { closeMenu } = useContextMenuContext();
20088
- const { handleRetry, message } = useMessageContext();
20089
- const { t } = WithAudioPlayback.useTranslationContext();
20090
- return /* @__PURE__ */ jsxRuntime.jsx(
20091
- ContextMenuButton,
20092
- {
20093
- "aria-label": t("aria/Resend Message"),
20094
- className: msgActionsBoxButtonClassName,
20095
- Icon: WithAudioPlayback.IconRetry,
20096
- onClick: () => {
20097
- handleRetry(message);
20098
- closeMenu();
20099
- },
20100
- children: t("Resend")
20101
- }
20102
- );
20103
- },
20104
- Edit() {
20105
- const messageComposer = WithAudioPlayback.useMessageComposerController();
20106
- const { message } = useMessageContext();
20107
- const { t } = WithAudioPlayback.useTranslationContext();
20108
- const { closeMenu } = useContextMenuContext();
20109
- return /* @__PURE__ */ jsxRuntime.jsx(
20110
- ContextMenuButton,
20111
- {
20112
- "aria-label": t("aria/Edit Message"),
20113
- className: msgActionsBoxButtonClassName,
20114
- Icon: WithAudioPlayback.IconEdit,
20115
- onClick: () => {
20116
- savePreEditSnapshot(messageComposer);
20117
- messageComposer.initState({ composition: message });
20118
- closeMenu();
20119
- },
20120
- children: t("Edit Message")
20121
- }
20122
- );
20123
- },
20124
- MarkUnread() {
20125
- const { closeMenu } = useContextMenuContext();
20126
- const { handleMarkUnread, message } = useMessageContext();
20127
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20128
- const { t } = WithAudioPlayback.useTranslationContext();
20129
- return /* @__PURE__ */ jsxRuntime.jsx(
20130
- ContextMenuButton,
20131
- {
20132
- "aria-label": t("aria/Mark Message Unread"),
20133
- className: msgActionsBoxButtonClassName,
20134
- Icon: WithAudioPlayback.IconNotification,
20135
- onClick: async (event) => {
20136
- try {
20137
- await handleMarkUnread(event);
20138
- addNotification({
20139
- context: {
20140
- message
20141
- },
20142
- emitter: "MessageActions",
20143
- message: t("Message marked as unread"),
20144
- severity: "success",
20145
- type: "api:message:markUnread:success"
20146
- });
20147
- } catch (error) {
20148
- addNotification({
20149
- context: {
20150
- message
20151
- },
20152
- emitter: "MessageActions",
20153
- error: getNotificationError(error),
20154
- message: getErrorMessage(
20155
- error,
20156
- t(
20157
- "Error marking message unread. Cannot mark unread messages older than the newest 100 channel messages."
20264
+ const UnMemoizedMessageReactions = (props) => {
20265
+ const {
20266
+ capLimit: { clustered: capLimitClustered = 5, segmented: capLimitSegmented = 4 } = {},
20267
+ flipHorizontalPosition = false,
20268
+ handleFetchReactions,
20269
+ reactionDetailsSort,
20270
+ verticalPosition = "top",
20271
+ visualStyle = "clustered",
20272
+ ...rest
20273
+ } = props;
20274
+ const {
20275
+ existingReactions,
20276
+ hasReactions,
20277
+ reactionGroups,
20278
+ totalReactionCount,
20279
+ uniqueReactionTypeCount
20280
+ } = useProcessReactions(rest);
20281
+ const [selectedReactionType, setSelectedReactionType] = React.useState(
20282
+ null
20283
+ );
20284
+ const { t } = WithAudioPlayback.useTranslationContext("MessageReactions");
20285
+ const { MessageReactionsDetail: MessageReactionsDetail$1 = MessageReactionsDetail } = WithAudioPlayback.useComponentContext();
20286
+ const { isMyMessage, message } = useMessageContext();
20287
+ const divRef = React.useRef(null);
20288
+ const dialogId2 = MessageReactionsDetail.getDialogId({
20289
+ messageId: message.id
20290
+ });
20291
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20292
+ const isDialogOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20293
+ const handleReactionButtonClick = (reactionType) => {
20294
+ if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
20295
+ return;
20296
+ }
20297
+ setSelectedReactionType(reactionType);
20298
+ dialog.open();
20299
+ };
20300
+ const cappedExistingReactions = React.useMemo(() => {
20301
+ if (visualStyle === "segmented" && verticalPosition !== "top") return null;
20302
+ const capLimit = visualStyle === "segmented" ? capLimitSegmented : capLimitClustered;
20303
+ const sliced = existingReactions.slice(0, capLimit);
20304
+ return {
20305
+ /**
20306
+ * Accumulated reaction count of capped reaction types, first four in case of
20307
+ * segmented(top) and first five in case of clustered(top/bottom) variations.
20308
+ */
20309
+ reactionCountToDisplay: sliced.reduce(
20310
+ (accumulatedCount, { reactionCount }) => accumulatedCount + reactionCount,
20311
+ 0
20312
+ ),
20313
+ reactionsToDisplay: sliced
20314
+ };
20315
+ }, [
20316
+ capLimitClustered,
20317
+ capLimitSegmented,
20318
+ existingReactions,
20319
+ verticalPosition,
20320
+ visualStyle
20321
+ ]);
20322
+ if (!hasReactions) return null;
20323
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20324
+ /* @__PURE__ */ jsxRuntime.jsx(
20325
+ "div",
20326
+ {
20327
+ "aria-label": t("aria/Reaction list"),
20328
+ className: clsx("str-chat__message-reactions", {
20329
+ [`str-chat__message-reactions--flipped-horizontally`]: flipHorizontalPosition,
20330
+ [`str-chat__message-reactions--${verticalPosition}`]: typeof verticalPosition === "string",
20331
+ [`str-chat__message-reactions--${visualStyle}`]: typeof visualStyle === "string"
20332
+ }),
20333
+ ref: divRef,
20334
+ role: "figure",
20335
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
20336
+ FragmentOrButton,
20337
+ {
20338
+ "aria-expanded": isDialogOpen,
20339
+ "aria-pressed": isDialogOpen,
20340
+ buttonIf: visualStyle === "clustered",
20341
+ className: "str-chat__message-reactions__list-button",
20342
+ "data-testid": "message-reactions-list-button",
20343
+ onClick: () => handleReactionButtonClick(null),
20344
+ children: [
20345
+ /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "str-chat__message-reactions__list", children: [
20346
+ (cappedExistingReactions?.reactionsToDisplay ?? existingReactions).map(
20347
+ ({ EmojiComponent, reactionCount, reactionType }) => EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(
20348
+ "li",
20349
+ {
20350
+ className: "str-chat__message-reactions__list-item",
20351
+ "data-testid": "message-reactions-list-item",
20352
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
20353
+ FragmentOrButton,
20354
+ {
20355
+ buttonIf: visualStyle === "segmented",
20356
+ className: "str-chat__message-reactions__list-item-button",
20357
+ onClick: () => handleReactionButtonClick(reactionType),
20358
+ children: [
20359
+ /* @__PURE__ */ jsxRuntime.jsx(
20360
+ "span",
20361
+ {
20362
+ className: "str-chat__message-reactions__list-item-icon",
20363
+ "data-testid": "message-reactions-list-item-icon",
20364
+ children: /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {})
20365
+ }
20366
+ ),
20367
+ visualStyle === "segmented" && reactionCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(
20368
+ "span",
20369
+ {
20370
+ className: "str-chat__message-reactions__list-item-count",
20371
+ "data-testclass": "message-reactions-item-count",
20372
+ children: reactionCount
20373
+ }
20374
+ )
20375
+ ]
20376
+ }
20377
+ )
20378
+ },
20379
+ reactionType
20158
20380
  )
20159
20381
  ),
20160
- severity: "error",
20161
- type: "api:message:markUnread:failed"
20162
- });
20163
- }
20164
- closeMenu();
20165
- },
20166
- children: t("Mark as unread")
20167
- }
20168
- );
20382
+ uniqueReactionTypeCount > 4 && cappedExistingReactions && visualStyle === "segmented" && /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__message-reactions__list-item str-chat__message-reactions__list-item--more", children: /* @__PURE__ */ jsxRuntime.jsx(
20383
+ "button",
20384
+ {
20385
+ className: "str-chat__message-reactions__list-item-button",
20386
+ onClick: () => handleReactionButtonClick(null),
20387
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "str-chat__message-reactions__overflow-count", children: [
20388
+ "+",
20389
+ totalReactionCount - cappedExistingReactions.reactionCountToDisplay
20390
+ ] })
20391
+ }
20392
+ ) })
20393
+ ] }),
20394
+ visualStyle === "clustered" && /* @__PURE__ */ jsxRuntime.jsx(
20395
+ "span",
20396
+ {
20397
+ className: "str-chat__message-reactions__total-count",
20398
+ "data-testid": "message-reactions-total-count",
20399
+ children: totalReactionCount
20400
+ }
20401
+ )
20402
+ ]
20403
+ }
20404
+ )
20405
+ }
20406
+ ),
20407
+ /* @__PURE__ */ jsxRuntime.jsx(
20408
+ DialogAnchor,
20409
+ {
20410
+ dialogManagerId: dialogManager?.id,
20411
+ id: dialogId2,
20412
+ offset: 8,
20413
+ placement: isMyMessage() ? "bottom-end" : "bottom-start",
20414
+ referenceElement: divRef.current,
20415
+ trapFocus: true,
20416
+ updatePositionOnContentResize: true,
20417
+ children: /* @__PURE__ */ jsxRuntime.jsx(
20418
+ MessageReactionsDetail$1,
20419
+ {
20420
+ handleFetchReactions,
20421
+ onSelectedReactionTypeChange: setSelectedReactionType,
20422
+ reactionDetailsSort,
20423
+ reactionGroups,
20424
+ reactions: existingReactions,
20425
+ selectedReactionType,
20426
+ totalReactionCount
20427
+ }
20428
+ )
20429
+ }
20430
+ )
20431
+ ] });
20432
+ };
20433
+ const MessageReactions = React.memo(
20434
+ UnMemoizedMessageReactions
20435
+ );
20436
+ const getImageDimensions = (source) => new Promise((resolve, reject) => {
20437
+ const image = new Image();
20438
+ image.addEventListener(
20439
+ "load",
20440
+ () => {
20441
+ resolve([image.width, image.height]);
20169
20442
  },
20170
- RemindMe() {
20171
- const { closeMenu, openSubmenu } = useContextMenuContext();
20172
- const { client } = WithAudioPlayback.useChatContext();
20173
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20174
- const { t } = WithAudioPlayback.useTranslationContext();
20175
- const { message } = useMessageContext();
20176
- const reminder = useMessageReminder(message.id);
20177
- const messageAlreadyBookmarked = reminder && !reminder?.remindAt;
20178
- if (messageAlreadyBookmarked) return null;
20179
- return /* @__PURE__ */ jsxRuntime.jsx(
20180
- ContextMenuButton,
20181
- {
20182
- "aria-label": reminder ? t("aria/Remind Me Message") : t("aria/Remove Reminder"),
20183
- className: msgActionsBoxButtonClassName,
20184
- hasSubMenu: !reminder,
20185
- Icon: reminder ? WithAudioPlayback.IconBellOff : WithAudioPlayback.IconBell,
20186
- onClick: async () => {
20187
- if (reminder) {
20188
- try {
20189
- await client.reminders.deleteReminder(reminder.id);
20190
- addNotification({
20191
- context: {
20192
- message
20193
- },
20194
- emitter: "MessageActions",
20195
- message: t("Remove reminder"),
20196
- severity: "success",
20197
- type: "api:message:reminder:delete:success"
20198
- });
20199
- } catch (error) {
20200
- addNotification({
20201
- context: {
20202
- message
20203
- },
20204
- emitter: "MessageActions",
20205
- error: getNotificationError(error),
20206
- message: getErrorMessage(error, "Error removing reminder"),
20207
- severity: "error",
20208
- type: "api:message:reminder:delete:failed"
20209
- });
20210
- } finally {
20211
- closeMenu();
20212
- }
20213
- } else {
20214
- openSubmenu({
20215
- Header: RemindMeSubmenuHeader,
20216
- Submenu: RemindMeSubmenu
20217
- });
20218
- }
20219
- },
20220
- children: reminder ? t("Remove reminder") : t("Remind me")
20221
- }
20222
- );
20223
- },
20224
- SaveForLater() {
20225
- const { closeMenu } = useContextMenuContext();
20226
- const { client } = WithAudioPlayback.useChatContext();
20227
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20228
- const { message } = useMessageContext();
20229
- const { t } = WithAudioPlayback.useTranslationContext();
20230
- const reminder = useMessageReminder(message.id);
20231
- const messageAlreadyHasReminderScheduled = Boolean(reminder && reminder?.remindAt);
20232
- if (messageAlreadyHasReminderScheduled) return null;
20233
- return /* @__PURE__ */ jsxRuntime.jsx(
20443
+ { once: true }
20444
+ );
20445
+ image.addEventListener("error", () => reject(`Couldn't load image from ${source}`), {
20446
+ once: true
20447
+ });
20448
+ image.src = source;
20449
+ });
20450
+ const SpriteImage = ({
20451
+ columns,
20452
+ fallback,
20453
+ height,
20454
+ position,
20455
+ rows,
20456
+ spriteUrl,
20457
+ style,
20458
+ width
20459
+ }) => {
20460
+ const [[spriteWidth, spriteHeight], setSpriteDimensions] = React.useState([0, 0]);
20461
+ React.useEffect(() => {
20462
+ getImageDimensions(spriteUrl).then(setSpriteDimensions).catch(console.error);
20463
+ }, [spriteUrl]);
20464
+ const [x, y] = position;
20465
+ if (!spriteHeight || !spriteWidth) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
20466
+ return /* @__PURE__ */ jsxRuntime.jsx(
20467
+ "div",
20468
+ {
20469
+ "data-testid": "sprite-image",
20470
+ style: {
20471
+ ...style,
20472
+ "--str-chat__sprite-image-resize-ratio": "var(--str-chat__sprite-image-resize-ratio-x, var(--str-chat__sprite-image-resize-ratio-y, 1))",
20473
+ "--str-chat__sprite-image-resize-ratio-x": "calc(var(--str-chat__sprite-image-width) / var(--str-chat__sprite-item-width))",
20474
+ "--str-chat__sprite-image-resize-ratio-y": "calc(var(--str-chat__sprite-image-height) / var(--str-chat__sprite-item-height))",
20475
+ "--str-chat__sprite-item-height": `${spriteHeight / rows}`,
20476
+ "--str-chat__sprite-item-width": `${spriteWidth / columns}`,
20477
+ ...Number.isFinite(height) ? { "--str-chat__sprite-image-height": `${height}px` } : {},
20478
+ ...Number.isFinite(width) ? { "--str-chat__sprite-image-width": `${width}px` } : {},
20479
+ backgroundImage: `url('${spriteUrl}')`,
20480
+ backgroundPosition: `${x * (100 / (columns - 1))}% ${y * (100 / (rows - 1))}%`,
20481
+ backgroundSize: `${columns * 100}% ${rows * 100}%`,
20482
+ height: "var(--str-chat__sprite-image-height, calc(var(--str-chat__sprite-item-height) * var(--str-chat__sprite-image-resize-ratio)))",
20483
+ width: "var(--str-chat__sprite-image-width, calc(var(--str-chat__sprite-item-width) * var(--str-chat__sprite-image-resize-ratio)))"
20484
+ }
20485
+ }
20486
+ );
20487
+ };
20488
+ const ReactionSelectorWithButton = ({
20489
+ ReactionIcon
20490
+ }) => {
20491
+ const { t } = WithAudioPlayback.useTranslationContext("ReactionSelectorWithButton");
20492
+ const { isMyMessage, message, threadList } = useMessageContext();
20493
+ const { ReactionSelector: ReactionSelector$1 = ReactionSelector } = WithAudioPlayback.useComponentContext();
20494
+ const buttonRef = React.useRef(null);
20495
+ const dialogId2 = ReactionSelector.getDialogId({
20496
+ messageId: message.id,
20497
+ threadList
20498
+ });
20499
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20500
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20501
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20502
+ /* @__PURE__ */ jsxRuntime.jsx(
20503
+ DialogAnchor,
20504
+ {
20505
+ dialogManagerId: dialogManager?.id,
20506
+ id: dialogId2,
20507
+ placement: isMyMessage() ? "top-end" : "top-start",
20508
+ referenceElement: buttonRef.current,
20509
+ trapFocus: true,
20510
+ updatePositionOnContentResize: true,
20511
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactionSelector$1, {})
20512
+ }
20513
+ ),
20514
+ /* @__PURE__ */ jsxRuntime.jsx(
20515
+ QuickMessageActionsButton,
20516
+ {
20517
+ "aria-expanded": dialogIsOpen,
20518
+ "aria-label": t("aria/Open Reaction Selector"),
20519
+ className: "str-chat__message-reactions-button",
20520
+ "data-testid": "message-reaction-action",
20521
+ onClick: () => dialog?.toggle(),
20522
+ ref: buttonRef,
20523
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactionIcon, { className: "str-chat__message-action-icon" })
20524
+ }
20525
+ )
20526
+ ] });
20527
+ };
20528
+ const getErrorMessage$1 = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
20529
+ const getNotificationError$1 = (error) => {
20530
+ if (error instanceof Error) return error;
20531
+ if (typeof error === "string") return new Error(error);
20532
+ if (error && typeof error === "object" && "message" in error) {
20533
+ const message = error.message;
20534
+ if (typeof message === "string") return new Error(message);
20535
+ }
20536
+ return void 0;
20537
+ };
20538
+ const RemindMeSubmenuHeader = () => {
20539
+ const { t } = WithAudioPlayback.useTranslationContext();
20540
+ const { returnToParentMenu } = useContextMenuContext();
20541
+ return /* @__PURE__ */ jsxRuntime.jsx(ContextMenuHeader, { children: /* @__PURE__ */ jsxRuntime.jsxs(ContextMenuBackButton, { onClick: returnToParentMenu, children: [
20542
+ /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconChevronLeft, {}),
20543
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t("Remind Me") })
20544
+ ] }) });
20545
+ };
20546
+ const RemindMeSubmenu = () => {
20547
+ const { t } = WithAudioPlayback.useTranslationContext();
20548
+ const { client } = WithAudioPlayback.useChatContext();
20549
+ const { message } = useMessageContext();
20550
+ const { closeMenu } = useContextMenuContext();
20551
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
20552
+ return /* @__PURE__ */ jsxRuntime.jsx(
20553
+ "div",
20554
+ {
20555
+ "aria-label": t("aria/Remind Me Options"),
20556
+ className: "str-chat__message-actions-box__submenu",
20557
+ role: "listbox",
20558
+ children: client.reminders.scheduledOffsetsMs.map((offsetMs) => /* @__PURE__ */ jsxRuntime.jsx(
20234
20559
  ContextMenuButton,
20235
20560
  {
20236
- "aria-label": reminder ? t("aria/Remove Save For Later") : t("aria/Bookmark Message"),
20237
- className: msgActionsBoxButtonClassName,
20238
- Icon: reminder ? WithAudioPlayback.IconBookmarkRemove : WithAudioPlayback.IconBookmark,
20561
+ className: "str-chat__message-actions-list-item-button",
20239
20562
  onClick: async () => {
20240
20563
  try {
20241
- if (reminder) {
20242
- await client.reminders.deleteReminder(reminder.id);
20243
- addNotification({
20244
- context: {
20245
- message
20246
- },
20247
- emitter: "MessageActions",
20248
- message: t("Remove save for later"),
20249
- severity: "success",
20250
- type: "api:message:saveForLater:delete:success"
20251
- });
20252
- } else {
20253
- await client.reminders.createReminder({ messageId: message.id });
20254
- addNotification({
20255
- context: {
20256
- message
20257
- },
20258
- emitter: "MessageActions",
20259
- message: t("Saved for later"),
20260
- severity: "success",
20261
- type: "api:message:saveForLater:create:success"
20262
- });
20263
- }
20264
- } catch (error) {
20265
- addNotification({
20266
- context: {
20267
- message
20268
- },
20269
- emitter: "MessageActions",
20270
- error: getNotificationError(error),
20271
- message: getErrorMessage(
20272
- error,
20273
- reminder ? "Error removing message from saved for later" : "Error saving message for later"
20274
- ),
20275
- severity: "error",
20276
- type: reminder ? "api:message:saveForLater:delete:failed" : "api:message:saveForLater:create:failed"
20564
+ await client.reminders.upsertReminder({
20565
+ messageId: message.id,
20566
+ remind_at: new Date((/* @__PURE__ */ new Date()).getTime() + offsetMs).toISOString()
20277
20567
  });
20278
- } finally {
20279
- closeMenu();
20280
- }
20281
- },
20282
- children: reminder ? t("Remove save for later") : t("Save for later")
20283
- }
20284
- );
20285
- },
20286
- Flag() {
20287
- const { closeMenu } = useContextMenuContext();
20288
- const { handleFlag, message } = useMessageContext();
20289
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20290
- const { t } = WithAudioPlayback.useTranslationContext();
20291
- return /* @__PURE__ */ jsxRuntime.jsx(
20292
- ContextMenuButton,
20293
- {
20294
- "aria-label": t("aria/Flag Message"),
20295
- className: msgActionsBoxButtonClassName,
20296
- Icon: WithAudioPlayback.IconFlag,
20297
- onClick: async (event) => {
20298
- try {
20299
- await handleFlag(event);
20300
20568
  addNotification({
20301
20569
  context: {
20302
20570
  message
20303
20571
  },
20304
20572
  emitter: "MessageActions",
20305
- message: t("Message has been successfully flagged"),
20573
+ message: t("Reminder set"),
20306
20574
  severity: "success",
20307
- type: "api:message:flag:success"
20575
+ type: "api:message:reminder:set:success"
20308
20576
  });
20309
20577
  } catch (error) {
20310
20578
  addNotification({
@@ -20312,46 +20580,218 @@ const DefaultMessageActionComponents = {
20312
20580
  message
20313
20581
  },
20314
20582
  emitter: "MessageActions",
20315
- error: getNotificationError(error),
20316
- message: getErrorMessage(error, t("Error adding flag")),
20583
+ error: getNotificationError$1(error),
20584
+ message: getErrorMessage$1(error, "Error setting reminder"),
20317
20585
  severity: "error",
20318
- type: "api:message:flag:failed"
20586
+ type: "api:message:reminder:set:failed"
20319
20587
  });
20588
+ } finally {
20589
+ closeMenu();
20320
20590
  }
20321
- closeMenu();
20322
20591
  },
20323
- children: t("Flag")
20324
- }
20325
- );
20326
- },
20327
- Mute() {
20328
- const { closeMenu } = useContextMenuContext();
20329
- const { handleMute, message } = useMessageContext();
20330
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20331
- const { mutes } = WithAudioPlayback.useChatContext();
20332
- const { t } = WithAudioPlayback.useTranslationContext();
20333
- const isMuted = WithAudioPlayback.isUserMuted(message, mutes);
20592
+ children: t("duration/Remind Me", { milliseconds: offsetMs })
20593
+ },
20594
+ `reminder-offset-option--${offsetMs}`
20595
+ ))
20596
+ }
20597
+ );
20598
+ };
20599
+ const QuickMessageActionsButton = ({ className, ...props }) => /* @__PURE__ */ jsxRuntime.jsx(
20600
+ WithAudioPlayback.Button,
20601
+ {
20602
+ appearance: "ghost",
20603
+ circular: true,
20604
+ className: clsx("str-chat__message-actions-box-button", className),
20605
+ size: "sm",
20606
+ variant: "secondary",
20607
+ ...props
20608
+ }
20609
+ );
20610
+ const DeleteMessageAlert = ({ onCancel, onDelete }) => {
20611
+ const { t } = WithAudioPlayback.useTranslationContext();
20612
+ const { close } = useModalContext();
20613
+ return /* @__PURE__ */ jsxRuntime.jsxs(
20614
+ Alert.Root,
20615
+ {
20616
+ className: "str-chat__delete-message-alert",
20617
+ "data-testid": "message-delete-alert",
20618
+ children: [
20619
+ /* @__PURE__ */ jsxRuntime.jsx(
20620
+ Alert.Header,
20621
+ {
20622
+ description: t("Are you sure you want to delete this message?"),
20623
+ title: t("Delete message")
20624
+ }
20625
+ ),
20626
+ /* @__PURE__ */ jsxRuntime.jsxs(Alert.Actions, { children: [
20627
+ /* @__PURE__ */ jsxRuntime.jsx(
20628
+ WithAudioPlayback.Button,
20629
+ {
20630
+ appearance: "outline",
20631
+ className: "str-chat__delete-message-alert__delete-button",
20632
+ "data-testid": "delete-message-alert-delete-button",
20633
+ onClick: onDelete,
20634
+ size: "md",
20635
+ variant: "danger",
20636
+ children: t("Delete message")
20637
+ }
20638
+ ),
20639
+ /* @__PURE__ */ jsxRuntime.jsx(
20640
+ WithAudioPlayback.Button,
20641
+ {
20642
+ appearance: "outline",
20643
+ className: "str-chat__delete-message-alert__cancel-button",
20644
+ "data-testid": "delete-message-alert-cancel-button",
20645
+ onClick: () => {
20646
+ onCancel();
20647
+ close();
20648
+ },
20649
+ size: "md",
20650
+ variant: "secondary",
20651
+ children: t("Cancel")
20652
+ }
20653
+ )
20654
+ ] })
20655
+ ]
20656
+ }
20657
+ );
20658
+ };
20659
+ const msgActionsBoxButtonClassName = "str-chat__message-actions-list-item-button";
20660
+ const getErrorMessage = (error, fallback) => error instanceof Error && error.message ? error.message : fallback;
20661
+ const getNotificationError = (error) => {
20662
+ if (error instanceof Error) return error;
20663
+ if (typeof error === "string") return new Error(error);
20664
+ if (error && typeof error === "object" && "message" in error) {
20665
+ const message = error.message;
20666
+ if (typeof message === "string") return new Error(message);
20667
+ }
20668
+ return void 0;
20669
+ };
20670
+ const DefaultMessageActionComponents = {
20671
+ dropdown: {
20672
+ React() {
20673
+ const { ReactionSelector: ReactionSelector$1 = ReactionSelector } = WithAudioPlayback.useComponentContext();
20674
+ const { anchorReferenceElement } = useContextMenuContext();
20675
+ const { isMyMessage, message, threadList } = useMessageContext();
20676
+ const { t } = WithAudioPlayback.useTranslationContext();
20677
+ const [referenceElement, setReferenceElement] = React.useState(null);
20678
+ const dialogId2 = `${ReactionSelector.getDialogId({
20679
+ messageId: message.id,
20680
+ threadList
20681
+ })}-dropdown`;
20682
+ const { dialog, dialogManager } = useDialogOnNearestManager({
20683
+ id: dialogId2
20684
+ });
20685
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20686
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20687
+ /* @__PURE__ */ jsxRuntime.jsx(
20688
+ DialogAnchor,
20689
+ {
20690
+ dialogManagerId: dialogManager?.id,
20691
+ id: dialogId2,
20692
+ offset: 8,
20693
+ placement: isMyMessage() ? "top-end" : "top-start",
20694
+ referenceElement,
20695
+ trapFocus: true,
20696
+ updatePositionOnContentResize: true,
20697
+ children: /* @__PURE__ */ jsxRuntime.jsx(ReactionSelector$1, { dialogId: dialogId2 })
20698
+ }
20699
+ ),
20700
+ /* @__PURE__ */ jsxRuntime.jsx(
20701
+ ContextMenuButton,
20702
+ {
20703
+ "aria-expanded": dialogIsOpen,
20704
+ "aria-label": t("aria/Open Reaction Selector"),
20705
+ className: clsx(
20706
+ msgActionsBoxButtonClassName,
20707
+ "str-chat__message-actions-list-item-button--react"
20708
+ ),
20709
+ "data-testid": "dropdown-react-action",
20710
+ Icon: WithAudioPlayback.IconEmoji,
20711
+ onClick: (event) => {
20712
+ if (dialogIsOpen) {
20713
+ dialog.close();
20714
+ return;
20715
+ }
20716
+ setReferenceElement(
20717
+ anchorReferenceElement instanceof HTMLElement ? anchorReferenceElement : event.currentTarget
20718
+ );
20719
+ dialog.open();
20720
+ },
20721
+ children: t("Add reaction")
20722
+ }
20723
+ )
20724
+ ] });
20725
+ },
20726
+ ThreadReply() {
20727
+ const { closeMenu } = useContextMenuContext();
20728
+ const { handleOpenThread } = useMessageContext();
20729
+ const { t } = WithAudioPlayback.useTranslationContext();
20334
20730
  return /* @__PURE__ */ jsxRuntime.jsx(
20335
20731
  ContextMenuButton,
20336
20732
  {
20337
- "aria-label": isMuted ? t("aria/Unmute User") : t("aria/Mute User"),
20733
+ "aria-label": t("aria/Open Thread"),
20338
20734
  className: msgActionsBoxButtonClassName,
20339
- Icon: isMuted ? WithAudioPlayback.IconAudio : WithAudioPlayback.IconMute,
20735
+ "data-testid": "thread-action",
20736
+ Icon: WithAudioPlayback.IconThread,
20737
+ onClick: (e) => {
20738
+ handleOpenThread(e);
20739
+ closeMenu();
20740
+ },
20741
+ children: t("Thread Reply")
20742
+ }
20743
+ );
20744
+ },
20745
+ Quote() {
20746
+ const { closeMenu } = useContextMenuContext();
20747
+ const { message } = useMessageContext();
20748
+ const { t } = WithAudioPlayback.useTranslationContext();
20749
+ const messageComposer = WithAudioPlayback.useMessageComposerController();
20750
+ const handleQuote = () => {
20751
+ messageComposer.setQuotedMessage(message);
20752
+ const elements = message.parent_id ? document.querySelectorAll(".str-chat__thread .str-chat__textarea__textarea") : document.getElementsByClassName("str-chat__textarea__textarea");
20753
+ const textarea = elements.item(0);
20754
+ if (textarea instanceof HTMLTextAreaElement) {
20755
+ textarea.focus();
20756
+ }
20757
+ };
20758
+ return /* @__PURE__ */ jsxRuntime.jsx(
20759
+ ContextMenuButton,
20760
+ {
20761
+ "aria-label": t("aria/Quote Message"),
20762
+ className: msgActionsBoxButtonClassName,
20763
+ Icon: WithAudioPlayback.IconQuote,
20764
+ onClick: () => {
20765
+ handleQuote();
20766
+ closeMenu();
20767
+ },
20768
+ children: t("Quote Reply")
20769
+ }
20770
+ );
20771
+ },
20772
+ Pin() {
20773
+ const { closeMenu } = useContextMenuContext();
20774
+ const { handlePin, message } = useMessageContext();
20775
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
20776
+ const { t } = WithAudioPlayback.useTranslationContext();
20777
+ const isPinned = !!message.pinned;
20778
+ return /* @__PURE__ */ jsxRuntime.jsx(
20779
+ ContextMenuButton,
20780
+ {
20781
+ "aria-label": isPinned ? t("aria/Unpin Message") : t("aria/Pin Message"),
20782
+ className: msgActionsBoxButtonClassName,
20783
+ Icon: isPinned ? WithAudioPlayback.IconUnpin : WithAudioPlayback.IconPin,
20340
20784
  onClick: async (event) => {
20341
20785
  try {
20342
- await handleMute(event);
20786
+ await handlePin(event);
20343
20787
  addNotification({
20344
20788
  context: {
20345
20789
  message
20346
20790
  },
20347
20791
  emitter: "MessageActions",
20348
- message: isMuted ? t("{{ user }} has been unmuted", {
20349
- user: message.user?.name || message.user?.id
20350
- }) : t("{{ user }} has been muted", {
20351
- user: message.user?.name || message.user?.id
20352
- }),
20792
+ message: isPinned ? t("Message unpinned") : t("Message pinned"),
20353
20793
  severity: "success",
20354
- type: isMuted ? "api:user:unmute:success" : "api:user:mute:success"
20794
+ type: isPinned ? "api:message:unpin:success" : "api:message:pin:success"
20355
20795
  });
20356
20796
  } catch (error) {
20357
20797
  addNotification({
@@ -20362,818 +20802,553 @@ const DefaultMessageActionComponents = {
20362
20802
  error: getNotificationError(error),
20363
20803
  message: getErrorMessage(
20364
20804
  error,
20365
- isMuted ? t("Error unmuting a user ...") : t("Error muting a user ...")
20805
+ isPinned ? t("Error removing message pin") : t("Error pinning message")
20366
20806
  ),
20367
20807
  severity: "error",
20368
- type: isMuted ? "api:user:unmute:failed" : "api:user:mute:failed"
20808
+ type: isPinned ? "api:message:unpin:failed" : "api:message:pin:failed"
20369
20809
  });
20370
20810
  }
20371
20811
  closeMenu();
20372
20812
  },
20373
- children: isMuted ? t("Unmute") : t("Mute")
20813
+ children: isPinned ? t("Unpin") : t("Pin")
20374
20814
  }
20375
20815
  );
20376
20816
  },
20377
- Delete() {
20378
- const { closeMenu } = useContextMenuContext();
20379
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20380
- const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
20381
- const { handleDelete, message } = useMessageContext();
20382
- const { t } = WithAudioPlayback.useTranslationContext();
20383
- const [openModal, setOpenModal] = React.useState(false);
20384
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
20385
- /* @__PURE__ */ jsxRuntime.jsx(
20386
- ContextMenuButton,
20387
- {
20388
- "aria-label": t("aria/Delete Message"),
20389
- className: msgActionsBoxButtonClassName,
20390
- Icon: WithAudioPlayback.IconDelete,
20391
- onClick: () => {
20392
- setOpenModal(true);
20393
- },
20394
- variant: "destructive",
20395
- children: t("Delete message")
20396
- }
20397
- ),
20398
- /* @__PURE__ */ jsxRuntime.jsx(Modal, { open: openModal, children: /* @__PURE__ */ jsxRuntime.jsx(
20399
- DeleteMessageAlert,
20400
- {
20401
- onCancel: () => {
20402
- setOpenModal(false);
20403
- closeMenu();
20404
- },
20405
- onDelete: async () => {
20406
- try {
20407
- await handleDelete();
20408
- addNotification({
20409
- context: {
20410
- message
20411
- },
20412
- emitter: "MessageActions",
20413
- message: t("Message deleted"),
20414
- severity: "success",
20415
- type: "api:message:delete:success"
20416
- });
20417
- } catch (error) {
20418
- addNotification({
20419
- context: {
20420
- message
20421
- },
20422
- emitter: "MessageActions",
20423
- error: getNotificationError(error),
20424
- message: getErrorMessage(error, t("Error deleting message")),
20425
- severity: "error",
20426
- type: "api:message:delete:failed"
20427
- });
20428
- } finally {
20429
- setOpenModal(false);
20430
- closeMenu();
20431
- }
20432
- }
20433
- }
20434
- ) })
20435
- ] });
20436
- },
20437
- BlockUser() {
20817
+ CopyMessageText() {
20438
20818
  const { closeMenu } = useContextMenuContext();
20439
- const { client } = WithAudioPlayback.useChatContext();
20440
20819
  const { message } = useMessageContext();
20441
20820
  const { t } = WithAudioPlayback.useTranslationContext();
20442
- const isBlocked = !message.user?.id || new Set(client.blockedUsers.getLatestValue().userIds).has(message.user?.id);
20443
20821
  return /* @__PURE__ */ jsxRuntime.jsx(
20444
20822
  ContextMenuButton,
20445
20823
  {
20446
- "aria-label": isBlocked ? t("aria/Unblock User") : t("aria/Block User"),
20447
- className: clsx(msgActionsBoxButtonClassName),
20448
- Icon: isBlocked ? WithAudioPlayback.IconUserCheck : WithAudioPlayback.IconNoSign,
20824
+ "aria-label": t("aria/Copy Message Text"),
20825
+ className: msgActionsBoxButtonClassName,
20826
+ Icon: WithAudioPlayback.IconCopy,
20449
20827
  onClick: () => {
20450
- const targetId = message.user?.id;
20451
- if (targetId) {
20452
- if (isBlocked) client.unBlockUser(targetId);
20453
- else client.blockUser(targetId);
20454
- }
20828
+ if (message.text) navigator.clipboard.writeText(message.text);
20455
20829
  closeMenu();
20456
20830
  },
20457
- children: isBlocked ? t("Unblock User") : t("Block User")
20831
+ children: t("Copy Message")
20458
20832
  }
20459
20833
  );
20460
- }
20461
- },
20462
- quick: {
20463
- // eslint-disable-next-line react/display-name
20464
- DropdownToggle: React.forwardRef((_, ref) => {
20834
+ },
20835
+ Resend() {
20836
+ const { closeMenu } = useContextMenuContext();
20837
+ const { handleRetry, message } = useMessageContext();
20465
20838
  const { t } = WithAudioPlayback.useTranslationContext();
20466
- const { message } = useMessageContext();
20467
- const dropdownDialogIsOpen = useDialogIsOpen(
20468
- MessageActions.getDialogId({ messageId: message.id })
20469
- );
20470
- const { dialog } = useDialogOnNearestManager({
20471
- id: MessageActions.getDialogId({ messageId: message.id })
20472
- });
20473
20839
  return /* @__PURE__ */ jsxRuntime.jsx(
20474
- QuickMessageActionsButton,
20840
+ ContextMenuButton,
20475
20841
  {
20476
- "aria-expanded": dropdownDialogIsOpen,
20477
- "aria-haspopup": "true",
20478
- "aria-label": t("aria/Open Message Actions Menu"),
20479
- className: "str-chat__message-actions-box-button",
20480
- "data-testid": "message-actions-toggle-button",
20842
+ "aria-label": t("aria/Resend Message"),
20843
+ className: msgActionsBoxButtonClassName,
20844
+ Icon: WithAudioPlayback.IconRetry,
20481
20845
  onClick: () => {
20482
- dialog?.toggle();
20846
+ handleRetry(message);
20847
+ closeMenu();
20483
20848
  },
20484
- ref,
20485
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMore, { className: "str-chat__message-action-icon" })
20849
+ children: t("Resend")
20486
20850
  }
20487
20851
  );
20488
- }),
20489
- React() {
20490
- return /* @__PURE__ */ jsxRuntime.jsx(ReactionSelectorWithButton, { ReactionIcon: WithAudioPlayback.IconEmoji });
20491
20852
  },
20492
- Reply() {
20493
- const { handleOpenThread } = useMessageContext();
20853
+ Edit() {
20854
+ const messageComposer = WithAudioPlayback.useMessageComposerController();
20855
+ const { message } = useMessageContext();
20494
20856
  const { t } = WithAudioPlayback.useTranslationContext();
20857
+ const { closeMenu } = useContextMenuContext();
20495
20858
  return /* @__PURE__ */ jsxRuntime.jsx(
20496
- QuickMessageActionsButton,
20859
+ ContextMenuButton,
20497
20860
  {
20498
- "aria-label": t("aria/Open Thread"),
20499
- className: "str-chat__message-reply-in-thread-button",
20500
- "data-testid": "thread-action",
20501
- onClick: handleOpenThread,
20502
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconReply, { className: "str-chat__message-action-icon" })
20861
+ "aria-label": t("aria/Edit Message"),
20862
+ className: msgActionsBoxButtonClassName,
20863
+ Icon: WithAudioPlayback.IconEdit,
20864
+ onClick: () => {
20865
+ savePreEditSnapshot(messageComposer);
20866
+ messageComposer.initState({ composition: message });
20867
+ closeMenu();
20868
+ },
20869
+ children: t("Edit Message")
20503
20870
  }
20504
20871
  );
20505
- }
20506
- }
20507
- };
20508
- const defaultMessageActionSet = [
20509
- {
20510
- Component: DefaultMessageActionComponents.quick.DropdownToggle,
20511
- placement: "quick-dropdown-toggle"
20512
- },
20513
- {
20514
- Component: DefaultMessageActionComponents.quick.Reply,
20515
- placement: "quick",
20516
- type: "reply"
20517
- },
20518
- {
20519
- Component: DefaultMessageActionComponents.quick.React,
20520
- placement: "quick",
20521
- type: "react"
20522
- },
20523
- {
20524
- Component: DefaultMessageActionComponents.dropdown.ThreadReply,
20525
- placement: "dropdown",
20526
- type: "reply"
20527
- },
20528
- {
20529
- Component: DefaultMessageActionComponents.dropdown.Quote,
20530
- placement: "dropdown",
20531
- type: "quote"
20532
- },
20533
- {
20534
- Component: DefaultMessageActionComponents.dropdown.Pin,
20535
- placement: "dropdown",
20536
- type: "pin"
20537
- },
20538
- {
20539
- Component: DefaultMessageActionComponents.dropdown.CopyMessageText,
20540
- placement: "dropdown",
20541
- type: "copyMessageText"
20542
- },
20543
- {
20544
- Component: DefaultMessageActionComponents.dropdown.Resend,
20545
- placement: "dropdown",
20546
- type: "resendMessage"
20547
- },
20548
- {
20549
- Component: DefaultMessageActionComponents.dropdown.Edit,
20550
- placement: "dropdown",
20551
- type: "edit"
20552
- },
20553
- {
20554
- Component: DefaultMessageActionComponents.dropdown.MarkUnread,
20555
- placement: "dropdown",
20556
- type: "markUnread"
20557
- },
20558
- {
20559
- Component: DefaultMessageActionComponents.dropdown.RemindMe,
20560
- placement: "dropdown",
20561
- type: "remindMe"
20562
- },
20563
- {
20564
- Component: DefaultMessageActionComponents.dropdown.SaveForLater,
20565
- placement: "dropdown",
20566
- type: "saveForLater"
20567
- },
20568
- {
20569
- Component: DefaultMessageActionComponents.dropdown.Flag,
20570
- placement: "dropdown",
20571
- type: "flag"
20572
- },
20573
- {
20574
- Component: DefaultMessageActionComponents.dropdown.Mute,
20575
- placement: "dropdown",
20576
- type: "mute"
20577
- },
20578
- {
20579
- Component: DefaultMessageActionComponents.dropdown.Delete,
20580
- placement: "dropdown",
20581
- type: "delete"
20582
- },
20583
- {
20584
- Component: DefaultMessageActionComponents.dropdown.BlockUser,
20585
- placement: "dropdown",
20586
- type: "blockUser"
20587
- }
20588
- ];
20589
- function useFetchReactions(options) {
20590
- const { addNotification } = WithAudioPlayback.useNotificationApi();
20591
- const { handleFetchReactions: contextHandleFetchReactions } = useMessageContext();
20592
- const { t } = WithAudioPlayback.useTranslationContext("useFetchReactions");
20593
- const [reactions, setReactions] = React.useState([]);
20594
- const {
20595
- handleFetchReactions: propHandleFetchReactions,
20596
- reactionType,
20597
- shouldFetch,
20598
- sort
20599
- } = options;
20600
- const [isLoading, setIsLoading] = React.useState(shouldFetch);
20601
- const handleFetchReactions = propHandleFetchReactions ?? contextHandleFetchReactions;
20602
- const [refetchNonce, setRefetchNonce] = React.useState(null);
20603
- React.useEffect(() => {
20604
- if (!shouldFetch) {
20605
- return;
20606
- }
20607
- let cancel = false;
20608
- (async () => {
20609
- try {
20610
- setIsLoading(true);
20611
- const reactions2 = await handleFetchReactions(reactionType ?? void 0, sort);
20612
- if (!cancel) {
20613
- setReactions(reactions2);
20614
- }
20615
- } catch (e) {
20616
- if (!cancel) {
20617
- setReactions([]);
20618
- addNotification({
20619
- emitter: "Reactions",
20620
- error: e instanceof Error ? e : void 0,
20621
- message: t("Error fetching reactions"),
20622
- severity: "error",
20623
- type: "api:message:reactions:fetch:failed"
20624
- });
20625
- }
20626
- } finally {
20627
- if (!cancel) {
20628
- setIsLoading(false);
20872
+ },
20873
+ MarkUnread() {
20874
+ const { closeMenu } = useContextMenuContext();
20875
+ const { handleMarkUnread, message } = useMessageContext();
20876
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
20877
+ const { t } = WithAudioPlayback.useTranslationContext();
20878
+ return /* @__PURE__ */ jsxRuntime.jsx(
20879
+ ContextMenuButton,
20880
+ {
20881
+ "aria-label": t("aria/Mark Message Unread"),
20882
+ className: msgActionsBoxButtonClassName,
20883
+ Icon: WithAudioPlayback.IconNotification,
20884
+ onClick: async (event) => {
20885
+ try {
20886
+ await handleMarkUnread(event);
20887
+ addNotification({
20888
+ context: {
20889
+ message
20890
+ },
20891
+ emitter: "MessageActions",
20892
+ message: t("Message marked as unread"),
20893
+ severity: "success",
20894
+ type: "api:message:markUnread:success"
20895
+ });
20896
+ } catch (error) {
20897
+ addNotification({
20898
+ context: {
20899
+ message
20900
+ },
20901
+ emitter: "MessageActions",
20902
+ error: getNotificationError(error),
20903
+ message: getErrorMessage(
20904
+ error,
20905
+ t(
20906
+ "Error marking message unread. Cannot mark unread messages older than the newest 100 channel messages."
20907
+ )
20908
+ ),
20909
+ severity: "error",
20910
+ type: "api:message:markUnread:failed"
20911
+ });
20912
+ }
20913
+ closeMenu();
20914
+ },
20915
+ children: t("Mark as unread")
20629
20916
  }
20630
- }
20631
- })();
20632
- return () => {
20633
- cancel = true;
20634
- };
20635
- }, [
20636
- addNotification,
20637
- handleFetchReactions,
20638
- reactionType,
20639
- refetchNonce,
20640
- shouldFetch,
20641
- sort,
20642
- t
20643
- ]);
20644
- const refetch = React.useCallback(() => {
20645
- setRefetchNonce(Math.random());
20646
- }, []);
20647
- return { isLoading, reactions, refetch };
20648
- }
20649
- const defaultReactionDetailsSort = { created_at: -1 };
20650
- const MessageReactionsDetailLoadingIndicator = () => {
20651
- const elements = React.useMemo(
20652
- () => Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__skeleton-item", children: [
20653
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-avatar" }),
20654
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__skeleton-line" })
20655
- ] }, index)),
20656
- []
20657
- );
20658
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: elements });
20659
- };
20660
- const MessageReactionsDetail = ({
20661
- handleFetchReactions,
20662
- handleReaction,
20663
- onSelectedReactionTypeChange,
20664
- own_reactions,
20665
- reactionDetailsSort: propReactionDetailsSort,
20666
- reactionGroups,
20667
- reactions,
20668
- selectedReactionType,
20669
- totalReactionCount
20670
- }) => {
20671
- const [extendedReactionListOpen, setExtendedReactionListOpen] = React.useState(false);
20672
- const { client } = WithAudioPlayback.useChatContext();
20673
- const {
20674
- Avatar: Avatar$1 = Avatar,
20675
- LoadingIndicator: LoadingIndicator2 = MessageReactionsDetailLoadingIndicator,
20676
- reactionOptions = defaultReactionOptions,
20677
- ReactionSelectorExtendedList: ReactionSelectorExtendedList2 = ReactionSelector.ExtendedList
20678
- } = WithAudioPlayback.useComponentContext(MessageReactionsDetail.name);
20679
- const { t } = WithAudioPlayback.useTranslationContext();
20680
- const {
20681
- handleReaction: contextHandleReaction,
20682
- message,
20683
- reactionDetailsSort: contextReactionDetailsSort
20684
- } = useMessageContext(MessageReactionsDetail.name);
20685
- const reactionDetailsSort = propReactionDetailsSort ?? contextReactionDetailsSort ?? defaultReactionDetailsSort;
20686
- const {
20687
- isLoading: areReactionsLoading,
20688
- reactions: reactionDetails,
20689
- refetch
20690
- } = useFetchReactions({
20691
- handleFetchReactions,
20692
- reactionType: selectedReactionType,
20693
- shouldFetch: true,
20694
- sort: reactionDetailsSort
20695
- });
20696
- if (extendedReactionListOpen) {
20697
- return /* @__PURE__ */ jsxRuntime.jsx(
20698
- "div",
20699
- {
20700
- className: "str-chat__message-reactions-detail",
20701
- "data-testid": "message-reactions-detail",
20702
- children: /* @__PURE__ */ jsxRuntime.jsx(
20703
- ReactionSelectorExtendedList2,
20704
- {
20705
- dialogId: MessageReactionsDetail.getDialogId({ messageId: message.id }),
20706
- handleReaction,
20707
- own_reactions
20708
- }
20709
- )
20710
- }
20711
- );
20712
- }
20713
- return /* @__PURE__ */ jsxRuntime.jsxs(
20714
- "div",
20715
- {
20716
- className: "str-chat__message-reactions-detail",
20717
- "data-testid": "message-reactions-detail",
20718
- children: [
20719
- typeof totalReactionCount === "number" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__total-count", children: t("{{ count }} reactions", { count: totalReactionCount }) }),
20720
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__reaction-type-list-container", children: /* @__PURE__ */ jsxRuntime.jsxs(
20721
- "ul",
20722
- {
20723
- className: "str-chat__message-reactions-detail__reaction-type-list",
20724
- "data-testid": "reaction-type-list",
20725
- children: [
20726
- /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__message-reactions-detail__reaction-type-list-item", children: /* @__PURE__ */ jsxRuntime.jsx(
20727
- "button",
20728
- {
20729
- "aria-label": t("Add reaction"),
20730
- className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20731
- "data-testid": "add-reaction-button",
20732
- onClick: () => setExtendedReactionListOpen(true),
20733
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconEmojiAdd, {}) })
20734
- }
20735
- ) }),
20736
- reactions.map(
20737
- ({ EmojiComponent, reactionCount, reactionType }) => EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(
20738
- "li",
20739
- {
20740
- className: "str-chat__message-reactions-detail__reaction-type-list-item",
20741
- children: /* @__PURE__ */ jsxRuntime.jsxs(
20742
- "button",
20743
- {
20744
- "aria-pressed": reactionType === selectedReactionType,
20745
- className: "str-chat__message-reactions-detail__reaction-type-list-item-button",
20746
- onClick: () => onSelectedReactionTypeChange?.(
20747
- selectedReactionType === reactionType ? null : reactionType
20748
- ),
20749
- children: [
20750
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__reaction-type-list-item-icon", children: /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) }),
20751
- /* @__PURE__ */ jsxRuntime.jsx(
20752
- "span",
20753
- {
20754
- className: "str-chat__message-reactions-detail__reaction-type-list-item-count",
20755
- "data-testid": "reaction-type-count",
20756
- children: reactionCount
20757
- }
20758
- )
20759
- ]
20760
- }
20761
- )
20917
+ );
20918
+ },
20919
+ RemindMe() {
20920
+ const { closeMenu, openSubmenu } = useContextMenuContext();
20921
+ const { client } = WithAudioPlayback.useChatContext();
20922
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
20923
+ const { t } = WithAudioPlayback.useTranslationContext();
20924
+ const { message } = useMessageContext();
20925
+ const reminder = useMessageReminder(message.id);
20926
+ const messageAlreadyBookmarked = reminder && !reminder?.remindAt;
20927
+ if (messageAlreadyBookmarked) return null;
20928
+ return /* @__PURE__ */ jsxRuntime.jsx(
20929
+ ContextMenuButton,
20930
+ {
20931
+ "aria-label": reminder ? t("aria/Remind Me Message") : t("aria/Remove Reminder"),
20932
+ className: msgActionsBoxButtonClassName,
20933
+ hasSubMenu: !reminder,
20934
+ Icon: reminder ? WithAudioPlayback.IconBellOff : WithAudioPlayback.IconBell,
20935
+ onClick: async () => {
20936
+ if (reminder) {
20937
+ try {
20938
+ await client.reminders.deleteReminder(reminder.id);
20939
+ addNotification({
20940
+ context: {
20941
+ message
20762
20942
  },
20763
- reactionType
20764
- )
20765
- )
20766
- ]
20767
- }
20768
- ) }),
20769
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "str-chat__message-reactions-detail__user-list-container", children: /* @__PURE__ */ jsxRuntime.jsxs(
20770
- "div",
20771
- {
20772
- className: "str-chat__message-reactions-detail__user-list",
20773
- "data-testid": "all-reacting-users",
20774
- children: [
20775
- areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator2, {}),
20776
- !areReactionsLoading && /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: reactionDetails.map(({ type, user }) => {
20777
- const belongsToCurrentUser = client.user?.id === user?.id;
20778
- const EmojiComponent = Array.isArray(reactionOptions) ? void 0 : reactionOptions.quick[type]?.Component ?? reactionOptions.extended?.[type]?.Component;
20779
- return /* @__PURE__ */ jsxRuntime.jsxs(
20780
- "div",
20781
- {
20782
- className: "str-chat__message-reactions-detail__user-list-item",
20783
- children: [
20784
- /* @__PURE__ */ jsxRuntime.jsx(
20785
- Avatar$1,
20786
- {
20787
- className: "str-chat__avatar--with-border",
20788
- "data-testid": "avatar",
20789
- imageUrl: user?.image,
20790
- size: "md",
20791
- userName: user?.name || user?.id
20792
- }
20793
- ),
20794
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "str-chat__message-reactions-detail__user-list-item-info", children: [
20795
- /* @__PURE__ */ jsxRuntime.jsx(
20796
- "span",
20797
- {
20798
- className: "str-chat__message-reactions-detail__user-list-item-username",
20799
- "data-testid": "reaction-user-username",
20800
- children: belongsToCurrentUser ? t("You") : user?.name || user?.id
20801
- }
20802
- ),
20803
- belongsToCurrentUser && /* @__PURE__ */ jsxRuntime.jsx(
20804
- "button",
20805
- {
20806
- className: "str-chat__message-reactions-detail__user-list-item-button",
20807
- "data-testid": "remove-reaction-button",
20808
- onClick: async (e) => {
20809
- const reactionCountBeforeRemoval = reactionGroups?.[type]?.count ?? 0;
20810
- await contextHandleReaction(type, e);
20811
- if (selectedReactionType !== null && reactionCountBeforeRemoval <= 1) {
20812
- onSelectedReactionTypeChange?.(null);
20813
- } else {
20814
- refetch();
20815
- }
20816
- },
20817
- children: t("Tap to remove")
20818
- }
20819
- )
20820
- ] }),
20821
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "str-chat__message-reactions-detail__user-list-item-icon", children: !selectedReactionType && EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {}) })
20822
- ]
20943
+ emitter: "MessageActions",
20944
+ message: t("Remove reminder"),
20945
+ severity: "success",
20946
+ type: "api:message:reminder:delete:success"
20947
+ });
20948
+ } catch (error) {
20949
+ addNotification({
20950
+ context: {
20951
+ message
20823
20952
  },
20824
- `${user?.id}-${type}`
20825
- );
20826
- }) })
20827
- ]
20828
- }
20829
- ) })
20830
- ]
20831
- }
20832
- );
20833
- };
20834
- MessageReactionsDetail.displayName = "MessageReactionsDetail";
20835
- MessageReactionsDetail.getDialogId = ({ messageId }) => `message-reactions-detail-${messageId}`;
20836
- const defaultReactionsSort = (a, b) => {
20837
- if (a.firstReactionAt && b.firstReactionAt) {
20838
- return +a.firstReactionAt - +b.firstReactionAt;
20839
- }
20840
- return a.reactionType.localeCompare(b.reactionType, "en");
20841
- };
20842
- const useProcessReactions = (params) => {
20843
- const {
20844
- own_reactions: propOwnReactions,
20845
- reaction_groups: propReactionGroups,
20846
- reactions: propReactions,
20847
- sortReactions: propSortReactions
20848
- } = params;
20849
- const { message, sortReactions: contextSortReactions } = useMessageContext();
20850
- const { reactionOptions = defaultReactionOptions } = WithAudioPlayback.useComponentContext();
20851
- const sortReactions = propSortReactions ?? contextSortReactions ?? defaultReactionsSort;
20852
- const latestReactions = propReactions ?? message.latest_reactions;
20853
- const ownReactions = propOwnReactions ?? message?.own_reactions;
20854
- const reactionGroups = propReactionGroups ?? message?.reaction_groups ?? void 0;
20855
- const isOwnReaction = React.useCallback(
20856
- (reactionType) => ownReactions?.some((reaction) => reaction.type === reactionType) ?? false,
20857
- [ownReactions]
20858
- );
20859
- const getEmojiByReactionType = React.useCallback(
20860
- (reactionType) => {
20861
- if (Array.isArray(reactionOptions)) {
20862
- return reactionOptions.find(({ type }) => type === reactionType)?.Component ?? null;
20863
- }
20864
- return reactionOptions.quick[reactionType]?.Component ?? reactionOptions.extended?.[reactionType]?.Component ?? null;
20865
- },
20866
- [reactionOptions]
20867
- );
20868
- const isSupportedReaction = React.useCallback(
20869
- (reactionType) => {
20870
- if (Array.isArray(reactionOptions)) {
20871
- return reactionOptions.some(
20872
- (reactionOption) => reactionOption.type === reactionType
20873
- );
20874
- }
20875
- return typeof reactionOptions.quick[reactionType] !== "undefined" || typeof reactionOptions.extended?.[reactionType] !== "undefined";
20876
- },
20877
- [reactionOptions]
20878
- );
20879
- const uniqueReactionTypeCount = React.useMemo(() => {
20880
- if (!reactionGroups) {
20881
- return 0;
20882
- }
20883
- return Object.keys(reactionGroups).filter(
20884
- (reactionType) => isSupportedReaction(reactionType)
20885
- ).length;
20886
- }, [isSupportedReaction, reactionGroups]);
20887
- const getLatestReactedUserNames = React.useCallback(
20888
- (reactionType) => latestReactions?.flatMap((reaction) => {
20889
- if (reactionType && reactionType === reaction.type) {
20890
- const username = reaction.user?.name || reaction.user?.id;
20891
- return username ? [username] : [];
20892
- }
20893
- return [];
20894
- }) ?? [],
20895
- [latestReactions]
20896
- );
20897
- const existingReactions = React.useMemo(() => {
20898
- if (!reactionGroups) {
20899
- return [];
20900
- }
20901
- const unsortedReactions = Object.entries(reactionGroups).flatMap(
20902
- ([reactionType, { count, first_reaction_at, last_reaction_at }]) => {
20903
- if (count === 0 || !isSupportedReaction(reactionType)) {
20904
- return [];
20953
+ emitter: "MessageActions",
20954
+ error: getNotificationError(error),
20955
+ message: getErrorMessage(error, "Error removing reminder"),
20956
+ severity: "error",
20957
+ type: "api:message:reminder:delete:failed"
20958
+ });
20959
+ } finally {
20960
+ closeMenu();
20961
+ }
20962
+ } else {
20963
+ openSubmenu({
20964
+ Header: RemindMeSubmenuHeader,
20965
+ Submenu: RemindMeSubmenu
20966
+ });
20967
+ }
20968
+ },
20969
+ children: reminder ? t("Remove reminder") : t("Remind me")
20905
20970
  }
20906
- const latestReactedUserNames = getLatestReactedUserNames(reactionType);
20907
- return [
20908
- {
20909
- EmojiComponent: getEmojiByReactionType(reactionType),
20910
- firstReactionAt: first_reaction_at ? new Date(first_reaction_at) : null,
20911
- isOwnReaction: isOwnReaction(reactionType),
20912
- lastReactionAt: last_reaction_at ? new Date(last_reaction_at) : null,
20913
- latestReactedUserNames,
20914
- reactionCount: count,
20915
- reactionType,
20916
- unlistedReactedUserCount: count - latestReactedUserNames.length
20917
- }
20918
- ];
20919
- }
20920
- );
20921
- return unsortedReactions.sort(sortReactions);
20922
- }, [
20923
- getEmojiByReactionType,
20924
- getLatestReactedUserNames,
20925
- isOwnReaction,
20926
- isSupportedReaction,
20927
- reactionGroups,
20928
- sortReactions
20929
- ]);
20930
- const hasReactions = existingReactions.length > 0;
20931
- const totalReactionCount = React.useMemo(
20932
- () => Object.values(reactionGroups ?? {}).reduce((total, { count }) => total + count, 0),
20933
- [reactionGroups]
20934
- );
20935
- return {
20936
- existingReactions,
20937
- hasReactions,
20938
- reactionGroups,
20939
- totalReactionCount,
20940
- uniqueReactionTypeCount
20941
- };
20942
- };
20943
- const FragmentOrButton = ({
20944
- buttonIf: renderButton = false,
20945
- children,
20946
- ...props
20947
- }) => {
20948
- if (renderButton) {
20949
- return /* @__PURE__ */ jsxRuntime.jsx("button", { ...props, children });
20950
- }
20951
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
20952
- };
20953
- const UnMemoizedMessageReactions = (props) => {
20954
- const {
20955
- capLimit: { clustered: capLimitClustered = 5, segmented: capLimitSegmented = 4 } = {},
20956
- flipHorizontalPosition = false,
20957
- handleFetchReactions,
20958
- reactionDetailsSort,
20959
- verticalPosition = "top",
20960
- visualStyle = "clustered",
20961
- ...rest
20962
- } = props;
20963
- const {
20964
- existingReactions,
20965
- hasReactions,
20966
- reactionGroups,
20967
- totalReactionCount,
20968
- uniqueReactionTypeCount
20969
- } = useProcessReactions(rest);
20970
- const [selectedReactionType, setSelectedReactionType] = React.useState(
20971
- null
20972
- );
20973
- const { t } = WithAudioPlayback.useTranslationContext("MessageReactions");
20974
- const { MessageReactionsDetail: MessageReactionsDetail$1 = MessageReactionsDetail } = WithAudioPlayback.useComponentContext();
20975
- const { isMyMessage, message } = useMessageContext();
20976
- const divRef = React.useRef(null);
20977
- const dialogId2 = MessageReactionsDetail.getDialogId({
20978
- messageId: message.id
20979
- });
20980
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
20981
- const isDialogOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
20982
- const handleReactionButtonClick = (reactionType) => {
20983
- if (totalReactionCount > MAX_MESSAGE_REACTIONS_TO_FETCH) {
20984
- return;
20985
- }
20986
- setSelectedReactionType(reactionType);
20987
- dialog.open();
20988
- };
20989
- const cappedExistingReactions = React.useMemo(() => {
20990
- if (visualStyle === "segmented" && verticalPosition !== "top") return null;
20991
- const capLimit = visualStyle === "segmented" ? capLimitSegmented : capLimitClustered;
20992
- const sliced = existingReactions.slice(0, capLimit);
20993
- return {
20994
- /**
20995
- * Accumulated reaction count of capped reaction types, first four in case of
20996
- * segmented(top) and first five in case of clustered(top/bottom) variations.
20997
- */
20998
- reactionCountToDisplay: sliced.reduce(
20999
- (accumulatedCount, { reactionCount }) => accumulatedCount + reactionCount,
21000
- 0
21001
- ),
21002
- reactionsToDisplay: sliced
21003
- };
21004
- }, [
21005
- capLimitClustered,
21006
- capLimitSegmented,
21007
- existingReactions,
21008
- verticalPosition,
21009
- visualStyle
21010
- ]);
21011
- if (!hasReactions) return null;
21012
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21013
- /* @__PURE__ */ jsxRuntime.jsx(
21014
- "div",
21015
- {
21016
- "aria-label": t("aria/Reaction list"),
21017
- className: clsx("str-chat__message-reactions", {
21018
- [`str-chat__message-reactions--flipped-horizontally`]: flipHorizontalPosition,
21019
- [`str-chat__message-reactions--${verticalPosition}`]: typeof verticalPosition === "string",
21020
- [`str-chat__message-reactions--${visualStyle}`]: typeof visualStyle === "string"
21021
- }),
21022
- ref: divRef,
21023
- role: "figure",
21024
- children: /* @__PURE__ */ jsxRuntime.jsxs(
21025
- FragmentOrButton,
21026
- {
21027
- "aria-expanded": isDialogOpen,
21028
- "aria-pressed": isDialogOpen,
21029
- buttonIf: visualStyle === "clustered",
21030
- className: "str-chat__message-reactions__list-button",
21031
- "data-testid": "message-reactions-list-button",
21032
- onClick: () => handleReactionButtonClick(null),
21033
- children: [
21034
- /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "str-chat__message-reactions__list", children: [
21035
- (cappedExistingReactions?.reactionsToDisplay ?? existingReactions).map(
21036
- ({ EmojiComponent, reactionCount, reactionType }) => EmojiComponent && /* @__PURE__ */ jsxRuntime.jsx(
21037
- "li",
21038
- {
21039
- className: "str-chat__message-reactions__list-item",
21040
- "data-testid": "message-reactions-list-item",
21041
- children: /* @__PURE__ */ jsxRuntime.jsxs(
21042
- FragmentOrButton,
21043
- {
21044
- buttonIf: visualStyle === "segmented",
21045
- className: "str-chat__message-reactions__list-item-button",
21046
- onClick: () => handleReactionButtonClick(reactionType),
21047
- children: [
21048
- /* @__PURE__ */ jsxRuntime.jsx(
21049
- "span",
21050
- {
21051
- className: "str-chat__message-reactions__list-item-icon",
21052
- "data-testid": "message-reactions-list-item-icon",
21053
- children: /* @__PURE__ */ jsxRuntime.jsx(EmojiComponent, {})
21054
- }
21055
- ),
21056
- visualStyle === "segmented" && reactionCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(
21057
- "span",
21058
- {
21059
- className: "str-chat__message-reactions__list-item-count",
21060
- "data-testclass": "message-reactions-item-count",
21061
- children: reactionCount
21062
- }
21063
- )
21064
- ]
21065
- }
21066
- )
21067
- },
21068
- reactionType
21069
- )
20971
+ );
20972
+ },
20973
+ SaveForLater() {
20974
+ const { closeMenu } = useContextMenuContext();
20975
+ const { client } = WithAudioPlayback.useChatContext();
20976
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
20977
+ const { message } = useMessageContext();
20978
+ const { t } = WithAudioPlayback.useTranslationContext();
20979
+ const reminder = useMessageReminder(message.id);
20980
+ const messageAlreadyHasReminderScheduled = Boolean(reminder && reminder?.remindAt);
20981
+ if (messageAlreadyHasReminderScheduled) return null;
20982
+ return /* @__PURE__ */ jsxRuntime.jsx(
20983
+ ContextMenuButton,
20984
+ {
20985
+ "aria-label": reminder ? t("aria/Remove Save For Later") : t("aria/Bookmark Message"),
20986
+ className: msgActionsBoxButtonClassName,
20987
+ Icon: reminder ? WithAudioPlayback.IconBookmarkRemove : WithAudioPlayback.IconBookmark,
20988
+ onClick: async () => {
20989
+ try {
20990
+ if (reminder) {
20991
+ await client.reminders.deleteReminder(reminder.id);
20992
+ addNotification({
20993
+ context: {
20994
+ message
20995
+ },
20996
+ emitter: "MessageActions",
20997
+ message: t("Remove save for later"),
20998
+ severity: "success",
20999
+ type: "api:message:saveForLater:delete:success"
21000
+ });
21001
+ } else {
21002
+ await client.reminders.createReminder({ messageId: message.id });
21003
+ addNotification({
21004
+ context: {
21005
+ message
21006
+ },
21007
+ emitter: "MessageActions",
21008
+ message: t("Saved for later"),
21009
+ severity: "success",
21010
+ type: "api:message:saveForLater:create:success"
21011
+ });
21012
+ }
21013
+ } catch (error) {
21014
+ addNotification({
21015
+ context: {
21016
+ message
21017
+ },
21018
+ emitter: "MessageActions",
21019
+ error: getNotificationError(error),
21020
+ message: getErrorMessage(
21021
+ error,
21022
+ reminder ? "Error removing message from saved for later" : "Error saving message for later"
21070
21023
  ),
21071
- uniqueReactionTypeCount > 4 && cappedExistingReactions && visualStyle === "segmented" && /* @__PURE__ */ jsxRuntime.jsx("li", { className: "str-chat__message-reactions__list-item str-chat__message-reactions__list-item--more", children: /* @__PURE__ */ jsxRuntime.jsx(
21072
- "button",
21073
- {
21074
- className: "str-chat__message-reactions__list-item-button",
21075
- onClick: () => handleReactionButtonClick(null),
21076
- children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "str-chat__message-reactions__overflow-count", children: [
21077
- "+",
21078
- totalReactionCount - cappedExistingReactions.reactionCountToDisplay
21079
- ] })
21080
- }
21081
- ) })
21082
- ] }),
21083
- visualStyle === "clustered" && /* @__PURE__ */ jsxRuntime.jsx(
21084
- "span",
21085
- {
21086
- className: "str-chat__message-reactions__total-count",
21087
- "data-testid": "message-reactions-total-count",
21088
- children: totalReactionCount
21089
- }
21090
- )
21091
- ]
21092
- }
21093
- )
21094
- }
21095
- ),
21096
- /* @__PURE__ */ jsxRuntime.jsx(
21097
- DialogAnchor,
21098
- {
21099
- dialogManagerId: dialogManager?.id,
21100
- id: dialogId2,
21101
- offset: 8,
21102
- placement: isMyMessage() ? "bottom-end" : "bottom-start",
21103
- referenceElement: divRef.current,
21104
- trapFocus: true,
21105
- updatePositionOnContentResize: true,
21106
- children: /* @__PURE__ */ jsxRuntime.jsx(
21107
- MessageReactionsDetail$1,
21024
+ severity: "error",
21025
+ type: reminder ? "api:message:saveForLater:delete:failed" : "api:message:saveForLater:create:failed"
21026
+ });
21027
+ } finally {
21028
+ closeMenu();
21029
+ }
21030
+ },
21031
+ children: reminder ? t("Remove save for later") : t("Save for later")
21032
+ }
21033
+ );
21034
+ },
21035
+ Flag() {
21036
+ const { closeMenu } = useContextMenuContext();
21037
+ const { handleFlag, message } = useMessageContext();
21038
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
21039
+ const { t } = WithAudioPlayback.useTranslationContext();
21040
+ return /* @__PURE__ */ jsxRuntime.jsx(
21041
+ ContextMenuButton,
21042
+ {
21043
+ "aria-label": t("aria/Flag Message"),
21044
+ className: msgActionsBoxButtonClassName,
21045
+ Icon: WithAudioPlayback.IconFlag,
21046
+ onClick: async (event) => {
21047
+ try {
21048
+ await handleFlag(event);
21049
+ addNotification({
21050
+ context: {
21051
+ message
21052
+ },
21053
+ emitter: "MessageActions",
21054
+ message: t("Message has been successfully flagged"),
21055
+ severity: "success",
21056
+ type: "api:message:flag:success"
21057
+ });
21058
+ } catch (error) {
21059
+ addNotification({
21060
+ context: {
21061
+ message
21062
+ },
21063
+ emitter: "MessageActions",
21064
+ error: getNotificationError(error),
21065
+ message: getErrorMessage(error, t("Error adding flag")),
21066
+ severity: "error",
21067
+ type: "api:message:flag:failed"
21068
+ });
21069
+ }
21070
+ closeMenu();
21071
+ },
21072
+ children: t("Flag")
21073
+ }
21074
+ );
21075
+ },
21076
+ Mute() {
21077
+ const { closeMenu } = useContextMenuContext();
21078
+ const { handleMute, message } = useMessageContext();
21079
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
21080
+ const { mutes } = WithAudioPlayback.useChatContext();
21081
+ const { t } = WithAudioPlayback.useTranslationContext();
21082
+ const isMuted = WithAudioPlayback.isUserMuted(message, mutes);
21083
+ return /* @__PURE__ */ jsxRuntime.jsx(
21084
+ ContextMenuButton,
21085
+ {
21086
+ "aria-label": isMuted ? t("aria/Unmute User") : t("aria/Mute User"),
21087
+ className: msgActionsBoxButtonClassName,
21088
+ Icon: isMuted ? WithAudioPlayback.IconAudio : WithAudioPlayback.IconMute,
21089
+ onClick: async (event) => {
21090
+ try {
21091
+ await handleMute(event);
21092
+ addNotification({
21093
+ context: {
21094
+ message
21095
+ },
21096
+ emitter: "MessageActions",
21097
+ message: isMuted ? t("{{ user }} has been unmuted", {
21098
+ user: message.user?.name || message.user?.id
21099
+ }) : t("{{ user }} has been muted", {
21100
+ user: message.user?.name || message.user?.id
21101
+ }),
21102
+ severity: "success",
21103
+ type: isMuted ? "api:user:unmute:success" : "api:user:mute:success"
21104
+ });
21105
+ } catch (error) {
21106
+ addNotification({
21107
+ context: {
21108
+ message
21109
+ },
21110
+ emitter: "MessageActions",
21111
+ error: getNotificationError(error),
21112
+ message: getErrorMessage(
21113
+ error,
21114
+ isMuted ? t("Error unmuting a user ...") : t("Error muting a user ...")
21115
+ ),
21116
+ severity: "error",
21117
+ type: isMuted ? "api:user:unmute:failed" : "api:user:mute:failed"
21118
+ });
21119
+ }
21120
+ closeMenu();
21121
+ },
21122
+ children: isMuted ? t("Unmute") : t("Mute")
21123
+ }
21124
+ );
21125
+ },
21126
+ Delete() {
21127
+ const { closeMenu } = useContextMenuContext();
21128
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
21129
+ const { Modal = GlobalModal } = WithAudioPlayback.useComponentContext();
21130
+ const { handleDelete, message } = useMessageContext();
21131
+ const { t } = WithAudioPlayback.useTranslationContext();
21132
+ const [openModal, setOpenModal] = React.useState(false);
21133
+ if (WithAudioPlayback.isMessageDeleted(message)) return null;
21134
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21135
+ /* @__PURE__ */ jsxRuntime.jsx(
21136
+ ContextMenuButton,
21108
21137
  {
21109
- handleFetchReactions,
21110
- onSelectedReactionTypeChange: setSelectedReactionType,
21111
- reactionDetailsSort,
21112
- reactionGroups,
21113
- reactions: existingReactions,
21114
- selectedReactionType,
21115
- totalReactionCount
21138
+ "aria-label": t("aria/Delete Message"),
21139
+ className: msgActionsBoxButtonClassName,
21140
+ Icon: WithAudioPlayback.IconDelete,
21141
+ onClick: () => {
21142
+ setOpenModal(true);
21143
+ },
21144
+ variant: "destructive",
21145
+ children: t("Delete message")
21116
21146
  }
21117
- )
21118
- }
21119
- )
21120
- ] });
21121
- };
21122
- const MessageReactions = React.memo(
21123
- UnMemoizedMessageReactions
21124
- );
21125
- const getImageDimensions = (source) => new Promise((resolve, reject) => {
21126
- const image = new Image();
21127
- image.addEventListener(
21128
- "load",
21129
- () => {
21130
- resolve([image.width, image.height]);
21147
+ ),
21148
+ /* @__PURE__ */ jsxRuntime.jsx(Modal, { open: openModal, children: /* @__PURE__ */ jsxRuntime.jsx(
21149
+ DeleteMessageAlert,
21150
+ {
21151
+ onCancel: () => {
21152
+ setOpenModal(false);
21153
+ closeMenu();
21154
+ },
21155
+ onDelete: async () => {
21156
+ try {
21157
+ await handleDelete();
21158
+ addNotification({
21159
+ context: {
21160
+ message
21161
+ },
21162
+ emitter: "MessageActions",
21163
+ message: t("Message deleted"),
21164
+ severity: "success",
21165
+ type: "api:message:delete:success"
21166
+ });
21167
+ } catch (error) {
21168
+ addNotification({
21169
+ context: {
21170
+ message
21171
+ },
21172
+ emitter: "MessageActions",
21173
+ error: getNotificationError(error),
21174
+ message: getErrorMessage(error, t("Error deleting message")),
21175
+ severity: "error",
21176
+ type: "api:message:delete:failed"
21177
+ });
21178
+ } finally {
21179
+ setOpenModal(false);
21180
+ closeMenu();
21181
+ }
21182
+ }
21183
+ }
21184
+ ) })
21185
+ ] });
21131
21186
  },
21132
- { once: true }
21133
- );
21134
- image.addEventListener("error", () => reject(`Couldn't load image from ${source}`), {
21135
- once: true
21136
- });
21137
- image.src = source;
21138
- });
21139
- const SpriteImage = ({
21140
- columns,
21141
- fallback,
21142
- height,
21143
- position,
21144
- rows,
21145
- spriteUrl,
21146
- style,
21147
- width
21148
- }) => {
21149
- const [[spriteWidth, spriteHeight], setSpriteDimensions] = React.useState([0, 0]);
21150
- React.useEffect(() => {
21151
- getImageDimensions(spriteUrl).then(setSpriteDimensions).catch(console.error);
21152
- }, [spriteUrl]);
21153
- const [x, y] = position;
21154
- if (!spriteHeight || !spriteWidth) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
21155
- return /* @__PURE__ */ jsxRuntime.jsx(
21156
- "div",
21157
- {
21158
- "data-testid": "sprite-image",
21159
- style: {
21160
- ...style,
21161
- "--str-chat__sprite-image-resize-ratio": "var(--str-chat__sprite-image-resize-ratio-x, var(--str-chat__sprite-image-resize-ratio-y, 1))",
21162
- "--str-chat__sprite-image-resize-ratio-x": "calc(var(--str-chat__sprite-image-width) / var(--str-chat__sprite-item-width))",
21163
- "--str-chat__sprite-image-resize-ratio-y": "calc(var(--str-chat__sprite-image-height) / var(--str-chat__sprite-item-height))",
21164
- "--str-chat__sprite-item-height": `${spriteHeight / rows}`,
21165
- "--str-chat__sprite-item-width": `${spriteWidth / columns}`,
21166
- ...Number.isFinite(height) ? { "--str-chat__sprite-image-height": `${height}px` } : {},
21167
- ...Number.isFinite(width) ? { "--str-chat__sprite-image-width": `${width}px` } : {},
21168
- backgroundImage: `url('${spriteUrl}')`,
21169
- backgroundPosition: `${x * (100 / (columns - 1))}% ${y * (100 / (rows - 1))}%`,
21170
- backgroundSize: `${columns * 100}% ${rows * 100}%`,
21171
- height: "var(--str-chat__sprite-image-height, calc(var(--str-chat__sprite-item-height) * var(--str-chat__sprite-image-resize-ratio)))",
21172
- width: "var(--str-chat__sprite-image-width, calc(var(--str-chat__sprite-item-width) * var(--str-chat__sprite-image-resize-ratio)))"
21173
- }
21187
+ BlockUser() {
21188
+ const { closeMenu } = useContextMenuContext();
21189
+ const { client } = WithAudioPlayback.useChatContext();
21190
+ const { message } = useMessageContext();
21191
+ const { t } = WithAudioPlayback.useTranslationContext();
21192
+ const isBlocked = !message.user?.id || new Set(client.blockedUsers.getLatestValue().userIds).has(message.user?.id);
21193
+ return /* @__PURE__ */ jsxRuntime.jsx(
21194
+ ContextMenuButton,
21195
+ {
21196
+ "aria-label": isBlocked ? t("aria/Unblock User") : t("aria/Block User"),
21197
+ className: clsx(msgActionsBoxButtonClassName),
21198
+ Icon: isBlocked ? WithAudioPlayback.IconUserCheck : WithAudioPlayback.IconNoSign,
21199
+ onClick: () => {
21200
+ const targetId = message.user?.id;
21201
+ if (targetId) {
21202
+ if (isBlocked) client.unBlockUser(targetId);
21203
+ else client.blockUser(targetId);
21204
+ }
21205
+ closeMenu();
21206
+ },
21207
+ children: isBlocked ? t("Unblock User") : t("Block User")
21208
+ }
21209
+ );
21174
21210
  }
21175
- );
21211
+ },
21212
+ quick: {
21213
+ // eslint-disable-next-line react/display-name
21214
+ DropdownToggle: React.forwardRef((_, ref) => {
21215
+ const { t } = WithAudioPlayback.useTranslationContext();
21216
+ const { message, threadList } = useMessageContext();
21217
+ const dropdownDialogIsOpen = useDialogIsOpen(
21218
+ MessageActions.getDialogId({ messageId: message.id })
21219
+ );
21220
+ const { dialog } = useDialogOnNearestManager({
21221
+ id: MessageActions.getDialogId({ messageId: message.id })
21222
+ });
21223
+ const reactionSelectorDialogId = ReactionSelector.getDialogId({
21224
+ messageId: message.id,
21225
+ threadList
21226
+ });
21227
+ const { dialog: dropdownReactionSelectorDialog } = useDialogOnNearestManager({
21228
+ id: `${reactionSelectorDialogId}-dropdown`
21229
+ });
21230
+ return /* @__PURE__ */ jsxRuntime.jsx(
21231
+ QuickMessageActionsButton,
21232
+ {
21233
+ "aria-expanded": dropdownDialogIsOpen,
21234
+ "aria-haspopup": "true",
21235
+ "aria-label": t("aria/Open Message Actions Menu"),
21236
+ className: "str-chat__message-actions-box-button",
21237
+ "data-testid": "message-actions-toggle-button",
21238
+ onClick: () => {
21239
+ dropdownReactionSelectorDialog?.close();
21240
+ dialog?.toggle();
21241
+ },
21242
+ ref,
21243
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMore, { className: "str-chat__message-action-icon" })
21244
+ }
21245
+ );
21246
+ }),
21247
+ React() {
21248
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactionSelectorWithButton, { ReactionIcon: WithAudioPlayback.IconEmoji });
21249
+ },
21250
+ Reply() {
21251
+ const { handleOpenThread } = useMessageContext();
21252
+ const { t } = WithAudioPlayback.useTranslationContext();
21253
+ return /* @__PURE__ */ jsxRuntime.jsx(
21254
+ QuickMessageActionsButton,
21255
+ {
21256
+ "aria-label": t("aria/Open Thread"),
21257
+ className: "str-chat__message-reply-in-thread-button",
21258
+ "data-testid": "thread-action",
21259
+ onClick: handleOpenThread,
21260
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconReply, { className: "str-chat__message-action-icon" })
21261
+ }
21262
+ );
21263
+ }
21264
+ }
21176
21265
  };
21266
+ const defaultMessageActionSet = [
21267
+ {
21268
+ Component: DefaultMessageActionComponents.quick.DropdownToggle,
21269
+ placement: "quick-dropdown-toggle"
21270
+ },
21271
+ {
21272
+ Component: DefaultMessageActionComponents.quick.Reply,
21273
+ placement: "quick",
21274
+ type: "reply"
21275
+ },
21276
+ {
21277
+ Component: DefaultMessageActionComponents.quick.React,
21278
+ placement: "quick",
21279
+ type: "react"
21280
+ },
21281
+ {
21282
+ Component: DefaultMessageActionComponents.dropdown.React,
21283
+ placement: "dropdown",
21284
+ type: "react"
21285
+ },
21286
+ {
21287
+ Component: DefaultMessageActionComponents.dropdown.ThreadReply,
21288
+ placement: "dropdown",
21289
+ type: "reply"
21290
+ },
21291
+ {
21292
+ Component: DefaultMessageActionComponents.dropdown.Quote,
21293
+ placement: "dropdown",
21294
+ type: "quote"
21295
+ },
21296
+ {
21297
+ Component: DefaultMessageActionComponents.dropdown.Pin,
21298
+ placement: "dropdown",
21299
+ type: "pin"
21300
+ },
21301
+ {
21302
+ Component: DefaultMessageActionComponents.dropdown.CopyMessageText,
21303
+ placement: "dropdown",
21304
+ type: "copyMessageText"
21305
+ },
21306
+ {
21307
+ Component: DefaultMessageActionComponents.dropdown.Resend,
21308
+ placement: "dropdown",
21309
+ type: "resendMessage"
21310
+ },
21311
+ {
21312
+ Component: DefaultMessageActionComponents.dropdown.Edit,
21313
+ placement: "dropdown",
21314
+ type: "edit"
21315
+ },
21316
+ {
21317
+ Component: DefaultMessageActionComponents.dropdown.MarkUnread,
21318
+ placement: "dropdown",
21319
+ type: "markUnread"
21320
+ },
21321
+ {
21322
+ Component: DefaultMessageActionComponents.dropdown.RemindMe,
21323
+ placement: "dropdown",
21324
+ type: "remindMe"
21325
+ },
21326
+ {
21327
+ Component: DefaultMessageActionComponents.dropdown.SaveForLater,
21328
+ placement: "dropdown",
21329
+ type: "saveForLater"
21330
+ },
21331
+ {
21332
+ Component: DefaultMessageActionComponents.dropdown.Flag,
21333
+ placement: "dropdown",
21334
+ type: "flag"
21335
+ },
21336
+ {
21337
+ Component: DefaultMessageActionComponents.dropdown.Mute,
21338
+ placement: "dropdown",
21339
+ type: "mute"
21340
+ },
21341
+ {
21342
+ Component: DefaultMessageActionComponents.dropdown.Delete,
21343
+ placement: "dropdown",
21344
+ type: "delete"
21345
+ },
21346
+ {
21347
+ Component: DefaultMessageActionComponents.dropdown.BlockUser,
21348
+ placement: "dropdown",
21349
+ type: "blockUser"
21350
+ }
21351
+ ];
21177
21352
  const useSplitActionSet = (actionSet) => React.useMemo(() => {
21178
21353
  const quickActionSet = [];
21179
21354
  const dropdownActionSet = [];
@@ -21207,6 +21382,7 @@ const MessageActions = ({
21207
21382
  messageId: message.id,
21208
21383
  threadList
21209
21384
  });
21385
+ const dropdownReactionSelectorDialogId = `${reactionSelectorDialogId}-dropdown`;
21210
21386
  const { dialog, dialogManager } = useDialogOnNearestManager({
21211
21387
  id: messageActionsDialogId
21212
21388
  });
@@ -21218,6 +21394,10 @@ const MessageActions = ({
21218
21394
  reactionSelectorDialogId,
21219
21395
  dialogManager?.id
21220
21396
  );
21397
+ const dropdownReactionSelectorDialogIsOpen = useDialogIsOpen(
21398
+ dropdownReactionSelectorDialogId,
21399
+ dialogManager?.id
21400
+ );
21221
21401
  if (dropdownActionSet.length + quickActionSet.length === 0) {
21222
21402
  return null;
21223
21403
  }
@@ -21225,7 +21405,7 @@ const MessageActions = ({
21225
21405
  "div",
21226
21406
  {
21227
21407
  className: clsx("str-chat__message-options", {
21228
- "str-chat__message-options--active": messageActionsDialogIsOpen || reactionSelectorDialogIsOpen
21408
+ "str-chat__message-options--active": messageActionsDialogIsOpen || reactionSelectorDialogIsOpen || dropdownReactionSelectorDialogIsOpen
21229
21409
  }),
21230
21410
  children: [
21231
21411
  quickDropdownToggleAction && dropdownActionSet.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -21235,6 +21415,7 @@ const MessageActions = ({
21235
21415
  {
21236
21416
  backLabel: t("Back"),
21237
21417
  className: clsx("str-chat__message-actions-box", {
21418
+ "str-chat__message-actions-box--hidden": dropdownReactionSelectorDialogIsOpen,
21238
21419
  "str-chat__message-actions-box--open": messageActionsDialogIsOpen
21239
21420
  }),
21240
21421
  dialogManagerId: dialogManager?.id,
@@ -21299,6 +21480,7 @@ const MessageUIWithContext = ({
21299
21480
  () => isMessageAIGenerated?.(message),
21300
21481
  [isMessageAIGenerated, message]
21301
21482
  );
21483
+ const isDeleted = WithAudioPlayback.isMessageDeleted(message);
21302
21484
  const finalAttachments = React.useMemo(
21303
21485
  () => !message.shared_location && !message.attachments ? [] : !message.shared_location ? message.attachments : [message.shared_location, ...message.attachments ?? []],
21304
21486
  [message]
@@ -21306,7 +21488,7 @@ const MessageUIWithContext = ({
21306
21488
  if (WithAudioPlayback.isDateSeparatorMessage(message)) {
21307
21489
  return null;
21308
21490
  }
21309
- if (MessageDeleted && (message.deleted_at || message.type === "deleted")) {
21491
+ if (MessageDeleted && isDeleted) {
21310
21492
  return /* @__PURE__ */ jsxRuntime.jsx(MessageDeleted, { message });
21311
21493
  }
21312
21494
  if (WithAudioPlayback.isMessageBlocked(message)) {
@@ -21314,7 +21496,6 @@ const MessageUIWithContext = ({
21314
21496
  }
21315
21497
  const poll = message.poll_id && client.polls.fromState(message.poll_id);
21316
21498
  const memberCount = Object.keys(channel?.state?.members ?? {}).length;
21317
- const isDeleted = !!message.deleted_at;
21318
21499
  const hasAttachment = !isDeleted && WithAudioPlayback.messageHasAttachments(message);
21319
21500
  const hasSingleAttachment = !isDeleted && WithAudioPlayback.messageHasSingleAttachment(message);
21320
21501
  const hasGiphyAttachment = !isDeleted && WithAudioPlayback.messageHasGiphyAttachment(message);
@@ -21405,7 +21586,7 @@ const MessageUIWithContext = ({
21405
21586
  thread_participants: message.thread_participants
21406
21587
  }
21407
21588
  ),
21408
- message.deleted_at ? /* @__PURE__ */ jsxRuntime.jsx(MessageDeletedBubble$1, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21589
+ isDeleted ? /* @__PURE__ */ jsxRuntime.jsx(MessageDeletedBubble$1, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
21409
21590
  /* @__PURE__ */ jsxRuntime.jsxs(MessageBubble, { children: [
21410
21591
  poll && /* @__PURE__ */ jsxRuntime.jsx(Poll, { poll }),
21411
21592
  message.quoted_message && /* @__PURE__ */ jsxRuntime.jsx(QuotedMessage$1, {}),
@@ -21690,26 +21871,21 @@ const useFloatingDateSeparatorMessageList = ({
21690
21871
  }
21691
21872
  const containerRect = listElement.getBoundingClientRect();
21692
21873
  let bestDate = null;
21693
- let bestBottom = -Infinity;
21694
- let anyVisible = false;
21874
+ let bestTop = -Infinity;
21695
21875
  for (const el of separators) {
21696
21876
  const rect = el.getBoundingClientRect();
21697
21877
  const dataDate = el.getAttribute("data-date");
21698
21878
  if (!dataDate) continue;
21699
- const isAboveViewport = rect.bottom < containerRect.top;
21700
- const isVisible = rect.top < containerRect.bottom && rect.bottom > containerRect.top;
21701
- if (isVisible) {
21702
- anyVisible = true;
21703
- }
21704
- if (isAboveViewport && rect.bottom > bestBottom) {
21705
- bestBottom = rect.bottom;
21879
+ const isAtOrAboveTopBoundary = rect.top <= containerRect.top;
21880
+ if (isAtOrAboveTopBoundary && rect.top > bestTop) {
21881
+ bestTop = rect.top;
21706
21882
  const d = new Date(dataDate);
21707
21883
  if (!isNaN(d.getTime())) bestDate = d;
21708
21884
  }
21709
21885
  }
21710
21886
  setState({
21711
- date: anyVisible ? null : bestDate,
21712
- visible: !anyVisible && bestDate !== null
21887
+ date: bestDate,
21888
+ visible: bestDate !== null
21713
21889
  });
21714
21890
  }, [disableDateSeparator, listElement, processedMessages]);
21715
21891
  React.useEffect(() => {
@@ -22706,6 +22882,10 @@ function getFloatingDateForFirstMessage(firstMessage, processedMessages, firstMe
22706
22882
  }
22707
22883
  return null;
22708
22884
  }
22885
+ function getFloatingDateForFirstItem(firstItem, processedMessages, firstItemIndex) {
22886
+ if (WithAudioPlayback.isDateSeparatorMessage(firstItem)) return firstItem.date;
22887
+ return getFloatingDateForFirstMessage(firstItem, processedMessages, firstItemIndex);
22888
+ }
22709
22889
  const HIDDEN_STATE = { date: null, visible: false };
22710
22890
  const useFloatingDateSeparator = ({
22711
22891
  disableDateSeparator,
@@ -22724,17 +22904,8 @@ const useFloatingDateSeparator = ({
22724
22904
  return;
22725
22905
  }
22726
22906
  const first = valid[0];
22727
- if (WithAudioPlayback.isDateSeparatorMessage(first)) {
22728
- setState(HIDDEN_STATE);
22729
- return;
22730
- }
22731
- const hasVisibleDateSeparator = valid.some(WithAudioPlayback.isDateSeparatorMessage);
22732
- if (hasVisibleDateSeparator) {
22733
- setState(HIDDEN_STATE);
22734
- return;
22735
- }
22736
22907
  const firstIndex = processedMessages.findIndex((m) => m.id === first.id);
22737
- const date = firstIndex >= 0 ? getFloatingDateForFirstMessage(first, processedMessages, firstIndex) : null;
22908
+ const date = firstIndex >= 0 ? getFloatingDateForFirstItem(first, processedMessages, firstIndex) : null;
22738
22909
  const visible = date !== null;
22739
22910
  setState((prev) => {
22740
22911
  const prevTime = prev.date?.getTime() ?? null;
@@ -25662,77 +25833,9 @@ const useArchiveActionButtonBehavior = () => {
25662
25833
  title: membership.archived_at ? t("Unarchive") : t("Archive")
25663
25834
  };
25664
25835
  };
25665
- const defaultChannelActionSet = [
25666
- {
25667
- // eslint-disable-next-line react/display-name
25668
- Component: React.forwardRef((_, ref) => {
25669
- const { channel } = useChannelListItemContext();
25670
- const dialogId2 = ChannelListItemActionButtons.getDialogId({
25671
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
25672
- channelId: channel.id
25673
- });
25674
- const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
25675
- const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
25676
- return /* @__PURE__ */ jsxRuntime.jsx(
25677
- WithAudioPlayback.Button,
25678
- {
25679
- appearance: "ghost",
25680
- "aria-expanded": dialogIsOpen,
25681
- "aria-pressed": dialogIsOpen,
25682
- circular: true,
25683
- onClick: (e) => {
25684
- e.stopPropagation();
25685
- dialog.toggle();
25686
- },
25687
- ref,
25688
- size: "sm",
25689
- variant: "secondary",
25690
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMore, {})
25691
- }
25692
- );
25693
- }),
25694
- placement: "quick-dropdown-toggle"
25695
- },
25696
- {
25697
- Component() {
25698
- const behaviorProps = useArchiveActionButtonBehavior();
25699
- return /* @__PURE__ */ jsxRuntime.jsx(
25700
- WithAudioPlayback.Button,
25701
- {
25702
- appearance: "ghost",
25703
- "aria-label": behaviorProps.title,
25704
- circular: true,
25705
- size: "sm",
25706
- variant: "secondary",
25707
- ...behaviorProps,
25708
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconArchive, {})
25709
- }
25710
- );
25711
- },
25712
- placement: "quick",
25713
- type: "archive"
25714
- },
25715
- {
25716
- Component() {
25717
- const behaviorProps = useMuteActionButtonBehavior();
25718
- return /* @__PURE__ */ jsxRuntime.jsx(
25719
- WithAudioPlayback.Button,
25720
- {
25721
- appearance: "ghost",
25722
- "aria-label": behaviorProps.title,
25723
- circular: true,
25724
- size: "sm",
25725
- variant: "secondary",
25726
- ...behaviorProps,
25727
- children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMute, {})
25728
- }
25729
- );
25730
- },
25731
- placement: "quick",
25732
- type: "mute"
25733
- },
25734
- {
25735
- Component() {
25836
+ const defaultComponents = {
25837
+ dropdown: {
25838
+ Archive() {
25736
25839
  const behaviorProps = useArchiveActionButtonBehavior();
25737
25840
  return /* @__PURE__ */ jsxRuntime.jsx(
25738
25841
  ContextMenuButton,
@@ -25744,27 +25847,7 @@ const defaultChannelActionSet = [
25744
25847
  }
25745
25848
  );
25746
25849
  },
25747
- placement: "dropdown",
25748
- type: "archive"
25749
- },
25750
- {
25751
- Component() {
25752
- const behaviorProps = useMuteActionButtonBehavior();
25753
- return /* @__PURE__ */ jsxRuntime.jsx(
25754
- ContextMenuButton,
25755
- {
25756
- "aria-label": behaviorProps.title,
25757
- Icon: WithAudioPlayback.IconMute,
25758
- ...behaviorProps,
25759
- children: behaviorProps.title
25760
- }
25761
- );
25762
- },
25763
- placement: "dropdown",
25764
- type: "mute"
25765
- },
25766
- {
25767
- Component() {
25850
+ Ban() {
25768
25851
  const { client } = WithAudioPlayback.useChatContext();
25769
25852
  const { addNotification } = WithAudioPlayback.useNotificationApi();
25770
25853
  const { t } = WithAudioPlayback.useTranslationContext();
@@ -25831,11 +25914,55 @@ const defaultChannelActionSet = [
25831
25914
  }
25832
25915
  );
25833
25916
  },
25834
- placement: "dropdown",
25835
- type: "ban"
25836
- },
25837
- {
25838
- Component() {
25917
+ Leave() {
25918
+ const { t } = WithAudioPlayback.useTranslationContext();
25919
+ const { channel } = useChannelListItemContext();
25920
+ const { client } = WithAudioPlayback.useChatContext();
25921
+ const { addNotification } = WithAudioPlayback.useNotificationApi();
25922
+ const [inProgress, setInProgress] = React.useState(false);
25923
+ const title = t("Leave Channel");
25924
+ return /* @__PURE__ */ jsxRuntime.jsx(
25925
+ ContextMenuButton,
25926
+ {
25927
+ "aria-label": title,
25928
+ disabled: inProgress,
25929
+ Icon: WithAudioPlayback.IconLeave,
25930
+ onClick: async (e) => {
25931
+ e.stopPropagation();
25932
+ try {
25933
+ setInProgress(true);
25934
+ await channel.removeMembers([client.userID]);
25935
+ addNotification({
25936
+ context: {
25937
+ channel
25938
+ },
25939
+ emitter: ChannelListItemActionButtons.name,
25940
+ message: t("Left channel"),
25941
+ severity: "success",
25942
+ type: "api:channel:leave:success"
25943
+ });
25944
+ } catch (error) {
25945
+ addNotification({
25946
+ context: {
25947
+ channel
25948
+ },
25949
+ emitter: ChannelListItemActionButtons.name,
25950
+ error: error instanceof Error ? error : new Error("An unknown error occurred"),
25951
+ message: t("Failed to leave channel"),
25952
+ severity: "error",
25953
+ type: "api:channel:leave:failed"
25954
+ });
25955
+ } finally {
25956
+ setInProgress(false);
25957
+ }
25958
+ },
25959
+ title,
25960
+ variant: "destructive",
25961
+ children: title
25962
+ }
25963
+ );
25964
+ },
25965
+ Pin() {
25839
25966
  const { t } = WithAudioPlayback.useTranslationContext();
25840
25967
  const { addNotification } = WithAudioPlayback.useNotificationApi();
25841
25968
  const { channel } = useChannelListItemContext();
@@ -25902,59 +26029,80 @@ const defaultChannelActionSet = [
25902
26029
  children: title
25903
26030
  }
25904
26031
  );
25905
- },
25906
- placement: "dropdown",
25907
- type: "pin"
26032
+ }
25908
26033
  },
25909
- {
25910
- Component() {
25911
- const { t } = WithAudioPlayback.useTranslationContext();
25912
- const { channel } = useChannelListItemContext();
25913
- const { client } = WithAudioPlayback.useChatContext();
25914
- const { addNotification } = WithAudioPlayback.useNotificationApi();
25915
- const [inProgress, setInProgress] = React.useState(false);
25916
- const title = t("Leave Channel");
26034
+ quick: {
26035
+ Mute() {
26036
+ const behaviorProps = useMuteActionButtonBehavior();
25917
26037
  return /* @__PURE__ */ jsxRuntime.jsx(
25918
- ContextMenuButton,
26038
+ WithAudioPlayback.Button,
25919
26039
  {
25920
- "aria-label": title,
25921
- disabled: inProgress,
25922
- Icon: WithAudioPlayback.IconLeave,
25923
- onClick: async (e) => {
25924
- e.stopPropagation();
25925
- try {
25926
- setInProgress(true);
25927
- await channel.removeMembers([client.userID]);
25928
- addNotification({
25929
- context: {
25930
- channel
25931
- },
25932
- emitter: ChannelListItemActionButtons.name,
25933
- message: t("Left channel"),
25934
- severity: "success",
25935
- type: "api:channel:leave:success"
25936
- });
25937
- } catch (error) {
25938
- addNotification({
25939
- context: {
25940
- channel
25941
- },
25942
- emitter: ChannelListItemActionButtons.name,
25943
- error: error instanceof Error ? error : new Error("An unknown error occurred"),
25944
- message: t("Failed to leave channel"),
25945
- severity: "error",
25946
- type: "api:channel:leave:failed"
25947
- });
25948
- } finally {
25949
- setInProgress(false);
25950
- }
25951
- },
25952
- title,
25953
- variant: "destructive",
25954
- children: title
26040
+ appearance: "ghost",
26041
+ "aria-label": behaviorProps.title,
26042
+ circular: true,
26043
+ size: "sm",
26044
+ variant: "secondary",
26045
+ ...behaviorProps,
26046
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMute, {})
25955
26047
  }
25956
26048
  );
25957
- },
26049
+ }
26050
+ },
26051
+ QuickDropdownToggle: React.forwardRef((_, ref) => {
26052
+ const { channel } = useChannelListItemContext();
26053
+ const dialogId2 = ChannelListItemActionButtons.getDialogId({
26054
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
26055
+ channelId: channel.id
26056
+ });
26057
+ const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId2 });
26058
+ const dialogIsOpen = useDialogIsOpen(dialogId2, dialogManager?.id);
26059
+ return /* @__PURE__ */ jsxRuntime.jsx(
26060
+ WithAudioPlayback.Button,
26061
+ {
26062
+ appearance: "ghost",
26063
+ "aria-expanded": dialogIsOpen,
26064
+ "aria-pressed": dialogIsOpen,
26065
+ circular: true,
26066
+ onClick: (e) => {
26067
+ e.stopPropagation();
26068
+ dialog.toggle();
26069
+ },
26070
+ ref,
26071
+ size: "sm",
26072
+ variant: "secondary",
26073
+ children: /* @__PURE__ */ jsxRuntime.jsx(WithAudioPlayback.IconMore, {})
26074
+ }
26075
+ );
26076
+ })
26077
+ };
26078
+ defaultComponents.QuickDropdownToggle.displayName = "QuickDropdownToggle";
26079
+ const defaultChannelActionSet = [
26080
+ {
26081
+ Component: defaultComponents.QuickDropdownToggle,
26082
+ placement: "quick-dropdown-toggle"
26083
+ },
26084
+ {
26085
+ Component: defaultComponents.quick.Mute,
26086
+ placement: "quick",
26087
+ type: "mute"
26088
+ },
26089
+ {
26090
+ Component: defaultComponents.dropdown.Archive,
26091
+ placement: "dropdown",
26092
+ type: "archive"
26093
+ },
26094
+ {
26095
+ Component: defaultComponents.dropdown.Ban,
26096
+ placement: "dropdown",
26097
+ type: "ban"
26098
+ },
26099
+ {
26100
+ Component: defaultComponents.dropdown.Pin,
26101
+ placement: "dropdown",
26102
+ type: "pin"
26103
+ },
26104
+ {
26105
+ Component: defaultComponents.dropdown.Leave,
25958
26106
  placement: "dropdown",
25959
26107
  type: "leave"
25960
26108
  }
@@ -25962,8 +26110,6 @@ const defaultChannelActionSet = [
25962
26110
  const useBaseChannelActionSetFilter = (channelActionSet) => {
25963
26111
  const { channel } = useChannelListItemContext();
25964
26112
  const membership = useChannelMembershipState(channel);
25965
- const isDirectMessageChannel = channel.type === "messaging" && // assuming one of the users is current user
25966
- channel.data?.member_count === 2 && channel.id?.startsWith("!members-");
25967
26113
  const memberCount = channel.data?.member_count ?? 0;
25968
26114
  const connectedUserIsMember = typeof membership.user !== "undefined";
25969
26115
  const ownCapabilities = channel.data?.own_capabilities;
@@ -25972,9 +26118,9 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25972
26118
  if (action.placement === "quick-dropdown-toggle") return true;
25973
26119
  switch (action.type) {
25974
26120
  case "archive":
25975
- return connectedUserIsMember && (action.placement === "quick" && isDirectMessageChannel || action.placement === "dropdown" && !isDirectMessageChannel);
26121
+ return connectedUserIsMember;
25976
26122
  case "mute":
25977
- return ownCapabilities?.includes("mute-channel") && (action.placement === "dropdown" && isDirectMessageChannel || action.placement === "quick" && !isDirectMessageChannel);
26123
+ return ownCapabilities?.includes("mute-channel");
25978
26124
  case "ban":
25979
26125
  return memberCount > 0 && memberCount <= 2 && ownCapabilities?.includes("ban-channel-members");
25980
26126
  case "leave":
@@ -25986,13 +26132,7 @@ const useBaseChannelActionSetFilter = (channelActionSet) => {
25986
26132
  }
25987
26133
  });
25988
26134
  return filtered;
25989
- }, [
25990
- channelActionSet,
25991
- isDirectMessageChannel,
25992
- memberCount,
25993
- ownCapabilities,
25994
- connectedUserIsMember
25995
- ]);
26135
+ }, [channelActionSet, memberCount, ownCapabilities, connectedUserIsMember]);
25996
26136
  };
25997
26137
  const ChannelListItemActionButtons = () => {
25998
26138
  const { ContextMenu: ContextMenuComponent = ContextMenu } = WithAudioPlayback.useComponentContext();
@@ -26122,7 +26262,7 @@ const useLatestMessagePreview = ({
26122
26262
  } else if (!isOwnMessage && participantCount !== void 0 && participantCount > 2) {
26123
26263
  senderName = latestMessage.user?.name || latestMessage.user?.id;
26124
26264
  }
26125
- if (latestMessage.deleted_at) {
26265
+ if (WithAudioPlayback.isMessageDeleted(latestMessage)) {
26126
26266
  return {
26127
26267
  deliveryStatus,
26128
26268
  senderName,
@@ -28247,7 +28387,7 @@ const useChat = ({
28247
28387
  };
28248
28388
  React.useEffect(() => {
28249
28389
  if (!client) return;
28250
- const version = "14.0.0-beta.7";
28390
+ const version = "14.0.0-beta.8";
28251
28391
  const userAgent = client.getUserAgent();
28252
28392
  if (!userAgent.includes("stream-chat-react")) {
28253
28393
  client.setUserAgent(`stream-chat-react-${version}-${userAgent}`);
@@ -28431,7 +28571,6 @@ const Chat = (props) => {
28431
28571
  defaultLanguage,
28432
28572
  i18nInstance
28433
28573
  });
28434
- useReportLostConnectionSystemNotification();
28435
28574
  const channelsQueryState = useChannelsQueryState();
28436
28575
  const searchController = React.useMemo(
28437
28576
  () => customChannelSearchController ?? new streamChat.SearchController({
@@ -28651,6 +28790,7 @@ exports.isLanguageSupported = WithAudioPlayback.isLanguageSupported;
28651
28790
  exports.isLocalMessage = WithAudioPlayback.isLocalMessage;
28652
28791
  exports.isMessageBlocked = WithAudioPlayback.isMessageBlocked;
28653
28792
  exports.isMessageBounced = WithAudioPlayback.isMessageBounced;
28793
+ exports.isMessageDeleted = WithAudioPlayback.isMessageDeleted;
28654
28794
  exports.isMessageEdited = WithAudioPlayback.isMessageEdited;
28655
28795
  exports.isMessageErrorRetryable = WithAudioPlayback.isMessageErrorRetryable;
28656
28796
  exports.isNetworkSendFailure = WithAudioPlayback.isNetworkSendFailure;
@@ -28886,6 +29026,10 @@ exports.Streami18n = Streami18n;
28886
29026
  exports.SuggestPollOptionPrompt = SuggestPollOptionPrompt;
28887
29027
  exports.SuggestionList = SuggestionList;
28888
29028
  exports.SuggestionListItem = SuggestionListItem;
29029
+ exports.SwitchField = SwitchField;
29030
+ exports.SwitchFieldDescription = SwitchFieldDescription;
29031
+ exports.SwitchFieldLabel = SwitchFieldLabel;
29032
+ exports.SwitchFieldTitle = SwitchFieldTitle;
28889
29033
  exports.TextInput = TextInput;
28890
29034
  exports.TextInputFieldSet = TextInputFieldSet;
28891
29035
  exports.TextareaComposer = TextareaComposer;
@@ -28926,7 +29070,7 @@ exports.deTranslations = deTranslations;
28926
29070
  exports.defaultAllowedTagNames = defaultAllowedTagNames;
28927
29071
  exports.defaultAttachmentActionsDefaultFocus = defaultAttachmentActionsDefaultFocus;
28928
29072
  exports.defaultAttachmentSelectorActionSet = defaultAttachmentSelectorActionSet;
28929
- exports.defaultComponents = defaultComponents;
29073
+ exports.defaultComponents = defaultComponents$1;
28930
29074
  exports.defaultMessageActionSet = defaultMessageActionSet;
28931
29075
  exports.defaultReactionOptions = defaultReactionOptions;
28932
29076
  exports.defaultRenderMessages = defaultRenderMessages;