PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.7
MxChat – AI Chatbot & Content Generation for WordPress v3.0.7
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 +202 -487 3.2.33.0.7 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);
@@ -1120,16 +1006,21 @@
1120 1006 errorMessage = "An error occurred. Please try again or contact support.";
1121 1007 }
1122 1008
1123 1009 // Handle session reset action (IP changed, session expired, etc.)
1124 - // Silent reset — keep chat UI intact, just get a new session and retry
1125 1010 if (data.data && data.data.action === 'reset_session') {
1126 - MxChatInstances.silentResetSession(botId);
1127 - // Re-send the original message with the new session (user message is already displayed)
1011 + // Clear the old session and generate a new one
1012 + resetChatSession(botId);
1013 + // Re-send the original message with the new session
1128 1014 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1129 1015 if (originalMessage) {
1130 1016 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1131 - 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';
1132 1023 if (shouldUseStreaming(currentModel)) {
1133 1024 callMxChatStream(originalMessage, callback, botId);
1134 1025 } else {
1135 1026 callMxChat(originalMessage, callback, botId);
@@ -1151,22 +1042,8 @@
1151 1042 }
1152 1043 return; // Exit early for errors
1153 1044 }
1154 1045
1155 - // Check for live agent response
1156 - if (data.success && data.data && data.data.status === 'waiting_for_agent') {
1157 - removeThinkingDots(botId);
1158 - // Also remove any leftover bot-message that lost its temporary-message class
1159 - var $chatBox = getElement(botId, 'chat-box');
1160 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1161 - updateChatModeIndicator('agent', botId);
1162 - enableChatInput(botId);
1163 - if (callback) {
1164 - callback('');
1165 - }
1166 - return;
1167 - }
1168 -
1169 1046 // Handle different response formats
1170 1047 if (data.text || data.html || data.message) {
1171 1048
1172 1049 // Apply response hooks
@@ -1211,15 +1088,19 @@
1211 1088 }
1212 1089
1213 1090 // Enhanced updateChatModeIndicator function for immediate DOM updates
1214 1091 function updateChatModeIndicator(mode, botId) {
1092 + console.log('[MxChat] updateChatModeIndicator called with mode:', mode, 'botId:', botId);
1215 1093 botId = botId || 'default';
1216 1094 const indicator = getElementDOM(botId, 'chat-mode-indicator');
1095 + console.log('[MxChat] chat-mode-indicator element found:', !!indicator);
1217 1096 if (indicator) {
1218 1097 const oldText = indicator.textContent;
1098 + console.log('[MxChat] Current indicator text:', oldText, '-> changing to mode:', mode);
1219 1099
1220 1100 if (mode === 'agent') {
1221 1101 indicator.textContent = 'Live Agent';
1102 + console.log('[MxChat] Mode is agent, calling startPolling...');
1222 1103 startPolling(botId);
1223 1104 } else {
1224 1105 // Everything else is AI mode
1225 1106 const customAiText = indicator.getAttribute('data-ai-text') || 'AI Agent';
@@ -1290,12 +1171,9 @@
1290 1171 // Update the event handlers to use the correct function names (using event delegation)
1291 1172 // Use class-based selectors for multi-instance support
1292 1173 $(document).on('click', '.send-button', function() {
1293 1174 var botId = getBotIdFromElement(this);
1294 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1295 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1296 - disableChatInput(botId);
1297 - }
1175 + disableChatInput(botId);
1298 1176 sendMessage(botId);
1299 1177 });
1300 1178
1301 1179 // Override enter key handler (using event delegation)
@@ -1302,12 +1180,9 @@
1302 1180 $(document).on('keypress', '.chat-input', function(e) {
1303 1181 if (e.which == 13 && !e.shiftKey) {
1304 1182 e.preventDefault();
1305 1183 var botId = getBotIdFromElement(this);
1306 - var modeIndicator = getElementDOM(botId, 'chat-mode-indicator');
1307 - if (!(modeIndicator && modeIndicator.textContent === 'Live Agent')) {
1308 - disableChatInput(botId);
1309 - }
1184 + disableChatInput(botId);
1310 1185 sendMessage(botId);
1311 1186 }
1312 1187 });
1313 1188
@@ -1350,12 +1225,17 @@
1350 1225 'margin-bottom': '1em'
1351 1226 });
1352 1227 }
1353 1228
1354 - // Process the message content - always run linkify to convert markdown
1355 - // links and format text. linkify() handles existing HTML safely via
1356 - // negative lookaheads that skip URLs already inside <a> tags.
1357 - let fullMessage = linkify(messageText);
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 + }
1358 1238
1359 1239 // Add images if provided
1360 1240 if (images && images.length > 0) {
1361 1241 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1490,12 +1370,26 @@
1490 1370 bgColor = botMessageBgColor;
1491 1371 fontColor = botMessageFontColor;
1492 1372 }
1493 1373
1494 - // Always run linkify to convert markdown links and format text.
1495 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1496 - // to avoid double-processing URLs that are already inside <a> tags).
1497 - var fullMessage = linkify(responseText);
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 + }
1498 1392
1499 1393 if (responseHtml) {
1500 1394 // Only add line breaks if there's actual text content before the HTML
1501 1395 if (fullMessage && fullMessage.trim()) {
@@ -1562,15 +1456,8 @@
1562 1456
1563 1457
1564 1458 function appendThinkingMessage(botId) {
1565 1459 botId = botId || 'default';
1566 -
1567 - // Don't show thinking dots in live agent mode - message is just forwarded to a human
1568 - var indicator = getElementDOM(botId, 'chat-mode-indicator');
1569 - if (indicator && indicator.textContent === 'Live Agent') {
1570 - return;
1571 - }
1572 -
1573 1460 var $chatBox = getElement(botId, 'chat-box');
1574 1461
1575 1462 // Remove any existing thinking dots in this bot's chat first
1576 1463 $chatBox.find('.thinking-dots').remove();
@@ -1592,9 +1479,9 @@
1592 1479 '</div>' +
1593 1480 '</div>';
1594 1481
1595 1482 // Append the thinking dots to this bot's chat container - skip inline colors if AI theme is active
1596 - var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + '; color: ' + botMessageFontColor + ';"';
1483 + var messageStyle = skipColors ? '' : ' style="background-color: ' + botMessageBgColor + ';"';
1597 1484 $chatBox.append('<div class="bot-message temporary-message"' + messageStyle + '>' + thinkingHtml + '</div>');
1598 1485 scrollToBottom(botId);
1599 1486 }
1600 1487
@@ -1600,11 +1487,9 @@
1600 1487
1601 1488 function removeThinkingDots(botId) {
1602 1489 botId = botId || 'default';
1603 1490 var $chatBox = getElement(botId, 'chat-box');
1604 - // Remove by temporary-message class first, then fall back to any bot-message containing thinking dots
1605 1491 $chatBox.find('.thinking-dots').closest('.temporary-message').remove();
1606 - $chatBox.find('.bot-message .thinking-dots').closest('.bot-message').remove();
1607 1492 }
1608 1493
1609 1494 // ====================================
1610 1495 // TEXT FORMATTING & PROCESSING
@@ -1638,12 +1523,9 @@
1638 1523 processedText = formatTextStyling(processedText);
1639 1524
1640 1525 // Process code blocks BEFORE processing links
1641 1526 processedText = formatCodeBlocks(processedText);
1642 -
1643 - // Process markdown tables BEFORE converting newlines to paragraphs
1644 - processedText = formatMarkdownTables(processedText);
1645 -
1527 +
1646 1528 // NOW convert to paragraphs
1647 1529 processedText = convertNewlinesToBreaks(processedText);
1648 1530
1649 1531 // IMPORTANT: Handle citation-style brackets FIRST [URL]
@@ -1656,63 +1538,37 @@
1656 1538 // Return as a proper link without the brackets
1657 1539 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1658 1540 });
1659 1541
1660 - // Process markdown links: [text](url) and [](url)
1661 - // Uses balanced parenthesis matching to handle URLs containing parens
1662 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1663 - processedText = (function(input) {
1664 - var result = '';
1665 - var i = 0;
1666 - while (i < input.length) {
1667 - // Look for [ at current position
1668 - if (input[i] === '[') {
1669 - // Find closing ]
1670 - var closeBracket = input.indexOf(']', i + 1);
1671 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1672 - result += input[i];
1673 - i++;
1674 - continue;
1675 - }
1676 - var linkText = input.substring(i + 1, closeBracket);
1677 - // Check if URL starts with http
1678 - var urlStart = closeBracket + 2;
1679 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1680 - result += input[i];
1681 - i++;
1682 - continue;
1683 - }
1684 - // Find balanced closing paren
1685 - var depth = 1;
1686 - var j = urlStart;
1687 - while (j < input.length && depth > 0) {
1688 - if (input[j] === '(') depth++;
1689 - else if (input[j] === ')') depth--;
1690 - if (depth > 0) j++;
1691 - }
1692 - if (depth !== 0) {
1693 - result += input[i];
1694 - i++;
1695 - continue;
1696 - }
1697 - var url = input.substring(urlStart, j);
1698 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1699 - var encodedUrl = safeEncodeUrl(cleanUrl);
1700 - if (!linkText || !linkText.trim()) {
1701 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1702 - } else {
1703 - var safeText = sanitizeUserInput(linkText);
1704 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1705 - }
1706 - i = j + 1; // Skip past the closing )
1707 - } else {
1708 - result += input[i];
1709 - i++;
1710 - }
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>`;
1711 1552 }
1712 - return result;
1713 - })(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 + });
1714 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 +
1715 1571 // Process phone numbers: [text](tel:number)
1716 1572 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1717 1573 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1718 1574 const safePhone = safeEncodeUrl(phone);
@@ -1864,78 +1720,9 @@
1864 1720 });
1865 1721
1866 1722 return text;
1867 1723 }
1868 -
1869 - function formatMarkdownTables(text) {
1870 - var lines = text.split('\n');
1871 - var result = [];
1872 - var i = 0;
1873 -
1874 - while (i < lines.length) {
1875 - // Check for a table: current line has pipes AND next line is a separator row
1876 - if (i + 1 < lines.length &&
1877 - lines[i].indexOf('|') !== -1 &&
1878 - /^\s*\|?[\s\-:]+(\|[\s\-:]+)+\|?\s*$/.test(lines[i + 1])) {
1879 -
1880 - var tableLines = [];
1881 - var headerLine = lines[i];
1882 - var separatorLine = lines[i + 1];
1883 - tableLines.push(headerLine);
1884 - tableLines.push(separatorLine);
1885 -
1886 - // Collect remaining table rows
1887 - var j = i + 2;
1888 - while (j < lines.length && lines[j].indexOf('|') !== -1 && lines[j].trim() !== '') {
1889 - tableLines.push(lines[j]);
1890 - j++;
1891 - }
1892 -
1893 - // Parse alignment from separator row
1894 - var sepCells = separatorLine.split('|').filter(function(c) { return c.trim() !== ''; });
1895 - var alignments = sepCells.map(function(cell) {
1896 - var trimmed = cell.trim();
1897 - if (trimmed.charAt(0) === ':' && trimmed.charAt(trimmed.length - 1) === ':') return 'center';
1898 - if (trimmed.charAt(trimmed.length - 1) === ':') return 'right';
1899 - return 'left';
1900 - });
1901 -
1902 - // Build HTML table
1903 - var html = '<div class="mxchat-table-wrapper"><table class="mxchat-table">';
1904 -
1905 - // Header row
1906 - var headerCells = tableLines[0].split('|').filter(function(c) { return c.trim() !== ''; });
1907 - html += '<thead><tr>';
1908 - headerCells.forEach(function(cell, idx) {
1909 - var align = alignments[idx] || 'left';
1910 - html += '<th style="text-align:' + align + '">' + cell.trim() + '</th>';
1911 - });
1912 - html += '</tr></thead>';
1913 -
1914 - // Body rows
1915 - html += '<tbody>';
1916 - for (var r = 2; r < tableLines.length; r++) {
1917 - var rowCells = tableLines[r].split('|').filter(function(c) { return c.trim() !== ''; });
1918 - html += '<tr>';
1919 - rowCells.forEach(function(cell, idx) {
1920 - var align = alignments[idx] || 'left';
1921 - html += '<td style="text-align:' + align + '">' + cell.trim() + '</td>';
1922 - });
1923 - html += '</tr>';
1924 - }
1925 - html += '</tbody></table></div>';
1926 -
1927 - result.push(html);
1928 - i = j;
1929 - } else {
1930 - result.push(lines[i]);
1931 - i++;
1932 - }
1933 - }
1934 -
1935 - return result.join('\n');
1936 - }
1937 -
1724 +
1938 1725 function sanitizeUserInput(text) {
1939 1726 const div = document.createElement('div');
1940 1727 div.textContent = text;
1941 1728 return div.innerHTML;
@@ -2156,12 +1943,15 @@
2156 1943 // LIVE AGENT FUNCTIONALITY
2157 1944 // ====================================
2158 1945
2159 1946 function startPolling(botId) {
1947 + console.log('[MxChat] startPolling called for botId:', botId);
2160 1948 botId = botId || 'default';
2161 1949 var instance = MxChatInstances.get(botId);
2162 1950 // Clear any existing interval first
2163 1951 stopPolling(botId);
1952 + // Start new polling interval
1953 + console.log('[MxChat] Starting polling interval (5s) for botId:', botId);
2164 1954 instance.pollingInterval = setInterval(function() {
2165 1955 checkForAgentMessages(botId);
2166 1956 }, 5000);
2167 1957 }
@@ -2166,17 +1956,20 @@
2166 1956 }, 5000);
2167 1957 }
2168 1958
2169 1959 function stopPolling(botId) {
1960 + console.log('[MxChat] stopPolling called for botId:', botId);
2170 1961 botId = botId || 'default';
2171 1962 var instance = MxChatInstances.get(botId);
2172 1963 if (instance.pollingInterval) {
2173 1964 clearInterval(instance.pollingInterval);
2174 1965 instance.pollingInterval = null;
1966 + console.log('[MxChat] Polling stopped for botId:', botId);
2175 1967 }
2176 1968 }
2177 1969
2178 1970 function checkForAgentMessages(botId) {
1971 + console.log('[MxChat] checkForAgentMessages called for botId:', botId);
2179 1972 botId = botId || 'default';
2180 1973 var instance = MxChatInstances.get(botId);
2181 1974 const sessionId = getChatSession(botId);
2182 1975 $.ajax({
@@ -2202,12 +1995,8 @@
2202 1995 instance.processedMessageIds.add(message.id);
2203 1996 }
2204 1997 });
2205 1998
2206 - if (hasNewMessage) {
2207 - enableChatInput(botId);
2208 - }
2209 -
2210 1999 var $floatingChatbot = getElement(botId, 'floating-chatbot');
2211 2000 if (hasNewMessage && $floatingChatbot.hasClass('hidden')) {
2212 2001 showNotification(botId);
2213 2002 }
@@ -2213,13 +2002,8 @@
2213 2002 }
2214 2003
2215 2004 scrollToBottom(botId, true);
2216 2005 }
2217 -
2218 - // Handle chat mode transitions (e.g. agent ended chat via !endchat)
2219 - if (response.success && response.data?.chat_mode) {
2220 - updateChatModeIndicator(response.data.chat_mode, botId);
2221 - }
2222 2006 },
2223 2007 error: function (xhr, status, error) {
2224 2008 // Polling error - silently continue
2225 2009 }
@@ -2229,29 +2013,20 @@
2229 2013 // ====================================
2230 2014 // CHAT HISTORY & PERSISTENCE
2231 2015 // ====================================
2232 2016
2233 -function loadChatHistory(botId, onComplete) {
2017 +function loadChatHistory(botId) {
2234 2018 botId = botId || 'default';
2235 2019 var instance = MxChatInstances.get(botId);
2236 2020
2237 2021 // Prevent duplicate loading
2238 2022 if (instance.chatHistoryLoaded) {
2239 - if (onComplete) onComplete();
2240 2023 return;
2241 2024 }
2242 2025
2243 - // Use getChatSession which returns null if no session exists (does NOT create one)
2244 2026 var sessionId = getChatSession(botId);
2245 2027 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2246 2028
2247 - // No session yet — nothing to load. History will load after first message via ensureSession.
2248 - if (!sessionId) {
2249 - instance.chatHistoryLoaded = true;
2250 - if (onComplete) onComplete();
2251 - return;
2252 - }
2253 -
2254 2029 if (chatPersistenceEnabled && sessionId) {
2255 2030 $.ajax({
2256 2031 url: mxchatChat.ajax_url,
2257 2032 type: 'POST',
@@ -2262,12 +2037,11 @@
2262 2037 },
2263 2038 success: function(response) {
2264 2039 // Handle session reset (IP changed while user was away)
2265 2040 if (response.success === false && response.data && response.data.action === 'reset_session') {
2266 - // Silent reset — new session but don't clear UI
2267 - MxChatInstances.silentResetSession(botId);
2041 + // Silently reset session - user will start fresh
2042 + resetChatSession(botId);
2268 2043 instance.chatHistoryLoaded = true; // Prevent retry loop
2269 - if (onComplete) onComplete();
2270 2044 return;
2271 2045 }
2272 2046
2273 2047 // Check if the response indicates success
@@ -2323,19 +2097,9 @@
2323 2097 var content = message.content;
2324 2098 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2325 2099 content = decodeHTMLEntities(content);
2326 2100
2327 - // Skip linkify for messages containing structured HTML
2328 - // (forms, product cards, galleries, etc.) to avoid
2329 - // markdown formatting corrupting HTML attributes
2330 - // (e.g. underscores in name="field_name" becoming <em> tags)
2331 - if (content.includes("mxchat-product-card") ||
2332 - content.includes("mxchat-image-gallery") ||
2333 - content.includes("mxchat-featured-products") ||
2334 - content.includes("<form") ||
2335 - content.includes("<input") ||
2336 - content.includes("<select") ||
2337 - content.includes("<textarea")) {
2101 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2338 2102 messageElement.html(content);
2339 2103 } else {
2340 2104 var formattedContent = linkify(content);
2341 2105 messageElement.html(formattedContent);
@@ -2375,17 +2139,13 @@
2375 2139 instance.chatHistoryLoaded = true;
2376 2140 }
2377 2141 }
2378 2142 }
2379 - if (onComplete) onComplete();
2380 2143 },
2381 2144 error: function(xhr, status, error) {
2382 2145 // Error loading chat history - silently continue
2383 - if (onComplete) onComplete();
2384 2146 }
2385 2147 });
2386 - } else {
2387 - if (onComplete) onComplete();
2388 2148 }
2389 2149 }
2390 2150
2391 2151
@@ -2561,35 +2321,45 @@
2561 2321 // ====================================
2562 2322
2563 2323 function checkPreChatDismissal(botId) {
2564 2324 botId = botId || 'default';
2565 - try {
2566 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2567 - if (dismissedAt) {
2568 - // Re-show after 24 hours
2569 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2570 - if (elapsed < 86400000) {
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 {
2571 2336 getElement(botId, 'pre-chat-message').hide();
2572 - return;
2573 2337 }
2574 - // Expired — clear and show again
2575 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2338 + },
2339 + error: function() {
2340 + // Error checking pre-chat dismissal - silently continue
2576 2341 }
2577 - getElement(botId, 'pre-chat-message').fadeIn(250);
2578 - } catch (e) {
2579 - // localStorage unavailable — show the message
2580 - getElement(botId, 'pre-chat-message').fadeIn(250);
2581 - }
2342 + });
2582 2343 }
2583 2344
2584 2345 function handlePreChatDismissal(botId) {
2585 2346 botId = botId || 'default';
2586 2347 getElement(botId, 'pre-chat-message').fadeOut(200);
2587 - try {
2588 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2589 - } catch (e) {
2590 - // localStorage unavailable — dismissal won't persist
2591 - }
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 + });
2592 2362 }
2593 2363
2594 2364
2595 2365 // ====================================
@@ -2656,26 +2426,8 @@
2656 2426 $(this).addClass('hidden');
2657 2427 $badge.hide(); // Hide notification when opening chat
2658 2428 disableScroll();
2659 2429 $preChat.fadeOut(250);
2660 -
2661 - // Load chat history for returning visitors (persistence)
2662 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2663 - if (chatPersistenceEnabled) {
2664 - MxChatInstances.ensureSession(botId);
2665 - }
2666 -
2667 - // Deferred email check — only on first widget open
2668 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2669 - var instance = MxChatInstances.get(botId);
2670 - if (emailBlocker && !instance.emailCheckDone) {
2671 - instance.emailCheckDone = true;
2672 - resolveEmailState(botId);
2673 - } else if (!emailBlocker) {
2674 - // No email collection — still route through showChatContainerForBot
2675 - // so the loader is shown while chat history loads
2676 - showChatContainerForBot(botId);
2677 - }
2678 2430 } else {
2679 2431 $chatbot.removeClass('visible').addClass('hidden');
2680 2432 $(this).removeClass('hidden');
2681 2433 enableScroll();
@@ -2693,9 +2445,11 @@
2693 2445
2694 2446 $(document).on('click', '.close-pre-chat-message', function(e) {
2695 2447 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2696 2448 var botId = getBotIdFromElement(this);
2697 - handlePreChatDismissal(botId);
2449 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2450 + $(this).remove();
2451 + });
2698 2452 });
2699 2453
2700 2454
2701 2455 // PDF upload button handlers - use class selector
@@ -2896,59 +2650,8 @@
2896 2650 });
2897 2651
2898 2652
2899 2653 // ====================================
2900 -// INIT LOADER & CHAT CONTAINER HELPERS
2901 -// ====================================
2902 -// These must be outside the email collection block so they're always available
2903 -// (used by persistence loading even when email collection is off)
2904 -
2905 -function showInitLoader(botId) {
2906 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2907 - if (loader) loader.style.display = 'flex';
2908 -}
2909 -
2910 -function hideInitLoader(botId) {
2911 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2912 - if (loader) loader.style.display = 'none';
2913 -}
2914 -
2915 -function showEmailFormForBot(botId) {
2916 - hideInitLoader(botId);
2917 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2918 - var chatContainer = getElementDOM(botId, 'chat-container');
2919 - if (emailBlocker) emailBlocker.style.display = 'flex';
2920 - if (chatContainer) chatContainer.style.display = 'none';
2921 -}
2922 -
2923 -function showChatContainerForBot(botId) {
2924 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2925 - var chatContainer = getElementDOM(botId, 'chat-container');
2926 - if (emailBlocker) emailBlocker.style.display = 'none';
2927 -
2928 - var instance = MxChatInstances.get(botId);
2929 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2930 -
2931 - // If persistence is on and history hasn't loaded yet, show loader
2932 - // while history loads to prevent flash of empty chat
2933 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2934 - if (chatContainer) chatContainer.style.display = 'none';
2935 - showInitLoader(botId);
2936 - loadChatHistory(botId, function() {
2937 - hideInitLoader(botId);
2938 - if (chatContainer) chatContainer.style.display = 'flex';
2939 - scrollToBottom(botId, true);
2940 - });
2941 - } else {
2942 - hideInitLoader(botId);
2943 - if (chatContainer) chatContainer.style.display = 'flex';
2944 - if (typeof loadChatHistory === 'function') {
2945 - loadChatHistory(botId);
2946 - }
2947 - }
2948 -}
2949 -
2950 -// ====================================
2951 2654 // EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2952 2655 // ====================================
2953 2656 // Only run email collection setup if it's enabled
2954 2657 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
@@ -2984,8 +2687,28 @@
2984 2687 `;
2985 2688 document.head.appendChild(style);
2986 2689 }
2987 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 +
2988 2711 function isValidEmailAddress(email) {
2989 2712 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2990 2713 return emailRegex.test(email.trim()) && email.length <= 254;
2991 2714 }
@@ -3107,31 +2830,11 @@
3107 2830 existingErrors.forEach(error => error.remove());
3108 2831 }
3109 2832 }
3110 2833
3111 - // Resolve email state using server-side data when available, AJAX fallback otherwise
3112 - function resolveEmailState(botId) {
3113 - if (mxchatChat.skip_email_check && mxchatChat.initial_email_state) {
3114 - if (mxchatChat.initial_email_state.show_email_form) {
3115 - showEmailFormForBot(botId);
3116 - } else {
3117 - showChatContainerForBot(botId);
3118 - }
3119 - } else {
3120 - checkSessionAndEmailForBot(botId);
3121 - }
3122 - }
3123 -
3124 2834 function checkSessionAndEmailForBot(botId) {
3125 - const sessionId = MxChatInstances.ensureSession(botId);
2835 + const sessionId = getChatSession(botId);
3126 2836
3127 - // Hide both panels while we check — show loader instead
3128 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3129 - var chatContainer = getElementDOM(botId, 'chat-container');
3130 - if (emailBlocker) emailBlocker.style.display = 'none';
3131 - if (chatContainer) chatContainer.style.display = 'none';
3132 - showInitLoader(botId);
3133 -
3134 2837 fetch(mxchatChat.ajax_url, {
3135 2838 method: 'POST',
3136 2839 headers: {
3137 2840 'Content-Type': 'application/x-www-form-urlencoded',
@@ -3179,9 +2882,9 @@
3179 2882 var emailInput = getElementDOM(botId, 'user-email');
3180 2883 var nameInput = getElementDOM(botId, 'user-name');
3181 2884 var userEmail = emailInput ? emailInput.value.trim() : '';
3182 2885 var userName = nameInput ? nameInput.value.trim() : '';
3183 - var sessionId = MxChatInstances.ensureSession(botId);
2886 + var sessionId = getChatSession(botId);
3184 2887
3185 2888 // Validate email
3186 2889 if (!userEmail) {
3187 2890 showEmailError(botId, 'Please enter your email address.');
@@ -3304,27 +3007,25 @@
3304 3007 }
3305 3008 });
3306 3009
3307 3010 // Initialize email check for all bot instances
3308 - // For floating bots: defer until widget is opened (zero passive AJAX)
3309 - // For embedded bots: check immediately since the form is visible
3310 3011 $('.mxchat-chatbot-wrapper').each(function() {
3311 3012 var botId = $(this).data('bot-id') || 'default';
3312 3013 var emailBlocker = getElementDOM(botId, 'email-blocker');
3313 3014
3015 + // Only check if email blocker exists for this bot
3314 3016 if (emailBlocker) {
3315 - if (isEmbeddedBot(botId)) {
3316 - // Embedded bots are always visible — check now
3317 - 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);
3318 3027 }
3319 - // Floating bots: handled in the widget open handler
3320 - } else if (isEmbeddedBot(botId)) {
3321 - // Embedded bot, no email collection — load history with loader
3322 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3323 - if (chatPersistenceEnabled) {
3324 - MxChatInstances.ensureSession(botId);
3325 - showChatContainerForBot(botId);
3326 - }
3327 3028 }
3328 3029 });
3329 3030 }
3330 3031
@@ -3334,32 +3035,39 @@
3334 3035 var $chatbot = getElement(botId, 'floating-chatbot');
3335 3036 if ($chatbot.hasClass('hidden')) {
3336 3037 $chatbot.removeClass('hidden').addClass('visible');
3337 3038 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3338 - handlePreChatDismissal(botId);
3039 + $(this).fadeOut(250); // Hide pre-chat message
3339 3040 disableScroll(); // Disable scroll when chatbot opens
3041 + }
3042 + });
3340 3043
3341 - // Load chat history for returning visitors (persistence)
3342 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3343 - if (chatPersistenceEnabled) {
3344 - MxChatInstances.ensureSession(botId);
3345 - }
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
3346 3050
3347 - // Deferred email check — only on first widget open
3348 - var emailBlocker = getElementDOM(botId, 'email-blocker');
3349 - var instance = MxChatInstances.get(botId);
3350 - if (emailBlocker && !instance.emailCheckDone) {
3351 - instance.emailCheckDone = true;
3352 - resolveEmailState(botId);
3353 - } else if (!emailBlocker) {
3354 - showChatContainerForBot(botId);
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
3355 3065 }
3356 - }
3066 + });
3357 3067 });
3358 3068
3359 - // Legacy duplicate close handler removed — handled by single event delegation above
3360 3069
3361 -
3362 3070 function hasQuickQuestions(botId) {
3363 3071 botId = botId || 'default';
3364 3072 var questionsContainer = getElementDOM(botId, 'mxchat-popular-questions');
3365 3073 if (!questionsContainer) return false;
@@ -3495,11 +3203,18 @@
3495 3203 });
3496 3204
3497 3205 // Initialize when document is ready
3498 3206 setFullHeight();
3207 + trackOriginatingPage();
3499 3208
3500 - // Note: trackOriginatingPage() and loadChatHistory() are now deferred
3501 - // 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 + }
3502 3217
3503 3218 // Initialize chat visibility for all instances
3504 3219 $('.mxchat-chatbot-wrapper').each(function() {
3505 3220 var botId = $(this).data('bot-id') || 'default';