@product7/feedback-sdk 1.2.6 → 1.2.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/README.md +35 -35
- package/dist/README.md +35 -35
- package/dist/feedback-sdk.js +717 -663
- 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/core/APIService.js +66 -41
- package/src/core/FeedbackSDK.js +5 -3
- package/src/core/WebSocketService.js +3 -1
- package/src/widgets/BaseWidget.js +6 -4
- package/src/widgets/ChangelogWidget.js +0 -5
- package/src/widgets/MessengerWidget.js +22 -7
- package/src/widgets/messenger/views/ChatView.js +12 -4
- package/src/widgets/messenger/views/HomeView.js +7 -2
- package/types/index.d.ts +49 -0
package/dist/feedback-sdk.js
CHANGED
|
@@ -158,7 +158,9 @@
|
|
|
158
158
|
id: 'conv_2',
|
|
159
159
|
subject: 'Feature request',
|
|
160
160
|
status: 'open',
|
|
161
|
-
last_message_at: new Date(
|
|
161
|
+
last_message_at: new Date(
|
|
162
|
+
Date.now() - 6 * 24 * 60 * 60 * 1000
|
|
163
|
+
).toISOString(),
|
|
162
164
|
created_at: new Date(Date.now() - 10 * 24 * 60 * 60 * 1000).toISOString(),
|
|
163
165
|
unread: 0,
|
|
164
166
|
assigned_user: {
|
|
@@ -200,11 +202,14 @@
|
|
|
200
202
|
id: 'msg_4',
|
|
201
203
|
content: 'I would love to see a dark mode feature!',
|
|
202
204
|
sender_type: 'customer',
|
|
203
|
-
created_at: new Date(
|
|
205
|
+
created_at: new Date(
|
|
206
|
+
Date.now() - 6 * 24 * 60 * 60 * 1000 - 30 * 60 * 1000
|
|
207
|
+
).toISOString(),
|
|
204
208
|
},
|
|
205
209
|
{
|
|
206
210
|
id: 'msg_5',
|
|
207
|
-
content:
|
|
211
|
+
content:
|
|
212
|
+
"Great suggestion! That feature will be available next week. I'll let you know when it's ready.",
|
|
208
213
|
sender_type: 'agent',
|
|
209
214
|
sender_name: 'Tom',
|
|
210
215
|
sender_avatar: null,
|
|
@@ -234,7 +239,8 @@
|
|
|
234
239
|
{
|
|
235
240
|
id: 'collection_3',
|
|
236
241
|
title: 'AI Agent',
|
|
237
|
-
description:
|
|
242
|
+
description:
|
|
243
|
+
'Resolving customer questions instantly and accurately—from live chat to email.',
|
|
238
244
|
articleCount: 82,
|
|
239
245
|
icon: 'ph-robot',
|
|
240
246
|
url: '#',
|
|
@@ -242,7 +248,8 @@
|
|
|
242
248
|
{
|
|
243
249
|
id: 'collection_4',
|
|
244
250
|
title: 'Channels',
|
|
245
|
-
description:
|
|
251
|
+
description:
|
|
252
|
+
'Enabling the channels you use to communicate with customers, all from the Inbox.',
|
|
246
253
|
articleCount: 45,
|
|
247
254
|
icon: 'ph-chat-circle',
|
|
248
255
|
url: '#',
|
|
@@ -789,10 +796,13 @@
|
|
|
789
796
|
};
|
|
790
797
|
}
|
|
791
798
|
|
|
792
|
-
return this._makeRequest(
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
799
|
+
return this._makeRequest(
|
|
800
|
+
`/widget/messenger/conversations/${conversationId}`,
|
|
801
|
+
{
|
|
802
|
+
method: 'GET',
|
|
803
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
804
|
+
}
|
|
805
|
+
);
|
|
796
806
|
}
|
|
797
807
|
|
|
798
808
|
/**
|
|
@@ -899,14 +909,17 @@
|
|
|
899
909
|
return { status: true, data: newMessage };
|
|
900
910
|
}
|
|
901
911
|
|
|
902
|
-
return this._makeRequest(
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
912
|
+
return this._makeRequest(
|
|
913
|
+
`/widget/messenger/conversations/${conversationId}/messages`,
|
|
914
|
+
{
|
|
915
|
+
method: 'POST',
|
|
916
|
+
headers: {
|
|
917
|
+
'Content-Type': 'application/json',
|
|
918
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
919
|
+
},
|
|
920
|
+
body: JSON.stringify({ content: data.content }),
|
|
921
|
+
}
|
|
922
|
+
);
|
|
910
923
|
}
|
|
911
924
|
|
|
912
925
|
/**
|
|
@@ -924,14 +937,17 @@
|
|
|
924
937
|
return { status: true };
|
|
925
938
|
}
|
|
926
939
|
|
|
927
|
-
return this._makeRequest(
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
940
|
+
return this._makeRequest(
|
|
941
|
+
`/widget/messenger/conversations/${conversationId}/typing`,
|
|
942
|
+
{
|
|
943
|
+
method: 'POST',
|
|
944
|
+
headers: {
|
|
945
|
+
'Content-Type': 'application/json',
|
|
946
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
947
|
+
},
|
|
948
|
+
body: JSON.stringify({ is_typing: isTyping }),
|
|
949
|
+
}
|
|
950
|
+
);
|
|
935
951
|
}
|
|
936
952
|
|
|
937
953
|
/**
|
|
@@ -948,10 +964,13 @@
|
|
|
948
964
|
return { status: true };
|
|
949
965
|
}
|
|
950
966
|
|
|
951
|
-
return this._makeRequest(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
967
|
+
return this._makeRequest(
|
|
968
|
+
`/widget/messenger/conversations/${conversationId}/read`,
|
|
969
|
+
{
|
|
970
|
+
method: 'POST',
|
|
971
|
+
headers: { Authorization: `Bearer ${this.sessionToken}` },
|
|
972
|
+
}
|
|
973
|
+
);
|
|
955
974
|
}
|
|
956
975
|
|
|
957
976
|
/**
|
|
@@ -964,7 +983,10 @@
|
|
|
964
983
|
}
|
|
965
984
|
|
|
966
985
|
if (this.mock) {
|
|
967
|
-
const count = MOCK_CONVERSATIONS.reduce(
|
|
986
|
+
const count = MOCK_CONVERSATIONS.reduce(
|
|
987
|
+
(sum, c) => sum + (c.unread || 0),
|
|
988
|
+
0
|
|
989
|
+
);
|
|
968
990
|
return {
|
|
969
991
|
status: true,
|
|
970
992
|
data: { unread_count: count, unread_conversations: count > 0 ? 1 : 0 },
|
|
@@ -994,17 +1016,20 @@
|
|
|
994
1016
|
return { status: true, message: 'Thank you for your feedback!' };
|
|
995
1017
|
}
|
|
996
1018
|
|
|
997
|
-
return this._makeRequest(
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1019
|
+
return this._makeRequest(
|
|
1020
|
+
`/widget/messenger/conversations/${conversationId}/rate`,
|
|
1021
|
+
{
|
|
1022
|
+
method: 'POST',
|
|
1023
|
+
headers: {
|
|
1024
|
+
'Content-Type': 'application/json',
|
|
1025
|
+
Authorization: `Bearer ${this.sessionToken}`,
|
|
1026
|
+
},
|
|
1027
|
+
body: JSON.stringify({
|
|
1028
|
+
rating: data.rating,
|
|
1029
|
+
comment: data.comment || '',
|
|
1030
|
+
}),
|
|
1031
|
+
}
|
|
1032
|
+
);
|
|
1008
1033
|
}
|
|
1009
1034
|
|
|
1010
1035
|
/**
|
|
@@ -1602,7 +1627,7 @@
|
|
|
1602
1627
|
if (this.options.suppressAfterSubmission && this._hasRecentlySubmitted()) {
|
|
1603
1628
|
this.sdk.eventBus.emit('widget:suppressed', {
|
|
1604
1629
|
widget: this,
|
|
1605
|
-
reason: 'recently_submitted'
|
|
1630
|
+
reason: 'recently_submitted',
|
|
1606
1631
|
});
|
|
1607
1632
|
return this;
|
|
1608
1633
|
}
|
|
@@ -1826,8 +1851,10 @@
|
|
|
1826
1851
|
// Check backend tracking first (from init response)
|
|
1827
1852
|
if (this.sdk.config.last_feedback_at) {
|
|
1828
1853
|
try {
|
|
1829
|
-
const backendTimestamp = new Date(
|
|
1830
|
-
|
|
1854
|
+
const backendTimestamp = new Date(
|
|
1855
|
+
this.sdk.config.last_feedback_at
|
|
1856
|
+
).getTime();
|
|
1857
|
+
if (now - backendTimestamp < cooldownMs) {
|
|
1831
1858
|
return true;
|
|
1832
1859
|
}
|
|
1833
1860
|
} catch (e) {
|
|
@@ -1846,7 +1873,7 @@
|
|
|
1846
1873
|
const data = JSON.parse(stored);
|
|
1847
1874
|
const submittedAt = data.submittedAt;
|
|
1848
1875
|
|
|
1849
|
-
return
|
|
1876
|
+
return now - submittedAt < cooldownMs;
|
|
1850
1877
|
} catch (e) {
|
|
1851
1878
|
// localStorage may not be available or data is corrupted
|
|
1852
1879
|
return false;
|
|
@@ -2603,11 +2630,6 @@
|
|
|
2603
2630
|
}
|
|
2604
2631
|
</div>
|
|
2605
2632
|
</div>
|
|
2606
|
-
<div class="changelog-list-item-arrow">
|
|
2607
|
-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2608
|
-
<path d="M9 18l6-6-6-6"/>
|
|
2609
|
-
</svg>
|
|
2610
|
-
</div>
|
|
2611
2633
|
</div>
|
|
2612
2634
|
`;
|
|
2613
2635
|
}
|
|
@@ -2971,133 +2993,409 @@
|
|
|
2971
2993
|
}
|
|
2972
2994
|
|
|
2973
2995
|
/**
|
|
2974
|
-
*
|
|
2996
|
+
* WebSocketService - Real-time communication for messenger widget
|
|
2975
2997
|
*/
|
|
2976
|
-
class MessengerState {
|
|
2977
|
-
constructor(options = {}) {
|
|
2978
|
-
this.currentView = 'home'; // 'home', 'messages', 'chat', 'help', 'changelog'
|
|
2979
|
-
this.isOpen = false;
|
|
2980
|
-
this.unreadCount = 0;
|
|
2981
|
-
this.activeConversationId = null;
|
|
2982
|
-
|
|
2983
|
-
// Conversations
|
|
2984
|
-
this.conversations = [];
|
|
2985
|
-
this.messages = {}; // { conversationId: [messages] }
|
|
2986
2998
|
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
this.
|
|
2999
|
+
class WebSocketService {
|
|
3000
|
+
constructor(config = {}) {
|
|
3001
|
+
this.baseURL = config.baseURL || '';
|
|
3002
|
+
this.workspace = config.workspace || '';
|
|
3003
|
+
this.sessionToken = config.sessionToken || null;
|
|
3004
|
+
this.mock = config.mock || false;
|
|
2990
3005
|
|
|
2991
|
-
|
|
2992
|
-
this.
|
|
2993
|
-
this.
|
|
3006
|
+
this.ws = null;
|
|
3007
|
+
this.reconnectAttempts = 0;
|
|
3008
|
+
this.maxReconnectAttempts = 5;
|
|
3009
|
+
this.reconnectDelay = 1000;
|
|
3010
|
+
this.pingInterval = null;
|
|
3011
|
+
this.isConnected = false;
|
|
2994
3012
|
|
|
2995
|
-
//
|
|
2996
|
-
this.
|
|
2997
|
-
this.teamAvatars = options.teamAvatars || [];
|
|
2998
|
-
this.welcomeMessage = options.welcomeMessage || 'How can we help?';
|
|
3013
|
+
// Event listeners
|
|
3014
|
+
this._listeners = new Map();
|
|
2999
3015
|
|
|
3000
|
-
//
|
|
3001
|
-
this.
|
|
3016
|
+
// Bind methods
|
|
3017
|
+
this._onOpen = this._onOpen.bind(this);
|
|
3018
|
+
this._onMessage = this._onMessage.bind(this);
|
|
3019
|
+
this._onClose = this._onClose.bind(this);
|
|
3020
|
+
this._onError = this._onError.bind(this);
|
|
3021
|
+
}
|
|
3002
3022
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3023
|
+
/**
|
|
3024
|
+
* Connect to WebSocket server
|
|
3025
|
+
*/
|
|
3026
|
+
connect(sessionToken = null) {
|
|
3027
|
+
if (sessionToken) {
|
|
3028
|
+
this.sessionToken = sessionToken;
|
|
3029
|
+
}
|
|
3006
3030
|
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3031
|
+
if (!this.sessionToken) {
|
|
3032
|
+
console.warn('[WebSocket] No session token provided');
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3011
3035
|
|
|
3012
|
-
//
|
|
3013
|
-
this.
|
|
3036
|
+
// Mock mode - simulate connection
|
|
3037
|
+
if (this.mock) {
|
|
3038
|
+
this.isConnected = true;
|
|
3039
|
+
this._emit('connected', {});
|
|
3040
|
+
this._startMockResponses();
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3014
3043
|
|
|
3015
|
-
//
|
|
3016
|
-
|
|
3017
|
-
this.
|
|
3044
|
+
// Build WebSocket URL
|
|
3045
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
3046
|
+
let wsURL = this.baseURL.replace(/^https?:/, wsProtocol);
|
|
3047
|
+
wsURL = wsURL.replace('/api/v1', '');
|
|
3048
|
+
wsURL = `${wsURL}/api/v1/widget/messenger/ws?token=${encodeURIComponent(this.sessionToken)}`;
|
|
3018
3049
|
|
|
3019
|
-
|
|
3020
|
-
|
|
3050
|
+
try {
|
|
3051
|
+
this.ws = new WebSocket(wsURL);
|
|
3052
|
+
this.ws.onopen = this._onOpen;
|
|
3053
|
+
this.ws.onmessage = this._onMessage;
|
|
3054
|
+
this.ws.onclose = this._onClose;
|
|
3055
|
+
this.ws.onerror = this._onError;
|
|
3056
|
+
} catch (error) {
|
|
3057
|
+
console.error('[WebSocket] Connection error:', error);
|
|
3058
|
+
this._scheduleReconnect();
|
|
3059
|
+
}
|
|
3021
3060
|
}
|
|
3022
3061
|
|
|
3023
3062
|
/**
|
|
3024
|
-
*
|
|
3063
|
+
* Disconnect from WebSocket server
|
|
3025
3064
|
*/
|
|
3026
|
-
|
|
3027
|
-
this.
|
|
3028
|
-
|
|
3029
|
-
}
|
|
3065
|
+
disconnect() {
|
|
3066
|
+
this.isConnected = false;
|
|
3067
|
+
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
|
|
3030
3068
|
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
this._listeners.forEach((cb) => cb(changeType, data, this));
|
|
3036
|
-
}
|
|
3069
|
+
if (this.pingInterval) {
|
|
3070
|
+
clearInterval(this.pingInterval);
|
|
3071
|
+
this.pingInterval = null;
|
|
3072
|
+
}
|
|
3037
3073
|
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
const previousView = this.currentView;
|
|
3043
|
-
this.currentView = view;
|
|
3044
|
-
this._notify('viewChange', { previousView, currentView: view });
|
|
3045
|
-
}
|
|
3074
|
+
if (this.ws) {
|
|
3075
|
+
this.ws.close();
|
|
3076
|
+
this.ws = null;
|
|
3077
|
+
}
|
|
3046
3078
|
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
this.isOpen = isOpen;
|
|
3052
|
-
this._notify('openChange', { isOpen });
|
|
3079
|
+
if (this._mockInterval) {
|
|
3080
|
+
clearInterval(this._mockInterval);
|
|
3081
|
+
this._mockInterval = null;
|
|
3082
|
+
}
|
|
3053
3083
|
}
|
|
3054
3084
|
|
|
3055
3085
|
/**
|
|
3056
|
-
*
|
|
3086
|
+
* Subscribe to events
|
|
3087
|
+
* @param {string} event - Event name
|
|
3088
|
+
* @param {Function} callback - Event handler
|
|
3089
|
+
* @returns {Function} Unsubscribe function
|
|
3057
3090
|
*/
|
|
3058
|
-
|
|
3059
|
-
this.
|
|
3060
|
-
|
|
3091
|
+
on(event, callback) {
|
|
3092
|
+
if (!this._listeners.has(event)) {
|
|
3093
|
+
this._listeners.set(event, new Set());
|
|
3094
|
+
}
|
|
3095
|
+
this._listeners.get(event).add(callback);
|
|
3096
|
+
return () => this._listeners.get(event).delete(callback);
|
|
3061
3097
|
}
|
|
3062
3098
|
|
|
3063
3099
|
/**
|
|
3064
|
-
*
|
|
3100
|
+
* Remove event listener
|
|
3065
3101
|
*/
|
|
3066
|
-
|
|
3067
|
-
this.
|
|
3068
|
-
|
|
3069
|
-
|
|
3102
|
+
off(event, callback) {
|
|
3103
|
+
if (this._listeners.has(event)) {
|
|
3104
|
+
this._listeners.get(event).delete(callback);
|
|
3105
|
+
}
|
|
3070
3106
|
}
|
|
3071
3107
|
|
|
3072
3108
|
/**
|
|
3073
|
-
*
|
|
3109
|
+
* Send message through WebSocket
|
|
3074
3110
|
*/
|
|
3075
|
-
|
|
3076
|
-
this.
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3111
|
+
send(type, payload = {}) {
|
|
3112
|
+
if (!this.isConnected) {
|
|
3113
|
+
console.warn('[WebSocket] Not connected, cannot send message');
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3080
3116
|
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
this._notify('messagesUpdate', { conversationId, messages });
|
|
3087
|
-
}
|
|
3117
|
+
if (this.mock) {
|
|
3118
|
+
// Mock mode - just log
|
|
3119
|
+
console.log('[WebSocket Mock] Sending:', type, payload);
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3088
3122
|
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
*/
|
|
3092
|
-
addMessage(conversationId, message) {
|
|
3093
|
-
if (!this.messages[conversationId]) {
|
|
3094
|
-
this.messages[conversationId] = [];
|
|
3123
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
3124
|
+
this.ws.send(JSON.stringify({ type, payload }));
|
|
3095
3125
|
}
|
|
3096
|
-
|
|
3126
|
+
}
|
|
3097
3127
|
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3128
|
+
// Private methods
|
|
3129
|
+
|
|
3130
|
+
_onOpen() {
|
|
3131
|
+
console.log('[WebSocket] Connected');
|
|
3132
|
+
this.isConnected = true;
|
|
3133
|
+
this.reconnectAttempts = 0;
|
|
3134
|
+
this._emit('connected', {});
|
|
3135
|
+
|
|
3136
|
+
// Start ping interval to keep connection alive
|
|
3137
|
+
this.pingInterval = setInterval(() => {
|
|
3138
|
+
this.send('ping', {});
|
|
3139
|
+
}, 30000);
|
|
3140
|
+
}
|
|
3141
|
+
|
|
3142
|
+
_onMessage(event) {
|
|
3143
|
+
try {
|
|
3144
|
+
const data = JSON.parse(event.data);
|
|
3145
|
+
const { type, payload } = data;
|
|
3146
|
+
|
|
3147
|
+
// Handle different event types
|
|
3148
|
+
switch (type) {
|
|
3149
|
+
case 'message:new':
|
|
3150
|
+
this._emit('message', payload);
|
|
3151
|
+
break;
|
|
3152
|
+
case 'typing:started':
|
|
3153
|
+
this._emit('typing_started', payload);
|
|
3154
|
+
break;
|
|
3155
|
+
case 'typing:stopped':
|
|
3156
|
+
this._emit('typing_stopped', payload);
|
|
3157
|
+
break;
|
|
3158
|
+
case 'conversation:updated':
|
|
3159
|
+
this._emit('conversation_updated', payload);
|
|
3160
|
+
break;
|
|
3161
|
+
case 'conversation:closed':
|
|
3162
|
+
this._emit('conversation_closed', payload);
|
|
3163
|
+
break;
|
|
3164
|
+
case 'availability:changed':
|
|
3165
|
+
this._emit('availability_changed', payload);
|
|
3166
|
+
break;
|
|
3167
|
+
case 'pong':
|
|
3168
|
+
// Ping response, ignore
|
|
3169
|
+
break;
|
|
3170
|
+
default:
|
|
3171
|
+
console.log('[WebSocket] Unknown event:', type, payload);
|
|
3172
|
+
}
|
|
3173
|
+
} catch (error) {
|
|
3174
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
_onClose(event) {
|
|
3179
|
+
console.log('[WebSocket] Disconnected:', event.code, event.reason);
|
|
3180
|
+
this.isConnected = false;
|
|
3181
|
+
|
|
3182
|
+
if (this.pingInterval) {
|
|
3183
|
+
clearInterval(this.pingInterval);
|
|
3184
|
+
this.pingInterval = null;
|
|
3185
|
+
}
|
|
3186
|
+
|
|
3187
|
+
this._emit('disconnected', { code: event.code, reason: event.reason });
|
|
3188
|
+
this._scheduleReconnect();
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
_onError(error) {
|
|
3192
|
+
console.error('[WebSocket] Error:', error);
|
|
3193
|
+
this._emit('error', { error });
|
|
3194
|
+
}
|
|
3195
|
+
|
|
3196
|
+
_scheduleReconnect() {
|
|
3197
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
3198
|
+
console.log('[WebSocket] Max reconnect attempts reached');
|
|
3199
|
+
this._emit('reconnect_failed', {});
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
this.reconnectAttempts++;
|
|
3204
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
3205
|
+
console.log(
|
|
3206
|
+
`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`
|
|
3207
|
+
);
|
|
3208
|
+
|
|
3209
|
+
setTimeout(() => {
|
|
3210
|
+
this.connect();
|
|
3211
|
+
}, delay);
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
_emit(event, data) {
|
|
3215
|
+
if (this._listeners.has(event)) {
|
|
3216
|
+
this._listeners.get(event).forEach((callback) => {
|
|
3217
|
+
try {
|
|
3218
|
+
callback(data);
|
|
3219
|
+
} catch (error) {
|
|
3220
|
+
console.error(`[WebSocket] Error in ${event} handler:`, error);
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
// Mock support for development
|
|
3227
|
+
_startMockResponses() {
|
|
3228
|
+
// Simulate agent typing and responses
|
|
3229
|
+
this._mockInterval = setInterval(() => {
|
|
3230
|
+
// Randomly emit typing or message events for demo
|
|
3231
|
+
const random = Math.random();
|
|
3232
|
+
if (random < 0.1) {
|
|
3233
|
+
this._emit('typing_started', {
|
|
3234
|
+
conversation_id: 'conv_1',
|
|
3235
|
+
user_id: 'agent_1',
|
|
3236
|
+
user_name: 'Sarah',
|
|
3237
|
+
is_agent: true,
|
|
3238
|
+
});
|
|
3239
|
+
|
|
3240
|
+
// Stop typing after 2 seconds
|
|
3241
|
+
setTimeout(() => {
|
|
3242
|
+
this._emit('typing_stopped', {
|
|
3243
|
+
conversation_id: 'conv_1',
|
|
3244
|
+
user_id: 'agent_1',
|
|
3245
|
+
});
|
|
3246
|
+
}, 2000);
|
|
3247
|
+
}
|
|
3248
|
+
}, 10000);
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
/**
|
|
3252
|
+
* Simulate receiving a message (for mock mode)
|
|
3253
|
+
*/
|
|
3254
|
+
simulateMessage(conversationId, message) {
|
|
3255
|
+
if (this.mock) {
|
|
3256
|
+
this._emit('message', {
|
|
3257
|
+
conversation_id: conversationId,
|
|
3258
|
+
message: {
|
|
3259
|
+
id: 'msg_' + Date.now(),
|
|
3260
|
+
content: message.content,
|
|
3261
|
+
sender_type: message.sender_type || 'agent',
|
|
3262
|
+
sender_name: message.sender_name || 'Support',
|
|
3263
|
+
sender_avatar: message.sender_avatar || null,
|
|
3264
|
+
created_at: new Date().toISOString(),
|
|
3265
|
+
},
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
/**
|
|
3272
|
+
* MessengerState - State management for the Messenger widget
|
|
3273
|
+
*/
|
|
3274
|
+
class MessengerState {
|
|
3275
|
+
constructor(options = {}) {
|
|
3276
|
+
this.currentView = 'home'; // 'home', 'messages', 'chat', 'help', 'changelog'
|
|
3277
|
+
this.isOpen = false;
|
|
3278
|
+
this.unreadCount = 0;
|
|
3279
|
+
this.activeConversationId = null;
|
|
3280
|
+
|
|
3281
|
+
// Conversations
|
|
3282
|
+
this.conversations = [];
|
|
3283
|
+
this.messages = {}; // { conversationId: [messages] }
|
|
3284
|
+
|
|
3285
|
+
// Help articles
|
|
3286
|
+
this.helpArticles = [];
|
|
3287
|
+
this.helpSearchQuery = '';
|
|
3288
|
+
|
|
3289
|
+
// Changelog
|
|
3290
|
+
this.homeChangelogItems = [];
|
|
3291
|
+
this.changelogItems = [];
|
|
3292
|
+
|
|
3293
|
+
// Team info
|
|
3294
|
+
this.teamName = options.teamName || 'Support';
|
|
3295
|
+
this.teamAvatars = options.teamAvatars || [];
|
|
3296
|
+
this.welcomeMessage = options.welcomeMessage || 'How can we help?';
|
|
3297
|
+
|
|
3298
|
+
// User info
|
|
3299
|
+
this.userContext = options.userContext || null;
|
|
3300
|
+
|
|
3301
|
+
// Feature flags
|
|
3302
|
+
this.enableHelp = options.enableHelp !== false;
|
|
3303
|
+
this.enableChangelog = options.enableChangelog !== false;
|
|
3304
|
+
|
|
3305
|
+
// Agent availability
|
|
3306
|
+
this.agentsOnline = false;
|
|
3307
|
+
this.onlineCount = 0;
|
|
3308
|
+
this.responseTime = 'Usually replies within a few minutes';
|
|
3309
|
+
|
|
3310
|
+
// Typing indicators
|
|
3311
|
+
this.typingUsers = {}; // { conversationId: { userName, timestamp } }
|
|
3312
|
+
|
|
3313
|
+
// Loading states
|
|
3314
|
+
this.isLoading = false;
|
|
3315
|
+
this.isLoadingMessages = false;
|
|
3316
|
+
|
|
3317
|
+
// Listeners
|
|
3318
|
+
this._listeners = new Set();
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
/**
|
|
3322
|
+
* Subscribe to state changes
|
|
3323
|
+
*/
|
|
3324
|
+
subscribe(callback) {
|
|
3325
|
+
this._listeners.add(callback);
|
|
3326
|
+
return () => this._listeners.delete(callback);
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
/**
|
|
3330
|
+
* Notify all listeners of state change
|
|
3331
|
+
*/
|
|
3332
|
+
_notify(changeType, data) {
|
|
3333
|
+
this._listeners.forEach((cb) => cb(changeType, data, this));
|
|
3334
|
+
}
|
|
3335
|
+
|
|
3336
|
+
/**
|
|
3337
|
+
* Set current view
|
|
3338
|
+
*/
|
|
3339
|
+
setView(view) {
|
|
3340
|
+
const previousView = this.currentView;
|
|
3341
|
+
this.currentView = view;
|
|
3342
|
+
this._notify('viewChange', { previousView, currentView: view });
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/**
|
|
3346
|
+
* Toggle panel open/closed
|
|
3347
|
+
*/
|
|
3348
|
+
setOpen(isOpen) {
|
|
3349
|
+
this.isOpen = isOpen;
|
|
3350
|
+
this._notify('openChange', { isOpen });
|
|
3351
|
+
}
|
|
3352
|
+
|
|
3353
|
+
/**
|
|
3354
|
+
* Set active conversation for chat view
|
|
3355
|
+
*/
|
|
3356
|
+
setActiveConversation(conversationId) {
|
|
3357
|
+
this.activeConversationId = conversationId;
|
|
3358
|
+
this._notify('conversationChange', { conversationId });
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
/**
|
|
3362
|
+
* Update conversations list
|
|
3363
|
+
*/
|
|
3364
|
+
setConversations(conversations) {
|
|
3365
|
+
this.conversations = conversations;
|
|
3366
|
+
this._updateUnreadCount();
|
|
3367
|
+
this._notify('conversationsUpdate', { conversations });
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
/**
|
|
3371
|
+
* Add a new conversation
|
|
3372
|
+
*/
|
|
3373
|
+
addConversation(conversation) {
|
|
3374
|
+
this.conversations.unshift(conversation);
|
|
3375
|
+
this._updateUnreadCount();
|
|
3376
|
+
this._notify('conversationAdded', { conversation });
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
/**
|
|
3380
|
+
* Update messages for a conversation
|
|
3381
|
+
*/
|
|
3382
|
+
setMessages(conversationId, messages) {
|
|
3383
|
+
this.messages[conversationId] = messages;
|
|
3384
|
+
this._notify('messagesUpdate', { conversationId, messages });
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
/**
|
|
3388
|
+
* Add a message to a conversation
|
|
3389
|
+
*/
|
|
3390
|
+
addMessage(conversationId, message) {
|
|
3391
|
+
if (!this.messages[conversationId]) {
|
|
3392
|
+
this.messages[conversationId] = [];
|
|
3393
|
+
}
|
|
3394
|
+
this.messages[conversationId].push(message);
|
|
3395
|
+
|
|
3396
|
+
// Update conversation preview
|
|
3397
|
+
const conv = this.conversations.find((c) => c.id === conversationId);
|
|
3398
|
+
if (conv) {
|
|
3101
3399
|
conv.lastMessage = message.content;
|
|
3102
3400
|
conv.lastMessageTime = message.timestamp;
|
|
3103
3401
|
if (!message.isOwn) {
|
|
@@ -3828,7 +4126,10 @@
|
|
|
3828
4126
|
data.conversationId === this.state.activeConversationId
|
|
3829
4127
|
) {
|
|
3830
4128
|
this._hideTypingIndicator();
|
|
3831
|
-
} else if (
|
|
4129
|
+
} else if (
|
|
4130
|
+
type === 'messagesUpdate' &&
|
|
4131
|
+
data.conversationId === this.state.activeConversationId
|
|
4132
|
+
) {
|
|
3832
4133
|
this._updateContent();
|
|
3833
4134
|
}
|
|
3834
4135
|
});
|
|
@@ -3888,14 +4189,17 @@
|
|
|
3888
4189
|
</div>
|
|
3889
4190
|
`;
|
|
3890
4191
|
|
|
3891
|
-
this._typingIndicator = this.element.querySelector(
|
|
4192
|
+
this._typingIndicator = this.element.querySelector(
|
|
4193
|
+
'.messenger-typing-indicator'
|
|
4194
|
+
);
|
|
3892
4195
|
this._attachEvents();
|
|
3893
4196
|
this._scrollToBottom();
|
|
3894
4197
|
}
|
|
3895
4198
|
|
|
3896
4199
|
_renderEmptyState(isNewConversation = false) {
|
|
3897
4200
|
const avatarHtml = this._renderTeamAvatars();
|
|
3898
|
-
const responseTime =
|
|
4201
|
+
const responseTime =
|
|
4202
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
3899
4203
|
const isOnline = this.state.agentsOnline;
|
|
3900
4204
|
|
|
3901
4205
|
return `
|
|
@@ -4151,7 +4455,9 @@
|
|
|
4151
4455
|
_showTypingIndicator(userName) {
|
|
4152
4456
|
if (this._typingIndicator) {
|
|
4153
4457
|
this._typingIndicator.style.display = 'flex';
|
|
4154
|
-
const textEl = this._typingIndicator.querySelector(
|
|
4458
|
+
const textEl = this._typingIndicator.querySelector(
|
|
4459
|
+
'.messenger-typing-text'
|
|
4460
|
+
);
|
|
4155
4461
|
if (textEl) {
|
|
4156
4462
|
textEl.textContent = `${userName || 'Support'} is typing...`;
|
|
4157
4463
|
}
|
|
@@ -4532,255 +4838,19 @@
|
|
|
4532
4838
|
<p>Try a different search term</p>
|
|
4533
4839
|
</div>
|
|
4534
4840
|
`;
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
|
-
return `
|
|
4538
|
-
<div class="messenger-help-empty">
|
|
4539
|
-
<div class="messenger-help-empty-icon">
|
|
4540
|
-
<i class="ph ph-question" style="font-size: 48px;"></i>
|
|
4541
|
-
</div>
|
|
4542
|
-
<h3>Help collections</h3>
|
|
4543
|
-
<p>No collections available yet</p>
|
|
4544
|
-
</div>
|
|
4545
|
-
`;
|
|
4546
|
-
}
|
|
4547
|
-
|
|
4548
|
-
_attachEvents() {
|
|
4549
|
-
// Close button
|
|
4550
|
-
this.element
|
|
4551
|
-
.querySelector('.messenger-close-btn')
|
|
4552
|
-
.addEventListener('click', () => {
|
|
4553
|
-
this.state.setOpen(false);
|
|
4554
|
-
});
|
|
4555
|
-
|
|
4556
|
-
// Search input
|
|
4557
|
-
const searchInput = this.element.querySelector(
|
|
4558
|
-
'.messenger-help-search-input'
|
|
4559
|
-
);
|
|
4560
|
-
let searchTimeout;
|
|
4561
|
-
searchInput.addEventListener('input', (e) => {
|
|
4562
|
-
clearTimeout(searchTimeout);
|
|
4563
|
-
searchTimeout = setTimeout(() => {
|
|
4564
|
-
this.state.setHelpSearchQuery(e.target.value);
|
|
4565
|
-
}, 300);
|
|
4566
|
-
});
|
|
4567
|
-
|
|
4568
|
-
this._attachCollectionEvents();
|
|
4569
|
-
}
|
|
4570
|
-
|
|
4571
|
-
_attachCollectionEvents() {
|
|
4572
|
-
this.element
|
|
4573
|
-
.querySelectorAll('.messenger-help-collection')
|
|
4574
|
-
.forEach((item) => {
|
|
4575
|
-
item.addEventListener('click', () => {
|
|
4576
|
-
const collectionId = item.dataset.collectionId;
|
|
4577
|
-
const collection = this.state.helpArticles.find(
|
|
4578
|
-
(c) => c.id === collectionId
|
|
4579
|
-
);
|
|
4580
|
-
if (collection && collection.url) {
|
|
4581
|
-
window.open(collection.url, '_blank');
|
|
4582
|
-
} else if (this.options.onArticleClick) {
|
|
4583
|
-
this.options.onArticleClick(collection);
|
|
4584
|
-
}
|
|
4585
|
-
});
|
|
4586
|
-
});
|
|
4587
|
-
}
|
|
4588
|
-
|
|
4589
|
-
destroy() {
|
|
4590
|
-
if (this._unsubscribe) {
|
|
4591
|
-
this._unsubscribe();
|
|
4592
|
-
}
|
|
4593
|
-
if (this.element && this.element.parentNode) {
|
|
4594
|
-
this.element.parentNode.removeChild(this.element);
|
|
4595
|
-
}
|
|
4596
|
-
}
|
|
4597
|
-
}
|
|
4598
|
-
|
|
4599
|
-
/**
|
|
4600
|
-
* HomeView - Welcome screen with team info and quick actions
|
|
4601
|
-
*/
|
|
4602
|
-
class HomeView {
|
|
4603
|
-
constructor(state, options = {}) {
|
|
4604
|
-
this.state = state;
|
|
4605
|
-
this.options = options;
|
|
4606
|
-
this.element = null;
|
|
4607
|
-
this._unsubscribe = null;
|
|
4608
|
-
}
|
|
4609
|
-
|
|
4610
|
-
render() {
|
|
4611
|
-
this.element = document.createElement('div');
|
|
4612
|
-
this.element.className = 'messenger-view messenger-home-view';
|
|
4613
|
-
|
|
4614
|
-
this._updateContent();
|
|
4615
|
-
|
|
4616
|
-
// Subscribe to state changes to re-render when data loads
|
|
4617
|
-
this._unsubscribe = this.state.subscribe((type) => {
|
|
4618
|
-
if (type === 'homeChangelogUpdate' || type === 'conversationsUpdate' || type === 'availabilityUpdate') {
|
|
4619
|
-
this._updateContent();
|
|
4620
|
-
}
|
|
4621
|
-
});
|
|
4622
|
-
|
|
4623
|
-
return this.element;
|
|
4624
|
-
}
|
|
4625
|
-
|
|
4626
|
-
_updateContent() {
|
|
4627
|
-
const avatarsHtml = this._renderAvatarStack();
|
|
4628
|
-
const recentChangelogHtml = this._renderRecentChangelog();
|
|
4629
|
-
|
|
4630
|
-
this.element.innerHTML = `
|
|
4631
|
-
<div class="messenger-home-header">
|
|
4632
|
-
<div class="messenger-home-header-top">
|
|
4633
|
-
<div class="messenger-home-logo">
|
|
4634
|
-
${this.options.logoUrl ? `<img src="${this.options.logoUrl}" alt="${this.state.teamName}" />` : ''}
|
|
4635
|
-
</div>
|
|
4636
|
-
<div class="messenger-home-avatars">${avatarsHtml}</div>
|
|
4637
|
-
<button class="messenger-close-btn" aria-label="Close">
|
|
4638
|
-
<i class="ph ph-x" style="font-size: 20px;"></i>
|
|
4639
|
-
</button>
|
|
4640
|
-
</div>
|
|
4641
|
-
<div class="messenger-home-welcome">
|
|
4642
|
-
<span class="messenger-home-greeting">Hello there.</span>
|
|
4643
|
-
<span class="messenger-home-question">${this.state.welcomeMessage}</span>
|
|
4644
|
-
${this._renderAvailabilityStatus()}
|
|
4645
|
-
</div>
|
|
4646
|
-
</div>
|
|
4647
|
-
|
|
4648
|
-
<div class="messenger-home-body">
|
|
4649
|
-
<button class="messenger-home-message-btn">
|
|
4650
|
-
<span>Send us a message</span>
|
|
4651
|
-
<i class="ph ph-arrow-right" style="font-size: 16px;"></i>
|
|
4652
|
-
</button>
|
|
4653
|
-
|
|
4654
|
-
${this._renderFeaturedCard()}
|
|
4655
|
-
|
|
4656
|
-
${recentChangelogHtml}
|
|
4657
|
-
</div>
|
|
4658
|
-
`;
|
|
4659
|
-
|
|
4660
|
-
this._attachEvents();
|
|
4661
|
-
}
|
|
4662
|
-
|
|
4663
|
-
_renderAvatarStack() {
|
|
4664
|
-
const avatars = this.state.teamAvatars;
|
|
4665
|
-
if (!avatars || avatars.length === 0) {
|
|
4666
|
-
// Default avatars with initials
|
|
4667
|
-
return `
|
|
4668
|
-
<div class="messenger-avatar-stack">
|
|
4669
|
-
<div class="messenger-avatar" style="background: #5856d6;">S</div>
|
|
4670
|
-
<div class="messenger-avatar" style="background: #007aff;">T</div>
|
|
4671
|
-
</div>
|
|
4672
|
-
`;
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
const avatarItems = avatars
|
|
4676
|
-
.slice(0, 3)
|
|
4677
|
-
.map((avatar, i) => {
|
|
4678
|
-
if (typeof avatar === 'string' && avatar.startsWith('http')) {
|
|
4679
|
-
return `<img class="messenger-avatar" src="${avatar}" alt="Team member" style="z-index: ${3 - i};" />`;
|
|
4680
|
-
}
|
|
4681
|
-
return `<div class="messenger-avatar" style="background: ${this._getAvatarColor(i)}; z-index: ${3 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
|
|
4682
|
-
})
|
|
4683
|
-
.join('');
|
|
4684
|
-
|
|
4685
|
-
return `<div class="messenger-avatar-stack">${avatarItems}</div>`;
|
|
4686
|
-
}
|
|
4687
|
-
|
|
4688
|
-
_getAvatarColor(index) {
|
|
4689
|
-
const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500', '#ff3b30'];
|
|
4690
|
-
return colors[index % colors.length];
|
|
4691
|
-
}
|
|
4692
|
-
|
|
4693
|
-
_renderAvailabilityStatus() {
|
|
4694
|
-
const isOnline = this.state.agentsOnline;
|
|
4695
|
-
const responseTime = this.state.responseTime || 'We typically reply within a few minutes';
|
|
4696
|
-
|
|
4697
|
-
if (isOnline) {
|
|
4698
|
-
return `
|
|
4699
|
-
<div class="messenger-home-availability">
|
|
4700
|
-
<span class="messenger-availability-dot messenger-availability-online"></span>
|
|
4701
|
-
<span class="messenger-availability-text">We're online now</span>
|
|
4702
|
-
</div>
|
|
4703
|
-
`;
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
|
-
return `
|
|
4707
|
-
<div class="messenger-home-availability">
|
|
4708
|
-
<span class="messenger-availability-dot messenger-availability-away"></span>
|
|
4709
|
-
<span class="messenger-availability-text">${responseTime}</span>
|
|
4710
|
-
</div>
|
|
4711
|
-
`;
|
|
4712
|
-
}
|
|
4713
|
-
|
|
4714
|
-
_renderFeaturedCard() {
|
|
4715
|
-
// Only show if there's featured content configured
|
|
4716
|
-
if (!this.options.featuredContent) {
|
|
4717
|
-
return '';
|
|
4718
|
-
}
|
|
4719
|
-
|
|
4720
|
-
const { title, description, imageUrl, action } =
|
|
4721
|
-
this.options.featuredContent;
|
|
4722
|
-
|
|
4723
|
-
return `
|
|
4724
|
-
<div class="messenger-home-featured">
|
|
4725
|
-
${imageUrl ? `<img src="${imageUrl}" alt="${title}" class="messenger-home-featured-image" onerror="this.style.display='none';" />` : ''}
|
|
4726
|
-
<div class="messenger-home-featured-content">
|
|
4727
|
-
<h3>${title}</h3>
|
|
4728
|
-
<p>${description}</p>
|
|
4729
|
-
</div>
|
|
4730
|
-
${action ? `<button class="messenger-home-featured-btn" data-action="${action.type}" data-value="${action.value}">${action.label}</button>` : ''}
|
|
4731
|
-
</div>
|
|
4732
|
-
`;
|
|
4733
|
-
}
|
|
4734
|
-
|
|
4735
|
-
_renderRecentChangelog() {
|
|
4736
|
-
// Show recent changelog preview as cards with images
|
|
4737
|
-
const changelogItems = this.state.homeChangelogItems;
|
|
4738
|
-
if (changelogItems.length === 0) {
|
|
4739
|
-
return '';
|
|
4740
|
-
}
|
|
4741
|
-
|
|
4742
|
-
const changelogHtml = changelogItems
|
|
4743
|
-
.map(
|
|
4744
|
-
(item) => `
|
|
4745
|
-
<div class="messenger-home-changelog-card" data-changelog-id="${item.id}">
|
|
4746
|
-
${
|
|
4747
|
-
item.coverImage
|
|
4748
|
-
? `
|
|
4749
|
-
<div class="messenger-home-changelog-cover">
|
|
4750
|
-
<img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
|
|
4751
|
-
${item.coverText ? `<span class="messenger-home-changelog-cover-text">${item.coverText}</span>` : ''}
|
|
4752
|
-
</div>
|
|
4753
|
-
`
|
|
4754
|
-
: ''
|
|
4755
|
-
}
|
|
4756
|
-
<div class="messenger-home-changelog-card-content">
|
|
4757
|
-
<h4 class="messenger-home-changelog-card-title">${item.title}</h4>
|
|
4758
|
-
<p class="messenger-home-changelog-card-desc">${item.description || ''}</p>
|
|
4759
|
-
</div>
|
|
4760
|
-
</div>
|
|
4761
|
-
`
|
|
4762
|
-
)
|
|
4763
|
-
.join('');
|
|
4841
|
+
}
|
|
4764
4842
|
|
|
4765
4843
|
return `
|
|
4766
|
-
<div class="messenger-
|
|
4767
|
-
|
|
4844
|
+
<div class="messenger-help-empty">
|
|
4845
|
+
<div class="messenger-help-empty-icon">
|
|
4846
|
+
<i class="ph ph-question" style="font-size: 48px;"></i>
|
|
4847
|
+
</div>
|
|
4848
|
+
<h3>Help collections</h3>
|
|
4849
|
+
<p>No collections available yet</p>
|
|
4768
4850
|
</div>
|
|
4769
4851
|
`;
|
|
4770
4852
|
}
|
|
4771
4853
|
|
|
4772
|
-
_formatDate(dateString) {
|
|
4773
|
-
if (!dateString) return '';
|
|
4774
|
-
const date = new Date(dateString);
|
|
4775
|
-
const now = new Date();
|
|
4776
|
-
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
4777
|
-
|
|
4778
|
-
if (diffDays === 0) return 'Today';
|
|
4779
|
-
if (diffDays === 1) return 'Yesterday';
|
|
4780
|
-
if (diffDays < 7) return `${diffDays}d ago`;
|
|
4781
|
-
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
4782
|
-
}
|
|
4783
|
-
|
|
4784
4854
|
_attachEvents() {
|
|
4785
4855
|
// Close button
|
|
4786
4856
|
this.element
|
|
@@ -4789,48 +4859,37 @@
|
|
|
4789
4859
|
this.state.setOpen(false);
|
|
4790
4860
|
});
|
|
4791
4861
|
|
|
4792
|
-
//
|
|
4793
|
-
this.element
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4862
|
+
// Search input
|
|
4863
|
+
const searchInput = this.element.querySelector(
|
|
4864
|
+
'.messenger-help-search-input'
|
|
4865
|
+
);
|
|
4866
|
+
let searchTimeout;
|
|
4867
|
+
searchInput.addEventListener('input', (e) => {
|
|
4868
|
+
clearTimeout(searchTimeout);
|
|
4869
|
+
searchTimeout = setTimeout(() => {
|
|
4870
|
+
this.state.setHelpSearchQuery(e.target.value);
|
|
4871
|
+
}, 300);
|
|
4872
|
+
});
|
|
4798
4873
|
|
|
4799
|
-
|
|
4874
|
+
this._attachCollectionEvents();
|
|
4875
|
+
}
|
|
4876
|
+
|
|
4877
|
+
_attachCollectionEvents() {
|
|
4800
4878
|
this.element
|
|
4801
|
-
.querySelectorAll('.messenger-
|
|
4879
|
+
.querySelectorAll('.messenger-help-collection')
|
|
4802
4880
|
.forEach((item) => {
|
|
4803
4881
|
item.addEventListener('click', () => {
|
|
4804
|
-
|
|
4805
|
-
this.state.
|
|
4882
|
+
const collectionId = item.dataset.collectionId;
|
|
4883
|
+
const collection = this.state.helpArticles.find(
|
|
4884
|
+
(c) => c.id === collectionId
|
|
4885
|
+
);
|
|
4886
|
+
if (collection && collection.url) {
|
|
4887
|
+
window.open(collection.url, '_blank');
|
|
4888
|
+
} else if (this.options.onArticleClick) {
|
|
4889
|
+
this.options.onArticleClick(collection);
|
|
4890
|
+
}
|
|
4806
4891
|
});
|
|
4807
4892
|
});
|
|
4808
|
-
|
|
4809
|
-
// See all changelog
|
|
4810
|
-
const seeAllBtn = this.element.querySelector(
|
|
4811
|
-
'.messenger-home-changelog-all'
|
|
4812
|
-
);
|
|
4813
|
-
if (seeAllBtn) {
|
|
4814
|
-
seeAllBtn.addEventListener('click', () => {
|
|
4815
|
-
this.state.setView('changelog');
|
|
4816
|
-
});
|
|
4817
|
-
}
|
|
4818
|
-
|
|
4819
|
-
// Featured card action
|
|
4820
|
-
const featuredBtn = this.element.querySelector(
|
|
4821
|
-
'.messenger-home-featured-btn'
|
|
4822
|
-
);
|
|
4823
|
-
if (featuredBtn) {
|
|
4824
|
-
featuredBtn.addEventListener('click', () => {
|
|
4825
|
-
const action = featuredBtn.dataset.action;
|
|
4826
|
-
const value = featuredBtn.dataset.value;
|
|
4827
|
-
if (action === 'url') {
|
|
4828
|
-
window.open(value, '_blank');
|
|
4829
|
-
} else if (action === 'view') {
|
|
4830
|
-
this.state.setView(value);
|
|
4831
|
-
}
|
|
4832
|
-
});
|
|
4833
|
-
}
|
|
4834
4893
|
}
|
|
4835
4894
|
|
|
4836
4895
|
destroy() {
|
|
@@ -4844,277 +4903,255 @@
|
|
|
4844
4903
|
}
|
|
4845
4904
|
|
|
4846
4905
|
/**
|
|
4847
|
-
*
|
|
4906
|
+
* HomeView - Welcome screen with team info and quick actions
|
|
4848
4907
|
*/
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
this.
|
|
4853
|
-
this.
|
|
4854
|
-
this.
|
|
4855
|
-
this.mock = config.mock || false;
|
|
4856
|
-
|
|
4857
|
-
this.ws = null;
|
|
4858
|
-
this.reconnectAttempts = 0;
|
|
4859
|
-
this.maxReconnectAttempts = 5;
|
|
4860
|
-
this.reconnectDelay = 1000;
|
|
4861
|
-
this.pingInterval = null;
|
|
4862
|
-
this.isConnected = false;
|
|
4863
|
-
|
|
4864
|
-
// Event listeners
|
|
4865
|
-
this._listeners = new Map();
|
|
4866
|
-
|
|
4867
|
-
// Bind methods
|
|
4868
|
-
this._onOpen = this._onOpen.bind(this);
|
|
4869
|
-
this._onMessage = this._onMessage.bind(this);
|
|
4870
|
-
this._onClose = this._onClose.bind(this);
|
|
4871
|
-
this._onError = this._onError.bind(this);
|
|
4908
|
+
class HomeView {
|
|
4909
|
+
constructor(state, options = {}) {
|
|
4910
|
+
this.state = state;
|
|
4911
|
+
this.options = options;
|
|
4912
|
+
this.element = null;
|
|
4913
|
+
this._unsubscribe = null;
|
|
4872
4914
|
}
|
|
4873
4915
|
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
connect(sessionToken = null) {
|
|
4878
|
-
if (sessionToken) {
|
|
4879
|
-
this.sessionToken = sessionToken;
|
|
4880
|
-
}
|
|
4881
|
-
|
|
4882
|
-
if (!this.sessionToken) {
|
|
4883
|
-
console.warn('[WebSocket] No session token provided');
|
|
4884
|
-
return;
|
|
4885
|
-
}
|
|
4916
|
+
render() {
|
|
4917
|
+
this.element = document.createElement('div');
|
|
4918
|
+
this.element.className = 'messenger-view messenger-home-view';
|
|
4886
4919
|
|
|
4887
|
-
|
|
4888
|
-
if (this.mock) {
|
|
4889
|
-
this.isConnected = true;
|
|
4890
|
-
this._emit('connected', {});
|
|
4891
|
-
this._startMockResponses();
|
|
4892
|
-
return;
|
|
4893
|
-
}
|
|
4920
|
+
this._updateContent();
|
|
4894
4921
|
|
|
4895
|
-
//
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4922
|
+
// Subscribe to state changes to re-render when data loads
|
|
4923
|
+
this._unsubscribe = this.state.subscribe((type) => {
|
|
4924
|
+
if (
|
|
4925
|
+
type === 'homeChangelogUpdate' ||
|
|
4926
|
+
type === 'conversationsUpdate' ||
|
|
4927
|
+
type === 'availabilityUpdate'
|
|
4928
|
+
) {
|
|
4929
|
+
this._updateContent();
|
|
4930
|
+
}
|
|
4931
|
+
});
|
|
4900
4932
|
|
|
4901
|
-
|
|
4902
|
-
this.ws = new WebSocket(wsURL);
|
|
4903
|
-
this.ws.onopen = this._onOpen;
|
|
4904
|
-
this.ws.onmessage = this._onMessage;
|
|
4905
|
-
this.ws.onclose = this._onClose;
|
|
4906
|
-
this.ws.onerror = this._onError;
|
|
4907
|
-
} catch (error) {
|
|
4908
|
-
console.error('[WebSocket] Connection error:', error);
|
|
4909
|
-
this._scheduleReconnect();
|
|
4910
|
-
}
|
|
4933
|
+
return this.element;
|
|
4911
4934
|
}
|
|
4912
4935
|
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
disconnect() {
|
|
4917
|
-
this.isConnected = false;
|
|
4918
|
-
this.reconnectAttempts = this.maxReconnectAttempts; // Prevent reconnection
|
|
4936
|
+
_updateContent() {
|
|
4937
|
+
const avatarsHtml = this._renderAvatarStack();
|
|
4938
|
+
const recentChangelogHtml = this._renderRecentChangelog();
|
|
4919
4939
|
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4940
|
+
this.element.innerHTML = `
|
|
4941
|
+
<div class="messenger-home-header">
|
|
4942
|
+
<div class="messenger-home-header-top">
|
|
4943
|
+
<div class="messenger-home-logo">
|
|
4944
|
+
${this.options.logoUrl ? `<img src="${this.options.logoUrl}" alt="${this.state.teamName}" />` : ''}
|
|
4945
|
+
</div>
|
|
4946
|
+
<div class="messenger-home-avatars">${avatarsHtml}</div>
|
|
4947
|
+
<button class="messenger-close-btn" aria-label="Close">
|
|
4948
|
+
<i class="ph ph-x" style="font-size: 20px;"></i>
|
|
4949
|
+
</button>
|
|
4950
|
+
</div>
|
|
4951
|
+
<div class="messenger-home-welcome">
|
|
4952
|
+
<span class="messenger-home-greeting">Hello there.</span>
|
|
4953
|
+
<span class="messenger-home-question">${this.state.welcomeMessage}</span>
|
|
4954
|
+
${this._renderAvailabilityStatus()}
|
|
4955
|
+
</div>
|
|
4956
|
+
</div>
|
|
4924
4957
|
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4958
|
+
<div class="messenger-home-body">
|
|
4959
|
+
<button class="messenger-home-message-btn">
|
|
4960
|
+
<span>Send us a message</span>
|
|
4961
|
+
<i class="ph ph-arrow-right" style="font-size: 16px;"></i>
|
|
4962
|
+
</button>
|
|
4929
4963
|
|
|
4930
|
-
|
|
4931
|
-
clearInterval(this._mockInterval);
|
|
4932
|
-
this._mockInterval = null;
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4964
|
+
${this._renderFeaturedCard()}
|
|
4935
4965
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
* @param {Function} callback - Event handler
|
|
4940
|
-
* @returns {Function} Unsubscribe function
|
|
4941
|
-
*/
|
|
4942
|
-
on(event, callback) {
|
|
4943
|
-
if (!this._listeners.has(event)) {
|
|
4944
|
-
this._listeners.set(event, new Set());
|
|
4945
|
-
}
|
|
4946
|
-
this._listeners.get(event).add(callback);
|
|
4947
|
-
return () => this._listeners.get(event).delete(callback);
|
|
4948
|
-
}
|
|
4966
|
+
${recentChangelogHtml}
|
|
4967
|
+
</div>
|
|
4968
|
+
`;
|
|
4949
4969
|
|
|
4950
|
-
|
|
4951
|
-
* Remove event listener
|
|
4952
|
-
*/
|
|
4953
|
-
off(event, callback) {
|
|
4954
|
-
if (this._listeners.has(event)) {
|
|
4955
|
-
this._listeners.get(event).delete(callback);
|
|
4956
|
-
}
|
|
4970
|
+
this._attachEvents();
|
|
4957
4971
|
}
|
|
4958
4972
|
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4973
|
+
_renderAvatarStack() {
|
|
4974
|
+
const avatars = this.state.teamAvatars;
|
|
4975
|
+
if (!avatars || avatars.length === 0) {
|
|
4976
|
+
// Default avatars with initials
|
|
4977
|
+
return `
|
|
4978
|
+
<div class="messenger-avatar-stack">
|
|
4979
|
+
<div class="messenger-avatar" style="background: #5856d6;">S</div>
|
|
4980
|
+
<div class="messenger-avatar" style="background: #007aff;">T</div>
|
|
4981
|
+
</div>
|
|
4982
|
+
`;
|
|
4966
4983
|
}
|
|
4967
4984
|
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4985
|
+
const avatarItems = avatars
|
|
4986
|
+
.slice(0, 3)
|
|
4987
|
+
.map((avatar, i) => {
|
|
4988
|
+
if (typeof avatar === 'string' && avatar.startsWith('http')) {
|
|
4989
|
+
return `<img class="messenger-avatar" src="${avatar}" alt="Team member" style="z-index: ${3 - i};" />`;
|
|
4990
|
+
}
|
|
4991
|
+
return `<div class="messenger-avatar" style="background: ${this._getAvatarColor(i)}; z-index: ${3 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
|
|
4992
|
+
})
|
|
4993
|
+
.join('');
|
|
4973
4994
|
|
|
4974
|
-
|
|
4975
|
-
this.ws.send(JSON.stringify({ type, payload }));
|
|
4976
|
-
}
|
|
4995
|
+
return `<div class="messenger-avatar-stack">${avatarItems}</div>`;
|
|
4977
4996
|
}
|
|
4978
4997
|
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
console.log('[WebSocket] Connected');
|
|
4983
|
-
this.isConnected = true;
|
|
4984
|
-
this.reconnectAttempts = 0;
|
|
4985
|
-
this._emit('connected', {});
|
|
4986
|
-
|
|
4987
|
-
// Start ping interval to keep connection alive
|
|
4988
|
-
this.pingInterval = setInterval(() => {
|
|
4989
|
-
this.send('ping', {});
|
|
4990
|
-
}, 30000);
|
|
4998
|
+
_getAvatarColor(index) {
|
|
4999
|
+
const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500', '#ff3b30'];
|
|
5000
|
+
return colors[index % colors.length];
|
|
4991
5001
|
}
|
|
4992
5002
|
|
|
4993
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
5003
|
+
_renderAvailabilityStatus() {
|
|
5004
|
+
const isOnline = this.state.agentsOnline;
|
|
5005
|
+
const responseTime =
|
|
5006
|
+
this.state.responseTime || 'We typically reply within a few minutes';
|
|
4997
5007
|
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
break;
|
|
5006
|
-
case 'typing:stopped':
|
|
5007
|
-
this._emit('typing_stopped', payload);
|
|
5008
|
-
break;
|
|
5009
|
-
case 'conversation:updated':
|
|
5010
|
-
this._emit('conversation_updated', payload);
|
|
5011
|
-
break;
|
|
5012
|
-
case 'conversation:closed':
|
|
5013
|
-
this._emit('conversation_closed', payload);
|
|
5014
|
-
break;
|
|
5015
|
-
case 'availability:changed':
|
|
5016
|
-
this._emit('availability_changed', payload);
|
|
5017
|
-
break;
|
|
5018
|
-
case 'pong':
|
|
5019
|
-
// Ping response, ignore
|
|
5020
|
-
break;
|
|
5021
|
-
default:
|
|
5022
|
-
console.log('[WebSocket] Unknown event:', type, payload);
|
|
5023
|
-
}
|
|
5024
|
-
} catch (error) {
|
|
5025
|
-
console.error('[WebSocket] Failed to parse message:', error);
|
|
5008
|
+
if (isOnline) {
|
|
5009
|
+
return `
|
|
5010
|
+
<div class="messenger-home-availability">
|
|
5011
|
+
<span class="messenger-availability-dot messenger-availability-online"></span>
|
|
5012
|
+
<span class="messenger-availability-text">We're online now</span>
|
|
5013
|
+
</div>
|
|
5014
|
+
`;
|
|
5026
5015
|
}
|
|
5027
|
-
}
|
|
5028
5016
|
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5017
|
+
return `
|
|
5018
|
+
<div class="messenger-home-availability">
|
|
5019
|
+
<span class="messenger-availability-dot messenger-availability-away"></span>
|
|
5020
|
+
<span class="messenger-availability-text">${responseTime}</span>
|
|
5021
|
+
</div>
|
|
5022
|
+
`;
|
|
5023
|
+
}
|
|
5032
5024
|
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5025
|
+
_renderFeaturedCard() {
|
|
5026
|
+
// Only show if there's featured content configured
|
|
5027
|
+
if (!this.options.featuredContent) {
|
|
5028
|
+
return '';
|
|
5036
5029
|
}
|
|
5037
5030
|
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
}
|
|
5031
|
+
const { title, description, imageUrl, action } =
|
|
5032
|
+
this.options.featuredContent;
|
|
5041
5033
|
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5034
|
+
return `
|
|
5035
|
+
<div class="messenger-home-featured">
|
|
5036
|
+
${imageUrl ? `<img src="${imageUrl}" alt="${title}" class="messenger-home-featured-image" onerror="this.style.display='none';" />` : ''}
|
|
5037
|
+
<div class="messenger-home-featured-content">
|
|
5038
|
+
<h3>${title}</h3>
|
|
5039
|
+
<p>${description}</p>
|
|
5040
|
+
</div>
|
|
5041
|
+
${action ? `<button class="messenger-home-featured-btn" data-action="${action.type}" data-value="${action.value}">${action.label}</button>` : ''}
|
|
5042
|
+
</div>
|
|
5043
|
+
`;
|
|
5045
5044
|
}
|
|
5046
5045
|
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
return;
|
|
5046
|
+
_renderRecentChangelog() {
|
|
5047
|
+
// Show recent changelog preview as cards with images
|
|
5048
|
+
const changelogItems = this.state.homeChangelogItems;
|
|
5049
|
+
if (changelogItems.length === 0) {
|
|
5050
|
+
return '';
|
|
5052
5051
|
}
|
|
5053
5052
|
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5053
|
+
const changelogHtml = changelogItems
|
|
5054
|
+
.map(
|
|
5055
|
+
(item) => `
|
|
5056
|
+
<div class="messenger-home-changelog-card" data-changelog-id="${item.id}">
|
|
5057
|
+
${
|
|
5058
|
+
item.coverImage
|
|
5059
|
+
? `
|
|
5060
|
+
<div class="messenger-home-changelog-cover">
|
|
5061
|
+
<img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
|
|
5062
|
+
${item.coverText ? `<span class="messenger-home-changelog-cover-text">${item.coverText}</span>` : ''}
|
|
5063
|
+
</div>
|
|
5064
|
+
`
|
|
5065
|
+
: ''
|
|
5066
|
+
}
|
|
5067
|
+
<div class="messenger-home-changelog-card-content">
|
|
5068
|
+
<h4 class="messenger-home-changelog-card-title">${item.title}</h4>
|
|
5069
|
+
<p class="messenger-home-changelog-card-desc">${item.description || ''}</p>
|
|
5070
|
+
</div>
|
|
5071
|
+
</div>
|
|
5072
|
+
`
|
|
5073
|
+
)
|
|
5074
|
+
.join('');
|
|
5057
5075
|
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5076
|
+
return `
|
|
5077
|
+
<div class="messenger-home-changelog-section">
|
|
5078
|
+
${changelogHtml}
|
|
5079
|
+
</div>
|
|
5080
|
+
`;
|
|
5061
5081
|
}
|
|
5062
5082
|
|
|
5063
|
-
|
|
5064
|
-
if (
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
}
|
|
5083
|
+
_formatDate(dateString) {
|
|
5084
|
+
if (!dateString) return '';
|
|
5085
|
+
const date = new Date(dateString);
|
|
5086
|
+
const now = new Date();
|
|
5087
|
+
const diffDays = Math.floor((now - date) / (1000 * 60 * 60 * 24));
|
|
5088
|
+
|
|
5089
|
+
if (diffDays === 0) return 'Today';
|
|
5090
|
+
if (diffDays === 1) return 'Yesterday';
|
|
5091
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
5092
|
+
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
5073
5093
|
}
|
|
5074
5094
|
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5095
|
+
_attachEvents() {
|
|
5096
|
+
// Close button
|
|
5097
|
+
this.element
|
|
5098
|
+
.querySelector('.messenger-close-btn')
|
|
5099
|
+
.addEventListener('click', () => {
|
|
5100
|
+
this.state.setOpen(false);
|
|
5101
|
+
});
|
|
5102
|
+
|
|
5103
|
+
// Send message button
|
|
5104
|
+
this.element
|
|
5105
|
+
.querySelector('.messenger-home-message-btn')
|
|
5106
|
+
.addEventListener('click', () => {
|
|
5107
|
+
this.state.setView('messages');
|
|
5108
|
+
});
|
|
5109
|
+
|
|
5110
|
+
// Changelog items
|
|
5111
|
+
this.element
|
|
5112
|
+
.querySelectorAll('.messenger-home-changelog-item')
|
|
5113
|
+
.forEach((item) => {
|
|
5114
|
+
item.addEventListener('click', () => {
|
|
5115
|
+
// Navigate to changelog view with specific item selected
|
|
5116
|
+
this.state.setView('changelog');
|
|
5087
5117
|
});
|
|
5118
|
+
});
|
|
5088
5119
|
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
}
|
|
5097
|
-
}
|
|
5098
|
-
}
|
|
5120
|
+
// See all changelog
|
|
5121
|
+
const seeAllBtn = this.element.querySelector(
|
|
5122
|
+
'.messenger-home-changelog-all'
|
|
5123
|
+
);
|
|
5124
|
+
if (seeAllBtn) {
|
|
5125
|
+
seeAllBtn.addEventListener('click', () => {
|
|
5126
|
+
this.state.setView('changelog');
|
|
5127
|
+
});
|
|
5128
|
+
}
|
|
5099
5129
|
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
if (
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
created_at: new Date().toISOString(),
|
|
5114
|
-
},
|
|
5130
|
+
// Featured card action
|
|
5131
|
+
const featuredBtn = this.element.querySelector(
|
|
5132
|
+
'.messenger-home-featured-btn'
|
|
5133
|
+
);
|
|
5134
|
+
if (featuredBtn) {
|
|
5135
|
+
featuredBtn.addEventListener('click', () => {
|
|
5136
|
+
const action = featuredBtn.dataset.action;
|
|
5137
|
+
const value = featuredBtn.dataset.value;
|
|
5138
|
+
if (action === 'url') {
|
|
5139
|
+
window.open(value, '_blank');
|
|
5140
|
+
} else if (action === 'view') {
|
|
5141
|
+
this.state.setView(value);
|
|
5142
|
+
}
|
|
5115
5143
|
});
|
|
5116
5144
|
}
|
|
5117
5145
|
}
|
|
5146
|
+
|
|
5147
|
+
destroy() {
|
|
5148
|
+
if (this._unsubscribe) {
|
|
5149
|
+
this._unsubscribe();
|
|
5150
|
+
}
|
|
5151
|
+
if (this.element && this.element.parentNode) {
|
|
5152
|
+
this.element.parentNode.removeChild(this.element);
|
|
5153
|
+
}
|
|
5154
|
+
}
|
|
5118
5155
|
}
|
|
5119
5156
|
|
|
5120
5157
|
/**
|
|
@@ -5325,7 +5362,10 @@
|
|
|
5325
5362
|
this.messengerState.addMessage(conversation_id, localMessage);
|
|
5326
5363
|
|
|
5327
5364
|
// Update unread count if panel is closed or viewing different conversation
|
|
5328
|
-
if (
|
|
5365
|
+
if (
|
|
5366
|
+
!this.messengerState.isOpen ||
|
|
5367
|
+
this.messengerState.activeConversationId !== conversation_id
|
|
5368
|
+
) {
|
|
5329
5369
|
this._updateUnreadCount();
|
|
5330
5370
|
}
|
|
5331
5371
|
}
|
|
@@ -5359,7 +5399,9 @@
|
|
|
5359
5399
|
const response = await this.apiService.getUnreadCount();
|
|
5360
5400
|
if (response.status && response.data) {
|
|
5361
5401
|
this.messengerState.unreadCount = response.data.unread_count || 0;
|
|
5362
|
-
this.messengerState._notify('unreadCountChange', {
|
|
5402
|
+
this.messengerState._notify('unreadCountChange', {
|
|
5403
|
+
count: this.messengerState.unreadCount,
|
|
5404
|
+
});
|
|
5363
5405
|
}
|
|
5364
5406
|
} catch (error) {
|
|
5365
5407
|
console.error('[MessengerWidget] Failed to get unread count:', error);
|
|
@@ -5533,9 +5575,16 @@
|
|
|
5533
5575
|
// Transform API response to local format
|
|
5534
5576
|
return response.data.map((conv) => ({
|
|
5535
5577
|
id: conv.id,
|
|
5536
|
-
title:
|
|
5578
|
+
title:
|
|
5579
|
+
conv.subject ||
|
|
5580
|
+
`Chat with ${conv.assigned_user?.name || 'Support'}`,
|
|
5537
5581
|
participants: conv.assigned_user
|
|
5538
|
-
? [
|
|
5582
|
+
? [
|
|
5583
|
+
{
|
|
5584
|
+
name: conv.assigned_user.name,
|
|
5585
|
+
avatarUrl: conv.assigned_user.avatar,
|
|
5586
|
+
},
|
|
5587
|
+
]
|
|
5539
5588
|
: [{ name: 'Support', avatarUrl: null }],
|
|
5540
5589
|
lastMessage: conv.preview || conv.snippet || '',
|
|
5541
5590
|
lastMessageTime: conv.last_message_at,
|
|
@@ -5559,7 +5608,8 @@
|
|
|
5559
5608
|
id: collection.id,
|
|
5560
5609
|
title: collection.title || collection.name,
|
|
5561
5610
|
description: collection.description || '',
|
|
5562
|
-
articleCount:
|
|
5611
|
+
articleCount:
|
|
5612
|
+
collection.article_count || collection.articleCount || 0,
|
|
5563
5613
|
icon: collection.icon || 'ph-book-open',
|
|
5564
5614
|
url: collection.url || `#/help/${collection.slug || collection.id}`,
|
|
5565
5615
|
}));
|
|
@@ -5584,7 +5634,9 @@
|
|
|
5584
5634
|
isOwn: msg.sender_type === 'customer',
|
|
5585
5635
|
timestamp: msg.created_at,
|
|
5586
5636
|
sender: {
|
|
5587
|
-
name:
|
|
5637
|
+
name:
|
|
5638
|
+
msg.sender_name ||
|
|
5639
|
+
(msg.sender_type === 'customer' ? 'You' : 'Support'),
|
|
5588
5640
|
avatarUrl: msg.sender_avatar || null,
|
|
5589
5641
|
},
|
|
5590
5642
|
}));
|
|
@@ -6929,8 +6981,10 @@
|
|
|
6929
6981
|
// Check backend tracking first (from init response)
|
|
6930
6982
|
if (this.config.last_feedback_at) {
|
|
6931
6983
|
try {
|
|
6932
|
-
const backendTimestamp = new Date(
|
|
6933
|
-
|
|
6984
|
+
const backendTimestamp = new Date(
|
|
6985
|
+
this.config.last_feedback_at
|
|
6986
|
+
).getTime();
|
|
6987
|
+
if (now - backendTimestamp < cooldownMs) {
|
|
6934
6988
|
return true;
|
|
6935
6989
|
}
|
|
6936
6990
|
} catch (e) {
|
|
@@ -6945,7 +6999,7 @@
|
|
|
6945
6999
|
if (!stored) return false;
|
|
6946
7000
|
|
|
6947
7001
|
const data = JSON.parse(stored);
|
|
6948
|
-
return
|
|
7002
|
+
return now - data.submittedAt < cooldownMs;
|
|
6949
7003
|
} catch (e) {
|
|
6950
7004
|
return false;
|
|
6951
7005
|
}
|