| 1 |
jQuery(document).ready(function($) { |
| 2 |
// Check if floating chatbot button exists |
| 3 |
var floatingButton = $('#floating-chatbot-button'); |
| 4 |
|
| 5 |
// Retrieve color settings from PHP |
| 6 |
var userMessageBgColor = mxchatChat.user_message_bg_color; |
| 7 |
var userMessageFontColor = mxchatChat.user_message_font_color; |
| 8 |
var botMessageBgColor = mxchatChat.bot_message_bg_color; |
| 9 |
var botMessageFontColor = mxchatChat.bot_message_font_color; |
| 10 |
// Retrieve link target setting (passed from PHP through wp_localize_script) |
| 11 |
var linkTarget = mxchatChat.link_target === 'on' ? '_blank' : '_self'; // Explicitly check for 'on' or 'off' |
| 12 |
|
| 13 |
// Function to handle sending a message |
| 14 |
function sendMessage() { |
| 15 |
var message = $('#chat-input').val(); |
| 16 |
if (message) { |
| 17 |
appendMessage("user", message); |
| 18 |
$('#chat-input').val(''); |
| 19 |
|
| 20 |
// Show typing indicator |
| 21 |
appendThinkingMessage(); // Use this instead of appendMessage for the typing indicator |
| 22 |
scrollToBottom(); // Add this line |
| 23 |
|
| 24 |
callMxChat(message, function(response) { |
| 25 |
// Replace typing indicator with actual response |
| 26 |
replaceLastMessage("bot", response); |
| 27 |
}); |
| 28 |
} |
| 29 |
} |
| 30 |
|
| 31 |
// Function to append a thinking message with animation |
| 32 |
function appendThinkingMessage() { |
| 33 |
// Remove any existing thinking dots first |
| 34 |
$('.thinking-dots').remove(); |
| 35 |
|
| 36 |
// Retrieve the bot message font color and background color |
| 37 |
var botMessageFontColor = mxchatChat.bot_message_font_color; |
| 38 |
var botMessageBgColor = mxchatChat.bot_message_bg_color; |
| 39 |
|
| 40 |
var thinkingHtml = '<div class="thinking-dots-container">' + |
| 41 |
'<div class="thinking-dots">' + |
| 42 |
'<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' + |
| 43 |
'<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' + |
| 44 |
'<span class="dot" style="background-color: ' + botMessageFontColor + ';"></span>' + |
| 45 |
'</div>' + |
| 46 |
'</div>'; |
| 47 |
|
| 48 |
// Append the thinking dots to the chat container (or within the temporary message div) |
| 49 |
$("#chat-box").append('<div class="bot-message temporary-message" style="background-color: ' + botMessageBgColor + ';">' + thinkingHtml + '</div>'); |
| 50 |
scrollToBottom(); |
| 51 |
} |
| 52 |
|
| 53 |
// Trigger send button click when "Enter" key is pressed in the input field |
| 54 |
$('#chat-input').keypress(function(e) { |
| 55 |
if (e.which == 13) { |
| 56 |
e.preventDefault(); |
| 57 |
$('#send-button').click(); |
| 58 |
} |
| 59 |
}); |
| 60 |
|
| 61 |
// Handle send button click |
| 62 |
$('#send-button').click(function() { |
| 63 |
sendMessage(); |
| 64 |
}); |
| 65 |
|
| 66 |
// Use the linkTarget in your linkify function |
| 67 |
function linkify(inputText) { |
| 68 |
// Convert Markdown-style links to HTML links first |
| 69 |
var markdownLinkPattern = /\[([^\]]+)\]\(([^)]+)\)/g; |
| 70 |
var replacedText = inputText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>'); |
| 71 |
|
| 72 |
// URLs starting with http://, https://, or ftp://, but not already inside an <a> tag |
| 73 |
var urlPattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[--A-Z0-9+&@#\/%=~_|])(?![^<]*<\/a>)/gim; |
| 74 |
replacedText = replacedText.replace(urlPattern, '<a href="$1" target="' + linkTarget + '">$1</a>'); |
| 75 |
|
| 76 |
// URLs starting with "www." not already inside an <a> tag |
| 77 |
var wwwPattern = /(^|[^\/])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim; |
| 78 |
replacedText = replacedText.replace(wwwPattern, '$1<a href="http://$2" target="' + linkTarget + '">$2</a>'); |
| 79 |
|
| 80 |
return replacedText; |
| 81 |
} |
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
// Updated appendMessage function |
| 86 |
function appendMessage(sender, message, isTemporary = false) { |
| 87 |
var messageClass = sender === "user" ? "user-message" : "bot-message"; |
| 88 |
var bgColor = sender === "user" ? mxchatChat.user_message_bg_color : mxchatChat.bot_message_bg_color; |
| 89 |
var fontColor = sender === "user" ? mxchatChat.user_message_font_color : mxchatChat.bot_message_font_color; |
| 90 |
|
| 91 |
var messageDiv = $('<div>').addClass(messageClass).css({ |
| 92 |
'background': bgColor, |
| 93 |
'color': fontColor |
| 94 |
}); |
| 95 |
|
| 96 |
if (sender === "assistant") { |
| 97 |
var codeRegex = /```([^]+)```/; // Regex to detect code blocks |
| 98 |
var match = codeRegex.exec(message); |
| 99 |
message = linkify(message); |
| 100 |
|
| 101 |
if (match) { |
| 102 |
var beforeCode = message.substring(0, match.index); |
| 103 |
var codePart = match[1]; // Extracted code content |
| 104 |
var afterCode = message.substring(match.index + match[0].length); |
| 105 |
|
| 106 |
if (beforeCode.trim()) { |
| 107 |
messageDiv.append($('<span>').html(convertNewlinesToBreaks(beforeCode))); |
| 108 |
} |
| 109 |
|
| 110 |
var codeBlock = $('<pre>').append($('<code>').text(codePart)); |
| 111 |
var copyButton = $('<button>').text('Copy').addClass('copy-btn'); |
| 112 |
messageDiv.append(codeBlock).append(copyButton); |
| 113 |
|
| 114 |
copyButton.on('click', function() { |
| 115 |
copyToClipboard(codePart); |
| 116 |
alert('Code copied to clipboard!'); |
| 117 |
}); |
| 118 |
|
| 119 |
if (afterCode.trim()) { |
| 120 |
messageDiv.append($('<span>').html(convertNewlinesToBreaks(afterCode))); |
| 121 |
} |
| 122 |
} else { |
| 123 |
messageDiv.html(formatBoldText(convertNewlinesToBreaks(message))); |
| 124 |
} |
| 125 |
} else { |
| 126 |
messageDiv.html(formatBoldText(convertNewlinesToBreaks(message))); |
| 127 |
} |
| 128 |
|
| 129 |
if (isTemporary) { |
| 130 |
messageDiv.addClass('temporary-message'); // Add a class for temporary messages |
| 131 |
} |
| 132 |
|
| 133 |
messageDiv.hide().appendTo('#chat-box').fadeIn(300); // Fade in the new message |
| 134 |
} |
| 135 |
|
| 136 |
// Function to replace the last message in the chat |
| 137 |
function replaceLastMessage(sender, newMessage) { |
| 138 |
var messageClass = sender === "user" ? "user-message" : "bot-message"; |
| 139 |
var lastMessageDiv = $('#chat-box').find('.' + messageClass + '.temporary-message').last(); |
| 140 |
|
| 141 |
// Check if the new message is the rate limit message before replacing |
| 142 |
if (newMessage === mxchatChat.rate_limit_message) { |
| 143 |
// Append rate limit message without replacing anything |
| 144 |
appendMessage("bot", newMessage); |
| 145 |
return; // Exit the function to prevent replacing the rate limit message |
| 146 |
} |
| 147 |
if (lastMessageDiv.length) { |
| 148 |
lastMessageDiv.fadeOut(200, function() { |
| 149 |
// Replace the content and fade in |
| 150 |
$(this).html(newMessage).removeClass('temporary-message').fadeIn(200); |
| 151 |
}); |
| 152 |
} else { |
| 153 |
appendMessage(sender, newMessage); |
| 154 |
} |
| 155 |
scrollToBottom(); |
| 156 |
} |
| 157 |
|
| 158 |
function scrollToBottom() { |
| 159 |
var chatBox = $('#chat-box'); |
| 160 |
var newMessage = chatBox.children().last(); |
| 161 |
|
| 162 |
// Calculate the position to scroll to |
| 163 |
// which is the top of the last message |
| 164 |
var positionToScroll = newMessage.position().top + chatBox.scrollTop(); |
| 165 |
|
| 166 |
// Animate the scrolling to the calculated position |
| 167 |
chatBox.animate({ |
| 168 |
scrollTop: positionToScroll |
| 169 |
}, 500); |
| 170 |
} |
| 171 |
|
| 172 |
// Function to format text with **bold** inside double asterisks |
| 173 |
function formatBoldText(text) { |
| 174 |
return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); |
| 175 |
} |
| 176 |
|
| 177 |
// Function to convert newline characters to HTML line breaks and handle paragraph spacing |
| 178 |
function convertNewlinesToBreaks(text) { |
| 179 |
var lines = text.split('\n'); |
| 180 |
var formattedText = ''; |
| 181 |
|
| 182 |
for (var i = 0; i < lines.length; i++) { |
| 183 |
formattedText += lines[i] + '<br>'; |
| 184 |
} |
| 185 |
|
| 186 |
return formattedText; |
| 187 |
} |
| 188 |
|
| 189 |
// Copy to clipboard function |
| 190 |
// Function to copy text to clipboard |
| 191 |
function copyToClipboard(text) { |
| 192 |
var tempInput = $('<input>'); |
| 193 |
$('body').append(tempInput); |
| 194 |
tempInput.val(text).select(); |
| 195 |
document.execCommand('copy'); |
| 196 |
tempInput.remove(); |
| 197 |
} |
| 198 |
|
| 199 |
// Retrieve the custom rate limit message |
| 200 |
var rateLimitMessage = mxchatChat.rate_limit_message || "Rate limit exceeded. Please try again later."; |
| 201 |
|
| 202 |
// Call MxChat API |
| 203 |
function callMxChat(message, callback) { |
| 204 |
$.ajax({ |
| 205 |
url: mxchatChat.ajax_url, |
| 206 |
type: 'POST', |
| 207 |
dataType: 'json', |
| 208 |
data: { |
| 209 |
action: 'mxchat_handle_chat_request', |
| 210 |
message: message, |
| 211 |
nonce: mxchatChat.nonce |
| 212 |
}, |
| 213 |
success: function(response) { |
| 214 |
removeThinkingDots(); |
| 215 |
|
| 216 |
if (response.redirect_url) { |
| 217 |
// Redirect the user to the checkout page if requested |
| 218 |
window.location.href = response.redirect_url; |
| 219 |
} else if (response.message && typeof response.message === 'string') { |
| 220 |
// Handle the valid response |
| 221 |
var botMessage = linkify(response.message); |
| 222 |
botMessage = convertNewlinesToBreaks(botMessage); |
| 223 |
callback(botMessage); |
| 224 |
} else if (response.error) { |
| 225 |
// Handle errors returned by the server |
| 226 |
appendMessage("bot", response.error.message || "An unexpected error occurred."); |
| 227 |
} else { |
| 228 |
// Handle unexpected formats |
| 229 |
appendMessage("bot", "An unexpected response was received. Please try again."); |
| 230 |
} |
| 231 |
}, |
| 232 |
error: function(jqXHR, textStatus, errorThrown) { |
| 233 |
removeThinkingDots(); |
| 234 |
appendMessage("bot", "Error communicating with the server."); |
| 235 |
} |
| 236 |
}); |
| 237 |
} |
| 238 |
|
| 239 |
|
| 240 |
|
| 241 |
// Helper function to check if a string is an image HTML |
| 242 |
function isImageHtml(str) { |
| 243 |
return str.startsWith('<img') && str.endsWith('>'); |
| 244 |
} |
| 245 |
|
| 246 |
// Function to remove thinking dots |
| 247 |
function removeThinkingDots() { |
| 248 |
$('.thinking-dots').closest('.temporary-message').remove(); |
| 249 |
} |
| 250 |
|
| 251 |
function isMobile() { |
| 252 |
// This can be a simple check, or more sophisticated detection of mobile devices |
| 253 |
return window.innerWidth <= 768; // Example threshold for mobile devices |
| 254 |
} |
| 255 |
|
| 256 |
function disableScroll() { |
| 257 |
if (isMobile()) { |
| 258 |
$('body').css('overflow', 'hidden'); |
| 259 |
} |
| 260 |
} |
| 261 |
|
| 262 |
function enableScroll() { |
| 263 |
if (isMobile()) { |
| 264 |
$('body').css('overflow', ''); |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
// Function to show the chatbot widget (moved outside the Complianz logic) |
| 269 |
function showChatWidget() { |
| 270 |
setTimeout(function() { |
| 271 |
$('#floating-chatbot-button').css('display', 'flex').fadeTo(500, 1); |
| 272 |
$('#pre-chat-message').fadeIn(500); |
| 273 |
}, 250); |
| 274 |
} |
| 275 |
|
| 276 |
// Function to hide the chatbot widget |
| 277 |
function hideChatWidget() { |
| 278 |
$('#floating-chatbot-button').css('display', 'none'); |
| 279 |
$('#pre-chat-message').fadeOut(250); |
| 280 |
} |
| 281 |
|
| 282 |
var applyComplianzLogic = mxchatChat.complianz_toggle; |
| 283 |
|
| 284 |
if (applyComplianzLogic) { |
| 285 |
// Apply the Complianz logic for showing/hiding the chatbot based on consent |
| 286 |
function checkConsentAndShowChat() { |
| 287 |
var consentStatus = typeof cmplz_has_consent === "function" && cmplz_has_consent('marketing'); |
| 288 |
var consentType = typeof complianz !== 'undefined' ? complianz.consenttype : null; |
| 289 |
console.log('cmplz_has_consent("marketing"):', consentStatus); |
| 290 |
console.log('Complianz consent type:', consentType); |
| 291 |
|
| 292 |
if (consentType === 'optin' && !consentStatus) { |
| 293 |
hideChatWidget(); |
| 294 |
} else if (consentType === 'optout' && !consentStatus) { |
| 295 |
hideChatWidget(); |
| 296 |
} else { |
| 297 |
showChatWidget(); |
| 298 |
} |
| 299 |
} |
| 300 |
|
| 301 |
checkConsentAndShowChat(); |
| 302 |
|
| 303 |
$(document).on('cmplz_status_change', function(event, category) { |
| 304 |
console.log('Consent status changed for category:', category); |
| 305 |
checkConsentAndShowChat(); |
| 306 |
}); |
| 307 |
} else { |
| 308 |
// Show the chatbot by default if Complianz logic is not applied |
| 309 |
showChatWidget(); |
| 310 |
} |
| 311 |
|
| 312 |
// Toggle chatbot visibility on floating button click |
| 313 |
$(document).on('click', '#floating-chatbot-button', function() { |
| 314 |
var chatbot = $('#floating-chatbot'); |
| 315 |
if (chatbot.hasClass('hidden')) { |
| 316 |
chatbot.removeClass('hidden').addClass('visible'); |
| 317 |
$(this).addClass('hidden'); |
| 318 |
$('#pre-chat-message').fadeOut(250); // Hide pre-chat message |
| 319 |
disableScroll(); // Disable scroll when chatbot opens |
| 320 |
} else { |
| 321 |
chatbot.removeClass('visible').addClass('hidden'); |
| 322 |
$(this).removeClass('hidden'); |
| 323 |
$('#pre-chat-message').fadeIn(250); // Show pre-chat message |
| 324 |
enableScroll(); // Enable scroll when chatbot closes |
| 325 |
} |
| 326 |
}); |
| 327 |
|
| 328 |
// Close button click handler for chat widget |
| 329 |
$(document).on('click', '#exit-chat-button', function() { |
| 330 |
$('#floating-chatbot').addClass('hidden').removeClass('visible'); |
| 331 |
$('#floating-chatbot-button').removeClass('hidden'); |
| 332 |
$('#pre-chat-message').fadeIn(250); // Show pre-chat message |
| 333 |
enableScroll(); // Enable scroll when chatbot is minimized |
| 334 |
}); |
| 335 |
|
| 336 |
// Close pre-chat message on click |
| 337 |
$(document).on('click', '.close-pre-chat-message', function(e) { |
| 338 |
e.stopPropagation(); // Prevent triggering the parent .pre-chat-message click |
| 339 |
$('#pre-chat-message').fadeOut(200, function() { |
| 340 |
$(this).remove(); |
| 341 |
}); |
| 342 |
}); |
| 343 |
|
| 344 |
// Open chatbot when pre-chat message is clicked |
| 345 |
$(document).on('click', '#pre-chat-message', function() { |
| 346 |
var chatbot = $('#floating-chatbot'); |
| 347 |
if (chatbot.hasClass('hidden')) { |
| 348 |
chatbot.removeClass('hidden').addClass('visible'); |
| 349 |
$('#floating-chatbot-button').addClass('hidden'); |
| 350 |
$('#pre-chat-message').fadeOut(250); // Hide pre-chat message |
| 351 |
disableScroll(); // Disable scroll when chatbot opens |
| 352 |
} |
| 353 |
}); |
| 354 |
|
| 355 |
// If the chatbot is initially hidden, ensure the button is visible |
| 356 |
if ($('#floating-chatbot').hasClass('hidden')) { |
| 357 |
$('#floating-chatbot-button').removeClass('hidden'); |
| 358 |
} |
| 359 |
|
| 360 |
function setFullHeight() { |
| 361 |
var vh = $(window).innerHeight() * 0.01; |
| 362 |
$(':root').css('--vh', vh + 'px'); |
| 363 |
} |
| 364 |
|
| 365 |
// Set the height when the page loads |
| 366 |
$(document).ready(function() { |
| 367 |
setFullHeight(); |
| 368 |
}); |
| 369 |
|
| 370 |
// Set the height on resize and orientation change events |
| 371 |
$(window).on('resize orientationchange', function() { |
| 372 |
setFullHeight(); |
| 373 |
}); |
| 374 |
|
| 375 |
|
| 376 |
|
| 377 |
|
| 378 |
var preChatMessage = document.getElementById('pre-chat-message'); |
| 379 |
var closeButton = document.querySelector('.close-pre-chat-message'); |
| 380 |
|
| 381 |
if (closeButton) { |
| 382 |
closeButton.addEventListener('click', function() { |
| 383 |
preChatMessage.style.display = 'none'; |
| 384 |
|
| 385 |
// Send an AJAX request to set the transient flag |
| 386 |
var xhr = new XMLHttpRequest(); |
| 387 |
xhr.open('POST', mxchatChat.ajax_url, true); |
| 388 |
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); |
| 389 |
xhr.send('action=mxchat_dismiss_pre_chat_message&_ajax_nonce=' + mxchatChat.nonce); |
| 390 |
}); |
| 391 |
} |
| 392 |
|
| 393 |
|
| 394 |
}); |
| 395 |
|