@product7/feedback-sdk 1.3.7 → 1.3.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.
- package/dist/feedback-sdk.js +3006 -2817
- 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/core/APIService.js +33 -14
- package/src/index.js +1 -1
- package/src/styles/base.js +1 -1
- package/src/styles/changelog.js +58 -40
- package/src/styles/components.js +19 -2
- package/src/styles/design-tokens.js +4 -4
- package/src/styles/feedback.js +3 -8
- package/src/styles/messenger-components.js +473 -0
- package/src/styles/messenger-core.js +37 -268
- package/src/styles/messenger-features.js +89 -267
- package/src/styles/messenger-views.js +391 -325
- package/src/styles/messenger.js +17 -558
- package/src/styles/styles.js +21 -24
- package/src/styles/{surveys.js → survey.js} +55 -20
- package/src/widgets/BaseWidget.js +1 -1
- package/src/widgets/ButtonWidget.js +1 -1
- package/src/widgets/ChangelogWidget.js +1 -1
- package/src/widgets/InlineWidget.js +1 -1
- package/src/widgets/MessengerWidget.js +74 -84
- package/src/widgets/SurveyWidget.js +1 -1
- package/src/widgets/TabWidget.js +1 -1
- package/src/widgets/messenger/MessengerState.js +50 -119
- package/src/widgets/messenger/components/MessengerLauncher.js +22 -18
- package/src/widgets/messenger/components/MessengerPanel.js +1 -1
- package/src/widgets/messenger/components/NavigationTabs.js +36 -15
- package/src/widgets/messenger/views/ChangelogView.js +8 -32
- package/src/widgets/messenger/views/ChatView.js +83 -219
- package/src/widgets/messenger/views/ConversationsView.js +67 -45
- package/src/widgets/messenger/views/HelpView.js +22 -32
- package/src/widgets/messenger/views/HomeView.js +58 -40
- package/src/widgets/messenger/views/PreChatFormView.js +47 -51
- package/src/styles/messenger-help.js +0 -298
- package/src/styles/messenger-themes.js +0 -500
|
@@ -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() {
|
|
@@ -41,7 +40,10 @@ export class ChatView {
|
|
|
41
40
|
data.conversationId === this.state.activeConversationId
|
|
42
41
|
) {
|
|
43
42
|
this._updateContent();
|
|
44
|
-
} else if (
|
|
43
|
+
} else if (
|
|
44
|
+
type === 'messagesUpdate' &&
|
|
45
|
+
data.conversationId === this.state.activeConversationId
|
|
46
|
+
) {
|
|
45
47
|
this._updateContent();
|
|
46
48
|
}
|
|
47
49
|
});
|
|
@@ -61,7 +63,6 @@ export class ChatView {
|
|
|
61
63
|
? this._renderEmptyState(isNewConversation)
|
|
62
64
|
: messages.map((msg) => this._renderMessage(msg)).join('');
|
|
63
65
|
|
|
64
|
-
const avatarHtml = this._renderConversationAvatar(conversation);
|
|
65
66
|
const title = isNewConversation
|
|
66
67
|
? 'New conversation'
|
|
67
68
|
: conversation?.title || 'Chat with team';
|
|
@@ -71,8 +72,6 @@ export class ChatView {
|
|
|
71
72
|
? 'Conversation closed'
|
|
72
73
|
: 'Write a message...';
|
|
73
74
|
|
|
74
|
-
const existingName = this.state.userContext?.name || '';
|
|
75
|
-
|
|
76
75
|
this.element.innerHTML = `
|
|
77
76
|
<div class="messenger-chat-header">
|
|
78
77
|
<button class="sdk-btn-icon messenger-back-btn" aria-label="Back">
|
|
@@ -81,7 +80,6 @@ export class ChatView {
|
|
|
81
80
|
</svg>
|
|
82
81
|
</button>
|
|
83
82
|
<div class="messenger-chat-header-info">
|
|
84
|
-
${avatarHtml}
|
|
85
83
|
<span class="messenger-chat-title">${title}</span>
|
|
86
84
|
</div>
|
|
87
85
|
<button class="sdk-close-btn" aria-label="Close">
|
|
@@ -93,12 +91,16 @@ export class ChatView {
|
|
|
93
91
|
|
|
94
92
|
<div class="messenger-chat-messages">
|
|
95
93
|
${messagesHtml}
|
|
96
|
-
${
|
|
94
|
+
${
|
|
95
|
+
isClosed
|
|
96
|
+
? `
|
|
97
97
|
<div class="messenger-closed-banner">
|
|
98
98
|
<i class="ph ph-check-circle"></i>
|
|
99
99
|
<span>This conversation has been resolved</span>
|
|
100
100
|
</div>
|
|
101
|
-
`
|
|
101
|
+
`
|
|
102
|
+
: ''
|
|
103
|
+
}
|
|
102
104
|
<div class="messenger-typing-indicator">
|
|
103
105
|
<div class="messenger-typing-dots">
|
|
104
106
|
<span></span><span></span><span></span>
|
|
@@ -107,7 +109,10 @@ export class ChatView {
|
|
|
107
109
|
</div>
|
|
108
110
|
</div>
|
|
109
111
|
|
|
110
|
-
${
|
|
112
|
+
${
|
|
113
|
+
isClosed
|
|
114
|
+
? ''
|
|
115
|
+
: `
|
|
111
116
|
<div class="messenger-compose-attachments-preview"></div>
|
|
112
117
|
|
|
113
118
|
<div class="messenger-chat-compose">
|
|
@@ -124,28 +129,8 @@ export class ChatView {
|
|
|
124
129
|
</button>
|
|
125
130
|
<input type="file" class="messenger-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
|
|
126
131
|
</div>
|
|
127
|
-
`
|
|
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>
|
|
132
|
+
`
|
|
133
|
+
}
|
|
149
134
|
`;
|
|
150
135
|
|
|
151
136
|
this._typingIndicator = this.element.querySelector(
|
|
@@ -154,11 +139,6 @@ export class ChatView {
|
|
|
154
139
|
this._attachEvents();
|
|
155
140
|
this._scrollToBottom();
|
|
156
141
|
this._renderAttachmentPreviews();
|
|
157
|
-
|
|
158
|
-
// Show email overlay after first message sent without email
|
|
159
|
-
if (this._showEmailOverlayFlag) {
|
|
160
|
-
this._showEmailOverlay();
|
|
161
|
-
}
|
|
162
142
|
}
|
|
163
143
|
|
|
164
144
|
_renderEmptyState(isNewConversation = false) {
|
|
@@ -182,16 +162,18 @@ export class ChatView {
|
|
|
182
162
|
|
|
183
163
|
_renderMessageAttachments(attachments) {
|
|
184
164
|
if (!attachments || attachments.length === 0) return '';
|
|
185
|
-
return attachments
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
165
|
+
return attachments
|
|
166
|
+
.map((att) => {
|
|
167
|
+
if (att.type === 'image') {
|
|
168
|
+
return `<img class="messenger-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
|
|
169
|
+
}
|
|
170
|
+
return `<a class="messenger-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
|
|
190
171
|
<i class="ph ph-file"></i>
|
|
191
172
|
<span>${this._escapeHtml(att.name || 'file')}</span>
|
|
192
173
|
<i class="ph ph-download-simple messenger-file-download-icon"></i>
|
|
193
174
|
</a>`;
|
|
194
|
-
|
|
175
|
+
})
|
|
176
|
+
.join('');
|
|
195
177
|
}
|
|
196
178
|
|
|
197
179
|
_renderMessage(message) {
|
|
@@ -202,9 +184,12 @@ export class ChatView {
|
|
|
202
184
|
const timeStr = this._formatMessageTime(message.timestamp);
|
|
203
185
|
const attachmentsHtml = this._renderMessageAttachments(message.attachments);
|
|
204
186
|
|
|
205
|
-
const contentHtml = message.content
|
|
206
|
-
|
|
207
|
-
|
|
187
|
+
const contentHtml = message.content
|
|
188
|
+
? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>`
|
|
189
|
+
: '';
|
|
190
|
+
const bubbleHtml = contentHtml
|
|
191
|
+
? `<div class="messenger-message-bubble">${contentHtml}</div>`
|
|
192
|
+
: '';
|
|
208
193
|
|
|
209
194
|
if (isOwn) {
|
|
210
195
|
return `
|
|
@@ -219,13 +204,15 @@ export class ChatView {
|
|
|
219
204
|
const avatarHtml = this._renderSenderAvatar(message.sender);
|
|
220
205
|
return `
|
|
221
206
|
<div class="messenger-message ${messageClass}">
|
|
222
|
-
<div class="messenger-message-
|
|
223
|
-
<div class="messenger-message-
|
|
224
|
-
<div class="messenger-message-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
207
|
+
<div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
|
|
208
|
+
<div class="messenger-message-row">
|
|
209
|
+
<div class="messenger-message-avatar">${avatarHtml}</div>
|
|
210
|
+
<div class="messenger-message-wrapper">
|
|
211
|
+
${bubbleHtml}
|
|
212
|
+
${attachmentsHtml}
|
|
213
|
+
</div>
|
|
228
214
|
</div>
|
|
215
|
+
<div class="messenger-message-time">${timeStr}</div>
|
|
229
216
|
</div>
|
|
230
217
|
`;
|
|
231
218
|
}
|
|
@@ -238,17 +225,6 @@ export class ChatView {
|
|
|
238
225
|
return `<div class="sdk-avatar sdk-avatar-sm">${initial}</div>`;
|
|
239
226
|
}
|
|
240
227
|
|
|
241
|
-
_renderConversationAvatar(conversation) {
|
|
242
|
-
if (!conversation?.participants?.length) {
|
|
243
|
-
return `<div class="sdk-avatar sdk-avatar-sm">S</div>`;
|
|
244
|
-
}
|
|
245
|
-
const p = conversation.participants[0];
|
|
246
|
-
if (p.avatarUrl) {
|
|
247
|
-
return `<div class="sdk-avatar sdk-avatar-sm"><img src="${p.avatarUrl}" alt="${p.name}" /></div>`;
|
|
248
|
-
}
|
|
249
|
-
return `<div class="sdk-avatar sdk-avatar-sm">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
228
|
_renderTeamAvatars() {
|
|
253
229
|
const avatars = this.state.teamAvatars;
|
|
254
230
|
if (!avatars || avatars.length === 0) {
|
|
@@ -322,12 +298,15 @@ export class ChatView {
|
|
|
322
298
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
323
299
|
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
324
300
|
if (input && sendBtn) {
|
|
325
|
-
sendBtn.disabled =
|
|
301
|
+
sendBtn.disabled =
|
|
302
|
+
!input.value.trim() && this._pendingAttachments.length === 0;
|
|
326
303
|
}
|
|
327
304
|
}
|
|
328
305
|
|
|
329
306
|
_renderAttachmentPreviews() {
|
|
330
|
-
const container = this.element.querySelector(
|
|
307
|
+
const container = this.element.querySelector(
|
|
308
|
+
'.messenger-compose-attachments-preview'
|
|
309
|
+
);
|
|
331
310
|
if (!container) return;
|
|
332
311
|
|
|
333
312
|
if (this._pendingAttachments.length === 0) {
|
|
@@ -337,28 +316,31 @@ export class ChatView {
|
|
|
337
316
|
}
|
|
338
317
|
|
|
339
318
|
container.style.display = 'flex';
|
|
340
|
-
container.innerHTML = this._pendingAttachments
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
319
|
+
container.innerHTML = this._pendingAttachments
|
|
320
|
+
.map((att, i) => {
|
|
321
|
+
const isImage = att.type.startsWith('image');
|
|
322
|
+
const thumb = isImage
|
|
323
|
+
? `<img class="messenger-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
|
|
324
|
+
: `<div class="messenger-attachment-thumb messenger-attachment-file-icon"><i class="ph ph-file"></i></div>`;
|
|
325
|
+
return `
|
|
346
326
|
<div class="messenger-attachment-preview" data-index="${i}">
|
|
347
327
|
${thumb}
|
|
348
328
|
<button class="messenger-attachment-remove" data-index="${i}" aria-label="Remove">×</button>
|
|
349
329
|
</div>
|
|
350
330
|
`;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
container
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
331
|
+
})
|
|
332
|
+
.join('');
|
|
333
|
+
|
|
334
|
+
container
|
|
335
|
+
.querySelectorAll('.messenger-attachment-remove')
|
|
336
|
+
.forEach((btn) => {
|
|
337
|
+
btn.addEventListener('click', (e) => {
|
|
338
|
+
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
339
|
+
this._pendingAttachments.splice(idx, 1);
|
|
340
|
+
this._renderAttachmentPreviews();
|
|
341
|
+
this._updateSendButtonState();
|
|
342
|
+
});
|
|
360
343
|
});
|
|
361
|
-
});
|
|
362
344
|
}
|
|
363
345
|
|
|
364
346
|
_attachEvents() {
|
|
@@ -374,20 +356,15 @@ export class ChatView {
|
|
|
374
356
|
this.state.setOpen(false);
|
|
375
357
|
});
|
|
376
358
|
|
|
377
|
-
// Compose input (not rendered when conversation is closed)
|
|
378
359
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
379
360
|
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
380
361
|
|
|
381
362
|
if (input && sendBtn) {
|
|
382
363
|
input.addEventListener('input', () => {
|
|
383
|
-
// Auto-resize textarea
|
|
384
364
|
input.style.height = 'auto';
|
|
385
365
|
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
|
|
386
|
-
|
|
387
|
-
// Enable/disable send button
|
|
388
366
|
this._updateSendButtonState();
|
|
389
367
|
|
|
390
|
-
// Send typing indicator
|
|
391
368
|
if (input.value.trim()) {
|
|
392
369
|
this._startTyping();
|
|
393
370
|
}
|
|
@@ -405,9 +382,10 @@ export class ChatView {
|
|
|
405
382
|
});
|
|
406
383
|
}
|
|
407
384
|
|
|
408
|
-
// Attach button + file input
|
|
409
385
|
const attachBtn = this.element.querySelector('.messenger-compose-attach');
|
|
410
|
-
const fileInput = this.element.querySelector(
|
|
386
|
+
const fileInput = this.element.querySelector(
|
|
387
|
+
'.messenger-compose-file-input'
|
|
388
|
+
);
|
|
411
389
|
|
|
412
390
|
if (attachBtn && fileInput) {
|
|
413
391
|
attachBtn.addEventListener('click', () => {
|
|
@@ -434,42 +412,11 @@ export class ChatView {
|
|
|
434
412
|
});
|
|
435
413
|
}
|
|
436
414
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
const messagesContainer = this.element.querySelector('.messenger-chat-messages');
|
|
415
|
+
const messagesContainer = this.element.querySelector(
|
|
416
|
+
'.messenger-chat-messages'
|
|
417
|
+
);
|
|
470
418
|
if (messagesContainer) {
|
|
471
419
|
messagesContainer.addEventListener('click', (e) => {
|
|
472
|
-
// File click -> download
|
|
473
420
|
const fileLink = e.target.closest('.messenger-message-file');
|
|
474
421
|
if (fileLink) {
|
|
475
422
|
e.preventDefault();
|
|
@@ -479,7 +426,6 @@ export class ChatView {
|
|
|
479
426
|
return;
|
|
480
427
|
}
|
|
481
428
|
|
|
482
|
-
// Image click -> open in new tab
|
|
483
429
|
const img = e.target.closest('.messenger-message-image');
|
|
484
430
|
if (img) {
|
|
485
431
|
const url = img.dataset.url || img.src;
|
|
@@ -503,82 +449,22 @@ export class ChatView {
|
|
|
503
449
|
document.body.removeChild(a);
|
|
504
450
|
URL.revokeObjectURL(blobUrl);
|
|
505
451
|
} catch {
|
|
506
|
-
// Fallback: open in new tab
|
|
507
452
|
window.open(url, '_blank');
|
|
508
453
|
}
|
|
509
454
|
}
|
|
510
455
|
|
|
511
456
|
_escapeHtml(text) {
|
|
512
457
|
if (!text) return '';
|
|
513
|
-
return text
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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
|
-
}
|
|
458
|
+
return text
|
|
459
|
+
.replace(/&/g, '&')
|
|
460
|
+
.replace(/</g, '<')
|
|
461
|
+
.replace(/>/g, '>')
|
|
462
|
+
.replace(/"/g, '"');
|
|
578
463
|
}
|
|
579
464
|
|
|
580
465
|
async _sendMessage() {
|
|
581
466
|
if (this._isConversationClosed) return;
|
|
467
|
+
|
|
582
468
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
583
469
|
const content = input.value.trim();
|
|
584
470
|
const hasAttachments = this._pendingAttachments.length > 0;
|
|
@@ -587,37 +473,12 @@ export class ChatView {
|
|
|
587
473
|
|
|
588
474
|
this._stopTyping();
|
|
589
475
|
|
|
590
|
-
// Collect attachments to upload
|
|
591
476
|
const attachmentsToSend = [...this._pendingAttachments];
|
|
592
|
-
|
|
593
477
|
const isNewConversation = !this.state.activeConversationId;
|
|
594
|
-
const needsContactInfo = !this.state.userContext?.email;
|
|
595
478
|
|
|
596
479
|
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
|
-
}
|
|
480
|
+
if (this.options.onStartConversation) {
|
|
481
|
+
this.options.onStartConversation(content, attachmentsToSend);
|
|
621
482
|
}
|
|
622
483
|
} else {
|
|
623
484
|
const message = {
|
|
@@ -635,11 +496,14 @@ export class ChatView {
|
|
|
635
496
|
this.state.addMessage(this.state.activeConversationId, message);
|
|
636
497
|
|
|
637
498
|
if (this.options.onSendMessage) {
|
|
638
|
-
this.options.onSendMessage(
|
|
499
|
+
this.options.onSendMessage(
|
|
500
|
+
this.state.activeConversationId,
|
|
501
|
+
message,
|
|
502
|
+
attachmentsToSend
|
|
503
|
+
);
|
|
639
504
|
}
|
|
640
505
|
}
|
|
641
506
|
|
|
642
|
-
// Clear input and attachments
|
|
643
507
|
input.value = '';
|
|
644
508
|
input.style.height = 'auto';
|
|
645
509
|
this._pendingAttachments = [];
|
|
@@ -708,4 +572,4 @@ export class ChatView {
|
|
|
708
572
|
this.element.parentNode.removeChild(this.element);
|
|
709
573
|
}
|
|
710
574
|
}
|
|
711
|
-
}
|
|
575
|
+
}
|
|
@@ -4,6 +4,15 @@ export class ConversationsView {
|
|
|
4
4
|
this.options = options;
|
|
5
5
|
this.element = null;
|
|
6
6
|
this._unsubscribe = null;
|
|
7
|
+
this.avatarColors = [
|
|
8
|
+
'#155EEF',
|
|
9
|
+
'#8b5cf6',
|
|
10
|
+
'#10b981',
|
|
11
|
+
'#f59e0b',
|
|
12
|
+
'#ef4444',
|
|
13
|
+
'#ec4899',
|
|
14
|
+
'#06b6d4',
|
|
15
|
+
];
|
|
7
16
|
}
|
|
8
17
|
|
|
9
18
|
render() {
|
|
@@ -27,55 +36,61 @@ export class ConversationsView {
|
|
|
27
36
|
return this.element;
|
|
28
37
|
}
|
|
29
38
|
|
|
39
|
+
_getAvatarColor(name) {
|
|
40
|
+
// Generate consistent color based on name
|
|
41
|
+
const charCode = (name || 'S').charCodeAt(0);
|
|
42
|
+
return this.avatarColors[charCode % this.avatarColors.length];
|
|
43
|
+
}
|
|
44
|
+
|
|
30
45
|
_updateContent() {
|
|
31
46
|
const conversations = this.state.conversations;
|
|
32
|
-
const avatarsHtml = this._renderAvatarStack();
|
|
33
47
|
|
|
34
48
|
let conversationsHtml;
|
|
35
49
|
if (conversations.length === 0) {
|
|
36
50
|
conversationsHtml = `
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
<div class="messenger-empty-state">
|
|
52
|
+
<div class="messenger-empty-state-icon">
|
|
53
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="currentColor" viewBox="0 0 256 256">
|
|
54
|
+
<path d="M216,48H40A16,16,0,0,0,24,64V224a15.85,15.85,0,0,0,9.24,14.5A16.13,16.13,0,0,0,40,240a15.89,15.89,0,0,0,10.25-3.78.69.69,0,0,0,.13-.11L82.5,208H216a16,16,0,0,0,16-16V64A16,16,0,0,0,216,48ZM40,224h0ZM216,192H82.5a16,16,0,0,0-10.3,3.75l-.12.11L40,224V64H216Z"></path>
|
|
55
|
+
</svg>
|
|
56
|
+
</div>
|
|
57
|
+
<h3>No conversations yet</h3>
|
|
58
|
+
<p>Start a new conversation with our team</p>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
47
61
|
} else {
|
|
48
62
|
conversationsHtml = `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
63
|
+
<div class="messenger-conversations-list">
|
|
64
|
+
${conversations.map((conv) => this._renderConversationItem(conv)).join('')}
|
|
65
|
+
</div>
|
|
66
|
+
`;
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
this.element.innerHTML = `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
<div class="messenger-conversations-header">
|
|
71
|
+
<h2>Messages</h2>
|
|
72
|
+
<button class="sdk-close-btn" aria-label="Close">
|
|
73
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#000000" viewBox="0 0 256 256">
|
|
74
|
+
<path d="M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z"></path>
|
|
75
|
+
</svg>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="messenger-conversations-body">
|
|
80
|
+
${conversationsHtml}
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="messenger-conversations-footer">
|
|
84
|
+
<button class="messenger-new-message-btn">
|
|
85
|
+
<span>Send us a message</span>
|
|
86
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 256 256" style="flex-shrink: 0;">
|
|
87
|
+
<rect width="256" height="256" fill="none"/>
|
|
88
|
+
<line x1="144" y1="128" x2="80" y2="128" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
89
|
+
<path d="M48.49,221.28A8,8,0,0,0,59.93,231l168-96.09a8,8,0,0,0,0-14l-168-95.85a8,8,0,0,0-11.44,9.67L80,128Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
|
|
90
|
+
</svg>
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
`;
|
|
79
94
|
|
|
80
95
|
this._attachEvents();
|
|
81
96
|
}
|
|
@@ -108,34 +123,41 @@ export class ConversationsView {
|
|
|
108
123
|
|
|
109
124
|
_renderConversationAvatars(participants) {
|
|
110
125
|
if (!participants || participants.length === 0) {
|
|
111
|
-
|
|
126
|
+
const color = this._getAvatarColor('S');
|
|
127
|
+
return `<div class="sdk-avatar sdk-avatar-md" style="background-color: ${color};">S</div>`;
|
|
112
128
|
}
|
|
113
129
|
|
|
114
130
|
const p = participants[0];
|
|
115
131
|
if (p.avatarUrl) {
|
|
116
132
|
return `<div class="sdk-avatar sdk-avatar-md"><img src="${p.avatarUrl}" alt="${p.name}" /></div>`;
|
|
117
133
|
}
|
|
118
|
-
|
|
134
|
+
const initial = (p.name || 'S').charAt(0).toUpperCase();
|
|
135
|
+
const color = this._getAvatarColor(p.name);
|
|
136
|
+
return `<div class="sdk-avatar sdk-avatar-md" style="background-color: ${color};">${initial}</div>`;
|
|
119
137
|
}
|
|
120
138
|
|
|
121
139
|
_renderAvatarStack() {
|
|
122
140
|
const avatars = this.state.teamAvatars;
|
|
123
141
|
if (!avatars || avatars.length === 0) {
|
|
142
|
+
const color1 = this._getAvatarColor('S');
|
|
143
|
+
const color2 = this._getAvatarColor('T');
|
|
124
144
|
return `
|
|
125
145
|
<div class="messenger-avatar-stack">
|
|
126
|
-
<div class="sdk-avatar sdk-avatar-sm">S</div>
|
|
127
|
-
<div class="sdk-avatar sdk-avatar-sm">T</div>
|
|
146
|
+
<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color1};">S</div>
|
|
147
|
+
<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color2};">T</div>
|
|
128
148
|
</div>
|
|
129
149
|
`;
|
|
130
150
|
}
|
|
131
151
|
|
|
132
152
|
const avatarItems = avatars
|
|
133
153
|
.slice(0, 2)
|
|
134
|
-
.map((avatar) => {
|
|
154
|
+
.map((avatar, index) => {
|
|
135
155
|
if (typeof avatar === 'string' && avatar.startsWith('http')) {
|
|
136
156
|
return `<div class="sdk-avatar sdk-avatar-sm"><img src="${avatar}" alt="Team member" /></div>`;
|
|
137
157
|
}
|
|
138
|
-
|
|
158
|
+
const initial = avatar.charAt(0).toUpperCase();
|
|
159
|
+
const color = this._getAvatarColor(avatar);
|
|
160
|
+
return `<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color};">${initial}</div>`;
|
|
139
161
|
})
|
|
140
162
|
.join('');
|
|
141
163
|
|
|
@@ -227,4 +249,4 @@ export class ConversationsView {
|
|
|
227
249
|
this.element.parentNode.removeChild(this.element);
|
|
228
250
|
}
|
|
229
251
|
}
|
|
230
|
-
}
|
|
252
|
+
}
|