PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.49
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.49
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Live_Chat / assets / admin.js

admin.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.49, at includes/extensions/Live_Chat/assets/admin.js

597 lines 21.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Live Chat Admin JavaScript
3 *
4 * Handles inbox functionality and conversation management.
5 *
6 * @package King_Addons
7 */
8
9 (function($) {
10 'use strict';
11
12 /**
13 * Live Chat Admin Module
14 */
15 const KingLiveChatAdmin = {
16 /**
17 * Current state
18 */
19 state: {
20 currentFilter: 'all',
21 currentSearch: '',
22 currentConversation: null,
23 conversations: [],
24 refreshInterval: null,
25 lastMessageId: 0
26 },
27
28 /**
29 * DOM elements cache
30 */
31 elements: {},
32
33 /**
34 * Initialize the module
35 */
36 init: function() {
37 this.cacheElements();
38 this.bindEvents();
39 this.loadConversations();
40 this.initColorPickers();
41 this.startAutoRefresh();
42 this.checkUrlParams();
43 },
44
45 /**
46 * Cache DOM elements
47 */
48 cacheElements: function() {
49 this.elements = {
50 conversationsList: $('#ka-inbox-conversations'),
51 conversationView: $('#ka-conversation-view'),
52 filters: $('.ka-inbox-filter'),
53 searchInput: $('.ka-inbox-search')
54 };
55 },
56
57 /**
58 * Bind event handlers
59 */
60 bindEvents: function() {
61 const self = this;
62
63 // Filter clicks
64 this.elements.filters.on('click', function() {
65 self.elements.filters.removeClass('active');
66 $(this).addClass('active');
67 self.state.currentFilter = $(this).data('status');
68 self.loadConversations();
69 });
70
71 // Search input
72 let searchTimeout;
73 this.elements.searchInput.on('input', function() {
74 clearTimeout(searchTimeout);
75 searchTimeout = setTimeout(function() {
76 self.state.currentSearch = self.elements.searchInput.val();
77 self.loadConversations();
78 }, 300);
79 });
80
81 // Conversation item click (delegated)
82 this.elements.conversationsList.on('click', '.ka-inbox-item', function() {
83 const id = $(this).data('id');
84 self.loadConversation(id);
85 });
86
87 // Reply form submit (delegated)
88 $(document).on('submit', '.ka-reply-form', function(e) {
89 e.preventDefault();
90 self.sendReply();
91 });
92
93 // Status toggle (delegated)
94 $(document).on('click', '.ka-toggle-status', function() {
95 const newStatus = $(this).data('status');
96 self.updateStatus(newStatus);
97 });
98
99 // Delete conversation (delegated)
100 $(document).on('click', '.ka-delete-conversation', function() {
101 if (confirm(kingLiveChatAdmin.strings.confirmDelete)) {
102 self.deleteConversation();
103 }
104 });
105
106 // Textarea enter to send
107 $(document).on('keydown', '.ka-reply-textarea', function(e) {
108 if (e.key === 'Enter' && !e.shiftKey) {
109 e.preventDefault();
110 self.sendReply();
111 }
112 });
113 },
114
115 /**
116 * Check URL parameters for direct conversation link
117 */
118 checkUrlParams: function() {
119 const urlParams = new URLSearchParams(window.location.search);
120 const conversationId = urlParams.get('conversation');
121 if (conversationId) {
122 this.loadConversation(parseInt(conversationId, 10));
123 }
124 },
125
126 /**
127 * Initialize color pickers
128 */
129 initColorPickers: function() {
130 if ($.fn.wpColorPicker) {
131 $('.ka-color-picker').wpColorPicker();
132 }
133 },
134
135 /**
136 * Start auto-refresh for inbox
137 */
138 startAutoRefresh: function() {
139 const self = this;
140 this.state.refreshInterval = setInterval(function() {
141 self.loadConversations(true);
142 if (self.state.currentConversation) {
143 self.pollNewMessages();
144 }
145 }, 10000);
146 },
147
148 /**
149 * Load conversations list
150 *
151 * @param {boolean} silent Don't show loading state
152 */
153 loadConversations: function(silent) {
154 const self = this;
155
156 if (!silent) {
157 this.elements.conversationsList.html('<div class="ka-loading"><div class="ka-loading-spinner"></div></div>');
158 }
159
160 $.ajax({
161 url: kingLiveChatAdmin.ajaxUrl,
162 type: 'POST',
163 data: {
164 action: 'king_live_chat_get_conversations',
165 nonce: kingLiveChatAdmin.nonce,
166 status: this.state.currentFilter,
167 search: this.state.currentSearch
168 },
169 success: function(response) {
170 if (response.success) {
171 self.state.conversations = response.data.conversations;
172 self.renderConversationsList();
173 }
174 },
175 error: function() {
176 self.elements.conversationsList.html(
177 '<div class="ka-inbox-empty">' + kingLiveChatAdmin.strings.error + '</div>'
178 );
179 }
180 });
181 },
182
183 /**
184 * Render conversations list
185 */
186 renderConversationsList: function() {
187 const self = this;
188 const conversations = this.state.conversations;
189
190 if (!conversations.length) {
191 this.elements.conversationsList.html(
192 '<div class="ka-inbox-empty">No conversations found</div>'
193 );
194 return;
195 }
196
197 let html = '';
198 conversations.forEach(function(conv) {
199 const initials = self.getInitials(conv.name);
200 const isActive = self.state.currentConversation === conv.id;
201 const isUnread = conv.unread > 0;
202 const timeAgo = self.formatTimeAgo(conv.last_message);
203
204 html += `
205 <div class="ka-inbox-item ${isActive ? 'active' : ''} ${isUnread ? 'unread' : ''}" data-id="${conv.id}">
206 <div class="ka-inbox-avatar">${initials}</div>
207 <div class="ka-inbox-item-content">
208 <div class="ka-inbox-item-header">
209 <span class="ka-inbox-item-name">
210 ${self.escapeHtml(conv.name)}
211 ${isUnread ? '<span class="ka-inbox-badge">' + conv.unread + '</span>' : ''}
212 </span>
213 <span class="ka-inbox-item-time">${timeAgo}</span>
214 </div>
215 <div class="ka-inbox-item-preview">${self.escapeHtml(conv.email || 'No email')}</div>
216 </div>
217 </div>
218 `;
219 });
220
221 this.elements.conversationsList.html(html);
222 },
223
224 /**
225 * Load single conversation
226 *
227 * @param {number} id Conversation ID
228 */
229 loadConversation: function(id) {
230 const self = this;
231 this.state.currentConversation = id;
232
233 // Update active state in list
234 this.elements.conversationsList.find('.ka-inbox-item').removeClass('active');
235 this.elements.conversationsList.find('[data-id="' + id + '"]').addClass('active').removeClass('unread');
236
237 this.elements.conversationView.html('<div class="ka-loading"><div class="ka-loading-spinner"></div></div>');
238
239 $.ajax({
240 url: kingLiveChatAdmin.ajaxUrl,
241 type: 'POST',
242 data: {
243 action: 'king_live_chat_get_conversation',
244 nonce: kingLiveChatAdmin.nonce,
245 conversation_id: id
246 },
247 success: function(response) {
248 if (response.success) {
249 self.renderConversation(response.data);
250 // Update last message ID for polling
251 const messages = response.data.messages;
252 if (messages.length) {
253 self.state.lastMessageId = messages[messages.length - 1].id;
254 }
255 } else {
256 self.elements.conversationView.html(
257 '<div class="ka-conversation-empty"><p>' + kingLiveChatAdmin.strings.error + '</p></div>'
258 );
259 }
260 }
261 });
262 },
263
264 /**
265 * Render conversation view
266 *
267 * @param {Object} data Conversation data
268 */
269 renderConversation: function(data) {
270 const self = this;
271 const conv = data.conversation;
272 const messages = data.messages;
273 const initials = this.getInitials(conv.name || 'Anonymous');
274 const isOpen = conv.status === 'open';
275
276 let messagesHtml = '';
277 messages.forEach(function(msg) {
278 const isAdmin = msg.type === 'admin';
279 const time = self.formatTime(msg.time);
280 messagesHtml += `
281 <div class="ka-message ka-message--${msg.type}">
282 <div class="ka-message-text">${self.escapeHtml(msg.text)}</div>
283 <div class="ka-message-meta">
284 ${isAdmin && msg.admin_name ? msg.admin_name + ' · ' : ''}${time}
285 </div>
286 </div>
287 `;
288 });
289
290 if (!messages.length) {
291 messagesHtml = '<div class="ka-inbox-empty">' + kingLiveChatAdmin.strings.noMessages + '</div>';
292 }
293
294 const html = `
295 <div class="ka-conversation-header">
296 <div class="ka-conversation-info">
297 <div class="ka-inbox-avatar">${initials}</div>
298 <div class="ka-conversation-details">
299 <h3>${self.escapeHtml(conv.name || 'Anonymous')}</h3>
300 <div class="ka-conversation-email">${self.escapeHtml(conv.email || 'No email')}</div>
301 </div>
302 </div>
303 <div class="ka-conversation-actions">
304 <button type="button" class="ka-conversation-btn ka-toggle-status" data-status="${isOpen ? 'closed' : 'open'}">
305 <span class="dashicons dashicons-${isOpen ? 'no' : 'yes'}"></span>
306 ${isOpen ? 'Close' : 'Reopen'}
307 </button>
308 <button type="button" class="ka-conversation-btn ka-conversation-btn--danger ka-delete-conversation">
309 <span class="dashicons dashicons-trash"></span>
310 Delete
311 </button>
312 </div>
313 </div>
314 <div class="ka-conversation-messages">
315 ${messagesHtml}
316 </div>
317 <div class="ka-conversation-reply">
318 <form class="ka-reply-form">
319 <textarea class="ka-reply-textarea" placeholder="Type your reply..." rows="3"></textarea>
320 <button type="submit" class="ka-reply-send">${kingLiveChatAdmin.strings.send}</button>
321 </form>
322 </div>
323 <div class="ka-visitor-info">
324 <h4>Visitor Info</h4>
325 <div class="ka-visitor-info-item">
326 <span class="ka-visitor-info-label">Status:</span>
327 <span class="ka-visitor-info-value ka-status-${conv.status}">${conv.status}</span>
328 </div>
329 ${conv.page_url ? `
330 <div class="ka-visitor-info-item">
331 <span class="ka-visitor-info-label">Page:</span>
332 <span class="ka-visitor-info-value"><a href="${conv.page_url}" target="_blank">${self.truncateUrl(conv.page_url)}</a></span>
333 </div>
334 ` : ''}
335 ${conv.referrer ? `
336 <div class="ka-visitor-info-item">
337 <span class="ka-visitor-info-label">Referrer:</span>
338 <span class="ka-visitor-info-value">${self.truncateUrl(conv.referrer)}</span>
339 </div>
340 ` : ''}
341 <div class="ka-visitor-info-item">
342 <span class="ka-visitor-info-label">Started:</span>
343 <span class="ka-visitor-info-value">${self.formatTime(conv.created)}</span>
344 </div>
345 </div>
346 `;
347
348 this.elements.conversationView.html(html);
349
350 // Scroll to bottom
351 const messagesEl = this.elements.conversationView.find('.ka-conversation-messages');
352 messagesEl.scrollTop(messagesEl[0].scrollHeight);
353 },
354
355 /**
356 * Send admin reply
357 */
358 sendReply: function() {
359 const self = this;
360 const textarea = this.elements.conversationView.find('.ka-reply-textarea');
361 const sendBtn = this.elements.conversationView.find('.ka-reply-send');
362 const message = textarea.val().trim();
363
364 if (!message || !this.state.currentConversation) {
365 return;
366 }
367
368 sendBtn.prop('disabled', true).text(kingLiveChatAdmin.strings.sending);
369
370 $.ajax({
371 url: kingLiveChatAdmin.ajaxUrl,
372 type: 'POST',
373 data: {
374 action: 'king_live_chat_send_reply',
375 nonce: kingLiveChatAdmin.nonce,
376 conversation_id: this.state.currentConversation,
377 message: message
378 },
379 success: function(response) {
380 if (response.success) {
381 // Add message to view
382 const msg = response.data.message;
383 const messagesEl = self.elements.conversationView.find('.ka-conversation-messages');
384
385 const msgHtml = `
386 <div class="ka-message ka-message--admin">
387 <div class="ka-message-text">${self.escapeHtml(msg.text)}</div>
388 <div class="ka-message-meta">
389 ${msg.admin_name ? msg.admin_name + ' · ' : ''}${self.formatTime(msg.time)}
390 </div>
391 </div>
392 `;
393
394 messagesEl.find('.ka-inbox-empty').remove();
395 messagesEl.append(msgHtml);
396 messagesEl.scrollTop(messagesEl[0].scrollHeight);
397
398 textarea.val('');
399 self.state.lastMessageId = msg.id;
400 } else {
401 alert(kingLiveChatAdmin.strings.error);
402 }
403 },
404 error: function() {
405 alert(kingLiveChatAdmin.strings.error);
406 },
407 complete: function() {
408 sendBtn.prop('disabled', false).text(kingLiveChatAdmin.strings.send);
409 }
410 });
411 },
412
413 /**
414 * Poll for new messages in current conversation
415 */
416 pollNewMessages: function() {
417 const self = this;
418
419 if (!this.state.currentConversation) {
420 return;
421 }
422
423 $.ajax({
424 url: kingLiveChatAdmin.ajaxUrl,
425 type: 'POST',
426 data: {
427 action: 'king_live_chat_get_conversation',
428 nonce: kingLiveChatAdmin.nonce,
429 conversation_id: this.state.currentConversation
430 },
431 success: function(response) {
432 if (response.success) {
433 const messages = response.data.messages;
434 if (messages.length) {
435 const lastMsg = messages[messages.length - 1];
436 if (lastMsg.id > self.state.lastMessageId) {
437 // New messages, reload view
438 self.renderConversation(response.data);
439 self.state.lastMessageId = lastMsg.id;
440 }
441 }
442 }
443 }
444 });
445 },
446
447 /**
448 * Update conversation status
449 *
450 * @param {string} status New status
451 */
452 updateStatus: function(status) {
453 const self = this;
454
455 $.ajax({
456 url: kingLiveChatAdmin.ajaxUrl,
457 type: 'POST',
458 data: {
459 action: 'king_live_chat_update_status',
460 nonce: kingLiveChatAdmin.nonce,
461 conversation_id: this.state.currentConversation,
462 status: status
463 },
464 success: function(response) {
465 if (response.success) {
466 self.loadConversation(self.state.currentConversation);
467 self.loadConversations(true);
468 }
469 }
470 });
471 },
472
473 /**
474 * Delete conversation
475 */
476 deleteConversation: function() {
477 const self = this;
478
479 $.ajax({
480 url: kingLiveChatAdmin.ajaxUrl,
481 type: 'POST',
482 data: {
483 action: 'king_live_chat_delete_conversation',
484 nonce: kingLiveChatAdmin.nonce,
485 conversation_id: this.state.currentConversation
486 },
487 success: function(response) {
488 if (response.success) {
489 self.state.currentConversation = null;
490 self.elements.conversationView.html(`
491 <div class="ka-conversation-empty">
492 <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
493 <path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
494 </svg>
495 <p>Select a conversation to view messages</p>
496 </div>
497 `);
498 self.loadConversations();
499 }
500 }
501 });
502 },
503
504 /**
505 * Get initials from name
506 *
507 * @param {string} name Full name
508 * @returns {string}
509 */
510 getInitials: function(name) {
511 if (!name) return '?';
512 const parts = name.trim().split(' ');
513 if (parts.length >= 2) {
514 return (parts[0][0] + parts[1][0]).toUpperCase();
515 }
516 return name.substring(0, 2).toUpperCase();
517 },
518
519 /**
520 * Format time for display
521 *
522 * @param {string} dateStr Date string
523 * @returns {string}
524 */
525 formatTime: function(dateStr) {
526 if (!dateStr) return '';
527 const date = new Date(dateStr.replace(' ', 'T'));
528 return date.toLocaleString();
529 },
530
531 /**
532 * Format time ago
533 *
534 * @param {string} dateStr Date string
535 * @returns {string}
536 */
537 formatTimeAgo: function(dateStr) {
538 if (!dateStr) return '';
539 const date = new Date(dateStr.replace(' ', 'T'));
540 const now = new Date();
541 const diff = Math.floor((now - date) / 1000);
542
543 if (diff < 60) return 'Just now';
544 if (diff < 3600) return Math.floor(diff / 60) + 'm';
545 if (diff < 86400) return Math.floor(diff / 3600) + 'h';
546 if (diff < 604800) return Math.floor(diff / 86400) + 'd';
547 return date.toLocaleDateString();
548 },
549
550 /**
551 * Truncate URL for display
552 *
553 * @param {string} url Full URL
554 * @returns {string}
555 */
556 truncateUrl: function(url) {
557 if (!url) return '';
558 try {
559 const parsed = new URL(url);
560 let path = parsed.pathname;
561 if (path.length > 30) {
562 path = path.substring(0, 30) + '...';
563 }
564 return parsed.host + path;
565 } catch (e) {
566 return url.substring(0, 40) + (url.length > 40 ? '...' : '');
567 }
568 },
569
570 /**
571 * Escape HTML entities
572 *
573 * @param {string} str Input string
574 * @returns {string}
575 */
576 escapeHtml: function(str) {
577 if (!str) return '';
578 const div = document.createElement('div');
579 div.textContent = str;
580 return div.innerHTML;
581 }
582 };
583
584 // Initialize on DOM ready
585 $(function() {
586 if ($('#ka-inbox-conversations').length) {
587 KingLiveChatAdmin.init();
588 } else {
589 // Settings page - just init color pickers
590 if ($.fn.wpColorPicker) {
591 $('.ka-color-picker').wpColorPicker();
592 }
593 }
594 });
595
596 })(jQuery);
597