PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.3
MxChat – AI Chatbot & Content Generation for WordPress v3.0.3
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 +502 -824 3.2.43.0.3 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 }
@@ -976,16 +846,8 @@
976 846
977 847 // Re-enable chat input when stream ends with content
978 848 enableChatInput(botId);
979 849
980 - // Scroll the user's last message to the top now that the
981 - // bot's full reply has rendered (gives max reading room).
982 - var $chatBoxDone = getElement(botId, 'chat-box');
983 - var $lastUserMsgDone = $chatBoxDone.find('.user-message').last();
984 - if ($lastUserMsgDone.length) {
985 - scrollElementToTop($lastUserMsgDone, botId);
986 - }
987 -
988 850 if (callback) {
989 851 callback(accumulatedContent);
990 852 }
991 853 return;
@@ -1008,16 +870,8 @@
1008 870
1009 871 // Re-enable chat input after streaming completes
1010 872 enableChatInput(botId);
1011 873
1012 - // Scroll the user's last message to the top now
1013 - // that the bot's full reply has rendered.
1014 - var $chatBoxStreamDone = getElement(botId, 'chat-box');
1015 - var $lastUserMsgStreamDone = $chatBoxStreamDone.find('.user-message').last();
1016 - if ($lastUserMsgStreamDone.length) {
1017 - scrollElementToTop($lastUserMsgStreamDone, botId);
1018 - }
1019 -
1020 874 if (callback) {
1021 875 callback(accumulatedContent);
1022 876 }
1023 877 return;
@@ -1136,16 +990,21 @@
1136 990 errorMessage = "An error occurred. Please try again or contact support.";
1137 991 }
1138 992
1139 993 // Handle session reset action (IP changed, session expired, etc.)
1140 - // Silent reset — keep chat UI intact, just get a new session and retry
1141 994 if (data.data && data.data.action === 'reset_session') {
1142 - MxChatInstances.silentResetSession(botId);
1143 - // Re-send the original message with the new session (user message is already displayed)
995 + // Clear the old session and generate a new one
996 + resetChatSession(botId);
997 + // Re-send the original message with the new session
1144 998 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1145 999 if (originalMessage) {
1146 1000 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1147 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1001 + // Re-add the user message and thinking indicator
1002 + appendMessage("user", originalMessage, '', [], false, botId);
1003 + appendThinkingMessage(botId);
1004 + scrollToBottom(botId);
1005 + // Determine whether to use streaming
1006 + const currentModel = mxchatChat.model || 'gpt-4o';
1148 1007 if (shouldUseStreaming(currentModel)) {
1149 1008 callMxChatStream(originalMessage, callback, botId);
1150 1009 } else {
1151 1010 callMxChat(originalMessage, callback, botId);
@@ -1167,22 +1026,8 @@
1167 1026 }
1168 1027 return; // Exit early for errors
1169 1028 }
1170 1029
1171 - // Check for live agent response
1172 - if (data.success && data.data && data.data.status === 'waiting_for_agent') {
1173 - removeThinkingDots(botId);
1174 - // Also remove any leftover bot-message that lost its temporary-message class
1175 - var $chatBox = getElement(botId, 'chat-box');
1176 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1177 - updateChatModeIndicator('agent', botId);
1178 - enableChatInput(botId);
1179 - if (callback) {
1180 - callback('');
1181 - }
1182 - return;
1183 - }
1184 -
1185 1030 // Handle different response formats
1186 1031 if (data.text || data.html || data.message) {
1187 1032
1188 1033 // Apply response hooks
@@ -1227,15 +1072,19 @@
1227 1072 }
1228 1073
1229 1074 // Enhanced updateChatModeIndicator function for immediate DOM updates
1230 1075 function updateChatModeIndicator(mode, botId) {
1076 + console.log('[MxChat] updateChatModeIndicator called with mode:', mode, 'botId:', botId);
1231 1077 botId = botId || 'default';
1232 1078 const indicator = getElementDOM(botId, 'chat-mode-indicator');
1079 + console.log('[MxChat] chat-mode-indicator element found:', !!indicator);
1233 1080 if (indicator) {
1234 1081 const oldText = indicator.textContent;
1082 + console.log('[MxChat] Current indicator text:', oldText, '-> changing to mode:', mode);
1235 1083
1236 1084 if (mode === 'agent') {
1237 1085 indicator.textContent = 'Live Agent';
1086 + console.log('[MxChat] Mode is agent, calling startPolling...');
1238 1087 startPolling(botId);
1239 1088 } else {
1240 1089 // Everything else is AI mode
1241 1090 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
@@ -1306,12 +1155,9 @@
1306 1155 // Update the event handlers to use the correct function names (using event delegation)
1307 1156 // Use class-based selectors for multi-instance support
1308 1157 $(document).on('click', '.send-button', function() {
1309 1158 var botId = getBotIdFromElement(this);
1310 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1311 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1312 - disableChatInput(botId);
1313 - }
1159 + disableChatInput(botId);
1314 1160 sendMessage(botId);
1315 1161 });
1316 1162
1317 1163 // Override enter key handler (using event delegation)
@@ -1318,12 +1164,9 @@
1318 1164 $(document).on('keypress', '.chat-input', function(e) {
1319 1165 if (e.which == 13 && !e.shiftKey) {
1320 1166 e.preventDefault();
1321 1167 var botId = getBotIdFromElement(this);
1322 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1323 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1324 - disableChatInput(botId);
1325 - }
1168 + disableChatInput(botId);
1326 1169 sendMessage(botId);
1327 1170 }
1328 1171 });
1329 1172
@@ -1366,12 +1209,17 @@
1366 1209 'margin-bottom': '1em'
1367 1210 });
1368 1211 }
1369 1212
1370 - // Process the message content - always run linkify to convert markdown
1371 - // links and format text. linkify() handles existing HTML safely via
1372 - // negative lookaheads that skip URLs already inside <a> tags.
1373 - let fullMessage = linkify(messageText);
1213 + // Process the message content based on sender
1214 + let fullMessage;
1215 + if (sender === "user") {
1216 + // For user messages, apply linkify after sanitization
1217 + fullMessage = linkify(messageText);
1218 + } else {
1219 + // For bot/agent messages, preserve HTML
1220 + fullMessage = messageText;
1221 + }
1374 1222
1375 1223 // Add images if provided
1376 1224 if (images && images.length > 0) {
1377 1225 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1506,12 +1354,26 @@
1506 1354 bgColor = botMessageBgColor;
1507 1355 fontColor = botMessageFontColor;
1508 1356 }
1509 1357
1510 - // Always run linkify to convert markdown links and format text.
1511 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1512 - // to avoid double-processing URLs that are already inside <a> tags).
1513 - var fullMessage = linkify(responseText);
1358 + // FIXED: Only linkify if response doesn't already contain HTML links or tags
1359 + // This prevents double-processing of URLs that are already formatted as HTML
1360 + var fullMessage;
1361 + if (sender === "user") {
1362 + // Always linkify user messages (they're plain text)
1363 + fullMessage = linkify(responseText);
1364 + } else {
1365 + // For bot/agent messages, check if HTML already exists
1366 + if (responseText.includes('<a href=') || responseText.includes('</a>') ||
1367 + responseText.includes('<img') || responseText.includes('<div') ||
1368 + responseText.includes('<p>') || responseText.includes('<br>')) {
1369 + // Response already has HTML, don't process it
1370 + fullMessage = responseText;
1371 + } else {
1372 + // Plain text response, apply linkify
1373 + fullMessage = linkify(responseText);
1374 + }
1375 + }
1514 1376
1515 1377 if (responseHtml) {
1516 1378 // Only add line breaks if there's actual text content before the HTML
1517 1379 if (fullMessage && fullMessage.trim()) {
@@ -1578,15 +1440,8 @@
1578 1440
1579 1441
1580 1442 function appendThinkingMessage(botId) {
1581 1443 botId = botId || 'default';
1582 -
1583 - // Don't show thinking dots in live agent mode - message is just forwarded to a human
1584 - var indicator = getElementDOM(botId, 'chat-mode-indicator');
1585 - if (indicator && indicator.textContent === 'Live Agent') {
1586 - return;
1587 - }
1588 -
1589 1444 var $chatBox = getElement(botId, 'chat-box');
1590 1445
1591 1446 // Remove any existing thinking dots in this bot's chat first
1592 1447 $chatBox.find('.thinking-dots').remove();
@@ -1608,9 +1463,9 @@
1608 1463 '</div>' +
1609 1464 '</div>';
1610 1465
1611 1466 // Append the thinking dots to this bot's chat container - skip inline colors if AI theme is active
1612 - var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + '; color: ' + botMessageFontColor + ';"';
1467 + var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + ';"';
1613 1468 $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1614 1469 scrollToBottom(botId);
1615 1470 }
1616 1471
@@ -1616,11 +1471,9 @@
1616 1471
1617 1472 function removeThinkingDots(botId) {
1618 1473 botId = botId || 'default';
1619 1474 var $chatBox = getElement(botId, 'chat-box');
1620 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1621 1475 $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1622 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1623 1476 }
1624 1477
1625 1478 // ====================================
1626 1479 // TEXT FORMATTING & PROCESSING
@@ -1654,12 +1507,9 @@
1654 1507 processedText = formatTextStyling(processedText);
1655 1508
1656 1509 // Process code blocks BEFORE processing links
1657 1510 processedText = formatCodeBlocks(processedText);
1658 -
1659 - // Process markdown tables BEFORE converting newlines to paragraphs
1660 - processedText = formatMarkdownTables(processedText);
1661 -
1511 +
1662 1512 // NOW convert to paragraphs
1663 1513 processedText = convertNewlinesToBreaks(processedText);
1664 1514
1665 1515 // IMPORTANT: Handle citation-style brackets FIRST [URL]
@@ -1672,63 +1522,37 @@
1672 1522 // Return as a proper link without the brackets
1673 1523 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1674 1524 });
1675 1525
1676 - // Process markdown links: [text](url) and [](url)
1677 - // Uses balanced parenthesis matching to handle URLs containing parens
1678 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1679 - processedText = (function(input) {
1680 - var result = '';
1681 - var i = 0;
1682 - while (i < input.length) {
1683 - // Look for [ at current position
1684 - if (input[i] === '[') {
1685 - // Find closing ]
1686 - var closeBracket = input.indexOf(']', i + 1);
1687 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1688 - result += input[i];
1689 - i++;
1690 - continue;
1691 - }
1692 - var linkText = input.substring(i + 1, closeBracket);
1693 - // Check if URL starts with http
1694 - var urlStart = closeBracket + 2;
1695 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1696 - result += input[i];
1697 - i++;
1698 - continue;
1699 - }
1700 - // Find balanced closing paren
1701 - var depth = 1;
1702 - var j = urlStart;
1703 - while (j < input.length && depth > 0) {
1704 - if (input[j] === '(') depth++;
1705 - else if (input[j] === ')') depth--;
1706 - if (depth > 0) j++;
1707 - }
1708 - if (depth !== 0) {
1709 - result += input[i];
1710 - i++;
1711 - continue;
1712 - }
1713 - var url = input.substring(urlStart, j);
1714 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1715 - var encodedUrl = safeEncodeUrl(cleanUrl);
1716 - if (!linkText || !linkText.trim()) {
1717 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1718 - } else {
1719 - var safeText = sanitizeUserInput(linkText);
1720 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1721 - }
1722 - i = j + 1; // Skip past the closing )
1723 - } else {
1724 - result += input[i];
1725 - i++;
1726 - }
1526 + // Process proper markdown links with text: [text](url)
1527 + // This MUST have non-empty text in the first brackets
1528 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
1529 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1530 + // Make sure we have actual text (not just whitespace)
1531 + if (!text || !text.trim()) {
1532 + // If no text, treat the URL as the text
1533 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1534 + const safeUrl = safeEncodeUrl(cleanUrl);
1535 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1727 1536 }
1728 - return result;
1729 - })(processedText);
1537 +
1538 + // Clean the URL
1539 + let cleanUrl = url.replace(/[\].,;!?]+$/, '');
1540 + const safeUrl = safeEncodeUrl(cleanUrl);
1541 + const safeText = sanitizeUserInput(text);
1542 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1543 + });
1730 1544
1545 + // Handle empty markdown links: [](url)
1546 + // This is a specific case where there's no text
1547 + const emptyMarkdownPattern = /\[\]\((https?:\/\/[^\s)]+)\)/g;
1548 + processedText = processedText.replace(emptyMarkdownPattern, (match, url) => {
1549 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1550 + const safeUrl = safeEncodeUrl(cleanUrl);
1551 + // Use the URL itself as the link text
1552 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1553 + });
1554 +
1731 1555 // Process phone numbers: [text](tel:number)
1732 1556 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1733 1557 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1734 1558 const safePhone = safeEncodeUrl(phone);
@@ -1880,78 +1704,9 @@
1880 1704 });
1881 1705
1882 1706 return text;
1883 1707 }
1884 -
1885 - function formatMarkdownTables(text) {
1886 - var lines = text.split('\n');
1887 - var result = [];
1888 - var i = 0;
1889 -
1890 - while (i < lines.length) {
1891 - // Check for a table: current line has pipes AND next line is a separator row
1892 - if (i + 1 < lines.length &&
1893 - lines[i].indexOf('|') !== -1 &&
1894 - /^\s*\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(lines[i + 1])) {
1895 -
1896 - var tableLines = [];
1897 - var headerLine = lines[i];
1898 - var separatorLine = lines[i + 1];
1899 - tableLines.push(headerLine);
1900 - tableLines.push(separatorLine);
1901 -
1902 - // Collect remaining table rows
1903 - var j = i + 2;
1904 - while (j < lines.length && lines[j].indexOf('|') !== -1 && lines[j].trim() !== '') {
1905 - tableLines.push(lines[j]);
1906 - j++;
1907 - }
1908 -
1909 - // Parse alignment from separator row
1910 - var sepCells = separatorLine.split('|').filter(function(c) { return c.trim() !== ''; });
1911 - var alignments = sepCells.map(function(cell) {
1912 - var trimmed = cell.trim();
1913 - if (trimmed.charAt(0) === ':' && trimmed.charAt(trimmed.length - 1) === ':') return 'center';
1914 - if (trimmed.charAt(trimmed.length - 1) === ':') return 'right';
1915 - return 'left';
1916 - });
1917 -
1918 - // Build HTML table
1919 - var html = '<div class="mxchat-table-wrapper"><table class="mxchat-table">';
1920 -
1921 - // Header row
1922 - var headerCells = tableLines[0].split('|').filter(function(c) { return c.trim() !== ''; });
1923 - html += '<thead><tr>';
1924 - headerCells.forEach(function(cell, idx) {
1925 - var align = alignments[idx] || 'left';
1926 - html += '<th style="text-align:' + align + '">' + cell.trim() + '</th>';
1927 - });
1928 - html += '</tr></thead>';
1929 -
1930 - // Body rows
1931 - html += '<tbody>';
1932 - for (var r = 2; r < tableLines.length; r++) {
1933 - var rowCells = tableLines[r].split('|').filter(function(c) { return c.trim() !== ''; });
1934 - html += '<tr>';
1935 - rowCells.forEach(function(cell, idx) {
1936 - var align = alignments[idx] || 'left';
1937 - html += '<td style="text-align:' + align + '">' + cell.trim() + '</td>';
1938 - });
1939 - html += '</tr>';
1940 - }
1941 - html += '</tbody></table></div>';
1942 -
1943 - result.push(html);
1944 - i = j;
1945 - } else {
1946 - result.push(lines[i]);
1947 - i++;
1948 - }
1949 - }
1950 -
1951 - return result.join('\n');
1952 - }
1953 -
1708 +
1954 1709 function sanitizeUserInput(text) {
1955 1710 const div = document.createElement('div');
1956 1711 div.textContent = text;
1957 1712 return div.innerHTML;
@@ -2022,14 +1777,13 @@
2022 1777 requestAnimationFrame(smoothScroll);
2023 1778 }
2024 1779 }
2025 1780
2026 - function scrollElementToTop(element, botId, topOffset) {
1781 + function scrollElementToTop(element, botId) {
2027 1782 botId = botId || 'default';
2028 - topOffset = (typeof topOffset === 'number') ? topOffset : 2;
2029 1783 var chatBox = getElement(botId, 'chat-box');
2030 1784 var elementTop = element.position().top + chatBox.scrollTop();
2031 - chatBox.animate({ scrollTop: Math.max(0, elementTop - topOffset) }, 500);
1785 + chatBox.animate({ scrollTop: elementTop }, 500);
2032 1786 }
2033 1787
2034 1788 function showChatWidget(botId) {
2035 1789 botId = botId || 'default';
@@ -2173,12 +1927,15 @@
2173 1927 // LIVE AGENT FUNCTIONALITY
2174 1928 // ====================================
2175 1929
2176 1930 function startPolling(botId) {
1931 + console.log('[MxChat] startPolling called for botId:', botId);
2177 1932 botId = botId || 'default';
2178 1933 var instance = MxChatInstances.get(botId);
2179 1934 // Clear any existing interval first
2180 1935 stopPolling(botId);
1936 + // Start new polling interval
1937 + console.log('[MxChat] Starting polling interval (5s) for botId:', botId);
2181 1938 instance.pollingInterval = setInterval(function() {
2182 1939 checkForAgentMessages(botId);
2183 1940 }, 5000);
2184 1941 }
@@ -2183,17 +1940,20 @@
2183 1940 }, 5000);
2184 1941 }
2185 1942
2186 1943 function stopPolling(botId) {
1944 + console.log('[MxChat] stopPolling called for botId:', botId);
2187 1945 botId = botId || 'default';
2188 1946 var instance = MxChatInstances.get(botId);
2189 1947 if (instance.pollingInterval) {
2190 1948 clearInterval(instance.pollingInterval);
2191 1949 instance.pollingInterval = null;
1950 + console.log('[MxChat] Polling stopped for botId:', botId);
2192 1951 }
2193 1952 }
2194 1953
2195 1954 function checkForAgentMessages(botId) {
1955 + console.log('[MxChat] checkForAgentMessages called for botId:', botId);
2196 1956 botId = botId || 'default';
2197 1957 var instance = MxChatInstances.get(botId);
2198 1958 const sessionId = getChatSession(botId);
2199 1959 $.ajax({
@@ -2219,12 +1979,8 @@
2219 1979 instance.processedMessageIds.add(message.id);
2220 1980 }
2221 1981 });
2222 1982
2223 - if (hasNewMessage) {
2224 - enableChatInput(botId);
2225 - }
2226 -
2227 1983 var $floatingChatbot = getElement(botId, 'floating-chatbot');
2228 1984 if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2229 1985 showNotification(botId);
2230 1986 }
@@ -2230,13 +1986,8 @@
2230 1986 }
2231 1987
2232 1988 scrollToBottom(botId, true);
2233 1989 }
2234 -
2235 - // Handle chat mode transitions (e.g. agent ended chat via !endchat)
2236 - if (response.success && response.data?.chat_mode) {
2237 - updateChatModeIndicator(response.data.chat_mode, botId);
2238 - }
2239 1990 },
2240 1991 error: function (xhr, status, error) {
2241 1992 // Polling error - silently continue
2242 1993 }
@@ -2246,29 +1997,20 @@
2246 1997 // ====================================
2247 1998 // CHAT HISTORY & PERSISTENCE
2248 1999 // ====================================
2249 2000
2250 -function loadChatHistory(botId, onComplete) {
2001 +function loadChatHistory(botId) {
2251 2002 botId = botId || 'default';
2252 2003 var instance = MxChatInstances.get(botId);
2253 2004
2254 2005 // Prevent duplicate loading
2255 2006 if (instance.chatHistoryLoaded) {
2256 - if (onComplete) onComplete();
2257 2007 return;
2258 2008 }
2259 2009
2260 - // Use getChatSession which returns null if no session exists (does NOT create one)
2261 2010 var sessionId = getChatSession(botId);
2262 2011 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2263 2012
2264 - // No session yet — nothing to load. History will load after first message via ensureSession.
2265 - if (!sessionId) {
2266 - instance.chatHistoryLoaded = true;
2267 - if (onComplete) onComplete();
2268 - return;
2269 - }
2270 -
2271 2013 if (chatPersistenceEnabled && sessionId) {
2272 2014 $.ajax({
2273 2015 url: mxchatChat.ajax_url,
2274 2016 type: 'POST',
@@ -2279,12 +2021,11 @@
2279 2021 },
2280 2022 success: function(response) {
2281 2023 // Handle session reset (IP changed while user was away)
2282 2024 if (response.success === false && response.data && response.data.action === 'reset_session') {
2283 - // Silent reset — new session but don't clear UI
2284 - MxChatInstances.silentResetSession(botId);
2025 + // Silently reset session - user will start fresh
2026 + resetChatSession(botId);
2285 2027 instance.chatHistoryLoaded = true; // Prevent retry loop
2286 - if (onComplete) onComplete();
2287 2028 return;
2288 2029 }
2289 2030
2290 2031 // Check if the response indicates success
@@ -2340,19 +2081,9 @@
2340 2081 var content = message.content;
2341 2082 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2342 2083 content = decodeHTMLEntities(content);
2343 2084
2344 - // Skip linkify for messages containing structured HTML
2345 - // (forms, product cards, galleries, etc.) to avoid
2346 - // markdown formatting corrupting HTML attributes
2347 - // (e.g. underscores in name="field_name" becoming <em> tags)
2348 - if (content.includes("mxchat-product-card") ||
2349 - content.includes("mxchat-image-gallery") ||
2350 - content.includes("mxchat-featured-products") ||
2351 - content.includes("<form") ||
2352 - content.includes("<input") ||
2353 - content.includes("<select") ||
2354 - content.includes("<textarea")) {
2085 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2355 2086 messageElement.html(content);
2356 2087 } else {
2357 2088 var formattedContent = linkify(content);
2358 2089 messageElement.html(formattedContent);
@@ -2392,17 +2123,13 @@
2392 2123 instance.chatHistoryLoaded = true;
2393 2124 }
2394 2125 }
2395 2126 }
2396 - if (onComplete) onComplete();
2397 2127 },
2398 2128 error: function(xhr, status, error) {
2399 2129 // Error loading chat history - silently continue
2400 - if (onComplete) onComplete();
2401 2130 }
2402 2131 });
2403 - } else {
2404 - if (onComplete) onComplete();
2405 2132 }
2406 2133 }
2407 2134
2408 2135
@@ -2578,35 +2305,45 @@
2578 2305 // ====================================
2579 2306
2580 2307 function checkPreChatDismissal(botId) {
2581 2308 botId = botId || 'default';
2582 - try {
2583 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2584 - if (dismissedAt) {
2585 - // Re-show after 24 hours
2586 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2587 - if (elapsed < 86400000) {
2309 + $.ajax({
2310 + url: mxchatChat.ajax_url,
2311 + type: 'POST',
2312 + data: {
2313 + action: 'mxchat_check_pre_chat_message_status',
2314 + _ajax_nonce: mxchatChat.nonce
2315 + },
2316 + success: function(response) {
2317 + if (response.success && !response.data.dismissed) {
2318 + getElement(botId, 'pre-chat-message').fadeIn(250);
2319 + } else {
2588 2320 getElement(botId, 'pre-chat-message').hide();
2589 - return;
2590 2321 }
2591 - // Expired — clear and show again
2592 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2322 + },
2323 + error: function() {
2324 + // Error checking pre-chat dismissal - silently continue
2593 2325 }
2594 - getElement(botId, 'pre-chat-message').fadeIn(250);
2595 - } catch (e) {
2596 - // localStorage unavailable — show the message
2597 - getElement(botId, 'pre-chat-message').fadeIn(250);
2598 - }
2326 + });
2599 2327 }
2600 2328
2601 2329 function handlePreChatDismissal(botId) {
2602 2330 botId = botId || 'default';
2603 2331 getElement(botId, 'pre-chat-message').fadeOut(200);
2604 - try {
2605 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2606 - } catch (e) {
2607 - // localStorage unavailable — dismissal won't persist
2608 - }
2332 + $.ajax({
2333 + url: mxchatChat.ajax_url,
2334 + type: 'POST',
2335 + data: {
2336 + action: 'mxchat_dismiss_pre_chat_message',
2337 + _ajax_nonce: mxchatChat.nonce
2338 + },
2339 + success: function() {
2340 + $('#pre-chat-message').hide();
2341 + },
2342 + error: function() {
2343 + // Error dismissing pre-chat message - silently continue
2344 + }
2345 + });
2609 2346 }
2610 2347
2611 2348
2612 2349 // ====================================
@@ -2673,26 +2410,8 @@
2673 2410 $(this).addClass('hidden');
2674 2411 $badge.hide(); // Hide notification when opening chat
2675 2412 disableScroll();
2676 2413 $preChat.fadeOut(250);
2677 -
2678 - // Load chat history for returning visitors (persistence)
2679 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2680 - if (chatPersistenceEnabled) {
2681 - MxChatInstances.ensureSession(botId);
2682 - }
2683 -
2684 - // Deferred email check — only on first widget open
2685 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2686 - var instance = MxChatInstances.get(botId);
2687 - if (emailBlocker && !instance.emailCheckDone) {
2688 - instance.emailCheckDone = true;
2689 - resolveEmailState(botId);
2690 - } else if (!emailBlocker) {
2691 - // No email collection — still route through showChatContainerForBot
2692 - // so the loader is shown while chat history loads
2693 - showChatContainerForBot(botId);
2694 - }
2695 2414 } else {
2696 2415 $chatbot.removeClass('visible').addClass('hidden');
2697 2416 $(this).removeClass('hidden');
2698 2417 enableScroll();
@@ -2710,9 +2429,11 @@
2710 2429
2711 2430 $(document).on('click', '.close-pre-chat-message', function(e) {
2712 2431 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2713 2432 var botId = getBotIdFromElement(this);
2714 - handlePreChatDismissal(botId);
2433 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2434 + $(this).remove();
2435 + });
2715 2436 });
2716 2437
2717 2438
2718 2439 // PDF upload button handlers - use class selector
@@ -2913,437 +2634,380 @@
2913 2634 });
2914 2635
2915 2636
2916 2637 // ====================================
2917 -// INIT LOADER & CHAT CONTAINER HELPERS
2638 +// EMAIL COLLECTION SETUP - FIXED VERSION
2918 2639 // ====================================
2919 -// These must be outside the email collection block so they're always available
2920 -// (used by persistence loading even when email collection is off)
2921 -
2922 -function showInitLoader(botId) {
2923 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2924 - if (loader) loader.style.display = 'flex';
2925 -}
2926 -
2927 -function hideInitLoader(botId) {
2928 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2929 - if (loader) loader.style.display = 'none';
2930 -}
2931 -
2932 -function showEmailFormForBot(botId) {
2933 - hideInitLoader(botId);
2934 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2935 - var chatContainer = getElementDOM(botId, 'chat-container');
2936 - if (emailBlocker) emailBlocker.style.display = 'flex';
2937 - if (chatContainer) chatContainer.style.display = 'none';
2938 -}
2939 -
2940 -function showChatContainerForBot(botId) {
2941 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2942 - var chatContainer = getElementDOM(botId, 'chat-container');
2943 - if (emailBlocker) emailBlocker.style.display = 'none';
2944 -
2945 - var instance = MxChatInstances.get(botId);
2946 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2947 -
2948 - // If persistence is on and history hasn't loaded yet, show loader
2949 - // while history loads to prevent flash of empty chat
2950 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2951 - if (chatContainer) chatContainer.style.display = 'none';
2952 - showInitLoader(botId);
2953 - loadChatHistory(botId, function() {
2954 - hideInitLoader(botId);
2955 - if (chatContainer) chatContainer.style.display = 'flex';
2956 - scrollToBottom(botId, true);
2957 - });
2958 - } else {
2959 - hideInitLoader(botId);
2960 - if (chatContainer) chatContainer.style.display = 'flex';
2961 - if (typeof loadChatHistory === 'function') {
2962 - loadChatHistory(botId);
2963 - }
2964 - }
2965 -}
2966 -
2967 -// ====================================
2968 -// EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2969 -// ====================================
2970 2640 // Only run email collection setup if it's enabled
2971 2641 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
2642 + // Email collection form setup and handlers
2643 + const emailForm = document.getElementById('email-collection-form');
2644 + const emailBlocker = document.getElementById('email-blocker');
2645 + const chatbotWrapper = document.getElementById('chat-container');
2972 2646
2973 - // Track submitting state per bot
2974 - const emailSubmittingState = {};
2647 + if (emailForm && emailBlocker && chatbotWrapper) {
2648 +
2649 + // Add loading state management
2650 + let isSubmitting = false;
2651 +
2652 + // Optimized UI transition functions
2653 + function showEmailForm() {
2654 + emailBlocker.style.display = 'flex';
2655 + chatbotWrapper.style.display = 'none';
2656 + }
2975 2657
2976 - // Add CSS animations for email form (once globally)
2977 - if (!document.getElementById('email-error-styles')) {
2978 - const style = document.createElement('style');
2979 - style.id = 'email-error-styles';
2980 - style.textContent = `
2981 - @keyframes fadeInError {
2982 - from { opacity: 0; transform: translateY(-5px); }
2983 - to { opacity: 1; transform: translateY(0); }
2658 + function showChatContainer() {
2659 + // Show chat immediately without delay
2660 + emailBlocker.style.display = 'none';
2661 + chatbotWrapper.style.display = 'flex';
2662 +
2663 + // Load chat history only after showing chat container
2664 + if (typeof loadChatHistory === 'function') {
2665 + loadChatHistory();
2984 2666 }
2985 - .email-input-shake {
2986 - animation: shake 0.5s ease-in-out;
2987 - }
2988 - @keyframes shake {
2989 - 0%, 100% { transform: translateX(0); }
2990 - 25% { transform: translateX(-5px); }
2991 - 75% { transform: translateX(5px); }
2992 - }
2993 - @keyframes spin {
2994 - from { transform: rotate(0deg); }
2995 - to { transform: rotate(360deg); }
2996 - }
2997 - .email-spinner {
2998 - display: inline-block;
2999 - vertical-align: middle;
3000 - }
3001 - `;
3002 - document.head.appendChild(style);
3003 - }
2667 + }
3004 2668
3005 - function isValidEmailAddress(email) {
3006 - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3007 - return emailRegex.test(email.trim()) && email.length <= 254;
3008 - }
2669 + // Enhanced email validation
2670 + function isValidEmail(email) {
2671 + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2672 + return emailRegex.test(email.trim()) && email.length <= 254; // RFC 5321 limit
2673 + }
3009 2674
3010 - function isValidNameInput(name) {
3011 - return name && name.trim().length >= 2 && name.trim().length <= 100;
3012 - }
2675 + // Enhanced name validation
2676 + function isValidName(name) {
2677 + return name && name.trim().length >= 2 && name.trim().length <= 100;
2678 + }
3013 2679
3014 - /**
3015 - * Replace {visitor_name} placeholder in intro message with actual visitor name
3016 - * @param {string} botId - The bot instance ID
3017 - * @param {string} visitorName - The visitor's name to insert
3018 - */
3019 - function replaceVisitorNamePlaceholder(botId, visitorName) {
3020 - var chatBox = getElementDOM(botId, 'chat-box');
3021 - if (!chatBox) return;
3022 -
3023 - // Find the first bot message (intro message)
3024 - var introMessage = chatBox.querySelector('.bot-message');
3025 - if (!introMessage) return;
3026 -
3027 - var messageContent = introMessage.querySelector('div[dir="auto"]');
3028 - if (!messageContent) return;
3029 -
3030 - var html = messageContent.innerHTML;
3031 -
3032 - // Replace {visitor_name} placeholder (case-insensitive)
3033 - if (visitorName && visitorName.trim()) {
3034 - // Escape HTML to prevent XSS
3035 - var safeName = $('<div>').text(visitorName.trim()).html();
3036 - html = html.replace(/\{visitor_name\}/gi, safeName);
3037 - } else {
3038 - // Remove placeholder and clean up spacing if no name provided
3039 - html = html.replace(/\{visitor_name\}/gi, '');
3040 - // Clean up any double spaces that might result
3041 - html = html.replace(/\s{2,}/g, ' ').trim();
2680 + // Show loading state with spinner
2681 + function setSubmissionState(loading) {
2682 + const submitButton = document.getElementById('email-submit-button');
2683 + const emailInput = document.getElementById('user-email');
2684 + const nameInput = document.getElementById('user-name');
2685 +
2686 + if (loading) {
2687 + isSubmitting = true;
2688 + if (submitButton) submitButton.disabled = true;
2689 + if (emailInput) emailInput.disabled = true;
2690 + if (nameInput) nameInput.disabled = true;
2691 +
2692 + // Store original content and add spinner
2693 + if (submitButton && !submitButton.getAttribute('data-original-html')) {
2694 + submitButton.setAttribute('data-original-html', submitButton.innerHTML);
2695 +
2696 + // Add loading spinner while keeping original text
2697 + const originalText = submitButton.textContent;
2698 + submitButton.innerHTML = `
2699 + <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
2700 + <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
2701 + <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
2702 + <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
2703 + </circle>
2704 + </svg>
2705 + ${originalText}
2706 + `;
2707 +
2708 + submitButton.style.opacity = '0.8';
2709 + }
2710 + } else {
2711 + isSubmitting = false;
2712 + if (submitButton) submitButton.disabled = false;
2713 + if (emailInput) emailInput.disabled = false;
2714 + if (nameInput) nameInput.disabled = false;
2715 +
2716 + // Restore original content
2717 + if (submitButton) {
2718 + const originalHtml = submitButton.getAttribute('data-original-html');
2719 + if (originalHtml) {
2720 + submitButton.innerHTML = originalHtml;
2721 + }
2722 + submitButton.style.opacity = '1';
2723 + }
2724 + }
3042 2725 }
3043 2726
3044 - messageContent.innerHTML = html;
3045 - }
3046 -
3047 - function setEmailSubmissionState(botId, loading) {
3048 - var submitButton = getElementDOM(botId, 'email-submit-button');
3049 - var emailInput = getElementDOM(botId, 'user-email');
3050 - var nameInput = getElementDOM(botId, 'user-name');
3051 -
3052 - if (loading) {
3053 - emailSubmittingState[botId] = true;
3054 - if (submitButton) submitButton.disabled = true;
3055 - if (emailInput) emailInput.disabled = true;
3056 - if (nameInput) nameInput.disabled = true;
3057 -
3058 - if (submitButton && !submitButton.getAttribute('data-original-html')) {
3059 - submitButton.setAttribute('data-original-html', submitButton.innerHTML);
3060 - const originalText = submitButton.textContent;
3061 - submitButton.innerHTML = `
3062 - <svg class="email-spinner" style="width: 16px; height: 16px; margin-right: 8px; animation: spin 1s linear infinite;" viewBox="0 0 24 24">
3063 - <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none" stroke-dasharray="31.416" stroke-dashoffset="31.416">
3064 - <animate attributeName="stroke-dasharray" dur="2s" values="0 31.416;15.708 15.708;0 31.416" repeatCount="indefinite"/>
3065 - <animate attributeName="stroke-dashoffset" dur="2s" values="0;-15.708;-31.416" repeatCount="indefinite"/>
3066 - </circle>
3067 - </svg>
3068 - ${originalText}
2727 + // Error display functions
2728 + function showEmailError(message) {
2729 + clearEmailError();
2730 +
2731 + const errorDiv = document.createElement('div');
2732 + errorDiv.className = 'email-error';
2733 + errorDiv.style.cssText = `
2734 + color: #e74c3c;
2735 + font-size: 12px;
2736 + margin-top: 8px;
2737 + padding: 4px 0;
2738 + animation: fadeInError 0.3s ease;
2739 + `;
2740 + errorDiv.textContent = message;
2741 +
2742 + // Add CSS animation if not already present
2743 + if (!document.getElementById('email-error-styles')) {
2744 + const style = document.createElement('style');
2745 + style.id = 'email-error-styles';
2746 + style.textContent = `
2747 + @keyframes fadeInError {
2748 + from { opacity: 0; transform: translateY(-5px); }
2749 + to { opacity: 1; transform: translateY(0); }
2750 + }
2751 + .email-input-shake {
2752 + animation: shake 0.5s ease-in-out;
2753 + }
2754 + @keyframes shake {
2755 + 0%, 100% { transform: translateX(0); }
2756 + 25% { transform: translateX(-5px); }
2757 + 75% { transform: translateX(5px); }
2758 + }
2759 + @keyframes spin {
2760 + from { transform: rotate(0deg); }
2761 + to { transform: rotate(360deg); }
2762 + }
2763 + .email-spinner {
2764 + display: inline-block;
2765 + vertical-align: middle;
2766 + }
3069 2767 `;
3070 - submitButton.style.opacity = '0.8';
2768 + document.head.appendChild(style);
3071 2769 }
3072 - } else {
3073 - emailSubmittingState[botId] = false;
3074 - if (submitButton) submitButton.disabled = false;
3075 - if (emailInput) emailInput.disabled = false;
3076 - if (nameInput) nameInput.disabled = false;
3077 -
3078 - if (submitButton) {
3079 - const originalHtml = submitButton.getAttribute('data-original-html');
3080 - if (originalHtml) {
3081 - submitButton.innerHTML = originalHtml;
3082 - }
3083 - submitButton.style.opacity = '1';
2770 +
2771 + emailForm.appendChild(errorDiv);
2772 +
2773 + // Add shake animation to inputs
2774 + const emailInput = document.getElementById('user-email');
2775 + const nameInput = document.getElementById('user-name');
2776 +
2777 + if (emailInput) {
2778 + emailInput.classList.add('email-input-shake');
2779 + setTimeout(() => {
2780 + emailInput.classList.remove('email-input-shake');
2781 + }, 500);
3084 2782 }
2783 +
2784 + if (nameInput) {
2785 + nameInput.classList.add('email-input-shake');
2786 + setTimeout(() => {
2787 + nameInput.classList.remove('email-input-shake');
2788 + }, 500);
2789 + }
3085 2790 }
3086 - }
3087 2791
3088 - function showEmailError(botId, message) {
3089 - clearEmailError(botId);
3090 -
3091 - var emailForm = getElementDOM(botId, 'email-collection-form');
3092 - if (!emailForm) return;
3093 -
3094 - const errorDiv = document.createElement('div');
3095 - errorDiv.className = 'email-error';
3096 - errorDiv.style.cssText = `
3097 - color: #e74c3c;
3098 - font-size: 12px;
3099 - margin-top: 8px;
3100 - padding: 4px 0;
3101 - animation: fadeInError 0.3s ease;
3102 - `;
3103 - errorDiv.textContent = message;
3104 - emailForm.appendChild(errorDiv);
3105 -
3106 - // Add shake animation to inputs
3107 - var emailInput = getElementDOM(botId, 'user-email');
3108 - var nameInput = getElementDOM(botId, 'user-name');
3109 -
3110 - if (emailInput) {
3111 - emailInput.classList.add('email-input-shake');
3112 - setTimeout(() => emailInput.classList.remove('email-input-shake'), 500);
3113 - }
3114 - if (nameInput) {
3115 - nameInput.classList.add('email-input-shake');
3116 - setTimeout(() => nameInput.classList.remove('email-input-shake'), 500);
3117 - }
3118 - }
3119 -
3120 - function clearEmailError(botId) {
3121 - var emailForm = getElementDOM(botId, 'email-collection-form');
3122 - if (emailForm) {
2792 + function clearEmailError() {
3123 2793 const existingErrors = emailForm.querySelectorAll('.email-error');
3124 2794 existingErrors.forEach(error => error.remove());
3125 2795 }
3126 - }
3127 2796
3128 - // Resolve email state using server-side data when available, AJAX fallback otherwise
3129 - function resolveEmailState(botId) {
3130 - if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3131 - if (mxchatChat.initial_email_state.show_email_form) {
3132 - showEmailFormForBot(botId);
3133 - } else {
3134 - showChatContainerForBot(botId);
3135 - }
3136 - } else {
3137 - checkSessionAndEmailForBot(botId);
3138 - }
3139 - }
2797 + // MAIN FORM SUBMIT HANDLER
2798 + // Remove any existing event listeners first
2799 + emailForm.removeEventListener('submit', handleFormSubmit);
3140 2800
3141 - function checkSessionAndEmailForBot(botId) {
3142 - const sessionId = MxChatInstances.ensureSession(botId);
2801 + // Add the form submit handler
2802 + emailForm.addEventListener('submit', handleFormSubmit);
3143 2803
3144 - // Hide both panels while we check — show loader instead
3145 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3146 - var chatContainer = getElementDOM(botId, 'chat-container');
3147 - if (emailBlocker) emailBlocker.style.display = 'none';
3148 - if (chatContainer) chatContainer.style.display = 'none';
3149 - showInitLoader(botId);
2804 + function handleFormSubmit(event) {
2805 + event.preventDefault();
2806 + event.stopPropagation();
3150 2807
3151 - fetch(mxchatChat.ajax_url, {
3152 - method: 'POST',
3153 - headers: {
3154 - 'Content-Type': 'application/x-www-form-urlencoded',
3155 - },
3156 - body: new URLSearchParams({
3157 - action: 'mxchat_check_email_provided',
3158 - session_id: sessionId,
3159 - nonce: mxchatChat.nonce,
3160 - })
3161 - })
3162 - .then((response) => {
3163 - if (!response.ok) {
3164 - throw new Error(`HTTP error! status: ${response.status}`);
2808 + // Prevent double submission
2809 + if (isSubmitting) {
2810 + return false;
3165 2811 }
3166 - return response.json();
3167 - })
3168 - .then((data) => {
3169 - if (data.success) {
3170 - if (data.data.logged_in || data.data.email) {
3171 - showChatContainerForBot(botId);
3172 - } else {
3173 - showEmailFormForBot(botId);
3174 - }
3175 - } else {
3176 - showEmailFormForBot(botId);
3177 - }
3178 - })
3179 - .catch((error) => {
3180 - showEmailFormForBot(botId);
3181 - });
3182 - }
3183 2812
3184 - // Event delegation for email form submission
3185 - $(document).on('submit', '.email-collection-form', function(e) {
3186 - e.preventDefault();
3187 - e.stopPropagation();
2813 + const userEmail = document.getElementById('user-email').value.trim();
2814 + const nameInput = document.getElementById('user-name');
2815 + const userName = nameInput ? nameInput.value.trim() : '';
2816 + const sessionId = getChatSession();
3188 2817
3189 - var botId = getBotIdFromElement(this);
2818 + // Validate email before submission
2819 + if (!userEmail) {
2820 + showEmailError('Please enter your email address.');
2821 + return false;
2822 + }
3190 2823
3191 - // Prevent double submission
3192 - if (emailSubmittingState[botId]) {
3193 - return false;
3194 - }
2824 + if (!isValidEmail(userEmail)) {
2825 + showEmailError('Please enter a valid email address.');
2826 + return false;
2827 + }
3195 2828
3196 - var emailInput = getElementDOM(botId, 'user-email');
3197 - var nameInput = getElementDOM(botId, 'user-name');
3198 - var userEmail = emailInput ? emailInput.value.trim() : '';
3199 - var userName = nameInput ? nameInput.value.trim() : '';
3200 - var sessionId = MxChatInstances.ensureSession(botId);
2829 + // Validate name if field exists
2830 + if (nameInput && !isValidName(userName)) {
2831 + showEmailError('Please enter a valid name (2-100 characters).');
2832 + return false;
2833 + }
3201 2834
3202 - // Validate email
3203 - if (!userEmail) {
3204 - showEmailError(botId, 'Please enter your email address.');
3205 - return false;
3206 - }
2835 + // Clear any existing errors
2836 + clearEmailError();
2837 + setSubmissionState(true);
3207 2838
3208 - if (!isValidEmailAddress(userEmail)) {
3209 - showEmailError(botId, 'Please enter a valid email address.');
3210 - return false;
3211 - }
2839 + // Prepare form data with optional name
2840 + const formData = new URLSearchParams({
2841 + action: 'mxchat_handle_save_email_and_response',
2842 + email: userEmail,
2843 + session_id: sessionId,
2844 + nonce: mxchatChat.nonce,
2845 + });
3212 2846
3213 - // Validate name if field exists and has content
3214 - if (nameInput && userName && !isValidNameInput(userName)) {
3215 - showEmailError(botId, 'Please enter a valid name (2-100 characters).');
3216 - return false;
3217 - }
2847 + // Add name to form data if provided
2848 + if (userName) {
2849 + formData.append('name', userName);
2850 + }
3218 2851
3219 - clearEmailError(botId);
3220 - setEmailSubmissionState(botId, true);
2852 + fetch(mxchatChat.ajax_url, {
2853 + method: 'POST',
2854 + headers: {
2855 + 'Content-Type': 'application/x-www-form-urlencoded',
2856 + },
2857 + body: formData
2858 + })
2859 + .then((response) => {
2860 + if (!response.ok) {
2861 + throw new Error(`HTTP error! status: ${response.status}`);
2862 + }
2863 + return response.json();
2864 + })
2865 + .then((data) => {
2866 + setSubmissionState(false);
3221 2867
3222 - // Prepare form data
3223 - const formData = new URLSearchParams({
3224 - action: 'mxchat_handle_save_email_and_response',
3225 - email: userEmail,
3226 - session_id: sessionId,
3227 - nonce: mxchatChat.nonce,
3228 - });
2868 + if (data.success) {
2869 + // Show chat immediately
2870 + showChatContainer();
3229 2871
3230 - if (userName) {
3231 - formData.append('name', userName);
2872 + // Handle bot response if provided
2873 + if (data.message && typeof appendMessage === 'function') {
2874 + setTimeout(() => {
2875 + appendMessage('bot', data.message);
2876 + if (typeof scrollToBottom === 'function') {
2877 + scrollToBottom();
2878 + }
2879 + }, 100);
2880 + }
2881 + } else {
2882 + showEmailError(data.message || 'Failed to save email. Please try again.');
2883 + }
2884 + })
2885 + .catch((error) => {
2886 + setSubmissionState(false);
2887 + showEmailError('An error occurred. Please try again.');
2888 + });
2889 +
2890 + return false; // Extra prevention
3232 2891 }
3233 2892
3234 - fetch(mxchatChat.ajax_url, {
3235 - method: 'POST',
3236 - headers: {
3237 - 'Content-Type': 'application/x-www-form-urlencoded',
3238 - },
3239 - body: formData
3240 - })
3241 - .then((response) => {
3242 - if (!response.ok) {
3243 - throw new Error(`HTTP error! status: ${response.status}`);
3244 - }
3245 - return response.json();
3246 - })
3247 - .then((data) => {
3248 - setEmailSubmissionState(botId, false);
2893 + // Real-time email validation
2894 + const emailInput = document.getElementById('user-email');
2895 + if (emailInput) {
2896 + let validationTimeout;
2897 +
2898 + emailInput.addEventListener('input', function() {
2899 + // Clear previous validation timeout
2900 + if (validationTimeout) {
2901 + clearTimeout(validationTimeout);
2902 + }
2903 +
2904 + // Debounce validation
2905 + validationTimeout = setTimeout(() => {
2906 + const email = this.value.trim();
2907 + clearEmailError();
2908 +
2909 + if (email && !isValidEmail(email)) {
2910 + showEmailError('Please enter a valid email address.');
2911 + }
2912 + }, 500);
2913 + });
3249 2914
3250 - if (data.success) {
3251 - showChatContainerForBot(botId);
2915 + // Handle Enter key
2916 + emailInput.addEventListener('keypress', function(e) {
2917 + if (e.key === 'Enter' && !isSubmitting) {
2918 + e.preventDefault();
2919 + emailForm.dispatchEvent(new Event('submit'));
2920 + }
2921 + });
2922 + }
3252 2923
3253 - // Replace {visitor_name} placeholder in intro message with actual name
3254 - if (userName) {
3255 - replaceVisitorNamePlaceholder(botId, userName);
3256 - } else {
3257 - // Remove placeholder if no name provided
3258 - replaceVisitorNamePlaceholder(botId, '');
2924 + // Real-time name validation
2925 + const nameInput = document.getElementById('user-name');
2926 + if (nameInput) {
2927 + let nameValidationTimeout;
2928 +
2929 + nameInput.addEventListener('input', function() {
2930 + // Clear previous validation timeout
2931 + if (nameValidationTimeout) {
2932 + clearTimeout(nameValidationTimeout);
3259 2933 }
2934 +
2935 + // Debounce validation
2936 + nameValidationTimeout = setTimeout(() => {
2937 + const name = this.value.trim();
2938 + clearEmailError();
2939 +
2940 + if (name && !isValidName(name)) {
2941 + showEmailError('Name must be between 2 and 100 characters.');
2942 + }
2943 + }, 500);
2944 + });
3260 2945
3261 - if (data.message && typeof appendMessage === 'function') {
3262 - setTimeout(() => {
3263 - appendMessage('bot', data.message, '', [], false, botId);
3264 - if (typeof scrollToBottom === 'function') {
3265 - scrollToBottom(botId);
3266 - }
3267 - }, 100);
2946 + // Handle Enter key
2947 + nameInput.addEventListener('keypress', function(e) {
2948 + if (e.key === 'Enter' && !isSubmitting) {
2949 + e.preventDefault();
2950 + emailForm.dispatchEvent(new Event('submit'));
3268 2951 }
2952 + });
2953 + }
2954 +
2955 + // Initial state check
2956 + if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
2957 + const emailState = mxchatChat.initial_email_state;
2958 + if (emailState.show_email_form) {
2959 + showEmailForm();
3269 2960 } else {
3270 - showEmailError(botId, data.message || 'Failed to save email. Please try again.');
2961 + showChatContainer();
3271 2962 }
3272 - })
3273 - .catch((error) => {
3274 - setEmailSubmissionState(botId, false);
3275 - showEmailError(botId, 'An error occurred. Please try again.');
3276 - });
3277 -
3278 - return false;
3279 - });
3280 -
3281 - // Real-time email validation using event delegation
3282 - $(document).on('input', '.mxchat-email-input', function() {
3283 - var botId = getBotIdFromElement(this);
3284 - var $input = $(this);
3285 -
3286 - // Clear previous timeout
3287 - clearTimeout($input.data('validationTimeout'));
3288 -
3289 - // Debounce validation
3290 - var timeout = setTimeout(() => {
3291 - var email = this.value.trim();
3292 - clearEmailError(botId);
3293 -
3294 - if (email && !isValidEmailAddress(email)) {
3295 - showEmailError(botId, 'Please enter a valid email address.');
3296 - }
3297 - }, 500);
3298 -
3299 - $input.data('validationTimeout', timeout);
3300 - });
3301 -
3302 - // Handle Enter key in email input
3303 - $(document).on('keypress', '.mxchat-email-input', function(e) {
3304 - if (e.key === 'Enter') {
3305 - e.preventDefault();
3306 - var botId = getBotIdFromElement(this);
3307 - if (!emailSubmittingState[botId]) {
3308 - $(this).closest('.email-collection-form').submit();
3309 - }
2963 + } else {
2964 + // Check email status via AJAX
2965 + setTimeout(checkSessionAndEmail, 100);
3310 2966 }
3311 - });
3312 2967
3313 - // Handle Enter key in name input
3314 - $(document).on('keypress', '.mxchat-name-input', function(e) {
3315 - if (e.key === 'Enter') {
3316 - e.preventDefault();
3317 - var botId = getBotIdFromElement(this);
3318 - if (!emailSubmittingState[botId]) {
3319 - $(this).closest('.email-collection-form').submit();
3320 - }
2968 + // Check if email exists for the current session
2969 + function checkSessionAndEmail() {
2970 + const sessionId = getChatSession();
2971 +
2972 + fetch(mxchatChat.ajax_url, {
2973 + method: 'POST',
2974 + headers: {
2975 + 'Content-Type': 'application/x-www-form-urlencoded',
2976 + },
2977 + body: new URLSearchParams({
2978 + action: 'mxchat_check_email_provided',
2979 + session_id: sessionId,
2980 + nonce: mxchatChat.nonce,
2981 + })
2982 + })
2983 + .then((response) => {
2984 + if (!response.ok) {
2985 + throw new Error(`HTTP error! status: ${response.status}`);
2986 + }
2987 + return response.json();
2988 + })
2989 + .then((data) => {
2990 + if (data.success) {
2991 + if (data.data.logged_in || data.data.email) {
2992 + showChatContainer();
2993 + } else {
2994 + showEmailForm();
2995 + }
2996 + } else {
2997 + // On error, default to showing email form
2998 + showEmailForm();
2999 + }
3000 + })
3001 + .catch((error) => {
3002 + // Email check failed - default to email form
3003 + showEmailForm();
3004 + });
3321 3005 }
3322 - });
3323 3006
3324 - // Initialize email check for all bot instances
3325 - // For floating bots: defer until widget is opened (zero passive AJAX)
3326 - // For embedded bots: check immediately since the form is visible
3327 - $('.mxchat-chatbot-wrapper').each(function() {
3328 - var botId = $(this).data('bot-id') || 'default';
3329 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3330 -
3331 - if (emailBlocker) {
3332 - if (isEmbeddedBot(botId)) {
3333 - // Embedded bots are always visible — check now
3334 - resolveEmailState(botId);
3335 - }
3336 - // Floating bots: handled in the widget open handler
3337 - } else if (isEmbeddedBot(botId)) {
3338 - // Embedded bot, no email collection — load history with loader
3339 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3340 - if (chatPersistenceEnabled) {
3341 - MxChatInstances.ensureSession(botId);
3342 - showChatContainerForBot(botId);
3343 - }
3344 - }
3345 - });
3007 + } else {
3008 + // Email collection is enabled but essential elements are missing - silently continue
3009 + }
3346 3010 }
3347 3011
3348 3012 // Open chatbot when pre-chat message is clicked - use class selector for multi-instance
3349 3013 $(document).on('click', '.pre-chat-message', function() {
@@ -3351,32 +3015,39 @@
3351 3015 var $chatbot = getElement(botId, 'floating-chatbot');
3352 3016 if ($chatbot.hasClass('hidden')) {
3353 3017 $chatbot.removeClass('hidden').addClass('visible');
3354 3018 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3355 - handlePreChatDismissal(botId);
3019 + $(this).fadeOut(250); // Hide pre-chat message
3356 3020 disableScroll(); // Disable scroll when chatbot opens
3021 + }
3022 + });
3357 3023
3358 - // Load chat history for returning visitors (persistence)
3359 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3360 - if (chatPersistenceEnabled) {
3361 - MxChatInstances.ensureSession(botId);
3362 - }
3024 + // Dismiss pre-chat message via close button - handled by event delegation above at line ~2376
3025 + // This is a fallback for legacy support
3026 + $(document).on('click', '.close-pre-chat-message', function() {
3027 + var botId = getBotIdFromElement(this);
3028 + var $preChat = getElement(botId, 'pre-chat-message');
3029 + $preChat.fadeOut(200); // Hide the message
3363 3030
3364 - // Deferred email check — only on first widget open
3365 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3366 - var instance = MxChatInstances.get(botId);
3367 - if (emailBlocker && !instance.emailCheckDone) {
3368 - instance.emailCheckDone = true;
3369 - resolveEmailState(botId);
3370 - } else if (!emailBlocker) {
3371 - showChatContainerForBot(botId);
3031 + // Send an AJAX request to set the transient flag for 24 hours
3032 + $.ajax({
3033 + url: mxchatChat.ajax_url,
3034 + type: 'POST',
3035 + data: {
3036 + action: 'mxchat_dismiss_pre_chat_message',
3037 + _ajax_nonce: mxchatChat.nonce
3038 + },
3039 + success: function() {
3040 + // Ensure the message is hidden after dismissal
3041 + $preChat.hide();
3042 + },
3043 + error: function() {
3044 + // Error dismissing pre-chat message - silently continue
3372 3045 }
3373 - }
3046 + });
3374 3047 });
3375 3048
3376 - // Legacy duplicate close handler removed — handled by single event delegation above
3377 3049
3378 -
3379 3050 function hasQuickQuestions(botId) {
3380 3051 botId = botId || 'default';
3381 3052 var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3382 3053 if (!questionsContainer) return false;
@@ -3512,11 +3183,18 @@
3512 3183 });
3513 3184
3514 3185 // Initialize when document is ready
3515 3186 setFullHeight();
3187 + trackOriginatingPage();
3516 3188
3517 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3518 - // until the user's first interaction via MxChatInstances.ensureSession()
3189 + // Only load chat history if email collection is disabled
3190 + if (mxchatChat.email_collection_enabled !== 'on') {
3191 + // Load history for all instances
3192 + $('.mxchat-chatbot-wrapper').each(function() {
3193 + var botId = $(this).data('bot-id') || 'default';
3194 + loadChatHistory(botId);
3195 + });
3196 + }
3519 3197
3520 3198 // Initialize chat visibility for all instances
3521 3199 $('.mxchat-chatbot-wrapper').each(function() {
3522 3200 var botId = $(this).data('bot-id') || 'default';