PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.2
MxChat – AI Chatbot & Content Generation for WordPress v3.0.2
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
← All changes | js/chat-script.js +500 -808 3.2.33.0.2 View file →
@@ -1,23 +1,6 @@
1 1 jQuery(document).ready(function($) {
2 2
3 - // Nonce refresh is deferred until first user interaction (ensureSession)
4 - // to avoid admin-ajax calls on passive page loads.
5 - var nonceRefreshed = false;
6 - function refreshNonceIfNeeded(callback) {
7 - if (nonceRefreshed || typeof mxchatChat === 'undefined' || !mxchatChat.ajax_url) {
8 - if (callback) callback();
9 - return;
10 - }
11 - nonceRefreshed = true;
12 - $.post(mxchatChat.ajax_url, { action: 'mxchat_refresh_nonce' }, function(res) {
13 - if (res && res.success && res.data && res.data.nonce) {
14 - mxchatChat.nonce = res.data.nonce;
15 - }
16 - if (callback) callback();
17 - });
18 - }
19 -
20 3 // ====================================
21 4 // MULTI-INSTANCE MANAGEMENT SYSTEM
22 5 // ====================================
23 6
@@ -27,15 +10,11 @@
27 10
28 11 // Initialize an instance for a bot
29 12 init: function(botId) {
30 13 if (!this.instances[botId]) {
31 - // When persistence is OFF, track when this session started
32 - // so the AI only sees messages from this page load
33 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
34 -
35 14 this.instances[botId] = {
36 15 botId: botId,
37 - sessionId: null,
16 + sessionId: this.getChatSession(botId),
38 17 lastSeenMessageId: '',
39 18 notificationCheckInterval: null,
40 19 pollingInterval: null,
41 20 processedMessageIds: new Set(),
@@ -41,11 +20,9 @@
41 20 processedMessageIds: new Set(),
42 21 activePdfFile: null,
43 22 activeWordFile: null,
44 23 chatHistoryLoaded: false,
45 - isStreaming: false,
46 - // Fresh context timestamp - only used when persistence is OFF
47 - sessionStartTimestamp: chatPersistenceEnabled ? 0 : Date.now()
24 + isStreaming: false
48 25 };
49 26 }
50 27 return this.instances[botId];
51 28 },
@@ -60,77 +37,23 @@
60 37 return Object.keys(this.instances);
61 38 },
62 39
63 40 // Session management per bot
64 - // Returns existing session ID from cookie or localStorage (with in-memory fallback),
65 - // or null if none exists. Does NOT create a new session — use ensureSession() for that.
66 41 getChatSession: function(botId) {
67 42 var cookieName = 'mxchat_session_id_' + botId;
68 - var storageKey = 'mxchat_session_id_' + botId;
69 43 var sessionId = getCookie(cookieName);
70 44
71 - // Fallback to localStorage if cookie is missing (e.g. cleared by browser/consent)
72 45 if (!sessionId) {
73 - try { sessionId = localStorage.getItem(storageKey); } catch (e) {}
46 + sessionId = generateSessionId();
47 + this.setChatSession(botId, sessionId);
74 48 }
75 49
76 - // Fallback to in-memory instance when cookie AND localStorage are both blocked
77 - // (Safari ITP, strict tracking prevention, cross-origin iframes with partitioned
78 - // storage). Without this, ensureSession() can generate and store an ID that
79 - // getChatSession() then can't read back, causing null session_ids on send.
80 - if (!sessionId && this.instances[botId] && this.instances[botId].sessionId) {
81 - sessionId = this.instances[botId].sessionId;
82 - }
83 -
84 - // Guard against stored sentinel values that indicate earlier broken writes.
85 - if (sessionId === 'null' || sessionId === 'undefined') {
86 - sessionId = null;
87 - }
88 -
89 - // Re-sync cookie from localStorage if cookie was lost
90 - if (sessionId && !getCookie(cookieName)) {
91 - document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
92 - }
93 -
94 - return sessionId || null;
50 + return sessionId;
95 51 },
96 52
97 - // Lazy session initializer — called on first user interaction
98 - ensureSession: function(botId) {
99 - botId = botId || 'default';
100 - var instance = this.instances[botId] || this.init(botId);
101 -
102 - if (instance.sessionId) {
103 - return instance.sessionId;
104 - }
105 -
106 - // Check for existing session from cookie or localStorage
107 - var existingSession = this.getChatSession(botId);
108 -
109 - if (existingSession) {
110 - instance.sessionId = existingSession;
111 - } else {
112 - // Brand new session
113 - var newId = generateSessionId();
114 - this.setChatSession(botId, newId);
115 - instance.sessionId = newId;
116 - }
117 -
118 - // Now that we have a session, do the deferred work
119 - refreshNonceIfNeeded();
120 - trackOriginatingPage();
121 -
122 - // Note: loadChatHistory is handled by showChatContainerForBot with loader UI,
123 - // so we do NOT call it here to avoid a race condition.
124 -
125 - return instance.sessionId;
126 - },
127 -
128 53 setChatSession: function(botId, sessionId) {
129 54 var cookieName = 'mxchat_session_id_' + botId;
130 - var storageKey = 'mxchat_session_id_' + botId;
131 55 document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
132 - try { localStorage.setItem(storageKey, sessionId); } catch (e) {}
133 56 if (this.instances[botId]) {
134 57 this.instances[botId].sessionId = sessionId;
135 58 }
136 59 },
@@ -135,10 +58,8 @@
135 58 }
136 59 },
137 60
138 61 resetChatSession: function(botId) {
139 - // Clear old session from localStorage before setting new one
140 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
141 62 var newSessionId = generateSessionId();
142 63 this.setChatSession(botId, newSessionId);
143 64 var $chatBox = getElement(botId, 'chat-box');
144 65 if ($chatBox.length) {
@@ -147,20 +68,8 @@
147 68 if (this.instances[botId]) {
148 69 this.instances[botId].chatHistoryLoaded = false;
149 70 this.instances[botId].processedMessageIds = new Set();
150 71 }
151 - },
152 -
153 - // Silent reset — new session ID without clearing the chat UI
154 - // Used when IP changes mid-conversation so the user doesn't see messages vanish
155 - silentResetSession: function(botId) {
156 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
157 - var newSessionId = generateSessionId();
158 - this.setChatSession(botId, newSessionId);
159 - if (this.instances[botId]) {
160 - this.instances[botId].sessionId = newSessionId;
161 - }
162 - return newSessionId;
163 72 }
164 73 };
165 74
166 75 // ====================================
@@ -465,9 +374,8 @@
465 374
466 375 // Update your existing sendMessage function
467 376 function sendMessage(botId) {
468 377 botId = botId || 'default';
469 - MxChatInstances.ensureSession(botId);
470 378 var $chatInput = getElement(botId, 'chat-input');
471 379 var message = $chatInput.val();
472 380
473 381 // ADD PROMPT HOOK HERE
@@ -475,14 +383,10 @@
475 383 message = customMxChatFilter(message, "prompt");
476 384 }
477 385
478 386 if (message) {
479 - // Don't disable input in live agent mode - let users chat freely
480 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
481 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
482 - if (!isAgentMode) {
483 - disableChatInput(botId);
484 - }
387 + // Disable input while waiting for response
388 + disableChatInput(botId);
485 389
486 390 appendMessage("user", message, '', [], false, botId);
487 391 $chatInput.val('');
488 392 $chatInput.css('height', 'auto');
@@ -492,9 +396,9 @@
492 396 }
493 397 appendThinkingMessage(botId);
494 398 scrollToBottom(botId);
495 399
496 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
400 + const currentModel = mxchatChat.model || 'gpt-4o';
497 401
498 402 // Check if streaming is enabled AND supported for this model
499 403 if (shouldUseStreaming(currentModel)) {
500 404 callMxChatStream(message, function(response) {
@@ -510,9 +414,8 @@
510 414
511 415 // Update your existing sendMessageToChatbot function
512 416 function sendMessageToChatbot(message, botId) {
513 417 botId = botId || 'default';
514 - MxChatInstances.ensureSession(botId);
515 418
516 419 // ADD PROMPT HOOK HERE
517 420 if (typeof customMxChatFilter === 'function') {
518 421 message = customMxChatFilter(message, "prompt");
@@ -517,14 +420,10 @@
517 420 if (typeof customMxChatFilter === 'function') {
518 421 message = customMxChatFilter(message, "prompt");
519 422 }
520 423
521 - // Don't disable input in live agent mode - let users chat freely
522 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
523 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
524 - if (!isAgentMode) {
525 - disableChatInput(botId);
526 - }
424 + // Disable input while waiting for response
425 + disableChatInput(botId);
527 426
528 427 var sessionId = getChatSession(botId);
529 428
530 429 if (hasQuickQuestions(botId)) {
@@ -532,9 +431,9 @@
532 431 }
533 432 appendThinkingMessage(botId);
534 433 scrollToBottom(botId);
535 434
536 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
435 + const currentModel = mxchatChat.model || 'gpt-4o';
537 436
538 437 // Check if streaming is enabled AND supported for this model
539 438 if (shouldUseStreaming(currentModel)) {
540 439 callMxChatStream(message, function(response) {
@@ -609,32 +508,17 @@
609 508
610 509 // Get page context if contextual awareness is enabled
611 510 const pageContext = getPageContext();
612 511
613 - // Get instance for session start timestamp (used when persistence is OFF)
614 - var instance = MxChatInstances.get(botId);
615 -
616 - // Guarantee a non-null session_id before the AJAX leaves. ensureSession() is idempotent
617 - // and returns the guaranteed-present session id from the in-memory instance even when
618 - // cookie/localStorage writes are silently blocked by the browser.
619 - var sessionId = MxChatInstances.ensureSession(botId);
620 - if (!sessionId || sessionId === 'null' || sessionId === 'undefined') {
621 - // Last-resort generation to ensure we never POST a null marker.
622 - sessionId = generateSessionId();
623 - MxChatInstances.setChatSession(botId, sessionId);
624 - }
625 -
626 512 // Prepare AJAX data
627 513 const ajaxData = {
628 514 action: 'mxchat_handle_chat_request',
629 515 message: message,
630 - session_id: sessionId,
516 + session_id: getChatSession(botId),
631 517 nonce: mxchatChat.nonce,
632 518 current_page_url: window.location.href,
633 519 current_page_title: document.title,
634 - bot_id: botId,
635 - // Pass session start timestamp so AI context matches what user sees
636 - session_start_timestamp: instance.sessionStartTimestamp || 0
520 + bot_id: botId
637 521 };
638 522
639 523 // Add page context if available
640 524 if (pageContext) {
@@ -690,16 +574,23 @@
690 574 errorMessage = "An error occurred. Please try again or contact support.";
691 575 }
692 576
693 577 // Handle session reset action (IP changed, session expired, etc.)
694 - // Silent reset — keep chat UI intact, just get a new session and retry
695 578 if (response.data && response.data.action === 'reset_session') {
696 - MxChatInstances.silentResetSession(botId);
697 - // Re-send the original message with the new session (user message is already displayed)
579 + // Clear the old session and generate a new one
580 + resetChatSession(botId);
581 + // Remove the temporary loading message
582 + getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
583 + // Re-send the original message with the new session
698 584 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
699 585 if (originalMessage) {
700 586 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
701 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
587 + // Re-add the user message and thinking indicator
588 + appendMessage("user", originalMessage, '', [], false, botId);
589 + appendThinkingMessage(botId);
590 + scrollToBottom(botId);
591 + // Determine whether to use streaming
592 + const currentModel = mxchatChat.model || 'gpt-4o';
702 593 if (shouldUseStreaming(currentModel)) {
703 594 callMxChatStream(originalMessage, function(response) {
704 595 getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
705 596 }, botId);
@@ -756,11 +647,9 @@
756 647 }
757 648
758 649 // Check for live agent response
759 650 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
760 - removeThinkingDots(botId);
761 651 updateChatModeIndicator('agent', botId);
762 - enableChatInput(botId);
763 652 return;
764 653 }
765 654
766 655 // Handle the message and show notification if chat is hidden
@@ -793,13 +682,9 @@
793 682 $badge.show();
794 683 }
795 684 }
796 685 } else {
797 - var emptyMsg = "I received an empty response. Please try again or contact support if this persists.";
798 - if (response.vectorstore_error) {
799 - emptyMsg = "I received an empty response. Debug info: " + response.vectorstore_error;
800 - }
801 - replaceLastMessage("bot", emptyMsg, '', [], botId);
686 + replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.", '', [], botId);
802 687 }
803 688
804 689 if (response.message_id) {
805 690 var instance = MxChatInstances.get(botId);
@@ -849,9 +734,9 @@
849 734
850 735 // Store the message in case we need to retry after session reset
851 736 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', message);
852 737
853 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
738 + const currentModel = mxchatChat.model || 'gpt-4o';
854 739 if (!isStreamingSupported(currentModel)) {
855 740 callMxChat(message, callback, botId);
856 741 return;
857 742 }
@@ -858,32 +743,17 @@
858 743
859 744 // Get page context if contextual awareness is enabled
860 745 const pageContext = getPageContext();
861 746
862 - // Get instance for session start timestamp (used when persistence is OFF)
863 - var instance = MxChatInstances.get(botId);
864 -
865 - // Guarantee a non-null session_id before the fetch. FormData.append() stringifies any
866 - // non-string value via String(), so passing `null` would POST the literal string "null"
867 - // and land in the transcripts table as a ghost session. ensureSession() always returns
868 - // a real string even when cookies/localStorage are blocked.
869 - var streamSessionId = MxChatInstances.ensureSession(botId);
870 - if (!streamSessionId || streamSessionId === 'null' || streamSessionId === 'undefined') {
871 - streamSessionId = generateSessionId();
872 - MxChatInstances.setChatSession(botId, streamSessionId);
873 - }
874 -
875 747 const formData = new FormData();
876 748 formData.append('action', 'mxchat_stream_chat');
877 749 formData.append('message', message);
878 - formData.append('session_id', streamSessionId);
750 + formData.append('session_id', getChatSession(botId));
879 751 formData.append('nonce', mxchatChat.nonce);
880 752 formData.append('current_page_url', window.location.href);
881 753 formData.append('current_page_title', document.title);
882 754 formData.append('bot_id', botId);
883 - // Pass session start timestamp so AI context matches what user sees
884 - formData.append('session_start_timestamp', instance.sessionStartTimestamp || 0);
885 -
755 +
886 756 // Add page context if available
887 757 if (pageContext) {
888 758 formData.append('page_context', JSON.stringify(pageContext));
889 759 }
@@ -973,11 +843,8 @@
973 843 });
974 844 return;
975 845 }
976 846
977 - // Re-enable chat input when stream ends with content
978 - enableChatInput(botId);
979 -
980 847 if (callback) {
981 848 callback(accumulatedContent);
982 849 }
983 850 return;
@@ -1120,16 +987,21 @@
1120 987 errorMessage = "An error occurred. Please try again or contact support.";
1121 988 }
1122 989
1123 990 // Handle session reset action (IP changed, session expired, etc.)
1124 - // Silent reset — keep chat UI intact, just get a new session and retry
1125 991 if (data.data && data.data.action === 'reset_session') {
1126 - MxChatInstances.silentResetSession(botId);
1127 - // Re-send the original message with the new session (user message is already displayed)
992 + // Clear the old session and generate a new one
993 + resetChatSession(botId);
994 + // Re-send the original message with the new session
1128 995 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1129 996 if (originalMessage) {
1130 997 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1131 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
998 + // Re-add the user message and thinking indicator
999 + appendMessage("user", originalMessage, '', [], false, botId);
1000 + appendThinkingMessage(botId);
1001 + scrollToBottom(botId);
1002 + // Determine whether to use streaming
1003 + const currentModel = mxchatChat.model || 'gpt-4o';
1132 1004 if (shouldUseStreaming(currentModel)) {
1133 1005 callMxChatStream(originalMessage, callback, botId);
1134 1006 } else {
1135 1007 callMxChat(originalMessage, callback, botId);
@@ -1151,22 +1023,8 @@
1151 1023 }
1152 1024 return; // Exit early for errors
1153 1025 }
1154 1026
1155 - // Check for live agent response
1156 - if (data.success && data.data && data.data.status === 'waiting_for_agent') {
1157 - removeThinkingDots(botId);
1158 - // Also remove any leftover bot-message that lost its temporary-message class
1159 - var $chatBox = getElement(botId, 'chat-box');
1160 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1161 - updateChatModeIndicator('agent', botId);
1162 - enableChatInput(botId);
1163 - if (callback) {
1164 - callback('');
1165 - }
1166 - return;
1167 - }
1168 -
1169 1027 // Handle different response formats
1170 1028 if (data.text || data.html || data.message) {
1171 1029
1172 1030 // Apply response hooks
@@ -1211,15 +1069,19 @@
1211 1069 }
1212 1070
1213 1071 // Enhanced updateChatModeIndicator function for immediate DOM updates
1214 1072 function updateChatModeIndicator(mode, botId) {
1073 + console.log('[MxChat] updateChatModeIndicator called with mode:', mode, 'botId:', botId);
1215 1074 botId = botId || 'default';
1216 1075 const indicator = getElementDOM(botId, 'chat-mode-indicator');
1076 + console.log('[MxChat] chat-mode-indicator element found:', !!indicator);
1217 1077 if (indicator) {
1218 1078 const oldText = indicator.textContent;
1079 + console.log('[MxChat] Current indicator text:', oldText, '-> changing to mode:', mode);
1219 1080
1220 1081 if (mode === 'agent') {
1221 1082 indicator.textContent = 'Live Agent';
1083 + console.log('[MxChat] Mode is agent, calling startPolling...');
1222 1084 startPolling(botId);
1223 1085 } else {
1224 1086 // Everything else is AI mode
1225 1087 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
@@ -1290,12 +1152,9 @@
1290 1152 // Update the event handlers to use the correct function names (using event delegation)
1291 1153 // Use class-based selectors for multi-instance support
1292 1154 $(document).on('click', '.send-button', function() {
1293 1155 var botId = getBotIdFromElement(this);
1294 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1295 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1296 - disableChatInput(botId);
1297 - }
1156 + disableChatInput(botId);
1298 1157 sendMessage(botId);
1299 1158 });
1300 1159
1301 1160 // Override enter key handler (using event delegation)
@@ -1302,12 +1161,9 @@
1302 1161 $(document).on('keypress', '.chat-input', function(e) {
1303 1162 if (e.which == 13 && !e.shiftKey) {
1304 1163 e.preventDefault();
1305 1164 var botId = getBotIdFromElement(this);
1306 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1307 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1308 - disableChatInput(botId);
1309 - }
1165 + disableChatInput(botId);
1310 1166 sendMessage(botId);
1311 1167 }
1312 1168 });
1313 1169
@@ -1350,12 +1206,17 @@
1350 1206 'margin-bottom': '1em'
1351 1207 });
1352 1208 }
1353 1209
1354 - // Process the message content - always run linkify to convert markdown
1355 - // links and format text. linkify() handles existing HTML safely via
1356 - // negative lookaheads that skip URLs already inside <a> tags.
1357 - let fullMessage = linkify(messageText);
1210 + // Process the message content based on sender
1211 + let fullMessage;
1212 + if (sender === "user") {
1213 + // For user messages, apply linkify after sanitization
1214 + fullMessage = linkify(messageText);
1215 + } else {
1216 + // For bot/agent messages, preserve HTML
1217 + fullMessage = messageText;
1218 + }
1358 1219
1359 1220 // Add images if provided
1360 1221 if (images && images.length > 0) {
1361 1222 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1490,12 +1351,26 @@
1490 1351 bgColor = botMessageBgColor;
1491 1352 fontColor = botMessageFontColor;
1492 1353 }
1493 1354
1494 - // Always run linkify to convert markdown links and format text.
1495 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1496 - // to avoid double-processing URLs that are already inside <a> tags).
1497 - var fullMessage = linkify(responseText);
1355 + // FIXED: Only linkify if response doesn't already contain HTML links or tags
1356 + // This prevents double-processing of URLs that are already formatted as HTML
1357 + var fullMessage;
1358 + if (sender === "user") {
1359 + // Always linkify user messages (they're plain text)
1360 + fullMessage = linkify(responseText);
1361 + } else {
1362 + // For bot/agent messages, check if HTML already exists
1363 + if (responseText.includes('<a href=') || responseText.includes('</a>') ||
1364 + responseText.includes('<img') || responseText.includes('<div') ||
1365 + responseText.includes('<p>') || responseText.includes('<br>')) {
1366 + // Response already has HTML, don't process it
1367 + fullMessage = responseText;
1368 + } else {
1369 + // Plain text response, apply linkify
1370 + fullMessage = linkify(responseText);
1371 + }
1372 + }
1498 1373
1499 1374 if (responseHtml) {
1500 1375 // Only add line breaks if there's actual text content before the HTML
1501 1376 if (fullMessage && fullMessage.trim()) {
@@ -1562,15 +1437,8 @@
1562 1437
1563 1438
1564 1439 function appendThinkingMessage(botId) {
1565 1440 botId = botId || 'default';
1566 -
1567 - // Don't show thinking dots in live agent mode - message is just forwarded to a human
1568 - var indicator = getElementDOM(botId, 'chat-mode-indicator');
1569 - if (indicator && indicator.textContent === 'Live Agent') {
1570 - return;
1571 - }
1572 -
1573 1441 var $chatBox = getElement(botId, 'chat-box');
1574 1442
1575 1443 // Remove any existing thinking dots in this bot's chat first
1576 1444 $chatBox.find('.thinking-dots').remove();
@@ -1592,9 +1460,9 @@
1592 1460 '</div>' +
1593 1461 '</div>';
1594 1462
1595 1463 // Append the thinking dots to this bot's chat container - skip inline colors if AI theme is active
1596 - var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + '; color: ' + botMessageFontColor + ';"';
1464 + var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + ';"';
1597 1465 $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1598 1466 scrollToBottom(botId);
1599 1467 }
1600 1468
@@ -1600,11 +1468,9 @@
1600 1468
1601 1469 function removeThinkingDots(botId) {
1602 1470 botId = botId || 'default';
1603 1471 var $chatBox = getElement(botId, 'chat-box');
1604 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1605 1472 $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1606 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1607 1473 }
1608 1474
1609 1475 // ====================================
1610 1476 // TEXT FORMATTING & PROCESSING
@@ -1638,12 +1504,9 @@
1638 1504 processedText = formatTextStyling(processedText);
1639 1505
1640 1506 // Process code blocks BEFORE processing links
1641 1507 processedText = formatCodeBlocks(processedText);
1642 -
1643 - // Process markdown tables BEFORE converting newlines to paragraphs
1644 - processedText = formatMarkdownTables(processedText);
1645 -
1508 +
1646 1509 // NOW convert to paragraphs
1647 1510 processedText = convertNewlinesToBreaks(processedText);
1648 1511
1649 1512 // IMPORTANT: Handle citation-style brackets FIRST [URL]
@@ -1656,63 +1519,37 @@
1656 1519 // Return as a proper link without the brackets
1657 1520 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1658 1521 });
1659 1522
1660 - // Process markdown links: [text](url) and [](url)
1661 - // Uses balanced parenthesis matching to handle URLs containing parens
1662 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1663 - processedText = (function(input) {
1664 - var result = '';
1665 - var i = 0;
1666 - while (i < input.length) {
1667 - // Look for [ at current position
1668 - if (input[i] === '[') {
1669 - // Find closing ]
1670 - var closeBracket = input.indexOf(']', i + 1);
1671 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1672 - result += input[i];
1673 - i++;
1674 - continue;
1675 - }
1676 - var linkText = input.substring(i + 1, closeBracket);
1677 - // Check if URL starts with http
1678 - var urlStart = closeBracket + 2;
1679 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1680 - result += input[i];
1681 - i++;
1682 - continue;
1683 - }
1684 - // Find balanced closing paren
1685 - var depth = 1;
1686 - var j = urlStart;
1687 - while (j < input.length && depth > 0) {
1688 - if (input[j] === '(') depth++;
1689 - else if (input[j] === ')') depth--;
1690 - if (depth > 0) j++;
1691 - }
1692 - if (depth !== 0) {
1693 - result += input[i];
1694 - i++;
1695 - continue;
1696 - }
1697 - var url = input.substring(urlStart, j);
1698 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1699 - var encodedUrl = safeEncodeUrl(cleanUrl);
1700 - if (!linkText || !linkText.trim()) {
1701 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1702 - } else {
1703 - var safeText = sanitizeUserInput(linkText);
1704 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1705 - }
1706 - i = j + 1; // Skip past the closing )
1707 - } else {
1708 - result += input[i];
1709 - i++;
1710 - }
1523 + // Process proper markdown links with text: [text](url)
1524 + // This MUST have non-empty text in the first brackets
1525 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
1526 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1527 + // Make sure we have actual text (not just whitespace)
1528 + if (!text || !text.trim()) {
1529 + // If no text, treat the URL as the text
1530 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1531 + const safeUrl = safeEncodeUrl(cleanUrl);
1532 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1711 1533 }
1712 - return result;
1713 - })(processedText);
1534 +
1535 + // Clean the URL
1536 + let cleanUrl = url.replace(/[\].,;!?]+$/, '');
1537 + const safeUrl = safeEncodeUrl(cleanUrl);
1538 + const safeText = sanitizeUserInput(text);
1539 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1540 + });
1714 1541
1542 + // Handle empty markdown links: [](url)
1543 + // This is a specific case where there's no text
1544 + const emptyMarkdownPattern = /\[\]\((https?:\/\/[^\s)]+)\)/g;
1545 + processedText = processedText.replace(emptyMarkdownPattern, (match, url) => {
1546 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1547 + const safeUrl = safeEncodeUrl(cleanUrl);
1548 + // Use the URL itself as the link text
1549 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1550 + });
1551 +
1715 1552 // Process phone numbers: [text](tel:number)
1716 1553 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1717 1554 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1718 1555 const safePhone = safeEncodeUrl(phone);
@@ -1864,78 +1701,9 @@
1864 1701 });
1865 1702
1866 1703 return text;
1867 1704 }
1868 -
1869 - function formatMarkdownTables(text) {
1870 - var lines = text.split('\n');
1871 - var result = [];
1872 - var i = 0;
1873 -
1874 - while (i < lines.length) {
1875 - // Check for a table: current line has pipes AND next line is a separator row
1876 - if (i + 1 < lines.length &&
1877 - lines[i].indexOf('|') !== -1 &&
1878 - /^\s*\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(lines[i + 1])) {
1879 -
1880 - var tableLines = [];
1881 - var headerLine = lines[i];
1882 - var separatorLine = lines[i + 1];
1883 - tableLines.push(headerLine);
1884 - tableLines.push(separatorLine);
1885 -
1886 - // Collect remaining table rows
1887 - var j = i + 2;
1888 - while (j < lines.length && lines[j].indexOf('|') !== -1 && lines[j].trim() !== '') {
1889 - tableLines.push(lines[j]);
1890 - j++;
1891 - }
1892 -
1893 - // Parse alignment from separator row
1894 - var sepCells = separatorLine.split('|').filter(function(c) { return c.trim() !== ''; });
1895 - var alignments = sepCells.map(function(cell) {
1896 - var trimmed = cell.trim();
1897 - if (trimmed.charAt(0) === ':' && trimmed.charAt(trimmed.length - 1) === ':') return 'center';
1898 - if (trimmed.charAt(trimmed.length - 1) === ':') return 'right';
1899 - return 'left';
1900 - });
1901 -
1902 - // Build HTML table
1903 - var html = '<div class="mxchat-table-wrapper"><table class="mxchat-table">';
1904 -
1905 - // Header row
1906 - var headerCells = tableLines[0].split('|').filter(function(c) { return c.trim() !== ''; });
1907 - html += '<thead><tr>';
1908 - headerCells.forEach(function(cell, idx) {
1909 - var align = alignments[idx] || 'left';
1910 - html += '<th style="text-align:' + align + '">' + cell.trim() + '</th>';
1911 - });
1912 - html += '</tr></thead>';
1913 -
1914 - // Body rows
1915 - html += '<tbody>';
1916 - for (var r = 2; r < tableLines.length; r++) {
1917 - var rowCells = tableLines[r].split('|').filter(function(c) { return c.trim() !== ''; });
1918 - html += '<tr>';
1919 - rowCells.forEach(function(cell, idx) {
1920 - var align = alignments[idx] || 'left';
1921 - html += '<td style="text-align:' + align + '">' + cell.trim() + '</td>';
1922 - });
1923 - html += '</tr>';
1924 - }
1925 - html += '</tbody></table></div>';
1926 -
1927 - result.push(html);
1928 - i = j;
1929 - } else {
1930 - result.push(lines[i]);
1931 - i++;
1932 - }
1933 - }
1934 -
1935 - return result.join('\n');
1936 - }
1937 -
1705 +
1938 1706 function sanitizeUserInput(text) {
1939 1707 const div = document.createElement('div');
1940 1708 div.textContent = text;
1941 1709 return div.innerHTML;
@@ -2156,12 +1924,15 @@
2156 1924 // LIVE AGENT FUNCTIONALITY
2157 1925 // ====================================
2158 1926
2159 1927 function startPolling(botId) {
1928 + console.log('[MxChat] startPolling called for botId:', botId);
2160 1929 botId = botId || 'default';
2161 1930 var instance = MxChatInstances.get(botId);
2162 1931 // Clear any existing interval first
2163 1932 stopPolling(botId);
1933 + // Start new polling interval
1934 + console.log('[MxChat] Starting polling interval (5s) for botId:', botId);
2164 1935 instance.pollingInterval = setInterval(function() {
2165 1936 checkForAgentMessages(botId);
2166 1937 }, 5000);
2167 1938 }
@@ -2166,17 +1937,20 @@
2166 1937 }, 5000);
2167 1938 }
2168 1939
2169 1940 function stopPolling(botId) {
1941 + console.log('[MxChat] stopPolling called for botId:', botId);
2170 1942 botId = botId || 'default';
2171 1943 var instance = MxChatInstances.get(botId);
2172 1944 if (instance.pollingInterval) {
2173 1945 clearInterval(instance.pollingInterval);
2174 1946 instance.pollingInterval = null;
1947 + console.log('[MxChat] Polling stopped for botId:', botId);
2175 1948 }
2176 1949 }
2177 1950
2178 1951 function checkForAgentMessages(botId) {
1952 + console.log('[MxChat] checkForAgentMessages called for botId:', botId);
2179 1953 botId = botId || 'default';
2180 1954 var instance = MxChatInstances.get(botId);
2181 1955 const sessionId = getChatSession(botId);
2182 1956 $.ajax({
@@ -2202,12 +1976,8 @@
2202 1976 instance.processedMessageIds.add(message.id);
2203 1977 }
2204 1978 });
2205 1979
2206 - if (hasNewMessage) {
2207 - enableChatInput(botId);
2208 - }
2209 -
2210 1980 var $floatingChatbot = getElement(botId, 'floating-chatbot');
2211 1981 if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2212 1982 showNotification(botId);
2213 1983 }
@@ -2213,13 +1983,8 @@
2213 1983 }
2214 1984
2215 1985 scrollToBottom(botId, true);
2216 1986 }
2217 -
2218 - // Handle chat mode transitions (e.g. agent ended chat via !endchat)
2219 - if (response.success && response.data?.chat_mode) {
2220 - updateChatModeIndicator(response.data.chat_mode, botId);
2221 - }
2222 1987 },
2223 1988 error: function (xhr, status, error) {
2224 1989 // Polling error - silently continue
2225 1990 }
@@ -2229,29 +1994,20 @@
2229 1994 // ====================================
2230 1995 // CHAT HISTORY & PERSISTENCE
2231 1996 // ====================================
2232 1997
2233 -function loadChatHistory(botId, onComplete) {
1998 +function loadChatHistory(botId) {
2234 1999 botId = botId || 'default';
2235 2000 var instance = MxChatInstances.get(botId);
2236 2001
2237 2002 // Prevent duplicate loading
2238 2003 if (instance.chatHistoryLoaded) {
2239 - if (onComplete) onComplete();
2240 2004 return;
2241 2005 }
2242 2006
2243 - // Use getChatSession which returns null if no session exists (does NOT create one)
2244 2007 var sessionId = getChatSession(botId);
2245 2008 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2246 2009
2247 - // No session yet — nothing to load. History will load after first message via ensureSession.
2248 - if (!sessionId) {
2249 - instance.chatHistoryLoaded = true;
2250 - if (onComplete) onComplete();
2251 - return;
2252 - }
2253 -
2254 2010 if (chatPersistenceEnabled && sessionId) {
2255 2011 $.ajax({
2256 2012 url: mxchatChat.ajax_url,
2257 2013 type: 'POST',
@@ -2262,12 +2018,11 @@
2262 2018 },
2263 2019 success: function(response) {
2264 2020 // Handle session reset (IP changed while user was away)
2265 2021 if (response.success === false && response.data && response.data.action === 'reset_session') {
2266 - // Silent reset — new session but don't clear UI
2267 - MxChatInstances.silentResetSession(botId);
2022 + // Silently reset session - user will start fresh
2023 + resetChatSession(botId);
2268 2024 instance.chatHistoryLoaded = true; // Prevent retry loop
2269 - if (onComplete) onComplete();
2270 2025 return;
2271 2026 }
2272 2027
2273 2028 // Check if the response indicates success
@@ -2323,19 +2078,9 @@
2323 2078 var content = message.content;
2324 2079 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2325 2080 content = decodeHTMLEntities(content);
2326 2081
2327 - // Skip linkify for messages containing structured HTML
2328 - // (forms, product cards, galleries, etc.) to avoid
2329 - // markdown formatting corrupting HTML attributes
2330 - // (e.g. underscores in name="field_name" becoming <em> tags)
2331 - if (content.includes("mxchat-product-card") ||
2332 - content.includes("mxchat-image-gallery") ||
2333 - content.includes("mxchat-featured-products") ||
2334 - content.includes("<form") ||
2335 - content.includes("<input") ||
2336 - content.includes("<select") ||
2337 - content.includes("<textarea")) {
2082 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2338 2083 messageElement.html(content);
2339 2084 } else {
2340 2085 var formattedContent = linkify(content);
2341 2086 messageElement.html(formattedContent);
@@ -2375,17 +2120,13 @@
2375 2120 instance.chatHistoryLoaded = true;
2376 2121 }
2377 2122 }
2378 2123 }
2379 - if (onComplete) onComplete();
2380 2124 },
2381 2125 error: function(xhr, status, error) {
2382 2126 // Error loading chat history - silently continue
2383 - if (onComplete) onComplete();
2384 2127 }
2385 2128 });
2386 - } else {
2387 - if (onComplete) onComplete();
2388 2129 }
2389 2130 }
2390 2131
2391 2132
@@ -2561,35 +2302,45 @@
2561 2302 // ====================================
2562 2303
2563 2304 function checkPreChatDismissal(botId) {
2564 2305 botId = botId || 'default';
2565 - try {
2566 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2567 - if (dismissedAt) {
2568 - // Re-show after 24 hours
2569 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2570 - if (elapsed < 86400000) {
2306 + $.ajax({
2307 + url: mxchatChat.ajax_url,
2308 + type: 'POST',
2309 + data: {
2310 + action: 'mxchat_check_pre_chat_message_status',
2311 + _ajax_nonce: mxchatChat.nonce
2312 + },
2313 + success: function(response) {
2314 + if (response.success && !response.data.dismissed) {
2315 + getElement(botId, 'pre-chat-message').fadeIn(250);
2316 + } else {
2571 2317 getElement(botId, 'pre-chat-message').hide();
2572 - return;
2573 2318 }
2574 - // Expired — clear and show again
2575 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2319 + },
2320 + error: function() {
2321 + // Error checking pre-chat dismissal - silently continue
2576 2322 }
2577 - getElement(botId, 'pre-chat-message').fadeIn(250);
2578 - } catch (e) {
2579 - // localStorage unavailable — show the message
2580 - getElement(botId, 'pre-chat-message').fadeIn(250);
2581 - }
2323 + });
2582 2324 }
2583 2325
2584 2326 function handlePreChatDismissal(botId) {
2585 2327 botId = botId || 'default';
2586 2328 getElement(botId, 'pre-chat-message').fadeOut(200);
2587 - try {
2588 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2589 - } catch (e) {
2590 - // localStorage unavailable — dismissal won't persist
2591 - }
2329 + $.ajax({
2330 + url: mxchatChat.ajax_url,
2331 + type: 'POST',
2332 + data: {
2333 + action: 'mxchat_dismiss_pre_chat_message',
2334 + _ajax_nonce: mxchatChat.nonce
2335 + },
2336 + success: function() {
2337 + $('#pre-chat-message').hide();
2338 + },
2339 + error: function() {
2340 + // Error dismissing pre-chat message - silently continue
2341 + }
2342 + });
2592 2343 }
2593 2344
2594 2345
2595 2346 // ====================================
@@ -2656,26 +2407,8 @@
2656 2407 $(this).addClass('hidden');
2657 2408 $badge.hide(); // Hide notification when opening chat
2658 2409 disableScroll();
2659 2410 $preChat.fadeOut(250);
2660 -
2661 - // Load chat history for returning visitors (persistence)
2662 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2663 - if (chatPersistenceEnabled) {
2664 - MxChatInstances.ensureSession(botId);
2665 - }
2666 -
2667 - // Deferred email check — only on first widget open
2668 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2669 - var instance = MxChatInstances.get(botId);
2670 - if (emailBlocker && !instance.emailCheckDone) {
2671 - instance.emailCheckDone = true;
2672 - resolveEmailState(botId);
2673 - } else if (!emailBlocker) {
2674 - // No email collection — still route through showChatContainerForBot
2675 - // so the loader is shown while chat history loads
2676 - showChatContainerForBot(botId);
2677 - }
2678 2411 } else {
2679 2412 $chatbot.removeClass('visible').addClass('hidden');
2680 2413 $(this).removeClass('hidden');
2681 2414 enableScroll();
@@ -2693,9 +2426,11 @@
2693 2426
2694 2427 $(document).on('click', '.close-pre-chat-message', function(e) {
2695 2428 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2696 2429 var botId = getBotIdFromElement(this);
2697 - handlePreChatDismissal(botId);
2430 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2431 + $(this).remove();
2432 + });
2698 2433 });
2699 2434
2700 2435
2701 2436 // PDF upload button handlers - use class selector
@@ -2896,437 +2631,380 @@
2896 2631 });
2897 2632
2898 2633
2899 2634 // ====================================
2900 -// INIT LOADER & CHAT CONTAINER HELPERS
2635 +// EMAIL COLLECTION SETUP - FIXED VERSION
2901 2636 // ====================================
2902 -// These must be outside the email collection block so they're always available
2903 -// (used by persistence loading even when email collection is off)
2904 -
2905 -function showInitLoader(botId) {
2906 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2907 - if (loader) loader.style.display = 'flex';
2908 -}
2909 -
2910 -function hideInitLoader(botId) {
2911 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2912 - if (loader) loader.style.display = 'none';
2913 -}
2914 -
2915 -function showEmailFormForBot(botId) {
2916 - hideInitLoader(botId);
2917 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2918 - var chatContainer = getElementDOM(botId, 'chat-container');
2919 - if (emailBlocker) emailBlocker.style.display = 'flex';
2920 - if (chatContainer) chatContainer.style.display = 'none';
2921 -}
2922 -
2923 -function showChatContainerForBot(botId) {
2924 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2925 - var chatContainer = getElementDOM(botId, 'chat-container');
2926 - if (emailBlocker) emailBlocker.style.display = 'none';
2927 -
2928 - var instance = MxChatInstances.get(botId);
2929 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2930 -
2931 - // If persistence is on and history hasn't loaded yet, show loader
2932 - // while history loads to prevent flash of empty chat
2933 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2934 - if (chatContainer) chatContainer.style.display = 'none';
2935 - showInitLoader(botId);
2936 - loadChatHistory(botId, function() {
2937 - hideInitLoader(botId);
2938 - if (chatContainer) chatContainer.style.display = 'flex';
2939 - scrollToBottom(botId, true);
2940 - });
2941 - } else {
2942 - hideInitLoader(botId);
2943 - if (chatContainer) chatContainer.style.display = 'flex';
2944 - if (typeof loadChatHistory === 'function') {
2945 - loadChatHistory(botId);
2946 - }
2947 - }
2948 -}
2949 -
2950 -// ====================================
2951 -// EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2952 -// ====================================
2953 2637 // Only run email collection setup if it's enabled
2954 2638 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
2639 + // Email collection form setup and handlers
2640 + const emailForm = document.getElementById('email-collection-form');
2641 + const emailBlocker = document.getElementById('email-blocker');
2642 + const chatbotWrapper = document.getElementById('chat-container');
2955 2643
2956 - // Track submitting state per bot
2957 - const emailSubmittingState = {};
2644 + if (emailForm && emailBlocker && chatbotWrapper) {
2645 +
2646 + // Add loading state management
2647 + let isSubmitting = false;
2648 +
2649 + // Optimized UI transition functions
2650 + function showEmailForm() {
2651 + emailBlocker.style.display = 'flex';
2652 + chatbotWrapper.style.display = 'none';
2653 + }
2958 2654
2959 - // Add CSS animations for email form (once globally)
2960 - if (!document.getElementById('email-error-styles')) {
2961 - const style = document.createElement('style');
2962 - style.id = 'email-error-styles';
2963 - style.textContent = `
2964 - @keyframes fadeInError {
2965 - from { opacity: 0; transform: translateY(-5px); }
2966 - to { opacity: 1; transform: translateY(0); }
2655 + function showChatContainer() {
2656 + // Show chat immediately without delay
2657 + emailBlocker.style.display = 'none';
2658 + chatbotWrapper.style.display = 'flex';
2659 +
2660 + // Load chat history only after showing chat container
2661 + if (typeof loadChatHistory === 'function') {
2662 + loadChatHistory();
2967 2663 }
2968 - .email-input-shake {
2969 - animation: shake 0.5s ease-in-out;
2970 - }
2971 - @keyframes shake {
2972 - 0%, 100% { transform: translateX(0); }
2973 - 25% { transform: translateX(-5px); }
2974 - 75% { transform: translateX(5px); }
2975 - }
2976 - @keyframes spin {
2977 - from { transform: rotate(0deg); }
2978 - to { transform: rotate(360deg); }
2979 - }
2980 - .email-spinner {
2981 - display: inline-block;
2982 - vertical-align: middle;
2983 - }
2984 - `;
2985 - document.head.appendChild(style);
2986 - }
2664 + }
2987 2665
2988 - function isValidEmailAddress(email) {
2989 - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2990 - return emailRegex.test(email.trim()) && email.length <= 254;
2991 - }
2666 + // Enhanced email validation
2667 + function isValidEmail(email) {
2668 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2669 + return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
2670 + }
2992 2671
2993 - function isValidNameInput(name) {
2994 - return name && name.trim().length >= 2 && name.trim().length <= 100;
2995 - }
2672 + // Enhanced name validation
2673 + function isValidName(name) {
2674 + return name && name.trim().length >= 2 && name.trim().length <= 100;
2675 + }
2996 2676
2997 - /**
2998 - * Replace {visitor_name} placeholder in intro message with actual visitor name
2999 - * @param {string} botId - The bot instance ID
3000 - * @param {string} visitorName - The visitor's name to insert
3001 - */
3002 - function replaceVisitorNamePlaceholder(botId, visitorName) {
3003 - var chatBox = getElementDOM(botId, 'chat-box');
3004 - if (!chatBox) return;
3005 -
3006 - // Find the first bot message (intro message)
3007 - var introMessage = chatBox.querySelector('.bot-message');
3008 - if (!introMessage) return;
3009 -
3010 - var messageContent = introMessage.querySelector('div[dir="auto"]');
3011 - if (!messageContent) return;
3012 -
3013 - var html = messageContent.innerHTML;
3014 -
3015 - // Replace {visitor_name} placeholder (case-insensitive)
3016 - if (visitorName && visitorName.trim()) {
3017 - // Escape HTML to prevent XSS
3018 - var safeName = $('<div>').text(visitorName.trim()).html();
3019 - html = html.replace(/\{visitor_name\}/gi, safeName);
3020 - } else {
3021 - // Remove placeholder and clean up spacing if no name provided
3022 - html = html.replace(/\{visitor_name\}/gi, '');
3023 - // Clean up any double spaces that might result
3024 - html = html.replace(/\s{2,}/g, ' ').trim();
2677 + // Show loading state with spinner
2678 + function setSubmissionState(loading) {
2679 + const submitButton = document.getElementById('email-submit-button');
2680 + const emailInput = document.getElementById('user-email');
2681 + const nameInput = document.getElementById('user-name');
2682 +
2683 + if (loading) {
2684 + isSubmitting = true;
2685 + if (submitButton) submitButton.disabled = true;
2686 + if (emailInput) emailInput.disabled = true;
2687 + if (nameInput) nameInput.disabled = true;
2688 +
2689 + // Store original content and add spinner
2690 + if (submitButton && !submitButton.getAttribute('data-original-html')) {
2691 + submitButton.setAttribute('data-original-html', submitButton.innerHTML);
2692 +
2693 + // Add loading spinner while keeping original text
2694 + const originalText = submitButton.textContent;
2695 + submitButton.innerHTML = `
2696 + <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
2697 + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
2698 + <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
2699 + <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
2700 + </circle>
2701 + </svg>
2702 + ${originalText}
2703 + `;
2704 +
2705 + submitButton.style.opacity = '0.8';
2706 + }
2707 + } else {
2708 + isSubmitting = false;
2709 + if (submitButton) submitButton.disabled = false;
2710 + if (emailInput) emailInput.disabled = false;
2711 + if (nameInput) nameInput.disabled = false;
2712 +
2713 + // Restore original content
2714 + if (submitButton) {
2715 + const originalHtml = submitButton.getAttribute('data-original-html');
2716 + if (originalHtml) {
2717 + submitButton.innerHTML = originalHtml;
2718 + }
2719 + submitButton.style.opacity = '1';
2720 + }
2721 + }
3025 2722 }
3026 2723
3027 - messageContent.innerHTML = html;
3028 - }
3029 -
3030 - function setEmailSubmissionState(botId, loading) {
3031 - var submitButton = getElementDOM(botId, 'email-submit-button');
3032 - var emailInput = getElementDOM(botId, 'user-email');
3033 - var nameInput = getElementDOM(botId, 'user-name');
3034 -
3035 - if (loading) {
3036 - emailSubmittingState[botId] = true;
3037 - if (submitButton) submitButton.disabled = true;
3038 - if (emailInput) emailInput.disabled = true;
3039 - if (nameInput) nameInput.disabled = true;
3040 -
3041 - if (submitButton && !submitButton.getAttribute('data-original-html')) {
3042 - submitButton.setAttribute('data-original-html', submitButton.innerHTML);
3043 - const originalText = submitButton.textContent;
3044 - submitButton.innerHTML = `
3045 - <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
3046 - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
3047 - <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
3048 - <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
3049 - </circle>
3050 - </svg>
3051 - ${originalText}
2724 + // Error display functions
2725 + function showEmailError(message) {
2726 + clearEmailError();
2727 +
2728 + const errorDiv = document.createElement('div');
2729 + errorDiv.className = 'email-error';
2730 + errorDiv.style.cssText = `
2731 + color: #e74c3c;
2732 + font-size: 12px;
2733 + margin-top: 8px;
2734 + padding: 4px 0;
2735 + animation: fadeInError 0.3s ease;
2736 + `;
2737 + errorDiv.textContent = message;
2738 +
2739 + // Add CSS animation if not already present
2740 + if (!document.getElementById('email-error-styles')) {
2741 + const style = document.createElement('style');
2742 + style.id = 'email-error-styles';
2743 + style.textContent = `
2744 + @keyframes fadeInError {
2745 + from { opacity: 0; transform: translateY(-5px); }
2746 + to { opacity: 1; transform: translateY(0); }
2747 + }
2748 + .email-input-shake {
2749 + animation: shake 0.5s ease-in-out;
2750 + }
2751 + @keyframes shake {
2752 + 0%, 100% { transform: translateX(0); }
2753 + 25% { transform: translateX(-5px); }
2754 + 75% { transform: translateX(5px); }
2755 + }
2756 + @keyframes spin {
2757 + from { transform: rotate(0deg); }
2758 + to { transform: rotate(360deg); }
2759 + }
2760 + .email-spinner {
2761 + display: inline-block;
2762 + vertical-align: middle;
2763 + }
3052 2764 `;
3053 - submitButton.style.opacity = '0.8';
2765 + document.head.appendChild(style);
3054 2766 }
3055 - } else {
3056 - emailSubmittingState[botId] = false;
3057 - if (submitButton) submitButton.disabled = false;
3058 - if (emailInput) emailInput.disabled = false;
3059 - if (nameInput) nameInput.disabled = false;
3060 -
3061 - if (submitButton) {
3062 - const originalHtml = submitButton.getAttribute('data-original-html');
3063 - if (originalHtml) {
3064 - submitButton.innerHTML = originalHtml;
3065 - }
3066 - submitButton.style.opacity = '1';
2767 +
2768 + emailForm.appendChild(errorDiv);
2769 +
2770 + // Add shake animation to inputs
2771 + const emailInput = document.getElementById('user-email');
2772 + const nameInput = document.getElementById('user-name');
2773 +
2774 + if (emailInput) {
2775 + emailInput.classList.add('email-input-shake');
2776 + setTimeout(() => {
2777 + emailInput.classList.remove('email-input-shake');
2778 + }, 500);
3067 2779 }
2780 +
2781 + if (nameInput) {
2782 + nameInput.classList.add('email-input-shake');
2783 + setTimeout(() => {
2784 + nameInput.classList.remove('email-input-shake');
2785 + }, 500);
2786 + }
3068 2787 }
3069 - }
3070 2788
3071 - function showEmailError(botId, message) {
3072 - clearEmailError(botId);
3073 -
3074 - var emailForm = getElementDOM(botId, 'email-collection-form');
3075 - if (!emailForm) return;
3076 -
3077 - const errorDiv = document.createElement('div');
3078 - errorDiv.className = 'email-error';
3079 - errorDiv.style.cssText = `
3080 - color: #e74c3c;
3081 - font-size: 12px;
3082 - margin-top: 8px;
3083 - padding: 4px 0;
3084 - animation: fadeInError 0.3s ease;
3085 - `;
3086 - errorDiv.textContent = message;
3087 - emailForm.appendChild(errorDiv);
3088 -
3089 - // Add shake animation to inputs
3090 - var emailInput = getElementDOM(botId, 'user-email');
3091 - var nameInput = getElementDOM(botId, 'user-name');
3092 -
3093 - if (emailInput) {
3094 - emailInput.classList.add('email-input-shake');
3095 - setTimeout(() => emailInput.classList.remove('email-input-shake'), 500);
3096 - }
3097 - if (nameInput) {
3098 - nameInput.classList.add('email-input-shake');
3099 - setTimeout(() => nameInput.classList.remove('email-input-shake'), 500);
3100 - }
3101 - }
3102 -
3103 - function clearEmailError(botId) {
3104 - var emailForm = getElementDOM(botId, 'email-collection-form');
3105 - if (emailForm) {
2789 + function clearEmailError() {
3106 2790 const existingErrors = emailForm.querySelectorAll('.email-error');
3107 2791 existingErrors.forEach(error => error.remove());
3108 2792 }
3109 - }
3110 2793
3111 - // Resolve email state using server-side data when available, AJAX fallback otherwise
3112 - function resolveEmailState(botId) {
3113 - if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3114 - if (mxchatChat.initial_email_state.show_email_form) {
3115 - showEmailFormForBot(botId);
3116 - } else {
3117 - showChatContainerForBot(botId);
3118 - }
3119 - } else {
3120 - checkSessionAndEmailForBot(botId);
3121 - }
3122 - }
2794 + // MAIN FORM SUBMIT HANDLER
2795 + // Remove any existing event listeners first
2796 + emailForm.removeEventListener('submit', handleFormSubmit);
3123 2797
3124 - function checkSessionAndEmailForBot(botId) {
3125 - const sessionId = MxChatInstances.ensureSession(botId);
2798 + // Add the form submit handler
2799 + emailForm.addEventListener('submit', handleFormSubmit);
3126 2800
3127 - // Hide both panels while we check — show loader instead
3128 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3129 - var chatContainer = getElementDOM(botId, 'chat-container');
3130 - if (emailBlocker) emailBlocker.style.display = 'none';
3131 - if (chatContainer) chatContainer.style.display = 'none';
3132 - showInitLoader(botId);
2801 + function handleFormSubmit(event) {
2802 + event.preventDefault();
2803 + event.stopPropagation();
3133 2804
3134 - fetch(mxchatChat.ajax_url, {
3135 - method: 'POST',
3136 - headers: {
3137 - 'Content-Type': 'application/x-www-form-urlencoded',
3138 - },
3139 - body: new URLSearchParams({
3140 - action: 'mxchat_check_email_provided',
3141 - session_id: sessionId,
3142 - nonce: mxchatChat.nonce,
3143 - })
3144 - })
3145 - .then((response) => {
3146 - if (!response.ok) {
3147 - throw new Error(`HTTP error! status: ${response.status}`);
2805 + // Prevent double submission
2806 + if (isSubmitting) {
2807 + return false;
3148 2808 }
3149 - return response.json();
3150 - })
3151 - .then((data) => {
3152 - if (data.success) {
3153 - if (data.data.logged_in || data.data.email) {
3154 - showChatContainerForBot(botId);
3155 - } else {
3156 - showEmailFormForBot(botId);
3157 - }
3158 - } else {
3159 - showEmailFormForBot(botId);
3160 - }
3161 - })
3162 - .catch((error) => {
3163 - showEmailFormForBot(botId);
3164 - });
3165 - }
3166 2809
3167 - // Event delegation for email form submission
3168 - $(document).on('submit', '.email-collection-form', function(e) {
3169 - e.preventDefault();
3170 - e.stopPropagation();
2810 + const userEmail = document.getElementById('user-email').value.trim();
2811 + const nameInput = document.getElementById('user-name');
2812 + const userName = nameInput ? nameInput.value.trim() : '';
2813 + const sessionId = getChatSession();
3171 2814
3172 - var botId = getBotIdFromElement(this);
2815 + // Validate email before submission
2816 + if (!userEmail) {
2817 + showEmailError('Please enter your email address.');
2818 + return false;
2819 + }
3173 2820
3174 - // Prevent double submission
3175 - if (emailSubmittingState[botId]) {
3176 - return false;
3177 - }
2821 + if (!isValidEmail(userEmail)) {
2822 + showEmailError('Please enter a valid email address.');
2823 + return false;
2824 + }
3178 2825
3179 - var emailInput = getElementDOM(botId, 'user-email');
3180 - var nameInput = getElementDOM(botId, 'user-name');
3181 - var userEmail = emailInput ? emailInput.value.trim() : '';
3182 - var userName = nameInput ? nameInput.value.trim() : '';
3183 - var sessionId = MxChatInstances.ensureSession(botId);
2826 + // Validate name if field exists
2827 + if (nameInput && !isValidName(userName)) {
2828 + showEmailError('Please enter a valid name (2-100 characters).');
2829 + return false;
2830 + }
3184 2831
3185 - // Validate email
3186 - if (!userEmail) {
3187 - showEmailError(botId, 'Please enter your email address.');
3188 - return false;
3189 - }
2832 + // Clear any existing errors
2833 + clearEmailError();
2834 + setSubmissionState(true);
3190 2835
3191 - if (!isValidEmailAddress(userEmail)) {
3192 - showEmailError(botId, 'Please enter a valid email address.');
3193 - return false;
3194 - }
2836 + // Prepare form data with optional name
2837 + const formData = new URLSearchParams({
2838 + action: 'mxchat_handle_save_email_and_response',
2839 + email: userEmail,
2840 + session_id: sessionId,
2841 + nonce: mxchatChat.nonce,
2842 + });
3195 2843
3196 - // Validate name if field exists and has content
3197 - if (nameInput && userName && !isValidNameInput(userName)) {
3198 - showEmailError(botId, 'Please enter a valid name (2-100 characters).');
3199 - return false;
3200 - }
2844 + // Add name to form data if provided
2845 + if (userName) {
2846 + formData.append('name', userName);
2847 + }
3201 2848
3202 - clearEmailError(botId);
3203 - setEmailSubmissionState(botId, true);
2849 + fetch(mxchatChat.ajax_url, {
2850 + method: 'POST',
2851 + headers: {
2852 + 'Content-Type': 'application/x-www-form-urlencoded',
2853 + },
2854 + body: formData
2855 + })
2856 + .then((response) => {
2857 + if (!response.ok) {
2858 + throw new Error(`HTTP error! status: ${response.status}`);
2859 + }
2860 + return response.json();
2861 + })
2862 + .then((data) => {
2863 + setSubmissionState(false);
3204 2864
3205 - // Prepare form data
3206 - const formData = new URLSearchParams({
3207 - action: 'mxchat_handle_save_email_and_response',
3208 - email: userEmail,
3209 - session_id: sessionId,
3210 - nonce: mxchatChat.nonce,
3211 - });
2865 + if (data.success) {
2866 + // Show chat immediately
2867 + showChatContainer();
3212 2868
3213 - if (userName) {
3214 - formData.append('name', userName);
2869 + // Handle bot response if provided
2870 + if (data.message && typeof appendMessage === 'function') {
2871 + setTimeout(() => {
2872 + appendMessage('bot', data.message);
2873 + if (typeof scrollToBottom === 'function') {
2874 + scrollToBottom();
2875 + }
2876 + }, 100);
2877 + }
2878 + } else {
2879 + showEmailError(data.message || 'Failed to save email. Please try again.');
2880 + }
2881 + })
2882 + .catch((error) => {
2883 + setSubmissionState(false);
2884 + showEmailError('An error occurred. Please try again.');
2885 + });
2886 +
2887 + return false; // Extra prevention
3215 2888 }
3216 2889
3217 - fetch(mxchatChat.ajax_url, {
3218 - method: 'POST',
3219 - headers: {
3220 - 'Content-Type': 'application/x-www-form-urlencoded',
3221 - },
3222 - body: formData
3223 - })
3224 - .then((response) => {
3225 - if (!response.ok) {
3226 - throw new Error(`HTTP error! status: ${response.status}`);
3227 - }
3228 - return response.json();
3229 - })
3230 - .then((data) => {
3231 - setEmailSubmissionState(botId, false);
2890 + // Real-time email validation
2891 + const emailInput = document.getElementById('user-email');
2892 + if (emailInput) {
2893 + let validationTimeout;
2894 +
2895 + emailInput.addEventListener('input', function() {
2896 + // Clear previous validation timeout
2897 + if (validationTimeout) {
2898 + clearTimeout(validationTimeout);
2899 + }
2900 +
2901 + // Debounce validation
2902 + validationTimeout = setTimeout(() => {
2903 + const email = this.value.trim();
2904 + clearEmailError();
2905 +
2906 + if (email && !isValidEmail(email)) {
2907 + showEmailError('Please enter a valid email address.');
2908 + }
2909 + }, 500);
2910 + });
3232 2911
3233 - if (data.success) {
3234 - showChatContainerForBot(botId);
2912 + // Handle Enter key
2913 + emailInput.addEventListener('keypress', function(e) {
2914 + if (e.key === 'Enter' && !isSubmitting) {
2915 + e.preventDefault();
2916 + emailForm.dispatchEvent(new Event('submit'));
2917 + }
2918 + });
2919 + }
3235 2920
3236 - // Replace {visitor_name} placeholder in intro message with actual name
3237 - if (userName) {
3238 - replaceVisitorNamePlaceholder(botId, userName);
3239 - } else {
3240 - // Remove placeholder if no name provided
3241 - replaceVisitorNamePlaceholder(botId, '');
2921 + // Real-time name validation
2922 + const nameInput = document.getElementById('user-name');
2923 + if (nameInput) {
2924 + let nameValidationTimeout;
2925 +
2926 + nameInput.addEventListener('input', function() {
2927 + // Clear previous validation timeout
2928 + if (nameValidationTimeout) {
2929 + clearTimeout(nameValidationTimeout);
3242 2930 }
2931 +
2932 + // Debounce validation
2933 + nameValidationTimeout = setTimeout(() => {
2934 + const name = this.value.trim();
2935 + clearEmailError();
2936 +
2937 + if (name && !isValidName(name)) {
2938 + showEmailError('Name must be between 2 and 100 characters.');
2939 + }
2940 + }, 500);
2941 + });
3243 2942
3244 - if (data.message && typeof appendMessage === 'function') {
3245 - setTimeout(() => {
3246 - appendMessage('bot', data.message, '', [], false, botId);
3247 - if (typeof scrollToBottom === 'function') {
3248 - scrollToBottom(botId);
3249 - }
3250 - }, 100);
2943 + // Handle Enter key
2944 + nameInput.addEventListener('keypress', function(e) {
2945 + if (e.key === 'Enter' && !isSubmitting) {
2946 + e.preventDefault();
2947 + emailForm.dispatchEvent(new Event('submit'));
3251 2948 }
2949 + });
2950 + }
2951 +
2952 + // Initial state check
2953 + if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
2954 + const emailState = mxchatChat.initial_email_state;
2955 + if (emailState.show_email_form) {
2956 + showEmailForm();
3252 2957 } else {
3253 - showEmailError(botId, data.message || 'Failed to save email. Please try again.');
2958 + showChatContainer();
3254 2959 }
3255 - })
3256 - .catch((error) => {
3257 - setEmailSubmissionState(botId, false);
3258 - showEmailError(botId, 'An error occurred. Please try again.');
3259 - });
3260 -
3261 - return false;
3262 - });
3263 -
3264 - // Real-time email validation using event delegation
3265 - $(document).on('input', '.mxchat-email-input', function() {
3266 - var botId = getBotIdFromElement(this);
3267 - var $input = $(this);
3268 -
3269 - // Clear previous timeout
3270 - clearTimeout($input.data('validationTimeout'));
3271 -
3272 - // Debounce validation
3273 - var timeout = setTimeout(() => {
3274 - var email = this.value.trim();
3275 - clearEmailError(botId);
3276 -
3277 - if (email && !isValidEmailAddress(email)) {
3278 - showEmailError(botId, 'Please enter a valid email address.');
3279 - }
3280 - }, 500);
3281 -
3282 - $input.data('validationTimeout', timeout);
3283 - });
3284 -
3285 - // Handle Enter key in email input
3286 - $(document).on('keypress', '.mxchat-email-input', function(e) {
3287 - if (e.key === 'Enter') {
3288 - e.preventDefault();
3289 - var botId = getBotIdFromElement(this);
3290 - if (!emailSubmittingState[botId]) {
3291 - $(this).closest('.email-collection-form').submit();
3292 - }
2960 + } else {
2961 + // Check email status via AJAX
2962 + setTimeout(checkSessionAndEmail, 100);
3293 2963 }
3294 - });
3295 2964
3296 - // Handle Enter key in name input
3297 - $(document).on('keypress', '.mxchat-name-input', function(e) {
3298 - if (e.key === 'Enter') {
3299 - e.preventDefault();
3300 - var botId = getBotIdFromElement(this);
3301 - if (!emailSubmittingState[botId]) {
3302 - $(this).closest('.email-collection-form').submit();
3303 - }
2965 + // Check if email exists for the current session
2966 + function checkSessionAndEmail() {
2967 + const sessionId = getChatSession();
2968 +
2969 + fetch(mxchatChat.ajax_url, {
2970 + method: 'POST',
2971 + headers: {
2972 + 'Content-Type': 'application/x-www-form-urlencoded',
2973 + },
2974 + body: new URLSearchParams({
2975 + action: 'mxchat_check_email_provided',
2976 + session_id: sessionId,
2977 + nonce: mxchatChat.nonce,
2978 + })
2979 + })
2980 + .then((response) => {
2981 + if (!response.ok) {
2982 + throw new Error(`HTTP error! status: ${response.status}`);
2983 + }
2984 + return response.json();
2985 + })
2986 + .then((data) => {
2987 + if (data.success) {
2988 + if (data.data.logged_in || data.data.email) {
2989 + showChatContainer();
2990 + } else {
2991 + showEmailForm();
2992 + }
2993 + } else {
2994 + // On error, default to showing email form
2995 + showEmailForm();
2996 + }
2997 + })
2998 + .catch((error) => {
2999 + // Email check failed - default to email form
3000 + showEmailForm();
3001 + });
3304 3002 }
3305 - });
3306 3003
3307 - // Initialize email check for all bot instances
3308 - // For floating bots: defer until widget is opened (zero passive AJAX)
3309 - // For embedded bots: check immediately since the form is visible
3310 - $('.mxchat-chatbot-wrapper').each(function() {
3311 - var botId = $(this).data('bot-id') || 'default';
3312 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3313 -
3314 - if (emailBlocker) {
3315 - if (isEmbeddedBot(botId)) {
3316 - // Embedded bots are always visible — check now
3317 - resolveEmailState(botId);
3318 - }
3319 - // Floating bots: handled in the widget open handler
3320 - } else if (isEmbeddedBot(botId)) {
3321 - // Embedded bot, no email collection — load history with loader
3322 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3323 - if (chatPersistenceEnabled) {
3324 - MxChatInstances.ensureSession(botId);
3325 - showChatContainerForBot(botId);
3326 - }
3327 - }
3328 - });
3004 + } else {
3005 + // Email collection is enabled but essential elements are missing - silently continue
3006 + }
3329 3007 }
3330 3008
3331 3009 // Open chatbot when pre-chat message is clicked - use class selector for multi-instance
3332 3010 $(document).on('click', '.pre-chat-message', function() {
@@ -3334,32 +3012,39 @@
3334 3012 var $chatbot = getElement(botId, 'floating-chatbot');
3335 3013 if ($chatbot.hasClass('hidden')) {
3336 3014 $chatbot.removeClass('hidden').addClass('visible');
3337 3015 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3338 - handlePreChatDismissal(botId);
3016 + $(this).fadeOut(250); // Hide pre-chat message
3339 3017 disableScroll(); // Disable scroll when chatbot opens
3018 + }
3019 + });
3340 3020
3341 - // Load chat history for returning visitors (persistence)
3342 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3343 - if (chatPersistenceEnabled) {
3344 - MxChatInstances.ensureSession(botId);
3345 - }
3021 + // Dismiss pre-chat message via close button - handled by event delegation above at line ~2376
3022 + // This is a fallback for legacy support
3023 + $(document).on('click', '.close-pre-chat-message', function() {
3024 + var botId = getBotIdFromElement(this);
3025 + var $preChat = getElement(botId, 'pre-chat-message');
3026 + $preChat.fadeOut(200); // Hide the message
3346 3027
3347 - // Deferred email check — only on first widget open
3348 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3349 - var instance = MxChatInstances.get(botId);
3350 - if (emailBlocker && !instance.emailCheckDone) {
3351 - instance.emailCheckDone = true;
3352 - resolveEmailState(botId);
3353 - } else if (!emailBlocker) {
3354 - showChatContainerForBot(botId);
3028 + // Send an AJAX request to set the transient flag for 24 hours
3029 + $.ajax({
3030 + url: mxchatChat.ajax_url,
3031 + type: 'POST',
3032 + data: {
3033 + action: 'mxchat_dismiss_pre_chat_message',
3034 + _ajax_nonce: mxchatChat.nonce
3035 + },
3036 + success: function() {
3037 + // Ensure the message is hidden after dismissal
3038 + $preChat.hide();
3039 + },
3040 + error: function() {
3041 + // Error dismissing pre-chat message - silently continue
3355 3042 }
3356 - }
3043 + });
3357 3044 });
3358 3045
3359 - // Legacy duplicate close handler removed — handled by single event delegation above
3360 3046
3361 -
3362 3047 function hasQuickQuestions(botId) {
3363 3048 botId = botId || 'default';
3364 3049 var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3365 3050 if (!questionsContainer) return false;
@@ -3495,11 +3180,18 @@
3495 3180 });
3496 3181
3497 3182 // Initialize when document is ready
3498 3183 setFullHeight();
3184 + trackOriginatingPage();
3499 3185
3500 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3501 - // until the user's first interaction via MxChatInstances.ensureSession()
3186 + // Only load chat history if email collection is disabled
3187 + if (mxchatChat.email_collection_enabled !== 'on') {
3188 + // Load history for all instances
3189 + $('.mxchat-chatbot-wrapper').each(function() {
3190 + var botId = $(this).data('bot-id') || 'default';
3191 + loadChatHistory(botId);
3192 + });
3193 + }
3502 3194
3503 3195 // Initialize chat visibility for all instances
3504 3196 $('.mxchat-chatbot-wrapper').each(function() {
3505 3197 var botId = $(this).data('bot-id') || 'default';