@fluid-app/portal-sdk 0.1.98 → 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-DA62lyrN.mjs → MessagingScreen-84R1a-lD.mjs} +2 -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-C0o1gVNw.mjs → ProductsScreen-6J79mnIB.mjs} +2 -2
- package/dist/{ProductsScreen-C0o1gVNw.mjs.map → ProductsScreen-6J79mnIB.mjs.map} +1 -1
- package/dist/{ProductsScreen-CL16lwsI.cjs → ProductsScreen-DZnKtPBp.cjs} +2 -2
- package/dist/{ProductsScreen-CigOdDOU.mjs → ProductsScreen-DbHS3p4U.mjs} +6 -2
- package/dist/{ProductsScreen-BTrBbxX6.cjs → ProductsScreen-PJ95OcSX.cjs} +2 -2
- package/dist/{ProductsScreen-BTrBbxX6.cjs.map → ProductsScreen-PJ95OcSX.cjs.map} +1 -1
- package/dist/ShareablesScreen-CVT7u2hN.cjs +398 -0
- package/dist/ShareablesScreen-CVT7u2hN.cjs.map +1 -0
- package/dist/ShareablesScreen-Cy7w85IH.mjs +380 -0
- package/dist/ShareablesScreen-Cy7w85IH.mjs.map +1 -0
- package/dist/{ShareablesScreen-CSLB0ZXS.mjs → ShareablesScreen-DUpaH8VU.mjs} +6 -2
- package/dist/{ShareablesScreen-DufZz22d.cjs → ShareablesScreen-Dk5EQGMa.cjs} +2 -2
- package/dist/index.cjs +10 -10
- 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 +10 -10
- package/dist/{src-D9jWnRlX.mjs → src-BrwLqPPh.mjs} +90 -269
- package/dist/src-BrwLqPPh.mjs.map +1 -0
- package/dist/{src-C3R03o4d.cjs → src-Dlsw83js.cjs} +95 -268
- package/dist/src-Dlsw83js.cjs.map +1 -0
- package/package.json +12 -12
- package/dist/MessagingScreen-B9CCsimy.cjs.map +0 -1
- package/dist/MessagingScreen-DTXSZ-Oa.mjs.map +0 -1
- package/dist/ShareablesScreen-B9c5Mw5b.mjs +0 -159
- package/dist/ShareablesScreen-B9c5Mw5b.mjs.map +0 -1
- package/dist/ShareablesScreen-Bldl8tta.cjs +0 -177
- package/dist/ShareablesScreen-Bldl8tta.cjs.map +0 -1
- package/dist/src-C3R03o4d.cjs.map +0 -1
- package/dist/src-D9jWnRlX.mjs.map +0 -1
|
@@ -955,742 +955,6 @@ function useMessagingAuth() {
|
|
|
955
955
|
};
|
|
956
956
|
}
|
|
957
957
|
//#endregion
|
|
958
|
-
//#region src/messaging/use-messaging-config.ts
|
|
959
|
-
/**
|
|
960
|
-
* Hook that derives MessagingApiConfig from the portal SDK's FluidProvider context.
|
|
961
|
-
*
|
|
962
|
-
* Maps FluidSDKConfig fields to the shape expected by MessagingApp:
|
|
963
|
-
* - baseUrl -> from config.baseUrl
|
|
964
|
-
* - getHeaders -> builds Authorization header from config.getAuthToken()
|
|
965
|
-
* - onAuthError -> from config.onAuthError
|
|
966
|
-
* - websocketUrl -> config.websocketUrl or derived from baseUrl
|
|
967
|
-
* - token -> from auth context
|
|
968
|
-
*/
|
|
969
|
-
function deriveWebsocketUrl(baseUrl) {
|
|
970
|
-
return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
|
|
971
|
-
}
|
|
972
|
-
function useMessagingConfig() {
|
|
973
|
-
const { config } = useFluidContext();
|
|
974
|
-
const auth = useFluidAuthContext();
|
|
975
|
-
const getHeaders = useCallback(async () => {
|
|
976
|
-
const headers = {
|
|
977
|
-
"Content-Type": "application/json",
|
|
978
|
-
...config.defaultHeaders
|
|
979
|
-
};
|
|
980
|
-
if (config.getAuthToken) {
|
|
981
|
-
const token = await config.getAuthToken();
|
|
982
|
-
if (token) headers.Authorization = `Bearer ${token}`;
|
|
983
|
-
}
|
|
984
|
-
return headers;
|
|
985
|
-
}, [config]);
|
|
986
|
-
const apiBaseUrl = useMemo(() => {
|
|
987
|
-
const base = config.baseUrl.replace(/\/+$/, "");
|
|
988
|
-
return base.endsWith("/api") ? base : `${base}/api`;
|
|
989
|
-
}, [config.baseUrl]);
|
|
990
|
-
return {
|
|
991
|
-
apiConfig: useMemo(() => ({
|
|
992
|
-
baseUrl: apiBaseUrl,
|
|
993
|
-
getHeaders,
|
|
994
|
-
...config.onAuthError != null && { onAuthError: config.onAuthError }
|
|
995
|
-
}), [
|
|
996
|
-
apiBaseUrl,
|
|
997
|
-
config.onAuthError,
|
|
998
|
-
getHeaders
|
|
999
|
-
]),
|
|
1000
|
-
websocketUrl: useMemo(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
|
|
1001
|
-
token: auth.token
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region ../../messaging/core/src/query-keys.ts
|
|
1006
|
-
/**
|
|
1007
|
-
* Query key factory for all messaging-related TanStack Query queries.
|
|
1008
|
-
* Ensures consistent key shapes across hooks and cache helpers.
|
|
1009
|
-
*/
|
|
1010
|
-
const MESSAGES_QUERY_KEYS = {
|
|
1011
|
-
all: () => ["messages"],
|
|
1012
|
-
conversationMetadata: (conversationId) => [
|
|
1013
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1014
|
-
"conversation",
|
|
1015
|
-
conversationId
|
|
1016
|
-
],
|
|
1017
|
-
newMessageFlag: (conversationId) => [
|
|
1018
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1019
|
-
"conversation",
|
|
1020
|
-
conversationId,
|
|
1021
|
-
"new_message_flag"
|
|
1022
|
-
],
|
|
1023
|
-
listConversations: () => [...MESSAGES_QUERY_KEYS.all(), "conversations"],
|
|
1024
|
-
listMessages: (conversationId, messageId, startAtISO) => [
|
|
1025
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1026
|
-
"conversation",
|
|
1027
|
-
conversationId,
|
|
1028
|
-
"messages",
|
|
1029
|
-
messageId ? { startFromMessage: messageId } : startAtISO ? { startFromDate: startAtISO } : { startFromBottom: true }
|
|
1030
|
-
],
|
|
1031
|
-
listPinnedMessages: (conversationId) => [
|
|
1032
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1033
|
-
"conversation",
|
|
1034
|
-
conversationId,
|
|
1035
|
-
"pinned"
|
|
1036
|
-
],
|
|
1037
|
-
outbox: (conversationId) => [
|
|
1038
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1039
|
-
"conversation",
|
|
1040
|
-
conversationId,
|
|
1041
|
-
"outbox"
|
|
1042
|
-
],
|
|
1043
|
-
listScheduledMessages: (conversationId) => [
|
|
1044
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1045
|
-
"conversation",
|
|
1046
|
-
conversationId,
|
|
1047
|
-
"scheduled"
|
|
1048
|
-
],
|
|
1049
|
-
listMyScheduledMessages: () => [
|
|
1050
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1051
|
-
"scheduled",
|
|
1052
|
-
"mine"
|
|
1053
|
-
],
|
|
1054
|
-
draft: (conversationId) => [
|
|
1055
|
-
...MESSAGES_QUERY_KEYS.all(),
|
|
1056
|
-
"draft",
|
|
1057
|
-
conversationId
|
|
1058
|
-
],
|
|
1059
|
-
announcementChannel: () => [...MESSAGES_QUERY_KEYS.all(), "announcement-channel"],
|
|
1060
|
-
downline: () => [...MESSAGES_QUERY_KEYS.all(), "downline"]
|
|
1061
|
-
};
|
|
1062
|
-
//#endregion
|
|
1063
|
-
//#region ../../messaging/core/src/constants.ts
|
|
1064
|
-
const WEBSOCKET_QUERY_OPTIONS = {
|
|
1065
|
-
staleTime: Infinity,
|
|
1066
|
-
refetchOnMount: "always",
|
|
1067
|
-
refetchOnReconnect: "always",
|
|
1068
|
-
refetchOnWindowFocus: false
|
|
1069
|
-
};
|
|
1070
|
-
/**
|
|
1071
|
-
* Scheduled messages are not websocket-driven; poll to keep fresh.
|
|
1072
|
-
*/
|
|
1073
|
-
const SCHEDULED_MESSAGES_STALE_TIME = 6e4;
|
|
1074
|
-
const SCHEDULED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1075
|
-
/**
|
|
1076
|
-
* Pinned messages are not websocket-driven; poll to keep fresh.
|
|
1077
|
-
*/
|
|
1078
|
-
const PINNED_MESSAGES_STALE_TIME = 6e4;
|
|
1079
|
-
const PINNED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1080
|
-
//#endregion
|
|
1081
|
-
//#region ../../messaging/core/src/cache-helpers.ts
|
|
1082
|
-
/**
|
|
1083
|
-
* seed conversation metadata caches from any conversations list queries.
|
|
1084
|
-
* returns the matched conversation if conversationId is provided and present in any list cache.
|
|
1085
|
-
*/
|
|
1086
|
-
function seedConversationMetadataFromLists(queryClient, conversationId) {
|
|
1087
|
-
let matched;
|
|
1088
|
-
const seedFromData = (data) => {
|
|
1089
|
-
const pages = data?.pages ?? [];
|
|
1090
|
-
for (const page of pages) {
|
|
1091
|
-
const items = page?.[1]?.items ?? [];
|
|
1092
|
-
for (const item of items) {
|
|
1093
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(item.id), item);
|
|
1094
|
-
if (!matched && typeof conversationId === "number" && item.id === conversationId) matched = item;
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
seedFromData(queryClient.getQueryData(MESSAGES_QUERY_KEYS.listConversations()));
|
|
1099
|
-
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
1100
|
-
const key = q.queryKey;
|
|
1101
|
-
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
1102
|
-
} });
|
|
1103
|
-
for (const q of listQueries) seedFromData(queryClient.getQueryData(q.queryKey));
|
|
1104
|
-
return matched;
|
|
1105
|
-
}
|
|
1106
|
-
function isMessagesQueryKeyForConversation(queryKey, conversationId) {
|
|
1107
|
-
if (!Array.isArray(queryKey)) return false;
|
|
1108
|
-
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "messages";
|
|
1109
|
-
}
|
|
1110
|
-
function findMessageInCache(queryClient, conversationId, messageId) {
|
|
1111
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1112
|
-
for (const query of queries) {
|
|
1113
|
-
const data = queryClient.getQueryData(query.queryKey);
|
|
1114
|
-
if (data) for (const page of data.pages) {
|
|
1115
|
-
const message = page[1].items.find((m) => m.id === messageId);
|
|
1116
|
-
if (message) return message;
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
function forEachMessagesQuery(queryClient, conversationId, fn) {
|
|
1121
|
-
queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
1122
|
-
}
|
|
1123
|
-
function isBottomAnchorKey(queryKey) {
|
|
1124
|
-
if (!Array.isArray(queryKey)) return false;
|
|
1125
|
-
const anchor = queryKey[queryKey.length - 1];
|
|
1126
|
-
if (typeof anchor === "object" && anchor !== null) {
|
|
1127
|
-
if ("startFromBottom" in anchor) return anchor.startFromBottom === true;
|
|
1128
|
-
}
|
|
1129
|
-
return false;
|
|
1130
|
-
}
|
|
1131
|
-
function isPinnedQueryKeyForConversation(queryKey, conversationId) {
|
|
1132
|
-
if (!Array.isArray(queryKey)) return false;
|
|
1133
|
-
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "pinned";
|
|
1134
|
-
}
|
|
1135
|
-
function forEachPinnedQuery(queryClient, conversationId, fn) {
|
|
1136
|
-
queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
1137
|
-
}
|
|
1138
|
-
async function cancelMessageQueries(queryClient, conversationId) {
|
|
1139
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1140
|
-
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
1141
|
-
}
|
|
1142
|
-
async function cancelPinnedQueries(queryClient, conversationId) {
|
|
1143
|
-
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1144
|
-
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
1145
|
-
}
|
|
1146
|
-
function applyMessagesOptimisticSend({ queryClient, conversationId, optimisticMessage }) {
|
|
1147
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1148
|
-
if (!isBottomAnchorKey(key)) return;
|
|
1149
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1150
|
-
const newOptimisticPage = [{ pagination: {
|
|
1151
|
-
current: 1,
|
|
1152
|
-
previous: null,
|
|
1153
|
-
next: null,
|
|
1154
|
-
per_page: oldData?.pages?.[0]?.[0]?.pagination?.per_page ?? 20,
|
|
1155
|
-
pages: oldData?.pages?.[0]?.[0]?.pagination?.pages ?? 1,
|
|
1156
|
-
count: oldData?.pages?.[0]?.[0]?.pagination?.count ?? 0
|
|
1157
|
-
} }, { items: [optimisticMessage] }];
|
|
1158
|
-
if (!oldData) return {
|
|
1159
|
-
pages: [newOptimisticPage],
|
|
1160
|
-
pageParams: [1]
|
|
1161
|
-
};
|
|
1162
|
-
const pages = oldData.pages.map((page, idx) => {
|
|
1163
|
-
if (idx === 0) {
|
|
1164
|
-
const currentItems = page?.[1]?.items ?? [];
|
|
1165
|
-
return [{ pagination: {
|
|
1166
|
-
...page?.[0]?.pagination ?? {},
|
|
1167
|
-
count: page?.[0]?.pagination?.count ?? 0
|
|
1168
|
-
} }, { items: [optimisticMessage, ...currentItems] }];
|
|
1169
|
-
}
|
|
1170
|
-
return page;
|
|
1171
|
-
});
|
|
1172
|
-
return {
|
|
1173
|
-
...oldData,
|
|
1174
|
-
pages
|
|
1175
|
-
};
|
|
1176
|
-
});
|
|
1177
|
-
});
|
|
1178
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1179
|
-
const prev = old?.items ?? [];
|
|
1180
|
-
if (prev.some((m) => m.id === optimisticMessage.id)) return old;
|
|
1181
|
-
return { items: [...prev, optimisticMessage] };
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
/**
|
|
1185
|
-
* markNewMessageFlagForConversation
|
|
1186
|
-
*
|
|
1187
|
-
* sets a lightweight flag query for a conversation indicating a new message
|
|
1188
|
-
* arrived while the user may not be at the bottom. UI can reset it after
|
|
1189
|
-
* scrolling or showing a pill.
|
|
1190
|
-
*/
|
|
1191
|
-
function markNewMessageFlagForConversation(queryClient, conversationId) {
|
|
1192
|
-
queryClient.setQueryData([
|
|
1193
|
-
MESSAGES_QUERY_KEYS.all()[0],
|
|
1194
|
-
"conversation",
|
|
1195
|
-
conversationId,
|
|
1196
|
-
"new_message_flag"
|
|
1197
|
-
], true);
|
|
1198
|
-
}
|
|
1199
|
-
function clearNewMessageFlagForConversation(queryClient, conversationId) {
|
|
1200
|
-
queryClient.setQueryData([
|
|
1201
|
-
MESSAGES_QUERY_KEYS.all()[0],
|
|
1202
|
-
"conversation",
|
|
1203
|
-
conversationId,
|
|
1204
|
-
"new_message_flag"
|
|
1205
|
-
], false);
|
|
1206
|
-
}
|
|
1207
|
-
function applyMessageUpsertToCache({ queryClient, conversationId, message, replaceOptimisticId, clientMessageId }) {
|
|
1208
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1209
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1210
|
-
if (!oldData?.pages?.length) {
|
|
1211
|
-
if (!isBottomAnchorKey(key)) return oldData;
|
|
1212
|
-
return {
|
|
1213
|
-
pages: [[{ pagination: {
|
|
1214
|
-
current: 1,
|
|
1215
|
-
previous: null,
|
|
1216
|
-
next: null,
|
|
1217
|
-
per_page: 20,
|
|
1218
|
-
pages: 1,
|
|
1219
|
-
count: 1
|
|
1220
|
-
} }, { items: [message] }]],
|
|
1221
|
-
pageParams: [1]
|
|
1222
|
-
};
|
|
1223
|
-
}
|
|
1224
|
-
const normalizedClientMessageId = (() => {
|
|
1225
|
-
if (typeof clientMessageId === "string" && clientMessageId.length > 0) return clientMessageId;
|
|
1226
|
-
const fromMeta = (() => {
|
|
1227
|
-
const meta = message.metadata;
|
|
1228
|
-
if (meta && typeof meta === "object" && "client_message_id" in meta) {
|
|
1229
|
-
const id = meta.client_message_id;
|
|
1230
|
-
if (typeof id === "string" || typeof id === "number" && Number.isFinite(id)) return id;
|
|
1231
|
-
}
|
|
1232
|
-
})();
|
|
1233
|
-
if (typeof fromMeta === "string" || typeof fromMeta === "number" && Number.isFinite(fromMeta)) return String(fromMeta);
|
|
1234
|
-
})();
|
|
1235
|
-
const finalMessage = (() => {
|
|
1236
|
-
if (!normalizedClientMessageId) return message;
|
|
1237
|
-
const currentMeta = message.metadata;
|
|
1238
|
-
if (currentMeta != null && typeof currentMeta === "object" && (typeof currentMeta.client_message_id === "string" || typeof currentMeta.client_message_id === "number")) return message;
|
|
1239
|
-
return {
|
|
1240
|
-
...message,
|
|
1241
|
-
metadata: {
|
|
1242
|
-
...currentMeta && typeof currentMeta === "object" ? currentMeta : {},
|
|
1243
|
-
client_message_id: normalizedClientMessageId
|
|
1244
|
-
}
|
|
1245
|
-
};
|
|
1246
|
-
})();
|
|
1247
|
-
let replaced = false;
|
|
1248
|
-
const pages = oldData.pages.map((page) => {
|
|
1249
|
-
const [meta, payload] = page;
|
|
1250
|
-
const items = payload.items.slice();
|
|
1251
|
-
const idxById = items.findIndex((m) => m.id === (replaceOptimisticId ?? message.id));
|
|
1252
|
-
if (idxById !== -1) {
|
|
1253
|
-
items[idxById] = finalMessage;
|
|
1254
|
-
replaced = true;
|
|
1255
|
-
return [meta, { items }];
|
|
1256
|
-
}
|
|
1257
|
-
if (normalizedClientMessageId) {
|
|
1258
|
-
const idxByClientId = items.findIndex((m) => typeof m?.metadata === "object" && m?.metadata != null && ((id) => id != null && String(id) === normalizedClientMessageId)(m.metadata?.client_message_id));
|
|
1259
|
-
if (idxByClientId !== -1) {
|
|
1260
|
-
items[idxByClientId] = finalMessage;
|
|
1261
|
-
replaced = true;
|
|
1262
|
-
return [meta, { items }];
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
const idxExisting = items.findIndex((m) => m.id === message.id);
|
|
1266
|
-
if (idxExisting !== -1) {
|
|
1267
|
-
items[idxExisting] = finalMessage;
|
|
1268
|
-
replaced = true;
|
|
1269
|
-
return [meta, { items }];
|
|
1270
|
-
}
|
|
1271
|
-
return [meta, { items }];
|
|
1272
|
-
});
|
|
1273
|
-
if (!replaced) {
|
|
1274
|
-
if (!isBottomAnchorKey(key)) return oldData;
|
|
1275
|
-
const first = pages[0];
|
|
1276
|
-
const nextPages = pages.slice();
|
|
1277
|
-
const newFirstItems = [finalMessage, ...first?.[1]?.items ?? []];
|
|
1278
|
-
const newCount = (first?.[0]?.pagination?.count ?? 0) + 1;
|
|
1279
|
-
nextPages[0] = [{ pagination: {
|
|
1280
|
-
...first?.[0]?.pagination ?? {},
|
|
1281
|
-
count: newCount
|
|
1282
|
-
} }, { items: newFirstItems }];
|
|
1283
|
-
return {
|
|
1284
|
-
...oldData,
|
|
1285
|
-
pages: nextPages
|
|
1286
|
-
};
|
|
1287
|
-
}
|
|
1288
|
-
return {
|
|
1289
|
-
...oldData,
|
|
1290
|
-
pages
|
|
1291
|
-
};
|
|
1292
|
-
});
|
|
1293
|
-
});
|
|
1294
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1295
|
-
if (!old?.items) return old;
|
|
1296
|
-
return { items: old.items.filter((m) => m.id !== (replaceOptimisticId ?? -99999999)) };
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
function applyMessageDeleteFromCache({ queryClient, conversationId, messageId }) {
|
|
1300
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1301
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1302
|
-
if (!oldData) return oldData;
|
|
1303
|
-
const pages = oldData.pages.map((page) => {
|
|
1304
|
-
const [meta, payload] = page;
|
|
1305
|
-
const items = payload.items.filter((m) => m.id !== messageId);
|
|
1306
|
-
const newCount = Math.max(0, (meta?.pagination?.count ?? 0) - (payload.items.length - items.length));
|
|
1307
|
-
return [{ pagination: {
|
|
1308
|
-
...meta?.pagination ?? {},
|
|
1309
|
-
count: newCount
|
|
1310
|
-
} }, { items }];
|
|
1311
|
-
});
|
|
1312
|
-
return {
|
|
1313
|
-
...oldData,
|
|
1314
|
-
pages
|
|
1315
|
-
};
|
|
1316
|
-
});
|
|
1317
|
-
});
|
|
1318
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
1319
|
-
if (!old?.items) return old;
|
|
1320
|
-
return { items: old.items.filter((m) => m.id !== messageId) };
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
function applyReactionUpdateToCache({ queryClient, conversationId, messageId, reaction, reaction_stats }) {
|
|
1324
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1325
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1326
|
-
if (!oldData) return oldData;
|
|
1327
|
-
const pages = oldData.pages.map((page) => {
|
|
1328
|
-
const [meta, payload] = page;
|
|
1329
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1330
|
-
...m,
|
|
1331
|
-
reaction,
|
|
1332
|
-
reaction_stats
|
|
1333
|
-
} : m) }];
|
|
1334
|
-
});
|
|
1335
|
-
return {
|
|
1336
|
-
...oldData,
|
|
1337
|
-
pages
|
|
1338
|
-
};
|
|
1339
|
-
});
|
|
1340
|
-
});
|
|
1341
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1342
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1343
|
-
if (!oldData) return oldData;
|
|
1344
|
-
const pages = oldData.pages.map((page) => {
|
|
1345
|
-
const [meta, payload] = page;
|
|
1346
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1347
|
-
...m,
|
|
1348
|
-
reaction,
|
|
1349
|
-
reaction_stats
|
|
1350
|
-
} : m) }];
|
|
1351
|
-
});
|
|
1352
|
-
return {
|
|
1353
|
-
...oldData,
|
|
1354
|
-
pages
|
|
1355
|
-
};
|
|
1356
|
-
});
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
function applyReactionStatsToCache({ queryClient, conversationId, messageId, reaction_stats }) {
|
|
1360
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1361
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1362
|
-
if (!oldData) return;
|
|
1363
|
-
const pages = oldData.pages.map((page) => {
|
|
1364
|
-
const [meta, payload] = page;
|
|
1365
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1366
|
-
...m,
|
|
1367
|
-
reaction_stats
|
|
1368
|
-
} : m) }];
|
|
1369
|
-
});
|
|
1370
|
-
return {
|
|
1371
|
-
...oldData,
|
|
1372
|
-
pages
|
|
1373
|
-
};
|
|
1374
|
-
});
|
|
1375
|
-
});
|
|
1376
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1377
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1378
|
-
if (!oldData) return oldData;
|
|
1379
|
-
const pages = oldData.pages.map((page) => {
|
|
1380
|
-
const [meta, payload] = page;
|
|
1381
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1382
|
-
...m,
|
|
1383
|
-
reaction_stats
|
|
1384
|
-
} : m) }];
|
|
1385
|
-
});
|
|
1386
|
-
return {
|
|
1387
|
-
...oldData,
|
|
1388
|
-
pages
|
|
1389
|
-
};
|
|
1390
|
-
});
|
|
1391
|
-
});
|
|
1392
|
-
}
|
|
1393
|
-
function applyConversationMetaToCaches({ queryClient, conversation }) {
|
|
1394
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(conversation.id), (old) => {
|
|
1395
|
-
if (!old) return old;
|
|
1396
|
-
return {
|
|
1397
|
-
...old,
|
|
1398
|
-
...conversation
|
|
1399
|
-
};
|
|
1400
|
-
});
|
|
1401
|
-
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
1402
|
-
const key = q.queryKey;
|
|
1403
|
-
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
1404
|
-
} });
|
|
1405
|
-
for (const q of listQueries) queryClient.setQueryData(q.queryKey, (oldData) => {
|
|
1406
|
-
const data = oldData;
|
|
1407
|
-
if (!data?.pages) return oldData;
|
|
1408
|
-
return {
|
|
1409
|
-
...data,
|
|
1410
|
-
pages: data.pages.map((page) => [page[0], {
|
|
1411
|
-
...page[1],
|
|
1412
|
-
items: page[1].items.map((conv) => conv.id === conversation.id ? {
|
|
1413
|
-
...conv,
|
|
1414
|
-
...typeof conversation.unread === "boolean" ? { unread: conversation.unread } : {},
|
|
1415
|
-
...typeof conversation.unread_messages_count === "number" ? { unread_messages_count: conversation.unread_messages_count } : {}
|
|
1416
|
-
} : conv)
|
|
1417
|
-
}])
|
|
1418
|
-
};
|
|
1419
|
-
});
|
|
1420
|
-
}
|
|
1421
|
-
function applyConversationDestroyed({ queryClient, conversationId }) {
|
|
1422
|
-
queryClient.removeQueries({ queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId) });
|
|
1423
|
-
queryClient.setQueryData(MESSAGES_QUERY_KEYS.listConversations(), (oldData) => {
|
|
1424
|
-
const data = oldData;
|
|
1425
|
-
if (!data?.pages) return oldData;
|
|
1426
|
-
return {
|
|
1427
|
-
...data,
|
|
1428
|
-
pages: data.pages.map((page) => [page[0], {
|
|
1429
|
-
...page[1],
|
|
1430
|
-
items: page[1].items.filter((c) => c.id !== conversationId)
|
|
1431
|
-
}])
|
|
1432
|
-
};
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1435
|
-
function applyMessagePinnedFlag({ queryClient, conversationId, messageId, pinned, message }) {
|
|
1436
|
-
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
1437
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1438
|
-
if (!oldData) return oldData;
|
|
1439
|
-
const pages = oldData.pages.map((page) => {
|
|
1440
|
-
const [meta, payload] = page;
|
|
1441
|
-
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
1442
|
-
...m,
|
|
1443
|
-
pinned
|
|
1444
|
-
} : m) }];
|
|
1445
|
-
});
|
|
1446
|
-
return {
|
|
1447
|
-
...oldData,
|
|
1448
|
-
pages
|
|
1449
|
-
};
|
|
1450
|
-
});
|
|
1451
|
-
});
|
|
1452
|
-
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
1453
|
-
queryClient.setQueryData(key, (oldData) => {
|
|
1454
|
-
if (!oldData) return oldData;
|
|
1455
|
-
const pages = oldData.pages.map((page, idx) => {
|
|
1456
|
-
const [meta, payload] = page;
|
|
1457
|
-
if (idx !== 0) return page;
|
|
1458
|
-
const exists = payload.items.some((m) => m.id === messageId);
|
|
1459
|
-
if (pinned) {
|
|
1460
|
-
if (exists) return page;
|
|
1461
|
-
const msg = message ?? payload.items.find((m) => m.id === messageId);
|
|
1462
|
-
if (!msg) return page;
|
|
1463
|
-
return [meta, { items: [msg, ...payload.items] }];
|
|
1464
|
-
}
|
|
1465
|
-
return [meta, { items: payload.items.filter((m) => m.id !== messageId) }];
|
|
1466
|
-
});
|
|
1467
|
-
return {
|
|
1468
|
-
...oldData,
|
|
1469
|
-
pages
|
|
1470
|
-
};
|
|
1471
|
-
});
|
|
1472
|
-
});
|
|
1473
|
-
}
|
|
1474
|
-
//#endregion
|
|
1475
|
-
//#region ../../messaging/core/src/utils/reaction-utils.ts
|
|
1476
|
-
/**
|
|
1477
|
-
* Splits an emoji string into an array of grapheme clusters (perceived characters/emojis).
|
|
1478
|
-
* Uses `Intl.Segmenter` when available for correct multi-codepoint emoji handling.
|
|
1479
|
-
* Falls back to `[...string]` (code-point iteration) on browsers without `Intl.Segmenter`
|
|
1480
|
-
* (e.g. Firefox < 119), which may incorrectly split ZWJ sequences, skin-tone modifiers,
|
|
1481
|
-
* and flag emojis into multiple entries.
|
|
1482
|
-
* @param emojiString The string of emojis to split.
|
|
1483
|
-
* @returns An array of strings, where each string is a single emoji.
|
|
1484
|
-
*/
|
|
1485
|
-
function splitEmojis(emojiString) {
|
|
1486
|
-
if (!emojiString) return [];
|
|
1487
|
-
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
1488
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
1489
|
-
return Array.from(segmenter.segment(emojiString)).map((segment) => segment.segment);
|
|
1490
|
-
}
|
|
1491
|
-
return [...emojiString];
|
|
1492
|
-
}
|
|
1493
|
-
/**
|
|
1494
|
-
* Flattens reaction_stats to count individual emojis.
|
|
1495
|
-
* E.g., {"😀😂": 1, "👍": 2, "😀": 1} becomes {"😀": 2, "😂": 1, "👍": 2}
|
|
1496
|
-
* Handles multi-codepoint emojis correctly.
|
|
1497
|
-
* @param stats The raw reaction_stats object from the message.
|
|
1498
|
-
* @returns A record where keys are single emojis and values are their total counts.
|
|
1499
|
-
*/
|
|
1500
|
-
function flattenReactionStats(stats) {
|
|
1501
|
-
if (!stats) return {};
|
|
1502
|
-
const flattened = {};
|
|
1503
|
-
for (const emojiKey in stats) {
|
|
1504
|
-
const count = stats[emojiKey];
|
|
1505
|
-
if (typeof count !== "number" || count === 0) continue;
|
|
1506
|
-
const individualEmojis = splitEmojis(emojiKey);
|
|
1507
|
-
for (const emoji of individualEmojis) flattened[emoji] = (flattened[emoji] || 0) + count;
|
|
1508
|
-
}
|
|
1509
|
-
return flattened;
|
|
1510
|
-
}
|
|
1511
|
-
/**
|
|
1512
|
-
* Calculates the optimistic state for a message's reactions when a user's reaction changes.
|
|
1513
|
-
* @param currentState The current reaction parts of the message ({ reaction, reaction_stats }).
|
|
1514
|
-
* @param newUserReaction The user's new complete reaction string (e.g., "😀👍", "😂", or null to unreact).
|
|
1515
|
-
* @returns An object with the updated `reaction` and `reaction_stats`.
|
|
1516
|
-
*/
|
|
1517
|
-
function calculateOptimisticReactionUpdate(currentState, newUserReaction) {
|
|
1518
|
-
const previousUserReaction = currentState.reaction;
|
|
1519
|
-
const newStats = { ...currentState.reaction_stats || {} };
|
|
1520
|
-
const finalNewUserReaction = newUserReaction === "" ? null : newUserReaction;
|
|
1521
|
-
if (previousUserReaction === finalNewUserReaction) return {
|
|
1522
|
-
reaction: previousUserReaction,
|
|
1523
|
-
reaction_stats: newStats
|
|
1524
|
-
};
|
|
1525
|
-
if (previousUserReaction && typeof newStats[previousUserReaction] === "number") newStats[previousUserReaction] = Math.max(0, newStats[previousUserReaction] - 1);
|
|
1526
|
-
if (finalNewUserReaction) newStats[finalNewUserReaction] = (typeof newStats[finalNewUserReaction] === "number" ? newStats[finalNewUserReaction] : 0) + 1;
|
|
1527
|
-
return {
|
|
1528
|
-
reaction: finalNewUserReaction,
|
|
1529
|
-
reaction_stats: newStats
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
/**
|
|
1533
|
-
* Generates a unique, sorted string from an array of emoji characters.
|
|
1534
|
-
* Each string in the input array is assumed to be a single, correctly segmented emoji.
|
|
1535
|
-
* E.g., ["😂", "😀", "😂"] becomes "😀😂"
|
|
1536
|
-
* @param emojis An array of single emoji strings (grapheme clusters).
|
|
1537
|
-
* @returns A sorted string of unique emojis, or an empty string if no emojis are provided.
|
|
1538
|
-
*/
|
|
1539
|
-
function generateSortedEmojiStringFromArray(emojis) {
|
|
1540
|
-
if (!emojis || emojis.length === 0) return "";
|
|
1541
|
-
return [...new Set(emojis)].sort((a, b) => a.localeCompare(b, "en")).join("");
|
|
1542
|
-
}
|
|
1543
|
-
//#endregion
|
|
1544
|
-
//#region ../../messaging/core/src/utils/messaging.ts
|
|
1545
|
-
function mapConversationTypeToMessageType(conversationType) {
|
|
1546
|
-
switch (conversationType) {
|
|
1547
|
-
case "DirectConversation": return "DirectMessage";
|
|
1548
|
-
case "SmsConversation": return "SmsMessage";
|
|
1549
|
-
case "EmailConversation": return "EmailMessage";
|
|
1550
|
-
case "ChannelConversation":
|
|
1551
|
-
case "GroupConversation": return "DirectMessage";
|
|
1552
|
-
case "BotConversation": return "BotMessage";
|
|
1553
|
-
default: return "DirectMessage";
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
/**
|
|
1557
|
-
* Determines the category of a chat. This is slightly different from the backend's
|
|
1558
|
-
* category field.
|
|
1559
|
-
*
|
|
1560
|
-
* @param item - Conversation item to check
|
|
1561
|
-
* @returns the type of channel
|
|
1562
|
-
*/
|
|
1563
|
-
const getMessageType = (item) => {
|
|
1564
|
-
if (item.type === "GroupConversation" && item.name.toLowerCase().includes("other")) return {
|
|
1565
|
-
isChannel: false,
|
|
1566
|
-
type: "group-direct-message"
|
|
1567
|
-
};
|
|
1568
|
-
if (item.broadcasting) return {
|
|
1569
|
-
isChannel: true,
|
|
1570
|
-
type: "broadcast"
|
|
1571
|
-
};
|
|
1572
|
-
if (item.type === "GroupConversation") return {
|
|
1573
|
-
isChannel: true,
|
|
1574
|
-
type: "private-channel"
|
|
1575
|
-
};
|
|
1576
|
-
if (item.type === "DirectConversation") return {
|
|
1577
|
-
isChannel: false,
|
|
1578
|
-
type: "direct-message"
|
|
1579
|
-
};
|
|
1580
|
-
if (item.type === "EmailConversation") return {
|
|
1581
|
-
isChannel: false,
|
|
1582
|
-
type: "email"
|
|
1583
|
-
};
|
|
1584
|
-
if (item.type === "SmsConversation") return {
|
|
1585
|
-
isChannel: false,
|
|
1586
|
-
type: "sms"
|
|
1587
|
-
};
|
|
1588
|
-
if (item.type === "ChannelConversation") return {
|
|
1589
|
-
isChannel: true,
|
|
1590
|
-
type: "public-channel"
|
|
1591
|
-
};
|
|
1592
|
-
if (item.type === "BotConversation") return {
|
|
1593
|
-
isChannel: false,
|
|
1594
|
-
type: "bot"
|
|
1595
|
-
};
|
|
1596
|
-
if (item.type === "DownlineConversation") return {
|
|
1597
|
-
isChannel: true,
|
|
1598
|
-
type: "downline"
|
|
1599
|
-
};
|
|
1600
|
-
throw new Error("Invalid conversation type");
|
|
1601
|
-
};
|
|
1602
|
-
const getUserInitials = (conversation, user) => {
|
|
1603
|
-
if (user.phone === conversation.name) return "#";
|
|
1604
|
-
if (user.email === conversation.name) return "@";
|
|
1605
|
-
return [user.first_name?.charAt(0), user.last_name?.charAt(0)].filter(Boolean).join("") || "?";
|
|
1606
|
-
};
|
|
1607
|
-
const getFileTypeFromMimetype = (mimetype) => {
|
|
1608
|
-
if (mimetype.startsWith("image/")) return "image";
|
|
1609
|
-
if (mimetype.startsWith("video/")) return "video";
|
|
1610
|
-
if (mimetype === "application/pdf") return "pdf";
|
|
1611
|
-
if (mimetype.includes("spreadsheet") || mimetype.includes("excel") || mimetype.includes("sheet") || mimetype === "text/csv") return "sheets";
|
|
1612
|
-
if (mimetype.includes("powerpoint") || mimetype.includes("presentation")) return "powerpoint";
|
|
1613
|
-
if (mimetype.includes("document") || mimetype.includes("word") || mimetype.includes("rtf")) return "docx";
|
|
1614
|
-
if (mimetype.startsWith("text/")) return "txt";
|
|
1615
|
-
if (mimetype.includes("zip") || mimetype.includes("compressed") || mimetype.includes("archive") || mimetype.includes("tar") || mimetype.includes("gzip")) return "compressed";
|
|
1616
|
-
if (mimetype.startsWith("audio/")) return "audio";
|
|
1617
|
-
return "unknown";
|
|
1618
|
-
};
|
|
1619
|
-
//#endregion
|
|
1620
|
-
//#region ../../messaging/core/src/utils/string-utils.ts
|
|
1621
|
-
/**
|
|
1622
|
-
* Extracts the leading emoji from a string.
|
|
1623
|
-
* @param text The string to process.
|
|
1624
|
-
* @returns An object containing the emoji and the remaining text.
|
|
1625
|
-
*/
|
|
1626
|
-
function extractEmoji$1(text) {
|
|
1627
|
-
const match = text.match(/^\p{Emoji_Presentation}/u);
|
|
1628
|
-
if (match) {
|
|
1629
|
-
const emoji = match[0];
|
|
1630
|
-
return {
|
|
1631
|
-
emoji,
|
|
1632
|
-
text: text.substring(emoji.length).trimStart()
|
|
1633
|
-
};
|
|
1634
|
-
}
|
|
1635
|
-
return {
|
|
1636
|
-
emoji: null,
|
|
1637
|
-
text
|
|
1638
|
-
};
|
|
1639
|
-
}
|
|
1640
|
-
/**
|
|
1641
|
-
* Formats a message channel name by replacing spaces and underscores with hyphens,
|
|
1642
|
-
* converting to lowercase, and removing any non-alphanumeric characters except hyphens.
|
|
1643
|
-
* @param name - The original channel name
|
|
1644
|
-
* @return The formatted channel name
|
|
1645
|
-
*/
|
|
1646
|
-
function formatMessageChannelName$1(name) {
|
|
1647
|
-
return name.toLowerCase().replace(/[ _]/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
1648
|
-
}
|
|
1649
|
-
const MAX_GROUP_NAME_LENGTH$1 = 30;
|
|
1650
|
-
function formatGroupDisplayName$1(names, maxLength = MAX_GROUP_NAME_LENGTH$1) {
|
|
1651
|
-
const fullDisplayName = names.join(", ");
|
|
1652
|
-
if (fullDisplayName.length <= maxLength) return { displayName: fullDisplayName };
|
|
1653
|
-
for (let i = 0; i < names.length; i++) {
|
|
1654
|
-
const currentNames = names.slice(0, i + 1);
|
|
1655
|
-
const remainingCount = names.length - currentNames.length;
|
|
1656
|
-
if (remainingCount > 0) {
|
|
1657
|
-
const suffix = `+ ${remainingCount} other${remainingCount > 1 ? "s" : ""}`;
|
|
1658
|
-
if (`${currentNames.join(", ")} ${suffix}`.length > maxLength && i > 0) {
|
|
1659
|
-
const namesToShow = names.slice(0, i);
|
|
1660
|
-
const othersCount = names.length - i;
|
|
1661
|
-
const othersSuffix = `+${othersCount} other${othersCount > 1 ? "s" : ""}`;
|
|
1662
|
-
const truncatedDisplayName = namesToShow.join(", ");
|
|
1663
|
-
if (othersCount === 1) {
|
|
1664
|
-
const nameWithOneMore = names.slice(0, i + 1).join(", ");
|
|
1665
|
-
if (nameWithOneMore.length < truncatedDisplayName.length) return { displayName: nameWithOneMore };
|
|
1666
|
-
}
|
|
1667
|
-
return {
|
|
1668
|
-
displayName: truncatedDisplayName,
|
|
1669
|
-
remainder: othersSuffix
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
}
|
|
1674
|
-
const firstName = names[0] || "";
|
|
1675
|
-
const othersCount = names.length - 1;
|
|
1676
|
-
if (othersCount > 0) return {
|
|
1677
|
-
displayName: firstName,
|
|
1678
|
-
remainder: `+ ${othersCount} other${othersCount > 1 ? "s" : ""}`
|
|
1679
|
-
};
|
|
1680
|
-
return { displayName: firstName };
|
|
1681
|
-
}
|
|
1682
|
-
//#endregion
|
|
1683
|
-
//#region ../../messaging/core/src/utils/file-kinds.ts
|
|
1684
|
-
const ServerFileKindEnum$1 = z.enum([
|
|
1685
|
-
"image",
|
|
1686
|
-
"video",
|
|
1687
|
-
"pdf",
|
|
1688
|
-
"sheets",
|
|
1689
|
-
"powerpoint",
|
|
1690
|
-
"docx"
|
|
1691
|
-
]);
|
|
1692
|
-
const fallbackFileType$1 = "docx";
|
|
1693
|
-
//#endregion
|
|
1694
958
|
//#region ../../messaging/api-client/src/client.ts
|
|
1695
959
|
/**
|
|
1696
960
|
* Messaging-specific API error, extends the shared ApiError from api-client-core.
|
|
@@ -1980,7 +1244,7 @@ const WithContactRecipientSchema = z.strictObject({
|
|
|
1980
1244
|
});
|
|
1981
1245
|
createPaginatedSchema(RecipientSchema);
|
|
1982
1246
|
createPaginatedSchema(ReducedRecipientSchema);
|
|
1983
|
-
z.strictObject({ meta: z.strictObject({ count: z.number() }) });
|
|
1247
|
+
const RecipientSearchCountSchema = z.strictObject({ meta: z.strictObject({ count: z.number() }) });
|
|
1984
1248
|
//#endregion
|
|
1985
1249
|
//#region ../../messaging/api-client/src/schemas/drafts-scheduled.schema.ts
|
|
1986
1250
|
const DraftMessageSchema = z.strictObject({
|
|
@@ -2126,7 +1390,7 @@ z.strictObject({ success: z.boolean() }).or(z.strictObject({
|
|
|
2126
1390
|
*
|
|
2127
1391
|
* These schemas normalize the slightly different index vs show/update shapes.
|
|
2128
1392
|
*/
|
|
2129
|
-
const ReactionStatsSchema = z.record(z.string(), z.number());
|
|
1393
|
+
const ReactionStatsSchema$1 = z.record(z.string(), z.number());
|
|
2130
1394
|
const MessageCoreSchema = z.strictObject({
|
|
2131
1395
|
id: z.number(),
|
|
2132
1396
|
body: z.string(),
|
|
@@ -2138,7 +1402,7 @@ const MessageCoreSchema = z.strictObject({
|
|
|
2138
1402
|
conversation_id: z.number(),
|
|
2139
1403
|
status: z.string(),
|
|
2140
1404
|
attachments: z.array(AttachmentSchema),
|
|
2141
|
-
reaction_stats: ReactionStatsSchema.nullable(),
|
|
1405
|
+
reaction_stats: ReactionStatsSchema$1.nullable(),
|
|
2142
1406
|
reaction: z.string().nullable().optional(),
|
|
2143
1407
|
metadata: MessageMetadataSchema.optional(),
|
|
2144
1408
|
emoji_only_count: z.number().optional(),
|
|
@@ -2152,9 +1416,9 @@ const MessageCoreSchema = z.strictObject({
|
|
|
2152
1416
|
seen_by_count: z.unknown()
|
|
2153
1417
|
});
|
|
2154
1418
|
const MessageIndexItemSchema = MessageCoreSchema;
|
|
2155
|
-
const MessageItemSchema = MessageCoreSchema;
|
|
1419
|
+
const MessageItemSchema$1 = MessageCoreSchema;
|
|
2156
1420
|
const MessageIndexResponseSchema = createPaginatedSchema(MessageIndexItemSchema);
|
|
2157
|
-
const MessageListResponseSchema = createPaginatedSchema(MessageItemSchema);
|
|
1421
|
+
const MessageListResponseSchema = createPaginatedSchema(MessageItemSchema$1);
|
|
2158
1422
|
const MessagePinResponseSchema = z.strictObject({
|
|
2159
1423
|
message: z.string(),
|
|
2160
1424
|
pinned: z.boolean()
|
|
@@ -2207,328 +1471,1117 @@ async function listMessages(config, conversation_id, input) {
|
|
|
2207
1471
|
endpoint: `/v1/messaging/conversations/${conversation_id}/messages`,
|
|
2208
1472
|
method: "GET",
|
|
2209
1473
|
input,
|
|
2210
|
-
outputSchema: MessageIndexResponseSchema
|
|
1474
|
+
outputSchema: MessageIndexResponseSchema
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
const _createMessageInputSchema = z.strictObject({
|
|
1478
|
+
message: z.strictObject({
|
|
1479
|
+
body: z.string().optional(),
|
|
1480
|
+
theme: z.string().optional(),
|
|
1481
|
+
subject: z.string().optional(),
|
|
1482
|
+
type: z.string().optional(),
|
|
1483
|
+
attachments_attributes: z.array(AttachmentInputSchema).optional()
|
|
1484
|
+
}),
|
|
1485
|
+
client_message_id: z.union([z.string(), z.number()]).optional()
|
|
1486
|
+
});
|
|
1487
|
+
const createMessageInputSchema = _createMessageInputSchema;
|
|
1488
|
+
async function createMessage(config, conversation_id, input) {
|
|
1489
|
+
return fetchMessaging(config, {
|
|
1490
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages`,
|
|
1491
|
+
method: "POST",
|
|
1492
|
+
input,
|
|
1493
|
+
inputSchema: createMessageInputSchema,
|
|
1494
|
+
outputSchema: MessageItemSchema$1
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* POST /api/v1/messaging/conversations/:conversation_id/messages/reply
|
|
1499
|
+
* Reply to an existing message by id; thread is captured in replied_to_id.
|
|
1500
|
+
*/
|
|
1501
|
+
async function replyToMessage(config, conversation_id, message_id, input) {
|
|
1502
|
+
return fetchMessaging(config, {
|
|
1503
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/reply`,
|
|
1504
|
+
method: "POST",
|
|
1505
|
+
input: {
|
|
1506
|
+
...input,
|
|
1507
|
+
message_id
|
|
1508
|
+
},
|
|
1509
|
+
inputSchema: _createMessageInputSchema.extend({ message_id: z.number() }),
|
|
1510
|
+
outputSchema: MessageItemSchema$1
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
z.strictObject({ message: z.strictObject({
|
|
1514
|
+
body: z.string().optional(),
|
|
1515
|
+
theme: z.string().optional()
|
|
1516
|
+
}) });
|
|
1517
|
+
/**
|
|
1518
|
+
* POST /api/v1/messaging/conversations/:conversation_id/messages/:message_id/react.json
|
|
1519
|
+
* React to a message with an emoji.
|
|
1520
|
+
*/
|
|
1521
|
+
async function reactToMessage(config, conversation_id, input) {
|
|
1522
|
+
const { message_id, reaction } = input;
|
|
1523
|
+
return fetchMessaging(config, {
|
|
1524
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${message_id}/react.json?reaction=${encodeURIComponent(reaction)}`,
|
|
1525
|
+
method: "POST",
|
|
1526
|
+
outputSchema: z.string()
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* DELETE /api/v1/messaging/conversations/:conversation_id/messages/:message_id/unreact.json
|
|
1531
|
+
* Remove the current recipient's reaction.
|
|
1532
|
+
*/
|
|
1533
|
+
async function unreactToMessage(config, conversation_id, input) {
|
|
1534
|
+
const { message_id } = input;
|
|
1535
|
+
return fetchMessaging(config, {
|
|
1536
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${message_id}/unreact.json`,
|
|
1537
|
+
method: "DELETE",
|
|
1538
|
+
outputSchema: z.string()
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* GET /api/v1/messaging/conversations/:conversation_id/messages/pinned
|
|
1543
|
+
* List pinned messages in the conversation.
|
|
1544
|
+
*/
|
|
1545
|
+
async function listPinnedMessages(config, conversation_id, input) {
|
|
1546
|
+
return fetchMessaging(config, {
|
|
1547
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/pinned`,
|
|
1548
|
+
method: "GET",
|
|
1549
|
+
input,
|
|
1550
|
+
outputSchema: MessageListResponseSchema
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
/**
|
|
1554
|
+
* PUT /api/v1/messaging/conversations/:conversation_id/messages/:id/pin
|
|
1555
|
+
* Pin a message (current recipient).
|
|
1556
|
+
*/
|
|
1557
|
+
async function pinMessage(config, conversation_id, id) {
|
|
1558
|
+
return fetchMessaging(config, {
|
|
1559
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${id}/pin`,
|
|
1560
|
+
method: "PUT",
|
|
1561
|
+
outputSchema: MessagePinResponseSchema
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* PUT /api/v1/messaging/conversations/:conversation_id/messages/:id/unpin
|
|
1566
|
+
* Unpin a message (current recipient).
|
|
1567
|
+
*/
|
|
1568
|
+
async function unpinMessage(config, conversation_id, id) {
|
|
1569
|
+
return fetchMessaging(config, {
|
|
1570
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/messages/${id}/unpin`,
|
|
1571
|
+
method: "PUT",
|
|
1572
|
+
outputSchema: MessagePinResponseSchema
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
//#endregion
|
|
1576
|
+
//#region ../../messaging/api-client/src/api/conversations.api.ts
|
|
1577
|
+
/**
|
|
1578
|
+
* Conversations API (v1 messaging)
|
|
1579
|
+
*
|
|
1580
|
+
* Thin, fully-typed wrappers around the Rails messaging controllers.
|
|
1581
|
+
* All functions validate inputs/outputs with zod and use `fetchMessaging`.
|
|
1582
|
+
*/
|
|
1583
|
+
/**
|
|
1584
|
+
* GET /api/v1/messaging/announcement_channel
|
|
1585
|
+
*/
|
|
1586
|
+
async function getAnnouncementChannel(config) {
|
|
1587
|
+
return fetchMessaging(config, {
|
|
1588
|
+
endpoint: "/v1/messaging/announcement_channel",
|
|
1589
|
+
method: "GET",
|
|
1590
|
+
outputSchema: ConversationResponseSchema
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
async function listConversations(config, input) {
|
|
1594
|
+
return fetchMessaging(config, {
|
|
1595
|
+
endpoint: "/v1/messaging/conversations",
|
|
1596
|
+
method: "GET",
|
|
1597
|
+
input,
|
|
1598
|
+
outputSchema: ConversationListResponseSchema
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* GET /api/v1/messaging/conversations/search
|
|
1603
|
+
*/
|
|
1604
|
+
async function searchConversations(config, input) {
|
|
1605
|
+
return fetchMessaging(config, {
|
|
1606
|
+
endpoint: "/v1/messaging/conversations/search",
|
|
1607
|
+
method: "GET",
|
|
1608
|
+
input,
|
|
1609
|
+
outputSchema: NominalConversationsResponseSchema
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* GET /api/v1/messaging/conversations/find
|
|
1614
|
+
*/
|
|
1615
|
+
async function findConversation(config, input) {
|
|
1616
|
+
return fetchMessaging(config, {
|
|
1617
|
+
endpoint: "/v1/messaging/conversations/find",
|
|
1618
|
+
method: "GET",
|
|
1619
|
+
input,
|
|
1620
|
+
outputSchema: ConversationResponseSchema
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* GET /api/v1/messaging/conversations/:id
|
|
1625
|
+
*/
|
|
1626
|
+
async function getConversation(config, id) {
|
|
1627
|
+
return fetchMessaging(config, {
|
|
1628
|
+
endpoint: `/v1/messaging/conversations/${id}`,
|
|
1629
|
+
method: "GET",
|
|
1630
|
+
outputSchema: ConversationResponseSchema
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
const conversationCreateInputSchema = z.strictObject({
|
|
1634
|
+
conversation: z.strictObject({
|
|
1635
|
+
kind: z.string().optional(),
|
|
1636
|
+
type: z.string().optional(),
|
|
1637
|
+
name: z.string().optional(),
|
|
1638
|
+
description: z.string().nullable().optional(),
|
|
1639
|
+
broadcasting: z.boolean().optional(),
|
|
1640
|
+
filter: z.record(z.string(), z.unknown()).optional(),
|
|
1641
|
+
auto_add_recipients: z.boolean().optional()
|
|
1642
|
+
}),
|
|
1643
|
+
recipients: z.array(z.strictObject({
|
|
1644
|
+
id: z.number().optional(),
|
|
1645
|
+
uid: z.number().optional(),
|
|
1646
|
+
email: z.string().email().optional(),
|
|
1647
|
+
phone: z.string().optional(),
|
|
1648
|
+
first_name: z.string().optional(),
|
|
1649
|
+
last_name: z.string().optional(),
|
|
1650
|
+
image_url: z.string().url().optional()
|
|
1651
|
+
})).optional()
|
|
1652
|
+
});
|
|
1653
|
+
async function createConversation(config, input) {
|
|
1654
|
+
return fetchMessaging(config, {
|
|
1655
|
+
endpoint: "/v1/messaging/conversations",
|
|
1656
|
+
method: "POST",
|
|
1657
|
+
input,
|
|
1658
|
+
inputSchema: conversationCreateInputSchema,
|
|
1659
|
+
outputSchema: ConversationResponseSchema
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
z.strictObject({ conversation: z.strictObject({
|
|
1663
|
+
name: z.string().optional(),
|
|
1664
|
+
description: z.string().nullable().optional(),
|
|
1665
|
+
additional_recipient_params: z.strictObject({
|
|
1666
|
+
receivable_id: z.number(),
|
|
1667
|
+
receivable_type: z.string()
|
|
1668
|
+
}).optional(),
|
|
1669
|
+
auto_add_recipients: z.boolean().optional()
|
|
1670
|
+
}) });
|
|
1671
|
+
//#endregion
|
|
1672
|
+
//#region ../../messaging/api-client/src/api/recipients.api.ts
|
|
1673
|
+
/**
|
|
1674
|
+
* POST /api/v1/messaging/recipients/search
|
|
1675
|
+
*/
|
|
1676
|
+
async function searchRecipientsCount(config, input) {
|
|
1677
|
+
return fetchMessaging(config, {
|
|
1678
|
+
endpoint: "/v1/messaging/recipients/search",
|
|
1679
|
+
method: "POST",
|
|
1680
|
+
input,
|
|
1681
|
+
outputSchema: RecipientSearchCountSchema
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* GET /api/v1/messaging/conversations/connected_recipients
|
|
1686
|
+
*/
|
|
1687
|
+
async function listConnectedRecipients(config, input) {
|
|
1688
|
+
return fetchMessaging(config, {
|
|
1689
|
+
endpoint: "/v1/messaging/conversations/connected_recipients",
|
|
1690
|
+
method: "GET",
|
|
1691
|
+
input,
|
|
1692
|
+
outputSchema: createPaginatedSchema(input?.kind && [
|
|
1693
|
+
"external",
|
|
1694
|
+
"sms",
|
|
1695
|
+
"email"
|
|
1696
|
+
].includes(input.kind) ? WithContactRecipientSchema : RecipientSchema)
|
|
1697
|
+
});
|
|
1698
|
+
}
|
|
1699
|
+
//#endregion
|
|
1700
|
+
//#region ../../messaging/api-client/src/api/drafts-scheduled.api.ts
|
|
1701
|
+
const saveDraftInputSchema = z.strictObject({ draft_message: z.strictObject({
|
|
1702
|
+
body: z.string().nullable().optional(),
|
|
1703
|
+
replied_to_id: z.number().nullable().optional(),
|
|
1704
|
+
attachments_attributes: z.array(z.strictObject({
|
|
1705
|
+
url: z.string().url(),
|
|
1706
|
+
filename: z.string(),
|
|
1707
|
+
kind: z.string(),
|
|
1708
|
+
content_type: z.string().optional(),
|
|
1709
|
+
content_size: z.number().optional(),
|
|
1710
|
+
metadata: AttachmentInputMetadataSchema,
|
|
1711
|
+
id: z.number().optional(),
|
|
1712
|
+
_destroy: z.boolean().optional()
|
|
1713
|
+
})).optional()
|
|
1714
|
+
}) });
|
|
1715
|
+
async function saveDraft(config, conversation_id, input) {
|
|
1716
|
+
return fetchMessaging(config, {
|
|
1717
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/save_draft`,
|
|
1718
|
+
method: "PUT",
|
|
1719
|
+
input,
|
|
1720
|
+
inputSchema: saveDraftInputSchema,
|
|
1721
|
+
outputSchema: DraftMessageSchema.or(z.strictObject({}))
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
async function destroyDraft(config, conversation_id) {
|
|
1725
|
+
return fetchMessaging(config, {
|
|
1726
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/destroy_draft`,
|
|
1727
|
+
method: "DELETE",
|
|
1728
|
+
outputSchema: z.unknown()
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Scheduled Messages: per conversation
|
|
1733
|
+
*/
|
|
1734
|
+
async function listScheduledMessages(config, conversation_id, input) {
|
|
1735
|
+
return fetchMessaging(config, {
|
|
1736
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
1737
|
+
method: "GET",
|
|
1738
|
+
input,
|
|
1739
|
+
outputSchema: MessageListResponseSchema
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1742
|
+
const scheduledMessageInputSchema = z.strictObject({ scheduled_message: z.strictObject({
|
|
1743
|
+
body: z.string().optional(),
|
|
1744
|
+
theme: z.string().optional(),
|
|
1745
|
+
scheduled_at: z.string().datetime({ offset: true }),
|
|
1746
|
+
attachments_attributes: z.array(AttachmentInputSchema).optional(),
|
|
1747
|
+
type: z.string().optional()
|
|
1748
|
+
}) });
|
|
1749
|
+
async function createScheduledMessage(config, conversation_id, input) {
|
|
1750
|
+
return fetchMessaging(config, {
|
|
1751
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
1752
|
+
method: "POST",
|
|
1753
|
+
input,
|
|
1754
|
+
inputSchema: scheduledMessageInputSchema,
|
|
1755
|
+
outputSchema: MessageItemSchema$1
|
|
2211
1756
|
});
|
|
2212
1757
|
}
|
|
2213
|
-
|
|
2214
|
-
message: z.strictObject({
|
|
2215
|
-
body: z.string().optional(),
|
|
2216
|
-
theme: z.string().optional(),
|
|
2217
|
-
subject: z.string().optional(),
|
|
2218
|
-
type: z.string().optional(),
|
|
2219
|
-
attachments_attributes: z.array(AttachmentInputSchema).optional()
|
|
2220
|
-
}),
|
|
2221
|
-
client_message_id: z.union([z.string(), z.number()]).optional()
|
|
2222
|
-
});
|
|
2223
|
-
const createMessageInputSchema = _createMessageInputSchema;
|
|
2224
|
-
async function createMessage(config, conversation_id, input) {
|
|
1758
|
+
async function updateScheduledMessage(config, conversation_id, id, input) {
|
|
2225
1759
|
return fetchMessaging(config, {
|
|
2226
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/
|
|
2227
|
-
method: "
|
|
1760
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
1761
|
+
method: "PUT",
|
|
2228
1762
|
input,
|
|
2229
|
-
inputSchema:
|
|
2230
|
-
outputSchema: MessageItemSchema
|
|
1763
|
+
inputSchema: scheduledMessageInputSchema,
|
|
1764
|
+
outputSchema: MessageItemSchema$1
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
async function destroyScheduledMessage(config, conversation_id, id) {
|
|
1768
|
+
return fetchMessaging(config, {
|
|
1769
|
+
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
1770
|
+
method: "DELETE",
|
|
1771
|
+
outputSchema: z.strictObject({
|
|
1772
|
+
success: z.boolean(),
|
|
1773
|
+
message: z.string()
|
|
1774
|
+
})
|
|
2231
1775
|
});
|
|
2232
1776
|
}
|
|
2233
1777
|
/**
|
|
2234
|
-
*
|
|
2235
|
-
* Reply to an existing message by id; thread is captured in replied_to_id.
|
|
1778
|
+
* My Scheduled Messages
|
|
2236
1779
|
*/
|
|
2237
|
-
async function
|
|
1780
|
+
async function listMyScheduledMessages(config, input) {
|
|
2238
1781
|
return fetchMessaging(config, {
|
|
2239
|
-
endpoint: `/v1/messaging/
|
|
2240
|
-
method: "
|
|
2241
|
-
input
|
|
2242
|
-
|
|
2243
|
-
message_id
|
|
2244
|
-
},
|
|
2245
|
-
inputSchema: _createMessageInputSchema.extend({ message_id: z.number() }),
|
|
2246
|
-
outputSchema: MessageItemSchema
|
|
1782
|
+
endpoint: `/v1/messaging/my_scheduled_messages`,
|
|
1783
|
+
method: "GET",
|
|
1784
|
+
input,
|
|
1785
|
+
outputSchema: DraftMessageListResponseSchema
|
|
2247
1786
|
});
|
|
2248
1787
|
}
|
|
2249
|
-
z.strictObject({
|
|
2250
|
-
|
|
2251
|
-
|
|
1788
|
+
z.strictObject({ bot: z.strictObject({
|
|
1789
|
+
title: z.string(),
|
|
1790
|
+
description: z.string().nullable().optional(),
|
|
1791
|
+
external_uri: z.string().url().nullable().optional(),
|
|
1792
|
+
image_url: z.string().url().nullable().optional(),
|
|
1793
|
+
type: z.string().optional(),
|
|
1794
|
+
avatar_background_color: z.string().nullable().optional(),
|
|
1795
|
+
avatar_emoji: z.string().nullable().optional(),
|
|
1796
|
+
webhook_external_uri: z.string().url().nullable().optional(),
|
|
1797
|
+
webhook_auth_token: z.string().nullable().optional(),
|
|
1798
|
+
webhook_http_method: z.string().nullable().optional(),
|
|
1799
|
+
conversation_ids: z.array(z.number()).optional()
|
|
2252
1800
|
}) });
|
|
1801
|
+
//#endregion
|
|
1802
|
+
//#region src/messaging/create-messaging-api.ts
|
|
2253
1803
|
/**
|
|
2254
|
-
*
|
|
2255
|
-
*
|
|
1804
|
+
* Creates a {@link MessagingApi} adapter backed by the messaging API client.
|
|
1805
|
+
* This is the portal's adapter — it wires the concrete client to the
|
|
1806
|
+
* interface that messaging-core hooks consume via context.
|
|
2256
1807
|
*/
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
1808
|
+
function createMessagingApi(config) {
|
|
1809
|
+
return {
|
|
1810
|
+
listConversations: (input) => listConversations(config, input),
|
|
1811
|
+
getConversation: (id) => getConversation(config, id),
|
|
1812
|
+
findConversation: (input) => findConversation(config, input),
|
|
1813
|
+
createConversation: (input) => createConversation(config, input),
|
|
1814
|
+
getAnnouncementChannel: () => getAnnouncementChannel(config),
|
|
1815
|
+
listMessages: (cid, input) => listMessages(config, cid, input),
|
|
1816
|
+
createMessage: (cid, input) => createMessage(config, cid, input),
|
|
1817
|
+
replyToMessage: (cid, mid, input) => replyToMessage(config, cid, mid, input),
|
|
1818
|
+
reactToMessage: (cid, input) => reactToMessage(config, cid, input),
|
|
1819
|
+
unreactToMessage: (cid, input) => unreactToMessage(config, cid, input),
|
|
1820
|
+
pinMessage: (cid, mid) => pinMessage(config, cid, mid),
|
|
1821
|
+
unpinMessage: (cid, mid) => unpinMessage(config, cid, mid),
|
|
1822
|
+
listPinnedMessages: (cid, input) => listPinnedMessages(config, cid, input),
|
|
1823
|
+
listScheduledMessages: (cid, input) => listScheduledMessages(config, cid, input),
|
|
1824
|
+
createScheduledMessage: (cid, input) => createScheduledMessage(config, cid, input),
|
|
1825
|
+
updateScheduledMessage: (cid, id, input) => updateScheduledMessage(config, cid, id, input),
|
|
1826
|
+
destroyScheduledMessage: (cid, id) => destroyScheduledMessage(config, cid, id),
|
|
1827
|
+
listMyScheduledMessages: (input) => listMyScheduledMessages(config, input),
|
|
1828
|
+
saveDraft: (cid, input) => saveDraft(config, cid, input),
|
|
1829
|
+
destroyDraft: (cid) => destroyDraft(config, cid),
|
|
1830
|
+
searchRecipientsCount: (input) => searchRecipientsCount(config, input),
|
|
1831
|
+
listConnectedRecipients: (input) => listConnectedRecipients(config, input)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
//#endregion
|
|
1835
|
+
//#region src/messaging/use-messaging-config.ts
|
|
1836
|
+
/**
|
|
1837
|
+
* Hook that derives a MessagingApi adapter from the portal SDK's FluidProvider context.
|
|
1838
|
+
*
|
|
1839
|
+
* Maps FluidSDKConfig fields to a MessagingApiConfig, then wraps the
|
|
1840
|
+
* concrete API client functions into a MessagingApi interface instance
|
|
1841
|
+
* that messaging-core hooks consume via context.
|
|
1842
|
+
*/
|
|
1843
|
+
function deriveWebsocketUrl(baseUrl) {
|
|
1844
|
+
return `${baseUrl.replace(/\/+$/, "").replace(/\/api$/, "")}/cable`;
|
|
1845
|
+
}
|
|
1846
|
+
function useMessagingConfig() {
|
|
1847
|
+
const { config } = useFluidContext();
|
|
1848
|
+
const auth = useFluidAuthContext();
|
|
1849
|
+
const getHeaders = useCallback(async () => {
|
|
1850
|
+
const headers = {
|
|
1851
|
+
"Content-Type": "application/json",
|
|
1852
|
+
...config.defaultHeaders
|
|
1853
|
+
};
|
|
1854
|
+
if (config.getAuthToken) {
|
|
1855
|
+
const token = await config.getAuthToken();
|
|
1856
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
1857
|
+
}
|
|
1858
|
+
return headers;
|
|
1859
|
+
}, [config]);
|
|
1860
|
+
const apiBaseUrl = useMemo(() => {
|
|
1861
|
+
const base = config.baseUrl.replace(/\/+$/, "");
|
|
1862
|
+
return base.endsWith("/api") ? base : `${base}/api`;
|
|
1863
|
+
}, [config.baseUrl]);
|
|
1864
|
+
const apiConfig = useMemo(() => ({
|
|
1865
|
+
baseUrl: apiBaseUrl,
|
|
1866
|
+
getHeaders,
|
|
1867
|
+
...config.onAuthError != null && { onAuthError: config.onAuthError }
|
|
1868
|
+
}), [
|
|
1869
|
+
apiBaseUrl,
|
|
1870
|
+
config.onAuthError,
|
|
1871
|
+
getHeaders
|
|
1872
|
+
]);
|
|
1873
|
+
return {
|
|
1874
|
+
apiConfig,
|
|
1875
|
+
messagingApi: useMemo(() => createMessagingApi(apiConfig), [apiConfig]),
|
|
1876
|
+
websocketUrl: useMemo(() => config.websocketUrl ?? deriveWebsocketUrl(config.baseUrl), [config.websocketUrl, config.baseUrl]),
|
|
1877
|
+
token: auth.token
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
//#endregion
|
|
1881
|
+
//#region ../../messaging/core/src/query-keys.ts
|
|
1882
|
+
/**
|
|
1883
|
+
* Query key factory for all messaging-related TanStack Query queries.
|
|
1884
|
+
* Ensures consistent key shapes across hooks and cache helpers.
|
|
1885
|
+
*/
|
|
1886
|
+
const MESSAGES_QUERY_KEYS = {
|
|
1887
|
+
all: () => ["messages"],
|
|
1888
|
+
conversationMetadata: (conversationId) => [
|
|
1889
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1890
|
+
"conversation",
|
|
1891
|
+
conversationId
|
|
1892
|
+
],
|
|
1893
|
+
newMessageFlag: (conversationId) => [
|
|
1894
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1895
|
+
"conversation",
|
|
1896
|
+
conversationId,
|
|
1897
|
+
"new_message_flag"
|
|
1898
|
+
],
|
|
1899
|
+
listConversations: () => [...MESSAGES_QUERY_KEYS.all(), "conversations"],
|
|
1900
|
+
listMessages: (conversationId, messageId, startAtISO) => [
|
|
1901
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1902
|
+
"conversation",
|
|
1903
|
+
conversationId,
|
|
1904
|
+
"messages",
|
|
1905
|
+
messageId ? { startFromMessage: messageId } : startAtISO ? { startFromDate: startAtISO } : { startFromBottom: true }
|
|
1906
|
+
],
|
|
1907
|
+
listPinnedMessages: (conversationId) => [
|
|
1908
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1909
|
+
"conversation",
|
|
1910
|
+
conversationId,
|
|
1911
|
+
"pinned"
|
|
1912
|
+
],
|
|
1913
|
+
outbox: (conversationId) => [
|
|
1914
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1915
|
+
"conversation",
|
|
1916
|
+
conversationId,
|
|
1917
|
+
"outbox"
|
|
1918
|
+
],
|
|
1919
|
+
listScheduledMessages: (conversationId) => [
|
|
1920
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1921
|
+
"conversation",
|
|
1922
|
+
conversationId,
|
|
1923
|
+
"scheduled"
|
|
1924
|
+
],
|
|
1925
|
+
listMyScheduledMessages: () => [
|
|
1926
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1927
|
+
"scheduled",
|
|
1928
|
+
"mine"
|
|
1929
|
+
],
|
|
1930
|
+
draft: (conversationId) => [
|
|
1931
|
+
...MESSAGES_QUERY_KEYS.all(),
|
|
1932
|
+
"draft",
|
|
1933
|
+
conversationId
|
|
1934
|
+
],
|
|
1935
|
+
announcementChannel: () => [...MESSAGES_QUERY_KEYS.all(), "announcement-channel"],
|
|
1936
|
+
downline: () => [...MESSAGES_QUERY_KEYS.all(), "downline"]
|
|
1937
|
+
};
|
|
1938
|
+
//#endregion
|
|
1939
|
+
//#region ../../messaging/core/src/constants.ts
|
|
1940
|
+
const WEBSOCKET_QUERY_OPTIONS = {
|
|
1941
|
+
staleTime: Infinity,
|
|
1942
|
+
refetchOnMount: "always",
|
|
1943
|
+
refetchOnReconnect: "always",
|
|
1944
|
+
refetchOnWindowFocus: false
|
|
1945
|
+
};
|
|
1946
|
+
/**
|
|
1947
|
+
* Scheduled messages are not websocket-driven; poll to keep fresh.
|
|
1948
|
+
*/
|
|
1949
|
+
const SCHEDULED_MESSAGES_STALE_TIME = 6e4;
|
|
1950
|
+
const SCHEDULED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1951
|
+
/**
|
|
1952
|
+
* Pinned messages are not websocket-driven; poll to keep fresh.
|
|
1953
|
+
*/
|
|
1954
|
+
const PINNED_MESSAGES_STALE_TIME = 6e4;
|
|
1955
|
+
const PINNED_MESSAGES_REFETCH_INTERVAL = 6e4;
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region ../../messaging/core/src/cache-helpers.ts
|
|
1958
|
+
/**
|
|
1959
|
+
* seed conversation metadata caches from any conversations list queries.
|
|
1960
|
+
* returns the matched conversation if conversationId is provided and present in any list cache.
|
|
1961
|
+
*/
|
|
1962
|
+
function seedConversationMetadataFromLists(queryClient, conversationId) {
|
|
1963
|
+
let matched;
|
|
1964
|
+
const seedFromData = (data) => {
|
|
1965
|
+
const pages = data?.pages ?? [];
|
|
1966
|
+
for (const page of pages) {
|
|
1967
|
+
const items = page?.[1]?.items ?? [];
|
|
1968
|
+
for (const item of items) {
|
|
1969
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(item.id), item);
|
|
1970
|
+
if (!matched && typeof conversationId === "number" && item.id === conversationId) matched = item;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
seedFromData(queryClient.getQueryData(MESSAGES_QUERY_KEYS.listConversations()));
|
|
1975
|
+
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
1976
|
+
const key = q.queryKey;
|
|
1977
|
+
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
1978
|
+
} });
|
|
1979
|
+
for (const q of listQueries) seedFromData(queryClient.getQueryData(q.queryKey));
|
|
1980
|
+
return matched;
|
|
1981
|
+
}
|
|
1982
|
+
function isMessagesQueryKeyForConversation(queryKey, conversationId) {
|
|
1983
|
+
if (!Array.isArray(queryKey)) return false;
|
|
1984
|
+
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "messages";
|
|
1985
|
+
}
|
|
1986
|
+
function findMessageInCache(queryClient, conversationId, messageId) {
|
|
1987
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
1988
|
+
for (const query of queries) {
|
|
1989
|
+
const data = queryClient.getQueryData(query.queryKey);
|
|
1990
|
+
if (data) for (const page of data.pages) {
|
|
1991
|
+
const message = page[1].items.find((m) => m.id === messageId);
|
|
1992
|
+
if (message) return message;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function forEachMessagesQuery(queryClient, conversationId, fn) {
|
|
1997
|
+
queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
1998
|
+
}
|
|
1999
|
+
function isBottomAnchorKey(queryKey) {
|
|
2000
|
+
if (!Array.isArray(queryKey)) return false;
|
|
2001
|
+
const anchor = queryKey[queryKey.length - 1];
|
|
2002
|
+
if (typeof anchor === "object" && anchor !== null) {
|
|
2003
|
+
if ("startFromBottom" in anchor) return anchor.startFromBottom === true;
|
|
2004
|
+
}
|
|
2005
|
+
return false;
|
|
2006
|
+
}
|
|
2007
|
+
function isPinnedQueryKeyForConversation(queryKey, conversationId) {
|
|
2008
|
+
if (!Array.isArray(queryKey)) return false;
|
|
2009
|
+
return queryKey.length >= 4 && queryKey[0] === MESSAGES_QUERY_KEYS.all()[0] && queryKey[1] === "conversation" && queryKey[2] === conversationId && queryKey[3] === "pinned";
|
|
2010
|
+
}
|
|
2011
|
+
function forEachPinnedQuery(queryClient, conversationId, fn) {
|
|
2012
|
+
queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) }).forEach((q) => fn(q.queryKey));
|
|
2013
|
+
}
|
|
2014
|
+
async function cancelMessageQueries(queryClient, conversationId) {
|
|
2015
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isMessagesQueryKeyForConversation(q.queryKey, conversationId) });
|
|
2016
|
+
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
2017
|
+
}
|
|
2018
|
+
async function cancelPinnedQueries(queryClient, conversationId) {
|
|
2019
|
+
const queries = queryClient.getQueryCache().findAll({ predicate: (q) => isPinnedQueryKeyForConversation(q.queryKey, conversationId) });
|
|
2020
|
+
await Promise.all(queries.map((q) => queryClient.cancelQueries({ queryKey: q.queryKey })));
|
|
2021
|
+
}
|
|
2022
|
+
function applyMessagesOptimisticSend({ queryClient, conversationId, optimisticMessage }) {
|
|
2023
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2024
|
+
if (!isBottomAnchorKey(key)) return;
|
|
2025
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2026
|
+
const newOptimisticPage = [{ pagination: {
|
|
2027
|
+
current: 1,
|
|
2028
|
+
previous: null,
|
|
2029
|
+
next: null,
|
|
2030
|
+
per_page: oldData?.pages?.[0]?.[0]?.pagination?.per_page ?? 20,
|
|
2031
|
+
pages: oldData?.pages?.[0]?.[0]?.pagination?.pages ?? 1,
|
|
2032
|
+
count: oldData?.pages?.[0]?.[0]?.pagination?.count ?? 0
|
|
2033
|
+
} }, { items: [optimisticMessage] }];
|
|
2034
|
+
if (!oldData) return {
|
|
2035
|
+
pages: [newOptimisticPage],
|
|
2036
|
+
pageParams: [1]
|
|
2037
|
+
};
|
|
2038
|
+
const pages = oldData.pages.map((page, idx) => {
|
|
2039
|
+
if (idx === 0) {
|
|
2040
|
+
const currentItems = page?.[1]?.items ?? [];
|
|
2041
|
+
return [{ pagination: {
|
|
2042
|
+
...page?.[0]?.pagination ?? {},
|
|
2043
|
+
count: page?.[0]?.pagination?.count ?? 0
|
|
2044
|
+
} }, { items: [optimisticMessage, ...currentItems] }];
|
|
2045
|
+
}
|
|
2046
|
+
return page;
|
|
2047
|
+
});
|
|
2048
|
+
return {
|
|
2049
|
+
...oldData,
|
|
2050
|
+
pages
|
|
2051
|
+
};
|
|
2052
|
+
});
|
|
2053
|
+
});
|
|
2054
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
2055
|
+
const prev = old?.items ?? [];
|
|
2056
|
+
if (prev.some((m) => m.id === optimisticMessage.id)) return old;
|
|
2057
|
+
return { items: [...prev, optimisticMessage] };
|
|
2263
2058
|
});
|
|
2264
2059
|
}
|
|
2265
2060
|
/**
|
|
2266
|
-
*
|
|
2267
|
-
*
|
|
2061
|
+
* markNewMessageFlagForConversation
|
|
2062
|
+
*
|
|
2063
|
+
* sets a lightweight flag query for a conversation indicating a new message
|
|
2064
|
+
* arrived while the user may not be at the bottom. UI can reset it after
|
|
2065
|
+
* scrolling or showing a pill.
|
|
2268
2066
|
*/
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2067
|
+
function markNewMessageFlagForConversation(queryClient, conversationId) {
|
|
2068
|
+
queryClient.setQueryData([
|
|
2069
|
+
MESSAGES_QUERY_KEYS.all()[0],
|
|
2070
|
+
"conversation",
|
|
2071
|
+
conversationId,
|
|
2072
|
+
"new_message_flag"
|
|
2073
|
+
], true);
|
|
2074
|
+
}
|
|
2075
|
+
function clearNewMessageFlagForConversation(queryClient, conversationId) {
|
|
2076
|
+
queryClient.setQueryData([
|
|
2077
|
+
MESSAGES_QUERY_KEYS.all()[0],
|
|
2078
|
+
"conversation",
|
|
2079
|
+
conversationId,
|
|
2080
|
+
"new_message_flag"
|
|
2081
|
+
], false);
|
|
2082
|
+
}
|
|
2083
|
+
function applyMessageUpsertToCache({ queryClient, conversationId, message, replaceOptimisticId, clientMessageId }) {
|
|
2084
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2085
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2086
|
+
if (!oldData?.pages?.length) {
|
|
2087
|
+
if (!isBottomAnchorKey(key)) return oldData;
|
|
2088
|
+
return {
|
|
2089
|
+
pages: [[{ pagination: {
|
|
2090
|
+
current: 1,
|
|
2091
|
+
previous: null,
|
|
2092
|
+
next: null,
|
|
2093
|
+
per_page: 20,
|
|
2094
|
+
pages: 1,
|
|
2095
|
+
count: 1
|
|
2096
|
+
} }, { items: [message] }]],
|
|
2097
|
+
pageParams: [1]
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
const normalizedClientMessageId = (() => {
|
|
2101
|
+
if (typeof clientMessageId === "string" && clientMessageId.length > 0) return clientMessageId;
|
|
2102
|
+
const fromMeta = (() => {
|
|
2103
|
+
const meta = message.metadata;
|
|
2104
|
+
if (meta && typeof meta === "object" && "client_message_id" in meta) {
|
|
2105
|
+
const id = meta.client_message_id;
|
|
2106
|
+
if (typeof id === "string" || typeof id === "number" && Number.isFinite(id)) return id;
|
|
2107
|
+
}
|
|
2108
|
+
})();
|
|
2109
|
+
if (typeof fromMeta === "string" || typeof fromMeta === "number" && Number.isFinite(fromMeta)) return String(fromMeta);
|
|
2110
|
+
})();
|
|
2111
|
+
const finalMessage = (() => {
|
|
2112
|
+
if (!normalizedClientMessageId) return message;
|
|
2113
|
+
const currentMeta = message.metadata;
|
|
2114
|
+
if (currentMeta != null && typeof currentMeta === "object" && (typeof currentMeta.client_message_id === "string" || typeof currentMeta.client_message_id === "number")) return message;
|
|
2115
|
+
return {
|
|
2116
|
+
...message,
|
|
2117
|
+
metadata: {
|
|
2118
|
+
...currentMeta && typeof currentMeta === "object" ? currentMeta : {},
|
|
2119
|
+
client_message_id: normalizedClientMessageId
|
|
2120
|
+
}
|
|
2121
|
+
};
|
|
2122
|
+
})();
|
|
2123
|
+
let replaced = false;
|
|
2124
|
+
const pages = oldData.pages.map((page) => {
|
|
2125
|
+
const [meta, payload] = page;
|
|
2126
|
+
const items = payload.items.slice();
|
|
2127
|
+
const idxById = items.findIndex((m) => m.id === (replaceOptimisticId ?? message.id));
|
|
2128
|
+
if (idxById !== -1) {
|
|
2129
|
+
items[idxById] = finalMessage;
|
|
2130
|
+
replaced = true;
|
|
2131
|
+
return [meta, { items }];
|
|
2132
|
+
}
|
|
2133
|
+
if (normalizedClientMessageId) {
|
|
2134
|
+
const idxByClientId = items.findIndex((m) => typeof m?.metadata === "object" && m?.metadata != null && ((id) => id != null && String(id) === normalizedClientMessageId)(m.metadata?.client_message_id));
|
|
2135
|
+
if (idxByClientId !== -1) {
|
|
2136
|
+
items[idxByClientId] = finalMessage;
|
|
2137
|
+
replaced = true;
|
|
2138
|
+
return [meta, { items }];
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
const idxExisting = items.findIndex((m) => m.id === message.id);
|
|
2142
|
+
if (idxExisting !== -1) {
|
|
2143
|
+
items[idxExisting] = finalMessage;
|
|
2144
|
+
replaced = true;
|
|
2145
|
+
return [meta, { items }];
|
|
2146
|
+
}
|
|
2147
|
+
return [meta, { items }];
|
|
2148
|
+
});
|
|
2149
|
+
if (!replaced) {
|
|
2150
|
+
if (!isBottomAnchorKey(key)) return oldData;
|
|
2151
|
+
const first = pages[0];
|
|
2152
|
+
const nextPages = pages.slice();
|
|
2153
|
+
const newFirstItems = [finalMessage, ...first?.[1]?.items ?? []];
|
|
2154
|
+
const newCount = (first?.[0]?.pagination?.count ?? 0) + 1;
|
|
2155
|
+
nextPages[0] = [{ pagination: {
|
|
2156
|
+
...first?.[0]?.pagination ?? {},
|
|
2157
|
+
count: newCount
|
|
2158
|
+
} }, { items: newFirstItems }];
|
|
2159
|
+
return {
|
|
2160
|
+
...oldData,
|
|
2161
|
+
pages: nextPages
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
return {
|
|
2165
|
+
...oldData,
|
|
2166
|
+
pages
|
|
2167
|
+
};
|
|
2168
|
+
});
|
|
2169
|
+
});
|
|
2170
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
2171
|
+
if (!old?.items) return old;
|
|
2172
|
+
return { items: old.items.filter((m) => m.id !== (replaceOptimisticId ?? -99999999)) };
|
|
2173
|
+
});
|
|
2174
|
+
}
|
|
2175
|
+
function applyMessageDeleteFromCache({ queryClient, conversationId, messageId }) {
|
|
2176
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2177
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2178
|
+
if (!oldData) return oldData;
|
|
2179
|
+
const pages = oldData.pages.map((page) => {
|
|
2180
|
+
const [meta, payload] = page;
|
|
2181
|
+
const items = payload.items.filter((m) => m.id !== messageId);
|
|
2182
|
+
const newCount = Math.max(0, (meta?.pagination?.count ?? 0) - (payload.items.length - items.length));
|
|
2183
|
+
return [{ pagination: {
|
|
2184
|
+
...meta?.pagination ?? {},
|
|
2185
|
+
count: newCount
|
|
2186
|
+
} }, { items }];
|
|
2187
|
+
});
|
|
2188
|
+
return {
|
|
2189
|
+
...oldData,
|
|
2190
|
+
pages
|
|
2191
|
+
};
|
|
2192
|
+
});
|
|
2193
|
+
});
|
|
2194
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.outbox(conversationId), (old) => {
|
|
2195
|
+
if (!old?.items) return old;
|
|
2196
|
+
return { items: old.items.filter((m) => m.id !== messageId) };
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
function applyReactionUpdateToCache({ queryClient, conversationId, messageId, reaction, reaction_stats }) {
|
|
2200
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2201
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2202
|
+
if (!oldData) return oldData;
|
|
2203
|
+
const pages = oldData.pages.map((page) => {
|
|
2204
|
+
const [meta, payload] = page;
|
|
2205
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
2206
|
+
...m,
|
|
2207
|
+
reaction,
|
|
2208
|
+
reaction_stats
|
|
2209
|
+
} : m) }];
|
|
2210
|
+
});
|
|
2211
|
+
return {
|
|
2212
|
+
...oldData,
|
|
2213
|
+
pages
|
|
2214
|
+
};
|
|
2215
|
+
});
|
|
2216
|
+
});
|
|
2217
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
2218
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2219
|
+
if (!oldData) return oldData;
|
|
2220
|
+
const pages = oldData.pages.map((page) => {
|
|
2221
|
+
const [meta, payload] = page;
|
|
2222
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
2223
|
+
...m,
|
|
2224
|
+
reaction,
|
|
2225
|
+
reaction_stats
|
|
2226
|
+
} : m) }];
|
|
2227
|
+
});
|
|
2228
|
+
return {
|
|
2229
|
+
...oldData,
|
|
2230
|
+
pages
|
|
2231
|
+
};
|
|
2232
|
+
});
|
|
2233
|
+
});
|
|
2234
|
+
}
|
|
2235
|
+
function applyReactionStatsToCache({ queryClient, conversationId, messageId, reaction_stats }) {
|
|
2236
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2237
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2238
|
+
if (!oldData) return;
|
|
2239
|
+
const pages = oldData.pages.map((page) => {
|
|
2240
|
+
const [meta, payload] = page;
|
|
2241
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
2242
|
+
...m,
|
|
2243
|
+
reaction_stats
|
|
2244
|
+
} : m) }];
|
|
2245
|
+
});
|
|
2246
|
+
return {
|
|
2247
|
+
...oldData,
|
|
2248
|
+
pages
|
|
2249
|
+
};
|
|
2250
|
+
});
|
|
2251
|
+
});
|
|
2252
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
2253
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2254
|
+
if (!oldData) return oldData;
|
|
2255
|
+
const pages = oldData.pages.map((page) => {
|
|
2256
|
+
const [meta, payload] = page;
|
|
2257
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
2258
|
+
...m,
|
|
2259
|
+
reaction_stats
|
|
2260
|
+
} : m) }];
|
|
2261
|
+
});
|
|
2262
|
+
return {
|
|
2263
|
+
...oldData,
|
|
2264
|
+
pages
|
|
2265
|
+
};
|
|
2266
|
+
});
|
|
2267
|
+
});
|
|
2268
|
+
}
|
|
2269
|
+
function applyConversationMetaToCaches({ queryClient, conversation }) {
|
|
2270
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.conversationMetadata(conversation.id), (old) => {
|
|
2271
|
+
if (!old) return old;
|
|
2272
|
+
return {
|
|
2273
|
+
...old,
|
|
2274
|
+
...conversation
|
|
2275
|
+
};
|
|
2276
|
+
});
|
|
2277
|
+
const listQueries = queryClient.getQueryCache().findAll({ predicate: (q) => {
|
|
2278
|
+
const key = q.queryKey;
|
|
2279
|
+
return Array.isArray(key) && key.length >= 2 && key[0] === MESSAGES_QUERY_KEYS.all()[0] && key[1] === MESSAGES_QUERY_KEYS.listConversations()[1];
|
|
2280
|
+
} });
|
|
2281
|
+
for (const q of listQueries) queryClient.setQueryData(q.queryKey, (oldData) => {
|
|
2282
|
+
const data = oldData;
|
|
2283
|
+
if (!data?.pages) return oldData;
|
|
2284
|
+
return {
|
|
2285
|
+
...data,
|
|
2286
|
+
pages: data.pages.map((page) => [page[0], {
|
|
2287
|
+
...page[1],
|
|
2288
|
+
items: page[1].items.map((conv) => conv.id === conversation.id ? {
|
|
2289
|
+
...conv,
|
|
2290
|
+
...typeof conversation.unread === "boolean" ? { unread: conversation.unread } : {},
|
|
2291
|
+
...typeof conversation.unread_messages_count === "number" ? { unread_messages_count: conversation.unread_messages_count } : {}
|
|
2292
|
+
} : conv)
|
|
2293
|
+
}])
|
|
2294
|
+
};
|
|
2275
2295
|
});
|
|
2276
2296
|
}
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2297
|
+
function applyConversationDestroyed({ queryClient, conversationId }) {
|
|
2298
|
+
queryClient.removeQueries({ queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId) });
|
|
2299
|
+
queryClient.setQueryData(MESSAGES_QUERY_KEYS.listConversations(), (oldData) => {
|
|
2300
|
+
const data = oldData;
|
|
2301
|
+
if (!data?.pages) return oldData;
|
|
2302
|
+
return {
|
|
2303
|
+
...data,
|
|
2304
|
+
pages: data.pages.map((page) => [page[0], {
|
|
2305
|
+
...page[1],
|
|
2306
|
+
items: page[1].items.filter((c) => c.id !== conversationId)
|
|
2307
|
+
}])
|
|
2308
|
+
};
|
|
2287
2309
|
});
|
|
2288
2310
|
}
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2311
|
+
function applyMessagePinnedFlag({ queryClient, conversationId, messageId, pinned, message }) {
|
|
2312
|
+
forEachMessagesQuery(queryClient, conversationId, (key) => {
|
|
2313
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2314
|
+
if (!oldData) return oldData;
|
|
2315
|
+
const pages = oldData.pages.map((page) => {
|
|
2316
|
+
const [meta, payload] = page;
|
|
2317
|
+
return [meta, { items: payload.items.map((m) => m.id === messageId ? {
|
|
2318
|
+
...m,
|
|
2319
|
+
pinned
|
|
2320
|
+
} : m) }];
|
|
2321
|
+
});
|
|
2322
|
+
return {
|
|
2323
|
+
...oldData,
|
|
2324
|
+
pages
|
|
2325
|
+
};
|
|
2326
|
+
});
|
|
2298
2327
|
});
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2328
|
+
forEachPinnedQuery(queryClient, conversationId, (key) => {
|
|
2329
|
+
queryClient.setQueryData(key, (oldData) => {
|
|
2330
|
+
if (!oldData) return oldData;
|
|
2331
|
+
const pages = oldData.pages.map((page, idx) => {
|
|
2332
|
+
const [meta, payload] = page;
|
|
2333
|
+
if (idx !== 0) return page;
|
|
2334
|
+
const exists = payload.items.some((m) => m.id === messageId);
|
|
2335
|
+
if (pinned) {
|
|
2336
|
+
if (exists) return page;
|
|
2337
|
+
const msg = message ?? payload.items.find((m) => m.id === messageId);
|
|
2338
|
+
if (!msg) return page;
|
|
2339
|
+
return [meta, { items: [msg, ...payload.items] }];
|
|
2340
|
+
}
|
|
2341
|
+
return [meta, { items: payload.items.filter((m) => m.id !== messageId) }];
|
|
2342
|
+
});
|
|
2343
|
+
return {
|
|
2344
|
+
...oldData,
|
|
2345
|
+
pages
|
|
2346
|
+
};
|
|
2347
|
+
});
|
|
2309
2348
|
});
|
|
2310
2349
|
}
|
|
2311
2350
|
//#endregion
|
|
2312
|
-
//#region ../../messaging/
|
|
2313
|
-
/**
|
|
2314
|
-
* Conversations API (v1 messaging)
|
|
2315
|
-
*
|
|
2316
|
-
* Thin, fully-typed wrappers around the Rails messaging controllers.
|
|
2317
|
-
* All functions validate inputs/outputs with zod and use `fetchMessaging`.
|
|
2318
|
-
*/
|
|
2351
|
+
//#region ../../messaging/core/src/utils/reaction-utils.ts
|
|
2319
2352
|
/**
|
|
2320
|
-
*
|
|
2353
|
+
* Splits an emoji string into an array of grapheme clusters (perceived characters/emojis).
|
|
2354
|
+
* Uses `Intl.Segmenter` when available for correct multi-codepoint emoji handling.
|
|
2355
|
+
* Falls back to `[...string]` (code-point iteration) on browsers without `Intl.Segmenter`
|
|
2356
|
+
* (e.g. Firefox < 119), which may incorrectly split ZWJ sequences, skin-tone modifiers,
|
|
2357
|
+
* and flag emojis into multiple entries.
|
|
2358
|
+
* @param emojiString The string of emojis to split.
|
|
2359
|
+
* @returns An array of strings, where each string is a single emoji.
|
|
2321
2360
|
*/
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
async function listConversations(config, input) {
|
|
2330
|
-
return fetchMessaging(config, {
|
|
2331
|
-
endpoint: "/v1/messaging/conversations",
|
|
2332
|
-
method: "GET",
|
|
2333
|
-
input,
|
|
2334
|
-
outputSchema: ConversationListResponseSchema
|
|
2335
|
-
});
|
|
2361
|
+
function splitEmojis(emojiString) {
|
|
2362
|
+
if (!emojiString) return [];
|
|
2363
|
+
if (typeof Intl !== "undefined" && typeof Intl.Segmenter === "function") {
|
|
2364
|
+
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
2365
|
+
return Array.from(segmenter.segment(emojiString)).map((segment) => segment.segment);
|
|
2366
|
+
}
|
|
2367
|
+
return [...emojiString];
|
|
2336
2368
|
}
|
|
2337
2369
|
/**
|
|
2338
|
-
*
|
|
2370
|
+
* Flattens reaction_stats to count individual emojis.
|
|
2371
|
+
* E.g., {"😀😂": 1, "👍": 2, "😀": 1} becomes {"😀": 2, "😂": 1, "👍": 2}
|
|
2372
|
+
* Handles multi-codepoint emojis correctly.
|
|
2373
|
+
* @param stats The raw reaction_stats object from the message.
|
|
2374
|
+
* @returns A record where keys are single emojis and values are their total counts.
|
|
2339
2375
|
*/
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2376
|
+
function flattenReactionStats(stats) {
|
|
2377
|
+
if (!stats) return {};
|
|
2378
|
+
const flattened = {};
|
|
2379
|
+
for (const emojiKey in stats) {
|
|
2380
|
+
const count = stats[emojiKey];
|
|
2381
|
+
if (typeof count !== "number" || count === 0) continue;
|
|
2382
|
+
const individualEmojis = splitEmojis(emojiKey);
|
|
2383
|
+
for (const emoji of individualEmojis) flattened[emoji] = (flattened[emoji] || 0) + count;
|
|
2384
|
+
}
|
|
2385
|
+
return flattened;
|
|
2347
2386
|
}
|
|
2348
2387
|
/**
|
|
2349
|
-
*
|
|
2388
|
+
* Calculates the optimistic state for a message's reactions when a user's reaction changes.
|
|
2389
|
+
* @param currentState The current reaction parts of the message ({ reaction, reaction_stats }).
|
|
2390
|
+
* @param newUserReaction The user's new complete reaction string (e.g., "😀👍", "😂", or null to unreact).
|
|
2391
|
+
* @returns An object with the updated `reaction` and `reaction_stats`.
|
|
2350
2392
|
*/
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2393
|
+
function calculateOptimisticReactionUpdate(currentState, newUserReaction) {
|
|
2394
|
+
const previousUserReaction = currentState.reaction;
|
|
2395
|
+
const newStats = { ...currentState.reaction_stats || {} };
|
|
2396
|
+
const finalNewUserReaction = newUserReaction === "" ? null : newUserReaction;
|
|
2397
|
+
if (previousUserReaction === finalNewUserReaction) return {
|
|
2398
|
+
reaction: previousUserReaction,
|
|
2399
|
+
reaction_stats: newStats
|
|
2400
|
+
};
|
|
2401
|
+
if (previousUserReaction && typeof newStats[previousUserReaction] === "number") newStats[previousUserReaction] = Math.max(0, newStats[previousUserReaction] - 1);
|
|
2402
|
+
if (finalNewUserReaction) newStats[finalNewUserReaction] = (typeof newStats[finalNewUserReaction] === "number" ? newStats[finalNewUserReaction] : 0) + 1;
|
|
2403
|
+
return {
|
|
2404
|
+
reaction: finalNewUserReaction,
|
|
2405
|
+
reaction_stats: newStats
|
|
2406
|
+
};
|
|
2358
2407
|
}
|
|
2359
2408
|
/**
|
|
2360
|
-
*
|
|
2409
|
+
* Generates a unique, sorted string from an array of emoji characters.
|
|
2410
|
+
* Each string in the input array is assumed to be a single, correctly segmented emoji.
|
|
2411
|
+
* E.g., ["😂", "😀", "😂"] becomes "😀😂"
|
|
2412
|
+
* @param emojis An array of single emoji strings (grapheme clusters).
|
|
2413
|
+
* @returns A sorted string of unique emojis, or an empty string if no emojis are provided.
|
|
2361
2414
|
*/
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
method: "GET",
|
|
2366
|
-
outputSchema: ConversationResponseSchema
|
|
2367
|
-
});
|
|
2368
|
-
}
|
|
2369
|
-
const conversationCreateInputSchema = z.strictObject({
|
|
2370
|
-
conversation: z.strictObject({
|
|
2371
|
-
kind: z.string().optional(),
|
|
2372
|
-
type: z.string().optional(),
|
|
2373
|
-
name: z.string().optional(),
|
|
2374
|
-
description: z.string().nullable().optional(),
|
|
2375
|
-
broadcasting: z.boolean().optional(),
|
|
2376
|
-
filter: z.record(z.string(), z.unknown()).optional(),
|
|
2377
|
-
auto_add_recipients: z.boolean().optional()
|
|
2378
|
-
}),
|
|
2379
|
-
recipients: z.array(z.strictObject({
|
|
2380
|
-
id: z.number().optional(),
|
|
2381
|
-
uid: z.number().optional(),
|
|
2382
|
-
email: z.string().email().optional(),
|
|
2383
|
-
phone: z.string().optional(),
|
|
2384
|
-
first_name: z.string().optional(),
|
|
2385
|
-
last_name: z.string().optional(),
|
|
2386
|
-
image_url: z.string().url().optional()
|
|
2387
|
-
})).optional()
|
|
2388
|
-
});
|
|
2389
|
-
async function createConversation(config, input) {
|
|
2390
|
-
return fetchMessaging(config, {
|
|
2391
|
-
endpoint: "/v1/messaging/conversations",
|
|
2392
|
-
method: "POST",
|
|
2393
|
-
input,
|
|
2394
|
-
inputSchema: conversationCreateInputSchema,
|
|
2395
|
-
outputSchema: ConversationResponseSchema
|
|
2396
|
-
});
|
|
2415
|
+
function generateSortedEmojiStringFromArray(emojis) {
|
|
2416
|
+
if (!emojis || emojis.length === 0) return "";
|
|
2417
|
+
return [...new Set(emojis)].sort((a, b) => a.localeCompare(b, "en")).join("");
|
|
2397
2418
|
}
|
|
2398
|
-
z.strictObject({ conversation: z.strictObject({
|
|
2399
|
-
name: z.string().optional(),
|
|
2400
|
-
description: z.string().nullable().optional(),
|
|
2401
|
-
additional_recipient_params: z.strictObject({
|
|
2402
|
-
receivable_id: z.number(),
|
|
2403
|
-
receivable_type: z.string()
|
|
2404
|
-
}).optional(),
|
|
2405
|
-
auto_add_recipients: z.boolean().optional()
|
|
2406
|
-
}) });
|
|
2407
2419
|
//#endregion
|
|
2408
|
-
//#region ../../messaging/
|
|
2420
|
+
//#region ../../messaging/core/src/utils/messaging.ts
|
|
2421
|
+
function mapConversationTypeToMessageType(conversationType) {
|
|
2422
|
+
switch (conversationType) {
|
|
2423
|
+
case "DirectConversation": return "DirectMessage";
|
|
2424
|
+
case "SmsConversation": return "SmsMessage";
|
|
2425
|
+
case "EmailConversation": return "EmailMessage";
|
|
2426
|
+
case "ChannelConversation":
|
|
2427
|
+
case "GroupConversation": return "DirectMessage";
|
|
2428
|
+
case "BotConversation": return "BotMessage";
|
|
2429
|
+
default: return "DirectMessage";
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2409
2432
|
/**
|
|
2410
|
-
*
|
|
2433
|
+
* Determines the category of a chat. This is slightly different from the backend's
|
|
2434
|
+
* category field.
|
|
2435
|
+
*
|
|
2436
|
+
* @param item - Conversation item to check
|
|
2437
|
+
* @returns the type of channel
|
|
2411
2438
|
*/
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2439
|
+
const getMessageType = (item) => {
|
|
2440
|
+
if (item.type === "GroupConversation" && item.name.toLowerCase().includes("other")) return {
|
|
2441
|
+
isChannel: false,
|
|
2442
|
+
type: "group-direct-message"
|
|
2443
|
+
};
|
|
2444
|
+
if (item.broadcasting) return {
|
|
2445
|
+
isChannel: true,
|
|
2446
|
+
type: "broadcast"
|
|
2447
|
+
};
|
|
2448
|
+
if (item.type === "GroupConversation") return {
|
|
2449
|
+
isChannel: true,
|
|
2450
|
+
type: "private-channel"
|
|
2451
|
+
};
|
|
2452
|
+
if (item.type === "DirectConversation") return {
|
|
2453
|
+
isChannel: false,
|
|
2454
|
+
type: "direct-message"
|
|
2455
|
+
};
|
|
2456
|
+
if (item.type === "EmailConversation") return {
|
|
2457
|
+
isChannel: false,
|
|
2458
|
+
type: "email"
|
|
2459
|
+
};
|
|
2460
|
+
if (item.type === "SmsConversation") return {
|
|
2461
|
+
isChannel: false,
|
|
2462
|
+
type: "sms"
|
|
2463
|
+
};
|
|
2464
|
+
if (item.type === "ChannelConversation") return {
|
|
2465
|
+
isChannel: true,
|
|
2466
|
+
type: "public-channel"
|
|
2467
|
+
};
|
|
2468
|
+
if (item.type === "BotConversation") return {
|
|
2469
|
+
isChannel: false,
|
|
2470
|
+
type: "bot"
|
|
2471
|
+
};
|
|
2472
|
+
if (item.type === "DownlineConversation") return {
|
|
2473
|
+
isChannel: true,
|
|
2474
|
+
type: "downline"
|
|
2475
|
+
};
|
|
2476
|
+
throw new Error("Invalid conversation type");
|
|
2477
|
+
};
|
|
2478
|
+
const getUserInitials = (conversation, user) => {
|
|
2479
|
+
if (user.phone === conversation.name) return "#";
|
|
2480
|
+
if (user.email === conversation.name) return "@";
|
|
2481
|
+
return [user.first_name?.charAt(0), user.last_name?.charAt(0)].filter(Boolean).join("") || "?";
|
|
2482
|
+
};
|
|
2483
|
+
const getFileTypeFromMimetype = (mimetype) => {
|
|
2484
|
+
if (mimetype.startsWith("image/")) return "image";
|
|
2485
|
+
if (mimetype.startsWith("video/")) return "video";
|
|
2486
|
+
if (mimetype === "application/pdf") return "pdf";
|
|
2487
|
+
if (mimetype.includes("spreadsheet") || mimetype.includes("excel") || mimetype.includes("sheet") || mimetype === "text/csv") return "sheets";
|
|
2488
|
+
if (mimetype.includes("powerpoint") || mimetype.includes("presentation")) return "powerpoint";
|
|
2489
|
+
if (mimetype.includes("document") || mimetype.includes("word") || mimetype.includes("rtf")) return "docx";
|
|
2490
|
+
if (mimetype.startsWith("text/")) return "txt";
|
|
2491
|
+
if (mimetype.includes("zip") || mimetype.includes("compressed") || mimetype.includes("archive") || mimetype.includes("tar") || mimetype.includes("gzip")) return "compressed";
|
|
2492
|
+
if (mimetype.startsWith("audio/")) return "audio";
|
|
2493
|
+
return "unknown";
|
|
2494
|
+
};
|
|
2424
2495
|
//#endregion
|
|
2425
|
-
//#region ../../messaging/
|
|
2426
|
-
const saveDraftInputSchema = z.strictObject({ draft_message: z.strictObject({
|
|
2427
|
-
body: z.string().nullable().optional(),
|
|
2428
|
-
replied_to_id: z.number().nullable().optional(),
|
|
2429
|
-
attachments_attributes: z.array(z.strictObject({
|
|
2430
|
-
url: z.string().url(),
|
|
2431
|
-
filename: z.string(),
|
|
2432
|
-
kind: z.string(),
|
|
2433
|
-
content_type: z.string().optional(),
|
|
2434
|
-
content_size: z.number().optional(),
|
|
2435
|
-
metadata: AttachmentInputMetadataSchema,
|
|
2436
|
-
id: z.number().optional(),
|
|
2437
|
-
_destroy: z.boolean().optional()
|
|
2438
|
-
})).optional()
|
|
2439
|
-
}) });
|
|
2440
|
-
async function saveDraft(config, conversation_id, input) {
|
|
2441
|
-
return fetchMessaging(config, {
|
|
2442
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/save_draft`,
|
|
2443
|
-
method: "PUT",
|
|
2444
|
-
input,
|
|
2445
|
-
inputSchema: saveDraftInputSchema,
|
|
2446
|
-
outputSchema: DraftMessageSchema.or(z.strictObject({}))
|
|
2447
|
-
});
|
|
2448
|
-
}
|
|
2449
|
-
async function destroyDraft(config, conversation_id) {
|
|
2450
|
-
return fetchMessaging(config, {
|
|
2451
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/destroy_draft`,
|
|
2452
|
-
method: "DELETE",
|
|
2453
|
-
outputSchema: z.unknown()
|
|
2454
|
-
});
|
|
2455
|
-
}
|
|
2496
|
+
//#region ../../messaging/core/src/utils/string-utils.ts
|
|
2456
2497
|
/**
|
|
2457
|
-
*
|
|
2498
|
+
* Extracts the leading emoji from a string.
|
|
2499
|
+
* @param text The string to process.
|
|
2500
|
+
* @returns An object containing the emoji and the remaining text.
|
|
2458
2501
|
*/
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
type: z.string().optional()
|
|
2473
|
-
}) });
|
|
2474
|
-
async function createScheduledMessage(config, conversation_id, input) {
|
|
2475
|
-
return fetchMessaging(config, {
|
|
2476
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages`,
|
|
2477
|
-
method: "POST",
|
|
2478
|
-
input,
|
|
2479
|
-
inputSchema: scheduledMessageInputSchema,
|
|
2480
|
-
outputSchema: MessageItemSchema
|
|
2481
|
-
});
|
|
2482
|
-
}
|
|
2483
|
-
async function updateScheduledMessage(config, conversation_id, id, input) {
|
|
2484
|
-
return fetchMessaging(config, {
|
|
2485
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
2486
|
-
method: "PUT",
|
|
2487
|
-
input,
|
|
2488
|
-
inputSchema: scheduledMessageInputSchema,
|
|
2489
|
-
outputSchema: MessageItemSchema
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
|
-
async function destroyScheduledMessage(config, conversation_id, id) {
|
|
2493
|
-
return fetchMessaging(config, {
|
|
2494
|
-
endpoint: `/v1/messaging/conversations/${conversation_id}/scheduled_messages/${id}`,
|
|
2495
|
-
method: "DELETE",
|
|
2496
|
-
outputSchema: z.strictObject({
|
|
2497
|
-
success: z.boolean(),
|
|
2498
|
-
message: z.string()
|
|
2499
|
-
})
|
|
2500
|
-
});
|
|
2502
|
+
function extractEmoji$1(text) {
|
|
2503
|
+
const match = text.match(/^\p{Emoji_Presentation}/u);
|
|
2504
|
+
if (match) {
|
|
2505
|
+
const emoji = match[0];
|
|
2506
|
+
return {
|
|
2507
|
+
emoji,
|
|
2508
|
+
text: text.substring(emoji.length).trimStart()
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
return {
|
|
2512
|
+
emoji: null,
|
|
2513
|
+
text
|
|
2514
|
+
};
|
|
2501
2515
|
}
|
|
2502
2516
|
/**
|
|
2503
|
-
*
|
|
2517
|
+
* Formats a message channel name by replacing spaces and underscores with hyphens,
|
|
2518
|
+
* converting to lowercase, and removing any non-alphanumeric characters except hyphens.
|
|
2519
|
+
* @param name - The original channel name
|
|
2520
|
+
* @return The formatted channel name
|
|
2504
2521
|
*/
|
|
2505
|
-
|
|
2506
|
-
return
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
}
|
|
2522
|
+
function formatMessageChannelName$1(name) {
|
|
2523
|
+
return name.toLowerCase().replace(/[ _]/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
2524
|
+
}
|
|
2525
|
+
const MAX_GROUP_NAME_LENGTH$1 = 30;
|
|
2526
|
+
function formatGroupDisplayName$1(names, maxLength = MAX_GROUP_NAME_LENGTH$1) {
|
|
2527
|
+
const fullDisplayName = names.join(", ");
|
|
2528
|
+
if (fullDisplayName.length <= maxLength) return { displayName: fullDisplayName };
|
|
2529
|
+
for (let i = 0; i < names.length; i++) {
|
|
2530
|
+
const currentNames = names.slice(0, i + 1);
|
|
2531
|
+
const remainingCount = names.length - currentNames.length;
|
|
2532
|
+
if (remainingCount > 0) {
|
|
2533
|
+
const suffix = `+ ${remainingCount} other${remainingCount > 1 ? "s" : ""}`;
|
|
2534
|
+
if (`${currentNames.join(", ")} ${suffix}`.length > maxLength && i > 0) {
|
|
2535
|
+
const namesToShow = names.slice(0, i);
|
|
2536
|
+
const othersCount = names.length - i;
|
|
2537
|
+
const othersSuffix = `+${othersCount} other${othersCount > 1 ? "s" : ""}`;
|
|
2538
|
+
const truncatedDisplayName = namesToShow.join(", ");
|
|
2539
|
+
if (othersCount === 1) {
|
|
2540
|
+
const nameWithOneMore = names.slice(0, i + 1).join(", ");
|
|
2541
|
+
if (nameWithOneMore.length < truncatedDisplayName.length) return { displayName: nameWithOneMore };
|
|
2542
|
+
}
|
|
2543
|
+
return {
|
|
2544
|
+
displayName: truncatedDisplayName,
|
|
2545
|
+
remainder: othersSuffix
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2550
|
+
const firstName = names[0] || "";
|
|
2551
|
+
const othersCount = names.length - 1;
|
|
2552
|
+
if (othersCount > 0) return {
|
|
2553
|
+
displayName: firstName,
|
|
2554
|
+
remainder: `+ ${othersCount} other${othersCount > 1 ? "s" : ""}`
|
|
2555
|
+
};
|
|
2556
|
+
return { displayName: firstName };
|
|
2557
|
+
}
|
|
2558
|
+
//#endregion
|
|
2559
|
+
//#region ../../messaging/core/src/utils/file-kinds.ts
|
|
2560
|
+
const ServerFileKindEnum$1 = z.enum([
|
|
2561
|
+
"image",
|
|
2562
|
+
"video",
|
|
2563
|
+
"pdf",
|
|
2564
|
+
"sheets",
|
|
2565
|
+
"powerpoint",
|
|
2566
|
+
"docx"
|
|
2567
|
+
]);
|
|
2568
|
+
const fallbackFileType$1 = "docx";
|
|
2569
|
+
//#endregion
|
|
2570
|
+
//#region ../../messaging/core/src/messaging-api.ts
|
|
2571
|
+
const MessagingApiContext = createContext(null);
|
|
2572
|
+
const MessagingApiProvider = MessagingApiContext.Provider;
|
|
2573
|
+
function useMessagingApi() {
|
|
2574
|
+
const api = useContext(MessagingApiContext);
|
|
2575
|
+
if (!api) throw new Error("useMessagingApi must be used within a MessagingApiProvider");
|
|
2576
|
+
return api;
|
|
2512
2577
|
}
|
|
2513
|
-
z.strictObject({ bot: z.strictObject({
|
|
2514
|
-
title: z.string(),
|
|
2515
|
-
description: z.string().nullable().optional(),
|
|
2516
|
-
external_uri: z.string().url().nullable().optional(),
|
|
2517
|
-
image_url: z.string().url().nullable().optional(),
|
|
2518
|
-
type: z.string().optional(),
|
|
2519
|
-
avatar_background_color: z.string().nullable().optional(),
|
|
2520
|
-
avatar_emoji: z.string().nullable().optional(),
|
|
2521
|
-
webhook_external_uri: z.string().url().nullable().optional(),
|
|
2522
|
-
webhook_auth_token: z.string().nullable().optional(),
|
|
2523
|
-
webhook_http_method: z.string().nullable().optional(),
|
|
2524
|
-
conversation_ids: z.array(z.number()).optional()
|
|
2525
|
-
}) });
|
|
2526
2578
|
//#endregion
|
|
2527
2579
|
//#region ../../messaging/core/src/hooks/useConversations.ts
|
|
2528
|
-
function useConversations(
|
|
2580
|
+
function useConversations(input) {
|
|
2581
|
+
const api = useMessagingApi();
|
|
2529
2582
|
return useInfiniteQuery({
|
|
2530
2583
|
queryKey: [...MESSAGES_QUERY_KEYS.listConversations(), input ?? {}],
|
|
2531
|
-
queryFn: ({ pageParam }) => listConversations(
|
|
2584
|
+
queryFn: ({ pageParam }) => api.listConversations({
|
|
2532
2585
|
...input || {},
|
|
2533
2586
|
page: pageParam ?? 1
|
|
2534
2587
|
}),
|
|
@@ -2538,14 +2591,15 @@ function useConversations(config, input) {
|
|
|
2538
2591
|
...WEBSOCKET_QUERY_OPTIONS
|
|
2539
2592
|
});
|
|
2540
2593
|
}
|
|
2541
|
-
function useConversation(
|
|
2594
|
+
function useConversation(conversationId) {
|
|
2595
|
+
const api = useMessagingApi();
|
|
2542
2596
|
const queryClient = useQueryClient();
|
|
2543
2597
|
return useQuery({
|
|
2544
2598
|
queryKey: MESSAGES_QUERY_KEYS.conversationMetadata(conversationId),
|
|
2545
2599
|
queryFn: async () => {
|
|
2546
2600
|
const fromSeed = seedConversationMetadataFromLists(queryClient, conversationId);
|
|
2547
2601
|
if (fromSeed) return fromSeed;
|
|
2548
|
-
const result = await getConversation(
|
|
2602
|
+
const result = await api.getConversation(conversationId);
|
|
2549
2603
|
applyConversationMetaToCaches({
|
|
2550
2604
|
queryClient,
|
|
2551
2605
|
conversation: {
|
|
@@ -2561,8 +2615,9 @@ function useConversation(config, conversationId) {
|
|
|
2561
2615
|
}
|
|
2562
2616
|
//#endregion
|
|
2563
2617
|
//#region ../../messaging/core/src/hooks/useFindConversation.ts
|
|
2564
|
-
function useFindConversation(
|
|
2618
|
+
function useFindConversation(params) {
|
|
2565
2619
|
const { recipients, contact_id, conversation_type, enabled } = params;
|
|
2620
|
+
const api = useMessagingApi();
|
|
2566
2621
|
const useRecipientsFlow = Array.isArray(recipients) && recipients.length > 0;
|
|
2567
2622
|
const inferredType = conversation_type || (useRecipientsFlow ? recipients.length > 1 ? "GroupConversation" : "DirectConversation" : void 0);
|
|
2568
2623
|
return useQuery({
|
|
@@ -2576,11 +2631,11 @@ function useFindConversation(config, params) {
|
|
|
2576
2631
|
conversation_type
|
|
2577
2632
|
],
|
|
2578
2633
|
queryFn: async () => {
|
|
2579
|
-
if (useRecipientsFlow) return await createConversation(
|
|
2634
|
+
if (useRecipientsFlow) return await api.createConversation({
|
|
2580
2635
|
conversation: { type: inferredType },
|
|
2581
2636
|
recipients: recipients.map((r) => ({ uid: r.uid }))
|
|
2582
2637
|
});
|
|
2583
|
-
if (typeof contact_id === "number" && conversation_type) return await findConversation(
|
|
2638
|
+
if (typeof contact_id === "number" && conversation_type) return await api.findConversation({
|
|
2584
2639
|
contact_id,
|
|
2585
2640
|
conversation_type
|
|
2586
2641
|
});
|
|
@@ -2592,20 +2647,22 @@ function useFindConversation(config, params) {
|
|
|
2592
2647
|
}
|
|
2593
2648
|
//#endregion
|
|
2594
2649
|
//#region ../../messaging/core/src/hooks/useAnnouncementChannel.ts
|
|
2595
|
-
function useAnnouncementChannel(
|
|
2650
|
+
function useAnnouncementChannel() {
|
|
2651
|
+
const api = useMessagingApi();
|
|
2596
2652
|
return useQuery({
|
|
2597
2653
|
queryKey: MESSAGES_QUERY_KEYS.announcementChannel(),
|
|
2598
|
-
queryFn: () => getAnnouncementChannel(
|
|
2654
|
+
queryFn: () => api.getAnnouncementChannel(),
|
|
2599
2655
|
staleTime: Infinity,
|
|
2600
2656
|
refetchOnWindowFocus: false
|
|
2601
2657
|
});
|
|
2602
2658
|
}
|
|
2603
2659
|
//#endregion
|
|
2604
2660
|
//#region ../../messaging/core/src/hooks/useScheduledMessages.ts
|
|
2605
|
-
function useScheduledMessages$1(
|
|
2661
|
+
function useScheduledMessages$1(conversationId, perPage = 20) {
|
|
2662
|
+
const api = useMessagingApi();
|
|
2606
2663
|
return useInfiniteQuery({
|
|
2607
2664
|
queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId),
|
|
2608
|
-
queryFn: async ({ pageParam }) => listScheduledMessages(
|
|
2665
|
+
queryFn: async ({ pageParam }) => api.listScheduledMessages(conversationId, {
|
|
2609
2666
|
page: pageParam ?? 1,
|
|
2610
2667
|
per_page: perPage
|
|
2611
2668
|
}),
|
|
@@ -2620,10 +2677,11 @@ function useScheduledMessages$1(config, conversationId, perPage = 20) {
|
|
|
2620
2677
|
}
|
|
2621
2678
|
//#endregion
|
|
2622
2679
|
//#region ../../messaging/core/src/hooks/useMyScheduledMessages.ts
|
|
2623
|
-
function useMyScheduledMessages(
|
|
2680
|
+
function useMyScheduledMessages() {
|
|
2681
|
+
const api = useMessagingApi();
|
|
2624
2682
|
return useInfiniteQuery({
|
|
2625
2683
|
queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages(),
|
|
2626
|
-
queryFn: async ({ pageParam }) => listMyScheduledMessages(
|
|
2684
|
+
queryFn: async ({ pageParam }) => api.listMyScheduledMessages({
|
|
2627
2685
|
page: pageParam ?? 1,
|
|
2628
2686
|
per_page: 50
|
|
2629
2687
|
}),
|
|
@@ -2635,12 +2693,13 @@ function useMyScheduledMessages(config) {
|
|
|
2635
2693
|
}
|
|
2636
2694
|
//#endregion
|
|
2637
2695
|
//#region ../../messaging/core/src/hooks/useReactions.ts
|
|
2638
|
-
function useReaction(
|
|
2696
|
+
function useReaction() {
|
|
2697
|
+
const api = useMessagingApi();
|
|
2639
2698
|
const queryClient = useQueryClient();
|
|
2640
2699
|
const reactMutation = useMutation({
|
|
2641
2700
|
mutationFn: async (params) => {
|
|
2642
2701
|
const { conversationId, messageId, reactionString } = params;
|
|
2643
|
-
return reactToMessage(
|
|
2702
|
+
return api.reactToMessage(conversationId, {
|
|
2644
2703
|
message_id: messageId,
|
|
2645
2704
|
reaction: reactionString
|
|
2646
2705
|
});
|
|
@@ -2688,7 +2747,7 @@ function useReaction(config) {
|
|
|
2688
2747
|
const unreactMutation = useMutation({
|
|
2689
2748
|
mutationFn: async (params) => {
|
|
2690
2749
|
const { conversationId, messageId } = params;
|
|
2691
|
-
return unreactToMessage(
|
|
2750
|
+
return api.unreactToMessage(conversationId, { message_id: messageId });
|
|
2692
2751
|
},
|
|
2693
2752
|
onMutate: async (variables) => {
|
|
2694
2753
|
const { conversationId, messageId } = variables;
|
|
@@ -2737,8 +2796,9 @@ function useReaction(config) {
|
|
|
2737
2796
|
}
|
|
2738
2797
|
//#endregion
|
|
2739
2798
|
//#region ../../messaging/core/src/hooks/useMessages.ts
|
|
2740
|
-
function useMessages$1(
|
|
2799
|
+
function useMessages$1(params, options) {
|
|
2741
2800
|
const { conversationId, messageId, perPage = 50 } = params;
|
|
2801
|
+
const api = useMessagingApi();
|
|
2742
2802
|
const queryClient = useQueryClient();
|
|
2743
2803
|
const isBottomAnchored = !messageId;
|
|
2744
2804
|
const query = useInfiniteQuery({
|
|
@@ -2747,7 +2807,7 @@ function useMessages$1(config, params, options) {
|
|
|
2747
2807
|
const param = pageParam;
|
|
2748
2808
|
const pageNumber = param?.type === "page" ? param.number ?? 1 : void 0;
|
|
2749
2809
|
const shouldMarkRead = isBottomAnchored && pageNumber === 1;
|
|
2750
|
-
const
|
|
2810
|
+
const input = param?.type === "message" ? {
|
|
2751
2811
|
preview: true,
|
|
2752
2812
|
message_id: param.id,
|
|
2753
2813
|
per_page: perPage
|
|
@@ -2755,7 +2815,8 @@ function useMessages$1(config, params, options) {
|
|
|
2755
2815
|
preview: !shouldMarkRead ? true : false,
|
|
2756
2816
|
page: pageNumber ?? 1,
|
|
2757
2817
|
per_page: perPage
|
|
2758
|
-
}
|
|
2818
|
+
};
|
|
2819
|
+
const result = await api.listMessages(conversationId, input);
|
|
2759
2820
|
if (shouldMarkRead) {
|
|
2760
2821
|
applyConversationMetaToCaches({
|
|
2761
2822
|
queryClient,
|
|
@@ -2824,7 +2885,8 @@ function useMessages$1(config, params, options) {
|
|
|
2824
2885
|
}
|
|
2825
2886
|
//#endregion
|
|
2826
2887
|
//#region ../../messaging/core/src/hooks/useSendMessage.ts
|
|
2827
|
-
function useSendMessage$1(
|
|
2888
|
+
function useSendMessage$1(conversationId, user, options) {
|
|
2889
|
+
const api = useMessagingApi();
|
|
2828
2890
|
const queryClient = useQueryClient();
|
|
2829
2891
|
const clientMessageIdRef = useRef(null);
|
|
2830
2892
|
return useMutation({
|
|
@@ -2847,8 +2909,8 @@ function useSendMessage$1(config, conversationId, user, options) {
|
|
|
2847
2909
|
},
|
|
2848
2910
|
client_message_id: clientMessageIdRef.current ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`)
|
|
2849
2911
|
};
|
|
2850
|
-
if (input.replyToId) return replyToMessage(
|
|
2851
|
-
return createMessage(
|
|
2912
|
+
if (input.replyToId) return api.replyToMessage(conversationId, input.replyToId, payload);
|
|
2913
|
+
return api.createMessage(conversationId, payload);
|
|
2852
2914
|
},
|
|
2853
2915
|
onMutate: async (input) => {
|
|
2854
2916
|
const rnd = Math.floor(Math.random() * 2147483647);
|
|
@@ -2958,8 +3020,9 @@ function useSendMessage$1(config, conversationId, user, options) {
|
|
|
2958
3020
|
}
|
|
2959
3021
|
//#endregion
|
|
2960
3022
|
//#region ../../messaging/core/src/hooks/useUpdateScheduledMessage.ts
|
|
2961
|
-
function useUpdateScheduledMessage$1(
|
|
3023
|
+
function useUpdateScheduledMessage$1(params, callbacks) {
|
|
2962
3024
|
const { conversationId, editScheduledId, editScheduledMessage, conversationType } = params;
|
|
3025
|
+
const api = useMessagingApi();
|
|
2963
3026
|
const queryClient = useQueryClient();
|
|
2964
3027
|
return useMutation({
|
|
2965
3028
|
mutationFn: async ({ body, attachments }) => {
|
|
@@ -2977,14 +3040,14 @@ function useUpdateScheduledMessage$1(config, params, callbacks) {
|
|
|
2977
3040
|
const scheduledAtISO = editScheduledMessage?.scheduled_at || editScheduledMessage?.metadata?.scheduled_at || null;
|
|
2978
3041
|
if (!scheduledAtISO) throw new Error("Missing scheduled time for the message");
|
|
2979
3042
|
const messageType = mapConversationTypeToMessageType(conversationType);
|
|
2980
|
-
await createScheduledMessage(
|
|
3043
|
+
await api.createScheduledMessage(conversationId, { scheduled_message: {
|
|
2981
3044
|
body,
|
|
2982
3045
|
theme: "inquiry",
|
|
2983
3046
|
scheduled_at: new Date(scheduledAtISO).toISOString(),
|
|
2984
3047
|
type: messageType,
|
|
2985
3048
|
attachments_attributes: mappedAttachments
|
|
2986
3049
|
} });
|
|
2987
|
-
await destroyScheduledMessage(
|
|
3050
|
+
await api.destroyScheduledMessage(conversationId, editScheduledId);
|
|
2988
3051
|
},
|
|
2989
3052
|
onSuccess: async () => {
|
|
2990
3053
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -3446,6 +3509,39 @@ function getConfig(name) {
|
|
|
3446
3509
|
}
|
|
3447
3510
|
//#endregion
|
|
3448
3511
|
//#region ../../messaging/core/src/cable/schemas.ts
|
|
3512
|
+
/**
|
|
3513
|
+
* Locally-defined schemas for WebSocket event parsing.
|
|
3514
|
+
* These replace imports from the API client to keep core dependency-free.
|
|
3515
|
+
*/
|
|
3516
|
+
const ReactionStatsSchema = z.record(z.string(), z.number());
|
|
3517
|
+
/**
|
|
3518
|
+
* Lenient schema for parsing incoming WebSocket message payloads.
|
|
3519
|
+
* Uses passthrough() to accept extra/unknown fields from the server.
|
|
3520
|
+
*/
|
|
3521
|
+
const MessageItemSchema = z.object({
|
|
3522
|
+
id: z.number(),
|
|
3523
|
+
body: z.string(),
|
|
3524
|
+
type: z.string(),
|
|
3525
|
+
created_at: z.string(),
|
|
3526
|
+
updated_at: z.string().nullable().optional(),
|
|
3527
|
+
deleted_at: z.string().nullable().optional(),
|
|
3528
|
+
edited_at: z.string().nullable().optional(),
|
|
3529
|
+
conversation_id: z.number(),
|
|
3530
|
+
status: z.string(),
|
|
3531
|
+
attachments: z.array(z.any()),
|
|
3532
|
+
reaction_stats: z.record(z.string(), z.number()).nullable(),
|
|
3533
|
+
reaction: z.string().nullable().optional(),
|
|
3534
|
+
metadata: z.any().optional(),
|
|
3535
|
+
emoji_only_count: z.number().optional(),
|
|
3536
|
+
mentioned_recipients: z.array(z.any()),
|
|
3537
|
+
sent_at: z.string().optional(),
|
|
3538
|
+
pinned: z.boolean().optional(),
|
|
3539
|
+
scheduled_at: z.string().optional(),
|
|
3540
|
+
replied_to_message: z.any().nullable().optional(),
|
|
3541
|
+
sender_recipient: z.any(),
|
|
3542
|
+
seen_by: z.unknown(),
|
|
3543
|
+
seen_by_count: z.unknown()
|
|
3544
|
+
}).passthrough();
|
|
3449
3545
|
const NewMessageSchema = z.strictObject({
|
|
3450
3546
|
type: z.literal("new_message"),
|
|
3451
3547
|
message: z.string(),
|
|
@@ -3999,7 +4095,7 @@ function createFluidFileUploader(apiKey) {
|
|
|
3999
4095
|
//#endregion
|
|
4000
4096
|
//#region ../../messaging/ui/src/app/MessagingAppProvider.tsx
|
|
4001
4097
|
const MessagingAppContext = createContext(null);
|
|
4002
|
-
function MessagingAppProvider({
|
|
4098
|
+
function MessagingAppProvider({ api, auth, websocketUrl, token, renderImage, renderProfileTrigger, onNavigate, initialRoute, children }) {
|
|
4003
4099
|
const [currentRoute, setCurrentRoute] = useState(() => initialRoute ?? { view: "index" });
|
|
4004
4100
|
const navigation = {
|
|
4005
4101
|
navigate: useCallback((route) => {
|
|
@@ -4015,17 +4111,19 @@ function MessagingAppProvider({ config, auth, websocketUrl, token, renderImage,
|
|
|
4015
4111
|
}, [currentRoute.view, onNavigate]),
|
|
4016
4112
|
currentRoute
|
|
4017
4113
|
};
|
|
4018
|
-
return /* @__PURE__ */ jsx(
|
|
4019
|
-
value:
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4114
|
+
return /* @__PURE__ */ jsx(MessagingApiProvider, {
|
|
4115
|
+
value: api,
|
|
4116
|
+
children: /* @__PURE__ */ jsx(MessagingAppContext.Provider, {
|
|
4117
|
+
value: {
|
|
4118
|
+
auth,
|
|
4119
|
+
websocketUrl,
|
|
4120
|
+
token,
|
|
4121
|
+
renderImage,
|
|
4122
|
+
renderProfileTrigger,
|
|
4123
|
+
navigation
|
|
4124
|
+
},
|
|
4125
|
+
children
|
|
4126
|
+
})
|
|
4029
4127
|
});
|
|
4030
4128
|
}
|
|
4031
4129
|
function useMessagingApp() {
|
|
@@ -4255,12 +4353,12 @@ function getPageTitleFromRoute(route) {
|
|
|
4255
4353
|
}
|
|
4256
4354
|
}
|
|
4257
4355
|
function MessagesTopBar({ renderSidebarTrigger, renderBreadcrumb, history, onToast }) {
|
|
4258
|
-
const {
|
|
4356
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
4259
4357
|
const navigation = useMessagingNavigation();
|
|
4260
4358
|
const currentRoute = navigation.currentRoute;
|
|
4261
4359
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
4262
4360
|
const conversationId = "conversationId" in currentRoute ? currentRoute.conversationId : 0;
|
|
4263
|
-
const { data: conversation } = useConversation(
|
|
4361
|
+
const { data: conversation } = useConversation(conversationId);
|
|
4264
4362
|
const currentUser = auth.currentUser;
|
|
4265
4363
|
const affiliateId = currentUser?.affiliateId;
|
|
4266
4364
|
const otherUsers = conversation?.recipients.filter((r) => r.receivable_id !== affiliateId);
|
|
@@ -4720,9 +4818,9 @@ function Channel({ item, renderImage }) {
|
|
|
4720
4818
|
})] });
|
|
4721
4819
|
}
|
|
4722
4820
|
function SidebarItem({ item }) {
|
|
4723
|
-
const {
|
|
4821
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
4724
4822
|
const navigation = useMessagingNavigation();
|
|
4725
|
-
const { data: meta } = useConversation(
|
|
4823
|
+
const { data: meta } = useConversation(item.id);
|
|
4726
4824
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
4727
4825
|
const currentUser = auth.currentUser;
|
|
4728
4826
|
if (!currentUser) return null;
|
|
@@ -4886,7 +4984,6 @@ function useScrollerRef() {
|
|
|
4886
4984
|
return context;
|
|
4887
4985
|
}
|
|
4888
4986
|
function MessagingLayout({ children, topBar, renderLockedDownlineItem, renderNewChannelSidebar, showAdminFeatures = true, canUseMyDownline = false, downlineData }) {
|
|
4889
|
-
const { config } = useMessagingApp();
|
|
4890
4987
|
const navigation = useMessagingNavigation();
|
|
4891
4988
|
const [isDragging, setIsDragging] = useState(false);
|
|
4892
4989
|
const sidebarRef = useRef(null);
|
|
@@ -4924,8 +5021,8 @@ function MessagingLayout({ children, topBar, renderLockedDownlineItem, renderNew
|
|
|
4924
5021
|
handleMouseMove,
|
|
4925
5022
|
handleMouseUp
|
|
4926
5023
|
]);
|
|
4927
|
-
const result = useConversations(
|
|
4928
|
-
const { data: announcementChannel, isLoading: isLoadingAnnouncementChannel } = useAnnouncementChannel(
|
|
5024
|
+
const result = useConversations({ per_page: CHANNELS_PER_FETCH });
|
|
5025
|
+
const { data: announcementChannel, isLoading: isLoadingAnnouncementChannel } = useAnnouncementChannel();
|
|
4929
5026
|
const hasMyDownline = downlineData?.hasDownline ?? false;
|
|
4930
5027
|
const myDownlineConversation = downlineData?.conversation ?? null;
|
|
4931
5028
|
const allEntries = result.data?.pages.flatMap((page) => page[1].items) ?? [];
|
|
@@ -38605,11 +38702,12 @@ function Timestamp({ children: timestamp, className = "", displayMode = "auto" }
|
|
|
38605
38702
|
}
|
|
38606
38703
|
//#endregion
|
|
38607
38704
|
//#region ../../messaging/ui/src/components/Message.tsx
|
|
38608
|
-
function Message({ conversation, message, isGrouped, highlightType = "auto",
|
|
38705
|
+
function Message({ conversation, message, isGrouped, highlightType = "auto", renderProfileTrigger, renderProfileContent, renderImage, onPinError, onCopyLinkSuccess, onCopyLinkError, getMessageLink }) {
|
|
38609
38706
|
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);
|
|
38610
38707
|
const [optionsOpen, setOptionsOpen] = useState(false);
|
|
38611
38708
|
const queryClient = useQueryClient();
|
|
38612
|
-
const { react, unreact } = useReaction(
|
|
38709
|
+
const { react, unreact } = useReaction();
|
|
38710
|
+
const api = useMessagingApi();
|
|
38613
38711
|
const addReaction = (reaction) => {
|
|
38614
38712
|
react({
|
|
38615
38713
|
conversationId: conversation.id,
|
|
@@ -38635,7 +38733,8 @@ function Message({ conversation, message, isGrouped, highlightType = "auto", con
|
|
|
38635
38733
|
});
|
|
38636
38734
|
setOptimisticPinned(newPinned);
|
|
38637
38735
|
try {
|
|
38638
|
-
|
|
38736
|
+
if (newPinned) await api.pinMessage(conversation.id, message.id);
|
|
38737
|
+
else await api.unpinMessage(conversation.id, message.id);
|
|
38639
38738
|
} catch {
|
|
38640
38739
|
applyMessagePinnedFlag({
|
|
38641
38740
|
queryClient,
|
|
@@ -39299,8 +39398,8 @@ const deepEqual = (obj1, obj2) => {
|
|
|
39299
39398
|
* Hook to manage conversation draft state using React Query as the single source of truth.
|
|
39300
39399
|
* Initializes with data from conversation metadata to avoid loading delays.
|
|
39301
39400
|
*/
|
|
39302
|
-
function useConversationDraft(
|
|
39303
|
-
const { data: conversationMetadata } = useConversation(
|
|
39401
|
+
function useConversationDraft(conversationId) {
|
|
39402
|
+
const { data: conversationMetadata } = useConversation(conversationId);
|
|
39304
39403
|
const queryClient = useQueryClient();
|
|
39305
39404
|
const isLocalDraft = !conversationId;
|
|
39306
39405
|
const abortFunctionsRef = useRef(/* @__PURE__ */ new Map());
|
|
@@ -39625,13 +39724,14 @@ function useIsDragging() {
|
|
|
39625
39724
|
}
|
|
39626
39725
|
//#endregion
|
|
39627
39726
|
//#region ../../messaging/ui/src/hooks/use-save-draft.ts
|
|
39628
|
-
function useSaveDraft(
|
|
39727
|
+
function useSaveDraft(conversationId, options) {
|
|
39629
39728
|
const queryClient = useQueryClient();
|
|
39729
|
+
const api = useMessagingApi();
|
|
39630
39730
|
return useMutation({
|
|
39631
39731
|
mutationFn: async () => {
|
|
39632
39732
|
if (!conversationId) return null;
|
|
39633
39733
|
try {
|
|
39634
|
-
await destroyDraft(
|
|
39734
|
+
await api.destroyDraft(conversationId);
|
|
39635
39735
|
} catch (err) {
|
|
39636
39736
|
if (err.status !== 404) console.warn("destroyDraft failed (continuing with save):", err);
|
|
39637
39737
|
}
|
|
@@ -39649,7 +39749,7 @@ function useSaveDraft(config, conversationId, options) {
|
|
|
39649
39749
|
const bodyIsEmpty = !latestBody.trim();
|
|
39650
39750
|
const hasFiles = latestAttachments.length > 0;
|
|
39651
39751
|
if (bodyIsEmpty && !hasFiles) return {};
|
|
39652
|
-
return await saveDraft(
|
|
39752
|
+
return await api.saveDraft(conversationId, { draft_message: {
|
|
39653
39753
|
body: latestBody,
|
|
39654
39754
|
attachments_attributes: hasFiles ? latestAttachments : void 0
|
|
39655
39755
|
} });
|
|
@@ -39685,10 +39785,10 @@ function useSaveDraft(config, conversationId, options) {
|
|
|
39685
39785
|
}
|
|
39686
39786
|
});
|
|
39687
39787
|
}
|
|
39688
|
-
function useDebouncedSaveDraft(
|
|
39788
|
+
function useDebouncedSaveDraft(conversationId, options) {
|
|
39689
39789
|
const debounceMs = options?.debounceMs ?? 1e3;
|
|
39690
39790
|
const debounceTimerRef = useRef(null);
|
|
39691
|
-
const saveDraftMutation = useSaveDraft(
|
|
39791
|
+
const saveDraftMutation = useSaveDraft(conversationId, { onError: options?.onError });
|
|
39692
39792
|
const debouncedSave = () => {
|
|
39693
39793
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
39694
39794
|
debounceTimerRef.current = setTimeout(() => {
|
|
@@ -52200,7 +52300,8 @@ function convertMentionTokensInJson(node, users) {
|
|
|
52200
52300
|
}
|
|
52201
52301
|
//#endregion
|
|
52202
52302
|
//#region ../../messaging/ui/src/components/composer/useComposerActions.ts
|
|
52203
|
-
function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFiles, completedFiles, clearDebounce, saveImmediately, draft, mode, sendWithEnter, conversationType, conversationId,
|
|
52303
|
+
function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFiles, completedFiles, clearDebounce, saveImmediately, draft, mode, sendWithEnter, conversationType, conversationId, onScheduleSuccess, onScheduleComplete, onScheduleError }) {
|
|
52304
|
+
const api = useMessagingApi();
|
|
52204
52305
|
const handleSend = useCallback(async () => {
|
|
52205
52306
|
if (!editor || !onSend) return;
|
|
52206
52307
|
if (isUploadingFiles) return;
|
|
@@ -52270,7 +52371,7 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
52270
52371
|
return parsed.success ? parsed.data : fallbackFileType;
|
|
52271
52372
|
};
|
|
52272
52373
|
const messageType = mapConversationTypeToMessageType(conversationType);
|
|
52273
|
-
await createScheduledMessage(
|
|
52374
|
+
await api.createScheduledMessage(conversationId, { scheduled_message: {
|
|
52274
52375
|
body: markdown,
|
|
52275
52376
|
theme: "inquiry",
|
|
52276
52377
|
scheduled_at: scheduledAt.toISOString(),
|
|
@@ -52301,7 +52402,7 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
52301
52402
|
completedFiles,
|
|
52302
52403
|
conversationId,
|
|
52303
52404
|
conversationType,
|
|
52304
|
-
|
|
52405
|
+
api,
|
|
52305
52406
|
draft,
|
|
52306
52407
|
saveImmediately,
|
|
52307
52408
|
onScheduleSuccess,
|
|
@@ -52313,11 +52414,11 @@ function useComposerActions({ editor, onSend, onSaveScheduledEdit, isUploadingFi
|
|
|
52313
52414
|
}
|
|
52314
52415
|
//#endregion
|
|
52315
52416
|
//#region ../../messaging/ui/src/components/composer/MessageComposer.tsx
|
|
52316
|
-
function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, placeholder = "Type your message...", disabled = false, mentionableUsers, conversationType, conversationId, saveDrafts = true, mode = "normal", initialEditData,
|
|
52417
|
+
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 }) {
|
|
52317
52418
|
const [sendWithEnter] = use_local_storage_state_default("messages-send-with-enter", { defaultValue: true });
|
|
52318
52419
|
const effectiveConversationId = saveDrafts ? conversationId : 0;
|
|
52319
|
-
const draft = useConversationDraft(
|
|
52320
|
-
const { debouncedSave, saveImmediately, isSaving, clearDebounce } = useDebouncedSaveDraft(
|
|
52420
|
+
const draft = useConversationDraft(effectiveConversationId);
|
|
52421
|
+
const { debouncedSave, saveImmediately, isSaving, clearDebounce } = useDebouncedSaveDraft(effectiveConversationId);
|
|
52321
52422
|
const { handleFiles, handleRemoveFile } = useDraftFileUpload({
|
|
52322
52423
|
uploader,
|
|
52323
52424
|
draftApi: {
|
|
@@ -52441,7 +52542,6 @@ function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, place
|
|
|
52441
52542
|
sendWithEnter,
|
|
52442
52543
|
conversationType,
|
|
52443
52544
|
conversationId,
|
|
52444
|
-
config,
|
|
52445
52545
|
onScheduleSuccess,
|
|
52446
52546
|
onScheduleComplete,
|
|
52447
52547
|
onScheduleError
|
|
@@ -52495,13 +52595,12 @@ function MessageComposer({ onSend, onScheduleSuccess, onSaveScheduledEdit, place
|
|
|
52495
52595
|
}
|
|
52496
52596
|
//#endregion
|
|
52497
52597
|
//#region ../../messaging/ui/src/app/MessagesViewComposer.tsx
|
|
52498
|
-
function MessagesViewComposer({ conversationId, conversationMetadata,
|
|
52598
|
+
function MessagesViewComposer({ conversationId, conversationMetadata, uploader, saveDrafts, isEditingScheduled, editScheduledMessage, mentionableUsers, renderImage, isLoading, user, isSendPending, isUpdatePending, onSend, onSaveScheduledEdit, onScheduleSuccess }) {
|
|
52499
52599
|
const input = /* @__PURE__ */ jsx(MessageComposer, {
|
|
52500
52600
|
conversationId,
|
|
52501
52601
|
conversationType: conversationMetadata?.type,
|
|
52502
52602
|
saveDrafts: isEditingScheduled ? false : saveDrafts,
|
|
52503
52603
|
mode: isEditingScheduled ? "editScheduled" : "normal",
|
|
52504
|
-
config,
|
|
52505
52604
|
uploader,
|
|
52506
52605
|
initialEditData: isEditingScheduled && editScheduledMessage ? {
|
|
52507
52606
|
body: editScheduledMessage.body || "",
|
|
@@ -52562,13 +52661,11 @@ function HeaderSlot({ render, conversation }) {
|
|
|
52562
52661
|
return /* @__PURE__ */ jsx(Fragment$1, { children: render(conversation) });
|
|
52563
52662
|
}
|
|
52564
52663
|
function ViewMessage({ conversation, message, isGrouped, highlightType = "auto", renderProfileTrigger, renderProfileContent, renderImage, onToast, getMessageLink }) {
|
|
52565
|
-
const { config } = useMessagingApp();
|
|
52566
52664
|
return /* @__PURE__ */ jsx(Message, {
|
|
52567
52665
|
conversation,
|
|
52568
52666
|
message,
|
|
52569
52667
|
isGrouped,
|
|
52570
52668
|
highlightType,
|
|
52571
|
-
config,
|
|
52572
52669
|
renderProfileTrigger,
|
|
52573
52670
|
renderProfileContent,
|
|
52574
52671
|
renderImage,
|
|
@@ -52610,7 +52707,7 @@ const getFallbackSystemMessageText = (senderName, conversationMetadata) => {
|
|
|
52610
52707
|
};
|
|
52611
52708
|
function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, renderProfileTrigger, renderProfileContent, onToast, getMessageLink, renderScheduledBanner, renderHeader, onMarkRead, uploader }) {
|
|
52612
52709
|
const queryClient = useQueryClient();
|
|
52613
|
-
const {
|
|
52710
|
+
const { auth, renderImage: contextRenderImage } = useMessagingApp();
|
|
52614
52711
|
const navigation = useMessagingNavigation();
|
|
52615
52712
|
const scrollerRef = useScrollerRef();
|
|
52616
52713
|
const renderImage = contextRenderImage ?? defaultRenderImage;
|
|
@@ -52649,7 +52746,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
52649
52746
|
scrollTo
|
|
52650
52747
|
]);
|
|
52651
52748
|
const { messages, fetchNextPage, hasNextPage, isFetchingNextPage, fetchPreviousPage, hasPreviousPage, isFetchingPreviousPage, isLoading: isLoadingMessages, isError, error: messagesError } = useMessages({
|
|
52652
|
-
config,
|
|
52653
52749
|
conversationId,
|
|
52654
52750
|
messageId: messageId ? Number(messageId) : void 0,
|
|
52655
52751
|
perPage: 50,
|
|
@@ -52659,7 +52755,7 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
52659
52755
|
const lastId = messages[messages.length - 1]?.id ?? "x";
|
|
52660
52756
|
return `${messages.length}:${String(lastId)}`;
|
|
52661
52757
|
}, [messages]);
|
|
52662
|
-
const { data: conversationMetadata, error: metadataError, isLoading: isLoadingMetadata } = useConversation(
|
|
52758
|
+
const { data: conversationMetadata, error: metadataError, isLoading: isLoadingMetadata } = useConversation(conversationId);
|
|
52663
52759
|
useEffect(() => {
|
|
52664
52760
|
baselineCapturedRef.current = false;
|
|
52665
52761
|
setEntryUnreadBaselineId(null);
|
|
@@ -52678,7 +52774,7 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
52678
52774
|
scrollToBottom,
|
|
52679
52775
|
resetKey: conversationId
|
|
52680
52776
|
});
|
|
52681
|
-
const scheduledHook = useScheduledMessages(
|
|
52777
|
+
const scheduledHook = useScheduledMessages(conversationId);
|
|
52682
52778
|
const scheduledData = scheduledHook.data;
|
|
52683
52779
|
const refetchScheduled = scheduledHook.refetch;
|
|
52684
52780
|
const editScheduledId = (() => {
|
|
@@ -52717,7 +52813,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
52717
52813
|
};
|
|
52718
52814
|
}, [currentUser]);
|
|
52719
52815
|
const sendMessageMutation = useSendMessage({
|
|
52720
|
-
config,
|
|
52721
52816
|
conversationId,
|
|
52722
52817
|
user,
|
|
52723
52818
|
onSuccess: () => {
|
|
@@ -52731,7 +52826,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
52731
52826
|
}
|
|
52732
52827
|
});
|
|
52733
52828
|
const updateScheduledMutation = useUpdateScheduledMessage({
|
|
52734
|
-
config,
|
|
52735
52829
|
conversationId,
|
|
52736
52830
|
editScheduledId,
|
|
52737
52831
|
editScheduledMessage,
|
|
@@ -53029,7 +53123,6 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
53029
53123
|
const composer = /* @__PURE__ */ jsx(MessagesViewComposer, {
|
|
53030
53124
|
conversationId,
|
|
53031
53125
|
conversationMetadata,
|
|
53032
|
-
config,
|
|
53033
53126
|
uploader,
|
|
53034
53127
|
saveDrafts,
|
|
53035
53128
|
isEditingScheduled,
|
|
@@ -53161,24 +53254,24 @@ function MessagesView({ conversationId, messageId, saveDrafts, noDateResults, re
|
|
|
53161
53254
|
}, "composer")]
|
|
53162
53255
|
}, "layer-1");
|
|
53163
53256
|
}
|
|
53164
|
-
function useMessages({
|
|
53165
|
-
return useMessages$1(
|
|
53257
|
+
function useMessages({ conversationId, messageId, perPage, onMarkRead }) {
|
|
53258
|
+
return useMessages$1({
|
|
53166
53259
|
conversationId,
|
|
53167
53260
|
messageId,
|
|
53168
53261
|
perPage
|
|
53169
53262
|
}, { onMarkRead });
|
|
53170
53263
|
}
|
|
53171
|
-
function useSendMessage({
|
|
53172
|
-
return useSendMessage$1(
|
|
53264
|
+
function useSendMessage({ conversationId, user, onSuccess, onError }) {
|
|
53265
|
+
return useSendMessage$1(conversationId, user, {
|
|
53173
53266
|
onSuccess,
|
|
53174
53267
|
onError
|
|
53175
53268
|
});
|
|
53176
53269
|
}
|
|
53177
|
-
function useScheduledMessages(
|
|
53178
|
-
return useScheduledMessages$1(
|
|
53270
|
+
function useScheduledMessages(conversationId) {
|
|
53271
|
+
return useScheduledMessages$1(conversationId, 50);
|
|
53179
53272
|
}
|
|
53180
|
-
function useUpdateScheduledMessage({
|
|
53181
|
-
return useUpdateScheduledMessage$1(
|
|
53273
|
+
function useUpdateScheduledMessage({ conversationId, editScheduledId, editScheduledMessage, conversationType, onSuccess, onError }) {
|
|
53274
|
+
return useUpdateScheduledMessage$1({
|
|
53182
53275
|
conversationId,
|
|
53183
53276
|
editScheduledId,
|
|
53184
53277
|
editScheduledMessage,
|
|
@@ -53191,13 +53284,12 @@ function useUpdateScheduledMessage({ config, conversationId, editScheduledId, ed
|
|
|
53191
53284
|
//#endregion
|
|
53192
53285
|
//#region ../../messaging/ui/src/app/PinnedMessagesView.tsx
|
|
53193
53286
|
function PinnedMessage({ conversation, message, onToast, renderProfileContent, getMessageLink }) {
|
|
53194
|
-
const {
|
|
53287
|
+
const { renderImage, renderProfileTrigger } = useMessagingApp();
|
|
53195
53288
|
return /* @__PURE__ */ jsx(Message, {
|
|
53196
53289
|
conversation,
|
|
53197
53290
|
message,
|
|
53198
53291
|
isGrouped: false,
|
|
53199
53292
|
highlightType: "never",
|
|
53200
|
-
config,
|
|
53201
53293
|
renderProfileTrigger: renderProfileTrigger ? ({ children }) => /* @__PURE__ */ jsx("div", { children }) : void 0,
|
|
53202
53294
|
renderProfileContent,
|
|
53203
53295
|
renderImage,
|
|
@@ -53208,12 +53300,12 @@ function PinnedMessage({ conversation, message, onToast, renderProfileContent, g
|
|
|
53208
53300
|
});
|
|
53209
53301
|
}
|
|
53210
53302
|
function PinnedMessagesView({ conversationId, onToast, renderProfileContent, getMessageLink }) {
|
|
53211
|
-
const { config } = useMessagingApp();
|
|
53212
53303
|
const { navigate } = useMessagingNavigation();
|
|
53304
|
+
const api = useMessagingApi();
|
|
53213
53305
|
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useInfiniteQuery({
|
|
53214
53306
|
queryKey: MESSAGES_QUERY_KEYS.listPinnedMessages(conversationId),
|
|
53215
53307
|
queryFn: async ({ pageParam }) => {
|
|
53216
|
-
return listPinnedMessages(
|
|
53308
|
+
return api.listPinnedMessages(conversationId, {
|
|
53217
53309
|
page: pageParam,
|
|
53218
53310
|
per_page: 50
|
|
53219
53311
|
});
|
|
@@ -53224,7 +53316,7 @@ function PinnedMessagesView({ conversationId, onToast, renderProfileContent, get
|
|
|
53224
53316
|
staleTime: PINNED_MESSAGES_STALE_TIME,
|
|
53225
53317
|
refetchInterval: PINNED_MESSAGES_REFETCH_INTERVAL
|
|
53226
53318
|
});
|
|
53227
|
-
const { data: conversationMetadata } = useConversation(
|
|
53319
|
+
const { data: conversationMetadata } = useConversation(conversationId);
|
|
53228
53320
|
const messages = useMemo(() => {
|
|
53229
53321
|
if (!data?.pages) return [];
|
|
53230
53322
|
return data.pages.flatMap((pageData) => pageData[1].items);
|
|
@@ -53841,10 +53933,9 @@ function getConversationIdFromRecipients(recipients) {
|
|
|
53841
53933
|
return conversationRecipient ? conversationRecipient.id : null;
|
|
53842
53934
|
}
|
|
53843
53935
|
function NewMessageView({ searchUsers, searchChannels, getCachedChannels, messagesViewProps }) {
|
|
53844
|
-
const { config } = useMessagingApp();
|
|
53845
53936
|
const [selectedRecipients, setSelectedRecipients] = useState([]);
|
|
53846
53937
|
const selectedConversationId = useMemo(() => getConversationIdFromRecipients(selectedRecipients), [selectedRecipients]);
|
|
53847
|
-
const { data: foundConversation } = useFindConversation(
|
|
53938
|
+
const { data: foundConversation } = useFindConversation({
|
|
53848
53939
|
recipients: selectedRecipients.length > 0 && !selectedConversationId ? selectedRecipients.map((r) => ({ uid: r.id })) : void 0,
|
|
53849
53940
|
enabled: selectedRecipients.length > 0 && !selectedConversationId
|
|
53850
53941
|
});
|
|
@@ -54104,9 +54195,10 @@ function formatScheduledShort(iso) {
|
|
|
54104
54195
|
}
|
|
54105
54196
|
}
|
|
54106
54197
|
function ScheduledMessageRow({ message, onToast }) {
|
|
54107
|
-
const {
|
|
54198
|
+
const { auth, renderImage } = useMessagingApp();
|
|
54108
54199
|
const { navigate } = useMessagingNavigation();
|
|
54109
54200
|
const queryClient = useQueryClient();
|
|
54201
|
+
const api = useMessagingApi();
|
|
54110
54202
|
const [openConfirm, setOpenConfirm] = useState(false);
|
|
54111
54203
|
const [openDeleteConfirm, setOpenDeleteConfirm] = useState(false);
|
|
54112
54204
|
const [openReschedule, setOpenReschedule] = useState(false);
|
|
@@ -54115,7 +54207,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
54115
54207
|
const conversation = message.conversation;
|
|
54116
54208
|
const rawConversationId = conversation?.id ?? message.conversation_id ?? 0;
|
|
54117
54209
|
const conversationId = typeof rawConversationId === "string" ? Number.parseInt(rawConversationId, 10) : Number(rawConversationId);
|
|
54118
|
-
const { data: fullConversation } = useConversation(
|
|
54210
|
+
const { data: fullConversation } = useConversation(conversationId);
|
|
54119
54211
|
const messageType = message.type;
|
|
54120
54212
|
const effectiveConversation = fullConversation || conversation;
|
|
54121
54213
|
const affiliateId = auth.currentUser?.affiliateId;
|
|
@@ -54153,12 +54245,13 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
54153
54245
|
metadata: a.metadata ?? void 0
|
|
54154
54246
|
};
|
|
54155
54247
|
});
|
|
54156
|
-
|
|
54157
|
-
|
|
54248
|
+
const typeFromConversation = mapConversationTypeToMessageType(fullConversation.type);
|
|
54249
|
+
await api.createMessage(conversationId, { message: {
|
|
54250
|
+
type: typeFromConversation,
|
|
54158
54251
|
body: message.body || "",
|
|
54159
54252
|
attachments_attributes: mappedAttachments
|
|
54160
54253
|
} });
|
|
54161
|
-
await destroyScheduledMessage(
|
|
54254
|
+
await api.destroyScheduledMessage(conversationId, message.id);
|
|
54162
54255
|
},
|
|
54163
54256
|
onSuccess: async () => {
|
|
54164
54257
|
await Promise.all([
|
|
@@ -54175,7 +54268,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
54175
54268
|
});
|
|
54176
54269
|
const deleteMutation = useMutation({
|
|
54177
54270
|
mutationFn: async () => {
|
|
54178
|
-
await destroyScheduledMessage(
|
|
54271
|
+
await api.destroyScheduledMessage(conversationId, message.id);
|
|
54179
54272
|
},
|
|
54180
54273
|
onSuccess: async () => {
|
|
54181
54274
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -54185,7 +54278,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
54185
54278
|
});
|
|
54186
54279
|
const rescheduleMutation = useMutation({
|
|
54187
54280
|
mutationFn: async (isoDate) => {
|
|
54188
|
-
await updateScheduledMessage(
|
|
54281
|
+
await api.updateScheduledMessage(conversationId, message.id, { scheduled_message: { scheduled_at: isoDate } });
|
|
54189
54282
|
},
|
|
54190
54283
|
onSuccess: async () => {
|
|
54191
54284
|
await Promise.all([queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listScheduledMessages(conversationId) }), queryClient.invalidateQueries({ queryKey: MESSAGES_QUERY_KEYS.listMyScheduledMessages() })]);
|
|
@@ -54335,8 +54428,7 @@ function ScheduledMessageRow({ message, onToast }) {
|
|
|
54335
54428
|
//#endregion
|
|
54336
54429
|
//#region ../../messaging/ui/src/app/ScheduledMessagesView.tsx
|
|
54337
54430
|
function ScheduledMessagesView({ onToast }) {
|
|
54338
|
-
const {
|
|
54339
|
-
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useMyScheduledMessages(config);
|
|
54431
|
+
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading, isError, error } = useMyScheduledMessages();
|
|
54340
54432
|
const messages = useMemo(() => {
|
|
54341
54433
|
if (!data?.pages) return [];
|
|
54342
54434
|
return data.pages.flatMap((p) => p[1].items);
|
|
@@ -54401,9 +54493,9 @@ function ScheduledMessagesView({ onToast }) {
|
|
|
54401
54493
|
}
|
|
54402
54494
|
//#endregion
|
|
54403
54495
|
//#region ../../messaging/ui/src/app/MessagingApp.tsx
|
|
54404
|
-
function MessagingApp({
|
|
54496
|
+
function MessagingApp({ api, auth, websocketUrl, token, renderImage, renderProfileTrigger, onNavigate, initialRoute, renderLockedDownlineItem, renderNewChannelSidebar, showAdminFeatures, canUseMyDownline, downlineData, topBar, messagesViewProps, newMessageViewProps, onToast, getMessageLink, renderProfileContent, children }) {
|
|
54405
54497
|
return /* @__PURE__ */ jsxs(MessagingAppProvider, {
|
|
54406
|
-
|
|
54498
|
+
api,
|
|
54407
54499
|
auth,
|
|
54408
54500
|
websocketUrl,
|
|
54409
54501
|
token,
|
|
@@ -54489,14 +54581,14 @@ function defaultToast(message, type) {
|
|
|
54489
54581
|
}
|
|
54490
54582
|
function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlOverride, background, textColor, accentColor, padding, borderRadius, ...divProps }) {
|
|
54491
54583
|
const { config } = useFluidContext();
|
|
54492
|
-
const { apiConfig, websocketUrl, token } = useMessagingConfig();
|
|
54584
|
+
const { apiConfig, messagingApi, websocketUrl, token } = useMessagingConfig();
|
|
54493
54585
|
const messagingAuth = useMessagingAuth();
|
|
54494
54586
|
const effectiveApiKey = filestackApiKey ?? config.filestackApiKey;
|
|
54495
54587
|
const uploader = useMemo(() => createFluidFileUploader(effectiveApiKey), [effectiveApiKey]);
|
|
54496
54588
|
const effectiveWsUrl = websocketUrlOverride ?? websocketUrl;
|
|
54497
54589
|
const effectiveToast = onToast ?? defaultToast;
|
|
54498
54590
|
const searchUsers = useCallback(async (query) => {
|
|
54499
|
-
return ((await listConnectedRecipients(
|
|
54591
|
+
return ((await messagingApi.listConnectedRecipients({
|
|
54500
54592
|
filterrific: { search_query: query },
|
|
54501
54593
|
per_page: 10,
|
|
54502
54594
|
page: 1
|
|
@@ -54516,7 +54608,7 @@ function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlO
|
|
|
54516
54608
|
conversationName: name
|
|
54517
54609
|
};
|
|
54518
54610
|
});
|
|
54519
|
-
}, [
|
|
54611
|
+
}, [messagingApi]);
|
|
54520
54612
|
const searchChannels = useCallback(async (query) => {
|
|
54521
54613
|
return (await searchConversations(apiConfig, { filterrific: { search_query: query } }) ?? []).map((channel) => {
|
|
54522
54614
|
const { text: nameWithoutEmoji } = extractEmoji(channel.name);
|
|
@@ -54559,7 +54651,7 @@ function MessagingScreen({ onToast, filestackApiKey, websocketUrl: websocketUrlO
|
|
|
54559
54651
|
...divProps,
|
|
54560
54652
|
className: `h-full ${divProps.className ?? ""}`,
|
|
54561
54653
|
children: /* @__PURE__ */ jsx(MessagingApp, {
|
|
54562
|
-
|
|
54654
|
+
api: messagingApi,
|
|
54563
54655
|
auth: messagingAuth,
|
|
54564
54656
|
websocketUrl: effectiveWsUrl,
|
|
54565
54657
|
token,
|
|
@@ -54590,4 +54682,4 @@ const messagingScreenPropertySchema = {
|
|
|
54590
54682
|
//#endregion
|
|
54591
54683
|
export { useMessagingAuth as a, useMessagingConfig as i, messagingScreenPropertySchema as n, createFluidFileUploader as r, MessagingScreen as t };
|
|
54592
54684
|
|
|
54593
|
-
//# sourceMappingURL=MessagingScreen-
|
|
54685
|
+
//# sourceMappingURL=MessagingScreen-CBuI3fu6.mjs.map
|