@acorex-platform/module-communication-management-client 0.0.37 → 0.0.46

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.
Files changed (38) hide show
  1. package/fesm2022/{acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-CZh_tBTN.mjs → acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-BJvTWpBc.mjs} +268 -91
  2. package/fesm2022/acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-BJvTWpBc.mjs.map +1 -0
  3. package/fesm2022/{acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs → acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs} +2 -2
  4. package/fesm2022/{acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs.map → acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs.map} +1 -1
  5. package/fesm2022/{acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-D4o0Bhce.mjs → acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-yaRijsuh.mjs} +3 -3
  6. package/fesm2022/{acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-D4o0Bhce.mjs.map → acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-yaRijsuh.mjs.map} +1 -1
  7. package/fesm2022/{acorex-platform-module-communication-management-client-communication-send.command-BH9jjHfA.mjs → acorex-platform-module-communication-management-client-communication-send.command-D19heywC.mjs} +2 -2
  8. package/fesm2022/{acorex-platform-module-communication-management-client-communication-send.command-BH9jjHfA.mjs.map → acorex-platform-module-communication-management-client-communication-send.command-D19heywC.mjs.map} +1 -1
  9. package/fesm2022/{acorex-platform-module-communication-management-client-communication-test-smtp.command-BMsTVFBN.mjs → acorex-platform-module-communication-management-client-communication-test-smtp.command-Bck8R4as.mjs} +2 -2
  10. package/fesm2022/{acorex-platform-module-communication-management-client-communication-test-smtp.command-BMsTVFBN.mjs.map → acorex-platform-module-communication-management-client-communication-test-smtp.command-Bck8R4as.mjs.map} +1 -1
  11. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-communication-delivery.mjs +25 -0
  12. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-communication-delivery.mjs.map +1 -1
  13. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-communication.mjs +25 -0
  14. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-communication.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-entity-change-notify-policy.mjs +195 -0
  16. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entities-entity-change-notify-policy.mjs.map +1 -0
  17. package/fesm2022/{acorex-platform-module-communication-management-client-contracts-entity-catalog-acorex-platform-module-communication-management-client-contracts-entity-catalog-BvLoGDK5.mjs → acorex-platform-module-communication-management-client-contracts-entity-catalog-acorex-platform-module-communication-management-client-contracts-entity-catalog-DTHjzWFn.mjs} +15 -3
  18. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-acorex-platform-module-communication-management-client-contracts-entity-catalog-DTHjzWFn.mjs.map +1 -0
  19. package/fesm2022/{acorex-platform-module-communication-management-client-contracts-entity-catalog-communication-delivery.entity-DPCU8P32.mjs → acorex-platform-module-communication-management-client-contracts-entity-catalog-communication-delivery.entity-DN0_CPBO.mjs} +16 -2
  20. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-communication-delivery.entity-DN0_CPBO.mjs.map +1 -0
  21. package/fesm2022/{acorex-platform-module-communication-management-client-contracts-entity-catalog-communication.entity-DGj9OD3Z.mjs → acorex-platform-module-communication-management-client-contracts-entity-catalog-communication.entity-BAa4oCXu.mjs} +16 -2
  22. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-communication.entity-BAa4oCXu.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-entity-change-notify-policy.entity-DmM894ok.mjs +144 -0
  24. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-entity-change-notify-policy.entity-DmM894ok.mjs.map +1 -0
  25. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog.mjs +1 -1
  26. package/fesm2022/acorex-platform-module-communication-management-client-contracts.mjs +29 -1
  27. package/fesm2022/acorex-platform-module-communication-management-client-contracts.mjs.map +1 -1
  28. package/fesm2022/acorex-platform-module-communication-management-client-local-entities.mjs +1 -0
  29. package/fesm2022/acorex-platform-module-communication-management-client-local-entities.mjs.map +1 -1
  30. package/fesm2022/acorex-platform-module-communication-management-client.mjs +1 -1
  31. package/package.json +7 -3
  32. package/types/acorex-platform-module-communication-management-client-contracts-entities-entity-change-notify-policy.d.ts +5 -0
  33. package/types/acorex-platform-module-communication-management-client-contracts.d.ts +48 -2
  34. package/types/acorex-platform-module-communication-management-client.d.ts +17 -0
  35. package/fesm2022/acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-CZh_tBTN.mjs.map +0 -1
  36. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-acorex-platform-module-communication-management-client-contracts-entity-catalog-BvLoGDK5.mjs.map +0 -1
  37. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-communication-delivery.entity-DPCU8P32.mjs.map +0 -1
  38. package/fesm2022/acorex-platform-module-communication-management-client-contracts-entity-catalog-communication.entity-DGj9OD3Z.mjs.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { AXPSessionService, AXP_PERMISSION_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/auth';
2
- import { AXPLocalEntityEngine, AXPLocalEntityRealtimeBridge, AXP_IN_APP_NOTIFICATION_SERVICE, AXPSearchCommandProvider, AXP_PROTECTED_ROUTE_GUARDS, AXP_MENU_PROVIDER, AXP_STATUS_PROVIDERS, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
2
+ import { AXPLocalEntityEngine, AXPLocalEntityRealtimeBridge, AXPLocalEntityOutboxService, AXPNetworkConnectivityService, axpLocalEntitySupportsOfflineWrite, axpIsLikelyNetworkFailure, AXP_IN_APP_NOTIFICATION_SERVICE, AXPSearchCommandProvider, AXP_PROTECTED_ROUTE_GUARDS, AXP_MENU_PROVIDER, AXP_STATUS_PROVIDERS, AXP_SEARCH_PROVIDER, AXP_SETTING_DEFINITION_PROVIDER } from '@acorex-platform/framework-client/common';
3
3
  import * as i1$2 from '@acorex-platform/framework-client/core';
4
- import { provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY, AXPComponentSlotModule, AXP_MODULE_MANIFEST_PROVIDER } from '@acorex-platform/framework-client/core';
4
+ import { provideAppStartUpTask, AXP_APP_STARTUP_PRIORITY, AXPComponentSlotModule } from '@acorex-platform/framework-client/core';
5
5
  import { AXPUserAvatarComponent, AXPUserCardDirective, AXPStateMessageComponent, AXP_TASK_BADGE_PROVIDERS } from '@acorex-platform/framework-client/layout/components';
6
6
  import { AXPRootLayoutComponent } from '@acorex-platform/framework-client/themes/default';
7
7
  import { AXPCommandService, provideCommandSetups } from '@acorex-platform/framework-client/runtime';
@@ -342,12 +342,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
342
342
  const AXP_IN_APP_INBOX_PORT = new InjectionToken('AXP_IN_APP_INBOX_PORT');
343
343
  //#endregion
344
344
 
345
+ //#region ----- Inbox outbox actions -----
346
+ const AXP_IN_APP_INBOX_OUTBOX_ACTIONS = {
347
+ markSeen: 'markSeen',
348
+ markRead: 'markRead',
349
+ markAllRead: 'markAllRead',
350
+ togglePin: 'togglePin',
351
+ archive: 'archive',
352
+ delete: 'delete',
353
+ };
354
+ //#endregion
355
+
345
356
  //#region ----- Imports -----
346
357
  //#endregion
347
358
  //#region ----- Shared in-app inbox connector -----
348
359
  /**
349
360
  * Single source of truth for active in-app inbox UI.
350
361
  * Active items live in the shared local-entity engine (max 100) and sync via realtime / reconnect.
362
+ * Offline writes enqueue on the local-entity outbox and flush over HTTP on reconnect.
351
363
  * Archived views always load from the API.
352
364
  */
353
365
  class AXPInAppInboxConnectorService {
@@ -356,6 +368,8 @@ class AXPInAppInboxConnectorService {
356
368
  this.realtime = inject(AXPInAppInboxRealtimeService);
357
369
  this.engine = inject(AXPLocalEntityEngine);
358
370
  this.localBridge = inject(AXPLocalEntityRealtimeBridge);
371
+ this.outbox = inject(AXPLocalEntityOutboxService);
372
+ this.connectivity = inject(AXPNetworkConnectivityService);
359
373
  this.sessionService = inject(AXPSessionService);
360
374
  this.destroyRef = inject(DestroyRef);
361
375
  this.entityKey = AXP_IN_APP_INBOX_LOCAL_ENTITY_KEY;
@@ -419,7 +433,7 @@ class AXPInAppInboxConnectorService {
419
433
  this.unreadCount = computed(() => this.readLocalItems().filter((item) => isInAppInboxUnread(item.readAt)).length, /* @ts-ignore */
420
434
  ...(ngDevMode ? [{ debugName: "unreadCount" }] : /* istanbul ignore next */ []));
421
435
  this.engine.registerPolicy(this.entityKey, {
422
- mode: 'local-first',
436
+ mode: 'offline-write',
423
437
  maxItems: AXP_IN_APP_INBOX_LOCAL_MAX_ITEMS,
424
438
  sortField: 'createdAt',
425
439
  role: 'entity',
@@ -428,6 +442,10 @@ class AXPInAppInboxConnectorService {
428
442
  fetchByIds: this.port.loadByIds ? (ids) => this.fetchActiveByIds(ids) : undefined,
429
443
  fetchHot: () => this.fetchActiveHot(),
430
444
  });
445
+ this.outbox.register(this.entityKey, {
446
+ flush: (op) => this.flushOutboxOp(op),
447
+ revert: (op) => this.revertOutboxOp(op),
448
+ });
431
449
  this.sessionService.isReadyForAppScopedApi$
432
450
  .pipe(distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
433
451
  .subscribe((isReady) => {
@@ -495,13 +513,16 @@ class AXPInAppInboxConnectorService {
495
513
  }
496
514
  const now = new Date().toISOString();
497
515
  await this.engine.patchMany(this.entityKey, unseenIds, (item) => item['seenAt'] ? item : { ...item, seenAt: now });
498
- try {
499
- await this.port.markSeen(unseenIds);
500
- }
501
- catch (err) {
502
- this.error.set(err instanceof Error ? err.message : 'Failed to mark inbox as seen');
503
- await this.syncActiveFromServer();
504
- }
516
+ await this.commitOrEnqueue({
517
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markSeen,
518
+ recordIds: unseenIds,
519
+ payload: { seenAt: now },
520
+ commit: () => this.port.markSeen(unseenIds),
521
+ revert: async () => {
522
+ await this.syncActiveFromServer();
523
+ },
524
+ failureMessage: 'Failed to mark inbox as seen',
525
+ });
505
526
  }
506
527
  async markAsRead(ids) {
507
528
  if (ids.length === 0 || !this.port.capabilities.readTracking || !this.canLoadInbox()) {
@@ -514,13 +535,14 @@ class AXPInAppInboxConnectorService {
514
535
  seenAt: item['seenAt'] ?? now,
515
536
  readAt: item['readAt'] ?? now,
516
537
  }));
517
- try {
518
- await this.port.markRead(ids);
519
- }
520
- catch (err) {
521
- this.error.set(err instanceof Error ? err.message : 'Failed to mark inbox as read');
522
- await this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids });
523
- }
538
+ await this.commitOrEnqueue({
539
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markRead,
540
+ recordIds: ids,
541
+ payload: { readAt: now, seenAt: now },
542
+ commit: () => this.port.markRead(ids),
543
+ revert: () => this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids }),
544
+ failureMessage: 'Failed to mark inbox as read',
545
+ });
524
546
  }
525
547
  async markAllAsRead() {
526
548
  if (!this.port.capabilities.readTracking || !this.canLoadInbox()) {
@@ -535,13 +557,16 @@ class AXPInAppInboxConnectorService {
535
557
  }
536
558
  return { ...item, readAt: now, seenAt: item['seenAt'] ?? now };
537
559
  });
538
- try {
539
- await this.port.markAllRead();
540
- }
541
- catch (err) {
542
- this.error.set(err instanceof Error ? err.message : 'Failed to mark all as read');
543
- await this.syncActiveFromServer();
544
- }
560
+ await this.commitOrEnqueue({
561
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markAllRead,
562
+ recordIds: [],
563
+ payload: { readAt: now, seenAt: now },
564
+ commit: () => this.port.markAllRead(),
565
+ revert: async () => {
566
+ await this.syncActiveFromServer();
567
+ },
568
+ failureMessage: 'Failed to mark all as read',
569
+ });
545
570
  }
546
571
  async togglePin(id) {
547
572
  if (!this.port.capabilities.pinSupport || !this.port.togglePin || !this.canLoadInbox()) {
@@ -549,20 +574,24 @@ class AXPInAppInboxConnectorService {
549
574
  }
550
575
  await this.ensureReady();
551
576
  const current = this.readLocalItems().find((item) => item.id === id);
577
+ const nextPinned = current ? !current.isPinned : true;
552
578
  if (current) {
553
579
  await this.engine.patchMany(this.entityKey, [id], (item) => ({
554
580
  ...item,
555
- isPinned: !Boolean(item['isPinned']),
581
+ isPinned: nextPinned,
556
582
  }));
557
583
  }
558
- try {
559
- await this.port.togglePin(id);
560
- await this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids: [id] });
561
- }
562
- catch (err) {
563
- this.error.set(err instanceof Error ? err.message : 'Failed to toggle pin');
564
- await this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids: [id] });
565
- }
584
+ await this.commitOrEnqueue({
585
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.togglePin,
586
+ recordIds: [id],
587
+ payload: { isPinned: nextPinned },
588
+ commit: async () => {
589
+ await this.port.togglePin?.(id);
590
+ await this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids: [id] });
591
+ },
592
+ revert: () => this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids: [id] }),
593
+ failureMessage: 'Failed to toggle pin',
594
+ });
566
595
  }
567
596
  async archive(ids) {
568
597
  if (!this.port.archive || ids.length === 0 || !this.canLoadInbox()) {
@@ -570,13 +599,19 @@ class AXPInAppInboxConnectorService {
570
599
  }
571
600
  await this.ensureReady();
572
601
  await this.engine.removeMany(this.entityKey, ids);
573
- try {
574
- await this.port.archive(ids);
575
- }
576
- catch (err) {
577
- this.error.set(err instanceof Error ? err.message : 'Failed to archive inbox items');
578
- await this.syncActiveFromServer();
579
- }
602
+ await this.commitOrEnqueue({
603
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.archive,
604
+ recordIds: ids,
605
+ commit: async () => {
606
+ if (this.port.archive) {
607
+ await this.port.archive(ids);
608
+ }
609
+ },
610
+ revert: async () => {
611
+ await this.syncActiveFromServer();
612
+ },
613
+ failureMessage: 'Failed to archive inbox items',
614
+ });
580
615
  const query = this.pageQuery();
581
616
  if (query?.mode === 'archive') {
582
617
  await this.loadArchivePage(query.filters, query.skip, query.take);
@@ -586,20 +621,30 @@ class AXPInAppInboxConnectorService {
586
621
  if (!this.port.deleteDeliveries || !this.canLoadInbox()) {
587
622
  return;
588
623
  }
624
+ const deleteDeliveries = this.port.deleteDeliveries;
589
625
  await this.ensureReady();
590
626
  await this.engine.removeMany(this.entityKey, [id]);
591
- try {
592
- await this.port.deleteDeliveries([id]);
593
- }
594
- catch (err) {
595
- if (this.isBenignDeliveryDeleteError(err)) {
596
- this.realtime.notifyChanged({ type: 'deleted', ids: [id] });
597
- return;
598
- }
599
- this.error.set(err instanceof Error ? err.message : 'Failed to delete inbox item');
600
- await this.syncActiveFromServer();
601
- return;
602
- }
627
+ await this.commitOrEnqueue({
628
+ action: AXP_IN_APP_INBOX_OUTBOX_ACTIONS.delete,
629
+ recordIds: [id],
630
+ commit: async () => {
631
+ if (!deleteDeliveries) {
632
+ return;
633
+ }
634
+ try {
635
+ await deleteDeliveries([id]);
636
+ }
637
+ catch (err) {
638
+ if (!this.isBenignDeliveryDeleteError(err)) {
639
+ throw err;
640
+ }
641
+ }
642
+ },
643
+ revert: async () => {
644
+ await this.syncActiveFromServer();
645
+ },
646
+ failureMessage: 'Failed to delete inbox item',
647
+ });
603
648
  this.realtime.notifyChanged({ type: 'deleted', ids: [id] });
604
649
  const query = this.pageQuery();
605
650
  if (query?.mode === 'archive') {
@@ -734,6 +779,7 @@ class AXPInAppInboxConnectorService {
734
779
  const response = await this.fetchActiveHot();
735
780
  const items = sortInAppInboxItems(response.items.map((item) => normalizeInAppInboxItem(item)));
736
781
  await this.engine.replaceAll(this.entityKey, items.map((item) => this.toPayload(item)));
782
+ await this.reapplyPendingOutboxOps();
737
783
  const local = this.readLocalItems();
738
784
  return { items: local, total: local.length };
739
785
  }
@@ -851,6 +897,127 @@ class AXPInAppInboxConnectorService {
851
897
  await this.localBridge.apply({ entityKey: this.entityKey, type: 'invalidate' });
852
898
  }
853
899
  //#endregion
900
+ //#region ----- Outbox -----
901
+ canQueueWrites() {
902
+ return axpLocalEntitySupportsOfflineWrite(this.engine.getPolicy(this.entityKey)?.mode);
903
+ }
904
+ async commitOrEnqueue(options) {
905
+ if (!this.connectivity.online && this.canQueueWrites()) {
906
+ await this.outbox.enqueue({
907
+ entityKey: this.entityKey,
908
+ action: options.action,
909
+ recordIds: options.recordIds,
910
+ payload: options.payload,
911
+ });
912
+ return;
913
+ }
914
+ try {
915
+ await options.commit();
916
+ }
917
+ catch (err) {
918
+ if (this.canQueueWrites() && axpIsLikelyNetworkFailure(err)) {
919
+ await this.outbox.enqueue({
920
+ entityKey: this.entityKey,
921
+ action: options.action,
922
+ recordIds: options.recordIds,
923
+ payload: options.payload,
924
+ });
925
+ return;
926
+ }
927
+ this.error.set(err instanceof Error ? err.message : options.failureMessage);
928
+ await options.revert();
929
+ }
930
+ }
931
+ async flushOutboxOp(op) {
932
+ switch (op.action) {
933
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markSeen:
934
+ if (op.recordIds.length > 0) {
935
+ await this.port.markSeen(op.recordIds);
936
+ }
937
+ return;
938
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markRead:
939
+ if (op.recordIds.length > 0) {
940
+ await this.port.markRead(op.recordIds);
941
+ }
942
+ return;
943
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markAllRead:
944
+ await this.port.markAllRead();
945
+ return;
946
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.togglePin:
947
+ if (op.recordIds[0] && this.port.togglePin) {
948
+ await this.port.togglePin(op.recordIds[0]);
949
+ }
950
+ return;
951
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.archive:
952
+ if (op.recordIds.length > 0 && this.port.archive) {
953
+ await this.port.archive(op.recordIds);
954
+ }
955
+ return;
956
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.delete:
957
+ if (op.recordIds.length > 0 && this.port.deleteDeliveries) {
958
+ try {
959
+ await this.port.deleteDeliveries(op.recordIds);
960
+ }
961
+ catch (err) {
962
+ if (!this.isBenignDeliveryDeleteError(err)) {
963
+ throw err;
964
+ }
965
+ }
966
+ }
967
+ return;
968
+ default:
969
+ return;
970
+ }
971
+ }
972
+ async revertOutboxOp(op) {
973
+ if (op.action === AXP_IN_APP_INBOX_OUTBOX_ACTIONS.delete ||
974
+ op.action === AXP_IN_APP_INBOX_OUTBOX_ACTIONS.archive ||
975
+ op.action === AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markAllRead) {
976
+ await this.syncActiveFromServer();
977
+ return;
978
+ }
979
+ if (op.recordIds.length > 0) {
980
+ await this.localBridge.apply({ entityKey: this.entityKey, type: 'upsert', ids: op.recordIds });
981
+ }
982
+ }
983
+ async reapplyPendingOutboxOps() {
984
+ const ops = this.outbox.pendingFor(this.entityKey);
985
+ for (const op of ops) {
986
+ await this.applyOutboxOpLocally(op);
987
+ }
988
+ }
989
+ async applyOutboxOpLocally(op) {
990
+ const seenAt = typeof op.payload?.['seenAt'] === 'string' ? op.payload['seenAt'] : new Date().toISOString();
991
+ const readAt = typeof op.payload?.['readAt'] === 'string' ? op.payload['readAt'] : new Date().toISOString();
992
+ switch (op.action) {
993
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markSeen:
994
+ await this.engine.patchMany(this.entityKey, op.recordIds, (item) => item['seenAt'] ? item : { ...item, seenAt });
995
+ return;
996
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markRead:
997
+ await this.engine.patchMany(this.entityKey, op.recordIds, (item) => ({
998
+ ...item,
999
+ seenAt: item['seenAt'] ?? seenAt,
1000
+ readAt: item['readAt'] ?? readAt,
1001
+ }));
1002
+ return;
1003
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.markAllRead:
1004
+ await this.engine.patchAll(this.entityKey, (item) => item['readAt'] != null ? item : { ...item, readAt, seenAt: item['seenAt'] ?? seenAt });
1005
+ return;
1006
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.togglePin:
1007
+ await this.engine.patchMany(this.entityKey, op.recordIds, (item) => ({
1008
+ ...item,
1009
+ isPinned: Boolean(op.payload?.['isPinned']),
1010
+ }));
1011
+ return;
1012
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.archive:
1013
+ case AXP_IN_APP_INBOX_OUTBOX_ACTIONS.delete:
1014
+ await this.engine.removeMany(this.entityKey, op.recordIds);
1015
+ return;
1016
+ default:
1017
+ return;
1018
+ }
1019
+ }
1020
+ //#endregion
854
1021
  //#region ----- Local helpers -----
855
1022
  canLoadInbox() {
856
1023
  return this.sessionService.isReadyForAppScopedApi;
@@ -1044,6 +1211,7 @@ const communicationInAppInboxI18n = {
1044
1211
  selectItems: '@communication-management:in-app.actions.select-items',
1045
1212
  clearFilters: '@communication-management:in-app.actions.clear-filters',
1046
1213
  liveUpdatesOffline: '@communication-management:in-app.live-updates-offline',
1214
+ pendingSync: '@communication-management:in-app.pending-sync',
1047
1215
  };
1048
1216
  //#endregion
1049
1217
 
@@ -1427,20 +1595,25 @@ const RECONNECT_REFRESH_DELAY_MS = 250;
1427
1595
  //#region ---- In-app inbox fallback sync ----
1428
1596
  /**
1429
1597
  * Keeps the inbox fresh when Socket.IO is down: poll while disconnected,
1430
- * refresh on reconnect and when the tab becomes visible again.
1598
+ * refresh on reconnect and when the tab becomes visible again while disconnected.
1431
1599
  */
1432
1600
  function provideCommunicationInAppInboxFallbackSync() {
1433
1601
  return [
1434
1602
  provideAppStartUpTask(() => {
1435
1603
  const connector = inject(AXPInAppInboxConnectorService);
1436
1604
  const sessionService = inject(AXPSessionService);
1605
+ const outbox = inject(AXPLocalEntityOutboxService);
1437
1606
  const syncStatus = inject(AXMCommunicationInAppInboxSyncStatusService);
1438
1607
  const connection = inject(AXPRealtimeConnectionService, { optional: true });
1439
1608
  const destroyRef = inject(DestroyRef);
1440
1609
  const refreshIfAuthorized = () => {
1441
- if (sessionService.isReadyForAppScopedApi) {
1442
- void connector.refresh();
1610
+ if (!sessionService.isReadyForAppScopedApi) {
1611
+ return;
1443
1612
  }
1613
+ void (async () => {
1614
+ await outbox.flushAll();
1615
+ await connector.refresh();
1616
+ })();
1444
1617
  };
1445
1618
  return {
1446
1619
  name: 'communication-in-app-inbox-fallback',
@@ -1476,8 +1649,7 @@ function provideCommunicationInAppInboxFallbackSync() {
1476
1649
  refreshIfAuthorized();
1477
1650
  });
1478
1651
  document.addEventListener('visibilitychange', () => {
1479
- if (document.visibilityState === 'visible') {
1480
- // Re-sync active inbox in case realtime was missed while hidden.
1652
+ if (document.visibilityState === 'visible' && !connection.isSocketConnected()) {
1481
1653
  refreshIfAuthorized();
1482
1654
  }
1483
1655
  }, { passive: true });
@@ -1765,7 +1937,7 @@ class AXPInAppInboxPanelComponent {
1765
1937
  ];
1766
1938
  }
1767
1939
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPInAppInboxPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1768
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPInAppInboxPanelComponent, isStandalone: true, selector: "axp-in-app-inbox-panel", outputs: { unreadCountChange: "unreadCountChange", closePopover: "closePopover" }, ngImport: i0, template: "<div class=\"axp-in-app-inbox-panel__header\">\n <span class=\"axp-in-app-inbox-panel__title\">\n {{ i18n.panelTitle | translate | async }}\n </span>\n @if (config.settingsRoute) {\n <ax-button color=\"default\" look=\"blank\" (onClick)=\"openSettings()\">\n <ax-icon><i class=\"fa-regular fa-gear fa-lg\"></i></ax-icon>\n </ax-button>\n }\n</div>\n<ax-tabs [content]=\"tabContent\" look=\"with-line\" location=\"bottom\">\n @for (tab of tabItems(); track tab.key) {\n <ax-tab-item\n [text]=\"(tab.labelKey | translate | async) ?? ''\"\n [active]=\"activeTab() === tab.key\"\n (activeChange)=\"changeActiveTab(tab.key)\"\n >\n <ax-suffix>\n @if (tab.count && tab.key === 'Unread') {\n <ax-badge [color]=\"activeTab() === tab.key ? 'primary' : 'ghost'\" [text]=\"tab.count.toString()\"></ax-badge>\n }\n </ax-suffix>\n <ax-content>\n <div class=\"axp-in-app-inbox-panel__list\">\n @if (isLoading()) {\n <div class=\"axp-in-app-inbox-panel__loading\">\n @for (_ of [1, 2, 3]; track $index) {\n <div class=\"axp-in-app-inbox-panel__skeleton\">\n <div class=\"axp-in-app-inbox-panel__skeleton-avatar\"></div>\n <div class=\"axp-in-app-inbox-panel__skeleton-lines\">\n <div class=\"axp-in-app-inbox-panel__skeleton-line --wide\"></div>\n <div class=\"axp-in-app-inbox-panel__skeleton-line\"></div>\n </div>\n </div>\n }\n </div>\n } @else if (getItems(tab.key).length === 0) {\n <axp-state-message\n icon=\"fa-regular fa-bell-slash\"\n [title]=\"i18n.emptyTitle\"\n [description]=\"i18n.emptyDescription\"\n />\n } @else {\n @for (item of getItems(tab.key); track item.id) {\n <axp-in-app-inbox-item\n [data]=\"item\"\n [highlightUnreadBackground]=\"tab.key === 'All'\"\n [showPin]=\"showPin()\"\n [pinLabelKey]=\"i18n.pin\"\n [unpinLabelKey]=\"i18n.unpin\"\n [markAsReadLabelKey]=\"i18n.markAsRead\"\n (itemPress)=\"openInboxItem($event)\"\n (markAsRead)=\"dismissUnread($event)\"\n (togglePin)=\"togglePin($event)\"\n (closePopover)=\"closePopover.emit()\"\n />\n }\n }\n </div>\n <div class=\"axp-in-app-inbox-panel__footer\">\n <ax-button\n class=\"ax-sm\"\n [disabled]=\"(tabItems()[0]?.count ?? 0) === 0\"\n look=\"outline\"\n color=\"default\"\n [text]=\"(i18n.markAllAsRead | translate | async) ?? ''\"\n (onClick)=\"markAllAsRead()\"\n />\n <ax-button\n class=\"ax-sm\"\n look=\"outline\"\n color=\"primary\"\n [text]=\"(i18n.viewAll | translate | async) ?? ''\"\n (onClick)=\"viewAll()\"\n />\n </div>\n </ax-content>\n </ax-tab-item>\n }\n</ax-tabs>\n\n<ng-template [axTabContent] #tabContent=\"axTabContent\"></ng-template>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";.axp-in-app-inbox-panel__header{@apply p-4 flex justify-between items-center;}.axp-in-app-inbox-panel__title{@apply text-xl font-semibold;color:rgb(var(--ax-sys-color-on-surface))}.axp-in-app-inbox-panel__list{@apply max-h-[50vh] md:w-[32rem] overflow-y-auto flex flex-col;}.axp-in-app-inbox-panel__footer{@apply flex gap-4 justify-between p-4 border-t;border-color:rgb(var(--ax-sys-color-border-surface))}.axp-in-app-inbox-panel__loading{@apply p-2 space-y-2;}.axp-in-app-inbox-panel__skeleton{@apply flex gap-4 p-4 animate-pulse;}.axp-in-app-inbox-panel__skeleton-avatar{@apply w-12 h-12 rounded-full shrink-0;background:rgb(var(--ax-sys-color-lighter-surface))}.axp-in-app-inbox-panel__skeleton-lines{@apply flex-1 space-y-2 py-1;}.axp-in-app-inbox-panel__skeleton-line{@apply h-3 rounded-md;background:rgb(var(--ax-sys-color-lighter-surface))}.axp-in-app-inbox-panel__skeleton-line.--wide{@apply w-3/4;}.axp-in-app-inbox-panel__skeleton-line:not(.--wide){@apply w-1/2;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i2.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "reorderable", "reorderDelay", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged", "onTabsReordered"] }, { kind: "component", type: i2.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "directive", type: i2.AXTabContentDirective, selector: "[axTabContent]", inputs: ["axTabContent"], exportAs: ["axTabContent"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "component", type: AXPInAppInboxItemComponent, selector: "axp-in-app-inbox-item", inputs: ["data", "showAvatar", "showDate", "showPin", "pinLabelKey", "unpinLabelKey", "highlightUnreadBackground", "showDismissUnread", "markAsReadLabelKey"], outputs: ["itemPress", "markAsRead", "togglePin", "closePopover"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1940
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPInAppInboxPanelComponent, isStandalone: true, selector: "axp-in-app-inbox-panel", outputs: { unreadCountChange: "unreadCountChange", closePopover: "closePopover" }, ngImport: i0, template: "<div class=\"axp-in-app-inbox-panel__header\">\n <span class=\"axp-in-app-inbox-panel__title\">\n {{ i18n.panelTitle | translate | async }}\n </span>\n @if (config.settingsRoute) {\n <ax-button color=\"default\" look=\"blank\" (onClick)=\"openSettings()\">\n <ax-icon><i class=\"fa-regular fa-gear fa-lg\"></i></ax-icon>\n </ax-button>\n }\n</div>\n<ax-tabs [content]=\"tabContent\" look=\"with-line\" location=\"bottom\">\n @for (tab of tabItems(); track tab.key) {\n <ax-tab-item\n [text]=\"(tab.labelKey | translate | async) ?? ''\"\n [active]=\"activeTab() === tab.key\"\n (activeChange)=\"changeActiveTab(tab.key)\"\n >\n <ax-suffix>\n @if (tab.count && tab.key === 'Unread') {\n <ax-badge [color]=\"activeTab() === tab.key ? 'primary' : 'ghost'\" [text]=\"tab.count.toString()\"></ax-badge>\n }\n </ax-suffix>\n <ax-content>\n <div class=\"axp-in-app-inbox-panel__list\">\n @if (isLoading()) {\n <div class=\"axp-in-app-inbox-panel__loading\">\n @for (_ of [1, 2, 3]; track $index) {\n <div class=\"axp-in-app-inbox-panel__skeleton\">\n <div class=\"axp-in-app-inbox-panel__skeleton-avatar\"></div>\n <div class=\"axp-in-app-inbox-panel__skeleton-lines\">\n <div class=\"axp-in-app-inbox-panel__skeleton-line --wide\"></div>\n <div class=\"axp-in-app-inbox-panel__skeleton-line\"></div>\n </div>\n </div>\n }\n </div>\n } @else if (getItems(tab.key).length === 0) {\n <axp-state-message\n icon=\"fa-regular fa-bell-slash\"\n [title]=\"i18n.emptyTitle\"\n [description]=\"i18n.emptyDescription\"\n />\n } @else {\n @for (item of getItems(tab.key); track item.id) {\n <axp-in-app-inbox-item\n [data]=\"item\"\n [highlightUnreadBackground]=\"tab.key === 'All'\"\n [showPin]=\"showPin()\"\n [pinLabelKey]=\"i18n.pin\"\n [unpinLabelKey]=\"i18n.unpin\"\n [markAsReadLabelKey]=\"i18n.markAsRead\"\n (itemPress)=\"openInboxItem($event)\"\n (markAsRead)=\"dismissUnread($event)\"\n (togglePin)=\"togglePin($event)\"\n (closePopover)=\"closePopover.emit()\"\n />\n }\n }\n </div>\n <div class=\"axp-in-app-inbox-panel__footer\">\n <ax-button\n class=\"ax-sm\"\n [disabled]=\"(tabItems()[0]?.count ?? 0) === 0\"\n look=\"outline\"\n color=\"default\"\n [text]=\"(i18n.markAllAsRead | translate | async) ?? ''\"\n (onClick)=\"markAllAsRead()\"\n />\n <ax-button\n class=\"ax-sm\"\n look=\"outline\"\n color=\"primary\"\n [text]=\"(i18n.viewAll | translate | async) ?? ''\"\n (onClick)=\"viewAll()\"\n />\n </div>\n </ax-content>\n </ax-tab-item>\n }\n</ax-tabs>\n\n<ng-template [axTabContent] #tabContent=\"axTabContent\"></ng-template>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";.axp-in-app-inbox-panel__header{@apply p-4 flex justify-between items-center;}.axp-in-app-inbox-panel__title{@apply text-xl font-semibold;color:rgb(var(--ax-sys-color-on-surface))}.axp-in-app-inbox-panel__list{@apply max-h-[50vh] md:w-[32rem] overflow-y-auto flex flex-col;}.axp-in-app-inbox-panel__footer{@apply flex gap-4 justify-between p-4 border-t;border-color:rgb(var(--ax-sys-color-border-surface))}.axp-in-app-inbox-panel__loading{@apply p-2 space-y-2;}.axp-in-app-inbox-panel__skeleton{@apply flex gap-4 p-4 animate-pulse;}.axp-in-app-inbox-panel__skeleton-avatar{@apply w-12 h-12 rounded-full shrink-0;background:rgb(var(--ax-sys-color-lighter-surface))}.axp-in-app-inbox-panel__skeleton-lines{@apply flex-1 space-y-2 py-1;}.axp-in-app-inbox-panel__skeleton-line{@apply h-3 rounded-md;background:rgb(var(--ax-sys-color-lighter-surface))}.axp-in-app-inbox-panel__skeleton-line.--wide{@apply w-3/4;}.axp-in-app-inbox-panel__skeleton-line:not(.--wide){@apply w-1/2;}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXTabsModule }, { kind: "component", type: i2.AXTabsComponent, selector: "ax-tabs", inputs: ["look", "location", "reorderable", "reorderDelay", "renderMode", "stacked", "fitParent", "minWidth", "content"], outputs: ["onActiveTabChanged", "onTabsReordered"] }, { kind: "component", type: i2.AXTabItemComponent, selector: "ax-tab-item", inputs: ["disabled", "text", "key", "headerTemplate", "active"], outputs: ["disabledChange", "onClick", "onBlur", "onFocus", "activeChange"] }, { kind: "directive", type: i2.AXTabContentDirective, selector: "[axTabContent]", inputs: ["axTabContent"], exportAs: ["axTabContent"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "component", type: i3.AXDecoratorGenericComponent, selector: "ax-footer, ax-header, ax-content, ax-divider, ax-form-hint, ax-prefix, ax-suffix, ax-text, ax-title, ax-subtitle, ax-placeholder, ax-overlay" }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPStateMessageComponent, selector: "axp-state-message", inputs: ["mode", "icon", "title", "description", "titleParams", "descriptionParams", "look"] }, { kind: "component", type: AXPInAppInboxItemComponent, selector: "axp-in-app-inbox-item", inputs: ["data", "showAvatar", "showDate", "showPin", "pinLabelKey", "unpinLabelKey", "highlightUnreadBackground", "showDismissUnread", "markAsReadLabelKey"], outputs: ["itemPress", "markAsRead", "togglePin", "closePopover"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1769
1941
  }
1770
1942
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPInAppInboxPanelComponent, decorators: [{
1771
1943
  type: Component,
@@ -1788,9 +1960,22 @@ class AXPInAppInboxSlotComponent {
1788
1960
  this.i18n = inject(AXP_IN_APP_INBOX_I18N);
1789
1961
  this.themeStore = inject(AXPLayoutThemeService);
1790
1962
  this.connector = inject(AXPInAppInboxConnectorService);
1963
+ this.outbox = inject(AXPLocalEntityOutboxService);
1791
1964
  this.syncStatus = inject(AXP_IN_APP_INBOX_SYNC_STATUS, { optional: true });
1792
1965
  this.liveUpdatesOffline = computed(() => this.syncStatus != null && !this.syncStatus.liveUpdatesConnected(), /* @ts-ignore */
1793
1966
  ...(ngDevMode ? [{ debugName: "liveUpdatesOffline" }] : /* istanbul ignore next */ []));
1967
+ this.pendingSync = computed(() => this.outbox.pendingCount() > 0, /* @ts-ignore */
1968
+ ...(ngDevMode ? [{ debugName: "pendingSync" }] : /* istanbul ignore next */ []));
1969
+ this.statusTitleKey = computed(() => {
1970
+ if (this.pendingSync()) {
1971
+ return this.i18n.pendingSync;
1972
+ }
1973
+ if (this.liveUpdatesOffline()) {
1974
+ return this.i18n.liveUpdatesOffline;
1975
+ }
1976
+ return '';
1977
+ }, /* @ts-ignore */
1978
+ ...(ngDevMode ? [{ debugName: "statusTitleKey" }] : /* istanbul ignore next */ []));
1794
1979
  this.badgeDisplay = computed(() => {
1795
1980
  const total = this.connector.unreadCount();
1796
1981
  if (total <= 0) {
@@ -1806,7 +1991,7 @@ class AXPInAppInboxSlotComponent {
1806
1991
  this.popover()?.close();
1807
1992
  }
1808
1993
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPInAppInboxSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1809
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPInAppInboxSlotComponent, isStandalone: true, selector: "axp-in-app-inbox-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<ax-button\n [iconOnly]=\"true\"\n class=\"relative axp-in-app-inbox-slot__button\"\n [color]=\"themeStore.headerButtonColor()\"\n [look]=\"themeStore.headerButtonLook()\"\n #inboxTrigger\n [title]=\"liveUpdatesOffline() ? ((i18n.liveUpdatesOffline | translate | async) ?? '') : ''\"\n>\n <ax-icon>\n <i [class]=\"config.headerIconClass\"></i>\n </ax-icon>\n</ax-button>\n\n<div\n class=\"axp-in-app-inbox-slot__badge\"\n [class.--xs]=\"badgeDisplay().length > 2\"\n>\n @if (badgeDisplay()) {\n <ax-badge color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n</div>\n<ax-popover\n [target]=\"inboxTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n>\n <div class=\"bg-lightest rounded-md w-full\">\n <axp-in-app-inbox-panel (closePopover)=\"closePopover()\" />\n </div>\n</ax-popover>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";axp-in-app-inbox-slot{@apply relative;}axp-in-app-inbox-slot .axp-in-app-inbox-slot__badge{@apply absolute top-[-.25rem] end-[-.25rem];pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i4$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPInAppInboxPanelComponent, selector: "axp-in-app-inbox-panel", outputs: ["unreadCountChange", "closePopover"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1994
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: AXPInAppInboxSlotComponent, isStandalone: true, selector: "axp-in-app-inbox-slot", viewQueries: [{ propertyName: "popover", first: true, predicate: AXPopoverComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<ax-button\n [iconOnly]=\"true\"\n class=\"relative axp-in-app-inbox-slot__button\"\n [color]=\"themeStore.headerButtonColor()\"\n [look]=\"themeStore.headerButtonLook()\"\n #inboxTrigger\n [title]=\"statusTitleKey() ? ((statusTitleKey() | translate | async) ?? '') : ''\"\n>\n <ax-icon>\n <i [class]=\"config.headerIconClass\"></i>\n </ax-icon>\n</ax-button>\n\n<div\n class=\"axp-in-app-inbox-slot__badge\"\n [class.--xs]=\"badgeDisplay().length > 2\"\n>\n @if (badgeDisplay()) {\n <ax-badge color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n</div>\n<ax-popover\n [target]=\"inboxTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n>\n <div class=\"bg-lightest rounded-md w-full\">\n <axp-in-app-inbox-panel (closePopover)=\"closePopover()\" />\n </div>\n</ax-popover>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";axp-in-app-inbox-slot{@apply relative;}axp-in-app-inbox-slot .axp-in-app-inbox-slot__badge{@apply absolute top-[-.25rem] end-[-.25rem];pointer-events:none}\n"], dependencies: [{ kind: "ngmodule", type: AXButtonModule }, { kind: "component", type: i1$1.AXButtonComponent, selector: "ax-button", inputs: ["disabled", "tabIndex", "color", "look", "text", "toggleable", "selected", "iconOnly", "type", "loadingText"], outputs: ["onBlur", "onFocus", "onClick", "selectedChange", "toggleableChange", "lookChange", "colorChange", "disabledChange", "loadingTextChange"] }, { kind: "ngmodule", type: AXDecoratorModule }, { kind: "component", type: i3.AXDecoratorIconComponent, selector: "ax-icon", inputs: ["icon"] }, { kind: "ngmodule", type: AXBadgeModule }, { kind: "component", type: i4$1.AXBadgeComponent, selector: "ax-badge", inputs: ["color", "look", "text"] }, { kind: "ngmodule", type: AXPopoverModule }, { kind: "component", type: i4$2.AXPopoverComponent, selector: "ax-popover", inputs: ["width", "disablePanelClass", "visualContextScope", "disabled", "offsetX", "offsetY", "target", "placement", "content", "openOn", "closeOn", "hasBackdrop", "openAfter", "closeAfter", "closeOnScroll", "closeOnNavigation", "backdropClass", "panelClass", "adaptivityEnabled"], outputs: ["onOpened", "onClosed"] }, { kind: "ngmodule", type: AXTranslationModule }, { kind: "component", type: AXPInAppInboxPanelComponent, selector: "axp-in-app-inbox-panel", outputs: ["unreadCountChange", "closePopover"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: i4.AXTranslatorPipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
1810
1995
  }
1811
1996
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: AXPInAppInboxSlotComponent, decorators: [{
1812
1997
  type: Component,
@@ -1818,7 +2003,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
1818
2003
  AXPopoverModule,
1819
2004
  AXTranslationModule,
1820
2005
  AXPInAppInboxPanelComponent,
1821
- ], template: "<ax-button\n [iconOnly]=\"true\"\n class=\"relative axp-in-app-inbox-slot__button\"\n [color]=\"themeStore.headerButtonColor()\"\n [look]=\"themeStore.headerButtonLook()\"\n #inboxTrigger\n [title]=\"liveUpdatesOffline() ? ((i18n.liveUpdatesOffline | translate | async) ?? '') : ''\"\n>\n <ax-icon>\n <i [class]=\"config.headerIconClass\"></i>\n </ax-icon>\n</ax-button>\n\n<div\n class=\"axp-in-app-inbox-slot__badge\"\n [class.--xs]=\"badgeDisplay().length > 2\"\n>\n @if (badgeDisplay()) {\n <ax-badge color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n</div>\n<ax-popover\n [target]=\"inboxTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n>\n <div class=\"bg-lightest rounded-md w-full\">\n <axp-in-app-inbox-panel (closePopover)=\"closePopover()\" />\n </div>\n</ax-popover>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";axp-in-app-inbox-slot{@apply relative;}axp-in-app-inbox-slot .axp-in-app-inbox-slot__badge{@apply absolute top-[-.25rem] end-[-.25rem];pointer-events:none}\n"] }]
2006
+ ], template: "<ax-button\n [iconOnly]=\"true\"\n class=\"relative axp-in-app-inbox-slot__button\"\n [color]=\"themeStore.headerButtonColor()\"\n [look]=\"themeStore.headerButtonLook()\"\n #inboxTrigger\n [title]=\"statusTitleKey() ? ((statusTitleKey() | translate | async) ?? '') : ''\"\n>\n <ax-icon>\n <i [class]=\"config.headerIconClass\"></i>\n </ax-icon>\n</ax-button>\n\n<div\n class=\"axp-in-app-inbox-slot__badge\"\n [class.--xs]=\"badgeDisplay().length > 2\"\n>\n @if (badgeDisplay()) {\n <ax-badge color=\"danger\" [text]=\"badgeDisplay()\"></ax-badge>\n }\n</div>\n<ax-popover\n [target]=\"inboxTrigger\"\n [openOn]=\"'toggle'\"\n [closeOn]=\"'clickOut'\"\n [adaptivityEnabled]=\"true\"\n>\n <div class=\"bg-lightest rounded-md w-full\">\n <axp-in-app-inbox-panel (closePopover)=\"closePopover()\" />\n </div>\n</ax-popover>\n", styles: ["@reference \"@acorex/styles/themes/default.css\";axp-in-app-inbox-slot{@apply relative;}axp-in-app-inbox-slot .axp-in-app-inbox-slot__badge{@apply absolute top-[-.25rem] end-[-.25rem];pointer-events:none}\n"] }]
1822
2007
  }], propDecorators: { popover: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPopoverComponent), { isSignal: true }] }] } });
1823
2008
 
1824
2009
  const I18N$2 = '@communication-management:settings.user-notifications';
@@ -1854,17 +2039,6 @@ class AXMCommunicationUserNotificationsSettingProvider {
1854
2039
  }
1855
2040
  //#endregion
1856
2041
 
1857
- const CommunicationManagementManifest = {
1858
- name: RootConfig.module.name,
1859
- version: '0.0.1',
1860
- title: RootConfig.module.title,
1861
- icon: RootConfig.module.icon,
1862
- i18n: RootConfig.config.i18n,
1863
- type: 'domain',
1864
- domain: 'communication',
1865
- required: false,
1866
- };
1867
-
1868
2042
  class AXMCommunicationManagementMenuProvider {
1869
2043
  constructor() {
1870
2044
  this.entityService = inject(AXPEntityService);
@@ -1897,6 +2071,19 @@ class AXMCommunicationManagementMenuProvider {
1897
2071
  },
1898
2072
  },
1899
2073
  ]);
2074
+ context.find(AXPCommonMenuKeys.Administration).addItems([
2075
+ {
2076
+ name: AXMCommunicationManagementMenuKeys.EntityChangeNotifyPolicies,
2077
+ text: RootConfig.entities.entityChangeNotifyPolicy.titlePlural,
2078
+ path: this.entityService.createPath(module, RootConfig.entities.entityChangeNotifyPolicy.name),
2079
+ icon: RootConfig.entities.entityChangeNotifyPolicy.icon,
2080
+ priority: 42,
2081
+ policy: {
2082
+ features: [module],
2083
+ permissions: [AXPCollaborationPermissionKeys.MaintainCommunications],
2084
+ },
2085
+ },
2086
+ ]);
1900
2087
  }
1901
2088
  }
1902
2089
 
@@ -2337,7 +2524,7 @@ function routesFactory() {
2337
2524
  children: [
2338
2525
  {
2339
2526
  path: '',
2340
- loadComponent: () => import('./acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-D4o0Bhce.mjs').then((m) => m.AXMCommunicationInAppInboxPageComponent),
2527
+ loadComponent: () => import('./acorex-platform-module-communication-management-client-communication-in-app-inbox-page.component-yaRijsuh.mjs').then((m) => m.AXMCommunicationInAppInboxPageComponent),
2341
2528
  data: { reuse: true },
2342
2529
  },
2343
2530
  ],
@@ -2351,11 +2538,11 @@ class AXMCommunicationManagementModule {
2351
2538
  provideCommandSetups([
2352
2539
  {
2353
2540
  key: AXMCommunicationManagementCommandKeys$1.Send,
2354
- command: () => import('./acorex-platform-module-communication-management-client-communication-send.command-BH9jjHfA.mjs').then((m) => m.CommunicationSendCommand),
2541
+ command: () => import('./acorex-platform-module-communication-management-client-communication-send.command-D19heywC.mjs').then((m) => m.CommunicationSendCommand),
2355
2542
  },
2356
2543
  {
2357
2544
  key: AXMCommunicationManagementCommandKeys$1.TestSmtp,
2358
- command: () => import('./acorex-platform-module-communication-management-client-communication-test-smtp.command-BMsTVFBN.mjs').then((m) => m.CommunicationTestSmtpCommand),
2545
+ command: () => import('./acorex-platform-module-communication-management-client-communication-test-smtp.command-Bck8R4as.mjs').then((m) => m.CommunicationTestSmtpCommand),
2359
2546
  },
2360
2547
  {
2361
2548
  key: AXMCommunicationManagementCommandKeys$1.EmailShowDialog,
@@ -2363,7 +2550,7 @@ class AXMCommunicationManagementModule {
2363
2550
  },
2364
2551
  {
2365
2552
  key: AXMCommunicationManagementCommandKeys$1.EmailSend,
2366
- command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2553
+ command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2367
2554
  },
2368
2555
  {
2369
2556
  key: AXMCommunicationManagementCommandKeys$1.ShowSentMessage,
@@ -2375,14 +2562,9 @@ class AXMCommunicationManagementModule {
2375
2562
  },
2376
2563
  {
2377
2564
  key: LEGACY_NOTIFICATION_EMAIL_SEND_COMMAND_KEY,
2378
- command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2565
+ command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2379
2566
  },
2380
2567
  ]),
2381
- {
2382
- provide: AXP_MODULE_MANIFEST_PROVIDER,
2383
- useValue: CommunicationManagementManifest,
2384
- multi: true,
2385
- },
2386
2568
  {
2387
2569
  provide: AXP_PERMISSION_DEFINITION_PROVIDER,
2388
2570
  useClass: AXMCommunicationManagementPermissionDefinitionProvider,
@@ -2479,11 +2661,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2479
2661
  provideCommandSetups([
2480
2662
  {
2481
2663
  key: AXMCommunicationManagementCommandKeys$1.Send,
2482
- command: () => import('./acorex-platform-module-communication-management-client-communication-send.command-BH9jjHfA.mjs').then((m) => m.CommunicationSendCommand),
2664
+ command: () => import('./acorex-platform-module-communication-management-client-communication-send.command-D19heywC.mjs').then((m) => m.CommunicationSendCommand),
2483
2665
  },
2484
2666
  {
2485
2667
  key: AXMCommunicationManagementCommandKeys$1.TestSmtp,
2486
- command: () => import('./acorex-platform-module-communication-management-client-communication-test-smtp.command-BMsTVFBN.mjs').then((m) => m.CommunicationTestSmtpCommand),
2668
+ command: () => import('./acorex-platform-module-communication-management-client-communication-test-smtp.command-Bck8R4as.mjs').then((m) => m.CommunicationTestSmtpCommand),
2487
2669
  },
2488
2670
  {
2489
2671
  key: AXMCommunicationManagementCommandKeys$1.EmailShowDialog,
@@ -2491,7 +2673,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2491
2673
  },
2492
2674
  {
2493
2675
  key: AXMCommunicationManagementCommandKeys$1.EmailSend,
2494
- command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2676
+ command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2495
2677
  },
2496
2678
  {
2497
2679
  key: AXMCommunicationManagementCommandKeys$1.ShowSentMessage,
@@ -2503,14 +2685,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImpor
2503
2685
  },
2504
2686
  {
2505
2687
  key: LEGACY_NOTIFICATION_EMAIL_SEND_COMMAND_KEY,
2506
- command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-Bnqy-YMs.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2688
+ command: () => import('./acorex-platform-module-communication-management-client-communication-email-send.command-CR4RjM-X.mjs').then((m) => m.CommunicationManagementEmailSendCommand),
2507
2689
  },
2508
2690
  ]),
2509
- {
2510
- provide: AXP_MODULE_MANIFEST_PROVIDER,
2511
- useValue: CommunicationManagementManifest,
2512
- multi: true,
2513
- },
2514
2691
  {
2515
2692
  provide: AXP_PERMISSION_DEFINITION_PROVIDER,
2516
2693
  useClass: AXMCommunicationManagementPermissionDefinitionProvider,
@@ -2624,4 +2801,4 @@ function resolveCommunicationDeliveryChannelsSettingsSection(context) {
2624
2801
  */
2625
2802
 
2626
2803
  export { AXPInAppInboxConnectorService as A, isInAppInboxUnseen as B, normalizeInAppInboxItem as C, resolveCommunicationDeliveryChannelsSettingsSection as D, sortInAppInboxItems as E, AXPInAppInboxActionService as a, AXP_IN_APP_INBOX_I18N as b, AXPInAppInboxItemComponent as c, AXPCommunicationManagementClient as d, AXPInAppInboxRealtimeService as e, AXMCommunicationManagementMenuProvider as f, AXMCommunicationManagementModule as g, AXMCommunicationManagementPermissionDefinitionProvider as h, AXMCommunicationManagementSearchCommandProvider as i, AXPCommunicationInAppInboxWidgetViewComponent as j, AXPInAppInboxPanelComponent as k, AXPInAppInboxSlotComponent as l, AXPMyInAppInboxDashboardWidget as m, AXP_IN_APP_INBOX_CONFIG as n, AXP_IN_APP_INBOX_LEGACY_DB_NAME as o, AXP_IN_APP_INBOX_LOCAL_ENTITY_KEY as p, AXP_IN_APP_INBOX_LOCAL_MAX_ITEMS as q, AXP_IN_APP_INBOX_PORT as r, AXP_IN_APP_INBOX_SYNC_STATUS as s, AXP_MY_IN_APP_INBOX_DASHBOARD_DIMENSIONS as t, buildMyInAppInboxDashboardWidget as u, dedupeInAppInboxItems as v, filterInAppInboxItemsLocally as w, getInAppInboxPriorityClass as x, isArchiveCategoryFilter as y, isInAppInboxUnread as z };
2627
- //# sourceMappingURL=acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-CZh_tBTN.mjs.map
2804
+ //# sourceMappingURL=acorex-platform-module-communication-management-client-acorex-platform-module-communication-management-client-BJvTWpBc.mjs.map