@product7/feedback-sdk 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,198 @@
1
+ /**
2
+ * ChangelogView - Changelog and announcements
3
+ */
4
+ export class ChangelogView {
5
+ constructor(state, options = {}) {
6
+ this.state = state;
7
+ this.options = options;
8
+ this.element = null;
9
+ this._unsubscribe = null;
10
+ }
11
+
12
+ render() {
13
+ this.element = document.createElement('div');
14
+ this.element.className = 'messenger-view messenger-changelog-view';
15
+
16
+ this._updateContent();
17
+
18
+ // Subscribe to state changes
19
+ this._unsubscribe = this.state.subscribe((type) => {
20
+ if (type === 'changelogUpdate') {
21
+ this._updateChangelogList();
22
+ }
23
+ });
24
+
25
+ return this.element;
26
+ }
27
+
28
+ _updateContent() {
29
+ const avatarsHtml = this._renderTeamAvatars();
30
+
31
+ this.element.innerHTML = `
32
+ <div class="messenger-changelog-header">
33
+ <h2>Changelog</h2>
34
+ <button class="messenger-close-btn" aria-label="Close">
35
+ <i class="ph ph-x" style="font-size: 20px;"></i>
36
+ </button>
37
+ </div>
38
+
39
+ <div class="messenger-changelog-subheader">
40
+ <span class="messenger-changelog-latest">Latest</span>
41
+ <div class="messenger-changelog-team">
42
+ <span>From ${this.state.teamName}</span>
43
+ ${avatarsHtml}
44
+ </div>
45
+ </div>
46
+
47
+ <div class="messenger-changelog-body">
48
+ <div class="messenger-changelog-list"></div>
49
+ </div>
50
+ `;
51
+
52
+ this._updateChangelogList();
53
+ this._attachEvents();
54
+ }
55
+
56
+ _updateChangelogList() {
57
+ const changelogContainer = this.element.querySelector(
58
+ '.messenger-changelog-list'
59
+ );
60
+ const changelogItems = this.state.changelogItems;
61
+
62
+ if (changelogItems.length === 0) {
63
+ changelogContainer.innerHTML = this._renderEmptyState();
64
+ return;
65
+ }
66
+
67
+ changelogContainer.innerHTML = changelogItems
68
+ .map((item) => this._renderChangelogCard(item))
69
+ .join('');
70
+
71
+ // Attach click events
72
+ this._attachChangelogEvents();
73
+ }
74
+
75
+ _renderChangelogCard(item) {
76
+ const tagsHtml =
77
+ item.tags && item.tags.length > 0
78
+ ? `<div class="messenger-changelog-tags">
79
+ ${item.tags.map((tag) => `<span class="messenger-changelog-tag">${tag}</span>`).join('')}
80
+ </div>`
81
+ : '';
82
+
83
+ const dateStr = this._formatDate(item.publishedAt || item.date);
84
+
85
+ return `
86
+ <div class="messenger-changelog-card" data-changelog-id="${item.id}">
87
+ ${
88
+ item.coverImage
89
+ ? `
90
+ <div class="messenger-changelog-cover">
91
+ <img src="${item.coverImage}" alt="${item.title}" onerror="this.style.display='none';" />
92
+ </div>
93
+ `
94
+ : ''
95
+ }
96
+ <div class="messenger-changelog-content">
97
+ ${tagsHtml}
98
+ <h3 class="messenger-changelog-title">${item.title}</h3>
99
+ ${item.description ? `<p class="messenger-changelog-description">${this._truncateText(item.description, 100)}</p>` : ''}
100
+ <div class="messenger-changelog-meta">
101
+ <span class="messenger-changelog-date">${dateStr}</span>
102
+ <i class="ph ph-caret-right messenger-changelog-arrow" style="font-size: 16px;"></i>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ `;
107
+ }
108
+
109
+ _renderTeamAvatars() {
110
+ const avatars = this.state.teamAvatars;
111
+ if (!avatars || avatars.length === 0) {
112
+ return `
113
+ <div class="messenger-avatar-stack messenger-avatar-stack-tiny">
114
+ <div class="messenger-avatar messenger-avatar-tiny" style="background: #5856d6;">S</div>
115
+ </div>
116
+ `;
117
+ }
118
+
119
+ const colors = ['#5856d6', '#007aff', '#34c759'];
120
+ const avatarItems = avatars
121
+ .slice(0, 2)
122
+ .map((avatar, i) => {
123
+ if (typeof avatar === 'string' && avatar.startsWith('http')) {
124
+ return `<img class="messenger-avatar messenger-avatar-tiny" src="${avatar}" alt="Team member" style="z-index: ${2 - i};" />`;
125
+ }
126
+ return `<div class="messenger-avatar messenger-avatar-tiny" style="background: ${colors[i % colors.length]}; z-index: ${2 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
127
+ })
128
+ .join('');
129
+
130
+ return `<div class="messenger-avatar-stack messenger-avatar-stack-tiny">${avatarItems}</div>`;
131
+ }
132
+
133
+ _renderEmptyState() {
134
+ return `
135
+ <div class="messenger-changelog-empty">
136
+ <div class="messenger-changelog-empty-icon">
137
+ <i class="ph ph-megaphone" style="font-size: 48px;"></i>
138
+ </div>
139
+ <h3>No changelog yet</h3>
140
+ <p>Check back later for updates</p>
141
+ </div>
142
+ `;
143
+ }
144
+
145
+ _formatDate(dateString) {
146
+ if (!dateString) return '';
147
+ const date = new Date(dateString);
148
+ return date.toLocaleDateString('en-US', {
149
+ month: 'short',
150
+ day: 'numeric',
151
+ year: 'numeric',
152
+ });
153
+ }
154
+
155
+ _truncateText(text, maxLength) {
156
+ if (!text) return '';
157
+ if (text.length <= maxLength) return text;
158
+ return text.substring(0, maxLength).trim() + '...';
159
+ }
160
+
161
+ _attachEvents() {
162
+ // Close button
163
+ this.element
164
+ .querySelector('.messenger-close-btn')
165
+ .addEventListener('click', () => {
166
+ this.state.setOpen(false);
167
+ });
168
+
169
+ this._attachChangelogEvents();
170
+ }
171
+
172
+ _attachChangelogEvents() {
173
+ this.element
174
+ .querySelectorAll('.messenger-changelog-card')
175
+ .forEach((card) => {
176
+ card.addEventListener('click', () => {
177
+ const changelogId = card.dataset.changelogId;
178
+ const changelogItem = this.state.changelogItems.find(
179
+ (n) => n.id === changelogId
180
+ );
181
+ if (changelogItem && changelogItem.url) {
182
+ window.open(changelogItem.url, '_blank');
183
+ } else if (this.options.onChangelogClick) {
184
+ this.options.onChangelogClick(changelogItem);
185
+ }
186
+ });
187
+ });
188
+ }
189
+
190
+ destroy() {
191
+ if (this._unsubscribe) {
192
+ this._unsubscribe();
193
+ }
194
+ if (this.element && this.element.parentNode) {
195
+ this.element.parentNode.removeChild(this.element);
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * ChatView - Individual conversation chat
3
+ */
4
+ export class ChatView {
5
+ constructor(state, options = {}) {
6
+ this.state = state;
7
+ this.options = options;
8
+ this.element = null;
9
+ this._unsubscribe = null;
10
+ }
11
+
12
+ render() {
13
+ this.element = document.createElement('div');
14
+ this.element.className = 'messenger-view messenger-chat-view';
15
+
16
+ this._updateContent();
17
+
18
+ // Subscribe to state changes
19
+ this._unsubscribe = this.state.subscribe((type, data) => {
20
+ if (
21
+ type === 'messageAdded' &&
22
+ data.conversationId === this.state.activeConversationId
23
+ ) {
24
+ this._appendMessage(data.message);
25
+ this._scrollToBottom();
26
+ }
27
+ });
28
+
29
+ return this.element;
30
+ }
31
+
32
+ _updateContent() {
33
+ const conversation = this.state.getActiveConversation();
34
+ const messages = this.state.getActiveMessages();
35
+
36
+ const messagesHtml =
37
+ messages.length === 0
38
+ ? this._renderEmptyState()
39
+ : messages.map((msg) => this._renderMessage(msg)).join('');
40
+
41
+ const avatarHtml = this._renderConversationAvatar(conversation);
42
+
43
+ this.element.innerHTML = `
44
+ <div class="messenger-chat-header">
45
+ <button class="messenger-back-btn" aria-label="Back">
46
+ <i class="ph ph-caret-left" style="font-size: 20px;"></i>
47
+ </button>
48
+ <div class="messenger-chat-header-info">
49
+ ${avatarHtml}
50
+ <span class="messenger-chat-title">${conversation?.title || 'Chat with team'}</span>
51
+ </div>
52
+ <button class="messenger-close-btn" aria-label="Close">
53
+ <i class="ph ph-x" style="font-size: 20px;"></i>
54
+ </button>
55
+ </div>
56
+
57
+ <div class="messenger-chat-messages">
58
+ ${messagesHtml}
59
+ </div>
60
+
61
+ <div class="messenger-chat-compose">
62
+ <div class="messenger-compose-input-wrapper">
63
+ <textarea class="messenger-compose-input" placeholder="Write a message..." rows="1"></textarea>
64
+ </div>
65
+ <button class="messenger-compose-send" aria-label="Send" disabled>
66
+ <i class="ph ph-paper-plane-tilt" style="font-size: 20px;"></i>
67
+ </button>
68
+ </div>
69
+ `;
70
+
71
+ this._attachEvents();
72
+ this._scrollToBottom();
73
+ }
74
+
75
+ _renderEmptyState() {
76
+ const avatarHtml = this._renderTeamAvatars();
77
+ return `
78
+ <div class="messenger-chat-empty">
79
+ <div class="messenger-chat-empty-avatars">${avatarHtml}</div>
80
+ <h3>Start the conversation</h3>
81
+ <p>Send us a message and we'll get back to you as soon as possible.</p>
82
+ </div>
83
+ `;
84
+ }
85
+
86
+ _renderMessage(message) {
87
+ const isOwn = message.isOwn;
88
+ const messageClass = isOwn
89
+ ? 'messenger-message-own'
90
+ : 'messenger-message-received';
91
+ const timeStr = this._formatMessageTime(message.timestamp);
92
+
93
+ if (isOwn) {
94
+ return `
95
+ <div class="messenger-message ${messageClass}">
96
+ <div class="messenger-message-bubble">
97
+ <div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>
98
+ </div>
99
+ <div class="messenger-message-time">${timeStr}</div>
100
+ </div>
101
+ `;
102
+ }
103
+
104
+ const avatarHtml = this._renderSenderAvatar(message.sender);
105
+ return `
106
+ <div class="messenger-message ${messageClass}">
107
+ <div class="messenger-message-avatar">${avatarHtml}</div>
108
+ <div class="messenger-message-wrapper">
109
+ <div class="messenger-message-sender">${message.sender?.name || 'Support'}</div>
110
+ <div class="messenger-message-bubble">
111
+ <div class="messenger-message-content">${this._formatMessageContent(message.content)}</div>
112
+ </div>
113
+ <div class="messenger-message-time">${timeStr}</div>
114
+ </div>
115
+ </div>
116
+ `;
117
+ }
118
+
119
+ _renderSenderAvatar(sender) {
120
+ if (sender?.avatarUrl) {
121
+ return `<img class="messenger-avatar messenger-avatar-small" src="${sender.avatarUrl}" alt="${sender.name}" />`;
122
+ }
123
+ const initial = (sender?.name || 'S').charAt(0).toUpperCase();
124
+ return `<div class="messenger-avatar messenger-avatar-small" style="background: #5856d6;">${initial}</div>`;
125
+ }
126
+
127
+ _renderConversationAvatar(conversation) {
128
+ if (!conversation?.participants?.length) {
129
+ return `<div class="messenger-avatar messenger-avatar-small" style="background: #5856d6;">S</div>`;
130
+ }
131
+ const p = conversation.participants[0];
132
+ if (p.avatarUrl) {
133
+ return `<img class="messenger-avatar messenger-avatar-small" src="${p.avatarUrl}" alt="${p.name}" />`;
134
+ }
135
+ return `<div class="messenger-avatar messenger-avatar-small" style="background: #5856d6;">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
136
+ }
137
+
138
+ _renderTeamAvatars() {
139
+ const avatars = this.state.teamAvatars;
140
+ if (!avatars || avatars.length === 0) {
141
+ return `
142
+ <div class="messenger-avatar-stack">
143
+ <div class="messenger-avatar" style="background: #5856d6;">S</div>
144
+ <div class="messenger-avatar" style="background: #007aff;">T</div>
145
+ </div>
146
+ `;
147
+ }
148
+
149
+ const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500', '#ff3b30'];
150
+ const avatarItems = avatars
151
+ .slice(0, 3)
152
+ .map((avatar, i) => {
153
+ if (typeof avatar === 'string' && avatar.startsWith('http')) {
154
+ return `<img class="messenger-avatar" src="${avatar}" alt="Team member" style="z-index: ${3 - i};" />`;
155
+ }
156
+ return `<div class="messenger-avatar" style="background: ${colors[i % colors.length]}; z-index: ${3 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
157
+ })
158
+ .join('');
159
+
160
+ return `<div class="messenger-avatar-stack">${avatarItems}</div>`;
161
+ }
162
+
163
+ _formatMessageTime(timestamp) {
164
+ if (!timestamp) return '';
165
+ const date = new Date(timestamp);
166
+ return date.toLocaleTimeString('en-US', {
167
+ hour: 'numeric',
168
+ minute: '2-digit',
169
+ hour12: true,
170
+ });
171
+ }
172
+
173
+ _formatMessageContent(content) {
174
+ if (!content) return '';
175
+ // Basic HTML escaping and line breaks
176
+ return content
177
+ .replace(/&/g, '&amp;')
178
+ .replace(/</g, '&lt;')
179
+ .replace(/>/g, '&gt;')
180
+ .replace(/\n/g, '<br>');
181
+ }
182
+
183
+ _appendMessage(message) {
184
+ const messagesContainer = this.element.querySelector(
185
+ '.messenger-chat-messages'
186
+ );
187
+ const emptyState = messagesContainer.querySelector('.messenger-chat-empty');
188
+ if (emptyState) {
189
+ emptyState.remove();
190
+ }
191
+
192
+ const messageHtml = this._renderMessage(message);
193
+ const tempDiv = document.createElement('div');
194
+ tempDiv.innerHTML = messageHtml;
195
+ messagesContainer.appendChild(tempDiv.firstElementChild);
196
+ }
197
+
198
+ _scrollToBottom() {
199
+ const messagesContainer = this.element.querySelector(
200
+ '.messenger-chat-messages'
201
+ );
202
+ if (messagesContainer) {
203
+ setTimeout(() => {
204
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
205
+ }, 50);
206
+ }
207
+ }
208
+
209
+ _attachEvents() {
210
+ // Back button
211
+ this.element
212
+ .querySelector('.messenger-back-btn')
213
+ .addEventListener('click', () => {
214
+ this.state.setView('messages');
215
+ });
216
+
217
+ // Close button
218
+ this.element
219
+ .querySelector('.messenger-close-btn')
220
+ .addEventListener('click', () => {
221
+ this.state.setOpen(false);
222
+ });
223
+
224
+ // Compose input
225
+ const input = this.element.querySelector('.messenger-compose-input');
226
+ const sendBtn = this.element.querySelector('.messenger-compose-send');
227
+
228
+ input.addEventListener('input', () => {
229
+ // Auto-resize textarea
230
+ input.style.height = 'auto';
231
+ input.style.height = Math.min(input.scrollHeight, 120) + 'px';
232
+
233
+ // Enable/disable send button
234
+ sendBtn.disabled = !input.value.trim();
235
+ });
236
+
237
+ input.addEventListener('keydown', (e) => {
238
+ if (e.key === 'Enter' && !e.shiftKey) {
239
+ e.preventDefault();
240
+ this._sendMessage();
241
+ }
242
+ });
243
+
244
+ sendBtn.addEventListener('click', () => {
245
+ this._sendMessage();
246
+ });
247
+ }
248
+
249
+ _sendMessage() {
250
+ const input = this.element.querySelector('.messenger-compose-input');
251
+ const content = input.value.trim();
252
+
253
+ if (!content) return;
254
+
255
+ // Add message to state
256
+ const message = {
257
+ id: 'msg_' + Date.now(),
258
+ content: content,
259
+ isOwn: true,
260
+ timestamp: new Date().toISOString(),
261
+ };
262
+
263
+ this.state.addMessage(this.state.activeConversationId, message);
264
+
265
+ // Clear input
266
+ input.value = '';
267
+ input.style.height = 'auto';
268
+ this.element.querySelector('.messenger-compose-send').disabled = true;
269
+
270
+ // Emit event for API integration
271
+ if (this.options.onSendMessage) {
272
+ this.options.onSendMessage(this.state.activeConversationId, message);
273
+ }
274
+ }
275
+
276
+ destroy() {
277
+ if (this._unsubscribe) {
278
+ this._unsubscribe();
279
+ }
280
+ if (this.element && this.element.parentNode) {
281
+ this.element.parentNode.removeChild(this.element);
282
+ }
283
+ }
284
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * ConversationsView - Message thread list
3
+ */
4
+ export class ConversationsView {
5
+ constructor(state, options = {}) {
6
+ this.state = state;
7
+ this.options = options;
8
+ this.element = null;
9
+ this._unsubscribe = null;
10
+ }
11
+
12
+ render() {
13
+ this.element = document.createElement('div');
14
+ this.element.className = 'messenger-view messenger-conversations-view';
15
+
16
+ this._updateContent();
17
+ this._attachEvents();
18
+
19
+ // Subscribe to state changes
20
+ this._unsubscribe = this.state.subscribe((type) => {
21
+ if (
22
+ type === 'conversationsUpdate' ||
23
+ type === 'conversationAdded' ||
24
+ type === 'conversationRead'
25
+ ) {
26
+ this._updateContent();
27
+ }
28
+ });
29
+
30
+ return this.element;
31
+ }
32
+
33
+ _updateContent() {
34
+ const conversations = this.state.conversations;
35
+ const avatarsHtml = this._renderAvatarStack();
36
+
37
+ let conversationsHtml;
38
+ if (conversations.length === 0) {
39
+ conversationsHtml = `
40
+ <div class="messenger-conversations-empty">
41
+ <div class="messenger-conversations-empty-icon">
42
+ <i class="ph ph-chat" style="font-size: 48px;"></i>
43
+ </div>
44
+ <h3>No conversations yet</h3>
45
+ <p>Start a new conversation with our team</p>
46
+ </div>
47
+ `;
48
+ } else {
49
+ conversationsHtml = `
50
+ <div class="messenger-conversations-list">
51
+ ${conversations.map((conv) => this._renderConversationItem(conv)).join('')}
52
+ </div>
53
+ `;
54
+ }
55
+
56
+ this.element.innerHTML = `
57
+ <div class="messenger-conversations-header">
58
+ <h2>Messages</h2>
59
+ <button class="messenger-close-btn" aria-label="Close">
60
+ <i class="ph ph-x" style="font-size: 20px;"></i>
61
+ </button>
62
+ </div>
63
+
64
+ <div class="messenger-conversations-body">
65
+ ${conversationsHtml}
66
+ </div>
67
+
68
+ <div class="messenger-conversations-footer">
69
+ <button class="messenger-new-message-btn">
70
+ <div class="messenger-new-message-avatars">${avatarsHtml}</div>
71
+ <span>Send us a message</span>
72
+ <i class="ph ph-arrow-right" style="font-size: 16px;"></i>
73
+ </button>
74
+ </div>
75
+ `;
76
+
77
+ this._attachEvents();
78
+ }
79
+
80
+ _renderConversationItem(conversation) {
81
+ const unreadClass = conversation.unread > 0 ? 'unread' : '';
82
+ const timeAgo = this._formatTimeAgo(conversation.lastMessageTime);
83
+ const avatarsHtml = this._renderConversationAvatars(
84
+ conversation.participants
85
+ );
86
+
87
+ return `
88
+ <div class="messenger-conversation-item ${unreadClass}" data-conversation-id="${conversation.id}">
89
+ <div class="messenger-conversation-avatars">
90
+ ${avatarsHtml}
91
+ </div>
92
+ <div class="messenger-conversation-content">
93
+ <div class="messenger-conversation-header">
94
+ <span class="messenger-conversation-title">${conversation.title || 'Chat with team'}</span>
95
+ <span class="messenger-conversation-time">${timeAgo}</span>
96
+ </div>
97
+ <div class="messenger-conversation-preview">
98
+ ${conversation.unread > 0 ? '<span class="messenger-unread-dot"></span>' : ''}
99
+ <span class="messenger-conversation-message">${this._truncateMessage(conversation.lastMessage)}</span>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ `;
104
+ }
105
+
106
+ _renderConversationAvatars(participants) {
107
+ if (!participants || participants.length === 0) {
108
+ return `<div class="messenger-avatar messenger-avatar-medium" style="background: #5856d6;">S</div>`;
109
+ }
110
+
111
+ const p = participants[0];
112
+ if (p.avatarUrl) {
113
+ return `<img class="messenger-avatar messenger-avatar-medium" src="${p.avatarUrl}" alt="${p.name}" />`;
114
+ }
115
+ return `<div class="messenger-avatar messenger-avatar-medium" style="background: ${this._getAvatarColor(0)};">${(p.name || 'S').charAt(0).toUpperCase()}</div>`;
116
+ }
117
+
118
+ _renderAvatarStack() {
119
+ const avatars = this.state.teamAvatars;
120
+ if (!avatars || avatars.length === 0) {
121
+ return `
122
+ <div class="messenger-avatar-stack messenger-avatar-stack-small">
123
+ <div class="messenger-avatar messenger-avatar-small" style="background: #5856d6;">S</div>
124
+ <div class="messenger-avatar messenger-avatar-small" style="background: #007aff;">T</div>
125
+ </div>
126
+ `;
127
+ }
128
+
129
+ const avatarItems = avatars
130
+ .slice(0, 2)
131
+ .map((avatar, i) => {
132
+ if (typeof avatar === 'string' && avatar.startsWith('http')) {
133
+ return `<img class="messenger-avatar messenger-avatar-small" src="${avatar}" alt="Team member" style="z-index: ${2 - i};" />`;
134
+ }
135
+ return `<div class="messenger-avatar messenger-avatar-small" style="background: ${this._getAvatarColor(i)}; z-index: ${2 - i};">${avatar.charAt(0).toUpperCase()}</div>`;
136
+ })
137
+ .join('');
138
+
139
+ return `<div class="messenger-avatar-stack messenger-avatar-stack-small">${avatarItems}</div>`;
140
+ }
141
+
142
+ _getAvatarColor(index) {
143
+ const colors = ['#5856d6', '#007aff', '#34c759', '#ff9500', '#ff3b30'];
144
+ return colors[index % colors.length];
145
+ }
146
+
147
+ _formatTimeAgo(timestamp) {
148
+ if (!timestamp) return '';
149
+ const date = new Date(timestamp);
150
+ const now = new Date();
151
+ const diffMs = now - date;
152
+ const diffMins = Math.floor(diffMs / 60000);
153
+ const diffHours = Math.floor(diffMs / 3600000);
154
+ const diffDays = Math.floor(diffMs / 86400000);
155
+
156
+ if (diffMins < 1) return 'now';
157
+ if (diffMins < 60) return `${diffMins}m`;
158
+ if (diffHours < 24) return `${diffHours}h`;
159
+ if (diffDays < 7) return `${diffDays}d`;
160
+ return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
161
+ }
162
+
163
+ _truncateMessage(message) {
164
+ if (!message) return 'No messages yet';
165
+ const maxLength = 50;
166
+ if (message.length <= maxLength) return message;
167
+ return message.substring(0, maxLength) + '...';
168
+ }
169
+
170
+ _attachEvents() {
171
+ // Close button
172
+ const closeBtn = this.element.querySelector('.messenger-close-btn');
173
+ if (closeBtn) {
174
+ closeBtn.addEventListener('click', () => {
175
+ this.state.setOpen(false);
176
+ });
177
+ }
178
+
179
+ // Conversation items
180
+ this.element
181
+ .querySelectorAll('.messenger-conversation-item')
182
+ .forEach((item) => {
183
+ item.addEventListener('click', () => {
184
+ const convId = item.dataset.conversationId;
185
+ this.state.setActiveConversation(convId);
186
+ this.state.markAsRead(convId);
187
+ this.state.setView('chat');
188
+ });
189
+ });
190
+
191
+ // New message button
192
+ const newMsgBtn = this.element.querySelector('.messenger-new-message-btn');
193
+ if (newMsgBtn) {
194
+ newMsgBtn.addEventListener('click', () => {
195
+ this._startNewConversation();
196
+ });
197
+ }
198
+ }
199
+
200
+ _startNewConversation() {
201
+ // Create a new conversation and navigate to chat
202
+ const newConv = {
203
+ id: 'conv_' + Date.now(),
204
+ title: 'New conversation',
205
+ participants: [],
206
+ lastMessage: null,
207
+ lastMessageTime: new Date().toISOString(),
208
+ unread: 0,
209
+ };
210
+ this.state.addConversation(newConv);
211
+ this.state.setActiveConversation(newConv.id);
212
+ this.state.setView('chat');
213
+ }
214
+
215
+ destroy() {
216
+ if (this._unsubscribe) {
217
+ this._unsubscribe();
218
+ }
219
+ if (this.element && this.element.parentNode) {
220
+ this.element.parentNode.removeChild(this.element);
221
+ }
222
+ }
223
+ }