Changeset 3763174 for mxchat-basic/trunk/js/chat-script.js
- Timestamp:
- 10/24/2026 11:12:46 AM (5 days ago)
- File:
-
- 1 edited
-
mxchat-basic/trunk/js/chat-script.js (modified) (28 diffs)
Legend:
- Unmodified
- Added
- Removed
-
mxchat-basic/trunk/js/chat-script.js
r3751971 r3763174 60 60 mxchatChat.nonce = cachedFreshNonce; 61 61 if (callback) callback(); 62 return; 63 } 64 // The page's first nonce rides on the dynamic-settings request (plan 65 // feeb79): one /nonce?with_settings=1 call instead of two ~1 ms apart, 66 // the second of which the endpoint's 2-second per-IP limit answered 429. 67 // When that request fails this falls through to its own fetch below. 68 if (dynamicSettingsState !== 'done') { 69 dynamicSettingsWaiters.push(function () { withFreshNonce(callback); }); 70 if (dynamicSettingsState === 'idle') fetchDynamicSettings(); 62 71 return; 63 72 } … … 119 128 // posture). At most one request per page load, only if a widget opens. 120 129 var dynamicSettingsState = 'idle'; // 'idle' | 'pending' | 'done' 130 var dynamicSettingsWaiters = []; // withFreshNonce() calls waiting on it (plan feeb79) 121 131 122 132 function mxchatRefreshDynamicSettings() { 123 133 if (dynamicSettingsState !== 'idle') return; 124 134 if (typeof mxchatChat === 'undefined') return; 135 fetchDynamicSettings(); 136 } 137 138 // The one ?with_settings=1 request per page load. Also issues the page's 139 // first nonce, so withFreshNonce() waits on it instead of racing it. 140 function fetchDynamicSettings() { 125 141 dynamicSettingsState = 'pending'; 126 142 127 143 var applied = function (data) { 128 144 dynamicSettingsState = 'done'; 129 if (!data) return; // endpoint unavailable — inline values stand. 130 if (data.nonce) { 145 if (data && data.nonce) { 131 146 // Seed the nonce cache too: saves the first send's REST 132 147 // round-trip and keeps us under the endpoint's rate limit. … … 135 150 mxchatChat.nonce = data.nonce; 136 151 } 137 if (data .settings && typeof data.settings === 'object') {152 if (data && data.settings && typeof data.settings === 'object') { 138 153 $.extend(mxchatChat, data.settings); 139 154 mxchatRebuildHeaderMenus(); 140 155 } 156 // No data = endpoint unavailable: inline values stand, and each 157 // waiting withFreshNonce() call runs its own fetch and fallback. 158 var waiting = dynamicSettingsWaiters; 159 dynamicSettingsWaiters = []; 160 waiting.forEach(function (cb) { try { cb(); } catch (e) {} }); 141 161 }; 142 162 … … 253 273 if (instance.sessionId) { 254 274 return instance.sessionId; 275 } 276 277 // An embedded chat waiting for cookie consent starts no session: 278 // no session cookie, nonce request or page tracking (plan 2b137b). 279 if (mxchatEmbeddedConsentPending(botId)) { 280 return null; 255 281 } 256 282 … … 840 866 function sendMessage(botId) { 841 867 botId = botId || 'default'; 868 if (mxchatEmbeddedConsentBlocksSend(botId)) { 869 return; 870 } 842 871 MxChatInstances.ensureSession(botId); 843 872 var $chatInput = getElement(botId, 'chat-input'); … … 891 920 function sendMessageToChatbot(message, botId) { 892 921 botId = botId || 'default'; 922 if (mxchatEmbeddedConsentBlocksSend(botId)) { 923 return; 924 } 893 925 MxChatInstances.ensureSession(botId); 894 926 … … 990 1022 function callMxChat(message, callback, botId) { 991 1023 botId = botId || getMxChatBotId(); 1024 if (mxchatEmbeddedConsentBlocksSend(botId)) { 1025 return; 1026 } 992 1027 993 1028 // Streaming fallbacks land here: drop any leftover stream handle and … … 1256 1291 function callMxChatStream(message, callback, botId) { 1257 1292 botId = botId || getMxChatBotId(); 1293 if (mxchatEmbeddedConsentBlocksSend(botId)) { 1294 return; 1295 } 1258 1296 1259 1297 // Store the message in case we need to retry after session reset … … 1767 1805 const modelPrefix = model.split('-')[0].toLowerCase(); 1768 1806 1769 // Support streaming for OpenAI, Claude, Grok, DeepSeek, and OpenRouter models 1770 const isSupported = modelPrefix === 'gpt' || 1771 modelPrefix === 'o1' || 1772 modelPrefix === 'claude' || 1773 modelPrefix === 'grok' || 1774 modelPrefix === 'deepseek' || 1775 model === 'openrouter'; // Add this line - check full model name for OpenRouter 1807 // Support streaming for OpenAI, Claude, Grok, DeepSeek, OpenRouter and the 1808 // Custom (OpenAI-compatible) provider: every provider the server can stream 1809 // (plan d6c505). Gemini has no streaming route on the server, so it stays on 1810 // the regular request. 1811 const isSupported = modelPrefix === 'gpt' || 1812 modelPrefix === 'o1' || 1813 modelPrefix === 'claude' || 1814 modelPrefix === 'grok' || 1815 modelPrefix === 'deepseek' || 1816 model === 'openrouter' || // check full model name for OpenRouter 1817 model === 'custom-provider'; 1776 1818 1777 1819 return isSupported; … … 2161 2203 // renders — refresh dynamic settings at init (plan-32db95). Floating 2162 2204 // widgets refresh on first launcher open instead. 2205 // An embedded chat waiting for cookie consent refreshes when it is 2206 // revealed instead (plan 2b137b). 2163 2207 var hasEmbeddedWidget = $('.mxchat-chatbot-wrapper').filter(function() { 2164 return !$(this).closest('.floating-chatbot').length ;2208 return !$(this).closest('.floating-chatbot').length && !$(this).hasClass('no-consent'); 2165 2209 }).length > 0; 2166 2210 if (hasEmbeddedWidget) { … … 2168 2212 } 2169 2213 }); 2214 2215 // Whether a reply's html part puts anything on screen. A part made only of 2216 // markup that renders nothing (a script, a style block, comments, whitespace), 2217 // such as the Add to Cart reply's script, or an element carrying the hidden 2218 // attribute (the Add to Cart reply's cart signal since mxchat-woo 1.7.15), 2219 // or one whose inline style says display none (plan 0634d7), gets no line 2220 // breaks in front of it, so it leaves no empty lines in the bubble (plan 2221 // 4bca7e). Any other element counts as visible, even an empty one a script 2222 // fills later. Parsed in an inert template, so nothing in it runs or loads 2223 // here. Same rule as MxChat_Admin::mxchat_transcript_part_kind() in PHP. 2224 function mxchatHtmlHasVisibleContent(html) { 2225 var inlineDisplayNone = /(?:^|;)\s*display\s*:\s*none\s*(?:!\s*important\s*)?(?:;|$)/i; 2226 if (!html) { 2227 return false; 2228 } 2229 var tpl = document.createElement('template'); 2230 if (!('content' in tpl)) { 2231 return true; 2232 } 2233 tpl.innerHTML = html; 2234 var nodes = tpl.content.childNodes; 2235 for (var i = 0; i < nodes.length; i++) { 2236 var node = nodes[i]; 2237 if (node.nodeType === 3 && node.textContent.trim() !== '') { 2238 return true; 2239 } 2240 if (node.nodeType === 1 && !/^(SCRIPT|STYLE|LINK|META|TEMPLATE|NOSCRIPT)$/.test(node.tagName) && !node.hasAttribute('hidden') 2241 && !inlineDisplayNone.test(node.getAttribute('style') || '')) { 2242 return true; 2243 } 2244 } 2245 return false; 2246 } 2247 2248 // What goes between a reply's text and its html part (plans 4bca7e, 45d9b5). 2249 // Nothing in front of a part that renders nothing. Reply text that already 2250 // ends in a paragraph (linkify() wraps reply text in paragraphs, and so does 2251 // the replay) is followed by that paragraph's own bottom margin, one normal 2252 // paragraph gap; the two line breaks used to add two blank lines on top of 2253 // it. Text that does not end in a paragraph keeps the two line breaks. 2254 function mxchatHtmlPartSeparator(textHtml, html) { 2255 if (!mxchatHtmlHasVisibleContent(html)) { 2256 return ''; 2257 } 2258 return /<\/p>\s*$/i.test(String(textHtml || '')) ? '' : '<br><br>'; 2259 } 2170 2260 2171 2261 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false, botId = 'default') { … … 2234 2324 // Append HTML content if provided 2235 2325 if (messageHtml && sender !== "user") { 2236 // Only add line breaks if there's actual text content before the HTML 2326 // Only separate the HTML from actual text content before it, and 2327 // only when the HTML puts something on screen (plans 4bca7e, 45d9b5) 2237 2328 if (fullMessage && fullMessage.trim()) { 2238 fullMessage += '<br><br>'+ messageHtml;2329 fullMessage += mxchatHtmlPartSeparator(fullMessage, messageHtml) + messageHtml; 2239 2330 } else { 2240 2331 fullMessage = messageHtml; … … 2390 2481 2391 2482 if (responseHtml) { 2392 // Only add line breaks if there's actual text content before the HTML 2483 // Only separate the HTML from actual text content before it, and 2484 // only when the HTML puts something on screen (plans 4bca7e, 45d9b5) 2393 2485 if (fullMessage && fullMessage.trim()) { 2394 fullMessage += '<br><br>'+ responseHtml;2486 fullMessage += mxchatHtmlPartSeparator(fullMessage, responseHtml) + responseHtml; 2395 2487 } else { 2396 2488 fullMessage = responseHtml; … … 3089 3181 // ==================================== 3090 3182 3091 function createNotificationBadge() {3092 const chatButton = document.getElementById('floating-chatbot-button');3093 3094 if (!chatButton) return;3095 3096 // Remove any existing badge first3097 const existingBadge = chatButton.querySelector('.chat-notification-badge');3098 if (existingBadge) {3099 existingBadge.remove();3100 }3101 3102 notificationBadge = document.createElement('div');3103 notificationBadge.className = 'chat-notification-badge';3104 notificationBadge.style.cssText = `3105 display: none;3106 position: absolute;3107 top: -5px;3108 right: -5px;3109 background-color: red;3110 color: white;3111 border-radius: 50%;3112 padding: 4px 8px;3113 font-size: 12px;3114 font-weight: bold;3115 z-index: 10001;3116 `;3117 chatButton.style.position = 'relative';3118 chatButton.appendChild(notificationBadge);3119 3120 }3121 3122 3183 function showNotification(botId) { 3123 3184 botId = botId || 'default'; … … 3138 3199 } 3139 3200 3140 function startNotificationChecking(botId) { 3141 botId = botId || 'default'; 3142 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on'; 3143 if (!chatPersistenceEnabled) return; 3144 3145 createNotificationBadge(botId); 3146 var instance = MxChatInstances.get(botId); 3147 instance.notificationCheckInterval = setInterval(function() { 3148 checkForNewMessages(botId); 3149 }, 30000); // Check every 30 seconds 3150 } 3151 3152 function stopNotificationChecking(botId) { 3153 botId = botId || 'default'; 3154 var instance = MxChatInstances.get(botId); 3155 if (instance.notificationCheckInterval) { 3156 clearInterval(instance.notificationCheckInterval); 3157 } 3158 } 3159 3160 function checkForNewMessages() { 3161 const sessionId = getChatSession(); 3162 const chatPersistenceEnabled = mxchatChat.chat_persistence_toggle === 'on'; 3163 3164 if (!chatPersistenceEnabled) return; 3165 3166 $.ajax({ 3167 url: mxchatChat.ajax_url, 3168 type: 'POST', 3169 data: { 3170 action: 'mxchat_check_new_messages', 3171 session_id: sessionId, 3172 last_seen_id: lastSeenMessageId, 3173 nonce: mxchatChat.nonce 3174 }, 3175 success: function(response) { 3176 if (response.success && response.data.hasNewMessages) { 3177 showNotification(); 3178 } 3179 } 3180 }); 3181 } 3201 // Plan 0c480a: the old 30-second unread-badge poll helpers were removed. Nothing ever started them and no 3202 // server handler existed for their AJAX action. Live-agent replies reach the widget through 3203 // checkForAgentMessages() below (mxchat_fetch_new_messages every 5 s in agent mode), which also shows the 3204 // badge via showNotification(). 3182 3205 3183 3206 … … 3260 3283 // ==================================== 3261 3284 3285 // A stored bot row that is MxChat-generated markup (forms, product cards, 3286 // galleries, generated images, a script) rather than reply text. The server 3287 // stores a reply's text and its html part as two rows; replay joins them 3288 // back into one bubble (plan e19ea8). Same family check as the replay's 3289 // raw-html branch below, plus markup that renders nothing (a script part). 3290 function mxchatIsStoredHtmlPart(content) { 3291 if (/<[a-z][^>]*class\s*=\s*["'][^"']*\bmxchat-/i.test(content) || 3292 content.includes("<form") || 3293 content.includes("<input") || 3294 content.includes("<select") || 3295 content.includes("<textarea")) { 3296 return true; 3297 } 3298 return /^\s*</.test(content) && !mxchatHtmlHasVisibleContent(content); 3299 } 3300 3262 3301 function loadChatHistory(botId, onComplete) { 3263 3302 botId = botId || 'default'; … … 3339 3378 $chatBox.empty(); 3340 3379 3380 // The bot bubble the next html part of the same 3381 // turn joins (plan e19ea8), and its row's time. 3382 var $mxTurnBubble = null; 3383 var mxTurnBubbleTs = 0; 3384 var mxTurnBubbleHasHtml = false; 3385 3341 3386 $.each(response.data.conversation, function(index, message) { 3342 3387 // Skip agent messages if persistence is off 3343 3388 if (!chatPersistenceEnabled && message.role === 'agent') { 3344 3389 return; 3390 } 3391 3392 // A reply's html part is stored as its own row 3393 // after the reply's text. Live, both were one 3394 // bubble: join the part to the bot bubble just 3395 // before it when no other row came between and 3396 // it was written in the same request (plan 3397 // e19ea8). A part that renders nothing (the Add 3398 // to Cart or checkout script) already ran live 3399 // and is not replayed. A YouTube embed stays its 3400 // own bubble, as a streamed reply shows it. 3401 if (message.role === 'bot' && typeof message.content === 'string') { 3402 var mxPart = decodeHTMLEntities(message.content.replace(/\\'/g, "'").replace(/\\"/g, '"')); 3403 if (mxPart.trim() !== '' && mxchatIsStoredHtmlPart(mxPart)) { 3404 var mxPartTs = parseInt(message.timestamp, 10) || 0; 3405 var mxIsEmbed = /\bmxchat-youtube-embed\b/.test(mxPart); 3406 var mxRendersNothing = !mxchatHtmlHasVisibleContent(mxPart); 3407 var mxSameTurn = $mxTurnBubble && !mxIsEmbed && 3408 (!mxPartTs || !mxTurnBubbleTs || Math.abs(mxPartTs - mxTurnBubbleTs) <= 10000); 3409 if (mxRendersNothing || mxSameTurn) { 3410 if (!mxRendersNothing) { 3411 var mxSep = (!mxTurnBubbleHasHtml && $.trim($mxTurnBubble.text()) !== '') ? mxchatHtmlPartSeparator($mxTurnBubble.html(), mxPart) : ''; 3412 $mxTurnBubble.append(mxSep + mxPart); 3413 mxTurnBubbleHasHtml = true; 3414 } 3415 if (message.id) { 3416 highestMessageId = Math.max(highestMessageId, message.id); 3417 instance.processedMessageIds.add(message.id); 3418 } 3419 return; 3420 } 3421 } 3345 3422 } 3346 3423 … … 3407 3484 $fragment.append(messageElement); 3408 3485 3486 // Only a bot bubble from this turn takes the 3487 // turn's html parts; any other row ends the turn. 3488 if (message.role === 'bot' && !/\bmxchat-youtube-embed\b/.test(content)) { 3489 $mxTurnBubble = messageElement; 3490 mxTurnBubbleTs = parseInt(message.timestamp, 10) || 0; 3491 mxTurnBubbleHasHtml = mxchatIsStoredHtmlPart(content); 3492 } else { 3493 $mxTurnBubble = null; 3494 } 3495 3409 3496 // Track message IDs 3410 3497 if (message.id) { … … 3577 3664 // ==================================== 3578 3665 3666 // A setting arrives as "1" from the normal loader (wp_localize_script), as 3667 // a JSON true from the delayed and On User Interaction loader, or as "on" 3668 // as the settings store it: every loading mode reads it the same way (M7g). 3669 function isSettingOn(value) { 3670 return value === true || value === 'on' || value === '1' || value === 1; 3671 } 3672 3673 // MxChat's Complianz integration is switched on. 3674 function isComplianzEnabled() { 3675 return isSettingOn(mxchatChat.complianz_toggle); 3676 } 3677 3678 // Complianz's browser API is on the page. 3679 function isComplianzReady() { 3680 return typeof cmplz_has_consent === 'function' && typeof complianz !== 'undefined'; 3681 } 3682 3683 // The server found Complianz active on the site (plan 352c55), so its 3684 // script is coming even when a Delay JavaScript setup holds it back. 3685 function isComplianzExpected() { 3686 return isSettingOn(mxchatChat.complianz_active); 3687 } 3688 3689 // The consent check every opener goes through (plan 993fbf): the 3690 // visitor's click, the pre-chat bubble, Trigger, the Multi-Bot handoff and 3691 // any custom code clicking the launcher. With the integration on, the chat 3692 // opens only with Complianz's marketing consent; while Complianz's script 3693 // is expected but not loaded yet, it stays closed. 3694 function consentAllowsOpen() { 3695 if (!isComplianzEnabled()) { 3696 return true; 3697 } 3698 if (isComplianzReady()) { 3699 return cmplz_has_consent('marketing') === true; 3700 } 3701 return !isComplianzExpected(); 3702 } 3703 3704 // ---- Embedded chats (shortcode floating="no", the inline block) ------- 3705 // With the integration on, the server renders an embedded chat hidden and 3706 // marked no-consent, with a one-line placeholder in its place (plan 3707 // 2b137b). Until consent, nothing of it runs: no settings, nonce, session, 3708 // email check or history request, no mxchat:opened, no send. That work is 3709 // queued here and runs once, when the chat is revealed in place. 3710 var mxchatEmbeddedConsentWork = {}; 3711 3712 function mxchatConsentPlaceholder(botId) { 3713 return document.getElementById('mxchat-consent-placeholder-' + (botId || 'default')); 3714 } 3715 3716 // The bot is an embedded chat the server gated, still waiting for consent. 3717 function mxchatEmbeddedConsentPending(botId) { 3718 botId = botId || 'default'; 3719 var wrapper = getElementDOM(botId, 'mxchat-chatbot-wrapper'); 3720 return !!(wrapper && wrapper.classList.contains('no-consent') && isEmbeddedBot(botId)); 3721 } 3722 3723 // A send to a gated embedded chat (a script, an add-on) goes nowhere and 3724 // leaves no thinking bubble behind. 3725 function mxchatEmbeddedConsentBlocksSend(botId) { 3726 if (!mxchatEmbeddedConsentPending(botId)) { 3727 return false; 3728 } 3729 getElement(botId, 'chat-box').find('.temporary-message').remove(); 3730 return true; 3731 } 3732 3733 // Run now, or once the embedded chat is revealed. 3734 function mxchatWhenEmbeddedConsented(botId, work) { 3735 botId = botId || 'default'; 3736 if (!mxchatEmbeddedConsentPending(botId)) { 3737 work(); 3738 return; 3739 } 3740 if (!mxchatEmbeddedConsentWork) { 3741 mxchatEmbeddedConsentWork = {}; 3742 } 3743 (mxchatEmbeddedConsentWork[botId] = mxchatEmbeddedConsentWork[botId] || []).push(work); 3744 } 3745 3746 // Consent given (or nothing to ask): the chat replaces its placeholder. 3747 function mxchatShowEmbeddedChat(botId) { 3748 botId = botId || 'default'; 3749 var placeholder = mxchatConsentPlaceholder(botId); 3750 var wrapper = getElementDOM(botId, 'mxchat-chatbot-wrapper'); 3751 if (!placeholder || !wrapper || !isEmbeddedBot(botId)) { 3752 return; 3753 } 3754 placeholder.hidden = true; 3755 wrapper.classList.remove('no-consent'); 3756 var work = (mxchatEmbeddedConsentWork && mxchatEmbeddedConsentWork[botId]) || []; 3757 if (mxchatEmbeddedConsentWork) { 3758 delete mxchatEmbeddedConsentWork[botId]; 3759 } 3760 work.forEach(function(fn) { 3761 try { fn(); } catch (e) { /* one failing step never blocks the rest */ } 3762 }); 3763 } 3764 3765 // Consent withdrawn: the placeholder comes back, a reply still streaming 3766 // ends and keyboard focus leaves the chat, as the floating window does 3767 // (plan 8df5b3). 3768 function mxchatHideEmbeddedChat(botId) { 3769 botId = botId || 'default'; 3770 var placeholder = mxchatConsentPlaceholder(botId); 3771 var wrapper = getElementDOM(botId, 'mxchat-chatbot-wrapper'); 3772 if (!placeholder || !wrapper || !isEmbeddedBot(botId)) { 3773 return; 3774 } 3775 if (!wrapper.classList.contains('no-consent')) { 3776 mxchatStopStreaming(botId); 3777 wrapper.classList.add('no-consent'); 3778 if (wrapper.contains(document.activeElement)) { 3779 document.activeElement.blur(); 3780 } 3781 } 3782 placeholder.hidden = false; 3783 } 3784 3785 function watchComplianzConsent(botId) { 3786 // Initial check 3787 checkConsentAndShowChat(botId); 3788 3789 // Listen for consent changes 3790 $(document).on('cmplz_status_change', function(event) { 3791 checkConsentAndShowChat(botId); 3792 }); 3793 } 3794 3579 3795 function initializeChatVisibility(botId) { 3580 3796 botId = botId || 'default'; 3581 // The Complianz setting arrives as "1" from the normal loader 3582 // (wp_localize_script), as a JSON true from the delayed and On User 3583 // Interaction loader, or as "on" as the settings store it: every 3584 // loading mode asks for consent the same way (M7g). 3585 const complianzToggle = mxchatChat.complianz_toggle; 3586 const complianzEnabled = complianzToggle === true || 3587 complianzToggle === 'on' || 3588 complianzToggle === '1' || 3589 complianzToggle === 1; 3590 3591 if (complianzEnabled && typeof cmplz_has_consent === "function" && typeof complianz !== 'undefined') { 3592 // Initial check 3593 checkConsentAndShowChat(botId); 3594 3595 // Listen for consent changes 3596 $(document).on('cmplz_status_change', function(event) { 3597 checkConsentAndShowChat(botId); 3598 }); 3797 const complianzEnabled = isComplianzEnabled(); 3798 3799 if (complianzEnabled && isComplianzReady()) { 3800 watchComplianzConsent(botId); 3801 } else if (complianzEnabled && isComplianzExpected()) { 3802 // Complianz is active but its script is not on the page yet: a 3803 // Delay JavaScript setup can hold it back while this script runs 3804 // (plan 352c55). The launcher stays as the server rendered it, 3805 // hidden and marked no-consent, until Complianz loads and answers. 3806 var complianzWait = setInterval(function() { 3807 if (isComplianzReady()) { 3808 clearInterval(complianzWait); 3809 watchComplianzConsent(botId); 3810 } 3811 }, 250); 3599 3812 } else { 3600 // If Complianz is not enabled (or its script is not on the page),3601 // always show. The window's no-consent mark is cleared with the3813 // If Complianz is not enabled (or not installed while the setting 3814 // is on), always show. The window's no-consent mark is cleared with the 3602 3815 // launcher's, so once this script has run both marks mean the same 3603 3816 // thing in every loading mode: consent is needed and not given. … … 3607 3820 .fadeTo(500, 1); 3608 3821 getElement(botId, 'floating-chatbot').removeClass('no-consent'); 3822 mxchatShowEmbeddedChat(botId); 3609 3823 3610 3824 // Also check pre-chat message when Complianz is not enabled … … 3630 3844 .fadeTo(500, 1); 3631 3845 $chatbot.removeClass('no-consent'); 3846 mxchatShowEmbeddedChat(botId); 3632 3847 3633 3848 // Show pre-chat message if not dismissed … … 3642 3857 }); 3643 3858 $chatbot.addClass('no-consent'); 3859 3860 // Consent withdrawn while the chat is open (plan 8df5b3): close the 3861 // window too, as the title bar does, end a reply still streaming, 3862 // and take keyboard focus out of it so nothing more can be typed 3863 // or sent. The launcher stays hidden until consent is given again. 3864 if ($chatbot.hasClass('visible')) { 3865 mxchatStopStreaming(botId); 3866 $chatbot.removeClass('visible').addClass('hidden').removeAttr('aria-modal'); 3867 $widget.attr('aria-expanded', 'false'); 3868 enableScroll(); 3869 if ($chatbot[0] && $chatbot[0].contains(document.activeElement)) { 3870 document.activeElement.blur(); 3871 } 3872 } 3873 3874 mxchatHideEmbeddedChat(botId); 3644 3875 3645 3876 // Hide pre-chat message when no consent … … 3772 4003 3773 4004 if ($chatbot.hasClass('hidden')) { 4005 // Every launcher click lands here, scripted ones included: no 4006 // opening without the cookie consent Complianz requires (993fbf). 4007 if (!consentAllowsOpen()) { 4008 return; 4009 } 3774 4010 $chatbot.removeClass('hidden').addClass('visible') 3775 4011 .attr('aria-modal', 'true').attr('role', 'dialog'); … … 4538 4774 if (emailBlocker) { 4539 4775 if (isEmbeddedBot(botId)) { 4540 // Embedded bots are always visible — check now 4541 resolveEmailState(botId); 4776 // Embedded bots are always visible — check now (once 4777 // revealed, when waiting for cookie consent: plan 2b137b) 4778 mxchatWhenEmbeddedConsented(botId, function() { 4779 resolveEmailState(botId); 4780 }); 4542 4781 } 4543 4782 // Floating bots: handled in the widget open handler … … 4546 4785 var chatPersistenceEnabled = typeof mxchatChat !== 'undefined' && mxchatChat.chat_persistence_toggle === 'on'; 4547 4786 if (chatPersistenceEnabled) { 4548 MxChatInstances.ensureSession(botId); 4549 showChatContainerForBot(botId); 4787 mxchatWhenEmbeddedConsented(botId, function() { 4788 MxChatInstances.ensureSession(botId); 4789 showChatContainerForBot(botId); 4790 }); 4550 4791 } 4551 4792 } … … 4557 4798 var botId = getBotIdFromElement(this); 4558 4799 var $chatbot = getElement(botId, 'floating-chatbot'); 4559 if ($chatbot.hasClass('hidden') ) {4800 if ($chatbot.hasClass('hidden') && consentAllowsOpen()) { 4560 4801 $chatbot.removeClass('hidden').addClass('visible'); 4561 4802 getElement(botId, 'floating-chatbot-button').addClass('hidden'); … … 4712 4953 // An embedded widget is on screen from the first paint; a floating one 4713 4954 // reports mxchat:opened when its window is shown (plan 65c9b6). 4955 // An embedded chat waiting for cookie consent reports it (and 4956 // refreshes its settings) when revealed (plan 2b137b). 4714 4957 if (isEmbeddedBot(botId)) { 4715 mxchatFireOpened(botId); 4958 if (mxchatEmbeddedConsentPending(botId)) { 4959 mxchatWhenEmbeddedConsented(botId, function() { 4960 mxchatRefreshDynamicSettings(); 4961 mxchatFireOpened(botId); 4962 }); 4963 } else { 4964 mxchatFireOpened(botId); 4965 } 4716 4966 } else { 4717 4967 mxchatWatchForOpen(botId); … … 4734 4984 window.updateStreamingMessage = updateStreamingMessage; 4735 4985 window.MxChatInstances = MxChatInstances; 4986 // Add-ons wait on core's own nonce refresh (cache, in-flight request and 4987 // admin-ajax fallback shared) instead of guessing its timing (plan 3301ab). 4988 // Feature-detect it: core 3.2.26 and older do not define it. 4989 window.mxchatWithFreshNonce = withFreshNonce; 4736 4990 window.getElement = getElement; 4737 4991 window.getElementDOM = getElementDOM;
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)