@ethora/chat-component 26.9.5 → 26.9.6

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 (49) hide show
  1. package/README.md +2 -0
  2. package/dist/{ChatProfileModal-Pr4--RdW.js → ChatProfileModal-DKuxJsdM.js} +526 -519
  3. package/dist/{FilePreviewModal-CYHlKzRc.js → FilePreviewModal-BcaSoO3-.js} +89 -89
  4. package/dist/{ManageDataModal-BE1eGlYh.js → ManageDataModal-Dw4dCQll.js} +2 -2
  5. package/dist/{MarkdownBody-G0_J0NL6.js → MarkdownBody-xtKDMR0h.js} +1 -1
  6. package/dist/MessageSearchModal-BVKU6H3v.js +389 -0
  7. package/dist/PublicChatsModal-CscOnVaQ.js +202 -0
  8. package/dist/{SideDrawer-BG2m7Bcm.js → SideDrawer-WneDrrWG.js} +27 -27
  9. package/dist/{UserProfileModal-B2Hp5bAF.js → UserProfileModal-CMHFq1I9.js} +3 -3
  10. package/dist/{UserSettingsModal-D871FDAT.js → UserSettingsModal-BoZXZ0oR.js} +2 -2
  11. package/dist/{VideoCallOverlay-Br0ji0nZ.js → VideoCallOverlay-CQNi7wp5.js} +1 -1
  12. package/dist/{VisibilityModal-DdFBV1_g.js → VisibilityModal-BPsNiMzJ.js} +2 -2
  13. package/dist/assets/icons.d.ts +9 -0
  14. package/dist/components/InputComponents/Search.d.ts +4 -0
  15. package/dist/components/MainComponents/ArchivedMessageCard.d.ts +11 -0
  16. package/dist/components/MainComponents/computeAnchoredScrollTop.d.ts +9 -0
  17. package/dist/components/MainComponents/useJumpToMessage.d.ts +63 -0
  18. package/dist/components/MainComponents/useStickToBottom.d.ts +27 -0
  19. package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +22 -0
  20. package/dist/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +6 -0
  21. package/dist/components/Modals/MessageSearchModal/resolveSender.d.ts +33 -0
  22. package/dist/components/Modals/MessageSearchModal/searchFilters.d.ts +34 -0
  23. package/dist/components/Modals/MessageSearchModal/snippet.d.ts +14 -0
  24. package/dist/components/Modals/MessageSearchModal/useMessageSearch.d.ts +22 -0
  25. package/dist/components/Modals/PublicChatsModal/PublicChatsModal.d.ts +13 -0
  26. package/dist/components/styled/RoomListComponents/index.d.ts +3 -0
  27. package/dist/context/ToastContext.d.ts +7 -0
  28. package/dist/firebase-messaging-sw.js +21 -3
  29. package/dist/{handleCopyClick-DPjxMoJn.js → handleCopyClick-B70vLjJD.js} +1 -1
  30. package/dist/helpers/constants/MODAL_TYPES.d.ts +2 -0
  31. package/dist/helpers/nanoToMs.d.ts +1 -1
  32. package/dist/helpers/openRoomAtMessage.d.ts +17 -0
  33. package/dist/hooks/useMessageSearchShortcut.d.ts +14 -0
  34. package/dist/hooks/useRoomState.d.ts +1 -0
  35. package/dist/{main-BRIZ98KV.js → main-rJpE4hh-.js} +12318 -11108
  36. package/dist/main.css +1 -1
  37. package/dist/main.js +6 -6
  38. package/dist/networking/api-requests/messageSearch.api.d.ts +56 -0
  39. package/dist/networking/api-requests/publicChats.api.d.ts +23 -0
  40. package/dist/networking/api-requests/xmppToken.api.d.ts +1 -0
  41. package/dist/networking/xmppClient.d.ts +2 -0
  42. package/dist/roomStore/chatSettingsSlice.d.ts +1 -1
  43. package/dist/roomStore/roomsSlice.d.ts +38 -1
  44. package/dist/styles/classNames.d.ts +2 -0
  45. package/dist/types/models/config.model.d.ts +11 -0
  46. package/dist/utils/notificationUtils.d.ts +12 -0
  47. package/dist/version.d.ts +1 -1
  48. package/package.json +1 -1
  49. package/public/firebase-messaging-sw.js +21 -3
@@ -1,6 +1,6 @@
1
1
  import { jsxs as i, jsx as o } from "react/jsx-runtime";
2
2
  import { useId as f, useRef as v } from "react";
3
- import { p as u, y as g, aI as m, aJ as w, B as b, aK as y, x as e, aL as D, aM as k } from "./main-BRIZ98KV.js";
3
+ import { p as u, y as g, aI as m, aO as w, B as b, aP as y, x as e, aQ as D, aR as k } from "./main-rJpE4hh-.js";
4
4
  const c = 767, R = `${c}px`, z = e.div`
5
5
  position: relative;
6
6
  pointer-events: auto;
@@ -58,11 +58,11 @@ const c = 767, R = `${c}px`, z = e.div`
58
58
  padding-left: var(--ethora-space-4, 16px);
59
59
  padding-right: var(--ethora-space-4, 16px);
60
60
  }
61
- `, $ = e.section`
61
+ `, O = e.section`
62
62
  display: flex;
63
63
  flex-direction: column;
64
64
  gap: var(--ethora-space-2, 8px);
65
- `, K = e.h3`
65
+ `, $ = e.h3`
66
66
  margin: 0;
67
67
  padding: 0 var(--ethora-space-1, 4px);
68
68
  font-size: var(--ethora-font-size-xs, 12px);
@@ -70,7 +70,7 @@ const c = 767, R = `${c}px`, z = e.div`
70
70
  letter-spacing: 0.06em;
71
71
  text-transform: uppercase;
72
72
  color: var(--ethora-color-text-muted, #8c8c8c);
73
- `, O = e.div`
73
+ `, W = e.div`
74
74
  display: flex;
75
75
  flex-direction: column;
76
76
  box-sizing: border-box;
@@ -79,12 +79,12 @@ const c = 767, R = `${c}px`, z = e.div`
79
79
  border-radius: var(--ethora-radius-md, 12px);
80
80
  background-color: var(--ethora-color-bg, #fff);
81
81
  overflow: hidden;
82
- `, W = e.div`
82
+ `, _ = e.div`
83
83
  display: flex;
84
84
  flex-direction: column;
85
85
  gap: var(--ethora-space-1, 4px);
86
86
  padding: var(--ethora-space-3, 12px) var(--ethora-space-4, 16px);
87
- `, _ = e.div`
87
+ `, K = e.div`
88
88
  display: flex;
89
89
  align-items: baseline;
90
90
  justify-content: space-between;
@@ -109,7 +109,7 @@ const c = 767, R = `${c}px`, z = e.div`
109
109
  outline-offset: 2px;
110
110
  border-radius: var(--ethora-radius-sm, 8px);
111
111
  }
112
- `, J = e.div`
112
+ `, Q = e.div`
113
113
  height: 1px;
114
114
  margin: 0 var(--ethora-space-4, 16px);
115
115
  background-color: var(--ethora-color-border, #e6e8ec);
@@ -152,18 +152,18 @@ const c = 767, R = `${c}px`, z = e.div`
152
152
  display: flex;
153
153
  flex-direction: column;
154
154
  width: 100%;
155
- `, L = e.div`
155
+ `, T = e.div`
156
156
  font-size: var(--ethora-font-size-sm, 14px);
157
157
  font-weight: var(--ethora-font-weight-medium, 500);
158
158
  color: var(--ethora-color-text, #141414);
159
159
  text-align: start;
160
- `, T = e.div`
160
+ `, C = e.div`
161
161
  font-size: var(--ethora-font-size-xs, 12px);
162
162
  font-weight: var(--ethora-font-weight-regular, 400);
163
163
  color: var(--ethora-color-text-muted, #8c8c8c);
164
164
  text-align: start;
165
165
  line-height: 1.4;
166
- `, C = e.button`
166
+ `, E = e.button`
167
167
  display: flex;
168
168
  align-items: center;
169
169
  gap: var(--ethora-space-3, 12px);
@@ -186,13 +186,13 @@ const c = 767, R = `${c}px`, z = e.div`
186
186
  outline: 2px solid var(--ethora-color-primary, #0052cd);
187
187
  outline-offset: -2px;
188
188
  }
189
- `, E = e.span`
189
+ `, L = e.span`
190
190
  display: flex;
191
191
  flex-direction: column;
192
192
  gap: var(--ethora-space-1, 4px);
193
193
  flex: 1 1 auto;
194
194
  min-width: 0;
195
- `, M = e.span`
195
+ `, P = e.span`
196
196
  flex: 0 0 auto;
197
197
  display: inline-flex;
198
198
  color: var(--ethora-color-text-muted, #8c8c8c);
@@ -209,13 +209,13 @@ const c = 767, R = `${c}px`, z = e.div`
209
209
  label: r,
210
210
  hint: a,
211
211
  onClick: t
212
- }) => /* @__PURE__ */ i(C, { type: "button", onClick: t, children: [
213
- /* @__PURE__ */ i(E, { children: [
214
- /* @__PURE__ */ o(L, { children: r }),
215
- a && /* @__PURE__ */ o(T, { children: a })
212
+ }) => /* @__PURE__ */ i(E, { type: "button", onClick: t, children: [
213
+ /* @__PURE__ */ i(L, { children: [
214
+ /* @__PURE__ */ o(T, { children: r }),
215
+ a && /* @__PURE__ */ o(C, { children: a })
216
216
  ] }),
217
- /* @__PURE__ */ o(M, { "aria-hidden": "true", children: /* @__PURE__ */ o(j, {}) })
218
- ] }), Q = ({
217
+ /* @__PURE__ */ o(P, { "aria-hidden": "true", children: /* @__PURE__ */ o(j, {}) })
218
+ ] }), J = ({
219
219
  title: r,
220
220
  onClose: a,
221
221
  headerActions: t,
@@ -253,16 +253,16 @@ const c = 767, R = `${c}px`, z = e.div`
253
253
  );
254
254
  };
255
255
  export {
256
- $ as D,
257
- Q as S,
258
- K as a,
259
- O as b,
260
- J as c,
256
+ O as D,
257
+ J as S,
258
+ $ as a,
259
+ W as b,
260
+ Q as c,
261
261
  G as d,
262
- W as e,
263
- T as f,
264
- L as g,
265
- _ as h,
262
+ _ as e,
263
+ C as f,
264
+ T as g,
265
+ K as h,
266
266
  V as i,
267
267
  X as j,
268
268
  q as k,
@@ -1,8 +1,8 @@
1
1
  import { jsxs as u, Fragment as $, jsx as o } from "react/jsx-runtime";
2
2
  import { forwardRef as ge, useState as I, useCallback as y, useImperativeHandle as ve, useRef as ae, useMemo as M } from "react";
3
- import { a as be, p as J, t as ye, v as xe, w as we, P as re, I as H, x as D, y as Ce, z as Se, C as Ie, D as te, E as De, F as Ne, G as ke, J as Ue, u as Q, K as Fe, L as Pe, N as Ae, O as Re, Q as Y, q as Z, R as ze, S as je, T as Be, s as $e, B as X, U as Ee, W as Oe, X as Te, Y as ee, Z as R, _ as Le, $ as Me, V as He, A as Xe, a0 as We, a1 as Je, a2 as Ke, a3 as Ve } from "./main-BRIZ98KV.js";
4
- import { D as E, a as W, b as Ge, e as qe, f as _e, S as Qe } from "./SideDrawer-BG2m7Bcm.js";
5
- import { P as ne, a as Ye, D as Ze, b as eo, h as oo } from "./handleCopyClick-DPjxMoJn.js";
3
+ import { a as be, p as J, t as ye, v as xe, w as we, P as re, I as H, x as D, y as Ce, z as Se, C as Ie, D as te, E as De, F as Ne, G as ke, J as Ue, u as Q, K as Fe, L as Pe, N as Ae, O as Re, Q as Y, q as Z, R as ze, S as je, T as Be, s as $e, B as X, U as Ee, W as Oe, X as Te, Y as ee, Z as R, _ as Le, $ as Me, V as He, A as Xe, a0 as We, a1 as Je, a2 as Ke, a3 as Ve } from "./main-rJpE4hh-.js";
4
+ import { D as E, a as W, b as Ge, e as qe, f as _e, S as Qe } from "./SideDrawer-WneDrrWG.js";
5
+ import { P as ne, a as Ye, D as Ze, b as eo, h as oo } from "./handleCopyClick-B70vLjJD.js";
6
6
  const ao = (c, i) => {
7
7
  const l = atob(c.split(",")[1]), g = new ArrayBuffer(l.length), e = new Uint8Array(g);
8
8
  for (let r = 0; r < l.length; r++)
@@ -1,7 +1,7 @@
1
1
  import { jsx as i, jsxs as r } from "react/jsx-runtime";
2
2
  import m, { useCallback as D, useMemo as h } from "react";
3
- import { p, a as g, q as w, M as l } from "./main-BRIZ98KV.js";
4
- import { S as b, D as u, a as k, b as y, c as S, d as f } from "./SideDrawer-BG2m7Bcm.js";
3
+ import { p, a as g, q as w, M as l } from "./main-rJpE4hh-.js";
4
+ import { S as b, D as u, a as k, b as y, c as S, d as f } from "./SideDrawer-WneDrrWG.js";
5
5
  const T = ({
6
6
  handleCloseModal: n
7
7
  }) => {
@@ -1,7 +1,7 @@
1
1
  import { jsxs as K, jsx as x, Fragment as Hr } from "react/jsx-runtime";
2
2
  import * as F from "react";
3
3
  import { useState as Yt, useRef as ke, useEffect as be, useMemo as Wt, useCallback as mi } from "react";
4
- import { u as Xi, P as Kn, H as xi, a as Hd, b as zd, c as Kd, d as Gd, s as po, e as Jd, f as Yd, g as fo, h as $d, i as Xd, j as mo, k as Qd, l as Zd, r as go, m as eu, n as tu, A as vo, V as bo, B as nu } from "./main-BRIZ98KV.js";
4
+ import { u as Xi, P as Kn, H as xi, a as Hd, b as zd, c as Kd, d as Gd, s as po, e as Jd, f as Yd, g as fo, h as $d, i as Xd, j as mo, k as Qd, l as Zd, r as go, m as eu, n as tu, A as vo, V as bo, B as nu } from "./main-rJpE4hh-.js";
5
5
  function iu(n, e) {
6
6
  return e.forEach(function(t) {
7
7
  t && typeof t != "string" && !Array.isArray(t) && Object.keys(t).forEach(function(i) {
@@ -1,7 +1,7 @@
1
1
  import { jsxs as r, jsx as s } from "react/jsx-runtime";
2
2
  import { useState as b, useEffect as x } from "react";
3
- import { x as o, a as L, p as P, u as z, aG as D, aH as I } from "./main-BRIZ98KV.js";
4
- import { S as N, D as C, a as $, b as k, e as w, f as p } from "./SideDrawer-BG2m7Bcm.js";
3
+ import { x as o, a as L, p as P, u as z, aG as D, aH as I } from "./main-rJpE4hh-.js";
4
+ import { S as N, D as C, a as $, b as k, e as w, f as p } from "./SideDrawer-WneDrrWG.js";
5
5
  o.div``;
6
6
  o.div`
7
7
  font-size: 16px;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * A compass: "discover". Deliberately not the globe, which is the language
3
+ * selector's icon in the chat header, so the two buttons read as different
4
+ * things when they sit next to each other.
5
+ */
6
+ export declare const DiscoverIcon: ({ color, ...props }: {
7
+ [x: string]: any;
8
+ color?: string;
9
+ }) => import("react/jsx-runtime").JSX.Element;
1
10
  export declare const GlobeIcon: ({ color, ...props }: {
2
11
  [x: string]: any;
3
12
  color?: string;
@@ -4,6 +4,10 @@ interface SearchInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
4
4
  animated?: boolean;
5
5
  direction?: 'left' | 'right';
6
6
  colorBg?: string;
7
+ /** When set, a clear button shows while the input has text. */
8
+ onClear?: () => void;
9
+ /** Accessible label for the clear button (pass a translated string). */
10
+ clearLabel?: string;
7
11
  }
8
12
  declare const SearchInput: React.FC<SearchInputProps>;
9
13
  export { SearchInput };
@@ -0,0 +1,11 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * The message a search hit points at, shown when the transcript cannot reach
4
+ * it (the search archive is deeper than the chat's own history). It sits over
5
+ * the conversation of the room it came from, so it needs no navigation and
6
+ * says what the message is instead of reporting that it was not found.
7
+ */
8
+ declare const ArchivedMessageCard: React.FC<{
9
+ roomJID: string;
10
+ }>;
11
+ export default ArchivedMessageCard;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Where to put scrollTop after content was added ABOVE the reader.
3
+ *
4
+ * `liveTop` must be the scrollTop at the moment the content arrived, not the
5
+ * one captured when the request started: the reader keeps scrolling while a
6
+ * page is in flight, and restoring the old value throws them back down by
7
+ * however far they travelled meanwhile.
8
+ */
9
+ export declare function computeAnchoredScrollTop(liveTop: number, heightBefore: number, heightAfter: number): number;
@@ -0,0 +1,63 @@
1
+ import { MutableRefObject, RefObject } from 'react';
2
+ import { IMessage } from '../../types/types';
3
+ /**
4
+ * Index of the message a jump names.
5
+ *
6
+ * By id first (message.id is the MAM stanza id for history, xmppId the client
7
+ * message id). When the request has no id at all, which is common for archive
8
+ * rows, by content: same text, sent within a few seconds of the same time.
9
+ */
10
+ export declare const findMessageIndex: (messages: IMessage[], ids: string[], content?: {
11
+ createdAt?: string;
12
+ body?: string;
13
+ }) => number;
14
+ /** What the server said about one page of older history. */
15
+ export interface OlderPage {
16
+ /** False when the request failed or timed out. */
17
+ ok: boolean;
18
+ /**
19
+ * The RSM `<first>` of the page: the id (microseconds) of the oldest ROW the
20
+ * server returned. The next page continues from here. It is NOT the oldest
21
+ * message that got displayed: a page can be made entirely of reactions,
22
+ * system rows or deleted messages, display nothing, and still be followed
23
+ * by plenty of history.
24
+ */
25
+ cursor?: number | null;
26
+ /** The server's `<fin complete>`: there is nothing older. */
27
+ complete?: boolean;
28
+ }
29
+ interface Options {
30
+ roomJID: string;
31
+ /** The room's messages, in display order. */
32
+ messages: IMessage[];
33
+ /** How many of them are currently mounted (the newest N). */
34
+ visibleCount: number;
35
+ setRenderWindow: (size: number) => void;
36
+ /**
37
+ * Fetches the next page of older history by MAM and resolves once that page
38
+ * is in the store, with the server's own cursor for what it returned.
39
+ */
40
+ fetchOlderPage: (roomJID: string, before: number, max: number) => Promise<OlderPage>;
41
+ containerRef: RefObject<HTMLElement>;
42
+ historyComplete?: boolean;
43
+ /**
44
+ * MessageList's "reader is away from the bottom" flag. Set for the length of
45
+ * a jump: paging older history grows the message count, and with the flag
46
+ * false the list reads that as new messages arriving and scrolls to the
47
+ * bottom, undoing the jump the moment the target loads.
48
+ */
49
+ isUserScrolledUpRef: MutableRefObject<boolean>;
50
+ }
51
+ /**
52
+ * Fulfils a pending "scroll to this message" request for this room.
53
+ *
54
+ * The target can be in one of three places, tried in this order:
55
+ * 1. mounted: scroll to it and flash it;
56
+ * 2. in the store but older than the render window: widen the window to
57
+ * include it, then scroll;
58
+ * 3. not loaded at all: page older history from the server until it shows
59
+ * up, bounded by MAX_HISTORY_PAGES and by the end of the archive.
60
+ * If it cannot be reached, say so rather than leaving the tap looking dead.
61
+ */
62
+ export declare function useJumpToMessage({ roomJID, messages, visibleCount, setRenderWindow, fetchOlderPage, containerRef, historyComplete, isUserScrolledUpRef, }: Options): void;
63
+ export {};
@@ -0,0 +1,27 @@
1
+ import { MutableRefObject, RefObject } from 'react';
2
+ interface Options {
3
+ /** The scrolling element. */
4
+ containerRef: RefObject<HTMLElement>;
5
+ /** The element that grows as messages, images and history arrive. */
6
+ contentRef: RefObject<HTMLElement>;
7
+ /** MessageList's "the reader is away from the bottom" flag. */
8
+ isUserScrolledUpRef: MutableRefObject<boolean>;
9
+ /** An unread delimiter means the list opens ON it, not at the bottom. */
10
+ hasUnreadDelimiter: boolean;
11
+ }
12
+ /**
13
+ * Keeps a transcript pinned to the bottom while it is still filling in.
14
+ *
15
+ * Opening a room scrolls to the bottom once, and then the room keeps
16
+ * growing: history pages arrive (prepended above), images and embeds finish
17
+ * sizing. The scrollTop stays where it was, so the reader is left hundreds of
18
+ * pixels above the newest message (measured 1477px, and it stayed there),
19
+ * looking at the middle of the conversation for no visible reason.
20
+ *
21
+ * So: whenever the content's height changes and the reader has not scrolled
22
+ * away, go back to the bottom. A reader who has scrolled up (which also
23
+ * covers a jump to a message, and paging older history by scrolling up) is
24
+ * left exactly where they are.
25
+ */
26
+ export declare function useStickToBottom({ containerRef, contentRef, isUserScrolledUpRef, hasUnreadDelimiter, }: Options): void;
27
+ export {};
@@ -0,0 +1,22 @@
1
+ import { default as React } from 'react';
2
+ import { MessageSearchHit } from '../../../networking/api-requests/messageSearch.api';
3
+ /**
4
+ * What a tap on a search hit does, shared by the search panel and the room
5
+ * list's search box: switch to the hit's room and ask it to scroll to, load
6
+ * and flash the message.
7
+ */
8
+ export declare function useMessageHitActions(): {
9
+ rooms: any;
10
+ senderName: (hit: MessageSearchHit) => string;
11
+ open: (hit: MessageSearchHit) => boolean;
12
+ isReachable: (hit: MessageSearchHit) => boolean;
13
+ };
14
+ interface MessageHitListProps {
15
+ hits: MessageSearchHit[];
16
+ query: string;
17
+ /** Prefix each row with its chat's title (search across chats). */
18
+ showRoom: boolean;
19
+ onOpen: (hit: MessageSearchHit) => void;
20
+ }
21
+ export declare const MessageHitList: React.FC<MessageHitListProps>;
22
+ export {};
@@ -0,0 +1,6 @@
1
+ import { default as React } from 'react';
2
+ interface MessageSearchModalProps {
3
+ handleCloseModal: () => void;
4
+ }
5
+ declare const MessageSearchModal: React.FC<MessageSearchModalProps>;
6
+ export default MessageSearchModal;
@@ -0,0 +1,33 @@
1
+ interface PersonLike {
2
+ _id?: string;
3
+ id?: string;
4
+ firstName?: string;
5
+ lastName?: string;
6
+ name?: string;
7
+ xmppUsername?: string;
8
+ }
9
+ /** `appId_userId@xmpp.host` (or without the host) -> `appId_userId`. */
10
+ export declare const localPart: (jid?: string) => string;
11
+ interface Sources {
12
+ /** rooms.usersSet: keyed by `appId_userId`. */
13
+ usersSet?: Record<string, PersonLike>;
14
+ /** The hit's room members, which carry the user id search reports. */
15
+ members?: PersonLike[];
16
+ myXmppUsername?: string;
17
+ }
18
+ /**
19
+ * Who sent a search hit.
20
+ *
21
+ * `from` arrives as a full JID with the XMPP host on it, while usersSet is
22
+ * keyed by the local part, so looking it up as-is found nobody and every hit
23
+ * was attributed to "Someone". The user id (`fromUserId`) is the other way
24
+ * in: room members carry it as `_id`.
25
+ */
26
+ export declare function resolveSender(hit: {
27
+ from: string;
28
+ fromUserId: string;
29
+ }, { usersSet, members, myXmppUsername }: Sources): {
30
+ name: string;
31
+ isSelf: boolean;
32
+ };
33
+ export {};
@@ -0,0 +1,34 @@
1
+ export interface SearchFilters {
2
+ /** Sender's user id (a room member's `_id`). */
3
+ fromUserId?: string;
4
+ /** ISO 8601 instant: start of the chosen day, in the reader's time zone. */
5
+ since?: string;
6
+ /** ISO 8601 instant: end of the chosen day, in the reader's time zone. */
7
+ until?: string;
8
+ }
9
+ /** `2026-07-14` -> that day's first instant in the reader's time zone. */
10
+ export declare function dayStartISO(day: string): string | undefined;
11
+ /**
12
+ * The day's LAST instant, not just the date. The server reads a bare date in
13
+ * `until` as midnight at the START of that day, so passing "2026-07-14"
14
+ * silently excluded the whole day the reader picked (measured on QA: 329
15
+ * hits with the bare date against 340 with the end of the day).
16
+ */
17
+ export declare function dayEndISO(day: string): string | undefined;
18
+ /** True when both days are set and the range runs backwards. */
19
+ export declare const isBackwardsRange: (from: string, to: string) => boolean;
20
+ export interface Person {
21
+ _id?: string;
22
+ firstName?: string;
23
+ lastName?: string;
24
+ name?: string;
25
+ }
26
+ /**
27
+ * Up to `limit` people whose name contains `needle`, best matches first
28
+ * (a name that STARTS with it ranks above one that merely contains it).
29
+ * Rooms here have thousands of members, so this never renders them all.
30
+ */
31
+ export declare function matchPeople(people: Iterable<Person>, needle: string, limit?: number): {
32
+ id: string;
33
+ name: string;
34
+ }[];
@@ -0,0 +1,14 @@
1
+ export interface SnippetPart {
2
+ text: string;
3
+ match: boolean;
4
+ }
5
+ /**
6
+ * The part of a message worth showing for a hit: a window around the first
7
+ * match, cut on word boundaries with ellipses, split into matching and
8
+ * non-matching runs so the caller can emphasise the match.
9
+ *
10
+ * Plain substring, case-insensitive, like the server's default mode. The
11
+ * query is never used as a regular expression, so `.` or `(` in it are just
12
+ * characters.
13
+ */
14
+ export declare function buildSnippet(body: string, query: string): SnippetPart[];
@@ -0,0 +1,22 @@
1
+ import { SearchFilters } from './searchFilters';
2
+ import { MessageSearchHit } from '../../../networking/api-requests/messageSearch.api';
3
+ export declare const MIN_QUERY_LENGTH = 2;
4
+ export type SearchScope = 'chat' | 'all';
5
+ /**
6
+ * Debounced, cancellable message search with paging.
7
+ *
8
+ * A newer query (or scope) aborts the request in flight, and a response that
9
+ * arrives after it was superseded is dropped, so a slow answer to "he" can
10
+ * never overwrite the results for "hello".
11
+ */
12
+ export declare function useMessageSearch(query: string, scope: SearchScope, roomName?: string, filters?: SearchFilters): {
13
+ searchable: boolean;
14
+ hasMore: boolean;
15
+ loadMore: () => void;
16
+ retry: () => any;
17
+ status: "idle" | "loading" | "loadingMore" | "done" | "error";
18
+ items: MessageSearchHit[];
19
+ total: number;
20
+ /** Server rows consumed so far: the offset of the next page. */
21
+ nextOffset: number;
22
+ };
@@ -0,0 +1,13 @@
1
+ import { default as React } from 'react';
2
+ interface PublicChatsModalProps {
3
+ handleCloseModal: () => void;
4
+ }
5
+ /**
6
+ * A directory of the app's public chats (GET /v1/chats/public), so finding a
7
+ * chat to join no longer needs someone to hand over a link or a QR code.
8
+ *
9
+ * Joining reuses what a shared link already does: selecting a room you are
10
+ * not in makes the room initialiser join it and refresh the room list.
11
+ */
12
+ declare const PublicChatsModal: React.FC<PublicChatsModalProps>;
13
+ export default PublicChatsModal;
@@ -37,3 +37,6 @@ export declare const SkeletonLines: import('styled-components/dist/types').IStyl
37
37
  export declare const SkeletonLine: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components/dist/types').Substitute<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
38
38
  $width?: string;
39
39
  }>> & string;
40
+ export declare const MessageMatches: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
41
+ export declare const MessageMatchesTitle: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
42
+ export declare const MessageMatchesNote: import('styled-components/dist/types').IStyledComponentBase<"web", import('styled-components').FastOmit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
@@ -7,4 +7,11 @@ export declare const ToastProvider: React.FC<{
7
7
  children: ReactNode;
8
8
  }>;
9
9
  export declare const useToast: () => ToastContextType;
10
+ /**
11
+ * `useToast` for code that can run outside a ToastProvider (a MessageList
12
+ * rendered on its own, a host that mounts pieces of the chat directly): it
13
+ * gets `undefined` instead of a thrown error, and simply has nowhere to show
14
+ * a toast.
15
+ */
16
+ export declare const useOptionalToast: () => ToastContextType | undefined;
10
17
  export {};
@@ -172,8 +172,26 @@ function pickUrl(raw = {}, data = {}, notification = {}) {
172
172
  );
173
173
  }
174
174
 
175
- function pickIcon() {
176
- return CUSTOM_ICON || DEFAULT_ICON;
175
+ // An icon the push itself carries (data.icon, notification.icon or icon on
176
+ // the payload) wins over the one configured for the whole app: a push about a
177
+ // specific chat or sender knows better than the app-wide default. Only http(s)
178
+ // and same-origin paths are accepted; anything else (data:, javascript:, a
179
+ // bare word) is ignored and the configured/default icon is used instead.
180
+ function sanitizeIconUrl(value) {
181
+ const url = typeof value === 'string' ? value.trim() : '';
182
+ if (!url) return '';
183
+ if (url.charAt(0) === '/' && url.charAt(1) !== '/') return url;
184
+ return /^https?:\/\/[^\s]+$/i.test(url) ? url : '';
185
+ }
186
+
187
+ function pickIcon(raw = {}, data = {}, notification = {}) {
188
+ return (
189
+ sanitizeIconUrl(data.icon) ||
190
+ sanitizeIconUrl(notification.icon) ||
191
+ sanitizeIconUrl(raw.icon) ||
192
+ CUSTOM_ICON ||
193
+ DEFAULT_ICON
194
+ );
177
195
  }
178
196
 
179
197
  function pickBadge() {
@@ -332,7 +350,7 @@ async function handlePush(raw = {}, source = 'push') {
332
350
  const title = pickTitle(raw, data, notification);
333
351
  const body = pickBody(raw, data, notification);
334
352
  const url = pickUrl(raw, data, notification);
335
- const icon = pickIcon();
353
+ const icon = pickIcon(raw, data, notification);
336
354
  const badge = pickBadge();
337
355
  const image = pickImage(raw, data, notification);
338
356
 
@@ -1,4 +1,4 @@
1
- import { x as e, z as t } from "./main-BRIZ98KV.js";
1
+ import { x as e, z as t } from "./main-rJpE4hh-.js";
2
2
  const i = e.div`
3
3
  display: flex;
4
4
  flex-direction: column;
@@ -4,6 +4,8 @@ export declare const MODAL_TYPES: {
4
4
  CHAT_PROFILE: string;
5
5
  MANAGE_DATA: string;
6
6
  VISIBILITY: string;
7
+ MESSAGE_SEARCH: string;
8
+ PUBLIC_CHATS: string;
7
9
  FILE_PREVIEW: string;
8
10
  };
9
11
  /**
@@ -1 +1 @@
1
- export declare const nanoToMs: (number: string) => number;
1
+ export declare const nanoToMs: (number?: string | number | null) => number;
@@ -0,0 +1,17 @@
1
+ import { AppDispatch } from '../roomStore';
2
+ type MaybeId = string | number | null | undefined;
3
+ /**
4
+ * Every id a caller may know the target message by, without blanks and
5
+ * duplicates. A notification, a push payload or a link can carry the client
6
+ * (xmpp) message id, the archive stanza id or the stored message id, and the
7
+ * jump matcher checks message.id and message.xmppId, so all of them go in.
8
+ */
9
+ export declare const collectMessageIds: (...candidates: MaybeId[]) => string[];
10
+ /**
11
+ * Open a room and, when the caller knows which message it is about, ask the
12
+ * mounted room to land on it (scroll if mounted, widen the window, or page
13
+ * older history; see useJumpToMessage). With no message id this is just
14
+ * "open the room".
15
+ */
16
+ export declare const openRoomAtMessage: (dispatch: AppDispatch, roomJID: string, ...messageIds: MaybeId[]) => void;
17
+ export {};
@@ -0,0 +1,14 @@
1
+ interface Options {
2
+ enabled: boolean;
3
+ /** Whether the search panel is already open (then the shortcut re-focuses it). */
4
+ isOpen: boolean;
5
+ open: () => void;
6
+ }
7
+ /**
8
+ * Ctrl/Cmd+F opens message search, but only while the reader is working in the
9
+ * chat (focus or pointer inside it). A host page that embeds the chat keeps its
10
+ * browser find everywhere else: hijacking it page-wide would be a hostile thing
11
+ * for an SDK to do.
12
+ */
13
+ export declare function useMessageSearchShortcut({ enabled, isOpen, open }: Options): void;
14
+ export {};
@@ -9,5 +9,6 @@ export declare const useRoomState: (roomJID?: string) => {
9
9
  roomMessages: any;
10
10
  roomsLoadedOnce: any;
11
11
  roomsLoadError: any;
12
+ joiningRoomJID: string;
12
13
  };
13
14
  export declare const useUsersSet: () => any;