@product7/feedback-sdk 1.4.7 → 1.4.9

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.
@@ -2165,6 +2165,7 @@
2165
2165
  constructor(options) {
2166
2166
  super({ ...options, type: 'button' });
2167
2167
  this.isMinimized = false;
2168
+ this._hiddenForOpenPanel = false;
2168
2169
  }
2169
2170
 
2170
2171
  _render() {
@@ -2251,6 +2252,28 @@
2251
2252
  this.element.classList.remove('minimized');
2252
2253
  }
2253
2254
 
2255
+ openPanel() {
2256
+ if (!this.state.isOpen) {
2257
+ this._hiddenForOpenPanel = true;
2258
+ this.hide();
2259
+ }
2260
+ super.openPanel();
2261
+ }
2262
+
2263
+ closePanel() {
2264
+ const shouldRestoreButton = this._hiddenForOpenPanel;
2265
+ super.closePanel();
2266
+
2267
+ if (shouldRestoreButton) {
2268
+ setTimeout(() => {
2269
+ if (!this.destroyed) {
2270
+ this.show();
2271
+ }
2272
+ this._hiddenForOpenPanel = false;
2273
+ }, 320);
2274
+ }
2275
+ }
2276
+
2254
2277
  mount(container) {
2255
2278
  super.mount(container);
2256
2279
  }
@@ -3475,23 +3498,133 @@
3475
3498
  this._notify('messagesUpdate', { conversationId, messages });
3476
3499
  }
3477
3500
 
3478
- addMessage(conversationId, message) {
3501
+ _getMessageAttachmentsSignature(message) {
3502
+ if (!Array.isArray(message?.attachments) || message.attachments.length === 0) {
3503
+ return '';
3504
+ }
3505
+
3506
+ return message.attachments
3507
+ .map((att) => `${att?.type || ''}:${att?.name || ''}`)
3508
+ .join('|');
3509
+ }
3510
+
3511
+ _findOptimisticMatchIndex(conversationId, incomingMessage, matchWindowMs) {
3512
+ const messages = this.messages[conversationId] || [];
3513
+ const incomingTimestamp = Date.parse(incomingMessage.timestamp);
3514
+ const incomingSignature =
3515
+ this._getMessageAttachmentsSignature(incomingMessage);
3516
+
3517
+ for (let i = messages.length - 1; i >= 0; i--) {
3518
+ const candidate = messages[i];
3519
+ if (!candidate?.isOptimistic || !candidate?.isOwn) {
3520
+ continue;
3521
+ }
3522
+ if ((candidate.content || '') !== (incomingMessage.content || '')) {
3523
+ continue;
3524
+ }
3525
+
3526
+ const candidateSignature =
3527
+ this._getMessageAttachmentsSignature(candidate);
3528
+ if (candidateSignature !== incomingSignature) {
3529
+ continue;
3530
+ }
3531
+
3532
+ const candidateTimestamp = Date.parse(candidate.timestamp);
3533
+ if (
3534
+ Number.isNaN(incomingTimestamp) ||
3535
+ Number.isNaN(candidateTimestamp) ||
3536
+ Math.abs(incomingTimestamp - candidateTimestamp) <= matchWindowMs
3537
+ ) {
3538
+ return i;
3539
+ }
3540
+ }
3541
+
3542
+ return -1;
3543
+ }
3544
+
3545
+ _updateConversationFromMessage(conversationId, message, countUnread) {
3546
+ const conv = this.conversations.find((c) => c.id === conversationId);
3547
+ if (!conv) {
3548
+ return;
3549
+ }
3550
+
3551
+ conv.lastMessage = message.content;
3552
+ conv.lastMessageTime = message.timestamp;
3553
+
3554
+ if (countUnread && !message.isOwn) {
3555
+ conv.unread = (conv.unread || 0) + 1;
3556
+ this._updateUnreadCount();
3557
+ }
3558
+ }
3559
+
3560
+ upsertMessage(conversationId, message, options = {}) {
3479
3561
  if (!this.messages[conversationId]) {
3480
3562
  this.messages[conversationId] = [];
3481
3563
  }
3482
- this.messages[conversationId].push(message);
3483
3564
 
3484
- const conv = this.conversations.find((c) => c.id === conversationId);
3485
- if (conv) {
3486
- conv.lastMessage = message.content;
3487
- conv.lastMessageTime = message.timestamp;
3488
- if (!message.isOwn) {
3489
- conv.unread = (conv.unread || 0) + 1;
3490
- this._updateUnreadCount();
3565
+ const reconcileOwnOptimistic = options.reconcileOwnOptimistic === true;
3566
+ const optimisticMatchWindowMs = options.optimisticMatchWindowMs || 30000;
3567
+ const messages = this.messages[conversationId];
3568
+ const existingIndex =
3569
+ message?.id != null
3570
+ ? messages.findIndex((msg) => msg?.id === message.id)
3571
+ : -1;
3572
+
3573
+ if (existingIndex !== -1) {
3574
+ messages[existingIndex] = {
3575
+ ...messages[existingIndex],
3576
+ ...message,
3577
+ isOptimistic: false,
3578
+ };
3579
+ this._updateConversationFromMessage(
3580
+ conversationId,
3581
+ messages[existingIndex],
3582
+ false
3583
+ );
3584
+ this._notify('messagesUpdate', {
3585
+ conversationId,
3586
+ messages: [...messages],
3587
+ });
3588
+ return messages[existingIndex];
3589
+ }
3590
+
3591
+ if (reconcileOwnOptimistic && message?.isOwn) {
3592
+ const optimisticIndex = this._findOptimisticMatchIndex(
3593
+ conversationId,
3594
+ message,
3595
+ optimisticMatchWindowMs
3596
+ );
3597
+ if (optimisticIndex !== -1) {
3598
+ messages[optimisticIndex] = {
3599
+ ...messages[optimisticIndex],
3600
+ ...message,
3601
+ isOptimistic: false,
3602
+ };
3603
+ this._updateConversationFromMessage(
3604
+ conversationId,
3605
+ messages[optimisticIndex],
3606
+ false
3607
+ );
3608
+ this._notify('messagesUpdate', {
3609
+ conversationId,
3610
+ messages: [...messages],
3611
+ });
3612
+ return messages[optimisticIndex];
3491
3613
  }
3492
3614
  }
3493
3615
 
3494
- this._notify('messageAdded', { conversationId, message });
3616
+ const storedMessage = {
3617
+ ...message,
3618
+ isOptimistic: Boolean(message?.isOptimistic),
3619
+ };
3620
+ messages.push(storedMessage);
3621
+ this._updateConversationFromMessage(conversationId, storedMessage, true);
3622
+ this._notify('messageAdded', { conversationId, message: storedMessage });
3623
+ return storedMessage;
3624
+ }
3625
+
3626
+ addMessage(conversationId, message) {
3627
+ return this.upsertMessage(conversationId, message);
3495
3628
  }
3496
3629
 
3497
3630
  updateConversation(conversationId, updates) {
@@ -4637,6 +4770,7 @@
4637
4770
  id: 'msg_' + Date.now(),
4638
4771
  content: content,
4639
4772
  isOwn: true,
4773
+ isOptimistic: true,
4640
4774
  timestamp: new Date().toISOString(),
4641
4775
  attachments: attachmentsToSend.map((a) => ({
4642
4776
  url: a.preview,
@@ -6039,7 +6173,10 @@
6039
6173
  },
6040
6174
  };
6041
6175
 
6042
- this.messengerState.addMessage(conversation_id, localMessage);
6176
+ this.messengerState.upsertMessage(conversation_id, localMessage, {
6177
+ reconcileOwnOptimistic: true,
6178
+ optimisticMatchWindowMs: 30000,
6179
+ });
6043
6180
 
6044
6181
  if (
6045
6182
  !this.messengerState.isOpen ||
@@ -9094,44 +9231,30 @@
9094
9231
  }
9095
9232
 
9096
9233
  .feedback-panel {
9097
- width: 100%;
9234
+ width: min(420px, calc(100vw - (var(--spacing-4) * 2)));
9235
+ max-height: min(500px, calc(100vh - 88px));
9098
9236
  top: auto;
9099
- bottom: 0;
9100
- right: 0;
9101
- left: 0;
9102
- height: 85vh;
9103
- max-height: 85vh;
9104
- transform: translateY(100%);
9105
- border-radius: var(--radius-3xl) var(--radius-3xl) 0 0;
9237
+ bottom: 72px;
9238
+ right: var(--spacing-4);
9239
+ left: auto;
9240
+ transform: translateX(calc(100% + 24px));
9241
+ border-radius: var(--radius-2xl);
9106
9242
  }
9107
9243
 
9108
9244
  .feedback-panel.open {
9109
- transform: translateY(0);
9245
+ transform: translateX(0);
9110
9246
  }
9111
9247
 
9112
9248
  .feedback-panel-header {
9113
- padding: var(--spacing-5);
9114
- position: relative;
9115
- }
9116
-
9117
- .feedback-panel-header::before {
9118
- content: '';
9119
- position: absolute;
9120
- top: var(--spacing-2);
9121
- left: 50%;
9122
- transform: translateX(-50%);
9123
- width: 40px;
9124
- height: 4px;
9125
- background: var(--color-neutral-300);
9126
- border-radius: 2px;
9249
+ padding: var(--spacing-4) var(--spacing-6);
9127
9250
  }
9128
9251
 
9129
9252
  .feedback-panel-body {
9130
- padding: var(--spacing-5);
9253
+ padding: var(--spacing-6);
9131
9254
  }
9132
9255
 
9133
9256
  .feedback-form-group textarea {
9134
- min-height: 150px;
9257
+ min-height: 120px;
9135
9258
  }
9136
9259
  }
9137
9260
  `;