@acorex/components 20.8.20 → 20.8.22
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/conversation2/index.d.ts +59 -27
- package/fesm2022/acorex-components-conversation2.mjs +1115 -1032
- package/fesm2022/acorex-components-conversation2.mjs.map +1 -1
- package/fesm2022/acorex-components-scheduler.mjs +1 -4
- package/fesm2022/acorex-components-scheduler.mjs.map +1 -1
- package/package.json +39 -39
- package/scheduler/index.d.ts +1 -3
|
@@ -7,10 +7,10 @@ import { InjectionToken, signal, computed, inject, Injectable, input, output, Ch
|
|
|
7
7
|
import { AXDialogService } from '@acorex/components/dialog';
|
|
8
8
|
import { AXPopupService } from '@acorex/components/popup';
|
|
9
9
|
import * as i3 from '@acorex/core/translation';
|
|
10
|
-
import { AXTranslationService, AXTranslationModule
|
|
10
|
+
import { translateSync, AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
|
|
11
11
|
import { Subject, BehaviorSubject, Observable, filter, firstValueFrom, takeUntil, catchError, EMPTY } from 'rxjs';
|
|
12
12
|
import { AXUploaderBrowseDirective, AXUploaderZoneDirective, AXUploaderService } from '@acorex/cdk/uploader';
|
|
13
|
-
import { createFileTypeMetadata, AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } from '@acorex/core/file';
|
|
13
|
+
import { createFileTypeMetadata, AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText, runFileTypeOpen } from '@acorex/core/file';
|
|
14
14
|
import * as i1$2 from '@acorex/components/progress-bar';
|
|
15
15
|
import { AXProgressBarModule } from '@acorex/components/progress-bar';
|
|
16
16
|
import { AXToastService } from '@acorex/components/toast';
|
|
@@ -1039,6 +1039,410 @@ function validateLongitude(longitude) {
|
|
|
1039
1039
|
return { valid: true };
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
|
+
class AXConversationMessageUtilsService {
|
|
1043
|
+
/**
|
|
1044
|
+
* Normalize optional avatar/icon values so empty or whitespace-only strings
|
|
1045
|
+
* are treated as missing data.
|
|
1046
|
+
*/
|
|
1047
|
+
static normalizeOptionalMediaValue(value) {
|
|
1048
|
+
if (typeof value !== 'string') {
|
|
1049
|
+
return undefined;
|
|
1050
|
+
}
|
|
1051
|
+
const normalized = value.trim();
|
|
1052
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Get conversation avatar image URL.
|
|
1056
|
+
*/
|
|
1057
|
+
static getConversationAvatar(conversation) {
|
|
1058
|
+
return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.avatar);
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Font Awesome icon class(es) for a conversation when there is no avatar image.
|
|
1062
|
+
*/
|
|
1063
|
+
static getConversationAvatarIcon(conversation) {
|
|
1064
|
+
if (AXConversationMessageUtilsService.getConversationAvatar(conversation)) {
|
|
1065
|
+
return undefined;
|
|
1066
|
+
}
|
|
1067
|
+
return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.icon);
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Get sender name from message
|
|
1071
|
+
*/
|
|
1072
|
+
static getSenderName(message, conversation) {
|
|
1073
|
+
const participant = conversation.participants.find((p) => p.id === message.senderId);
|
|
1074
|
+
return participant?.name || translateSync('@acorex:chat.fallbacks.unknown-user');
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Get sender avatar image URL (takes precedence over {@link getSenderAvatarIcon}).
|
|
1078
|
+
*/
|
|
1079
|
+
static getSenderAvatar(message, conversation) {
|
|
1080
|
+
const participant = conversation.participants.find((p) => p.id === message.senderId);
|
|
1081
|
+
return AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar);
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Font Awesome icon class(es) for the sender when there is no avatar image:
|
|
1085
|
+
* participant `icon` first, then conversation-level `icon`.
|
|
1086
|
+
*/
|
|
1087
|
+
static getSenderAvatarIcon(message, conversation) {
|
|
1088
|
+
const participant = conversation.participants.find((p) => p.id === message.senderId);
|
|
1089
|
+
if (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar)) {
|
|
1090
|
+
return undefined;
|
|
1091
|
+
}
|
|
1092
|
+
return (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.icon) ??
|
|
1093
|
+
AXConversationMessageUtilsService.getConversationAvatarIcon(conversation));
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Get initials from name
|
|
1097
|
+
*/
|
|
1098
|
+
static getInitials(name) {
|
|
1099
|
+
if (!name)
|
|
1100
|
+
return '?';
|
|
1101
|
+
return name
|
|
1102
|
+
.split(' ')
|
|
1103
|
+
.map((n) => n[0])
|
|
1104
|
+
.join('')
|
|
1105
|
+
.toUpperCase()
|
|
1106
|
+
.substring(0, 2);
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Type guard for text payload
|
|
1110
|
+
*/
|
|
1111
|
+
static isTextPayload(payload) {
|
|
1112
|
+
return 'text' in payload && typeof payload.text === 'string';
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Get message text content
|
|
1116
|
+
*/
|
|
1117
|
+
static getMessageText(message) {
|
|
1118
|
+
if (message.type === 'text' && AXConversationMessageUtilsService.isTextPayload(message.payload)) {
|
|
1119
|
+
return message.payload.text;
|
|
1120
|
+
}
|
|
1121
|
+
return `[${message.type}]`;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Format message preview text
|
|
1125
|
+
*/
|
|
1126
|
+
static getPreviewText(message, maxLength = 50) {
|
|
1127
|
+
// Handle different message types
|
|
1128
|
+
switch (message.type) {
|
|
1129
|
+
case 'text': {
|
|
1130
|
+
const textPayload = message.payload;
|
|
1131
|
+
const text = textPayload.text || '';
|
|
1132
|
+
const truncatedText = text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
|
|
1133
|
+
return {
|
|
1134
|
+
value: truncatedText,
|
|
1135
|
+
type: 'text',
|
|
1136
|
+
icon: 'fa-light fa-message',
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
case 'image': {
|
|
1140
|
+
const imagePayload = normalizeImagePayload(message.payload);
|
|
1141
|
+
const first = imagePayload.images[0];
|
|
1142
|
+
const label = imagePayload.caption?.trim() ||
|
|
1143
|
+
(imagePayload.images && imagePayload.images.length > 1
|
|
1144
|
+
? `${imagePayload.images.length} images`
|
|
1145
|
+
: '');
|
|
1146
|
+
return {
|
|
1147
|
+
value: label || first?.thumbnailUrl || first?.url || '',
|
|
1148
|
+
type: 'image',
|
|
1149
|
+
icon: 'fa-light fa-image',
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
case 'video': {
|
|
1153
|
+
const videoPayload = normalizeVideoPayload(message.payload);
|
|
1154
|
+
const first = videoPayload.videos[0];
|
|
1155
|
+
const label = videoPayload.caption?.trim() ||
|
|
1156
|
+
(videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
|
|
1157
|
+
return {
|
|
1158
|
+
value: label || first?.url || '',
|
|
1159
|
+
type: 'video',
|
|
1160
|
+
icon: 'fa-light fa-video',
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
case 'audio': {
|
|
1164
|
+
const audioPayload = normalizeAudioPayload(message.payload);
|
|
1165
|
+
const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
|
|
1166
|
+
const first = audioPayload.audios[0];
|
|
1167
|
+
const label = audioPayload.caption?.trim() ||
|
|
1168
|
+
(audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
|
|
1169
|
+
return {
|
|
1170
|
+
value: label || first?.url || '',
|
|
1171
|
+
type: 'audio',
|
|
1172
|
+
icon: 'fa-light fa-music',
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
case 'voice': {
|
|
1176
|
+
const voicePayload = message.payload;
|
|
1177
|
+
return {
|
|
1178
|
+
value: voicePayload.url || '',
|
|
1179
|
+
type: 'voice',
|
|
1180
|
+
icon: 'fa-light fa-microphone',
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
case 'file': {
|
|
1184
|
+
const filePayload = normalizeFilePayload(message.payload);
|
|
1185
|
+
const names = filePayload.files.map((f) => f.name).join(', ');
|
|
1186
|
+
const label = filePayload.caption?.trim() ||
|
|
1187
|
+
(filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
|
|
1188
|
+
return {
|
|
1189
|
+
value: label || names,
|
|
1190
|
+
type: 'file',
|
|
1191
|
+
icon: 'fa-light fa-file',
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
case 'location': {
|
|
1195
|
+
const locationPayload = message.payload;
|
|
1196
|
+
return {
|
|
1197
|
+
value: locationPayload.latitude && locationPayload.longitude
|
|
1198
|
+
? `${locationPayload.latitude},${locationPayload.longitude}`
|
|
1199
|
+
: '',
|
|
1200
|
+
type: 'location',
|
|
1201
|
+
icon: 'fa-light fa-location-dot',
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
case 'sticker': {
|
|
1205
|
+
const stickerPayload = message.payload;
|
|
1206
|
+
return {
|
|
1207
|
+
value: stickerPayload.url || '',
|
|
1208
|
+
type: 'sticker',
|
|
1209
|
+
icon: 'fa-light fa-face-smile',
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
default:
|
|
1213
|
+
return {
|
|
1214
|
+
value: '',
|
|
1215
|
+
type: message.type,
|
|
1216
|
+
icon: 'fa-light fa-message',
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Check if message is from current user
|
|
1222
|
+
*/
|
|
1223
|
+
static isOwnMessage(message, currentUserId) {
|
|
1224
|
+
return message.senderId === currentUserId;
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Get message status icon class (Font Awesome)
|
|
1228
|
+
*/
|
|
1229
|
+
static getStatusIcon(message) {
|
|
1230
|
+
switch (message.status) {
|
|
1231
|
+
case 'sending':
|
|
1232
|
+
return 'fa-light fa-clock';
|
|
1233
|
+
case 'sent':
|
|
1234
|
+
return 'fa-light fa-check';
|
|
1235
|
+
case 'delivered':
|
|
1236
|
+
return 'fa-light fa-check-double';
|
|
1237
|
+
case 'read':
|
|
1238
|
+
return 'fa-light fa-check-double';
|
|
1239
|
+
case 'failed':
|
|
1240
|
+
return 'fa-light fa-circle-exclamation';
|
|
1241
|
+
default:
|
|
1242
|
+
return 'fa-light fa-check';
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Check if message should show avatar
|
|
1247
|
+
*/
|
|
1248
|
+
static shouldShowAvatar(message, previousMessage, conversation) {
|
|
1249
|
+
// Always show avatar for group conversations
|
|
1250
|
+
if (conversation.type === 'group' || conversation.type === 'channel') {
|
|
1251
|
+
// Don't show if same sender as previous message within 5 minutes
|
|
1252
|
+
if (previousMessage && previousMessage.senderId === message.senderId) {
|
|
1253
|
+
const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
|
|
1254
|
+
return timeDiff > 5 * 60 * 1000; // 5 minutes
|
|
1255
|
+
}
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Group messages by sender for consecutive messages
|
|
1262
|
+
*/
|
|
1263
|
+
static shouldGroupWithPrevious(message, previousMessage) {
|
|
1264
|
+
if (!previousMessage)
|
|
1265
|
+
return false;
|
|
1266
|
+
// Same sender
|
|
1267
|
+
if (message.senderId !== previousMessage.senderId)
|
|
1268
|
+
return false;
|
|
1269
|
+
// Within 5 minutes
|
|
1270
|
+
const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
|
|
1271
|
+
return timeDiff < 5 * 60 * 1000;
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Get conversation status for avatar (private conversations only)
|
|
1275
|
+
*/
|
|
1276
|
+
static getConversationStatus(conversation) {
|
|
1277
|
+
if (conversation.type === 'private') {
|
|
1278
|
+
return conversation.status.presence;
|
|
1279
|
+
}
|
|
1280
|
+
return undefined;
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get typing indicator text for conversation
|
|
1284
|
+
*/
|
|
1285
|
+
static getTypingText(conversation) {
|
|
1286
|
+
const typingUsers = conversation.status.typingUsers;
|
|
1287
|
+
if (typingUsers.length === 0)
|
|
1288
|
+
return '';
|
|
1289
|
+
if (conversation.type === 'private') {
|
|
1290
|
+
return translateSync('@acorex:chat.status.typing');
|
|
1291
|
+
}
|
|
1292
|
+
const firstUser = conversation.participants.find((p) => p.id === typingUsers[0]);
|
|
1293
|
+
if (typingUsers.length === 1) {
|
|
1294
|
+
return translateSync('@acorex:chat.status.user-is-typing', {
|
|
1295
|
+
params: { userName: firstUser?.name || translateSync('@acorex:chat.fallbacks.someone') },
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
return translateSync('@acorex:chat.status.people-typing', { params: { count: typingUsers.length } });
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Format last seen time
|
|
1302
|
+
*/
|
|
1303
|
+
static formatLastSeen(date) {
|
|
1304
|
+
const now = new Date();
|
|
1305
|
+
const diff = now.getTime() - date.getTime();
|
|
1306
|
+
const seconds = Math.floor(diff / 1000);
|
|
1307
|
+
if (seconds < 60)
|
|
1308
|
+
return translateSync('@acorex:chat.time.just-now');
|
|
1309
|
+
if (seconds < 3600)
|
|
1310
|
+
return translateSync('@acorex:chat.time.minutes-ago', { params: { count: Math.floor(seconds / 60) } });
|
|
1311
|
+
if (seconds < 86400)
|
|
1312
|
+
return translateSync('@acorex:chat.time.hours-ago', { params: { count: Math.floor(seconds / 3600) } });
|
|
1313
|
+
if (seconds < 604800)
|
|
1314
|
+
return translateSync('@acorex:chat.time.days-ago', { params: { count: Math.floor(seconds / 86400) } });
|
|
1315
|
+
return date.toLocaleDateString();
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Get conversation subtitle (status or member count)
|
|
1319
|
+
*/
|
|
1320
|
+
static getConversationSubtitle(conversation) {
|
|
1321
|
+
if (conversation.status.isTyping) {
|
|
1322
|
+
return AXConversationMessageUtilsService.getTypingText(conversation);
|
|
1323
|
+
}
|
|
1324
|
+
switch (conversation.type) {
|
|
1325
|
+
case 'private':
|
|
1326
|
+
if (conversation.status.presence === 'online') {
|
|
1327
|
+
return translateSync('@acorex:chat.status.online');
|
|
1328
|
+
}
|
|
1329
|
+
if (conversation.status.lastSeen) {
|
|
1330
|
+
return translateSync('@acorex:chat.status.last-seen', {
|
|
1331
|
+
params: { value: AXConversationMessageUtilsService.formatLastSeen(conversation.status.lastSeen) },
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
return translateSync('@acorex:chat.status.offline');
|
|
1335
|
+
case 'group':
|
|
1336
|
+
return translateSync('@acorex:chat.members.count', { params: { count: conversation.participants.length } });
|
|
1337
|
+
case 'channel':
|
|
1338
|
+
return translateSync('@acorex:chat.members.subscribers-count', { params: { count: conversation.participants.length } });
|
|
1339
|
+
case 'bot':
|
|
1340
|
+
return translateSync('@acorex:chat.bot');
|
|
1341
|
+
default:
|
|
1342
|
+
return '';
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/** Other participant in a private chat (excludes the current user). */
|
|
1348
|
+
function resolvePrivatePeerUserId(conversation, currentUserId) {
|
|
1349
|
+
if (conversation.type !== 'private') {
|
|
1350
|
+
return undefined;
|
|
1351
|
+
}
|
|
1352
|
+
const currentId = currentUserId ?? 'current-user';
|
|
1353
|
+
return conversation.participants.find((participant) => participant.id !== currentId)?.id;
|
|
1354
|
+
}
|
|
1355
|
+
/** Whether `auto` kind should render a user avatar for this conversation. */
|
|
1356
|
+
function shouldUseUserAvatarForConversation(conversation, currentUserId) {
|
|
1357
|
+
return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
|
|
1358
|
+
}
|
|
1359
|
+
function resolveUserAvatarDisplay(userId, conversation, message) {
|
|
1360
|
+
if (message && conversation) {
|
|
1361
|
+
return {
|
|
1362
|
+
name: AXConversationMessageUtilsService.getSenderName(message, conversation),
|
|
1363
|
+
avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
|
|
1364
|
+
icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
|
|
1365
|
+
};
|
|
1366
|
+
}
|
|
1367
|
+
const participant = conversation?.participants.find((p) => p.id === userId);
|
|
1368
|
+
return {
|
|
1369
|
+
name: participant?.name ?? userId,
|
|
1370
|
+
avatar: participant?.avatar,
|
|
1371
|
+
icon: participant?.icon,
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
function resolveConversationAvatarDisplay(conversation, currentUserId) {
|
|
1375
|
+
const title = currentUserId !== undefined
|
|
1376
|
+
? resolveConversationTitleForViewer(conversation, currentUserId)
|
|
1377
|
+
: conversation.title;
|
|
1378
|
+
return {
|
|
1379
|
+
name: title,
|
|
1380
|
+
avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
|
|
1381
|
+
icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const GENERIC_PRIVATE_TITLES = new Set(['', 'new chat', 'new conversation']);
|
|
1386
|
+
/** True when the stored title is a placeholder, not a user-defined name. */
|
|
1387
|
+
function isGenericPrivateConversationTitle(title) {
|
|
1388
|
+
return GENERIC_PRIVATE_TITLES.has((title ?? '').trim().toLowerCase());
|
|
1389
|
+
}
|
|
1390
|
+
/** Other participant in a private 1v1 chat (excludes the current viewer). */
|
|
1391
|
+
function resolvePrivatePeerParticipant(conversation, currentUserId) {
|
|
1392
|
+
if (conversation.type !== 'private') {
|
|
1393
|
+
return undefined;
|
|
1394
|
+
}
|
|
1395
|
+
const peerId = resolvePrivatePeerUserId(conversation, currentUserId);
|
|
1396
|
+
if (!peerId) {
|
|
1397
|
+
return undefined;
|
|
1398
|
+
}
|
|
1399
|
+
return conversation.participants.find((participant) => participant.id === peerId);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Resolves the display title for the current viewer.
|
|
1403
|
+
* Private 1v1 chats show the other participant's name when no custom title is set.
|
|
1404
|
+
*/
|
|
1405
|
+
function resolveConversationTitleForViewer(conversation, currentUserId) {
|
|
1406
|
+
if (conversation.type !== 'private') {
|
|
1407
|
+
return conversation.title;
|
|
1408
|
+
}
|
|
1409
|
+
const peer = resolvePrivatePeerParticipant(conversation, currentUserId);
|
|
1410
|
+
if (peer?.name) {
|
|
1411
|
+
return peer.name;
|
|
1412
|
+
}
|
|
1413
|
+
if (isGenericPrivateConversationTitle(conversation.title)) {
|
|
1414
|
+
return conversation.title || 'New Chat';
|
|
1415
|
+
}
|
|
1416
|
+
return conversation.title;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Returns a viewer-scoped copy of a conversation with dynamic private title/avatar/icon.
|
|
1420
|
+
* Does not mutate the source object.
|
|
1421
|
+
*/
|
|
1422
|
+
function resolveConversationForViewer(conversation, currentUserId) {
|
|
1423
|
+
if (conversation.type !== 'private' || !currentUserId) {
|
|
1424
|
+
return conversation;
|
|
1425
|
+
}
|
|
1426
|
+
const peer = resolvePrivatePeerParticipant(conversation, currentUserId);
|
|
1427
|
+
if (!peer) {
|
|
1428
|
+
return conversation;
|
|
1429
|
+
}
|
|
1430
|
+
const title = resolveConversationTitleForViewer(conversation, currentUserId);
|
|
1431
|
+
const avatar = conversation.avatar ?? peer.avatar;
|
|
1432
|
+
const icon = conversation.icon ?? peer.icon;
|
|
1433
|
+
if (title === conversation.title &&
|
|
1434
|
+
avatar === conversation.avatar &&
|
|
1435
|
+
icon === conversation.icon) {
|
|
1436
|
+
return conversation;
|
|
1437
|
+
}
|
|
1438
|
+
return {
|
|
1439
|
+
...conversation,
|
|
1440
|
+
title,
|
|
1441
|
+
avatar,
|
|
1442
|
+
icon,
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1042
1446
|
/**
|
|
1043
1447
|
* In-memory conversation and message graph (signal-based).
|
|
1044
1448
|
* Plain class — not DI-registered; instantiated by `AXConversationService`.
|
|
@@ -2554,9 +2958,22 @@ class AXComposerService {
|
|
|
2554
2958
|
await this.conversationService.sendTypingIndicator(conversationId);
|
|
2555
2959
|
}
|
|
2556
2960
|
async sendMessage(command, options) {
|
|
2961
|
+
this.clearComposerInput(command.conversationId);
|
|
2557
2962
|
await this.conversationService.sendMessage(command, options);
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Resets composer input immediately when a send starts so upload/API work continues in the background.
|
|
2966
|
+
*/
|
|
2967
|
+
clearComposerInput(conversationId) {
|
|
2968
|
+
this.draftText.set('');
|
|
2969
|
+
this.attachments.set([]);
|
|
2558
2970
|
this.hideComponent();
|
|
2559
|
-
this.
|
|
2971
|
+
this._replyMessage.set(null);
|
|
2972
|
+
this._editMessage.set(null);
|
|
2973
|
+
if (conversationId) {
|
|
2974
|
+
this.clearDraft(conversationId);
|
|
2975
|
+
}
|
|
2976
|
+
this.focusComposer();
|
|
2560
2977
|
}
|
|
2561
2978
|
/**
|
|
2562
2979
|
* Show a component in the composer (full-width mode)
|
|
@@ -3560,17 +3977,12 @@ class AXAudioPickerComponent {
|
|
|
3560
3977
|
},
|
|
3561
3978
|
};
|
|
3562
3979
|
this.retainUploadedOnDismiss = true;
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
this.dismissPicker();
|
|
3568
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
catch {
|
|
3980
|
+
const popup = this.__popup__();
|
|
3981
|
+
this.dismissPicker();
|
|
3982
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
3983
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
3572
3984
|
this.retainUploadedOnDismiss = false;
|
|
3573
|
-
}
|
|
3985
|
+
});
|
|
3574
3986
|
}
|
|
3575
3987
|
canSend() {
|
|
3576
3988
|
const list = this.audioFiles();
|
|
@@ -3978,18 +4390,13 @@ class AXFilePickerComponent {
|
|
|
3978
4390
|
caption: this.caption.trim() || undefined,
|
|
3979
4391
|
},
|
|
3980
4392
|
};
|
|
3981
|
-
this.retainUploadedOnDismiss = true;
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
this.dismissPicker();
|
|
3987
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
3988
|
-
}
|
|
3989
|
-
}
|
|
3990
|
-
catch {
|
|
4393
|
+
this.retainUploadedOnDismiss = true;
|
|
4394
|
+
const popup = this.__popup__();
|
|
4395
|
+
this.dismissPicker();
|
|
4396
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
4397
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
3991
4398
|
this.retainUploadedOnDismiss = false;
|
|
3992
|
-
}
|
|
4399
|
+
});
|
|
3993
4400
|
}
|
|
3994
4401
|
updateFileProgress(index, progress, uploading, uploaded = false, uploadedUrl, mediaId, error) {
|
|
3995
4402
|
if (this.uploadsDismissed) {
|
|
@@ -4441,17 +4848,12 @@ class AXImagePickerComponent {
|
|
|
4441
4848
|
},
|
|
4442
4849
|
};
|
|
4443
4850
|
this.retainUploadedOnDismiss = true;
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
this.dismissPicker();
|
|
4449
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
4450
|
-
}
|
|
4451
|
-
}
|
|
4452
|
-
catch {
|
|
4851
|
+
const popup = this.__popup__();
|
|
4852
|
+
this.dismissPicker();
|
|
4853
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
4854
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
4453
4855
|
this.retainUploadedOnDismiss = false;
|
|
4454
|
-
}
|
|
4856
|
+
});
|
|
4455
4857
|
}
|
|
4456
4858
|
canSend() {
|
|
4457
4859
|
const list = this.images();
|
|
@@ -4707,8 +5109,10 @@ class AXLocationPickerComponent {
|
|
|
4707
5109
|
type: 'location',
|
|
4708
5110
|
payload: location,
|
|
4709
5111
|
};
|
|
4710
|
-
await this.composerService.sendMessage(command);
|
|
4711
5112
|
this.cancel();
|
|
5113
|
+
void this.composerService.sendMessage(command).catch((error) => {
|
|
5114
|
+
console.error('Failed to send location:', error);
|
|
5115
|
+
});
|
|
4712
5116
|
}
|
|
4713
5117
|
cancel() {
|
|
4714
5118
|
// Reset to default location
|
|
@@ -4957,17 +5361,12 @@ class AXVideoPickerComponent {
|
|
|
4957
5361
|
},
|
|
4958
5362
|
};
|
|
4959
5363
|
this.retainUploadedOnDismiss = true;
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
this.dismissPicker();
|
|
4965
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
4966
|
-
}
|
|
4967
|
-
}
|
|
4968
|
-
catch {
|
|
5364
|
+
const popup = this.__popup__();
|
|
5365
|
+
this.dismissPicker();
|
|
5366
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
5367
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
4969
5368
|
this.retainUploadedOnDismiss = false;
|
|
4970
|
-
}
|
|
5369
|
+
});
|
|
4971
5370
|
}
|
|
4972
5371
|
canSend() {
|
|
4973
5372
|
const list = this.videos();
|
|
@@ -5374,44 +5773,44 @@ class AXVoiceRecorderComponent {
|
|
|
5374
5773
|
const uploadSignal = this.uploadAbortController.signal;
|
|
5375
5774
|
this.isUploading.set(true);
|
|
5376
5775
|
this.uploadProgress.set(0);
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5776
|
+
const command = {
|
|
5777
|
+
conversationId: conversation.id,
|
|
5778
|
+
type: 'voice',
|
|
5779
|
+
payload: {
|
|
5380
5780
|
type: 'voice',
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
},
|
|
5399
|
-
signal: uploadSignal,
|
|
5781
|
+
url: '',
|
|
5782
|
+
duration: this.recordingDuration(),
|
|
5783
|
+
size: audioBlob.size,
|
|
5784
|
+
mimeType: 'audio/webm',
|
|
5785
|
+
},
|
|
5786
|
+
};
|
|
5787
|
+
this.composerService.hideComponent();
|
|
5788
|
+
void this.composerService
|
|
5789
|
+
.sendMessage(command, {
|
|
5790
|
+
media: {
|
|
5791
|
+
source: audioBlob,
|
|
5792
|
+
fileName: `voice-${Date.now()}.webm`,
|
|
5793
|
+
mimeType: 'audio/webm',
|
|
5794
|
+
onProgress: (percent) => {
|
|
5795
|
+
if (!uploadSignal.aborted) {
|
|
5796
|
+
this.uploadProgress.set(percent);
|
|
5797
|
+
}
|
|
5400
5798
|
},
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5799
|
+
signal: uploadSignal,
|
|
5800
|
+
},
|
|
5801
|
+
})
|
|
5802
|
+
.catch((error) => {
|
|
5404
5803
|
if (!isUploadAborted(error, uploadSignal)) {
|
|
5405
5804
|
console.error('Failed to send voice message:', error);
|
|
5406
5805
|
}
|
|
5407
|
-
}
|
|
5408
|
-
|
|
5806
|
+
})
|
|
5807
|
+
.finally(() => {
|
|
5409
5808
|
this.uploadAbortController = undefined;
|
|
5410
5809
|
if (!uploadSignal.aborted) {
|
|
5411
5810
|
this.isUploading.set(false);
|
|
5412
5811
|
this.uploadProgress.set(0);
|
|
5413
5812
|
}
|
|
5414
|
-
}
|
|
5813
|
+
});
|
|
5415
5814
|
}
|
|
5416
5815
|
stopAndCleanup() {
|
|
5417
5816
|
this.abortInProgressUploads();
|
|
@@ -5986,282 +6385,67 @@ class AXBaseRegistry {
|
|
|
5986
6385
|
update(id, updates) {
|
|
5987
6386
|
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
5988
6387
|
}
|
|
5989
|
-
/**
|
|
5990
|
-
* Clear all items
|
|
5991
|
-
*/
|
|
5992
|
-
clear() {
|
|
5993
|
-
this._items.set([]);
|
|
5994
|
-
}
|
|
5995
|
-
/**
|
|
5996
|
-
* Get count of registered items
|
|
5997
|
-
*/
|
|
5998
|
-
get count() {
|
|
5999
|
-
return this._items().length;
|
|
6000
|
-
}
|
|
6001
|
-
/**
|
|
6002
|
-
* Get count of enabled items
|
|
6003
|
-
*/
|
|
6004
|
-
get enabledCount() {
|
|
6005
|
-
return this.enabledItems().length;
|
|
6006
|
-
}
|
|
6007
|
-
/**
|
|
6008
|
-
* Validate item before registration
|
|
6009
|
-
* Override in child classes for custom validation
|
|
6010
|
-
*/
|
|
6011
|
-
validateItem(item) {
|
|
6012
|
-
if (!item.id) {
|
|
6013
|
-
throw new Error(`${this.getRegistryName()}: Item must have an id`);
|
|
6014
|
-
}
|
|
6015
|
-
}
|
|
6016
|
-
}
|
|
6017
|
-
|
|
6018
|
-
/**
|
|
6019
|
-
* Conversation Item Action Registry
|
|
6020
|
-
* Manages actions available in the conversation list item context menu
|
|
6021
|
-
*/
|
|
6022
|
-
/**
|
|
6023
|
-
* Conversation Item Action Registry Service
|
|
6024
|
-
* Manages available actions in the conversation list item context menu
|
|
6025
|
-
*/
|
|
6026
|
-
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6027
|
-
constructor() {
|
|
6028
|
-
super();
|
|
6029
|
-
this.translation = inject(AXTranslationService);
|
|
6030
|
-
this.injector = inject(Injector);
|
|
6031
|
-
// Register default actions from individual token
|
|
6032
|
-
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6033
|
-
// Register actions from REGISTRY_CONFIG
|
|
6034
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6035
|
-
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6036
|
-
}
|
|
6037
|
-
/**
|
|
6038
|
-
* Get registry name for logging
|
|
6039
|
-
*/
|
|
6040
|
-
getRegistryName() {
|
|
6041
|
-
return 'ConversationItemActionRegistry';
|
|
6042
|
-
}
|
|
6043
|
-
/**
|
|
6044
|
-
* Get actions for a specific conversation
|
|
6045
|
-
*/
|
|
6046
|
-
getActionsForConversation(conversation) {
|
|
6047
|
-
const context = { conversation };
|
|
6048
|
-
return this.enabledItems()
|
|
6049
|
-
.filter((action) => {
|
|
6050
|
-
// Filter by visibility
|
|
6051
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6052
|
-
return false;
|
|
6053
|
-
}
|
|
6054
|
-
return true;
|
|
6055
|
-
})
|
|
6056
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6057
|
-
}
|
|
6058
|
-
isActionEnabled(action, context) {
|
|
6059
|
-
if (action.enabled === false) {
|
|
6060
|
-
return false;
|
|
6061
|
-
}
|
|
6062
|
-
if (action.enabledWhen) {
|
|
6063
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6064
|
-
}
|
|
6065
|
-
return true;
|
|
6066
|
-
}
|
|
6067
|
-
/**
|
|
6068
|
-
* Get label for an action
|
|
6069
|
-
*/
|
|
6070
|
-
getActionLabel(action, conversation) {
|
|
6071
|
-
if (typeof action.label === 'function') {
|
|
6072
|
-
const labelFactory = action.label;
|
|
6073
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6074
|
-
}
|
|
6075
|
-
return this.translation.translateSync(action.label);
|
|
6076
|
-
}
|
|
6077
|
-
/**
|
|
6078
|
-
* Get icon for an action
|
|
6079
|
-
*/
|
|
6080
|
-
getActionIcon(action, conversation) {
|
|
6081
|
-
if (!action.icon)
|
|
6082
|
-
return undefined;
|
|
6083
|
-
if (typeof action.icon === 'function') {
|
|
6084
|
-
const iconFactory = action.icon;
|
|
6085
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6086
|
-
}
|
|
6087
|
-
return action.icon;
|
|
6088
|
-
}
|
|
6089
|
-
/**
|
|
6090
|
-
* Get tooltip for an action
|
|
6091
|
-
*/
|
|
6092
|
-
getActionTooltip(action, conversation) {
|
|
6093
|
-
if (!action.tooltip)
|
|
6094
|
-
return undefined;
|
|
6095
|
-
if (typeof action.tooltip === 'function') {
|
|
6096
|
-
const tooltipFactory = action.tooltip;
|
|
6097
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6098
|
-
}
|
|
6099
|
-
return this.translation.translateSync(action.tooltip);
|
|
6100
|
-
}
|
|
6101
|
-
async executeAction(actionId, context) {
|
|
6102
|
-
const action = this.getById(actionId);
|
|
6103
|
-
if (!action) {
|
|
6104
|
-
throw new Error(`Action "${actionId}" not found`);
|
|
6105
|
-
}
|
|
6106
|
-
if (!this.isActionEnabled(action, context)) {
|
|
6107
|
-
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6108
|
-
}
|
|
6109
|
-
if (action.handler) {
|
|
6110
|
-
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6114
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry }); }
|
|
6115
|
-
}
|
|
6116
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
|
|
6117
|
-
type: Injectable
|
|
6118
|
-
}], ctorParameters: () => [] });
|
|
6119
|
-
|
|
6120
|
-
/**
|
|
6121
|
-
* Conversation Tab Registry
|
|
6122
|
-
* Register custom tabs for filtering conversations in the sidebar
|
|
6123
|
-
*/
|
|
6124
|
-
/**
|
|
6125
|
-
* Conversation Tab Registry Service
|
|
6126
|
-
*/
|
|
6127
|
-
class AXConversationTabRegistry {
|
|
6128
|
-
constructor() {
|
|
6129
|
-
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
6130
|
-
this.translation = inject(AXTranslationService);
|
|
6131
|
-
/** All registered tabs */
|
|
6132
|
-
this.tabs = this._tabs.asReadonly();
|
|
6133
|
-
/** Enabled tabs sorted by priority */
|
|
6134
|
-
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
6135
|
-
/** Default active tab */
|
|
6136
|
-
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
|
|
6137
|
-
// Register default tabs from individual token
|
|
6138
|
-
inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6139
|
-
// Register tabs from REGISTRY_CONFIG
|
|
6140
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6141
|
-
config?.conversationTabs?.forEach((tab) => this.register(tab));
|
|
6142
|
-
}
|
|
6143
|
-
/**
|
|
6144
|
-
* Register a conversation tab
|
|
6145
|
-
*/
|
|
6146
|
-
register(tab) {
|
|
6147
|
-
// Validate tab
|
|
6148
|
-
if (!tab.id) {
|
|
6149
|
-
throw new Error('Conversation tab must have an id');
|
|
6150
|
-
}
|
|
6151
|
-
if (!tab.label) {
|
|
6152
|
-
throw new Error('Conversation tab must have a label');
|
|
6153
|
-
}
|
|
6154
|
-
if (!tab.filter) {
|
|
6155
|
-
throw new Error('Conversation tab must have a filter function');
|
|
6156
|
-
}
|
|
6157
|
-
// Check for duplicates
|
|
6158
|
-
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6159
|
-
if (existing) {
|
|
6160
|
-
console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
|
|
6161
|
-
this.unregister(tab.id);
|
|
6162
|
-
}
|
|
6163
|
-
// Add to registry
|
|
6164
|
-
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6165
|
-
}
|
|
6166
|
-
/**
|
|
6167
|
-
* Unregister a conversation tab
|
|
6168
|
-
*/
|
|
6169
|
-
unregister(id) {
|
|
6170
|
-
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6171
|
-
}
|
|
6172
|
-
/**
|
|
6173
|
-
* Get tab by ID
|
|
6174
|
-
*/
|
|
6175
|
-
getTabById(id) {
|
|
6176
|
-
return this._tabs().find((t) => t.id === id);
|
|
6177
|
-
}
|
|
6178
|
-
getTabLabel(tab) {
|
|
6179
|
-
return this.translation.translateSync(tab.label);
|
|
6180
|
-
}
|
|
6181
|
-
/**
|
|
6182
|
-
* Apply tab filter to conversations
|
|
6183
|
-
*/
|
|
6184
|
-
filterConversations(tabId, conversations) {
|
|
6185
|
-
const tab = this.getTabById(tabId);
|
|
6186
|
-
if (!tab) {
|
|
6187
|
-
throw new Error(`Tab "${tabId}" not found`);
|
|
6188
|
-
}
|
|
6189
|
-
return tab.filter(conversations);
|
|
6190
|
-
}
|
|
6191
|
-
/**
|
|
6192
|
-
* Get badge for a tab
|
|
6193
|
-
*/
|
|
6194
|
-
getBadge(tabId, conversations) {
|
|
6195
|
-
const tab = this.getTabById(tabId);
|
|
6196
|
-
if (!tab || !tab.badge) {
|
|
6197
|
-
return undefined;
|
|
6198
|
-
}
|
|
6199
|
-
return tab.badge(conversations);
|
|
6388
|
+
/**
|
|
6389
|
+
* Clear all items
|
|
6390
|
+
*/
|
|
6391
|
+
clear() {
|
|
6392
|
+
this._items.set([]);
|
|
6200
6393
|
}
|
|
6201
6394
|
/**
|
|
6202
|
-
*
|
|
6395
|
+
* Get count of registered items
|
|
6203
6396
|
*/
|
|
6204
|
-
|
|
6205
|
-
this.
|
|
6397
|
+
get count() {
|
|
6398
|
+
return this._items().length;
|
|
6206
6399
|
}
|
|
6207
6400
|
/**
|
|
6208
|
-
*
|
|
6401
|
+
* Get count of enabled items
|
|
6209
6402
|
*/
|
|
6210
|
-
|
|
6211
|
-
this.
|
|
6212
|
-
...tab,
|
|
6213
|
-
default: tab.id === id,
|
|
6214
|
-
})));
|
|
6403
|
+
get enabledCount() {
|
|
6404
|
+
return this.enabledItems().length;
|
|
6215
6405
|
}
|
|
6216
6406
|
/**
|
|
6217
|
-
*
|
|
6407
|
+
* Validate item before registration
|
|
6408
|
+
* Override in child classes for custom validation
|
|
6218
6409
|
*/
|
|
6219
|
-
|
|
6220
|
-
|
|
6410
|
+
validateItem(item) {
|
|
6411
|
+
if (!item.id) {
|
|
6412
|
+
throw new Error(`${this.getRegistryName()}: Item must have an id`);
|
|
6413
|
+
}
|
|
6221
6414
|
}
|
|
6222
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6223
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry }); }
|
|
6224
6415
|
}
|
|
6225
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
|
|
6226
|
-
type: Injectable
|
|
6227
|
-
}], ctorParameters: () => [] });
|
|
6228
6416
|
|
|
6229
6417
|
/**
|
|
6230
|
-
*
|
|
6231
|
-
* Manages actions available in the conversation
|
|
6418
|
+
* Conversation Item Action Registry
|
|
6419
|
+
* Manages actions available in the conversation list item context menu
|
|
6232
6420
|
*/
|
|
6233
6421
|
/**
|
|
6234
|
-
*
|
|
6235
|
-
* Manages available actions in the conversation
|
|
6422
|
+
* Conversation Item Action Registry Service
|
|
6423
|
+
* Manages available actions in the conversation list item context menu
|
|
6236
6424
|
*/
|
|
6237
|
-
class
|
|
6425
|
+
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6238
6426
|
constructor() {
|
|
6239
6427
|
super();
|
|
6240
6428
|
this.translation = inject(AXTranslationService);
|
|
6241
6429
|
this.injector = inject(Injector);
|
|
6242
6430
|
// Register default actions from individual token
|
|
6243
|
-
inject(
|
|
6431
|
+
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6244
6432
|
// Register actions from REGISTRY_CONFIG
|
|
6245
6433
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6246
|
-
config?.
|
|
6434
|
+
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6247
6435
|
}
|
|
6248
6436
|
/**
|
|
6249
6437
|
* Get registry name for logging
|
|
6250
6438
|
*/
|
|
6251
6439
|
getRegistryName() {
|
|
6252
|
-
return '
|
|
6440
|
+
return 'ConversationItemActionRegistry';
|
|
6253
6441
|
}
|
|
6254
6442
|
/**
|
|
6255
6443
|
* Get actions for a specific conversation
|
|
6256
6444
|
*/
|
|
6257
|
-
getActionsForConversation(conversation
|
|
6445
|
+
getActionsForConversation(conversation) {
|
|
6258
6446
|
const context = { conversation };
|
|
6259
6447
|
return this.enabledItems()
|
|
6260
6448
|
.filter((action) => {
|
|
6261
|
-
// Filter by location if specified
|
|
6262
|
-
if (location && action.location && action.location !== location) {
|
|
6263
|
-
return false;
|
|
6264
|
-
}
|
|
6265
6449
|
// Filter by visibility
|
|
6266
6450
|
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6267
6451
|
return false;
|
|
@@ -6270,18 +6454,6 @@ class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
|
6270
6454
|
})
|
|
6271
6455
|
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6272
6456
|
}
|
|
6273
|
-
/**
|
|
6274
|
-
* Get inline actions (toolbar buttons)
|
|
6275
|
-
*/
|
|
6276
|
-
getInlineActions(conversation) {
|
|
6277
|
-
return this.getActionsForConversation(conversation, 'inline');
|
|
6278
|
-
}
|
|
6279
|
-
/**
|
|
6280
|
-
* Get menu actions (dropdown items)
|
|
6281
|
-
*/
|
|
6282
|
-
getMenuActions(conversation) {
|
|
6283
|
-
return this.getActionsForConversation(conversation, 'menu');
|
|
6284
|
-
}
|
|
6285
6457
|
isActionEnabled(action, context) {
|
|
6286
6458
|
if (action.enabled === false) {
|
|
6287
6459
|
return false;
|
|
@@ -6325,653 +6497,575 @@ class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
|
6325
6497
|
}
|
|
6326
6498
|
return this.translation.translateSync(action.tooltip);
|
|
6327
6499
|
}
|
|
6328
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6329
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6330
|
-
}
|
|
6331
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6332
|
-
type: Injectable
|
|
6333
|
-
}], ctorParameters: () => [] });
|
|
6334
|
-
|
|
6335
|
-
/**
|
|
6336
|
-
* Message Action Registry
|
|
6337
|
-
* Register custom actions that can be performed on messages
|
|
6338
|
-
*/
|
|
6339
|
-
/**
|
|
6340
|
-
* Message Action Registry Service
|
|
6341
|
-
*/
|
|
6342
|
-
class AXMessageActionRegistry {
|
|
6343
|
-
constructor() {
|
|
6344
|
-
this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
6345
|
-
this.translation = inject(AXTranslationService);
|
|
6346
|
-
this.injector = inject(Injector);
|
|
6347
|
-
/** All registered actions */
|
|
6348
|
-
this.actions = this._actions.asReadonly();
|
|
6349
|
-
/** Actions sorted by priority */
|
|
6350
|
-
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
|
|
6351
|
-
// Register default actions from individual token
|
|
6352
|
-
const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
|
|
6353
|
-
if (defaultActions) {
|
|
6354
|
-
defaultActions.forEach((action) => this.register(action));
|
|
6355
|
-
}
|
|
6356
|
-
// Register actions from REGISTRY_CONFIG
|
|
6357
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6358
|
-
config?.messageActions?.forEach((action) => this.register(action));
|
|
6359
|
-
}
|
|
6360
|
-
/**
|
|
6361
|
-
* Register a message action
|
|
6362
|
-
*/
|
|
6363
|
-
register(action) {
|
|
6364
|
-
// Validate action
|
|
6365
|
-
if (!action.id) {
|
|
6366
|
-
throw new Error('Message action must have an id');
|
|
6367
|
-
}
|
|
6368
|
-
if (!action.label) {
|
|
6369
|
-
throw new Error('Message action must have a label');
|
|
6370
|
-
}
|
|
6371
|
-
if (!action.handler) {
|
|
6372
|
-
throw new Error('Message action must have a handler');
|
|
6373
|
-
}
|
|
6374
|
-
// Check for duplicates
|
|
6375
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
6376
|
-
if (existing) {
|
|
6377
|
-
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6378
|
-
this.unregister(action.id);
|
|
6379
|
-
}
|
|
6380
|
-
// Add to registry
|
|
6381
|
-
this._actions.update((actions) => [...actions, action]);
|
|
6382
|
-
}
|
|
6383
|
-
/**
|
|
6384
|
-
* Unregister a message action
|
|
6385
|
-
*/
|
|
6386
|
-
unregister(id) {
|
|
6387
|
-
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6388
|
-
}
|
|
6389
|
-
/**
|
|
6390
|
-
* Get all actions for a specific message
|
|
6391
|
-
*/
|
|
6392
|
-
getActions(message, currentUser) {
|
|
6393
|
-
return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
|
|
6394
|
-
runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
|
|
6395
|
-
}
|
|
6396
|
-
/**
|
|
6397
|
-
* Get menu actions for a message
|
|
6398
|
-
*/
|
|
6399
|
-
getMenuActions(message, currentUser) {
|
|
6400
|
-
return this.getActions(message, currentUser);
|
|
6401
|
-
}
|
|
6402
|
-
/** Whether an action is enabled (safe to call outside an injection context). */
|
|
6403
|
-
isActionEnabled(action, message, currentUser) {
|
|
6404
|
-
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6405
|
-
}
|
|
6406
|
-
getActionLabel(action) {
|
|
6407
|
-
return this.translation.translateSync(action.label);
|
|
6408
|
-
}
|
|
6409
|
-
/**
|
|
6410
|
-
* Get inline actions for a message
|
|
6411
|
-
*/
|
|
6412
|
-
getInlineActions(message, currentUser) {
|
|
6413
|
-
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6414
|
-
}
|
|
6415
|
-
/**
|
|
6416
|
-
* Get action by ID
|
|
6417
|
-
*/
|
|
6418
|
-
getActionById(id) {
|
|
6419
|
-
return this._actions().find((a) => a.id === id);
|
|
6420
|
-
}
|
|
6421
|
-
/**
|
|
6422
|
-
* Execute an action
|
|
6423
|
-
*/
|
|
6424
6500
|
async executeAction(actionId, context) {
|
|
6425
|
-
const action = this.
|
|
6501
|
+
const action = this.getById(actionId);
|
|
6426
6502
|
if (!action) {
|
|
6427
6503
|
throw new Error(`Action "${actionId}" not found`);
|
|
6428
6504
|
}
|
|
6429
|
-
|
|
6430
|
-
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6505
|
+
if (!this.isActionEnabled(action, context)) {
|
|
6431
6506
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6432
6507
|
}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
/**
|
|
6437
|
-
* Clear all actions
|
|
6438
|
-
*/
|
|
6439
|
-
clear() {
|
|
6440
|
-
this._actions.set([]);
|
|
6508
|
+
if (action.handler) {
|
|
6509
|
+
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6510
|
+
}
|
|
6441
6511
|
}
|
|
6442
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6443
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6512
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6513
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry }); }
|
|
6444
6514
|
}
|
|
6445
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6515
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
|
|
6446
6516
|
type: Injectable
|
|
6447
6517
|
}], ctorParameters: () => [] });
|
|
6448
6518
|
|
|
6449
6519
|
/**
|
|
6450
|
-
*
|
|
6451
|
-
*
|
|
6520
|
+
* Conversation Tab Registry
|
|
6521
|
+
* Register custom tabs for filtering conversations in the sidebar
|
|
6452
6522
|
*/
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
PARTICIPANTS: 'participants',
|
|
6458
|
-
CONVERSATIONS: 'conversations',
|
|
6459
|
-
MESSAGES: 'messages',
|
|
6460
|
-
MEDIA: 'media',
|
|
6461
|
-
SETTINGS: 'settings',
|
|
6462
|
-
};
|
|
6463
|
-
class AXConversationIndexedDbStorage {
|
|
6523
|
+
/**
|
|
6524
|
+
* Conversation Tab Registry Service
|
|
6525
|
+
*/
|
|
6526
|
+
class AXConversationTabRegistry {
|
|
6464
6527
|
constructor() {
|
|
6465
|
-
this.
|
|
6466
|
-
this.
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
request.onerror = () => reject(request.error);
|
|
6479
|
-
request.onsuccess = () => {
|
|
6480
|
-
this.db = request.result;
|
|
6481
|
-
resolve();
|
|
6482
|
-
};
|
|
6483
|
-
request.onupgradeneeded = (event) => {
|
|
6484
|
-
const db = event.target.result;
|
|
6485
|
-
// Create object stores if they don't exist
|
|
6486
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6487
|
-
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6488
|
-
}
|
|
6489
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6490
|
-
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6491
|
-
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6492
|
-
}
|
|
6493
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6494
|
-
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6495
|
-
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6496
|
-
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6497
|
-
}
|
|
6498
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6499
|
-
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6500
|
-
}
|
|
6501
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6502
|
-
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6503
|
-
}
|
|
6504
|
-
};
|
|
6505
|
-
});
|
|
6506
|
-
return this.initPromise;
|
|
6528
|
+
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
6529
|
+
this.translation = inject(AXTranslationService);
|
|
6530
|
+
/** All registered tabs */
|
|
6531
|
+
this.tabs = this._tabs.asReadonly();
|
|
6532
|
+
/** Enabled tabs sorted by priority */
|
|
6533
|
+
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
6534
|
+
/** Default active tab */
|
|
6535
|
+
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
|
|
6536
|
+
// Register default tabs from individual token
|
|
6537
|
+
inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6538
|
+
// Register tabs from REGISTRY_CONFIG
|
|
6539
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6540
|
+
config?.conversationTabs?.forEach((tab) => this.register(tab));
|
|
6507
6541
|
}
|
|
6508
6542
|
/**
|
|
6509
|
-
*
|
|
6543
|
+
* Register a conversation tab
|
|
6510
6544
|
*/
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
if (!
|
|
6514
|
-
throw new Error('
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6545
|
+
register(tab) {
|
|
6546
|
+
// Validate tab
|
|
6547
|
+
if (!tab.id) {
|
|
6548
|
+
throw new Error('Conversation tab must have an id');
|
|
6549
|
+
}
|
|
6550
|
+
if (!tab.label) {
|
|
6551
|
+
throw new Error('Conversation tab must have a label');
|
|
6552
|
+
}
|
|
6553
|
+
if (!tab.filter) {
|
|
6554
|
+
throw new Error('Conversation tab must have a filter function');
|
|
6555
|
+
}
|
|
6556
|
+
// Check for duplicates
|
|
6557
|
+
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6558
|
+
if (existing) {
|
|
6559
|
+
console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
|
|
6560
|
+
this.unregister(tab.id);
|
|
6561
|
+
}
|
|
6562
|
+
// Add to registry
|
|
6563
|
+
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6523
6564
|
}
|
|
6524
6565
|
/**
|
|
6525
|
-
*
|
|
6566
|
+
* Unregister a conversation tab
|
|
6526
6567
|
*/
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
if (!this.db)
|
|
6530
|
-
throw new Error('Database not initialized');
|
|
6531
|
-
const db = this.db;
|
|
6532
|
-
return new Promise((resolve, reject) => {
|
|
6533
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6534
|
-
const store = transaction.objectStore(storeName);
|
|
6535
|
-
const request = store.getAll();
|
|
6536
|
-
request.onsuccess = () => resolve(request.result);
|
|
6537
|
-
request.onerror = () => reject(request.error);
|
|
6538
|
-
});
|
|
6568
|
+
unregister(id) {
|
|
6569
|
+
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6539
6570
|
}
|
|
6540
6571
|
/**
|
|
6541
|
-
* Get
|
|
6572
|
+
* Get tab by ID
|
|
6542
6573
|
*/
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
return new Promise((resolve, reject) => {
|
|
6549
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6550
|
-
const store = transaction.objectStore(storeName);
|
|
6551
|
-
const index = store.index(indexName);
|
|
6552
|
-
const request = index.getAll(query);
|
|
6553
|
-
request.onsuccess = () => resolve(request.result);
|
|
6554
|
-
request.onerror = () => reject(request.error);
|
|
6555
|
-
});
|
|
6574
|
+
getTabById(id) {
|
|
6575
|
+
return this._tabs().find((t) => t.id === id);
|
|
6576
|
+
}
|
|
6577
|
+
getTabLabel(tab) {
|
|
6578
|
+
return this.translation.translateSync(tab.label);
|
|
6556
6579
|
}
|
|
6557
6580
|
/**
|
|
6558
|
-
*
|
|
6581
|
+
* Apply tab filter to conversations
|
|
6559
6582
|
*/
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
if (!
|
|
6563
|
-
throw new Error(
|
|
6564
|
-
|
|
6565
|
-
return
|
|
6566
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6567
|
-
const store = transaction.objectStore(storeName);
|
|
6568
|
-
const request = store.put(value);
|
|
6569
|
-
request.onsuccess = () => resolve();
|
|
6570
|
-
request.onerror = () => reject(request.error);
|
|
6571
|
-
});
|
|
6583
|
+
filterConversations(tabId, conversations) {
|
|
6584
|
+
const tab = this.getTabById(tabId);
|
|
6585
|
+
if (!tab) {
|
|
6586
|
+
throw new Error(`Tab "${tabId}" not found`);
|
|
6587
|
+
}
|
|
6588
|
+
return tab.filter(conversations);
|
|
6572
6589
|
}
|
|
6573
6590
|
/**
|
|
6574
|
-
*
|
|
6591
|
+
* Get badge for a tab
|
|
6575
6592
|
*/
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
if (!
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
return
|
|
6582
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6583
|
-
const store = transaction.objectStore(storeName);
|
|
6584
|
-
const request = store.delete(key);
|
|
6585
|
-
request.onsuccess = () => resolve();
|
|
6586
|
-
request.onerror = () => reject(request.error);
|
|
6587
|
-
});
|
|
6593
|
+
getBadge(tabId, conversations) {
|
|
6594
|
+
const tab = this.getTabById(tabId);
|
|
6595
|
+
if (!tab || !tab.badge) {
|
|
6596
|
+
return undefined;
|
|
6597
|
+
}
|
|
6598
|
+
return tab.badge(conversations);
|
|
6588
6599
|
}
|
|
6589
6600
|
/**
|
|
6590
|
-
*
|
|
6601
|
+
* Enable/disable a tab
|
|
6591
6602
|
*/
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
if (!this.db)
|
|
6595
|
-
throw new Error('Database not initialized');
|
|
6596
|
-
const db = this.db;
|
|
6597
|
-
return new Promise((resolve, reject) => {
|
|
6598
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6599
|
-
const store = transaction.objectStore(storeName);
|
|
6600
|
-
const request = store.clear();
|
|
6601
|
-
request.onsuccess = () => resolve();
|
|
6602
|
-
request.onerror = () => reject(request.error);
|
|
6603
|
-
});
|
|
6604
|
-
}
|
|
6605
|
-
// =====================
|
|
6606
|
-
// Convenience Methods
|
|
6607
|
-
// =====================
|
|
6608
|
-
async getParticipant(id) {
|
|
6609
|
-
return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
|
|
6610
|
-
}
|
|
6611
|
-
async getAllParticipants() {
|
|
6612
|
-
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
6613
|
-
}
|
|
6614
|
-
async putParticipant(participant) {
|
|
6615
|
-
return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
|
|
6616
|
-
}
|
|
6617
|
-
async getConversation(id) {
|
|
6618
|
-
return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
6619
|
-
}
|
|
6620
|
-
async getAllConversations() {
|
|
6621
|
-
return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
|
|
6622
|
-
}
|
|
6623
|
-
async putConversation(conversation) {
|
|
6624
|
-
return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
|
|
6625
|
-
}
|
|
6626
|
-
async deleteConversation(id) {
|
|
6627
|
-
return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
6628
|
-
}
|
|
6629
|
-
async getMessage(id) {
|
|
6630
|
-
return this.get(AXConversationIndexedDbStores.MESSAGES, id);
|
|
6631
|
-
}
|
|
6632
|
-
async getAllMessages() {
|
|
6633
|
-
return this.getAll(AXConversationIndexedDbStores.MESSAGES);
|
|
6634
|
-
}
|
|
6635
|
-
async getMessagesByConversation(conversationId) {
|
|
6636
|
-
return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
|
|
6637
|
-
}
|
|
6638
|
-
async putMessage(message) {
|
|
6639
|
-
return this.put(AXConversationIndexedDbStores.MESSAGES, message);
|
|
6640
|
-
}
|
|
6641
|
-
async deleteMessage(id) {
|
|
6642
|
-
return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
|
|
6603
|
+
setEnabled(id, enabled) {
|
|
6604
|
+
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
6643
6605
|
}
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6606
|
+
/**
|
|
6607
|
+
* Set default tab
|
|
6608
|
+
*/
|
|
6609
|
+
setDefault(id) {
|
|
6610
|
+
this._tabs.update((tabs) => tabs.map((tab) => ({
|
|
6611
|
+
...tab,
|
|
6612
|
+
default: tab.id === id,
|
|
6613
|
+
})));
|
|
6647
6614
|
}
|
|
6648
|
-
|
|
6649
|
-
|
|
6615
|
+
/**
|
|
6616
|
+
* Clear all tabs
|
|
6617
|
+
*/
|
|
6618
|
+
clear() {
|
|
6619
|
+
this._tabs.set([]);
|
|
6650
6620
|
}
|
|
6651
|
-
|
|
6652
|
-
|
|
6621
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6622
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry }); }
|
|
6623
|
+
}
|
|
6624
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
|
|
6625
|
+
type: Injectable
|
|
6626
|
+
}], ctorParameters: () => [] });
|
|
6627
|
+
|
|
6628
|
+
/**
|
|
6629
|
+
* Info Bar Action Registry
|
|
6630
|
+
* Manages actions available in the conversation info bar
|
|
6631
|
+
*/
|
|
6632
|
+
/**
|
|
6633
|
+
* Info Bar Action Registry Service
|
|
6634
|
+
* Manages available actions in the conversation info bar
|
|
6635
|
+
*/
|
|
6636
|
+
class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
6637
|
+
constructor() {
|
|
6638
|
+
super();
|
|
6639
|
+
this.translation = inject(AXTranslationService);
|
|
6640
|
+
this.injector = inject(Injector);
|
|
6641
|
+
// Register default actions from individual token
|
|
6642
|
+
inject(DEFAULT_INFO_BAR_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6643
|
+
// Register actions from REGISTRY_CONFIG
|
|
6644
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6645
|
+
config?.infoBarActions?.forEach((action) => this.register(action));
|
|
6653
6646
|
}
|
|
6654
|
-
|
|
6655
|
-
|
|
6647
|
+
/**
|
|
6648
|
+
* Get registry name for logging
|
|
6649
|
+
*/
|
|
6650
|
+
getRegistryName() {
|
|
6651
|
+
return 'InfoBarActionRegistry';
|
|
6656
6652
|
}
|
|
6657
|
-
|
|
6658
|
-
|
|
6653
|
+
/**
|
|
6654
|
+
* Get actions for a specific conversation
|
|
6655
|
+
*/
|
|
6656
|
+
getActionsForConversation(conversation, location) {
|
|
6657
|
+
const context = { conversation };
|
|
6658
|
+
return this.enabledItems()
|
|
6659
|
+
.filter((action) => {
|
|
6660
|
+
// Filter by location if specified
|
|
6661
|
+
if (location && action.location && action.location !== location) {
|
|
6662
|
+
return false;
|
|
6663
|
+
}
|
|
6664
|
+
// Filter by visibility
|
|
6665
|
+
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6666
|
+
return false;
|
|
6667
|
+
}
|
|
6668
|
+
return true;
|
|
6669
|
+
})
|
|
6670
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6671
|
+
}
|
|
6672
|
+
/**
|
|
6673
|
+
* Get inline actions (toolbar buttons)
|
|
6674
|
+
*/
|
|
6675
|
+
getInlineActions(conversation) {
|
|
6676
|
+
return this.getActionsForConversation(conversation, 'inline');
|
|
6659
6677
|
}
|
|
6660
|
-
}
|
|
6661
|
-
// Export singleton instance
|
|
6662
|
-
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
6663
|
-
|
|
6664
|
-
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
6665
|
-
__proto__: null,
|
|
6666
|
-
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
6667
|
-
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
6668
|
-
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
6669
|
-
});
|
|
6670
|
-
|
|
6671
|
-
class AXConversationMessageUtilsService {
|
|
6672
6678
|
/**
|
|
6673
|
-
*
|
|
6674
|
-
* are treated as missing data.
|
|
6679
|
+
* Get menu actions (dropdown items)
|
|
6675
6680
|
*/
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6681
|
+
getMenuActions(conversation) {
|
|
6682
|
+
return this.getActionsForConversation(conversation, 'menu');
|
|
6683
|
+
}
|
|
6684
|
+
isActionEnabled(action, context) {
|
|
6685
|
+
if (action.enabled === false) {
|
|
6686
|
+
return false;
|
|
6679
6687
|
}
|
|
6680
|
-
|
|
6681
|
-
|
|
6688
|
+
if (action.enabledWhen) {
|
|
6689
|
+
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6690
|
+
}
|
|
6691
|
+
return true;
|
|
6682
6692
|
}
|
|
6683
6693
|
/**
|
|
6684
|
-
* Get
|
|
6694
|
+
* Get label for an action
|
|
6685
6695
|
*/
|
|
6686
|
-
|
|
6687
|
-
|
|
6696
|
+
getActionLabel(action, conversation) {
|
|
6697
|
+
if (typeof action.label === 'function') {
|
|
6698
|
+
const labelFactory = action.label;
|
|
6699
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6700
|
+
}
|
|
6701
|
+
return this.translation.translateSync(action.label);
|
|
6688
6702
|
}
|
|
6689
6703
|
/**
|
|
6690
|
-
*
|
|
6704
|
+
* Get icon for an action
|
|
6691
6705
|
*/
|
|
6692
|
-
|
|
6693
|
-
if (
|
|
6706
|
+
getActionIcon(action, conversation) {
|
|
6707
|
+
if (!action.icon)
|
|
6694
6708
|
return undefined;
|
|
6709
|
+
if (typeof action.icon === 'function') {
|
|
6710
|
+
const iconFactory = action.icon;
|
|
6711
|
+
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6695
6712
|
}
|
|
6696
|
-
return
|
|
6713
|
+
return action.icon;
|
|
6697
6714
|
}
|
|
6698
6715
|
/**
|
|
6699
|
-
* Get
|
|
6716
|
+
* Get tooltip for an action
|
|
6700
6717
|
*/
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
|
|
6718
|
+
getActionTooltip(action, conversation) {
|
|
6719
|
+
if (!action.tooltip)
|
|
6720
|
+
return undefined;
|
|
6721
|
+
if (typeof action.tooltip === 'function') {
|
|
6722
|
+
const tooltipFactory = action.tooltip;
|
|
6723
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6724
|
+
}
|
|
6725
|
+
return this.translation.translateSync(action.tooltip);
|
|
6726
|
+
}
|
|
6727
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6728
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6729
|
+
}
|
|
6730
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6731
|
+
type: Injectable
|
|
6732
|
+
}], ctorParameters: () => [] });
|
|
6733
|
+
|
|
6734
|
+
/**
|
|
6735
|
+
* Message Action Registry
|
|
6736
|
+
* Register custom actions that can be performed on messages
|
|
6737
|
+
*/
|
|
6738
|
+
/**
|
|
6739
|
+
* Message Action Registry Service
|
|
6740
|
+
*/
|
|
6741
|
+
class AXMessageActionRegistry {
|
|
6742
|
+
constructor() {
|
|
6743
|
+
this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
6744
|
+
this.translation = inject(AXTranslationService);
|
|
6745
|
+
this.injector = inject(Injector);
|
|
6746
|
+
/** All registered actions */
|
|
6747
|
+
this.actions = this._actions.asReadonly();
|
|
6748
|
+
/** Actions sorted by priority */
|
|
6749
|
+
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
|
|
6750
|
+
// Register default actions from individual token
|
|
6751
|
+
const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
|
|
6752
|
+
if (defaultActions) {
|
|
6753
|
+
defaultActions.forEach((action) => this.register(action));
|
|
6754
|
+
}
|
|
6755
|
+
// Register actions from REGISTRY_CONFIG
|
|
6756
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6757
|
+
config?.messageActions?.forEach((action) => this.register(action));
|
|
6704
6758
|
}
|
|
6705
6759
|
/**
|
|
6706
|
-
*
|
|
6760
|
+
* Register a message action
|
|
6707
6761
|
*/
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
6762
|
+
register(action) {
|
|
6763
|
+
// Validate action
|
|
6764
|
+
if (!action.id) {
|
|
6765
|
+
throw new Error('Message action must have an id');
|
|
6766
|
+
}
|
|
6767
|
+
if (!action.label) {
|
|
6768
|
+
throw new Error('Message action must have a label');
|
|
6769
|
+
}
|
|
6770
|
+
if (!action.handler) {
|
|
6771
|
+
throw new Error('Message action must have a handler');
|
|
6772
|
+
}
|
|
6773
|
+
// Check for duplicates
|
|
6774
|
+
const existing = this._actions().find((a) => a.id === action.id);
|
|
6775
|
+
if (existing) {
|
|
6776
|
+
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6777
|
+
this.unregister(action.id);
|
|
6778
|
+
}
|
|
6779
|
+
// Add to registry
|
|
6780
|
+
this._actions.update((actions) => [...actions, action]);
|
|
6711
6781
|
}
|
|
6712
6782
|
/**
|
|
6713
|
-
*
|
|
6714
|
-
* participant `icon` first, then conversation-level `icon`.
|
|
6783
|
+
* Unregister a message action
|
|
6715
6784
|
*/
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
if (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar)) {
|
|
6719
|
-
return undefined;
|
|
6720
|
-
}
|
|
6721
|
-
return (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.icon) ??
|
|
6722
|
-
AXConversationMessageUtilsService.getConversationAvatarIcon(conversation));
|
|
6785
|
+
unregister(id) {
|
|
6786
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6723
6787
|
}
|
|
6724
6788
|
/**
|
|
6725
|
-
* Get
|
|
6789
|
+
* Get all actions for a specific message
|
|
6726
6790
|
*/
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6730
|
-
return name
|
|
6731
|
-
.split(' ')
|
|
6732
|
-
.map((n) => n[0])
|
|
6733
|
-
.join('')
|
|
6734
|
-
.toUpperCase()
|
|
6735
|
-
.substring(0, 2);
|
|
6791
|
+
getActions(message, currentUser) {
|
|
6792
|
+
return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
|
|
6793
|
+
runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
|
|
6736
6794
|
}
|
|
6737
6795
|
/**
|
|
6738
|
-
*
|
|
6796
|
+
* Get menu actions for a message
|
|
6739
6797
|
*/
|
|
6740
|
-
|
|
6741
|
-
return
|
|
6798
|
+
getMenuActions(message, currentUser) {
|
|
6799
|
+
return this.getActions(message, currentUser);
|
|
6800
|
+
}
|
|
6801
|
+
/** Whether an action is enabled (safe to call outside an injection context). */
|
|
6802
|
+
isActionEnabled(action, message, currentUser) {
|
|
6803
|
+
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6804
|
+
}
|
|
6805
|
+
getActionLabel(action) {
|
|
6806
|
+
return this.translation.translateSync(action.label);
|
|
6742
6807
|
}
|
|
6743
6808
|
/**
|
|
6744
|
-
* Get
|
|
6809
|
+
* Get inline actions for a message
|
|
6745
6810
|
*/
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
return message.payload.text;
|
|
6749
|
-
}
|
|
6750
|
-
return `[${message.type}]`;
|
|
6811
|
+
getInlineActions(message, currentUser) {
|
|
6812
|
+
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6751
6813
|
}
|
|
6752
6814
|
/**
|
|
6753
|
-
*
|
|
6815
|
+
* Get action by ID
|
|
6754
6816
|
*/
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
const imagePayload = normalizeImagePayload(message.payload);
|
|
6770
|
-
const first = imagePayload.images[0];
|
|
6771
|
-
const label = imagePayload.caption?.trim() ||
|
|
6772
|
-
(imagePayload.images && imagePayload.images.length > 1
|
|
6773
|
-
? `${imagePayload.images.length} images`
|
|
6774
|
-
: '');
|
|
6775
|
-
return {
|
|
6776
|
-
value: label || first?.thumbnailUrl || first?.url || '',
|
|
6777
|
-
type: 'image',
|
|
6778
|
-
icon: 'fa-light fa-image',
|
|
6779
|
-
};
|
|
6780
|
-
}
|
|
6781
|
-
case 'video': {
|
|
6782
|
-
const videoPayload = normalizeVideoPayload(message.payload);
|
|
6783
|
-
const first = videoPayload.videos[0];
|
|
6784
|
-
const label = videoPayload.caption?.trim() ||
|
|
6785
|
-
(videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
|
|
6786
|
-
return {
|
|
6787
|
-
value: label || first?.url || '',
|
|
6788
|
-
type: 'video',
|
|
6789
|
-
icon: 'fa-light fa-video',
|
|
6790
|
-
};
|
|
6791
|
-
}
|
|
6792
|
-
case 'audio': {
|
|
6793
|
-
const audioPayload = normalizeAudioPayload(message.payload);
|
|
6794
|
-
const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
|
|
6795
|
-
const first = audioPayload.audios[0];
|
|
6796
|
-
const label = audioPayload.caption?.trim() ||
|
|
6797
|
-
(audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
|
|
6798
|
-
return {
|
|
6799
|
-
value: label || first?.url || '',
|
|
6800
|
-
type: 'audio',
|
|
6801
|
-
icon: 'fa-light fa-music',
|
|
6802
|
-
};
|
|
6803
|
-
}
|
|
6804
|
-
case 'voice': {
|
|
6805
|
-
const voicePayload = message.payload;
|
|
6806
|
-
return {
|
|
6807
|
-
value: voicePayload.url || '',
|
|
6808
|
-
type: 'voice',
|
|
6809
|
-
icon: 'fa-light fa-microphone',
|
|
6810
|
-
};
|
|
6811
|
-
}
|
|
6812
|
-
case 'file': {
|
|
6813
|
-
const filePayload = normalizeFilePayload(message.payload);
|
|
6814
|
-
const names = filePayload.files.map((f) => f.name).join(', ');
|
|
6815
|
-
const label = filePayload.caption?.trim() ||
|
|
6816
|
-
(filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
|
|
6817
|
-
return {
|
|
6818
|
-
value: label || names,
|
|
6819
|
-
type: 'file',
|
|
6820
|
-
icon: 'fa-light fa-file',
|
|
6821
|
-
};
|
|
6822
|
-
}
|
|
6823
|
-
case 'location': {
|
|
6824
|
-
const locationPayload = message.payload;
|
|
6825
|
-
return {
|
|
6826
|
-
value: locationPayload.latitude && locationPayload.longitude
|
|
6827
|
-
? `${locationPayload.latitude},${locationPayload.longitude}`
|
|
6828
|
-
: '',
|
|
6829
|
-
type: 'location',
|
|
6830
|
-
icon: 'fa-light fa-location-dot',
|
|
6831
|
-
};
|
|
6832
|
-
}
|
|
6833
|
-
case 'sticker': {
|
|
6834
|
-
const stickerPayload = message.payload;
|
|
6835
|
-
return {
|
|
6836
|
-
value: stickerPayload.url || '',
|
|
6837
|
-
type: 'sticker',
|
|
6838
|
-
icon: 'fa-light fa-face-smile',
|
|
6839
|
-
};
|
|
6840
|
-
}
|
|
6841
|
-
default:
|
|
6842
|
-
return {
|
|
6843
|
-
value: '',
|
|
6844
|
-
type: message.type,
|
|
6845
|
-
icon: 'fa-light fa-message',
|
|
6846
|
-
};
|
|
6817
|
+
getActionById(id) {
|
|
6818
|
+
return this._actions().find((a) => a.id === id);
|
|
6819
|
+
}
|
|
6820
|
+
/**
|
|
6821
|
+
* Execute an action
|
|
6822
|
+
*/
|
|
6823
|
+
async executeAction(actionId, context) {
|
|
6824
|
+
const action = this.getActionById(actionId);
|
|
6825
|
+
if (!action) {
|
|
6826
|
+
throw new Error(`Action "${actionId}" not found`);
|
|
6827
|
+
}
|
|
6828
|
+
// Check if enabled
|
|
6829
|
+
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6830
|
+
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6847
6831
|
}
|
|
6832
|
+
// Execute handler
|
|
6833
|
+
await runInInjectionContext(this.injector, () => action.handler(context));
|
|
6848
6834
|
}
|
|
6849
6835
|
/**
|
|
6850
|
-
*
|
|
6836
|
+
* Clear all actions
|
|
6851
6837
|
*/
|
|
6852
|
-
|
|
6853
|
-
|
|
6838
|
+
clear() {
|
|
6839
|
+
this._actions.set([]);
|
|
6840
|
+
}
|
|
6841
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6842
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry }); }
|
|
6843
|
+
}
|
|
6844
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, decorators: [{
|
|
6845
|
+
type: Injectable
|
|
6846
|
+
}], ctorParameters: () => [] });
|
|
6847
|
+
|
|
6848
|
+
/**
|
|
6849
|
+
* IndexedDB Storage Wrapper
|
|
6850
|
+
* Provides persistent storage for conversation data
|
|
6851
|
+
*/
|
|
6852
|
+
const DB_NAME = 'acorex-conversation-db';
|
|
6853
|
+
const DB_VERSION = 2;
|
|
6854
|
+
// Store names
|
|
6855
|
+
const AXConversationIndexedDbStores = {
|
|
6856
|
+
PARTICIPANTS: 'participants',
|
|
6857
|
+
CONVERSATIONS: 'conversations',
|
|
6858
|
+
MESSAGES: 'messages',
|
|
6859
|
+
MEDIA: 'media',
|
|
6860
|
+
SETTINGS: 'settings',
|
|
6861
|
+
};
|
|
6862
|
+
class AXConversationIndexedDbStorage {
|
|
6863
|
+
constructor() {
|
|
6864
|
+
this.db = null;
|
|
6865
|
+
this.initPromise = null;
|
|
6854
6866
|
}
|
|
6855
6867
|
/**
|
|
6856
|
-
*
|
|
6868
|
+
* Initialize IndexedDB
|
|
6857
6869
|
*/
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6870
|
+
async init() {
|
|
6871
|
+
if (this.db)
|
|
6872
|
+
return;
|
|
6873
|
+
if (this.initPromise)
|
|
6874
|
+
return this.initPromise;
|
|
6875
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
6876
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
6877
|
+
request.onerror = () => reject(request.error);
|
|
6878
|
+
request.onsuccess = () => {
|
|
6879
|
+
this.db = request.result;
|
|
6880
|
+
resolve();
|
|
6881
|
+
};
|
|
6882
|
+
request.onupgradeneeded = (event) => {
|
|
6883
|
+
const db = event.target.result;
|
|
6884
|
+
// Create object stores if they don't exist
|
|
6885
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6886
|
+
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6887
|
+
}
|
|
6888
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6889
|
+
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6890
|
+
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6891
|
+
}
|
|
6892
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6893
|
+
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6894
|
+
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6895
|
+
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6896
|
+
}
|
|
6897
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6898
|
+
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6899
|
+
}
|
|
6900
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6901
|
+
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6902
|
+
}
|
|
6903
|
+
};
|
|
6904
|
+
});
|
|
6905
|
+
return this.initPromise;
|
|
6873
6906
|
}
|
|
6874
6907
|
/**
|
|
6875
|
-
*
|
|
6908
|
+
* Get a value from a store
|
|
6876
6909
|
*/
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
if (
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6910
|
+
async get(storeName, key) {
|
|
6911
|
+
await this.init();
|
|
6912
|
+
if (!this.db)
|
|
6913
|
+
throw new Error('Database not initialized');
|
|
6914
|
+
const db = this.db;
|
|
6915
|
+
return new Promise((resolve, reject) => {
|
|
6916
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6917
|
+
const store = transaction.objectStore(storeName);
|
|
6918
|
+
const request = store.get(key);
|
|
6919
|
+
request.onsuccess = () => resolve(request.result);
|
|
6920
|
+
request.onerror = () => reject(request.error);
|
|
6921
|
+
});
|
|
6888
6922
|
}
|
|
6889
6923
|
/**
|
|
6890
|
-
*
|
|
6924
|
+
* Get all values from a store
|
|
6891
6925
|
*/
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6926
|
+
async getAll(storeName) {
|
|
6927
|
+
await this.init();
|
|
6928
|
+
if (!this.db)
|
|
6929
|
+
throw new Error('Database not initialized');
|
|
6930
|
+
const db = this.db;
|
|
6931
|
+
return new Promise((resolve, reject) => {
|
|
6932
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6933
|
+
const store = transaction.objectStore(storeName);
|
|
6934
|
+
const request = store.getAll();
|
|
6935
|
+
request.onsuccess = () => resolve(request.result);
|
|
6936
|
+
request.onerror = () => reject(request.error);
|
|
6937
|
+
});
|
|
6901
6938
|
}
|
|
6902
6939
|
/**
|
|
6903
|
-
* Get
|
|
6940
|
+
* Get values by index
|
|
6904
6941
|
*/
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6942
|
+
async getAllByIndex(storeName, indexName, query) {
|
|
6943
|
+
await this.init();
|
|
6944
|
+
if (!this.db)
|
|
6945
|
+
throw new Error('Database not initialized');
|
|
6946
|
+
const db = this.db;
|
|
6947
|
+
return new Promise((resolve, reject) => {
|
|
6948
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6949
|
+
const store = transaction.objectStore(storeName);
|
|
6950
|
+
const index = store.index(indexName);
|
|
6951
|
+
const request = index.getAll(query);
|
|
6952
|
+
request.onsuccess = () => resolve(request.result);
|
|
6953
|
+
request.onerror = () => reject(request.error);
|
|
6954
|
+
});
|
|
6910
6955
|
}
|
|
6911
6956
|
/**
|
|
6912
|
-
*
|
|
6957
|
+
* Put a value into a store
|
|
6913
6958
|
*/
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
if (
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6959
|
+
async put(storeName, value) {
|
|
6960
|
+
await this.init();
|
|
6961
|
+
if (!this.db)
|
|
6962
|
+
throw new Error('Database not initialized');
|
|
6963
|
+
const db = this.db;
|
|
6964
|
+
return new Promise((resolve, reject) => {
|
|
6965
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6966
|
+
const store = transaction.objectStore(storeName);
|
|
6967
|
+
const request = store.put(value);
|
|
6968
|
+
request.onsuccess = () => resolve();
|
|
6969
|
+
request.onerror = () => reject(request.error);
|
|
6970
|
+
});
|
|
6971
|
+
}
|
|
6972
|
+
/**
|
|
6973
|
+
* Delete a value from a store
|
|
6974
|
+
*/
|
|
6975
|
+
async delete(storeName, key) {
|
|
6976
|
+
await this.init();
|
|
6977
|
+
if (!this.db)
|
|
6978
|
+
throw new Error('Database not initialized');
|
|
6979
|
+
const db = this.db;
|
|
6980
|
+
return new Promise((resolve, reject) => {
|
|
6981
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6982
|
+
const store = transaction.objectStore(storeName);
|
|
6983
|
+
const request = store.delete(key);
|
|
6984
|
+
request.onsuccess = () => resolve();
|
|
6985
|
+
request.onerror = () => reject(request.error);
|
|
6986
|
+
});
|
|
6987
|
+
}
|
|
6988
|
+
/**
|
|
6989
|
+
* Clear all data from a store
|
|
6990
|
+
*/
|
|
6991
|
+
async clear(storeName) {
|
|
6992
|
+
await this.init();
|
|
6993
|
+
if (!this.db)
|
|
6994
|
+
throw new Error('Database not initialized');
|
|
6995
|
+
const db = this.db;
|
|
6996
|
+
return new Promise((resolve, reject) => {
|
|
6997
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6998
|
+
const store = transaction.objectStore(storeName);
|
|
6999
|
+
const request = store.clear();
|
|
7000
|
+
request.onsuccess = () => resolve();
|
|
7001
|
+
request.onerror = () => reject(request.error);
|
|
7002
|
+
});
|
|
7003
|
+
}
|
|
7004
|
+
// =====================
|
|
7005
|
+
// Convenience Methods
|
|
7006
|
+
// =====================
|
|
7007
|
+
async getParticipant(id) {
|
|
7008
|
+
return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
|
|
7009
|
+
}
|
|
7010
|
+
async getAllParticipants() {
|
|
7011
|
+
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
7012
|
+
}
|
|
7013
|
+
async putParticipant(participant) {
|
|
7014
|
+
return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
|
|
7015
|
+
}
|
|
7016
|
+
async getConversation(id) {
|
|
7017
|
+
return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7018
|
+
}
|
|
7019
|
+
async getAllConversations() {
|
|
7020
|
+
return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
|
|
7021
|
+
}
|
|
7022
|
+
async putConversation(conversation) {
|
|
7023
|
+
return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
|
|
7024
|
+
}
|
|
7025
|
+
async deleteConversation(id) {
|
|
7026
|
+
return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7027
|
+
}
|
|
7028
|
+
async getMessage(id) {
|
|
7029
|
+
return this.get(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7030
|
+
}
|
|
7031
|
+
async getAllMessages() {
|
|
7032
|
+
return this.getAll(AXConversationIndexedDbStores.MESSAGES);
|
|
7033
|
+
}
|
|
7034
|
+
async getMessagesByConversation(conversationId) {
|
|
7035
|
+
return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
|
|
6928
7036
|
}
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
*/
|
|
6932
|
-
static formatLastSeen(date) {
|
|
6933
|
-
const now = new Date();
|
|
6934
|
-
const diff = now.getTime() - date.getTime();
|
|
6935
|
-
const seconds = Math.floor(diff / 1000);
|
|
6936
|
-
if (seconds < 60)
|
|
6937
|
-
return translateSync('@acorex:chat.time.just-now');
|
|
6938
|
-
if (seconds < 3600)
|
|
6939
|
-
return translateSync('@acorex:chat.time.minutes-ago', { params: { count: Math.floor(seconds / 60) } });
|
|
6940
|
-
if (seconds < 86400)
|
|
6941
|
-
return translateSync('@acorex:chat.time.hours-ago', { params: { count: Math.floor(seconds / 3600) } });
|
|
6942
|
-
if (seconds < 604800)
|
|
6943
|
-
return translateSync('@acorex:chat.time.days-ago', { params: { count: Math.floor(seconds / 86400) } });
|
|
6944
|
-
return date.toLocaleDateString();
|
|
7037
|
+
async putMessage(message) {
|
|
7038
|
+
return this.put(AXConversationIndexedDbStores.MESSAGES, message);
|
|
6945
7039
|
}
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
case 'group':
|
|
6965
|
-
return translateSync('@acorex:chat.members.count', { params: { count: conversation.participants.length } });
|
|
6966
|
-
case 'channel':
|
|
6967
|
-
return translateSync('@acorex:chat.members.subscribers-count', { params: { count: conversation.participants.length } });
|
|
6968
|
-
case 'bot':
|
|
6969
|
-
return translateSync('@acorex:chat.bot');
|
|
6970
|
-
default:
|
|
6971
|
-
return '';
|
|
6972
|
-
}
|
|
7040
|
+
async deleteMessage(id) {
|
|
7041
|
+
return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7042
|
+
}
|
|
7043
|
+
async getSetting(key) {
|
|
7044
|
+
const result = await this.get(AXConversationIndexedDbStores.SETTINGS, key);
|
|
7045
|
+
return result?.value;
|
|
7046
|
+
}
|
|
7047
|
+
async putSetting(key, value) {
|
|
7048
|
+
return this.put(AXConversationIndexedDbStores.SETTINGS, { key, value });
|
|
7049
|
+
}
|
|
7050
|
+
async getMedia(id) {
|
|
7051
|
+
return this.get(AXConversationIndexedDbStores.MEDIA, id);
|
|
7052
|
+
}
|
|
7053
|
+
async putMedia(record) {
|
|
7054
|
+
return this.put(AXConversationIndexedDbStores.MEDIA, record);
|
|
7055
|
+
}
|
|
7056
|
+
async deleteMedia(id) {
|
|
7057
|
+
return this.delete(AXConversationIndexedDbStores.MEDIA, id);
|
|
6973
7058
|
}
|
|
6974
7059
|
}
|
|
7060
|
+
// Export singleton instance
|
|
7061
|
+
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
7062
|
+
|
|
7063
|
+
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
7064
|
+
__proto__: null,
|
|
7065
|
+
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
7066
|
+
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
7067
|
+
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
7068
|
+
});
|
|
6975
7069
|
|
|
6976
7070
|
/**
|
|
6977
7071
|
* Shared read/write helpers for IndexedDB chat storage (in-memory + persistence).
|
|
@@ -8417,20 +8511,27 @@ class AXConversationIndexedDbConversationApi extends AXConversationApi {
|
|
|
8417
8511
|
// Conversation CRUD
|
|
8418
8512
|
// =====================
|
|
8419
8513
|
async createConversation(data) {
|
|
8514
|
+
const creatorId = data.creatorId ?? conversationSharedStorage.currentUserId;
|
|
8515
|
+
const participantIds = new Set(data.participantIds);
|
|
8516
|
+
if (creatorId) {
|
|
8517
|
+
participantIds.add(creatorId);
|
|
8518
|
+
}
|
|
8420
8519
|
const conversation = {
|
|
8421
8520
|
id: `conv-${Date.now()}`,
|
|
8422
8521
|
type: data.type,
|
|
8423
|
-
|
|
8522
|
+
creatorId,
|
|
8523
|
+
title: data.title ||
|
|
8524
|
+
(data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
|
|
8424
8525
|
avatar: data.avatar,
|
|
8425
8526
|
description: data.description,
|
|
8426
|
-
participants:
|
|
8527
|
+
participants: [...participantIds].map((id) => {
|
|
8427
8528
|
const participant = conversationSharedStorage.participants.get(id);
|
|
8428
8529
|
if (!participant) {
|
|
8429
8530
|
throw this.createError('PARTICIPANT_NOT_FOUND', `Participant ${id} not found`, 404);
|
|
8430
8531
|
}
|
|
8431
8532
|
return {
|
|
8432
8533
|
...participant,
|
|
8433
|
-
role: id ===
|
|
8534
|
+
role: id === creatorId ? 'admin' : 'member',
|
|
8434
8535
|
};
|
|
8435
8536
|
}),
|
|
8436
8537
|
lastMessageAt: new Date(),
|
|
@@ -10400,41 +10501,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
10400
10501
|
`, styles: [":host{display:block}.avatar-picker{display:flex;flex-flow:row wrap;align-items:center;gap:1.5rem;padding:1rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-light-surface))}.avatar-preview{position:relative;width:100px;height:100px;border-radius:50%;overflow:hidden;background:rgb(var(--ax-sys-color-lighter-surface));border:3px solid rgb(var(--ax-sys-color-border-light-surface));display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .25s ease;flex-shrink:0;box-shadow:0 2px 8px #00000014}.avatar-preview:hover{border-color:rgb(var(--ax-sys-color-primary-500));// transform: scale(1.05);box-shadow:0 4px 12px #0000001f}.avatar-preview:hover .preview-overlay{opacity:1}.avatar-preview ax-image{width:100%;height:100%}.preview-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:#000000a6;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s ease;color:#fff;font-size:1.75rem}.avatar-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.375rem;font-size:2rem;color:rgb(var(--ax-sys-color-on-surface-variant));text-align:center;padding:.5rem}.placeholder-text{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;opacity:.8}.avatar-actions{display:flex;flex-direction:column;gap:.5rem;flex:1}.avatar-actions ax-button{width:100%}\n"] }]
|
|
10401
10502
|
}] });
|
|
10402
10503
|
|
|
10403
|
-
/** Other participant in a private chat (excludes the current user). */
|
|
10404
|
-
function resolvePrivatePeerUserId(conversation, currentUserId) {
|
|
10405
|
-
if (conversation.type !== 'private') {
|
|
10406
|
-
return undefined;
|
|
10407
|
-
}
|
|
10408
|
-
const currentId = currentUserId ?? 'current-user';
|
|
10409
|
-
return conversation.participants.find((participant) => participant.id !== currentId)?.id;
|
|
10410
|
-
}
|
|
10411
|
-
/** Whether `auto` kind should render a user avatar for this conversation. */
|
|
10412
|
-
function shouldUseUserAvatarForConversation(conversation, currentUserId) {
|
|
10413
|
-
return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
|
|
10414
|
-
}
|
|
10415
|
-
function resolveUserAvatarDisplay(userId, conversation, message) {
|
|
10416
|
-
if (message && conversation) {
|
|
10417
|
-
return {
|
|
10418
|
-
name: AXConversationMessageUtilsService.getSenderName(message, conversation),
|
|
10419
|
-
avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
|
|
10420
|
-
icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
|
|
10421
|
-
};
|
|
10422
|
-
}
|
|
10423
|
-
const participant = conversation?.participants.find((p) => p.id === userId);
|
|
10424
|
-
return {
|
|
10425
|
-
name: participant?.name ?? userId,
|
|
10426
|
-
avatar: participant?.avatar,
|
|
10427
|
-
icon: participant?.icon,
|
|
10428
|
-
};
|
|
10429
|
-
}
|
|
10430
|
-
function resolveConversationAvatarDisplay(conversation) {
|
|
10431
|
-
return {
|
|
10432
|
-
name: conversation.title,
|
|
10433
|
-
avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
|
|
10434
|
-
icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
|
|
10435
|
-
};
|
|
10436
|
-
}
|
|
10437
|
-
|
|
10438
10504
|
/**
|
|
10439
10505
|
* Conversation avatar host — renders a registered custom component or built-in fallback.
|
|
10440
10506
|
*/
|
|
@@ -10519,7 +10585,7 @@ class AXConversationAvatarComponent {
|
|
|
10519
10585
|
if (!conversation) {
|
|
10520
10586
|
return { name: explicitName ?? '?', avatar: explicitAvatar, icon: explicitIcon };
|
|
10521
10587
|
}
|
|
10522
|
-
const resolved = resolveConversationAvatarDisplay(conversation);
|
|
10588
|
+
const resolved = resolveConversationAvatarDisplay(conversation, this.currentUserId());
|
|
10523
10589
|
return {
|
|
10524
10590
|
name: explicitName ?? resolved.name,
|
|
10525
10591
|
avatar: explicitAvatar ?? resolved.avatar,
|
|
@@ -10670,6 +10736,58 @@ class AXConversationDateUtilsService {
|
|
|
10670
10736
|
}
|
|
10671
10737
|
}
|
|
10672
10738
|
|
|
10739
|
+
function toUploaderReference(item) {
|
|
10740
|
+
return {
|
|
10741
|
+
url: item.url,
|
|
10742
|
+
mediaId: item.mediaId,
|
|
10743
|
+
mimeType: item.mimeType,
|
|
10744
|
+
size: item.size,
|
|
10745
|
+
};
|
|
10746
|
+
}
|
|
10747
|
+
|
|
10748
|
+
/**
|
|
10749
|
+
* Resolves multimedia payload URLs for renderers (direct url or mediaId via uploader).
|
|
10750
|
+
*/
|
|
10751
|
+
/**
|
|
10752
|
+
* Creates a signal of the resolved playback URL for a media reference.
|
|
10753
|
+
*/
|
|
10754
|
+
function createResolvedMediaUrlSignal(reference) {
|
|
10755
|
+
const uploader = inject(AXUploaderService);
|
|
10756
|
+
const destroyRef = inject(DestroyRef);
|
|
10757
|
+
const resolvedUrl = signal(null, ...(ngDevMode ? [{ debugName: "resolvedUrl" }] : []));
|
|
10758
|
+
let abortController = null;
|
|
10759
|
+
effect(() => {
|
|
10760
|
+
const ref = reference();
|
|
10761
|
+
abortController?.abort();
|
|
10762
|
+
abortController = new AbortController();
|
|
10763
|
+
const trimmedUrl = ref?.url?.trim();
|
|
10764
|
+
if (!trimmedUrl && !ref?.mediaId) {
|
|
10765
|
+
resolvedUrl.set(null);
|
|
10766
|
+
return;
|
|
10767
|
+
}
|
|
10768
|
+
const directUrl = trimmedUrl && !isNonPersistableMediaUrl(trimmedUrl) ? trimmedUrl : undefined;
|
|
10769
|
+
if (!ref?.mediaId) {
|
|
10770
|
+
resolvedUrl.set(directUrl ?? null);
|
|
10771
|
+
return;
|
|
10772
|
+
}
|
|
10773
|
+
resolvedUrl.set(directUrl ?? null);
|
|
10774
|
+
void uploader
|
|
10775
|
+
.resolvePlaybackUrl(ref, abortController.signal)
|
|
10776
|
+
.then((url) => {
|
|
10777
|
+
if (!abortController?.signal.aborted) {
|
|
10778
|
+
resolvedUrl.set(url);
|
|
10779
|
+
}
|
|
10780
|
+
})
|
|
10781
|
+
.catch(() => {
|
|
10782
|
+
if (!abortController?.signal.aborted && directUrl) {
|
|
10783
|
+
resolvedUrl.set(directUrl);
|
|
10784
|
+
}
|
|
10785
|
+
});
|
|
10786
|
+
});
|
|
10787
|
+
destroyRef.onDestroy(() => abortController?.abort());
|
|
10788
|
+
return resolvedUrl.asReadonly();
|
|
10789
|
+
}
|
|
10790
|
+
|
|
10673
10791
|
function isMessageDeliveryPending(status) {
|
|
10674
10792
|
return status === 'sending';
|
|
10675
10793
|
}
|
|
@@ -10868,15 +10986,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
10868
10986
|
`, styles: [":host{display:block}.ax-cnv-fallback{display:flex;flex-direction:column;gap:.375rem;max-width:min(100%,18rem)}.ax-cnv-renderer-state__row{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;padding:.375rem .5rem;border-radius:var(--ax-sys-border-radius);font-size:.8125rem;line-height:1.35}.ax-cnv-renderer-state__row--warn{background:rgb(var(--ax-sys-color-warning-lightest-surface));color:rgb(var(--ax-sys-color-warning-800))}.ax-cnv-renderer-state__msg{flex:1;min-width:0}\n"] }]
|
|
10869
10987
|
}] });
|
|
10870
10988
|
|
|
10871
|
-
function toUploaderReference(item) {
|
|
10872
|
-
return {
|
|
10873
|
-
url: item.url,
|
|
10874
|
-
mediaId: item.mediaId,
|
|
10875
|
-
mimeType: item.mimeType,
|
|
10876
|
-
size: item.size,
|
|
10877
|
-
};
|
|
10878
|
-
}
|
|
10879
|
-
|
|
10880
10989
|
/** Shared formatting for message renderer UIs. */
|
|
10881
10990
|
function formatMediaDuration(seconds) {
|
|
10882
10991
|
if (!Number.isFinite(seconds)) {
|
|
@@ -10896,49 +11005,6 @@ function formatFileByteSize(bytes) {
|
|
|
10896
11005
|
return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
|
|
10897
11006
|
}
|
|
10898
11007
|
|
|
10899
|
-
/**
|
|
10900
|
-
* Resolves multimedia payload URLs for renderers (direct url or mediaId via uploader).
|
|
10901
|
-
*/
|
|
10902
|
-
/**
|
|
10903
|
-
* Creates a signal of the resolved playback URL for a media reference.
|
|
10904
|
-
*/
|
|
10905
|
-
function createResolvedMediaUrlSignal(reference) {
|
|
10906
|
-
const uploader = inject(AXUploaderService);
|
|
10907
|
-
const destroyRef = inject(DestroyRef);
|
|
10908
|
-
const resolvedUrl = signal(null, ...(ngDevMode ? [{ debugName: "resolvedUrl" }] : []));
|
|
10909
|
-
let abortController = null;
|
|
10910
|
-
effect(() => {
|
|
10911
|
-
const ref = reference();
|
|
10912
|
-
abortController?.abort();
|
|
10913
|
-
abortController = new AbortController();
|
|
10914
|
-
const trimmedUrl = ref?.url?.trim();
|
|
10915
|
-
if (!trimmedUrl && !ref?.mediaId) {
|
|
10916
|
-
resolvedUrl.set(null);
|
|
10917
|
-
return;
|
|
10918
|
-
}
|
|
10919
|
-
const directUrl = trimmedUrl && !isNonPersistableMediaUrl(trimmedUrl) ? trimmedUrl : undefined;
|
|
10920
|
-
if (!ref?.mediaId) {
|
|
10921
|
-
resolvedUrl.set(directUrl ?? null);
|
|
10922
|
-
return;
|
|
10923
|
-
}
|
|
10924
|
-
resolvedUrl.set(directUrl ?? null);
|
|
10925
|
-
void uploader
|
|
10926
|
-
.resolvePlaybackUrl(ref, abortController.signal)
|
|
10927
|
-
.then((url) => {
|
|
10928
|
-
if (!abortController?.signal.aborted) {
|
|
10929
|
-
resolvedUrl.set(url);
|
|
10930
|
-
}
|
|
10931
|
-
})
|
|
10932
|
-
.catch(() => {
|
|
10933
|
-
if (!abortController?.signal.aborted && directUrl) {
|
|
10934
|
-
resolvedUrl.set(directUrl);
|
|
10935
|
-
}
|
|
10936
|
-
});
|
|
10937
|
-
});
|
|
10938
|
-
destroyRef.onDestroy(() => abortController?.abort());
|
|
10939
|
-
return resolvedUrl.asReadonly();
|
|
10940
|
-
}
|
|
10941
|
-
|
|
10942
11008
|
class AXAudioAttachmentComponent {
|
|
10943
11009
|
constructor() {
|
|
10944
11010
|
this.item = input.required(...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
@@ -13013,6 +13079,18 @@ async function copyWithFileTypeFallback(message, registry, fallback) {
|
|
|
13013
13079
|
const result = await fileType.copy(message.payload);
|
|
13014
13080
|
return resolveFileCopyText(result)?.trim() || fallback();
|
|
13015
13081
|
}
|
|
13082
|
+
/** Use file-type `open` on the message when `message.fileType` is set. */
|
|
13083
|
+
async function openWithFileType(message, registry, ctx) {
|
|
13084
|
+
if (!message.fileType) {
|
|
13085
|
+
return false;
|
|
13086
|
+
}
|
|
13087
|
+
const fileType = await registry.get(message.fileType);
|
|
13088
|
+
if (!fileType?.open) {
|
|
13089
|
+
return false;
|
|
13090
|
+
}
|
|
13091
|
+
const result = await runFileTypeOpen(fileType, ctx, message.payload);
|
|
13092
|
+
return result !== false;
|
|
13093
|
+
}
|
|
13016
13094
|
/**
|
|
13017
13095
|
* Registers the live renderer on {@link AXMessageListService} for message actions.
|
|
13018
13096
|
*/
|
|
@@ -14923,14 +15001,23 @@ class AXConversationService {
|
|
|
14923
15001
|
this._messageDeleted$ = new Subject();
|
|
14924
15002
|
this._typingIndicator$ = new Subject();
|
|
14925
15003
|
this._presenceUpdate$ = new Subject();
|
|
14926
|
-
/** All conversations */
|
|
14927
|
-
this.conversations =
|
|
15004
|
+
/** All conversations (viewer-scoped display titles for private 1v1 chats) */
|
|
15005
|
+
this.conversations = computed(() => {
|
|
15006
|
+
const currentUserId = this._currentUser()?.id;
|
|
15007
|
+
return this.state.conversations().map((conversation) => resolveConversationForViewer(conversation, currentUserId));
|
|
15008
|
+
}, ...(ngDevMode ? [{ debugName: "conversations" }] : []));
|
|
14928
15009
|
/** Active conversation ID */
|
|
14929
15010
|
this.activeConversationId = this._activeConversationId.asReadonly();
|
|
14930
15011
|
/** Active conversation */
|
|
14931
15012
|
this.activeConversation = computed(() => {
|
|
14932
15013
|
const id = this._activeConversationId();
|
|
14933
|
-
|
|
15014
|
+
if (!id) {
|
|
15015
|
+
return null;
|
|
15016
|
+
}
|
|
15017
|
+
const conversation = this.state.getConversation(id);
|
|
15018
|
+
return conversation
|
|
15019
|
+
? resolveConversationForViewer(conversation, this._currentUser()?.id)
|
|
15020
|
+
: null;
|
|
14934
15021
|
}, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
|
|
14935
15022
|
/** Messages for active conversation */
|
|
14936
15023
|
this.activeMessages = computed(() => {
|
|
@@ -15721,17 +15808,29 @@ class AXConversationService {
|
|
|
15721
15808
|
*/
|
|
15722
15809
|
async createConversation(participantIds, type, metadata) {
|
|
15723
15810
|
try {
|
|
15811
|
+
const currentUser = this._currentUser();
|
|
15812
|
+
const creatorId = currentUser?.id;
|
|
15813
|
+
const isPrivate = type === 'private';
|
|
15814
|
+
const scopedParticipantIds = [...participantIds];
|
|
15815
|
+
if (creatorId && !scopedParticipantIds.includes(creatorId)) {
|
|
15816
|
+
scopedParticipantIds.unshift(creatorId);
|
|
15817
|
+
}
|
|
15724
15818
|
const conversation = await this.conversationApi.createConversation({
|
|
15725
15819
|
type,
|
|
15726
|
-
participantIds,
|
|
15727
|
-
|
|
15820
|
+
participantIds: scopedParticipantIds,
|
|
15821
|
+
creatorId,
|
|
15822
|
+
title: isPrivate ? undefined : metadata?.['title'],
|
|
15728
15823
|
description: metadata?.['description'],
|
|
15729
|
-
avatar:
|
|
15730
|
-
|
|
15731
|
-
|
|
15824
|
+
avatar: isPrivate
|
|
15825
|
+
? undefined
|
|
15826
|
+
: AXConversationService.normalizeOptionalString(metadata?.['avatar']),
|
|
15827
|
+
icon: isPrivate
|
|
15828
|
+
? undefined
|
|
15829
|
+
: AXConversationService.normalizeOptionalString(metadata?.['icon']),
|
|
15830
|
+
metadata: isPrivate ? undefined : metadata,
|
|
15732
15831
|
});
|
|
15733
15832
|
this.state.setConversation(conversation);
|
|
15734
|
-
return conversation;
|
|
15833
|
+
return resolveConversationForViewer(conversation, creatorId);
|
|
15735
15834
|
}
|
|
15736
15835
|
catch (error) {
|
|
15737
15836
|
this.errorHandler.handle(error, 'createConversation', { participantIds, type });
|
|
@@ -16437,8 +16536,6 @@ class AXComposerComponent {
|
|
|
16437
16536
|
this.composerPopupComponent = AXComposerPopupComponent;
|
|
16438
16537
|
/** Input placeholder - use service */
|
|
16439
16538
|
this.placeholder = this.composerService.placeholder;
|
|
16440
|
-
/** Sending message state */
|
|
16441
|
-
this.sendingMessage = signal(false, ...(ngDevMode ? [{ debugName: "sendingMessage" }] : []));
|
|
16442
16539
|
/** Typing indicator subject for rate limiting */
|
|
16443
16540
|
this.typingSubject = new Subject();
|
|
16444
16541
|
// Focus text area when requested via AXComposerService.focusComposer()
|
|
@@ -16565,51 +16662,44 @@ class AXComposerComponent {
|
|
|
16565
16662
|
}
|
|
16566
16663
|
/** Handle send click */
|
|
16567
16664
|
async onSendClick() {
|
|
16568
|
-
if (!this.canSend()
|
|
16665
|
+
if (!this.canSend())
|
|
16569
16666
|
return;
|
|
16570
16667
|
const conversation = this.activeConversation();
|
|
16571
16668
|
if (!conversation)
|
|
16572
16669
|
return;
|
|
16573
16670
|
const text = this.draftText().trim();
|
|
16574
16671
|
const editingMsg = this.editingMessage();
|
|
16575
|
-
|
|
16576
|
-
|
|
16577
|
-
// Check if we're editing a message
|
|
16578
|
-
if (editingMsg) {
|
|
16579
|
-
// Edit existing message
|
|
16672
|
+
if (editingMsg) {
|
|
16673
|
+
try {
|
|
16580
16674
|
await this.conversationService.editMessage(editingMsg.id, { type: 'text', text });
|
|
16581
|
-
|
|
16582
|
-
|
|
16583
|
-
|
|
16584
|
-
const command = {
|
|
16585
|
-
conversationId: conversation.id,
|
|
16586
|
-
type: 'text',
|
|
16587
|
-
payload: { type: 'text', text },
|
|
16588
|
-
replyToId: this.replyingToMessage()?.id,
|
|
16589
|
-
};
|
|
16590
|
-
// Send new message
|
|
16591
|
-
await this.composerService.sendMessage(command);
|
|
16592
|
-
this.messageSent.emit(command);
|
|
16593
|
-
// Request message list to scroll to bottom after sending
|
|
16594
|
-
this.messageListService.requestScrollToBottom();
|
|
16595
|
-
}
|
|
16596
|
-
// Clear input and draft
|
|
16597
|
-
this.draftText.set('');
|
|
16598
|
-
this.attachments.set([]);
|
|
16599
|
-
this.cancelEditReply();
|
|
16600
|
-
this.composerService.clear();
|
|
16601
|
-
// Clear saved draft after successful send
|
|
16602
|
-
if (conversation.id) {
|
|
16675
|
+
this.draftText.set('');
|
|
16676
|
+
this.attachments.set([]);
|
|
16677
|
+
this.cancelEditReply();
|
|
16603
16678
|
this.composerService.clearDraft(conversation.id);
|
|
16679
|
+
this.composerService.focusComposer();
|
|
16604
16680
|
}
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
}
|
|
16610
|
-
finally {
|
|
16611
|
-
this.sendingMessage.set(false);
|
|
16681
|
+
catch (error) {
|
|
16682
|
+
console.error('Failed to edit message:', error);
|
|
16683
|
+
}
|
|
16684
|
+
return;
|
|
16612
16685
|
}
|
|
16686
|
+
const command = {
|
|
16687
|
+
conversationId: conversation.id,
|
|
16688
|
+
type: 'text',
|
|
16689
|
+
payload: { type: 'text', text },
|
|
16690
|
+
replyToId: this.replyingToMessage()?.id,
|
|
16691
|
+
};
|
|
16692
|
+
this.editingMessage.set(null);
|
|
16693
|
+
this.replyingToMessage.set(null);
|
|
16694
|
+
void this.composerService
|
|
16695
|
+
.sendMessage(command)
|
|
16696
|
+
.then(() => {
|
|
16697
|
+
this.messageSent.emit(command);
|
|
16698
|
+
this.messageListService.requestScrollToBottom();
|
|
16699
|
+
})
|
|
16700
|
+
.catch((error) => {
|
|
16701
|
+
console.error('Failed to send message:', error);
|
|
16702
|
+
});
|
|
16613
16703
|
}
|
|
16614
16704
|
/** Handle emoji click */
|
|
16615
16705
|
async onEmojiClick(event) {
|
|
@@ -16663,16 +16753,14 @@ class AXComposerComponent {
|
|
|
16663
16753
|
},
|
|
16664
16754
|
replyToId: this.replyingToMessage()?.id,
|
|
16665
16755
|
};
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
this.cancelEditReply();
|
|
16670
|
-
// Request message list to scroll to bottom after sending sticker
|
|
16756
|
+
void this.composerService
|
|
16757
|
+
.sendMessage(command)
|
|
16758
|
+
.then(() => {
|
|
16671
16759
|
this.messageListService.requestScrollToBottom();
|
|
16672
|
-
}
|
|
16673
|
-
|
|
16760
|
+
})
|
|
16761
|
+
.catch((error) => {
|
|
16674
16762
|
console.error('Failed to send sticker:', error);
|
|
16675
|
-
}
|
|
16763
|
+
});
|
|
16676
16764
|
}
|
|
16677
16765
|
/** Get inputs for emoji popup component */
|
|
16678
16766
|
getEmojiPopupInputs() {
|
|
@@ -20960,12 +21048,7 @@ class AXNewConversationDialogComponent extends AXBasePageComponent {
|
|
|
20960
21048
|
try {
|
|
20961
21049
|
let conversation;
|
|
20962
21050
|
if (selectedIds.length === 1) {
|
|
20963
|
-
|
|
20964
|
-
const metadata = {
|
|
20965
|
-
title: selectedUser?.name,
|
|
20966
|
-
avatar: selectedUser?.avatar,
|
|
20967
|
-
};
|
|
20968
|
-
conversation = await this.conversationService.createConversation(selectedIds, 'private', metadata);
|
|
21051
|
+
conversation = await this.conversationService.createConversation(selectedIds, 'private');
|
|
20969
21052
|
}
|
|
20970
21053
|
else {
|
|
20971
21054
|
const metadata = {
|
|
@@ -22179,5 +22262,5 @@ function getErrorMessage(code, params) {
|
|
|
22179
22262
|
* Generated bundle index. Do not edit.
|
|
22180
22263
|
*/
|
|
22181
22264
|
|
|
22182
|
-
export { AXAudioPickerComponent, AXAudioRendererComponent, AXBaseRegistry, AXCnvMediaPlaybackInfoBarBannerComponent, AXComposerActionRegistry, AXComposerComponent, AXComposerPopupComponent, AXComposerService, AXComposerTabRegistry, AXConversation2Module, AXConversationAiApiKey, AXConversationAiResponderService, AXConversationApi, AXConversationContainerComponent, AXConversationContainerDirective, AXConversationDateUtilsService, AXConversationIndexedDbConversationApi, AXConversationIndexedDbMessageAiApi, AXConversationIndexedDbMessageApi, AXConversationIndexedDbRealtimeApi, AXConversationIndexedDbStorage, AXConversationIndexedDbStores, AXConversationIndexedDbUserApi, AXConversationInfoMediaViewComponent, AXConversationInfoPanelComponent, AXConversationItemActionRegistry, AXConversationMessageRendererStateComponent, AXConversationMessageUtilsService, AXConversationService, AXConversationSharedStorage, AXConversationTabRegistry, AXEmojiTabComponent, AXErrorHandlerService, AXFallbackRendererComponent, AXFilePickerComponent, AXFileRendererComponent, AXForwardMessageDialogComponent, AXImagePickerComponent, AXImageRendererComponent, AXInfiniteScrollDirective, AXInfoBarActionRegistry, AXInfoBarComponent, AXInfoBarSearchComponent, AXInfoBarService, AXLocationPickerComponent, AXLocationRendererComponent, AXMessageActionRegistry, AXMessageApi, AXMessageListComponent, AXMessageListNoActiveDefaultComponent, AXMessageListService, AXMessageRendererRegistry, AXNewConversationDialogComponent, AXPickerFooterComponent, AXPickerHeaderComponent, AXRealtimeApi, AXRegistryService, AXSidebarComponent, AXSidebarService, AXStickerRendererComponent, AXStickerTabComponent, AXSystemRendererComponent, AXTextRendererComponent, AXUserApi, AXVideoPickerComponent, AXVideoRendererComponent, AXVoiceRecorderComponent, AXVoiceRendererComponent, AX_CONVERSATION_AUDIO_RENDERER, AX_CONVERSATION_COMPOSER_AUDIO_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_TAB, AX_CONVERSATION_COMPOSER_FILE_ACTION, AX_CONVERSATION_COMPOSER_FILE_TYPE_PROVIDERS, AX_CONVERSATION_COMPOSER_IMAGE_ACTION, AX_CONVERSATION_COMPOSER_LOCATION_ACTION, AX_CONVERSATION_COMPOSER_STICKER_TAB, AX_CONVERSATION_COMPOSER_VIDEO_ACTION, AX_CONVERSATION_COMPOSER_VOICE_RECORDING_ACTION, AX_CONVERSATION_CONVERSATION_AVATAR_COMPONENT, AX_CONVERSATION_CURSOR_PREFIX, AX_CONVERSATION_FALLBACK_RENDERER, AX_CONVERSATION_FILE_RENDERER, AX_CONVERSATION_IMAGE_RENDERER, AX_CONVERSATION_INFO_BAR_ARCHIVE_ACTION, AX_CONVERSATION_INFO_BAR_BLOCK_ACTION, AX_CONVERSATION_INFO_BAR_DELETE_ACTION, AX_CONVERSATION_INFO_BAR_DIVIDER, AX_CONVERSATION_INFO_BAR_MUTE_ACTION, AX_CONVERSATION_INFO_BAR_SEARCH_ACTION, AX_CONVERSATION_ITEM_BLOCK_ACTION, AX_CONVERSATION_ITEM_DELETE_ACTION, AX_CONVERSATION_ITEM_DIVIDER, AX_CONVERSATION_ITEM_MARK_READ_ACTION, AX_CONVERSATION_ITEM_MUTE_ACTION, AX_CONVERSATION_ITEM_PIN_ACTION, AX_CONVERSATION_LOCATION_RENDERER, AX_CONVERSATION_MESSAGE_DELETE_ACTION, AX_CONVERSATION_MESSAGE_EDIT_ACTION, AX_CONVERSATION_MESSAGE_FORWARD_ACTION, AX_CONVERSATION_MESSAGE_REPLY_ACTION, AX_CONVERSATION_STICKER_RENDERER, AX_CONVERSATION_SYSTEM_RENDERER, AX_CONVERSATION_TAB_ALL, AX_CONVERSATION_TAB_ARCHIVED, AX_CONVERSATION_TAB_BOT, AX_CONVERSATION_TAB_CHANNELS, AX_CONVERSATION_TAB_GROUPS, AX_CONVERSATION_TAB_PRIVATE, AX_CONVERSATION_TAB_UNREAD, AX_CONVERSATION_TEXT_RENDERER, AX_CONVERSATION_USER_AVATAR_COMPONENT, AX_CONVERSATION_VIDEO_RENDERER, AX_CONVERSATION_VOICE_RENDERER, AX_DEFAULT_CONVERSATION_CONFIG, AX_MESSAGE_CURSOR_PREFIX, AX_STICKER_API_KEY, CONNECTION_ERRORS, CONVERSATION_CONFIG, CONVERSATION_ERRORS, CONVERSATION_FILE_TYPES_READY, DEFAULT_COMPOSER_ACTIONS, DEFAULT_COMPOSER_TABS, DEFAULT_CONVERSATION_ITEM_ACTIONS, DEFAULT_CONVERSATION_TABS, DEFAULT_INFO_BAR_ACTIONS, DEFAULT_MESSAGE_ACTIONS, DEFAULT_MESSAGE_RENDERERS, ERROR_HANDLER_CONFIG, ERROR_MESSAGES, FILE_ERRORS, LOCATION_ERRORS, MESSAGE_ERRORS, REGISTRY_CONFIG, URL_ERRORS, USER_ERRORS, applyConversationFilters, applyLocalPreview, axConversationIndexedDbStorage, conversationSharedStorage, createLocalPreviewUrl, createObjectUrl, encodeConversationCursor, encodeMessageCursor, formatErrorMessage, getConversationLastActivity, getConversationMessagesNewestFirst, getErrorMessage, getSortedConversationsForInbox, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveParticipantProfile, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
|
|
22265
|
+
export { AXAudioPickerComponent, AXAudioRendererComponent, AXBaseRegistry, AXCnvMediaPlaybackInfoBarBannerComponent, AXComposerActionRegistry, AXComposerComponent, AXComposerPopupComponent, AXComposerService, AXComposerTabRegistry, AXConversation2Module, AXConversationAiApiKey, AXConversationAiResponderService, AXConversationApi, AXConversationContainerComponent, AXConversationContainerDirective, AXConversationDateUtilsService, AXConversationIndexedDbConversationApi, AXConversationIndexedDbMessageAiApi, AXConversationIndexedDbMessageApi, AXConversationIndexedDbRealtimeApi, AXConversationIndexedDbStorage, AXConversationIndexedDbStores, AXConversationIndexedDbUserApi, AXConversationInfoMediaViewComponent, AXConversationInfoPanelComponent, AXConversationItemActionRegistry, AXConversationMessageRendererStateComponent, AXConversationMessageUtilsService, AXConversationService, AXConversationSharedStorage, AXConversationTabRegistry, AXEmojiTabComponent, AXErrorHandlerService, AXFallbackRendererComponent, AXFilePickerComponent, AXFileRendererComponent, AXForwardMessageDialogComponent, AXImagePickerComponent, AXImageRendererComponent, AXInfiniteScrollDirective, AXInfoBarActionRegistry, AXInfoBarComponent, AXInfoBarSearchComponent, AXInfoBarService, AXLocationPickerComponent, AXLocationRendererComponent, AXMessageActionRegistry, AXMessageApi, AXMessageListComponent, AXMessageListNoActiveDefaultComponent, AXMessageListService, AXMessageRendererCopyHostComponent, AXMessageRendererRegistry, AXNewConversationDialogComponent, AXPickerFooterComponent, AXPickerHeaderComponent, AXRealtimeApi, AXRegistryService, AXSidebarComponent, AXSidebarService, AXStickerRendererComponent, AXStickerTabComponent, AXSystemRendererComponent, AXTextRendererComponent, AXUserApi, AXVideoPickerComponent, AXVideoRendererComponent, AXVoiceRecorderComponent, AXVoiceRendererComponent, AX_CONVERSATION_AUDIO_RENDERER, AX_CONVERSATION_COMPOSER_AUDIO_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_TAB, AX_CONVERSATION_COMPOSER_FILE_ACTION, AX_CONVERSATION_COMPOSER_FILE_TYPE_PROVIDERS, AX_CONVERSATION_COMPOSER_IMAGE_ACTION, AX_CONVERSATION_COMPOSER_LOCATION_ACTION, AX_CONVERSATION_COMPOSER_STICKER_TAB, AX_CONVERSATION_COMPOSER_VIDEO_ACTION, AX_CONVERSATION_COMPOSER_VOICE_RECORDING_ACTION, AX_CONVERSATION_CONVERSATION_AVATAR_COMPONENT, AX_CONVERSATION_CURSOR_PREFIX, AX_CONVERSATION_FALLBACK_RENDERER, AX_CONVERSATION_FILE_RENDERER, AX_CONVERSATION_IMAGE_RENDERER, AX_CONVERSATION_INFO_BAR_ARCHIVE_ACTION, AX_CONVERSATION_INFO_BAR_BLOCK_ACTION, AX_CONVERSATION_INFO_BAR_DELETE_ACTION, AX_CONVERSATION_INFO_BAR_DIVIDER, AX_CONVERSATION_INFO_BAR_MUTE_ACTION, AX_CONVERSATION_INFO_BAR_SEARCH_ACTION, AX_CONVERSATION_ITEM_BLOCK_ACTION, AX_CONVERSATION_ITEM_DELETE_ACTION, AX_CONVERSATION_ITEM_DIVIDER, AX_CONVERSATION_ITEM_MARK_READ_ACTION, AX_CONVERSATION_ITEM_MUTE_ACTION, AX_CONVERSATION_ITEM_PIN_ACTION, AX_CONVERSATION_LOCATION_RENDERER, AX_CONVERSATION_MESSAGE_DELETE_ACTION, AX_CONVERSATION_MESSAGE_EDIT_ACTION, AX_CONVERSATION_MESSAGE_FORWARD_ACTION, AX_CONVERSATION_MESSAGE_REPLY_ACTION, AX_CONVERSATION_STICKER_RENDERER, AX_CONVERSATION_SYSTEM_RENDERER, AX_CONVERSATION_TAB_ALL, AX_CONVERSATION_TAB_ARCHIVED, AX_CONVERSATION_TAB_BOT, AX_CONVERSATION_TAB_CHANNELS, AX_CONVERSATION_TAB_GROUPS, AX_CONVERSATION_TAB_PRIVATE, AX_CONVERSATION_TAB_UNREAD, AX_CONVERSATION_TEXT_RENDERER, AX_CONVERSATION_USER_AVATAR_COMPONENT, AX_CONVERSATION_VIDEO_RENDERER, AX_CONVERSATION_VOICE_RENDERER, AX_DEFAULT_CONVERSATION_CONFIG, AX_MESSAGE_CURSOR_PREFIX, AX_STICKER_API_KEY, CONNECTION_ERRORS, CONVERSATION_CONFIG, CONVERSATION_ERRORS, CONVERSATION_FILE_TYPES_READY, DEFAULT_COMPOSER_ACTIONS, DEFAULT_COMPOSER_TABS, DEFAULT_CONVERSATION_ITEM_ACTIONS, DEFAULT_CONVERSATION_TABS, DEFAULT_INFO_BAR_ACTIONS, DEFAULT_MESSAGE_ACTIONS, DEFAULT_MESSAGE_RENDERERS, ERROR_HANDLER_CONFIG, ERROR_MESSAGES, FILE_ERRORS, LOCATION_ERRORS, MESSAGE_ERRORS, REGISTRY_CONFIG, URL_ERRORS, USER_ERRORS, applyConversationFilters, applyLocalPreview, axConversationIndexedDbStorage, conversationSharedStorage, copyWithFileTypeFallback, createLocalPreviewUrl, createObjectUrl, createResolvedMediaUrlSignal, encodeConversationCursor, encodeMessageCursor, formatErrorMessage, getConversationLastActivity, getConversationMessagesNewestFirst, getErrorMessage, getSortedConversationsForInbox, isGenericPrivateConversationTitle, mediaCopyText, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, normalizeImagePayload, openWithFileType, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, pickDisplayMediaUrl, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveConversationForViewer, resolveConversationTitleForViewer, resolveImageDisplayUrl, resolveParticipantProfile, resolvePrivatePeerParticipant, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference as toMediaItemUploaderReference, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
|
|
22183
22266
|
//# sourceMappingURL=acorex-components-conversation2.mjs.map
|