@product7/product7-js 0.2.9 → 0.3.1

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.
@@ -2512,6 +2512,11 @@
2512
2512
  transition-duration: 100ms;
2513
2513
  }
2514
2514
 
2515
+ .sdk-btn-icon:focus-visible {
2516
+ outline: 2px solid var(--color-primary);
2517
+ outline-offset: 2px;
2518
+ }
2519
+
2515
2520
  .sdk-input {
2516
2521
  width: 100%;
2517
2522
  height: 40px;
@@ -3388,7 +3393,7 @@
3388
3393
  .messenger-message {
3389
3394
  display: flex;
3390
3395
  gap: var(--spacing-2);
3391
- max-width: 85%;
3396
+ max-width: 75%;
3392
3397
  }
3393
3398
 
3394
3399
  .messenger-message-system {
@@ -3409,11 +3414,11 @@
3409
3414
  }
3410
3415
 
3411
3416
  .messenger-message-system-text {
3412
- font-size: 0.6875rem;
3413
- color: var(--msg-text-tertiary);
3417
+ font-size: 0.875rem;
3418
+ font-weight: 500;
3419
+ color: var(--msg-text-secondary);
3414
3420
  white-space: nowrap;
3415
3421
  padding: 0 var(--spacing-2);
3416
- letter-spacing: 0.01em;
3417
3422
  }
3418
3423
 
3419
3424
  /* Rich join/leave system event */
@@ -3422,8 +3427,8 @@
3422
3427
  display: flex;
3423
3428
  flex-direction: column;
3424
3429
  align-items: center;
3425
- gap: var(--spacing-2);
3426
- padding: var(--spacing-4) 0;
3430
+ gap: 4px;
3431
+ padding: var(--spacing-3) 0;
3427
3432
  width: 100%;
3428
3433
  text-align: center;
3429
3434
  }
@@ -3432,39 +3437,43 @@
3432
3437
  width: 3rem;
3433
3438
  height: 3rem;
3434
3439
  border-radius: var(--radius-full);
3435
- border: 2px solid var(--msg-border);
3440
+ border: 1px solid var(--msg-border);
3441
+ background: var(--msg-bg);
3436
3442
  display: flex;
3437
3443
  align-items: center;
3438
3444
  justify-content: center;
3439
3445
  font-size: 1.125rem;
3440
3446
  font-weight: var(--font-weight-semibold);
3441
- color: #ffffff;
3447
+ color: var(--msg-text);
3442
3448
  overflow: hidden;
3443
3449
  flex-shrink: 0;
3450
+ margin-bottom: 4px;
3444
3451
  }
3445
3452
 
3446
3453
  .messenger-message-system-event-avatar img {
3447
- width: 100%;
3448
- height: 100%;
3449
- object-fit: cover;
3454
+ width: 60%;
3455
+ height: 60%;
3456
+ object-fit: contain;
3450
3457
  }
3451
3458
 
3452
3459
  .messenger-message-system-event-name {
3453
- font-size: var(--font-size-sm);
3460
+ font-size: 0.875rem;
3454
3461
  font-weight: var(--font-weight-semibold);
3455
3462
  color: var(--msg-text);
3456
3463
  line-height: 1.3;
3457
3464
  }
3458
3465
 
3459
3466
  .messenger-message-system-event-action {
3460
- font-size: var(--font-size-xs);
3461
- color: var(--msg-text-tertiary);
3467
+ font-size: 0.875rem;
3468
+ font-weight: 500;
3469
+ color: var(--msg-text-secondary);
3462
3470
  margin-top: -2px;
3463
3471
  }
3464
3472
 
3465
3473
  .messenger-message-system-event-time {
3466
- font-size: 0.6875rem;
3467
- color: var(--msg-text-muted);
3474
+ font-size: 0.875rem;
3475
+ font-weight: 500;
3476
+ color: var(--msg-text-tertiary);
3468
3477
  }
3469
3478
 
3470
3479
  .messenger-message-own {
@@ -3517,8 +3526,8 @@
3517
3526
  }
3518
3527
 
3519
3528
  .messenger-message-content {
3520
- font-size: var(--font-size-base);
3521
- font-weight: var(--font-weight-semibold);
3529
+ font-size: 0.875rem;
3530
+ font-weight: 500;
3522
3531
  line-height: var(--line-height-relaxed);
3523
3532
  }
3524
3533
 
@@ -3551,6 +3560,14 @@
3551
3560
  justify-content: flex-end;
3552
3561
  }
3553
3562
 
3563
+ .messenger-message-sent-status {
3564
+ color: var(--msg-text-muted);
3565
+ }
3566
+
3567
+ .messenger-message-optimistic .messenger-message-bubble {
3568
+ opacity: 0.7;
3569
+ }
3570
+
3554
3571
  .messenger-message-image {
3555
3572
  max-width: 220px;
3556
3573
  max-height: 200px;
@@ -3608,6 +3625,48 @@
3608
3625
  font-weight: var(--font-weight-medium);
3609
3626
  }
3610
3627
 
3628
+ /* ========================================
3629
+ SCROLL PILL & CONNECTION BANNER
3630
+ ======================================== */
3631
+
3632
+ .messenger-scroll-pill {
3633
+ position: absolute;
3634
+ bottom: 80px;
3635
+ left: 50%;
3636
+ transform: translateX(-50%);
3637
+ display: flex;
3638
+ align-items: center;
3639
+ gap: var(--spacing-1);
3640
+ padding: 6px 14px;
3641
+ background: var(--color-primary);
3642
+ color: #ffffff;
3643
+ font-size: var(--font-size-xs);
3644
+ font-weight: 500;
3645
+ border-radius: var(--radius-full);
3646
+ cursor: pointer;
3647
+ box-shadow: var(--shadow-lg);
3648
+ z-index: 10;
3649
+ white-space: nowrap;
3650
+ }
3651
+
3652
+ .messenger-connection-banner {
3653
+ display: flex;
3654
+ align-items: center;
3655
+ justify-content: center;
3656
+ gap: var(--spacing-2);
3657
+ padding: 6px var(--spacing-4);
3658
+ background: #fef3c7;
3659
+ color: #92400e;
3660
+ font-size: var(--font-size-xs);
3661
+ font-weight: 500;
3662
+ flex-shrink: 0;
3663
+ }
3664
+
3665
+ .messenger-widget.theme-dark .messenger-connection-banner {
3666
+ background: rgba(245, 158, 11, 0.15);
3667
+ color: #fbbf24;
3668
+ }
3669
+
3611
3670
  /* ========================================
3612
3671
  CHAT HEADER
3613
3672
  ======================================== */
@@ -3624,18 +3683,20 @@
3624
3683
  width: 2.25rem;
3625
3684
  height: 2.25rem;
3626
3685
  border-radius: var(--radius-lg);
3627
- background: var(--color-neutral-100);
3686
+ background: var(--msg-bg-surface);
3687
+ border: 1px solid var(--msg-border);
3628
3688
  display: flex;
3629
3689
  align-items: center;
3630
3690
  justify-content: center;
3631
3691
  flex-shrink: 0;
3632
3692
  overflow: hidden;
3693
+ padding: 4px;
3633
3694
  }
3634
3695
 
3635
3696
  .messenger-chat-header-avatar img {
3636
3697
  width: 100%;
3637
3698
  height: 100%;
3638
- object-fit: cover;
3699
+ object-fit: contain;
3639
3700
  }
3640
3701
 
3641
3702
  .messenger-chat-header-avatar svg {
@@ -3695,6 +3756,24 @@
3695
3756
  margin-bottom: var(--spacing-4);
3696
3757
  }
3697
3758
 
3759
+ .messenger-chat-empty-logo {
3760
+ width: 48px;
3761
+ height: 48px;
3762
+ border-radius: var(--radius-xl);
3763
+ overflow: hidden;
3764
+ margin-bottom: var(--spacing-4);
3765
+ display: flex;
3766
+ align-items: center;
3767
+ justify-content: center;
3768
+ background: var(--msg-bg-surface);
3769
+ }
3770
+
3771
+ .messenger-chat-empty-logo img {
3772
+ width: 100%;
3773
+ height: 100%;
3774
+ object-fit: contain;
3775
+ }
3776
+
3698
3777
  .messenger-chat-empty h3 {
3699
3778
  margin: 0 0 var(--spacing-2);
3700
3779
  font-size: var(--font-size-base);
@@ -3702,12 +3781,6 @@
3702
3781
  color: var(--msg-text);
3703
3782
  }
3704
3783
 
3705
- .messenger-chat-empty p {
3706
- margin: 0;
3707
- font-size: var(--font-size-base);
3708
- color: var(--msg-text-secondary);
3709
- max-width: 240px;
3710
- }
3711
3784
 
3712
3785
  /* ========================================
3713
3786
  COMPOSE AREA
@@ -3788,6 +3861,15 @@
3788
3861
  color: var(--msg-text-tertiary);
3789
3862
  }
3790
3863
 
3864
+ .messenger-compose-input:focus {
3865
+ outline: none;
3866
+ }
3867
+
3868
+ .messenger-chat-compose:focus-within {
3869
+ border-color: var(--color-primary-border);
3870
+ box-shadow: 0 0 0 3px var(--color-primary-light);
3871
+ }
3872
+
3791
3873
  .messenger-compose-bottom {
3792
3874
  display: flex;
3793
3875
  align-items: center;
@@ -4264,6 +4346,7 @@
4264
4346
  display: flex;
4265
4347
  flex-direction: column;
4266
4348
  overflow: hidden;
4349
+ position: relative;
4267
4350
  }
4268
4351
 
4269
4352
  .messenger-avatar-stack {
@@ -4629,6 +4712,10 @@
4629
4712
  margin-left: var(--spacing-4);
4630
4713
  }
4631
4714
 
4715
+ .messenger-home-avatars:empty {
4716
+ display: none;
4717
+ }
4718
+
4632
4719
  .messenger-home-welcome {
4633
4720
  display: flex;
4634
4721
  flex-direction: column;
@@ -4638,17 +4725,19 @@
4638
4725
  }
4639
4726
 
4640
4727
  .messenger-home-greeting {
4641
- font-size: 34px;
4728
+ font-size: clamp(22px, 8vw, 34px);
4642
4729
  font-weight: var(--font-weight-bold);
4643
4730
  color: var(--msg-text);
4644
4731
  line-height: var(--line-height-tight);
4732
+ word-break: break-word;
4645
4733
  }
4646
4734
 
4647
4735
  .messenger-home-question {
4648
- font-size: 28px;
4736
+ font-size: clamp(18px, 6.5vw, 28px);
4649
4737
  font-weight: var(--font-weight-bold);
4650
4738
  color: var(--msg-text);
4651
4739
  line-height: var(--line-height-tight);
4740
+ word-break: break-word;
4652
4741
  }
4653
4742
 
4654
4743
  .messenger-home-body {
@@ -4658,6 +4747,112 @@
4658
4747
  z-index: 2;
4659
4748
  }
4660
4749
 
4750
+ .messenger-home-recent-card {
4751
+ background: var(--msg-bg);
4752
+ border: 1px solid var(--msg-border);
4753
+ border-radius: 10px;
4754
+ padding: var(--spacing-3) var(--spacing-4);
4755
+ margin-bottom: var(--spacing-2);
4756
+ cursor: pointer;
4757
+ transition: background var(--transition-base);
4758
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
4759
+ }
4760
+
4761
+ .messenger-home-recent-card:hover {
4762
+ background: var(--msg-bg-hover);
4763
+ }
4764
+
4765
+ .messenger-home-recent-card-label {
4766
+ font-size: 0.875rem;
4767
+ font-weight: 600;
4768
+ color: var(--msg-text-tertiary);
4769
+ margin-bottom: var(--spacing-2);
4770
+ }
4771
+
4772
+ .messenger-home-recent-card-row {
4773
+ display: flex;
4774
+ align-items: center;
4775
+ gap: var(--spacing-3);
4776
+ }
4777
+
4778
+ .messenger-home-recent-avatar {
4779
+ width: 36px;
4780
+ height: 36px;
4781
+ border-radius: var(--radius-full);
4782
+ flex-shrink: 0;
4783
+ display: flex;
4784
+ align-items: center;
4785
+ justify-content: center;
4786
+ font-size: var(--font-size-sm);
4787
+ font-weight: var(--font-weight-semibold);
4788
+ color: #ffffff;
4789
+ overflow: hidden;
4790
+ }
4791
+
4792
+ .messenger-home-recent-avatar img {
4793
+ width: 100%;
4794
+ height: 100%;
4795
+ object-fit: cover;
4796
+ }
4797
+
4798
+ .messenger-home-recent-avatar-logo {
4799
+ background: var(--msg-bg-surface);
4800
+ border: 1px solid var(--msg-border);
4801
+ padding: 4px;
4802
+ }
4803
+
4804
+ .messenger-home-recent-avatar-logo img {
4805
+ object-fit: contain;
4806
+ }
4807
+
4808
+ .messenger-home-recent-card-content {
4809
+ flex: 1;
4810
+ min-width: 0;
4811
+ }
4812
+
4813
+ .messenger-home-recent-card-header {
4814
+ display: flex;
4815
+ align-items: center;
4816
+ justify-content: space-between;
4817
+ gap: var(--spacing-2);
4818
+ margin-bottom: 2px;
4819
+ }
4820
+
4821
+ .messenger-home-recent-card-name {
4822
+ font-size: var(--font-size-sm);
4823
+ font-weight: var(--font-weight-semibold);
4824
+ color: var(--msg-text);
4825
+ }
4826
+
4827
+ .messenger-home-recent-card-time {
4828
+ font-size: var(--font-size-xs);
4829
+ color: var(--msg-text-tertiary);
4830
+ flex-shrink: 0;
4831
+ }
4832
+
4833
+ .messenger-home-recent-card-preview {
4834
+ display: flex;
4835
+ align-items: center;
4836
+ justify-content: space-between;
4837
+ gap: var(--spacing-2);
4838
+ }
4839
+
4840
+ .messenger-home-recent-card-message {
4841
+ font-size: var(--font-size-sm);
4842
+ color: var(--msg-text-secondary);
4843
+ white-space: nowrap;
4844
+ overflow: hidden;
4845
+ text-overflow: ellipsis;
4846
+ }
4847
+
4848
+ .messenger-home-recent-unread-dot {
4849
+ width: 8px;
4850
+ height: 8px;
4851
+ border-radius: var(--radius-full);
4852
+ background: var(--color-primary);
4853
+ flex-shrink: 0;
4854
+ }
4855
+
4661
4856
  .messenger-home-message-btn {
4662
4857
  width: 100%;
4663
4858
  display: flex;
@@ -4678,13 +4873,11 @@
4678
4873
  }
4679
4874
 
4680
4875
  .messenger-home-message-btn:hover {
4681
- background: var(--msg-bg-elevated);
4682
- transform: translateX(4px);
4683
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
4876
+ background: var(--msg-bg-hover);
4684
4877
  }
4685
4878
 
4686
4879
  .messenger-home-message-btn:active {
4687
- transform: translateX(4px) translateY(1px);
4880
+ transform: translateY(1px);
4688
4881
  transition-duration: 100ms;
4689
4882
  }
4690
4883
 
@@ -4702,14 +4895,21 @@
4702
4895
  .messenger-home-continue-info {
4703
4896
  display: flex;
4704
4897
  flex-direction: column;
4705
- gap: var(--spacing-1);
4898
+ gap: 2px;
4706
4899
  text-align: left;
4707
4900
  flex: 1;
4708
4901
  }
4709
4902
 
4710
4903
  .messenger-home-continue-label {
4711
- font-size: var(--font-size-base);
4712
- font-weight: var(--font-weight-normal);
4904
+ font-size: 0.875rem;
4905
+ font-weight: var(--font-weight-semibold);
4906
+ color: var(--msg-text);
4907
+ }
4908
+
4909
+ .messenger-home-message-subtext {
4910
+ font-size: var(--font-size-xs);
4911
+ font-weight: 400;
4912
+ color: var(--msg-text-secondary);
4713
4913
  }
4714
4914
 
4715
4915
  .messenger-home-continue-preview {
@@ -4852,6 +5052,8 @@
4852
5052
  height: 8px;
4853
5053
  border-radius: var(--radius-full);
4854
5054
  flex-shrink: 0;
5055
+ align-self: center;
5056
+ display: block;
4855
5057
  }
4856
5058
 
4857
5059
  .messenger-availability-online {
@@ -4908,7 +5110,7 @@
4908
5110
 
4909
5111
  .messenger-conversation-item {
4910
5112
  display: flex;
4911
- align-items: flex-start;
5113
+ align-items: center;
4912
5114
  gap: var(--spacing-3);
4913
5115
  padding: 10px var(--spacing-4);
4914
5116
  cursor: pointer;
@@ -4926,31 +5128,40 @@
4926
5128
 
4927
5129
  .messenger-conversation-avatars {
4928
5130
  flex-shrink: 0;
5131
+ display: flex;
5132
+ align-items: center;
5133
+ align-self: stretch;
4929
5134
  }
4930
5135
 
4931
5136
  .messenger-conversation-content {
4932
5137
  flex: 1;
4933
5138
  min-width: 0;
5139
+ display: flex;
5140
+ flex-direction: column;
5141
+ justify-content: center;
4934
5142
  }
4935
5143
 
4936
5144
  .messenger-conversation-header {
4937
5145
  display: flex;
4938
5146
  align-items: center;
4939
5147
  justify-content: space-between;
4940
- margin-bottom: var(--spacing-1);
5148
+ margin-bottom: 2px;
4941
5149
  gap: var(--spacing-3);
4942
5150
  }
4943
5151
 
4944
5152
  .messenger-conversation-title {
4945
- font-size: var(--font-size-md);
4946
- font-weight: var(--font-weight-medium);
5153
+ font-size: var(--font-size-sm);
5154
+ font-weight: var(--font-weight-semibold);
4947
5155
  color: var(--msg-text);
4948
- line-height: var(--line-height-snug);
5156
+ line-height: 1.4;
5157
+ white-space: nowrap;
5158
+ overflow: hidden;
5159
+ text-overflow: ellipsis;
4949
5160
  }
4950
5161
 
4951
5162
  .messenger-conversation-time {
4952
5163
  font-size: var(--font-size-sm);
4953
- color: var(--msg-text-secondary);
5164
+ color: var(--msg-text-tertiary);
4954
5165
  flex-shrink: 0;
4955
5166
  }
4956
5167
 
@@ -4961,18 +5172,18 @@
4961
5172
  }
4962
5173
 
4963
5174
  .messenger-conversation-message {
4964
- font-size: var(--font-size-base);
4965
- font-weight: var(--font-weight-normal);
4966
- color: var(--msg-text-tertiary);
5175
+ font-size: var(--font-size-sm);
5176
+ font-weight: 400;
5177
+ color: var(--msg-text-secondary);
4967
5178
  white-space: nowrap;
4968
5179
  overflow: hidden;
4969
5180
  text-overflow: ellipsis;
4970
- line-height: var(--line-height-normal);
5181
+ line-height: 1.4;
4971
5182
  }
4972
5183
 
4973
5184
  .messenger-conversation-item.unread .messenger-conversation-message {
4974
5185
  color: var(--msg-text);
4975
- font-weight: var(--font-weight-medium);
5186
+ font-weight: 500;
4976
5187
  }
4977
5188
 
4978
5189
  .messenger-unread-dot {
@@ -4983,35 +5194,44 @@
4983
5194
  flex-shrink: 0;
4984
5195
  }
4985
5196
 
5197
+ .messenger-conversation-item.closed .messenger-conversation-title,
5198
+ .messenger-conversation-item.closed .messenger-conversation-message {
5199
+ color: var(--msg-text-tertiary);
5200
+ }
5201
+
5202
+ .messenger-conversation-resolved-badge {
5203
+ font-size: 0.6875rem;
5204
+ font-weight: 500;
5205
+ color: var(--color-success-dark);
5206
+ background: var(--color-success-light);
5207
+ border-radius: var(--radius-full);
5208
+ padding: 1px 7px;
5209
+ flex-shrink: 0;
5210
+ }
5211
+
4986
5212
  .messenger-conversations-footer {
4987
- padding: var(--spacing-4);
4988
- border-top: none;
5213
+ border-top: 1px solid var(--msg-border);
4989
5214
  }
4990
5215
 
4991
5216
  .messenger-new-message-btn {
4992
- max-width: 220px;
4993
- margin: 0 auto;
5217
+ width: 100%;
4994
5218
  display: flex;
4995
5219
  align-items: center;
4996
- justify-content: space-between;
4997
- gap: var(--spacing-2);
4998
- padding: var(--spacing-3) var(--spacing-4);
4999
- background: var(--msg-bg);
5220
+ gap: var(--spacing-3);
5221
+ padding: 12px var(--spacing-4);
5222
+ background: transparent;
5000
5223
  border: none;
5001
- border-radius: var(--radius-xl);
5002
5224
  color: var(--msg-text);
5003
- font-size: var(--font-size-base);
5225
+ font-size: var(--font-size-sm);
5004
5226
  font-weight: var(--font-weight-medium);
5005
5227
  cursor: pointer;
5006
- transition: all var(--transition-base);
5228
+ transition: background var(--transition-base);
5007
5229
  font-family: inherit;
5008
- box-shadow: var(--msg-shadow-card);
5230
+ text-align: left;
5009
5231
  }
5010
5232
 
5011
5233
  .messenger-new-message-btn:hover {
5012
5234
  background: var(--msg-bg-hover);
5013
- transform: translateX(4px);
5014
- box-shadow: var(--msg-shadow-card-hover);
5015
5235
  }
5016
5236
 
5017
5237
  .messenger-new-message-btn:active {
@@ -9271,13 +9491,19 @@
9271
9491
  this._updateContent();
9272
9492
 
9273
9493
  this._unsubscribe = this.state.subscribe((type, data) => {
9494
+ if (type === 'connectionChange') {
9495
+ const banner = this.element?.querySelector('.messenger-connection-banner');
9496
+ if (banner) {
9497
+ banner.style.display = data.connected ? 'none' : 'flex';
9498
+ }
9499
+ return;
9500
+ }
9274
9501
  if (
9275
9502
  type === 'messageAdded' &&
9276
9503
  data.conversationId === this.state.activeConversationId
9277
9504
  ) {
9278
9505
  this._hideTypingIndicator();
9279
9506
  this._appendMessage(data.message);
9280
- this._scrollToBottom();
9281
9507
  } else if (
9282
9508
  type === 'typingStarted' &&
9283
9509
  data.conversationId === this.state.activeConversationId
@@ -9314,16 +9540,20 @@
9314
9540
  const messagesHtml =
9315
9541
  messages.length === 0
9316
9542
  ? this._renderEmptyState(isNewConversation)
9317
- : messages.map((msg) => this._renderMessage(msg)).join('');
9543
+ : this._renderGroupedMessages(messages);
9318
9544
 
9319
- const title = isNewConversation
9320
- ? 'New conversation'
9321
- : conversation?.title || 'Chat with team';
9545
+ const defaultPlaceholder = this.options.composePlaceholder || 'Write a message...';
9322
9546
  const placeholder = isNewConversation
9323
- ? 'Start typing your message...'
9547
+ ? (this.options.composePlaceholder || 'Start typing your message...')
9324
9548
  : isClosed
9325
9549
  ? 'Conversation closed'
9326
- : 'Write a message...';
9550
+ : defaultPlaceholder;
9551
+
9552
+ const logoUrl = this.options.logoUrl;
9553
+ const teamName = this.state.teamName || 'Support';
9554
+ const headerAvatarHtml = logoUrl
9555
+ ? `<img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(teamName)}" />`
9556
+ : `<iconify-icon icon="ph:chats-circle-duotone" width="20" height="20"></iconify-icon>`;
9327
9557
 
9328
9558
  this.element.innerHTML = `
9329
9559
  <div class="messenger-chat-header">
@@ -9331,11 +9561,11 @@
9331
9561
  <iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
9332
9562
  </button>
9333
9563
  <div class="messenger-chat-header-avatar">
9334
- <iconify-icon icon="ph:lightbulb-duotone" width="20" height="20"></iconify-icon>
9564
+ ${headerAvatarHtml}
9335
9565
  </div>
9336
9566
  <div class="messenger-chat-header-info">
9337
- <span class="messenger-chat-title">${title}</span>
9338
- <span class="messenger-chat-subtitle">The team can also help</span>
9567
+ <span class="messenger-chat-title">${this._escapeHtml(teamName)}</span>
9568
+ <span class="messenger-chat-subtitle">${isClosed ? 'Conversation resolved' : (this.state.responseTime || 'Typically replies within minutes')}</span>
9339
9569
  </div>
9340
9570
  <div class="messenger-chat-header-actions">
9341
9571
  <button class="sdk-btn-icon sdk-close-btn messenger-mobile-close-btn" aria-label="Close">
@@ -9344,6 +9574,11 @@
9344
9574
  </div>
9345
9575
  </div>
9346
9576
 
9577
+ <div class="messenger-connection-banner" style="display:none;">
9578
+ <iconify-icon icon="ph:wifi-slash" width="14" height="14"></iconify-icon>
9579
+ <span>Reconnecting…</span>
9580
+ </div>
9581
+
9347
9582
  <div class="messenger-chat-messages">
9348
9583
  ${messagesHtml}
9349
9584
  ${
@@ -9364,6 +9599,11 @@
9364
9599
  </div>
9365
9600
  </div>
9366
9601
 
9602
+ <div class="messenger-scroll-pill" style="display:none;">
9603
+ <iconify-icon icon="ph:arrow-down" width="14" height="14"></iconify-icon>
9604
+ <span>New message</span>
9605
+ </div>
9606
+
9367
9607
  ${
9368
9608
  isClosed
9369
9609
  ? ''
@@ -9399,23 +9639,20 @@
9399
9639
  this._attachEvents();
9400
9640
  this._scrollToBottom();
9401
9641
  this._renderAttachmentPreviews();
9642
+ this._setupScrollPill();
9402
9643
  }
9403
9644
 
9404
9645
  _renderEmptyState(isNewConversation = false) {
9405
- const avatarHtml = this._renderTeamAvatars();
9406
- const responseTime =
9407
- this.state.responseTime || 'We typically reply within a few minutes';
9408
- const isOnline = this.state.agentsOnline;
9646
+ const logoUrl = this.options.logoUrl;
9647
+
9648
+ const logoHtml = logoUrl
9649
+ ? `<div class="messenger-chat-empty-logo"><img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(this.state.teamName)}" /></div>`
9650
+ : '';
9409
9651
 
9410
9652
  return `
9411
9653
  <div class="messenger-chat-empty">
9412
- <div class="messenger-chat-empty-avatars">${avatarHtml}</div>
9654
+ ${logoHtml}
9413
9655
  <h3>${isNewConversation ? 'Start a new conversation' : 'Start the conversation'}</h3>
9414
- <p>Send us a message and we'll get back to you as soon as possible.</p>
9415
- <div class="messenger-chat-availability">
9416
- <span class="messenger-availability-dot ${isOnline ? 'messenger-availability-online' : 'messenger-availability-away'}"></span>
9417
- <span>${isOnline ? "We're online now" : responseTime}</span>
9418
- </div>
9419
9656
  </div>
9420
9657
  `;
9421
9658
  }
@@ -9436,7 +9673,7 @@
9436
9673
  .join('');
9437
9674
  }
9438
9675
 
9439
- _renderMessage(message) {
9676
+ _renderMessage(message, isLastInGroup = true) {
9440
9677
  if (message.isSystem) {
9441
9678
  return this._renderSystemMessage(message);
9442
9679
  }
@@ -9445,8 +9682,9 @@
9445
9682
  const messageClass = isOwn
9446
9683
  ? 'messenger-message-own'
9447
9684
  : 'messenger-message-received';
9448
- const timeStr = this._formatMessageTime(message.timestamp);
9685
+ const timeStr = isLastInGroup ? this._formatMessageTime(message.timestamp) : '';
9449
9686
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
9687
+ const isOptimistic = message.isOptimistic;
9450
9688
 
9451
9689
  const contentHtml = message.content
9452
9690
  ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>`
@@ -9456,11 +9694,20 @@
9456
9694
  : '';
9457
9695
 
9458
9696
  if (isOwn) {
9697
+ const sentIndicator = isLastInGroup
9698
+ ? `<div class="messenger-message-meta messenger-message-meta-own">
9699
+ ${isOptimistic
9700
+ ? `<span class="messenger-message-sent-status">Sending…</span>`
9701
+ : `<span class="messenger-message-sent-status">Sent</span>`
9702
+ }
9703
+ ${timeStr ? `<span>·</span><span>${timeStr}</span>` : ''}
9704
+ </div>`
9705
+ : '';
9459
9706
  return `
9460
- <div class="messenger-message ${messageClass}">
9707
+ <div class="messenger-message ${messageClass}${isOptimistic ? ' messenger-message-optimistic' : ''}">
9461
9708
  ${bubbleHtml}
9462
9709
  ${attachmentsHtml}
9463
- ${timeStr ? `<div class="messenger-message-meta messenger-message-meta-own"><span>${timeStr}</span></div>` : ''}
9710
+ ${sentIndicator}
9464
9711
  </div>
9465
9712
  `;
9466
9713
  }
@@ -9489,25 +9736,23 @@
9489
9736
  content.includes('left the conversation');
9490
9737
 
9491
9738
  if (isJoinLeave && message.sender) {
9492
- const name = message.sender.name || '';
9493
- const avatarUrl = message.sender.avatarUrl;
9494
- const initial = name.charAt(0).toUpperCase() || '?';
9495
- const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500'];
9496
- const colorIndex = name.charCodeAt(0) % colors.length;
9497
- const avatarHtml = avatarUrl
9498
- ? `<img src="${this._escapeHtml(avatarUrl)}" alt="${this._escapeHtml(name)}" />`
9499
- : initial;
9500
- const avatarStyle = avatarUrl
9501
- ? ''
9502
- : `style="background: ${colors[colorIndex]};"`;
9739
+ const rawName = message.sender.name || '';
9740
+ const name = rawName
9741
+ .split(' ')
9742
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
9743
+ .join(' ');
9744
+ const logoUrl = this.options.logoUrl;
9745
+ const logoHtml = logoUrl
9746
+ ? `<img src="${this._escapeHtml(logoUrl)}" alt="logo" />`
9747
+ : name.charAt(0).toUpperCase();
9503
9748
  const timeStr = this._formatMessageTime(message.timestamp);
9504
9749
 
9505
- // Split "Name joined the chat" → name part already in sender, extract action
9506
- const action = content.replace(name, '').trim();
9750
+ const rawAction = content.replace(rawName, '').trim();
9751
+ const action = rawAction.charAt(0).toUpperCase() + rawAction.slice(1);
9507
9752
 
9508
9753
  return `
9509
9754
  <div class="messenger-message-system-event">
9510
- <div class="messenger-message-system-event-avatar" ${avatarStyle}>${avatarHtml}</div>
9755
+ <div class="messenger-message-system-event-avatar">${logoHtml}</div>
9511
9756
  <span class="messenger-message-system-event-name">${this._escapeHtml(name)}</span>
9512
9757
  <span class="messenger-message-system-event-action">${this._escapeHtml(action)}</span>
9513
9758
  ${timeStr ? `<span class="messenger-message-system-event-time">${timeStr}</span>` : ''}
@@ -9587,6 +9832,20 @@
9587
9832
  const tempDiv = document.createElement('div');
9588
9833
  tempDiv.innerHTML = messageHtml;
9589
9834
  messagesContainer.appendChild(tempDiv.firstElementChild);
9835
+
9836
+ // Show scroll pill if user is scrolled up, otherwise scroll to bottom
9837
+ const isNearBottom =
9838
+ messagesContainer.scrollHeight -
9839
+ messagesContainer.scrollTop -
9840
+ messagesContainer.clientHeight <
9841
+ 80;
9842
+
9843
+ if (isNearBottom) {
9844
+ this._scrollToBottom();
9845
+ } else if (!message.isOwn) {
9846
+ const pill = this.element.querySelector('.messenger-scroll-pill');
9847
+ if (pill) pill.style.display = 'flex';
9848
+ }
9590
9849
  }
9591
9850
 
9592
9851
  _scrollToBottom() {
@@ -9600,6 +9859,64 @@
9600
9859
  }
9601
9860
  }
9602
9861
 
9862
+ _setupScrollPill() {
9863
+ const messagesContainer = this.element.querySelector(
9864
+ '.messenger-chat-messages'
9865
+ );
9866
+ const pill = this.element.querySelector('.messenger-scroll-pill');
9867
+ if (!messagesContainer || !pill) return;
9868
+
9869
+ pill.addEventListener('click', () => {
9870
+ this._scrollToBottom();
9871
+ pill.style.display = 'none';
9872
+ });
9873
+
9874
+ messagesContainer.addEventListener('scroll', () => {
9875
+ const isNearBottom =
9876
+ messagesContainer.scrollHeight -
9877
+ messagesContainer.scrollTop -
9878
+ messagesContainer.clientHeight <
9879
+ 80;
9880
+ if (isNearBottom) {
9881
+ pill.style.display = 'none';
9882
+ }
9883
+ });
9884
+ }
9885
+
9886
+ _renderGroupedMessages(messages) {
9887
+ const GROUP_TIME_GAP = 5 * 60 * 1000; // 5 minutes
9888
+ let html = '';
9889
+
9890
+ messages.forEach((msg, index) => {
9891
+ const isLast = index === messages.length - 1;
9892
+ const nextMsg = messages[index + 1];
9893
+ const currentTime = msg.timestamp ? new Date(msg.timestamp).getTime() : 0;
9894
+ const nextTime = nextMsg?.timestamp
9895
+ ? new Date(nextMsg.timestamp).getTime()
9896
+ : null;
9897
+ const senderKey = msg.isSystem
9898
+ ? 'system'
9899
+ : msg.isOwn
9900
+ ? 'own'
9901
+ : msg.sender?.name || 'agent';
9902
+ const nextSenderKey = nextMsg
9903
+ ? nextMsg.isSystem
9904
+ ? 'system'
9905
+ : nextMsg.isOwn
9906
+ ? 'own'
9907
+ : nextMsg.sender?.name || 'agent'
9908
+ : null;
9909
+
9910
+ const timeGapAfter = nextTime && nextTime - currentTime > GROUP_TIME_GAP;
9911
+ const senderChanges = nextMsg && senderKey !== nextSenderKey;
9912
+ const isLastInGroup = isLast || timeGapAfter || senderChanges;
9913
+
9914
+ html += this._renderMessage(msg, isLastInGroup);
9915
+ });
9916
+
9917
+ return html;
9918
+ }
9919
+
9603
9920
  _updateSendButtonState() {
9604
9921
  const input = this.element.querySelector('.messenger-compose-input');
9605
9922
  const sendBtn = this.element.querySelector('.messenger-compose-send');
@@ -10059,8 +10376,9 @@
10059
10376
 
10060
10377
  <div class="messenger-conversations-footer">
10061
10378
  <button class="messenger-new-message-btn">
10062
- <span>Send us a message</span>
10063
- <iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>
10379
+ <iconify-icon icon="ph:pencil-simple" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-secondary);"></iconify-icon>
10380
+ <span style="flex: 1;">New conversation</span>
10381
+ <iconify-icon icon="ph:caret-right" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-tertiary);"></iconify-icon>
10064
10382
  </button>
10065
10383
  </div>
10066
10384
  `;
@@ -10069,14 +10387,16 @@
10069
10387
  }
10070
10388
 
10071
10389
  _renderConversationItem(conversation) {
10390
+ const isClosed = conversation.status === 'closed';
10072
10391
  const unreadClass = conversation.unread > 0 ? 'unread' : '';
10392
+ const closedClass = isClosed ? 'closed' : '';
10073
10393
  const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
10074
10394
  const avatarsHtml = this._renderConversationAvatars(
10075
10395
  conversation.participants
10076
10396
  );
10077
10397
 
10078
10398
  return `
10079
- <div class="messenger-conversation-item ${unreadClass}" data-conversation-id="${conversation.id}">
10399
+ <div class="messenger-conversation-item ${unreadClass} ${closedClass}" data-conversation-id="${conversation.id}">
10080
10400
  <div class="messenger-conversation-avatars">
10081
10401
  ${avatarsHtml}
10082
10402
  </div>
@@ -10087,6 +10407,7 @@
10087
10407
  </div>
10088
10408
  <div class="messenger-conversation-preview">
10089
10409
  ${conversation.unread > 0 ? '<span class="messenger-unread-dot"></span>' : ''}
10410
+ ${isClosed ? '<span class="messenger-conversation-resolved-badge">Resolved</span>' : ''}
10090
10411
  <span class="messenger-conversation-message">${this._truncateMessage(conversation.lastMessage)}</span>
10091
10412
  </div>
10092
10413
  </div>
@@ -10124,7 +10445,7 @@
10124
10445
 
10125
10446
  const avatarItems = avatars
10126
10447
  .slice(0, 2)
10127
- .map((avatar, index) => {
10448
+ .map((avatar) => {
10128
10449
  if (typeof avatar === 'string' && avatar.startsWith('http')) {
10129
10450
  return `<div class="sdk-avatar sdk-avatar-sm"><img src="${avatar}" alt="Team member" /></div>`;
10130
10451
  }
@@ -10512,7 +10833,6 @@
10512
10833
  <div class="messenger-home-welcome">
10513
10834
  <span class="messenger-home-greeting">${this.state.greetingMessage}</span>
10514
10835
  <span class="messenger-home-question">${this.state.welcomeMessage}</span>
10515
- ${this._renderAvailabilityStatus()}
10516
10836
  </div>
10517
10837
  </div>
10518
10838
 
@@ -10576,24 +10896,20 @@
10576
10896
  const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
10577
10897
  const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
10578
10898
 
10579
- if (openConversation) {
10580
- return `
10581
- <button class="messenger-home-message-btn messenger-home-continue-btn" data-conversation-id="${openConversation.id}">
10582
- <div class="messenger-home-continue-info">
10583
- <span class="messenger-home-continue-label">Continue conversation</span>
10584
- </div>
10585
- ${sendIcon}
10586
- </button>
10587
- <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
10588
- <span class="messenger-home-continue-label">Leave us feedback</span>
10589
- ${caretIcon}
10590
- </button>
10591
- `;
10592
- }
10899
+ const responseTime =
10900
+ this.state.responseTime || 'We typically reply within a few minutes';
10901
+
10902
+ const recentCardHtml = openConversation
10903
+ ? this._renderRecentMessageCard(openConversation)
10904
+ : '';
10593
10905
 
10594
10906
  return `
10907
+ ${recentCardHtml}
10595
10908
  <button class="messenger-home-message-btn">
10596
- <span>Start a conversation</span>
10909
+ <div class="messenger-home-continue-info">
10910
+ <span class="messenger-home-continue-label">Send us a message</span>
10911
+ <span class="messenger-home-message-subtext">${responseTime}</span>
10912
+ </div>
10597
10913
  ${sendIcon}
10598
10914
  </button>
10599
10915
  <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
@@ -10603,6 +10919,55 @@
10603
10919
  `;
10604
10920
  }
10605
10921
 
10922
+ _renderRecentMessageCard(conversation) {
10923
+ const logoUrl = this.options.logoUrl;
10924
+ const teamName = this.state.teamName || 'Support';
10925
+ const logoHtml = logoUrl
10926
+ ? `<div class="messenger-home-recent-avatar messenger-home-recent-avatar-logo"><img src="${logoUrl}" alt="${teamName}" /></div>`
10927
+ : `<div class="messenger-home-recent-avatar" style="background: var(--color-primary);">${teamName.charAt(0).toUpperCase()}</div>`;
10928
+
10929
+ const title = conversation.title || teamName;
10930
+ const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
10931
+ const preview = conversation.lastMessage
10932
+ ? conversation.lastMessage.substring(0, 48) + (conversation.lastMessage.length > 48 ? '...' : '')
10933
+ : '';
10934
+ const hasUnread = conversation.unread > 0;
10935
+
10936
+ return `
10937
+ <div class="messenger-home-recent-card" data-conversation-id="${conversation.id}">
10938
+ <div class="messenger-home-recent-card-label">Recent message</div>
10939
+ <div class="messenger-home-recent-card-row">
10940
+ ${logoHtml}
10941
+ <div class="messenger-home-recent-card-content">
10942
+ <div class="messenger-home-recent-card-header">
10943
+ <span class="messenger-home-recent-card-name">${title}</span>
10944
+ <span class="messenger-home-recent-card-time">${timeAgo}</span>
10945
+ </div>
10946
+ <div class="messenger-home-recent-card-preview">
10947
+ <span class="messenger-home-recent-card-message">${preview}</span>
10948
+ ${hasUnread ? '<span class="messenger-home-recent-unread-dot"></span>' : ''}
10949
+ </div>
10950
+ </div>
10951
+ </div>
10952
+ </div>
10953
+ `;
10954
+ }
10955
+
10956
+ _formatTimeAgo(timestamp) {
10957
+ if (!timestamp) return '';
10958
+ const date = new Date(timestamp);
10959
+ const now = new Date();
10960
+ const diffMs = now - date;
10961
+ const diffMins = Math.floor(diffMs / 60000);
10962
+ const diffHours = Math.floor(diffMs / 3600000);
10963
+ const diffDays = Math.floor(diffMs / 86400000);
10964
+ if (diffMins < 1) return 'now';
10965
+ if (diffMins < 60) return `${diffMins}m`;
10966
+ if (diffHours < 24) return `${diffHours}h`;
10967
+ if (diffDays < 7) return `${diffDays}d`;
10968
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
10969
+ }
10970
+
10606
10971
  _renderFeaturedCard() {
10607
10972
  if (!this.options.featuredContent) {
10608
10973
  return '';
@@ -10680,22 +11045,26 @@
10680
11045
  }
10681
11046
 
10682
11047
  _attachEvents() {
11048
+ const recentCard = this.element.querySelector('.messenger-home-recent-card');
11049
+ if (recentCard) {
11050
+ recentCard.addEventListener('click', () => {
11051
+ const convId = recentCard.dataset.conversationId;
11052
+ this.state.setActiveConversation(convId);
11053
+ this.state.markAsRead(convId);
11054
+ this.state.setView('chat');
11055
+ if (this.options.onSelectConversation) {
11056
+ this.options.onSelectConversation(convId);
11057
+ }
11058
+ });
11059
+ }
11060
+
10683
11061
  const msgBtn = this.element.querySelector(
10684
11062
  '.messenger-home-message-btn:not(.messenger-feedback-btn)'
10685
11063
  );
10686
11064
  if (msgBtn) {
10687
11065
  msgBtn.addEventListener('click', () => {
10688
- const convId = msgBtn.dataset.conversationId;
10689
- if (convId) {
10690
- this.state.setActiveConversation(convId);
10691
- this.state.setView('chat');
10692
- if (this.options.onSelectConversation) {
10693
- this.options.onSelectConversation(convId);
10694
- }
10695
- } else {
10696
- this.state.setActiveConversation(null);
10697
- this.state.setView('chat');
10698
- }
11066
+ this.state.setActiveConversation(null);
11067
+ this.state.setView('chat');
10699
11068
  });
10700
11069
  }
10701
11070
 
@@ -10703,9 +11072,8 @@
10703
11072
  if (feedbackBtn) {
10704
11073
  feedbackBtn.addEventListener('click', () => {
10705
11074
  if (this.options.onFeedbackClick) {
10706
- // Close the messenger panel first, then open the feedback modal
10707
11075
  this.state.setOpen(false);
10708
- setTimeout(() => this.options.onFeedbackClick(), 200);
11076
+ this.options.onFeedbackClick();
10709
11077
  } else if (this.state.urls?.feedback) {
10710
11078
  window.open(this.state.urls.feedback, '_blank');
10711
11079
  }
@@ -10919,6 +11287,7 @@
10919
11287
  changelogUrl: options.changelogUrl || null,
10920
11288
  helpUrl: options.helpUrl || null,
10921
11289
  roadmapUrl: options.roadmapUrl || null,
11290
+ composePlaceholder: options.composePlaceholder || null,
10922
11291
  onSendMessage: options.onSendMessage || null,
10923
11292
  onFeedbackClick: options.onFeedbackClick || null,
10924
11293
  onArticleClick: options.onArticleClick || null,
@@ -11351,6 +11720,12 @@
11351
11720
  this._wsUnsubscribers.push(
11352
11721
  this.wsService.on('disconnected', () => {
11353
11722
  console.log('[MessengerWidget] WebSocket disconnected');
11723
+ this.messengerState._notify('connectionChange', { connected: false });
11724
+ })
11725
+ );
11726
+ this._wsUnsubscribers.push(
11727
+ this.wsService.on('connected', () => {
11728
+ this.messengerState._notify('connectionChange', { connected: true });
11354
11729
  })
11355
11730
  );
11356
11731