@product7/product7-js 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/product7-js.js +506 -131
- package/dist/product7-js.js.map +1 -1
- package/dist/product7-js.min.js +1 -1
- package/dist/product7-js.min.js.map +1 -1
- package/package.json +1 -1
- package/src/styles/components.js +5 -0
- package/src/styles/messenger-components.js +103 -26
- package/src/styles/messenger-core.js +1 -0
- package/src/styles/messenger-views.js +171 -34
- package/src/widgets/MessengerWidget.js +7 -0
- package/src/widgets/messenger/views/ChatView.js +135 -38
- package/src/widgets/messenger/views/ConversationsView.js +8 -4
- package/src/widgets/messenger/views/HomeView.js +76 -29
|
@@ -20,13 +20,19 @@ export class ChatView {
|
|
|
20
20
|
this._updateContent();
|
|
21
21
|
|
|
22
22
|
this._unsubscribe = this.state.subscribe((type, data) => {
|
|
23
|
+
if (type === 'connectionChange') {
|
|
24
|
+
const banner = this.element?.querySelector('.messenger-connection-banner');
|
|
25
|
+
if (banner) {
|
|
26
|
+
banner.style.display = data.connected ? 'none' : 'flex';
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
23
30
|
if (
|
|
24
31
|
type === 'messageAdded' &&
|
|
25
32
|
data.conversationId === this.state.activeConversationId
|
|
26
33
|
) {
|
|
27
34
|
this._hideTypingIndicator();
|
|
28
35
|
this._appendMessage(data.message);
|
|
29
|
-
this._scrollToBottom();
|
|
30
36
|
} else if (
|
|
31
37
|
type === 'typingStarted' &&
|
|
32
38
|
data.conversationId === this.state.activeConversationId
|
|
@@ -63,16 +69,20 @@ export class ChatView {
|
|
|
63
69
|
const messagesHtml =
|
|
64
70
|
messages.length === 0
|
|
65
71
|
? this._renderEmptyState(isNewConversation)
|
|
66
|
-
:
|
|
72
|
+
: this._renderGroupedMessages(messages);
|
|
67
73
|
|
|
68
|
-
const
|
|
69
|
-
? 'New conversation'
|
|
70
|
-
: conversation?.title || 'Chat with team';
|
|
74
|
+
const defaultPlaceholder = this.options.composePlaceholder || 'Write a message...';
|
|
71
75
|
const placeholder = isNewConversation
|
|
72
|
-
? 'Start typing your message...'
|
|
76
|
+
? (this.options.composePlaceholder || 'Start typing your message...')
|
|
73
77
|
: isClosed
|
|
74
78
|
? 'Conversation closed'
|
|
75
|
-
:
|
|
79
|
+
: defaultPlaceholder;
|
|
80
|
+
|
|
81
|
+
const logoUrl = this.options.logoUrl;
|
|
82
|
+
const teamName = this.state.teamName || 'Support';
|
|
83
|
+
const headerAvatarHtml = logoUrl
|
|
84
|
+
? `<img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(teamName)}" />`
|
|
85
|
+
: `<iconify-icon icon="ph:chats-circle-duotone" width="20" height="20"></iconify-icon>`;
|
|
76
86
|
|
|
77
87
|
this.element.innerHTML = `
|
|
78
88
|
<div class="messenger-chat-header">
|
|
@@ -80,11 +90,11 @@ export class ChatView {
|
|
|
80
90
|
<iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
|
|
81
91
|
</button>
|
|
82
92
|
<div class="messenger-chat-header-avatar">
|
|
83
|
-
|
|
93
|
+
${headerAvatarHtml}
|
|
84
94
|
</div>
|
|
85
95
|
<div class="messenger-chat-header-info">
|
|
86
|
-
<span class="messenger-chat-title">${
|
|
87
|
-
<span class="messenger-chat-subtitle"
|
|
96
|
+
<span class="messenger-chat-title">${this._escapeHtml(teamName)}</span>
|
|
97
|
+
<span class="messenger-chat-subtitle">${isClosed ? 'Conversation resolved' : (this.state.responseTime || 'Typically replies within minutes')}</span>
|
|
88
98
|
</div>
|
|
89
99
|
<div class="messenger-chat-header-actions">
|
|
90
100
|
<button class="sdk-btn-icon sdk-close-btn messenger-mobile-close-btn" aria-label="Close">
|
|
@@ -93,6 +103,11 @@ export class ChatView {
|
|
|
93
103
|
</div>
|
|
94
104
|
</div>
|
|
95
105
|
|
|
106
|
+
<div class="messenger-connection-banner" style="display:none;">
|
|
107
|
+
<iconify-icon icon="ph:wifi-slash" width="14" height="14"></iconify-icon>
|
|
108
|
+
<span>Reconnecting…</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
96
111
|
<div class="messenger-chat-messages">
|
|
97
112
|
${messagesHtml}
|
|
98
113
|
${
|
|
@@ -113,6 +128,11 @@ export class ChatView {
|
|
|
113
128
|
</div>
|
|
114
129
|
</div>
|
|
115
130
|
|
|
131
|
+
<div class="messenger-scroll-pill" style="display:none;">
|
|
132
|
+
<iconify-icon icon="ph:arrow-down" width="14" height="14"></iconify-icon>
|
|
133
|
+
<span>New message</span>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
116
136
|
${
|
|
117
137
|
isClosed
|
|
118
138
|
? ''
|
|
@@ -148,23 +168,20 @@ export class ChatView {
|
|
|
148
168
|
this._attachEvents();
|
|
149
169
|
this._scrollToBottom();
|
|
150
170
|
this._renderAttachmentPreviews();
|
|
171
|
+
this._setupScrollPill();
|
|
151
172
|
}
|
|
152
173
|
|
|
153
174
|
_renderEmptyState(isNewConversation = false) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
const logoUrl = this.options.logoUrl;
|
|
176
|
+
|
|
177
|
+
const logoHtml = logoUrl
|
|
178
|
+
? `<div class="messenger-chat-empty-logo"><img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(this.state.teamName)}" /></div>`
|
|
179
|
+
: '';
|
|
158
180
|
|
|
159
181
|
return `
|
|
160
182
|
<div class="messenger-chat-empty">
|
|
161
|
-
|
|
183
|
+
${logoHtml}
|
|
162
184
|
<h3>${isNewConversation ? 'Start a new conversation' : 'Start the conversation'}</h3>
|
|
163
|
-
<p>Send us a message and we'll get back to you as soon as possible.</p>
|
|
164
|
-
<div class="messenger-chat-availability">
|
|
165
|
-
<span class="messenger-availability-dot ${isOnline ? 'messenger-availability-online' : 'messenger-availability-away'}"></span>
|
|
166
|
-
<span>${isOnline ? "We're online now" : responseTime}</span>
|
|
167
|
-
</div>
|
|
168
185
|
</div>
|
|
169
186
|
`;
|
|
170
187
|
}
|
|
@@ -185,7 +202,7 @@ export class ChatView {
|
|
|
185
202
|
.join('');
|
|
186
203
|
}
|
|
187
204
|
|
|
188
|
-
_renderMessage(message) {
|
|
205
|
+
_renderMessage(message, isLastInGroup = true) {
|
|
189
206
|
if (message.isSystem) {
|
|
190
207
|
return this._renderSystemMessage(message);
|
|
191
208
|
}
|
|
@@ -194,8 +211,9 @@ export class ChatView {
|
|
|
194
211
|
const messageClass = isOwn
|
|
195
212
|
? 'messenger-message-own'
|
|
196
213
|
: 'messenger-message-received';
|
|
197
|
-
const timeStr = this._formatMessageTime(message.timestamp);
|
|
214
|
+
const timeStr = isLastInGroup ? this._formatMessageTime(message.timestamp) : '';
|
|
198
215
|
const attachmentsHtml = this._renderMessageAttachments(message.attachments);
|
|
216
|
+
const isOptimistic = message.isOptimistic;
|
|
199
217
|
|
|
200
218
|
const contentHtml = message.content
|
|
201
219
|
? `<div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>`
|
|
@@ -205,11 +223,20 @@ export class ChatView {
|
|
|
205
223
|
: '';
|
|
206
224
|
|
|
207
225
|
if (isOwn) {
|
|
226
|
+
const sentIndicator = isLastInGroup
|
|
227
|
+
? `<div class="messenger-message-meta messenger-message-meta-own">
|
|
228
|
+
${isOptimistic
|
|
229
|
+
? `<span class="messenger-message-sent-status">Sending…</span>`
|
|
230
|
+
: `<span class="messenger-message-sent-status">Sent</span>`
|
|
231
|
+
}
|
|
232
|
+
${timeStr ? `<span>·</span><span>${timeStr}</span>` : ''}
|
|
233
|
+
</div>`
|
|
234
|
+
: '';
|
|
208
235
|
return `
|
|
209
|
-
<div class="messenger-message ${messageClass}">
|
|
236
|
+
<div class="messenger-message ${messageClass}${isOptimistic ? ' messenger-message-optimistic' : ''}">
|
|
210
237
|
${bubbleHtml}
|
|
211
238
|
${attachmentsHtml}
|
|
212
|
-
${
|
|
239
|
+
${sentIndicator}
|
|
213
240
|
</div>
|
|
214
241
|
`;
|
|
215
242
|
}
|
|
@@ -238,25 +265,23 @@ export class ChatView {
|
|
|
238
265
|
content.includes('left the conversation');
|
|
239
266
|
|
|
240
267
|
if (isJoinLeave && message.sender) {
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
? ''
|
|
251
|
-
: `style="background: ${colors[colorIndex]};"`;
|
|
268
|
+
const rawName = message.sender.name || '';
|
|
269
|
+
const name = rawName
|
|
270
|
+
.split(' ')
|
|
271
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
272
|
+
.join(' ');
|
|
273
|
+
const logoUrl = this.options.logoUrl;
|
|
274
|
+
const logoHtml = logoUrl
|
|
275
|
+
? `<img src="${this._escapeHtml(logoUrl)}" alt="logo" />`
|
|
276
|
+
: name.charAt(0).toUpperCase();
|
|
252
277
|
const timeStr = this._formatMessageTime(message.timestamp);
|
|
253
278
|
|
|
254
|
-
|
|
255
|
-
const action =
|
|
279
|
+
const rawAction = content.replace(rawName, '').trim();
|
|
280
|
+
const action = rawAction.charAt(0).toUpperCase() + rawAction.slice(1);
|
|
256
281
|
|
|
257
282
|
return `
|
|
258
283
|
<div class="messenger-message-system-event">
|
|
259
|
-
<div class="messenger-message-system-event-avatar"
|
|
284
|
+
<div class="messenger-message-system-event-avatar">${logoHtml}</div>
|
|
260
285
|
<span class="messenger-message-system-event-name">${this._escapeHtml(name)}</span>
|
|
261
286
|
<span class="messenger-message-system-event-action">${this._escapeHtml(action)}</span>
|
|
262
287
|
${timeStr ? `<span class="messenger-message-system-event-time">${timeStr}</span>` : ''}
|
|
@@ -336,6 +361,20 @@ export class ChatView {
|
|
|
336
361
|
const tempDiv = document.createElement('div');
|
|
337
362
|
tempDiv.innerHTML = messageHtml;
|
|
338
363
|
messagesContainer.appendChild(tempDiv.firstElementChild);
|
|
364
|
+
|
|
365
|
+
// Show scroll pill if user is scrolled up, otherwise scroll to bottom
|
|
366
|
+
const isNearBottom =
|
|
367
|
+
messagesContainer.scrollHeight -
|
|
368
|
+
messagesContainer.scrollTop -
|
|
369
|
+
messagesContainer.clientHeight <
|
|
370
|
+
80;
|
|
371
|
+
|
|
372
|
+
if (isNearBottom) {
|
|
373
|
+
this._scrollToBottom();
|
|
374
|
+
} else if (!message.isOwn) {
|
|
375
|
+
const pill = this.element.querySelector('.messenger-scroll-pill');
|
|
376
|
+
if (pill) pill.style.display = 'flex';
|
|
377
|
+
}
|
|
339
378
|
}
|
|
340
379
|
|
|
341
380
|
_scrollToBottom() {
|
|
@@ -349,6 +388,64 @@ export class ChatView {
|
|
|
349
388
|
}
|
|
350
389
|
}
|
|
351
390
|
|
|
391
|
+
_setupScrollPill() {
|
|
392
|
+
const messagesContainer = this.element.querySelector(
|
|
393
|
+
'.messenger-chat-messages'
|
|
394
|
+
);
|
|
395
|
+
const pill = this.element.querySelector('.messenger-scroll-pill');
|
|
396
|
+
if (!messagesContainer || !pill) return;
|
|
397
|
+
|
|
398
|
+
pill.addEventListener('click', () => {
|
|
399
|
+
this._scrollToBottom();
|
|
400
|
+
pill.style.display = 'none';
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
messagesContainer.addEventListener('scroll', () => {
|
|
404
|
+
const isNearBottom =
|
|
405
|
+
messagesContainer.scrollHeight -
|
|
406
|
+
messagesContainer.scrollTop -
|
|
407
|
+
messagesContainer.clientHeight <
|
|
408
|
+
80;
|
|
409
|
+
if (isNearBottom) {
|
|
410
|
+
pill.style.display = 'none';
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
_renderGroupedMessages(messages) {
|
|
416
|
+
const GROUP_TIME_GAP = 5 * 60 * 1000; // 5 minutes
|
|
417
|
+
let html = '';
|
|
418
|
+
|
|
419
|
+
messages.forEach((msg, index) => {
|
|
420
|
+
const isLast = index === messages.length - 1;
|
|
421
|
+
const nextMsg = messages[index + 1];
|
|
422
|
+
const currentTime = msg.timestamp ? new Date(msg.timestamp).getTime() : 0;
|
|
423
|
+
const nextTime = nextMsg?.timestamp
|
|
424
|
+
? new Date(nextMsg.timestamp).getTime()
|
|
425
|
+
: null;
|
|
426
|
+
const senderKey = msg.isSystem
|
|
427
|
+
? 'system'
|
|
428
|
+
: msg.isOwn
|
|
429
|
+
? 'own'
|
|
430
|
+
: msg.sender?.name || 'agent';
|
|
431
|
+
const nextSenderKey = nextMsg
|
|
432
|
+
? nextMsg.isSystem
|
|
433
|
+
? 'system'
|
|
434
|
+
: nextMsg.isOwn
|
|
435
|
+
? 'own'
|
|
436
|
+
: nextMsg.sender?.name || 'agent'
|
|
437
|
+
: null;
|
|
438
|
+
|
|
439
|
+
const timeGapAfter = nextTime && nextTime - currentTime > GROUP_TIME_GAP;
|
|
440
|
+
const senderChanges = nextMsg && senderKey !== nextSenderKey;
|
|
441
|
+
const isLastInGroup = isLast || timeGapAfter || senderChanges;
|
|
442
|
+
|
|
443
|
+
html += this._renderMessage(msg, isLastInGroup);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
return html;
|
|
447
|
+
}
|
|
448
|
+
|
|
352
449
|
_updateSendButtonState() {
|
|
353
450
|
const input = this.element.querySelector('.messenger-compose-input');
|
|
354
451
|
const sendBtn = this.element.querySelector('.messenger-compose-send');
|
|
@@ -78,8 +78,9 @@ export class ConversationsView {
|
|
|
78
78
|
|
|
79
79
|
<div class="messenger-conversations-footer">
|
|
80
80
|
<button class="messenger-new-message-btn">
|
|
81
|
-
<
|
|
82
|
-
<
|
|
81
|
+
<iconify-icon icon="ph:pencil-simple" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-secondary);"></iconify-icon>
|
|
82
|
+
<span style="flex: 1;">New conversation</span>
|
|
83
|
+
<iconify-icon icon="ph:caret-right" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-tertiary);"></iconify-icon>
|
|
83
84
|
</button>
|
|
84
85
|
</div>
|
|
85
86
|
`;
|
|
@@ -88,14 +89,16 @@ export class ConversationsView {
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
_renderConversationItem(conversation) {
|
|
92
|
+
const isClosed = conversation.status === 'closed';
|
|
91
93
|
const unreadClass = conversation.unread > 0 ? 'unread' : '';
|
|
94
|
+
const closedClass = isClosed ? 'closed' : '';
|
|
92
95
|
const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
|
|
93
96
|
const avatarsHtml = this._renderConversationAvatars(
|
|
94
97
|
conversation.participants
|
|
95
98
|
);
|
|
96
99
|
|
|
97
100
|
return `
|
|
98
|
-
<div class="messenger-conversation-item ${unreadClass}" data-conversation-id="${conversation.id}">
|
|
101
|
+
<div class="messenger-conversation-item ${unreadClass} ${closedClass}" data-conversation-id="${conversation.id}">
|
|
99
102
|
<div class="messenger-conversation-avatars">
|
|
100
103
|
${avatarsHtml}
|
|
101
104
|
</div>
|
|
@@ -106,6 +109,7 @@ export class ConversationsView {
|
|
|
106
109
|
</div>
|
|
107
110
|
<div class="messenger-conversation-preview">
|
|
108
111
|
${conversation.unread > 0 ? '<span class="messenger-unread-dot"></span>' : ''}
|
|
112
|
+
${isClosed ? '<span class="messenger-conversation-resolved-badge">Resolved</span>' : ''}
|
|
109
113
|
<span class="messenger-conversation-message">${this._truncateMessage(conversation.lastMessage)}</span>
|
|
110
114
|
</div>
|
|
111
115
|
</div>
|
|
@@ -143,7 +147,7 @@ export class ConversationsView {
|
|
|
143
147
|
|
|
144
148
|
const avatarItems = avatars
|
|
145
149
|
.slice(0, 2)
|
|
146
|
-
.map((avatar
|
|
150
|
+
.map((avatar) => {
|
|
147
151
|
if (typeof avatar === 'string' && avatar.startsWith('http')) {
|
|
148
152
|
return `<div class="sdk-avatar sdk-avatar-sm"><img src="${avatar}" alt="Team member" /></div>`;
|
|
149
153
|
}
|
|
@@ -41,7 +41,6 @@ export class HomeView {
|
|
|
41
41
|
<div class="messenger-home-welcome">
|
|
42
42
|
<span class="messenger-home-greeting">${this.state.greetingMessage}</span>
|
|
43
43
|
<span class="messenger-home-question">${this.state.welcomeMessage}</span>
|
|
44
|
-
${this._renderAvailabilityStatus()}
|
|
45
44
|
</div>
|
|
46
45
|
</div>
|
|
47
46
|
|
|
@@ -105,24 +104,20 @@ export class HomeView {
|
|
|
105
104
|
const sendIcon = `<iconify-icon icon="ph:paper-plane-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
|
|
106
105
|
const caretIcon = `<iconify-icon icon="ph:caret-right" width="20" height="20" style="flex-shrink: 0;"></iconify-icon>`;
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
${sendIcon}
|
|
115
|
-
</button>
|
|
116
|
-
<button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
|
|
117
|
-
<span class="messenger-home-continue-label">Leave us feedback</span>
|
|
118
|
-
${caretIcon}
|
|
119
|
-
</button>
|
|
120
|
-
`;
|
|
121
|
-
}
|
|
107
|
+
const responseTime =
|
|
108
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
109
|
+
|
|
110
|
+
const recentCardHtml = openConversation
|
|
111
|
+
? this._renderRecentMessageCard(openConversation)
|
|
112
|
+
: '';
|
|
122
113
|
|
|
123
114
|
return `
|
|
115
|
+
${recentCardHtml}
|
|
124
116
|
<button class="messenger-home-message-btn">
|
|
125
|
-
<
|
|
117
|
+
<div class="messenger-home-continue-info">
|
|
118
|
+
<span class="messenger-home-continue-label">Send us a message</span>
|
|
119
|
+
<span class="messenger-home-message-subtext">${responseTime}</span>
|
|
120
|
+
</div>
|
|
126
121
|
${sendIcon}
|
|
127
122
|
</button>
|
|
128
123
|
<button class="messenger-home-message-btn messenger-feedback-btn" data-action="feedback">
|
|
@@ -132,6 +127,55 @@ export class HomeView {
|
|
|
132
127
|
`;
|
|
133
128
|
}
|
|
134
129
|
|
|
130
|
+
_renderRecentMessageCard(conversation) {
|
|
131
|
+
const logoUrl = this.options.logoUrl;
|
|
132
|
+
const teamName = this.state.teamName || 'Support';
|
|
133
|
+
const logoHtml = logoUrl
|
|
134
|
+
? `<div class="messenger-home-recent-avatar messenger-home-recent-avatar-logo"><img src="${logoUrl}" alt="${teamName}" /></div>`
|
|
135
|
+
: `<div class="messenger-home-recent-avatar" style="background: var(--color-primary);">${teamName.charAt(0).toUpperCase()}</div>`;
|
|
136
|
+
|
|
137
|
+
const title = conversation.title || teamName;
|
|
138
|
+
const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
|
|
139
|
+
const preview = conversation.lastMessage
|
|
140
|
+
? conversation.lastMessage.substring(0, 48) + (conversation.lastMessage.length > 48 ? '...' : '')
|
|
141
|
+
: '';
|
|
142
|
+
const hasUnread = conversation.unread > 0;
|
|
143
|
+
|
|
144
|
+
return `
|
|
145
|
+
<div class="messenger-home-recent-card" data-conversation-id="${conversation.id}">
|
|
146
|
+
<div class="messenger-home-recent-card-label">Recent message</div>
|
|
147
|
+
<div class="messenger-home-recent-card-row">
|
|
148
|
+
${logoHtml}
|
|
149
|
+
<div class="messenger-home-recent-card-content">
|
|
150
|
+
<div class="messenger-home-recent-card-header">
|
|
151
|
+
<span class="messenger-home-recent-card-name">${title}</span>
|
|
152
|
+
<span class="messenger-home-recent-card-time">${timeAgo}</span>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="messenger-home-recent-card-preview">
|
|
155
|
+
<span class="messenger-home-recent-card-message">${preview}</span>
|
|
156
|
+
${hasUnread ? '<span class="messenger-home-recent-unread-dot"></span>' : ''}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_formatTimeAgo(timestamp) {
|
|
165
|
+
if (!timestamp) return '';
|
|
166
|
+
const date = new Date(timestamp);
|
|
167
|
+
const now = new Date();
|
|
168
|
+
const diffMs = now - date;
|
|
169
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
170
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
171
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
172
|
+
if (diffMins < 1) return 'now';
|
|
173
|
+
if (diffMins < 60) return `${diffMins}m`;
|
|
174
|
+
if (diffHours < 24) return `${diffHours}h`;
|
|
175
|
+
if (diffDays < 7) return `${diffDays}d`;
|
|
176
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
177
|
+
}
|
|
178
|
+
|
|
135
179
|
_renderFeaturedCard() {
|
|
136
180
|
if (!this.options.featuredContent) {
|
|
137
181
|
return '';
|
|
@@ -209,22 +253,26 @@ export class HomeView {
|
|
|
209
253
|
}
|
|
210
254
|
|
|
211
255
|
_attachEvents() {
|
|
256
|
+
const recentCard = this.element.querySelector('.messenger-home-recent-card');
|
|
257
|
+
if (recentCard) {
|
|
258
|
+
recentCard.addEventListener('click', () => {
|
|
259
|
+
const convId = recentCard.dataset.conversationId;
|
|
260
|
+
this.state.setActiveConversation(convId);
|
|
261
|
+
this.state.markAsRead(convId);
|
|
262
|
+
this.state.setView('chat');
|
|
263
|
+
if (this.options.onSelectConversation) {
|
|
264
|
+
this.options.onSelectConversation(convId);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
212
269
|
const msgBtn = this.element.querySelector(
|
|
213
270
|
'.messenger-home-message-btn:not(.messenger-feedback-btn)'
|
|
214
271
|
);
|
|
215
272
|
if (msgBtn) {
|
|
216
273
|
msgBtn.addEventListener('click', () => {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
this.state.setActiveConversation(convId);
|
|
220
|
-
this.state.setView('chat');
|
|
221
|
-
if (this.options.onSelectConversation) {
|
|
222
|
-
this.options.onSelectConversation(convId);
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
this.state.setActiveConversation(null);
|
|
226
|
-
this.state.setView('chat');
|
|
227
|
-
}
|
|
274
|
+
this.state.setActiveConversation(null);
|
|
275
|
+
this.state.setView('chat');
|
|
228
276
|
});
|
|
229
277
|
}
|
|
230
278
|
|
|
@@ -232,9 +280,8 @@ export class HomeView {
|
|
|
232
280
|
if (feedbackBtn) {
|
|
233
281
|
feedbackBtn.addEventListener('click', () => {
|
|
234
282
|
if (this.options.onFeedbackClick) {
|
|
235
|
-
// Close the messenger panel first, then open the feedback modal
|
|
236
283
|
this.state.setOpen(false);
|
|
237
|
-
|
|
284
|
+
this.options.onFeedbackClick();
|
|
238
285
|
} else if (this.state.urls?.feedback) {
|
|
239
286
|
window.open(this.state.urls.feedback, '_blank');
|
|
240
287
|
}
|