PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.9
MxChat – AI Chatbot & Content Generation for WordPress v3.0.9
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 +204 -506 3.2.43.0.9 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
@@ -33,9 +16,9 @@
33 16 var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
34 17
35 18 this.instances[botId] = {
36 19 botId: botId,
37 - sessionId: null,
20 + sessionId: this.getChatSession(botId),
38 21 lastSeenMessageId: '',
39 22 notificationCheckInterval: null,
40 23 pollingInterval: null,
41 24 processedMessageIds: new Set(),
@@ -60,77 +43,23 @@
60 43 return Object.keys(this.instances);
61 44 },
62 45
63 46 // 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 47 getChatSession: function(botId) {
67 48 var cookieName = 'mxchat_session_id_' + botId;
68 - var storageKey = 'mxchat_session_id_' + botId;
69 49 var sessionId = getCookie(cookieName);
70 50
71 - // Fallback to localStorage if cookie is missing (e.g. cleared by browser/consent)
72 51 if (!sessionId) {
73 - try { sessionId = localStorage.getItem(storageKey); } catch (e) {}
52 + sessionId = generateSessionId();
53 + this.setChatSession(botId, sessionId);
74 54 }
75 55
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;
56 + return sessionId;
95 57 },
96 58
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 59 setChatSession: function(botId, sessionId) {
129 60 var cookieName = 'mxchat_session_id_' + botId;
130 - var storageKey = 'mxchat_session_id_' + botId;
131 61 document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
132 - try { localStorage.setItem(storageKey, sessionId); } catch (e) {}
133 62 if (this.instances[botId]) {
134 63 this.instances[botId].sessionId = sessionId;
135 64 }
136 65 },
@@ -135,10 +64,8 @@
135 64 }
136 65 },
137 66
138 67 resetChatSession: function(botId) {
139 - // Clear old session from localStorage before setting new one
140 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
141 68 var newSessionId = generateSessionId();
142 69 this.setChatSession(botId, newSessionId);
143 70 var $chatBox = getElement(botId, 'chat-box');
144 71 if ($chatBox.length) {
@@ -147,20 +74,8 @@
147 74 if (this.instances[botId]) {
148 75 this.instances[botId].chatHistoryLoaded = false;
149 76 this.instances[botId].processedMessageIds = new Set();
150 77 }
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 78 }
164 79 };
165 80
166 81 // ====================================
@@ -465,9 +380,8 @@
465 380
466 381 // Update your existing sendMessage function
467 382 function sendMessage(botId) {
468 383 botId = botId || 'default';
469 - MxChatInstances.ensureSession(botId);
470 384 var $chatInput = getElement(botId, 'chat-input');
471 385 var message = $chatInput.val();
472 386
473 387 // ADD PROMPT HOOK HERE
@@ -475,14 +389,10 @@
475 389 message = customMxChatFilter(message, "prompt");
476 390 }
477 391
478 392 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 - }
393 + // Disable input while waiting for response
394 + disableChatInput(botId);
485 395
486 396 appendMessage("user", message, '', [], false, botId);
487 397 $chatInput.val('');
488 398 $chatInput.css('height', 'auto');
@@ -510,9 +420,8 @@
510 420
511 421 // Update your existing sendMessageToChatbot function
512 422 function sendMessageToChatbot(message, botId) {
513 423 botId = botId || 'default';
514 - MxChatInstances.ensureSession(botId);
515 424
516 425 // ADD PROMPT HOOK HERE
517 426 if (typeof customMxChatFilter === 'function') {
518 427 message = customMxChatFilter(message, "prompt");
@@ -517,14 +426,10 @@
517 426 if (typeof customMxChatFilter === 'function') {
518 427 message = customMxChatFilter(message, "prompt");
519 428 }
520 429
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 - }
430 + // Disable input while waiting for response
431 + disableChatInput(botId);
527 432
528 433 var sessionId = getChatSession(botId);
529 434
530 435 if (hasQuickQuestions(botId)) {
@@ -612,23 +517,13 @@
612 517
613 518 // Get instance for session start timestamp (used when persistence is OFF)
614 519 var instance = MxChatInstances.get(botId);
615 520
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 521 // Prepare AJAX data
627 522 const ajaxData = {
628 523 action: 'mxchat_handle_chat_request',
629 524 message: message,
630 - session_id: sessionId,
525 + session_id: getChatSession(botId),
631 526 nonce: mxchatChat.nonce,
632 527 current_page_url: window.location.href,
633 528 current_page_title: document.title,
634 529 bot_id: botId,
@@ -690,16 +585,23 @@
690 585 errorMessage = "An error occurred. Please try again or contact support.";
691 586 }
692 587
693 588 // Handle session reset action (IP changed, session expired, etc.)
694 - // Silent reset — keep chat UI intact, just get a new session and retry
695 589 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)
590 + // Clear the old session and generate a new one
591 + resetChatSession(botId);
592 + // Remove the temporary loading message
593 + getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
594 + // Re-send the original message with the new session
698 595 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
699 596 if (originalMessage) {
700 597 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
701 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
598 + // Re-add the user message and thinking indicator
599 + appendMessage("user", originalMessage, '', [], false, botId);
600 + appendThinkingMessage(botId);
601 + scrollToBottom(botId);
602 + // Determine whether to use streaming
603 + const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
702 604 if (shouldUseStreaming(currentModel)) {
703 605 callMxChatStream(originalMessage, function(response) {
704 606 getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
705 607 }, botId);
@@ -756,11 +658,9 @@
756 658 }
757 659
758 660 // Check for live agent response
759 661 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
760 - removeThinkingDots(botId);
761 662 updateChatModeIndicator('agent', botId);
762 - enableChatInput(botId);
763 663 return;
764 664 }
765 665
766 666 // Handle the message and show notification if chat is hidden
@@ -793,13 +693,9 @@
793 693 $badge.show();
794 694 }
795 695 }
796 696 } 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);
697 + replaceLastMessage("bot", "I received an empty response. Please try again or contact support if this persists.", '', [], botId);
802 698 }
803 699
804 700 if (response.message_id) {
805 701 var instance = MxChatInstances.get(botId);
@@ -861,22 +757,12 @@
861 757
862 758 // Get instance for session start timestamp (used when persistence is OFF)
863 759 var instance = MxChatInstances.get(botId);
864 760
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 761 const formData = new FormData();
876 762 formData.append('action', 'mxchat_stream_chat');
877 763 formData.append('message', message);
878 - formData.append('session_id', streamSessionId);
764 + formData.append('session_id', getChatSession(botId));
879 765 formData.append('nonce', mxchatChat.nonce);
880 766 formData.append('current_page_url', window.location.href);
881 767 formData.append('current_page_title', document.title);
882 768 formData.append('bot_id', botId);
@@ -976,16 +862,8 @@
976 862
977 863 // Re-enable chat input when stream ends with content
978 864 enableChatInput(botId);
979 865
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 866 if (callback) {
989 867 callback(accumulatedContent);
990 868 }
991 869 return;
@@ -1008,16 +886,8 @@
1008 886
1009 887 // Re-enable chat input after streaming completes
1010 888 enableChatInput(botId);
1011 889
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 890 if (callback) {
1021 891 callback(accumulatedContent);
1022 892 }
1023 893 return;
@@ -1136,16 +1006,21 @@
1136 1006 errorMessage = "An error occurred. Please try again or contact support.";
1137 1007 }
1138 1008
1139 1009 // Handle session reset action (IP changed, session expired, etc.)
1140 - // Silent reset — keep chat UI intact, just get a new session and retry
1141 1010 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)
1011 + // Clear the old session and generate a new one
1012 + resetChatSession(botId);
1013 + // Re-send the original message with the new session
1144 1014 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1145 1015 if (originalMessage) {
1146 1016 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1147 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1017 + // Re-add the user message and thinking indicator
1018 + appendMessage("user", originalMessage, '', [], false, botId);
1019 + appendThinkingMessage(botId);
1020 + scrollToBottom(botId);
1021 + // Determine whether to use streaming
1022 + const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1148 1023 if (shouldUseStreaming(currentModel)) {
1149 1024 callMxChatStream(originalMessage, callback, botId);
1150 1025 } else {
1151 1026 callMxChat(originalMessage, callback, botId);
@@ -1167,22 +1042,8 @@
1167 1042 }
1168 1043 return; // Exit early for errors
1169 1044 }
1170 1045
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 1046 // Handle different response formats
1186 1047 if (data.text || data.html || data.message) {
1187 1048
1188 1049 // Apply response hooks
@@ -1227,15 +1088,19 @@
1227 1088 }
1228 1089
1229 1090 // Enhanced updateChatModeIndicator function for immediate DOM updates
1230 1091 function updateChatModeIndicator(mode, botId) {
1092 + console.log('[MxChat] updateChatModeIndicator called with mode:', mode, 'botId:', botId);
1231 1093 botId = botId || 'default';
1232 1094 const indicator = getElementDOM(botId, 'chat-mode-indicator');
1095 + console.log('[MxChat] chat-mode-indicator element found:', !!indicator);
1233 1096 if (indicator) {
1234 1097 const oldText = indicator.textContent;
1098 + console.log('[MxChat] Current indicator text:', oldText, '-> changing to mode:', mode);
1235 1099
1236 1100 if (mode === 'agent') {
1237 1101 indicator.textContent = 'Live Agent';
1102 + console.log('[MxChat] Mode is agent, calling startPolling...');
1238 1103 startPolling(botId);
1239 1104 } else {
1240 1105 // Everything else is AI mode
1241 1106 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
@@ -1306,12 +1171,9 @@
1306 1171 // Update the event handlers to use the correct function names (using event delegation)
1307 1172 // Use class-based selectors for multi-instance support
1308 1173 $(document).on('click', '.send-button', function() {
1309 1174 var botId = getBotIdFromElement(this);
1310 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1311 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1312 - disableChatInput(botId);
1313 - }
1175 + disableChatInput(botId);
1314 1176 sendMessage(botId);
1315 1177 });
1316 1178
1317 1179 // Override enter key handler (using event delegation)
@@ -1318,12 +1180,9 @@
1318 1180 $(document).on('keypress', '.chat-input', function(e) {
1319 1181 if (e.which == 13 && !e.shiftKey) {
1320 1182 e.preventDefault();
1321 1183 var botId = getBotIdFromElement(this);
1322 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1323 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1324 - disableChatInput(botId);
1325 - }
1184 + disableChatInput(botId);
1326 1185 sendMessage(botId);
1327 1186 }
1328 1187 });
1329 1188
@@ -1366,12 +1225,17 @@
1366 1225 'margin-bottom': '1em'
1367 1226 });
1368 1227 }
1369 1228
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);
1229 + // Process the message content based on sender
1230 + let fullMessage;
1231 + if (sender === "user") {
1232 + // For user messages, apply linkify after sanitization
1233 + fullMessage = linkify(messageText);
1234 + } else {
1235 + // For bot/agent messages, preserve HTML
1236 + fullMessage = messageText;
1237 + }
1374 1238
1375 1239 // Add images if provided
1376 1240 if (images && images.length > 0) {
1377 1241 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1506,12 +1370,26 @@
1506 1370 bgColor = botMessageBgColor;
1507 1371 fontColor = botMessageFontColor;
1508 1372 }
1509 1373
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);
1374 + // FIXED: Only linkify if response doesn't already contain HTML links or tags
1375 + // This prevents double-processing of URLs that are already formatted as HTML
1376 + var fullMessage;
1377 + if (sender === "user") {
1378 + // Always linkify user messages (they're plain text)
1379 + fullMessage = linkify(responseText);
1380 + } else {
1381 + // For bot/agent messages, check if HTML already exists
1382 + if (responseText.includes('<a href=') || responseText.includes('</a>') ||
1383 + responseText.includes('<img') || responseText.includes('<div') ||
1384 + responseText.includes('<p>') || responseText.includes('<br>')) {
1385 + // Response already has HTML, don't process it
1386 + fullMessage = responseText;
1387 + } else {
1388 + // Plain text response, apply linkify
1389 + fullMessage = linkify(responseText);
1390 + }
1391 + }
1514 1392
1515 1393 if (responseHtml) {
1516 1394 // Only add line breaks if there's actual text content before the HTML
1517 1395 if (fullMessage && fullMessage.trim()) {
@@ -1578,15 +1456,8 @@
1578 1456
1579 1457
1580 1458 function appendThinkingMessage(botId) {
1581 1459 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 1460 var $chatBox = getElement(botId, 'chat-box');
1590 1461
1591 1462 // Remove any existing thinking dots in this bot's chat first
1592 1463 $chatBox.find('.thinking-dots').remove();
@@ -1608,9 +1479,9 @@
1608 1479 '</div>' +
1609 1480 '</div>';
1610 1481
1611 1482 // 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 + ';"';
1483 + var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + ';"';
1613 1484 $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1614 1485 scrollToBottom(botId);
1615 1486 }
1616 1487
@@ -1616,11 +1487,9 @@
1616 1487
1617 1488 function removeThinkingDots(botId) {
1618 1489 botId = botId || 'default';
1619 1490 var $chatBox = getElement(botId, 'chat-box');
1620 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1621 1491 $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1622 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1623 1492 }
1624 1493
1625 1494 // ====================================
1626 1495 // TEXT FORMATTING & PROCESSING
@@ -1654,12 +1523,9 @@
1654 1523 processedText = formatTextStyling(processedText);
1655 1524
1656 1525 // Process code blocks BEFORE processing links
1657 1526 processedText = formatCodeBlocks(processedText);
1658 -
1659 - // Process markdown tables BEFORE converting newlines to paragraphs
1660 - processedText = formatMarkdownTables(processedText);
1661 -
1527 +
1662 1528 // NOW convert to paragraphs
1663 1529 processedText = convertNewlinesToBreaks(processedText);
1664 1530
1665 1531 // IMPORTANT: Handle citation-style brackets FIRST [URL]
@@ -1672,63 +1538,37 @@
1672 1538 // Return as a proper link without the brackets
1673 1539 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1674 1540 });
1675 1541
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 - }
1542 + // Process proper markdown links with text: [text](url)
1543 + // This MUST have non-empty text in the first brackets
1544 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
1545 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1546 + // Make sure we have actual text (not just whitespace)
1547 + if (!text || !text.trim()) {
1548 + // If no text, treat the URL as the text
1549 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1550 + const safeUrl = safeEncodeUrl(cleanUrl);
1551 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1727 1552 }
1728 - return result;
1729 - })(processedText);
1553 +
1554 + // Clean the URL
1555 + let cleanUrl = url.replace(/[\].,;!?]+$/, '');
1556 + const safeUrl = safeEncodeUrl(cleanUrl);
1557 + const safeText = sanitizeUserInput(text);
1558 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1559 + });
1730 1560
1561 + // Handle empty markdown links: [](url)
1562 + // This is a specific case where there's no text
1563 + const emptyMarkdownPattern = /\[\]\((https?:\/\/[^\s)]+)\)/g;
1564 + processedText = processedText.replace(emptyMarkdownPattern, (match, url) => {
1565 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1566 + const safeUrl = safeEncodeUrl(cleanUrl);
1567 + // Use the URL itself as the link text
1568 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1569 + });
1570 +
1731 1571 // Process phone numbers: [text](tel:number)
1732 1572 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1733 1573 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1734 1574 const safePhone = safeEncodeUrl(phone);
@@ -1880,78 +1720,9 @@
1880 1720 });
1881 1721
1882 1722 return text;
1883 1723 }
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 -
1724 +
1954 1725 function sanitizeUserInput(text) {
1955 1726 const div = document.createElement('div');
1956 1727 div.textContent = text;
1957 1728 return div.innerHTML;
@@ -2022,14 +1793,13 @@
2022 1793 requestAnimationFrame(smoothScroll);
2023 1794 }
2024 1795 }
2025 1796
2026 - function scrollElementToTop(element, botId, topOffset) {
1797 + function scrollElementToTop(element, botId) {
2027 1798 botId = botId || 'default';
2028 - topOffset = (typeof topOffset === 'number') ? topOffset : 2;
2029 1799 var chatBox = getElement(botId, 'chat-box');
2030 1800 var elementTop = element.position().top + chatBox.scrollTop();
2031 - chatBox.animate({ scrollTop: Math.max(0, elementTop - topOffset) }, 500);
1801 + chatBox.animate({ scrollTop: elementTop }, 500);
2032 1802 }
2033 1803
2034 1804 function showChatWidget(botId) {
2035 1805 botId = botId || 'default';
@@ -2173,12 +1943,15 @@
2173 1943 // LIVE AGENT FUNCTIONALITY
2174 1944 // ====================================
2175 1945
2176 1946 function startPolling(botId) {
1947 + console.log('[MxChat] startPolling called for botId:', botId);
2177 1948 botId = botId || 'default';
2178 1949 var instance = MxChatInstances.get(botId);
2179 1950 // Clear any existing interval first
2180 1951 stopPolling(botId);
1952 + // Start new polling interval
1953 + console.log('[MxChat] Starting polling interval (5s) for botId:', botId);
2181 1954 instance.pollingInterval = setInterval(function() {
2182 1955 checkForAgentMessages(botId);
2183 1956 }, 5000);
2184 1957 }
@@ -2183,17 +1956,20 @@
2183 1956 }, 5000);
2184 1957 }
2185 1958
2186 1959 function stopPolling(botId) {
1960 + console.log('[MxChat] stopPolling called for botId:', botId);
2187 1961 botId = botId || 'default';
2188 1962 var instance = MxChatInstances.get(botId);
2189 1963 if (instance.pollingInterval) {
2190 1964 clearInterval(instance.pollingInterval);
2191 1965 instance.pollingInterval = null;
1966 + console.log('[MxChat] Polling stopped for botId:', botId);
2192 1967 }
2193 1968 }
2194 1969
2195 1970 function checkForAgentMessages(botId) {
1971 + console.log('[MxChat] checkForAgentMessages called for botId:', botId);
2196 1972 botId = botId || 'default';
2197 1973 var instance = MxChatInstances.get(botId);
2198 1974 const sessionId = getChatSession(botId);
2199 1975 $.ajax({
@@ -2219,12 +1995,8 @@
2219 1995 instance.processedMessageIds.add(message.id);
2220 1996 }
2221 1997 });
2222 1998
2223 - if (hasNewMessage) {
2224 - enableChatInput(botId);
2225 - }
2226 -
2227 1999 var $floatingChatbot = getElement(botId, 'floating-chatbot');
2228 2000 if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2229 2001 showNotification(botId);
2230 2002 }
@@ -2230,13 +2002,8 @@
2230 2002 }
2231 2003
2232 2004 scrollToBottom(botId, true);
2233 2005 }
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 2006 },
2240 2007 error: function (xhr, status, error) {
2241 2008 // Polling error - silently continue
2242 2009 }
@@ -2246,29 +2013,20 @@
2246 2013 // ====================================
2247 2014 // CHAT HISTORY & PERSISTENCE
2248 2015 // ====================================
2249 2016
2250 -function loadChatHistory(botId, onComplete) {
2017 +function loadChatHistory(botId) {
2251 2018 botId = botId || 'default';
2252 2019 var instance = MxChatInstances.get(botId);
2253 2020
2254 2021 // Prevent duplicate loading
2255 2022 if (instance.chatHistoryLoaded) {
2256 - if (onComplete) onComplete();
2257 2023 return;
2258 2024 }
2259 2025
2260 - // Use getChatSession which returns null if no session exists (does NOT create one)
2261 2026 var sessionId = getChatSession(botId);
2262 2027 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2263 2028
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 2029 if (chatPersistenceEnabled && sessionId) {
2272 2030 $.ajax({
2273 2031 url: mxchatChat.ajax_url,
2274 2032 type: 'POST',
@@ -2279,12 +2037,11 @@
2279 2037 },
2280 2038 success: function(response) {
2281 2039 // Handle session reset (IP changed while user was away)
2282 2040 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);
2041 + // Silently reset session - user will start fresh
2042 + resetChatSession(botId);
2285 2043 instance.chatHistoryLoaded = true; // Prevent retry loop
2286 - if (onComplete) onComplete();
2287 2044 return;
2288 2045 }
2289 2046
2290 2047 // Check if the response indicates success
@@ -2340,19 +2097,9 @@
2340 2097 var content = message.content;
2341 2098 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2342 2099 content = decodeHTMLEntities(content);
2343 2100
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")) {
2101 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2355 2102 messageElement.html(content);
2356 2103 } else {
2357 2104 var formattedContent = linkify(content);
2358 2105 messageElement.html(formattedContent);
@@ -2392,17 +2139,13 @@
2392 2139 instance.chatHistoryLoaded = true;
2393 2140 }
2394 2141 }
2395 2142 }
2396 - if (onComplete) onComplete();
2397 2143 },
2398 2144 error: function(xhr, status, error) {
2399 2145 // Error loading chat history - silently continue
2400 - if (onComplete) onComplete();
2401 2146 }
2402 2147 });
2403 - } else {
2404 - if (onComplete) onComplete();
2405 2148 }
2406 2149 }
2407 2150
2408 2151
@@ -2578,35 +2321,45 @@
2578 2321 // ====================================
2579 2322
2580 2323 function checkPreChatDismissal(botId) {
2581 2324 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) {
2325 + $.ajax({
2326 + url: mxchatChat.ajax_url,
2327 + type: 'POST',
2328 + data: {
2329 + action: 'mxchat_check_pre_chat_message_status',
2330 + _ajax_nonce: mxchatChat.nonce
2331 + },
2332 + success: function(response) {
2333 + if (response.success && !response.data.dismissed) {
2334 + getElement(botId, 'pre-chat-message').fadeIn(250);
2335 + } else {
2588 2336 getElement(botId, 'pre-chat-message').hide();
2589 - return;
2590 2337 }
2591 - // Expired — clear and show again
2592 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2338 + },
2339 + error: function() {
2340 + // Error checking pre-chat dismissal - silently continue
2593 2341 }
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 - }
2342 + });
2599 2343 }
2600 2344
2601 2345 function handlePreChatDismissal(botId) {
2602 2346 botId = botId || 'default';
2603 2347 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 - }
2348 + $.ajax({
2349 + url: mxchatChat.ajax_url,
2350 + type: 'POST',
2351 + data: {
2352 + action: 'mxchat_dismiss_pre_chat_message',
2353 + _ajax_nonce: mxchatChat.nonce
2354 + },
2355 + success: function() {
2356 + $('#pre-chat-message').hide();
2357 + },
2358 + error: function() {
2359 + // Error dismissing pre-chat message - silently continue
2360 + }
2361 + });
2609 2362 }
2610 2363
2611 2364
2612 2365 // ====================================
@@ -2673,26 +2426,8 @@
2673 2426 $(this).addClass('hidden');
2674 2427 $badge.hide(); // Hide notification when opening chat
2675 2428 disableScroll();
2676 2429 $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 2430 } else {
2696 2431 $chatbot.removeClass('visible').addClass('hidden');
2697 2432 $(this).removeClass('hidden');
2698 2433 enableScroll();
@@ -2710,9 +2445,11 @@
2710 2445
2711 2446 $(document).on('click', '.close-pre-chat-message', function(e) {
2712 2447 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2713 2448 var botId = getBotIdFromElement(this);
2714 - handlePreChatDismissal(botId);
2449 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2450 + $(this).remove();
2451 + });
2715 2452 });
2716 2453
2717 2454
2718 2455 // PDF upload button handlers - use class selector
@@ -2913,59 +2650,8 @@
2913 2650 });
2914 2651
2915 2652
2916 2653 // ====================================
2917 -// INIT LOADER & CHAT CONTAINER HELPERS
2918 -// ====================================
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 2654 // EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2969 2655 // ====================================
2970 2656 // Only run email collection setup if it's enabled
2971 2657 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
@@ -3001,8 +2687,28 @@
3001 2687 `;
3002 2688 document.head.appendChild(style);
3003 2689 }
3004 2690
2691 + // Helper functions for email collection (multi-instance aware)
2692 + function showEmailFormForBot(botId) {
2693 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2694 + var chatContainer = getElementDOM(botId, 'chat-container');
2695 + if (emailBlocker) emailBlocker.style.display = 'flex';
2696 + if (chatContainer) chatContainer.style.display = 'none';
2697 + }
2698 +
2699 + function showChatContainerForBot(botId) {
2700 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2701 + var chatContainer = getElementDOM(botId, 'chat-container');
2702 + if (emailBlocker) emailBlocker.style.display = 'none';
2703 + if (chatContainer) chatContainer.style.display = 'flex';
2704 +
2705 + // Load chat history for this bot
2706 + if (typeof loadChatHistory === 'function') {
2707 + loadChatHistory(botId);
2708 + }
2709 + }
2710 +
3005 2711 function isValidEmailAddress(email) {
3006 2712 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3007 2713 return emailRegex.test(email.trim()) && email.length <= 254;
3008 2714 }
@@ -3124,31 +2830,11 @@
3124 2830 existingErrors.forEach(error => error.remove());
3125 2831 }
3126 2832 }
3127 2833
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 - }
3140 -
3141 2834 function checkSessionAndEmailForBot(botId) {
3142 - const sessionId = MxChatInstances.ensureSession(botId);
2835 + const sessionId = getChatSession(botId);
3143 2836
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);
3150 -
3151 2837 fetch(mxchatChat.ajax_url, {
3152 2838 method: 'POST',
3153 2839 headers: {
3154 2840 'Content-Type': 'application/x-www-form-urlencoded',
@@ -3196,9 +2882,9 @@
3196 2882 var emailInput = getElementDOM(botId, 'user-email');
3197 2883 var nameInput = getElementDOM(botId, 'user-name');
3198 2884 var userEmail = emailInput ? emailInput.value.trim() : '';
3199 2885 var userName = nameInput ? nameInput.value.trim() : '';
3200 - var sessionId = MxChatInstances.ensureSession(botId);
2886 + var sessionId = getChatSession(botId);
3201 2887
3202 2888 // Validate email
3203 2889 if (!userEmail) {
3204 2890 showEmailError(botId, 'Please enter your email address.');
@@ -3321,27 +3007,25 @@
3321 3007 }
3322 3008 });
3323 3009
3324 3010 // 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 3011 $('.mxchat-chatbot-wrapper').each(function() {
3328 3012 var botId = $(this).data('bot-id') || 'default';
3329 3013 var emailBlocker = getElementDOM(botId, 'email-blocker');
3330 3014
3015 + // Only check if email blocker exists for this bot
3331 3016 if (emailBlocker) {
3332 - if (isEmbeddedBot(botId)) {
3333 - // Embedded bots are always visible — check now
3334 - resolveEmailState(botId);
3017 + if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3018 + if (mxchatChat.initial_email_state.show_email_form) {
3019 + showEmailFormForBot(botId);
3020 + } else {
3021 + showChatContainerForBot(botId);
3022 + }
3023 + } else {
3024 + setTimeout(function() {
3025 + checkSessionAndEmailForBot(botId);
3026 + }, 100);
3335 3027 }
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 3028 }
3345 3029 });
3346 3030 }
3347 3031
@@ -3351,32 +3035,39 @@
3351 3035 var $chatbot = getElement(botId, 'floating-chatbot');
3352 3036 if ($chatbot.hasClass('hidden')) {
3353 3037 $chatbot.removeClass('hidden').addClass('visible');
3354 3038 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3355 - handlePreChatDismissal(botId);
3039 + $(this).fadeOut(250); // Hide pre-chat message
3356 3040 disableScroll(); // Disable scroll when chatbot opens
3041 + }
3042 + });
3357 3043
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 - }
3044 + // Dismiss pre-chat message via close button - handled by event delegation above at line ~2376
3045 + // This is a fallback for legacy support
3046 + $(document).on('click', '.close-pre-chat-message', function() {
3047 + var botId = getBotIdFromElement(this);
3048 + var $preChat = getElement(botId, 'pre-chat-message');
3049 + $preChat.fadeOut(200); // Hide the message
3363 3050
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);
3051 + // Send an AJAX request to set the transient flag for 24 hours
3052 + $.ajax({
3053 + url: mxchatChat.ajax_url,
3054 + type: 'POST',
3055 + data: {
3056 + action: 'mxchat_dismiss_pre_chat_message',
3057 + _ajax_nonce: mxchatChat.nonce
3058 + },
3059 + success: function() {
3060 + // Ensure the message is hidden after dismissal
3061 + $preChat.hide();
3062 + },
3063 + error: function() {
3064 + // Error dismissing pre-chat message - silently continue
3372 3065 }
3373 - }
3066 + });
3374 3067 });
3375 3068
3376 - // Legacy duplicate close handler removed — handled by single event delegation above
3377 3069
3378 -
3379 3070 function hasQuickQuestions(botId) {
3380 3071 botId = botId || 'default';
3381 3072 var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3382 3073 if (!questionsContainer) return false;
@@ -3512,11 +3203,18 @@
3512 3203 });
3513 3204
3514 3205 // Initialize when document is ready
3515 3206 setFullHeight();
3207 + trackOriginatingPage();
3516 3208
3517 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3518 - // until the user's first interaction via MxChatInstances.ensureSession()
3209 + // Only load chat history if email collection is disabled
3210 + if (mxchatChat.email_collection_enabled !== 'on') {
3211 + // Load history for all instances
3212 + $('.mxchat-chatbot-wrapper').each(function() {
3213 + var botId = $(this).data('bot-id') || 'default';
3214 + loadChatHistory(botId);
3215 + });
3216 + }
3519 3217
3520 3218 // Initialize chat visibility for all instances
3521 3219 $('.mxchat-chatbot-wrapper').each(function() {
3522 3220 var botId = $(this).data('bot-id') || 'default';