PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.4
MxChat – AI Chatbot & Content Generation for WordPress v3.1.4
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 +157 -216 3.2.23.1.4 View file →
@@ -60,26 +60,18 @@
60 60 return Object.keys(this.instances);
61 61 },
62 62
63 63 // Session management per bot
64 - // Returns existing session ID from cookie or localStorage, or null if none exists.
65 - // Does NOT create a new session — use ensureSession() for that.
66 64 getChatSession: function(botId) {
67 65 var cookieName = 'mxchat_session_id_' + botId;
68 - var storageKey = 'mxchat_session_id_' + botId;
69 66 var sessionId = getCookie(cookieName);
70 67
71 - // Fallback to localStorage if cookie is missing (e.g. cleared by browser/consent)
72 68 if (!sessionId) {
73 - try { sessionId = localStorage.getItem(storageKey); } catch (e) {}
69 + sessionId = generateSessionId();
70 + this.setChatSession(botId, sessionId);
74 71 }
75 72
76 - // Re-sync cookie from localStorage if cookie was lost
77 - if (sessionId && !getCookie(cookieName)) {
78 - document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
79 - }
80 -
81 - return sessionId || null;
73 + return sessionId;
82 74 },
83 75
84 76 // Lazy session initializer — called on first user interaction
85 77 ensureSession: function(botId) {
@@ -89,10 +81,11 @@
89 81 if (instance.sessionId) {
90 82 return instance.sessionId;
91 83 }
92 84
93 - // Check for existing session from cookie or localStorage
94 - var existingSession = this.getChatSession(botId);
85 + // Check if a cookie already exists from a prior visit
86 + var cookieName = 'mxchat_session_id_' + botId;
87 + var existingSession = getCookie(cookieName);
95 88
96 89 if (existingSession) {
97 90 instance.sessionId = existingSession;
98 91 } else {
@@ -105,10 +98,12 @@
105 98 // Now that we have a session, do the deferred work
106 99 refreshNonceIfNeeded();
107 100 trackOriginatingPage();
108 101
109 - // Note: loadChatHistory is handled by showChatContainerForBot with loader UI,
110 - // so we do NOT call it here to avoid a race condition.
102 + var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
103 + if (chatPersistenceEnabled && mxchatChat.email_collection_enabled !== 'on') {
104 + loadChatHistory(botId);
105 + }
111 106
112 107 return instance.sessionId;
113 108 },
114 109
@@ -113,11 +108,9 @@
113 108 },
114 109
115 110 setChatSession: function(botId, sessionId) {
116 111 var cookieName = 'mxchat_session_id_' + botId;
117 - var storageKey = 'mxchat_session_id_' + botId;
118 112 document.cookie = cookieName + "=" + sessionId + "; path=/; max-age=86400; SameSite=Lax";
119 - try { localStorage.setItem(storageKey, sessionId); } catch (e) {}
120 113 if (this.instances[botId]) {
121 114 this.instances[botId].sessionId = sessionId;
122 115 }
123 116 },
@@ -122,10 +115,8 @@
122 115 }
123 116 },
124 117
125 118 resetChatSession: function(botId) {
126 - // Clear old session from localStorage before setting new one
127 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
128 119 var newSessionId = generateSessionId();
129 120 this.setChatSession(botId, newSessionId);
130 121 var $chatBox = getElement(botId, 'chat-box');
131 122 if ($chatBox.length) {
@@ -134,20 +125,8 @@
134 125 if (this.instances[botId]) {
135 126 this.instances[botId].chatHistoryLoaded = false;
136 127 this.instances[botId].processedMessageIds = new Set();
137 128 }
138 - },
139 -
140 - // Silent reset — new session ID without clearing the chat UI
141 - // Used when IP changes mid-conversation so the user doesn't see messages vanish
142 - silentResetSession: function(botId) {
143 - try { localStorage.removeItem('mxchat_session_id_' + botId); } catch (e) {}
144 - var newSessionId = generateSessionId();
145 - this.setChatSession(botId, newSessionId);
146 - if (this.instances[botId]) {
147 - this.instances[botId].sessionId = newSessionId;
148 - }
149 - return newSessionId;
150 129 }
151 130 };
152 131
153 132 // ====================================
@@ -667,16 +646,23 @@
667 646 errorMessage = "An error occurred. Please try again or contact support.";
668 647 }
669 648
670 649 // Handle session reset action (IP changed, session expired, etc.)
671 - // Silent reset — keep chat UI intact, just get a new session and retry
672 650 if (response.data && response.data.action === 'reset_session') {
673 - MxChatInstances.silentResetSession(botId);
674 - // Re-send the original message with the new session (user message is already displayed)
651 + // Clear the old session and generate a new one
652 + resetChatSession(botId);
653 + // Remove the temporary loading message
654 + getElement(botId, 'chat-box').find('.bot-message.temporary-message').remove();
655 + // Re-send the original message with the new session
675 656 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
676 657 if (originalMessage) {
677 658 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
678 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
659 + // Re-add the user message and thinking indicator
660 + appendMessage("user", originalMessage, '', [], false, botId);
661 + appendThinkingMessage(botId);
662 + scrollToBottom(botId);
663 + // Determine whether to use streaming
664 + const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
679 665 if (shouldUseStreaming(currentModel)) {
680 666 callMxChatStream(originalMessage, function(response) {
681 667 getElement(botId, 'chat-box').find('.bot-message.temporary-message').removeClass('temporary-message');
682 668 }, botId);
@@ -1087,16 +1073,21 @@
1087 1073 errorMessage = "An error occurred. Please try again or contact support.";
1088 1074 }
1089 1075
1090 1076 // Handle session reset action (IP changed, session expired, etc.)
1091 - // Silent reset — keep chat UI intact, just get a new session and retry
1092 1077 if (data.data && data.data.action === 'reset_session') {
1093 - MxChatInstances.silentResetSession(botId);
1094 - // Re-send the original message with the new session (user message is already displayed)
1078 + // Clear the old session and generate a new one
1079 + resetChatSession(botId);
1080 + // Re-send the original message with the new session
1095 1081 var originalMessage = getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message');
1096 1082 if (originalMessage) {
1097 1083 getElement(botId, 'mxchat-chatbot-wrapper').find('.mxchat-input-holder textarea').data('pending-message', null);
1098 - var currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1084 + // Re-add the user message and thinking indicator
1085 + appendMessage("user", originalMessage, '', [], false, botId);
1086 + appendThinkingMessage(botId);
1087 + scrollToBottom(botId);
1088 + // Determine whether to use streaming
1089 + const currentModel = mxchatChat.model || 'gpt-5.1-chat-latest';
1099 1090 if (shouldUseStreaming(currentModel)) {
1100 1091 callMxChatStream(originalMessage, callback, botId);
1101 1092 } else {
1102 1093 callMxChat(originalMessage, callback, botId);
@@ -1317,12 +1308,17 @@
1317 1308 'margin-bottom': '1em'
1318 1309 });
1319 1310 }
1320 1311
1321 - // Process the message content - always run linkify to convert markdown
1322 - // links and format text. linkify() handles existing HTML safely via
1323 - // negative lookaheads that skip URLs already inside <a> tags.
1324 - let fullMessage = linkify(messageText);
1312 + // Process the message content based on sender
1313 + let fullMessage;
1314 + if (sender === "user") {
1315 + // For user messages, apply linkify after sanitization
1316 + fullMessage = linkify(messageText);
1317 + } else {
1318 + // For bot/agent messages, preserve HTML
1319 + fullMessage = messageText;
1320 + }
1325 1321
1326 1322 // Add images if provided
1327 1323 if (images && images.length > 0) {
1328 1324 fullMessage += '<div class="image-gallery" dir="auto">';
@@ -1457,12 +1453,26 @@
1457 1453 bgColor = botMessageBgColor;
1458 1454 fontColor = botMessageFontColor;
1459 1455 }
1460 1456
1461 - // Always run linkify to convert markdown links and format text.
1462 - // linkify() already handles existing HTML (its URL patterns use negative lookaheads
1463 - // to avoid double-processing URLs that are already inside <a> tags).
1464 - var fullMessage = linkify(responseText);
1457 + // FIXED: Only linkify if response doesn't already contain HTML links or tags
1458 + // This prevents double-processing of URLs that are already formatted as HTML
1459 + var fullMessage;
1460 + if (sender === "user") {
1461 + // Always linkify user messages (they're plain text)
1462 + fullMessage = linkify(responseText);
1463 + } else {
1464 + // For bot/agent messages, check if HTML already exists
1465 + if (responseText.includes('<a href=') || responseText.includes('</a>') ||
1466 + responseText.includes('<img') || responseText.includes('<div') ||
1467 + responseText.includes('<p>') || responseText.includes('<br>')) {
1468 + // Response already has HTML, don't process it
1469 + fullMessage = responseText;
1470 + } else {
1471 + // Plain text response, apply linkify
1472 + fullMessage = linkify(responseText);
1473 + }
1474 + }
1465 1475
1466 1476 if (responseHtml) {
1467 1477 // Only add line breaks if there's actual text content before the HTML
1468 1478 if (fullMessage && fullMessage.trim()) {
@@ -1623,63 +1633,37 @@
1623 1633 // Return as a proper link without the brackets
1624 1634 return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1625 1635 });
1626 1636
1627 - // Process markdown links: [text](url) and [](url)
1628 - // Uses balanced parenthesis matching to handle URLs containing parens
1629 - // (e.g. PDF filenames with dates like (2025-08-28).pdf)
1630 - processedText = (function(input) {
1631 - var result = '';
1632 - var i = 0;
1633 - while (i < input.length) {
1634 - // Look for [ at current position
1635 - if (input[i] === '[') {
1636 - // Find closing ]
1637 - var closeBracket = input.indexOf(']', i + 1);
1638 - if (closeBracket === -1 || closeBracket + 1 >= input.length || input[closeBracket + 1] !== '(') {
1639 - result += input[i];
1640 - i++;
1641 - continue;
1642 - }
1643 - var linkText = input.substring(i + 1, closeBracket);
1644 - // Check if URL starts with http
1645 - var urlStart = closeBracket + 2;
1646 - if (!input.substring(urlStart).match(/^https?:\/\//)) {
1647 - result += input[i];
1648 - i++;
1649 - continue;
1650 - }
1651 - // Find balanced closing paren
1652 - var depth = 1;
1653 - var j = urlStart;
1654 - while (j < input.length && depth > 0) {
1655 - if (input[j] === '(') depth++;
1656 - else if (input[j] === ')') depth--;
1657 - if (depth > 0) j++;
1658 - }
1659 - if (depth !== 0) {
1660 - result += input[i];
1661 - i++;
1662 - continue;
1663 - }
1664 - var url = input.substring(urlStart, j);
1665 - var cleanUrl = url.replace(/[\].,;!?]+$/, '');
1666 - var encodedUrl = safeEncodeUrl(cleanUrl);
1667 - if (!linkText || !linkText.trim()) {
1668 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + cleanUrl + '</a>';
1669 - } else {
1670 - var safeText = sanitizeUserInput(linkText);
1671 - result += '<a href="' + encodedUrl + '" target="' + linkTarget + '">' + safeText + '</a>';
1672 - }
1673 - i = j + 1; // Skip past the closing )
1674 - } else {
1675 - result += input[i];
1676 - i++;
1677 - }
1637 + // Process proper markdown links with text: [text](url)
1638 + // This MUST have non-empty text in the first brackets
1639 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
1640 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
1641 + // Make sure we have actual text (not just whitespace)
1642 + if (!text || !text.trim()) {
1643 + // If no text, treat the URL as the text
1644 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1645 + const safeUrl = safeEncodeUrl(cleanUrl);
1646 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1678 1647 }
1679 - return result;
1680 - })(processedText);
1648 +
1649 + // Clean the URL
1650 + let cleanUrl = url.replace(/[\].,;!?]+$/, '');
1651 + const safeUrl = safeEncodeUrl(cleanUrl);
1652 + const safeText = sanitizeUserInput(text);
1653 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
1654 + });
1681 1655
1656 + // Handle empty markdown links: [](url)
1657 + // This is a specific case where there's no text
1658 + const emptyMarkdownPattern = /\[\]\((https?:\/\/[^\s)]+)\)/g;
1659 + processedText = processedText.replace(emptyMarkdownPattern, (match, url) => {
1660 + let cleanUrl = url.replace(/[.,;!?]+$/, '');
1661 + const safeUrl = safeEncodeUrl(cleanUrl);
1662 + // Use the URL itself as the link text
1663 + return `<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`;
1664 + });
1665 +
1682 1666 // Process phone numbers: [text](tel:number)
1683 1667 const phonePattern = /\[([^\]]+)\]\((tel:[\d+\-\s()]+)\)/g;
1684 1668 processedText = processedText.replace(phonePattern, (match, text, phone) => {
1685 1669 const safePhone = safeEncodeUrl(phone);
@@ -2206,19 +2190,11 @@
2206 2190 if (onComplete) onComplete();
2207 2191 return;
2208 2192 }
2209 2193
2210 - // Use getChatSession which returns null if no session exists (does NOT create one)
2211 2194 var sessionId = getChatSession(botId);
2212 2195 var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2213 2196
2214 - // No session yet — nothing to load. History will load after first message via ensureSession.
2215 - if (!sessionId) {
2216 - instance.chatHistoryLoaded = true;
2217 - if (onComplete) onComplete();
2218 - return;
2219 - }
2220 -
2221 2197 if (chatPersistenceEnabled && sessionId) {
2222 2198 $.ajax({
2223 2199 url: mxchatChat.ajax_url,
2224 2200 type: 'POST',
@@ -2229,10 +2205,10 @@
2229 2205 },
2230 2206 success: function(response) {
2231 2207 // Handle session reset (IP changed while user was away)
2232 2208 if (response.success === false && response.data && response.data.action === 'reset_session') {
2233 - // Silent reset — new session but don't clear UI
2234 - MxChatInstances.silentResetSession(botId);
2209 + // Silently reset session - user will start fresh
2210 + resetChatSession(botId);
2235 2211 instance.chatHistoryLoaded = true; // Prevent retry loop
2236 2212 if (onComplete) onComplete();
2237 2213 return;
2238 2214 }
@@ -2290,19 +2266,9 @@
2290 2266 var content = message.content;
2291 2267 content = content.replace(/\\'/g, "'").replace(/\\"/g, '"');
2292 2268 content = decodeHTMLEntities(content);
2293 2269
2294 - // Skip linkify for messages containing structured HTML
2295 - // (forms, product cards, galleries, etc.) to avoid
2296 - // markdown formatting corrupting HTML attributes
2297 - // (e.g. underscores in name="field_name" becoming <em> tags)
2298 - if (content.includes("mxchat-product-card") ||
2299 - content.includes("mxchat-image-gallery") ||
2300 - content.includes("mxchat-featured-products") ||
2301 - content.includes("<form") ||
2302 - content.includes("<input") ||
2303 - content.includes("<select") ||
2304 - content.includes("<textarea")) {
2270 + if (content.includes("mxchat-product-card") || content.includes("mxchat-image-gallery")) {
2305 2271 messageElement.html(content);
2306 2272 } else {
2307 2273 var formattedContent = linkify(content);
2308 2274 messageElement.html(formattedContent);
@@ -2529,20 +2495,14 @@
2529 2495
2530 2496 function checkPreChatDismissal(botId) {
2531 2497 botId = botId || 'default';
2532 2498 try {
2533 - var dismissedAt = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2534 - if (dismissedAt) {
2535 - // Re-show after 24 hours
2536 - var elapsed = Date.now() - parseInt(dismissedAt, 10);
2537 - if (elapsed < 86400000) {
2538 - getElement(botId, 'pre-chat-message').hide();
2539 - return;
2540 - }
2541 - // Expired — clear and show again
2542 - localStorage.removeItem('mxchat_pre_chat_dismissed_' + botId);
2499 + var dismissed = localStorage.getItem('mxchat_pre_chat_dismissed_' + botId);
2500 + if (!dismissed) {
2501 + getElement(botId, 'pre-chat-message').fadeIn(250);
2502 + } else {
2503 + getElement(botId, 'pre-chat-message').hide();
2543 2504 }
2544 - getElement(botId, 'pre-chat-message').fadeIn(250);
2545 2505 } catch (e) {
2546 2506 // localStorage unavailable — show the message
2547 2507 getElement(botId, 'pre-chat-message').fadeIn(250);
2548 2508 }
@@ -2551,9 +2511,9 @@
2551 2511 function handlePreChatDismissal(botId) {
2552 2512 botId = botId || 'default';
2553 2513 getElement(botId, 'pre-chat-message').fadeOut(200);
2554 2514 try {
2555 - localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, String(Date.now()));
2515 + localStorage.setItem('mxchat_pre_chat_dismissed_' + botId, '1');
2556 2516 } catch (e) {
2557 2517 // localStorage unavailable — dismissal won't persist
2558 2518 }
2559 2519 }
@@ -2624,14 +2584,8 @@
2624 2584 $badge.hide(); // Hide notification when opening chat
2625 2585 disableScroll();
2626 2586 $preChat.fadeOut(250);
2627 2587
2628 - // Load chat history for returning visitors (persistence)
2629 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
2630 - if (chatPersistenceEnabled) {
2631 - MxChatInstances.ensureSession(botId);
2632 - }
2633 -
2634 2588 // Deferred email check — only on first widget open
2635 2589 var emailBlocker = getElementDOM(botId, 'email-blocker');
2636 2590 var instance = MxChatInstances.get(botId);
2637 2591 if (emailBlocker && !instance.emailCheckDone) {
@@ -2636,12 +2590,8 @@
2636 2590 var instance = MxChatInstances.get(botId);
2637 2591 if (emailBlocker && !instance.emailCheckDone) {
2638 2592 instance.emailCheckDone = true;
2639 2593 resolveEmailState(botId);
2640 - } else if (!emailBlocker) {
2641 - // No email collection — still route through showChatContainerForBot
2642 - // so the loader is shown while chat history loads
2643 - showChatContainerForBot(botId);
2644 2594 }
2645 2595 } else {
2646 2596 $chatbot.removeClass('visible').addClass('hidden');
2647 2597 $(this).removeClass('hidden');
@@ -2660,9 +2610,11 @@
2660 2610
2661 2611 $(document).on('click', '.close-pre-chat-message', function(e) {
2662 2612 e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click
2663 2613 var botId = getBotIdFromElement(this);
2664 - handlePreChatDismissal(botId);
2614 + getElement(botId, 'pre-chat-message').fadeOut(200, function() {
2615 + $(this).remove();
2616 + });
2665 2617 });
2666 2618
2667 2619
2668 2620 // PDF upload button handlers - use class selector
@@ -2863,59 +2815,8 @@
2863 2815 });
2864 2816
2865 2817
2866 2818 // ====================================
2867 -// INIT LOADER & CHAT CONTAINER HELPERS
2868 -// ====================================
2869 -// These must be outside the email collection block so they're always available
2870 -// (used by persistence loading even when email collection is off)
2871 -
2872 -function showInitLoader(botId) {
2873 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2874 - if (loader) loader.style.display = 'flex';
2875 -}
2876 -
2877 -function hideInitLoader(botId) {
2878 - var loader = getElementDOM(botId, 'mxchat-init-loader');
2879 - if (loader) loader.style.display = 'none';
2880 -}
2881 -
2882 -function showEmailFormForBot(botId) {
2883 - hideInitLoader(botId);
2884 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2885 - var chatContainer = getElementDOM(botId, 'chat-container');
2886 - if (emailBlocker) emailBlocker.style.display = 'flex';
2887 - if (chatContainer) chatContainer.style.display = 'none';
2888 -}
2889 -
2890 -function showChatContainerForBot(botId) {
2891 - var emailBlocker = getElementDOM(botId, 'email-blocker');
2892 - var chatContainer = getElementDOM(botId, 'chat-container');
2893 - if (emailBlocker) emailBlocker.style.display = 'none';
2894 -
2895 - var instance = MxChatInstances.get(botId);
2896 - var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2897 -
2898 - // If persistence is on and history hasn't loaded yet, show loader
2899 - // while history loads to prevent flash of empty chat
2900 - if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2901 - if (chatContainer) chatContainer.style.display = 'none';
2902 - showInitLoader(botId);
2903 - loadChatHistory(botId, function() {
2904 - hideInitLoader(botId);
2905 - if (chatContainer) chatContainer.style.display = 'flex';
2906 - scrollToBottom(botId, true);
2907 - });
2908 - } else {
2909 - hideInitLoader(botId);
2910 - if (chatContainer) chatContainer.style.display = 'flex';
2911 - if (typeof loadChatHistory === 'function') {
2912 - loadChatHistory(botId);
2913 - }
2914 - }
2915 -}
2916 -
2917 -// ====================================
2918 2819 // EMAIL COLLECTION SETUP - MULTI-INSTANCE VERSION
2919 2820 // ====================================
2920 2821 // Only run email collection setup if it's enabled
2921 2822 if (mxchatChat && mxchatChat.email_collection_enabled === 'on') {
@@ -2951,8 +2852,40 @@
2951 2852 `;
2952 2853 document.head.appendChild(style);
2953 2854 }
2954 2855
2856 + // Helper functions for email collection (multi-instance aware)
2857 + function showEmailFormForBot(botId) {
2858 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2859 + var chatContainer = getElementDOM(botId, 'chat-container');
2860 + if (emailBlocker) emailBlocker.style.display = 'flex';
2861 + if (chatContainer) chatContainer.style.display = 'none';
2862 + }
2863 +
2864 + function showChatContainerForBot(botId) {
2865 + var emailBlocker = getElementDOM(botId, 'email-blocker');
2866 + var chatContainer = getElementDOM(botId, 'chat-container');
2867 + if (emailBlocker) emailBlocker.style.display = 'none';
2868 +
2869 + var instance = MxChatInstances.get(botId);
2870 + var chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on';
2871 +
2872 + // If persistence is on and history hasn't loaded yet, keep container
2873 + // hidden until history loads to prevent flash of empty chat
2874 + if (chatPersistenceEnabled && !instance.chatHistoryLoaded) {
2875 + if (chatContainer) chatContainer.style.display = 'none';
2876 + loadChatHistory(botId, function() {
2877 + if (chatContainer) chatContainer.style.display = 'flex';
2878 + scrollToBottom(botId, true);
2879 + });
2880 + } else {
2881 + if (chatContainer) chatContainer.style.display = 'flex';
2882 + if (typeof loadChatHistory === 'function') {
2883 + loadChatHistory(botId);
2884 + }
2885 + }
2886 + }
2887 +
2955 2888 function isValidEmailAddress(email) {
2956 2889 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2957 2890 return emailRegex.test(email.trim()) && email.length <= 254;
2958 2891 }
@@ -3088,16 +3021,15 @@
3088 3021 }
3089 3022 }
3090 3023
3091 3024 function checkSessionAndEmailForBot(botId) {
3092 - const sessionId = MxChatInstances.ensureSession(botId);
3025 + const sessionId = getChatSession(botId);
3093 3026
3094 - // Hide both panels while we check — show loader instead
3027 + // Hide both panels while we check — prevents flash of wrong state
3095 3028 var emailBlocker = getElementDOM(botId, 'email-blocker');
3096 3029 var chatContainer = getElementDOM(botId, 'chat-container');
3097 3030 if (emailBlocker) emailBlocker.style.display = 'none';
3098 3031 if (chatContainer) chatContainer.style.display = 'none';
3099 - showInitLoader(botId);
3100 3032
3101 3033 fetch(mxchatChat.ajax_url, {
3102 3034 method: 'POST',
3103 3035 headers: {
@@ -3146,9 +3078,9 @@
3146 3078 var emailInput = getElementDOM(botId, 'user-email');
3147 3079 var nameInput = getElementDOM(botId, 'user-name');
3148 3080 var userEmail = emailInput ? emailInput.value.trim() : '';
3149 3081 var userName = nameInput ? nameInput.value.trim() : '';
3150 - var sessionId = MxChatInstances.ensureSession(botId);
3082 + var sessionId = getChatSession(botId);
3151 3083
3152 3084 // Validate email
3153 3085 if (!userEmail) {
3154 3086 showEmailError(botId, 'Please enter your email address.');
@@ -3277,8 +3209,9 @@
3277 3209 $('.mxchat-chatbot-wrapper').each(function() {
3278 3210 var botId = $(this).data('bot-id') || 'default';
3279 3211 var emailBlocker = getElementDOM(botId, 'email-blocker');
3280 3212
3213 + // Only check if email blocker exists for this bot
3281 3214 if (emailBlocker) {
3282 3215 if (isEmbeddedBot(botId)) {
3283 3216 // Embedded bots are always visible — check now
3284 3217 resolveEmailState(botId);
@@ -3283,15 +3216,8 @@
3283 3216 // Embedded bots are always visible — check now
3284 3217 resolveEmailState(botId);
3285 3218 }
3286 3219 // Floating bots: handled in the widget open handler
3287 - } else if (isEmbeddedBot(botId)) {
3288 - // Embedded bot, no email collection — load history with loader
3289 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3290 - if (chatPersistenceEnabled) {
3291 - MxChatInstances.ensureSession(botId);
3292 - showChatContainerForBot(botId);
3293 - }
3294 3220 }
3295 3221 });
3296 3222 }
3297 3223
@@ -3301,17 +3227,11 @@
3301 3227 var $chatbot = getElement(botId, 'floating-chatbot');
3302 3228 if ($chatbot.hasClass('hidden')) {
3303 3229 $chatbot.removeClass('hidden').addClass('visible');
3304 3230 getElement(botId, 'floating-chatbot-button').addClass('hidden');
3305 - handlePreChatDismissal(botId);
3231 + $(this).fadeOut(250); // Hide pre-chat message
3306 3232 disableScroll(); // Disable scroll when chatbot opens
3307 3233
3308 - // Load chat history for returning visitors (persistence)
3309 - var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on';
3310 - if (chatPersistenceEnabled) {
3311 - MxChatInstances.ensureSession(botId);
3312 - }
3313 -
3314 3234 // Deferred email check — only on first widget open
3315 3235 var emailBlocker = getElementDOM(botId, 'email-blocker');
3316 3236 var instance = MxChatInstances.get(botId);
3317 3237 if (emailBlocker && !instance.emailCheckDone) {
@@ -3316,15 +3236,36 @@
3316 3236 var instance = MxChatInstances.get(botId);
3317 3237 if (emailBlocker && !instance.emailCheckDone) {
3318 3238 instance.emailCheckDone = true;
3319 3239 resolveEmailState(botId);
3320 - } else if (!emailBlocker) {
3321 - showChatContainerForBot(botId);
3322 3240 }
3323 3241 }
3324 3242 });
3325 3243
3326 - // Legacy duplicate close handler removed — handled by single event delegation above
3244 + // Dismiss pre-chat message via close button - handled by event delegation above at line ~2376
3245 + // This is a fallback for legacy support
3246 + $(document).on('click', '.close-pre-chat-message', function() {
3247 + var botId = getBotIdFromElement(this);
3248 + var $preChat = getElement(botId, 'pre-chat-message');
3249 + $preChat.fadeOut(200); // Hide the message
3250 +
3251 + // Send an AJAX request to set the transient flag for 24 hours
3252 + $.ajax({
3253 + url: mxchatChat.ajax_url,
3254 + type: 'POST',
3255 + data: {
3256 + action: 'mxchat_dismiss_pre_chat_message',
3257 + _ajax_nonce: mxchatChat.nonce
3258 + },
3259 + success: function() {
3260 + // Ensure the message is hidden after dismissal
3261 + $preChat.hide();
3262 + },
3263 + error: function() {
3264 + // Error dismissing pre-chat message - silently continue
3265 + }
3266 + });
3267 + });
3327 3268
3328 3269
3329 3270 function hasQuickQuestions(botId) {
3330 3271 botId = botId || 'default';