@acorex/components 21.0.3-next.12 → 21.0.3-next.14
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/fesm2022/acorex-components-conversation2.mjs +1115 -1032
- package/fesm2022/acorex-components-conversation2.mjs.map +1 -1
- package/fesm2022/acorex-components-image-editor.mjs +368 -179
- package/fesm2022/acorex-components-image-editor.mjs.map +1 -1
- package/fesm2022/acorex-components-scheduler.mjs +2 -5
- package/fesm2022/acorex-components-scheduler.mjs.map +1 -1
- package/package.json +3 -3
- package/types/acorex-components-conversation2.d.ts +59 -27
- package/types/acorex-components-image-editor.d.ts +74 -47
- package/types/acorex-components-scheduler.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`.
|
|
@@ -2552,9 +2956,22 @@ class AXComposerService {
|
|
|
2552
2956
|
await this.conversationService.sendTypingIndicator(conversationId);
|
|
2553
2957
|
}
|
|
2554
2958
|
async sendMessage(command, options) {
|
|
2959
|
+
this.clearComposerInput(command.conversationId);
|
|
2555
2960
|
await this.conversationService.sendMessage(command, options);
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Resets composer input immediately when a send starts so upload/API work continues in the background.
|
|
2964
|
+
*/
|
|
2965
|
+
clearComposerInput(conversationId) {
|
|
2966
|
+
this.draftText.set('');
|
|
2967
|
+
this.attachments.set([]);
|
|
2556
2968
|
this.hideComponent();
|
|
2557
|
-
this.
|
|
2969
|
+
this._replyMessage.set(null);
|
|
2970
|
+
this._editMessage.set(null);
|
|
2971
|
+
if (conversationId) {
|
|
2972
|
+
this.clearDraft(conversationId);
|
|
2973
|
+
}
|
|
2974
|
+
this.focusComposer();
|
|
2558
2975
|
}
|
|
2559
2976
|
/**
|
|
2560
2977
|
* Show a component in the composer (full-width mode)
|
|
@@ -3558,17 +3975,12 @@ class AXAudioPickerComponent {
|
|
|
3558
3975
|
},
|
|
3559
3976
|
};
|
|
3560
3977
|
this.retainUploadedOnDismiss = true;
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
this.dismissPicker();
|
|
3566
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
|
-
catch {
|
|
3978
|
+
const popup = this.__popup__();
|
|
3979
|
+
this.dismissPicker();
|
|
3980
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
3981
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
3570
3982
|
this.retainUploadedOnDismiss = false;
|
|
3571
|
-
}
|
|
3983
|
+
});
|
|
3572
3984
|
}
|
|
3573
3985
|
canSend() {
|
|
3574
3986
|
const list = this.audioFiles();
|
|
@@ -3976,18 +4388,13 @@ class AXFilePickerComponent {
|
|
|
3976
4388
|
caption: this.caption.trim() || undefined,
|
|
3977
4389
|
},
|
|
3978
4390
|
};
|
|
3979
|
-
this.retainUploadedOnDismiss = true;
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
this.dismissPicker();
|
|
3985
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
3986
|
-
}
|
|
3987
|
-
}
|
|
3988
|
-
catch {
|
|
4391
|
+
this.retainUploadedOnDismiss = true;
|
|
4392
|
+
const popup = this.__popup__();
|
|
4393
|
+
this.dismissPicker();
|
|
4394
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
4395
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
3989
4396
|
this.retainUploadedOnDismiss = false;
|
|
3990
|
-
}
|
|
4397
|
+
});
|
|
3991
4398
|
}
|
|
3992
4399
|
updateFileProgress(index, progress, uploading, uploaded = false, uploadedUrl, mediaId, error) {
|
|
3993
4400
|
if (this.uploadsDismissed) {
|
|
@@ -4439,17 +4846,12 @@ class AXImagePickerComponent {
|
|
|
4439
4846
|
},
|
|
4440
4847
|
};
|
|
4441
4848
|
this.retainUploadedOnDismiss = true;
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
this.dismissPicker();
|
|
4447
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
4448
|
-
}
|
|
4449
|
-
}
|
|
4450
|
-
catch {
|
|
4849
|
+
const popup = this.__popup__();
|
|
4850
|
+
this.dismissPicker();
|
|
4851
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
4852
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
4451
4853
|
this.retainUploadedOnDismiss = false;
|
|
4452
|
-
}
|
|
4854
|
+
});
|
|
4453
4855
|
}
|
|
4454
4856
|
canSend() {
|
|
4455
4857
|
const list = this.images();
|
|
@@ -4705,8 +5107,10 @@ class AXLocationPickerComponent {
|
|
|
4705
5107
|
type: 'location',
|
|
4706
5108
|
payload: location,
|
|
4707
5109
|
};
|
|
4708
|
-
await this.composerService.sendMessage(command);
|
|
4709
5110
|
this.cancel();
|
|
5111
|
+
void this.composerService.sendMessage(command).catch((error) => {
|
|
5112
|
+
console.error('Failed to send location:', error);
|
|
5113
|
+
});
|
|
4710
5114
|
}
|
|
4711
5115
|
cancel() {
|
|
4712
5116
|
// Reset to default location
|
|
@@ -4955,17 +5359,12 @@ class AXVideoPickerComponent {
|
|
|
4955
5359
|
},
|
|
4956
5360
|
};
|
|
4957
5361
|
this.retainUploadedOnDismiss = true;
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
this.dismissPicker();
|
|
4963
|
-
dismissComposerPickerHost(this.composerService, popup);
|
|
4964
|
-
}
|
|
4965
|
-
}
|
|
4966
|
-
catch {
|
|
5362
|
+
const popup = this.__popup__();
|
|
5363
|
+
this.dismissPicker();
|
|
5364
|
+
dismissComposerPickerHost(this.composerService, popup);
|
|
5365
|
+
void this.composerService.sendMessage(command).catch(() => {
|
|
4967
5366
|
this.retainUploadedOnDismiss = false;
|
|
4968
|
-
}
|
|
5367
|
+
});
|
|
4969
5368
|
}
|
|
4970
5369
|
canSend() {
|
|
4971
5370
|
const list = this.videos();
|
|
@@ -5372,44 +5771,44 @@ class AXVoiceRecorderComponent {
|
|
|
5372
5771
|
const uploadSignal = this.uploadAbortController.signal;
|
|
5373
5772
|
this.isUploading.set(true);
|
|
5374
5773
|
this.uploadProgress.set(0);
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5774
|
+
const command = {
|
|
5775
|
+
conversationId: conversation.id,
|
|
5776
|
+
type: 'voice',
|
|
5777
|
+
payload: {
|
|
5378
5778
|
type: 'voice',
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
},
|
|
5397
|
-
signal: uploadSignal,
|
|
5779
|
+
url: '',
|
|
5780
|
+
duration: this.recordingDuration(),
|
|
5781
|
+
size: audioBlob.size,
|
|
5782
|
+
mimeType: 'audio/webm',
|
|
5783
|
+
},
|
|
5784
|
+
};
|
|
5785
|
+
this.composerService.hideComponent();
|
|
5786
|
+
void this.composerService
|
|
5787
|
+
.sendMessage(command, {
|
|
5788
|
+
media: {
|
|
5789
|
+
source: audioBlob,
|
|
5790
|
+
fileName: `voice-${Date.now()}.webm`,
|
|
5791
|
+
mimeType: 'audio/webm',
|
|
5792
|
+
onProgress: (percent) => {
|
|
5793
|
+
if (!uploadSignal.aborted) {
|
|
5794
|
+
this.uploadProgress.set(percent);
|
|
5795
|
+
}
|
|
5398
5796
|
},
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5797
|
+
signal: uploadSignal,
|
|
5798
|
+
},
|
|
5799
|
+
})
|
|
5800
|
+
.catch((error) => {
|
|
5402
5801
|
if (!isUploadAborted(error, uploadSignal)) {
|
|
5403
5802
|
console.error('Failed to send voice message:', error);
|
|
5404
5803
|
}
|
|
5405
|
-
}
|
|
5406
|
-
|
|
5804
|
+
})
|
|
5805
|
+
.finally(() => {
|
|
5407
5806
|
this.uploadAbortController = undefined;
|
|
5408
5807
|
if (!uploadSignal.aborted) {
|
|
5409
5808
|
this.isUploading.set(false);
|
|
5410
5809
|
this.uploadProgress.set(0);
|
|
5411
5810
|
}
|
|
5412
|
-
}
|
|
5811
|
+
});
|
|
5413
5812
|
}
|
|
5414
5813
|
stopAndCleanup() {
|
|
5415
5814
|
this.abortInProgressUploads();
|
|
@@ -6000,282 +6399,67 @@ class AXBaseRegistry {
|
|
|
6000
6399
|
update(id, updates) {
|
|
6001
6400
|
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
6002
6401
|
}
|
|
6003
|
-
/**
|
|
6004
|
-
* Clear all items
|
|
6005
|
-
*/
|
|
6006
|
-
clear() {
|
|
6007
|
-
this._items.set([]);
|
|
6008
|
-
}
|
|
6009
|
-
/**
|
|
6010
|
-
* Get count of registered items
|
|
6011
|
-
*/
|
|
6012
|
-
get count() {
|
|
6013
|
-
return this._items().length;
|
|
6014
|
-
}
|
|
6015
|
-
/**
|
|
6016
|
-
* Get count of enabled items
|
|
6017
|
-
*/
|
|
6018
|
-
get enabledCount() {
|
|
6019
|
-
return this.enabledItems().length;
|
|
6020
|
-
}
|
|
6021
|
-
/**
|
|
6022
|
-
* Validate item before registration
|
|
6023
|
-
* Override in child classes for custom validation
|
|
6024
|
-
*/
|
|
6025
|
-
validateItem(item) {
|
|
6026
|
-
if (!item.id) {
|
|
6027
|
-
throw new Error(`${this.getRegistryName()}: Item must have an id`);
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
}
|
|
6031
|
-
|
|
6032
|
-
/**
|
|
6033
|
-
* Conversation Item Action Registry
|
|
6034
|
-
* Manages actions available in the conversation list item context menu
|
|
6035
|
-
*/
|
|
6036
|
-
/**
|
|
6037
|
-
* Conversation Item Action Registry Service
|
|
6038
|
-
* Manages available actions in the conversation list item context menu
|
|
6039
|
-
*/
|
|
6040
|
-
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6041
|
-
constructor() {
|
|
6042
|
-
super();
|
|
6043
|
-
this.translation = inject(AXTranslationService);
|
|
6044
|
-
this.injector = inject(Injector);
|
|
6045
|
-
// Register default actions from individual token
|
|
6046
|
-
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6047
|
-
// Register actions from REGISTRY_CONFIG
|
|
6048
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6049
|
-
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6050
|
-
}
|
|
6051
|
-
/**
|
|
6052
|
-
* Get registry name for logging
|
|
6053
|
-
*/
|
|
6054
|
-
getRegistryName() {
|
|
6055
|
-
return 'ConversationItemActionRegistry';
|
|
6056
|
-
}
|
|
6057
|
-
/**
|
|
6058
|
-
* Get actions for a specific conversation
|
|
6059
|
-
*/
|
|
6060
|
-
getActionsForConversation(conversation) {
|
|
6061
|
-
const context = { conversation };
|
|
6062
|
-
return this.enabledItems()
|
|
6063
|
-
.filter((action) => {
|
|
6064
|
-
// Filter by visibility
|
|
6065
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6066
|
-
return false;
|
|
6067
|
-
}
|
|
6068
|
-
return true;
|
|
6069
|
-
})
|
|
6070
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6071
|
-
}
|
|
6072
|
-
isActionEnabled(action, context) {
|
|
6073
|
-
if (action.enabled === false) {
|
|
6074
|
-
return false;
|
|
6075
|
-
}
|
|
6076
|
-
if (action.enabledWhen) {
|
|
6077
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6078
|
-
}
|
|
6079
|
-
return true;
|
|
6080
|
-
}
|
|
6081
|
-
/**
|
|
6082
|
-
* Get label for an action
|
|
6083
|
-
*/
|
|
6084
|
-
getActionLabel(action, conversation) {
|
|
6085
|
-
if (typeof action.label === 'function') {
|
|
6086
|
-
const labelFactory = action.label;
|
|
6087
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6088
|
-
}
|
|
6089
|
-
return this.translation.translateSync(action.label);
|
|
6090
|
-
}
|
|
6091
|
-
/**
|
|
6092
|
-
* Get icon for an action
|
|
6093
|
-
*/
|
|
6094
|
-
getActionIcon(action, conversation) {
|
|
6095
|
-
if (!action.icon)
|
|
6096
|
-
return undefined;
|
|
6097
|
-
if (typeof action.icon === 'function') {
|
|
6098
|
-
const iconFactory = action.icon;
|
|
6099
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6100
|
-
}
|
|
6101
|
-
return action.icon;
|
|
6102
|
-
}
|
|
6103
|
-
/**
|
|
6104
|
-
* Get tooltip for an action
|
|
6105
|
-
*/
|
|
6106
|
-
getActionTooltip(action, conversation) {
|
|
6107
|
-
if (!action.tooltip)
|
|
6108
|
-
return undefined;
|
|
6109
|
-
if (typeof action.tooltip === 'function') {
|
|
6110
|
-
const tooltipFactory = action.tooltip;
|
|
6111
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6112
|
-
}
|
|
6113
|
-
return this.translation.translateSync(action.tooltip);
|
|
6114
|
-
}
|
|
6115
|
-
async executeAction(actionId, context) {
|
|
6116
|
-
const action = this.getById(actionId);
|
|
6117
|
-
if (!action) {
|
|
6118
|
-
throw new Error(`Action "${actionId}" not found`);
|
|
6119
|
-
}
|
|
6120
|
-
if (!this.isActionEnabled(action, context)) {
|
|
6121
|
-
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6122
|
-
}
|
|
6123
|
-
if (action.handler) {
|
|
6124
|
-
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6125
|
-
}
|
|
6126
|
-
}
|
|
6127
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6128
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry }); }
|
|
6129
|
-
}
|
|
6130
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
|
|
6131
|
-
type: Injectable
|
|
6132
|
-
}], ctorParameters: () => [] });
|
|
6133
|
-
|
|
6134
|
-
/**
|
|
6135
|
-
* Conversation Tab Registry
|
|
6136
|
-
* Register custom tabs for filtering conversations in the sidebar
|
|
6137
|
-
*/
|
|
6138
|
-
/**
|
|
6139
|
-
* Conversation Tab Registry Service
|
|
6140
|
-
*/
|
|
6141
|
-
class AXConversationTabRegistry {
|
|
6142
|
-
constructor() {
|
|
6143
|
-
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : /* istanbul ignore next */ []));
|
|
6144
|
-
this.translation = inject(AXTranslationService);
|
|
6145
|
-
/** All registered tabs */
|
|
6146
|
-
this.tabs = this._tabs.asReadonly();
|
|
6147
|
-
/** Enabled tabs sorted by priority */
|
|
6148
|
-
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : /* istanbul ignore next */ []));
|
|
6149
|
-
/** Default active tab */
|
|
6150
|
-
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : /* istanbul ignore next */ []));
|
|
6151
|
-
// Register default tabs from individual token
|
|
6152
|
-
inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6153
|
-
// Register tabs from REGISTRY_CONFIG
|
|
6154
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6155
|
-
config?.conversationTabs?.forEach((tab) => this.register(tab));
|
|
6156
|
-
}
|
|
6157
|
-
/**
|
|
6158
|
-
* Register a conversation tab
|
|
6159
|
-
*/
|
|
6160
|
-
register(tab) {
|
|
6161
|
-
// Validate tab
|
|
6162
|
-
if (!tab.id) {
|
|
6163
|
-
throw new Error('Conversation tab must have an id');
|
|
6164
|
-
}
|
|
6165
|
-
if (!tab.label) {
|
|
6166
|
-
throw new Error('Conversation tab must have a label');
|
|
6167
|
-
}
|
|
6168
|
-
if (!tab.filter) {
|
|
6169
|
-
throw new Error('Conversation tab must have a filter function');
|
|
6170
|
-
}
|
|
6171
|
-
// Check for duplicates
|
|
6172
|
-
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6173
|
-
if (existing) {
|
|
6174
|
-
console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
|
|
6175
|
-
this.unregister(tab.id);
|
|
6176
|
-
}
|
|
6177
|
-
// Add to registry
|
|
6178
|
-
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6179
|
-
}
|
|
6180
|
-
/**
|
|
6181
|
-
* Unregister a conversation tab
|
|
6182
|
-
*/
|
|
6183
|
-
unregister(id) {
|
|
6184
|
-
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6185
|
-
}
|
|
6186
|
-
/**
|
|
6187
|
-
* Get tab by ID
|
|
6188
|
-
*/
|
|
6189
|
-
getTabById(id) {
|
|
6190
|
-
return this._tabs().find((t) => t.id === id);
|
|
6191
|
-
}
|
|
6192
|
-
getTabLabel(tab) {
|
|
6193
|
-
return this.translation.translateSync(tab.label);
|
|
6194
|
-
}
|
|
6195
|
-
/**
|
|
6196
|
-
* Apply tab filter to conversations
|
|
6197
|
-
*/
|
|
6198
|
-
filterConversations(tabId, conversations) {
|
|
6199
|
-
const tab = this.getTabById(tabId);
|
|
6200
|
-
if (!tab) {
|
|
6201
|
-
throw new Error(`Tab "${tabId}" not found`);
|
|
6202
|
-
}
|
|
6203
|
-
return tab.filter(conversations);
|
|
6204
|
-
}
|
|
6205
|
-
/**
|
|
6206
|
-
* Get badge for a tab
|
|
6207
|
-
*/
|
|
6208
|
-
getBadge(tabId, conversations) {
|
|
6209
|
-
const tab = this.getTabById(tabId);
|
|
6210
|
-
if (!tab || !tab.badge) {
|
|
6211
|
-
return undefined;
|
|
6212
|
-
}
|
|
6213
|
-
return tab.badge(conversations);
|
|
6402
|
+
/**
|
|
6403
|
+
* Clear all items
|
|
6404
|
+
*/
|
|
6405
|
+
clear() {
|
|
6406
|
+
this._items.set([]);
|
|
6214
6407
|
}
|
|
6215
6408
|
/**
|
|
6216
|
-
*
|
|
6409
|
+
* Get count of registered items
|
|
6217
6410
|
*/
|
|
6218
|
-
|
|
6219
|
-
this.
|
|
6411
|
+
get count() {
|
|
6412
|
+
return this._items().length;
|
|
6220
6413
|
}
|
|
6221
6414
|
/**
|
|
6222
|
-
*
|
|
6415
|
+
* Get count of enabled items
|
|
6223
6416
|
*/
|
|
6224
|
-
|
|
6225
|
-
this.
|
|
6226
|
-
...tab,
|
|
6227
|
-
default: tab.id === id,
|
|
6228
|
-
})));
|
|
6417
|
+
get enabledCount() {
|
|
6418
|
+
return this.enabledItems().length;
|
|
6229
6419
|
}
|
|
6230
6420
|
/**
|
|
6231
|
-
*
|
|
6421
|
+
* Validate item before registration
|
|
6422
|
+
* Override in child classes for custom validation
|
|
6232
6423
|
*/
|
|
6233
|
-
|
|
6234
|
-
|
|
6424
|
+
validateItem(item) {
|
|
6425
|
+
if (!item.id) {
|
|
6426
|
+
throw new Error(`${this.getRegistryName()}: Item must have an id`);
|
|
6427
|
+
}
|
|
6235
6428
|
}
|
|
6236
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6237
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry }); }
|
|
6238
6429
|
}
|
|
6239
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
|
|
6240
|
-
type: Injectable
|
|
6241
|
-
}], ctorParameters: () => [] });
|
|
6242
6430
|
|
|
6243
6431
|
/**
|
|
6244
|
-
*
|
|
6245
|
-
* Manages actions available in the conversation
|
|
6432
|
+
* Conversation Item Action Registry
|
|
6433
|
+
* Manages actions available in the conversation list item context menu
|
|
6246
6434
|
*/
|
|
6247
6435
|
/**
|
|
6248
|
-
*
|
|
6249
|
-
* Manages available actions in the conversation
|
|
6436
|
+
* Conversation Item Action Registry Service
|
|
6437
|
+
* Manages available actions in the conversation list item context menu
|
|
6250
6438
|
*/
|
|
6251
|
-
class
|
|
6439
|
+
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6252
6440
|
constructor() {
|
|
6253
6441
|
super();
|
|
6254
6442
|
this.translation = inject(AXTranslationService);
|
|
6255
6443
|
this.injector = inject(Injector);
|
|
6256
6444
|
// Register default actions from individual token
|
|
6257
|
-
inject(
|
|
6445
|
+
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6258
6446
|
// Register actions from REGISTRY_CONFIG
|
|
6259
6447
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6260
|
-
config?.
|
|
6448
|
+
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6261
6449
|
}
|
|
6262
6450
|
/**
|
|
6263
6451
|
* Get registry name for logging
|
|
6264
6452
|
*/
|
|
6265
6453
|
getRegistryName() {
|
|
6266
|
-
return '
|
|
6454
|
+
return 'ConversationItemActionRegistry';
|
|
6267
6455
|
}
|
|
6268
6456
|
/**
|
|
6269
6457
|
* Get actions for a specific conversation
|
|
6270
6458
|
*/
|
|
6271
|
-
getActionsForConversation(conversation
|
|
6459
|
+
getActionsForConversation(conversation) {
|
|
6272
6460
|
const context = { conversation };
|
|
6273
6461
|
return this.enabledItems()
|
|
6274
6462
|
.filter((action) => {
|
|
6275
|
-
// Filter by location if specified
|
|
6276
|
-
if (location && action.location && action.location !== location) {
|
|
6277
|
-
return false;
|
|
6278
|
-
}
|
|
6279
6463
|
// Filter by visibility
|
|
6280
6464
|
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6281
6465
|
return false;
|
|
@@ -6284,18 +6468,6 @@ class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
|
6284
6468
|
})
|
|
6285
6469
|
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6286
6470
|
}
|
|
6287
|
-
/**
|
|
6288
|
-
* Get inline actions (toolbar buttons)
|
|
6289
|
-
*/
|
|
6290
|
-
getInlineActions(conversation) {
|
|
6291
|
-
return this.getActionsForConversation(conversation, 'inline');
|
|
6292
|
-
}
|
|
6293
|
-
/**
|
|
6294
|
-
* Get menu actions (dropdown items)
|
|
6295
|
-
*/
|
|
6296
|
-
getMenuActions(conversation) {
|
|
6297
|
-
return this.getActionsForConversation(conversation, 'menu');
|
|
6298
|
-
}
|
|
6299
6471
|
isActionEnabled(action, context) {
|
|
6300
6472
|
if (action.enabled === false) {
|
|
6301
6473
|
return false;
|
|
@@ -6339,653 +6511,575 @@ class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
|
6339
6511
|
}
|
|
6340
6512
|
return this.translation.translateSync(action.tooltip);
|
|
6341
6513
|
}
|
|
6342
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6343
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6344
|
-
}
|
|
6345
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6346
|
-
type: Injectable
|
|
6347
|
-
}], ctorParameters: () => [] });
|
|
6348
|
-
|
|
6349
|
-
/**
|
|
6350
|
-
* Message Action Registry
|
|
6351
|
-
* Register custom actions that can be performed on messages
|
|
6352
|
-
*/
|
|
6353
|
-
/**
|
|
6354
|
-
* Message Action Registry Service
|
|
6355
|
-
*/
|
|
6356
|
-
class AXMessageActionRegistry {
|
|
6357
|
-
constructor() {
|
|
6358
|
-
this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : /* istanbul ignore next */ []));
|
|
6359
|
-
this.translation = inject(AXTranslationService);
|
|
6360
|
-
this.injector = inject(Injector);
|
|
6361
|
-
/** All registered actions */
|
|
6362
|
-
this.actions = this._actions.asReadonly();
|
|
6363
|
-
/** Actions sorted by priority */
|
|
6364
|
-
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : /* istanbul ignore next */ []));
|
|
6365
|
-
// Register default actions from individual token
|
|
6366
|
-
const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
|
|
6367
|
-
if (defaultActions) {
|
|
6368
|
-
defaultActions.forEach((action) => this.register(action));
|
|
6369
|
-
}
|
|
6370
|
-
// Register actions from REGISTRY_CONFIG
|
|
6371
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6372
|
-
config?.messageActions?.forEach((action) => this.register(action));
|
|
6373
|
-
}
|
|
6374
|
-
/**
|
|
6375
|
-
* Register a message action
|
|
6376
|
-
*/
|
|
6377
|
-
register(action) {
|
|
6378
|
-
// Validate action
|
|
6379
|
-
if (!action.id) {
|
|
6380
|
-
throw new Error('Message action must have an id');
|
|
6381
|
-
}
|
|
6382
|
-
if (!action.label) {
|
|
6383
|
-
throw new Error('Message action must have a label');
|
|
6384
|
-
}
|
|
6385
|
-
if (!action.handler) {
|
|
6386
|
-
throw new Error('Message action must have a handler');
|
|
6387
|
-
}
|
|
6388
|
-
// Check for duplicates
|
|
6389
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
6390
|
-
if (existing) {
|
|
6391
|
-
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6392
|
-
this.unregister(action.id);
|
|
6393
|
-
}
|
|
6394
|
-
// Add to registry
|
|
6395
|
-
this._actions.update((actions) => [...actions, action]);
|
|
6396
|
-
}
|
|
6397
|
-
/**
|
|
6398
|
-
* Unregister a message action
|
|
6399
|
-
*/
|
|
6400
|
-
unregister(id) {
|
|
6401
|
-
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6402
|
-
}
|
|
6403
|
-
/**
|
|
6404
|
-
* Get all actions for a specific message
|
|
6405
|
-
*/
|
|
6406
|
-
getActions(message, currentUser) {
|
|
6407
|
-
return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
|
|
6408
|
-
runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
|
|
6409
|
-
}
|
|
6410
|
-
/**
|
|
6411
|
-
* Get menu actions for a message
|
|
6412
|
-
*/
|
|
6413
|
-
getMenuActions(message, currentUser) {
|
|
6414
|
-
return this.getActions(message, currentUser);
|
|
6415
|
-
}
|
|
6416
|
-
/** Whether an action is enabled (safe to call outside an injection context). */
|
|
6417
|
-
isActionEnabled(action, message, currentUser) {
|
|
6418
|
-
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6419
|
-
}
|
|
6420
|
-
getActionLabel(action) {
|
|
6421
|
-
return this.translation.translateSync(action.label);
|
|
6422
|
-
}
|
|
6423
|
-
/**
|
|
6424
|
-
* Get inline actions for a message
|
|
6425
|
-
*/
|
|
6426
|
-
getInlineActions(message, currentUser) {
|
|
6427
|
-
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6428
|
-
}
|
|
6429
|
-
/**
|
|
6430
|
-
* Get action by ID
|
|
6431
|
-
*/
|
|
6432
|
-
getActionById(id) {
|
|
6433
|
-
return this._actions().find((a) => a.id === id);
|
|
6434
|
-
}
|
|
6435
|
-
/**
|
|
6436
|
-
* Execute an action
|
|
6437
|
-
*/
|
|
6438
6514
|
async executeAction(actionId, context) {
|
|
6439
|
-
const action = this.
|
|
6515
|
+
const action = this.getById(actionId);
|
|
6440
6516
|
if (!action) {
|
|
6441
6517
|
throw new Error(`Action "${actionId}" not found`);
|
|
6442
6518
|
}
|
|
6443
|
-
|
|
6444
|
-
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6519
|
+
if (!this.isActionEnabled(action, context)) {
|
|
6445
6520
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6446
6521
|
}
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
/**
|
|
6451
|
-
* Clear all actions
|
|
6452
|
-
*/
|
|
6453
|
-
clear() {
|
|
6454
|
-
this._actions.set([]);
|
|
6522
|
+
if (action.handler) {
|
|
6523
|
+
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6524
|
+
}
|
|
6455
6525
|
}
|
|
6456
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6457
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6526
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6527
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry }); }
|
|
6458
6528
|
}
|
|
6459
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
6529
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
|
|
6460
6530
|
type: Injectable
|
|
6461
6531
|
}], ctorParameters: () => [] });
|
|
6462
6532
|
|
|
6463
6533
|
/**
|
|
6464
|
-
*
|
|
6465
|
-
*
|
|
6534
|
+
* Conversation Tab Registry
|
|
6535
|
+
* Register custom tabs for filtering conversations in the sidebar
|
|
6466
6536
|
*/
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
PARTICIPANTS: 'participants',
|
|
6472
|
-
CONVERSATIONS: 'conversations',
|
|
6473
|
-
MESSAGES: 'messages',
|
|
6474
|
-
MEDIA: 'media',
|
|
6475
|
-
SETTINGS: 'settings',
|
|
6476
|
-
};
|
|
6477
|
-
class AXConversationIndexedDbStorage {
|
|
6537
|
+
/**
|
|
6538
|
+
* Conversation Tab Registry Service
|
|
6539
|
+
*/
|
|
6540
|
+
class AXConversationTabRegistry {
|
|
6478
6541
|
constructor() {
|
|
6479
|
-
this.
|
|
6480
|
-
this.
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
request.onerror = () => reject(request.error);
|
|
6493
|
-
request.onsuccess = () => {
|
|
6494
|
-
this.db = request.result;
|
|
6495
|
-
resolve();
|
|
6496
|
-
};
|
|
6497
|
-
request.onupgradeneeded = (event) => {
|
|
6498
|
-
const db = event.target.result;
|
|
6499
|
-
// Create object stores if they don't exist
|
|
6500
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6501
|
-
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6502
|
-
}
|
|
6503
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6504
|
-
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6505
|
-
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6506
|
-
}
|
|
6507
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6508
|
-
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6509
|
-
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6510
|
-
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6511
|
-
}
|
|
6512
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6513
|
-
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6514
|
-
}
|
|
6515
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6516
|
-
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6517
|
-
}
|
|
6518
|
-
};
|
|
6519
|
-
});
|
|
6520
|
-
return this.initPromise;
|
|
6542
|
+
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : /* istanbul ignore next */ []));
|
|
6543
|
+
this.translation = inject(AXTranslationService);
|
|
6544
|
+
/** All registered tabs */
|
|
6545
|
+
this.tabs = this._tabs.asReadonly();
|
|
6546
|
+
/** Enabled tabs sorted by priority */
|
|
6547
|
+
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : /* istanbul ignore next */ []));
|
|
6548
|
+
/** Default active tab */
|
|
6549
|
+
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : /* istanbul ignore next */ []));
|
|
6550
|
+
// Register default tabs from individual token
|
|
6551
|
+
inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6552
|
+
// Register tabs from REGISTRY_CONFIG
|
|
6553
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6554
|
+
config?.conversationTabs?.forEach((tab) => this.register(tab));
|
|
6521
6555
|
}
|
|
6522
6556
|
/**
|
|
6523
|
-
*
|
|
6557
|
+
* Register a conversation tab
|
|
6524
6558
|
*/
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
if (!
|
|
6528
|
-
throw new Error('
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6559
|
+
register(tab) {
|
|
6560
|
+
// Validate tab
|
|
6561
|
+
if (!tab.id) {
|
|
6562
|
+
throw new Error('Conversation tab must have an id');
|
|
6563
|
+
}
|
|
6564
|
+
if (!tab.label) {
|
|
6565
|
+
throw new Error('Conversation tab must have a label');
|
|
6566
|
+
}
|
|
6567
|
+
if (!tab.filter) {
|
|
6568
|
+
throw new Error('Conversation tab must have a filter function');
|
|
6569
|
+
}
|
|
6570
|
+
// Check for duplicates
|
|
6571
|
+
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6572
|
+
if (existing) {
|
|
6573
|
+
console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
|
|
6574
|
+
this.unregister(tab.id);
|
|
6575
|
+
}
|
|
6576
|
+
// Add to registry
|
|
6577
|
+
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6537
6578
|
}
|
|
6538
6579
|
/**
|
|
6539
|
-
*
|
|
6580
|
+
* Unregister a conversation tab
|
|
6540
6581
|
*/
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
if (!this.db)
|
|
6544
|
-
throw new Error('Database not initialized');
|
|
6545
|
-
const db = this.db;
|
|
6546
|
-
return new Promise((resolve, reject) => {
|
|
6547
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6548
|
-
const store = transaction.objectStore(storeName);
|
|
6549
|
-
const request = store.getAll();
|
|
6550
|
-
request.onsuccess = () => resolve(request.result);
|
|
6551
|
-
request.onerror = () => reject(request.error);
|
|
6552
|
-
});
|
|
6582
|
+
unregister(id) {
|
|
6583
|
+
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6553
6584
|
}
|
|
6554
6585
|
/**
|
|
6555
|
-
* Get
|
|
6586
|
+
* Get tab by ID
|
|
6556
6587
|
*/
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
return new Promise((resolve, reject) => {
|
|
6563
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6564
|
-
const store = transaction.objectStore(storeName);
|
|
6565
|
-
const index = store.index(indexName);
|
|
6566
|
-
const request = index.getAll(query);
|
|
6567
|
-
request.onsuccess = () => resolve(request.result);
|
|
6568
|
-
request.onerror = () => reject(request.error);
|
|
6569
|
-
});
|
|
6588
|
+
getTabById(id) {
|
|
6589
|
+
return this._tabs().find((t) => t.id === id);
|
|
6590
|
+
}
|
|
6591
|
+
getTabLabel(tab) {
|
|
6592
|
+
return this.translation.translateSync(tab.label);
|
|
6570
6593
|
}
|
|
6571
6594
|
/**
|
|
6572
|
-
*
|
|
6595
|
+
* Apply tab filter to conversations
|
|
6573
6596
|
*/
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
if (!
|
|
6577
|
-
throw new Error(
|
|
6578
|
-
|
|
6579
|
-
return
|
|
6580
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6581
|
-
const store = transaction.objectStore(storeName);
|
|
6582
|
-
const request = store.put(value);
|
|
6583
|
-
request.onsuccess = () => resolve();
|
|
6584
|
-
request.onerror = () => reject(request.error);
|
|
6585
|
-
});
|
|
6597
|
+
filterConversations(tabId, conversations) {
|
|
6598
|
+
const tab = this.getTabById(tabId);
|
|
6599
|
+
if (!tab) {
|
|
6600
|
+
throw new Error(`Tab "${tabId}" not found`);
|
|
6601
|
+
}
|
|
6602
|
+
return tab.filter(conversations);
|
|
6586
6603
|
}
|
|
6587
6604
|
/**
|
|
6588
|
-
*
|
|
6605
|
+
* Get badge for a tab
|
|
6589
6606
|
*/
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
if (!
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
return
|
|
6596
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6597
|
-
const store = transaction.objectStore(storeName);
|
|
6598
|
-
const request = store.delete(key);
|
|
6599
|
-
request.onsuccess = () => resolve();
|
|
6600
|
-
request.onerror = () => reject(request.error);
|
|
6601
|
-
});
|
|
6607
|
+
getBadge(tabId, conversations) {
|
|
6608
|
+
const tab = this.getTabById(tabId);
|
|
6609
|
+
if (!tab || !tab.badge) {
|
|
6610
|
+
return undefined;
|
|
6611
|
+
}
|
|
6612
|
+
return tab.badge(conversations);
|
|
6602
6613
|
}
|
|
6603
6614
|
/**
|
|
6604
|
-
*
|
|
6615
|
+
* Enable/disable a tab
|
|
6605
6616
|
*/
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
if (!this.db)
|
|
6609
|
-
throw new Error('Database not initialized');
|
|
6610
|
-
const db = this.db;
|
|
6611
|
-
return new Promise((resolve, reject) => {
|
|
6612
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6613
|
-
const store = transaction.objectStore(storeName);
|
|
6614
|
-
const request = store.clear();
|
|
6615
|
-
request.onsuccess = () => resolve();
|
|
6616
|
-
request.onerror = () => reject(request.error);
|
|
6617
|
-
});
|
|
6618
|
-
}
|
|
6619
|
-
// =====================
|
|
6620
|
-
// Convenience Methods
|
|
6621
|
-
// =====================
|
|
6622
|
-
async getParticipant(id) {
|
|
6623
|
-
return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
|
|
6624
|
-
}
|
|
6625
|
-
async getAllParticipants() {
|
|
6626
|
-
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
6627
|
-
}
|
|
6628
|
-
async putParticipant(participant) {
|
|
6629
|
-
return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
|
|
6630
|
-
}
|
|
6631
|
-
async getConversation(id) {
|
|
6632
|
-
return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
6633
|
-
}
|
|
6634
|
-
async getAllConversations() {
|
|
6635
|
-
return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
|
|
6636
|
-
}
|
|
6637
|
-
async putConversation(conversation) {
|
|
6638
|
-
return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
|
|
6639
|
-
}
|
|
6640
|
-
async deleteConversation(id) {
|
|
6641
|
-
return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
6642
|
-
}
|
|
6643
|
-
async getMessage(id) {
|
|
6644
|
-
return this.get(AXConversationIndexedDbStores.MESSAGES, id);
|
|
6645
|
-
}
|
|
6646
|
-
async getAllMessages() {
|
|
6647
|
-
return this.getAll(AXConversationIndexedDbStores.MESSAGES);
|
|
6648
|
-
}
|
|
6649
|
-
async getMessagesByConversation(conversationId) {
|
|
6650
|
-
return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
|
|
6651
|
-
}
|
|
6652
|
-
async putMessage(message) {
|
|
6653
|
-
return this.put(AXConversationIndexedDbStores.MESSAGES, message);
|
|
6654
|
-
}
|
|
6655
|
-
async deleteMessage(id) {
|
|
6656
|
-
return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
|
|
6617
|
+
setEnabled(id, enabled) {
|
|
6618
|
+
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
6657
6619
|
}
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6620
|
+
/**
|
|
6621
|
+
* Set default tab
|
|
6622
|
+
*/
|
|
6623
|
+
setDefault(id) {
|
|
6624
|
+
this._tabs.update((tabs) => tabs.map((tab) => ({
|
|
6625
|
+
...tab,
|
|
6626
|
+
default: tab.id === id,
|
|
6627
|
+
})));
|
|
6661
6628
|
}
|
|
6662
|
-
|
|
6663
|
-
|
|
6629
|
+
/**
|
|
6630
|
+
* Clear all tabs
|
|
6631
|
+
*/
|
|
6632
|
+
clear() {
|
|
6633
|
+
this._tabs.set([]);
|
|
6664
6634
|
}
|
|
6665
|
-
|
|
6666
|
-
|
|
6635
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6636
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry }); }
|
|
6637
|
+
}
|
|
6638
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
|
|
6639
|
+
type: Injectable
|
|
6640
|
+
}], ctorParameters: () => [] });
|
|
6641
|
+
|
|
6642
|
+
/**
|
|
6643
|
+
* Info Bar Action Registry
|
|
6644
|
+
* Manages actions available in the conversation info bar
|
|
6645
|
+
*/
|
|
6646
|
+
/**
|
|
6647
|
+
* Info Bar Action Registry Service
|
|
6648
|
+
* Manages available actions in the conversation info bar
|
|
6649
|
+
*/
|
|
6650
|
+
class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
6651
|
+
constructor() {
|
|
6652
|
+
super();
|
|
6653
|
+
this.translation = inject(AXTranslationService);
|
|
6654
|
+
this.injector = inject(Injector);
|
|
6655
|
+
// Register default actions from individual token
|
|
6656
|
+
inject(DEFAULT_INFO_BAR_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6657
|
+
// Register actions from REGISTRY_CONFIG
|
|
6658
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6659
|
+
config?.infoBarActions?.forEach((action) => this.register(action));
|
|
6667
6660
|
}
|
|
6668
|
-
|
|
6669
|
-
|
|
6661
|
+
/**
|
|
6662
|
+
* Get registry name for logging
|
|
6663
|
+
*/
|
|
6664
|
+
getRegistryName() {
|
|
6665
|
+
return 'InfoBarActionRegistry';
|
|
6670
6666
|
}
|
|
6671
|
-
|
|
6672
|
-
|
|
6667
|
+
/**
|
|
6668
|
+
* Get actions for a specific conversation
|
|
6669
|
+
*/
|
|
6670
|
+
getActionsForConversation(conversation, location) {
|
|
6671
|
+
const context = { conversation };
|
|
6672
|
+
return this.enabledItems()
|
|
6673
|
+
.filter((action) => {
|
|
6674
|
+
// Filter by location if specified
|
|
6675
|
+
if (location && action.location && action.location !== location) {
|
|
6676
|
+
return false;
|
|
6677
|
+
}
|
|
6678
|
+
// Filter by visibility
|
|
6679
|
+
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6680
|
+
return false;
|
|
6681
|
+
}
|
|
6682
|
+
return true;
|
|
6683
|
+
})
|
|
6684
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6685
|
+
}
|
|
6686
|
+
/**
|
|
6687
|
+
* Get inline actions (toolbar buttons)
|
|
6688
|
+
*/
|
|
6689
|
+
getInlineActions(conversation) {
|
|
6690
|
+
return this.getActionsForConversation(conversation, 'inline');
|
|
6673
6691
|
}
|
|
6674
|
-
}
|
|
6675
|
-
// Export singleton instance
|
|
6676
|
-
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
6677
|
-
|
|
6678
|
-
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
6679
|
-
__proto__: null,
|
|
6680
|
-
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
6681
|
-
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
6682
|
-
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
6683
|
-
});
|
|
6684
|
-
|
|
6685
|
-
class AXConversationMessageUtilsService {
|
|
6686
6692
|
/**
|
|
6687
|
-
*
|
|
6688
|
-
* are treated as missing data.
|
|
6693
|
+
* Get menu actions (dropdown items)
|
|
6689
6694
|
*/
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6695
|
+
getMenuActions(conversation) {
|
|
6696
|
+
return this.getActionsForConversation(conversation, 'menu');
|
|
6697
|
+
}
|
|
6698
|
+
isActionEnabled(action, context) {
|
|
6699
|
+
if (action.enabled === false) {
|
|
6700
|
+
return false;
|
|
6693
6701
|
}
|
|
6694
|
-
|
|
6695
|
-
|
|
6702
|
+
if (action.enabledWhen) {
|
|
6703
|
+
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6704
|
+
}
|
|
6705
|
+
return true;
|
|
6696
6706
|
}
|
|
6697
6707
|
/**
|
|
6698
|
-
* Get
|
|
6708
|
+
* Get label for an action
|
|
6699
6709
|
*/
|
|
6700
|
-
|
|
6701
|
-
|
|
6710
|
+
getActionLabel(action, conversation) {
|
|
6711
|
+
if (typeof action.label === 'function') {
|
|
6712
|
+
const labelFactory = action.label;
|
|
6713
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6714
|
+
}
|
|
6715
|
+
return this.translation.translateSync(action.label);
|
|
6702
6716
|
}
|
|
6703
6717
|
/**
|
|
6704
|
-
*
|
|
6718
|
+
* Get icon for an action
|
|
6705
6719
|
*/
|
|
6706
|
-
|
|
6707
|
-
if (
|
|
6720
|
+
getActionIcon(action, conversation) {
|
|
6721
|
+
if (!action.icon)
|
|
6708
6722
|
return undefined;
|
|
6723
|
+
if (typeof action.icon === 'function') {
|
|
6724
|
+
const iconFactory = action.icon;
|
|
6725
|
+
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6709
6726
|
}
|
|
6710
|
-
return
|
|
6727
|
+
return action.icon;
|
|
6711
6728
|
}
|
|
6712
6729
|
/**
|
|
6713
|
-
* Get
|
|
6730
|
+
* Get tooltip for an action
|
|
6714
6731
|
*/
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6732
|
+
getActionTooltip(action, conversation) {
|
|
6733
|
+
if (!action.tooltip)
|
|
6734
|
+
return undefined;
|
|
6735
|
+
if (typeof action.tooltip === 'function') {
|
|
6736
|
+
const tooltipFactory = action.tooltip;
|
|
6737
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6738
|
+
}
|
|
6739
|
+
return this.translation.translateSync(action.tooltip);
|
|
6740
|
+
}
|
|
6741
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6742
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6743
|
+
}
|
|
6744
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6745
|
+
type: Injectable
|
|
6746
|
+
}], ctorParameters: () => [] });
|
|
6747
|
+
|
|
6748
|
+
/**
|
|
6749
|
+
* Message Action Registry
|
|
6750
|
+
* Register custom actions that can be performed on messages
|
|
6751
|
+
*/
|
|
6752
|
+
/**
|
|
6753
|
+
* Message Action Registry Service
|
|
6754
|
+
*/
|
|
6755
|
+
class AXMessageActionRegistry {
|
|
6756
|
+
constructor() {
|
|
6757
|
+
this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : /* istanbul ignore next */ []));
|
|
6758
|
+
this.translation = inject(AXTranslationService);
|
|
6759
|
+
this.injector = inject(Injector);
|
|
6760
|
+
/** All registered actions */
|
|
6761
|
+
this.actions = this._actions.asReadonly();
|
|
6762
|
+
/** Actions sorted by priority */
|
|
6763
|
+
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : /* istanbul ignore next */ []));
|
|
6764
|
+
// Register default actions from individual token
|
|
6765
|
+
const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
|
|
6766
|
+
if (defaultActions) {
|
|
6767
|
+
defaultActions.forEach((action) => this.register(action));
|
|
6768
|
+
}
|
|
6769
|
+
// Register actions from REGISTRY_CONFIG
|
|
6770
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6771
|
+
config?.messageActions?.forEach((action) => this.register(action));
|
|
6718
6772
|
}
|
|
6719
6773
|
/**
|
|
6720
|
-
*
|
|
6774
|
+
* Register a message action
|
|
6721
6775
|
*/
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6776
|
+
register(action) {
|
|
6777
|
+
// Validate action
|
|
6778
|
+
if (!action.id) {
|
|
6779
|
+
throw new Error('Message action must have an id');
|
|
6780
|
+
}
|
|
6781
|
+
if (!action.label) {
|
|
6782
|
+
throw new Error('Message action must have a label');
|
|
6783
|
+
}
|
|
6784
|
+
if (!action.handler) {
|
|
6785
|
+
throw new Error('Message action must have a handler');
|
|
6786
|
+
}
|
|
6787
|
+
// Check for duplicates
|
|
6788
|
+
const existing = this._actions().find((a) => a.id === action.id);
|
|
6789
|
+
if (existing) {
|
|
6790
|
+
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6791
|
+
this.unregister(action.id);
|
|
6792
|
+
}
|
|
6793
|
+
// Add to registry
|
|
6794
|
+
this._actions.update((actions) => [...actions, action]);
|
|
6725
6795
|
}
|
|
6726
6796
|
/**
|
|
6727
|
-
*
|
|
6728
|
-
* participant `icon` first, then conversation-level `icon`.
|
|
6797
|
+
* Unregister a message action
|
|
6729
6798
|
*/
|
|
6730
|
-
|
|
6731
|
-
|
|
6732
|
-
if (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar)) {
|
|
6733
|
-
return undefined;
|
|
6734
|
-
}
|
|
6735
|
-
return (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.icon) ??
|
|
6736
|
-
AXConversationMessageUtilsService.getConversationAvatarIcon(conversation));
|
|
6799
|
+
unregister(id) {
|
|
6800
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6737
6801
|
}
|
|
6738
6802
|
/**
|
|
6739
|
-
* Get
|
|
6803
|
+
* Get all actions for a specific message
|
|
6740
6804
|
*/
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
return name
|
|
6745
|
-
.split(' ')
|
|
6746
|
-
.map((n) => n[0])
|
|
6747
|
-
.join('')
|
|
6748
|
-
.toUpperCase()
|
|
6749
|
-
.substring(0, 2);
|
|
6805
|
+
getActions(message, currentUser) {
|
|
6806
|
+
return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
|
|
6807
|
+
runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
|
|
6750
6808
|
}
|
|
6751
6809
|
/**
|
|
6752
|
-
*
|
|
6810
|
+
* Get menu actions for a message
|
|
6753
6811
|
*/
|
|
6754
|
-
|
|
6755
|
-
return
|
|
6812
|
+
getMenuActions(message, currentUser) {
|
|
6813
|
+
return this.getActions(message, currentUser);
|
|
6814
|
+
}
|
|
6815
|
+
/** Whether an action is enabled (safe to call outside an injection context). */
|
|
6816
|
+
isActionEnabled(action, message, currentUser) {
|
|
6817
|
+
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6818
|
+
}
|
|
6819
|
+
getActionLabel(action) {
|
|
6820
|
+
return this.translation.translateSync(action.label);
|
|
6756
6821
|
}
|
|
6757
6822
|
/**
|
|
6758
|
-
* Get
|
|
6823
|
+
* Get inline actions for a message
|
|
6759
6824
|
*/
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
return message.payload.text;
|
|
6763
|
-
}
|
|
6764
|
-
return `[${message.type}]`;
|
|
6825
|
+
getInlineActions(message, currentUser) {
|
|
6826
|
+
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6765
6827
|
}
|
|
6766
6828
|
/**
|
|
6767
|
-
*
|
|
6829
|
+
* Get action by ID
|
|
6768
6830
|
*/
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
const imagePayload = normalizeImagePayload(message.payload);
|
|
6784
|
-
const first = imagePayload.images[0];
|
|
6785
|
-
const label = imagePayload.caption?.trim() ||
|
|
6786
|
-
(imagePayload.images && imagePayload.images.length > 1
|
|
6787
|
-
? `${imagePayload.images.length} images`
|
|
6788
|
-
: '');
|
|
6789
|
-
return {
|
|
6790
|
-
value: label || first?.thumbnailUrl || first?.url || '',
|
|
6791
|
-
type: 'image',
|
|
6792
|
-
icon: 'fa-light fa-image',
|
|
6793
|
-
};
|
|
6794
|
-
}
|
|
6795
|
-
case 'video': {
|
|
6796
|
-
const videoPayload = normalizeVideoPayload(message.payload);
|
|
6797
|
-
const first = videoPayload.videos[0];
|
|
6798
|
-
const label = videoPayload.caption?.trim() ||
|
|
6799
|
-
(videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
|
|
6800
|
-
return {
|
|
6801
|
-
value: label || first?.url || '',
|
|
6802
|
-
type: 'video',
|
|
6803
|
-
icon: 'fa-light fa-video',
|
|
6804
|
-
};
|
|
6805
|
-
}
|
|
6806
|
-
case 'audio': {
|
|
6807
|
-
const audioPayload = normalizeAudioPayload(message.payload);
|
|
6808
|
-
const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
|
|
6809
|
-
const first = audioPayload.audios[0];
|
|
6810
|
-
const label = audioPayload.caption?.trim() ||
|
|
6811
|
-
(audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
|
|
6812
|
-
return {
|
|
6813
|
-
value: label || first?.url || '',
|
|
6814
|
-
type: 'audio',
|
|
6815
|
-
icon: 'fa-light fa-music',
|
|
6816
|
-
};
|
|
6817
|
-
}
|
|
6818
|
-
case 'voice': {
|
|
6819
|
-
const voicePayload = message.payload;
|
|
6820
|
-
return {
|
|
6821
|
-
value: voicePayload.url || '',
|
|
6822
|
-
type: 'voice',
|
|
6823
|
-
icon: 'fa-light fa-microphone',
|
|
6824
|
-
};
|
|
6825
|
-
}
|
|
6826
|
-
case 'file': {
|
|
6827
|
-
const filePayload = normalizeFilePayload(message.payload);
|
|
6828
|
-
const names = filePayload.files.map((f) => f.name).join(', ');
|
|
6829
|
-
const label = filePayload.caption?.trim() ||
|
|
6830
|
-
(filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
|
|
6831
|
-
return {
|
|
6832
|
-
value: label || names,
|
|
6833
|
-
type: 'file',
|
|
6834
|
-
icon: 'fa-light fa-file',
|
|
6835
|
-
};
|
|
6836
|
-
}
|
|
6837
|
-
case 'location': {
|
|
6838
|
-
const locationPayload = message.payload;
|
|
6839
|
-
return {
|
|
6840
|
-
value: locationPayload.latitude && locationPayload.longitude
|
|
6841
|
-
? `${locationPayload.latitude},${locationPayload.longitude}`
|
|
6842
|
-
: '',
|
|
6843
|
-
type: 'location',
|
|
6844
|
-
icon: 'fa-light fa-location-dot',
|
|
6845
|
-
};
|
|
6846
|
-
}
|
|
6847
|
-
case 'sticker': {
|
|
6848
|
-
const stickerPayload = message.payload;
|
|
6849
|
-
return {
|
|
6850
|
-
value: stickerPayload.url || '',
|
|
6851
|
-
type: 'sticker',
|
|
6852
|
-
icon: 'fa-light fa-face-smile',
|
|
6853
|
-
};
|
|
6854
|
-
}
|
|
6855
|
-
default:
|
|
6856
|
-
return {
|
|
6857
|
-
value: '',
|
|
6858
|
-
type: message.type,
|
|
6859
|
-
icon: 'fa-light fa-message',
|
|
6860
|
-
};
|
|
6831
|
+
getActionById(id) {
|
|
6832
|
+
return this._actions().find((a) => a.id === id);
|
|
6833
|
+
}
|
|
6834
|
+
/**
|
|
6835
|
+
* Execute an action
|
|
6836
|
+
*/
|
|
6837
|
+
async executeAction(actionId, context) {
|
|
6838
|
+
const action = this.getActionById(actionId);
|
|
6839
|
+
if (!action) {
|
|
6840
|
+
throw new Error(`Action "${actionId}" not found`);
|
|
6841
|
+
}
|
|
6842
|
+
// Check if enabled
|
|
6843
|
+
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6844
|
+
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6861
6845
|
}
|
|
6846
|
+
// Execute handler
|
|
6847
|
+
await runInInjectionContext(this.injector, () => action.handler(context));
|
|
6862
6848
|
}
|
|
6863
6849
|
/**
|
|
6864
|
-
*
|
|
6850
|
+
* Clear all actions
|
|
6865
6851
|
*/
|
|
6866
|
-
|
|
6867
|
-
|
|
6852
|
+
clear() {
|
|
6853
|
+
this._actions.set([]);
|
|
6854
|
+
}
|
|
6855
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMessageActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6856
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMessageActionRegistry }); }
|
|
6857
|
+
}
|
|
6858
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXMessageActionRegistry, decorators: [{
|
|
6859
|
+
type: Injectable
|
|
6860
|
+
}], ctorParameters: () => [] });
|
|
6861
|
+
|
|
6862
|
+
/**
|
|
6863
|
+
* IndexedDB Storage Wrapper
|
|
6864
|
+
* Provides persistent storage for conversation data
|
|
6865
|
+
*/
|
|
6866
|
+
const DB_NAME = 'acorex-conversation-db';
|
|
6867
|
+
const DB_VERSION = 2;
|
|
6868
|
+
// Store names
|
|
6869
|
+
const AXConversationIndexedDbStores = {
|
|
6870
|
+
PARTICIPANTS: 'participants',
|
|
6871
|
+
CONVERSATIONS: 'conversations',
|
|
6872
|
+
MESSAGES: 'messages',
|
|
6873
|
+
MEDIA: 'media',
|
|
6874
|
+
SETTINGS: 'settings',
|
|
6875
|
+
};
|
|
6876
|
+
class AXConversationIndexedDbStorage {
|
|
6877
|
+
constructor() {
|
|
6878
|
+
this.db = null;
|
|
6879
|
+
this.initPromise = null;
|
|
6868
6880
|
}
|
|
6869
6881
|
/**
|
|
6870
|
-
*
|
|
6882
|
+
* Initialize IndexedDB
|
|
6871
6883
|
*/
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6884
|
+
async init() {
|
|
6885
|
+
if (this.db)
|
|
6886
|
+
return;
|
|
6887
|
+
if (this.initPromise)
|
|
6888
|
+
return this.initPromise;
|
|
6889
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
6890
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
6891
|
+
request.onerror = () => reject(request.error);
|
|
6892
|
+
request.onsuccess = () => {
|
|
6893
|
+
this.db = request.result;
|
|
6894
|
+
resolve();
|
|
6895
|
+
};
|
|
6896
|
+
request.onupgradeneeded = (event) => {
|
|
6897
|
+
const db = event.target.result;
|
|
6898
|
+
// Create object stores if they don't exist
|
|
6899
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6900
|
+
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6901
|
+
}
|
|
6902
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6903
|
+
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6904
|
+
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6905
|
+
}
|
|
6906
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6907
|
+
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6908
|
+
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6909
|
+
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6910
|
+
}
|
|
6911
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6912
|
+
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6913
|
+
}
|
|
6914
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6915
|
+
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6916
|
+
}
|
|
6917
|
+
};
|
|
6918
|
+
});
|
|
6919
|
+
return this.initPromise;
|
|
6887
6920
|
}
|
|
6888
6921
|
/**
|
|
6889
|
-
*
|
|
6922
|
+
* Get a value from a store
|
|
6890
6923
|
*/
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
if (
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6924
|
+
async get(storeName, key) {
|
|
6925
|
+
await this.init();
|
|
6926
|
+
if (!this.db)
|
|
6927
|
+
throw new Error('Database not initialized');
|
|
6928
|
+
const db = this.db;
|
|
6929
|
+
return new Promise((resolve, reject) => {
|
|
6930
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6931
|
+
const store = transaction.objectStore(storeName);
|
|
6932
|
+
const request = store.get(key);
|
|
6933
|
+
request.onsuccess = () => resolve(request.result);
|
|
6934
|
+
request.onerror = () => reject(request.error);
|
|
6935
|
+
});
|
|
6902
6936
|
}
|
|
6903
6937
|
/**
|
|
6904
|
-
*
|
|
6938
|
+
* Get all values from a store
|
|
6905
6939
|
*/
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6940
|
+
async getAll(storeName) {
|
|
6941
|
+
await this.init();
|
|
6942
|
+
if (!this.db)
|
|
6943
|
+
throw new Error('Database not initialized');
|
|
6944
|
+
const db = this.db;
|
|
6945
|
+
return new Promise((resolve, reject) => {
|
|
6946
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6947
|
+
const store = transaction.objectStore(storeName);
|
|
6948
|
+
const request = store.getAll();
|
|
6949
|
+
request.onsuccess = () => resolve(request.result);
|
|
6950
|
+
request.onerror = () => reject(request.error);
|
|
6951
|
+
});
|
|
6915
6952
|
}
|
|
6916
6953
|
/**
|
|
6917
|
-
* Get
|
|
6954
|
+
* Get values by index
|
|
6918
6955
|
*/
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6956
|
+
async getAllByIndex(storeName, indexName, query) {
|
|
6957
|
+
await this.init();
|
|
6958
|
+
if (!this.db)
|
|
6959
|
+
throw new Error('Database not initialized');
|
|
6960
|
+
const db = this.db;
|
|
6961
|
+
return new Promise((resolve, reject) => {
|
|
6962
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6963
|
+
const store = transaction.objectStore(storeName);
|
|
6964
|
+
const index = store.index(indexName);
|
|
6965
|
+
const request = index.getAll(query);
|
|
6966
|
+
request.onsuccess = () => resolve(request.result);
|
|
6967
|
+
request.onerror = () => reject(request.error);
|
|
6968
|
+
});
|
|
6924
6969
|
}
|
|
6925
6970
|
/**
|
|
6926
|
-
*
|
|
6971
|
+
* Put a value into a store
|
|
6927
6972
|
*/
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
if (
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6973
|
+
async put(storeName, value) {
|
|
6974
|
+
await this.init();
|
|
6975
|
+
if (!this.db)
|
|
6976
|
+
throw new Error('Database not initialized');
|
|
6977
|
+
const db = this.db;
|
|
6978
|
+
return new Promise((resolve, reject) => {
|
|
6979
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6980
|
+
const store = transaction.objectStore(storeName);
|
|
6981
|
+
const request = store.put(value);
|
|
6982
|
+
request.onsuccess = () => resolve();
|
|
6983
|
+
request.onerror = () => reject(request.error);
|
|
6984
|
+
});
|
|
6985
|
+
}
|
|
6986
|
+
/**
|
|
6987
|
+
* Delete a value from a store
|
|
6988
|
+
*/
|
|
6989
|
+
async delete(storeName, key) {
|
|
6990
|
+
await this.init();
|
|
6991
|
+
if (!this.db)
|
|
6992
|
+
throw new Error('Database not initialized');
|
|
6993
|
+
const db = this.db;
|
|
6994
|
+
return new Promise((resolve, reject) => {
|
|
6995
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6996
|
+
const store = transaction.objectStore(storeName);
|
|
6997
|
+
const request = store.delete(key);
|
|
6998
|
+
request.onsuccess = () => resolve();
|
|
6999
|
+
request.onerror = () => reject(request.error);
|
|
7000
|
+
});
|
|
7001
|
+
}
|
|
7002
|
+
/**
|
|
7003
|
+
* Clear all data from a store
|
|
7004
|
+
*/
|
|
7005
|
+
async clear(storeName) {
|
|
7006
|
+
await this.init();
|
|
7007
|
+
if (!this.db)
|
|
7008
|
+
throw new Error('Database not initialized');
|
|
7009
|
+
const db = this.db;
|
|
7010
|
+
return new Promise((resolve, reject) => {
|
|
7011
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
7012
|
+
const store = transaction.objectStore(storeName);
|
|
7013
|
+
const request = store.clear();
|
|
7014
|
+
request.onsuccess = () => resolve();
|
|
7015
|
+
request.onerror = () => reject(request.error);
|
|
7016
|
+
});
|
|
7017
|
+
}
|
|
7018
|
+
// =====================
|
|
7019
|
+
// Convenience Methods
|
|
7020
|
+
// =====================
|
|
7021
|
+
async getParticipant(id) {
|
|
7022
|
+
return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
|
|
7023
|
+
}
|
|
7024
|
+
async getAllParticipants() {
|
|
7025
|
+
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
7026
|
+
}
|
|
7027
|
+
async putParticipant(participant) {
|
|
7028
|
+
return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
|
|
7029
|
+
}
|
|
7030
|
+
async getConversation(id) {
|
|
7031
|
+
return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7032
|
+
}
|
|
7033
|
+
async getAllConversations() {
|
|
7034
|
+
return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
|
|
7035
|
+
}
|
|
7036
|
+
async putConversation(conversation) {
|
|
7037
|
+
return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
|
|
7038
|
+
}
|
|
7039
|
+
async deleteConversation(id) {
|
|
7040
|
+
return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7041
|
+
}
|
|
7042
|
+
async getMessage(id) {
|
|
7043
|
+
return this.get(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7044
|
+
}
|
|
7045
|
+
async getAllMessages() {
|
|
7046
|
+
return this.getAll(AXConversationIndexedDbStores.MESSAGES);
|
|
7047
|
+
}
|
|
7048
|
+
async getMessagesByConversation(conversationId) {
|
|
7049
|
+
return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
|
|
6942
7050
|
}
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
*/
|
|
6946
|
-
static formatLastSeen(date) {
|
|
6947
|
-
const now = new Date();
|
|
6948
|
-
const diff = now.getTime() - date.getTime();
|
|
6949
|
-
const seconds = Math.floor(diff / 1000);
|
|
6950
|
-
if (seconds < 60)
|
|
6951
|
-
return translateSync('@acorex:chat.time.just-now');
|
|
6952
|
-
if (seconds < 3600)
|
|
6953
|
-
return translateSync('@acorex:chat.time.minutes-ago', { params: { count: Math.floor(seconds / 60) } });
|
|
6954
|
-
if (seconds < 86400)
|
|
6955
|
-
return translateSync('@acorex:chat.time.hours-ago', { params: { count: Math.floor(seconds / 3600) } });
|
|
6956
|
-
if (seconds < 604800)
|
|
6957
|
-
return translateSync('@acorex:chat.time.days-ago', { params: { count: Math.floor(seconds / 86400) } });
|
|
6958
|
-
return date.toLocaleDateString();
|
|
7051
|
+
async putMessage(message) {
|
|
7052
|
+
return this.put(AXConversationIndexedDbStores.MESSAGES, message);
|
|
6959
7053
|
}
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
case 'group':
|
|
6979
|
-
return translateSync('@acorex:chat.members.count', { params: { count: conversation.participants.length } });
|
|
6980
|
-
case 'channel':
|
|
6981
|
-
return translateSync('@acorex:chat.members.subscribers-count', { params: { count: conversation.participants.length } });
|
|
6982
|
-
case 'bot':
|
|
6983
|
-
return translateSync('@acorex:chat.bot');
|
|
6984
|
-
default:
|
|
6985
|
-
return '';
|
|
6986
|
-
}
|
|
7054
|
+
async deleteMessage(id) {
|
|
7055
|
+
return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7056
|
+
}
|
|
7057
|
+
async getSetting(key) {
|
|
7058
|
+
const result = await this.get(AXConversationIndexedDbStores.SETTINGS, key);
|
|
7059
|
+
return result?.value;
|
|
7060
|
+
}
|
|
7061
|
+
async putSetting(key, value) {
|
|
7062
|
+
return this.put(AXConversationIndexedDbStores.SETTINGS, { key, value });
|
|
7063
|
+
}
|
|
7064
|
+
async getMedia(id) {
|
|
7065
|
+
return this.get(AXConversationIndexedDbStores.MEDIA, id);
|
|
7066
|
+
}
|
|
7067
|
+
async putMedia(record) {
|
|
7068
|
+
return this.put(AXConversationIndexedDbStores.MEDIA, record);
|
|
7069
|
+
}
|
|
7070
|
+
async deleteMedia(id) {
|
|
7071
|
+
return this.delete(AXConversationIndexedDbStores.MEDIA, id);
|
|
6987
7072
|
}
|
|
6988
7073
|
}
|
|
7074
|
+
// Export singleton instance
|
|
7075
|
+
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
7076
|
+
|
|
7077
|
+
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
7078
|
+
__proto__: null,
|
|
7079
|
+
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
7080
|
+
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
7081
|
+
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
7082
|
+
});
|
|
6989
7083
|
|
|
6990
7084
|
/**
|
|
6991
7085
|
* Shared read/write helpers for IndexedDB chat storage (in-memory + persistence).
|
|
@@ -8431,20 +8525,27 @@ class AXConversationIndexedDbConversationApi extends AXConversationApi {
|
|
|
8431
8525
|
// Conversation CRUD
|
|
8432
8526
|
// =====================
|
|
8433
8527
|
async createConversation(data) {
|
|
8528
|
+
const creatorId = data.creatorId ?? conversationSharedStorage.currentUserId;
|
|
8529
|
+
const participantIds = new Set(data.participantIds);
|
|
8530
|
+
if (creatorId) {
|
|
8531
|
+
participantIds.add(creatorId);
|
|
8532
|
+
}
|
|
8434
8533
|
const conversation = {
|
|
8435
8534
|
id: `conv-${Date.now()}`,
|
|
8436
8535
|
type: data.type,
|
|
8437
|
-
|
|
8536
|
+
creatorId,
|
|
8537
|
+
title: data.title ||
|
|
8538
|
+
(data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
|
|
8438
8539
|
avatar: data.avatar,
|
|
8439
8540
|
description: data.description,
|
|
8440
|
-
participants:
|
|
8541
|
+
participants: [...participantIds].map((id) => {
|
|
8441
8542
|
const participant = conversationSharedStorage.participants.get(id);
|
|
8442
8543
|
if (!participant) {
|
|
8443
8544
|
throw this.createError('PARTICIPANT_NOT_FOUND', `Participant ${id} not found`, 404);
|
|
8444
8545
|
}
|
|
8445
8546
|
return {
|
|
8446
8547
|
...participant,
|
|
8447
|
-
role: id ===
|
|
8548
|
+
role: id === creatorId ? 'admin' : 'member',
|
|
8448
8549
|
};
|
|
8449
8550
|
}),
|
|
8450
8551
|
lastMessageAt: new Date(),
|
|
@@ -10414,41 +10515,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
10414
10515
|
`, 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));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"] }]
|
|
10415
10516
|
}], propDecorators: { fileInput: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], showClearButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showClearButton", required: false }] }], avatarUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "avatarUrl", required: false }] }, { type: i0.Output, args: ["avatarUrlChange"] }], onFileSelected: [{ type: i0.Output, args: ["onFileSelected"] }] } });
|
|
10416
10517
|
|
|
10417
|
-
/** Other participant in a private chat (excludes the current user). */
|
|
10418
|
-
function resolvePrivatePeerUserId(conversation, currentUserId) {
|
|
10419
|
-
if (conversation.type !== 'private') {
|
|
10420
|
-
return undefined;
|
|
10421
|
-
}
|
|
10422
|
-
const currentId = currentUserId ?? 'current-user';
|
|
10423
|
-
return conversation.participants.find((participant) => participant.id !== currentId)?.id;
|
|
10424
|
-
}
|
|
10425
|
-
/** Whether `auto` kind should render a user avatar for this conversation. */
|
|
10426
|
-
function shouldUseUserAvatarForConversation(conversation, currentUserId) {
|
|
10427
|
-
return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
|
|
10428
|
-
}
|
|
10429
|
-
function resolveUserAvatarDisplay(userId, conversation, message) {
|
|
10430
|
-
if (message && conversation) {
|
|
10431
|
-
return {
|
|
10432
|
-
name: AXConversationMessageUtilsService.getSenderName(message, conversation),
|
|
10433
|
-
avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
|
|
10434
|
-
icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
|
|
10435
|
-
};
|
|
10436
|
-
}
|
|
10437
|
-
const participant = conversation?.participants.find((p) => p.id === userId);
|
|
10438
|
-
return {
|
|
10439
|
-
name: participant?.name ?? userId,
|
|
10440
|
-
avatar: participant?.avatar,
|
|
10441
|
-
icon: participant?.icon,
|
|
10442
|
-
};
|
|
10443
|
-
}
|
|
10444
|
-
function resolveConversationAvatarDisplay(conversation) {
|
|
10445
|
-
return {
|
|
10446
|
-
name: conversation.title,
|
|
10447
|
-
avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
|
|
10448
|
-
icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
|
|
10449
|
-
};
|
|
10450
|
-
}
|
|
10451
|
-
|
|
10452
10518
|
/**
|
|
10453
10519
|
* Conversation avatar host — renders a registered custom component or built-in fallback.
|
|
10454
10520
|
*/
|
|
@@ -10533,7 +10599,7 @@ class AXConversationAvatarComponent {
|
|
|
10533
10599
|
if (!conversation) {
|
|
10534
10600
|
return { name: explicitName ?? '?', avatar: explicitAvatar, icon: explicitIcon };
|
|
10535
10601
|
}
|
|
10536
|
-
const resolved = resolveConversationAvatarDisplay(conversation);
|
|
10602
|
+
const resolved = resolveConversationAvatarDisplay(conversation, this.currentUserId());
|
|
10537
10603
|
return {
|
|
10538
10604
|
name: explicitName ?? resolved.name,
|
|
10539
10605
|
avatar: explicitAvatar ?? resolved.avatar,
|
|
@@ -10684,6 +10750,58 @@ class AXConversationDateUtilsService {
|
|
|
10684
10750
|
}
|
|
10685
10751
|
}
|
|
10686
10752
|
|
|
10753
|
+
function toUploaderReference(item) {
|
|
10754
|
+
return {
|
|
10755
|
+
url: item.url,
|
|
10756
|
+
mediaId: item.mediaId,
|
|
10757
|
+
mimeType: item.mimeType,
|
|
10758
|
+
size: item.size,
|
|
10759
|
+
};
|
|
10760
|
+
}
|
|
10761
|
+
|
|
10762
|
+
/**
|
|
10763
|
+
* Resolves multimedia payload URLs for renderers (direct url or mediaId via uploader).
|
|
10764
|
+
*/
|
|
10765
|
+
/**
|
|
10766
|
+
* Creates a signal of the resolved playback URL for a media reference.
|
|
10767
|
+
*/
|
|
10768
|
+
function createResolvedMediaUrlSignal(reference) {
|
|
10769
|
+
const uploader = inject(AXUploaderService);
|
|
10770
|
+
const destroyRef = inject(DestroyRef);
|
|
10771
|
+
const resolvedUrl = signal(null, ...(ngDevMode ? [{ debugName: "resolvedUrl" }] : /* istanbul ignore next */ []));
|
|
10772
|
+
let abortController = null;
|
|
10773
|
+
effect(() => {
|
|
10774
|
+
const ref = reference();
|
|
10775
|
+
abortController?.abort();
|
|
10776
|
+
abortController = new AbortController();
|
|
10777
|
+
const trimmedUrl = ref?.url?.trim();
|
|
10778
|
+
if (!trimmedUrl && !ref?.mediaId) {
|
|
10779
|
+
resolvedUrl.set(null);
|
|
10780
|
+
return;
|
|
10781
|
+
}
|
|
10782
|
+
const directUrl = trimmedUrl && !isNonPersistableMediaUrl(trimmedUrl) ? trimmedUrl : undefined;
|
|
10783
|
+
if (!ref?.mediaId) {
|
|
10784
|
+
resolvedUrl.set(directUrl ?? null);
|
|
10785
|
+
return;
|
|
10786
|
+
}
|
|
10787
|
+
resolvedUrl.set(directUrl ?? null);
|
|
10788
|
+
void uploader
|
|
10789
|
+
.resolvePlaybackUrl(ref, abortController.signal)
|
|
10790
|
+
.then((url) => {
|
|
10791
|
+
if (!abortController?.signal.aborted) {
|
|
10792
|
+
resolvedUrl.set(url);
|
|
10793
|
+
}
|
|
10794
|
+
})
|
|
10795
|
+
.catch(() => {
|
|
10796
|
+
if (!abortController?.signal.aborted && directUrl) {
|
|
10797
|
+
resolvedUrl.set(directUrl);
|
|
10798
|
+
}
|
|
10799
|
+
});
|
|
10800
|
+
});
|
|
10801
|
+
destroyRef.onDestroy(() => abortController?.abort());
|
|
10802
|
+
return resolvedUrl.asReadonly();
|
|
10803
|
+
}
|
|
10804
|
+
|
|
10687
10805
|
function isMessageDeliveryPending(status) {
|
|
10688
10806
|
return status === 'sending';
|
|
10689
10807
|
}
|
|
@@ -10882,15 +11000,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
10882
11000
|
`, 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"] }]
|
|
10883
11001
|
}], propDecorators: { message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: true }] }] } });
|
|
10884
11002
|
|
|
10885
|
-
function toUploaderReference(item) {
|
|
10886
|
-
return {
|
|
10887
|
-
url: item.url,
|
|
10888
|
-
mediaId: item.mediaId,
|
|
10889
|
-
mimeType: item.mimeType,
|
|
10890
|
-
size: item.size,
|
|
10891
|
-
};
|
|
10892
|
-
}
|
|
10893
|
-
|
|
10894
11003
|
/** Shared formatting for message renderer UIs. */
|
|
10895
11004
|
function formatMediaDuration(seconds) {
|
|
10896
11005
|
if (!Number.isFinite(seconds)) {
|
|
@@ -10910,49 +11019,6 @@ function formatFileByteSize(bytes) {
|
|
|
10910
11019
|
return `${Math.round((bytes / k ** i) * 100) / 100} ${sizes[i]}`;
|
|
10911
11020
|
}
|
|
10912
11021
|
|
|
10913
|
-
/**
|
|
10914
|
-
* Resolves multimedia payload URLs for renderers (direct url or mediaId via uploader).
|
|
10915
|
-
*/
|
|
10916
|
-
/**
|
|
10917
|
-
* Creates a signal of the resolved playback URL for a media reference.
|
|
10918
|
-
*/
|
|
10919
|
-
function createResolvedMediaUrlSignal(reference) {
|
|
10920
|
-
const uploader = inject(AXUploaderService);
|
|
10921
|
-
const destroyRef = inject(DestroyRef);
|
|
10922
|
-
const resolvedUrl = signal(null, ...(ngDevMode ? [{ debugName: "resolvedUrl" }] : /* istanbul ignore next */ []));
|
|
10923
|
-
let abortController = null;
|
|
10924
|
-
effect(() => {
|
|
10925
|
-
const ref = reference();
|
|
10926
|
-
abortController?.abort();
|
|
10927
|
-
abortController = new AbortController();
|
|
10928
|
-
const trimmedUrl = ref?.url?.trim();
|
|
10929
|
-
if (!trimmedUrl && !ref?.mediaId) {
|
|
10930
|
-
resolvedUrl.set(null);
|
|
10931
|
-
return;
|
|
10932
|
-
}
|
|
10933
|
-
const directUrl = trimmedUrl && !isNonPersistableMediaUrl(trimmedUrl) ? trimmedUrl : undefined;
|
|
10934
|
-
if (!ref?.mediaId) {
|
|
10935
|
-
resolvedUrl.set(directUrl ?? null);
|
|
10936
|
-
return;
|
|
10937
|
-
}
|
|
10938
|
-
resolvedUrl.set(directUrl ?? null);
|
|
10939
|
-
void uploader
|
|
10940
|
-
.resolvePlaybackUrl(ref, abortController.signal)
|
|
10941
|
-
.then((url) => {
|
|
10942
|
-
if (!abortController?.signal.aborted) {
|
|
10943
|
-
resolvedUrl.set(url);
|
|
10944
|
-
}
|
|
10945
|
-
})
|
|
10946
|
-
.catch(() => {
|
|
10947
|
-
if (!abortController?.signal.aborted && directUrl) {
|
|
10948
|
-
resolvedUrl.set(directUrl);
|
|
10949
|
-
}
|
|
10950
|
-
});
|
|
10951
|
-
});
|
|
10952
|
-
destroyRef.onDestroy(() => abortController?.abort());
|
|
10953
|
-
return resolvedUrl.asReadonly();
|
|
10954
|
-
}
|
|
10955
|
-
|
|
10956
11022
|
class AXAudioAttachmentComponent {
|
|
10957
11023
|
constructor() {
|
|
10958
11024
|
this.item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
@@ -13027,6 +13093,18 @@ async function copyWithFileTypeFallback(message, registry, fallback) {
|
|
|
13027
13093
|
const result = await fileType.copy(message.payload);
|
|
13028
13094
|
return resolveFileCopyText(result)?.trim() || fallback();
|
|
13029
13095
|
}
|
|
13096
|
+
/** Use file-type `open` on the message when `message.fileType` is set. */
|
|
13097
|
+
async function openWithFileType(message, registry, ctx) {
|
|
13098
|
+
if (!message.fileType) {
|
|
13099
|
+
return false;
|
|
13100
|
+
}
|
|
13101
|
+
const fileType = await registry.get(message.fileType);
|
|
13102
|
+
if (!fileType?.open) {
|
|
13103
|
+
return false;
|
|
13104
|
+
}
|
|
13105
|
+
const result = await runFileTypeOpen(fileType, ctx, message.payload);
|
|
13106
|
+
return result !== false;
|
|
13107
|
+
}
|
|
13030
13108
|
/**
|
|
13031
13109
|
* Registers the live renderer on {@link AXMessageListService} for message actions.
|
|
13032
13110
|
*/
|
|
@@ -14937,14 +15015,23 @@ class AXConversationService {
|
|
|
14937
15015
|
this._messageDeleted$ = new Subject();
|
|
14938
15016
|
this._typingIndicator$ = new Subject();
|
|
14939
15017
|
this._presenceUpdate$ = new Subject();
|
|
14940
|
-
/** All conversations */
|
|
14941
|
-
this.conversations =
|
|
15018
|
+
/** All conversations (viewer-scoped display titles for private 1v1 chats) */
|
|
15019
|
+
this.conversations = computed(() => {
|
|
15020
|
+
const currentUserId = this._currentUser()?.id;
|
|
15021
|
+
return this.state.conversations().map((conversation) => resolveConversationForViewer(conversation, currentUserId));
|
|
15022
|
+
}, ...(ngDevMode ? [{ debugName: "conversations" }] : /* istanbul ignore next */ []));
|
|
14942
15023
|
/** Active conversation ID */
|
|
14943
15024
|
this.activeConversationId = this._activeConversationId.asReadonly();
|
|
14944
15025
|
/** Active conversation */
|
|
14945
15026
|
this.activeConversation = computed(() => {
|
|
14946
15027
|
const id = this._activeConversationId();
|
|
14947
|
-
|
|
15028
|
+
if (!id) {
|
|
15029
|
+
return null;
|
|
15030
|
+
}
|
|
15031
|
+
const conversation = this.state.getConversation(id);
|
|
15032
|
+
return conversation
|
|
15033
|
+
? resolveConversationForViewer(conversation, this._currentUser()?.id)
|
|
15034
|
+
: null;
|
|
14948
15035
|
}, ...(ngDevMode ? [{ debugName: "activeConversation" }] : /* istanbul ignore next */ []));
|
|
14949
15036
|
/** Messages for active conversation */
|
|
14950
15037
|
this.activeMessages = computed(() => {
|
|
@@ -15735,17 +15822,29 @@ class AXConversationService {
|
|
|
15735
15822
|
*/
|
|
15736
15823
|
async createConversation(participantIds, type, metadata) {
|
|
15737
15824
|
try {
|
|
15825
|
+
const currentUser = this._currentUser();
|
|
15826
|
+
const creatorId = currentUser?.id;
|
|
15827
|
+
const isPrivate = type === 'private';
|
|
15828
|
+
const scopedParticipantIds = [...participantIds];
|
|
15829
|
+
if (creatorId && !scopedParticipantIds.includes(creatorId)) {
|
|
15830
|
+
scopedParticipantIds.unshift(creatorId);
|
|
15831
|
+
}
|
|
15738
15832
|
const conversation = await this.conversationApi.createConversation({
|
|
15739
15833
|
type,
|
|
15740
|
-
participantIds,
|
|
15741
|
-
|
|
15834
|
+
participantIds: scopedParticipantIds,
|
|
15835
|
+
creatorId,
|
|
15836
|
+
title: isPrivate ? undefined : metadata?.['title'],
|
|
15742
15837
|
description: metadata?.['description'],
|
|
15743
|
-
avatar:
|
|
15744
|
-
|
|
15745
|
-
|
|
15838
|
+
avatar: isPrivate
|
|
15839
|
+
? undefined
|
|
15840
|
+
: AXConversationService.normalizeOptionalString(metadata?.['avatar']),
|
|
15841
|
+
icon: isPrivate
|
|
15842
|
+
? undefined
|
|
15843
|
+
: AXConversationService.normalizeOptionalString(metadata?.['icon']),
|
|
15844
|
+
metadata: isPrivate ? undefined : metadata,
|
|
15746
15845
|
});
|
|
15747
15846
|
this.state.setConversation(conversation);
|
|
15748
|
-
return conversation;
|
|
15847
|
+
return resolveConversationForViewer(conversation, creatorId);
|
|
15749
15848
|
}
|
|
15750
15849
|
catch (error) {
|
|
15751
15850
|
this.errorHandler.handle(error, 'createConversation', { participantIds, type });
|
|
@@ -16453,8 +16552,6 @@ class AXComposerComponent {
|
|
|
16453
16552
|
this.composerPopupComponent = AXComposerPopupComponent;
|
|
16454
16553
|
/** Input placeholder - use service */
|
|
16455
16554
|
this.placeholder = this.composerService.placeholder;
|
|
16456
|
-
/** Sending message state */
|
|
16457
|
-
this.sendingMessage = signal(false, ...(ngDevMode ? [{ debugName: "sendingMessage" }] : /* istanbul ignore next */ []));
|
|
16458
16555
|
/** Typing indicator subject for rate limiting */
|
|
16459
16556
|
this.typingSubject = new Subject();
|
|
16460
16557
|
// Focus text area when requested via AXComposerService.focusComposer()
|
|
@@ -16581,51 +16678,44 @@ class AXComposerComponent {
|
|
|
16581
16678
|
}
|
|
16582
16679
|
/** Handle send click */
|
|
16583
16680
|
async onSendClick() {
|
|
16584
|
-
if (!this.canSend()
|
|
16681
|
+
if (!this.canSend())
|
|
16585
16682
|
return;
|
|
16586
16683
|
const conversation = this.activeConversation();
|
|
16587
16684
|
if (!conversation)
|
|
16588
16685
|
return;
|
|
16589
16686
|
const text = this.draftText().trim();
|
|
16590
16687
|
const editingMsg = this.editingMessage();
|
|
16591
|
-
|
|
16592
|
-
|
|
16593
|
-
// Check if we're editing a message
|
|
16594
|
-
if (editingMsg) {
|
|
16595
|
-
// Edit existing message
|
|
16688
|
+
if (editingMsg) {
|
|
16689
|
+
try {
|
|
16596
16690
|
await this.conversationService.editMessage(editingMsg.id, { type: 'text', text });
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
const command = {
|
|
16601
|
-
conversationId: conversation.id,
|
|
16602
|
-
type: 'text',
|
|
16603
|
-
payload: { type: 'text', text },
|
|
16604
|
-
replyToId: this.replyingToMessage()?.id,
|
|
16605
|
-
};
|
|
16606
|
-
// Send new message
|
|
16607
|
-
await this.composerService.sendMessage(command);
|
|
16608
|
-
this.messageSent.emit(command);
|
|
16609
|
-
// Request message list to scroll to bottom after sending
|
|
16610
|
-
this.messageListService.requestScrollToBottom();
|
|
16611
|
-
}
|
|
16612
|
-
// Clear input and draft
|
|
16613
|
-
this.draftText.set('');
|
|
16614
|
-
this.attachments.set([]);
|
|
16615
|
-
this.cancelEditReply();
|
|
16616
|
-
this.composerService.clear();
|
|
16617
|
-
// Clear saved draft after successful send
|
|
16618
|
-
if (conversation.id) {
|
|
16691
|
+
this.draftText.set('');
|
|
16692
|
+
this.attachments.set([]);
|
|
16693
|
+
this.cancelEditReply();
|
|
16619
16694
|
this.composerService.clearDraft(conversation.id);
|
|
16695
|
+
this.composerService.focusComposer();
|
|
16620
16696
|
}
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
}
|
|
16626
|
-
finally {
|
|
16627
|
-
this.sendingMessage.set(false);
|
|
16697
|
+
catch (error) {
|
|
16698
|
+
console.error('Failed to edit message:', error);
|
|
16699
|
+
}
|
|
16700
|
+
return;
|
|
16628
16701
|
}
|
|
16702
|
+
const command = {
|
|
16703
|
+
conversationId: conversation.id,
|
|
16704
|
+
type: 'text',
|
|
16705
|
+
payload: { type: 'text', text },
|
|
16706
|
+
replyToId: this.replyingToMessage()?.id,
|
|
16707
|
+
};
|
|
16708
|
+
this.editingMessage.set(null);
|
|
16709
|
+
this.replyingToMessage.set(null);
|
|
16710
|
+
void this.composerService
|
|
16711
|
+
.sendMessage(command)
|
|
16712
|
+
.then(() => {
|
|
16713
|
+
this.messageSent.emit(command);
|
|
16714
|
+
this.messageListService.requestScrollToBottom();
|
|
16715
|
+
})
|
|
16716
|
+
.catch((error) => {
|
|
16717
|
+
console.error('Failed to send message:', error);
|
|
16718
|
+
});
|
|
16629
16719
|
}
|
|
16630
16720
|
/** Handle emoji click */
|
|
16631
16721
|
async onEmojiClick(event) {
|
|
@@ -16679,16 +16769,14 @@ class AXComposerComponent {
|
|
|
16679
16769
|
},
|
|
16680
16770
|
replyToId: this.replyingToMessage()?.id,
|
|
16681
16771
|
};
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
this.cancelEditReply();
|
|
16686
|
-
// Request message list to scroll to bottom after sending sticker
|
|
16772
|
+
void this.composerService
|
|
16773
|
+
.sendMessage(command)
|
|
16774
|
+
.then(() => {
|
|
16687
16775
|
this.messageListService.requestScrollToBottom();
|
|
16688
|
-
}
|
|
16689
|
-
|
|
16776
|
+
})
|
|
16777
|
+
.catch((error) => {
|
|
16690
16778
|
console.error('Failed to send sticker:', error);
|
|
16691
|
-
}
|
|
16779
|
+
});
|
|
16692
16780
|
}
|
|
16693
16781
|
/** Get inputs for emoji popup component */
|
|
16694
16782
|
getEmojiPopupInputs() {
|
|
@@ -20976,12 +21064,7 @@ class AXNewConversationDialogComponent extends AXBasePageComponent {
|
|
|
20976
21064
|
try {
|
|
20977
21065
|
let conversation;
|
|
20978
21066
|
if (selectedIds.length === 1) {
|
|
20979
|
-
|
|
20980
|
-
const metadata = {
|
|
20981
|
-
title: selectedUser?.name,
|
|
20982
|
-
avatar: selectedUser?.avatar,
|
|
20983
|
-
};
|
|
20984
|
-
conversation = await this.conversationService.createConversation(selectedIds, 'private', metadata);
|
|
21067
|
+
conversation = await this.conversationService.createConversation(selectedIds, 'private');
|
|
20985
21068
|
}
|
|
20986
21069
|
else {
|
|
20987
21070
|
const metadata = {
|
|
@@ -22195,5 +22278,5 @@ function getErrorMessage(code, params) {
|
|
|
22195
22278
|
* Generated bundle index. Do not edit.
|
|
22196
22279
|
*/
|
|
22197
22280
|
|
|
22198
|
-
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 };
|
|
22281
|
+
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 };
|
|
22199
22282
|
//# sourceMappingURL=acorex-components-conversation2.mjs.map
|