@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.
- package/README.md +2 -0
- package/dist/{ChatProfileModal-Pr4--RdW.js → ChatProfileModal-DKuxJsdM.js} +526 -519
- package/dist/{FilePreviewModal-CYHlKzRc.js → FilePreviewModal-BcaSoO3-.js} +89 -89
- package/dist/{ManageDataModal-BE1eGlYh.js → ManageDataModal-Dw4dCQll.js} +2 -2
- package/dist/{MarkdownBody-G0_J0NL6.js → MarkdownBody-xtKDMR0h.js} +1 -1
- package/dist/MessageSearchModal-BVKU6H3v.js +389 -0
- package/dist/PublicChatsModal-CscOnVaQ.js +202 -0
- package/dist/{SideDrawer-BG2m7Bcm.js → SideDrawer-WneDrrWG.js} +27 -27
- package/dist/{UserProfileModal-B2Hp5bAF.js → UserProfileModal-CMHFq1I9.js} +3 -3
- package/dist/{UserSettingsModal-D871FDAT.js → UserSettingsModal-BoZXZ0oR.js} +2 -2
- package/dist/{VideoCallOverlay-Br0ji0nZ.js → VideoCallOverlay-CQNi7wp5.js} +1 -1
- package/dist/{VisibilityModal-DdFBV1_g.js → VisibilityModal-BPsNiMzJ.js} +2 -2
- package/dist/assets/icons.d.ts +9 -0
- package/dist/components/InputComponents/Search.d.ts +4 -0
- package/dist/components/MainComponents/ArchivedMessageCard.d.ts +11 -0
- package/dist/components/MainComponents/computeAnchoredScrollTop.d.ts +9 -0
- package/dist/components/MainComponents/useJumpToMessage.d.ts +63 -0
- package/dist/components/MainComponents/useStickToBottom.d.ts +27 -0
- package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +22 -0
- package/dist/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +6 -0
- package/dist/components/Modals/MessageSearchModal/resolveSender.d.ts +33 -0
- package/dist/components/Modals/MessageSearchModal/searchFilters.d.ts +34 -0
- package/dist/components/Modals/MessageSearchModal/snippet.d.ts +14 -0
- package/dist/components/Modals/MessageSearchModal/useMessageSearch.d.ts +22 -0
- package/dist/components/Modals/PublicChatsModal/PublicChatsModal.d.ts +13 -0
- package/dist/components/styled/RoomListComponents/index.d.ts +3 -0
- package/dist/context/ToastContext.d.ts +7 -0
- package/dist/firebase-messaging-sw.js +21 -3
- package/dist/{handleCopyClick-DPjxMoJn.js → handleCopyClick-B70vLjJD.js} +1 -1
- package/dist/helpers/constants/MODAL_TYPES.d.ts +2 -0
- package/dist/helpers/nanoToMs.d.ts +1 -1
- package/dist/helpers/openRoomAtMessage.d.ts +17 -0
- package/dist/hooks/useMessageSearchShortcut.d.ts +14 -0
- package/dist/hooks/useRoomState.d.ts +1 -0
- package/dist/{main-BRIZ98KV.js → main-rJpE4hh-.js} +12318 -11108
- package/dist/main.css +1 -1
- package/dist/main.js +6 -6
- package/dist/networking/api-requests/messageSearch.api.d.ts +56 -0
- package/dist/networking/api-requests/publicChats.api.d.ts +23 -0
- package/dist/networking/api-requests/xmppToken.api.d.ts +1 -0
- package/dist/networking/xmppClient.d.ts +2 -0
- package/dist/roomStore/chatSettingsSlice.d.ts +1 -1
- package/dist/roomStore/roomsSlice.d.ts +38 -1
- package/dist/styles/classNames.d.ts +2 -0
- package/dist/types/models/config.model.d.ts +11 -0
- package/dist/utils/notificationUtils.d.ts +12 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- 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,
|
|
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
|
-
`,
|
|
61
|
+
`, O = e.section`
|
|
62
62
|
display: flex;
|
|
63
63
|
flex-direction: column;
|
|
64
64
|
gap: var(--ethora-space-2, 8px);
|
|
65
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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
|
-
`,
|
|
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(
|
|
213
|
-
/* @__PURE__ */ i(
|
|
214
|
-
/* @__PURE__ */ o(
|
|
215
|
-
a && /* @__PURE__ */ o(
|
|
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(
|
|
218
|
-
] }),
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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-
|
|
4
|
-
import { D as E, a as W, b as Ge, e as qe, f as _e, S as Qe } from "./SideDrawer-
|
|
5
|
-
import { P as ne, a as Ye, D as Ze, b as eo, h as oo } from "./handleCopyClick-
|
|
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-
|
|
4
|
-
import { S as b, D as u, a as k, b as y, c as S, d as f } from "./SideDrawer-
|
|
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-
|
|
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-
|
|
4
|
-
import { S as N, D as C, a as $, b as k, e as w, f as p } from "./SideDrawer-
|
|
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;
|
package/dist/assets/icons.d.ts
CHANGED
|
@@ -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,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
|
-
|
|
176
|
-
|
|
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 +1 @@
|
|
|
1
|
-
export declare const nanoToMs: (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 {};
|