@product7/product7-js 0.5.2 → 0.5.5
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/README.md +4 -4
- package/dist/README.md +4 -4
- package/dist/product7-js.js +7120 -7132
- 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/api/APIService.js +1 -0
- package/src/api/services/{MessengerService.js → WebChatService.js} +15 -15
- package/src/core/APIService.js +16 -16
- package/src/core/Product7.js +4 -4
- package/src/core/WebSocketService.js +3 -3
- package/src/docs/api.md +9 -9
- package/src/docs/example.md +12 -12
- package/src/docs/framework-integrations.md +5 -5
- package/src/index.js +41 -42
- package/src/styles/base.js +9 -9
- package/src/styles/design-tokens.js +2 -2
- package/src/styles/feedback.js +2 -2
- package/src/styles/styles.js +4 -4
- package/src/styles/{messenger-components.js → web-chat-components.js} +114 -114
- package/src/styles/{messenger-core.js → web-chat-core.js} +32 -32
- package/src/styles/{messenger-features.js → web-chat-features.js} +20 -20
- package/src/styles/{messenger-views.js → web-chat-views.js} +137 -137
- package/src/styles/web-chat.js +17 -0
- package/src/styles/{messengerCustomStyles.js → webChatCustomStyles.js} +17 -19
- package/src/widgets/{MessengerWidget.js → WebChatWidget.js} +169 -172
- package/src/widgets/WidgetFactory.js +3 -3
- package/src/widgets/{messenger/MessengerState.js → web-chat/WebChatState.js} +1 -1
- package/src/widgets/{messenger → web-chat}/components/NavigationTabs.js +13 -13
- package/src/widgets/{messenger/components/MessengerLauncher.js → web-chat/components/WebChatLauncher.js} +15 -17
- package/src/widgets/{messenger/components/MessengerPanel.js → web-chat/components/WebChatPanel.js} +11 -11
- package/src/widgets/{messenger → web-chat}/views/ChangelogView.js +17 -17
- package/src/widgets/{messenger → web-chat}/views/ChatView.js +97 -99
- package/src/widgets/{messenger → web-chat}/views/ConversationsView.js +24 -24
- package/src/widgets/{messenger → web-chat}/views/HelpView.js +29 -29
- package/src/widgets/{messenger → web-chat}/views/HomeView.js +53 -55
- package/src/widgets/{messenger → web-chat}/views/PreChatFormView.js +17 -17
- package/types/index.d.ts +10 -9
- package/src/styles/messenger.js +0 -17
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class ChatView {
|
|
1
|
+
export class ChatView {
|
|
2
2
|
constructor(state, options = {}) {
|
|
3
3
|
this.state = state;
|
|
4
4
|
this.options = options;
|
|
@@ -15,14 +15,14 @@ export class ChatView {
|
|
|
15
15
|
|
|
16
16
|
render() {
|
|
17
17
|
this.element = document.createElement('div');
|
|
18
|
-
this.element.className = '
|
|
18
|
+
this.element.className = 'web-chat-view web-chat-chat-view';
|
|
19
19
|
|
|
20
20
|
this._updateContent();
|
|
21
21
|
|
|
22
22
|
this._unsubscribe = this.state.subscribe((type, data) => {
|
|
23
23
|
if (type === 'connectionChange') {
|
|
24
24
|
const banner = this.element?.querySelector(
|
|
25
|
-
'.
|
|
25
|
+
'.web-chat-connection-banner'
|
|
26
26
|
);
|
|
27
27
|
if (banner) {
|
|
28
28
|
banner.style.display = data.connected ? 'none' : 'flex';
|
|
@@ -88,50 +88,50 @@ export class ChatView {
|
|
|
88
88
|
: `<iconify-icon icon="ph:chats-circle-duotone" width="20" height="20"></iconify-icon>`;
|
|
89
89
|
|
|
90
90
|
this.element.innerHTML = `
|
|
91
|
-
<div class="
|
|
92
|
-
<button class="sdk-btn-icon
|
|
91
|
+
<div class="web-chat-chat-header">
|
|
92
|
+
<button class="sdk-btn-icon web-chat-back-btn" aria-label="Back">
|
|
93
93
|
<iconify-icon icon="ph:arrow-left" width="20" height="20"></iconify-icon>
|
|
94
94
|
</button>
|
|
95
|
-
<div class="
|
|
95
|
+
<div class="web-chat-chat-header-avatar">
|
|
96
96
|
${headerAvatarHtml}
|
|
97
97
|
</div>
|
|
98
|
-
<div class="
|
|
99
|
-
<span class="
|
|
100
|
-
<span class="
|
|
98
|
+
<div class="web-chat-chat-header-info">
|
|
99
|
+
<span class="web-chat-chat-title">${this._escapeHtml(teamName)}</span>
|
|
100
|
+
<span class="web-chat-chat-subtitle">${isClosed ? 'Conversation resolved' : this.state.responseTime || 'Typically replies within minutes'}</span>
|
|
101
101
|
</div>
|
|
102
|
-
<div class="
|
|
103
|
-
<button class="sdk-btn-icon sdk-close-btn
|
|
102
|
+
<div class="web-chat-chat-header-actions">
|
|
103
|
+
<button class="sdk-btn-icon sdk-close-btn web-chat-mobile-close-btn" aria-label="Close">
|
|
104
104
|
<iconify-icon icon="ph:x" width="18" height="18"></iconify-icon>
|
|
105
105
|
</button>
|
|
106
106
|
</div>
|
|
107
107
|
</div>
|
|
108
108
|
|
|
109
|
-
<div class="
|
|
109
|
+
<div class="web-chat-connection-banner" style="display:none;">
|
|
110
110
|
<iconify-icon icon="ph:wifi-slash" width="14" height="14"></iconify-icon>
|
|
111
111
|
<span>Reconnecting…</span>
|
|
112
112
|
</div>
|
|
113
113
|
|
|
114
|
-
<div class="
|
|
114
|
+
<div class="web-chat-chat-messages">
|
|
115
115
|
${messagesHtml}
|
|
116
116
|
${
|
|
117
117
|
isClosed
|
|
118
118
|
? `
|
|
119
|
-
<div class="
|
|
119
|
+
<div class="web-chat-closed-banner">
|
|
120
120
|
<iconify-icon icon="ph:check-circle-duotone" width="18" height="18"></iconify-icon>
|
|
121
121
|
<span>This conversation has been resolved</span>
|
|
122
122
|
</div>
|
|
123
123
|
`
|
|
124
124
|
: ''
|
|
125
125
|
}
|
|
126
|
-
<div class="
|
|
127
|
-
<div class="
|
|
126
|
+
<div class="web-chat-typing-indicator">
|
|
127
|
+
<div class="web-chat-typing-dots">
|
|
128
128
|
<span></span><span></span><span></span>
|
|
129
129
|
</div>
|
|
130
|
-
<span class="
|
|
130
|
+
<span class="web-chat-typing-text"></span>
|
|
131
131
|
</div>
|
|
132
132
|
</div>
|
|
133
133
|
|
|
134
|
-
<div class="
|
|
134
|
+
<div class="web-chat-scroll-pill" style="display:none;">
|
|
135
135
|
<iconify-icon icon="ph:arrow-down" width="14" height="14"></iconify-icon>
|
|
136
136
|
<span>New message</span>
|
|
137
137
|
</div>
|
|
@@ -140,33 +140,33 @@ export class ChatView {
|
|
|
140
140
|
isClosed
|
|
141
141
|
? ''
|
|
142
142
|
: `
|
|
143
|
-
<div class="
|
|
143
|
+
<div class="web-chat-compose-attachments-preview"></div>
|
|
144
144
|
|
|
145
|
-
<div class="
|
|
146
|
-
<div class="
|
|
147
|
-
<textarea class="
|
|
145
|
+
<div class="web-chat-chat-compose">
|
|
146
|
+
<div class="web-chat-compose-input-wrapper">
|
|
147
|
+
<textarea class="web-chat-compose-input" placeholder="${placeholder}" rows="1"></textarea>
|
|
148
148
|
</div>
|
|
149
|
-
<div class="
|
|
150
|
-
<div class="
|
|
151
|
-
<button class="sdk-btn-icon
|
|
149
|
+
<div class="web-chat-compose-bottom">
|
|
150
|
+
<div class="web-chat-compose-actions">
|
|
151
|
+
<button class="sdk-btn-icon web-chat-compose-attach" aria-label="Attach file">
|
|
152
152
|
<iconify-icon icon="ph:paperclip-duotone" width="20" height="20"></iconify-icon>
|
|
153
153
|
</button>
|
|
154
|
-
<button class="sdk-btn-icon
|
|
154
|
+
<button class="sdk-btn-icon web-chat-emoji-btn" aria-label="Emoji">
|
|
155
155
|
<iconify-icon icon="ph:smiley-duotone" width="20" height="20"></iconify-icon>
|
|
156
156
|
</button>
|
|
157
157
|
</div>
|
|
158
|
-
<button class="
|
|
158
|
+
<button class="web-chat-compose-send" aria-label="Send" disabled>
|
|
159
159
|
<iconify-icon icon="ph:paper-plane-right" width="20" height="20"></iconify-icon>
|
|
160
160
|
</button>
|
|
161
161
|
</div>
|
|
162
|
-
<input type="file" class="
|
|
162
|
+
<input type="file" class="web-chat-compose-file-input" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.txt,.zip" />
|
|
163
163
|
</div>
|
|
164
164
|
`
|
|
165
165
|
}
|
|
166
166
|
`;
|
|
167
167
|
|
|
168
168
|
this._typingIndicator = this.element.querySelector(
|
|
169
|
-
'.
|
|
169
|
+
'.web-chat-typing-indicator'
|
|
170
170
|
);
|
|
171
171
|
this._attachEvents();
|
|
172
172
|
this._scrollToBottom();
|
|
@@ -178,11 +178,11 @@ export class ChatView {
|
|
|
178
178
|
const logoUrl = this.options.logoUrl;
|
|
179
179
|
|
|
180
180
|
const logoHtml = logoUrl
|
|
181
|
-
? `<div class="
|
|
181
|
+
? `<div class="web-chat-chat-empty-logo"><img src="${this._escapeHtml(logoUrl)}" alt="${this._escapeHtml(this.state.teamName)}" /></div>`
|
|
182
182
|
: '';
|
|
183
183
|
|
|
184
184
|
return `
|
|
185
|
-
<div class="
|
|
185
|
+
<div class="web-chat-chat-empty">
|
|
186
186
|
${logoHtml}
|
|
187
187
|
<h3>${isNewConversation ? 'Start a new conversation' : 'Start the conversation'}</h3>
|
|
188
188
|
</div>
|
|
@@ -194,12 +194,12 @@ export class ChatView {
|
|
|
194
194
|
return attachments
|
|
195
195
|
.map((att) => {
|
|
196
196
|
if (att.type === 'image') {
|
|
197
|
-
return `<img class="
|
|
197
|
+
return `<img class="web-chat-message-image" src="${this._escapeHtml(att.url)}" alt="${this._escapeHtml(att.name || 'image')}" data-url="${this._escapeHtml(att.url)}" />`;
|
|
198
198
|
}
|
|
199
|
-
return `<a class="
|
|
199
|
+
return `<a class="web-chat-message-file" href="${this._escapeHtml(att.url)}" data-url="${this._escapeHtml(att.url)}" data-name="${this._escapeHtml(att.name || 'file')}">
|
|
200
200
|
<iconify-icon icon="ph:file-duotone" width="18" height="18"></iconify-icon>
|
|
201
201
|
<span>${this._escapeHtml(att.name || 'file')}</span>
|
|
202
|
-
<iconify-icon icon="ph:download-simple" width="16" height="16" class="
|
|
202
|
+
<iconify-icon icon="ph:download-simple" width="16" height="16" class="web-chat-file-download-icon"></iconify-icon>
|
|
203
203
|
</a>`;
|
|
204
204
|
})
|
|
205
205
|
.join('');
|
|
@@ -212,8 +212,8 @@ export class ChatView {
|
|
|
212
212
|
|
|
213
213
|
const isOwn = message.isOwn;
|
|
214
214
|
const messageClass = isOwn
|
|
215
|
-
? '
|
|
216
|
-
: '
|
|
215
|
+
? 'web-chat-message-own'
|
|
216
|
+
: 'web-chat-message-received';
|
|
217
217
|
const timeStr = isLastInGroup
|
|
218
218
|
? this._formatMessageTime(message.timestamp)
|
|
219
219
|
: '';
|
|
@@ -221,25 +221,25 @@ export class ChatView {
|
|
|
221
221
|
const isOptimistic = message.isOptimistic;
|
|
222
222
|
|
|
223
223
|
const contentHtml = message.content
|
|
224
|
-
? `<div class="
|
|
224
|
+
? `<div class="web-chat-message-content">${this._formatMessageContent(message.content)}</div>`
|
|
225
225
|
: '';
|
|
226
226
|
const bubbleHtml = contentHtml
|
|
227
|
-
? `<div class="
|
|
227
|
+
? `<div class="web-chat-message-bubble">${contentHtml}</div>`
|
|
228
228
|
: '';
|
|
229
229
|
|
|
230
230
|
if (isOwn) {
|
|
231
231
|
const sentIndicator = isLastInGroup
|
|
232
|
-
? `<div class="
|
|
232
|
+
? `<div class="web-chat-message-meta web-chat-message-meta-own">
|
|
233
233
|
${
|
|
234
234
|
isOptimistic
|
|
235
|
-
? `<span class="
|
|
236
|
-
: `<span class="
|
|
235
|
+
? `<span class="web-chat-message-sent-status">Sending…</span>`
|
|
236
|
+
: `<span class="web-chat-message-sent-status">Sent</span>`
|
|
237
237
|
}
|
|
238
238
|
${timeStr ? `<span>·</span><span>${timeStr}</span>` : ''}
|
|
239
239
|
</div>`
|
|
240
240
|
: '';
|
|
241
241
|
return `
|
|
242
|
-
<div class="
|
|
242
|
+
<div class="web-chat-message ${messageClass}${isOptimistic ? ' web-chat-message-optimistic' : ''}">
|
|
243
243
|
${bubbleHtml}
|
|
244
244
|
${attachmentsHtml}
|
|
245
245
|
${sentIndicator}
|
|
@@ -249,15 +249,15 @@ export class ChatView {
|
|
|
249
249
|
|
|
250
250
|
const avatarHtml = this._renderSenderAvatar(message.sender);
|
|
251
251
|
return `
|
|
252
|
-
<div class="
|
|
253
|
-
<div class="
|
|
254
|
-
<div class="
|
|
255
|
-
<div class="
|
|
252
|
+
<div class="web-chat-message ${messageClass}">
|
|
253
|
+
<div class="web-chat-message-row">
|
|
254
|
+
<div class="web-chat-message-avatar">${avatarHtml}</div>
|
|
255
|
+
<div class="web-chat-message-wrapper">
|
|
256
256
|
${bubbleHtml}
|
|
257
257
|
${attachmentsHtml}
|
|
258
258
|
</div>
|
|
259
259
|
</div>
|
|
260
|
-
${timeStr ? `<div class="
|
|
260
|
+
${timeStr ? `<div class="web-chat-message-meta"><span>${timeStr}</span></div>` : ''}
|
|
261
261
|
</div>
|
|
262
262
|
`;
|
|
263
263
|
}
|
|
@@ -286,11 +286,11 @@ export class ChatView {
|
|
|
286
286
|
const action = rawAction.charAt(0).toUpperCase() + rawAction.slice(1);
|
|
287
287
|
|
|
288
288
|
return `
|
|
289
|
-
<div class="
|
|
290
|
-
<div class="
|
|
291
|
-
<span class="
|
|
292
|
-
<span class="
|
|
293
|
-
${timeStr ? `<span class="
|
|
289
|
+
<div class="web-chat-message-system-event">
|
|
290
|
+
<div class="web-chat-message-system-event-avatar">${logoHtml}</div>
|
|
291
|
+
<span class="web-chat-message-system-event-name">${this._escapeHtml(name)}</span>
|
|
292
|
+
<span class="web-chat-message-system-event-action">${this._escapeHtml(action)}</span>
|
|
293
|
+
${timeStr ? `<span class="web-chat-message-system-event-time">${timeStr}</span>` : ''}
|
|
294
294
|
</div>
|
|
295
295
|
`;
|
|
296
296
|
}
|
|
@@ -304,16 +304,16 @@ export class ChatView {
|
|
|
304
304
|
const timeStr = this._formatMessageTime(message.timestamp);
|
|
305
305
|
|
|
306
306
|
return `
|
|
307
|
-
<div class="
|
|
308
|
-
<div class="
|
|
309
|
-
<div class="
|
|
310
|
-
<div class="
|
|
311
|
-
<div class="
|
|
312
|
-
<div class="
|
|
307
|
+
<div class="web-chat-message web-chat-message-received">
|
|
308
|
+
<div class="web-chat-message-row">
|
|
309
|
+
<div class="web-chat-message-avatar">${avatarHtml}</div>
|
|
310
|
+
<div class="web-chat-message-wrapper">
|
|
311
|
+
<div class="web-chat-message-bubble">
|
|
312
|
+
<div class="web-chat-message-content">${this._formatMessageContent(content)}</div>
|
|
313
313
|
</div>
|
|
314
314
|
</div>
|
|
315
315
|
</div>
|
|
316
|
-
${timeStr ? `<div class="
|
|
316
|
+
${timeStr ? `<div class="web-chat-message-meta"><span>${timeStr}</span></div>` : ''}
|
|
317
317
|
</div>
|
|
318
318
|
`;
|
|
319
319
|
}
|
|
@@ -342,7 +342,7 @@ export class ChatView {
|
|
|
342
342
|
})
|
|
343
343
|
.join('');
|
|
344
344
|
|
|
345
|
-
return `<div class="
|
|
345
|
+
return `<div class="web-chat-avatar-stack">${avatarItems}</div>`;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
_formatMessageTime(timestamp) {
|
|
@@ -372,9 +372,9 @@ export class ChatView {
|
|
|
372
372
|
|
|
373
373
|
_appendMessage(message) {
|
|
374
374
|
const messagesContainer = this.element.querySelector(
|
|
375
|
-
'.
|
|
375
|
+
'.web-chat-chat-messages'
|
|
376
376
|
);
|
|
377
|
-
const emptyState = messagesContainer.querySelector('.
|
|
377
|
+
const emptyState = messagesContainer.querySelector('.web-chat-chat-empty');
|
|
378
378
|
if (emptyState) {
|
|
379
379
|
emptyState.remove();
|
|
380
380
|
}
|
|
@@ -394,14 +394,14 @@ export class ChatView {
|
|
|
394
394
|
if (isNearBottom) {
|
|
395
395
|
this._scrollToBottom();
|
|
396
396
|
} else if (!message.isOwn) {
|
|
397
|
-
const pill = this.element.querySelector('.
|
|
397
|
+
const pill = this.element.querySelector('.web-chat-scroll-pill');
|
|
398
398
|
if (pill) pill.style.display = 'flex';
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
402
|
_scrollToBottom() {
|
|
403
403
|
const messagesContainer = this.element.querySelector(
|
|
404
|
-
'.
|
|
404
|
+
'.web-chat-chat-messages'
|
|
405
405
|
);
|
|
406
406
|
if (messagesContainer) {
|
|
407
407
|
setTimeout(() => {
|
|
@@ -412,9 +412,9 @@ export class ChatView {
|
|
|
412
412
|
|
|
413
413
|
_setupScrollPill() {
|
|
414
414
|
const messagesContainer = this.element.querySelector(
|
|
415
|
-
'.
|
|
415
|
+
'.web-chat-chat-messages'
|
|
416
416
|
);
|
|
417
|
-
const pill = this.element.querySelector('.
|
|
417
|
+
const pill = this.element.querySelector('.web-chat-scroll-pill');
|
|
418
418
|
if (!messagesContainer || !pill) return;
|
|
419
419
|
|
|
420
420
|
pill.addEventListener('click', () => {
|
|
@@ -469,8 +469,8 @@ export class ChatView {
|
|
|
469
469
|
}
|
|
470
470
|
|
|
471
471
|
_updateSendButtonState() {
|
|
472
|
-
const input = this.element.querySelector('.
|
|
473
|
-
const sendBtn = this.element.querySelector('.
|
|
472
|
+
const input = this.element.querySelector('.web-chat-compose-input');
|
|
473
|
+
const sendBtn = this.element.querySelector('.web-chat-compose-send');
|
|
474
474
|
if (input && sendBtn) {
|
|
475
475
|
sendBtn.disabled =
|
|
476
476
|
!input.value.trim() && this._pendingAttachments.length === 0;
|
|
@@ -479,7 +479,7 @@ export class ChatView {
|
|
|
479
479
|
|
|
480
480
|
_renderAttachmentPreviews() {
|
|
481
481
|
const container = this.element.querySelector(
|
|
482
|
-
'.
|
|
482
|
+
'.web-chat-compose-attachments-preview'
|
|
483
483
|
);
|
|
484
484
|
if (!container) return;
|
|
485
485
|
|
|
@@ -494,38 +494,36 @@ export class ChatView {
|
|
|
494
494
|
.map((att, i) => {
|
|
495
495
|
const isImage = att.type.startsWith('image');
|
|
496
496
|
const thumb = isImage
|
|
497
|
-
? `<img class="
|
|
498
|
-
: `<div class="
|
|
497
|
+
? `<img class="web-chat-attachment-thumb" src="${att.preview}" alt="${this._escapeHtml(att.file.name)}" />`
|
|
498
|
+
: `<div class="web-chat-attachment-thumb web-chat-attachment-file-icon"><iconify-icon icon="ph:file-duotone" width="24" height="24"></iconify-icon></div>`;
|
|
499
499
|
return `
|
|
500
|
-
<div class="
|
|
500
|
+
<div class="web-chat-attachment-preview" data-index="${i}">
|
|
501
501
|
${thumb}
|
|
502
|
-
<button class="
|
|
502
|
+
<button class="web-chat-attachment-remove" data-index="${i}" aria-label="Remove">×</button>
|
|
503
503
|
</div>
|
|
504
504
|
`;
|
|
505
505
|
})
|
|
506
506
|
.join('');
|
|
507
507
|
|
|
508
|
-
container
|
|
509
|
-
.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
this._renderAttachmentPreviews();
|
|
515
|
-
this._updateSendButtonState();
|
|
516
|
-
});
|
|
508
|
+
container.querySelectorAll('.web-chat-attachment-remove').forEach((btn) => {
|
|
509
|
+
btn.addEventListener('click', (e) => {
|
|
510
|
+
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
511
|
+
this._pendingAttachments.splice(idx, 1);
|
|
512
|
+
this._renderAttachmentPreviews();
|
|
513
|
+
this._updateSendButtonState();
|
|
517
514
|
});
|
|
515
|
+
});
|
|
518
516
|
}
|
|
519
517
|
|
|
520
518
|
_attachEvents() {
|
|
521
519
|
this.element
|
|
522
|
-
.querySelector('.
|
|
520
|
+
.querySelector('.web-chat-back-btn')
|
|
523
521
|
.addEventListener('click', () => {
|
|
524
522
|
this.state.setView('messages');
|
|
525
523
|
});
|
|
526
524
|
|
|
527
525
|
const mobileCloseBtn = this.element.querySelector(
|
|
528
|
-
'.
|
|
526
|
+
'.web-chat-mobile-close-btn'
|
|
529
527
|
);
|
|
530
528
|
if (mobileCloseBtn) {
|
|
531
529
|
mobileCloseBtn.addEventListener('click', () => {
|
|
@@ -533,8 +531,8 @@ export class ChatView {
|
|
|
533
531
|
});
|
|
534
532
|
}
|
|
535
533
|
|
|
536
|
-
const input = this.element.querySelector('.
|
|
537
|
-
const sendBtn = this.element.querySelector('.
|
|
534
|
+
const input = this.element.querySelector('.web-chat-compose-input');
|
|
535
|
+
const sendBtn = this.element.querySelector('.web-chat-compose-send');
|
|
538
536
|
|
|
539
537
|
if (input && sendBtn) {
|
|
540
538
|
input.addEventListener('input', () => {
|
|
@@ -559,7 +557,7 @@ export class ChatView {
|
|
|
559
557
|
});
|
|
560
558
|
}
|
|
561
559
|
|
|
562
|
-
const emojiBtn = this.element.querySelector('.
|
|
560
|
+
const emojiBtn = this.element.querySelector('.web-chat-emoji-btn');
|
|
563
561
|
if (emojiBtn) {
|
|
564
562
|
emojiBtn.addEventListener('click', (e) => {
|
|
565
563
|
e.stopPropagation();
|
|
@@ -567,9 +565,9 @@ export class ChatView {
|
|
|
567
565
|
});
|
|
568
566
|
}
|
|
569
567
|
|
|
570
|
-
const attachBtn = this.element.querySelector('.
|
|
568
|
+
const attachBtn = this.element.querySelector('.web-chat-compose-attach');
|
|
571
569
|
const fileInput = this.element.querySelector(
|
|
572
|
-
'.
|
|
570
|
+
'.web-chat-compose-file-input'
|
|
573
571
|
);
|
|
574
572
|
|
|
575
573
|
if (attachBtn && fileInput) {
|
|
@@ -598,11 +596,11 @@ export class ChatView {
|
|
|
598
596
|
}
|
|
599
597
|
|
|
600
598
|
const messagesContainer = this.element.querySelector(
|
|
601
|
-
'.
|
|
599
|
+
'.web-chat-chat-messages'
|
|
602
600
|
);
|
|
603
601
|
if (messagesContainer) {
|
|
604
602
|
messagesContainer.addEventListener('click', (e) => {
|
|
605
|
-
const fileLink = e.target.closest('.
|
|
603
|
+
const fileLink = e.target.closest('.web-chat-message-file');
|
|
606
604
|
if (fileLink) {
|
|
607
605
|
e.preventDefault();
|
|
608
606
|
const url = fileLink.dataset.url;
|
|
@@ -611,7 +609,7 @@ export class ChatView {
|
|
|
611
609
|
return;
|
|
612
610
|
}
|
|
613
611
|
|
|
614
|
-
const img = e.target.closest('.
|
|
612
|
+
const img = e.target.closest('.web-chat-message-image');
|
|
615
613
|
if (img) {
|
|
616
614
|
const url = img.dataset.url || img.src;
|
|
617
615
|
window.open(url, '_blank');
|
|
@@ -650,7 +648,7 @@ export class ChatView {
|
|
|
650
648
|
async _sendMessage() {
|
|
651
649
|
if (this._isConversationClosed) return;
|
|
652
650
|
|
|
653
|
-
const input = this.element.querySelector('.
|
|
651
|
+
const input = this.element.querySelector('.web-chat-compose-input');
|
|
654
652
|
const content = input.value.trim();
|
|
655
653
|
const hasAttachments = this._pendingAttachments.length > 0;
|
|
656
654
|
|
|
@@ -715,7 +713,7 @@ export class ChatView {
|
|
|
715
713
|
|
|
716
714
|
async _toggleEmojiPicker() {
|
|
717
715
|
const existing = this.element.querySelector(
|
|
718
|
-
'.
|
|
716
|
+
'.web-chat-emoji-picker-container'
|
|
719
717
|
);
|
|
720
718
|
if (existing) {
|
|
721
719
|
existing.remove();
|
|
@@ -734,12 +732,12 @@ export class ChatView {
|
|
|
734
732
|
}
|
|
735
733
|
|
|
736
734
|
const container = document.createElement('div');
|
|
737
|
-
container.className = '
|
|
735
|
+
container.className = 'web-chat-emoji-picker-container';
|
|
738
736
|
|
|
739
737
|
const picker = document.createElement('emoji-picker');
|
|
740
738
|
container.appendChild(picker);
|
|
741
739
|
|
|
742
|
-
const compose = this.element.querySelector('.
|
|
740
|
+
const compose = this.element.querySelector('.web-chat-chat-compose');
|
|
743
741
|
compose.parentNode.insertBefore(container, compose);
|
|
744
742
|
this._emojiPickerOpen = true;
|
|
745
743
|
|
|
@@ -756,7 +754,7 @@ export class ChatView {
|
|
|
756
754
|
this._emojiOutsideHandler = (e) => {
|
|
757
755
|
if (
|
|
758
756
|
!container.contains(e.target) &&
|
|
759
|
-
!e.target.closest('.
|
|
757
|
+
!e.target.closest('.web-chat-emoji-btn')
|
|
760
758
|
) {
|
|
761
759
|
container.remove();
|
|
762
760
|
this._emojiPickerOpen = false;
|
|
@@ -771,7 +769,7 @@ export class ChatView {
|
|
|
771
769
|
}
|
|
772
770
|
|
|
773
771
|
_insertEmoji(emoji) {
|
|
774
|
-
const input = this.element.querySelector('.
|
|
772
|
+
const input = this.element.querySelector('.web-chat-compose-input');
|
|
775
773
|
if (!input) return;
|
|
776
774
|
const start = input.selectionStart;
|
|
777
775
|
const end = input.selectionEnd;
|
|
@@ -815,7 +813,7 @@ export class ChatView {
|
|
|
815
813
|
if (this._typingIndicator) {
|
|
816
814
|
this._typingIndicator.style.display = 'flex';
|
|
817
815
|
const textEl = this._typingIndicator.querySelector(
|
|
818
|
-
'.
|
|
816
|
+
'.web-chat-typing-text'
|
|
819
817
|
);
|
|
820
818
|
if (textEl) {
|
|
821
819
|
textEl.textContent = `${userName || 'Support'} is typing...`;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class ConversationsView {
|
|
1
|
+
export class ConversationsView {
|
|
2
2
|
constructor(state, options = {}) {
|
|
3
3
|
this.state = state;
|
|
4
4
|
this.options = options;
|
|
@@ -17,7 +17,7 @@ export class ConversationsView {
|
|
|
17
17
|
|
|
18
18
|
render() {
|
|
19
19
|
this.element = document.createElement('div');
|
|
20
|
-
this.element.className = '
|
|
20
|
+
this.element.className = 'web-chat-view web-chat-conversations-view';
|
|
21
21
|
|
|
22
22
|
this._updateContent();
|
|
23
23
|
this._attachEvents();
|
|
@@ -48,8 +48,8 @@ export class ConversationsView {
|
|
|
48
48
|
let conversationsHtml;
|
|
49
49
|
if (conversations.length === 0) {
|
|
50
50
|
conversationsHtml = `
|
|
51
|
-
<div class="
|
|
52
|
-
<div class="
|
|
51
|
+
<div class="web-chat-empty-state">
|
|
52
|
+
<div class="web-chat-empty-state-icon">
|
|
53
53
|
<iconify-icon icon="ph:chat-circle-duotone" width="48" height="48"></iconify-icon>
|
|
54
54
|
</div>
|
|
55
55
|
<h3>No conversations yet</h3>
|
|
@@ -58,26 +58,26 @@ export class ConversationsView {
|
|
|
58
58
|
`;
|
|
59
59
|
} else {
|
|
60
60
|
conversationsHtml = `
|
|
61
|
-
<div class="
|
|
61
|
+
<div class="web-chat-conversations-list">
|
|
62
62
|
${conversations.map((conv) => this._renderConversationItem(conv)).join('')}
|
|
63
63
|
</div>
|
|
64
64
|
`;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
this.element.innerHTML = `
|
|
68
|
-
<div class="
|
|
68
|
+
<div class="web-chat-conversations-header">
|
|
69
69
|
<h2>Messages</h2>
|
|
70
|
-
<button class="sdk-close-btn
|
|
70
|
+
<button class="sdk-close-btn web-chat-mobile-close-btn" aria-label="Close">
|
|
71
71
|
<iconify-icon icon="ph:x" width="18" height="18"></iconify-icon>
|
|
72
72
|
</button>
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
|
-
<div class="
|
|
75
|
+
<div class="web-chat-conversations-body">
|
|
76
76
|
${conversationsHtml}
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
-
<div class="
|
|
80
|
-
<button class="
|
|
79
|
+
<div class="web-chat-conversations-footer">
|
|
80
|
+
<button class="web-chat-new-message-btn">
|
|
81
81
|
<iconify-icon icon="ph:pencil-simple" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-secondary);"></iconify-icon>
|
|
82
82
|
<span style="flex: 1;">New conversation</span>
|
|
83
83
|
<iconify-icon icon="ph:caret-right" width="16" height="16" style="flex-shrink: 0; color: var(--msg-text-tertiary);"></iconify-icon>
|
|
@@ -98,19 +98,19 @@ export class ConversationsView {
|
|
|
98
98
|
);
|
|
99
99
|
|
|
100
100
|
return `
|
|
101
|
-
<div class="
|
|
102
|
-
<div class="
|
|
101
|
+
<div class="web-chat-conversation-item ${unreadClass} ${closedClass}" data-conversation-id="${conversation.id}">
|
|
102
|
+
<div class="web-chat-conversation-avatars">
|
|
103
103
|
${avatarsHtml}
|
|
104
104
|
</div>
|
|
105
|
-
<div class="
|
|
106
|
-
<div class="
|
|
107
|
-
<span class="
|
|
108
|
-
<span class="
|
|
105
|
+
<div class="web-chat-conversation-content">
|
|
106
|
+
<div class="web-chat-conversation-header">
|
|
107
|
+
<span class="web-chat-conversation-title">${conversation.title || 'Chat with team'}</span>
|
|
108
|
+
<span class="web-chat-conversation-time">${timeAgo}</span>
|
|
109
109
|
</div>
|
|
110
|
-
<div class="
|
|
111
|
-
${conversation.unread > 0 ? '<span class="
|
|
112
|
-
${isClosed ? '<span class="
|
|
113
|
-
<span class="
|
|
110
|
+
<div class="web-chat-conversation-preview">
|
|
111
|
+
${conversation.unread > 0 ? '<span class="web-chat-unread-dot"></span>' : ''}
|
|
112
|
+
${isClosed ? '<span class="web-chat-conversation-resolved-badge">Resolved</span>' : ''}
|
|
113
|
+
<span class="web-chat-conversation-message">${this._truncateMessage(conversation.lastMessage)}</span>
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
116
116
|
</div>
|
|
@@ -138,7 +138,7 @@ export class ConversationsView {
|
|
|
138
138
|
const color1 = this._getAvatarColor('S');
|
|
139
139
|
const color2 = this._getAvatarColor('T');
|
|
140
140
|
return `
|
|
141
|
-
<div class="
|
|
141
|
+
<div class="web-chat-avatar-stack">
|
|
142
142
|
<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color1};">S</div>
|
|
143
143
|
<div class="sdk-avatar sdk-avatar-sm" style="background-color: ${color2};">T</div>
|
|
144
144
|
</div>
|
|
@@ -157,7 +157,7 @@ export class ConversationsView {
|
|
|
157
157
|
})
|
|
158
158
|
.join('');
|
|
159
159
|
|
|
160
|
-
return `<div class="
|
|
160
|
+
return `<div class="web-chat-avatar-stack">${avatarItems}</div>`;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
_formatTimeAgo(timestamp) {
|
|
@@ -192,7 +192,7 @@ export class ConversationsView {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
this.element
|
|
195
|
-
.querySelectorAll('.
|
|
195
|
+
.querySelectorAll('.web-chat-conversation-item')
|
|
196
196
|
.forEach((item) => {
|
|
197
197
|
item.addEventListener('click', () => {
|
|
198
198
|
const convId = item.dataset.conversationId;
|
|
@@ -206,7 +206,7 @@ export class ConversationsView {
|
|
|
206
206
|
});
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
-
const newMsgBtn = this.element.querySelector('.
|
|
209
|
+
const newMsgBtn = this.element.querySelector('.web-chat-new-message-btn');
|
|
210
210
|
if (newMsgBtn) {
|
|
211
211
|
newMsgBtn.addEventListener('click', () => {
|
|
212
212
|
this._startNewConversation();
|