@product7/product7-js 0.3.0 → 0.3.2

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,21 +3437,23 @@
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 {
@@ -3553,6 +3560,14 @@
3553
3560
  justify-content: flex-end;
3554
3561
  }
3555
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
+
3556
3571
  .messenger-message-image {
3557
3572
  max-width: 220px;
3558
3573
  max-height: 200px;
@@ -3610,6 +3625,48 @@
3610
3625
  font-weight: var(--font-weight-medium);
3611
3626
  }
3612
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
+
3613
3670
  /* ========================================
3614
3671
  CHAT HEADER
3615
3672
  ======================================== */
@@ -3626,18 +3683,20 @@
3626
3683
  width: 2.25rem;
3627
3684
  height: 2.25rem;
3628
3685
  border-radius: var(--radius-lg);
3629
- background: var(--color-neutral-100);
3686
+ background: var(--msg-bg-surface);
3687
+ border: 1px solid var(--msg-border);
3630
3688
  display: flex;
3631
3689
  align-items: center;
3632
3690
  justify-content: center;
3633
3691
  flex-shrink: 0;
3634
3692
  overflow: hidden;
3693
+ padding: 4px;
3635
3694
  }
3636
3695
 
3637
3696
  .messenger-chat-header-avatar img {
3638
3697
  width: 100%;
3639
3698
  height: 100%;
3640
- object-fit: cover;
3699
+ object-fit: contain;
3641
3700
  }
3642
3701
 
3643
3702
  .messenger-chat-header-avatar svg {
@@ -3697,6 +3756,24 @@
3697
3756
  margin-bottom: var(--spacing-4);
3698
3757
  }
3699
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
+
3700
3777
  .messenger-chat-empty h3 {
3701
3778
  margin: 0 0 var(--spacing-2);
3702
3779
  font-size: var(--font-size-base);
@@ -3704,12 +3781,6 @@
3704
3781
  color: var(--msg-text);
3705
3782
  }
3706
3783
 
3707
- .messenger-chat-empty p {
3708
- margin: 0;
3709
- font-size: var(--font-size-base);
3710
- color: var(--msg-text-secondary);
3711
- max-width: 240px;
3712
- }
3713
3784
 
3714
3785
  /* ========================================
3715
3786
  COMPOSE AREA
@@ -3790,6 +3861,15 @@
3790
3861
  color: var(--msg-text-tertiary);
3791
3862
  }
3792
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
+
3793
3873
  .messenger-compose-bottom {
3794
3874
  display: flex;
3795
3875
  align-items: center;
@@ -4266,6 +4346,7 @@
4266
4346
  display: flex;
4267
4347
  flex-direction: column;
4268
4348
  overflow: hidden;
4349
+ position: relative;
4269
4350
  }
4270
4351
 
4271
4352
  .messenger-avatar-stack {
@@ -4631,6 +4712,10 @@
4631
4712
  margin-left: var(--spacing-4);
4632
4713
  }
4633
4714
 
4715
+ .messenger-home-avatars:empty {
4716
+ display: none;
4717
+ }
4718
+
4634
4719
  .messenger-home-welcome {
4635
4720
  display: flex;
4636
4721
  flex-direction: column;
@@ -4640,17 +4725,19 @@
4640
4725
  }
4641
4726
 
4642
4727
  .messenger-home-greeting {
4643
- font-size: 34px;
4728
+ font-size: clamp(22px, 8vw, 34px);
4644
4729
  font-weight: var(--font-weight-bold);
4645
4730
  color: var(--msg-text);
4646
4731
  line-height: var(--line-height-tight);
4732
+ word-break: break-word;
4647
4733
  }
4648
4734
 
4649
4735
  .messenger-home-question {
4650
- font-size: 28px;
4736
+ font-size: clamp(18px, 6.5vw, 28px);
4651
4737
  font-weight: var(--font-weight-bold);
4652
4738
  color: var(--msg-text);
4653
4739
  line-height: var(--line-height-tight);
4740
+ word-break: break-word;
4654
4741
  }
4655
4742
 
4656
4743
  .messenger-home-body {
@@ -4660,6 +4747,112 @@
4660
4747
  z-index: 2;
4661
4748
  }
4662
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
+
4663
4856
  .messenger-home-message-btn {
4664
4857
  width: 100%;
4665
4858
  display: flex;
@@ -4680,13 +4873,11 @@
4680
4873
  }
4681
4874
 
4682
4875
  .messenger-home-message-btn:hover {
4683
- background: var(--msg-bg-elevated);
4684
- transform: translateX(4px);
4685
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1);
4876
+ background: var(--msg-bg-hover);
4686
4877
  }
4687
4878
 
4688
4879
  .messenger-home-message-btn:active {
4689
- transform: translateX(4px) translateY(1px);
4880
+ transform: translateY(1px);
4690
4881
  transition-duration: 100ms;
4691
4882
  }
4692
4883
 
@@ -4704,14 +4895,21 @@
4704
4895
  .messenger-home-continue-info {
4705
4896
  display: flex;
4706
4897
  flex-direction: column;
4707
- gap: var(--spacing-1);
4898
+ gap: 2px;
4708
4899
  text-align: left;
4709
4900
  flex: 1;
4710
4901
  }
4711
4902
 
4712
4903
  .messenger-home-continue-label {
4713
- font-size: var(--font-size-base);
4714
- 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);
4715
4913
  }
4716
4914
 
4717
4915
  .messenger-home-continue-preview {
@@ -4854,6 +5052,8 @@
4854
5052
  height: 8px;
4855
5053
  border-radius: var(--radius-full);
4856
5054
  flex-shrink: 0;
5055
+ align-self: center;
5056
+ display: block;
4857
5057
  }
4858
5058
 
4859
5059
  .messenger-availability-online {
@@ -4910,7 +5110,7 @@
4910
5110
 
4911
5111
  .messenger-conversation-item {
4912
5112
  display: flex;
4913
- align-items: flex-start;
5113
+ align-items: center;
4914
5114
  gap: var(--spacing-3);
4915
5115
  padding: 10px var(--spacing-4);
4916
5116
  cursor: pointer;
@@ -4928,31 +5128,40 @@
4928
5128
 
4929
5129
  .messenger-conversation-avatars {
4930
5130
  flex-shrink: 0;
5131
+ display: flex;
5132
+ align-items: center;
5133
+ align-self: stretch;
4931
5134
  }
4932
5135
 
4933
5136
  .messenger-conversation-content {
4934
5137
  flex: 1;
4935
5138
  min-width: 0;
5139
+ display: flex;
5140
+ flex-direction: column;
5141
+ justify-content: center;
4936
5142
  }
4937
5143
 
4938
5144
  .messenger-conversation-header {
4939
5145
  display: flex;
4940
5146
  align-items: center;
4941
5147
  justify-content: space-between;
4942
- margin-bottom: var(--spacing-1);
5148
+ margin-bottom: 2px;
4943
5149
  gap: var(--spacing-3);
4944
5150
  }
4945
5151
 
4946
5152
  .messenger-conversation-title {
4947
- font-size: var(--font-size-md);
4948
- font-weight: var(--font-weight-medium);
5153
+ font-size: var(--font-size-sm);
5154
+ font-weight: var(--font-weight-semibold);
4949
5155
  color: var(--msg-text);
4950
- line-height: var(--line-height-snug);
5156
+ line-height: 1.4;
5157
+ white-space: nowrap;
5158
+ overflow: hidden;
5159
+ text-overflow: ellipsis;
4951
5160
  }
4952
5161
 
4953
5162
  .messenger-conversation-time {
4954
5163
  font-size: var(--font-size-sm);
4955
- color: var(--msg-text-secondary);
5164
+ color: var(--msg-text-tertiary);
4956
5165
  flex-shrink: 0;
4957
5166
  }
4958
5167
 
@@ -4963,18 +5172,18 @@
4963
5172
  }
4964
5173
 
4965
5174
  .messenger-conversation-message {
4966
- font-size: var(--font-size-base);
4967
- font-weight: var(--font-weight-normal);
4968
- color: var(--msg-text-tertiary);
5175
+ font-size: var(--font-size-sm);
5176
+ font-weight: 400;
5177
+ color: var(--msg-text-secondary);
4969
5178
  white-space: nowrap;
4970
5179
  overflow: hidden;
4971
5180
  text-overflow: ellipsis;
4972
- line-height: var(--line-height-normal);
5181
+ line-height: 1.4;
4973
5182
  }
4974
5183
 
4975
5184
  .messenger-conversation-item.unread .messenger-conversation-message {
4976
5185
  color: var(--msg-text);
4977
- font-weight: var(--font-weight-medium);
5186
+ font-weight: 500;
4978
5187
  }
4979
5188
 
4980
5189
  .messenger-unread-dot {
@@ -4985,35 +5194,44 @@
4985
5194
  flex-shrink: 0;
4986
5195
  }
4987
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
+
4988
5212
  .messenger-conversations-footer {
4989
- padding: var(--spacing-4);
4990
- border-top: none;
5213
+ border-top: 1px solid var(--msg-border);
4991
5214
  }
4992
5215
 
4993
5216
  .messenger-new-message-btn {
4994
- max-width: 220px;
4995
- margin: 0 auto;
5217
+ width: 100%;
4996
5218
  display: flex;
4997
5219
  align-items: center;
4998
- justify-content: space-between;
4999
- gap: var(--spacing-2);
5000
- padding: var(--spacing-3) var(--spacing-4);
5001
- background: var(--msg-bg);
5220
+ gap: var(--spacing-3);
5221
+ padding: 12px var(--spacing-4);
5222
+ background: transparent;
5002
5223
  border: none;
5003
- border-radius: var(--radius-xl);
5004
5224
  color: var(--msg-text);
5005
- font-size: var(--font-size-base);
5225
+ font-size: var(--font-size-sm);
5006
5226
  font-weight: var(--font-weight-medium);
5007
5227
  cursor: pointer;
5008
- transition: all var(--transition-base);
5228
+ transition: background var(--transition-base);
5009
5229
  font-family: inherit;
5010
- box-shadow: var(--msg-shadow-card);
5230
+ text-align: left;
5011
5231
  }
5012
5232
 
5013
5233
  .messenger-new-message-btn:hover {
5014
5234
  background: var(--msg-bg-hover);
5015
- transform: translateX(4px);
5016
- box-shadow: var(--msg-shadow-card-hover);
5017
5235
  }
5018
5236
 
5019
5237
  .messenger-new-message-btn:active {
@@ -9273,13 +9491,19 @@
9273
9491
  this._updateContent();
9274
9492
 
9275
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
+ }
9276
9501
  if (
9277
9502
  type === 'messageAdded' &&
9278
9503
  data.conversationId === this.state.activeConversationId
9279
9504
  ) {
9280
9505
  this._hideTypingIndicator();
9281
9506
  this._appendMessage(data.message);
9282
- this._scrollToBottom();
9283
9507
  } else if (
9284
9508
  type === 'typingStarted' &&
9285
9509
  data.conversationId === this.state.activeConversationId
@@ -9316,16 +9540,20 @@
9316
9540
  const messagesHtml =
9317
9541
  messages.length === 0
9318
9542
  ? this._renderEmptyState(isNewConversation)
9319
- : messages.map((msg) => this._renderMessage(msg)).join('');
9543
+ : this._renderGroupedMessages(messages);
9320
9544
 
9321
- const title = isNewConversation
9322
- ? 'New conversation'
9323
- : conversation?.title || 'Chat with team';
9545
+ const defaultPlaceholder = this.options.composePlaceholder || 'Write a message...';
9324
9546
  const placeholder = isNewConversation
9325
- ? 'Start typing your message...'
9547
+ ? (this.options.composePlaceholder || 'Start typing your message...')
9326
9548
  : isClosed
9327
9549
  ? 'Conversation closed'
9328
- : '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>`;
9329
9557
 
9330
9558
  this.element.innerHTML = `
9331
9559
  <div class="messenger-chat-header">
@@ -9333,11 +9561,11 @@
9333
9561
  <iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
9334
9562
  </button>
9335
9563
  <div class="messenger-chat-header-avatar">
9336
- <iconify-icon icon="ph:lightbulb-duotone" width="20" height="20"></iconify-icon>
9564
+ ${headerAvatarHtml}
9337
9565
  </div>
9338
9566
  <div class="messenger-chat-header-info">
9339
- <span class="messenger-chat-title">${title}</span>
9340
- <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>
9341
9569
  </div>
9342
9570
  <div class="messenger-chat-header-actions">
9343
9571
  <button class="sdk-btn-icon sdk-close-btn messenger-mobile-close-btn" aria-label="Close">
@@ -9346,6 +9574,11 @@
9346
9574
  </div>
9347
9575
  </div>
9348
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
+
9349
9582
  <div class="messenger-chat-messages">
9350
9583
  ${messagesHtml}
9351
9584
  ${
@@ -9366,6 +9599,11 @@
9366
9599
  </div>
9367
9600
  </div>
9368
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
+
9369
9607
  ${
9370
9608
  isClosed
9371
9609
  ? ''
@@ -9401,23 +9639,20 @@
9401
9639
  this._attachEvents();
9402
9640
  this._scrollToBottom();
9403
9641
  this._renderAttachmentPreviews();
9642
+ this._setupScrollPill();
9404
9643
  }
9405
9644
 
9406
9645
  _renderEmptyState(isNewConversation = false) {
9407
- const avatarHtml = this._renderTeamAvatars();
9408
- const responseTime =
9409
- this.state.responseTime || 'We typically reply within a few minutes';
9410
- 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
+ : '';
9411
9651
 
9412
9652
  return `
9413
9653
  <div class="messenger-chat-empty">
9414
- <div class="messenger-chat-empty-avatars">${avatarHtml}</div>
9654
+ ${logoHtml}
9415
9655
  <h3>${isNewConversation ? 'Start a new conversation' : 'Start the conversation'}</h3>
9416
- <p>Send us a message and we'll get back to you as soon as possible.</p>
9417
- <div class="messenger-chat-availability">
9418
- <span class="messenger-availability-dot ${isOnline ? 'messenger-availability-online' : 'messenger-availability-away'}"></span>
9419
- <span>${isOnline ? "We're online now" : responseTime}</span>
9420
- </div>
9421
9656
  </div>
9422
9657
  `;
9423
9658
  }
@@ -9438,7 +9673,7 @@
9438
9673
  .join('');
9439
9674
  }
9440
9675
 
9441
- _renderMessage(message) {
9676
+ _renderMessage(message, isLastInGroup = true) {
9442
9677
  if (message.isSystem) {
9443
9678
  return this._renderSystemMessage(message);
9444
9679
  }
@@ -9447,8 +9682,9 @@
9447
9682
  const messageClass = isOwn
9448
9683
  ? 'messenger-message-own'
9449
9684
  : 'messenger-message-received';
9450
- const timeStr = this._formatMessageTime(message.timestamp);
9685
+ const timeStr = isLastInGroup ? this._formatMessageTime(message.timestamp) : '';
9451
9686
  const attachmentsHtml = this._renderMessageAttachments(message.attachments);
9687
+ const isOptimistic = message.isOptimistic;
9452
9688
 
9453
9689
  const contentHtml = message.content
9454
9690
  ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>`
@@ -9458,11 +9694,20 @@
9458
9694
  : '';
9459
9695
 
9460
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
+ : '';
9461
9706
  return `
9462
- <div class="messenger-message ${messageClass}">
9707
+ <div class="messenger-message ${messageClass}${isOptimistic ? ' messenger-message-optimistic' : ''}">
9463
9708
  ${bubbleHtml}
9464
9709
  ${attachmentsHtml}
9465
- ${timeStr ? `<div class="messenger-message-meta messenger-message-meta-own"><span>${timeStr}</span></div>` : ''}
9710
+ ${sentIndicator}
9466
9711
  </div>
9467
9712
  `;
9468
9713
  }
@@ -9496,16 +9741,10 @@
9496
9741
  .split(' ')
9497
9742
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
9498
9743
  .join(' ');
9499
- const avatarUrl = message.sender.avatarUrl;
9500
- const initial = name.charAt(0).toUpperCase() || '?';
9501
- const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500'];
9502
- const colorIndex = rawName.charCodeAt(0) % colors.length;
9503
- const avatarHtml = avatarUrl
9504
- ? `<img src="${this._escapeHtml(avatarUrl)}" alt="${this._escapeHtml(name)}" />`
9505
- : initial;
9506
- const avatarStyle = avatarUrl
9507
- ? ''
9508
- : `style="background: ${colors[colorIndex]};"`;
9744
+ const logoUrl = this.options.logoUrl;
9745
+ const logoHtml = logoUrl
9746
+ ? `<img src="${this._escapeHtml(logoUrl)}" alt="logo" />`
9747
+ : name.charAt(0).toUpperCase();
9509
9748
  const timeStr = this._formatMessageTime(message.timestamp);
9510
9749
 
9511
9750
  const rawAction = content.replace(rawName, '').trim();
@@ -9513,7 +9752,7 @@
9513
9752
 
9514
9753
  return `
9515
9754
  <div class="messenger-message-system-event">
9516
- <div class="messenger-message-system-event-avatar" ${avatarStyle}>${avatarHtml}</div>
9755
+ <div class="messenger-message-system-event-avatar">${logoHtml}</div>
9517
9756
  <span class="messenger-message-system-event-name">${this._escapeHtml(name)}</span>
9518
9757
  <span class="messenger-message-system-event-action">${this._escapeHtml(action)}</span>
9519
9758
  ${timeStr ? `<span class="messenger-message-system-event-time">${timeStr}</span>` : ''}
@@ -9593,6 +9832,20 @@
9593
9832
  const tempDiv = document.createElement('div');
9594
9833
  tempDiv.innerHTML = messageHtml;
9595
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
+ }
9596
9849
  }
9597
9850
 
9598
9851
  _scrollToBottom() {
@@ -9606,6 +9859,64 @@
9606
9859
  }
9607
9860
  }
9608
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
+
9609
9920
  _updateSendButtonState() {
9610
9921
  const input = this.element.querySelector('.messenger-compose-input');
9611
9922
  const sendBtn = this.element.querySelector('.messenger-compose-send');
@@ -10065,8 +10376,9 @@
10065
10376
 
10066
10377
  <div class="messenger-conversations-footer">
10067
10378
  <button class="messenger-new-message-btn">
10068
- <span>Send us a message</span>
10069
- <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>
10070
10382
  </button>
10071
10383
  </div>
10072
10384
  `;
@@ -10075,14 +10387,16 @@
10075
10387
  }
10076
10388
 
10077
10389
  _renderConversationItem(conversation) {
10390
+ const isClosed = conversation.status === 'closed';
10078
10391
  const unreadClass = conversation.unread > 0 ? 'unread' : '';
10392
+ const closedClass = isClosed ? 'closed' : '';
10079
10393
  const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
10080
10394
  const avatarsHtml = this._renderConversationAvatars(
10081
10395
  conversation.participants
10082
10396
  );
10083
10397
 
10084
10398
  return `
10085
- <div class="messenger-conversation-item ${unreadClass}" data-conversation-id="${conversation.id}">
10399
+ <div class="messenger-conversation-item ${unreadClass} ${closedClass}" data-conversation-id="${conversation.id}">
10086
10400
  <div class="messenger-conversation-avatars">
10087
10401
  ${avatarsHtml}
10088
10402
  </div>
@@ -10093,6 +10407,7 @@
10093
10407
  </div>
10094
10408
  <div class="messenger-conversation-preview">
10095
10409
  ${conversation.unread > 0 ? '<span class="messenger-unread-dot"></span>' : ''}
10410
+ ${isClosed ? '<span class="messenger-conversation-resolved-badge">Resolved</span>' : ''}
10096
10411
  <span class="messenger-conversation-message">${this._truncateMessage(conversation.lastMessage)}</span>
10097
10412
  </div>
10098
10413
  </div>
@@ -10130,7 +10445,7 @@
10130
10445
 
10131
10446
  const avatarItems = avatars
10132
10447
  .slice(0, 2)
10133
- .map((avatar, index) => {
10448
+ .map((avatar) => {
10134
10449
  if (typeof avatar === 'string' && avatar.startsWith('http')) {
10135
10450
  return `<div class="sdk-avatar sdk-avatar-sm"><img src="${avatar}" alt="Team member" /></div>`;
10136
10451
  }
@@ -10518,7 +10833,6 @@
10518
10833
  <div class="messenger-home-welcome">
10519
10834
  <span class="messenger-home-greeting">${this.state.greetingMessage}</span>
10520
10835
  <span class="messenger-home-question">${this.state.welcomeMessage}</span>
10521
- ${this._renderAvailabilityStatus()}
10522
10836
  </div>
10523
10837
  </div>
10524
10838
 
@@ -10582,24 +10896,20 @@
10582
10896
  const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
10583
10897
  const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
10584
10898
 
10585
- if (openConversation) {
10586
- return `
10587
- <button class="messenger-home-message-btn messenger-home-continue-btn" data-conversation-id="${openConversation.id}">
10588
- <div class="messenger-home-continue-info">
10589
- <span class="messenger-home-continue-label">Continue conversation</span>
10590
- </div>
10591
- ${sendIcon}
10592
- </button>
10593
- <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
10594
- <span class="messenger-home-continue-label">Leave us feedback</span>
10595
- ${caretIcon}
10596
- </button>
10597
- `;
10598
- }
10899
+ const responseTime =
10900
+ this.state.responseTime || 'We typically reply within a few minutes';
10901
+
10902
+ const recentCardHtml = openConversation
10903
+ ? this._renderRecentMessageCard(openConversation)
10904
+ : '';
10599
10905
 
10600
10906
  return `
10907
+ ${recentCardHtml}
10601
10908
  <button class="messenger-home-message-btn">
10602
- <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>
10603
10913
  ${sendIcon}
10604
10914
  </button>
10605
10915
  <button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
@@ -10609,6 +10919,55 @@
10609
10919
  `;
10610
10920
  }
10611
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
+
10612
10971
  _renderFeaturedCard() {
10613
10972
  if (!this.options.featuredContent) {
10614
10973
  return '';
@@ -10686,22 +11045,26 @@
10686
11045
  }
10687
11046
 
10688
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
+
10689
11061
  const msgBtn = this.element.querySelector(
10690
11062
  '.messenger-home-message-btn:not(.messenger-feedback-btn)'
10691
11063
  );
10692
11064
  if (msgBtn) {
10693
11065
  msgBtn.addEventListener('click', () => {
10694
- const convId = msgBtn.dataset.conversationId;
10695
- if (convId) {
10696
- this.state.setActiveConversation(convId);
10697
- this.state.setView('chat');
10698
- if (this.options.onSelectConversation) {
10699
- this.options.onSelectConversation(convId);
10700
- }
10701
- } else {
10702
- this.state.setActiveConversation(null);
10703
- this.state.setView('chat');
10704
- }
11066
+ this.state.setActiveConversation(null);
11067
+ this.state.setView('chat');
10705
11068
  });
10706
11069
  }
10707
11070
 
@@ -10709,9 +11072,8 @@
10709
11072
  if (feedbackBtn) {
10710
11073
  feedbackBtn.addEventListener('click', () => {
10711
11074
  if (this.options.onFeedbackClick) {
10712
- // Close the messenger panel first, then open the feedback modal
10713
11075
  this.state.setOpen(false);
10714
- setTimeout(() => this.options.onFeedbackClick(), 200);
11076
+ this.options.onFeedbackClick();
10715
11077
  } else if (this.state.urls?.feedback) {
10716
11078
  window.open(this.state.urls.feedback, '_blank');
10717
11079
  }
@@ -10881,6 +11243,7 @@
10881
11243
  class MessengerWidget extends BaseWidget {
10882
11244
  constructor(options) {
10883
11245
  super({ ...options, type: 'messenger' });
11246
+ this._explicitOptions = options || {};
10884
11247
  const resolvedTheme = options.theme || 'light';
10885
11248
  const hasExplicitTextColor = Object.prototype.hasOwnProperty.call(
10886
11249
  options,
@@ -10925,6 +11288,7 @@
10925
11288
  changelogUrl: options.changelogUrl || null,
10926
11289
  helpUrl: options.helpUrl || null,
10927
11290
  roadmapUrl: options.roadmapUrl || null,
11291
+ composePlaceholder: options.composePlaceholder || null,
10928
11292
  onSendMessage: options.onSendMessage || null,
10929
11293
  onFeedbackClick: options.onFeedbackClick || null,
10930
11294
  onArticleClick: options.onArticleClick || null,
@@ -11150,6 +11514,11 @@
11150
11514
  name: contactData.name,
11151
11515
  email: contactData.email,
11152
11516
  });
11517
+
11518
+ // Start WebSocket now that session token is available
11519
+ if (this.apiService?.sessionToken && !this.wsService?.isConnected) {
11520
+ this._initWebSocket();
11521
+ }
11153
11522
  }
11154
11523
 
11155
11524
  return response;
@@ -11162,6 +11531,11 @@
11162
11531
  markAsIdentified(name, email) {
11163
11532
  this.messengerState.setIdentified(true, { name, email });
11164
11533
  console.log('[MessengerWidget] Marked as identified:', email);
11534
+
11535
+ // Start WebSocket now that we have a session token
11536
+ if (this.apiService?.sessionToken && !this.wsService?.isConnected) {
11537
+ this._initWebSocket();
11538
+ }
11165
11539
  }
11166
11540
 
11167
11541
  async _handleUploadFile(base64Data, filename) {
@@ -11357,6 +11731,12 @@
11357
11731
  this._wsUnsubscribers.push(
11358
11732
  this.wsService.on('disconnected', () => {
11359
11733
  console.log('[MessengerWidget] WebSocket disconnected');
11734
+ this.messengerState._notify('connectionChange', { connected: false });
11735
+ })
11736
+ );
11737
+ this._wsUnsubscribers.push(
11738
+ this.wsService.on('connected', () => {
11739
+ this.messengerState._notify('connectionChange', { connected: true });
11360
11740
  })
11361
11741
  );
11362
11742
 
@@ -11666,6 +12046,39 @@
11666
12046
  }
11667
12047
  }
11668
12048
 
12049
+ async _fetchAndApplySettings() {
12050
+ try {
12051
+ const response = await this.apiService.getMessengerSettings();
12052
+ if (!response?.status || !response?.data) return;
12053
+
12054
+ const s = response.data;
12055
+
12056
+ // Only apply values that were NOT explicitly passed in options
12057
+ if (s.team_name && !this._hasExplicitOption('teamName')) {
12058
+ this.messengerOptions.teamName = s.team_name;
12059
+ this.messengerState.teamName = s.team_name;
12060
+ }
12061
+ if (s.logo_url && !this._hasExplicitOption('logoUrl')) {
12062
+ this.messengerOptions.logoUrl = s.logo_url;
12063
+ }
12064
+ if (s.greeting_message && !this._hasExplicitOption('greetingMessage')) {
12065
+ this.messengerState.greetingMessage = s.greeting_message;
12066
+ }
12067
+ if (s.response_time && !this._hasExplicitOption('responseTime')) {
12068
+ this.messengerState.responseTime = s.response_time;
12069
+ }
12070
+
12071
+ // Notify views to re-render with new values
12072
+ this.messengerState._notify('availabilityUpdate', {});
12073
+ } catch (e) {
12074
+ // non-fatal
12075
+ }
12076
+ }
12077
+
12078
+ _hasExplicitOption(key) {
12079
+ return Object.prototype.hasOwnProperty.call(this._explicitOptions || {}, key);
12080
+ }
12081
+
11669
12082
  async checkAgentAvailability() {
11670
12083
  try {
11671
12084
  const response = await this.apiService.checkAgentsOnline();
@@ -11811,6 +12224,9 @@
11811
12224
  this._applyPreviewData();
11812
12225
 
11813
12226
  if (this.messengerOptions.autoLoadData) {
12227
+ // Fetch workspace settings and apply only if not explicitly configured
12228
+ this._fetchAndApplySettings();
12229
+
11814
12230
  this.loadInitialData();
11815
12231
 
11816
12232
  if (this.apiService?.sessionToken) {