@fluid-app/portal-sdk 0.1.99 → 0.1.100
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/dist/{MessagingScreen-BGr41874.mjs → MessagingScreen-84R1a-lD.mjs} +1 -1
- package/dist/{MessagingScreen-DTXSZ-Oa.mjs → MessagingScreen-CBuI3fu6.mjs} +1204 -1112
- package/dist/MessagingScreen-CBuI3fu6.mjs.map +1 -0
- package/dist/{MessagingScreen-5jw8KSLQ.cjs → MessagingScreen-CGS7aG1A.cjs} +1 -1
- package/dist/{MessagingScreen-B9CCsimy.cjs → MessagingScreen-Cgx3jwpr.cjs} +1204 -1112
- package/dist/MessagingScreen-Cgx3jwpr.cjs.map +1 -0
- package/dist/{ProductsScreen-DCPVyEyQ.mjs → ProductsScreen-DbHS3p4U.mjs} +1 -0
- package/dist/{ShareablesScreen-jAj9hmif.mjs → ShareablesScreen-DUpaH8VU.mjs} +1 -0
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +429 -9
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +429 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +7 -7
- package/package.json +9 -9
- package/dist/MessagingScreen-B9CCsimy.cjs.map +0 -1
- package/dist/MessagingScreen-DTXSZ-Oa.mjs.map +0 -1
|
@@ -69,742 +69,6 @@ function useMessagingAuth() {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
//#endregion
|
|
72
|
-
//#region src/messaging/use-messaging-config.ts
|
|
73
|
-
/**
|
|
74
|
-
* Hook that derives MessagingApiConfig from the portal SDK's FluidProvider context.
|
|
75
|
-
*
|
|
76
|
-
* Maps FluidSDKConfig fields to the shape expected by MessagingApp:
|
|
77
|
-
* - baseUrl -> from config.baseUrl
|
|
78
|
-
* - getHeaders -> builds Authorization header from config.getAuthToken()
|
|
79
|
-
* - onAuthError -> from config.onAuthError
|
|
80
|
-
* - websocketUrl -> config.websocketUrl or derived from baseUrl
|
|
81
|
-
* - token -> from auth context
|
|
82
|
-
*/
|
|
83
|
-
function deriveWebsocketUrl(baseUrl) {
|
|
84
|
-
return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
|
|
85
|
-
}
|
|
86
|
-
function useMessagingConfig() {
|
|
87
|
-
const { config } = require_FluidProvider.useFluidContext();
|
|
88
|
-
const auth = require_FluidProvider.useFluidAuthContext();
|
|
89
|
-
const getHeaders = (0, react.useCallback)(async () => {
|
|
90
|
-
const headers = {
|
|
91
|
-
"Content-Type": "application/json",
|
|
92
|
-
...config.defaultHeaders
|
|
93
|
-
};
|
|
94
|
-
if (config.getAuthToken) {
|
|
95
|
-
const token = await config.getAuthToken();
|
|
96
|
-
if (token) headers.Authorization = `Bearer ${token}`;
|
|
97
|
-
}
|
|
98
|
-
return headers;
|
|
99
|
-
}, [config]);
|
|
100
|
-
const apiBaseUrl = (0, react.useMemo)(() => {
|
|
101
|
-
const base = config.baseUrl.replace(/\/+$/, "");
|
|
102
|
-
return base.endsWith("/api") ? base : `${base}/api`;
|
|
103
|
-
}, [config.baseUrl]);
|
|
104
|
-
return {
|
|
105
|
-
apiConfig: (0, react.useMemo)(() => ({
|
|
106
|
-
baseUrl: apiBaseUrl,
|
|
107
|
-
getHeaders,
|
|
108
|
-
...config.onAuthError != null && { onAuthError: config.onAuthError }
|
|
109
|
-
}), [
|
|
110
|
-
apiBaseUrl,
|
|
111
|
-
config.onAuthError,
|
|
112
|
-
getHeaders
|
|
113
|
-
]),
|
|
114
|
-
websocketUrl: (0, react.useMemo)(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
|
|
115
|
-
token: auth.token
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region ../../messaging/core/src/query-keys.ts
|
|
120
|
-
/**
|
|
121
|
-
* Query key factory for all messaging-related TanStack Query queries.
|
|
122
|
-
* Ensures consistent key shapes across hooks and cache helpers.
|
|
123
|
-
*/
|
|
124
|
-
const MESSAGES_QUERY_KEYS = {
|
|
125
|
-
all: () => ["messages"],
|
|
126
|
-
conversationMetadata: (conversationId) => [
|
|
127
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
128
|
-
"conversation",
|
|
129
|
-
conversationId
|
|
130
|
-
],
|
|
131
|
-
newMessageFlag: (conversationId) => [
|
|
132
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
133
|
-
"conversation",
|
|
134
|
-
conversationId,
|
|
135
|
-
"new_message_flag"
|
|
136
|
-
],
|
|
137
|
-
listConversations: () => [...MESSAGES_QUERY_KEYS.all(), "conversations"],
|
|
138
|
-
listMessages: (conversationId, messageId, startAtISO) => [
|
|
139
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
140
|
-
"conversation",
|
|
141
|
-
conversationId,
|
|
142
|
-
"messages",
|
|
143
|
-
messageId ? { startFromMessage: messageId } : startAtISO ? { startFromDate: startAtISO } : { startFromBottom: true }
|
|
144
|
-
],
|
|
145
|
-
listPinnedMessages: (conversationId) => [
|
|
146
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
147
|
-
"conversation",
|
|
148
|
-
conversationId,
|
|
149
|
-
"pinned"
|
|
150
|
-
],
|
|
151
|
-
outbox: (conversationId) => [
|
|
152
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
153
|
-
"conversation",
|
|
154
|
-
conversationId,
|
|
155
|
-
"outbox"
|
|
156
|
-
],
|
|
157
|
-
listScheduledMessages: (conversationId) => [
|
|
158
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
159
|
-
"conversation",
|
|
160
|
-
conversationId,
|
|
161
|
-
"scheduled"
|
|
162
|
-
],
|
|
163
|
-
listMyScheduledMessages: () => [
|
|
164
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
165
|
-
"scheduled",
|
|
166
|
-
"mine"
|
|
167
|
-
],
|
|
168
|
-
draft: (conversationId) => [
|
|
169
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
170
|
-
"draft",
|
|
171
|
-
conversationId
|
|
172
|
-
],
|
|
173
|
-
announcementChannel: () => [...MESSAGES_QUERY_KEYS.all(), "announcement-channel"],
|
|
174
|
-
downline: () => [...MESSAGES_QUERY_KEYS.all(), "downline"]
|
|
175
|
-
};
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region ../../messaging/core/src/constants.ts
|
|
178
|
-
const WEBSOCKET_QUERY_OPTIONS = {
|
|
179
|
-
staleTime: Infinity,
|
|
180
|
-
refetchOnMount: "always",
|
|
181
|
-
refetchOnReconnect: "always",
|
|
182
|
-
refetchOnWindowFocus: false
|
|
183
|
-
};
|
|
184
|
-
/**
|
|
185
|
-
* Scheduled messages are not websocket-driven; poll to keep fresh.
|
|
186
|
-
*/
|
|
187
|
-
const SCHEDULED_MESSAGES_STALE_TIME = 6e4;
|
|
188
|
-
const SCHEDULED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
189
|
-
/**
|
|
190
|
-
* Pinned messages are not websocket-driven; poll to keep fresh.
|
|
191
|
-
*/
|
|
192
|
-
const PINNED_MESSAGES_STALE_TIME = 6e4;
|
|
193
|
-
const PINNED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
194
|
-
//#endregion
|
|
195
|
-
//#region ../../messaging/core/src/cache-helpers.ts
|
|
196
|
-
/**
|
|
197
|
-
* seed conversation metadata caches from any conversations list queries.
|
|
198
|
-
* returns the matched conversation if conversationId is provided and present in any list cache.
|
|
199
|
-
*/
|
|
200
|
-
function seedConversationMetadataFromLists(queryClient, conversationId) {
|
|
201
|
-
let matched;
|
|
202
|
-
const seedFromData = (data) => {
|
|
203
|
-
const pages = data?.pages ?? [];
|
|
204
|
-
for (const page of pages) {
|
|
205
|
-
const items = page?.[1]?.items ?? [];
|
|
206
|
-
for (const item of items) {
|
|
207
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(item.id), item);
|
|
208
|
-
if (!matched && typeof conversationId === "number" && item.id === conversationId) matched = item;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
seedFromData(queryClient.getQueryData(MESSAGES_QUERY_KEYS.listConversations()));
|
|
213
|
-
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
214
|
-
const key = q.queryKey;
|
|
215
|
-
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
216
|
-
} });
|
|
217
|
-
for (const q of listQueries) seedFromData(queryClient.getQueryData(q.queryKey));
|
|
218
|
-
return matched;
|
|
219
|
-
}
|
|
220
|
-
function isMessagesQueryKeyForConversation(queryKey, conversationId) {
|
|
221
|
-
if (!Array.isArray(queryKey)) return false;
|
|
222
|
-
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "messages";
|
|
223
|
-
}
|
|
224
|
-
function findMessageInCache(queryClient, conversationId, messageId) {
|
|
225
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
226
|
-
for (const query of queries) {
|
|
227
|
-
const data = queryClient.getQueryData(query.queryKey);
|
|
228
|
-
if (data) for (const page of data.pages) {
|
|
229
|
-
const message = page[1].items.find((m) => m.id === messageId);
|
|
230
|
-
if (message) return message;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function forEachMessagesQuery(queryClient, conversationId, fn) {
|
|
235
|
-
queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
236
|
-
}
|
|
237
|
-
function isBottomAnchorKey(queryKey) {
|
|
238
|
-
if (!Array.isArray(queryKey)) return false;
|
|
239
|
-
const anchor = queryKey[queryKey.length - 1];
|
|
240
|
-
if (typeof anchor === "object" && anchor !== null) {
|
|
241
|
-
if ("startFromBottom" in anchor) return anchor.startFromBottom === true;
|
|
242
|
-
}
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
function isPinnedQueryKeyForConversation(queryKey, conversationId) {
|
|
246
|
-
if (!Array.isArray(queryKey)) return false;
|
|
247
|
-
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "pinned";
|
|
248
|
-
}
|
|
249
|
-
function forEachPinnedQuery(queryClient, conversationId, fn) {
|
|
250
|
-
queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
251
|
-
}
|
|
252
|
-
async function cancelMessageQueries(queryClient, conversationId) {
|
|
253
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
254
|
-
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
255
|
-
}
|
|
256
|
-
async function cancelPinnedQueries(queryClient, conversationId) {
|
|
257
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) });
|
|
258
|
-
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
259
|
-
}
|
|
260
|
-
function applyMessagesOptimisticSend({ queryClient, conversationId, optimisticMessage }) {
|
|
261
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
262
|
-
if (!isBottomAnchorKey(key)) return;
|
|
263
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
264
|
-
const newOptimisticPage = [{ pagination: {
|
|
265
|
-
current: 1,
|
|
266
|
-
previous: null,
|
|
267
|
-
next: null,
|
|
268
|
-
per_page: oldData?.pages?.[0]?.[0]?.pagination?.per_page ?? 20,
|
|
269
|
-
pages: oldData?.pages?.[0]?.[0]?.pagination?.pages ?? 1,
|
|
270
|
-
count: oldData?.pages?.[0]?.[0]?.pagination?.count ?? 0
|
|
271
|
-
} }, { items: [optimisticMessage] }];
|
|
272
|
-
if (!oldData) return {
|
|
273
|
-
pages: [newOptimisticPage],
|
|
274
|
-
pageParams: [1]
|
|
275
|
-
};
|
|
276
|
-
const pages = oldData.pages.map((page, idx) => {
|
|
277
|
-
if (idx === 0) {
|
|
278
|
-
const currentItems = page?.[1]?.items ?? [];
|
|
279
|
-
return [{ pagination: {
|
|
280
|
-
...page?.[0]?.pagination ?? {},
|
|
281
|
-
count: page?.[0]?.pagination?.count ?? 0
|
|
282
|
-
} }, { items: [optimisticMessage, ...currentItems] }];
|
|
283
|
-
}
|
|
284
|
-
return page;
|
|
285
|
-
});
|
|
286
|
-
return {
|
|
287
|
-
...oldData,
|
|
288
|
-
pages
|
|
289
|
-
};
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
293
|
-
const prev = old?.items ?? [];
|
|
294
|
-
if (prev.some((m) => m.id === optimisticMessage.id)) return old;
|
|
295
|
-
return { items: [...prev, optimisticMessage] };
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* markNewMessageFlagForConversation
|
|
300
|
-
*
|
|
301
|
-
* sets a lightweight flag query for a conversation indicating a new message
|
|
302
|
-
* arrived while the user may not be at the bottom. UI can reset it after
|
|
303
|
-
* scrolling or showing a pill.
|
|
304
|
-
*/
|
|
305
|
-
function markNewMessageFlagForConversation(queryClient, conversationId) {
|
|
306
|
-
queryClient.setQueryData([
|
|
307
|
-
MESSAGES_QUERY_KEYS.all()[0],
|
|
308
|
-
"conversation",
|
|
309
|
-
conversationId,
|
|
310
|
-
"new_message_flag"
|
|
311
|
-
], true);
|
|
312
|
-
}
|
|
313
|
-
function clearNewMessageFlagForConversation(queryClient, conversationId) {
|
|
314
|
-
queryClient.setQueryData([
|
|
315
|
-
MESSAGES_QUERY_KEYS.all()[0],
|
|
316
|
-
"conversation",
|
|
317
|
-
conversationId,
|
|
318
|
-
"new_message_flag"
|
|
319
|
-
], false);
|
|
320
|
-
}
|
|
321
|
-
function applyMessageUpsertToCache({ queryClient, conversationId, message, replaceOptimisticId, clientMessageId }) {
|
|
322
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
323
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
324
|
-
if (!oldData?.pages?.length) {
|
|
325
|
-
if (!isBottomAnchorKey(key)) return oldData;
|
|
326
|
-
return {
|
|
327
|
-
pages: [[{ pagination: {
|
|
328
|
-
current: 1,
|
|
329
|
-
previous: null,
|
|
330
|
-
next: null,
|
|
331
|
-
per_page: 20,
|
|
332
|
-
pages: 1,
|
|
333
|
-
count: 1
|
|
334
|
-
} }, { items: [message] }]],
|
|
335
|
-
pageParams: [1]
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const normalizedClientMessageId = (() => {
|
|
339
|
-
if (typeof clientMessageId === "string" && clientMessageId.length > 0) return clientMessageId;
|
|
340
|
-
const fromMeta = (() => {
|
|
341
|
-
const meta = message.metadata;
|
|
342
|
-
if (meta && typeof meta === "object" && "client_message_id" in meta) {
|
|
343
|
-
const id = meta.client_message_id;
|
|
344
|
-
if (typeof id === "string" || typeof id === "number" && Number.isFinite(id)) return id;
|
|
345
|
-
}
|
|
346
|
-
})();
|
|
347
|
-
if (typeof fromMeta === "string" || typeof fromMeta === "number" && Number.isFinite(fromMeta)) return String(fromMeta);
|
|
348
|
-
})();
|
|
349
|
-
const finalMessage = (() => {
|
|
350
|
-
if (!normalizedClientMessageId) return message;
|
|
351
|
-
const currentMeta = message.metadata;
|
|
352
|
-
if (currentMeta != null && typeof currentMeta === "object" && (typeof currentMeta.client_message_id === "string" || typeof currentMeta.client_message_id === "number")) return message;
|
|
353
|
-
return {
|
|
354
|
-
...message,
|
|
355
|
-
metadata: {
|
|
356
|
-
...currentMeta && typeof currentMeta === "object" ? currentMeta : {},
|
|
357
|
-
client_message_id: normalizedClientMessageId
|
|
358
|
-
}
|
|
359
|
-
};
|
|
360
|
-
})();
|
|
361
|
-
let replaced = false;
|
|
362
|
-
const pages = oldData.pages.map((page) => {
|
|
363
|
-
const [meta, payload] = page;
|
|
364
|
-
const items = payload.items.slice();
|
|
365
|
-
const idxById = items.findIndex((m) => m.id === (replaceOptimisticId ?? message.id));
|
|
366
|
-
if (idxById !== -1) {
|
|
367
|
-
items[idxById] = finalMessage;
|
|
368
|
-
replaced = true;
|
|
369
|
-
return [meta, { items }];
|
|
370
|
-
}
|
|
371
|
-
if (normalizedClientMessageId) {
|
|
372
|
-
const idxByClientId = items.findIndex((m) => typeof m?.metadata === "object" && m?.metadata != null && ((id) => id != null && String(id) === normalizedClientMessageId)(m.metadata?.client_message_id));
|
|
373
|
-
if (idxByClientId !== -1) {
|
|
374
|
-
items[idxByClientId] = finalMessage;
|
|
375
|
-
replaced = true;
|
|
376
|
-
return [meta, { items }];
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
const idxExisting = items.findIndex((m) => m.id === message.id);
|
|
380
|
-
if (idxExisting !== -1) {
|
|
381
|
-
items[idxExisting] = finalMessage;
|
|
382
|
-
replaced = true;
|
|
383
|
-
return [meta, { items }];
|
|
384
|
-
}
|
|
385
|
-
return [meta, { items }];
|
|
386
|
-
});
|
|
387
|
-
if (!replaced) {
|
|
388
|
-
if (!isBottomAnchorKey(key)) return oldData;
|
|
389
|
-
const first = pages[0];
|
|
390
|
-
const nextPages = pages.slice();
|
|
391
|
-
const newFirstItems = [finalMessage, ...first?.[1]?.items ?? []];
|
|
392
|
-
const newCount = (first?.[0]?.pagination?.count ?? 0) + 1;
|
|
393
|
-
nextPages[0] = [{ pagination: {
|
|
394
|
-
...first?.[0]?.pagination ?? {},
|
|
395
|
-
count: newCount
|
|
396
|
-
} }, { items: newFirstItems }];
|
|
397
|
-
return {
|
|
398
|
-
...oldData,
|
|
399
|
-
pages: nextPages
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
return {
|
|
403
|
-
...oldData,
|
|
404
|
-
pages
|
|
405
|
-
};
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
409
|
-
if (!old?.items) return old;
|
|
410
|
-
return { items: old.items.filter((m) => m.id !== (replaceOptimisticId ?? -99999999)) };
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
function applyMessageDeleteFromCache({ queryClient, conversationId, messageId }) {
|
|
414
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
415
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
416
|
-
if (!oldData) return oldData;
|
|
417
|
-
const pages = oldData.pages.map((page) => {
|
|
418
|
-
const [meta, payload] = page;
|
|
419
|
-
const items = payload.items.filter((m) => m.id !== messageId);
|
|
420
|
-
const newCount = Math.max(0, (meta?.pagination?.count ?? 0) - (payload.items.length - items.length));
|
|
421
|
-
return [{ pagination: {
|
|
422
|
-
...meta?.pagination ?? {},
|
|
423
|
-
count: newCount
|
|
424
|
-
} }, { items }];
|
|
425
|
-
});
|
|
426
|
-
return {
|
|
427
|
-
...oldData,
|
|
428
|
-
pages
|
|
429
|
-
};
|
|
430
|
-
});
|
|
431
|
-
});
|
|
432
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
433
|
-
if (!old?.items) return old;
|
|
434
|
-
return { items: old.items.filter((m) => m.id !== messageId) };
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
function applyReactionUpdateToCache({ queryClient, conversationId, messageId, reaction, reaction_stats }) {
|
|
438
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
439
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
440
|
-
if (!oldData) return oldData;
|
|
441
|
-
const pages = oldData.pages.map((page) => {
|
|
442
|
-
const [meta, payload] = page;
|
|
443
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
444
|
-
...m,
|
|
445
|
-
reaction,
|
|
446
|
-
reaction_stats
|
|
447
|
-
} : m) }];
|
|
448
|
-
});
|
|
449
|
-
return {
|
|
450
|
-
...oldData,
|
|
451
|
-
pages
|
|
452
|
-
};
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
456
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
457
|
-
if (!oldData) return oldData;
|
|
458
|
-
const pages = oldData.pages.map((page) => {
|
|
459
|
-
const [meta, payload] = page;
|
|
460
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
461
|
-
...m,
|
|
462
|
-
reaction,
|
|
463
|
-
reaction_stats
|
|
464
|
-
} : m) }];
|
|
465
|
-
});
|
|
466
|
-
return {
|
|
467
|
-
...oldData,
|
|
468
|
-
pages
|
|
469
|
-
};
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
function applyReactionStatsToCache({ queryClient, conversationId, messageId, reaction_stats }) {
|
|
474
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
475
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
476
|
-
if (!oldData) return;
|
|
477
|
-
const pages = oldData.pages.map((page) => {
|
|
478
|
-
const [meta, payload] = page;
|
|
479
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
480
|
-
...m,
|
|
481
|
-
reaction_stats
|
|
482
|
-
} : m) }];
|
|
483
|
-
});
|
|
484
|
-
return {
|
|
485
|
-
...oldData,
|
|
486
|
-
pages
|
|
487
|
-
};
|
|
488
|
-
});
|
|
489
|
-
});
|
|
490
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
491
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
492
|
-
if (!oldData) return oldData;
|
|
493
|
-
const pages = oldData.pages.map((page) => {
|
|
494
|
-
const [meta, payload] = page;
|
|
495
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
496
|
-
...m,
|
|
497
|
-
reaction_stats
|
|
498
|
-
} : m) }];
|
|
499
|
-
});
|
|
500
|
-
return {
|
|
501
|
-
...oldData,
|
|
502
|
-
pages
|
|
503
|
-
};
|
|
504
|
-
});
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
function applyConversationMetaToCaches({ queryClient, conversation }) {
|
|
508
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(conversation.id), (old) => {
|
|
509
|
-
if (!old) return old;
|
|
510
|
-
return {
|
|
511
|
-
...old,
|
|
512
|
-
...conversation
|
|
513
|
-
};
|
|
514
|
-
});
|
|
515
|
-
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
516
|
-
const key = q.queryKey;
|
|
517
|
-
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
518
|
-
} });
|
|
519
|
-
for (const q of listQueries) queryClient.setQueryData(q.queryKey, (oldData) => {
|
|
520
|
-
const data = oldData;
|
|
521
|
-
if (!data?.pages) return oldData;
|
|
522
|
-
return {
|
|
523
|
-
...data,
|
|
524
|
-
pages: data.pages.map((page) => [page[0], {
|
|
525
|
-
...page[1],
|
|
526
|
-
items: page[1].items.map((conv) => conv.id === conversation.id ? {
|
|
527
|
-
...conv,
|
|
528
|
-
...typeof conversation.unread === "boolean" ? { unread: conversation.unread } : {},
|
|
529
|
-
...typeof conversation.unread_messages_count === "number" ? { unread_messages_count: conversation.unread_messages_count } : {}
|
|
530
|
-
} : conv)
|
|
531
|
-
}])
|
|
532
|
-
};
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
function applyConversationDestroyed({ queryClient, conversationId }) {
|
|
536
|
-
queryClient.removeQueries({ queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId) });
|
|
537
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.listConversations(), (oldData) => {
|
|
538
|
-
const data = oldData;
|
|
539
|
-
if (!data?.pages) return oldData;
|
|
540
|
-
return {
|
|
541
|
-
...data,
|
|
542
|
-
pages: data.pages.map((page) => [page[0], {
|
|
543
|
-
...page[1],
|
|
544
|
-
items: page[1].items.filter((c) => c.id !== conversationId)
|
|
545
|
-
}])
|
|
546
|
-
};
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
function applyMessagePinnedFlag({ queryClient, conversationId, messageId, pinned, message }) {
|
|
550
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
551
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
552
|
-
if (!oldData) return oldData;
|
|
553
|
-
const pages = oldData.pages.map((page) => {
|
|
554
|
-
const [meta, payload] = page;
|
|
555
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
556
|
-
...m,
|
|
557
|
-
pinned
|
|
558
|
-
} : m) }];
|
|
559
|
-
});
|
|
560
|
-
return {
|
|
561
|
-
...oldData,
|
|
562
|
-
pages
|
|
563
|
-
};
|
|
564
|
-
});
|
|
565
|
-
});
|
|
566
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
567
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
568
|
-
if (!oldData) return oldData;
|
|
569
|
-
const pages = oldData.pages.map((page, idx) => {
|
|
570
|
-
const [meta, payload] = page;
|
|
571
|
-
if (idx !== 0) return page;
|
|
572
|
-
const exists = payload.items.some((m) => m.id === messageId);
|
|
573
|
-
if (pinned) {
|
|
574
|
-
if (exists) return page;
|
|
575
|
-
const msg = message ?? payload.items.find((m) => m.id === messageId);
|
|
576
|
-
if (!msg) return page;
|
|
577
|
-
return [meta, { items: [msg, ...payload.items] }];
|
|
578
|
-
}
|
|
579
|
-
return [meta, { items: payload.items.filter((m) => m.id !== messageId) }];
|
|
580
|
-
});
|
|
581
|
-
return {
|
|
582
|
-
...oldData,
|
|
583
|
-
pages
|
|
584
|
-
};
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
//#endregion
|
|
589
|
-
//#region ../../messaging/core/src/utils/reaction-utils.ts
|
|
590
|
-
/**
|
|
591
|
-
* Splits an emoji string into an array of grapheme clusters (perceived characters/emojis).
|
|
592
|
-
* Uses `Intl.Segmenter` when available for correct multi-codepoint emoji handling.
|
|
593
|
-
* Falls back to `[...string]` (code-point iteration) on browsers without `Intl.Segmenter`
|
|
594
|
-
* (e.g. Firefox < 119), which may incorrectly split ZWJ sequences, skin-tone modifiers,
|
|
595
|
-
* and flag emojis into multiple entries.
|
|
596
|
-
* @param emojiString The string of emojis to split.
|
|
597
|
-
* @returns An array of strings, where each string is a single emoji.
|
|
598
|
-
*/
|
|
599
|
-
function splitEmojis(emojiString) {
|
|
600
|
-
if (!emojiString) return [];
|
|
601
|
-
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
602
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
603
|
-
return Array.from(segmenter.segment(emojiString)).map((segment) => segment.segment);
|
|
604
|
-
}
|
|
605
|
-
return [...emojiString];
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Flattens reaction_stats to count individual emojis.
|
|
609
|
-
* E.g., {"😀😂": 1, "👍": 2, "😀": 1} becomes {"😀": 2, "😂": 1, "👍": 2}
|
|
610
|
-
* Handles multi-codepoint emojis correctly.
|
|
611
|
-
* @param stats The raw reaction_stats object from the message.
|
|
612
|
-
* @returns A record where keys are single emojis and values are their total counts.
|
|
613
|
-
*/
|
|
614
|
-
function flattenReactionStats(stats) {
|
|
615
|
-
if (!stats) return {};
|
|
616
|
-
const flattened = {};
|
|
617
|
-
for (const emojiKey in stats) {
|
|
618
|
-
const count = stats[emojiKey];
|
|
619
|
-
if (typeof count !== "number" || count === 0) continue;
|
|
620
|
-
const individualEmojis = splitEmojis(emojiKey);
|
|
621
|
-
for (const emoji of individualEmojis) flattened[emoji] = (flattened[emoji] || 0) + count;
|
|
622
|
-
}
|
|
623
|
-
return flattened;
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Calculates the optimistic state for a message's reactions when a user's reaction changes.
|
|
627
|
-
* @param currentState The current reaction parts of the message ({ reaction, reaction_stats }).
|
|
628
|
-
* @param newUserReaction The user's new complete reaction string (e.g., "😀👍", "😂", or null to unreact).
|
|
629
|
-
* @returns An object with the updated `reaction` and `reaction_stats`.
|
|
630
|
-
*/
|
|
631
|
-
function calculateOptimisticReactionUpdate(currentState, newUserReaction) {
|
|
632
|
-
const previousUserReaction = currentState.reaction;
|
|
633
|
-
const newStats = { ...currentState.reaction_stats || {} };
|
|
634
|
-
const finalNewUserReaction = newUserReaction === "" ? null : newUserReaction;
|
|
635
|
-
if (previousUserReaction === finalNewUserReaction) return {
|
|
636
|
-
reaction: previousUserReaction,
|
|
637
|
-
reaction_stats: newStats
|
|
638
|
-
};
|
|
639
|
-
if (previousUserReaction && typeof newStats[previousUserReaction] === "number") newStats[previousUserReaction] = Math.max(0, newStats[previousUserReaction] - 1);
|
|
640
|
-
if (finalNewUserReaction) newStats[finalNewUserReaction] = (typeof newStats[finalNewUserReaction] === "number" ? newStats[finalNewUserReaction] : 0) + 1;
|
|
641
|
-
return {
|
|
642
|
-
reaction: finalNewUserReaction,
|
|
643
|
-
reaction_stats: newStats
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Generates a unique, sorted string from an array of emoji characters.
|
|
648
|
-
* Each string in the input array is assumed to be a single, correctly segmented emoji.
|
|
649
|
-
* E.g., ["😂", "😀", "😂"] becomes "😀😂"
|
|
650
|
-
* @param emojis An array of single emoji strings (grapheme clusters).
|
|
651
|
-
* @returns A sorted string of unique emojis, or an empty string if no emojis are provided.
|
|
652
|
-
*/
|
|
653
|
-
function generateSortedEmojiStringFromArray(emojis) {
|
|
654
|
-
if (!emojis || emojis.length === 0) return "";
|
|
655
|
-
return [...new Set(emojis)].sort((a, b) => a.localeCompare(b, "en")).join("");
|
|
656
|
-
}
|
|
657
|
-
//#endregion
|
|
658
|
-
//#region ../../messaging/core/src/utils/messaging.ts
|
|
659
|
-
function mapConversationTypeToMessageType(conversationType) {
|
|
660
|
-
switch (conversationType) {
|
|
661
|
-
case "DirectConversation": return "DirectMessage";
|
|
662
|
-
case "SmsConversation": return "SmsMessage";
|
|
663
|
-
case "EmailConversation": return "EmailMessage";
|
|
664
|
-
case "ChannelConversation":
|
|
665
|
-
case "GroupConversation": return "DirectMessage";
|
|
666
|
-
case "BotConversation": return "BotMessage";
|
|
667
|
-
default: return "DirectMessage";
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Determines the category of a chat. This is slightly different from the backend's
|
|
672
|
-
* category field.
|
|
673
|
-
*
|
|
674
|
-
* @param item - Conversation item to check
|
|
675
|
-
* @returns the type of channel
|
|
676
|
-
*/
|
|
677
|
-
const getMessageType = (item) => {
|
|
678
|
-
if (item.type === "GroupConversation" && item.name.toLowerCase().includes("other")) return {
|
|
679
|
-
isChannel: false,
|
|
680
|
-
type: "group-direct-message"
|
|
681
|
-
};
|
|
682
|
-
if (item.broadcasting) return {
|
|
683
|
-
isChannel: true,
|
|
684
|
-
type: "broadcast"
|
|
685
|
-
};
|
|
686
|
-
if (item.type === "GroupConversation") return {
|
|
687
|
-
isChannel: true,
|
|
688
|
-
type: "private-channel"
|
|
689
|
-
};
|
|
690
|
-
if (item.type === "DirectConversation") return {
|
|
691
|
-
isChannel: false,
|
|
692
|
-
type: "direct-message"
|
|
693
|
-
};
|
|
694
|
-
if (item.type === "EmailConversation") return {
|
|
695
|
-
isChannel: false,
|
|
696
|
-
type: "email"
|
|
697
|
-
};
|
|
698
|
-
if (item.type === "SmsConversation") return {
|
|
699
|
-
isChannel: false,
|
|
700
|
-
type: "sms"
|
|
701
|
-
};
|
|
702
|
-
if (item.type === "ChannelConversation") return {
|
|
703
|
-
isChannel: true,
|
|
704
|
-
type: "public-channel"
|
|
705
|
-
};
|
|
706
|
-
if (item.type === "BotConversation") return {
|
|
707
|
-
isChannel: false,
|
|
708
|
-
type: "bot"
|
|
709
|
-
};
|
|
710
|
-
if (item.type === "DownlineConversation") return {
|
|
711
|
-
isChannel: true,
|
|
712
|
-
type: "downline"
|
|
713
|
-
};
|
|
714
|
-
throw new Error("Invalid conversation type");
|
|
715
|
-
};
|
|
716
|
-
const getUserInitials = (conversation, user) => {
|
|
717
|
-
if (user.phone === conversation.name) return "#";
|
|
718
|
-
if (user.email === conversation.name) return "@";
|
|
719
|
-
return [user.first_name?.charAt(0), user.last_name?.charAt(0)].filter(Boolean).join("") || "?";
|
|
720
|
-
};
|
|
721
|
-
const getFileTypeFromMimetype = (mimetype) => {
|
|
722
|
-
if (mimetype.startsWith("image/")) return "image";
|
|
723
|
-
if (mimetype.startsWith("video/")) return "video";
|
|
724
|
-
if (mimetype === "application/pdf") return "pdf";
|
|
725
|
-
if (mimetype.includes("spreadsheet") || mimetype.includes("excel") || mimetype.includes("sheet") || mimetype === "text/csv") return "sheets";
|
|
726
|
-
if (mimetype.includes("powerpoint") || mimetype.includes("presentation")) return "powerpoint";
|
|
727
|
-
if (mimetype.includes("document") || mimetype.includes("word") || mimetype.includes("rtf")) return "docx";
|
|
728
|
-
if (mimetype.startsWith("text/")) return "txt";
|
|
729
|
-
if (mimetype.includes("zip") || mimetype.includes("compressed") || mimetype.includes("archive") || mimetype.includes("tar") || mimetype.includes("gzip")) return "compressed";
|
|
730
|
-
if (mimetype.startsWith("audio/")) return "audio";
|
|
731
|
-
return "unknown";
|
|
732
|
-
};
|
|
733
|
-
//#endregion
|
|
734
|
-
//#region ../../messaging/core/src/utils/string-utils.ts
|
|
735
|
-
/**
|
|
736
|
-
* Extracts the leading emoji from a string.
|
|
737
|
-
* @param text The string to process.
|
|
738
|
-
* @returns An object containing the emoji and the remaining text.
|
|
739
|
-
*/
|
|
740
|
-
function extractEmoji$1(text) {
|
|
741
|
-
const match = text.match(/^\p{Emoji_Presentation}/u);
|
|
742
|
-
if (match) {
|
|
743
|
-
const emoji = match[0];
|
|
744
|
-
return {
|
|
745
|
-
emoji,
|
|
746
|
-
text: text.substring(emoji.length).trimStart()
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
return {
|
|
750
|
-
emoji: null,
|
|
751
|
-
text
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Formats a message channel name by replacing spaces and underscores with hyphens,
|
|
756
|
-
* converting to lowercase, and removing any non-alphanumeric characters except hyphens.
|
|
757
|
-
* @param name - The original channel name
|
|
758
|
-
* @return The formatted channel name
|
|
759
|
-
*/
|
|
760
|
-
function formatMessageChannelName$1(name) {
|
|
761
|
-
return name.toLowerCase().replace(/[ _]/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
762
|
-
}
|
|
763
|
-
const MAX_GROUP_NAME_LENGTH$1 = 30;
|
|
764
|
-
function formatGroupDisplayName$1(names, maxLength = MAX_GROUP_NAME_LENGTH$1) {
|
|
765
|
-
const fullDisplayName = names.join(", ");
|
|
766
|
-
if (fullDisplayName.length <= maxLength) return { displayName: fullDisplayName };
|
|
767
|
-
for (let i = 0; i < names.length; i++) {
|
|
768
|
-
const currentNames = names.slice(0, i + 1);
|
|
769
|
-
const remainingCount = names.length - currentNames.length;
|
|
770
|
-
if (remainingCount > 0) {
|
|
771
|
-
const suffix = `+ ${remainingCount} other${remainingCount > 1 ? "s" : ""}`;
|
|
772
|
-
if (`${currentNames.join(", ")} ${suffix}`.length > maxLength && i > 0) {
|
|
773
|
-
const namesToShow = names.slice(0, i);
|
|
774
|
-
const othersCount = names.length - i;
|
|
775
|
-
const othersSuffix = `+${othersCount} other${othersCount > 1 ? "s" : ""}`;
|
|
776
|
-
const truncatedDisplayName = namesToShow.join(", ");
|
|
777
|
-
if (othersCount === 1) {
|
|
778
|
-
const nameWithOneMore = names.slice(0, i + 1).join(", ");
|
|
779
|
-
if (nameWithOneMore.length < truncatedDisplayName.length) return { displayName: nameWithOneMore };
|
|
780
|
-
}
|
|
781
|
-
return {
|
|
782
|
-
displayName: truncatedDisplayName,
|
|
783
|
-
remainder: othersSuffix
|
|
784
|
-
};
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
const firstName = names[0] || "";
|
|
789
|
-
const othersCount = names.length - 1;
|
|
790
|
-
if (othersCount > 0) return {
|
|
791
|
-
displayName: firstName,
|
|
792
|
-
remainder: `+ ${othersCount} other${othersCount > 1 ? "s" : ""}`
|
|
793
|
-
};
|
|
794
|
-
return { displayName: firstName };
|
|
795
|
-
}
|
|
796
|
-
//#endregion
|
|
797
|
-
//#region ../../messaging/core/src/utils/file-kinds.ts
|
|
798
|
-
const ServerFileKindEnum$1 = zod.z.enum([
|
|
799
|
-
"image",
|
|
800
|
-
"video",
|
|
801
|
-
"pdf",
|
|
802
|
-
"sheets",
|
|
803
|
-
"powerpoint",
|
|
804
|
-
"docx"
|
|
805
|
-
]);
|
|
806
|
-
const fallbackFileType$1 = "docx";
|
|
807
|
-
//#endregion
|
|
808
72
|
//#region ../../messaging/api-client/src/client.ts
|
|
809
73
|
/**
|
|
810
74
|
* Messaging-specific API error, extends the shared ApiError from api-client-core.
|
|
@@ -1094,7 +358,7 @@ const WithContactRecipientSchema = zod.z.strictObject({
|
|
|
1094
358
|
});
|
|
1095
359
|
createPaginatedSchema(RecipientSchema);
|
|
1096
360
|
createPaginatedSchema(ReducedRecipientSchema);
|
|
1097
|
-
zod.z.strictObject({ meta: zod.z.strictObject({ count: zod.z.number() }) });
|
|
361
|
+
const RecipientSearchCountSchema = zod.z.strictObject({ meta: zod.z.strictObject({ count: zod.z.number() }) });
|
|
1098
362
|
//#endregion
|
|
1099
363
|
//#region ../../messaging/api-client/src/schemas/drafts-scheduled.schema.ts
|
|
1100
364
|
const DraftMessageSchema = zod.z.strictObject({
|
|
@@ -1240,7 +504,7 @@ zod.z.strictObject({ success: zod.z.boolean() }).or(zod.z.strictObject({
|
|
|
1240
504
|
*
|
|
1241
505
|
* These schemas normalize the slightly different index vs show/update shapes.
|
|
1242
506
|
*/
|
|
1243
|
-
const ReactionStatsSchema = zod.z.record(zod.z.string(), zod.z.number());
|
|
507
|
+
const ReactionStatsSchema$1 = zod.z.record(zod.z.string(), zod.z.number());
|
|
1244
508
|
const MessageCoreSchema = zod.z.strictObject({
|
|
1245
509
|
id: zod.z.number(),
|
|
1246
510
|
body: zod.z.string(),
|
|
@@ -1252,7 +516,7 @@ const MessageCoreSchema = zod.z.strictObject({
|
|
|
1252
516
|
conversation_id: zod.z.number(),
|
|
1253
517
|
status: zod.z.string(),
|
|
1254
518
|
attachments: zod.z.array(AttachmentSchema),
|
|
1255
|
-
reaction_stats: ReactionStatsSchema.nullable(),
|
|
519
|
+
reaction_stats: ReactionStatsSchema$1.nullable(),
|
|
1256
520
|
reaction: zod.z.string().nullable().optional(),
|
|
1257
521
|
metadata: MessageMetadataSchema.optional(),
|
|
1258
522
|
emoji_only_count: zod.z.number().optional(),
|
|
@@ -1266,9 +530,9 @@ const MessageCoreSchema = zod.z.strictObject({
|
|
|
1266
530
|
seen_by_count: zod.z.unknown()
|
|
1267
531
|
});
|
|
1268
532
|
const MessageIndexItemSchema = MessageCoreSchema;
|
|
1269
|
-
const MessageItemSchema = MessageCoreSchema;
|
|
533
|
+
const MessageItemSchema$1 = MessageCoreSchema;
|
|
1270
534
|
const MessageIndexResponseSchema = createPaginatedSchema(MessageIndexItemSchema);
|
|
1271
|
-
const MessageListResponseSchema = createPaginatedSchema(MessageItemSchema);
|
|
535
|
+
const MessageListResponseSchema = createPaginatedSchema(MessageItemSchema$1);
|
|
1272
536
|
const MessagePinResponseSchema = zod.z.strictObject({
|
|
1273
537
|
message: zod.z.string(),
|
|
1274
538
|
pinned: zod.z.boolean()
|
|
@@ -1321,328 +585,1117 @@ async function listMessages(config, conversation_id, input) {
|
|
|
1321
585
|
endpoint: `/v1/messaging/conversations/${conversation_id}/messages`,
|
|
1322
586
|
method: "GET",
|
|
1323
587
|
input,
|
|
1324
|
-
outputSchema: MessageIndexResponseSchema
|
|
588
|
+
outputSchema: MessageIndexResponseSchema
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
const _createMessageInputSchema = zod.z.strictObject({
|
|
592
|
+
message: zod.z.strictObject({
|
|
593
|
+
body: zod.z.string().optional(),
|
|
594
|
+
theme: zod.z.string().optional(),
|
|
595
|
+
subject: zod.z.string().optional(),
|
|
596
|
+
type: zod.z.string().optional(),
|
|
597
|
+
attachments_attributes: zod.z.array(AttachmentInputSchema).optional()
|
|
598
|
+
}),
|
|
599
|
+
client_message_id: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
600
|
+
});
|
|
601
|
+
const createMessageInputSchema = _createMessageInputSchema;
|
|
602
|
+
async function createMessage(config, conversation_id, input) {
|
|
603
|
+
return fetchMessaging(config, {
|
|
604
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages`,
|
|
605
|
+
method: "POST",
|
|
606
|
+
input,
|
|
607
|
+
inputSchema: createMessageInputSchema,
|
|
608
|
+
outputSchema: MessageItemSchema$1
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* POST /api/v1/messaging/conversations/:conversation_id/messages/reply
|
|
613
|
+
* Reply to an existing message by id; thread is captured in replied_to_id.
|
|
614
|
+
*/
|
|
615
|
+
async function replyToMessage(config, conversation_id, message_id, input) {
|
|
616
|
+
return fetchMessaging(config, {
|
|
617
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/reply`,
|
|
618
|
+
method: "POST",
|
|
619
|
+
input: {
|
|
620
|
+
...input,
|
|
621
|
+
message_id
|
|
622
|
+
},
|
|
623
|
+
inputSchema: _createMessageInputSchema.extend({ message_id: zod.z.number() }),
|
|
624
|
+
outputSchema: MessageItemSchema$1
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
zod.z.strictObject({ message: zod.z.strictObject({
|
|
628
|
+
body: zod.z.string().optional(),
|
|
629
|
+
theme: zod.z.string().optional()
|
|
630
|
+
}) });
|
|
631
|
+
/**
|
|
632
|
+
* POST /api/v1/messaging/conversations/:conversation_id/messages/:message_id/react.json
|
|
633
|
+
* React to a message with an emoji.
|
|
634
|
+
*/
|
|
635
|
+
async function reactToMessage(config, conversation_id, input) {
|
|
636
|
+
const { message_id, reaction } = input;
|
|
637
|
+
return fetchMessaging(config, {
|
|
638
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${message_id}/react.json?reaction=${encodeURIComponent(reaction)}`,
|
|
639
|
+
method: "POST",
|
|
640
|
+
outputSchema: zod.z.string()
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* DELETE /api/v1/messaging/conversations/:conversation_id/messages/:message_id/unreact.json
|
|
645
|
+
* Remove the current recipient's reaction.
|
|
646
|
+
*/
|
|
647
|
+
async function unreactToMessage(config, conversation_id, input) {
|
|
648
|
+
const { message_id } = input;
|
|
649
|
+
return fetchMessaging(config, {
|
|
650
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${message_id}/unreact.json`,
|
|
651
|
+
method: "DELETE",
|
|
652
|
+
outputSchema: zod.z.string()
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* GET /api/v1/messaging/conversations/:conversation_id/messages/pinned
|
|
657
|
+
* List pinned messages in the conversation.
|
|
658
|
+
*/
|
|
659
|
+
async function listPinnedMessages(config, conversation_id, input) {
|
|
660
|
+
return fetchMessaging(config, {
|
|
661
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/pinned`,
|
|
662
|
+
method: "GET",
|
|
663
|
+
input,
|
|
664
|
+
outputSchema: MessageListResponseSchema
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* PUT /api/v1/messaging/conversations/:conversation_id/messages/:id/pin
|
|
669
|
+
* Pin a message (current recipient).
|
|
670
|
+
*/
|
|
671
|
+
async function pinMessage(config, conversation_id, id) {
|
|
672
|
+
return fetchMessaging(config, {
|
|
673
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${id}/pin`,
|
|
674
|
+
method: "PUT",
|
|
675
|
+
outputSchema: MessagePinResponseSchema
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* PUT /api/v1/messaging/conversations/:conversation_id/messages/:id/unpin
|
|
680
|
+
* Unpin a message (current recipient).
|
|
681
|
+
*/
|
|
682
|
+
async function unpinMessage(config, conversation_id, id) {
|
|
683
|
+
return fetchMessaging(config, {
|
|
684
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${id}/unpin`,
|
|
685
|
+
method: "PUT",
|
|
686
|
+
outputSchema: MessagePinResponseSchema
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
690
|
+
//#region ../../messaging/api-client/src/api/conversations.api.ts
|
|
691
|
+
/**
|
|
692
|
+
* Conversations API (v1 messaging)
|
|
693
|
+
*
|
|
694
|
+
* Thin, fully-typed wrappers around the Rails messaging controllers.
|
|
695
|
+
* All functions validate inputs/outputs with zod and use `fetchMessaging`.
|
|
696
|
+
*/
|
|
697
|
+
/**
|
|
698
|
+
* GET /api/v1/messaging/announcement_channel
|
|
699
|
+
*/
|
|
700
|
+
async function getAnnouncementChannel(config) {
|
|
701
|
+
return fetchMessaging(config, {
|
|
702
|
+
endpoint: "/v1/messaging/announcement_channel",
|
|
703
|
+
method: "GET",
|
|
704
|
+
outputSchema: ConversationResponseSchema
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
async function listConversations(config, input) {
|
|
708
|
+
return fetchMessaging(config, {
|
|
709
|
+
endpoint: "/v1/messaging/conversations",
|
|
710
|
+
method: "GET",
|
|
711
|
+
input,
|
|
712
|
+
outputSchema: ConversationListResponseSchema
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* GET /api/v1/messaging/conversations/search
|
|
717
|
+
*/
|
|
718
|
+
async function searchConversations(config, input) {
|
|
719
|
+
return fetchMessaging(config, {
|
|
720
|
+
endpoint: "/v1/messaging/conversations/search",
|
|
721
|
+
method: "GET",
|
|
722
|
+
input,
|
|
723
|
+
outputSchema: NominalConversationsResponseSchema
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* GET /api/v1/messaging/conversations/find
|
|
728
|
+
*/
|
|
729
|
+
async function findConversation(config, input) {
|
|
730
|
+
return fetchMessaging(config, {
|
|
731
|
+
endpoint: "/v1/messaging/conversations/find",
|
|
732
|
+
method: "GET",
|
|
733
|
+
input,
|
|
734
|
+
outputSchema: ConversationResponseSchema
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* GET /api/v1/messaging/conversations/:id
|
|
739
|
+
*/
|
|
740
|
+
async function getConversation(config, id) {
|
|
741
|
+
return fetchMessaging(config, {
|
|
742
|
+
endpoint: `/v1/messaging/conversations/${id}`,
|
|
743
|
+
method: "GET",
|
|
744
|
+
outputSchema: ConversationResponseSchema
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
const conversationCreateInputSchema = zod.z.strictObject({
|
|
748
|
+
conversation: zod.z.strictObject({
|
|
749
|
+
kind: zod.z.string().optional(),
|
|
750
|
+
type: zod.z.string().optional(),
|
|
751
|
+
name: zod.z.string().optional(),
|
|
752
|
+
description: zod.z.string().nullable().optional(),
|
|
753
|
+
broadcasting: zod.z.boolean().optional(),
|
|
754
|
+
filter: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
755
|
+
auto_add_recipients: zod.z.boolean().optional()
|
|
756
|
+
}),
|
|
757
|
+
recipients: zod.z.array(zod.z.strictObject({
|
|
758
|
+
id: zod.z.number().optional(),
|
|
759
|
+
uid: zod.z.number().optional(),
|
|
760
|
+
email: zod.z.string().email().optional(),
|
|
761
|
+
phone: zod.z.string().optional(),
|
|
762
|
+
first_name: zod.z.string().optional(),
|
|
763
|
+
last_name: zod.z.string().optional(),
|
|
764
|
+
image_url: zod.z.string().url().optional()
|
|
765
|
+
})).optional()
|
|
766
|
+
});
|
|
767
|
+
async function createConversation(config, input) {
|
|
768
|
+
return fetchMessaging(config, {
|
|
769
|
+
endpoint: "/v1/messaging/conversations",
|
|
770
|
+
method: "POST",
|
|
771
|
+
input,
|
|
772
|
+
inputSchema: conversationCreateInputSchema,
|
|
773
|
+
outputSchema: ConversationResponseSchema
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
zod.z.strictObject({ conversation: zod.z.strictObject({
|
|
777
|
+
name: zod.z.string().optional(),
|
|
778
|
+
description: zod.z.string().nullable().optional(),
|
|
779
|
+
additional_recipient_params: zod.z.strictObject({
|
|
780
|
+
receivable_id: zod.z.number(),
|
|
781
|
+
receivable_type: zod.z.string()
|
|
782
|
+
}).optional(),
|
|
783
|
+
auto_add_recipients: zod.z.boolean().optional()
|
|
784
|
+
}) });
|
|
785
|
+
//#endregion
|
|
786
|
+
//#region ../../messaging/api-client/src/api/recipients.api.ts
|
|
787
|
+
/**
|
|
788
|
+
* POST /api/v1/messaging/recipients/search
|
|
789
|
+
*/
|
|
790
|
+
async function searchRecipientsCount(config, input) {
|
|
791
|
+
return fetchMessaging(config, {
|
|
792
|
+
endpoint: "/v1/messaging/recipients/search",
|
|
793
|
+
method: "POST",
|
|
794
|
+
input,
|
|
795
|
+
outputSchema: RecipientSearchCountSchema
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* GET /api/v1/messaging/conversations/connected_recipients
|
|
800
|
+
*/
|
|
801
|
+
async function listConnectedRecipients(config, input) {
|
|
802
|
+
return fetchMessaging(config, {
|
|
803
|
+
endpoint: "/v1/messaging/conversations/connected_recipients",
|
|
804
|
+
method: "GET",
|
|
805
|
+
input,
|
|
806
|
+
outputSchema: createPaginatedSchema(input?.kind && [
|
|
807
|
+
"external",
|
|
808
|
+
"sms",
|
|
809
|
+
"email"
|
|
810
|
+
].includes(input.kind) ? WithContactRecipientSchema : RecipientSchema)
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
//#endregion
|
|
814
|
+
//#region ../../messaging/api-client/src/api/drafts-scheduled.api.ts
|
|
815
|
+
const saveDraftInputSchema = zod.z.strictObject({ draft_message: zod.z.strictObject({
|
|
816
|
+
body: zod.z.string().nullable().optional(),
|
|
817
|
+
replied_to_id: zod.z.number().nullable().optional(),
|
|
818
|
+
attachments_attributes: zod.z.array(zod.z.strictObject({
|
|
819
|
+
url: zod.z.string().url(),
|
|
820
|
+
filename: zod.z.string(),
|
|
821
|
+
kind: zod.z.string(),
|
|
822
|
+
content_type: zod.z.string().optional(),
|
|
823
|
+
content_size: zod.z.number().optional(),
|
|
824
|
+
metadata: AttachmentInputMetadataSchema,
|
|
825
|
+
id: zod.z.number().optional(),
|
|
826
|
+
_destroy: zod.z.boolean().optional()
|
|
827
|
+
})).optional()
|
|
828
|
+
}) });
|
|
829
|
+
async function saveDraft(config, conversation_id, input) {
|
|
830
|
+
return fetchMessaging(config, {
|
|
831
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/save_draft`,
|
|
832
|
+
method: "PUT",
|
|
833
|
+
input,
|
|
834
|
+
inputSchema: saveDraftInputSchema,
|
|
835
|
+
outputSchema: DraftMessageSchema.or(zod.z.strictObject({}))
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
async function destroyDraft(config, conversation_id) {
|
|
839
|
+
return fetchMessaging(config, {
|
|
840
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/destroy_draft`,
|
|
841
|
+
method: "DELETE",
|
|
842
|
+
outputSchema: zod.z.unknown()
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Scheduled Messages: per conversation
|
|
847
|
+
*/
|
|
848
|
+
async function listScheduledMessages(config, conversation_id, input) {
|
|
849
|
+
return fetchMessaging(config, {
|
|
850
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
851
|
+
method: "GET",
|
|
852
|
+
input,
|
|
853
|
+
outputSchema: MessageListResponseSchema
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const scheduledMessageInputSchema = zod.z.strictObject({ scheduled_message: zod.z.strictObject({
|
|
857
|
+
body: zod.z.string().optional(),
|
|
858
|
+
theme: zod.z.string().optional(),
|
|
859
|
+
scheduled_at: zod.z.string().datetime({ offset: true }),
|
|
860
|
+
attachments_attributes: zod.z.array(AttachmentInputSchema).optional(),
|
|
861
|
+
type: zod.z.string().optional()
|
|
862
|
+
}) });
|
|
863
|
+
async function createScheduledMessage(config, conversation_id, input) {
|
|
864
|
+
return fetchMessaging(config, {
|
|
865
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
866
|
+
method: "POST",
|
|
867
|
+
input,
|
|
868
|
+
inputSchema: scheduledMessageInputSchema,
|
|
869
|
+
outputSchema: MessageItemSchema$1
|
|
1325
870
|
});
|
|
1326
871
|
}
|
|
1327
|
-
|
|
1328
|
-
message: zod.z.strictObject({
|
|
1329
|
-
body: zod.z.string().optional(),
|
|
1330
|
-
theme: zod.z.string().optional(),
|
|
1331
|
-
subject: zod.z.string().optional(),
|
|
1332
|
-
type: zod.z.string().optional(),
|
|
1333
|
-
attachments_attributes: zod.z.array(AttachmentInputSchema).optional()
|
|
1334
|
-
}),
|
|
1335
|
-
client_message_id: zod.z.union([zod.z.string(), zod.z.number()]).optional()
|
|
1336
|
-
});
|
|
1337
|
-
const createMessageInputSchema = _createMessageInputSchema;
|
|
1338
|
-
async function createMessage(config, conversation_id, input) {
|
|
872
|
+
async function updateScheduledMessage(config, conversation_id, id, input) {
|
|
1339
873
|
return fetchMessaging(config, {
|
|
1340
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/
|
|
1341
|
-
method: "
|
|
874
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
875
|
+
method: "PUT",
|
|
1342
876
|
input,
|
|
1343
|
-
inputSchema:
|
|
1344
|
-
outputSchema: MessageItemSchema
|
|
877
|
+
inputSchema: scheduledMessageInputSchema,
|
|
878
|
+
outputSchema: MessageItemSchema$1
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
async function destroyScheduledMessage(config, conversation_id, id) {
|
|
882
|
+
return fetchMessaging(config, {
|
|
883
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
884
|
+
method: "DELETE",
|
|
885
|
+
outputSchema: zod.z.strictObject({
|
|
886
|
+
success: zod.z.boolean(),
|
|
887
|
+
message: zod.z.string()
|
|
888
|
+
})
|
|
1345
889
|
});
|
|
1346
890
|
}
|
|
1347
891
|
/**
|
|
1348
|
-
*
|
|
1349
|
-
* Reply to an existing message by id; thread is captured in replied_to_id.
|
|
892
|
+
* My Scheduled Messages
|
|
1350
893
|
*/
|
|
1351
|
-
async function
|
|
894
|
+
async function listMyScheduledMessages(config, input) {
|
|
1352
895
|
return fetchMessaging(config, {
|
|
1353
|
-
endpoint: `/v1/messaging/
|
|
1354
|
-
method: "
|
|
1355
|
-
input
|
|
1356
|
-
|
|
1357
|
-
message_id
|
|
1358
|
-
},
|
|
1359
|
-
inputSchema: _createMessageInputSchema.extend({ message_id: zod.z.number() }),
|
|
1360
|
-
outputSchema: MessageItemSchema
|
|
896
|
+
endpoint: `/v1/messaging/my_scheduled_messages`,
|
|
897
|
+
method: "GET",
|
|
898
|
+
input,
|
|
899
|
+
outputSchema: DraftMessageListResponseSchema
|
|
1361
900
|
});
|
|
1362
901
|
}
|
|
1363
|
-
zod.z.strictObject({
|
|
1364
|
-
|
|
1365
|
-
|
|
902
|
+
zod.z.strictObject({ bot: zod.z.strictObject({
|
|
903
|
+
title: zod.z.string(),
|
|
904
|
+
description: zod.z.string().nullable().optional(),
|
|
905
|
+
external_uri: zod.z.string().url().nullable().optional(),
|
|
906
|
+
image_url: zod.z.string().url().nullable().optional(),
|
|
907
|
+
type: zod.z.string().optional(),
|
|
908
|
+
avatar_background_color: zod.z.string().nullable().optional(),
|
|
909
|
+
avatar_emoji: zod.z.string().nullable().optional(),
|
|
910
|
+
webhook_external_uri: zod.z.string().url().nullable().optional(),
|
|
911
|
+
webhook_auth_token: zod.z.string().nullable().optional(),
|
|
912
|
+
webhook_http_method: zod.z.string().nullable().optional(),
|
|
913
|
+
conversation_ids: zod.z.array(zod.z.number()).optional()
|
|
1366
914
|
}) });
|
|
915
|
+
//#endregion
|
|
916
|
+
//#region src/messaging/create-messaging-api.ts
|
|
1367
917
|
/**
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
918
|
+
* Creates a {@link MessagingApi} adapter backed by the messaging API client.
|
|
919
|
+
* This is the portal's adapter — it wires the concrete client to the
|
|
920
|
+
* interface that messaging-core hooks consume via context.
|
|
1370
921
|
*/
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
922
|
+
function createMessagingApi(config) {
|
|
923
|
+
return {
|
|
924
|
+
listConversations: (input) => listConversations(config, input),
|
|
925
|
+
getConversation: (id) => getConversation(config, id),
|
|
926
|
+
findConversation: (input) => findConversation(config, input),
|
|
927
|
+
createConversation: (input) => createConversation(config, input),
|
|
928
|
+
getAnnouncementChannel: () => getAnnouncementChannel(config),
|
|
929
|
+
listMessages: (cid, input) => listMessages(config, cid, input),
|
|
930
|
+
createMessage: (cid, input) => createMessage(config, cid, input),
|
|
931
|
+
replyToMessage: (cid, mid, input) => replyToMessage(config, cid, mid, input),
|
|
932
|
+
reactToMessage: (cid, input) => reactToMessage(config, cid, input),
|
|
933
|
+
unreactToMessage: (cid, input) => unreactToMessage(config, cid, input),
|
|
934
|
+
pinMessage: (cid, mid) => pinMessage(config, cid, mid),
|
|
935
|
+
unpinMessage: (cid, mid) => unpinMessage(config, cid, mid),
|
|
936
|
+
listPinnedMessages: (cid, input) => listPinnedMessages(config, cid, input),
|
|
937
|
+
listScheduledMessages: (cid, input) => listScheduledMessages(config, cid, input),
|
|
938
|
+
createScheduledMessage: (cid, input) => createScheduledMessage(config, cid, input),
|
|
939
|
+
updateScheduledMessage: (cid, id, input) => updateScheduledMessage(config, cid, id, input),
|
|
940
|
+
destroyScheduledMessage: (cid, id) => destroyScheduledMessage(config, cid, id),
|
|
941
|
+
listMyScheduledMessages: (input) => listMyScheduledMessages(config, input),
|
|
942
|
+
saveDraft: (cid, input) => saveDraft(config, cid, input),
|
|
943
|
+
destroyDraft: (cid) => destroyDraft(config, cid),
|
|
944
|
+
searchRecipientsCount: (input) => searchRecipientsCount(config, input),
|
|
945
|
+
listConnectedRecipients: (input) => listConnectedRecipients(config, input)
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/messaging/use-messaging-config.ts
|
|
950
|
+
/**
|
|
951
|
+
* Hook that derives a MessagingApi adapter from the portal SDK's FluidProvider context.
|
|
952
|
+
*
|
|
953
|
+
* Maps FluidSDKConfig fields to a MessagingApiConfig, then wraps the
|
|
954
|
+
* concrete API client functions into a MessagingApi interface instance
|
|
955
|
+
* that messaging-core hooks consume via context.
|
|
956
|
+
*/
|
|
957
|
+
function deriveWebsocketUrl(baseUrl) {
|
|
958
|
+
return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
|
|
959
|
+
}
|
|
960
|
+
function useMessagingConfig() {
|
|
961
|
+
const { config } = require_FluidProvider.useFluidContext();
|
|
962
|
+
const auth = require_FluidProvider.useFluidAuthContext();
|
|
963
|
+
const getHeaders = (0, react.useCallback)(async () => {
|
|
964
|
+
const headers = {
|
|
965
|
+
"Content-Type": "application/json",
|
|
966
|
+
...config.defaultHeaders
|
|
967
|
+
};
|
|
968
|
+
if (config.getAuthToken) {
|
|
969
|
+
const token = await config.getAuthToken();
|
|
970
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
971
|
+
}
|
|
972
|
+
return headers;
|
|
973
|
+
}, [config]);
|
|
974
|
+
const apiBaseUrl = (0, react.useMemo)(() => {
|
|
975
|
+
const base = config.baseUrl.replace(/\/+$/, "");
|
|
976
|
+
return base.endsWith("/api") ? base : `${base}/api`;
|
|
977
|
+
}, [config.baseUrl]);
|
|
978
|
+
const apiConfig = (0, react.useMemo)(() => ({
|
|
979
|
+
baseUrl: apiBaseUrl,
|
|
980
|
+
getHeaders,
|
|
981
|
+
...config.onAuthError != null && { onAuthError: config.onAuthError }
|
|
982
|
+
}), [
|
|
983
|
+
apiBaseUrl,
|
|
984
|
+
config.onAuthError,
|
|
985
|
+
getHeaders
|
|
986
|
+
]);
|
|
987
|
+
return {
|
|
988
|
+
apiConfig,
|
|
989
|
+
messagingApi: (0, react.useMemo)(() => createMessagingApi(apiConfig), [apiConfig]),
|
|
990
|
+
websocketUrl: (0, react.useMemo)(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
|
|
991
|
+
token: auth.token
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
//#endregion
|
|
995
|
+
//#region ../../messaging/core/src/query-keys.ts
|
|
996
|
+
/**
|
|
997
|
+
* Query key factory for all messaging-related TanStack Query queries.
|
|
998
|
+
* Ensures consistent key shapes across hooks and cache helpers.
|
|
999
|
+
*/
|
|
1000
|
+
const MESSAGES_QUERY_KEYS = {
|
|
1001
|
+
all: () => ["messages"],
|
|
1002
|
+
conversationMetadata: (conversationId) => [
|
|
1003
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1004
|
+
"conversation",
|
|
1005
|
+
conversationId
|
|
1006
|
+
],
|
|
1007
|
+
newMessageFlag: (conversationId) => [
|
|
1008
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1009
|
+
"conversation",
|
|
1010
|
+
conversationId,
|
|
1011
|
+
"new_message_flag"
|
|
1012
|
+
],
|
|
1013
|
+
listConversations: () => [...MESSAGES_QUERY_KEYS.all(), "conversations"],
|
|
1014
|
+
listMessages: (conversationId, messageId, startAtISO) => [
|
|
1015
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1016
|
+
"conversation",
|
|
1017
|
+
conversationId,
|
|
1018
|
+
"messages",
|
|
1019
|
+
messageId ? { startFromMessage: messageId } : startAtISO ? { startFromDate: startAtISO } : { startFromBottom: true }
|
|
1020
|
+
],
|
|
1021
|
+
listPinnedMessages: (conversationId) => [
|
|
1022
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1023
|
+
"conversation",
|
|
1024
|
+
conversationId,
|
|
1025
|
+
"pinned"
|
|
1026
|
+
],
|
|
1027
|
+
outbox: (conversationId) => [
|
|
1028
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1029
|
+
"conversation",
|
|
1030
|
+
conversationId,
|
|
1031
|
+
"outbox"
|
|
1032
|
+
],
|
|
1033
|
+
listScheduledMessages: (conversationId) => [
|
|
1034
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1035
|
+
"conversation",
|
|
1036
|
+
conversationId,
|
|
1037
|
+
"scheduled"
|
|
1038
|
+
],
|
|
1039
|
+
listMyScheduledMessages: () => [
|
|
1040
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1041
|
+
"scheduled",
|
|
1042
|
+
"mine"
|
|
1043
|
+
],
|
|
1044
|
+
draft: (conversationId) => [
|
|
1045
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1046
|
+
"draft",
|
|
1047
|
+
conversationId
|
|
1048
|
+
],
|
|
1049
|
+
announcementChannel: () => [...MESSAGES_QUERY_KEYS.all(), "announcement-channel"],
|
|
1050
|
+
downline: () => [...MESSAGES_QUERY_KEYS.all(), "downline"]
|
|
1051
|
+
};
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region ../../messaging/core/src/constants.ts
|
|
1054
|
+
const WEBSOCKET_QUERY_OPTIONS = {
|
|
1055
|
+
staleTime: Infinity,
|
|
1056
|
+
refetchOnMount: "always",
|
|
1057
|
+
refetchOnReconnect: "always",
|
|
1058
|
+
refetchOnWindowFocus: false
|
|
1059
|
+
};
|
|
1060
|
+
/**
|
|
1061
|
+
* Scheduled messages are not websocket-driven; poll to keep fresh.
|
|
1062
|
+
*/
|
|
1063
|
+
const SCHEDULED_MESSAGES_STALE_TIME = 6e4;
|
|
1064
|
+
const SCHEDULED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1065
|
+
/**
|
|
1066
|
+
* Pinned messages are not websocket-driven; poll to keep fresh.
|
|
1067
|
+
*/
|
|
1068
|
+
const PINNED_MESSAGES_STALE_TIME = 6e4;
|
|
1069
|
+
const PINNED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1070
|
+
//#endregion
|
|
1071
|
+
//#region ../../messaging/core/src/cache-helpers.ts
|
|
1072
|
+
/**
|
|
1073
|
+
* seed conversation metadata caches from any conversations list queries.
|
|
1074
|
+
* returns the matched conversation if conversationId is provided and present in any list cache.
|
|
1075
|
+
*/
|
|
1076
|
+
function seedConversationMetadataFromLists(queryClient, conversationId) {
|
|
1077
|
+
let matched;
|
|
1078
|
+
const seedFromData = (data) => {
|
|
1079
|
+
const pages = data?.pages ?? [];
|
|
1080
|
+
for (const page of pages) {
|
|
1081
|
+
const items = page?.[1]?.items ?? [];
|
|
1082
|
+
for (const item of items) {
|
|
1083
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(item.id), item);
|
|
1084
|
+
if (!matched && typeof conversationId === "number" && item.id === conversationId) matched = item;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
seedFromData(queryClient.getQueryData(MESSAGES_QUERY_KEYS.listConversations()));
|
|
1089
|
+
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
1090
|
+
const key = q.queryKey;
|
|
1091
|
+
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
1092
|
+
} });
|
|
1093
|
+
for (const q of listQueries) seedFromData(queryClient.getQueryData(q.queryKey));
|
|
1094
|
+
return matched;
|
|
1095
|
+
}
|
|
1096
|
+
function isMessagesQueryKeyForConversation(queryKey, conversationId) {
|
|
1097
|
+
if (!Array.isArray(queryKey)) return false;
|
|
1098
|
+
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "messages";
|
|
1099
|
+
}
|
|
1100
|
+
function findMessageInCache(queryClient, conversationId, messageId) {
|
|
1101
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1102
|
+
for (const query of queries) {
|
|
1103
|
+
const data = queryClient.getQueryData(query.queryKey);
|
|
1104
|
+
if (data) for (const page of data.pages) {
|
|
1105
|
+
const message = page[1].items.find((m) => m.id === messageId);
|
|
1106
|
+
if (message) return message;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
function forEachMessagesQuery(queryClient, conversationId, fn) {
|
|
1111
|
+
queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
1112
|
+
}
|
|
1113
|
+
function isBottomAnchorKey(queryKey) {
|
|
1114
|
+
if (!Array.isArray(queryKey)) return false;
|
|
1115
|
+
const anchor = queryKey[queryKey.length - 1];
|
|
1116
|
+
if (typeof anchor === "object" && anchor !== null) {
|
|
1117
|
+
if ("startFromBottom" in anchor) return anchor.startFromBottom === true;
|
|
1118
|
+
}
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
function isPinnedQueryKeyForConversation(queryKey, conversationId) {
|
|
1122
|
+
if (!Array.isArray(queryKey)) return false;
|
|
1123
|
+
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "pinned";
|
|
1124
|
+
}
|
|
1125
|
+
function forEachPinnedQuery(queryClient, conversationId, fn) {
|
|
1126
|
+
queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
1127
|
+
}
|
|
1128
|
+
async function cancelMessageQueries(queryClient, conversationId) {
|
|
1129
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1130
|
+
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
1131
|
+
}
|
|
1132
|
+
async function cancelPinnedQueries(queryClient, conversationId) {
|
|
1133
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1134
|
+
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
1135
|
+
}
|
|
1136
|
+
function applyMessagesOptimisticSend({ queryClient, conversationId, optimisticMessage }) {
|
|
1137
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1138
|
+
if (!isBottomAnchorKey(key)) return;
|
|
1139
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1140
|
+
const newOptimisticPage = [{ pagination: {
|
|
1141
|
+
current: 1,
|
|
1142
|
+
previous: null,
|
|
1143
|
+
next: null,
|
|
1144
|
+
per_page: oldData?.pages?.[0]?.[0]?.pagination?.per_page ?? 20,
|
|
1145
|
+
pages: oldData?.pages?.[0]?.[0]?.pagination?.pages ?? 1,
|
|
1146
|
+
count: oldData?.pages?.[0]?.[0]?.pagination?.count ?? 0
|
|
1147
|
+
} }, { items: [optimisticMessage] }];
|
|
1148
|
+
if (!oldData) return {
|
|
1149
|
+
pages: [newOptimisticPage],
|
|
1150
|
+
pageParams: [1]
|
|
1151
|
+
};
|
|
1152
|
+
const pages = oldData.pages.map((page, idx) => {
|
|
1153
|
+
if (idx === 0) {
|
|
1154
|
+
const currentItems = page?.[1]?.items ?? [];
|
|
1155
|
+
return [{ pagination: {
|
|
1156
|
+
...page?.[0]?.pagination ?? {},
|
|
1157
|
+
count: page?.[0]?.pagination?.count ?? 0
|
|
1158
|
+
} }, { items: [optimisticMessage, ...currentItems] }];
|
|
1159
|
+
}
|
|
1160
|
+
return page;
|
|
1161
|
+
});
|
|
1162
|
+
return {
|
|
1163
|
+
...oldData,
|
|
1164
|
+
pages
|
|
1165
|
+
};
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1169
|
+
const prev = old?.items ?? [];
|
|
1170
|
+
if (prev.some((m) => m.id === optimisticMessage.id)) return old;
|
|
1171
|
+
return { items: [...prev, optimisticMessage] };
|
|
1377
1172
|
});
|
|
1378
1173
|
}
|
|
1379
1174
|
/**
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1175
|
+
* markNewMessageFlagForConversation
|
|
1176
|
+
*
|
|
1177
|
+
* sets a lightweight flag query for a conversation indicating a new message
|
|
1178
|
+
* arrived while the user may not be at the bottom. UI can reset it after
|
|
1179
|
+
* scrolling or showing a pill.
|
|
1382
1180
|
*/
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1181
|
+
function markNewMessageFlagForConversation(queryClient, conversationId) {
|
|
1182
|
+
queryClient.setQueryData([
|
|
1183
|
+
MESSAGES_QUERY_KEYS.all()[0],
|
|
1184
|
+
"conversation",
|
|
1185
|
+
conversationId,
|
|
1186
|
+
"new_message_flag"
|
|
1187
|
+
], true);
|
|
1188
|
+
}
|
|
1189
|
+
function clearNewMessageFlagForConversation(queryClient, conversationId) {
|
|
1190
|
+
queryClient.setQueryData([
|
|
1191
|
+
MESSAGES_QUERY_KEYS.all()[0],
|
|
1192
|
+
"conversation",
|
|
1193
|
+
conversationId,
|
|
1194
|
+
"new_message_flag"
|
|
1195
|
+
], false);
|
|
1196
|
+
}
|
|
1197
|
+
function applyMessageUpsertToCache({ queryClient, conversationId, message, replaceOptimisticId, clientMessageId }) {
|
|
1198
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1199
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1200
|
+
if (!oldData?.pages?.length) {
|
|
1201
|
+
if (!isBottomAnchorKey(key)) return oldData;
|
|
1202
|
+
return {
|
|
1203
|
+
pages: [[{ pagination: {
|
|
1204
|
+
current: 1,
|
|
1205
|
+
previous: null,
|
|
1206
|
+
next: null,
|
|
1207
|
+
per_page: 20,
|
|
1208
|
+
pages: 1,
|
|
1209
|
+
count: 1
|
|
1210
|
+
} }, { items: [message] }]],
|
|
1211
|
+
pageParams: [1]
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
const normalizedClientMessageId = (() => {
|
|
1215
|
+
if (typeof clientMessageId === "string" && clientMessageId.length > 0) return clientMessageId;
|
|
1216
|
+
const fromMeta = (() => {
|
|
1217
|
+
const meta = message.metadata;
|
|
1218
|
+
if (meta && typeof meta === "object" && "client_message_id" in meta) {
|
|
1219
|
+
const id = meta.client_message_id;
|
|
1220
|
+
if (typeof id === "string" || typeof id === "number" && Number.isFinite(id)) return id;
|
|
1221
|
+
}
|
|
1222
|
+
})();
|
|
1223
|
+
if (typeof fromMeta === "string" || typeof fromMeta === "number" && Number.isFinite(fromMeta)) return String(fromMeta);
|
|
1224
|
+
})();
|
|
1225
|
+
const finalMessage = (() => {
|
|
1226
|
+
if (!normalizedClientMessageId) return message;
|
|
1227
|
+
const currentMeta = message.metadata;
|
|
1228
|
+
if (currentMeta != null && typeof currentMeta === "object" && (typeof currentMeta.client_message_id === "string" || typeof currentMeta.client_message_id === "number")) return message;
|
|
1229
|
+
return {
|
|
1230
|
+
...message,
|
|
1231
|
+
metadata: {
|
|
1232
|
+
...currentMeta && typeof currentMeta === "object" ? currentMeta : {},
|
|
1233
|
+
client_message_id: normalizedClientMessageId
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
})();
|
|
1237
|
+
let replaced = false;
|
|
1238
|
+
const pages = oldData.pages.map((page) => {
|
|
1239
|
+
const [meta, payload] = page;
|
|
1240
|
+
const items = payload.items.slice();
|
|
1241
|
+
const idxById = items.findIndex((m) => m.id === (replaceOptimisticId ?? message.id));
|
|
1242
|
+
if (idxById !== -1) {
|
|
1243
|
+
items[idxById] = finalMessage;
|
|
1244
|
+
replaced = true;
|
|
1245
|
+
return [meta, { items }];
|
|
1246
|
+
}
|
|
1247
|
+
if (normalizedClientMessageId) {
|
|
1248
|
+
const idxByClientId = items.findIndex((m) => typeof m?.metadata === "object" && m?.metadata != null && ((id) => id != null && String(id) === normalizedClientMessageId)(m.metadata?.client_message_id));
|
|
1249
|
+
if (idxByClientId !== -1) {
|
|
1250
|
+
items[idxByClientId] = finalMessage;
|
|
1251
|
+
replaced = true;
|
|
1252
|
+
return [meta, { items }];
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
const idxExisting = items.findIndex((m) => m.id === message.id);
|
|
1256
|
+
if (idxExisting !== -1) {
|
|
1257
|
+
items[idxExisting] = finalMessage;
|
|
1258
|
+
replaced = true;
|
|
1259
|
+
return [meta, { items }];
|
|
1260
|
+
}
|
|
1261
|
+
return [meta, { items }];
|
|
1262
|
+
});
|
|
1263
|
+
if (!replaced) {
|
|
1264
|
+
if (!isBottomAnchorKey(key)) return oldData;
|
|
1265
|
+
const first = pages[0];
|
|
1266
|
+
const nextPages = pages.slice();
|
|
1267
|
+
const newFirstItems = [finalMessage, ...first?.[1]?.items ?? []];
|
|
1268
|
+
const newCount = (first?.[0]?.pagination?.count ?? 0) + 1;
|
|
1269
|
+
nextPages[0] = [{ pagination: {
|
|
1270
|
+
...first?.[0]?.pagination ?? {},
|
|
1271
|
+
count: newCount
|
|
1272
|
+
} }, { items: newFirstItems }];
|
|
1273
|
+
return {
|
|
1274
|
+
...oldData,
|
|
1275
|
+
pages: nextPages
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
return {
|
|
1279
|
+
...oldData,
|
|
1280
|
+
pages
|
|
1281
|
+
};
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1285
|
+
if (!old?.items) return old;
|
|
1286
|
+
return { items: old.items.filter((m) => m.id !== (replaceOptimisticId ?? -99999999)) };
|
|
1287
|
+
});
|
|
1288
|
+
}
|
|
1289
|
+
function applyMessageDeleteFromCache({ queryClient, conversationId, messageId }) {
|
|
1290
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1291
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1292
|
+
if (!oldData) return oldData;
|
|
1293
|
+
const pages = oldData.pages.map((page) => {
|
|
1294
|
+
const [meta, payload] = page;
|
|
1295
|
+
const items = payload.items.filter((m) => m.id !== messageId);
|
|
1296
|
+
const newCount = Math.max(0, (meta?.pagination?.count ?? 0) - (payload.items.length - items.length));
|
|
1297
|
+
return [{ pagination: {
|
|
1298
|
+
...meta?.pagination ?? {},
|
|
1299
|
+
count: newCount
|
|
1300
|
+
} }, { items }];
|
|
1301
|
+
});
|
|
1302
|
+
return {
|
|
1303
|
+
...oldData,
|
|
1304
|
+
pages
|
|
1305
|
+
};
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1309
|
+
if (!old?.items) return old;
|
|
1310
|
+
return { items: old.items.filter((m) => m.id !== messageId) };
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
function applyReactionUpdateToCache({ queryClient, conversationId, messageId, reaction, reaction_stats }) {
|
|
1314
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1315
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1316
|
+
if (!oldData) return oldData;
|
|
1317
|
+
const pages = oldData.pages.map((page) => {
|
|
1318
|
+
const [meta, payload] = page;
|
|
1319
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1320
|
+
...m,
|
|
1321
|
+
reaction,
|
|
1322
|
+
reaction_stats
|
|
1323
|
+
} : m) }];
|
|
1324
|
+
});
|
|
1325
|
+
return {
|
|
1326
|
+
...oldData,
|
|
1327
|
+
pages
|
|
1328
|
+
};
|
|
1329
|
+
});
|
|
1330
|
+
});
|
|
1331
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1332
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1333
|
+
if (!oldData) return oldData;
|
|
1334
|
+
const pages = oldData.pages.map((page) => {
|
|
1335
|
+
const [meta, payload] = page;
|
|
1336
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1337
|
+
...m,
|
|
1338
|
+
reaction,
|
|
1339
|
+
reaction_stats
|
|
1340
|
+
} : m) }];
|
|
1341
|
+
});
|
|
1342
|
+
return {
|
|
1343
|
+
...oldData,
|
|
1344
|
+
pages
|
|
1345
|
+
};
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
function applyReactionStatsToCache({ queryClient, conversationId, messageId, reaction_stats }) {
|
|
1350
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1351
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1352
|
+
if (!oldData) return;
|
|
1353
|
+
const pages = oldData.pages.map((page) => {
|
|
1354
|
+
const [meta, payload] = page;
|
|
1355
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1356
|
+
...m,
|
|
1357
|
+
reaction_stats
|
|
1358
|
+
} : m) }];
|
|
1359
|
+
});
|
|
1360
|
+
return {
|
|
1361
|
+
...oldData,
|
|
1362
|
+
pages
|
|
1363
|
+
};
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1366
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1367
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1368
|
+
if (!oldData) return oldData;
|
|
1369
|
+
const pages = oldData.pages.map((page) => {
|
|
1370
|
+
const [meta, payload] = page;
|
|
1371
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1372
|
+
...m,
|
|
1373
|
+
reaction_stats
|
|
1374
|
+
} : m) }];
|
|
1375
|
+
});
|
|
1376
|
+
return {
|
|
1377
|
+
...oldData,
|
|
1378
|
+
pages
|
|
1379
|
+
};
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
function applyConversationMetaToCaches({ queryClient, conversation }) {
|
|
1384
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(conversation.id), (old) => {
|
|
1385
|
+
if (!old) return old;
|
|
1386
|
+
return {
|
|
1387
|
+
...old,
|
|
1388
|
+
...conversation
|
|
1389
|
+
};
|
|
1390
|
+
});
|
|
1391
|
+
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
1392
|
+
const key = q.queryKey;
|
|
1393
|
+
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
1394
|
+
} });
|
|
1395
|
+
for (const q of listQueries) queryClient.setQueryData(q.queryKey, (oldData) => {
|
|
1396
|
+
const data = oldData;
|
|
1397
|
+
if (!data?.pages) return oldData;
|
|
1398
|
+
return {
|
|
1399
|
+
...data,
|
|
1400
|
+
pages: data.pages.map((page) => [page[0], {
|
|
1401
|
+
...page[1],
|
|
1402
|
+
items: page[1].items.map((conv) => conv.id === conversation.id ? {
|
|
1403
|
+
...conv,
|
|
1404
|
+
...typeof conversation.unread === "boolean" ? { unread: conversation.unread } : {},
|
|
1405
|
+
...typeof conversation.unread_messages_count === "number" ? { unread_messages_count: conversation.unread_messages_count } : {}
|
|
1406
|
+
} : conv)
|
|
1407
|
+
}])
|
|
1408
|
+
};
|
|
1389
1409
|
});
|
|
1390
1410
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1411
|
+
function applyConversationDestroyed({ queryClient, conversationId }) {
|
|
1412
|
+
queryClient.removeQueries({ queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId) });
|
|
1413
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.listConversations(), (oldData) => {
|
|
1414
|
+
const data = oldData;
|
|
1415
|
+
if (!data?.pages) return oldData;
|
|
1416
|
+
return {
|
|
1417
|
+
...data,
|
|
1418
|
+
pages: data.pages.map((page) => [page[0], {
|
|
1419
|
+
...page[1],
|
|
1420
|
+
items: page[1].items.filter((c) => c.id !== conversationId)
|
|
1421
|
+
}])
|
|
1422
|
+
};
|
|
1401
1423
|
});
|
|
1402
1424
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1425
|
+
function applyMessagePinnedFlag({ queryClient, conversationId, messageId, pinned, message }) {
|
|
1426
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1427
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1428
|
+
if (!oldData) return oldData;
|
|
1429
|
+
const pages = oldData.pages.map((page) => {
|
|
1430
|
+
const [meta, payload] = page;
|
|
1431
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1432
|
+
...m,
|
|
1433
|
+
pinned
|
|
1434
|
+
} : m) }];
|
|
1435
|
+
});
|
|
1436
|
+
return {
|
|
1437
|
+
...oldData,
|
|
1438
|
+
pages
|
|
1439
|
+
};
|
|
1440
|
+
});
|
|
1412
1441
|
});
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1442
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1443
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
1444
|
+
if (!oldData) return oldData;
|
|
1445
|
+
const pages = oldData.pages.map((page, idx) => {
|
|
1446
|
+
const [meta, payload] = page;
|
|
1447
|
+
if (idx !== 0) return page;
|
|
1448
|
+
const exists = payload.items.some((m) => m.id === messageId);
|
|
1449
|
+
if (pinned) {
|
|
1450
|
+
if (exists) return page;
|
|
1451
|
+
const msg = message ?? payload.items.find((m) => m.id === messageId);
|
|
1452
|
+
if (!msg) return page;
|
|
1453
|
+
return [meta, { items: [msg, ...payload.items] }];
|
|
1454
|
+
}
|
|
1455
|
+
return [meta, { items: payload.items.filter((m) => m.id !== messageId) }];
|
|
1456
|
+
});
|
|
1457
|
+
return {
|
|
1458
|
+
...oldData,
|
|
1459
|
+
pages
|
|
1460
|
+
};
|
|
1461
|
+
});
|
|
1423
1462
|
});
|
|
1424
1463
|
}
|
|
1425
1464
|
//#endregion
|
|
1426
|
-
//#region ../../messaging/
|
|
1427
|
-
/**
|
|
1428
|
-
* Conversations API (v1 messaging)
|
|
1429
|
-
*
|
|
1430
|
-
* Thin, fully-typed wrappers around the Rails messaging controllers.
|
|
1431
|
-
* All functions validate inputs/outputs with zod and use `fetchMessaging`.
|
|
1432
|
-
*/
|
|
1465
|
+
//#region ../../messaging/core/src/utils/reaction-utils.ts
|
|
1433
1466
|
/**
|
|
1434
|
-
*
|
|
1467
|
+
* Splits an emoji string into an array of grapheme clusters (perceived characters/emojis).
|
|
1468
|
+
* Uses `Intl.Segmenter` when available for correct multi-codepoint emoji handling.
|
|
1469
|
+
* Falls back to `[...string]` (code-point iteration) on browsers without `Intl.Segmenter`
|
|
1470
|
+
* (e.g. Firefox < 119), which may incorrectly split ZWJ sequences, skin-tone modifiers,
|
|
1471
|
+
* and flag emojis into multiple entries.
|
|
1472
|
+
* @param emojiString The string of emojis to split.
|
|
1473
|
+
* @returns An array of strings, where each string is a single emoji.
|
|
1435
1474
|
*/
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
async function listConversations(config, input) {
|
|
1444
|
-
return fetchMessaging(config, {
|
|
1445
|
-
endpoint: "/v1/messaging/conversations",
|
|
1446
|
-
method: "GET",
|
|
1447
|
-
input,
|
|
1448
|
-
outputSchema: ConversationListResponseSchema
|
|
1449
|
-
});
|
|
1475
|
+
function splitEmojis(emojiString) {
|
|
1476
|
+
if (!emojiString) return [];
|
|
1477
|
+
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
1478
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
1479
|
+
return Array.from(segmenter.segment(emojiString)).map((segment) => segment.segment);
|
|
1480
|
+
}
|
|
1481
|
+
return [...emojiString];
|
|
1450
1482
|
}
|
|
1451
1483
|
/**
|
|
1452
|
-
*
|
|
1484
|
+
* Flattens reaction_stats to count individual emojis.
|
|
1485
|
+
* E.g., {"😀😂": 1, "👍": 2, "😀": 1} becomes {"😀": 2, "😂": 1, "👍": 2}
|
|
1486
|
+
* Handles multi-codepoint emojis correctly.
|
|
1487
|
+
* @param stats The raw reaction_stats object from the message.
|
|
1488
|
+
* @returns A record where keys are single emojis and values are their total counts.
|
|
1453
1489
|
*/
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1490
|
+
function flattenReactionStats(stats) {
|
|
1491
|
+
if (!stats) return {};
|
|
1492
|
+
const flattened = {};
|
|
1493
|
+
for (const emojiKey in stats) {
|
|
1494
|
+
const count = stats[emojiKey];
|
|
1495
|
+
if (typeof count !== "number" || count === 0) continue;
|
|
1496
|
+
const individualEmojis = splitEmojis(emojiKey);
|
|
1497
|
+
for (const emoji of individualEmojis) flattened[emoji] = (flattened[emoji] || 0) + count;
|
|
1498
|
+
}
|
|
1499
|
+
return flattened;
|
|
1461
1500
|
}
|
|
1462
1501
|
/**
|
|
1463
|
-
*
|
|
1502
|
+
* Calculates the optimistic state for a message's reactions when a user's reaction changes.
|
|
1503
|
+
* @param currentState The current reaction parts of the message ({ reaction, reaction_stats }).
|
|
1504
|
+
* @param newUserReaction The user's new complete reaction string (e.g., "😀👍", "😂", or null to unreact).
|
|
1505
|
+
* @returns An object with the updated `reaction` and `reaction_stats`.
|
|
1464
1506
|
*/
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1507
|
+
function calculateOptimisticReactionUpdate(currentState, newUserReaction) {
|
|
1508
|
+
const previousUserReaction = currentState.reaction;
|
|
1509
|
+
const newStats = { ...currentState.reaction_stats || {} };
|
|
1510
|
+
const finalNewUserReaction = newUserReaction === "" ? null : newUserReaction;
|
|
1511
|
+
if (previousUserReaction === finalNewUserReaction) return {
|
|
1512
|
+
reaction: previousUserReaction,
|
|
1513
|
+
reaction_stats: newStats
|
|
1514
|
+
};
|
|
1515
|
+
if (previousUserReaction && typeof newStats[previousUserReaction] === "number") newStats[previousUserReaction] = Math.max(0, newStats[previousUserReaction] - 1);
|
|
1516
|
+
if (finalNewUserReaction) newStats[finalNewUserReaction] = (typeof newStats[finalNewUserReaction] === "number" ? newStats[finalNewUserReaction] : 0) + 1;
|
|
1517
|
+
return {
|
|
1518
|
+
reaction: finalNewUserReaction,
|
|
1519
|
+
reaction_stats: newStats
|
|
1520
|
+
};
|
|
1472
1521
|
}
|
|
1473
1522
|
/**
|
|
1474
|
-
*
|
|
1523
|
+
* Generates a unique, sorted string from an array of emoji characters.
|
|
1524
|
+
* Each string in the input array is assumed to be a single, correctly segmented emoji.
|
|
1525
|
+
* E.g., ["😂", "😀", "😂"] becomes "😀😂"
|
|
1526
|
+
* @param emojis An array of single emoji strings (grapheme clusters).
|
|
1527
|
+
* @returns A sorted string of unique emojis, or an empty string if no emojis are provided.
|
|
1475
1528
|
*/
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
method: "GET",
|
|
1480
|
-
outputSchema: ConversationResponseSchema
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
const conversationCreateInputSchema = zod.z.strictObject({
|
|
1484
|
-
conversation: zod.z.strictObject({
|
|
1485
|
-
kind: zod.z.string().optional(),
|
|
1486
|
-
type: zod.z.string().optional(),
|
|
1487
|
-
name: zod.z.string().optional(),
|
|
1488
|
-
description: zod.z.string().nullable().optional(),
|
|
1489
|
-
broadcasting: zod.z.boolean().optional(),
|
|
1490
|
-
filter: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
1491
|
-
auto_add_recipients: zod.z.boolean().optional()
|
|
1492
|
-
}),
|
|
1493
|
-
recipients: zod.z.array(zod.z.strictObject({
|
|
1494
|
-
id: zod.z.number().optional(),
|
|
1495
|
-
uid: zod.z.number().optional(),
|
|
1496
|
-
email: zod.z.string().email().optional(),
|
|
1497
|
-
phone: zod.z.string().optional(),
|
|
1498
|
-
first_name: zod.z.string().optional(),
|
|
1499
|
-
last_name: zod.z.string().optional(),
|
|
1500
|
-
image_url: zod.z.string().url().optional()
|
|
1501
|
-
})).optional()
|
|
1502
|
-
});
|
|
1503
|
-
async function createConversation(config, input) {
|
|
1504
|
-
return fetchMessaging(config, {
|
|
1505
|
-
endpoint: "/v1/messaging/conversations",
|
|
1506
|
-
method: "POST",
|
|
1507
|
-
input,
|
|
1508
|
-
inputSchema: conversationCreateInputSchema,
|
|
1509
|
-
outputSchema: ConversationResponseSchema
|
|
1510
|
-
});
|
|
1529
|
+
function generateSortedEmojiStringFromArray(emojis) {
|
|
1530
|
+
if (!emojis || emojis.length === 0) return "";
|
|
1531
|
+
return [...new Set(emojis)].sort((a, b) => a.localeCompare(b, "en")).join("");
|
|
1511
1532
|
}
|
|
1512
|
-
zod.z.strictObject({ conversation: zod.z.strictObject({
|
|
1513
|
-
name: zod.z.string().optional(),
|
|
1514
|
-
description: zod.z.string().nullable().optional(),
|
|
1515
|
-
additional_recipient_params: zod.z.strictObject({
|
|
1516
|
-
receivable_id: zod.z.number(),
|
|
1517
|
-
receivable_type: zod.z.string()
|
|
1518
|
-
}).optional(),
|
|
1519
|
-
auto_add_recipients: zod.z.boolean().optional()
|
|
1520
|
-
}) });
|
|
1521
1533
|
//#endregion
|
|
1522
|
-
//#region ../../messaging/
|
|
1534
|
+
//#region ../../messaging/core/src/utils/messaging.ts
|
|
1535
|
+
function mapConversationTypeToMessageType(conversationType) {
|
|
1536
|
+
switch (conversationType) {
|
|
1537
|
+
case "DirectConversation": return "DirectMessage";
|
|
1538
|
+
case "SmsConversation": return "SmsMessage";
|
|
1539
|
+
case "EmailConversation": return "EmailMessage";
|
|
1540
|
+
case "ChannelConversation":
|
|
1541
|
+
case "GroupConversation": return "DirectMessage";
|
|
1542
|
+
case "BotConversation": return "BotMessage";
|
|
1543
|
+
default: return "DirectMessage";
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1523
1546
|
/**
|
|
1524
|
-
*
|
|
1547
|
+
* Determines the category of a chat. This is slightly different from the backend's
|
|
1548
|
+
* category field.
|
|
1549
|
+
*
|
|
1550
|
+
* @param item - Conversation item to check
|
|
1551
|
+
* @returns the type of channel
|
|
1525
1552
|
*/
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1553
|
+
const getMessageType = (item) => {
|
|
1554
|
+
if (item.type === "GroupConversation" && item.name.toLowerCase().includes("other")) return {
|
|
1555
|
+
isChannel: false,
|
|
1556
|
+
type: "group-direct-message"
|
|
1557
|
+
};
|
|
1558
|
+
if (item.broadcasting) return {
|
|
1559
|
+
isChannel: true,
|
|
1560
|
+
type: "broadcast"
|
|
1561
|
+
};
|
|
1562
|
+
if (item.type === "GroupConversation") return {
|
|
1563
|
+
isChannel: true,
|
|
1564
|
+
type: "private-channel"
|
|
1565
|
+
};
|
|
1566
|
+
if (item.type === "DirectConversation") return {
|
|
1567
|
+
isChannel: false,
|
|
1568
|
+
type: "direct-message"
|
|
1569
|
+
};
|
|
1570
|
+
if (item.type === "EmailConversation") return {
|
|
1571
|
+
isChannel: false,
|
|
1572
|
+
type: "email"
|
|
1573
|
+
};
|
|
1574
|
+
if (item.type === "SmsConversation") return {
|
|
1575
|
+
isChannel: false,
|
|
1576
|
+
type: "sms"
|
|
1577
|
+
};
|
|
1578
|
+
if (item.type === "ChannelConversation") return {
|
|
1579
|
+
isChannel: true,
|
|
1580
|
+
type: "public-channel"
|
|
1581
|
+
};
|
|
1582
|
+
if (item.type === "BotConversation") return {
|
|
1583
|
+
isChannel: false,
|
|
1584
|
+
type: "bot"
|
|
1585
|
+
};
|
|
1586
|
+
if (item.type === "DownlineConversation") return {
|
|
1587
|
+
isChannel: true,
|
|
1588
|
+
type: "downline"
|
|
1589
|
+
};
|
|
1590
|
+
throw new Error("Invalid conversation type");
|
|
1591
|
+
};
|
|
1592
|
+
const getUserInitials = (conversation, user) => {
|
|
1593
|
+
if (user.phone === conversation.name) return "#";
|
|
1594
|
+
if (user.email === conversation.name) return "@";
|
|
1595
|
+
return [user.first_name?.charAt(0), user.last_name?.charAt(0)].filter(Boolean).join("") || "?";
|
|
1596
|
+
};
|
|
1597
|
+
const getFileTypeFromMimetype = (mimetype) => {
|
|
1598
|
+
if (mimetype.startsWith("image/")) return "image";
|
|
1599
|
+
if (mimetype.startsWith("video/")) return "video";
|
|
1600
|
+
if (mimetype === "application/pdf") return "pdf";
|
|
1601
|
+
if (mimetype.includes("spreadsheet") || mimetype.includes("excel") || mimetype.includes("sheet") || mimetype === "text/csv") return "sheets";
|
|
1602
|
+
if (mimetype.includes("powerpoint") || mimetype.includes("presentation")) return "powerpoint";
|
|
1603
|
+
if (mimetype.includes("document") || mimetype.includes("word") || mimetype.includes("rtf")) return "docx";
|
|
1604
|
+
if (mimetype.startsWith("text/")) return "txt";
|
|
1605
|
+
if (mimetype.includes("zip") || mimetype.includes("compressed") || mimetype.includes("archive") || mimetype.includes("tar") || mimetype.includes("gzip")) return "compressed";
|
|
1606
|
+
if (mimetype.startsWith("audio/")) return "audio";
|
|
1607
|
+
return "unknown";
|
|
1608
|
+
};
|
|
1538
1609
|
//#endregion
|
|
1539
|
-
//#region ../../messaging/
|
|
1540
|
-
const saveDraftInputSchema = zod.z.strictObject({ draft_message: zod.z.strictObject({
|
|
1541
|
-
body: zod.z.string().nullable().optional(),
|
|
1542
|
-
replied_to_id: zod.z.number().nullable().optional(),
|
|
1543
|
-
attachments_attributes: zod.z.array(zod.z.strictObject({
|
|
1544
|
-
url: zod.z.string().url(),
|
|
1545
|
-
filename: zod.z.string(),
|
|
1546
|
-
kind: zod.z.string(),
|
|
1547
|
-
content_type: zod.z.string().optional(),
|
|
1548
|
-
content_size: zod.z.number().optional(),
|
|
1549
|
-
metadata: AttachmentInputMetadataSchema,
|
|
1550
|
-
id: zod.z.number().optional(),
|
|
1551
|
-
_destroy: zod.z.boolean().optional()
|
|
1552
|
-
})).optional()
|
|
1553
|
-
}) });
|
|
1554
|
-
async function saveDraft(config, conversation_id, input) {
|
|
1555
|
-
return fetchMessaging(config, {
|
|
1556
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/save_draft`,
|
|
1557
|
-
method: "PUT",
|
|
1558
|
-
input,
|
|
1559
|
-
inputSchema: saveDraftInputSchema,
|
|
1560
|
-
outputSchema: DraftMessageSchema.or(zod.z.strictObject({}))
|
|
1561
|
-
});
|
|
1562
|
-
}
|
|
1563
|
-
async function destroyDraft(config, conversation_id) {
|
|
1564
|
-
return fetchMessaging(config, {
|
|
1565
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/destroy_draft`,
|
|
1566
|
-
method: "DELETE",
|
|
1567
|
-
outputSchema: zod.z.unknown()
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1610
|
+
//#region ../../messaging/core/src/utils/string-utils.ts
|
|
1570
1611
|
/**
|
|
1571
|
-
*
|
|
1612
|
+
* Extracts the leading emoji from a string.
|
|
1613
|
+
* @param text The string to process.
|
|
1614
|
+
* @returns An object containing the emoji and the remaining text.
|
|
1572
1615
|
*/
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
type: zod.z.string().optional()
|
|
1587
|
-
}) });
|
|
1588
|
-
async function createScheduledMessage(config, conversation_id, input) {
|
|
1589
|
-
return fetchMessaging(config, {
|
|
1590
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
1591
|
-
method: "POST",
|
|
1592
|
-
input,
|
|
1593
|
-
inputSchema: scheduledMessageInputSchema,
|
|
1594
|
-
outputSchema: MessageItemSchema
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1597
|
-
async function updateScheduledMessage(config, conversation_id, id, input) {
|
|
1598
|
-
return fetchMessaging(config, {
|
|
1599
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
1600
|
-
method: "PUT",
|
|
1601
|
-
input,
|
|
1602
|
-
inputSchema: scheduledMessageInputSchema,
|
|
1603
|
-
outputSchema: MessageItemSchema
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
async function destroyScheduledMessage(config, conversation_id, id) {
|
|
1607
|
-
return fetchMessaging(config, {
|
|
1608
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
1609
|
-
method: "DELETE",
|
|
1610
|
-
outputSchema: zod.z.strictObject({
|
|
1611
|
-
success: zod.z.boolean(),
|
|
1612
|
-
message: zod.z.string()
|
|
1613
|
-
})
|
|
1614
|
-
});
|
|
1616
|
+
function extractEmoji$1(text) {
|
|
1617
|
+
const match = text.match(/^\p{Emoji_Presentation}/u);
|
|
1618
|
+
if (match) {
|
|
1619
|
+
const emoji = match[0];
|
|
1620
|
+
return {
|
|
1621
|
+
emoji,
|
|
1622
|
+
text: text.substring(emoji.length).trimStart()
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
return {
|
|
1626
|
+
emoji: null,
|
|
1627
|
+
text
|
|
1628
|
+
};
|
|
1615
1629
|
}
|
|
1616
1630
|
/**
|
|
1617
|
-
*
|
|
1631
|
+
* Formats a message channel name by replacing spaces and underscores with hyphens,
|
|
1632
|
+
* converting to lowercase, and removing any non-alphanumeric characters except hyphens.
|
|
1633
|
+
* @param name - The original channel name
|
|
1634
|
+
* @return The formatted channel name
|
|
1618
1635
|
*/
|
|
1619
|
-
|
|
1620
|
-
return
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1636
|
+
function formatMessageChannelName$1(name) {
|
|
1637
|
+
return name.toLowerCase().replace(/[ _]/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
1638
|
+
}
|
|
1639
|
+
const MAX_GROUP_NAME_LENGTH$1 = 30;
|
|
1640
|
+
function formatGroupDisplayName$1(names, maxLength = MAX_GROUP_NAME_LENGTH$1) {
|
|
1641
|
+
const fullDisplayName = names.join(", ");
|
|
1642
|
+
if (fullDisplayName.length <= maxLength) return { displayName: fullDisplayName };
|
|
1643
|
+
for (let i = 0; i < names.length; i++) {
|
|
1644
|
+
const currentNames = names.slice(0, i + 1);
|
|
1645
|
+
const remainingCount = names.length - currentNames.length;
|
|
1646
|
+
if (remainingCount > 0) {
|
|
1647
|
+
const suffix = `+ ${remainingCount} other${remainingCount > 1 ? "s" : ""}`;
|
|
1648
|
+
if (`${currentNames.join(", ")} ${suffix}`.length > maxLength && i > 0) {
|
|
1649
|
+
const namesToShow = names.slice(0, i);
|
|
1650
|
+
const othersCount = names.length - i;
|
|
1651
|
+
const othersSuffix = `+${othersCount} other${othersCount > 1 ? "s" : ""}`;
|
|
1652
|
+
const truncatedDisplayName = namesToShow.join(", ");
|
|
1653
|
+
if (othersCount === 1) {
|
|
1654
|
+
const nameWithOneMore = names.slice(0, i + 1).join(", ");
|
|
1655
|
+
if (nameWithOneMore.length < truncatedDisplayName.length) return { displayName: nameWithOneMore };
|
|
1656
|
+
}
|
|
1657
|
+
return {
|
|
1658
|
+
displayName: truncatedDisplayName,
|
|
1659
|
+
remainder: othersSuffix
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
const firstName = names[0] || "";
|
|
1665
|
+
const othersCount = names.length - 1;
|
|
1666
|
+
if (othersCount > 0) return {
|
|
1667
|
+
displayName: firstName,
|
|
1668
|
+
remainder: `+ ${othersCount} other${othersCount > 1 ? "s" : ""}`
|
|
1669
|
+
};
|
|
1670
|
+
return { displayName: firstName };
|
|
1671
|
+
}
|
|
1672
|
+
//#endregion
|
|
1673
|
+
//#region ../../messaging/core/src/utils/file-kinds.ts
|
|
1674
|
+
const ServerFileKindEnum$1 = zod.z.enum([
|
|
1675
|
+
"image",
|
|
1676
|
+
"video",
|
|
1677
|
+
"pdf",
|
|
1678
|
+
"sheets",
|
|
1679
|
+
"powerpoint",
|
|
1680
|
+
"docx"
|
|
1681
|
+
]);
|
|
1682
|
+
const fallbackFileType$1 = "docx";
|
|
1683
|
+
//#endregion
|
|
1684
|
+
//#region ../../messaging/core/src/messaging-api.ts
|
|
1685
|
+
const MessagingApiContext = (0, react.createContext)(null);
|
|
1686
|
+
const MessagingApiProvider = MessagingApiContext.Provider;
|
|
1687
|
+
function useMessagingApi() {
|
|
1688
|
+
const api = (0, react.useContext)(MessagingApiContext);
|
|
1689
|
+
if (!api) throw new Error("useMessagingApi must be used within a MessagingApiProvider");
|
|
1690
|
+
return api;
|
|
1626
1691
|
}
|
|
1627
|
-
zod.z.strictObject({ bot: zod.z.strictObject({
|
|
1628
|
-
title: zod.z.string(),
|
|
1629
|
-
description: zod.z.string().nullable().optional(),
|
|
1630
|
-
external_uri: zod.z.string().url().nullable().optional(),
|
|
1631
|
-
image_url: zod.z.string().url().nullable().optional(),
|
|
1632
|
-
type: zod.z.string().optional(),
|
|
1633
|
-
avatar_background_color: zod.z.string().nullable().optional(),
|
|
1634
|
-
avatar_emoji: zod.z.string().nullable().optional(),
|
|
1635
|
-
webhook_external_uri: zod.z.string().url().nullable().optional(),
|
|
1636
|
-
webhook_auth_token: zod.z.string().nullable().optional(),
|
|
1637
|
-
webhook_http_method: zod.z.string().nullable().optional(),
|
|
1638
|
-
conversation_ids: zod.z.array(zod.z.number()).optional()
|
|
1639
|
-
}) });
|
|
1640
1692
|
//#endregion
|
|
1641
1693
|
//#region ../../messaging/core/src/hooks/useConversations.ts
|
|
1642
|
-
function useConversations(
|
|
1694
|
+
function useConversations(input) {
|
|
1695
|
+
const api = useMessagingApi();
|
|
1643
1696
|
return (0, _tanstack_react_query.useInfiniteQuery)({
|
|
1644
1697
|
queryKey: [...MESSAGES_QUERY_KEYS.listConversations(), input ?? {}],
|
|
1645
|
-
queryFn: ({ pageParam }) => listConversations(
|
|
1698
|
+
queryFn: ({ pageParam }) => api.listConversations({
|
|
1646
1699
|
...input || {},
|
|
1647
1700
|
page: pageParam ?? 1
|
|
1648
1701
|
}),
|
|
@@ -1652,14 +1705,15 @@ function useConversations(config, input) {
|
|
|
1652
1705
|
...WEBSOCKET_QUERY_OPTIONS
|
|
1653
1706
|
});
|
|
1654
1707
|
}
|
|
1655
|
-
function useConversation(
|
|
1708
|
+
function useConversation(conversationId) {
|
|
1709
|
+
const api = useMessagingApi();
|
|
1656
1710
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
1657
1711
|
return (0, _tanstack_react_query.useQuery)({
|
|
1658
1712
|
queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId),
|
|
1659
1713
|
queryFn: async () => {
|
|
1660
1714
|
const fromSeed = seedConversationMetadataFromLists(queryClient, conversationId);
|
|
1661
1715
|
if (fromSeed) return fromSeed;
|
|
1662
|
-
const result = await getConversation(
|
|
1716
|
+
const result = await api.getConversation(conversationId);
|
|
1663
1717
|
applyConversationMetaToCaches({
|
|
1664
1718
|
queryClient,
|
|
1665
1719
|
conversation: {
|
|
@@ -1675,8 +1729,9 @@ function useConversation(config, conversationId) {
|
|
|
1675
1729
|
}
|
|
1676
1730
|
//#endregion
|
|
1677
1731
|
//#region ../../messaging/core/src/hooks/useFindConversation.ts
|
|
1678
|
-
function useFindConversation(
|
|
1732
|
+
function useFindConversation(params) {
|
|
1679
1733
|
const { recipients, contact_id, conversation_type, enabled } = params;
|
|
1734
|
+
const api = useMessagingApi();
|
|
1680
1735
|
const useRecipientsFlow = Array.isArray(recipients) && recipients.length > 0;
|
|
1681
1736
|
const inferredType = conversation_type || (useRecipientsFlow ? recipients.length > 1 ? "GroupConversation" : "DirectConversation" : void 0);
|
|
1682
1737
|
return (0, _tanstack_react_query.useQuery)({
|
|
@@ -1690,11 +1745,11 @@ function useFindConversation(config, params) {
|
|
|
1690
1745
|
conversation_type
|
|
1691
1746
|
],
|
|
1692
1747
|
queryFn: async () => {
|
|
1693
|
-
if (useRecipientsFlow) return await createConversation(
|
|
1748
|
+
if (useRecipientsFlow) return await api.createConversation({
|
|
1694
1749
|
conversation: { type: inferredType },
|
|
1695
1750
|
recipients: recipients.map((r) => ({ uid: r.uid }))
|
|
1696
1751
|
});
|
|
1697
|
-
if (typeof contact_id === "number" && conversation_type) return await findConversation(
|
|
1752
|
+
if (typeof contact_id === "number" && conversation_type) return await api.findConversation({
|
|
1698
1753
|
contact_id,
|
|
1699
1754
|
conversation_type
|
|
1700
1755
|
});
|
|
@@ -1706,20 +1761,22 @@ function useFindConversation(config, params) {
|
|
|
1706
1761
|
}
|
|
1707
1762
|
//#endregion
|
|
1708
1763
|
//#region ../../messaging/core/src/hooks/useAnnouncementChannel.ts
|
|
1709
|
-
function useAnnouncementChannel(
|
|
1764
|
+
function useAnnouncementChannel() {
|
|
1765
|
+
const api = useMessagingApi();
|
|
1710
1766
|
return (0, _tanstack_react_query.useQuery)({
|
|
1711
1767
|
queryKey: MESSAGES_QUERY_KEYS.announcementChannel(),
|
|
1712
|
-
queryFn: () => getAnnouncementChannel(
|
|
1768
|
+
queryFn: () => api.getAnnouncementChannel(),
|
|
1713
1769
|
staleTime: Infinity,
|
|
1714
1770
|
refetchOnWindowFocus: false
|
|
1715
1771
|
});
|
|
1716
1772
|
}
|
|
1717
1773
|
//#endregion
|
|
1718
1774
|
//#region ../../messaging/core/src/hooks/useScheduledMessages.ts
|
|
1719
|
-
function useScheduledMessages$1(
|
|
1775
|
+
function useScheduledMessages$1(conversationId, perPage = 20) {
|
|
1776
|
+
const api = useMessagingApi();
|
|
1720
1777
|
return (0, _tanstack_react_query.useInfiniteQuery)({
|
|
1721
1778
|
queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId),
|
|
1722
|
-
queryFn: async ({ pageParam }) => listScheduledMessages(
|
|
1779
|
+
queryFn: async ({ pageParam }) => api.listScheduledMessages(conversationId, {
|
|
1723
1780
|
page: pageParam ?? 1,
|
|
1724
1781
|
per_page: perPage
|
|
1725
1782
|
}),
|
|
@@ -1734,10 +1791,11 @@ function useScheduledMessages$1(config, conversationId, perPage = 20) {
|
|
|
1734
1791
|
}
|
|
1735
1792
|
//#endregion
|
|
1736
1793
|
//#region ../../messaging/core/src/hooks/useMyScheduledMessages.ts
|
|
1737
|
-
function useMyScheduledMessages(
|
|
1794
|
+
function useMyScheduledMessages() {
|
|
1795
|
+
const api = useMessagingApi();
|
|
1738
1796
|
return (0, _tanstack_react_query.useInfiniteQuery)({
|
|
1739
1797
|
queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages(),
|
|
1740
|
-
queryFn: async ({ pageParam }) => listMyScheduledMessages(
|
|
1798
|
+
queryFn: async ({ pageParam }) => api.listMyScheduledMessages({
|
|
1741
1799
|
page: pageParam ?? 1,
|
|
1742
1800
|
per_page: 50
|
|
1743
1801
|
}),
|
|
@@ -1749,12 +1807,13 @@ function useMyScheduledMessages(config) {
|
|
|
1749
1807
|
}
|
|
1750
1808
|
//#endregion
|
|
1751
1809
|
//#region ../../messaging/core/src/hooks/useReactions.ts
|
|
1752
|
-
function useReaction(
|
|
1810
|
+
function useReaction() {
|
|
1811
|
+
const api = useMessagingApi();
|
|
1753
1812
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
1754
1813
|
const reactMutation = (0, _tanstack_react_query.useMutation)({
|
|
1755
1814
|
mutationFn: async (params) => {
|
|
1756
1815
|
const { conversationId, messageId, reactionString } = params;
|
|
1757
|
-
return reactToMessage(
|
|
1816
|
+
return api.reactToMessage(conversationId, {
|
|
1758
1817
|
message_id: messageId,
|
|
1759
1818
|
reaction: reactionString
|
|
1760
1819
|
});
|
|
@@ -1802,7 +1861,7 @@ function useReaction(config) {
|
|
|
1802
1861
|
const unreactMutation = (0, _tanstack_react_query.useMutation)({
|
|
1803
1862
|
mutationFn: async (params) => {
|
|
1804
1863
|
const { conversationId, messageId } = params;
|
|
1805
|
-
return unreactToMessage(
|
|
1864
|
+
return api.unreactToMessage(conversationId, { message_id: messageId });
|
|
1806
1865
|
},
|
|
1807
1866
|
onMutate: async (variables) => {
|
|
1808
1867
|
const { conversationId, messageId } = variables;
|
|
@@ -1851,8 +1910,9 @@ function useReaction(config) {
|
|
|
1851
1910
|
}
|
|
1852
1911
|
//#endregion
|
|
1853
1912
|
//#region ../../messaging/core/src/hooks/useMessages.ts
|
|
1854
|
-
function useMessages$1(
|
|
1913
|
+
function useMessages$1(params, options) {
|
|
1855
1914
|
const { conversationId, messageId, perPage = 50 } = params;
|
|
1915
|
+
const api = useMessagingApi();
|
|
1856
1916
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
1857
1917
|
const isBottomAnchored = !messageId;
|
|
1858
1918
|
const query = (0, _tanstack_react_query.useInfiniteQuery)({
|
|
@@ -1861,7 +1921,7 @@ function useMessages$1(config, params, options) {
|
|
|
1861
1921
|
const param = pageParam;
|
|
1862
1922
|
const pageNumber = param?.type === "page" ? param.number ?? 1 : void 0;
|
|
1863
1923
|
const shouldMarkRead = isBottomAnchored && pageNumber === 1;
|
|
1864
|
-
const
|
|
1924
|
+
const input = param?.type === "message" ? {
|
|
1865
1925
|
preview: true,
|
|
1866
1926
|
message_id: param.id,
|
|
1867
1927
|
per_page: perPage
|
|
@@ -1869,7 +1929,8 @@ function useMessages$1(config, params, options) {
|
|
|
1869
1929
|
preview: !shouldMarkRead ? true : false,
|
|
1870
1930
|
page: pageNumber ?? 1,
|
|
1871
1931
|
per_page: perPage
|
|
1872
|
-
}
|
|
1932
|
+
};
|
|
1933
|
+
const result = await api.listMessages(conversationId, input);
|
|
1873
1934
|
if (shouldMarkRead) {
|
|
1874
1935
|
applyConversationMetaToCaches({
|
|
1875
1936
|
queryClient,
|
|
@@ -1938,7 +1999,8 @@ function useMessages$1(config, params, options) {
|
|
|
1938
1999
|
}
|
|
1939
2000
|
//#endregion
|
|
1940
2001
|
//#region ../../messaging/core/src/hooks/useSendMessage.ts
|
|
1941
|
-
function useSendMessage$1(
|
|
2002
|
+
function useSendMessage$1(conversationId, user, options) {
|
|
2003
|
+
const api = useMessagingApi();
|
|
1942
2004
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
1943
2005
|
const clientMessageIdRef = (0, react.useRef)(null);
|
|
1944
2006
|
return (0, _tanstack_react_query.useMutation)({
|
|
@@ -1961,8 +2023,8 @@ function useSendMessage$1(config, conversationId, user, options) {
|
|
|
1961
2023
|
},
|
|
1962
2024
|
client_message_id: clientMessageIdRef.current ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
|
1963
2025
|
};
|
|
1964
|
-
if (input.replyToId) return replyToMessage(
|
|
1965
|
-
return createMessage(
|
|
2026
|
+
if (input.replyToId) return api.replyToMessage(conversationId, input.replyToId, payload);
|
|
2027
|
+
return api.createMessage(conversationId, payload);
|
|
1966
2028
|
},
|
|
1967
2029
|
onMutate: async (input) => {
|
|
1968
2030
|
const rnd = Math.floor(Math.random() * 2147483647);
|
|
@@ -2072,8 +2134,9 @@ function useSendMessage$1(config, conversationId, user, options) {
|
|
|
2072
2134
|
}
|
|
2073
2135
|
//#endregion
|
|
2074
2136
|
//#region ../../messaging/core/src/hooks/useUpdateScheduledMessage.ts
|
|
2075
|
-
function useUpdateScheduledMessage$1(
|
|
2137
|
+
function useUpdateScheduledMessage$1(params, callbacks) {
|
|
2076
2138
|
const { conversationId, editScheduledId, editScheduledMessage, conversationType } = params;
|
|
2139
|
+
const api = useMessagingApi();
|
|
2077
2140
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
2078
2141
|
return (0, _tanstack_react_query.useMutation)({
|
|
2079
2142
|
mutationFn: async ({ body, attachments }) => {
|
|
@@ -2091,14 +2154,14 @@ function useUpdateScheduledMessage$1(config, params, callbacks) {
|
|
|
2091
2154
|
const scheduledAtISO = editScheduledMessage?.scheduled_at || editScheduledMessage?.metadata?.scheduled_at || null;
|
|
2092
2155
|
if (!scheduledAtISO) throw new Error("Missing scheduled time for the message");
|
|
2093
2156
|
const messageType = mapConversationTypeToMessageType(conversationType);
|
|
2094
|
-
await createScheduledMessage(
|
|
2157
|
+
await api.createScheduledMessage(conversationId, { scheduled_message: {
|
|
2095
2158
|
body,
|
|
2096
2159
|
theme: "inquiry",
|
|
2097
2160
|
scheduled_at: new Date(scheduledAtISO).toISOString(),
|
|
2098
2161
|
type: messageType,
|
|
2099
2162
|
attachments_attributes: mappedAttachments
|
|
2100
2163
|
} });
|
|
2101
|
-
await destroyScheduledMessage(
|
|
2164
|
+
await api.destroyScheduledMessage(conversationId, editScheduledId);
|
|
2102
2165
|
},
|
|
2103
2166
|
onSuccess: async () => {
|
|
2104
2167
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -2580,6 +2643,39 @@ var require_actioncable = /* @__PURE__ */ require_chunk.__commonJSMin(((exports,
|
|
|
2580
2643
|
}));
|
|
2581
2644
|
//#endregion
|
|
2582
2645
|
//#region ../../messaging/core/src/cable/schemas.ts
|
|
2646
|
+
/**
|
|
2647
|
+
* Locally-defined schemas for WebSocket event parsing.
|
|
2648
|
+
* These replace imports from the API client to keep core dependency-free.
|
|
2649
|
+
*/
|
|
2650
|
+
const ReactionStatsSchema = zod.z.record(zod.z.string(), zod.z.number());
|
|
2651
|
+
/**
|
|
2652
|
+
* Lenient schema for parsing incoming WebSocket message payloads.
|
|
2653
|
+
* Uses passthrough() to accept extra/unknown fields from the server.
|
|
2654
|
+
*/
|
|
2655
|
+
const MessageItemSchema = zod.z.object({
|
|
2656
|
+
id: zod.z.number(),
|
|
2657
|
+
body: zod.z.string(),
|
|
2658
|
+
type: zod.z.string(),
|
|
2659
|
+
created_at: zod.z.string(),
|
|
2660
|
+
updated_at: zod.z.string().nullable().optional(),
|
|
2661
|
+
deleted_at: zod.z.string().nullable().optional(),
|
|
2662
|
+
edited_at: zod.z.string().nullable().optional(),
|
|
2663
|
+
conversation_id: zod.z.number(),
|
|
2664
|
+
status: zod.z.string(),
|
|
2665
|
+
attachments: zod.z.array(zod.z.any()),
|
|
2666
|
+
reaction_stats: zod.z.record(zod.z.string(), zod.z.number()).nullable(),
|
|
2667
|
+
reaction: zod.z.string().nullable().optional(),
|
|
2668
|
+
metadata: zod.z.any().optional(),
|
|
2669
|
+
emoji_only_count: zod.z.number().optional(),
|
|
2670
|
+
mentioned_recipients: zod.z.array(zod.z.any()),
|
|
2671
|
+
sent_at: zod.z.string().optional(),
|
|
2672
|
+
pinned: zod.z.boolean().optional(),
|
|
2673
|
+
scheduled_at: zod.z.string().optional(),
|
|
2674
|
+
replied_to_message: zod.z.any().nullable().optional(),
|
|
2675
|
+
sender_recipient: zod.z.any(),
|
|
2676
|
+
seen_by: zod.z.unknown(),
|
|
2677
|
+
seen_by_count: zod.z.unknown()
|
|
2678
|
+
}).passthrough();
|
|
2583
2679
|
const NewMessageSchema = zod.z.strictObject({
|
|
2584
2680
|
type: zod.z.literal("new_message"),
|
|
2585
2681
|
message: zod.z.string(),
|
|
@@ -3134,7 +3230,7 @@ function createFluidFileUploader(apiKey) {
|
|
|
3134
3230
|
//#endregion
|
|
3135
3231
|
//#region ../../messaging/ui/src/app/MessagingAppProvider.tsx
|
|
3136
3232
|
const MessagingAppContext = (0, react.createContext)(null);
|
|
3137
|
-
function MessagingAppProvider({
|
|
3233
|
+
function MessagingAppProvider({ api, auth, websocketUrl, token, renderImage, renderProfileTrigger, onNavigate, initialRoute, children }) {
|
|
3138
3234
|
const [currentRoute, setCurrentRoute] = (0, react.useState)(() => initialRoute ?? { view: "index" });
|
|
3139
3235
|
const navigation = {
|
|
3140
3236
|
navigate: (0, react.useCallback)((route) => {
|
|
@@ -3150,17 +3246,19 @@ function MessagingAppProvider({ config, auth, websocketUrl, token, renderImage,
|
|
|
3150
3246
|
}, [currentRoute.view, onNavigate]),
|
|
3151
3247
|
currentRoute
|
|
3152
3248
|
};
|
|
3153
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
3154
|
-
value:
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3249
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessagingApiProvider, {
|
|
3250
|
+
value: api,
|
|
3251
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessagingAppContext.Provider, {
|
|
3252
|
+
value: {
|
|
3253
|
+
auth,
|
|
3254
|
+
websocketUrl,
|
|
3255
|
+
token,
|
|
3256
|
+
renderImage,
|
|
3257
|
+
renderProfileTrigger,
|
|
3258
|
+
navigation
|
|
3259
|
+
},
|
|
3260
|
+
children
|
|
3261
|
+
})
|
|
3164
3262
|
});
|
|
3165
3263
|
}
|
|
3166
3264
|
function useMessagingApp() {
|
|
@@ -3390,12 +3488,12 @@ function getPageTitleFromRoute(route) {
|
|
|
3390
3488
|
}
|
|
3391
3489
|
}
|
|
3392
3490
|
function MessagesTopBar({ renderSidebarTrigger, renderBreadcrumb, history, onToast }) {
|
|
3393
|
-
const {
|
|
3491
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
3394
3492
|
const navigation = useMessagingNavigation();
|
|
3395
3493
|
const currentRoute = navigation.currentRoute;
|
|
3396
3494
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
3397
3495
|
const conversationId = "conversationId" in currentRoute ? currentRoute.conversationId : 0;
|
|
3398
|
-
const { data: conversation } = useConversation(
|
|
3496
|
+
const { data: conversation } = useConversation(conversationId);
|
|
3399
3497
|
const currentUser = auth.currentUser;
|
|
3400
3498
|
const affiliateId = currentUser?.affiliateId;
|
|
3401
3499
|
const otherUsers = conversation?.recipients.filter((r) => r.receivable_id !== affiliateId);
|
|
@@ -3855,9 +3953,9 @@ function Channel({ item, renderImage }) {
|
|
|
3855
3953
|
})] });
|
|
3856
3954
|
}
|
|
3857
3955
|
function SidebarItem({ item }) {
|
|
3858
|
-
const {
|
|
3956
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
3859
3957
|
const navigation = useMessagingNavigation();
|
|
3860
|
-
const { data: meta } = useConversation(
|
|
3958
|
+
const { data: meta } = useConversation(item.id);
|
|
3861
3959
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
3862
3960
|
const currentUser = auth.currentUser;
|
|
3863
3961
|
if (!currentUser) return null;
|
|
@@ -4021,7 +4119,6 @@ function useScrollerRef() {
|
|
|
4021
4119
|
return context;
|
|
4022
4120
|
}
|
|
4023
4121
|
function MessagingLayout({ children, topBar, renderLockedDownlineItem, renderNewChannelSidebar, showAdminFeatures = true, canUseMyDownline = false, downlineData }) {
|
|
4024
|
-
const { config } = useMessagingApp();
|
|
4025
4122
|
const navigation = useMessagingNavigation();
|
|
4026
4123
|
const [isDragging, setIsDragging] = (0, react.useState)(false);
|
|
4027
4124
|
const sidebarRef = (0, react.useRef)(null);
|
|
@@ -4059,8 +4156,8 @@ function MessagingLayout({ children, topBar, renderLockedDownlineItem, renderNew
|
|
|
4059
4156
|
handleMouseMove,
|
|
4060
4157
|
handleMouseUp
|
|
4061
4158
|
]);
|
|
4062
|
-
const result = useConversations(
|
|
4063
|
-
const { data: announcementChannel, isLoading: isLoadingAnnouncementChannel } = useAnnouncementChannel(
|
|
4159
|
+
const result = useConversations({ per_page: CHANNELS_PER_FETCH });
|
|
4160
|
+
const { data: announcementChannel, isLoading: isLoadingAnnouncementChannel } = useAnnouncementChannel();
|
|
4064
4161
|
const hasMyDownline = downlineData?.hasDownline ?? false;
|
|
4065
4162
|
const myDownlineConversation = downlineData?.conversation ?? null;
|
|
4066
4163
|
const allEntries = result.data?.pages.flatMap((page) => page[1].items) ?? [];
|
|
@@ -42958,11 +43055,12 @@ function Timestamp({ children: timestamp, className = "", displayMode = "auto" }
|
|
|
42958
43055
|
}
|
|
42959
43056
|
//#endregion
|
|
42960
43057
|
//#region ../../messaging/ui/src/components/Message.tsx
|
|
42961
|
-
function Message({ conversation, message, isGrouped, highlightType = "auto",
|
|
43058
|
+
function Message({ conversation, message, isGrouped, highlightType = "auto", renderProfileTrigger, renderProfileContent, renderImage, onPinError, onCopyLinkSuccess, onCopyLinkError, getMessageLink }) {
|
|
42962
43059
|
const [emojiPickerOpen, setEmojiPickerOpen] = (0, react.useState)(false);
|
|
42963
43060
|
const [optionsOpen, setOptionsOpen] = (0, react.useState)(false);
|
|
42964
43061
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
42965
|
-
const { react: react$1, unreact } = useReaction(
|
|
43062
|
+
const { react: react$1, unreact } = useReaction();
|
|
43063
|
+
const api = useMessagingApi();
|
|
42966
43064
|
const addReaction = (reaction) => {
|
|
42967
43065
|
react$1({
|
|
42968
43066
|
conversationId: conversation.id,
|
|
@@ -42988,7 +43086,8 @@ function Message({ conversation, message, isGrouped, highlightType = "auto", con
|
|
|
42988
43086
|
});
|
|
42989
43087
|
setOptimisticPinned(newPinned);
|
|
42990
43088
|
try {
|
|
42991
|
-
|
|
43089
|
+
if (newPinned) await api.pinMessage(conversation.id, message.id);
|
|
43090
|
+
else await api.unpinMessage(conversation.id, message.id);
|
|
42992
43091
|
} catch {
|
|
42993
43092
|
applyMessagePinnedFlag({
|
|
42994
43093
|
queryClient,
|
|
@@ -43652,8 +43751,8 @@ const deepEqual = (obj1, obj2) => {
|
|
|
43652
43751
|
* Hook to manage conversation draft state using React Query as the single source of truth.
|
|
43653
43752
|
* Initializes with data from conversation metadata to avoid loading delays.
|
|
43654
43753
|
*/
|
|
43655
|
-
function useConversationDraft(
|
|
43656
|
-
const { data: conversationMetadata } = useConversation(
|
|
43754
|
+
function useConversationDraft(conversationId) {
|
|
43755
|
+
const { data: conversationMetadata } = useConversation(conversationId);
|
|
43657
43756
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
43658
43757
|
const isLocalDraft = !conversationId;
|
|
43659
43758
|
const abortFunctionsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
@@ -43978,13 +44077,14 @@ function useIsDragging() {
|
|
|
43978
44077
|
}
|
|
43979
44078
|
//#endregion
|
|
43980
44079
|
//#region ../../messaging/ui/src/hooks/use-save-draft.ts
|
|
43981
|
-
function useSaveDraft(
|
|
44080
|
+
function useSaveDraft(conversationId, options) {
|
|
43982
44081
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
44082
|
+
const api = useMessagingApi();
|
|
43983
44083
|
return (0, _tanstack_react_query.useMutation)({
|
|
43984
44084
|
mutationFn: async () => {
|
|
43985
44085
|
if (!conversationId) return null;
|
|
43986
44086
|
try {
|
|
43987
|
-
await destroyDraft(
|
|
44087
|
+
await api.destroyDraft(conversationId);
|
|
43988
44088
|
} catch (err) {
|
|
43989
44089
|
if (err.status !== 404) console.warn("destroyDraft failed (continuing with save):", err);
|
|
43990
44090
|
}
|
|
@@ -44002,7 +44102,7 @@ function useSaveDraft(config, conversationId, options) {
|
|
|
44002
44102
|
const bodyIsEmpty = !latestBody.trim();
|
|
44003
44103
|
const hasFiles = latestAttachments.length > 0;
|
|
44004
44104
|
if (bodyIsEmpty && !hasFiles) return {};
|
|
44005
|
-
return await saveDraft(
|
|
44105
|
+
return await api.saveDraft(conversationId, { draft_message: {
|
|
44006
44106
|
body: latestBody,
|
|
44007
44107
|
attachments_attributes: hasFiles ? latestAttachments : void 0
|
|
44008
44108
|
} });
|
|
@@ -44038,10 +44138,10 @@ function useSaveDraft(config, conversationId, options) {
|
|
|
44038
44138
|
}
|
|
44039
44139
|
});
|
|
44040
44140
|
}
|
|
44041
|
-
function useDebouncedSaveDraft(
|
|
44141
|
+
function useDebouncedSaveDraft(conversationId, options) {
|
|
44042
44142
|
const debounceMs = options?.debounceMs ?? 1e3;
|
|
44043
44143
|
const debounceTimerRef = (0, react.useRef)(null);
|
|
44044
|
-
const saveDraftMutation = useSaveDraft(
|
|
44144
|
+
const saveDraftMutation = useSaveDraft(conversationId, { onError: options?.onError });
|
|
44045
44145
|
const debouncedSave = () => {
|
|
44046
44146
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
44047
44147
|
debounceTimerRef.current = setTimeout(() => {
|
|
@@ -57208,7 +57308,8 @@ function convertMentionTokensInJson(node, users) {
|
|
|
57208
57308
|
}
|
|
57209
57309
|
//#endregion
|
|
57210
57310
|
//#region ../../messaging/ui/src/components/composer/useComposerActions.ts
|
|
57211
|
-
function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFiles, completedFiles, clearDebounce, saveImmediately, draft, mode, sendWithEnter, conversationType, conversationId,
|
|
57311
|
+
function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFiles, completedFiles, clearDebounce, saveImmediately, draft, mode, sendWithEnter, conversationType, conversationId, onScheduleSuccess, onScheduleComplete, onScheduleError }) {
|
|
57312
|
+
const api = useMessagingApi();
|
|
57212
57313
|
const handleSend = (0, react.useCallback)(async () => {
|
|
57213
57314
|
if (!editor || !onSend) return;
|
|
57214
57315
|
if (isUploadingFiles) return;
|
|
@@ -57278,7 +57379,7 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
57278
57379
|
return parsed.success ? parsed.data : fallbackFileType;
|
|
57279
57380
|
};
|
|
57280
57381
|
const messageType = mapConversationTypeToMessageType(conversationType);
|
|
57281
|
-
await createScheduledMessage(
|
|
57382
|
+
await api.createScheduledMessage(conversationId, { scheduled_message: {
|
|
57282
57383
|
body: markdown,
|
|
57283
57384
|
theme: "inquiry",
|
|
57284
57385
|
scheduled_at: scheduledAt.toISOString(),
|
|
@@ -57309,7 +57410,7 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
57309
57410
|
completedFiles,
|
|
57310
57411
|
conversationId,
|
|
57311
57412
|
conversationType,
|
|
57312
|
-
|
|
57413
|
+
api,
|
|
57313
57414
|
draft,
|
|
57314
57415
|
saveImmediately,
|
|
57315
57416
|
onScheduleSuccess,
|
|
@@ -57321,11 +57422,11 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
57321
57422
|
}
|
|
57322
57423
|
//#endregion
|
|
57323
57424
|
//#region ../../messaging/ui/src/components/composer/MessageComposer.tsx
|
|
57324
|
-
function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, placeholder = "Type your message...", disabled = false, mentionableUsers, conversationType, conversationId, saveDrafts = true, mode = "normal", initialEditData,
|
|
57425
|
+
function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, placeholder = "Type your message...", disabled = false, mentionableUsers, conversationType, conversationId, saveDrafts = true, mode = "normal", initialEditData, uploader, canSchedule = false, renderUpgradePrompt, renderLinkModal, renderImage, onScheduleComplete, onScheduleError, onUploadError }) {
|
|
57325
57426
|
const [sendWithEnter] = use_local_storage_state_default("messages-send-with-enter", { defaultValue: true });
|
|
57326
57427
|
const effectiveConversationId = saveDrafts ? conversationId : 0;
|
|
57327
|
-
const draft = useConversationDraft(
|
|
57328
|
-
const { debouncedSave, saveImmediately, isSaving, clearDebounce } = useDebouncedSaveDraft(
|
|
57428
|
+
const draft = useConversationDraft(effectiveConversationId);
|
|
57429
|
+
const { debouncedSave, saveImmediately, isSaving, clearDebounce } = useDebouncedSaveDraft(effectiveConversationId);
|
|
57329
57430
|
const { handleFiles, handleRemoveFile } = useDraftFileUpload({
|
|
57330
57431
|
uploader,
|
|
57331
57432
|
draftApi: {
|
|
@@ -57449,7 +57550,6 @@ function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, place
|
|
|
57449
57550
|
sendWithEnter,
|
|
57450
57551
|
conversationType,
|
|
57451
57552
|
conversationId,
|
|
57452
|
-
config,
|
|
57453
57553
|
onScheduleSuccess,
|
|
57454
57554
|
onScheduleComplete,
|
|
57455
57555
|
onScheduleError
|
|
@@ -57503,13 +57603,12 @@ function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, place
|
|
|
57503
57603
|
}
|
|
57504
57604
|
//#endregion
|
|
57505
57605
|
//#region ../../messaging/ui/src/app/MessagesViewComposer.tsx
|
|
57506
|
-
function MessagesViewComposer({ conversationId, conversationMetadata,
|
|
57606
|
+
function MessagesViewComposer({ conversationId, conversationMetadata, uploader, saveDrafts, isEditingScheduled, editScheduledMessage, mentionableUsers, renderImage, isLoading, user, isSendPending, isUpdatePending, onSend, onSaveScheduledEdit, onScheduleSuccess }) {
|
|
57507
57607
|
const input = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageComposer, {
|
|
57508
57608
|
conversationId,
|
|
57509
57609
|
conversationType: conversationMetadata?.type,
|
|
57510
57610
|
saveDrafts: isEditingScheduled ? false : saveDrafts,
|
|
57511
57611
|
mode: isEditingScheduled ? "editScheduled" : "normal",
|
|
57512
|
-
config,
|
|
57513
57612
|
uploader,
|
|
57514
57613
|
initialEditData: isEditingScheduled && editScheduledMessage ? {
|
|
57515
57614
|
body: editScheduledMessage.body || "",
|
|
@@ -57570,13 +57669,11 @@ function HeaderSlot({ render, conversation }) {
|
|
|
57570
57669
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: render(conversation) });
|
|
57571
57670
|
}
|
|
57572
57671
|
function ViewMessage({ conversation, message, isGrouped, highlightType = "auto", renderProfileTrigger, renderProfileContent, renderImage, onToast, getMessageLink }) {
|
|
57573
|
-
const { config } = useMessagingApp();
|
|
57574
57672
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Message, {
|
|
57575
57673
|
conversation,
|
|
57576
57674
|
message,
|
|
57577
57675
|
isGrouped,
|
|
57578
57676
|
highlightType,
|
|
57579
|
-
config,
|
|
57580
57677
|
renderProfileTrigger,
|
|
57581
57678
|
renderProfileContent,
|
|
57582
57679
|
renderImage,
|
|
@@ -57618,7 +57715,7 @@ const getFallbackSystemMessageText = (senderName, conversationMetadata) => {
|
|
|
57618
57715
|
};
|
|
57619
57716
|
function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, renderProfileTrigger, renderProfileContent, onToast, getMessageLink, renderScheduledBanner, renderHeader, onMarkRead, uploader }) {
|
|
57620
57717
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
57621
|
-
const {
|
|
57718
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
57622
57719
|
const navigation = useMessagingNavigation();
|
|
57623
57720
|
const scrollerRef = useScrollerRef();
|
|
57624
57721
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
@@ -57657,7 +57754,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
57657
57754
|
scrollTo
|
|
57658
57755
|
]);
|
|
57659
57756
|
const { messages, fetchNextPage, hasNextPage, isFetchingNextPage, fetchPreviousPage, hasPreviousPage, isFetchingPreviousPage, isLoading: isLoadingMessages, isError, error: messagesError } = useMessages({
|
|
57660
|
-
config,
|
|
57661
57757
|
conversationId,
|
|
57662
57758
|
messageId: messageId ? Number(messageId) : void 0,
|
|
57663
57759
|
perPage: 50,
|
|
@@ -57667,7 +57763,7 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
57667
57763
|
const lastId = messages[messages.length - 1]?.id ?? "x";
|
|
57668
57764
|
return `${messages.length}:${String(lastId)}`;
|
|
57669
57765
|
}, [messages]);
|
|
57670
|
-
const { data: conversationMetadata, error: metadataError, isLoading: isLoadingMetadata } = useConversation(
|
|
57766
|
+
const { data: conversationMetadata, error: metadataError, isLoading: isLoadingMetadata } = useConversation(conversationId);
|
|
57671
57767
|
(0, react.useEffect)(() => {
|
|
57672
57768
|
baselineCapturedRef.current = false;
|
|
57673
57769
|
setEntryUnreadBaselineId(null);
|
|
@@ -57686,7 +57782,7 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
57686
57782
|
scrollToBottom,
|
|
57687
57783
|
resetKey: conversationId
|
|
57688
57784
|
});
|
|
57689
|
-
const scheduledHook = useScheduledMessages(
|
|
57785
|
+
const scheduledHook = useScheduledMessages(conversationId);
|
|
57690
57786
|
const scheduledData = scheduledHook.data;
|
|
57691
57787
|
const refetchScheduled = scheduledHook.refetch;
|
|
57692
57788
|
const editScheduledId = (() => {
|
|
@@ -57725,7 +57821,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
57725
57821
|
};
|
|
57726
57822
|
}, [currentUser]);
|
|
57727
57823
|
const sendMessageMutation = useSendMessage({
|
|
57728
|
-
config,
|
|
57729
57824
|
conversationId,
|
|
57730
57825
|
user,
|
|
57731
57826
|
onSuccess: () => {
|
|
@@ -57739,7 +57834,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
57739
57834
|
}
|
|
57740
57835
|
});
|
|
57741
57836
|
const updateScheduledMutation = useUpdateScheduledMessage({
|
|
57742
|
-
config,
|
|
57743
57837
|
conversationId,
|
|
57744
57838
|
editScheduledId,
|
|
57745
57839
|
editScheduledMessage,
|
|
@@ -58037,7 +58131,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
58037
58131
|
const composer = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessagesViewComposer, {
|
|
58038
58132
|
conversationId,
|
|
58039
58133
|
conversationMetadata,
|
|
58040
|
-
config,
|
|
58041
58134
|
uploader,
|
|
58042
58135
|
saveDrafts,
|
|
58043
58136
|
isEditingScheduled,
|
|
@@ -58169,24 +58262,24 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
58169
58262
|
}, "composer")]
|
|
58170
58263
|
}, "layer-1");
|
|
58171
58264
|
}
|
|
58172
|
-
function useMessages({
|
|
58173
|
-
return useMessages$1(
|
|
58265
|
+
function useMessages({ conversationId, messageId, perPage, onMarkRead }) {
|
|
58266
|
+
return useMessages$1({
|
|
58174
58267
|
conversationId,
|
|
58175
58268
|
messageId,
|
|
58176
58269
|
perPage
|
|
58177
58270
|
}, { onMarkRead });
|
|
58178
58271
|
}
|
|
58179
|
-
function useSendMessage({
|
|
58180
|
-
return useSendMessage$1(
|
|
58272
|
+
function useSendMessage({ conversationId, user, onSuccess, onError }) {
|
|
58273
|
+
return useSendMessage$1(conversationId, user, {
|
|
58181
58274
|
onSuccess,
|
|
58182
58275
|
onError
|
|
58183
58276
|
});
|
|
58184
58277
|
}
|
|
58185
|
-
function useScheduledMessages(
|
|
58186
|
-
return useScheduledMessages$1(
|
|
58278
|
+
function useScheduledMessages(conversationId) {
|
|
58279
|
+
return useScheduledMessages$1(conversationId, 50);
|
|
58187
58280
|
}
|
|
58188
|
-
function useUpdateScheduledMessage({
|
|
58189
|
-
return useUpdateScheduledMessage$1(
|
|
58281
|
+
function useUpdateScheduledMessage({ conversationId, editScheduledId, editScheduledMessage, conversationType, onSuccess, onError }) {
|
|
58282
|
+
return useUpdateScheduledMessage$1({
|
|
58190
58283
|
conversationId,
|
|
58191
58284
|
editScheduledId,
|
|
58192
58285
|
editScheduledMessage,
|
|
@@ -58199,13 +58292,12 @@ function useUpdateScheduledMessage({ config, conversationId, editScheduledId, ed
|
|
|
58199
58292
|
//#endregion
|
|
58200
58293
|
//#region ../../messaging/ui/src/app/PinnedMessagesView.tsx
|
|
58201
58294
|
function PinnedMessage({ conversation, message, onToast, renderProfileContent, getMessageLink }) {
|
|
58202
|
-
const {
|
|
58295
|
+
const { renderImage, renderProfileTrigger } = useMessagingApp();
|
|
58203
58296
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Message, {
|
|
58204
58297
|
conversation,
|
|
58205
58298
|
message,
|
|
58206
58299
|
isGrouped: false,
|
|
58207
58300
|
highlightType: "never",
|
|
58208
|
-
config,
|
|
58209
58301
|
renderProfileTrigger: renderProfileTrigger ? ({ children }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children }) : void 0,
|
|
58210
58302
|
renderProfileContent,
|
|
58211
58303
|
renderImage,
|
|
@@ -58216,12 +58308,12 @@ function PinnedMessage({ conversation, message, onToast, renderProfileContent, g
|
|
|
58216
58308
|
});
|
|
58217
58309
|
}
|
|
58218
58310
|
function PinnedMessagesView({ conversationId, onToast, renderProfileContent, getMessageLink }) {
|
|
58219
|
-
const { config } = useMessagingApp();
|
|
58220
58311
|
const { navigate } = useMessagingNavigation();
|
|
58312
|
+
const api = useMessagingApi();
|
|
58221
58313
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = (0, _tanstack_react_query.useInfiniteQuery)({
|
|
58222
58314
|
queryKey: MESSAGES_QUERY_KEYS.listPinnedMessages(conversationId),
|
|
58223
58315
|
queryFn: async ({ pageParam }) => {
|
|
58224
|
-
return listPinnedMessages(
|
|
58316
|
+
return api.listPinnedMessages(conversationId, {
|
|
58225
58317
|
page: pageParam,
|
|
58226
58318
|
per_page: 50
|
|
58227
58319
|
});
|
|
@@ -58232,7 +58324,7 @@ function PinnedMessagesView({ conversationId, onToast, renderProfileContent, get
|
|
|
58232
58324
|
staleTime: PINNED_MESSAGES_STALE_TIME,
|
|
58233
58325
|
refetchInterval: PINNED_MESSAGES_REFETCH_INTERVAL
|
|
58234
58326
|
});
|
|
58235
|
-
const { data: conversationMetadata } = useConversation(
|
|
58327
|
+
const { data: conversationMetadata } = useConversation(conversationId);
|
|
58236
58328
|
const messages = (0, react.useMemo)(() => {
|
|
58237
58329
|
if (!data?.pages) return [];
|
|
58238
58330
|
return data.pages.flatMap((pageData) => pageData[1].items);
|
|
@@ -58849,10 +58941,9 @@ function getConversationIdFromRecipients(recipients) {
|
|
|
58849
58941
|
return conversationRecipient ? conversationRecipient.id : null;
|
|
58850
58942
|
}
|
|
58851
58943
|
function NewMessageView({ searchUsers, searchChannels, getCachedChannels, messagesViewProps }) {
|
|
58852
|
-
const { config } = useMessagingApp();
|
|
58853
58944
|
const [selectedRecipients, setSelectedRecipients] = (0, react.useState)([]);
|
|
58854
58945
|
const selectedConversationId = (0, react.useMemo)(() => getConversationIdFromRecipients(selectedRecipients), [selectedRecipients]);
|
|
58855
|
-
const { data: foundConversation } = useFindConversation(
|
|
58946
|
+
const { data: foundConversation } = useFindConversation({
|
|
58856
58947
|
recipients: selectedRecipients.length > 0 && !selectedConversationId ? selectedRecipients.map((r) => ({ uid: r.id })) : void 0,
|
|
58857
58948
|
enabled: selectedRecipients.length > 0 && !selectedConversationId
|
|
58858
58949
|
});
|
|
@@ -59112,9 +59203,10 @@ function formatScheduledShort(iso) {
|
|
|
59112
59203
|
}
|
|
59113
59204
|
}
|
|
59114
59205
|
function ScheduledMessageRow({ message, onToast }) {
|
|
59115
|
-
const {
|
|
59206
|
+
const { auth, renderImage } = useMessagingApp();
|
|
59116
59207
|
const { navigate } = useMessagingNavigation();
|
|
59117
59208
|
const queryClient = (0, _tanstack_react_query.useQueryClient)();
|
|
59209
|
+
const api = useMessagingApi();
|
|
59118
59210
|
const [openConfirm, setOpenConfirm] = (0, react.useState)(false);
|
|
59119
59211
|
const [openDeleteConfirm, setOpenDeleteConfirm] = (0, react.useState)(false);
|
|
59120
59212
|
const [openReschedule, setOpenReschedule] = (0, react.useState)(false);
|
|
@@ -59123,7 +59215,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
59123
59215
|
const conversation = message.conversation;
|
|
59124
59216
|
const rawConversationId = conversation?.id ?? message.conversation_id ?? 0;
|
|
59125
59217
|
const conversationId = typeof rawConversationId === "string" ? Number.parseInt(rawConversationId, 10) : Number(rawConversationId);
|
|
59126
|
-
const { data: fullConversation } = useConversation(
|
|
59218
|
+
const { data: fullConversation } = useConversation(conversationId);
|
|
59127
59219
|
const messageType = message.type;
|
|
59128
59220
|
const effectiveConversation = fullConversation || conversation;
|
|
59129
59221
|
const affiliateId = auth.currentUser?.affiliateId;
|
|
@@ -59161,12 +59253,13 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
59161
59253
|
metadata: a.metadata ?? void 0
|
|
59162
59254
|
};
|
|
59163
59255
|
});
|
|
59164
|
-
|
|
59165
|
-
|
|
59256
|
+
const typeFromConversation = mapConversationTypeToMessageType(fullConversation.type);
|
|
59257
|
+
await api.createMessage(conversationId, { message: {
|
|
59258
|
+
type: typeFromConversation,
|
|
59166
59259
|
body: message.body || "",
|
|
59167
59260
|
attachments_attributes: mappedAttachments
|
|
59168
59261
|
} });
|
|
59169
|
-
await destroyScheduledMessage(
|
|
59262
|
+
await api.destroyScheduledMessage(conversationId, message.id);
|
|
59170
59263
|
},
|
|
59171
59264
|
onSuccess: async () => {
|
|
59172
59265
|
await Promise.all([
|
|
@@ -59183,7 +59276,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
59183
59276
|
});
|
|
59184
59277
|
const deleteMutation = (0, _tanstack_react_query.useMutation)({
|
|
59185
59278
|
mutationFn: async () => {
|
|
59186
|
-
await destroyScheduledMessage(
|
|
59279
|
+
await api.destroyScheduledMessage(conversationId, message.id);
|
|
59187
59280
|
},
|
|
59188
59281
|
onSuccess: async () => {
|
|
59189
59282
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -59193,7 +59286,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
59193
59286
|
});
|
|
59194
59287
|
const rescheduleMutation = (0, _tanstack_react_query.useMutation)({
|
|
59195
59288
|
mutationFn: async (isoDate) => {
|
|
59196
|
-
await updateScheduledMessage(
|
|
59289
|
+
await api.updateScheduledMessage(conversationId, message.id, { scheduled_message: { scheduled_at: isoDate } });
|
|
59197
59290
|
},
|
|
59198
59291
|
onSuccess: async () => {
|
|
59199
59292
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -59343,8 +59436,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
59343
59436
|
//#endregion
|
|
59344
59437
|
//#region ../../messaging/ui/src/app/ScheduledMessagesView.tsx
|
|
59345
59438
|
function ScheduledMessagesView({ onToast }) {
|
|
59346
|
-
const {
|
|
59347
|
-
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useMyScheduledMessages(config);
|
|
59439
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useMyScheduledMessages();
|
|
59348
59440
|
const messages = (0, react.useMemo)(() => {
|
|
59349
59441
|
if (!data?.pages) return [];
|
|
59350
59442
|
return data.pages.flatMap((p) => p[1].items);
|
|
@@ -59409,9 +59501,9 @@ function ScheduledMessagesView({ onToast }) {
|
|
|
59409
59501
|
}
|
|
59410
59502
|
//#endregion
|
|
59411
59503
|
//#region ../../messaging/ui/src/app/MessagingApp.tsx
|
|
59412
|
-
function MessagingApp({
|
|
59504
|
+
function MessagingApp({ api, auth, websocketUrl, token, renderImage, renderProfileTrigger, onNavigate, initialRoute, renderLockedDownlineItem, renderNewChannelSidebar, showAdminFeatures, canUseMyDownline, downlineData, topBar, messagesViewProps, newMessageViewProps, onToast, getMessageLink, renderProfileContent, children }) {
|
|
59413
59505
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(MessagingAppProvider, {
|
|
59414
|
-
|
|
59506
|
+
api,
|
|
59415
59507
|
auth,
|
|
59416
59508
|
websocketUrl,
|
|
59417
59509
|
token,
|
|
@@ -59497,14 +59589,14 @@ function defaultToast(message, type) {
|
|
|
59497
59589
|
}
|
|
59498
59590
|
function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlOverride, background, textColor, accentColor, padding, borderRadius, ...divProps }) {
|
|
59499
59591
|
const { config } = require_FluidProvider.useFluidContext();
|
|
59500
|
-
const { apiConfig, websocketUrl, token } = useMessagingConfig();
|
|
59592
|
+
const { apiConfig, messagingApi, websocketUrl, token } = useMessagingConfig();
|
|
59501
59593
|
const messagingAuth = useMessagingAuth();
|
|
59502
59594
|
const effectiveApiKey = filestackApiKey ?? config.filestackApiKey;
|
|
59503
59595
|
const uploader = (0, react.useMemo)(() => createFluidFileUploader(effectiveApiKey), [effectiveApiKey]);
|
|
59504
59596
|
const effectiveWsUrl = websocketUrlOverride ?? websocketUrl;
|
|
59505
59597
|
const effectiveToast = onToast ?? defaultToast;
|
|
59506
59598
|
const searchUsers = (0, react.useCallback)(async (query) => {
|
|
59507
|
-
return ((await listConnectedRecipients(
|
|
59599
|
+
return ((await messagingApi.listConnectedRecipients({
|
|
59508
59600
|
filterrific: { search_query: query },
|
|
59509
59601
|
per_page: 10,
|
|
59510
59602
|
page: 1
|
|
@@ -59524,7 +59616,7 @@ function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlO
|
|
|
59524
59616
|
conversationName: name
|
|
59525
59617
|
};
|
|
59526
59618
|
});
|
|
59527
|
-
}, [
|
|
59619
|
+
}, [messagingApi]);
|
|
59528
59620
|
const searchChannels = (0, react.useCallback)(async (query) => {
|
|
59529
59621
|
return (await searchConversations(apiConfig, { filterrific: { search_query: query } }) ?? []).map((channel) => {
|
|
59530
59622
|
const { text: nameWithoutEmoji } = extractEmoji(channel.name);
|
|
@@ -59567,7 +59659,7 @@ function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlO
|
|
|
59567
59659
|
...divProps,
|
|
59568
59660
|
className: `h-full ${divProps.className ?? ""}`,
|
|
59569
59661
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessagingApp, {
|
|
59570
|
-
|
|
59662
|
+
api: messagingApi,
|
|
59571
59663
|
auth: messagingAuth,
|
|
59572
59664
|
websocketUrl: effectiveWsUrl,
|
|
59573
59665
|
token,
|
|
@@ -59627,4 +59719,4 @@ Object.defineProperty(exports, "useMessagingConfig", {
|
|
|
59627
59719
|
}
|
|
59628
59720
|
});
|
|
59629
59721
|
|
|
59630
|
-
//# sourceMappingURL=MessagingScreen-
|
|
59722
|
+
//# sourceMappingURL=MessagingScreen-Cgx3jwpr.cjs.map
|