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 +498 -773 3.2.23.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,64 +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, or null if none exists.
65 - // 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 - // Re-sync cookie from localStorage if cookie was lost
77 - if (sessionId && !getCookie(cookieName)) {
78 - document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
79 - }
80 -
81 - return sessionId || null;
50 + return sessionId;
82 51 },
83 52
84 - // Lazy session initializer — called on first user interaction
85 - ensureSession: function(botId) {
86 - botId = botId || 'default';
87 - var instance = this.instances[botId] || this.init(botId);
88 -
89 - if (instance.sessionId) {
90 - return instance.sessionId;
91 - }
92 -
93 - // Check for existing session from cookie or localStorage
94 - var existingSession = this.getChatSession(botId);
95 -
96 - if (existingSession) {
97 - instance.sessionId = existingSession;
98 - } else {
99 - // Brand new session
100 - var newId = generateSessionId();
101 - this.setChatSession(botId, newId);
102 - instance.sessionId = newId;
103 - }
104 -
105 - // Now that we have a session, do the deferred work
106 - refreshNonceIfNeeded();
107 - trackOriginatingPage();
108 -
109 - // Note: loadChatHistory is handled by showChatContainerForBot with loader UI,
110 - // so we do NOT call it here to avoid a race condition.
111 -
112 - return instance.sessionId;
113 - },
114 -
115 53 setChatSession: function(botId, sessionId) {
116 54 var cookieName = 'mxchat_session_id_' + botId;
117 - var storageKey = 'mxchat_session_id_' + botId;
118 55 document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
119 - try { localStorage.setItem(storageKey, sessionId); } catch (e) {}
120 56 if (this.instances[botId]) {
121 57 this.instances[botId].sessionId = sessionId;
122 58 }
123 59 },
@@ -122,10 +58,8 @@
122 58 }
123 59 },
124 60
125 61 resetChatSession: function(botId) {
126 - // Clear old session from localStorage before setting new one
127 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
128 62 var newSessionId = generateSessionId();
129 63 this.setChatSession(botId, newSessionId);
130 64 var $chatBox = getElement(botId, 'chat-box');
131 65 if ($chatBox.length) {
@@ -134,20 +68,8 @@
134 68 if (this.instances[botId]) {
135 69 this.instances[botId].chatHistoryLoaded = false;
136 70 this.instances[botId].processedMessageIds = new Set();
137 71 }
138 - },
139 -
140 - // Silent reset — new session ID without clearing the chat UI
141 - // Used when IP changes mid-conversation so the user doesn't see messages vanish
142 - silentResetSession: function(botId) {
143 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
144 - var newSessionId = generateSessionId();
145 - this.setChatSession(botId, newSessionId);
146 - if (this.instances[botId]) {
147 - this.instances[botId].sessionId = newSessionId;
148 - }
149 - return newSessionId;
150 72 }
151 73 };
152 74
153 75 // ====================================
@@ -452,9 +374,8 @@
452 374
453 375 // Update your existing sendMessage function
454 376 function sendMessage(botId) {
455 377 botId = botId || 'default';
456 - MxChatInstances.ensureSession(botId);
457 378 var $chatInput = getElement(botId, 'chat-input');
458 379 var message = $chatInput.val();
459 380
460 381 // ADD PROMPT HOOK HERE
@@ -462,14 +383,10 @@
462 383 message = customMxChatFilter(message, "prompt");
463 384 }
464 385
465 386 if (message) {
466 - // Don't disable input in live agent mode - let users chat freely
467 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
468 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
469 - if (!isAgentMode) {
470 - disableChatInput(botId);
471 - }
387 + // Disable input while waiting for response
388 + disableChatInput(botId);
472 389
473 390 appendMessage("user", message, '', [], false, botId);
474 391 $chatInput.val('');
475 392 $chatInput.css('height', 'auto');
@@ -479,9 +396,9 @@
479 396 }
480 397 appendThinkingMessage(botId);
481 398 scrollToBottom(botId);
482 399
483 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
400 + const currentModel = mxchatChat.model || 'gpt-4o';
484 401
485 402 // Check if streaming is enabled AND supported for this model
486 403 if (shouldUseStreaming(currentModel)) {
487 404 callMxChatStream(message, function(response) {
@@ -497,9 +414,8 @@
497 414
498 415 // Update your existing sendMessageToChatbot function
499 416 function sendMessageToChatbot(message, botId) {
500 417 botId = botId || 'default';
501 - MxChatInstances.ensureSession(botId);
502 418
503 419 // ADD PROMPT HOOK HERE
504 420 if (typeof customMxChatFilter === 'function') {
505 421 message = customMxChatFilter(message, "prompt");
@@ -504,14 +420,10 @@
504 420 if (typeof customMxChatFilter === 'function') {
505 421 message = customMxChatFilter(message, "prompt");
506 422 }
507 423
508 - // Don't disable input in live agent mode - let users chat freely
509 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
510 - var isAgentMode = modeIndicator && modeIndicator.textContent === 'Live Agent';
511 - if (!isAgentMode) {
512 - disableChatInput(botId);
513 - }
424 + // Disable input while waiting for response
425 + disableChatInput(botId);
514 426
515 427 var sessionId = getChatSession(botId);
516 428
517 429 if (hasQuickQuestions(botId)) {
@@ -519,9 +431,9 @@
519 431 }
520 432 appendThinkingMessage(botId);
521 433 scrollToBottom(botId);
522 434
523 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
435 + const currentModel = mxchatChat.model || 'gpt-4o';
524 436
525 437 // Check if streaming is enabled AND supported for this model
526 438 if (shouldUseStreaming(currentModel)) {
527 439 callMxChatStream(message, function(response) {
@@ -596,11 +508,8 @@
596 508
597 509 // Get page context if contextual awareness is enabled
598 510 const pageContext = getPageContext();
599 511
600 - // Get instance for session start timestamp (used when persistence is OFF)
601 - var instance = MxChatInstances.get(botId);
602 -
603 512 // Prepare AJAX data
604 513 const ajaxData = {
605 514 action: 'mxchat_handle_chat_request',
606 515 message: message,
@@ -607,11 +516,9 @@
607 516 session_id: getChatSession(botId),
608 517 nonce: mxchatChat.nonce,
609 518 current_page_url: window.location.href,
610 519 current_page_title: document.title,
611 - bot_id: botId,
612 - // Pass session start timestamp so AI context matches what user sees
613 - session_start_timestamp: instance.sessionStartTimestamp || 0
520 + bot_id: botId
614 521 };
615 522
616 523 // Add page context if available
617 524 if (pageContext) {
@@ -667,16 +574,23 @@
667 574 errorMessage = "An error occurred. Please try again or contact support.";
668 575 }
669 576
670 577 // Handle session reset action (IP changed, session expired, etc.)
671 - // Silent reset — keep chat UI intact, just get a new session and retry
672 578 if (response.data && response.data.action === 'reset_session') {
673 - MxChatInstances.silentResetSession(botId);
674 - // 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
675 584 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
676 585 if (originalMessage) {
677 586 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
678 - 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';
679 593 if (shouldUseStreaming(currentModel)) {
680 594 callMxChatStream(originalMessage, function(response) {
681 595 getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
682 596 }, botId);
@@ -733,11 +647,9 @@
733 647 }
734 648
735 649 // Check for live agent response
736 650 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
737 - removeThinkingDots(botId);
738 651 updateChatModeIndicator('agent', botId);
739 - enableChatInput(botId);
740 652 return;
741 653 }
742 654
743 655 // Handle the message and show notification if chat is hidden
@@ -770,13 +682,9 @@
770 682 $badge.show();
771 683 }
772 684 }
773 685 } else {
774 - var emptyMsg = "I received an empty response. Please try again or contact support if this persists.";
775 - if (response.vectorstore_error) {
776 - emptyMsg = "I received an empty response. Debug info: " + response.vectorstore_error;
777 - }
778 - replaceLastMessage("bot", emptyMsg, '', [], botId);
686 + replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.", '', [], botId);
779 687 }
780 688
781 689 if (response.message_id) {
782 690 var instance = MxChatInstances.get(botId);
@@ -826,9 +734,9 @@
826 734
827 735 // Store the message in case we need to retry after session reset
828 736 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', message);
829 737
830 - const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
738 + const currentModel = mxchatChat.model || 'gpt-4o';
831 739 if (!isStreamingSupported(currentModel)) {
832 740 callMxChat(message, callback, botId);
833 741 return;
834 742 }
@@ -835,11 +743,8 @@
835 743
836 744 // Get page context if contextual awareness is enabled
837 745 const pageContext = getPageContext();
838 746
839 - // Get instance for session start timestamp (used when persistence is OFF)
840 - var instance = MxChatInstances.get(botId);
841 -
842 747 const formData = new FormData();
843 748 formData.append('action', 'mxchat_stream_chat');
844 749 formData.append('message', message);
845 750 formData.append('session_id', getChatSession(botId));
@@ -846,11 +751,9 @@
846 751 formData.append('nonce', mxchatChat.nonce);
847 752 formData.append('current_page_url', window.location.href);
848 753 formData.append('current_page_title', document.title);
849 754 formData.append('bot_id', botId);
850 - // Pass session start timestamp so AI context matches what user sees
851 - formData.append('session_start_timestamp', instance.sessionStartTimestamp || 0);
852 -
755 +
853 756 // Add page context if available
854 757 if (pageContext) {
855 758 formData.append('page_context', JSON.stringify(pageContext));
856 759 }
@@ -940,11 +843,8 @@
940 843 });
941 844 return;
942 845 }
943 846
944 - // Re-enable chat input when stream ends with content
945 - enableChatInput(botId);
946 -
947 847 if (callback) {
948 848 callback(accumulatedContent);
949 849 }
950 850 return;
@@ -1087,16 +987,21 @@
1087 987 errorMessage = "An error occurred. Please try again or contact support.";
1088 988 }
1089 989
1090 990 // Handle session reset action (IP changed, session expired, etc.)
1091 - // Silent reset — keep chat UI intact, just get a new session and retry
1092 991 if (data.data && data.data.action === 'reset_session') {
1093 - MxChatInstances.silentResetSession(botId);
1094 - // 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
1095 995 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1096 996 if (originalMessage) {
1097 997 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1098 - 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';
1099 1004 if (shouldUseStreaming(currentModel)) {
1100 1005 callMxChatStream(originalMessage, callback, botId);
1101 1006 } else {
1102 1007 callMxChat(originalMessage, callback, botId);
@@ -1118,22 +1023,8 @@
1118 1023 }
1119 1024 return; // Exit early for errors
1120 1025 }
1121 1026
1122 - // Check for live agent response
1123 - if (data.success && data.data && data.data.status === 'waiting_for_agent') {
1124 - removeThinkingDots(botId);
1125 - // Also remove any leftover bot-message that lost its temporary-message class
1126 - var $chatBox = getElement(botId, 'chat-box');
1127 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1128 - updateChatModeIndicator('agent', botId);
1129 - enableChatInput(botId);
1130 - if (callback) {
1131 - callback('');
1132 - }
1133 - return;
1134 - }
1135 -
1136 1027 // Handle different response formats
1137 1028 if (data.text || data.html || data.message) {
1138 1029
1139 1030 // Apply response hooks
@@ -1178,15 +1069,19 @@
1178 1069 }
1179 1070
1180 1071 // Enhanced updateChatModeIndicator function for immediate DOM updates
1181 1072 function updateChatModeIndicator(mode, botId) {
1073 + console.log('[MxChat] updateChatModeIndicator called with mode:', mode, 'botId:', botId);
1182 1074 botId = botId || 'default';
1183 1075 const indicator = getElementDOM(botId, 'chat-mode-indicator');
1076 + console.log('[MxChat] chat-mode-indicator element found:', !!indicator);
1184 1077 if (indicator) {
1185 1078 const oldText = indicator.textContent;
1079 + console.log('[MxChat] Current indicator text:', oldText, '-> changing to mode:', mode);
1186 1080
1187 1081 if (mode === 'agent') {
1188 1082 indicator.textContent = 'Live Agent';
1083 + console.log('[MxChat] Mode is agent, calling startPolling...');
1189 1084 startPolling(botId);
1190 1085 } else {
1191 1086 // Everything else is AI mode
1192 1087 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
@@ -1257,12 +1152,9 @@
1257 1152 // Update the event handlers to use the correct function names (using event delegation)
1258 1153 // Use class-based selectors for multi-instance support
1259 1154 $(document).on('click', '.send-button', function() {
1260 1155 var botId = getBotIdFromElement(this);
1261 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1262 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1263 - disableChatInput(botId);
1264 - }
1156 + disableChatInput(botId);
1265 1157 sendMessage(botId);
1266 1158 });
1267 1159
1268 1160 // Override enter key handler (using event delegation)
@@ -1269,12 +1161,9 @@
1269 1161 $(document).on('keypress', '.chat-input', function(e) {
1270 1162 if (e.which == 13 && !e.shiftKey) {
1271 1163 e.preventDefault();
1272 1164 var botId = getBotIdFromElement(this);
1273 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1274 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1275 - disableChatInput(botId);
1276 - }
1165 + disableChatInput(botId);
1277 1166 sendMessage(botId);
1278 1167 }
1279 1168 });
1280 1169
@@ -1317,12 +1206,17 @@
1317 1206 'margin-bottom': '1em'
1318 1207 });
1319 1208 }
1320 1209
1321 - // Process the message content - always run linkify to convert markdown
1322 - // links and format text. linkify() handles existing HTML safely via
1323 - // negative lookaheads that skip URLs already inside <a> tags.
1324 - 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 + }
1325 1219
1326 1220 // Add images if provided
1327 1221 if (images && images.length > 0) {
1328 1222 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1457,12 +1351,26 @@
1457 1351 bgColor = botMessageBgColor;
1458 1352 fontColor = botMessageFontColor;
1459 1353 }
1460 1354
1461 - // Always run linkify to convert markdown links and format text.
1462 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1463 - // to avoid double-processing URLs that are already inside <a> tags).
1464 - 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 + }
1465 1373
1466 1374 if (responseHtml) {
1467 1375 // Only add line breaks if there's actual text content before the HTML
1468 1376 if (fullMessage && fullMessage.trim()) {
@@ -1529,15 +1437,8 @@
1529 1437
1530 1438
1531 1439 function appendThinkingMessage(botId) {
1532 1440 botId = botId || 'default';
1533 -
1534 - // Don't show thinking dots in live agent mode - message is just forwarded to a human
1535 - var indicator = getElementDOM(botId, 'chat-mode-indicator');
1536 - if (indicator && indicator.textContent === 'Live Agent') {
1537 - return;
1538 - }
1539 -
1540 1441 var $chatBox = getElement(botId, 'chat-box');
1541 1442
1542 1443 // Remove any existing thinking dots in this bot's chat first
1543 1444 $chatBox.find('.thinking-dots').remove();
@@ -1559,9 +1460,9 @@
1559 1460 '</div>' +
1560 1461 '</div>';
1561 1462
1562 1463 // Append the thinking dots to this bot's chat container - skip inline colors if AI theme is active
1563 - var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + '; color: ' + botMessageFontColor + ';"';
1464 + var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + ';"';
1564 1465 $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1565 1466 scrollToBottom(botId);
1566 1467 }
1567 1468
@@ -1567,11 +1468,9 @@
1567 1468
1568 1469 function removeThinkingDots(botId) {
1569 1470 botId = botId || 'default';
1570 1471 var $chatBox = getElement(botId, 'chat-box');
1571 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1572 1472 $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1573 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1574 1473 }
1575 1474
1576 1475 // ====================================
1577 1476 // TEXT FORMATTING & PROCESSING
@@ -1605,12 +1504,9 @@
1605 1504 processedText = formatTextStyling(processedText);
1606 1505
1607 1506 // Process code blocks BEFORE processing links
1608 1507 processedText = formatCodeBlocks(processedText);
1609 -
1610 - // Process markdown tables BEFORE converting newlines to paragraphs
1611 - processedText = formatMarkdownTables(processedText);
1612 -
1508 +
1613 1509 // NOW convert to paragraphs
1614 1510 processedText = convertNewlinesToBreaks(processedText);
1615 1511
1616 1512 // IMPORTANT: Handle citation-style brackets FIRST [URL]
@@ -1623,63 +1519,37 @@
1623 1519 // Return as a proper link without the brackets
1624 1520 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1625 1521 });
1626 1522
1627 - // Process markdown links: [text](url) and [](url)
1628 - // Uses balanced parenthesis matching to handle URLs containing parens
1629 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1630 - processedText = (function(input) {
1631 - var result = '';
1632 - var i = 0;
1633 - while (i < input.length) {
1634 - // Look for [ at current position
1635 - if (input[i] === '[') {
1636 - // Find closing ]
1637 - var closeBracket = input.indexOf(']', i + 1);
1638 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1639 - result += input[i];
1640 - i++;
1641 - continue;
1642 - }
1643 - var linkText = input.substring(i + 1, closeBracket);
1644 - // Check if URL starts with http
1645 - var urlStart = closeBracket + 2;
1646 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1647 - result += input[i];
1648 - i++;
1649 - continue;
1650 - }
1651 - // Find balanced closing paren
1652 - var depth = 1;
1653 - var j = urlStart;
1654 - while (j < input.length && depth > 0) {
1655 - if (input[j] === '(') depth++;
1656 - else if (input[j] === ')') depth--;
1657 - if (depth > 0) j++;
1658 - }
1659 - if (depth !== 0) {
1660 - result += input[i];
1661 - i++;
1662 - continue;
1663 - }
1664 - var url = input.substring(urlStart, j);
1665 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1666 - var encodedUrl = safeEncodeUrl(cleanUrl);
1667 - if (!linkText || !linkText.trim()) {
1668 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1669 - } else {
1670 - var safeText = sanitizeUserInput(linkText);
1671 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1672 - }
1673 - i = j + 1; // Skip past the closing )
1674 - } else {
1675 - result += input[i];
1676 - i++;
1677 - }
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>`;
1678 1533 }
1679 - return result;
1680 - })(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 + });
1681 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 +
1682 1552 // Process phone numbers: [text](tel:number)
1683 1553 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1684 1554 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1685 1555 const safePhone = safeEncodeUrl(phone);
@@ -1831,78 +1701,9 @@
1831 1701 });
1832 1702
1833 1703 return text;
1834 1704 }
1835 -
1836 - function formatMarkdownTables(text) {
1837 - var lines = text.split('\n');
1838 - var result = [];
1839 - var i = 0;
1840 -
1841 - while (i < lines.length) {
1842 - // Check for a table: current line has pipes AND next line is a separator row
1843 - if (i + 1 < lines.length &&
1844 - lines[i].indexOf('|') !== -1 &&
1845 - /^\s*\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(lines[i + 1])) {
1846 -
1847 - var tableLines = [];
1848 - var headerLine = lines[i];
1849 - var separatorLine = lines[i + 1];
1850 - tableLines.push(headerLine);
1851 - tableLines.push(separatorLine);
1852 -
1853 - // Collect remaining table rows
1854 - var j = i + 2;
1855 - while (j < lines.length && lines[j].indexOf('|') !== -1 && lines[j].trim() !== '') {
1856 - tableLines.push(lines[j]);
1857 - j++;
1858 - }
1859 -
1860 - // Parse alignment from separator row
1861 - var sepCells = separatorLine.split('|').filter(function(c) { return c.trim() !== ''; });
1862 - var alignments = sepCells.map(function(cell) {
1863 - var trimmed = cell.trim();
1864 - if (trimmed.charAt(0) === ':' && trimmed.charAt(trimmed.length - 1) === ':') return 'center';
1865 - if (trimmed.charAt(trimmed.length - 1) === ':') return 'right';
1866 - return 'left';
1867 - });
1868 -
1869 - // Build HTML table
1870 - var html = '<div class="mxchat-table-wrapper"><table class="mxchat-table">';
1871 -
1872 - // Header row
1873 - var headerCells = tableLines[0].split('|').filter(function(c) { return c.trim() !== ''; });
1874 - html += '<thead><tr>';
1875 - headerCells.forEach(function(cell, idx) {
1876 - var align = alignments[idx] || 'left';
1877 - html += '<th style="text-align:' + align + '">' + cell.trim() + '</th>';
1878 - });
1879 - html += '</tr></thead>';
1880 -
1881 - // Body rows
1882 - html += '<tbody>';
1883 - for (var r = 2; r < tableLines.length; r++) {
1884 - var rowCells = tableLines[r].split('|').filter(function(c) { return c.trim() !== ''; });
1885 - html += '<tr>';
1886 - rowCells.forEach(function(cell, idx) {
1887 - var align = alignments[idx] || 'left';
1888 - html += '<td style="text-align:' + align + '">' + cell.trim() + '</td>';
1889 - });
1890 - html += '</tr>';
1891 - }
1892 - html += '</tbody></table></div>';
1893 -
1894 - result.push(html);
1895 - i = j;
1896 - } else {
1897 - result.push(lines[i]);
1898 - i++;
1899 - }
1900 - }
1901 -
1902 - return result.join('\n');
1903 - }
1904 -
1705 +
1905 1706 function sanitizeUserInput(text) {
1906 1707 const div = document.createElement('div');
1907 1708 div.textContent = text;
1908 1709 return div.innerHTML;
@@ -2123,12 +1924,15 @@
2123 1924 // LIVE AGENT FUNCTIONALITY
2124 1925 // ====================================
2125 1926
2126 1927 function startPolling(botId) {
1928 + console.log('[MxChat] startPolling called for botId:', botId);
2127 1929 botId = botId || 'default';
2128 1930 var instance = MxChatInstances.get(botId);
2129 1931 // Clear any existing interval first
2130 1932 stopPolling(botId);
1933 + // Start new polling interval
1934 + console.log('[MxChat] Starting polling interval (5s) for botId:', botId);
2131 1935 instance.pollingInterval = setInterval(function() {
2132 1936 checkForAgentMessages(botId);
2133 1937 }, 5000);
2134 1938 }
@@ -2133,17 +1937,20 @@
2133 1937 }, 5000);
2134 1938 }
2135 1939
2136 1940 function stopPolling(botId) {
1941 + console.log('[MxChat] stopPolling called for botId:', botId);
2137 1942 botId = botId || 'default';
2138 1943 var instance = MxChatInstances.get(botId);
2139 1944 if (instance.pollingInterval) {
2140 1945 clearInterval(instance.pollingInterval);
2141 1946 instance.pollingInterval = null;
1947 + console.log('[MxChat] Polling stopped for botId:', botId);
2142 1948 }
2143 1949 }
2144 1950
2145 1951 function checkForAgentMessages(botId) {
1952 + console.log('[MxChat] checkForAgentMessages called for botId:', botId);
2146 1953 botId = botId || 'default';
2147 1954 var instance = MxChatInstances.get(botId);
2148 1955 const sessionId = getChatSession(botId);
2149 1956 $.ajax({
@@ -2169,12 +1976,8 @@
2169 1976 instance.processedMessageIds.add(message.id);
2170 1977 }
2171 1978 });
2172 1979
2173 - if (hasNewMessage) {
2174 - enableChatInput(botId);
2175 - }
2176 -
2177 1980 var $floatingChatbot = getElement(botId, 'floating-chatbot');
2178 1981 if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2179 1982 showNotification(botId);
2180 1983 }
@@ -2180,13 +1983,8 @@
2180 1983 }
2181 1984
2182 1985 scrollToBottom(botId, true);
2183 1986 }
2184 -
2185 - // Handle chat mode transitions (e.g. agent ended chat via !endchat)
2186 - if (response.success && response.data?.chat_mode) {
2187 - updateChatModeIndicator(response.data.chat_mode, botId);
2188 - }
2189 1987 },
2190 1988 error: function (xhr, status, error) {
2191 1989 // Polling error - silently continue
2192 1990 }
@@ -2196,29 +1994,20 @@
2196 1994 // ====================================
2197 1995 // CHAT HISTORY & PERSISTENCE
2198 1996 // ====================================
2199 1997
2200 -function loadChatHistory(botId, onComplete) {
1998 +function loadChatHistory(botId) {
2201 1999 botId = botId || 'default';
2202 2000 var instance = MxChatInstances.get(botId);
2203 2001
2204 2002 // Prevent duplicate loading
2205 2003 if (instance.chatHistoryLoaded) {
2206 - if (onComplete) onComplete();
2207 2004 return;
2208 2005 }
2209 2006
2210 - // Use getChatSession which returns null if no session exists (does NOT create one)
2211 2007 var sessionId = getChatSession(botId);
2212 2008 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2213 2009
2214 - // No session yet — nothing to load. History will load after first message via ensureSession.
2215 - if (!sessionId) {
2216 - instance.chatHistoryLoaded = true;
2217 - if (onComplete) onComplete();
2218 - return;
2219 - }
2220 -
2221 2010 if (chatPersistenceEnabled && sessionId) {
2222 2011 $.ajax({
2223 2012 url: mxchatChat.ajax_url,
2224 2013 type: 'POST',
@@ -2229,12 +2018,11 @@
2229 2018 },
2230 2019 success: function(response) {
2231 2020 // Handle session reset (IP changed while user was away)
2232 2021 if (response.success === false && response.data && response.data.action === 'reset_session') {
2233 - // Silent reset — new session but don't clear UI
2234 - MxChatInstances.silentResetSession(botId);
2022 + // Silently reset session - user will start fresh
2023 + resetChatSession(botId);
2235 2024 instance.chatHistoryLoaded = true; // Prevent retry loop
2236 - if (onComplete) onComplete();
2237 2025 return;
2238 2026 }
2239 2027
2240 2028 // Check if the response indicates success
@@ -2290,19 +2078,9 @@
2290 2078 var content = message.content;
2291 2079 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2292 2080 content = decodeHTMLEntities(content);
2293 2081
2294 - // Skip linkify for messages containing structured HTML
2295 - // (forms, product cards, galleries, etc.) to avoid
2296 - // markdown formatting corrupting HTML attributes
2297 - // (e.g. underscores in name="field_name" becoming <em> tags)
2298 - if (content.includes("mxchat-product-card") ||
2299 - content.includes("mxchat-image-gallery") ||
2300 - content.includes("mxchat-featured-products") ||
2301 - content.includes("<form") ||
2302 - content.includes("<input") ||
2303 - content.includes("<select") ||
2304 - content.includes("<textarea")) {
2082 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2305 2083 messageElement.html(content);
2306 2084 } else {
2307 2085 var formattedContent = linkify(content);
2308 2086 messageElement.html(formattedContent);
@@ -2342,17 +2120,13 @@
2342 2120 instance.chatHistoryLoaded = true;
2343 2121 }
2344 2122 }
2345 2123 }
2346 - if (onComplete) onComplete();
2347 2124 },
2348 2125 error: function(xhr, status, error) {
2349 2126 // Error loading chat history - silently continue
2350 - if (onComplete) onComplete();
2351 2127 }
2352 2128 });
2353 - } else {
2354 - if (onComplete) onComplete();
2355 2129 }
2356 2130 }
2357 2131
2358 2132
@@ -2528,35 +2302,45 @@
2528 2302 // ====================================
2529 2303
2530 2304 function checkPreChatDismissal(botId) {
2531 2305 botId = botId || 'default';
2532 - try {
2533 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2534 - if (dismissedAt) {
2535 - // Re-show after 24 hours
2536 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2537 - 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 {
2538 2317 getElement(botId, 'pre-chat-message').hide();
2539 - return;
2540 2318 }
2541 - // Expired — clear and show again
2542 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2319 + },
2320 + error: function() {
2321 + // Error checking pre-chat dismissal - silently continue
2543 2322 }
2544 - getElement(botId, 'pre-chat-message').fadeIn(250);
2545 - } catch (e) {
2546 - // localStorage unavailable — show the message
2547 - getElement(botId, 'pre-chat-message').fadeIn(250);
2548 - }
2323 + });
2549 2324 }
2550 2325
2551 2326 function handlePreChatDismissal(botId) {
2552 2327 botId = botId || 'default';
2553 2328 getElement(botId, 'pre-chat-message').fadeOut(200);
2554 - try {
2555 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2556 - } catch (e) {
2557 - // localStorage unavailable — dismissal won't persist
2558 - }
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 + });
2559 2343 }
2560 2344
2561 2345
2562 2346 // ====================================
@@ -2623,26 +2407,8 @@
2623 2407 $(this).addClass('hidden');
2624 2408 $badge.hide(); // Hide notification when opening chat
2625 2409 disableScroll();
2626 2410 $preChat.fadeOut(250);
2627 -
2628 - // Load chat history for returning visitors (persistence)
2629 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2630 - if (chatPersistenceEnabled) {
2631 - MxChatInstances.ensureSession(botId);
2632 - }
2633 -
2634 - // Deferred email check — only on first widget open
2635 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2636 - var instance = MxChatInstances.get(botId);
2637 - if (emailBlocker && !instance.emailCheckDone) {
2638 - instance.emailCheckDone = true;
2639 - resolveEmailState(botId);
2640 - } else if (!emailBlocker) {
2641 - // No email collection — still route through showChatContainerForBot
2642 - // so the loader is shown while chat history loads
2643 - showChatContainerForBot(botId);
2644 - }
2645 2411 } else {
2646 2412 $chatbot.removeClass('visible').addClass('hidden');
2647 2413 $(this).removeClass('hidden');
2648 2414 enableScroll();
@@ -2660,9 +2426,11 @@
2660 2426
2661 2427 $(document).on('click', '.close-pre-chat-message', function(e) {
2662 2428 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2663 2429 var botId = getBotIdFromElement(this);
2664 - handlePreChatDismissal(botId);
2430 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2431 + $(this).remove();
2432 + });
2665 2433 });
2666 2434
2667 2435
2668 2436 // PDF upload button handlers - use class selector
@@ -2863,437 +2631,380 @@
2863 2631 });
2864 2632
2865 2633
2866 2634 // ====================================
2867 -// INIT LOADER & CHAT CONTAINER HELPERS
2635 +// EMAIL COLLECTION SETUP - FIXED VERSION
2868 2636 // ====================================
2869 -// These must be outside the email collection block so they're always available
2870 -// (used by persistence loading even when email collection is off)
2871 -
2872 -function showInitLoader(botId) {
2873 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2874 - if (loader) loader.style.display = 'flex';
2875 -}
2876 -
2877 -function hideInitLoader(botId) {
2878 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2879 - if (loader) loader.style.display = 'none';
2880 -}
2881 -
2882 -function showEmailFormForBot(botId) {
2883 - hideInitLoader(botId);
2884 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2885 - var chatContainer = getElementDOM(botId, 'chat-container');
2886 - if (emailBlocker) emailBlocker.style.display = 'flex';
2887 - if (chatContainer) chatContainer.style.display = 'none';
2888 -}
2889 -
2890 -function showChatContainerForBot(botId) {
2891 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2892 - var chatContainer = getElementDOM(botId, 'chat-container');
2893 - if (emailBlocker) emailBlocker.style.display = 'none';
2894 -
2895 - var instance = MxChatInstances.get(botId);
2896 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2897 -
2898 - // If persistence is on and history hasn't loaded yet, show loader
2899 - // while history loads to prevent flash of empty chat
2900 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2901 - if (chatContainer) chatContainer.style.display = 'none';
2902 - showInitLoader(botId);
2903 - loadChatHistory(botId, function() {
2904 - hideInitLoader(botId);
2905 - if (chatContainer) chatContainer.style.display = 'flex';
2906 - scrollToBottom(botId, true);
2907 - });
2908 - } else {
2909 - hideInitLoader(botId);
2910 - if (chatContainer) chatContainer.style.display = 'flex';
2911 - if (typeof loadChatHistory === 'function') {
2912 - loadChatHistory(botId);
2913 - }
2914 - }
2915 -}
2916 -
2917 -// ====================================
2918 -// EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2919 -// ====================================
2920 2637 // Only run email collection setup if it's enabled
2921 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');
2922 2643
2923 - // Track submitting state per bot
2924 - 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 + }
2925 2654
2926 - // Add CSS animations for email form (once globally)
2927 - if (!document.getElementById('email-error-styles')) {
2928 - const style = document.createElement('style');
2929 - style.id = 'email-error-styles';
2930 - style.textContent = `
2931 - @keyframes fadeInError {
2932 - from { opacity: 0; transform: translateY(-5px); }
2933 - 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();
2934 2663 }
2935 - .email-input-shake {
2936 - animation: shake 0.5s ease-in-out;
2937 - }
2938 - @keyframes shake {
2939 - 0%, 100% { transform: translateX(0); }
2940 - 25% { transform: translateX(-5px); }
2941 - 75% { transform: translateX(5px); }
2942 - }
2943 - @keyframes spin {
2944 - from { transform: rotate(0deg); }
2945 - to { transform: rotate(360deg); }
2946 - }
2947 - .email-spinner {
2948 - display: inline-block;
2949 - vertical-align: middle;
2950 - }
2951 - `;
2952 - document.head.appendChild(style);
2953 - }
2664 + }
2954 2665
2955 - function isValidEmailAddress(email) {
2956 - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2957 - return emailRegex.test(email.trim()) && email.length <= 254;
2958 - }
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 + }
2959 2671
2960 - function isValidNameInput(name) {
2961 - return name && name.trim().length >= 2 && name.trim().length <= 100;
2962 - }
2672 + // Enhanced name validation
2673 + function isValidName(name) {
2674 + return name && name.trim().length >= 2 && name.trim().length <= 100;
2675 + }
2963 2676
2964 - /**
2965 - * Replace {visitor_name} placeholder in intro message with actual visitor name
2966 - * @param {string} botId - The bot instance ID
2967 - * @param {string} visitorName - The visitor's name to insert
2968 - */
2969 - function replaceVisitorNamePlaceholder(botId, visitorName) {
2970 - var chatBox = getElementDOM(botId, 'chat-box');
2971 - if (!chatBox) return;
2972 -
2973 - // Find the first bot message (intro message)
2974 - var introMessage = chatBox.querySelector('.bot-message');
2975 - if (!introMessage) return;
2976 -
2977 - var messageContent = introMessage.querySelector('div[dir="auto"]');
2978 - if (!messageContent) return;
2979 -
2980 - var html = messageContent.innerHTML;
2981 -
2982 - // Replace {visitor_name} placeholder (case-insensitive)
2983 - if (visitorName && visitorName.trim()) {
2984 - // Escape HTML to prevent XSS
2985 - var safeName = $('<div>').text(visitorName.trim()).html();
2986 - html = html.replace(/\{visitor_name\}/gi, safeName);
2987 - } else {
2988 - // Remove placeholder and clean up spacing if no name provided
2989 - html = html.replace(/\{visitor_name\}/gi, '');
2990 - // Clean up any double spaces that might result
2991 - 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 + }
2992 2722 }
2993 2723
2994 - messageContent.innerHTML = html;
2995 - }
2996 -
2997 - function setEmailSubmissionState(botId, loading) {
2998 - var submitButton = getElementDOM(botId, 'email-submit-button');
2999 - var emailInput = getElementDOM(botId, 'user-email');
3000 - var nameInput = getElementDOM(botId, 'user-name');
3001 -
3002 - if (loading) {
3003 - emailSubmittingState[botId] = true;
3004 - if (submitButton) submitButton.disabled = true;
3005 - if (emailInput) emailInput.disabled = true;
3006 - if (nameInput) nameInput.disabled = true;
3007 -
3008 - if (submitButton && !submitButton.getAttribute('data-original-html')) {
3009 - submitButton.setAttribute('data-original-html', submitButton.innerHTML);
3010 - const originalText = submitButton.textContent;
3011 - submitButton.innerHTML = `
3012 - <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
3013 - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
3014 - <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
3015 - <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
3016 - </circle>
3017 - </svg>
3018 - ${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 + }
3019 2764 `;
3020 - submitButton.style.opacity = '0.8';
2765 + document.head.appendChild(style);
3021 2766 }
3022 - } else {
3023 - emailSubmittingState[botId] = false;
3024 - if (submitButton) submitButton.disabled = false;
3025 - if (emailInput) emailInput.disabled = false;
3026 - if (nameInput) nameInput.disabled = false;
3027 -
3028 - if (submitButton) {
3029 - const originalHtml = submitButton.getAttribute('data-original-html');
3030 - if (originalHtml) {
3031 - submitButton.innerHTML = originalHtml;
3032 - }
3033 - 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);
3034 2779 }
2780 +
2781 + if (nameInput) {
2782 + nameInput.classList.add('email-input-shake');
2783 + setTimeout(() => {
2784 + nameInput.classList.remove('email-input-shake');
2785 + }, 500);
2786 + }
3035 2787 }
3036 - }
3037 2788
3038 - function showEmailError(botId, message) {
3039 - clearEmailError(botId);
3040 -
3041 - var emailForm = getElementDOM(botId, 'email-collection-form');
3042 - if (!emailForm) return;
3043 -
3044 - const errorDiv = document.createElement('div');
3045 - errorDiv.className = 'email-error';
3046 - errorDiv.style.cssText = `
3047 - color: #e74c3c;
3048 - font-size: 12px;
3049 - margin-top: 8px;
3050 - padding: 4px 0;
3051 - animation: fadeInError 0.3s ease;
3052 - `;
3053 - errorDiv.textContent = message;
3054 - emailForm.appendChild(errorDiv);
3055 -
3056 - // Add shake animation to inputs
3057 - var emailInput = getElementDOM(botId, 'user-email');
3058 - var nameInput = getElementDOM(botId, 'user-name');
3059 -
3060 - if (emailInput) {
3061 - emailInput.classList.add('email-input-shake');
3062 - setTimeout(() => emailInput.classList.remove('email-input-shake'), 500);
3063 - }
3064 - if (nameInput) {
3065 - nameInput.classList.add('email-input-shake');
3066 - setTimeout(() => nameInput.classList.remove('email-input-shake'), 500);
3067 - }
3068 - }
3069 -
3070 - function clearEmailError(botId) {
3071 - var emailForm = getElementDOM(botId, 'email-collection-form');
3072 - if (emailForm) {
2789 + function clearEmailError() {
3073 2790 const existingErrors = emailForm.querySelectorAll('.email-error');
3074 2791 existingErrors.forEach(error => error.remove());
3075 2792 }
3076 - }
3077 2793
3078 - // Resolve email state using server-side data when available, AJAX fallback otherwise
3079 - function resolveEmailState(botId) {
3080 - if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3081 - if (mxchatChat.initial_email_state.show_email_form) {
3082 - showEmailFormForBot(botId);
3083 - } else {
3084 - showChatContainerForBot(botId);
3085 - }
3086 - } else {
3087 - checkSessionAndEmailForBot(botId);
3088 - }
3089 - }
2794 + // MAIN FORM SUBMIT HANDLER
2795 + // Remove any existing event listeners first
2796 + emailForm.removeEventListener('submit', handleFormSubmit);
3090 2797
3091 - function checkSessionAndEmailForBot(botId) {
3092 - const sessionId = MxChatInstances.ensureSession(botId);
2798 + // Add the form submit handler
2799 + emailForm.addEventListener('submit', handleFormSubmit);
3093 2800
3094 - // Hide both panels while we check — show loader instead
3095 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3096 - var chatContainer = getElementDOM(botId, 'chat-container');
3097 - if (emailBlocker) emailBlocker.style.display = 'none';
3098 - if (chatContainer) chatContainer.style.display = 'none';
3099 - showInitLoader(botId);
2801 + function handleFormSubmit(event) {
2802 + event.preventDefault();
2803 + event.stopPropagation();
3100 2804
3101 - fetch(mxchatChat.ajax_url, {
3102 - method: 'POST',
3103 - headers: {
3104 - 'Content-Type': 'application/x-www-form-urlencoded',
3105 - },
3106 - body: new URLSearchParams({
3107 - action: 'mxchat_check_email_provided',
3108 - session_id: sessionId,
3109 - nonce: mxchatChat.nonce,
3110 - })
3111 - })
3112 - .then((response) => {
3113 - if (!response.ok) {
3114 - throw new Error(`HTTP error! status: ${response.status}`);
2805 + // Prevent double submission
2806 + if (isSubmitting) {
2807 + return false;
3115 2808 }
3116 - return response.json();
3117 - })
3118 - .then((data) => {
3119 - if (data.success) {
3120 - if (data.data.logged_in || data.data.email) {
3121 - showChatContainerForBot(botId);
3122 - } else {
3123 - showEmailFormForBot(botId);
3124 - }
3125 - } else {
3126 - showEmailFormForBot(botId);
3127 - }
3128 - })
3129 - .catch((error) => {
3130 - showEmailFormForBot(botId);
3131 - });
3132 - }
3133 2809
3134 - // Event delegation for email form submission
3135 - $(document).on('submit', '.email-collection-form', function(e) {
3136 - e.preventDefault();
3137 - 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();
3138 2814
3139 - var botId = getBotIdFromElement(this);
2815 + // Validate email before submission
2816 + if (!userEmail) {
2817 + showEmailError('Please enter your email address.');
2818 + return false;
2819 + }
3140 2820
3141 - // Prevent double submission
3142 - if (emailSubmittingState[botId]) {
3143 - return false;
3144 - }
2821 + if (!isValidEmail(userEmail)) {
2822 + showEmailError('Please enter a valid email address.');
2823 + return false;
2824 + }
3145 2825
3146 - var emailInput = getElementDOM(botId, 'user-email');
3147 - var nameInput = getElementDOM(botId, 'user-name');
3148 - var userEmail = emailInput ? emailInput.value.trim() : '';
3149 - var userName = nameInput ? nameInput.value.trim() : '';
3150 - 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 + }
3151 2831
3152 - // Validate email
3153 - if (!userEmail) {
3154 - showEmailError(botId, 'Please enter your email address.');
3155 - return false;
3156 - }
2832 + // Clear any existing errors
2833 + clearEmailError();
2834 + setSubmissionState(true);
3157 2835
3158 - if (!isValidEmailAddress(userEmail)) {
3159 - showEmailError(botId, 'Please enter a valid email address.');
3160 - return false;
3161 - }
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 + });
3162 2843
3163 - // Validate name if field exists and has content
3164 - if (nameInput && userName && !isValidNameInput(userName)) {
3165 - showEmailError(botId, 'Please enter a valid name (2-100 characters).');
3166 - return false;
3167 - }
2844 + // Add name to form data if provided
2845 + if (userName) {
2846 + formData.append('name', userName);
2847 + }
3168 2848
3169 - clearEmailError(botId);
3170 - 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);
3171 2864
3172 - // Prepare form data
3173 - const formData = new URLSearchParams({
3174 - action: 'mxchat_handle_save_email_and_response',
3175 - email: userEmail,
3176 - session_id: sessionId,
3177 - nonce: mxchatChat.nonce,
3178 - });
2865 + if (data.success) {
2866 + // Show chat immediately
2867 + showChatContainer();
3179 2868
3180 - if (userName) {
3181 - 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
3182 2888 }
3183 2889
3184 - fetch(mxchatChat.ajax_url, {
3185 - method: 'POST',
3186 - headers: {
3187 - 'Content-Type': 'application/x-www-form-urlencoded',
3188 - },
3189 - body: formData
3190 - })
3191 - .then((response) => {
3192 - if (!response.ok) {
3193 - throw new Error(`HTTP error! status: ${response.status}`);
3194 - }
3195 - return response.json();
3196 - })
3197 - .then((data) => {
3198 - 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 + });
3199 2911
3200 - if (data.success) {
3201 - 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 + }
3202 2920
3203 - // Replace {visitor_name} placeholder in intro message with actual name
3204 - if (userName) {
3205 - replaceVisitorNamePlaceholder(botId, userName);
3206 - } else {
3207 - // Remove placeholder if no name provided
3208 - 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);
3209 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 + });
3210 2942
3211 - if (data.message && typeof appendMessage === 'function') {
3212 - setTimeout(() => {
3213 - appendMessage('bot', data.message, '', [], false, botId);
3214 - if (typeof scrollToBottom === 'function') {
3215 - scrollToBottom(botId);
3216 - }
3217 - }, 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'));
3218 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();
3219 2957 } else {
3220 - showEmailError(botId, data.message || 'Failed to save email. Please try again.');
2958 + showChatContainer();
3221 2959 }
3222 - })
3223 - .catch((error) => {
3224 - setEmailSubmissionState(botId, false);
3225 - showEmailError(botId, 'An error occurred. Please try again.');
3226 - });
3227 -
3228 - return false;
3229 - });
3230 -
3231 - // Real-time email validation using event delegation
3232 - $(document).on('input', '.mxchat-email-input', function() {
3233 - var botId = getBotIdFromElement(this);
3234 - var $input = $(this);
3235 -
3236 - // Clear previous timeout
3237 - clearTimeout($input.data('validationTimeout'));
3238 -
3239 - // Debounce validation
3240 - var timeout = setTimeout(() => {
3241 - var email = this.value.trim();
3242 - clearEmailError(botId);
3243 -
3244 - if (email && !isValidEmailAddress(email)) {
3245 - showEmailError(botId, 'Please enter a valid email address.');
3246 - }
3247 - }, 500);
3248 -
3249 - $input.data('validationTimeout', timeout);
3250 - });
3251 -
3252 - // Handle Enter key in email input
3253 - $(document).on('keypress', '.mxchat-email-input', function(e) {
3254 - if (e.key === 'Enter') {
3255 - e.preventDefault();
3256 - var botId = getBotIdFromElement(this);
3257 - if (!emailSubmittingState[botId]) {
3258 - $(this).closest('.email-collection-form').submit();
3259 - }
2960 + } else {
2961 + // Check email status via AJAX
2962 + setTimeout(checkSessionAndEmail, 100);
3260 2963 }
3261 - });
3262 2964
3263 - // Handle Enter key in name input
3264 - $(document).on('keypress', '.mxchat-name-input', function(e) {
3265 - if (e.key === 'Enter') {
3266 - e.preventDefault();
3267 - var botId = getBotIdFromElement(this);
3268 - if (!emailSubmittingState[botId]) {
3269 - $(this).closest('.email-collection-form').submit();
3270 - }
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 + });
3271 3002 }
3272 - });
3273 3003
3274 - // Initialize email check for all bot instances
3275 - // For floating bots: defer until widget is opened (zero passive AJAX)
3276 - // For embedded bots: check immediately since the form is visible
3277 - $('.mxchat-chatbot-wrapper').each(function() {
3278 - var botId = $(this).data('bot-id') || 'default';
3279 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3280 -
3281 - if (emailBlocker) {
3282 - if (isEmbeddedBot(botId)) {
3283 - // Embedded bots are always visible — check now
3284 - resolveEmailState(botId);
3285 - }
3286 - // Floating bots: handled in the widget open handler
3287 - } else if (isEmbeddedBot(botId)) {
3288 - // Embedded bot, no email collection — load history with loader
3289 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3290 - if (chatPersistenceEnabled) {
3291 - MxChatInstances.ensureSession(botId);
3292 - showChatContainerForBot(botId);
3293 - }
3294 - }
3295 - });
3004 + } else {
3005 + // Email collection is enabled but essential elements are missing - silently continue
3006 + }
3296 3007 }
3297 3008
3298 3009 // Open chatbot when pre-chat message is clicked - use class selector for multi-instance
3299 3010 $(document).on('click', '.pre-chat-message', function() {
@@ -3301,32 +3012,39 @@
3301 3012 var $chatbot = getElement(botId, 'floating-chatbot');
3302 3013 if ($chatbot.hasClass('hidden')) {
3303 3014 $chatbot.removeClass('hidden').addClass('visible');
3304 3015 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3305 - handlePreChatDismissal(botId);
3016 + $(this).fadeOut(250); // Hide pre-chat message
3306 3017 disableScroll(); // Disable scroll when chatbot opens
3018 + }
3019 + });
3307 3020
3308 - // Load chat history for returning visitors (persistence)
3309 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3310 - if (chatPersistenceEnabled) {
3311 - MxChatInstances.ensureSession(botId);
3312 - }
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
3313 3027
3314 - // Deferred email check — only on first widget open
3315 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3316 - var instance = MxChatInstances.get(botId);
3317 - if (emailBlocker && !instance.emailCheckDone) {
3318 - instance.emailCheckDone = true;
3319 - resolveEmailState(botId);
3320 - } else if (!emailBlocker) {
3321 - 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
3322 3042 }
3323 - }
3043 + });
3324 3044 });
3325 3045
3326 - // Legacy duplicate close handler removed — handled by single event delegation above
3327 3046
3328 -
3329 3047 function hasQuickQuestions(botId) {
3330 3048 botId = botId || 'default';
3331 3049 var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3332 3050 if (!questionsContainer) return false;
@@ -3462,11 +3180,18 @@
3462 3180 });
3463 3181
3464 3182 // Initialize when document is ready
3465 3183 setFullHeight();
3184 + trackOriginatingPage();
3466 3185
3467 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3468 - // 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 + }
3469 3194
3470 3195 // Initialize chat visibility for all instances
3471 3196 $('.mxchat-chatbot-wrapper').each(function() {
3472 3197 var botId = $(this).data('bot-id') || 'default';