@product7/feedback-sdk 1.3.6 → 1.3.8
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.
- package/dist/feedback-sdk.js +136 -398
- package/dist/feedback-sdk.js.map +1 -1
- package/dist/feedback-sdk.min.js +1 -1
- package/dist/feedback-sdk.min.js.map +1 -1
- package/package.json +1 -1
- package/src/api/services/MessengerService.js +8 -2
- package/src/widgets/MessengerWidget.js +61 -92
- package/src/widgets/messenger/MessengerState.js +47 -119
- package/src/widgets/messenger/views/ChatView.js +10 -182
- package/src/widgets/messenger/views/PreChatFormView.js +14 -8
|
@@ -8,8 +8,7 @@ export class ChatView {
|
|
|
8
8
|
this._isTyping = false;
|
|
9
9
|
this._typingIndicator = null;
|
|
10
10
|
this._isConversationClosed = false;
|
|
11
|
-
this.
|
|
12
|
-
this._pendingAttachments = [];
|
|
11
|
+
this._pendingAttachments = [];
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
render() {
|
|
@@ -71,8 +70,6 @@ export class ChatView {
|
|
|
71
70
|
? 'Conversation closed'
|
|
72
71
|
: 'Write a message...';
|
|
73
72
|
|
|
74
|
-
const existingName = this.state.userContext?.name || '';
|
|
75
|
-
|
|
76
73
|
this.element.innerHTML = `
|
|
77
74
|
<div class="messenger-chat-header">
|
|
78
75
|
<button class="sdk-btn-icon messenger-back-btn" aria-label="Back">
|
|
@@ -125,46 +122,17 @@ export class ChatView {
|
|
|
125
122
|
<input type="file" class="messenger-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
|
|
126
123
|
</div>
|
|
127
124
|
`}
|
|
128
|
-
|
|
129
|
-
<div class="messenger-email-overlay">
|
|
130
|
-
<div class="sdk-card messenger-email-card">
|
|
131
|
-
<div class="sdk-card-header">
|
|
132
|
-
<h4>What is your email address?</h4>
|
|
133
|
-
<p>Enter your email to know when we reply:</p>
|
|
134
|
-
</div>
|
|
135
|
-
<div class="sdk-card-body">
|
|
136
|
-
<div class="sdk-form-group">
|
|
137
|
-
<input type="text" class="sdk-input messenger-email-name" placeholder="Name (optional)" value="${this._escapeHtml(existingName)}" autocomplete="name" />
|
|
138
|
-
</div>
|
|
139
|
-
<div class="sdk-form-group">
|
|
140
|
-
<input type="email" class="sdk-input messenger-email-input" placeholder="Enter your email address..." autocomplete="email" />
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
<div class="sdk-card-footer messenger-email-actions">
|
|
144
|
-
<button class="sdk-btn sdk-btn-primary sdk-btn-block messenger-email-submit" disabled>Set my email</button>
|
|
145
|
-
<button class="sdk-btn sdk-btn-secondary sdk-btn-block messenger-email-skip">Skip</button>
|
|
146
|
-
</div>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
125
|
`;
|
|
150
126
|
|
|
151
|
-
this._typingIndicator = this.element.querySelector(
|
|
152
|
-
'.messenger-typing-indicator'
|
|
153
|
-
);
|
|
127
|
+
this._typingIndicator = this.element.querySelector('.messenger-typing-indicator');
|
|
154
128
|
this._attachEvents();
|
|
155
129
|
this._scrollToBottom();
|
|
156
130
|
this._renderAttachmentPreviews();
|
|
157
|
-
|
|
158
|
-
// Show email overlay after first message sent without email
|
|
159
|
-
if (this._showEmailOverlayFlag) {
|
|
160
|
-
this._showEmailOverlay();
|
|
161
|
-
}
|
|
162
131
|
}
|
|
163
132
|
|
|
164
133
|
_renderEmptyState(isNewConversation = false) {
|
|
165
134
|
const avatarHtml = this._renderTeamAvatars();
|
|
166
|
-
const responseTime =
|
|
167
|
-
this.state.responseTime || 'We typically reply within a few minutes';
|
|
135
|
+
const responseTime = this.state.responseTime || 'We typically reply within a few minutes';
|
|
168
136
|
const isOnline = this.state.agentsOnline;
|
|
169
137
|
|
|
170
138
|
return `
|
|
@@ -196,14 +164,11 @@ export class ChatView {
|
|
|
196
164
|
|
|
197
165
|
_renderMessage(message) {
|
|
198
166
|
const isOwn = message.isOwn;
|
|
199
|
-
const messageClass = isOwn
|
|
200
|
-
? 'messenger-message-own'
|
|
201
|
-
: 'messenger-message-received';
|
|
167
|
+
const messageClass = isOwn ? 'messenger-message-own' : 'messenger-message-received';
|
|
202
168
|
const timeStr = this._formatMessageTime(message.timestamp);
|
|
203
169
|
const attachmentsHtml = this._renderMessageAttachments(message.attachments);
|
|
204
170
|
|
|
205
171
|
const contentHtml = message.content ? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>` : '';
|
|
206
|
-
|
|
207
172
|
const bubbleHtml = contentHtml ? `<div class="messenger-message-bubble">${contentHtml}</div>` : '';
|
|
208
173
|
|
|
209
174
|
if (isOwn) {
|
|
@@ -293,9 +258,7 @@ export class ChatView {
|
|
|
293
258
|
}
|
|
294
259
|
|
|
295
260
|
_appendMessage(message) {
|
|
296
|
-
const messagesContainer = this.element.querySelector(
|
|
297
|
-
'.messenger-chat-messages'
|
|
298
|
-
);
|
|
261
|
+
const messagesContainer = this.element.querySelector('.messenger-chat-messages');
|
|
299
262
|
const emptyState = messagesContainer.querySelector('.messenger-chat-empty');
|
|
300
263
|
if (emptyState) {
|
|
301
264
|
emptyState.remove();
|
|
@@ -308,9 +271,7 @@ export class ChatView {
|
|
|
308
271
|
}
|
|
309
272
|
|
|
310
273
|
_scrollToBottom() {
|
|
311
|
-
const messagesContainer = this.element.querySelector(
|
|
312
|
-
'.messenger-chat-messages'
|
|
313
|
-
);
|
|
274
|
+
const messagesContainer = this.element.querySelector('.messenger-chat-messages');
|
|
314
275
|
if (messagesContainer) {
|
|
315
276
|
setTimeout(() => {
|
|
316
277
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
@@ -350,7 +311,6 @@ export class ChatView {
|
|
|
350
311
|
`;
|
|
351
312
|
}).join('');
|
|
352
313
|
|
|
353
|
-
// Attach remove button events
|
|
354
314
|
container.querySelectorAll('.messenger-attachment-remove').forEach((btn) => {
|
|
355
315
|
btn.addEventListener('click', (e) => {
|
|
356
316
|
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
@@ -374,20 +334,15 @@ export class ChatView {
|
|
|
374
334
|
this.state.setOpen(false);
|
|
375
335
|
});
|
|
376
336
|
|
|
377
|
-
// Compose input (not rendered when conversation is closed)
|
|
378
337
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
379
338
|
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
380
339
|
|
|
381
340
|
if (input && sendBtn) {
|
|
382
341
|
input.addEventListener('input', () => {
|
|
383
|
-
// Auto-resize textarea
|
|
384
342
|
input.style.height = 'auto';
|
|
385
343
|
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
|
|
386
|
-
|
|
387
|
-
// Enable/disable send button
|
|
388
344
|
this._updateSendButtonState();
|
|
389
345
|
|
|
390
|
-
// Send typing indicator
|
|
391
346
|
if (input.value.trim()) {
|
|
392
347
|
this._startTyping();
|
|
393
348
|
}
|
|
@@ -405,7 +360,6 @@ export class ChatView {
|
|
|
405
360
|
});
|
|
406
361
|
}
|
|
407
362
|
|
|
408
|
-
// Attach button + file input
|
|
409
363
|
const attachBtn = this.element.querySelector('.messenger-compose-attach');
|
|
410
364
|
const fileInput = this.element.querySelector('.messenger-compose-file-input');
|
|
411
365
|
|
|
@@ -434,42 +388,9 @@ export class ChatView {
|
|
|
434
388
|
});
|
|
435
389
|
}
|
|
436
390
|
|
|
437
|
-
// Email overlay events
|
|
438
|
-
const emailInput = this.element.querySelector('.messenger-email-input');
|
|
439
|
-
const emailSubmit = this.element.querySelector('.messenger-email-submit');
|
|
440
|
-
const emailSkip = this.element.querySelector('.messenger-email-skip');
|
|
441
|
-
|
|
442
|
-
if (emailInput) {
|
|
443
|
-
emailInput.addEventListener('input', () => {
|
|
444
|
-
const isValid = this._isValidEmail(emailInput.value.trim());
|
|
445
|
-
emailSubmit.disabled = !isValid;
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
emailInput.addEventListener('keydown', (e) => {
|
|
449
|
-
if (e.key === 'Enter' && !emailSubmit.disabled) {
|
|
450
|
-
e.preventDefault();
|
|
451
|
-
this._handleEmailSubmit();
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (emailSubmit) {
|
|
457
|
-
emailSubmit.addEventListener('click', () => {
|
|
458
|
-
this._handleEmailSubmit();
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (emailSkip) {
|
|
463
|
-
emailSkip.addEventListener('click', () => {
|
|
464
|
-
this._hideEmailOverlay();
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Delegated events for attachment clicks
|
|
469
391
|
const messagesContainer = this.element.querySelector('.messenger-chat-messages');
|
|
470
392
|
if (messagesContainer) {
|
|
471
393
|
messagesContainer.addEventListener('click', (e) => {
|
|
472
|
-
// File click -> download
|
|
473
394
|
const fileLink = e.target.closest('.messenger-message-file');
|
|
474
395
|
if (fileLink) {
|
|
475
396
|
e.preventDefault();
|
|
@@ -479,7 +400,6 @@ export class ChatView {
|
|
|
479
400
|
return;
|
|
480
401
|
}
|
|
481
402
|
|
|
482
|
-
// Image click -> open in new tab
|
|
483
403
|
const img = e.target.closest('.messenger-message-image');
|
|
484
404
|
if (img) {
|
|
485
405
|
const url = img.dataset.url || img.src;
|
|
@@ -503,7 +423,6 @@ export class ChatView {
|
|
|
503
423
|
document.body.removeChild(a);
|
|
504
424
|
URL.revokeObjectURL(blobUrl);
|
|
505
425
|
} catch {
|
|
506
|
-
// Fallback: open in new tab
|
|
507
426
|
window.open(url, '_blank');
|
|
508
427
|
}
|
|
509
428
|
}
|
|
@@ -513,72 +432,9 @@ export class ChatView {
|
|
|
513
432
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
514
433
|
}
|
|
515
434
|
|
|
516
|
-
_isValidEmail(email) {
|
|
517
|
-
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
_showEmailOverlay() {
|
|
521
|
-
const overlay = this.element.querySelector('.messenger-email-overlay');
|
|
522
|
-
if (overlay) {
|
|
523
|
-
overlay.style.display = 'flex';
|
|
524
|
-
const emailInput = overlay.querySelector('.messenger-email-input');
|
|
525
|
-
if (emailInput) {
|
|
526
|
-
setTimeout(() => emailInput.focus(), 100);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
_startPendingConversation() {
|
|
532
|
-
if (this._pendingMessage && this.options.onStartConversation) {
|
|
533
|
-
this.options.onStartConversation(this._pendingMessage, this._pendingAttachmentsForSend || []);
|
|
534
|
-
this._pendingMessage = null;
|
|
535
|
-
this._pendingAttachmentsForSend = null;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
_hideEmailOverlay() {
|
|
540
|
-
this._showEmailOverlayFlag = false;
|
|
541
|
-
const overlay = this.element.querySelector('.messenger-email-overlay');
|
|
542
|
-
if (overlay) {
|
|
543
|
-
overlay.style.display = 'none';
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
async _handleEmailSubmit() {
|
|
548
|
-
const nameInput = this.element.querySelector('.messenger-email-name');
|
|
549
|
-
const emailInput = this.element.querySelector('.messenger-email-input');
|
|
550
|
-
const submitBtn = this.element.querySelector('.messenger-email-submit');
|
|
551
|
-
|
|
552
|
-
const name = nameInput?.value.trim() || '';
|
|
553
|
-
const email = emailInput?.value.trim();
|
|
554
|
-
|
|
555
|
-
if (!email || !this._isValidEmail(email)) return;
|
|
556
|
-
|
|
557
|
-
submitBtn.disabled = true;
|
|
558
|
-
submitBtn.textContent = 'Saving...';
|
|
559
|
-
|
|
560
|
-
try {
|
|
561
|
-
if (this.options.onIdentifyContact) {
|
|
562
|
-
await this.options.onIdentifyContact({ name, email });
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
if (!this.state.userContext) {
|
|
566
|
-
this.state.userContext = {};
|
|
567
|
-
}
|
|
568
|
-
this.state.userContext.name = name;
|
|
569
|
-
this.state.userContext.email = email;
|
|
570
|
-
|
|
571
|
-
this._hideEmailOverlay();
|
|
572
|
-
this._startPendingConversation();
|
|
573
|
-
} catch (error) {
|
|
574
|
-
console.error('[ChatView] Failed to save email:', error);
|
|
575
|
-
submitBtn.disabled = false;
|
|
576
|
-
submitBtn.textContent = 'Set my email';
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
|
|
580
435
|
async _sendMessage() {
|
|
581
436
|
if (this._isConversationClosed) return;
|
|
437
|
+
|
|
582
438
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
583
439
|
const content = input.value.trim();
|
|
584
440
|
const hasAttachments = this._pendingAttachments.length > 0;
|
|
@@ -587,37 +443,12 @@ export class ChatView {
|
|
|
587
443
|
|
|
588
444
|
this._stopTyping();
|
|
589
445
|
|
|
590
|
-
// Collect attachments to upload
|
|
591
446
|
const attachmentsToSend = [...this._pendingAttachments];
|
|
592
|
-
|
|
593
447
|
const isNewConversation = !this.state.activeConversationId;
|
|
594
|
-
const needsContactInfo = !this.state.userContext?.email;
|
|
595
448
|
|
|
596
449
|
if (isNewConversation) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
id: 'msg_' + Date.now(),
|
|
600
|
-
content: content,
|
|
601
|
-
isOwn: true,
|
|
602
|
-
timestamp: new Date().toISOString(),
|
|
603
|
-
attachments: attachmentsToSend.map((a) => ({
|
|
604
|
-
url: a.preview,
|
|
605
|
-
type: a.type.startsWith('image') ? 'image' : 'file',
|
|
606
|
-
name: a.file.name,
|
|
607
|
-
})),
|
|
608
|
-
};
|
|
609
|
-
this._appendMessage(localMessage);
|
|
610
|
-
this._scrollToBottom();
|
|
611
|
-
|
|
612
|
-
if (needsContactInfo) {
|
|
613
|
-
this._pendingMessage = content;
|
|
614
|
-
this._pendingAttachmentsForSend = attachmentsToSend;
|
|
615
|
-
this._showEmailOverlayFlag = true;
|
|
616
|
-
setTimeout(() => this._showEmailOverlay(), 300);
|
|
617
|
-
} else {
|
|
618
|
-
if (this.options.onStartConversation) {
|
|
619
|
-
this.options.onStartConversation(content, attachmentsToSend);
|
|
620
|
-
}
|
|
450
|
+
if (this.options.onStartConversation) {
|
|
451
|
+
this.options.onStartConversation(content, attachmentsToSend);
|
|
621
452
|
}
|
|
622
453
|
} else {
|
|
623
454
|
const message = {
|
|
@@ -639,7 +470,6 @@ export class ChatView {
|
|
|
639
470
|
}
|
|
640
471
|
}
|
|
641
472
|
|
|
642
|
-
// Clear input and attachments
|
|
643
473
|
input.value = '';
|
|
644
474
|
input.style.height = 'auto';
|
|
645
475
|
this._pendingAttachments = [];
|
|
@@ -680,9 +510,7 @@ export class ChatView {
|
|
|
680
510
|
_showTypingIndicator(userName) {
|
|
681
511
|
if (this._typingIndicator) {
|
|
682
512
|
this._typingIndicator.style.display = 'flex';
|
|
683
|
-
const textEl = this._typingIndicator.querySelector(
|
|
684
|
-
'.messenger-typing-text'
|
|
685
|
-
);
|
|
513
|
+
const textEl = this._typingIndicator.querySelector('.messenger-typing-text');
|
|
686
514
|
if (textEl) {
|
|
687
515
|
textEl.textContent = `${userName || 'Support'} is typing...`;
|
|
688
516
|
}
|
|
@@ -99,7 +99,6 @@ export class PreChatFormView {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
_attachEvents() {
|
|
102
|
-
// Form validation
|
|
103
102
|
const form = this.element.querySelector('form');
|
|
104
103
|
const emailInput = this.element.querySelector('#messenger-prechat-email');
|
|
105
104
|
const submitBtn = this.element.querySelector('.messenger-prechat-submit');
|
|
@@ -184,15 +183,22 @@ export class PreChatFormView {
|
|
|
184
183
|
await this.options.onIdentifyContact({ name, email });
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
|
|
188
|
-
this.state.userContext = {};
|
|
189
|
-
}
|
|
190
|
-
this.state.userContext.name = name;
|
|
191
|
-
this.state.userContext.email = email;
|
|
186
|
+
this.state.setIdentified(true, { name, email });
|
|
192
187
|
|
|
193
188
|
this._isSubmitting = false;
|
|
194
189
|
|
|
195
|
-
this.state.
|
|
190
|
+
const pendingMessage = this.state.pendingMessage;
|
|
191
|
+
if (pendingMessage && this.options.onStartConversation) {
|
|
192
|
+
this.state.pendingMessage = null;
|
|
193
|
+
this.state.setView('chat');
|
|
194
|
+
|
|
195
|
+
await this.options.onStartConversation(
|
|
196
|
+
pendingMessage.content,
|
|
197
|
+
pendingMessage.attachments
|
|
198
|
+
);
|
|
199
|
+
} else {
|
|
200
|
+
this.state.setView('chat');
|
|
201
|
+
}
|
|
196
202
|
} catch (error) {
|
|
197
203
|
console.error('[PreChatFormView] Error submitting form:', error);
|
|
198
204
|
this._showError('messenger-email-error', 'Something went wrong. Please try again.');
|
|
@@ -209,4 +215,4 @@ export class PreChatFormView {
|
|
|
209
215
|
this.element.parentNode.removeChild(this.element);
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
|
-
}
|
|
218
|
+
}
|