@acorex/components 20.8.17 → 20.8.21
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.
|
@@ -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 { AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } from '@acorex/core/file';
|
|
13
|
+
import { createFileTypeMetadata, AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } 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`.
|
|
@@ -1681,6 +2085,7 @@ function createConversationAudioFileType() {
|
|
|
1681
2085
|
const presentation = CONVERSATION_AUDIO_PRESENTATION;
|
|
1682
2086
|
return {
|
|
1683
2087
|
name: CONVERSATION_AUDIO_CATALOG,
|
|
2088
|
+
metadata: createFileTypeMetadata('conversation'),
|
|
1684
2089
|
title: presentation.title,
|
|
1685
2090
|
icon: presentation.icon,
|
|
1686
2091
|
validations: {
|
|
@@ -1809,6 +2214,7 @@ function createConversationImageFileType() {
|
|
|
1809
2214
|
const presentation = CONVERSATION_IMAGE_PRESENTATION;
|
|
1810
2215
|
return {
|
|
1811
2216
|
name: CONVERSATION_IMAGE_CATALOG,
|
|
2217
|
+
metadata: createFileTypeMetadata('conversation'),
|
|
1812
2218
|
title: presentation.title,
|
|
1813
2219
|
icon: presentation.icon,
|
|
1814
2220
|
validations: {
|
|
@@ -1906,6 +2312,7 @@ function createConversationVideoFileType() {
|
|
|
1906
2312
|
const presentation = CONVERSATION_VIDEO_PRESENTATION;
|
|
1907
2313
|
return {
|
|
1908
2314
|
name: CONVERSATION_VIDEO_CATALOG,
|
|
2315
|
+
metadata: createFileTypeMetadata('conversation'),
|
|
1909
2316
|
title: presentation.title,
|
|
1910
2317
|
icon: presentation.icon,
|
|
1911
2318
|
validations: {
|
|
@@ -2003,6 +2410,7 @@ function createConversationFileFileType() {
|
|
|
2003
2410
|
const presentation = CONVERSATION_FILE_PRESENTATION;
|
|
2004
2411
|
return {
|
|
2005
2412
|
name: CONVERSATION_FILE_CATALOG,
|
|
2413
|
+
metadata: createFileTypeMetadata('conversation'),
|
|
2006
2414
|
title: presentation.title,
|
|
2007
2415
|
icon: presentation.icon,
|
|
2008
2416
|
validations: {
|
|
@@ -2215,6 +2623,7 @@ function createConversationVoiceFileType() {
|
|
|
2215
2623
|
const presentation = CONVERSATION_VOICE_PRESENTATION;
|
|
2216
2624
|
return {
|
|
2217
2625
|
name: CONVERSATION_VOICE_CATALOG,
|
|
2626
|
+
metadata: createFileTypeMetadata('conversation'),
|
|
2218
2627
|
title: presentation.title,
|
|
2219
2628
|
icon: presentation.icon,
|
|
2220
2629
|
validations: {
|
|
@@ -5683,443 +6092,162 @@ class AXComposerActionRegistry {
|
|
|
5683
6092
|
this.fileTypeRegistry = inject(AXFileTypeRegistryService);
|
|
5684
6093
|
/** All registered actions */
|
|
5685
6094
|
this.actions = this._actions.asReadonly();
|
|
5686
|
-
/** Left-side actions sorted by priority */
|
|
5687
|
-
this.leftActions = computed(() => [...this._actions()]
|
|
5688
|
-
.filter((action) => action.position === 'left')
|
|
5689
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "leftActions" }] : []));
|
|
5690
|
-
/** Right-side actions sorted by priority */
|
|
5691
|
-
this.rightActions = computed(() => [...this._actions()]
|
|
5692
|
-
.filter((action) => action.position === 'right')
|
|
5693
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "rightActions" }] : []));
|
|
5694
|
-
/** Left-side inline actions (location = 'inline' or undefined) */
|
|
5695
|
-
this.leftInlineActions = computed(() => this.leftActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "leftInlineActions" }] : []));
|
|
5696
|
-
/** Left-side menu actions (location = 'menu') */
|
|
5697
|
-
this.leftMenuActions = computed(() => this.leftActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "leftMenuActions" }] : []));
|
|
5698
|
-
/** Right-side inline actions (location = 'inline' or undefined) */
|
|
5699
|
-
this.rightInlineActions = computed(() => this.rightActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "rightInlineActions" }] : []));
|
|
5700
|
-
/** Right-side menu actions (location = 'menu') */
|
|
5701
|
-
this.rightMenuActions = computed(() => this.rightActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "rightMenuActions" }] : []));
|
|
5702
|
-
inject(CONVERSATION_FILE_TYPES_READY);
|
|
5703
|
-
// Register default actions from individual token
|
|
5704
|
-
inject(DEFAULT_COMPOSER_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
5705
|
-
// Register actions from REGISTRY_CONFIG
|
|
5706
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
5707
|
-
config?.composerActions?.forEach((action) => this.register(action));
|
|
5708
|
-
}
|
|
5709
|
-
/**
|
|
5710
|
-
* Register a composer action
|
|
5711
|
-
*/
|
|
5712
|
-
register(action) {
|
|
5713
|
-
// Validate action
|
|
5714
|
-
if (!action.id) {
|
|
5715
|
-
throw new Error('Composer action must have an id');
|
|
5716
|
-
}
|
|
5717
|
-
if (!action.label) {
|
|
5718
|
-
throw new Error('Composer action must have a label');
|
|
5719
|
-
}
|
|
5720
|
-
if (!action.icon) {
|
|
5721
|
-
throw new Error('Composer action must have an icon');
|
|
5722
|
-
}
|
|
5723
|
-
if (!action.handler && !action.component) {
|
|
5724
|
-
throw new Error('Composer action must have either a handler or a component');
|
|
5725
|
-
}
|
|
5726
|
-
// Check for duplicates
|
|
5727
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
5728
|
-
if (existing) {
|
|
5729
|
-
console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
|
|
5730
|
-
this.unregister(action.id);
|
|
5731
|
-
}
|
|
5732
|
-
// Add to registry
|
|
5733
|
-
this._actions.update((actions) => [...actions, action]);
|
|
5734
|
-
if (action.fileType) {
|
|
5735
|
-
void this.ensureFileTypeExists(action.fileType);
|
|
5736
|
-
}
|
|
5737
|
-
}
|
|
5738
|
-
async ensureFileTypeExists(fileType) {
|
|
5739
|
-
const match = await this.fileTypeRegistry.get(fileType);
|
|
5740
|
-
if (!match) {
|
|
5741
|
-
console.warn(`[AXComposerActionRegistry] Unknown fileType "${fileType}". ` +
|
|
5742
|
-
'Register its AXFileTypeInfoProvider in app providers before using this action.');
|
|
5743
|
-
}
|
|
5744
|
-
}
|
|
5745
|
-
/**
|
|
5746
|
-
* Unregister a composer action
|
|
5747
|
-
*/
|
|
5748
|
-
unregister(id) {
|
|
5749
|
-
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
5750
|
-
}
|
|
5751
|
-
/**
|
|
5752
|
-
* Get visible actions for a position
|
|
5753
|
-
*/
|
|
5754
|
-
getVisibleActions(position, context) {
|
|
5755
|
-
const actions = position === 'left' ? this.leftActions() : this.rightActions();
|
|
5756
|
-
return actions.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)));
|
|
5757
|
-
}
|
|
5758
|
-
/**
|
|
5759
|
-
* Get enabled actions for a position
|
|
5760
|
-
*/
|
|
5761
|
-
getEnabledActions(position, context) {
|
|
5762
|
-
return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
|
|
5763
|
-
}
|
|
5764
|
-
isActionEnabled(action, context) {
|
|
5765
|
-
return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
|
|
5766
|
-
}
|
|
5767
|
-
/**
|
|
5768
|
-
* Picker actions for a file catalog (by priority), when visible and enabled.
|
|
5769
|
-
*/
|
|
5770
|
-
findPickerActionsForFileType(catalogName, context) {
|
|
5771
|
-
return [...this._actions()]
|
|
5772
|
-
.filter((action) => action.component && action.fileType === catalogName)
|
|
5773
|
-
.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)))
|
|
5774
|
-
.filter((action) => this.isActionEnabled(action, context))
|
|
5775
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
5776
|
-
}
|
|
5777
|
-
/**
|
|
5778
|
-
* First picker action for a file catalog (by priority), when visible and enabled.
|
|
5779
|
-
*/
|
|
5780
|
-
findPickerActionForFileType(catalogName, context) {
|
|
5781
|
-
return this.findPickerActionsForFileType(catalogName, context)[0];
|
|
5782
|
-
}
|
|
5783
|
-
/**
|
|
5784
|
-
* Get action by ID
|
|
5785
|
-
*/
|
|
5786
|
-
getActionById(id) {
|
|
5787
|
-
return this._actions().find((a) => a.id === id);
|
|
5788
|
-
}
|
|
5789
|
-
getActionLabel(action) {
|
|
5790
|
-
return this.translation.translateSync(action.label);
|
|
5791
|
-
}
|
|
5792
|
-
getActionTooltip(action) {
|
|
5793
|
-
if (!action.tooltip)
|
|
5794
|
-
return undefined;
|
|
5795
|
-
return this.translation.translateSync(action.tooltip);
|
|
5796
|
-
}
|
|
5797
|
-
/**
|
|
5798
|
-
* Execute an action
|
|
5799
|
-
*/
|
|
5800
|
-
async executeAction(actionId, context) {
|
|
5801
|
-
const action = this.getActionById(actionId);
|
|
5802
|
-
if (!action) {
|
|
5803
|
-
throw new Error(`Action "${actionId}" not found`);
|
|
5804
|
-
}
|
|
5805
|
-
if (!runInInjectionContext(this.injector, () => action.visible(context))) {
|
|
5806
|
-
throw new Error(`Action "${actionId}" is not visible`);
|
|
5807
|
-
}
|
|
5808
|
-
if (action.enabled && !runInInjectionContext(this.injector, () => action.enabled?.(context))) {
|
|
5809
|
-
throw new Error(`Action "${actionId}" is not enabled`);
|
|
5810
|
-
}
|
|
5811
|
-
if (action.handler) {
|
|
5812
|
-
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
5813
|
-
}
|
|
5814
|
-
}
|
|
5815
|
-
/**
|
|
5816
|
-
* Update action badge
|
|
5817
|
-
*/
|
|
5818
|
-
setBadge(id, badge) {
|
|
5819
|
-
this._actions.update((actions) => actions.map((action) => (action.id === id ? { ...action, badge } : action)));
|
|
5820
|
-
}
|
|
5821
|
-
/**
|
|
5822
|
-
* Clear all actions
|
|
5823
|
-
*/
|
|
5824
|
-
clear() {
|
|
5825
|
-
this._actions.set([]);
|
|
5826
|
-
}
|
|
5827
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5828
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry }); }
|
|
5829
|
-
}
|
|
5830
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, decorators: [{
|
|
5831
|
-
type: Injectable
|
|
5832
|
-
}], ctorParameters: () => [] });
|
|
5833
|
-
|
|
5834
|
-
/**
|
|
5835
|
-
* Composer Tab Registry
|
|
5836
|
-
* Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
|
|
5837
|
-
*/
|
|
5838
|
-
/**
|
|
5839
|
-
* Composer Tab Registry Service
|
|
5840
|
-
*/
|
|
5841
|
-
class AXComposerTabRegistry {
|
|
5842
|
-
constructor() {
|
|
5843
|
-
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
5844
|
-
this.translation = inject(AXTranslationService);
|
|
5845
|
-
/** All registered tabs */
|
|
5846
|
-
this.tabs = this._tabs.asReadonly();
|
|
5847
|
-
/** Enabled tabs sorted by priority */
|
|
5848
|
-
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
5849
|
-
// Register default tabs from individual token
|
|
5850
|
-
inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
5851
|
-
// Register tabs from REGISTRY_CONFIG
|
|
5852
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
5853
|
-
config?.composerTabs?.forEach((tab) => this.register(tab));
|
|
5854
|
-
}
|
|
5855
|
-
/**
|
|
5856
|
-
* Register a composer tab
|
|
5857
|
-
*/
|
|
5858
|
-
register(tab) {
|
|
5859
|
-
// Validate tab
|
|
5860
|
-
if (!tab.id) {
|
|
5861
|
-
throw new Error('Composer tab must have an id');
|
|
5862
|
-
}
|
|
5863
|
-
if (!tab.title) {
|
|
5864
|
-
throw new Error('Composer tab must have a title');
|
|
5865
|
-
}
|
|
5866
|
-
if (!tab.component) {
|
|
5867
|
-
throw new Error('Composer tab must have a component');
|
|
5868
|
-
}
|
|
5869
|
-
// Check for duplicates
|
|
5870
|
-
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
5871
|
-
if (existing) {
|
|
5872
|
-
console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
|
|
5873
|
-
this.unregister(tab.id);
|
|
5874
|
-
}
|
|
5875
|
-
// Add to registry
|
|
5876
|
-
this._tabs.update((tabs) => [...tabs, tab]);
|
|
5877
|
-
}
|
|
5878
|
-
/**
|
|
5879
|
-
* Unregister a composer tab
|
|
5880
|
-
*/
|
|
5881
|
-
unregister(id) {
|
|
5882
|
-
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
5883
|
-
}
|
|
5884
|
-
/**
|
|
5885
|
-
* Get tab by ID
|
|
5886
|
-
*/
|
|
5887
|
-
getTabById(id) {
|
|
5888
|
-
return this._tabs().find((t) => t.id === id);
|
|
5889
|
-
}
|
|
5890
|
-
getTabTitle(tab) {
|
|
5891
|
-
return this.translation.translateSync(tab.title);
|
|
5892
|
-
}
|
|
5893
|
-
/**
|
|
5894
|
-
* Enable/disable a tab
|
|
5895
|
-
*/
|
|
5896
|
-
setEnabled(id, enabled) {
|
|
5897
|
-
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
5898
|
-
}
|
|
5899
|
-
/**
|
|
5900
|
-
* Update tab badge
|
|
5901
|
-
*/
|
|
5902
|
-
setBadge(id, badge) {
|
|
5903
|
-
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
|
|
5904
|
-
}
|
|
5905
|
-
/**
|
|
5906
|
-
* Clear all tabs
|
|
5907
|
-
*/
|
|
5908
|
-
clear() {
|
|
5909
|
-
this._tabs.set([]);
|
|
5910
|
-
}
|
|
5911
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5912
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry }); }
|
|
5913
|
-
}
|
|
5914
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, decorators: [{
|
|
5915
|
-
type: Injectable
|
|
5916
|
-
}], ctorParameters: () => [] });
|
|
5917
|
-
|
|
5918
|
-
/**
|
|
5919
|
-
* Abstract Base Registry
|
|
5920
|
-
* Base class for all registries with common functionality
|
|
5921
|
-
*/
|
|
5922
|
-
/**
|
|
5923
|
-
* Abstract Base Registry Class
|
|
5924
|
-
* Provides common registry functionality
|
|
5925
|
-
*/
|
|
5926
|
-
class AXBaseRegistry {
|
|
5927
|
-
constructor() {
|
|
5928
|
-
this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
|
|
5929
|
-
/** All registered items */
|
|
5930
|
-
this.items = this._items.asReadonly();
|
|
5931
|
-
/** Enabled items sorted by priority */
|
|
5932
|
-
this.enabledItems = computed(() => [...this._items()].filter((item) => item.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledItems" }] : []));
|
|
6095
|
+
/** Left-side actions sorted by priority */
|
|
6096
|
+
this.leftActions = computed(() => [...this._actions()]
|
|
6097
|
+
.filter((action) => action.position === 'left')
|
|
6098
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "leftActions" }] : []));
|
|
6099
|
+
/** Right-side actions sorted by priority */
|
|
6100
|
+
this.rightActions = computed(() => [...this._actions()]
|
|
6101
|
+
.filter((action) => action.position === 'right')
|
|
6102
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "rightActions" }] : []));
|
|
6103
|
+
/** Left-side inline actions (location = 'inline' or undefined) */
|
|
6104
|
+
this.leftInlineActions = computed(() => this.leftActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "leftInlineActions" }] : []));
|
|
6105
|
+
/** Left-side menu actions (location = 'menu') */
|
|
6106
|
+
this.leftMenuActions = computed(() => this.leftActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "leftMenuActions" }] : []));
|
|
6107
|
+
/** Right-side inline actions (location = 'inline' or undefined) */
|
|
6108
|
+
this.rightInlineActions = computed(() => this.rightActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "rightInlineActions" }] : []));
|
|
6109
|
+
/** Right-side menu actions (location = 'menu') */
|
|
6110
|
+
this.rightMenuActions = computed(() => this.rightActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "rightMenuActions" }] : []));
|
|
6111
|
+
inject(CONVERSATION_FILE_TYPES_READY);
|
|
6112
|
+
// Register default actions from individual token
|
|
6113
|
+
inject(DEFAULT_COMPOSER_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6114
|
+
// Register actions from REGISTRY_CONFIG
|
|
6115
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6116
|
+
config?.composerActions?.forEach((action) => this.register(action));
|
|
5933
6117
|
}
|
|
5934
6118
|
/**
|
|
5935
|
-
* Register
|
|
6119
|
+
* Register a composer action
|
|
5936
6120
|
*/
|
|
5937
|
-
register(
|
|
5938
|
-
|
|
6121
|
+
register(action) {
|
|
6122
|
+
// Validate action
|
|
6123
|
+
if (!action.id) {
|
|
6124
|
+
throw new Error('Composer action must have an id');
|
|
6125
|
+
}
|
|
6126
|
+
if (!action.label) {
|
|
6127
|
+
throw new Error('Composer action must have a label');
|
|
6128
|
+
}
|
|
6129
|
+
if (!action.icon) {
|
|
6130
|
+
throw new Error('Composer action must have an icon');
|
|
6131
|
+
}
|
|
6132
|
+
if (!action.handler && !action.component) {
|
|
6133
|
+
throw new Error('Composer action must have either a handler or a component');
|
|
6134
|
+
}
|
|
5939
6135
|
// Check for duplicates
|
|
5940
|
-
const existing = this.
|
|
6136
|
+
const existing = this._actions().find((a) => a.id === action.id);
|
|
5941
6137
|
if (existing) {
|
|
5942
|
-
console.warn(
|
|
5943
|
-
this.unregister(
|
|
6138
|
+
console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
|
|
6139
|
+
this.unregister(action.id);
|
|
5944
6140
|
}
|
|
5945
6141
|
// Add to registry
|
|
5946
|
-
this.
|
|
6142
|
+
this._actions.update((actions) => [...actions, action]);
|
|
6143
|
+
if (action.fileType) {
|
|
6144
|
+
void this.ensureFileTypeExists(action.fileType);
|
|
6145
|
+
}
|
|
5947
6146
|
}
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
6147
|
+
async ensureFileTypeExists(fileType) {
|
|
6148
|
+
const match = await this.fileTypeRegistry.get(fileType);
|
|
6149
|
+
if (!match) {
|
|
6150
|
+
console.warn(`[AXComposerActionRegistry] Unknown fileType "${fileType}". ` +
|
|
6151
|
+
'Register its AXFileTypeInfoProvider in app providers before using this action.');
|
|
6152
|
+
}
|
|
5953
6153
|
}
|
|
5954
6154
|
/**
|
|
5955
|
-
* Unregister
|
|
6155
|
+
* Unregister a composer action
|
|
5956
6156
|
*/
|
|
5957
6157
|
unregister(id) {
|
|
5958
|
-
this.
|
|
5959
|
-
}
|
|
5960
|
-
/**
|
|
5961
|
-
* Get item by ID
|
|
5962
|
-
*/
|
|
5963
|
-
getById(id) {
|
|
5964
|
-
return this._items().find((i) => i.id === id);
|
|
5965
|
-
}
|
|
5966
|
-
/**
|
|
5967
|
-
* Check if item exists
|
|
5968
|
-
*/
|
|
5969
|
-
has(id) {
|
|
5970
|
-
return this.getById(id) !== undefined;
|
|
5971
|
-
}
|
|
5972
|
-
/**
|
|
5973
|
-
* Enable/disable an item
|
|
5974
|
-
*/
|
|
5975
|
-
setEnabled(id, enabled) {
|
|
5976
|
-
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
|
|
5977
|
-
}
|
|
5978
|
-
/**
|
|
5979
|
-
* Update an item
|
|
5980
|
-
*/
|
|
5981
|
-
update(id, updates) {
|
|
5982
|
-
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
6158
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
5983
6159
|
}
|
|
5984
6160
|
/**
|
|
5985
|
-
*
|
|
6161
|
+
* Get visible actions for a position
|
|
5986
6162
|
*/
|
|
5987
|
-
|
|
5988
|
-
this.
|
|
6163
|
+
getVisibleActions(position, context) {
|
|
6164
|
+
const actions = position === 'left' ? this.leftActions() : this.rightActions();
|
|
6165
|
+
return actions.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)));
|
|
5989
6166
|
}
|
|
5990
6167
|
/**
|
|
5991
|
-
* Get
|
|
6168
|
+
* Get enabled actions for a position
|
|
5992
6169
|
*/
|
|
5993
|
-
|
|
5994
|
-
return this.
|
|
6170
|
+
getEnabledActions(position, context) {
|
|
6171
|
+
return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
|
|
5995
6172
|
}
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
*/
|
|
5999
|
-
get enabledCount() {
|
|
6000
|
-
return this.enabledItems().length;
|
|
6173
|
+
isActionEnabled(action, context) {
|
|
6174
|
+
return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
|
|
6001
6175
|
}
|
|
6002
6176
|
/**
|
|
6003
|
-
*
|
|
6004
|
-
* Override in child classes for custom validation
|
|
6177
|
+
* Picker actions for a file catalog (by priority), when visible and enabled.
|
|
6005
6178
|
*/
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
/**
|
|
6014
|
-
* Conversation Item Action Registry
|
|
6015
|
-
* Manages actions available in the conversation list item context menu
|
|
6016
|
-
*/
|
|
6017
|
-
/**
|
|
6018
|
-
* Conversation Item Action Registry Service
|
|
6019
|
-
* Manages available actions in the conversation list item context menu
|
|
6020
|
-
*/
|
|
6021
|
-
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6022
|
-
constructor() {
|
|
6023
|
-
super();
|
|
6024
|
-
this.translation = inject(AXTranslationService);
|
|
6025
|
-
this.injector = inject(Injector);
|
|
6026
|
-
// Register default actions from individual token
|
|
6027
|
-
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6028
|
-
// Register actions from REGISTRY_CONFIG
|
|
6029
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6030
|
-
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6179
|
+
findPickerActionsForFileType(catalogName, context) {
|
|
6180
|
+
return [...this._actions()]
|
|
6181
|
+
.filter((action) => action.component && action.fileType === catalogName)
|
|
6182
|
+
.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)))
|
|
6183
|
+
.filter((action) => this.isActionEnabled(action, context))
|
|
6184
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6031
6185
|
}
|
|
6032
6186
|
/**
|
|
6033
|
-
*
|
|
6187
|
+
* First picker action for a file catalog (by priority), when visible and enabled.
|
|
6034
6188
|
*/
|
|
6035
|
-
|
|
6036
|
-
return
|
|
6189
|
+
findPickerActionForFileType(catalogName, context) {
|
|
6190
|
+
return this.findPickerActionsForFileType(catalogName, context)[0];
|
|
6037
6191
|
}
|
|
6038
6192
|
/**
|
|
6039
|
-
* Get
|
|
6193
|
+
* Get action by ID
|
|
6040
6194
|
*/
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
return this.enabledItems()
|
|
6044
|
-
.filter((action) => {
|
|
6045
|
-
// Filter by visibility
|
|
6046
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6047
|
-
return false;
|
|
6048
|
-
}
|
|
6049
|
-
return true;
|
|
6050
|
-
})
|
|
6051
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6052
|
-
}
|
|
6053
|
-
isActionEnabled(action, context) {
|
|
6054
|
-
if (action.enabled === false) {
|
|
6055
|
-
return false;
|
|
6056
|
-
}
|
|
6057
|
-
if (action.enabledWhen) {
|
|
6058
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6059
|
-
}
|
|
6060
|
-
return true;
|
|
6195
|
+
getActionById(id) {
|
|
6196
|
+
return this._actions().find((a) => a.id === id);
|
|
6061
6197
|
}
|
|
6062
|
-
|
|
6063
|
-
* Get label for an action
|
|
6064
|
-
*/
|
|
6065
|
-
getActionLabel(action, conversation) {
|
|
6066
|
-
if (typeof action.label === 'function') {
|
|
6067
|
-
const labelFactory = action.label;
|
|
6068
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6069
|
-
}
|
|
6198
|
+
getActionLabel(action) {
|
|
6070
6199
|
return this.translation.translateSync(action.label);
|
|
6071
6200
|
}
|
|
6072
|
-
|
|
6073
|
-
* Get icon for an action
|
|
6074
|
-
*/
|
|
6075
|
-
getActionIcon(action, conversation) {
|
|
6076
|
-
if (!action.icon)
|
|
6077
|
-
return undefined;
|
|
6078
|
-
if (typeof action.icon === 'function') {
|
|
6079
|
-
const iconFactory = action.icon;
|
|
6080
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6081
|
-
}
|
|
6082
|
-
return action.icon;
|
|
6083
|
-
}
|
|
6084
|
-
/**
|
|
6085
|
-
* Get tooltip for an action
|
|
6086
|
-
*/
|
|
6087
|
-
getActionTooltip(action, conversation) {
|
|
6201
|
+
getActionTooltip(action) {
|
|
6088
6202
|
if (!action.tooltip)
|
|
6089
6203
|
return undefined;
|
|
6090
|
-
if (typeof action.tooltip === 'function') {
|
|
6091
|
-
const tooltipFactory = action.tooltip;
|
|
6092
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6093
|
-
}
|
|
6094
6204
|
return this.translation.translateSync(action.tooltip);
|
|
6095
6205
|
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Execute an action
|
|
6208
|
+
*/
|
|
6096
6209
|
async executeAction(actionId, context) {
|
|
6097
|
-
const action = this.
|
|
6210
|
+
const action = this.getActionById(actionId);
|
|
6098
6211
|
if (!action) {
|
|
6099
6212
|
throw new Error(`Action "${actionId}" not found`);
|
|
6100
6213
|
}
|
|
6101
|
-
if (!this.
|
|
6214
|
+
if (!runInInjectionContext(this.injector, () => action.visible(context))) {
|
|
6215
|
+
throw new Error(`Action "${actionId}" is not visible`);
|
|
6216
|
+
}
|
|
6217
|
+
if (action.enabled && !runInInjectionContext(this.injector, () => action.enabled?.(context))) {
|
|
6102
6218
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6103
6219
|
}
|
|
6104
6220
|
if (action.handler) {
|
|
6105
6221
|
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6106
6222
|
}
|
|
6107
6223
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6224
|
+
/**
|
|
6225
|
+
* Update action badge
|
|
6226
|
+
*/
|
|
6227
|
+
setBadge(id, badge) {
|
|
6228
|
+
this._actions.update((actions) => actions.map((action) => (action.id === id ? { ...action, badge } : action)));
|
|
6229
|
+
}
|
|
6230
|
+
/**
|
|
6231
|
+
* Clear all actions
|
|
6232
|
+
*/
|
|
6233
|
+
clear() {
|
|
6234
|
+
this._actions.set([]);
|
|
6235
|
+
}
|
|
6236
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6237
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry }); }
|
|
6110
6238
|
}
|
|
6111
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6239
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, decorators: [{
|
|
6112
6240
|
type: Injectable
|
|
6113
6241
|
}], ctorParameters: () => [] });
|
|
6114
6242
|
|
|
6115
6243
|
/**
|
|
6116
|
-
*
|
|
6117
|
-
* Register custom tabs for
|
|
6244
|
+
* Composer Tab Registry
|
|
6245
|
+
* Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
|
|
6118
6246
|
*/
|
|
6119
6247
|
/**
|
|
6120
|
-
*
|
|
6248
|
+
* Composer Tab Registry Service
|
|
6121
6249
|
*/
|
|
6122
|
-
class
|
|
6250
|
+
class AXComposerTabRegistry {
|
|
6123
6251
|
constructor() {
|
|
6124
6252
|
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
6125
6253
|
this.translation = inject(AXTranslationService);
|
|
@@ -6127,39 +6255,37 @@ class AXConversationTabRegistry {
|
|
|
6127
6255
|
this.tabs = this._tabs.asReadonly();
|
|
6128
6256
|
/** Enabled tabs sorted by priority */
|
|
6129
6257
|
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
6130
|
-
/** Default active tab */
|
|
6131
|
-
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
|
|
6132
6258
|
// Register default tabs from individual token
|
|
6133
|
-
inject(
|
|
6259
|
+
inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6134
6260
|
// Register tabs from REGISTRY_CONFIG
|
|
6135
6261
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6136
|
-
config?.
|
|
6262
|
+
config?.composerTabs?.forEach((tab) => this.register(tab));
|
|
6137
6263
|
}
|
|
6138
6264
|
/**
|
|
6139
|
-
* Register a
|
|
6265
|
+
* Register a composer tab
|
|
6140
6266
|
*/
|
|
6141
6267
|
register(tab) {
|
|
6142
6268
|
// Validate tab
|
|
6143
6269
|
if (!tab.id) {
|
|
6144
|
-
throw new Error('
|
|
6270
|
+
throw new Error('Composer tab must have an id');
|
|
6145
6271
|
}
|
|
6146
|
-
if (!tab.
|
|
6147
|
-
throw new Error('
|
|
6272
|
+
if (!tab.title) {
|
|
6273
|
+
throw new Error('Composer tab must have a title');
|
|
6148
6274
|
}
|
|
6149
|
-
if (!tab.
|
|
6150
|
-
throw new Error('
|
|
6275
|
+
if (!tab.component) {
|
|
6276
|
+
throw new Error('Composer tab must have a component');
|
|
6151
6277
|
}
|
|
6152
6278
|
// Check for duplicates
|
|
6153
6279
|
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6154
6280
|
if (existing) {
|
|
6155
|
-
console.warn(`
|
|
6281
|
+
console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
|
|
6156
6282
|
this.unregister(tab.id);
|
|
6157
6283
|
}
|
|
6158
6284
|
// Add to registry
|
|
6159
6285
|
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6160
6286
|
}
|
|
6161
6287
|
/**
|
|
6162
|
-
* Unregister a
|
|
6288
|
+
* Unregister a composer tab
|
|
6163
6289
|
*/
|
|
6164
6290
|
unregister(id) {
|
|
6165
6291
|
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
@@ -6170,28 +6296,8 @@ class AXConversationTabRegistry {
|
|
|
6170
6296
|
getTabById(id) {
|
|
6171
6297
|
return this._tabs().find((t) => t.id === id);
|
|
6172
6298
|
}
|
|
6173
|
-
|
|
6174
|
-
return this.translation.translateSync(tab.
|
|
6175
|
-
}
|
|
6176
|
-
/**
|
|
6177
|
-
* Apply tab filter to conversations
|
|
6178
|
-
*/
|
|
6179
|
-
filterConversations(tabId, conversations) {
|
|
6180
|
-
const tab = this.getTabById(tabId);
|
|
6181
|
-
if (!tab) {
|
|
6182
|
-
throw new Error(`Tab "${tabId}" not found`);
|
|
6183
|
-
}
|
|
6184
|
-
return tab.filter(conversations);
|
|
6185
|
-
}
|
|
6186
|
-
/**
|
|
6187
|
-
* Get badge for a tab
|
|
6188
|
-
*/
|
|
6189
|
-
getBadge(tabId, conversations) {
|
|
6190
|
-
const tab = this.getTabById(tabId);
|
|
6191
|
-
if (!tab || !tab.badge) {
|
|
6192
|
-
return undefined;
|
|
6193
|
-
}
|
|
6194
|
-
return tab.badge(conversations);
|
|
6299
|
+
getTabTitle(tab) {
|
|
6300
|
+
return this.translation.translateSync(tab.title);
|
|
6195
6301
|
}
|
|
6196
6302
|
/**
|
|
6197
6303
|
* Enable/disable a tab
|
|
@@ -6200,13 +6306,10 @@ class AXConversationTabRegistry {
|
|
|
6200
6306
|
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
6201
6307
|
}
|
|
6202
6308
|
/**
|
|
6203
|
-
*
|
|
6309
|
+
* Update tab badge
|
|
6204
6310
|
*/
|
|
6205
|
-
|
|
6206
|
-
this._tabs.update((tabs) => tabs.map((tab) => ({
|
|
6207
|
-
...tab,
|
|
6208
|
-
default: tab.id === id,
|
|
6209
|
-
})));
|
|
6311
|
+
setBadge(id, badge) {
|
|
6312
|
+
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
|
|
6210
6313
|
}
|
|
6211
6314
|
/**
|
|
6212
6315
|
* Clear all tabs
|
|
@@ -6214,759 +6317,760 @@ class AXConversationTabRegistry {
|
|
|
6214
6317
|
clear() {
|
|
6215
6318
|
this._tabs.set([]);
|
|
6216
6319
|
}
|
|
6217
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6218
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6320
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6321
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry }); }
|
|
6219
6322
|
}
|
|
6220
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, decorators: [{
|
|
6221
6324
|
type: Injectable
|
|
6222
6325
|
}], ctorParameters: () => [] });
|
|
6223
6326
|
|
|
6224
6327
|
/**
|
|
6225
|
-
*
|
|
6226
|
-
*
|
|
6328
|
+
* Abstract Base Registry
|
|
6329
|
+
* Base class for all registries with common functionality
|
|
6227
6330
|
*/
|
|
6228
6331
|
/**
|
|
6229
|
-
*
|
|
6230
|
-
*
|
|
6332
|
+
* Abstract Base Registry Class
|
|
6333
|
+
* Provides common registry functionality
|
|
6231
6334
|
*/
|
|
6232
|
-
class
|
|
6335
|
+
class AXBaseRegistry {
|
|
6233
6336
|
constructor() {
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
this.
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
// Register actions from REGISTRY_CONFIG
|
|
6240
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6241
|
-
config?.infoBarActions?.forEach((action) => this.register(action));
|
|
6337
|
+
this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
|
|
6338
|
+
/** All registered items */
|
|
6339
|
+
this.items = this._items.asReadonly();
|
|
6340
|
+
/** Enabled items sorted by priority */
|
|
6341
|
+
this.enabledItems = computed(() => [...this._items()].filter((item) => item.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledItems" }] : []));
|
|
6242
6342
|
}
|
|
6243
6343
|
/**
|
|
6244
|
-
*
|
|
6344
|
+
* Register an item
|
|
6245
6345
|
*/
|
|
6246
|
-
|
|
6247
|
-
|
|
6346
|
+
register(item) {
|
|
6347
|
+
this.validateItem(item);
|
|
6348
|
+
// Check for duplicates
|
|
6349
|
+
const existing = this._items().find((i) => i.id === item.id);
|
|
6350
|
+
if (existing) {
|
|
6351
|
+
console.warn(`${this.getRegistryName()}: Item "${item.id}" is already registered. Replacing...`);
|
|
6352
|
+
this.unregister(item.id);
|
|
6353
|
+
}
|
|
6354
|
+
// Add to registry
|
|
6355
|
+
this._items.update((items) => [...items, item]);
|
|
6248
6356
|
}
|
|
6249
6357
|
/**
|
|
6250
|
-
*
|
|
6358
|
+
* Register multiple items
|
|
6251
6359
|
*/
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
return this.enabledItems()
|
|
6255
|
-
.filter((action) => {
|
|
6256
|
-
// Filter by location if specified
|
|
6257
|
-
if (location && action.location && action.location !== location) {
|
|
6258
|
-
return false;
|
|
6259
|
-
}
|
|
6260
|
-
// Filter by visibility
|
|
6261
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6262
|
-
return false;
|
|
6263
|
-
}
|
|
6264
|
-
return true;
|
|
6265
|
-
})
|
|
6266
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6360
|
+
registerMany(items) {
|
|
6361
|
+
items.forEach((item) => this.register(item));
|
|
6267
6362
|
}
|
|
6268
6363
|
/**
|
|
6269
|
-
*
|
|
6364
|
+
* Unregister an item by ID
|
|
6270
6365
|
*/
|
|
6271
|
-
|
|
6272
|
-
|
|
6366
|
+
unregister(id) {
|
|
6367
|
+
this._items.update((items) => items.filter((i) => i.id !== id));
|
|
6273
6368
|
}
|
|
6274
6369
|
/**
|
|
6275
|
-
* Get
|
|
6370
|
+
* Get item by ID
|
|
6276
6371
|
*/
|
|
6277
|
-
|
|
6278
|
-
return this.
|
|
6372
|
+
getById(id) {
|
|
6373
|
+
return this._items().find((i) => i.id === id);
|
|
6279
6374
|
}
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6286
|
-
}
|
|
6287
|
-
return true;
|
|
6375
|
+
/**
|
|
6376
|
+
* Check if item exists
|
|
6377
|
+
*/
|
|
6378
|
+
has(id) {
|
|
6379
|
+
return this.getById(id) !== undefined;
|
|
6288
6380
|
}
|
|
6289
6381
|
/**
|
|
6290
|
-
*
|
|
6382
|
+
* Enable/disable an item
|
|
6291
6383
|
*/
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
const labelFactory = action.label;
|
|
6295
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6296
|
-
}
|
|
6297
|
-
return this.translation.translateSync(action.label);
|
|
6384
|
+
setEnabled(id, enabled) {
|
|
6385
|
+
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
|
|
6298
6386
|
}
|
|
6299
6387
|
/**
|
|
6300
|
-
*
|
|
6388
|
+
* Update an item
|
|
6301
6389
|
*/
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
return undefined;
|
|
6305
|
-
if (typeof action.icon === 'function') {
|
|
6306
|
-
const iconFactory = action.icon;
|
|
6307
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6308
|
-
}
|
|
6309
|
-
return action.icon;
|
|
6390
|
+
update(id, updates) {
|
|
6391
|
+
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
6310
6392
|
}
|
|
6311
6393
|
/**
|
|
6312
|
-
*
|
|
6394
|
+
* Clear all items
|
|
6313
6395
|
*/
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6396
|
+
clear() {
|
|
6397
|
+
this._items.set([]);
|
|
6398
|
+
}
|
|
6399
|
+
/**
|
|
6400
|
+
* Get count of registered items
|
|
6401
|
+
*/
|
|
6402
|
+
get count() {
|
|
6403
|
+
return this._items().length;
|
|
6404
|
+
}
|
|
6405
|
+
/**
|
|
6406
|
+
* Get count of enabled items
|
|
6407
|
+
*/
|
|
6408
|
+
get enabledCount() {
|
|
6409
|
+
return this.enabledItems().length;
|
|
6410
|
+
}
|
|
6411
|
+
/**
|
|
6412
|
+
* Validate item before registration
|
|
6413
|
+
* Override in child classes for custom validation
|
|
6414
|
+
*/
|
|
6415
|
+
validateItem(item) {
|
|
6416
|
+
if (!item.id) {
|
|
6417
|
+
throw new Error(`${this.getRegistryName()}: Item must have an id`);
|
|
6320
6418
|
}
|
|
6321
|
-
return this.translation.translateSync(action.tooltip);
|
|
6322
6419
|
}
|
|
6323
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6324
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6325
6420
|
}
|
|
6326
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6327
|
-
type: Injectable
|
|
6328
|
-
}], ctorParameters: () => [] });
|
|
6329
6421
|
|
|
6330
6422
|
/**
|
|
6331
|
-
*
|
|
6332
|
-
*
|
|
6423
|
+
* Conversation Item Action Registry
|
|
6424
|
+
* Manages actions available in the conversation list item context menu
|
|
6333
6425
|
*/
|
|
6334
6426
|
/**
|
|
6335
|
-
*
|
|
6427
|
+
* Conversation Item Action Registry Service
|
|
6428
|
+
* Manages available actions in the conversation list item context menu
|
|
6336
6429
|
*/
|
|
6337
|
-
class
|
|
6430
|
+
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6338
6431
|
constructor() {
|
|
6339
|
-
|
|
6432
|
+
super();
|
|
6340
6433
|
this.translation = inject(AXTranslationService);
|
|
6341
6434
|
this.injector = inject(Injector);
|
|
6342
|
-
/** All registered actions */
|
|
6343
|
-
this.actions = this._actions.asReadonly();
|
|
6344
|
-
/** Actions sorted by priority */
|
|
6345
|
-
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
|
|
6346
6435
|
// Register default actions from individual token
|
|
6347
|
-
|
|
6348
|
-
if (defaultActions) {
|
|
6349
|
-
defaultActions.forEach((action) => this.register(action));
|
|
6350
|
-
}
|
|
6436
|
+
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6351
6437
|
// Register actions from REGISTRY_CONFIG
|
|
6352
6438
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6353
|
-
config?.
|
|
6439
|
+
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6354
6440
|
}
|
|
6355
6441
|
/**
|
|
6356
|
-
*
|
|
6442
|
+
* Get registry name for logging
|
|
6357
6443
|
*/
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
if (!action.id) {
|
|
6361
|
-
throw new Error('Message action must have an id');
|
|
6362
|
-
}
|
|
6363
|
-
if (!action.label) {
|
|
6364
|
-
throw new Error('Message action must have a label');
|
|
6365
|
-
}
|
|
6366
|
-
if (!action.handler) {
|
|
6367
|
-
throw new Error('Message action must have a handler');
|
|
6368
|
-
}
|
|
6369
|
-
// Check for duplicates
|
|
6370
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
6371
|
-
if (existing) {
|
|
6372
|
-
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6373
|
-
this.unregister(action.id);
|
|
6374
|
-
}
|
|
6375
|
-
// Add to registry
|
|
6376
|
-
this._actions.update((actions) => [...actions, action]);
|
|
6444
|
+
getRegistryName() {
|
|
6445
|
+
return 'ConversationItemActionRegistry';
|
|
6377
6446
|
}
|
|
6378
6447
|
/**
|
|
6379
|
-
*
|
|
6448
|
+
* Get actions for a specific conversation
|
|
6380
6449
|
*/
|
|
6381
|
-
|
|
6382
|
-
|
|
6450
|
+
getActionsForConversation(conversation) {
|
|
6451
|
+
const context = { conversation };
|
|
6452
|
+
return this.enabledItems()
|
|
6453
|
+
.filter((action) => {
|
|
6454
|
+
// Filter by visibility
|
|
6455
|
+
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6456
|
+
return false;
|
|
6457
|
+
}
|
|
6458
|
+
return true;
|
|
6459
|
+
})
|
|
6460
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6383
6461
|
}
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
runInInjectionContext(this.injector, () => action.
|
|
6462
|
+
isActionEnabled(action, context) {
|
|
6463
|
+
if (action.enabled === false) {
|
|
6464
|
+
return false;
|
|
6465
|
+
}
|
|
6466
|
+
if (action.enabledWhen) {
|
|
6467
|
+
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6468
|
+
}
|
|
6469
|
+
return true;
|
|
6390
6470
|
}
|
|
6391
6471
|
/**
|
|
6392
|
-
* Get
|
|
6472
|
+
* Get label for an action
|
|
6393
6473
|
*/
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6400
|
-
}
|
|
6401
|
-
getActionLabel(action) {
|
|
6474
|
+
getActionLabel(action, conversation) {
|
|
6475
|
+
if (typeof action.label === 'function') {
|
|
6476
|
+
const labelFactory = action.label;
|
|
6477
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6478
|
+
}
|
|
6402
6479
|
return this.translation.translateSync(action.label);
|
|
6403
6480
|
}
|
|
6404
6481
|
/**
|
|
6405
|
-
* Get
|
|
6482
|
+
* Get icon for an action
|
|
6406
6483
|
*/
|
|
6407
|
-
|
|
6408
|
-
|
|
6484
|
+
getActionIcon(action, conversation) {
|
|
6485
|
+
if (!action.icon)
|
|
6486
|
+
return undefined;
|
|
6487
|
+
if (typeof action.icon === 'function') {
|
|
6488
|
+
const iconFactory = action.icon;
|
|
6489
|
+
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6490
|
+
}
|
|
6491
|
+
return action.icon;
|
|
6409
6492
|
}
|
|
6410
6493
|
/**
|
|
6411
|
-
* Get
|
|
6494
|
+
* Get tooltip for an action
|
|
6412
6495
|
*/
|
|
6413
|
-
|
|
6414
|
-
|
|
6496
|
+
getActionTooltip(action, conversation) {
|
|
6497
|
+
if (!action.tooltip)
|
|
6498
|
+
return undefined;
|
|
6499
|
+
if (typeof action.tooltip === 'function') {
|
|
6500
|
+
const tooltipFactory = action.tooltip;
|
|
6501
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6502
|
+
}
|
|
6503
|
+
return this.translation.translateSync(action.tooltip);
|
|
6415
6504
|
}
|
|
6416
|
-
/**
|
|
6417
|
-
* Execute an action
|
|
6418
|
-
*/
|
|
6419
6505
|
async executeAction(actionId, context) {
|
|
6420
|
-
const action = this.
|
|
6506
|
+
const action = this.getById(actionId);
|
|
6421
6507
|
if (!action) {
|
|
6422
6508
|
throw new Error(`Action "${actionId}" not found`);
|
|
6423
6509
|
}
|
|
6424
|
-
|
|
6425
|
-
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6510
|
+
if (!this.isActionEnabled(action, context)) {
|
|
6426
6511
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6427
6512
|
}
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
/**
|
|
6432
|
-
* Clear all actions
|
|
6433
|
-
*/
|
|
6434
|
-
clear() {
|
|
6435
|
-
this._actions.set([]);
|
|
6513
|
+
if (action.handler) {
|
|
6514
|
+
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6515
|
+
}
|
|
6436
6516
|
}
|
|
6437
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6438
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6517
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6518
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry }); }
|
|
6439
6519
|
}
|
|
6440
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6520
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
|
|
6441
6521
|
type: Injectable
|
|
6442
6522
|
}], ctorParameters: () => [] });
|
|
6443
6523
|
|
|
6444
6524
|
/**
|
|
6445
|
-
*
|
|
6446
|
-
*
|
|
6525
|
+
* Conversation Tab Registry
|
|
6526
|
+
* Register custom tabs for filtering conversations in the sidebar
|
|
6447
6527
|
*/
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
PARTICIPANTS: 'participants',
|
|
6453
|
-
CONVERSATIONS: 'conversations',
|
|
6454
|
-
MESSAGES: 'messages',
|
|
6455
|
-
MEDIA: 'media',
|
|
6456
|
-
SETTINGS: 'settings',
|
|
6457
|
-
};
|
|
6458
|
-
class AXConversationIndexedDbStorage {
|
|
6528
|
+
/**
|
|
6529
|
+
* Conversation Tab Registry Service
|
|
6530
|
+
*/
|
|
6531
|
+
class AXConversationTabRegistry {
|
|
6459
6532
|
constructor() {
|
|
6460
|
-
this.
|
|
6461
|
-
this.
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
request.onerror = () => reject(request.error);
|
|
6474
|
-
request.onsuccess = () => {
|
|
6475
|
-
this.db = request.result;
|
|
6476
|
-
resolve();
|
|
6477
|
-
};
|
|
6478
|
-
request.onupgradeneeded = (event) => {
|
|
6479
|
-
const db = event.target.result;
|
|
6480
|
-
// Create object stores if they don't exist
|
|
6481
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6482
|
-
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6483
|
-
}
|
|
6484
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6485
|
-
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6486
|
-
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6487
|
-
}
|
|
6488
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6489
|
-
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6490
|
-
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6491
|
-
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6492
|
-
}
|
|
6493
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6494
|
-
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6495
|
-
}
|
|
6496
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6497
|
-
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6498
|
-
}
|
|
6499
|
-
};
|
|
6500
|
-
});
|
|
6501
|
-
return this.initPromise;
|
|
6502
|
-
}
|
|
6503
|
-
/**
|
|
6504
|
-
* Get a value from a store
|
|
6505
|
-
*/
|
|
6506
|
-
async get(storeName, key) {
|
|
6507
|
-
await this.init();
|
|
6508
|
-
if (!this.db)
|
|
6509
|
-
throw new Error('Database not initialized');
|
|
6510
|
-
const db = this.db;
|
|
6511
|
-
return new Promise((resolve, reject) => {
|
|
6512
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6513
|
-
const store = transaction.objectStore(storeName);
|
|
6514
|
-
const request = store.get(key);
|
|
6515
|
-
request.onsuccess = () => resolve(request.result);
|
|
6516
|
-
request.onerror = () => reject(request.error);
|
|
6517
|
-
});
|
|
6518
|
-
}
|
|
6519
|
-
/**
|
|
6520
|
-
* Get all values from a store
|
|
6521
|
-
*/
|
|
6522
|
-
async getAll(storeName) {
|
|
6523
|
-
await this.init();
|
|
6524
|
-
if (!this.db)
|
|
6525
|
-
throw new Error('Database not initialized');
|
|
6526
|
-
const db = this.db;
|
|
6527
|
-
return new Promise((resolve, reject) => {
|
|
6528
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6529
|
-
const store = transaction.objectStore(storeName);
|
|
6530
|
-
const request = store.getAll();
|
|
6531
|
-
request.onsuccess = () => resolve(request.result);
|
|
6532
|
-
request.onerror = () => reject(request.error);
|
|
6533
|
-
});
|
|
6534
|
-
}
|
|
6535
|
-
/**
|
|
6536
|
-
* Get values by index
|
|
6537
|
-
*/
|
|
6538
|
-
async getAllByIndex(storeName, indexName, query) {
|
|
6539
|
-
await this.init();
|
|
6540
|
-
if (!this.db)
|
|
6541
|
-
throw new Error('Database not initialized');
|
|
6542
|
-
const db = this.db;
|
|
6543
|
-
return new Promise((resolve, reject) => {
|
|
6544
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6545
|
-
const store = transaction.objectStore(storeName);
|
|
6546
|
-
const index = store.index(indexName);
|
|
6547
|
-
const request = index.getAll(query);
|
|
6548
|
-
request.onsuccess = () => resolve(request.result);
|
|
6549
|
-
request.onerror = () => reject(request.error);
|
|
6550
|
-
});
|
|
6551
|
-
}
|
|
6552
|
-
/**
|
|
6553
|
-
* Put a value into a store
|
|
6554
|
-
*/
|
|
6555
|
-
async put(storeName, value) {
|
|
6556
|
-
await this.init();
|
|
6557
|
-
if (!this.db)
|
|
6558
|
-
throw new Error('Database not initialized');
|
|
6559
|
-
const db = this.db;
|
|
6560
|
-
return new Promise((resolve, reject) => {
|
|
6561
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6562
|
-
const store = transaction.objectStore(storeName);
|
|
6563
|
-
const request = store.put(value);
|
|
6564
|
-
request.onsuccess = () => resolve();
|
|
6565
|
-
request.onerror = () => reject(request.error);
|
|
6566
|
-
});
|
|
6567
|
-
}
|
|
6568
|
-
/**
|
|
6569
|
-
* Delete a value from a store
|
|
6570
|
-
*/
|
|
6571
|
-
async delete(storeName, key) {
|
|
6572
|
-
await this.init();
|
|
6573
|
-
if (!this.db)
|
|
6574
|
-
throw new Error('Database not initialized');
|
|
6575
|
-
const db = this.db;
|
|
6576
|
-
return new Promise((resolve, reject) => {
|
|
6577
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6578
|
-
const store = transaction.objectStore(storeName);
|
|
6579
|
-
const request = store.delete(key);
|
|
6580
|
-
request.onsuccess = () => resolve();
|
|
6581
|
-
request.onerror = () => reject(request.error);
|
|
6582
|
-
});
|
|
6533
|
+
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
6534
|
+
this.translation = inject(AXTranslationService);
|
|
6535
|
+
/** All registered tabs */
|
|
6536
|
+
this.tabs = this._tabs.asReadonly();
|
|
6537
|
+
/** Enabled tabs sorted by priority */
|
|
6538
|
+
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
6539
|
+
/** Default active tab */
|
|
6540
|
+
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
|
|
6541
|
+
// Register default tabs from individual token
|
|
6542
|
+
inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6543
|
+
// Register tabs from REGISTRY_CONFIG
|
|
6544
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6545
|
+
config?.conversationTabs?.forEach((tab) => this.register(tab));
|
|
6583
6546
|
}
|
|
6584
6547
|
/**
|
|
6585
|
-
*
|
|
6548
|
+
* Register a conversation tab
|
|
6586
6549
|
*/
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
if (!
|
|
6590
|
-
throw new Error('
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
async getAllParticipants() {
|
|
6607
|
-
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
6550
|
+
register(tab) {
|
|
6551
|
+
// Validate tab
|
|
6552
|
+
if (!tab.id) {
|
|
6553
|
+
throw new Error('Conversation tab must have an id');
|
|
6554
|
+
}
|
|
6555
|
+
if (!tab.label) {
|
|
6556
|
+
throw new Error('Conversation tab must have a label');
|
|
6557
|
+
}
|
|
6558
|
+
if (!tab.filter) {
|
|
6559
|
+
throw new Error('Conversation tab must have a filter function');
|
|
6560
|
+
}
|
|
6561
|
+
// Check for duplicates
|
|
6562
|
+
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6563
|
+
if (existing) {
|
|
6564
|
+
console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
|
|
6565
|
+
this.unregister(tab.id);
|
|
6566
|
+
}
|
|
6567
|
+
// Add to registry
|
|
6568
|
+
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6608
6569
|
}
|
|
6609
|
-
|
|
6610
|
-
|
|
6570
|
+
/**
|
|
6571
|
+
* Unregister a conversation tab
|
|
6572
|
+
*/
|
|
6573
|
+
unregister(id) {
|
|
6574
|
+
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6611
6575
|
}
|
|
6612
|
-
|
|
6613
|
-
|
|
6576
|
+
/**
|
|
6577
|
+
* Get tab by ID
|
|
6578
|
+
*/
|
|
6579
|
+
getTabById(id) {
|
|
6580
|
+
return this._tabs().find((t) => t.id === id);
|
|
6614
6581
|
}
|
|
6615
|
-
|
|
6616
|
-
return this.
|
|
6582
|
+
getTabLabel(tab) {
|
|
6583
|
+
return this.translation.translateSync(tab.label);
|
|
6617
6584
|
}
|
|
6618
|
-
|
|
6619
|
-
|
|
6585
|
+
/**
|
|
6586
|
+
* Apply tab filter to conversations
|
|
6587
|
+
*/
|
|
6588
|
+
filterConversations(tabId, conversations) {
|
|
6589
|
+
const tab = this.getTabById(tabId);
|
|
6590
|
+
if (!tab) {
|
|
6591
|
+
throw new Error(`Tab "${tabId}" not found`);
|
|
6592
|
+
}
|
|
6593
|
+
return tab.filter(conversations);
|
|
6620
6594
|
}
|
|
6621
|
-
|
|
6622
|
-
|
|
6595
|
+
/**
|
|
6596
|
+
* Get badge for a tab
|
|
6597
|
+
*/
|
|
6598
|
+
getBadge(tabId, conversations) {
|
|
6599
|
+
const tab = this.getTabById(tabId);
|
|
6600
|
+
if (!tab || !tab.badge) {
|
|
6601
|
+
return undefined;
|
|
6602
|
+
}
|
|
6603
|
+
return tab.badge(conversations);
|
|
6623
6604
|
}
|
|
6624
|
-
|
|
6625
|
-
|
|
6605
|
+
/**
|
|
6606
|
+
* Enable/disable a tab
|
|
6607
|
+
*/
|
|
6608
|
+
setEnabled(id, enabled) {
|
|
6609
|
+
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
6626
6610
|
}
|
|
6627
|
-
|
|
6628
|
-
|
|
6611
|
+
/**
|
|
6612
|
+
* Set default tab
|
|
6613
|
+
*/
|
|
6614
|
+
setDefault(id) {
|
|
6615
|
+
this._tabs.update((tabs) => tabs.map((tab) => ({
|
|
6616
|
+
...tab,
|
|
6617
|
+
default: tab.id === id,
|
|
6618
|
+
})));
|
|
6629
6619
|
}
|
|
6630
|
-
|
|
6631
|
-
|
|
6620
|
+
/**
|
|
6621
|
+
* Clear all tabs
|
|
6622
|
+
*/
|
|
6623
|
+
clear() {
|
|
6624
|
+
this._tabs.set([]);
|
|
6632
6625
|
}
|
|
6633
|
-
|
|
6634
|
-
|
|
6626
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6627
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry }); }
|
|
6628
|
+
}
|
|
6629
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
|
|
6630
|
+
type: Injectable
|
|
6631
|
+
}], ctorParameters: () => [] });
|
|
6632
|
+
|
|
6633
|
+
/**
|
|
6634
|
+
* Info Bar Action Registry
|
|
6635
|
+
* Manages actions available in the conversation info bar
|
|
6636
|
+
*/
|
|
6637
|
+
/**
|
|
6638
|
+
* Info Bar Action Registry Service
|
|
6639
|
+
* Manages available actions in the conversation info bar
|
|
6640
|
+
*/
|
|
6641
|
+
class AXInfoBarActionRegistry extends AXBaseRegistry {
|
|
6642
|
+
constructor() {
|
|
6643
|
+
super();
|
|
6644
|
+
this.translation = inject(AXTranslationService);
|
|
6645
|
+
this.injector = inject(Injector);
|
|
6646
|
+
// Register default actions from individual token
|
|
6647
|
+
inject(DEFAULT_INFO_BAR_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6648
|
+
// Register actions from REGISTRY_CONFIG
|
|
6649
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6650
|
+
config?.infoBarActions?.forEach((action) => this.register(action));
|
|
6635
6651
|
}
|
|
6636
|
-
|
|
6637
|
-
|
|
6652
|
+
/**
|
|
6653
|
+
* Get registry name for logging
|
|
6654
|
+
*/
|
|
6655
|
+
getRegistryName() {
|
|
6656
|
+
return 'InfoBarActionRegistry';
|
|
6638
6657
|
}
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6658
|
+
/**
|
|
6659
|
+
* Get actions for a specific conversation
|
|
6660
|
+
*/
|
|
6661
|
+
getActionsForConversation(conversation, location) {
|
|
6662
|
+
const context = { conversation };
|
|
6663
|
+
return this.enabledItems()
|
|
6664
|
+
.filter((action) => {
|
|
6665
|
+
// Filter by location if specified
|
|
6666
|
+
if (location && action.location && action.location !== location) {
|
|
6667
|
+
return false;
|
|
6668
|
+
}
|
|
6669
|
+
// Filter by visibility
|
|
6670
|
+
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6671
|
+
return false;
|
|
6672
|
+
}
|
|
6673
|
+
return true;
|
|
6674
|
+
})
|
|
6675
|
+
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6642
6676
|
}
|
|
6643
|
-
|
|
6644
|
-
|
|
6677
|
+
/**
|
|
6678
|
+
* Get inline actions (toolbar buttons)
|
|
6679
|
+
*/
|
|
6680
|
+
getInlineActions(conversation) {
|
|
6681
|
+
return this.getActionsForConversation(conversation, 'inline');
|
|
6645
6682
|
}
|
|
6646
|
-
|
|
6647
|
-
|
|
6683
|
+
/**
|
|
6684
|
+
* Get menu actions (dropdown items)
|
|
6685
|
+
*/
|
|
6686
|
+
getMenuActions(conversation) {
|
|
6687
|
+
return this.getActionsForConversation(conversation, 'menu');
|
|
6648
6688
|
}
|
|
6649
|
-
|
|
6650
|
-
|
|
6689
|
+
isActionEnabled(action, context) {
|
|
6690
|
+
if (action.enabled === false) {
|
|
6691
|
+
return false;
|
|
6692
|
+
}
|
|
6693
|
+
if (action.enabledWhen) {
|
|
6694
|
+
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6695
|
+
}
|
|
6696
|
+
return true;
|
|
6651
6697
|
}
|
|
6652
|
-
|
|
6653
|
-
|
|
6698
|
+
/**
|
|
6699
|
+
* Get label for an action
|
|
6700
|
+
*/
|
|
6701
|
+
getActionLabel(action, conversation) {
|
|
6702
|
+
if (typeof action.label === 'function') {
|
|
6703
|
+
const labelFactory = action.label;
|
|
6704
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6705
|
+
}
|
|
6706
|
+
return this.translation.translateSync(action.label);
|
|
6654
6707
|
}
|
|
6655
|
-
}
|
|
6656
|
-
// Export singleton instance
|
|
6657
|
-
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
6658
|
-
|
|
6659
|
-
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
6660
|
-
__proto__: null,
|
|
6661
|
-
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
6662
|
-
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
6663
|
-
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
6664
|
-
});
|
|
6665
|
-
|
|
6666
|
-
class AXConversationMessageUtilsService {
|
|
6667
6708
|
/**
|
|
6668
|
-
*
|
|
6669
|
-
* are treated as missing data.
|
|
6709
|
+
* Get icon for an action
|
|
6670
6710
|
*/
|
|
6671
|
-
|
|
6672
|
-
if (
|
|
6711
|
+
getActionIcon(action, conversation) {
|
|
6712
|
+
if (!action.icon)
|
|
6673
6713
|
return undefined;
|
|
6714
|
+
if (typeof action.icon === 'function') {
|
|
6715
|
+
const iconFactory = action.icon;
|
|
6716
|
+
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6674
6717
|
}
|
|
6675
|
-
|
|
6676
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
6718
|
+
return action.icon;
|
|
6677
6719
|
}
|
|
6678
6720
|
/**
|
|
6679
|
-
* Get
|
|
6721
|
+
* Get tooltip for an action
|
|
6680
6722
|
*/
|
|
6681
|
-
|
|
6682
|
-
|
|
6723
|
+
getActionTooltip(action, conversation) {
|
|
6724
|
+
if (!action.tooltip)
|
|
6725
|
+
return undefined;
|
|
6726
|
+
if (typeof action.tooltip === 'function') {
|
|
6727
|
+
const tooltipFactory = action.tooltip;
|
|
6728
|
+
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6729
|
+
}
|
|
6730
|
+
return this.translation.translateSync(action.tooltip);
|
|
6731
|
+
}
|
|
6732
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6733
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6734
|
+
}
|
|
6735
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6736
|
+
type: Injectable
|
|
6737
|
+
}], ctorParameters: () => [] });
|
|
6738
|
+
|
|
6739
|
+
/**
|
|
6740
|
+
* Message Action Registry
|
|
6741
|
+
* Register custom actions that can be performed on messages
|
|
6742
|
+
*/
|
|
6743
|
+
/**
|
|
6744
|
+
* Message Action Registry Service
|
|
6745
|
+
*/
|
|
6746
|
+
class AXMessageActionRegistry {
|
|
6747
|
+
constructor() {
|
|
6748
|
+
this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
|
|
6749
|
+
this.translation = inject(AXTranslationService);
|
|
6750
|
+
this.injector = inject(Injector);
|
|
6751
|
+
/** All registered actions */
|
|
6752
|
+
this.actions = this._actions.asReadonly();
|
|
6753
|
+
/** Actions sorted by priority */
|
|
6754
|
+
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
|
|
6755
|
+
// Register default actions from individual token
|
|
6756
|
+
const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
|
|
6757
|
+
if (defaultActions) {
|
|
6758
|
+
defaultActions.forEach((action) => this.register(action));
|
|
6759
|
+
}
|
|
6760
|
+
// Register actions from REGISTRY_CONFIG
|
|
6761
|
+
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6762
|
+
config?.messageActions?.forEach((action) => this.register(action));
|
|
6683
6763
|
}
|
|
6684
6764
|
/**
|
|
6685
|
-
*
|
|
6765
|
+
* Register a message action
|
|
6686
6766
|
*/
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6767
|
+
register(action) {
|
|
6768
|
+
// Validate action
|
|
6769
|
+
if (!action.id) {
|
|
6770
|
+
throw new Error('Message action must have an id');
|
|
6690
6771
|
}
|
|
6691
|
-
|
|
6772
|
+
if (!action.label) {
|
|
6773
|
+
throw new Error('Message action must have a label');
|
|
6774
|
+
}
|
|
6775
|
+
if (!action.handler) {
|
|
6776
|
+
throw new Error('Message action must have a handler');
|
|
6777
|
+
}
|
|
6778
|
+
// Check for duplicates
|
|
6779
|
+
const existing = this._actions().find((a) => a.id === action.id);
|
|
6780
|
+
if (existing) {
|
|
6781
|
+
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6782
|
+
this.unregister(action.id);
|
|
6783
|
+
}
|
|
6784
|
+
// Add to registry
|
|
6785
|
+
this._actions.update((actions) => [...actions, action]);
|
|
6692
6786
|
}
|
|
6693
6787
|
/**
|
|
6694
|
-
*
|
|
6788
|
+
* Unregister a message action
|
|
6695
6789
|
*/
|
|
6696
|
-
|
|
6697
|
-
|
|
6698
|
-
return participant?.name || translateSync('@acorex:chat.fallbacks.unknown-user');
|
|
6790
|
+
unregister(id) {
|
|
6791
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6699
6792
|
}
|
|
6700
6793
|
/**
|
|
6701
|
-
* Get
|
|
6794
|
+
* Get all actions for a specific message
|
|
6702
6795
|
*/
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6796
|
+
getActions(message, currentUser) {
|
|
6797
|
+
return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
|
|
6798
|
+
runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
|
|
6706
6799
|
}
|
|
6707
6800
|
/**
|
|
6708
|
-
*
|
|
6709
|
-
* participant `icon` first, then conversation-level `icon`.
|
|
6801
|
+
* Get menu actions for a message
|
|
6710
6802
|
*/
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
return (
|
|
6717
|
-
|
|
6803
|
+
getMenuActions(message, currentUser) {
|
|
6804
|
+
return this.getActions(message, currentUser);
|
|
6805
|
+
}
|
|
6806
|
+
/** Whether an action is enabled (safe to call outside an injection context). */
|
|
6807
|
+
isActionEnabled(action, message, currentUser) {
|
|
6808
|
+
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6809
|
+
}
|
|
6810
|
+
getActionLabel(action) {
|
|
6811
|
+
return this.translation.translateSync(action.label);
|
|
6718
6812
|
}
|
|
6719
6813
|
/**
|
|
6720
|
-
* Get
|
|
6814
|
+
* Get inline actions for a message
|
|
6721
6815
|
*/
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
return '?';
|
|
6725
|
-
return name
|
|
6726
|
-
.split(' ')
|
|
6727
|
-
.map((n) => n[0])
|
|
6728
|
-
.join('')
|
|
6729
|
-
.toUpperCase()
|
|
6730
|
-
.substring(0, 2);
|
|
6816
|
+
getInlineActions(message, currentUser) {
|
|
6817
|
+
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6731
6818
|
}
|
|
6732
6819
|
/**
|
|
6733
|
-
*
|
|
6820
|
+
* Get action by ID
|
|
6734
6821
|
*/
|
|
6735
|
-
|
|
6736
|
-
return
|
|
6822
|
+
getActionById(id) {
|
|
6823
|
+
return this._actions().find((a) => a.id === id);
|
|
6737
6824
|
}
|
|
6738
6825
|
/**
|
|
6739
|
-
*
|
|
6826
|
+
* Execute an action
|
|
6740
6827
|
*/
|
|
6741
|
-
|
|
6742
|
-
|
|
6743
|
-
|
|
6828
|
+
async executeAction(actionId, context) {
|
|
6829
|
+
const action = this.getActionById(actionId);
|
|
6830
|
+
if (!action) {
|
|
6831
|
+
throw new Error(`Action "${actionId}" not found`);
|
|
6744
6832
|
}
|
|
6745
|
-
|
|
6833
|
+
// Check if enabled
|
|
6834
|
+
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6835
|
+
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6836
|
+
}
|
|
6837
|
+
// Execute handler
|
|
6838
|
+
await runInInjectionContext(this.injector, () => action.handler(context));
|
|
6746
6839
|
}
|
|
6747
6840
|
/**
|
|
6748
|
-
*
|
|
6841
|
+
* Clear all actions
|
|
6749
6842
|
*/
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
const first = videoPayload.videos[0];
|
|
6779
|
-
const label = videoPayload.caption?.trim() ||
|
|
6780
|
-
(videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
|
|
6781
|
-
return {
|
|
6782
|
-
value: label || first?.url || '',
|
|
6783
|
-
type: 'video',
|
|
6784
|
-
icon: 'fa-light fa-video',
|
|
6785
|
-
};
|
|
6786
|
-
}
|
|
6787
|
-
case 'audio': {
|
|
6788
|
-
const audioPayload = normalizeAudioPayload(message.payload);
|
|
6789
|
-
const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
|
|
6790
|
-
const first = audioPayload.audios[0];
|
|
6791
|
-
const label = audioPayload.caption?.trim() ||
|
|
6792
|
-
(audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
|
|
6793
|
-
return {
|
|
6794
|
-
value: label || first?.url || '',
|
|
6795
|
-
type: 'audio',
|
|
6796
|
-
icon: 'fa-light fa-music',
|
|
6797
|
-
};
|
|
6798
|
-
}
|
|
6799
|
-
case 'voice': {
|
|
6800
|
-
const voicePayload = message.payload;
|
|
6801
|
-
return {
|
|
6802
|
-
value: voicePayload.url || '',
|
|
6803
|
-
type: 'voice',
|
|
6804
|
-
icon: 'fa-light fa-microphone',
|
|
6805
|
-
};
|
|
6806
|
-
}
|
|
6807
|
-
case 'file': {
|
|
6808
|
-
const filePayload = normalizeFilePayload(message.payload);
|
|
6809
|
-
const names = filePayload.files.map((f) => f.name).join(', ');
|
|
6810
|
-
const label = filePayload.caption?.trim() ||
|
|
6811
|
-
(filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
|
|
6812
|
-
return {
|
|
6813
|
-
value: label || names,
|
|
6814
|
-
type: 'file',
|
|
6815
|
-
icon: 'fa-light fa-file',
|
|
6816
|
-
};
|
|
6817
|
-
}
|
|
6818
|
-
case 'location': {
|
|
6819
|
-
const locationPayload = message.payload;
|
|
6820
|
-
return {
|
|
6821
|
-
value: locationPayload.latitude && locationPayload.longitude
|
|
6822
|
-
? `${locationPayload.latitude},${locationPayload.longitude}`
|
|
6823
|
-
: '',
|
|
6824
|
-
type: 'location',
|
|
6825
|
-
icon: 'fa-light fa-location-dot',
|
|
6826
|
-
};
|
|
6827
|
-
}
|
|
6828
|
-
case 'sticker': {
|
|
6829
|
-
const stickerPayload = message.payload;
|
|
6830
|
-
return {
|
|
6831
|
-
value: stickerPayload.url || '',
|
|
6832
|
-
type: 'sticker',
|
|
6833
|
-
icon: 'fa-light fa-face-smile',
|
|
6834
|
-
};
|
|
6835
|
-
}
|
|
6836
|
-
default:
|
|
6837
|
-
return {
|
|
6838
|
-
value: '',
|
|
6839
|
-
type: message.type,
|
|
6840
|
-
icon: 'fa-light fa-message',
|
|
6841
|
-
};
|
|
6842
|
-
}
|
|
6843
|
+
clear() {
|
|
6844
|
+
this._actions.set([]);
|
|
6845
|
+
}
|
|
6846
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6847
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry }); }
|
|
6848
|
+
}
|
|
6849
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, decorators: [{
|
|
6850
|
+
type: Injectable
|
|
6851
|
+
}], ctorParameters: () => [] });
|
|
6852
|
+
|
|
6853
|
+
/**
|
|
6854
|
+
* IndexedDB Storage Wrapper
|
|
6855
|
+
* Provides persistent storage for conversation data
|
|
6856
|
+
*/
|
|
6857
|
+
const DB_NAME = 'acorex-conversation-db';
|
|
6858
|
+
const DB_VERSION = 2;
|
|
6859
|
+
// Store names
|
|
6860
|
+
const AXConversationIndexedDbStores = {
|
|
6861
|
+
PARTICIPANTS: 'participants',
|
|
6862
|
+
CONVERSATIONS: 'conversations',
|
|
6863
|
+
MESSAGES: 'messages',
|
|
6864
|
+
MEDIA: 'media',
|
|
6865
|
+
SETTINGS: 'settings',
|
|
6866
|
+
};
|
|
6867
|
+
class AXConversationIndexedDbStorage {
|
|
6868
|
+
constructor() {
|
|
6869
|
+
this.db = null;
|
|
6870
|
+
this.initPromise = null;
|
|
6843
6871
|
}
|
|
6844
6872
|
/**
|
|
6845
|
-
*
|
|
6873
|
+
* Initialize IndexedDB
|
|
6846
6874
|
*/
|
|
6847
|
-
|
|
6848
|
-
|
|
6875
|
+
async init() {
|
|
6876
|
+
if (this.db)
|
|
6877
|
+
return;
|
|
6878
|
+
if (this.initPromise)
|
|
6879
|
+
return this.initPromise;
|
|
6880
|
+
this.initPromise = new Promise((resolve, reject) => {
|
|
6881
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
6882
|
+
request.onerror = () => reject(request.error);
|
|
6883
|
+
request.onsuccess = () => {
|
|
6884
|
+
this.db = request.result;
|
|
6885
|
+
resolve();
|
|
6886
|
+
};
|
|
6887
|
+
request.onupgradeneeded = (event) => {
|
|
6888
|
+
const db = event.target.result;
|
|
6889
|
+
// Create object stores if they don't exist
|
|
6890
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6891
|
+
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6892
|
+
}
|
|
6893
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6894
|
+
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6895
|
+
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6896
|
+
}
|
|
6897
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6898
|
+
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6899
|
+
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6900
|
+
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6901
|
+
}
|
|
6902
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6903
|
+
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6904
|
+
}
|
|
6905
|
+
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6906
|
+
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6907
|
+
}
|
|
6908
|
+
};
|
|
6909
|
+
});
|
|
6910
|
+
return this.initPromise;
|
|
6849
6911
|
}
|
|
6850
6912
|
/**
|
|
6851
|
-
* Get
|
|
6913
|
+
* Get a value from a store
|
|
6852
6914
|
*/
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
default:
|
|
6866
|
-
return 'fa-light fa-check';
|
|
6867
|
-
}
|
|
6915
|
+
async get(storeName, key) {
|
|
6916
|
+
await this.init();
|
|
6917
|
+
if (!this.db)
|
|
6918
|
+
throw new Error('Database not initialized');
|
|
6919
|
+
const db = this.db;
|
|
6920
|
+
return new Promise((resolve, reject) => {
|
|
6921
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6922
|
+
const store = transaction.objectStore(storeName);
|
|
6923
|
+
const request = store.get(key);
|
|
6924
|
+
request.onsuccess = () => resolve(request.result);
|
|
6925
|
+
request.onerror = () => reject(request.error);
|
|
6926
|
+
});
|
|
6868
6927
|
}
|
|
6869
6928
|
/**
|
|
6870
|
-
*
|
|
6929
|
+
* Get all values from a store
|
|
6871
6930
|
*/
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
if (
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6931
|
+
async getAll(storeName) {
|
|
6932
|
+
await this.init();
|
|
6933
|
+
if (!this.db)
|
|
6934
|
+
throw new Error('Database not initialized');
|
|
6935
|
+
const db = this.db;
|
|
6936
|
+
return new Promise((resolve, reject) => {
|
|
6937
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6938
|
+
const store = transaction.objectStore(storeName);
|
|
6939
|
+
const request = store.getAll();
|
|
6940
|
+
request.onsuccess = () => resolve(request.result);
|
|
6941
|
+
request.onerror = () => reject(request.error);
|
|
6942
|
+
});
|
|
6883
6943
|
}
|
|
6884
6944
|
/**
|
|
6885
|
-
*
|
|
6945
|
+
* Get values by index
|
|
6886
6946
|
*/
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6947
|
+
async getAllByIndex(storeName, indexName, query) {
|
|
6948
|
+
await this.init();
|
|
6949
|
+
if (!this.db)
|
|
6950
|
+
throw new Error('Database not initialized');
|
|
6951
|
+
const db = this.db;
|
|
6952
|
+
return new Promise((resolve, reject) => {
|
|
6953
|
+
const transaction = db.transaction(storeName, 'readonly');
|
|
6954
|
+
const store = transaction.objectStore(storeName);
|
|
6955
|
+
const index = store.index(indexName);
|
|
6956
|
+
const request = index.getAll(query);
|
|
6957
|
+
request.onsuccess = () => resolve(request.result);
|
|
6958
|
+
request.onerror = () => reject(request.error);
|
|
6959
|
+
});
|
|
6896
6960
|
}
|
|
6897
6961
|
/**
|
|
6898
|
-
*
|
|
6962
|
+
* Put a value into a store
|
|
6899
6963
|
*/
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6964
|
+
async put(storeName, value) {
|
|
6965
|
+
await this.init();
|
|
6966
|
+
if (!this.db)
|
|
6967
|
+
throw new Error('Database not initialized');
|
|
6968
|
+
const db = this.db;
|
|
6969
|
+
return new Promise((resolve, reject) => {
|
|
6970
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6971
|
+
const store = transaction.objectStore(storeName);
|
|
6972
|
+
const request = store.put(value);
|
|
6973
|
+
request.onsuccess = () => resolve();
|
|
6974
|
+
request.onerror = () => reject(request.error);
|
|
6975
|
+
});
|
|
6905
6976
|
}
|
|
6906
6977
|
/**
|
|
6907
|
-
*
|
|
6978
|
+
* Delete a value from a store
|
|
6908
6979
|
*/
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
if (
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
}
|
|
6922
|
-
return translateSync('@acorex:chat.status.people-typing', { params: { count: typingUsers.length } });
|
|
6980
|
+
async delete(storeName, key) {
|
|
6981
|
+
await this.init();
|
|
6982
|
+
if (!this.db)
|
|
6983
|
+
throw new Error('Database not initialized');
|
|
6984
|
+
const db = this.db;
|
|
6985
|
+
return new Promise((resolve, reject) => {
|
|
6986
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
6987
|
+
const store = transaction.objectStore(storeName);
|
|
6988
|
+
const request = store.delete(key);
|
|
6989
|
+
request.onsuccess = () => resolve();
|
|
6990
|
+
request.onerror = () => reject(request.error);
|
|
6991
|
+
});
|
|
6923
6992
|
}
|
|
6924
6993
|
/**
|
|
6925
|
-
*
|
|
6994
|
+
* Clear all data from a store
|
|
6926
6995
|
*/
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
return date.toLocaleDateString();
|
|
6996
|
+
async clear(storeName) {
|
|
6997
|
+
await this.init();
|
|
6998
|
+
if (!this.db)
|
|
6999
|
+
throw new Error('Database not initialized');
|
|
7000
|
+
const db = this.db;
|
|
7001
|
+
return new Promise((resolve, reject) => {
|
|
7002
|
+
const transaction = db.transaction(storeName, 'readwrite');
|
|
7003
|
+
const store = transaction.objectStore(storeName);
|
|
7004
|
+
const request = store.clear();
|
|
7005
|
+
request.onsuccess = () => resolve();
|
|
7006
|
+
request.onerror = () => reject(request.error);
|
|
7007
|
+
});
|
|
6940
7008
|
}
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
7009
|
+
// =====================
|
|
7010
|
+
// Convenience Methods
|
|
7011
|
+
// =====================
|
|
7012
|
+
async getParticipant(id) {
|
|
7013
|
+
return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
|
|
7014
|
+
}
|
|
7015
|
+
async getAllParticipants() {
|
|
7016
|
+
return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
|
|
7017
|
+
}
|
|
7018
|
+
async putParticipant(participant) {
|
|
7019
|
+
return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
|
|
7020
|
+
}
|
|
7021
|
+
async getConversation(id) {
|
|
7022
|
+
return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7023
|
+
}
|
|
7024
|
+
async getAllConversations() {
|
|
7025
|
+
return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
|
|
7026
|
+
}
|
|
7027
|
+
async putConversation(conversation) {
|
|
7028
|
+
return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
|
|
7029
|
+
}
|
|
7030
|
+
async deleteConversation(id) {
|
|
7031
|
+
return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
|
|
7032
|
+
}
|
|
7033
|
+
async getMessage(id) {
|
|
7034
|
+
return this.get(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7035
|
+
}
|
|
7036
|
+
async getAllMessages() {
|
|
7037
|
+
return this.getAll(AXConversationIndexedDbStores.MESSAGES);
|
|
7038
|
+
}
|
|
7039
|
+
async getMessagesByConversation(conversationId) {
|
|
7040
|
+
return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
|
|
7041
|
+
}
|
|
7042
|
+
async putMessage(message) {
|
|
7043
|
+
return this.put(AXConversationIndexedDbStores.MESSAGES, message);
|
|
7044
|
+
}
|
|
7045
|
+
async deleteMessage(id) {
|
|
7046
|
+
return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
|
|
7047
|
+
}
|
|
7048
|
+
async getSetting(key) {
|
|
7049
|
+
const result = await this.get(AXConversationIndexedDbStores.SETTINGS, key);
|
|
7050
|
+
return result?.value;
|
|
7051
|
+
}
|
|
7052
|
+
async putSetting(key, value) {
|
|
7053
|
+
return this.put(AXConversationIndexedDbStores.SETTINGS, { key, value });
|
|
7054
|
+
}
|
|
7055
|
+
async getMedia(id) {
|
|
7056
|
+
return this.get(AXConversationIndexedDbStores.MEDIA, id);
|
|
7057
|
+
}
|
|
7058
|
+
async putMedia(record) {
|
|
7059
|
+
return this.put(AXConversationIndexedDbStores.MEDIA, record);
|
|
7060
|
+
}
|
|
7061
|
+
async deleteMedia(id) {
|
|
7062
|
+
return this.delete(AXConversationIndexedDbStores.MEDIA, id);
|
|
6968
7063
|
}
|
|
6969
7064
|
}
|
|
7065
|
+
// Export singleton instance
|
|
7066
|
+
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
7067
|
+
|
|
7068
|
+
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
7069
|
+
__proto__: null,
|
|
7070
|
+
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
7071
|
+
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
7072
|
+
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
7073
|
+
});
|
|
6970
7074
|
|
|
6971
7075
|
/**
|
|
6972
7076
|
* Shared read/write helpers for IndexedDB chat storage (in-memory + persistence).
|
|
@@ -8412,20 +8516,27 @@ class AXConversationIndexedDbConversationApi extends AXConversationApi {
|
|
|
8412
8516
|
// Conversation CRUD
|
|
8413
8517
|
// =====================
|
|
8414
8518
|
async createConversation(data) {
|
|
8519
|
+
const creatorId = data.creatorId ?? conversationSharedStorage.currentUserId;
|
|
8520
|
+
const participantIds = new Set(data.participantIds);
|
|
8521
|
+
if (creatorId) {
|
|
8522
|
+
participantIds.add(creatorId);
|
|
8523
|
+
}
|
|
8415
8524
|
const conversation = {
|
|
8416
8525
|
id: `conv-${Date.now()}`,
|
|
8417
8526
|
type: data.type,
|
|
8418
|
-
|
|
8527
|
+
creatorId,
|
|
8528
|
+
title: data.title ||
|
|
8529
|
+
(data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
|
|
8419
8530
|
avatar: data.avatar,
|
|
8420
8531
|
description: data.description,
|
|
8421
|
-
participants:
|
|
8532
|
+
participants: [...participantIds].map((id) => {
|
|
8422
8533
|
const participant = conversationSharedStorage.participants.get(id);
|
|
8423
8534
|
if (!participant) {
|
|
8424
8535
|
throw this.createError('PARTICIPANT_NOT_FOUND', `Participant ${id} not found`, 404);
|
|
8425
8536
|
}
|
|
8426
8537
|
return {
|
|
8427
8538
|
...participant,
|
|
8428
|
-
role: id ===
|
|
8539
|
+
role: id === creatorId ? 'admin' : 'member',
|
|
8429
8540
|
};
|
|
8430
8541
|
}),
|
|
8431
8542
|
lastMessageAt: new Date(),
|
|
@@ -10395,41 +10506,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
10395
10506
|
`, styles: [":host{display:block}.avatar-picker{display:flex;flex-flow:row wrap;align-items:center;gap:1.5rem;padding:1rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-light-surface))}.avatar-preview{position:relative;width:100px;height:100px;border-radius:50%;overflow:hidden;background:rgb(var(--ax-sys-color-lighter-surface));border:3px solid rgb(var(--ax-sys-color-border-light-surface));display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .25s ease;flex-shrink:0;box-shadow:0 2px 8px #00000014}.avatar-preview:hover{border-color:rgb(var(--ax-sys-color-primary-500));// transform: scale(1.05);box-shadow:0 4px 12px #0000001f}.avatar-preview:hover .preview-overlay{opacity:1}.avatar-preview ax-image{width:100%;height:100%}.preview-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:#000000a6;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s ease;color:#fff;font-size:1.75rem}.avatar-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.375rem;font-size:2rem;color:rgb(var(--ax-sys-color-on-surface-variant));text-align:center;padding:.5rem}.placeholder-text{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;opacity:.8}.avatar-actions{display:flex;flex-direction:column;gap:.5rem;flex:1}.avatar-actions ax-button{width:100%}\n"] }]
|
|
10396
10507
|
}] });
|
|
10397
10508
|
|
|
10398
|
-
/** Other participant in a private chat (excludes the current user). */
|
|
10399
|
-
function resolvePrivatePeerUserId(conversation, currentUserId) {
|
|
10400
|
-
if (conversation.type !== 'private') {
|
|
10401
|
-
return undefined;
|
|
10402
|
-
}
|
|
10403
|
-
const currentId = currentUserId ?? 'current-user';
|
|
10404
|
-
return conversation.participants.find((participant) => participant.id !== currentId)?.id;
|
|
10405
|
-
}
|
|
10406
|
-
/** Whether `auto` kind should render a user avatar for this conversation. */
|
|
10407
|
-
function shouldUseUserAvatarForConversation(conversation, currentUserId) {
|
|
10408
|
-
return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
|
|
10409
|
-
}
|
|
10410
|
-
function resolveUserAvatarDisplay(userId, conversation, message) {
|
|
10411
|
-
if (message && conversation) {
|
|
10412
|
-
return {
|
|
10413
|
-
name: AXConversationMessageUtilsService.getSenderName(message, conversation),
|
|
10414
|
-
avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
|
|
10415
|
-
icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
|
|
10416
|
-
};
|
|
10417
|
-
}
|
|
10418
|
-
const participant = conversation?.participants.find((p) => p.id === userId);
|
|
10419
|
-
return {
|
|
10420
|
-
name: participant?.name ?? userId,
|
|
10421
|
-
avatar: participant?.avatar,
|
|
10422
|
-
icon: participant?.icon,
|
|
10423
|
-
};
|
|
10424
|
-
}
|
|
10425
|
-
function resolveConversationAvatarDisplay(conversation) {
|
|
10426
|
-
return {
|
|
10427
|
-
name: conversation.title,
|
|
10428
|
-
avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
|
|
10429
|
-
icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
|
|
10430
|
-
};
|
|
10431
|
-
}
|
|
10432
|
-
|
|
10433
10509
|
/**
|
|
10434
10510
|
* Conversation avatar host — renders a registered custom component or built-in fallback.
|
|
10435
10511
|
*/
|
|
@@ -10514,7 +10590,7 @@ class AXConversationAvatarComponent {
|
|
|
10514
10590
|
if (!conversation) {
|
|
10515
10591
|
return { name: explicitName ?? '?', avatar: explicitAvatar, icon: explicitIcon };
|
|
10516
10592
|
}
|
|
10517
|
-
const resolved = resolveConversationAvatarDisplay(conversation);
|
|
10593
|
+
const resolved = resolveConversationAvatarDisplay(conversation, this.currentUserId());
|
|
10518
10594
|
return {
|
|
10519
10595
|
name: explicitName ?? resolved.name,
|
|
10520
10596
|
avatar: explicitAvatar ?? resolved.avatar,
|
|
@@ -14918,14 +14994,23 @@ class AXConversationService {
|
|
|
14918
14994
|
this._messageDeleted$ = new Subject();
|
|
14919
14995
|
this._typingIndicator$ = new Subject();
|
|
14920
14996
|
this._presenceUpdate$ = new Subject();
|
|
14921
|
-
/** All conversations */
|
|
14922
|
-
this.conversations =
|
|
14997
|
+
/** All conversations (viewer-scoped display titles for private 1v1 chats) */
|
|
14998
|
+
this.conversations = computed(() => {
|
|
14999
|
+
const currentUserId = this._currentUser()?.id;
|
|
15000
|
+
return this.state.conversations().map((conversation) => resolveConversationForViewer(conversation, currentUserId));
|
|
15001
|
+
}, ...(ngDevMode ? [{ debugName: "conversations" }] : []));
|
|
14923
15002
|
/** Active conversation ID */
|
|
14924
15003
|
this.activeConversationId = this._activeConversationId.asReadonly();
|
|
14925
15004
|
/** Active conversation */
|
|
14926
15005
|
this.activeConversation = computed(() => {
|
|
14927
15006
|
const id = this._activeConversationId();
|
|
14928
|
-
|
|
15007
|
+
if (!id) {
|
|
15008
|
+
return null;
|
|
15009
|
+
}
|
|
15010
|
+
const conversation = this.state.getConversation(id);
|
|
15011
|
+
return conversation
|
|
15012
|
+
? resolveConversationForViewer(conversation, this._currentUser()?.id)
|
|
15013
|
+
: null;
|
|
14929
15014
|
}, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
|
|
14930
15015
|
/** Messages for active conversation */
|
|
14931
15016
|
this.activeMessages = computed(() => {
|
|
@@ -15716,17 +15801,29 @@ class AXConversationService {
|
|
|
15716
15801
|
*/
|
|
15717
15802
|
async createConversation(participantIds, type, metadata) {
|
|
15718
15803
|
try {
|
|
15804
|
+
const currentUser = this._currentUser();
|
|
15805
|
+
const creatorId = currentUser?.id;
|
|
15806
|
+
const isPrivate = type === 'private';
|
|
15807
|
+
const scopedParticipantIds = [...participantIds];
|
|
15808
|
+
if (creatorId && !scopedParticipantIds.includes(creatorId)) {
|
|
15809
|
+
scopedParticipantIds.unshift(creatorId);
|
|
15810
|
+
}
|
|
15719
15811
|
const conversation = await this.conversationApi.createConversation({
|
|
15720
15812
|
type,
|
|
15721
|
-
participantIds,
|
|
15722
|
-
|
|
15813
|
+
participantIds: scopedParticipantIds,
|
|
15814
|
+
creatorId,
|
|
15815
|
+
title: isPrivate ? undefined : metadata?.['title'],
|
|
15723
15816
|
description: metadata?.['description'],
|
|
15724
|
-
avatar:
|
|
15725
|
-
|
|
15726
|
-
|
|
15817
|
+
avatar: isPrivate
|
|
15818
|
+
? undefined
|
|
15819
|
+
: AXConversationService.normalizeOptionalString(metadata?.['avatar']),
|
|
15820
|
+
icon: isPrivate
|
|
15821
|
+
? undefined
|
|
15822
|
+
: AXConversationService.normalizeOptionalString(metadata?.['icon']),
|
|
15823
|
+
metadata: isPrivate ? undefined : metadata,
|
|
15727
15824
|
});
|
|
15728
15825
|
this.state.setConversation(conversation);
|
|
15729
|
-
return conversation;
|
|
15826
|
+
return resolveConversationForViewer(conversation, creatorId);
|
|
15730
15827
|
}
|
|
15731
15828
|
catch (error) {
|
|
15732
15829
|
this.errorHandler.handle(error, 'createConversation', { participantIds, type });
|
|
@@ -20955,12 +21052,7 @@ class AXNewConversationDialogComponent extends AXBasePageComponent {
|
|
|
20955
21052
|
try {
|
|
20956
21053
|
let conversation;
|
|
20957
21054
|
if (selectedIds.length === 1) {
|
|
20958
|
-
|
|
20959
|
-
const metadata = {
|
|
20960
|
-
title: selectedUser?.name,
|
|
20961
|
-
avatar: selectedUser?.avatar,
|
|
20962
|
-
};
|
|
20963
|
-
conversation = await this.conversationService.createConversation(selectedIds, 'private', metadata);
|
|
21055
|
+
conversation = await this.conversationService.createConversation(selectedIds, 'private');
|
|
20964
21056
|
}
|
|
20965
21057
|
else {
|
|
20966
21058
|
const metadata = {
|
|
@@ -22174,5 +22266,5 @@ function getErrorMessage(code, params) {
|
|
|
22174
22266
|
* Generated bundle index. Do not edit.
|
|
22175
22267
|
*/
|
|
22176
22268
|
|
|
22177
|
-
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 };
|
|
22269
|
+
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, isGenericPrivateConversationTitle, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveConversationForViewer, resolveConversationTitleForViewer, resolveParticipantProfile, resolvePrivatePeerParticipant, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
|
|
22178
22270
|
//# sourceMappingURL=acorex-components-conversation2.mjs.map
|