@acorex/components 20.8.17 → 20.8.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,10 +7,10 @@ import { InjectionToken, signal, computed, inject, Injectable, input, output, Ch
7
7
  import { AXDialogService } from '@acorex/components/dialog';
8
8
  import { AXPopupService } from '@acorex/components/popup';
9
9
  import * as i3 from '@acorex/core/translation';
10
- import { AXTranslationService, AXTranslationModule, translateSync } from '@acorex/core/translation';
10
+ import { translateSync, AXTranslationService, AXTranslationModule } from '@acorex/core/translation';
11
11
  import { Subject, BehaviorSubject, Observable, filter, firstValueFrom, takeUntil, catchError, EMPTY } from 'rxjs';
12
12
  import { AXUploaderBrowseDirective, AXUploaderZoneDirective, AXUploaderService } from '@acorex/cdk/uploader';
13
- import { AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } from '@acorex/core/file';
13
+ import { createFileTypeMetadata, AXFileTypeInfoProvider, formatFileSizeBytes, AXFileService, AXFileTypeRegistryService, provideFileValidationRules, provideFileTypeInfoProvider, resolveFileCopyText } from '@acorex/core/file';
14
14
  import * as i1$2 from '@acorex/components/progress-bar';
15
15
  import { AXProgressBarModule } from '@acorex/components/progress-bar';
16
16
  import { AXToastService } from '@acorex/components/toast';
@@ -1039,6 +1039,410 @@ function validateLongitude(longitude) {
1039
1039
  return { valid: true };
1040
1040
  }
1041
1041
 
1042
+ class AXConversationMessageUtilsService {
1043
+ /**
1044
+ * Normalize optional avatar/icon values so empty or whitespace-only strings
1045
+ * are treated as missing data.
1046
+ */
1047
+ static normalizeOptionalMediaValue(value) {
1048
+ if (typeof value !== 'string') {
1049
+ return undefined;
1050
+ }
1051
+ const normalized = value.trim();
1052
+ return normalized.length > 0 ? normalized : undefined;
1053
+ }
1054
+ /**
1055
+ * Get conversation avatar image URL.
1056
+ */
1057
+ static getConversationAvatar(conversation) {
1058
+ return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.avatar);
1059
+ }
1060
+ /**
1061
+ * Font Awesome icon class(es) for a conversation when there is no avatar image.
1062
+ */
1063
+ static getConversationAvatarIcon(conversation) {
1064
+ if (AXConversationMessageUtilsService.getConversationAvatar(conversation)) {
1065
+ return undefined;
1066
+ }
1067
+ return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.icon);
1068
+ }
1069
+ /**
1070
+ * Get sender name from message
1071
+ */
1072
+ static getSenderName(message, conversation) {
1073
+ const participant = conversation.participants.find((p) => p.id === message.senderId);
1074
+ return participant?.name || translateSync('@acorex:chat.fallbacks.unknown-user');
1075
+ }
1076
+ /**
1077
+ * Get sender avatar image URL (takes precedence over {@link getSenderAvatarIcon}).
1078
+ */
1079
+ static getSenderAvatar(message, conversation) {
1080
+ const participant = conversation.participants.find((p) => p.id === message.senderId);
1081
+ return AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar);
1082
+ }
1083
+ /**
1084
+ * Font Awesome icon class(es) for the sender when there is no avatar image:
1085
+ * participant `icon` first, then conversation-level `icon`.
1086
+ */
1087
+ static getSenderAvatarIcon(message, conversation) {
1088
+ const participant = conversation.participants.find((p) => p.id === message.senderId);
1089
+ if (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar)) {
1090
+ return undefined;
1091
+ }
1092
+ return (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.icon) ??
1093
+ AXConversationMessageUtilsService.getConversationAvatarIcon(conversation));
1094
+ }
1095
+ /**
1096
+ * Get initials from name
1097
+ */
1098
+ static getInitials(name) {
1099
+ if (!name)
1100
+ return '?';
1101
+ return name
1102
+ .split(' ')
1103
+ .map((n) => n[0])
1104
+ .join('')
1105
+ .toUpperCase()
1106
+ .substring(0, 2);
1107
+ }
1108
+ /**
1109
+ * Type guard for text payload
1110
+ */
1111
+ static isTextPayload(payload) {
1112
+ return 'text' in payload && typeof payload.text === 'string';
1113
+ }
1114
+ /**
1115
+ * Get message text content
1116
+ */
1117
+ static getMessageText(message) {
1118
+ if (message.type === 'text' && AXConversationMessageUtilsService.isTextPayload(message.payload)) {
1119
+ return message.payload.text;
1120
+ }
1121
+ return `[${message.type}]`;
1122
+ }
1123
+ /**
1124
+ * Format message preview text
1125
+ */
1126
+ static getPreviewText(message, maxLength = 50) {
1127
+ // Handle different message types
1128
+ switch (message.type) {
1129
+ case 'text': {
1130
+ const textPayload = message.payload;
1131
+ const text = textPayload.text || '';
1132
+ const truncatedText = text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
1133
+ return {
1134
+ value: truncatedText,
1135
+ type: 'text',
1136
+ icon: 'fa-light fa-message',
1137
+ };
1138
+ }
1139
+ case 'image': {
1140
+ const imagePayload = normalizeImagePayload(message.payload);
1141
+ const first = imagePayload.images[0];
1142
+ const label = imagePayload.caption?.trim() ||
1143
+ (imagePayload.images && imagePayload.images.length > 1
1144
+ ? `${imagePayload.images.length} images`
1145
+ : '');
1146
+ return {
1147
+ value: label || first?.thumbnailUrl || first?.url || '',
1148
+ type: 'image',
1149
+ icon: 'fa-light fa-image',
1150
+ };
1151
+ }
1152
+ case 'video': {
1153
+ const videoPayload = normalizeVideoPayload(message.payload);
1154
+ const first = videoPayload.videos[0];
1155
+ const label = videoPayload.caption?.trim() ||
1156
+ (videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
1157
+ return {
1158
+ value: label || first?.url || '',
1159
+ type: 'video',
1160
+ icon: 'fa-light fa-video',
1161
+ };
1162
+ }
1163
+ case 'audio': {
1164
+ const audioPayload = normalizeAudioPayload(message.payload);
1165
+ const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
1166
+ const first = audioPayload.audios[0];
1167
+ const label = audioPayload.caption?.trim() ||
1168
+ (audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
1169
+ return {
1170
+ value: label || first?.url || '',
1171
+ type: 'audio',
1172
+ icon: 'fa-light fa-music',
1173
+ };
1174
+ }
1175
+ case 'voice': {
1176
+ const voicePayload = message.payload;
1177
+ return {
1178
+ value: voicePayload.url || '',
1179
+ type: 'voice',
1180
+ icon: 'fa-light fa-microphone',
1181
+ };
1182
+ }
1183
+ case 'file': {
1184
+ const filePayload = normalizeFilePayload(message.payload);
1185
+ const names = filePayload.files.map((f) => f.name).join(', ');
1186
+ const label = filePayload.caption?.trim() ||
1187
+ (filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
1188
+ return {
1189
+ value: label || names,
1190
+ type: 'file',
1191
+ icon: 'fa-light fa-file',
1192
+ };
1193
+ }
1194
+ case 'location': {
1195
+ const locationPayload = message.payload;
1196
+ return {
1197
+ value: locationPayload.latitude && locationPayload.longitude
1198
+ ? `${locationPayload.latitude},${locationPayload.longitude}`
1199
+ : '',
1200
+ type: 'location',
1201
+ icon: 'fa-light fa-location-dot',
1202
+ };
1203
+ }
1204
+ case 'sticker': {
1205
+ const stickerPayload = message.payload;
1206
+ return {
1207
+ value: stickerPayload.url || '',
1208
+ type: 'sticker',
1209
+ icon: 'fa-light fa-face-smile',
1210
+ };
1211
+ }
1212
+ default:
1213
+ return {
1214
+ value: '',
1215
+ type: message.type,
1216
+ icon: 'fa-light fa-message',
1217
+ };
1218
+ }
1219
+ }
1220
+ /**
1221
+ * Check if message is from current user
1222
+ */
1223
+ static isOwnMessage(message, currentUserId) {
1224
+ return message.senderId === currentUserId;
1225
+ }
1226
+ /**
1227
+ * Get message status icon class (Font Awesome)
1228
+ */
1229
+ static getStatusIcon(message) {
1230
+ switch (message.status) {
1231
+ case 'sending':
1232
+ return 'fa-light fa-clock';
1233
+ case 'sent':
1234
+ return 'fa-light fa-check';
1235
+ case 'delivered':
1236
+ return 'fa-light fa-check-double';
1237
+ case 'read':
1238
+ return 'fa-light fa-check-double';
1239
+ case 'failed':
1240
+ return 'fa-light fa-circle-exclamation';
1241
+ default:
1242
+ return 'fa-light fa-check';
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Check if message should show avatar
1247
+ */
1248
+ static shouldShowAvatar(message, previousMessage, conversation) {
1249
+ // Always show avatar for group conversations
1250
+ if (conversation.type === 'group' || conversation.type === 'channel') {
1251
+ // Don't show if same sender as previous message within 5 minutes
1252
+ if (previousMessage && previousMessage.senderId === message.senderId) {
1253
+ const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
1254
+ return timeDiff > 5 * 60 * 1000; // 5 minutes
1255
+ }
1256
+ return true;
1257
+ }
1258
+ return false;
1259
+ }
1260
+ /**
1261
+ * Group messages by sender for consecutive messages
1262
+ */
1263
+ static shouldGroupWithPrevious(message, previousMessage) {
1264
+ if (!previousMessage)
1265
+ return false;
1266
+ // Same sender
1267
+ if (message.senderId !== previousMessage.senderId)
1268
+ return false;
1269
+ // Within 5 minutes
1270
+ const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
1271
+ return timeDiff < 5 * 60 * 1000;
1272
+ }
1273
+ /**
1274
+ * Get conversation status for avatar (private conversations only)
1275
+ */
1276
+ static getConversationStatus(conversation) {
1277
+ if (conversation.type === 'private') {
1278
+ return conversation.status.presence;
1279
+ }
1280
+ return undefined;
1281
+ }
1282
+ /**
1283
+ * Get typing indicator text for conversation
1284
+ */
1285
+ static getTypingText(conversation) {
1286
+ const typingUsers = conversation.status.typingUsers;
1287
+ if (typingUsers.length === 0)
1288
+ return '';
1289
+ if (conversation.type === 'private') {
1290
+ return translateSync('@acorex:chat.status.typing');
1291
+ }
1292
+ const firstUser = conversation.participants.find((p) => p.id === typingUsers[0]);
1293
+ if (typingUsers.length === 1) {
1294
+ return translateSync('@acorex:chat.status.user-is-typing', {
1295
+ params: { userName: firstUser?.name || translateSync('@acorex:chat.fallbacks.someone') },
1296
+ });
1297
+ }
1298
+ return translateSync('@acorex:chat.status.people-typing', { params: { count: typingUsers.length } });
1299
+ }
1300
+ /**
1301
+ * Format last seen time
1302
+ */
1303
+ static formatLastSeen(date) {
1304
+ const now = new Date();
1305
+ const diff = now.getTime() - date.getTime();
1306
+ const seconds = Math.floor(diff / 1000);
1307
+ if (seconds < 60)
1308
+ return translateSync('@acorex:chat.time.just-now');
1309
+ if (seconds < 3600)
1310
+ return translateSync('@acorex:chat.time.minutes-ago', { params: { count: Math.floor(seconds / 60) } });
1311
+ if (seconds < 86400)
1312
+ return translateSync('@acorex:chat.time.hours-ago', { params: { count: Math.floor(seconds / 3600) } });
1313
+ if (seconds < 604800)
1314
+ return translateSync('@acorex:chat.time.days-ago', { params: { count: Math.floor(seconds / 86400) } });
1315
+ return date.toLocaleDateString();
1316
+ }
1317
+ /**
1318
+ * Get conversation subtitle (status or member count)
1319
+ */
1320
+ static getConversationSubtitle(conversation) {
1321
+ if (conversation.status.isTyping) {
1322
+ return AXConversationMessageUtilsService.getTypingText(conversation);
1323
+ }
1324
+ switch (conversation.type) {
1325
+ case 'private':
1326
+ if (conversation.status.presence === 'online') {
1327
+ return translateSync('@acorex:chat.status.online');
1328
+ }
1329
+ if (conversation.status.lastSeen) {
1330
+ return translateSync('@acorex:chat.status.last-seen', {
1331
+ params: { value: AXConversationMessageUtilsService.formatLastSeen(conversation.status.lastSeen) },
1332
+ });
1333
+ }
1334
+ return translateSync('@acorex:chat.status.offline');
1335
+ case 'group':
1336
+ return translateSync('@acorex:chat.members.count', { params: { count: conversation.participants.length } });
1337
+ case 'channel':
1338
+ return translateSync('@acorex:chat.members.subscribers-count', { params: { count: conversation.participants.length } });
1339
+ case 'bot':
1340
+ return translateSync('@acorex:chat.bot');
1341
+ default:
1342
+ return '';
1343
+ }
1344
+ }
1345
+ }
1346
+
1347
+ /** Other participant in a private chat (excludes the current user). */
1348
+ function resolvePrivatePeerUserId(conversation, currentUserId) {
1349
+ if (conversation.type !== 'private') {
1350
+ return undefined;
1351
+ }
1352
+ const currentId = currentUserId ?? 'current-user';
1353
+ return conversation.participants.find((participant) => participant.id !== currentId)?.id;
1354
+ }
1355
+ /** Whether `auto` kind should render a user avatar for this conversation. */
1356
+ function shouldUseUserAvatarForConversation(conversation, currentUserId) {
1357
+ return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
1358
+ }
1359
+ function resolveUserAvatarDisplay(userId, conversation, message) {
1360
+ if (message && conversation) {
1361
+ return {
1362
+ name: AXConversationMessageUtilsService.getSenderName(message, conversation),
1363
+ avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
1364
+ icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
1365
+ };
1366
+ }
1367
+ const participant = conversation?.participants.find((p) => p.id === userId);
1368
+ return {
1369
+ name: participant?.name ?? userId,
1370
+ avatar: participant?.avatar,
1371
+ icon: participant?.icon,
1372
+ };
1373
+ }
1374
+ function resolveConversationAvatarDisplay(conversation, currentUserId) {
1375
+ const title = currentUserId !== undefined
1376
+ ? resolveConversationTitleForViewer(conversation, currentUserId)
1377
+ : conversation.title;
1378
+ return {
1379
+ name: title,
1380
+ avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
1381
+ icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
1382
+ };
1383
+ }
1384
+
1385
+ const GENERIC_PRIVATE_TITLES = new Set(['', 'new chat', 'new conversation']);
1386
+ /** True when the stored title is a placeholder, not a user-defined name. */
1387
+ function isGenericPrivateConversationTitle(title) {
1388
+ return GENERIC_PRIVATE_TITLES.has((title ?? '').trim().toLowerCase());
1389
+ }
1390
+ /** Other participant in a private 1v1 chat (excludes the current viewer). */
1391
+ function resolvePrivatePeerParticipant(conversation, currentUserId) {
1392
+ if (conversation.type !== 'private') {
1393
+ return undefined;
1394
+ }
1395
+ const peerId = resolvePrivatePeerUserId(conversation, currentUserId);
1396
+ if (!peerId) {
1397
+ return undefined;
1398
+ }
1399
+ return conversation.participants.find((participant) => participant.id === peerId);
1400
+ }
1401
+ /**
1402
+ * Resolves the display title for the current viewer.
1403
+ * Private 1v1 chats show the other participant's name when no custom title is set.
1404
+ */
1405
+ function resolveConversationTitleForViewer(conversation, currentUserId) {
1406
+ if (conversation.type !== 'private') {
1407
+ return conversation.title;
1408
+ }
1409
+ const peer = resolvePrivatePeerParticipant(conversation, currentUserId);
1410
+ if (peer?.name) {
1411
+ return peer.name;
1412
+ }
1413
+ if (isGenericPrivateConversationTitle(conversation.title)) {
1414
+ return conversation.title || 'New Chat';
1415
+ }
1416
+ return conversation.title;
1417
+ }
1418
+ /**
1419
+ * Returns a viewer-scoped copy of a conversation with dynamic private title/avatar/icon.
1420
+ * Does not mutate the source object.
1421
+ */
1422
+ function resolveConversationForViewer(conversation, currentUserId) {
1423
+ if (conversation.type !== 'private' || !currentUserId) {
1424
+ return conversation;
1425
+ }
1426
+ const peer = resolvePrivatePeerParticipant(conversation, currentUserId);
1427
+ if (!peer) {
1428
+ return conversation;
1429
+ }
1430
+ const title = resolveConversationTitleForViewer(conversation, currentUserId);
1431
+ const avatar = conversation.avatar ?? peer.avatar;
1432
+ const icon = conversation.icon ?? peer.icon;
1433
+ if (title === conversation.title &&
1434
+ avatar === conversation.avatar &&
1435
+ icon === conversation.icon) {
1436
+ return conversation;
1437
+ }
1438
+ return {
1439
+ ...conversation,
1440
+ title,
1441
+ avatar,
1442
+ icon,
1443
+ };
1444
+ }
1445
+
1042
1446
  /**
1043
1447
  * In-memory conversation and message graph (signal-based).
1044
1448
  * Plain class — not DI-registered; instantiated by `AXConversationService`.
@@ -1681,6 +2085,7 @@ function createConversationAudioFileType() {
1681
2085
  const presentation = CONVERSATION_AUDIO_PRESENTATION;
1682
2086
  return {
1683
2087
  name: CONVERSATION_AUDIO_CATALOG,
2088
+ metadata: createFileTypeMetadata('conversation'),
1684
2089
  title: presentation.title,
1685
2090
  icon: presentation.icon,
1686
2091
  validations: {
@@ -1809,6 +2214,7 @@ function createConversationImageFileType() {
1809
2214
  const presentation = CONVERSATION_IMAGE_PRESENTATION;
1810
2215
  return {
1811
2216
  name: CONVERSATION_IMAGE_CATALOG,
2217
+ metadata: createFileTypeMetadata('conversation'),
1812
2218
  title: presentation.title,
1813
2219
  icon: presentation.icon,
1814
2220
  validations: {
@@ -1906,6 +2312,7 @@ function createConversationVideoFileType() {
1906
2312
  const presentation = CONVERSATION_VIDEO_PRESENTATION;
1907
2313
  return {
1908
2314
  name: CONVERSATION_VIDEO_CATALOG,
2315
+ metadata: createFileTypeMetadata('conversation'),
1909
2316
  title: presentation.title,
1910
2317
  icon: presentation.icon,
1911
2318
  validations: {
@@ -2003,6 +2410,7 @@ function createConversationFileFileType() {
2003
2410
  const presentation = CONVERSATION_FILE_PRESENTATION;
2004
2411
  return {
2005
2412
  name: CONVERSATION_FILE_CATALOG,
2413
+ metadata: createFileTypeMetadata('conversation'),
2006
2414
  title: presentation.title,
2007
2415
  icon: presentation.icon,
2008
2416
  validations: {
@@ -2215,6 +2623,7 @@ function createConversationVoiceFileType() {
2215
2623
  const presentation = CONVERSATION_VOICE_PRESENTATION;
2216
2624
  return {
2217
2625
  name: CONVERSATION_VOICE_CATALOG,
2626
+ metadata: createFileTypeMetadata('conversation'),
2218
2627
  title: presentation.title,
2219
2628
  icon: presentation.icon,
2220
2629
  validations: {
@@ -5683,443 +6092,162 @@ class AXComposerActionRegistry {
5683
6092
  this.fileTypeRegistry = inject(AXFileTypeRegistryService);
5684
6093
  /** All registered actions */
5685
6094
  this.actions = this._actions.asReadonly();
5686
- /** Left-side actions sorted by priority */
5687
- this.leftActions = computed(() => [...this._actions()]
5688
- .filter((action) => action.position === 'left')
5689
- .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "leftActions" }] : []));
5690
- /** Right-side actions sorted by priority */
5691
- this.rightActions = computed(() => [...this._actions()]
5692
- .filter((action) => action.position === 'right')
5693
- .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "rightActions" }] : []));
5694
- /** Left-side inline actions (location = 'inline' or undefined) */
5695
- this.leftInlineActions = computed(() => this.leftActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "leftInlineActions" }] : []));
5696
- /** Left-side menu actions (location = 'menu') */
5697
- this.leftMenuActions = computed(() => this.leftActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "leftMenuActions" }] : []));
5698
- /** Right-side inline actions (location = 'inline' or undefined) */
5699
- this.rightInlineActions = computed(() => this.rightActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "rightInlineActions" }] : []));
5700
- /** Right-side menu actions (location = 'menu') */
5701
- this.rightMenuActions = computed(() => this.rightActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "rightMenuActions" }] : []));
5702
- inject(CONVERSATION_FILE_TYPES_READY);
5703
- // Register default actions from individual token
5704
- inject(DEFAULT_COMPOSER_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
5705
- // Register actions from REGISTRY_CONFIG
5706
- const config = inject(REGISTRY_CONFIG, { optional: true });
5707
- config?.composerActions?.forEach((action) => this.register(action));
5708
- }
5709
- /**
5710
- * Register a composer action
5711
- */
5712
- register(action) {
5713
- // Validate action
5714
- if (!action.id) {
5715
- throw new Error('Composer action must have an id');
5716
- }
5717
- if (!action.label) {
5718
- throw new Error('Composer action must have a label');
5719
- }
5720
- if (!action.icon) {
5721
- throw new Error('Composer action must have an icon');
5722
- }
5723
- if (!action.handler && !action.component) {
5724
- throw new Error('Composer action must have either a handler or a component');
5725
- }
5726
- // Check for duplicates
5727
- const existing = this._actions().find((a) => a.id === action.id);
5728
- if (existing) {
5729
- console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
5730
- this.unregister(action.id);
5731
- }
5732
- // Add to registry
5733
- this._actions.update((actions) => [...actions, action]);
5734
- if (action.fileType) {
5735
- void this.ensureFileTypeExists(action.fileType);
5736
- }
5737
- }
5738
- async ensureFileTypeExists(fileType) {
5739
- const match = await this.fileTypeRegistry.get(fileType);
5740
- if (!match) {
5741
- console.warn(`[AXComposerActionRegistry] Unknown fileType "${fileType}". ` +
5742
- 'Register its AXFileTypeInfoProvider in app providers before using this action.');
5743
- }
5744
- }
5745
- /**
5746
- * Unregister a composer action
5747
- */
5748
- unregister(id) {
5749
- this._actions.update((actions) => actions.filter((a) => a.id !== id));
5750
- }
5751
- /**
5752
- * Get visible actions for a position
5753
- */
5754
- getVisibleActions(position, context) {
5755
- const actions = position === 'left' ? this.leftActions() : this.rightActions();
5756
- return actions.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)));
5757
- }
5758
- /**
5759
- * Get enabled actions for a position
5760
- */
5761
- getEnabledActions(position, context) {
5762
- return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
5763
- }
5764
- isActionEnabled(action, context) {
5765
- return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
5766
- }
5767
- /**
5768
- * Picker actions for a file catalog (by priority), when visible and enabled.
5769
- */
5770
- findPickerActionsForFileType(catalogName, context) {
5771
- return [...this._actions()]
5772
- .filter((action) => action.component && action.fileType === catalogName)
5773
- .filter((action) => runInInjectionContext(this.injector, () => action.visible(context)))
5774
- .filter((action) => this.isActionEnabled(action, context))
5775
- .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
5776
- }
5777
- /**
5778
- * First picker action for a file catalog (by priority), when visible and enabled.
5779
- */
5780
- findPickerActionForFileType(catalogName, context) {
5781
- return this.findPickerActionsForFileType(catalogName, context)[0];
5782
- }
5783
- /**
5784
- * Get action by ID
5785
- */
5786
- getActionById(id) {
5787
- return this._actions().find((a) => a.id === id);
5788
- }
5789
- getActionLabel(action) {
5790
- return this.translation.translateSync(action.label);
5791
- }
5792
- getActionTooltip(action) {
5793
- if (!action.tooltip)
5794
- return undefined;
5795
- return this.translation.translateSync(action.tooltip);
5796
- }
5797
- /**
5798
- * Execute an action
5799
- */
5800
- async executeAction(actionId, context) {
5801
- const action = this.getActionById(actionId);
5802
- if (!action) {
5803
- throw new Error(`Action "${actionId}" not found`);
5804
- }
5805
- if (!runInInjectionContext(this.injector, () => action.visible(context))) {
5806
- throw new Error(`Action "${actionId}" is not visible`);
5807
- }
5808
- if (action.enabled && !runInInjectionContext(this.injector, () => action.enabled?.(context))) {
5809
- throw new Error(`Action "${actionId}" is not enabled`);
5810
- }
5811
- if (action.handler) {
5812
- await runInInjectionContext(this.injector, () => action.handler?.(context));
5813
- }
5814
- }
5815
- /**
5816
- * Update action badge
5817
- */
5818
- setBadge(id, badge) {
5819
- this._actions.update((actions) => actions.map((action) => (action.id === id ? { ...action, badge } : action)));
5820
- }
5821
- /**
5822
- * Clear all actions
5823
- */
5824
- clear() {
5825
- this._actions.set([]);
5826
- }
5827
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5828
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry }); }
5829
- }
5830
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, decorators: [{
5831
- type: Injectable
5832
- }], ctorParameters: () => [] });
5833
-
5834
- /**
5835
- * Composer Tab Registry
5836
- * Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
5837
- */
5838
- /**
5839
- * Composer Tab Registry Service
5840
- */
5841
- class AXComposerTabRegistry {
5842
- constructor() {
5843
- this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
5844
- this.translation = inject(AXTranslationService);
5845
- /** All registered tabs */
5846
- this.tabs = this._tabs.asReadonly();
5847
- /** Enabled tabs sorted by priority */
5848
- this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
5849
- // Register default tabs from individual token
5850
- inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
5851
- // Register tabs from REGISTRY_CONFIG
5852
- const config = inject(REGISTRY_CONFIG, { optional: true });
5853
- config?.composerTabs?.forEach((tab) => this.register(tab));
5854
- }
5855
- /**
5856
- * Register a composer tab
5857
- */
5858
- register(tab) {
5859
- // Validate tab
5860
- if (!tab.id) {
5861
- throw new Error('Composer tab must have an id');
5862
- }
5863
- if (!tab.title) {
5864
- throw new Error('Composer tab must have a title');
5865
- }
5866
- if (!tab.component) {
5867
- throw new Error('Composer tab must have a component');
5868
- }
5869
- // Check for duplicates
5870
- const existing = this._tabs().find((t) => t.id === tab.id);
5871
- if (existing) {
5872
- console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
5873
- this.unregister(tab.id);
5874
- }
5875
- // Add to registry
5876
- this._tabs.update((tabs) => [...tabs, tab]);
5877
- }
5878
- /**
5879
- * Unregister a composer tab
5880
- */
5881
- unregister(id) {
5882
- this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
5883
- }
5884
- /**
5885
- * Get tab by ID
5886
- */
5887
- getTabById(id) {
5888
- return this._tabs().find((t) => t.id === id);
5889
- }
5890
- getTabTitle(tab) {
5891
- return this.translation.translateSync(tab.title);
5892
- }
5893
- /**
5894
- * Enable/disable a tab
5895
- */
5896
- setEnabled(id, enabled) {
5897
- this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
5898
- }
5899
- /**
5900
- * Update tab badge
5901
- */
5902
- setBadge(id, badge) {
5903
- this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
5904
- }
5905
- /**
5906
- * Clear all tabs
5907
- */
5908
- clear() {
5909
- this._tabs.set([]);
5910
- }
5911
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
5912
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry }); }
5913
- }
5914
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, decorators: [{
5915
- type: Injectable
5916
- }], ctorParameters: () => [] });
5917
-
5918
- /**
5919
- * Abstract Base Registry
5920
- * Base class for all registries with common functionality
5921
- */
5922
- /**
5923
- * Abstract Base Registry Class
5924
- * Provides common registry functionality
5925
- */
5926
- class AXBaseRegistry {
5927
- constructor() {
5928
- this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
5929
- /** All registered items */
5930
- this.items = this._items.asReadonly();
5931
- /** Enabled items sorted by priority */
5932
- this.enabledItems = computed(() => [...this._items()].filter((item) => item.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledItems" }] : []));
6095
+ /** Left-side actions sorted by priority */
6096
+ this.leftActions = computed(() => [...this._actions()]
6097
+ .filter((action) => action.position === 'left')
6098
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "leftActions" }] : []));
6099
+ /** Right-side actions sorted by priority */
6100
+ this.rightActions = computed(() => [...this._actions()]
6101
+ .filter((action) => action.position === 'right')
6102
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "rightActions" }] : []));
6103
+ /** Left-side inline actions (location = 'inline' or undefined) */
6104
+ this.leftInlineActions = computed(() => this.leftActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "leftInlineActions" }] : []));
6105
+ /** Left-side menu actions (location = 'menu') */
6106
+ this.leftMenuActions = computed(() => this.leftActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "leftMenuActions" }] : []));
6107
+ /** Right-side inline actions (location = 'inline' or undefined) */
6108
+ this.rightInlineActions = computed(() => this.rightActions().filter((action) => action.location !== 'menu'), ...(ngDevMode ? [{ debugName: "rightInlineActions" }] : []));
6109
+ /** Right-side menu actions (location = 'menu') */
6110
+ this.rightMenuActions = computed(() => this.rightActions().filter((action) => action.location === 'menu'), ...(ngDevMode ? [{ debugName: "rightMenuActions" }] : []));
6111
+ inject(CONVERSATION_FILE_TYPES_READY);
6112
+ // Register default actions from individual token
6113
+ inject(DEFAULT_COMPOSER_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
6114
+ // Register actions from REGISTRY_CONFIG
6115
+ const config = inject(REGISTRY_CONFIG, { optional: true });
6116
+ config?.composerActions?.forEach((action) => this.register(action));
5933
6117
  }
5934
6118
  /**
5935
- * Register an item
6119
+ * Register a composer action
5936
6120
  */
5937
- register(item) {
5938
- this.validateItem(item);
6121
+ register(action) {
6122
+ // Validate action
6123
+ if (!action.id) {
6124
+ throw new Error('Composer action must have an id');
6125
+ }
6126
+ if (!action.label) {
6127
+ throw new Error('Composer action must have a label');
6128
+ }
6129
+ if (!action.icon) {
6130
+ throw new Error('Composer action must have an icon');
6131
+ }
6132
+ if (!action.handler && !action.component) {
6133
+ throw new Error('Composer action must have either a handler or a component');
6134
+ }
5939
6135
  // Check for duplicates
5940
- const existing = this._items().find((i) => i.id === item.id);
6136
+ const existing = this._actions().find((a) => a.id === action.id);
5941
6137
  if (existing) {
5942
- console.warn(`${this.getRegistryName()}: Item "${item.id}" is already registered. Replacing...`);
5943
- this.unregister(item.id);
6138
+ console.warn(`Composer action "${action.id}" is already registered. Replacing...`);
6139
+ this.unregister(action.id);
5944
6140
  }
5945
6141
  // Add to registry
5946
- this._items.update((items) => [...items, item]);
6142
+ this._actions.update((actions) => [...actions, action]);
6143
+ if (action.fileType) {
6144
+ void this.ensureFileTypeExists(action.fileType);
6145
+ }
5947
6146
  }
5948
- /**
5949
- * Register multiple items
5950
- */
5951
- registerMany(items) {
5952
- items.forEach((item) => this.register(item));
6147
+ async ensureFileTypeExists(fileType) {
6148
+ const match = await this.fileTypeRegistry.get(fileType);
6149
+ if (!match) {
6150
+ console.warn(`[AXComposerActionRegistry] Unknown fileType "${fileType}". ` +
6151
+ 'Register its AXFileTypeInfoProvider in app providers before using this action.');
6152
+ }
5953
6153
  }
5954
6154
  /**
5955
- * Unregister an item by ID
6155
+ * Unregister a composer action
5956
6156
  */
5957
6157
  unregister(id) {
5958
- this._items.update((items) => items.filter((i) => i.id !== id));
5959
- }
5960
- /**
5961
- * Get item by ID
5962
- */
5963
- getById(id) {
5964
- return this._items().find((i) => i.id === id);
5965
- }
5966
- /**
5967
- * Check if item exists
5968
- */
5969
- has(id) {
5970
- return this.getById(id) !== undefined;
5971
- }
5972
- /**
5973
- * Enable/disable an item
5974
- */
5975
- setEnabled(id, enabled) {
5976
- this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
5977
- }
5978
- /**
5979
- * Update an item
5980
- */
5981
- update(id, updates) {
5982
- this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
6158
+ this._actions.update((actions) => actions.filter((a) => a.id !== id));
5983
6159
  }
5984
6160
  /**
5985
- * Clear all items
6161
+ * Get visible actions for a position
5986
6162
  */
5987
- clear() {
5988
- this._items.set([]);
6163
+ getVisibleActions(position, context) {
6164
+ const actions = position === 'left' ? this.leftActions() : this.rightActions();
6165
+ return actions.filter((action) => runInInjectionContext(this.injector, () => action.visible(context)));
5989
6166
  }
5990
6167
  /**
5991
- * Get count of registered items
6168
+ * Get enabled actions for a position
5992
6169
  */
5993
- get count() {
5994
- return this._items().length;
6170
+ getEnabledActions(position, context) {
6171
+ return this.getVisibleActions(position, context).filter((action) => runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false);
5995
6172
  }
5996
- /**
5997
- * Get count of enabled items
5998
- */
5999
- get enabledCount() {
6000
- return this.enabledItems().length;
6173
+ isActionEnabled(action, context) {
6174
+ return runInInjectionContext(this.injector, () => action.enabled?.(context)) !== false;
6001
6175
  }
6002
6176
  /**
6003
- * Validate item before registration
6004
- * Override in child classes for custom validation
6177
+ * Picker actions for a file catalog (by priority), when visible and enabled.
6005
6178
  */
6006
- validateItem(item) {
6007
- if (!item.id) {
6008
- throw new Error(`${this.getRegistryName()}: Item must have an id`);
6009
- }
6010
- }
6011
- }
6012
-
6013
- /**
6014
- * Conversation Item Action Registry
6015
- * Manages actions available in the conversation list item context menu
6016
- */
6017
- /**
6018
- * Conversation Item Action Registry Service
6019
- * Manages available actions in the conversation list item context menu
6020
- */
6021
- class AXConversationItemActionRegistry extends AXBaseRegistry {
6022
- constructor() {
6023
- super();
6024
- this.translation = inject(AXTranslationService);
6025
- this.injector = inject(Injector);
6026
- // Register default actions from individual token
6027
- inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
6028
- // Register actions from REGISTRY_CONFIG
6029
- const config = inject(REGISTRY_CONFIG, { optional: true });
6030
- config?.conversationItemActions?.forEach((action) => this.register(action));
6179
+ findPickerActionsForFileType(catalogName, context) {
6180
+ return [...this._actions()]
6181
+ .filter((action) => action.component && action.fileType === catalogName)
6182
+ .filter((action) => runInInjectionContext(this.injector, () => action.visible(context)))
6183
+ .filter((action) => this.isActionEnabled(action, context))
6184
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
6031
6185
  }
6032
6186
  /**
6033
- * Get registry name for logging
6187
+ * First picker action for a file catalog (by priority), when visible and enabled.
6034
6188
  */
6035
- getRegistryName() {
6036
- return 'ConversationItemActionRegistry';
6189
+ findPickerActionForFileType(catalogName, context) {
6190
+ return this.findPickerActionsForFileType(catalogName, context)[0];
6037
6191
  }
6038
6192
  /**
6039
- * Get actions for a specific conversation
6193
+ * Get action by ID
6040
6194
  */
6041
- getActionsForConversation(conversation) {
6042
- const context = { conversation };
6043
- return this.enabledItems()
6044
- .filter((action) => {
6045
- // Filter by visibility
6046
- if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
6047
- return false;
6048
- }
6049
- return true;
6050
- })
6051
- .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
6052
- }
6053
- isActionEnabled(action, context) {
6054
- if (action.enabled === false) {
6055
- return false;
6056
- }
6057
- if (action.enabledWhen) {
6058
- return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
6059
- }
6060
- return true;
6195
+ getActionById(id) {
6196
+ return this._actions().find((a) => a.id === id);
6061
6197
  }
6062
- /**
6063
- * Get label for an action
6064
- */
6065
- getActionLabel(action, conversation) {
6066
- if (typeof action.label === 'function') {
6067
- const labelFactory = action.label;
6068
- return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
6069
- }
6198
+ getActionLabel(action) {
6070
6199
  return this.translation.translateSync(action.label);
6071
6200
  }
6072
- /**
6073
- * Get icon for an action
6074
- */
6075
- getActionIcon(action, conversation) {
6076
- if (!action.icon)
6077
- return undefined;
6078
- if (typeof action.icon === 'function') {
6079
- const iconFactory = action.icon;
6080
- return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
6081
- }
6082
- return action.icon;
6083
- }
6084
- /**
6085
- * Get tooltip for an action
6086
- */
6087
- getActionTooltip(action, conversation) {
6201
+ getActionTooltip(action) {
6088
6202
  if (!action.tooltip)
6089
6203
  return undefined;
6090
- if (typeof action.tooltip === 'function') {
6091
- const tooltipFactory = action.tooltip;
6092
- return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
6093
- }
6094
6204
  return this.translation.translateSync(action.tooltip);
6095
6205
  }
6206
+ /**
6207
+ * Execute an action
6208
+ */
6096
6209
  async executeAction(actionId, context) {
6097
- const action = this.getById(actionId);
6210
+ const action = this.getActionById(actionId);
6098
6211
  if (!action) {
6099
6212
  throw new Error(`Action "${actionId}" not found`);
6100
6213
  }
6101
- if (!this.isActionEnabled(action, context)) {
6214
+ if (!runInInjectionContext(this.injector, () => action.visible(context))) {
6215
+ throw new Error(`Action "${actionId}" is not visible`);
6216
+ }
6217
+ if (action.enabled && !runInInjectionContext(this.injector, () => action.enabled?.(context))) {
6102
6218
  throw new Error(`Action "${actionId}" is not enabled`);
6103
6219
  }
6104
6220
  if (action.handler) {
6105
6221
  await runInInjectionContext(this.injector, () => action.handler?.(context));
6106
6222
  }
6107
6223
  }
6108
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6109
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry }); }
6224
+ /**
6225
+ * Update action badge
6226
+ */
6227
+ setBadge(id, badge) {
6228
+ this._actions.update((actions) => actions.map((action) => (action.id === id ? { ...action, badge } : action)));
6229
+ }
6230
+ /**
6231
+ * Clear all actions
6232
+ */
6233
+ clear() {
6234
+ this._actions.set([]);
6235
+ }
6236
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6237
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry }); }
6110
6238
  }
6111
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
6239
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerActionRegistry, decorators: [{
6112
6240
  type: Injectable
6113
6241
  }], ctorParameters: () => [] });
6114
6242
 
6115
6243
  /**
6116
- * Conversation Tab Registry
6117
- * Register custom tabs for filtering conversations in the sidebar
6244
+ * Composer Tab Registry
6245
+ * Register custom tabs for the composer popup (emoji, stickers, GIFs, etc.)
6118
6246
  */
6119
6247
  /**
6120
- * Conversation Tab Registry Service
6248
+ * Composer Tab Registry Service
6121
6249
  */
6122
- class AXConversationTabRegistry {
6250
+ class AXComposerTabRegistry {
6123
6251
  constructor() {
6124
6252
  this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
6125
6253
  this.translation = inject(AXTranslationService);
@@ -6127,39 +6255,37 @@ class AXConversationTabRegistry {
6127
6255
  this.tabs = this._tabs.asReadonly();
6128
6256
  /** Enabled tabs sorted by priority */
6129
6257
  this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
6130
- /** Default active tab */
6131
- this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
6132
6258
  // Register default tabs from individual token
6133
- inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
6259
+ inject(DEFAULT_COMPOSER_TABS, { optional: true })?.forEach((tab) => this.register(tab));
6134
6260
  // Register tabs from REGISTRY_CONFIG
6135
6261
  const config = inject(REGISTRY_CONFIG, { optional: true });
6136
- config?.conversationTabs?.forEach((tab) => this.register(tab));
6262
+ config?.composerTabs?.forEach((tab) => this.register(tab));
6137
6263
  }
6138
6264
  /**
6139
- * Register a conversation tab
6265
+ * Register a composer tab
6140
6266
  */
6141
6267
  register(tab) {
6142
6268
  // Validate tab
6143
6269
  if (!tab.id) {
6144
- throw new Error('Conversation tab must have an id');
6270
+ throw new Error('Composer tab must have an id');
6145
6271
  }
6146
- if (!tab.label) {
6147
- throw new Error('Conversation tab must have a label');
6272
+ if (!tab.title) {
6273
+ throw new Error('Composer tab must have a title');
6148
6274
  }
6149
- if (!tab.filter) {
6150
- throw new Error('Conversation tab must have a filter function');
6275
+ if (!tab.component) {
6276
+ throw new Error('Composer tab must have a component');
6151
6277
  }
6152
6278
  // Check for duplicates
6153
6279
  const existing = this._tabs().find((t) => t.id === tab.id);
6154
6280
  if (existing) {
6155
- console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
6281
+ console.warn(`Composer tab "${tab.id}" is already registered. Replacing...`);
6156
6282
  this.unregister(tab.id);
6157
6283
  }
6158
6284
  // Add to registry
6159
6285
  this._tabs.update((tabs) => [...tabs, tab]);
6160
6286
  }
6161
6287
  /**
6162
- * Unregister a conversation tab
6288
+ * Unregister a composer tab
6163
6289
  */
6164
6290
  unregister(id) {
6165
6291
  this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
@@ -6170,28 +6296,8 @@ class AXConversationTabRegistry {
6170
6296
  getTabById(id) {
6171
6297
  return this._tabs().find((t) => t.id === id);
6172
6298
  }
6173
- getTabLabel(tab) {
6174
- return this.translation.translateSync(tab.label);
6175
- }
6176
- /**
6177
- * Apply tab filter to conversations
6178
- */
6179
- filterConversations(tabId, conversations) {
6180
- const tab = this.getTabById(tabId);
6181
- if (!tab) {
6182
- throw new Error(`Tab "${tabId}" not found`);
6183
- }
6184
- return tab.filter(conversations);
6185
- }
6186
- /**
6187
- * Get badge for a tab
6188
- */
6189
- getBadge(tabId, conversations) {
6190
- const tab = this.getTabById(tabId);
6191
- if (!tab || !tab.badge) {
6192
- return undefined;
6193
- }
6194
- return tab.badge(conversations);
6299
+ getTabTitle(tab) {
6300
+ return this.translation.translateSync(tab.title);
6195
6301
  }
6196
6302
  /**
6197
6303
  * Enable/disable a tab
@@ -6200,13 +6306,10 @@ class AXConversationTabRegistry {
6200
6306
  this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
6201
6307
  }
6202
6308
  /**
6203
- * Set default tab
6309
+ * Update tab badge
6204
6310
  */
6205
- setDefault(id) {
6206
- this._tabs.update((tabs) => tabs.map((tab) => ({
6207
- ...tab,
6208
- default: tab.id === id,
6209
- })));
6311
+ setBadge(id, badge) {
6312
+ this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, badge } : tab)));
6210
6313
  }
6211
6314
  /**
6212
6315
  * Clear all tabs
@@ -6214,759 +6317,760 @@ class AXConversationTabRegistry {
6214
6317
  clear() {
6215
6318
  this._tabs.set([]);
6216
6319
  }
6217
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6218
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry }); }
6320
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6321
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry }); }
6219
6322
  }
6220
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
6323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXComposerTabRegistry, decorators: [{
6221
6324
  type: Injectable
6222
6325
  }], ctorParameters: () => [] });
6223
6326
 
6224
6327
  /**
6225
- * Info Bar Action Registry
6226
- * Manages actions available in the conversation info bar
6328
+ * Abstract Base Registry
6329
+ * Base class for all registries with common functionality
6227
6330
  */
6228
6331
  /**
6229
- * Info Bar Action Registry Service
6230
- * Manages available actions in the conversation info bar
6332
+ * Abstract Base Registry Class
6333
+ * Provides common registry functionality
6231
6334
  */
6232
- class AXInfoBarActionRegistry extends AXBaseRegistry {
6335
+ class AXBaseRegistry {
6233
6336
  constructor() {
6234
- super();
6235
- this.translation = inject(AXTranslationService);
6236
- this.injector = inject(Injector);
6237
- // Register default actions from individual token
6238
- inject(DEFAULT_INFO_BAR_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
6239
- // Register actions from REGISTRY_CONFIG
6240
- const config = inject(REGISTRY_CONFIG, { optional: true });
6241
- config?.infoBarActions?.forEach((action) => this.register(action));
6337
+ this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : []));
6338
+ /** All registered items */
6339
+ this.items = this._items.asReadonly();
6340
+ /** Enabled items sorted by priority */
6341
+ this.enabledItems = computed(() => [...this._items()].filter((item) => item.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledItems" }] : []));
6242
6342
  }
6243
6343
  /**
6244
- * Get registry name for logging
6344
+ * Register an item
6245
6345
  */
6246
- getRegistryName() {
6247
- return 'InfoBarActionRegistry';
6346
+ register(item) {
6347
+ this.validateItem(item);
6348
+ // Check for duplicates
6349
+ const existing = this._items().find((i) => i.id === item.id);
6350
+ if (existing) {
6351
+ console.warn(`${this.getRegistryName()}: Item "${item.id}" is already registered. Replacing...`);
6352
+ this.unregister(item.id);
6353
+ }
6354
+ // Add to registry
6355
+ this._items.update((items) => [...items, item]);
6248
6356
  }
6249
6357
  /**
6250
- * Get actions for a specific conversation
6358
+ * Register multiple items
6251
6359
  */
6252
- getActionsForConversation(conversation, location) {
6253
- const context = { conversation };
6254
- return this.enabledItems()
6255
- .filter((action) => {
6256
- // Filter by location if specified
6257
- if (location && action.location && action.location !== location) {
6258
- return false;
6259
- }
6260
- // Filter by visibility
6261
- if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
6262
- return false;
6263
- }
6264
- return true;
6265
- })
6266
- .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
6360
+ registerMany(items) {
6361
+ items.forEach((item) => this.register(item));
6267
6362
  }
6268
6363
  /**
6269
- * Get inline actions (toolbar buttons)
6364
+ * Unregister an item by ID
6270
6365
  */
6271
- getInlineActions(conversation) {
6272
- return this.getActionsForConversation(conversation, 'inline');
6366
+ unregister(id) {
6367
+ this._items.update((items) => items.filter((i) => i.id !== id));
6273
6368
  }
6274
6369
  /**
6275
- * Get menu actions (dropdown items)
6370
+ * Get item by ID
6276
6371
  */
6277
- getMenuActions(conversation) {
6278
- return this.getActionsForConversation(conversation, 'menu');
6372
+ getById(id) {
6373
+ return this._items().find((i) => i.id === id);
6279
6374
  }
6280
- isActionEnabled(action, context) {
6281
- if (action.enabled === false) {
6282
- return false;
6283
- }
6284
- if (action.enabledWhen) {
6285
- return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
6286
- }
6287
- return true;
6375
+ /**
6376
+ * Check if item exists
6377
+ */
6378
+ has(id) {
6379
+ return this.getById(id) !== undefined;
6288
6380
  }
6289
6381
  /**
6290
- * Get label for an action
6382
+ * Enable/disable an item
6291
6383
  */
6292
- getActionLabel(action, conversation) {
6293
- if (typeof action.label === 'function') {
6294
- const labelFactory = action.label;
6295
- return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
6296
- }
6297
- return this.translation.translateSync(action.label);
6384
+ setEnabled(id, enabled) {
6385
+ this._items.update((items) => items.map((item) => (item.id === id ? { ...item, enabled } : item)));
6298
6386
  }
6299
6387
  /**
6300
- * Get icon for an action
6388
+ * Update an item
6301
6389
  */
6302
- getActionIcon(action, conversation) {
6303
- if (!action.icon)
6304
- return undefined;
6305
- if (typeof action.icon === 'function') {
6306
- const iconFactory = action.icon;
6307
- return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
6308
- }
6309
- return action.icon;
6390
+ update(id, updates) {
6391
+ this._items.update((items) => items.map((item) => (item.id === id ? { ...item, ...updates } : item)));
6310
6392
  }
6311
6393
  /**
6312
- * Get tooltip for an action
6394
+ * Clear all items
6313
6395
  */
6314
- getActionTooltip(action, conversation) {
6315
- if (!action.tooltip)
6316
- return undefined;
6317
- if (typeof action.tooltip === 'function') {
6318
- const tooltipFactory = action.tooltip;
6319
- return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
6396
+ clear() {
6397
+ this._items.set([]);
6398
+ }
6399
+ /**
6400
+ * Get count of registered items
6401
+ */
6402
+ get count() {
6403
+ return this._items().length;
6404
+ }
6405
+ /**
6406
+ * Get count of enabled items
6407
+ */
6408
+ get enabledCount() {
6409
+ return this.enabledItems().length;
6410
+ }
6411
+ /**
6412
+ * Validate item before registration
6413
+ * Override in child classes for custom validation
6414
+ */
6415
+ validateItem(item) {
6416
+ if (!item.id) {
6417
+ throw new Error(`${this.getRegistryName()}: Item must have an id`);
6320
6418
  }
6321
- return this.translation.translateSync(action.tooltip);
6322
6419
  }
6323
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6324
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
6325
6420
  }
6326
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
6327
- type: Injectable
6328
- }], ctorParameters: () => [] });
6329
6421
 
6330
6422
  /**
6331
- * Message Action Registry
6332
- * Register custom actions that can be performed on messages
6423
+ * Conversation Item Action Registry
6424
+ * Manages actions available in the conversation list item context menu
6333
6425
  */
6334
6426
  /**
6335
- * Message Action Registry Service
6427
+ * Conversation Item Action Registry Service
6428
+ * Manages available actions in the conversation list item context menu
6336
6429
  */
6337
- class AXMessageActionRegistry {
6430
+ class AXConversationItemActionRegistry extends AXBaseRegistry {
6338
6431
  constructor() {
6339
- this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
6432
+ super();
6340
6433
  this.translation = inject(AXTranslationService);
6341
6434
  this.injector = inject(Injector);
6342
- /** All registered actions */
6343
- this.actions = this._actions.asReadonly();
6344
- /** Actions sorted by priority */
6345
- this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
6346
6435
  // Register default actions from individual token
6347
- const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
6348
- if (defaultActions) {
6349
- defaultActions.forEach((action) => this.register(action));
6350
- }
6436
+ inject(DEFAULT_CONVERSATION_ITEM_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
6351
6437
  // Register actions from REGISTRY_CONFIG
6352
6438
  const config = inject(REGISTRY_CONFIG, { optional: true });
6353
- config?.messageActions?.forEach((action) => this.register(action));
6439
+ config?.conversationItemActions?.forEach((action) => this.register(action));
6354
6440
  }
6355
6441
  /**
6356
- * Register a message action
6442
+ * Get registry name for logging
6357
6443
  */
6358
- register(action) {
6359
- // Validate action
6360
- if (!action.id) {
6361
- throw new Error('Message action must have an id');
6362
- }
6363
- if (!action.label) {
6364
- throw new Error('Message action must have a label');
6365
- }
6366
- if (!action.handler) {
6367
- throw new Error('Message action must have a handler');
6368
- }
6369
- // Check for duplicates
6370
- const existing = this._actions().find((a) => a.id === action.id);
6371
- if (existing) {
6372
- console.warn(`Message action "${action.id}" is already registered. Replacing...`);
6373
- this.unregister(action.id);
6374
- }
6375
- // Add to registry
6376
- this._actions.update((actions) => [...actions, action]);
6444
+ getRegistryName() {
6445
+ return 'ConversationItemActionRegistry';
6377
6446
  }
6378
6447
  /**
6379
- * Unregister a message action
6448
+ * Get actions for a specific conversation
6380
6449
  */
6381
- unregister(id) {
6382
- this._actions.update((actions) => actions.filter((a) => a.id !== id));
6450
+ getActionsForConversation(conversation) {
6451
+ const context = { conversation };
6452
+ return this.enabledItems()
6453
+ .filter((action) => {
6454
+ // Filter by visibility
6455
+ if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
6456
+ return false;
6457
+ }
6458
+ return true;
6459
+ })
6460
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
6383
6461
  }
6384
- /**
6385
- * Get all actions for a specific message
6386
- */
6387
- getActions(message, currentUser) {
6388
- return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
6389
- runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
6462
+ isActionEnabled(action, context) {
6463
+ if (action.enabled === false) {
6464
+ return false;
6465
+ }
6466
+ if (action.enabledWhen) {
6467
+ return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
6468
+ }
6469
+ return true;
6390
6470
  }
6391
6471
  /**
6392
- * Get menu actions for a message
6472
+ * Get label for an action
6393
6473
  */
6394
- getMenuActions(message, currentUser) {
6395
- return this.getActions(message, currentUser);
6396
- }
6397
- /** Whether an action is enabled (safe to call outside an injection context). */
6398
- isActionEnabled(action, message, currentUser) {
6399
- return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
6400
- }
6401
- getActionLabel(action) {
6474
+ getActionLabel(action, conversation) {
6475
+ if (typeof action.label === 'function') {
6476
+ const labelFactory = action.label;
6477
+ return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
6478
+ }
6402
6479
  return this.translation.translateSync(action.label);
6403
6480
  }
6404
6481
  /**
6405
- * Get inline actions for a message
6482
+ * Get icon for an action
6406
6483
  */
6407
- getInlineActions(message, currentUser) {
6408
- return this.getActions(message, currentUser).filter((action) => action.showInline === true);
6484
+ getActionIcon(action, conversation) {
6485
+ if (!action.icon)
6486
+ return undefined;
6487
+ if (typeof action.icon === 'function') {
6488
+ const iconFactory = action.icon;
6489
+ return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
6490
+ }
6491
+ return action.icon;
6409
6492
  }
6410
6493
  /**
6411
- * Get action by ID
6494
+ * Get tooltip for an action
6412
6495
  */
6413
- getActionById(id) {
6414
- return this._actions().find((a) => a.id === id);
6496
+ getActionTooltip(action, conversation) {
6497
+ if (!action.tooltip)
6498
+ return undefined;
6499
+ if (typeof action.tooltip === 'function') {
6500
+ const tooltipFactory = action.tooltip;
6501
+ return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
6502
+ }
6503
+ return this.translation.translateSync(action.tooltip);
6415
6504
  }
6416
- /**
6417
- * Execute an action
6418
- */
6419
6505
  async executeAction(actionId, context) {
6420
- const action = this.getActionById(actionId);
6506
+ const action = this.getById(actionId);
6421
6507
  if (!action) {
6422
6508
  throw new Error(`Action "${actionId}" not found`);
6423
6509
  }
6424
- // Check if enabled
6425
- if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
6510
+ if (!this.isActionEnabled(action, context)) {
6426
6511
  throw new Error(`Action "${actionId}" is not enabled`);
6427
6512
  }
6428
- // Execute handler
6429
- await runInInjectionContext(this.injector, () => action.handler(context));
6430
- }
6431
- /**
6432
- * Clear all actions
6433
- */
6434
- clear() {
6435
- this._actions.set([]);
6513
+ if (action.handler) {
6514
+ await runInInjectionContext(this.injector, () => action.handler?.(context));
6515
+ }
6436
6516
  }
6437
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6438
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry }); }
6517
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6518
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry }); }
6439
6519
  }
6440
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, decorators: [{
6520
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationItemActionRegistry, decorators: [{
6441
6521
  type: Injectable
6442
6522
  }], ctorParameters: () => [] });
6443
6523
 
6444
6524
  /**
6445
- * IndexedDB Storage Wrapper
6446
- * Provides persistent storage for conversation data
6525
+ * Conversation Tab Registry
6526
+ * Register custom tabs for filtering conversations in the sidebar
6447
6527
  */
6448
- const DB_NAME = 'acorex-conversation-db';
6449
- const DB_VERSION = 2;
6450
- // Store names
6451
- const AXConversationIndexedDbStores = {
6452
- PARTICIPANTS: 'participants',
6453
- CONVERSATIONS: 'conversations',
6454
- MESSAGES: 'messages',
6455
- MEDIA: 'media',
6456
- SETTINGS: 'settings',
6457
- };
6458
- class AXConversationIndexedDbStorage {
6528
+ /**
6529
+ * Conversation Tab Registry Service
6530
+ */
6531
+ class AXConversationTabRegistry {
6459
6532
  constructor() {
6460
- this.db = null;
6461
- this.initPromise = null;
6462
- }
6463
- /**
6464
- * Initialize IndexedDB
6465
- */
6466
- async init() {
6467
- if (this.db)
6468
- return;
6469
- if (this.initPromise)
6470
- return this.initPromise;
6471
- this.initPromise = new Promise((resolve, reject) => {
6472
- const request = indexedDB.open(DB_NAME, DB_VERSION);
6473
- request.onerror = () => reject(request.error);
6474
- request.onsuccess = () => {
6475
- this.db = request.result;
6476
- resolve();
6477
- };
6478
- request.onupgradeneeded = (event) => {
6479
- const db = event.target.result;
6480
- // Create object stores if they don't exist
6481
- if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
6482
- db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
6483
- }
6484
- if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
6485
- const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
6486
- convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
6487
- }
6488
- if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
6489
- const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
6490
- msgStore.createIndex('conversationId', 'conversationId', { unique: false });
6491
- msgStore.createIndex('timestamp', 'timestamp', { unique: false });
6492
- }
6493
- if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
6494
- db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
6495
- }
6496
- if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
6497
- db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
6498
- }
6499
- };
6500
- });
6501
- return this.initPromise;
6502
- }
6503
- /**
6504
- * Get a value from a store
6505
- */
6506
- async get(storeName, key) {
6507
- await this.init();
6508
- if (!this.db)
6509
- throw new Error('Database not initialized');
6510
- const db = this.db;
6511
- return new Promise((resolve, reject) => {
6512
- const transaction = db.transaction(storeName, 'readonly');
6513
- const store = transaction.objectStore(storeName);
6514
- const request = store.get(key);
6515
- request.onsuccess = () => resolve(request.result);
6516
- request.onerror = () => reject(request.error);
6517
- });
6518
- }
6519
- /**
6520
- * Get all values from a store
6521
- */
6522
- async getAll(storeName) {
6523
- await this.init();
6524
- if (!this.db)
6525
- throw new Error('Database not initialized');
6526
- const db = this.db;
6527
- return new Promise((resolve, reject) => {
6528
- const transaction = db.transaction(storeName, 'readonly');
6529
- const store = transaction.objectStore(storeName);
6530
- const request = store.getAll();
6531
- request.onsuccess = () => resolve(request.result);
6532
- request.onerror = () => reject(request.error);
6533
- });
6534
- }
6535
- /**
6536
- * Get values by index
6537
- */
6538
- async getAllByIndex(storeName, indexName, query) {
6539
- await this.init();
6540
- if (!this.db)
6541
- throw new Error('Database not initialized');
6542
- const db = this.db;
6543
- return new Promise((resolve, reject) => {
6544
- const transaction = db.transaction(storeName, 'readonly');
6545
- const store = transaction.objectStore(storeName);
6546
- const index = store.index(indexName);
6547
- const request = index.getAll(query);
6548
- request.onsuccess = () => resolve(request.result);
6549
- request.onerror = () => reject(request.error);
6550
- });
6551
- }
6552
- /**
6553
- * Put a value into a store
6554
- */
6555
- async put(storeName, value) {
6556
- await this.init();
6557
- if (!this.db)
6558
- throw new Error('Database not initialized');
6559
- const db = this.db;
6560
- return new Promise((resolve, reject) => {
6561
- const transaction = db.transaction(storeName, 'readwrite');
6562
- const store = transaction.objectStore(storeName);
6563
- const request = store.put(value);
6564
- request.onsuccess = () => resolve();
6565
- request.onerror = () => reject(request.error);
6566
- });
6567
- }
6568
- /**
6569
- * Delete a value from a store
6570
- */
6571
- async delete(storeName, key) {
6572
- await this.init();
6573
- if (!this.db)
6574
- throw new Error('Database not initialized');
6575
- const db = this.db;
6576
- return new Promise((resolve, reject) => {
6577
- const transaction = db.transaction(storeName, 'readwrite');
6578
- const store = transaction.objectStore(storeName);
6579
- const request = store.delete(key);
6580
- request.onsuccess = () => resolve();
6581
- request.onerror = () => reject(request.error);
6582
- });
6533
+ this._tabs = signal([], ...(ngDevMode ? [{ debugName: "_tabs" }] : []));
6534
+ this.translation = inject(AXTranslationService);
6535
+ /** All registered tabs */
6536
+ this.tabs = this._tabs.asReadonly();
6537
+ /** Enabled tabs sorted by priority */
6538
+ this.enabledTabs = computed(() => [...this._tabs()].filter((tab) => tab.enabled !== false).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "enabledTabs" }] : []));
6539
+ /** Default active tab */
6540
+ this.defaultTab = computed(() => this.enabledTabs().find((tab) => tab.default === true) || this.enabledTabs()[0], ...(ngDevMode ? [{ debugName: "defaultTab" }] : []));
6541
+ // Register default tabs from individual token
6542
+ inject(DEFAULT_CONVERSATION_TABS, { optional: true })?.forEach((tab) => this.register(tab));
6543
+ // Register tabs from REGISTRY_CONFIG
6544
+ const config = inject(REGISTRY_CONFIG, { optional: true });
6545
+ config?.conversationTabs?.forEach((tab) => this.register(tab));
6583
6546
  }
6584
6547
  /**
6585
- * Clear all data from a store
6548
+ * Register a conversation tab
6586
6549
  */
6587
- async clear(storeName) {
6588
- await this.init();
6589
- if (!this.db)
6590
- throw new Error('Database not initialized');
6591
- const db = this.db;
6592
- return new Promise((resolve, reject) => {
6593
- const transaction = db.transaction(storeName, 'readwrite');
6594
- const store = transaction.objectStore(storeName);
6595
- const request = store.clear();
6596
- request.onsuccess = () => resolve();
6597
- request.onerror = () => reject(request.error);
6598
- });
6599
- }
6600
- // =====================
6601
- // Convenience Methods
6602
- // =====================
6603
- async getParticipant(id) {
6604
- return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
6605
- }
6606
- async getAllParticipants() {
6607
- return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
6550
+ register(tab) {
6551
+ // Validate tab
6552
+ if (!tab.id) {
6553
+ throw new Error('Conversation tab must have an id');
6554
+ }
6555
+ if (!tab.label) {
6556
+ throw new Error('Conversation tab must have a label');
6557
+ }
6558
+ if (!tab.filter) {
6559
+ throw new Error('Conversation tab must have a filter function');
6560
+ }
6561
+ // Check for duplicates
6562
+ const existing = this._tabs().find((t) => t.id === tab.id);
6563
+ if (existing) {
6564
+ console.warn(`Conversation tab "${tab.id}" is already registered. Replacing...`);
6565
+ this.unregister(tab.id);
6566
+ }
6567
+ // Add to registry
6568
+ this._tabs.update((tabs) => [...tabs, tab]);
6608
6569
  }
6609
- async putParticipant(participant) {
6610
- return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
6570
+ /**
6571
+ * Unregister a conversation tab
6572
+ */
6573
+ unregister(id) {
6574
+ this._tabs.update((tabs) => tabs.filter((t) => t.id !== id));
6611
6575
  }
6612
- async getConversation(id) {
6613
- return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
6576
+ /**
6577
+ * Get tab by ID
6578
+ */
6579
+ getTabById(id) {
6580
+ return this._tabs().find((t) => t.id === id);
6614
6581
  }
6615
- async getAllConversations() {
6616
- return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
6582
+ getTabLabel(tab) {
6583
+ return this.translation.translateSync(tab.label);
6617
6584
  }
6618
- async putConversation(conversation) {
6619
- return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
6585
+ /**
6586
+ * Apply tab filter to conversations
6587
+ */
6588
+ filterConversations(tabId, conversations) {
6589
+ const tab = this.getTabById(tabId);
6590
+ if (!tab) {
6591
+ throw new Error(`Tab "${tabId}" not found`);
6592
+ }
6593
+ return tab.filter(conversations);
6620
6594
  }
6621
- async deleteConversation(id) {
6622
- return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
6595
+ /**
6596
+ * Get badge for a tab
6597
+ */
6598
+ getBadge(tabId, conversations) {
6599
+ const tab = this.getTabById(tabId);
6600
+ if (!tab || !tab.badge) {
6601
+ return undefined;
6602
+ }
6603
+ return tab.badge(conversations);
6623
6604
  }
6624
- async getMessage(id) {
6625
- return this.get(AXConversationIndexedDbStores.MESSAGES, id);
6605
+ /**
6606
+ * Enable/disable a tab
6607
+ */
6608
+ setEnabled(id, enabled) {
6609
+ this._tabs.update((tabs) => tabs.map((tab) => (tab.id === id ? { ...tab, enabled } : tab)));
6626
6610
  }
6627
- async getAllMessages() {
6628
- return this.getAll(AXConversationIndexedDbStores.MESSAGES);
6611
+ /**
6612
+ * Set default tab
6613
+ */
6614
+ setDefault(id) {
6615
+ this._tabs.update((tabs) => tabs.map((tab) => ({
6616
+ ...tab,
6617
+ default: tab.id === id,
6618
+ })));
6629
6619
  }
6630
- async getMessagesByConversation(conversationId) {
6631
- return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
6620
+ /**
6621
+ * Clear all tabs
6622
+ */
6623
+ clear() {
6624
+ this._tabs.set([]);
6632
6625
  }
6633
- async putMessage(message) {
6634
- return this.put(AXConversationIndexedDbStores.MESSAGES, message);
6626
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6627
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry }); }
6628
+ }
6629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXConversationTabRegistry, decorators: [{
6630
+ type: Injectable
6631
+ }], ctorParameters: () => [] });
6632
+
6633
+ /**
6634
+ * Info Bar Action Registry
6635
+ * Manages actions available in the conversation info bar
6636
+ */
6637
+ /**
6638
+ * Info Bar Action Registry Service
6639
+ * Manages available actions in the conversation info bar
6640
+ */
6641
+ class AXInfoBarActionRegistry extends AXBaseRegistry {
6642
+ constructor() {
6643
+ super();
6644
+ this.translation = inject(AXTranslationService);
6645
+ this.injector = inject(Injector);
6646
+ // Register default actions from individual token
6647
+ inject(DEFAULT_INFO_BAR_ACTIONS, { optional: true })?.forEach((action) => this.register(action));
6648
+ // Register actions from REGISTRY_CONFIG
6649
+ const config = inject(REGISTRY_CONFIG, { optional: true });
6650
+ config?.infoBarActions?.forEach((action) => this.register(action));
6635
6651
  }
6636
- async deleteMessage(id) {
6637
- return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
6652
+ /**
6653
+ * Get registry name for logging
6654
+ */
6655
+ getRegistryName() {
6656
+ return 'InfoBarActionRegistry';
6638
6657
  }
6639
- async getSetting(key) {
6640
- const result = await this.get(AXConversationIndexedDbStores.SETTINGS, key);
6641
- return result?.value;
6658
+ /**
6659
+ * Get actions for a specific conversation
6660
+ */
6661
+ getActionsForConversation(conversation, location) {
6662
+ const context = { conversation };
6663
+ return this.enabledItems()
6664
+ .filter((action) => {
6665
+ // Filter by location if specified
6666
+ if (location && action.location && action.location !== location) {
6667
+ return false;
6668
+ }
6669
+ // Filter by visibility
6670
+ if (action.visible && !runInInjectionContext(this.injector, () => action.visible?.(context))) {
6671
+ return false;
6672
+ }
6673
+ return true;
6674
+ })
6675
+ .sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
6642
6676
  }
6643
- async putSetting(key, value) {
6644
- return this.put(AXConversationIndexedDbStores.SETTINGS, { key, value });
6677
+ /**
6678
+ * Get inline actions (toolbar buttons)
6679
+ */
6680
+ getInlineActions(conversation) {
6681
+ return this.getActionsForConversation(conversation, 'inline');
6645
6682
  }
6646
- async getMedia(id) {
6647
- return this.get(AXConversationIndexedDbStores.MEDIA, id);
6683
+ /**
6684
+ * Get menu actions (dropdown items)
6685
+ */
6686
+ getMenuActions(conversation) {
6687
+ return this.getActionsForConversation(conversation, 'menu');
6648
6688
  }
6649
- async putMedia(record) {
6650
- return this.put(AXConversationIndexedDbStores.MEDIA, record);
6689
+ isActionEnabled(action, context) {
6690
+ if (action.enabled === false) {
6691
+ return false;
6692
+ }
6693
+ if (action.enabledWhen) {
6694
+ return runInInjectionContext(this.injector, () => action.enabledWhen?.(context)) !== false;
6695
+ }
6696
+ return true;
6651
6697
  }
6652
- async deleteMedia(id) {
6653
- return this.delete(AXConversationIndexedDbStores.MEDIA, id);
6698
+ /**
6699
+ * Get label for an action
6700
+ */
6701
+ getActionLabel(action, conversation) {
6702
+ if (typeof action.label === 'function') {
6703
+ const labelFactory = action.label;
6704
+ return this.translation.translateSync(runInInjectionContext(this.injector, () => labelFactory({ conversation })));
6705
+ }
6706
+ return this.translation.translateSync(action.label);
6654
6707
  }
6655
- }
6656
- // Export singleton instance
6657
- const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
6658
-
6659
- var indexeddbStorage = /*#__PURE__*/Object.freeze({
6660
- __proto__: null,
6661
- AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
6662
- AXConversationIndexedDbStores: AXConversationIndexedDbStores,
6663
- axConversationIndexedDbStorage: axConversationIndexedDbStorage
6664
- });
6665
-
6666
- class AXConversationMessageUtilsService {
6667
6708
  /**
6668
- * Normalize optional avatar/icon values so empty or whitespace-only strings
6669
- * are treated as missing data.
6709
+ * Get icon for an action
6670
6710
  */
6671
- static normalizeOptionalMediaValue(value) {
6672
- if (typeof value !== 'string') {
6711
+ getActionIcon(action, conversation) {
6712
+ if (!action.icon)
6673
6713
  return undefined;
6714
+ if (typeof action.icon === 'function') {
6715
+ const iconFactory = action.icon;
6716
+ return runInInjectionContext(this.injector, () => iconFactory({ conversation }));
6674
6717
  }
6675
- const normalized = value.trim();
6676
- return normalized.length > 0 ? normalized : undefined;
6718
+ return action.icon;
6677
6719
  }
6678
6720
  /**
6679
- * Get conversation avatar image URL.
6721
+ * Get tooltip for an action
6680
6722
  */
6681
- static getConversationAvatar(conversation) {
6682
- return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.avatar);
6723
+ getActionTooltip(action, conversation) {
6724
+ if (!action.tooltip)
6725
+ return undefined;
6726
+ if (typeof action.tooltip === 'function') {
6727
+ const tooltipFactory = action.tooltip;
6728
+ return this.translation.translateSync(runInInjectionContext(this.injector, () => tooltipFactory({ conversation })));
6729
+ }
6730
+ return this.translation.translateSync(action.tooltip);
6731
+ }
6732
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6733
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry }); }
6734
+ }
6735
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXInfoBarActionRegistry, decorators: [{
6736
+ type: Injectable
6737
+ }], ctorParameters: () => [] });
6738
+
6739
+ /**
6740
+ * Message Action Registry
6741
+ * Register custom actions that can be performed on messages
6742
+ */
6743
+ /**
6744
+ * Message Action Registry Service
6745
+ */
6746
+ class AXMessageActionRegistry {
6747
+ constructor() {
6748
+ this._actions = signal([], ...(ngDevMode ? [{ debugName: "_actions" }] : []));
6749
+ this.translation = inject(AXTranslationService);
6750
+ this.injector = inject(Injector);
6751
+ /** All registered actions */
6752
+ this.actions = this._actions.asReadonly();
6753
+ /** Actions sorted by priority */
6754
+ this.sortedActions = computed(() => [...this._actions()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)), ...(ngDevMode ? [{ debugName: "sortedActions" }] : []));
6755
+ // Register default actions from individual token
6756
+ const defaultActions = inject(DEFAULT_MESSAGE_ACTIONS, { optional: true });
6757
+ if (defaultActions) {
6758
+ defaultActions.forEach((action) => this.register(action));
6759
+ }
6760
+ // Register actions from REGISTRY_CONFIG
6761
+ const config = inject(REGISTRY_CONFIG, { optional: true });
6762
+ config?.messageActions?.forEach((action) => this.register(action));
6683
6763
  }
6684
6764
  /**
6685
- * Font Awesome icon class(es) for a conversation when there is no avatar image.
6765
+ * Register a message action
6686
6766
  */
6687
- static getConversationAvatarIcon(conversation) {
6688
- if (AXConversationMessageUtilsService.getConversationAvatar(conversation)) {
6689
- return undefined;
6767
+ register(action) {
6768
+ // Validate action
6769
+ if (!action.id) {
6770
+ throw new Error('Message action must have an id');
6690
6771
  }
6691
- return AXConversationMessageUtilsService.normalizeOptionalMediaValue(conversation.icon);
6772
+ if (!action.label) {
6773
+ throw new Error('Message action must have a label');
6774
+ }
6775
+ if (!action.handler) {
6776
+ throw new Error('Message action must have a handler');
6777
+ }
6778
+ // Check for duplicates
6779
+ const existing = this._actions().find((a) => a.id === action.id);
6780
+ if (existing) {
6781
+ console.warn(`Message action "${action.id}" is already registered. Replacing...`);
6782
+ this.unregister(action.id);
6783
+ }
6784
+ // Add to registry
6785
+ this._actions.update((actions) => [...actions, action]);
6692
6786
  }
6693
6787
  /**
6694
- * Get sender name from message
6788
+ * Unregister a message action
6695
6789
  */
6696
- static getSenderName(message, conversation) {
6697
- const participant = conversation.participants.find((p) => p.id === message.senderId);
6698
- return participant?.name || translateSync('@acorex:chat.fallbacks.unknown-user');
6790
+ unregister(id) {
6791
+ this._actions.update((actions) => actions.filter((a) => a.id !== id));
6699
6792
  }
6700
6793
  /**
6701
- * Get sender avatar image URL (takes precedence over {@link getSenderAvatarIcon}).
6794
+ * Get all actions for a specific message
6702
6795
  */
6703
- static getSenderAvatar(message, conversation) {
6704
- const participant = conversation.participants.find((p) => p.id === message.senderId);
6705
- return AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar);
6796
+ getActions(message, currentUser) {
6797
+ return this.sortedActions().filter((action) => runInInjectionContext(this.injector, () => action.visible(message, currentUser)) &&
6798
+ runInInjectionContext(this.injector, () => action.enabled(message, currentUser)));
6706
6799
  }
6707
6800
  /**
6708
- * Font Awesome icon class(es) for the sender when there is no avatar image:
6709
- * participant `icon` first, then conversation-level `icon`.
6801
+ * Get menu actions for a message
6710
6802
  */
6711
- static getSenderAvatarIcon(message, conversation) {
6712
- const participant = conversation.participants.find((p) => p.id === message.senderId);
6713
- if (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.avatar)) {
6714
- return undefined;
6715
- }
6716
- return (AXConversationMessageUtilsService.normalizeOptionalMediaValue(participant?.icon) ??
6717
- AXConversationMessageUtilsService.getConversationAvatarIcon(conversation));
6803
+ getMenuActions(message, currentUser) {
6804
+ return this.getActions(message, currentUser);
6805
+ }
6806
+ /** Whether an action is enabled (safe to call outside an injection context). */
6807
+ isActionEnabled(action, message, currentUser) {
6808
+ return runInInjectionContext(this.injector, () => action.enabled(message, currentUser));
6809
+ }
6810
+ getActionLabel(action) {
6811
+ return this.translation.translateSync(action.label);
6718
6812
  }
6719
6813
  /**
6720
- * Get initials from name
6814
+ * Get inline actions for a message
6721
6815
  */
6722
- static getInitials(name) {
6723
- if (!name)
6724
- return '?';
6725
- return name
6726
- .split(' ')
6727
- .map((n) => n[0])
6728
- .join('')
6729
- .toUpperCase()
6730
- .substring(0, 2);
6816
+ getInlineActions(message, currentUser) {
6817
+ return this.getActions(message, currentUser).filter((action) => action.showInline === true);
6731
6818
  }
6732
6819
  /**
6733
- * Type guard for text payload
6820
+ * Get action by ID
6734
6821
  */
6735
- static isTextPayload(payload) {
6736
- return 'text' in payload && typeof payload.text === 'string';
6822
+ getActionById(id) {
6823
+ return this._actions().find((a) => a.id === id);
6737
6824
  }
6738
6825
  /**
6739
- * Get message text content
6826
+ * Execute an action
6740
6827
  */
6741
- static getMessageText(message) {
6742
- if (message.type === 'text' && AXConversationMessageUtilsService.isTextPayload(message.payload)) {
6743
- return message.payload.text;
6828
+ async executeAction(actionId, context) {
6829
+ const action = this.getActionById(actionId);
6830
+ if (!action) {
6831
+ throw new Error(`Action "${actionId}" not found`);
6744
6832
  }
6745
- return `[${message.type}]`;
6833
+ // Check if enabled
6834
+ if (!runInInjectionContext(this.injector, () => action.enabled(context.message, context.currentUser))) {
6835
+ throw new Error(`Action "${actionId}" is not enabled`);
6836
+ }
6837
+ // Execute handler
6838
+ await runInInjectionContext(this.injector, () => action.handler(context));
6746
6839
  }
6747
6840
  /**
6748
- * Format message preview text
6841
+ * Clear all actions
6749
6842
  */
6750
- static getPreviewText(message, maxLength = 50) {
6751
- // Handle different message types
6752
- switch (message.type) {
6753
- case 'text': {
6754
- const textPayload = message.payload;
6755
- const text = textPayload.text || '';
6756
- const truncatedText = text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
6757
- return {
6758
- value: truncatedText,
6759
- type: 'text',
6760
- icon: 'fa-light fa-message',
6761
- };
6762
- }
6763
- case 'image': {
6764
- const imagePayload = normalizeImagePayload(message.payload);
6765
- const first = imagePayload.images[0];
6766
- const label = imagePayload.caption?.trim() ||
6767
- (imagePayload.images && imagePayload.images.length > 1
6768
- ? `${imagePayload.images.length} images`
6769
- : '');
6770
- return {
6771
- value: label || first?.thumbnailUrl || first?.url || '',
6772
- type: 'image',
6773
- icon: 'fa-light fa-image',
6774
- };
6775
- }
6776
- case 'video': {
6777
- const videoPayload = normalizeVideoPayload(message.payload);
6778
- const first = videoPayload.videos[0];
6779
- const label = videoPayload.caption?.trim() ||
6780
- (videoPayload.videos.length > 1 ? `${videoPayload.videos.length} videos` : '');
6781
- return {
6782
- value: label || first?.url || '',
6783
- type: 'video',
6784
- icon: 'fa-light fa-video',
6785
- };
6786
- }
6787
- case 'audio': {
6788
- const audioPayload = normalizeAudioPayload(message.payload);
6789
- const names = audioPayload.audios.map((a) => a.title).filter(Boolean).join(', ');
6790
- const first = audioPayload.audios[0];
6791
- const label = audioPayload.caption?.trim() ||
6792
- (audioPayload.audios.length > 1 ? `${audioPayload.audios.length} audio files` : names);
6793
- return {
6794
- value: label || first?.url || '',
6795
- type: 'audio',
6796
- icon: 'fa-light fa-music',
6797
- };
6798
- }
6799
- case 'voice': {
6800
- const voicePayload = message.payload;
6801
- return {
6802
- value: voicePayload.url || '',
6803
- type: 'voice',
6804
- icon: 'fa-light fa-microphone',
6805
- };
6806
- }
6807
- case 'file': {
6808
- const filePayload = normalizeFilePayload(message.payload);
6809
- const names = filePayload.files.map((f) => f.name).join(', ');
6810
- const label = filePayload.caption?.trim() ||
6811
- (filePayload.files.length > 1 ? `${filePayload.files.length} files` : names);
6812
- return {
6813
- value: label || names,
6814
- type: 'file',
6815
- icon: 'fa-light fa-file',
6816
- };
6817
- }
6818
- case 'location': {
6819
- const locationPayload = message.payload;
6820
- return {
6821
- value: locationPayload.latitude && locationPayload.longitude
6822
- ? `${locationPayload.latitude},${locationPayload.longitude}`
6823
- : '',
6824
- type: 'location',
6825
- icon: 'fa-light fa-location-dot',
6826
- };
6827
- }
6828
- case 'sticker': {
6829
- const stickerPayload = message.payload;
6830
- return {
6831
- value: stickerPayload.url || '',
6832
- type: 'sticker',
6833
- icon: 'fa-light fa-face-smile',
6834
- };
6835
- }
6836
- default:
6837
- return {
6838
- value: '',
6839
- type: message.type,
6840
- icon: 'fa-light fa-message',
6841
- };
6842
- }
6843
+ clear() {
6844
+ this._actions.set([]);
6845
+ }
6846
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
6847
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry }); }
6848
+ }
6849
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXMessageActionRegistry, decorators: [{
6850
+ type: Injectable
6851
+ }], ctorParameters: () => [] });
6852
+
6853
+ /**
6854
+ * IndexedDB Storage Wrapper
6855
+ * Provides persistent storage for conversation data
6856
+ */
6857
+ const DB_NAME = 'acorex-conversation-db';
6858
+ const DB_VERSION = 2;
6859
+ // Store names
6860
+ const AXConversationIndexedDbStores = {
6861
+ PARTICIPANTS: 'participants',
6862
+ CONVERSATIONS: 'conversations',
6863
+ MESSAGES: 'messages',
6864
+ MEDIA: 'media',
6865
+ SETTINGS: 'settings',
6866
+ };
6867
+ class AXConversationIndexedDbStorage {
6868
+ constructor() {
6869
+ this.db = null;
6870
+ this.initPromise = null;
6843
6871
  }
6844
6872
  /**
6845
- * Check if message is from current user
6873
+ * Initialize IndexedDB
6846
6874
  */
6847
- static isOwnMessage(message, currentUserId) {
6848
- return message.senderId === currentUserId;
6875
+ async init() {
6876
+ if (this.db)
6877
+ return;
6878
+ if (this.initPromise)
6879
+ return this.initPromise;
6880
+ this.initPromise = new Promise((resolve, reject) => {
6881
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
6882
+ request.onerror = () => reject(request.error);
6883
+ request.onsuccess = () => {
6884
+ this.db = request.result;
6885
+ resolve();
6886
+ };
6887
+ request.onupgradeneeded = (event) => {
6888
+ const db = event.target.result;
6889
+ // Create object stores if they don't exist
6890
+ if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.PARTICIPANTS)) {
6891
+ db.createObjectStore(AXConversationIndexedDbStores.PARTICIPANTS, { keyPath: 'id' });
6892
+ }
6893
+ if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.CONVERSATIONS)) {
6894
+ const convStore = db.createObjectStore(AXConversationIndexedDbStores.CONVERSATIONS, { keyPath: 'id' });
6895
+ convStore.createIndex('lastMessageAt', 'lastMessageAt', { unique: false });
6896
+ }
6897
+ if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MESSAGES)) {
6898
+ const msgStore = db.createObjectStore(AXConversationIndexedDbStores.MESSAGES, { keyPath: 'id' });
6899
+ msgStore.createIndex('conversationId', 'conversationId', { unique: false });
6900
+ msgStore.createIndex('timestamp', 'timestamp', { unique: false });
6901
+ }
6902
+ if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.SETTINGS)) {
6903
+ db.createObjectStore(AXConversationIndexedDbStores.SETTINGS, { keyPath: 'key' });
6904
+ }
6905
+ if (!db.objectStoreNames.contains(AXConversationIndexedDbStores.MEDIA)) {
6906
+ db.createObjectStore(AXConversationIndexedDbStores.MEDIA, { keyPath: 'id' });
6907
+ }
6908
+ };
6909
+ });
6910
+ return this.initPromise;
6849
6911
  }
6850
6912
  /**
6851
- * Get message status icon class (Font Awesome)
6913
+ * Get a value from a store
6852
6914
  */
6853
- static getStatusIcon(message) {
6854
- switch (message.status) {
6855
- case 'sending':
6856
- return 'fa-light fa-clock';
6857
- case 'sent':
6858
- return 'fa-light fa-check';
6859
- case 'delivered':
6860
- return 'fa-light fa-check-double';
6861
- case 'read':
6862
- return 'fa-light fa-check-double';
6863
- case 'failed':
6864
- return 'fa-light fa-circle-exclamation';
6865
- default:
6866
- return 'fa-light fa-check';
6867
- }
6915
+ async get(storeName, key) {
6916
+ await this.init();
6917
+ if (!this.db)
6918
+ throw new Error('Database not initialized');
6919
+ const db = this.db;
6920
+ return new Promise((resolve, reject) => {
6921
+ const transaction = db.transaction(storeName, 'readonly');
6922
+ const store = transaction.objectStore(storeName);
6923
+ const request = store.get(key);
6924
+ request.onsuccess = () => resolve(request.result);
6925
+ request.onerror = () => reject(request.error);
6926
+ });
6868
6927
  }
6869
6928
  /**
6870
- * Check if message should show avatar
6929
+ * Get all values from a store
6871
6930
  */
6872
- static shouldShowAvatar(message, previousMessage, conversation) {
6873
- // Always show avatar for group conversations
6874
- if (conversation.type === 'group' || conversation.type === 'channel') {
6875
- // Don't show if same sender as previous message within 5 minutes
6876
- if (previousMessage && previousMessage.senderId === message.senderId) {
6877
- const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
6878
- return timeDiff > 5 * 60 * 1000; // 5 minutes
6879
- }
6880
- return true;
6881
- }
6882
- return false;
6931
+ async getAll(storeName) {
6932
+ await this.init();
6933
+ if (!this.db)
6934
+ throw new Error('Database not initialized');
6935
+ const db = this.db;
6936
+ return new Promise((resolve, reject) => {
6937
+ const transaction = db.transaction(storeName, 'readonly');
6938
+ const store = transaction.objectStore(storeName);
6939
+ const request = store.getAll();
6940
+ request.onsuccess = () => resolve(request.result);
6941
+ request.onerror = () => reject(request.error);
6942
+ });
6883
6943
  }
6884
6944
  /**
6885
- * Group messages by sender for consecutive messages
6945
+ * Get values by index
6886
6946
  */
6887
- static shouldGroupWithPrevious(message, previousMessage) {
6888
- if (!previousMessage)
6889
- return false;
6890
- // Same sender
6891
- if (message.senderId !== previousMessage.senderId)
6892
- return false;
6893
- // Within 5 minutes
6894
- const timeDiff = message.timestamp.getTime() - previousMessage.timestamp.getTime();
6895
- return timeDiff < 5 * 60 * 1000;
6947
+ async getAllByIndex(storeName, indexName, query) {
6948
+ await this.init();
6949
+ if (!this.db)
6950
+ throw new Error('Database not initialized');
6951
+ const db = this.db;
6952
+ return new Promise((resolve, reject) => {
6953
+ const transaction = db.transaction(storeName, 'readonly');
6954
+ const store = transaction.objectStore(storeName);
6955
+ const index = store.index(indexName);
6956
+ const request = index.getAll(query);
6957
+ request.onsuccess = () => resolve(request.result);
6958
+ request.onerror = () => reject(request.error);
6959
+ });
6896
6960
  }
6897
6961
  /**
6898
- * Get conversation status for avatar (private conversations only)
6962
+ * Put a value into a store
6899
6963
  */
6900
- static getConversationStatus(conversation) {
6901
- if (conversation.type === 'private') {
6902
- return conversation.status.presence;
6903
- }
6904
- return undefined;
6964
+ async put(storeName, value) {
6965
+ await this.init();
6966
+ if (!this.db)
6967
+ throw new Error('Database not initialized');
6968
+ const db = this.db;
6969
+ return new Promise((resolve, reject) => {
6970
+ const transaction = db.transaction(storeName, 'readwrite');
6971
+ const store = transaction.objectStore(storeName);
6972
+ const request = store.put(value);
6973
+ request.onsuccess = () => resolve();
6974
+ request.onerror = () => reject(request.error);
6975
+ });
6905
6976
  }
6906
6977
  /**
6907
- * Get typing indicator text for conversation
6978
+ * Delete a value from a store
6908
6979
  */
6909
- static getTypingText(conversation) {
6910
- const typingUsers = conversation.status.typingUsers;
6911
- if (typingUsers.length === 0)
6912
- return '';
6913
- if (conversation.type === 'private') {
6914
- return translateSync('@acorex:chat.status.typing');
6915
- }
6916
- const firstUser = conversation.participants.find((p) => p.id === typingUsers[0]);
6917
- if (typingUsers.length === 1) {
6918
- return translateSync('@acorex:chat.status.user-is-typing', {
6919
- params: { userName: firstUser?.name || translateSync('@acorex:chat.fallbacks.someone') },
6920
- });
6921
- }
6922
- return translateSync('@acorex:chat.status.people-typing', { params: { count: typingUsers.length } });
6980
+ async delete(storeName, key) {
6981
+ await this.init();
6982
+ if (!this.db)
6983
+ throw new Error('Database not initialized');
6984
+ const db = this.db;
6985
+ return new Promise((resolve, reject) => {
6986
+ const transaction = db.transaction(storeName, 'readwrite');
6987
+ const store = transaction.objectStore(storeName);
6988
+ const request = store.delete(key);
6989
+ request.onsuccess = () => resolve();
6990
+ request.onerror = () => reject(request.error);
6991
+ });
6923
6992
  }
6924
6993
  /**
6925
- * Format last seen time
6994
+ * Clear all data from a store
6926
6995
  */
6927
- static formatLastSeen(date) {
6928
- const now = new Date();
6929
- const diff = now.getTime() - date.getTime();
6930
- const seconds = Math.floor(diff / 1000);
6931
- if (seconds < 60)
6932
- return translateSync('@acorex:chat.time.just-now');
6933
- if (seconds < 3600)
6934
- return translateSync('@acorex:chat.time.minutes-ago', { params: { count: Math.floor(seconds / 60) } });
6935
- if (seconds < 86400)
6936
- return translateSync('@acorex:chat.time.hours-ago', { params: { count: Math.floor(seconds / 3600) } });
6937
- if (seconds < 604800)
6938
- return translateSync('@acorex:chat.time.days-ago', { params: { count: Math.floor(seconds / 86400) } });
6939
- return date.toLocaleDateString();
6996
+ async clear(storeName) {
6997
+ await this.init();
6998
+ if (!this.db)
6999
+ throw new Error('Database not initialized');
7000
+ const db = this.db;
7001
+ return new Promise((resolve, reject) => {
7002
+ const transaction = db.transaction(storeName, 'readwrite');
7003
+ const store = transaction.objectStore(storeName);
7004
+ const request = store.clear();
7005
+ request.onsuccess = () => resolve();
7006
+ request.onerror = () => reject(request.error);
7007
+ });
6940
7008
  }
6941
- /**
6942
- * Get conversation subtitle (status or member count)
6943
- */
6944
- static getConversationSubtitle(conversation) {
6945
- if (conversation.status.isTyping) {
6946
- return AXConversationMessageUtilsService.getTypingText(conversation);
6947
- }
6948
- switch (conversation.type) {
6949
- case 'private':
6950
- if (conversation.status.presence === 'online') {
6951
- return translateSync('@acorex:chat.status.online');
6952
- }
6953
- if (conversation.status.lastSeen) {
6954
- return translateSync('@acorex:chat.status.last-seen', {
6955
- params: { value: AXConversationMessageUtilsService.formatLastSeen(conversation.status.lastSeen) },
6956
- });
6957
- }
6958
- return translateSync('@acorex:chat.status.offline');
6959
- case 'group':
6960
- return translateSync('@acorex:chat.members.count', { params: { count: conversation.participants.length } });
6961
- case 'channel':
6962
- return translateSync('@acorex:chat.members.subscribers-count', { params: { count: conversation.participants.length } });
6963
- case 'bot':
6964
- return translateSync('@acorex:chat.bot');
6965
- default:
6966
- return '';
6967
- }
7009
+ // =====================
7010
+ // Convenience Methods
7011
+ // =====================
7012
+ async getParticipant(id) {
7013
+ return this.get(AXConversationIndexedDbStores.PARTICIPANTS, id);
7014
+ }
7015
+ async getAllParticipants() {
7016
+ return this.getAll(AXConversationIndexedDbStores.PARTICIPANTS);
7017
+ }
7018
+ async putParticipant(participant) {
7019
+ return this.put(AXConversationIndexedDbStores.PARTICIPANTS, participant);
7020
+ }
7021
+ async getConversation(id) {
7022
+ return this.get(AXConversationIndexedDbStores.CONVERSATIONS, id);
7023
+ }
7024
+ async getAllConversations() {
7025
+ return this.getAll(AXConversationIndexedDbStores.CONVERSATIONS);
7026
+ }
7027
+ async putConversation(conversation) {
7028
+ return this.put(AXConversationIndexedDbStores.CONVERSATIONS, conversation);
7029
+ }
7030
+ async deleteConversation(id) {
7031
+ return this.delete(AXConversationIndexedDbStores.CONVERSATIONS, id);
7032
+ }
7033
+ async getMessage(id) {
7034
+ return this.get(AXConversationIndexedDbStores.MESSAGES, id);
7035
+ }
7036
+ async getAllMessages() {
7037
+ return this.getAll(AXConversationIndexedDbStores.MESSAGES);
7038
+ }
7039
+ async getMessagesByConversation(conversationId) {
7040
+ return this.getAllByIndex(AXConversationIndexedDbStores.MESSAGES, 'conversationId', conversationId);
7041
+ }
7042
+ async putMessage(message) {
7043
+ return this.put(AXConversationIndexedDbStores.MESSAGES, message);
7044
+ }
7045
+ async deleteMessage(id) {
7046
+ return this.delete(AXConversationIndexedDbStores.MESSAGES, id);
7047
+ }
7048
+ async getSetting(key) {
7049
+ const result = await this.get(AXConversationIndexedDbStores.SETTINGS, key);
7050
+ return result?.value;
7051
+ }
7052
+ async putSetting(key, value) {
7053
+ return this.put(AXConversationIndexedDbStores.SETTINGS, { key, value });
7054
+ }
7055
+ async getMedia(id) {
7056
+ return this.get(AXConversationIndexedDbStores.MEDIA, id);
7057
+ }
7058
+ async putMedia(record) {
7059
+ return this.put(AXConversationIndexedDbStores.MEDIA, record);
7060
+ }
7061
+ async deleteMedia(id) {
7062
+ return this.delete(AXConversationIndexedDbStores.MEDIA, id);
6968
7063
  }
6969
7064
  }
7065
+ // Export singleton instance
7066
+ const axConversationIndexedDbStorage = new AXConversationIndexedDbStorage();
7067
+
7068
+ var indexeddbStorage = /*#__PURE__*/Object.freeze({
7069
+ __proto__: null,
7070
+ AXConversationIndexedDbStorage: AXConversationIndexedDbStorage,
7071
+ AXConversationIndexedDbStores: AXConversationIndexedDbStores,
7072
+ axConversationIndexedDbStorage: axConversationIndexedDbStorage
7073
+ });
6970
7074
 
6971
7075
  /**
6972
7076
  * Shared read/write helpers for IndexedDB chat storage (in-memory + persistence).
@@ -8412,20 +8516,27 @@ class AXConversationIndexedDbConversationApi extends AXConversationApi {
8412
8516
  // Conversation CRUD
8413
8517
  // =====================
8414
8518
  async createConversation(data) {
8519
+ const creatorId = data.creatorId ?? conversationSharedStorage.currentUserId;
8520
+ const participantIds = new Set(data.participantIds);
8521
+ if (creatorId) {
8522
+ participantIds.add(creatorId);
8523
+ }
8415
8524
  const conversation = {
8416
8525
  id: `conv-${Date.now()}`,
8417
8526
  type: data.type,
8418
- title: data.title || (data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
8527
+ creatorId,
8528
+ title: data.title ||
8529
+ (data.type === 'private' ? 'New Chat' : data.type === 'group' ? 'New Group' : 'New Channel'),
8419
8530
  avatar: data.avatar,
8420
8531
  description: data.description,
8421
- participants: data.participantIds.map((id) => {
8532
+ participants: [...participantIds].map((id) => {
8422
8533
  const participant = conversationSharedStorage.participants.get(id);
8423
8534
  if (!participant) {
8424
8535
  throw this.createError('PARTICIPANT_NOT_FOUND', `Participant ${id} not found`, 404);
8425
8536
  }
8426
8537
  return {
8427
8538
  ...participant,
8428
- role: id === conversationSharedStorage.currentUserId ? 'admin' : 'member',
8539
+ role: id === creatorId ? 'admin' : 'member',
8429
8540
  };
8430
8541
  }),
8431
8542
  lastMessageAt: new Date(),
@@ -10395,41 +10506,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
10395
10506
  `, styles: [":host{display:block}.avatar-picker{display:flex;flex-flow:row wrap;align-items:center;gap:1.5rem;padding:1rem;border-radius:.75rem;border:1px solid rgb(var(--ax-sys-color-border-light-surface))}.avatar-preview{position:relative;width:100px;height:100px;border-radius:50%;overflow:hidden;background:rgb(var(--ax-sys-color-lighter-surface));border:3px solid rgb(var(--ax-sys-color-border-light-surface));display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .25s ease;flex-shrink:0;box-shadow:0 2px 8px #00000014}.avatar-preview:hover{border-color:rgb(var(--ax-sys-color-primary-500));// transform: scale(1.05);box-shadow:0 4px 12px #0000001f}.avatar-preview:hover .preview-overlay{opacity:1}.avatar-preview ax-image{width:100%;height:100%}.preview-overlay{position:absolute;top:0;right:0;bottom:0;left:0;background:#000000a6;display:flex;align-items:center;justify-content:center;opacity:0;transition:opacity .25s ease;color:#fff;font-size:1.75rem}.avatar-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.375rem;font-size:2rem;color:rgb(var(--ax-sys-color-on-surface-variant));text-align:center;padding:.5rem}.placeholder-text{font-size:.625rem;font-weight:600;text-transform:uppercase;letter-spacing:.08em;opacity:.8}.avatar-actions{display:flex;flex-direction:column;gap:.5rem;flex:1}.avatar-actions ax-button{width:100%}\n"] }]
10396
10507
  }] });
10397
10508
 
10398
- /** Other participant in a private chat (excludes the current user). */
10399
- function resolvePrivatePeerUserId(conversation, currentUserId) {
10400
- if (conversation.type !== 'private') {
10401
- return undefined;
10402
- }
10403
- const currentId = currentUserId ?? 'current-user';
10404
- return conversation.participants.find((participant) => participant.id !== currentId)?.id;
10405
- }
10406
- /** Whether `auto` kind should render a user avatar for this conversation. */
10407
- function shouldUseUserAvatarForConversation(conversation, currentUserId) {
10408
- return conversation.type === 'private' && !!resolvePrivatePeerUserId(conversation, currentUserId);
10409
- }
10410
- function resolveUserAvatarDisplay(userId, conversation, message) {
10411
- if (message && conversation) {
10412
- return {
10413
- name: AXConversationMessageUtilsService.getSenderName(message, conversation),
10414
- avatar: AXConversationMessageUtilsService.getSenderAvatar(message, conversation),
10415
- icon: AXConversationMessageUtilsService.getSenderAvatarIcon(message, conversation),
10416
- };
10417
- }
10418
- const participant = conversation?.participants.find((p) => p.id === userId);
10419
- return {
10420
- name: participant?.name ?? userId,
10421
- avatar: participant?.avatar,
10422
- icon: participant?.icon,
10423
- };
10424
- }
10425
- function resolveConversationAvatarDisplay(conversation) {
10426
- return {
10427
- name: conversation.title,
10428
- avatar: AXConversationMessageUtilsService.getConversationAvatar(conversation),
10429
- icon: AXConversationMessageUtilsService.getConversationAvatarIcon(conversation),
10430
- };
10431
- }
10432
-
10433
10509
  /**
10434
10510
  * Conversation avatar host — renders a registered custom component or built-in fallback.
10435
10511
  */
@@ -10514,7 +10590,7 @@ class AXConversationAvatarComponent {
10514
10590
  if (!conversation) {
10515
10591
  return { name: explicitName ?? '?', avatar: explicitAvatar, icon: explicitIcon };
10516
10592
  }
10517
- const resolved = resolveConversationAvatarDisplay(conversation);
10593
+ const resolved = resolveConversationAvatarDisplay(conversation, this.currentUserId());
10518
10594
  return {
10519
10595
  name: explicitName ?? resolved.name,
10520
10596
  avatar: explicitAvatar ?? resolved.avatar,
@@ -14918,14 +14994,23 @@ class AXConversationService {
14918
14994
  this._messageDeleted$ = new Subject();
14919
14995
  this._typingIndicator$ = new Subject();
14920
14996
  this._presenceUpdate$ = new Subject();
14921
- /** All conversations */
14922
- this.conversations = this.state.conversations;
14997
+ /** All conversations (viewer-scoped display titles for private 1v1 chats) */
14998
+ this.conversations = computed(() => {
14999
+ const currentUserId = this._currentUser()?.id;
15000
+ return this.state.conversations().map((conversation) => resolveConversationForViewer(conversation, currentUserId));
15001
+ }, ...(ngDevMode ? [{ debugName: "conversations" }] : []));
14923
15002
  /** Active conversation ID */
14924
15003
  this.activeConversationId = this._activeConversationId.asReadonly();
14925
15004
  /** Active conversation */
14926
15005
  this.activeConversation = computed(() => {
14927
15006
  const id = this._activeConversationId();
14928
- return id ? this.state.getConversation(id) : null;
15007
+ if (!id) {
15008
+ return null;
15009
+ }
15010
+ const conversation = this.state.getConversation(id);
15011
+ return conversation
15012
+ ? resolveConversationForViewer(conversation, this._currentUser()?.id)
15013
+ : null;
14929
15014
  }, ...(ngDevMode ? [{ debugName: "activeConversation" }] : []));
14930
15015
  /** Messages for active conversation */
14931
15016
  this.activeMessages = computed(() => {
@@ -15716,17 +15801,29 @@ class AXConversationService {
15716
15801
  */
15717
15802
  async createConversation(participantIds, type, metadata) {
15718
15803
  try {
15804
+ const currentUser = this._currentUser();
15805
+ const creatorId = currentUser?.id;
15806
+ const isPrivate = type === 'private';
15807
+ const scopedParticipantIds = [...participantIds];
15808
+ if (creatorId && !scopedParticipantIds.includes(creatorId)) {
15809
+ scopedParticipantIds.unshift(creatorId);
15810
+ }
15719
15811
  const conversation = await this.conversationApi.createConversation({
15720
15812
  type,
15721
- participantIds,
15722
- title: metadata?.['title'],
15813
+ participantIds: scopedParticipantIds,
15814
+ creatorId,
15815
+ title: isPrivate ? undefined : metadata?.['title'],
15723
15816
  description: metadata?.['description'],
15724
- avatar: AXConversationService.normalizeOptionalString(metadata?.['avatar']),
15725
- icon: AXConversationService.normalizeOptionalString(metadata?.['icon']),
15726
- metadata,
15817
+ avatar: isPrivate
15818
+ ? undefined
15819
+ : AXConversationService.normalizeOptionalString(metadata?.['avatar']),
15820
+ icon: isPrivate
15821
+ ? undefined
15822
+ : AXConversationService.normalizeOptionalString(metadata?.['icon']),
15823
+ metadata: isPrivate ? undefined : metadata,
15727
15824
  });
15728
15825
  this.state.setConversation(conversation);
15729
- return conversation;
15826
+ return resolveConversationForViewer(conversation, creatorId);
15730
15827
  }
15731
15828
  catch (error) {
15732
15829
  this.errorHandler.handle(error, 'createConversation', { participantIds, type });
@@ -20955,12 +21052,7 @@ class AXNewConversationDialogComponent extends AXBasePageComponent {
20955
21052
  try {
20956
21053
  let conversation;
20957
21054
  if (selectedIds.length === 1) {
20958
- const selectedUser = this.availableUsers().find((u) => u.id === selectedIds[0]);
20959
- const metadata = {
20960
- title: selectedUser?.name,
20961
- avatar: selectedUser?.avatar,
20962
- };
20963
- conversation = await this.conversationService.createConversation(selectedIds, 'private', metadata);
21055
+ conversation = await this.conversationService.createConversation(selectedIds, 'private');
20964
21056
  }
20965
21057
  else {
20966
21058
  const metadata = {
@@ -22174,5 +22266,5 @@ function getErrorMessage(code, params) {
22174
22266
  * Generated bundle index. Do not edit.
22175
22267
  */
22176
22268
 
22177
- export { AXAudioPickerComponent, AXAudioRendererComponent, AXBaseRegistry, AXCnvMediaPlaybackInfoBarBannerComponent, AXComposerActionRegistry, AXComposerComponent, AXComposerPopupComponent, AXComposerService, AXComposerTabRegistry, AXConversation2Module, AXConversationAiApiKey, AXConversationAiResponderService, AXConversationApi, AXConversationContainerComponent, AXConversationContainerDirective, AXConversationDateUtilsService, AXConversationIndexedDbConversationApi, AXConversationIndexedDbMessageAiApi, AXConversationIndexedDbMessageApi, AXConversationIndexedDbRealtimeApi, AXConversationIndexedDbStorage, AXConversationIndexedDbStores, AXConversationIndexedDbUserApi, AXConversationInfoMediaViewComponent, AXConversationInfoPanelComponent, AXConversationItemActionRegistry, AXConversationMessageRendererStateComponent, AXConversationMessageUtilsService, AXConversationService, AXConversationSharedStorage, AXConversationTabRegistry, AXEmojiTabComponent, AXErrorHandlerService, AXFallbackRendererComponent, AXFilePickerComponent, AXFileRendererComponent, AXForwardMessageDialogComponent, AXImagePickerComponent, AXImageRendererComponent, AXInfiniteScrollDirective, AXInfoBarActionRegistry, AXInfoBarComponent, AXInfoBarSearchComponent, AXInfoBarService, AXLocationPickerComponent, AXLocationRendererComponent, AXMessageActionRegistry, AXMessageApi, AXMessageListComponent, AXMessageListNoActiveDefaultComponent, AXMessageListService, AXMessageRendererRegistry, AXNewConversationDialogComponent, AXPickerFooterComponent, AXPickerHeaderComponent, AXRealtimeApi, AXRegistryService, AXSidebarComponent, AXSidebarService, AXStickerRendererComponent, AXStickerTabComponent, AXSystemRendererComponent, AXTextRendererComponent, AXUserApi, AXVideoPickerComponent, AXVideoRendererComponent, AXVoiceRecorderComponent, AXVoiceRendererComponent, AX_CONVERSATION_AUDIO_RENDERER, AX_CONVERSATION_COMPOSER_AUDIO_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_TAB, AX_CONVERSATION_COMPOSER_FILE_ACTION, AX_CONVERSATION_COMPOSER_FILE_TYPE_PROVIDERS, AX_CONVERSATION_COMPOSER_IMAGE_ACTION, AX_CONVERSATION_COMPOSER_LOCATION_ACTION, AX_CONVERSATION_COMPOSER_STICKER_TAB, AX_CONVERSATION_COMPOSER_VIDEO_ACTION, AX_CONVERSATION_COMPOSER_VOICE_RECORDING_ACTION, AX_CONVERSATION_CONVERSATION_AVATAR_COMPONENT, AX_CONVERSATION_CURSOR_PREFIX, AX_CONVERSATION_FALLBACK_RENDERER, AX_CONVERSATION_FILE_RENDERER, AX_CONVERSATION_IMAGE_RENDERER, AX_CONVERSATION_INFO_BAR_ARCHIVE_ACTION, AX_CONVERSATION_INFO_BAR_BLOCK_ACTION, AX_CONVERSATION_INFO_BAR_DELETE_ACTION, AX_CONVERSATION_INFO_BAR_DIVIDER, AX_CONVERSATION_INFO_BAR_MUTE_ACTION, AX_CONVERSATION_INFO_BAR_SEARCH_ACTION, AX_CONVERSATION_ITEM_BLOCK_ACTION, AX_CONVERSATION_ITEM_DELETE_ACTION, AX_CONVERSATION_ITEM_DIVIDER, AX_CONVERSATION_ITEM_MARK_READ_ACTION, AX_CONVERSATION_ITEM_MUTE_ACTION, AX_CONVERSATION_ITEM_PIN_ACTION, AX_CONVERSATION_LOCATION_RENDERER, AX_CONVERSATION_MESSAGE_DELETE_ACTION, AX_CONVERSATION_MESSAGE_EDIT_ACTION, AX_CONVERSATION_MESSAGE_FORWARD_ACTION, AX_CONVERSATION_MESSAGE_REPLY_ACTION, AX_CONVERSATION_STICKER_RENDERER, AX_CONVERSATION_SYSTEM_RENDERER, AX_CONVERSATION_TAB_ALL, AX_CONVERSATION_TAB_ARCHIVED, AX_CONVERSATION_TAB_BOT, AX_CONVERSATION_TAB_CHANNELS, AX_CONVERSATION_TAB_GROUPS, AX_CONVERSATION_TAB_PRIVATE, AX_CONVERSATION_TAB_UNREAD, AX_CONVERSATION_TEXT_RENDERER, AX_CONVERSATION_USER_AVATAR_COMPONENT, AX_CONVERSATION_VIDEO_RENDERER, AX_CONVERSATION_VOICE_RENDERER, AX_DEFAULT_CONVERSATION_CONFIG, AX_MESSAGE_CURSOR_PREFIX, AX_STICKER_API_KEY, CONNECTION_ERRORS, CONVERSATION_CONFIG, CONVERSATION_ERRORS, CONVERSATION_FILE_TYPES_READY, DEFAULT_COMPOSER_ACTIONS, DEFAULT_COMPOSER_TABS, DEFAULT_CONVERSATION_ITEM_ACTIONS, DEFAULT_CONVERSATION_TABS, DEFAULT_INFO_BAR_ACTIONS, DEFAULT_MESSAGE_ACTIONS, DEFAULT_MESSAGE_RENDERERS, ERROR_HANDLER_CONFIG, ERROR_MESSAGES, FILE_ERRORS, LOCATION_ERRORS, MESSAGE_ERRORS, REGISTRY_CONFIG, URL_ERRORS, USER_ERRORS, applyConversationFilters, applyLocalPreview, axConversationIndexedDbStorage, conversationSharedStorage, createLocalPreviewUrl, createObjectUrl, encodeConversationCursor, encodeMessageCursor, formatErrorMessage, getConversationLastActivity, getConversationMessagesNewestFirst, getErrorMessage, getSortedConversationsForInbox, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveParticipantProfile, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
22269
+ export { AXAudioPickerComponent, AXAudioRendererComponent, AXBaseRegistry, AXCnvMediaPlaybackInfoBarBannerComponent, AXComposerActionRegistry, AXComposerComponent, AXComposerPopupComponent, AXComposerService, AXComposerTabRegistry, AXConversation2Module, AXConversationAiApiKey, AXConversationAiResponderService, AXConversationApi, AXConversationContainerComponent, AXConversationContainerDirective, AXConversationDateUtilsService, AXConversationIndexedDbConversationApi, AXConversationIndexedDbMessageAiApi, AXConversationIndexedDbMessageApi, AXConversationIndexedDbRealtimeApi, AXConversationIndexedDbStorage, AXConversationIndexedDbStores, AXConversationIndexedDbUserApi, AXConversationInfoMediaViewComponent, AXConversationInfoPanelComponent, AXConversationItemActionRegistry, AXConversationMessageRendererStateComponent, AXConversationMessageUtilsService, AXConversationService, AXConversationSharedStorage, AXConversationTabRegistry, AXEmojiTabComponent, AXErrorHandlerService, AXFallbackRendererComponent, AXFilePickerComponent, AXFileRendererComponent, AXForwardMessageDialogComponent, AXImagePickerComponent, AXImageRendererComponent, AXInfiniteScrollDirective, AXInfoBarActionRegistry, AXInfoBarComponent, AXInfoBarSearchComponent, AXInfoBarService, AXLocationPickerComponent, AXLocationRendererComponent, AXMessageActionRegistry, AXMessageApi, AXMessageListComponent, AXMessageListNoActiveDefaultComponent, AXMessageListService, AXMessageRendererRegistry, AXNewConversationDialogComponent, AXPickerFooterComponent, AXPickerHeaderComponent, AXRealtimeApi, AXRegistryService, AXSidebarComponent, AXSidebarService, AXStickerRendererComponent, AXStickerTabComponent, AXSystemRendererComponent, AXTextRendererComponent, AXUserApi, AXVideoPickerComponent, AXVideoRendererComponent, AXVoiceRecorderComponent, AXVoiceRendererComponent, AX_CONVERSATION_AUDIO_RENDERER, AX_CONVERSATION_COMPOSER_AUDIO_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_ACTION, AX_CONVERSATION_COMPOSER_EMOJI_TAB, AX_CONVERSATION_COMPOSER_FILE_ACTION, AX_CONVERSATION_COMPOSER_FILE_TYPE_PROVIDERS, AX_CONVERSATION_COMPOSER_IMAGE_ACTION, AX_CONVERSATION_COMPOSER_LOCATION_ACTION, AX_CONVERSATION_COMPOSER_STICKER_TAB, AX_CONVERSATION_COMPOSER_VIDEO_ACTION, AX_CONVERSATION_COMPOSER_VOICE_RECORDING_ACTION, AX_CONVERSATION_CONVERSATION_AVATAR_COMPONENT, AX_CONVERSATION_CURSOR_PREFIX, AX_CONVERSATION_FALLBACK_RENDERER, AX_CONVERSATION_FILE_RENDERER, AX_CONVERSATION_IMAGE_RENDERER, AX_CONVERSATION_INFO_BAR_ARCHIVE_ACTION, AX_CONVERSATION_INFO_BAR_BLOCK_ACTION, AX_CONVERSATION_INFO_BAR_DELETE_ACTION, AX_CONVERSATION_INFO_BAR_DIVIDER, AX_CONVERSATION_INFO_BAR_MUTE_ACTION, AX_CONVERSATION_INFO_BAR_SEARCH_ACTION, AX_CONVERSATION_ITEM_BLOCK_ACTION, AX_CONVERSATION_ITEM_DELETE_ACTION, AX_CONVERSATION_ITEM_DIVIDER, AX_CONVERSATION_ITEM_MARK_READ_ACTION, AX_CONVERSATION_ITEM_MUTE_ACTION, AX_CONVERSATION_ITEM_PIN_ACTION, AX_CONVERSATION_LOCATION_RENDERER, AX_CONVERSATION_MESSAGE_DELETE_ACTION, AX_CONVERSATION_MESSAGE_EDIT_ACTION, AX_CONVERSATION_MESSAGE_FORWARD_ACTION, AX_CONVERSATION_MESSAGE_REPLY_ACTION, AX_CONVERSATION_STICKER_RENDERER, AX_CONVERSATION_SYSTEM_RENDERER, AX_CONVERSATION_TAB_ALL, AX_CONVERSATION_TAB_ARCHIVED, AX_CONVERSATION_TAB_BOT, AX_CONVERSATION_TAB_CHANNELS, AX_CONVERSATION_TAB_GROUPS, AX_CONVERSATION_TAB_PRIVATE, AX_CONVERSATION_TAB_UNREAD, AX_CONVERSATION_TEXT_RENDERER, AX_CONVERSATION_USER_AVATAR_COMPONENT, AX_CONVERSATION_VIDEO_RENDERER, AX_CONVERSATION_VOICE_RENDERER, AX_DEFAULT_CONVERSATION_CONFIG, AX_MESSAGE_CURSOR_PREFIX, AX_STICKER_API_KEY, CONNECTION_ERRORS, CONVERSATION_CONFIG, CONVERSATION_ERRORS, CONVERSATION_FILE_TYPES_READY, DEFAULT_COMPOSER_ACTIONS, DEFAULT_COMPOSER_TABS, DEFAULT_CONVERSATION_ITEM_ACTIONS, DEFAULT_CONVERSATION_TABS, DEFAULT_INFO_BAR_ACTIONS, DEFAULT_MESSAGE_ACTIONS, DEFAULT_MESSAGE_RENDERERS, ERROR_HANDLER_CONFIG, ERROR_MESSAGES, FILE_ERRORS, LOCATION_ERRORS, MESSAGE_ERRORS, REGISTRY_CONFIG, URL_ERRORS, USER_ERRORS, applyConversationFilters, applyLocalPreview, axConversationIndexedDbStorage, conversationSharedStorage, createLocalPreviewUrl, createObjectUrl, encodeConversationCursor, encodeMessageCursor, formatErrorMessage, getConversationLastActivity, getConversationMessagesNewestFirst, getErrorMessage, getSortedConversationsForInbox, isGenericPrivateConversationTitle, mergeUploadResult, mergeWithDefaults, normalizeAllConversationMessageIndexes, paginateChatNewestFirst, paginateChatOldestFirst, parseChatCursor, provideConversation, provideConversationComposerFileTypes, provideConversationFileCatalog, registerChatMessage, resolveConversationAvatarDisplay, resolveConversationForViewer, resolveConversationTitleForViewer, resolveParticipantProfile, resolvePrivatePeerParticipant, resolvePrivatePeerUserId, resolveUserAvatarDisplay, revokeObjectUrl, sanitizeInput, shouldUseUserAvatarForConversation, sortConversationMessageIds, toUploaderReference$1 as toUploaderReference, unregisterChatMessage, validateConversationId, validateEmail, validateLatitude, validateLongitude, validateMessagePayload, validateMessageText, validateMessageType, validateUrl, validateUserId, validateUserIds };
22178
22270
  //# sourceMappingURL=acorex-components-conversation2.mjs.map