@acorex/components 20.8.20 → 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,7 +7,7 @@ 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
13
|
import { createFileTypeMetadata, AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } from '@acorex/core/file';
|
|
@@ -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`.
|
|
@@ -5688,443 +6092,162 @@ class AXComposerActionRegistry {
|
|
|
5688
6092
|
this.fileTypeRegistry = inject(AXFileTypeRegistryService);
|
|
5689
6093
|
/** All registered actions */
|
|
5690
6094
|
this.actions = this._actions.asReadonly();
|
|
5691
|
-
/** Left-side actions sorted by priority */
|
|
5692
|
-
this.leftActions = computed(() => [...this._actions()]
|
|
5693
|
-
.filter((action) => action.position === 'left')
|
|
5694
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "leftActions" }] : []));
|
|
5695
|
-
/** Right-side actions sorted by priority */
|
|
5696
|
-
this.rightActions = computed(() => [...this._actions()]
|
|
5697
|
-
.filter((action) => action.position === 'right')
|
|
5698
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "rightActions" }] : []));
|
|
5699
|
-
/** Left-side inline actions (location = 'inline' or undefined) */
|
|
5700
|
-
this.leftInlineActions = computed(() => this.leftActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "leftInlineActions" }] : []));
|
|
5701
|
-
/** Left-side menu actions (location = 'menu') */
|
|
5702
|
-
this.leftMenuActions = computed(() => this.leftActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "leftMenuActions" }] : []));
|
|
5703
|
-
/** Right-side inline actions (location = 'inline' or undefined) */
|
|
5704
|
-
this.rightInlineActions = computed(() => this.rightActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "rightInlineActions" }] : []));
|
|
5705
|
-
/** Right-side menu actions (location = 'menu') */
|
|
5706
|
-
this.rightMenuActions = computed(() => this.rightActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "rightMenuActions" }] : []));
|
|
5707
|
-
inject(CONVERSATION_FILE_TYPES_READY);
|
|
5708
|
-
// Register default actions from individual token
|
|
5709
|
-
inject(DEFAULT_COMPOSER_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
5710
|
-
// Register actions from REGISTRY_CONFIG
|
|
5711
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
5712
|
-
config?.composerActions?.forEach((action) => this.register(action));
|
|
5713
|
-
}
|
|
5714
|
-
/**
|
|
5715
|
-
* Register a composer action
|
|
5716
|
-
*/
|
|
5717
|
-
register(action) {
|
|
5718
|
-
// Validate action
|
|
5719
|
-
if (!action.id) {
|
|
5720
|
-
throw new Error('Composer action must have an id');
|
|
5721
|
-
}
|
|
5722
|
-
if (!action.label) {
|
|
5723
|
-
throw new Error('Composer action must have a label');
|
|
5724
|
-
}
|
|
5725
|
-
if (!action.icon) {
|
|
5726
|
-
throw new Error('Composer action must have an icon');
|
|
5727
|
-
}
|
|
5728
|
-
if (!action.handler && !action.component) {
|
|
5729
|
-
throw new Error('Composer action must have either a handler or a component');
|
|
5730
|
-
}
|
|
5731
|
-
// Check for duplicates
|
|
5732
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
5733
|
-
if (existing) {
|
|
5734
|
-
console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
|
|
5735
|
-
this.unregister(action.id);
|
|
5736
|
-
}
|
|
5737
|
-
// Add to registry
|
|
5738
|
-
this._actions.update((actions) => [...actions, action]);
|
|
5739
|
-
if (action.fileType) {
|
|
5740
|
-
void this.ensureFileTypeExists(action.fileType);
|
|
5741
|
-
}
|
|
5742
|
-
}
|
|
5743
|
-
async ensureFileTypeExists(fileType) {
|
|
5744
|
-
const match = await this.fileTypeRegistry.get(fileType);
|
|
5745
|
-
if (!match) {
|
|
5746
|
-
console.warn(`[AXComposerActionRegistry] Unknown fileType "${fileType}". ` +
|
|
5747
|
-
'Register its AXFileTypeInfoProvider in app providers before using this action.');
|
|
5748
|
-
}
|
|
5749
|
-
}
|
|
5750
|
-
/**
|
|
5751
|
-
* Unregister a composer action
|
|
5752
|
-
*/
|
|
5753
|
-
unregister(id) {
|
|
5754
|
-
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
5755
|
-
}
|
|
5756
|
-
/**
|
|
5757
|
-
* Get visible actions for a position
|
|
5758
|
-
*/
|
|
5759
|
-
getVisibleActions(position, context) {
|
|
5760
|
-
const actions = position === 'left' ? this.leftActions() : this.rightActions();
|
|
5761
|
-
return actions.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)));
|
|
5762
|
-
}
|
|
5763
|
-
/**
|
|
5764
|
-
* Get enabled actions for a position
|
|
5765
|
-
*/
|
|
5766
|
-
getEnabledActions(position, context) {
|
|
5767
|
-
return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
|
|
5768
|
-
}
|
|
5769
|
-
isActionEnabled(action, context) {
|
|
5770
|
-
return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
|
|
5771
|
-
}
|
|
5772
|
-
/**
|
|
5773
|
-
* Picker actions for a file catalog (by priority), when visible and enabled.
|
|
5774
|
-
*/
|
|
5775
|
-
findPickerActionsForFileType(catalogName, context) {
|
|
5776
|
-
return [...this._actions()]
|
|
5777
|
-
.filter((action) => action.component && action.fileType === catalogName)
|
|
5778
|
-
.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)))
|
|
5779
|
-
.filter((action) => this.isActionEnabled(action, context))
|
|
5780
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
5781
|
-
}
|
|
5782
|
-
/**
|
|
5783
|
-
* First picker action for a file catalog (by priority), when visible and enabled.
|
|
5784
|
-
*/
|
|
5785
|
-
findPickerActionForFileType(catalogName, context) {
|
|
5786
|
-
return this.findPickerActionsForFileType(catalogName, context)[0];
|
|
5787
|
-
}
|
|
5788
|
-
/**
|
|
5789
|
-
* Get action by ID
|
|
5790
|
-
*/
|
|
5791
|
-
getActionById(id) {
|
|
5792
|
-
return this._actions().find((a) => a.id === id);
|
|
5793
|
-
}
|
|
5794
|
-
getActionLabel(action) {
|
|
5795
|
-
return this.translation.translateSync(action.label);
|
|
5796
|
-
}
|
|
5797
|
-
getActionTooltip(action) {
|
|
5798
|
-
if (!action.tooltip)
|
|
5799
|
-
return undefined;
|
|
5800
|
-
return this.translation.translateSync(action.tooltip);
|
|
5801
|
-
}
|
|
5802
|
-
/**
|
|
5803
|
-
* Execute an action
|
|
5804
|
-
*/
|
|
5805
|
-
async executeAction(actionId, context) {
|
|
5806
|
-
const action = this.getActionById(actionId);
|
|
5807
|
-
if (!action) {
|
|
5808
|
-
throw new Error(`Action "${actionId}" not found`);
|
|
5809
|
-
}
|
|
5810
|
-
if (!runInInjectionContext(this.injector, () => action.visible(context))) {
|
|
5811
|
-
throw new Error(`Action "${actionId}" is not visible`);
|
|
5812
|
-
}
|
|
5813
|
-
if (action.enabled && !runInInjectionContext(this.injector, () => action.enabled?.(context))) {
|
|
5814
|
-
throw new Error(`Action "${actionId}" is not enabled`);
|
|
5815
|
-
}
|
|
5816
|
-
if (action.handler) {
|
|
5817
|
-
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
5818
|
-
}
|
|
5819
|
-
}
|
|
5820
|
-
/**
|
|
5821
|
-
* Update action badge
|
|
5822
|
-
*/
|
|
5823
|
-
setBadge(id, badge) {
|
|
5824
|
-
this._actions.update((actions) => actions.map((action) => (action.id === id ? { ...action, badge } : action)));
|
|
5825
|
-
}
|
|
5826
|
-
/**
|
|
5827
|
-
* Clear all actions
|
|
5828
|
-
*/
|
|
5829
|
-
clear() {
|
|
5830
|
-
this._actions.set([]);
|
|
5831
|
-
}
|
|
5832
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5833
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry }); }
|
|
5834
|
-
}
|
|
5835
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, decorators: [{
|
|
5836
|
-
type: Injectable
|
|
5837
|
-
}], ctorParameters: () => [] });
|
|
5838
|
-
|
|
5839
|
-
/**
|
|
5840
|
-
* Composer Tab Registry
|
|
5841
|
-
* Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
|
|
5842
|
-
*/
|
|
5843
|
-
/**
|
|
5844
|
-
* Composer Tab Registry Service
|
|
5845
|
-
*/
|
|
5846
|
-
class AXComposerTabRegistry {
|
|
5847
|
-
constructor() {
|
|
5848
|
-
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
5849
|
-
this.translation = inject(AXTranslationService);
|
|
5850
|
-
/** All registered tabs */
|
|
5851
|
-
this.tabs = this._tabs.asReadonly();
|
|
5852
|
-
/** Enabled tabs sorted by priority */
|
|
5853
|
-
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
5854
|
-
// Register default tabs from individual token
|
|
5855
|
-
inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
5856
|
-
// Register tabs from REGISTRY_CONFIG
|
|
5857
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
5858
|
-
config?.composerTabs?.forEach((tab) => this.register(tab));
|
|
5859
|
-
}
|
|
5860
|
-
/**
|
|
5861
|
-
* Register a composer tab
|
|
5862
|
-
*/
|
|
5863
|
-
register(tab) {
|
|
5864
|
-
// Validate tab
|
|
5865
|
-
if (!tab.id) {
|
|
5866
|
-
throw new Error('Composer tab must have an id');
|
|
5867
|
-
}
|
|
5868
|
-
if (!tab.title) {
|
|
5869
|
-
throw new Error('Composer tab must have a title');
|
|
5870
|
-
}
|
|
5871
|
-
if (!tab.component) {
|
|
5872
|
-
throw new Error('Composer tab must have a component');
|
|
5873
|
-
}
|
|
5874
|
-
// Check for duplicates
|
|
5875
|
-
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
5876
|
-
if (existing) {
|
|
5877
|
-
console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
|
|
5878
|
-
this.unregister(tab.id);
|
|
5879
|
-
}
|
|
5880
|
-
// Add to registry
|
|
5881
|
-
this._tabs.update((tabs) => [...tabs, tab]);
|
|
5882
|
-
}
|
|
5883
|
-
/**
|
|
5884
|
-
* Unregister a composer tab
|
|
5885
|
-
*/
|
|
5886
|
-
unregister(id) {
|
|
5887
|
-
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
5888
|
-
}
|
|
5889
|
-
/**
|
|
5890
|
-
* Get tab by ID
|
|
5891
|
-
*/
|
|
5892
|
-
getTabById(id) {
|
|
5893
|
-
return this._tabs().find((t) => t.id === id);
|
|
5894
|
-
}
|
|
5895
|
-
getTabTitle(tab) {
|
|
5896
|
-
return this.translation.translateSync(tab.title);
|
|
5897
|
-
}
|
|
5898
|
-
/**
|
|
5899
|
-
* Enable/disable a tab
|
|
5900
|
-
*/
|
|
5901
|
-
setEnabled(id, enabled) {
|
|
5902
|
-
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
5903
|
-
}
|
|
5904
|
-
/**
|
|
5905
|
-
* Update tab badge
|
|
5906
|
-
*/
|
|
5907
|
-
setBadge(id, badge) {
|
|
5908
|
-
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
|
|
5909
|
-
}
|
|
5910
|
-
/**
|
|
5911
|
-
* Clear all tabs
|
|
5912
|
-
*/
|
|
5913
|
-
clear() {
|
|
5914
|
-
this._tabs.set([]);
|
|
5915
|
-
}
|
|
5916
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
5917
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry }); }
|
|
5918
|
-
}
|
|
5919
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, decorators: [{
|
|
5920
|
-
type: Injectable
|
|
5921
|
-
}], ctorParameters: () => [] });
|
|
5922
|
-
|
|
5923
|
-
/**
|
|
5924
|
-
* Abstract Base Registry
|
|
5925
|
-
* Base class for all registries with common functionality
|
|
5926
|
-
*/
|
|
5927
|
-
/**
|
|
5928
|
-
* Abstract Base Registry Class
|
|
5929
|
-
* Provides common registry functionality
|
|
5930
|
-
*/
|
|
5931
|
-
class AXBaseRegistry {
|
|
5932
|
-
constructor() {
|
|
5933
|
-
this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
|
|
5934
|
-
/** All registered items */
|
|
5935
|
-
this.items = this._items.asReadonly();
|
|
5936
|
-
/** Enabled items sorted by priority */
|
|
5937
|
-
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));
|
|
5938
6117
|
}
|
|
5939
6118
|
/**
|
|
5940
|
-
* Register
|
|
6119
|
+
* Register a composer action
|
|
5941
6120
|
*/
|
|
5942
|
-
register(
|
|
5943
|
-
|
|
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
|
+
}
|
|
5944
6135
|
// Check for duplicates
|
|
5945
|
-
const existing = this.
|
|
6136
|
+
const existing = this._actions().find((a) => a.id === action.id);
|
|
5946
6137
|
if (existing) {
|
|
5947
|
-
console.warn(
|
|
5948
|
-
this.unregister(
|
|
6138
|
+
console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
|
|
6139
|
+
this.unregister(action.id);
|
|
5949
6140
|
}
|
|
5950
6141
|
// Add to registry
|
|
5951
|
-
this.
|
|
6142
|
+
this._actions.update((actions) => [...actions, action]);
|
|
6143
|
+
if (action.fileType) {
|
|
6144
|
+
void this.ensureFileTypeExists(action.fileType);
|
|
6145
|
+
}
|
|
5952
6146
|
}
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
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
|
+
}
|
|
5958
6153
|
}
|
|
5959
6154
|
/**
|
|
5960
|
-
* Unregister
|
|
6155
|
+
* Unregister a composer action
|
|
5961
6156
|
*/
|
|
5962
6157
|
unregister(id) {
|
|
5963
|
-
this.
|
|
5964
|
-
}
|
|
5965
|
-
/**
|
|
5966
|
-
* Get item by ID
|
|
5967
|
-
*/
|
|
5968
|
-
getById(id) {
|
|
5969
|
-
return this._items().find((i) => i.id === id);
|
|
5970
|
-
}
|
|
5971
|
-
/**
|
|
5972
|
-
* Check if item exists
|
|
5973
|
-
*/
|
|
5974
|
-
has(id) {
|
|
5975
|
-
return this.getById(id) !== undefined;
|
|
5976
|
-
}
|
|
5977
|
-
/**
|
|
5978
|
-
* Enable/disable an item
|
|
5979
|
-
*/
|
|
5980
|
-
setEnabled(id, enabled) {
|
|
5981
|
-
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
|
|
5982
|
-
}
|
|
5983
|
-
/**
|
|
5984
|
-
* Update an item
|
|
5985
|
-
*/
|
|
5986
|
-
update(id, updates) {
|
|
5987
|
-
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
6158
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
5988
6159
|
}
|
|
5989
6160
|
/**
|
|
5990
|
-
*
|
|
6161
|
+
* Get visible actions for a position
|
|
5991
6162
|
*/
|
|
5992
|
-
|
|
5993
|
-
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)));
|
|
5994
6166
|
}
|
|
5995
6167
|
/**
|
|
5996
|
-
* Get
|
|
6168
|
+
* Get enabled actions for a position
|
|
5997
6169
|
*/
|
|
5998
|
-
|
|
5999
|
-
return this.
|
|
6170
|
+
getEnabledActions(position, context) {
|
|
6171
|
+
return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
|
|
6000
6172
|
}
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
*/
|
|
6004
|
-
get enabledCount() {
|
|
6005
|
-
return this.enabledItems().length;
|
|
6173
|
+
isActionEnabled(action, context) {
|
|
6174
|
+
return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
|
|
6006
6175
|
}
|
|
6007
6176
|
/**
|
|
6008
|
-
*
|
|
6009
|
-
* Override in child classes for custom validation
|
|
6177
|
+
* Picker actions for a file catalog (by priority), when visible and enabled.
|
|
6010
6178
|
*/
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
/**
|
|
6019
|
-
* Conversation Item Action Registry
|
|
6020
|
-
* Manages actions available in the conversation list item context menu
|
|
6021
|
-
*/
|
|
6022
|
-
/**
|
|
6023
|
-
* Conversation Item Action Registry Service
|
|
6024
|
-
* Manages available actions in the conversation list item context menu
|
|
6025
|
-
*/
|
|
6026
|
-
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6027
|
-
constructor() {
|
|
6028
|
-
super();
|
|
6029
|
-
this.translation = inject(AXTranslationService);
|
|
6030
|
-
this.injector = inject(Injector);
|
|
6031
|
-
// Register default actions from individual token
|
|
6032
|
-
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6033
|
-
// Register actions from REGISTRY_CONFIG
|
|
6034
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6035
|
-
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
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));
|
|
6036
6185
|
}
|
|
6037
6186
|
/**
|
|
6038
|
-
*
|
|
6187
|
+
* First picker action for a file catalog (by priority), when visible and enabled.
|
|
6039
6188
|
*/
|
|
6040
|
-
|
|
6041
|
-
return
|
|
6189
|
+
findPickerActionForFileType(catalogName, context) {
|
|
6190
|
+
return this.findPickerActionsForFileType(catalogName, context)[0];
|
|
6042
6191
|
}
|
|
6043
6192
|
/**
|
|
6044
|
-
* Get
|
|
6193
|
+
* Get action by ID
|
|
6045
6194
|
*/
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
return this.enabledItems()
|
|
6049
|
-
.filter((action) => {
|
|
6050
|
-
// Filter by visibility
|
|
6051
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6052
|
-
return false;
|
|
6053
|
-
}
|
|
6054
|
-
return true;
|
|
6055
|
-
})
|
|
6056
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6057
|
-
}
|
|
6058
|
-
isActionEnabled(action, context) {
|
|
6059
|
-
if (action.enabled === false) {
|
|
6060
|
-
return false;
|
|
6061
|
-
}
|
|
6062
|
-
if (action.enabledWhen) {
|
|
6063
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6064
|
-
}
|
|
6065
|
-
return true;
|
|
6195
|
+
getActionById(id) {
|
|
6196
|
+
return this._actions().find((a) => a.id === id);
|
|
6066
6197
|
}
|
|
6067
|
-
|
|
6068
|
-
* Get label for an action
|
|
6069
|
-
*/
|
|
6070
|
-
getActionLabel(action, conversation) {
|
|
6071
|
-
if (typeof action.label === 'function') {
|
|
6072
|
-
const labelFactory = action.label;
|
|
6073
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6074
|
-
}
|
|
6198
|
+
getActionLabel(action) {
|
|
6075
6199
|
return this.translation.translateSync(action.label);
|
|
6076
6200
|
}
|
|
6077
|
-
|
|
6078
|
-
* Get icon for an action
|
|
6079
|
-
*/
|
|
6080
|
-
getActionIcon(action, conversation) {
|
|
6081
|
-
if (!action.icon)
|
|
6082
|
-
return undefined;
|
|
6083
|
-
if (typeof action.icon === 'function') {
|
|
6084
|
-
const iconFactory = action.icon;
|
|
6085
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6086
|
-
}
|
|
6087
|
-
return action.icon;
|
|
6088
|
-
}
|
|
6089
|
-
/**
|
|
6090
|
-
* Get tooltip for an action
|
|
6091
|
-
*/
|
|
6092
|
-
getActionTooltip(action, conversation) {
|
|
6201
|
+
getActionTooltip(action) {
|
|
6093
6202
|
if (!action.tooltip)
|
|
6094
6203
|
return undefined;
|
|
6095
|
-
if (typeof action.tooltip === 'function') {
|
|
6096
|
-
const tooltipFactory = action.tooltip;
|
|
6097
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
|
|
6098
|
-
}
|
|
6099
6204
|
return this.translation.translateSync(action.tooltip);
|
|
6100
6205
|
}
|
|
6206
|
+
/**
|
|
6207
|
+
* Execute an action
|
|
6208
|
+
*/
|
|
6101
6209
|
async executeAction(actionId, context) {
|
|
6102
|
-
const action = this.
|
|
6210
|
+
const action = this.getActionById(actionId);
|
|
6103
6211
|
if (!action) {
|
|
6104
6212
|
throw new Error(`Action "${actionId}" not found`);
|
|
6105
6213
|
}
|
|
6106
|
-
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))) {
|
|
6107
6218
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6108
6219
|
}
|
|
6109
6220
|
if (action.handler) {
|
|
6110
6221
|
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6111
6222
|
}
|
|
6112
6223
|
}
|
|
6113
|
-
|
|
6114
|
-
|
|
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 }); }
|
|
6115
6238
|
}
|
|
6116
|
-
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: [{
|
|
6117
6240
|
type: Injectable
|
|
6118
6241
|
}], ctorParameters: () => [] });
|
|
6119
6242
|
|
|
6120
6243
|
/**
|
|
6121
|
-
*
|
|
6122
|
-
* Register custom tabs for
|
|
6244
|
+
* Composer Tab Registry
|
|
6245
|
+
* Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
|
|
6123
6246
|
*/
|
|
6124
6247
|
/**
|
|
6125
|
-
*
|
|
6248
|
+
* Composer Tab Registry Service
|
|
6126
6249
|
*/
|
|
6127
|
-
class
|
|
6250
|
+
class AXComposerTabRegistry {
|
|
6128
6251
|
constructor() {
|
|
6129
6252
|
this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
|
|
6130
6253
|
this.translation = inject(AXTranslationService);
|
|
@@ -6132,39 +6255,37 @@ class AXConversationTabRegistry {
|
|
|
6132
6255
|
this.tabs = this._tabs.asReadonly();
|
|
6133
6256
|
/** Enabled tabs sorted by priority */
|
|
6134
6257
|
this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
|
|
6135
|
-
/** Default active tab */
|
|
6136
|
-
this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
|
|
6137
6258
|
// Register default tabs from individual token
|
|
6138
|
-
inject(
|
|
6259
|
+
inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
|
|
6139
6260
|
// Register tabs from REGISTRY_CONFIG
|
|
6140
6261
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6141
|
-
config?.
|
|
6262
|
+
config?.composerTabs?.forEach((tab) => this.register(tab));
|
|
6142
6263
|
}
|
|
6143
6264
|
/**
|
|
6144
|
-
* Register a
|
|
6265
|
+
* Register a composer tab
|
|
6145
6266
|
*/
|
|
6146
6267
|
register(tab) {
|
|
6147
6268
|
// Validate tab
|
|
6148
6269
|
if (!tab.id) {
|
|
6149
|
-
throw new Error('
|
|
6270
|
+
throw new Error('Composer tab must have an id');
|
|
6150
6271
|
}
|
|
6151
|
-
if (!tab.
|
|
6152
|
-
throw new Error('
|
|
6272
|
+
if (!tab.title) {
|
|
6273
|
+
throw new Error('Composer tab must have a title');
|
|
6153
6274
|
}
|
|
6154
|
-
if (!tab.
|
|
6155
|
-
throw new Error('
|
|
6275
|
+
if (!tab.component) {
|
|
6276
|
+
throw new Error('Composer tab must have a component');
|
|
6156
6277
|
}
|
|
6157
6278
|
// Check for duplicates
|
|
6158
6279
|
const existing = this._tabs().find((t) => t.id === tab.id);
|
|
6159
6280
|
if (existing) {
|
|
6160
|
-
console.warn(`
|
|
6281
|
+
console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
|
|
6161
6282
|
this.unregister(tab.id);
|
|
6162
6283
|
}
|
|
6163
6284
|
// Add to registry
|
|
6164
6285
|
this._tabs.update((tabs) => [...tabs, tab]);
|
|
6165
6286
|
}
|
|
6166
6287
|
/**
|
|
6167
|
-
* Unregister a
|
|
6288
|
+
* Unregister a composer tab
|
|
6168
6289
|
*/
|
|
6169
6290
|
unregister(id) {
|
|
6170
6291
|
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
@@ -6175,28 +6296,8 @@ class AXConversationTabRegistry {
|
|
|
6175
6296
|
getTabById(id) {
|
|
6176
6297
|
return this._tabs().find((t) => t.id === id);
|
|
6177
6298
|
}
|
|
6178
|
-
|
|
6179
|
-
return this.translation.translateSync(tab.
|
|
6180
|
-
}
|
|
6181
|
-
/**
|
|
6182
|
-
* Apply tab filter to conversations
|
|
6183
|
-
*/
|
|
6184
|
-
filterConversations(tabId, conversations) {
|
|
6185
|
-
const tab = this.getTabById(tabId);
|
|
6186
|
-
if (!tab) {
|
|
6187
|
-
throw new Error(`Tab "${tabId}" not found`);
|
|
6188
|
-
}
|
|
6189
|
-
return tab.filter(conversations);
|
|
6190
|
-
}
|
|
6191
|
-
/**
|
|
6192
|
-
* Get badge for a tab
|
|
6193
|
-
*/
|
|
6194
|
-
getBadge(tabId, conversations) {
|
|
6195
|
-
const tab = this.getTabById(tabId);
|
|
6196
|
-
if (!tab || !tab.badge) {
|
|
6197
|
-
return undefined;
|
|
6198
|
-
}
|
|
6199
|
-
return tab.badge(conversations);
|
|
6299
|
+
getTabTitle(tab) {
|
|
6300
|
+
return this.translation.translateSync(tab.title);
|
|
6200
6301
|
}
|
|
6201
6302
|
/**
|
|
6202
6303
|
* Enable/disable a tab
|
|
@@ -6205,13 +6306,10 @@ class AXConversationTabRegistry {
|
|
|
6205
6306
|
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
|
|
6206
6307
|
}
|
|
6207
6308
|
/**
|
|
6208
|
-
*
|
|
6309
|
+
* Update tab badge
|
|
6209
6310
|
*/
|
|
6210
|
-
|
|
6211
|
-
this._tabs.update((tabs) => tabs.map((tab) => ({
|
|
6212
|
-
...tab,
|
|
6213
|
-
default: tab.id === id,
|
|
6214
|
-
})));
|
|
6311
|
+
setBadge(id, badge) {
|
|
6312
|
+
this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
|
|
6215
6313
|
}
|
|
6216
6314
|
/**
|
|
6217
6315
|
* Clear all tabs
|
|
@@ -6219,759 +6317,760 @@ class AXConversationTabRegistry {
|
|
|
6219
6317
|
clear() {
|
|
6220
6318
|
this._tabs.set([]);
|
|
6221
6319
|
}
|
|
6222
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6223
|
-
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 }); }
|
|
6224
6322
|
}
|
|
6225
|
-
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: [{
|
|
6226
6324
|
type: Injectable
|
|
6227
6325
|
}], ctorParameters: () => [] });
|
|
6228
6326
|
|
|
6229
6327
|
/**
|
|
6230
|
-
*
|
|
6231
|
-
*
|
|
6328
|
+
* Abstract Base Registry
|
|
6329
|
+
* Base class for all registries with common functionality
|
|
6232
6330
|
*/
|
|
6233
6331
|
/**
|
|
6234
|
-
*
|
|
6235
|
-
*
|
|
6332
|
+
* Abstract Base Registry Class
|
|
6333
|
+
* Provides common registry functionality
|
|
6236
6334
|
*/
|
|
6237
|
-
class
|
|
6335
|
+
class AXBaseRegistry {
|
|
6238
6336
|
constructor() {
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
this.
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
// Register actions from REGISTRY_CONFIG
|
|
6245
|
-
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6246
|
-
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" }] : []));
|
|
6247
6342
|
}
|
|
6248
6343
|
/**
|
|
6249
|
-
*
|
|
6344
|
+
* Register an item
|
|
6250
6345
|
*/
|
|
6251
|
-
|
|
6252
|
-
|
|
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]);
|
|
6253
6356
|
}
|
|
6254
6357
|
/**
|
|
6255
|
-
*
|
|
6358
|
+
* Register multiple items
|
|
6256
6359
|
*/
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
return this.enabledItems()
|
|
6260
|
-
.filter((action) => {
|
|
6261
|
-
// Filter by location if specified
|
|
6262
|
-
if (location && action.location && action.location !== location) {
|
|
6263
|
-
return false;
|
|
6264
|
-
}
|
|
6265
|
-
// Filter by visibility
|
|
6266
|
-
if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
|
|
6267
|
-
return false;
|
|
6268
|
-
}
|
|
6269
|
-
return true;
|
|
6270
|
-
})
|
|
6271
|
-
.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
|
6360
|
+
registerMany(items) {
|
|
6361
|
+
items.forEach((item) => this.register(item));
|
|
6272
6362
|
}
|
|
6273
6363
|
/**
|
|
6274
|
-
*
|
|
6364
|
+
* Unregister an item by ID
|
|
6275
6365
|
*/
|
|
6276
|
-
|
|
6277
|
-
|
|
6366
|
+
unregister(id) {
|
|
6367
|
+
this._items.update((items) => items.filter((i) => i.id !== id));
|
|
6278
6368
|
}
|
|
6279
6369
|
/**
|
|
6280
|
-
* Get
|
|
6370
|
+
* Get item by ID
|
|
6281
6371
|
*/
|
|
6282
|
-
|
|
6283
|
-
return this.
|
|
6372
|
+
getById(id) {
|
|
6373
|
+
return this._items().find((i) => i.id === id);
|
|
6284
6374
|
}
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
|
|
6291
|
-
}
|
|
6292
|
-
return true;
|
|
6375
|
+
/**
|
|
6376
|
+
* Check if item exists
|
|
6377
|
+
*/
|
|
6378
|
+
has(id) {
|
|
6379
|
+
return this.getById(id) !== undefined;
|
|
6293
6380
|
}
|
|
6294
6381
|
/**
|
|
6295
|
-
*
|
|
6382
|
+
* Enable/disable an item
|
|
6296
6383
|
*/
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
const labelFactory = action.label;
|
|
6300
|
-
return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
|
|
6301
|
-
}
|
|
6302
|
-
return this.translation.translateSync(action.label);
|
|
6384
|
+
setEnabled(id, enabled) {
|
|
6385
|
+
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
|
|
6303
6386
|
}
|
|
6304
6387
|
/**
|
|
6305
|
-
*
|
|
6388
|
+
* Update an item
|
|
6306
6389
|
*/
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
return undefined;
|
|
6310
|
-
if (typeof action.icon === 'function') {
|
|
6311
|
-
const iconFactory = action.icon;
|
|
6312
|
-
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6313
|
-
}
|
|
6314
|
-
return action.icon;
|
|
6390
|
+
update(id, updates) {
|
|
6391
|
+
this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
|
|
6315
6392
|
}
|
|
6316
6393
|
/**
|
|
6317
|
-
*
|
|
6394
|
+
* Clear all items
|
|
6318
6395
|
*/
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
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`);
|
|
6325
6418
|
}
|
|
6326
|
-
return this.translation.translateSync(action.tooltip);
|
|
6327
6419
|
}
|
|
6328
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
6329
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
|
|
6330
6420
|
}
|
|
6331
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
|
|
6332
|
-
type: Injectable
|
|
6333
|
-
}], ctorParameters: () => [] });
|
|
6334
6421
|
|
|
6335
6422
|
/**
|
|
6336
|
-
*
|
|
6337
|
-
*
|
|
6423
|
+
* Conversation Item Action Registry
|
|
6424
|
+
* Manages actions available in the conversation list item context menu
|
|
6338
6425
|
*/
|
|
6339
6426
|
/**
|
|
6340
|
-
*
|
|
6427
|
+
* Conversation Item Action Registry Service
|
|
6428
|
+
* Manages available actions in the conversation list item context menu
|
|
6341
6429
|
*/
|
|
6342
|
-
class
|
|
6430
|
+
class AXConversationItemActionRegistry extends AXBaseRegistry {
|
|
6343
6431
|
constructor() {
|
|
6344
|
-
|
|
6432
|
+
super();
|
|
6345
6433
|
this.translation = inject(AXTranslationService);
|
|
6346
6434
|
this.injector = inject(Injector);
|
|
6347
|
-
/** All registered actions */
|
|
6348
|
-
this.actions = this._actions.asReadonly();
|
|
6349
|
-
/** Actions sorted by priority */
|
|
6350
|
-
this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
|
|
6351
6435
|
// Register default actions from individual token
|
|
6352
|
-
|
|
6353
|
-
if (defaultActions) {
|
|
6354
|
-
defaultActions.forEach((action) => this.register(action));
|
|
6355
|
-
}
|
|
6436
|
+
inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
|
|
6356
6437
|
// Register actions from REGISTRY_CONFIG
|
|
6357
6438
|
const config = inject(REGISTRY_CONFIG, { optional: true });
|
|
6358
|
-
config?.
|
|
6439
|
+
config?.conversationItemActions?.forEach((action) => this.register(action));
|
|
6359
6440
|
}
|
|
6360
6441
|
/**
|
|
6361
|
-
*
|
|
6442
|
+
* Get registry name for logging
|
|
6362
6443
|
*/
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
if (!action.id) {
|
|
6366
|
-
throw new Error('Message action must have an id');
|
|
6367
|
-
}
|
|
6368
|
-
if (!action.label) {
|
|
6369
|
-
throw new Error('Message action must have a label');
|
|
6370
|
-
}
|
|
6371
|
-
if (!action.handler) {
|
|
6372
|
-
throw new Error('Message action must have a handler');
|
|
6373
|
-
}
|
|
6374
|
-
// Check for duplicates
|
|
6375
|
-
const existing = this._actions().find((a) => a.id === action.id);
|
|
6376
|
-
if (existing) {
|
|
6377
|
-
console.warn(`Message action "${action.id}" is already registered. Replacing...`);
|
|
6378
|
-
this.unregister(action.id);
|
|
6379
|
-
}
|
|
6380
|
-
// Add to registry
|
|
6381
|
-
this._actions.update((actions) => [...actions, action]);
|
|
6444
|
+
getRegistryName() {
|
|
6445
|
+
return 'ConversationItemActionRegistry';
|
|
6382
6446
|
}
|
|
6383
6447
|
/**
|
|
6384
|
-
*
|
|
6448
|
+
* Get actions for a specific conversation
|
|
6385
6449
|
*/
|
|
6386
|
-
|
|
6387
|
-
|
|
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));
|
|
6388
6461
|
}
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
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;
|
|
6395
6470
|
}
|
|
6396
6471
|
/**
|
|
6397
|
-
* Get
|
|
6472
|
+
* Get label for an action
|
|
6398
6473
|
*/
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
|
|
6405
|
-
}
|
|
6406
|
-
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
|
+
}
|
|
6407
6479
|
return this.translation.translateSync(action.label);
|
|
6408
6480
|
}
|
|
6409
6481
|
/**
|
|
6410
|
-
* Get
|
|
6482
|
+
* Get icon for an action
|
|
6411
6483
|
*/
|
|
6412
|
-
|
|
6413
|
-
|
|
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;
|
|
6414
6492
|
}
|
|
6415
6493
|
/**
|
|
6416
|
-
* Get
|
|
6494
|
+
* Get tooltip for an action
|
|
6417
6495
|
*/
|
|
6418
|
-
|
|
6419
|
-
|
|
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);
|
|
6420
6504
|
}
|
|
6421
|
-
/**
|
|
6422
|
-
* Execute an action
|
|
6423
|
-
*/
|
|
6424
6505
|
async executeAction(actionId, context) {
|
|
6425
|
-
const action = this.
|
|
6506
|
+
const action = this.getById(actionId);
|
|
6426
6507
|
if (!action) {
|
|
6427
6508
|
throw new Error(`Action "${actionId}" not found`);
|
|
6428
6509
|
}
|
|
6429
|
-
|
|
6430
|
-
if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
|
|
6510
|
+
if (!this.isActionEnabled(action, context)) {
|
|
6431
6511
|
throw new Error(`Action "${actionId}" is not enabled`);
|
|
6432
6512
|
}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
/**
|
|
6437
|
-
* Clear all actions
|
|
6438
|
-
*/
|
|
6439
|
-
clear() {
|
|
6440
|
-
this._actions.set([]);
|
|
6513
|
+
if (action.handler) {
|
|
6514
|
+
await runInInjectionContext(this.injector, () => action.handler?.(context));
|
|
6515
|
+
}
|
|
6441
6516
|
}
|
|
6442
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
6443
|
-
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type:
|
|
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 }); }
|
|
6444
6519
|
}
|
|
6445
|
-
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: [{
|
|
6446
6521
|
type: Injectable
|
|
6447
6522
|
}], ctorParameters: () => [] });
|
|
6448
6523
|
|
|
6449
6524
|
/**
|
|
6450
|
-
*
|
|
6451
|
-
*
|
|
6525
|
+
* Conversation Tab Registry
|
|
6526
|
+
* Register custom tabs for filtering conversations in the sidebar
|
|
6452
6527
|
*/
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
PARTICIPANTS: 'participants',
|
|
6458
|
-
CONVERSATIONS: 'conversations',
|
|
6459
|
-
MESSAGES: 'messages',
|
|
6460
|
-
MEDIA: 'media',
|
|
6461
|
-
SETTINGS: 'settings',
|
|
6462
|
-
};
|
|
6463
|
-
class AXConversationIndexedDbStorage {
|
|
6528
|
+
/**
|
|
6529
|
+
* Conversation Tab Registry Service
|
|
6530
|
+
*/
|
|
6531
|
+
class AXConversationTabRegistry {
|
|
6464
6532
|
constructor() {
|
|
6465
|
-
this.
|
|
6466
|
-
this.
|
|
6467
|
-
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
request.onerror = () => reject(request.error);
|
|
6479
|
-
request.onsuccess = () => {
|
|
6480
|
-
this.db = request.result;
|
|
6481
|
-
resolve();
|
|
6482
|
-
};
|
|
6483
|
-
request.onupgradeneeded = (event) => {
|
|
6484
|
-
const db = event.target.result;
|
|
6485
|
-
// Create object stores if they don't exist
|
|
6486
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
|
|
6487
|
-
db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
|
|
6488
|
-
}
|
|
6489
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
|
|
6490
|
-
const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
|
|
6491
|
-
convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
|
|
6492
|
-
}
|
|
6493
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
|
|
6494
|
-
const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
|
|
6495
|
-
msgStore.createIndex('conversationId', 'conversationId', { unique: false });
|
|
6496
|
-
msgStore.createIndex('timestamp', 'timestamp', { unique: false });
|
|
6497
|
-
}
|
|
6498
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
|
|
6499
|
-
db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
|
|
6500
|
-
}
|
|
6501
|
-
if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
|
|
6502
|
-
db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
|
|
6503
|
-
}
|
|
6504
|
-
};
|
|
6505
|
-
});
|
|
6506
|
-
return this.initPromise;
|
|
6507
|
-
}
|
|
6508
|
-
/**
|
|
6509
|
-
* Get a value from a store
|
|
6510
|
-
*/
|
|
6511
|
-
async get(storeName, key) {
|
|
6512
|
-
await this.init();
|
|
6513
|
-
if (!this.db)
|
|
6514
|
-
throw new Error('Database not initialized');
|
|
6515
|
-
const db = this.db;
|
|
6516
|
-
return new Promise((resolve, reject) => {
|
|
6517
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6518
|
-
const store = transaction.objectStore(storeName);
|
|
6519
|
-
const request = store.get(key);
|
|
6520
|
-
request.onsuccess = () => resolve(request.result);
|
|
6521
|
-
request.onerror = () => reject(request.error);
|
|
6522
|
-
});
|
|
6523
|
-
}
|
|
6524
|
-
/**
|
|
6525
|
-
* Get all values from a store
|
|
6526
|
-
*/
|
|
6527
|
-
async getAll(storeName) {
|
|
6528
|
-
await this.init();
|
|
6529
|
-
if (!this.db)
|
|
6530
|
-
throw new Error('Database not initialized');
|
|
6531
|
-
const db = this.db;
|
|
6532
|
-
return new Promise((resolve, reject) => {
|
|
6533
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6534
|
-
const store = transaction.objectStore(storeName);
|
|
6535
|
-
const request = store.getAll();
|
|
6536
|
-
request.onsuccess = () => resolve(request.result);
|
|
6537
|
-
request.onerror = () => reject(request.error);
|
|
6538
|
-
});
|
|
6539
|
-
}
|
|
6540
|
-
/**
|
|
6541
|
-
* Get values by index
|
|
6542
|
-
*/
|
|
6543
|
-
async getAllByIndex(storeName, indexName, query) {
|
|
6544
|
-
await this.init();
|
|
6545
|
-
if (!this.db)
|
|
6546
|
-
throw new Error('Database not initialized');
|
|
6547
|
-
const db = this.db;
|
|
6548
|
-
return new Promise((resolve, reject) => {
|
|
6549
|
-
const transaction = db.transaction(storeName, 'readonly');
|
|
6550
|
-
const store = transaction.objectStore(storeName);
|
|
6551
|
-
const index = store.index(indexName);
|
|
6552
|
-
const request = index.getAll(query);
|
|
6553
|
-
request.onsuccess = () => resolve(request.result);
|
|
6554
|
-
request.onerror = () => reject(request.error);
|
|
6555
|
-
});
|
|
6556
|
-
}
|
|
6557
|
-
/**
|
|
6558
|
-
* Put a value into a store
|
|
6559
|
-
*/
|
|
6560
|
-
async put(storeName, value) {
|
|
6561
|
-
await this.init();
|
|
6562
|
-
if (!this.db)
|
|
6563
|
-
throw new Error('Database not initialized');
|
|
6564
|
-
const db = this.db;
|
|
6565
|
-
return new Promise((resolve, reject) => {
|
|
6566
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6567
|
-
const store = transaction.objectStore(storeName);
|
|
6568
|
-
const request = store.put(value);
|
|
6569
|
-
request.onsuccess = () => resolve();
|
|
6570
|
-
request.onerror = () => reject(request.error);
|
|
6571
|
-
});
|
|
6572
|
-
}
|
|
6573
|
-
/**
|
|
6574
|
-
* Delete a value from a store
|
|
6575
|
-
*/
|
|
6576
|
-
async delete(storeName, key) {
|
|
6577
|
-
await this.init();
|
|
6578
|
-
if (!this.db)
|
|
6579
|
-
throw new Error('Database not initialized');
|
|
6580
|
-
const db = this.db;
|
|
6581
|
-
return new Promise((resolve, reject) => {
|
|
6582
|
-
const transaction = db.transaction(storeName, 'readwrite');
|
|
6583
|
-
const store = transaction.objectStore(storeName);
|
|
6584
|
-
const request = store.delete(key);
|
|
6585
|
-
request.onsuccess = () => resolve();
|
|
6586
|
-
request.onerror = () => reject(request.error);
|
|
6587
|
-
});
|
|
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));
|
|
6588
6546
|
}
|
|
6589
6547
|
/**
|
|
6590
|
-
*
|
|
6548
|
+
* Register a conversation tab
|
|
6591
6549
|
*/
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
if (!
|
|
6595
|
-
throw new Error('
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
async getAllParticipants() {
|
|
6612
|
-
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]);
|
|
6613
6569
|
}
|
|
6614
|
-
|
|
6615
|
-
|
|
6570
|
+
/**
|
|
6571
|
+
* Unregister a conversation tab
|
|
6572
|
+
*/
|
|
6573
|
+
unregister(id) {
|
|
6574
|
+
this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
|
|
6616
6575
|
}
|
|
6617
|
-
|
|
6618
|
-
|
|
6576
|
+
/**
|
|
6577
|
+
* Get tab by ID
|
|
6578
|
+
*/
|
|
6579
|
+
getTabById(id) {
|
|
6580
|
+
return this._tabs().find((t) => t.id === id);
|
|
6619
6581
|
}
|
|
6620
|
-
|
|
6621
|
-
return this.
|
|
6582
|
+
getTabLabel(tab) {
|
|
6583
|
+
return this.translation.translateSync(tab.label);
|
|
6622
6584
|
}
|
|
6623
|
-
|
|
6624
|
-
|
|
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);
|
|
6625
6594
|
}
|
|
6626
|
-
|
|
6627
|
-
|
|
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);
|
|
6628
6604
|
}
|
|
6629
|
-
|
|
6630
|
-
|
|
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)));
|
|
6631
6610
|
}
|
|
6632
|
-
|
|
6633
|
-
|
|
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
|
+
})));
|
|
6634
6619
|
}
|
|
6635
|
-
|
|
6636
|
-
|
|
6620
|
+
/**
|
|
6621
|
+
* Clear all tabs
|
|
6622
|
+
*/
|
|
6623
|
+
clear() {
|
|
6624
|
+
this._tabs.set([]);
|
|
6637
6625
|
}
|
|
6638
|
-
|
|
6639
|
-
|
|
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));
|
|
6640
6651
|
}
|
|
6641
|
-
|
|
6642
|
-
|
|
6652
|
+
/**
|
|
6653
|
+
* Get registry name for logging
|
|
6654
|
+
*/
|
|
6655
|
+
getRegistryName() {
|
|
6656
|
+
return 'InfoBarActionRegistry';
|
|
6643
6657
|
}
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
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));
|
|
6647
6676
|
}
|
|
6648
|
-
|
|
6649
|
-
|
|
6677
|
+
/**
|
|
6678
|
+
* Get inline actions (toolbar buttons)
|
|
6679
|
+
*/
|
|
6680
|
+
getInlineActions(conversation) {
|
|
6681
|
+
return this.getActionsForConversation(conversation, 'inline');
|
|
6650
6682
|
}
|
|
6651
|
-
|
|
6652
|
-
|
|
6683
|
+
/**
|
|
6684
|
+
* Get menu actions (dropdown items)
|
|
6685
|
+
*/
|
|
6686
|
+
getMenuActions(conversation) {
|
|
6687
|
+
return this.getActionsForConversation(conversation, 'menu');
|
|
6653
6688
|
}
|
|
6654
|
-
|
|
6655
|
-
|
|
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;
|
|
6656
6697
|
}
|
|
6657
|
-
|
|
6658
|
-
|
|
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);
|
|
6659
6707
|
}
|
|
6660
|
-
}
|
|
6661
|
-
// Export singleton instance
|
|
6662
|
-
const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
|
|
6663
|
-
|
|
6664
|
-
var indexeddbStorage = /*#__PURE__*/Object.freeze({
|
|
6665
|
-
__proto__: null,
|
|
6666
|
-
AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
|
|
6667
|
-
AXConversationIndexedDbStores: AXConversationIndexedDbStores,
|
|
6668
|
-
axConversationIndexedDbStorage: axConversationIndexedDbStorage
|
|
6669
|
-
});
|
|
6670
|
-
|
|
6671
|
-
class AXConversationMessageUtilsService {
|
|
6672
6708
|
/**
|
|
6673
|
-
*
|
|
6674
|
-
* are treated as missing data.
|
|
6709
|
+
* Get icon for an action
|
|
6675
6710
|
*/
|
|
6676
|
-
|
|
6677
|
-
if (
|
|
6711
|
+
getActionIcon(action, conversation) {
|
|
6712
|
+
if (!action.icon)
|
|
6678
6713
|
return undefined;
|
|
6714
|
+
if (typeof action.icon === 'function') {
|
|
6715
|
+
const iconFactory = action.icon;
|
|
6716
|
+
return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
|
|
6679
6717
|
}
|
|
6680
|
-
|
|
6681
|
-
return normalized.length > 0 ? normalized : undefined;
|
|
6718
|
+
return action.icon;
|
|
6682
6719
|
}
|
|
6683
6720
|
/**
|
|
6684
|
-
* Get
|
|
6721
|
+
* Get tooltip for an action
|
|
6685
6722
|
*/
|
|
6686
|
-
|
|
6687
|
-
|
|
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));
|
|
6688
6763
|
}
|
|
6689
6764
|
/**
|
|
6690
|
-
*
|
|
6765
|
+
* Register a message action
|
|
6691
6766
|
*/
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6767
|
+
register(action) {
|
|
6768
|
+
// Validate action
|
|
6769
|
+
if (!action.id) {
|
|
6770
|
+
throw new Error('Message action must have an id');
|
|
6695
6771
|
}
|
|
6696
|
-
|
|
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]);
|
|
6697
6786
|
}
|
|
6698
6787
|
/**
|
|
6699
|
-
*
|
|
6788
|
+
* Unregister a message action
|
|
6700
6789
|
*/
|
|
6701
|
-
|
|
6702
|
-
|
|
6703
|
-
return participant?.name || translateSync('@acorex:chat.fallbacks.unknown-user');
|
|
6790
|
+
unregister(id) {
|
|
6791
|
+
this._actions.update((actions) => actions.filter((a) => a.id !== id));
|
|
6704
6792
|
}
|
|
6705
6793
|
/**
|
|
6706
|
-
* Get
|
|
6794
|
+
* Get all actions for a specific message
|
|
6707
6795
|
*/
|
|
6708
|
-
|
|
6709
|
-
|
|
6710
|
-
|
|
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)));
|
|
6711
6799
|
}
|
|
6712
6800
|
/**
|
|
6713
|
-
*
|
|
6714
|
-
* participant `icon` first, then conversation-level `icon`.
|
|
6801
|
+
* Get menu actions for a message
|
|
6715
6802
|
*/
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
return (
|
|
6722
|
-
|
|
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);
|
|
6723
6812
|
}
|
|
6724
6813
|
/**
|
|
6725
|
-
* Get
|
|
6814
|
+
* Get inline actions for a message
|
|
6726
6815
|
*/
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
return '?';
|
|
6730
|
-
return name
|
|
6731
|
-
.split(' ')
|
|
6732
|
-
.map((n) => n[0])
|
|
6733
|
-
.join('')
|
|
6734
|
-
.toUpperCase()
|
|
6735
|
-
.substring(0, 2);
|
|
6816
|
+
getInlineActions(message, currentUser) {
|
|
6817
|
+
return this.getActions(message, currentUser).filter((action) => action.showInline === true);
|
|
6736
6818
|
}
|
|
6737
6819
|
/**
|
|
6738
|
-
*
|
|
6820
|
+
* Get action by ID
|
|
6739
6821
|
*/
|
|
6740
|
-
|
|
6741
|
-
return
|
|
6822
|
+
getActionById(id) {
|
|
6823
|
+
return this._actions().find((a) => a.id === id);
|
|
6742
6824
|
}
|
|
6743
6825
|
/**
|
|
6744
|
-
*
|
|
6826
|
+
* Execute an action
|
|
6745
6827
|
*/
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
|
|
6828
|
+
async executeAction(actionId, context) {
|
|
6829
|
+
const action = this.getActionById(actionId);
|
|
6830
|
+
if (!action) {
|
|
6831
|
+
throw new Error(`Action "${actionId}" not found`);
|
|
6749
6832
|
}
|
|
6750
|
-
|
|
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));
|
|
6751
6839
|
}
|
|
6752
6840
|
/**
|
|
6753
|
-
*
|
|
6841
|
+
* Clear all actions
|
|
6754
6842
|
*/
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
const first = videoPayload.videos[0];
|
|
6784
|
-
const label = videoPayload.caption?.trim() ||
|
|
6785
|
-
(videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
|
|
6786
|
-
return {
|
|
6787
|
-
value: label || first?.url || '',
|
|
6788
|
-
type: 'video',
|
|
6789
|
-
icon: 'fa-light fa-video',
|
|
6790
|
-
};
|
|
6791
|
-
}
|
|
6792
|
-
case 'audio': {
|
|
6793
|
-
const audioPayload = normalizeAudioPayload(message.payload);
|
|
6794
|
-
const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
|
|
6795
|
-
const first = audioPayload.audios[0];
|
|
6796
|
-
const label = audioPayload.caption?.trim() ||
|
|
6797
|
-
(audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
|
|
6798
|
-
return {
|
|
6799
|
-
value: label || first?.url || '',
|
|
6800
|
-
type: 'audio',
|
|
6801
|
-
icon: 'fa-light fa-music',
|
|
6802
|
-
};
|
|
6803
|
-
}
|
|
6804
|
-
case 'voice': {
|
|
6805
|
-
const voicePayload = message.payload;
|
|
6806
|
-
return {
|
|
6807
|
-
value: voicePayload.url || '',
|
|
6808
|
-
type: 'voice',
|
|
6809
|
-
icon: 'fa-light fa-microphone',
|
|
6810
|
-
};
|
|
6811
|
-
}
|
|
6812
|
-
case 'file': {
|
|
6813
|
-
const filePayload = normalizeFilePayload(message.payload);
|
|
6814
|
-
const names = filePayload.files.map((f) => f.name).join(', ');
|
|
6815
|
-
const label = filePayload.caption?.trim() ||
|
|
6816
|
-
(filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
|
|
6817
|
-
return {
|
|
6818
|
-
value: label || names,
|
|
6819
|
-
type: 'file',
|
|
6820
|
-
icon: 'fa-light fa-file',
|
|
6821
|
-
};
|
|
6822
|
-
}
|
|
6823
|
-
case 'location': {
|
|
6824
|
-
const locationPayload = message.payload;
|
|
6825
|
-
return {
|
|
6826
|
-
value: locationPayload.latitude && locationPayload.longitude
|
|
6827
|
-
? `${locationPayload.latitude},${locationPayload.longitude}`
|
|
6828
|
-
: '',
|
|
6829
|
-
type: 'location',
|
|
6830
|
-
icon: 'fa-light fa-location-dot',
|
|
6831
|
-
};
|
|
6832
|
-
}
|
|
6833
|
-
case 'sticker': {
|
|
6834
|
-
const stickerPayload = message.payload;
|
|
6835
|
-
return {
|
|
6836
|
-
value: stickerPayload.url || '',
|
|
6837
|
-
type: 'sticker',
|
|
6838
|
-
icon: 'fa-light fa-face-smile',
|
|
6839
|
-
};
|
|
6840
|
-
}
|
|
6841
|
-
default:
|
|
6842
|
-
return {
|
|
6843
|
-
value: '',
|
|
6844
|
-
type: message.type,
|
|
6845
|
-
icon: 'fa-light fa-message',
|
|
6846
|
-
};
|
|
6847
|
-
}
|
|
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;
|
|
6848
6871
|
}
|
|
6849
6872
|
/**
|
|
6850
|
-
*
|
|
6873
|
+
* Initialize IndexedDB
|
|
6851
6874
|
*/
|
|
6852
|
-
|
|
6853
|
-
|
|
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;
|
|
6854
6911
|
}
|
|
6855
6912
|
/**
|
|
6856
|
-
* Get
|
|
6913
|
+
* Get a value from a store
|
|
6857
6914
|
*/
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
default:
|
|
6871
|
-
return 'fa-light fa-check';
|
|
6872
|
-
}
|
|
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
|
+
});
|
|
6873
6927
|
}
|
|
6874
6928
|
/**
|
|
6875
|
-
*
|
|
6929
|
+
* Get all values from a store
|
|
6876
6930
|
*/
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
if (
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
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
|
+
});
|
|
6888
6943
|
}
|
|
6889
6944
|
/**
|
|
6890
|
-
*
|
|
6945
|
+
* Get values by index
|
|
6891
6946
|
*/
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
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
|
+
});
|
|
6901
6960
|
}
|
|
6902
6961
|
/**
|
|
6903
|
-
*
|
|
6962
|
+
* Put a value into a store
|
|
6904
6963
|
*/
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
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
|
+
});
|
|
6910
6976
|
}
|
|
6911
6977
|
/**
|
|
6912
|
-
*
|
|
6978
|
+
* Delete a value from a store
|
|
6913
6979
|
*/
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
if (
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
}
|
|
6927
|
-
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
|
+
});
|
|
6928
6992
|
}
|
|
6929
6993
|
/**
|
|
6930
|
-
*
|
|
6994
|
+
* Clear all data from a store
|
|
6931
6995
|
*/
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
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
|
+
});
|
|
6945
7008
|
}
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
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);
|
|
6973
7063
|
}
|
|
6974
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
|
+
});
|
|
6975
7074
|
|
|
6976
7075
|
/**
|
|
6977
7076
|
* Shared read/write helpers for IndexedDB chat storage (in-memory + persistence).
|
|
@@ -8417,20 +8516,27 @@ class AXConversationIndexedDbConversationApi extends AXConversationApi {
|
|
|
8417
8516
|
// Conversation CRUD
|
|
8418
8517
|
// =====================
|
|
8419
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
|
+
}
|
|
8420
8524
|
const conversation = {
|
|
8421
8525
|
id: `conv-${Date.now()}`,
|
|
8422
8526
|
type: data.type,
|
|
8423
|
-
|
|
8527
|
+
creatorId,
|
|
8528
|
+
title: data.title ||
|
|
8529
|
+
(data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
|
|
8424
8530
|
avatar: data.avatar,
|
|
8425
8531
|
description: data.description,
|
|
8426
|
-
participants:
|
|
8532
|
+
participants: [...participantIds].map((id) => {
|
|
8427
8533
|
const participant = conversationSharedStorage.participants.get(id);
|
|
8428
8534
|
if (!participant) {
|
|
8429
8535
|
throw this.createError('PARTICIPANT_NOT_FOUND', `Participant ${id} not found`, 404);
|
|
8430
8536
|
}
|
|
8431
8537
|
return {
|
|
8432
8538
|
...participant,
|
|
8433
|
-
role: id ===
|
|
8539
|
+
role: id === creatorId ? 'admin' : 'member',
|
|
8434
8540
|
};
|
|
8435
8541
|
}),
|
|
8436
8542
|
lastMessageAt: new Date(),
|
|
@@ -10400,41 +10506,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
|
|
|
10400
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"] }]
|
|
10401
10507
|
}] });
|
|
10402
10508
|
|
|
10403
|
-
/** Other participant in a private chat (excludes the current user). */
|
|
10404
|
-
function resolvePrivatePeerUserId(conversation, currentUserId) {
|
|
10405
|
-
if (conversation.type !== 'private') {
|
|
10406
|
-
return undefined;
|
|
10407
|
-
}
|
|
10408
|
-
const currentId = currentUserId ?? 'current-user';
|
|
10409
|
-
return conversation.participants.find((participant) => participant.id !== currentId)?.id;
|
|
10410
|
-
}
|
|
10411
|
-
/** Whether `auto` kind should render a user avatar for this conversation. */
|
|
10412
|
-
function shouldUseUserAvatarForConversation(conversation, currentUserId) {
|
|
10413
|
-
return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
|
|
10414
|
-
}
|
|
10415
|
-
function resolveUserAvatarDisplay(userId, conversation, message) {
|
|
10416
|
-
if (message && conversation) {
|
|
10417
|
-
return {
|
|
10418
|
-
name: AXConversationMessageUtilsService.getSenderName(message, conversation),
|
|
10419
|
-
avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
|
|
10420
|
-
icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
|
|
10421
|
-
};
|
|
10422
|
-
}
|
|
10423
|
-
const participant = conversation?.participants.find((p) => p.id === userId);
|
|
10424
|
-
return {
|
|
10425
|
-
name: participant?.name ?? userId,
|
|
10426
|
-
avatar: participant?.avatar,
|
|
10427
|
-
icon: participant?.icon,
|
|
10428
|
-
};
|
|
10429
|
-
}
|
|
10430
|
-
function resolveConversationAvatarDisplay(conversation) {
|
|
10431
|
-
return {
|
|
10432
|
-
name: conversation.title,
|
|
10433
|
-
avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
|
|
10434
|
-
icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
|
|
10435
|
-
};
|
|
10436
|
-
}
|
|
10437
|
-
|
|
10438
10509
|
/**
|
|
10439
10510
|
* Conversation avatar host — renders a registered custom component or built-in fallback.
|
|
10440
10511
|
*/
|
|
@@ -10519,7 +10590,7 @@ class AXConversationAvatarComponent {
|
|
|
10519
10590
|
if (!conversation) {
|
|
10520
10591
|
return { name: explicitName ?? '?', avatar: explicitAvatar, icon: explicitIcon };
|
|
10521
10592
|
}
|
|
10522
|
-
const resolved = resolveConversationAvatarDisplay(conversation);
|
|
10593
|
+
const resolved = resolveConversationAvatarDisplay(conversation, this.currentUserId());
|
|
10523
10594
|
return {
|
|
10524
10595
|
name: explicitName ?? resolved.name,
|
|
10525
10596
|
avatar: explicitAvatar ?? resolved.avatar,
|
|
@@ -14923,14 +14994,23 @@ class AXConversationService {
|
|
|
14923
14994
|
this._messageDeleted$ = new Subject();
|
|
14924
14995
|
this._typingIndicator$ = new Subject();
|
|
14925
14996
|
this._presenceUpdate$ = new Subject();
|
|
14926
|
-
/** All conversations */
|
|
14927
|
-
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" }] : []));
|
|
14928
15002
|
/** Active conversation ID */
|
|
14929
15003
|
this.activeConversationId = this._activeConversationId.asReadonly();
|
|
14930
15004
|
/** Active conversation */
|
|
14931
15005
|
this.activeConversation = computed(() => {
|
|
14932
15006
|
const id = this._activeConversationId();
|
|
14933
|
-
|
|
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;
|
|
14934
15014
|
}, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
|
|
14935
15015
|
/** Messages for active conversation */
|
|
14936
15016
|
this.activeMessages = computed(() => {
|
|
@@ -15721,17 +15801,29 @@ class AXConversationService {
|
|
|
15721
15801
|
*/
|
|
15722
15802
|
async createConversation(participantIds, type, metadata) {
|
|
15723
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
|
+
}
|
|
15724
15811
|
const conversation = await this.conversationApi.createConversation({
|
|
15725
15812
|
type,
|
|
15726
|
-
participantIds,
|
|
15727
|
-
|
|
15813
|
+
participantIds: scopedParticipantIds,
|
|
15814
|
+
creatorId,
|
|
15815
|
+
title: isPrivate ? undefined : metadata?.['title'],
|
|
15728
15816
|
description: metadata?.['description'],
|
|
15729
|
-
avatar:
|
|
15730
|
-
|
|
15731
|
-
|
|
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,
|
|
15732
15824
|
});
|
|
15733
15825
|
this.state.setConversation(conversation);
|
|
15734
|
-
return conversation;
|
|
15826
|
+
return resolveConversationForViewer(conversation, creatorId);
|
|
15735
15827
|
}
|
|
15736
15828
|
catch (error) {
|
|
15737
15829
|
this.errorHandler.handle(error, 'createConversation', { participantIds, type });
|
|
@@ -20960,12 +21052,7 @@ class AXNewConversationDialogComponent extends AXBasePageComponent {
|
|
|
20960
21052
|
try {
|
|
20961
21053
|
let conversation;
|
|
20962
21054
|
if (selectedIds.length === 1) {
|
|
20963
|
-
|
|
20964
|
-
const metadata = {
|
|
20965
|
-
title: selectedUser?.name,
|
|
20966
|
-
avatar: selectedUser?.avatar,
|
|
20967
|
-
};
|
|
20968
|
-
conversation = await this.conversationService.createConversation(selectedIds, 'private', metadata);
|
|
21055
|
+
conversation = await this.conversationService.createConversation(selectedIds, 'private');
|
|
20969
21056
|
}
|
|
20970
21057
|
else {
|
|
20971
21058
|
const metadata = {
|
|
@@ -22179,5 +22266,5 @@ function getErrorMessage(code, params) {
|
|
|
22179
22266
|
* Generated bundle index. Do not edit.
|
|
22180
22267
|
*/
|
|
22181
22268
|
|
|
22182
|
-
export { AXAudioPickerComponent, AXAudioRendererComponent, AXBaseRegistry, AXCnvMediaPlaybackInfoBarBannerComponent, AXComposerActionRegistry, AXComposerComponent, AXComposerPopupComponent, AXComposerService, AXComposerTabRegistry, AXConversation2Module, AXConversationAiApiKey, AXConversationAiResponderService, AXConversationApi, AXConversationContainerComponent, AXConversationContainerDirective, AXConversationDateUtilsService, AXConversationIndexedDbConversationApi, AXConversationIndexedDbMessageAiApi, AXConversationIndexedDbMessageApi, AXConversationIndexedDbRealtimeApi, AXConversationIndexedDbStorage, AXConversationIndexedDbStores, AXConversationIndexedDbUserApi, AXConversationInfoMediaViewComponent, AXConversationInfoPanelComponent, AXConversationItemActionRegistry, AXConversationMessageRendererStateComponent, AXConversationMessageUtilsService, AXConversationService, AXConversationSharedStorage, AXConversationTabRegistry, AXEmojiTabComponent, AXErrorHandlerService, AXFallbackRendererComponent, AXFilePickerComponent, AXFileRendererComponent, AXForwardMessageDialogComponent, AXImagePickerComponent, AXImageRendererComponent, AXInfiniteScrollDirective, AXInfoBarActionRegistry, AXInfoBarComponent, AXInfoBarSearchComponent, AXInfoBarService, AXLocationPickerComponent, AXLocationRendererComponent, AXMessageActionRegistry, AXMessageApi, AXMessageListComponent, AXMessageListNoActiveDefaultComponent, AXMessageListService, AXMessageRendererRegistry, AXNewConversationDialogComponent, AXPickerFooterComponent, AXPickerHeaderComponent, AXRealtimeApi, AXRegistryService, AXSidebarComponent, AXSidebarService, AXStickerRendererComponent, AXStickerTabComponent, AXSystemRendererComponent, AXTextRendererComponent, AXUserApi, AXVideoPickerComponent, AXVideoRendererComponent, AXVoiceRecorderComponent, AXVoiceRendererComponent, AX_CONVERSATION_AUDIO_RENDERER, AX_CONVERSATION_COMPOSER_AUDIO_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_TAB, AX_CONVERSATION_COMPOSER_FILE_ACTION, AX_CONVERSATION_COMPOSER_FILE_TYPE_PROVIDERS, AX_CONVERSATION_COMPOSER_IMAGE_ACTION, AX_CONVERSATION_COMPOSER_LOCATION_ACTION, AX_CONVERSATION_COMPOSER_STICKER_TAB, AX_CONVERSATION_COMPOSER_VIDEO_ACTION, AX_CONVERSATION_COMPOSER_VOICE_RECORDING_ACTION, AX_CONVERSATION_CONVERSATION_AVATAR_COMPONENT, AX_CONVERSATION_CURSOR_PREFIX, AX_CONVERSATION_FALLBACK_RENDERER, AX_CONVERSATION_FILE_RENDERER, AX_CONVERSATION_IMAGE_RENDERER, AX_CONVERSATION_INFO_BAR_ARCHIVE_ACTION, AX_CONVERSATION_INFO_BAR_BLOCK_ACTION, AX_CONVERSATION_INFO_BAR_DELETE_ACTION, AX_CONVERSATION_INFO_BAR_DIVIDER, AX_CONVERSATION_INFO_BAR_MUTE_ACTION, AX_CONVERSATION_INFO_BAR_SEARCH_ACTION, AX_CONVERSATION_ITEM_BLOCK_ACTION, AX_CONVERSATION_ITEM_DELETE_ACTION, AX_CONVERSATION_ITEM_DIVIDER, AX_CONVERSATION_ITEM_MARK_READ_ACTION, AX_CONVERSATION_ITEM_MUTE_ACTION, AX_CONVERSATION_ITEM_PIN_ACTION, AX_CONVERSATION_LOCATION_RENDERER, AX_CONVERSATION_MESSAGE_DELETE_ACTION, AX_CONVERSATION_MESSAGE_EDIT_ACTION, AX_CONVERSATION_MESSAGE_FORWARD_ACTION, AX_CONVERSATION_MESSAGE_REPLY_ACTION, AX_CONVERSATION_STICKER_RENDERER, AX_CONVERSATION_SYSTEM_RENDERER, AX_CONVERSATION_TAB_ALL, AX_CONVERSATION_TAB_ARCHIVED, AX_CONVERSATION_TAB_BOT, AX_CONVERSATION_TAB_CHANNELS, AX_CONVERSATION_TAB_GROUPS, AX_CONVERSATION_TAB_PRIVATE, AX_CONVERSATION_TAB_UNREAD, AX_CONVERSATION_TEXT_RENDERER, AX_CONVERSATION_USER_AVATAR_COMPONENT, AX_CONVERSATION_VIDEO_RENDERER, AX_CONVERSATION_VOICE_RENDERER, AX_DEFAULT_CONVERSATION_CONFIG, AX_MESSAGE_CURSOR_PREFIX, AX_STICKER_API_KEY, CONNECTION_ERRORS, CONVERSATION_CONFIG, CONVERSATION_ERRORS, CONVERSATION_FILE_TYPES_READY, DEFAULT_COMPOSER_ACTIONS, DEFAULT_COMPOSER_TABS, DEFAULT_CONVERSATION_ITEM_ACTIONS, DEFAULT_CONVERSATION_TABS, DEFAULT_INFO_BAR_ACTIONS, DEFAULT_MESSAGE_ACTIONS, DEFAULT_MESSAGE_RENDERERS, ERROR_HANDLER_CONFIG, ERROR_MESSAGES, FILE_ERRORS, LOCATION_ERRORS, MESSAGE_ERRORS, REGISTRY_CONFIG, URL_ERRORS, USER_ERRORS, applyConversationFilters, applyLocalPreview, axConversationIndexedDbStorage, conversationSharedStorage, createLocalPreviewUrl, createObjectUrl, encodeConversationCursor, encodeMessageCursor, formatErrorMessage, getConversationLastActivity, getConversationMessagesNewestFirst, getErrorMessage, getSortedConversationsForInbox, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveParticipantProfile, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
|
|
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 };
|
|
22183
22270
|
//# sourceMappingURL=acorex-components-conversation2.mjs.map
|