PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 2.1.0
MxChat – AI Chatbot & Content Generation for WordPress v2.1.0
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 +110 -55 2.0.32.1.0 View file →
@@ -256,27 +256,57 @@
256 256 sendMessageToChatbot(question);
257 257 });
258 258
259 259
260 -// Use the linkTarget in your linkify function
260 +// Add this new function to handle markdown headers
261 +function formatMarkdownHeaders(text) {
262 + // Handle h1 to h6 headers
263 + return text.replace(/^(#{1,6})\s(.+)$/gm, function(match, hashes, content) {
264 + const level = hashes.length;
265 + return `<h${level} class="chat-heading">${content}</h${level}>`;
266 + });
267 +}
268 +
269 +// Update the linkify function to handle URLs, markdown, and phone numbers
261 270 function linkify(inputText) {
262 - // Check for already linked URLs and skip them
263 - // We use negative lookaheads to skip anything already in an <a> tag
264 - var markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
265 - var replacedText = inputText.replace(markdownLinkPattern, '<a href="$2" target="' + linkTarget + '">$1</a>');
271 + if (!inputText) return '';
272 +
273 + // Process markdown headers
274 + let processedText = formatMarkdownHeaders(inputText);
275 +
276 + // Process markdown links
277 + const markdownLinkPattern = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
278 + processedText = processedText.replace(markdownLinkPattern, (match, text, url) => {
279 + const safeUrl = encodeURI(url);
280 + const safeText = sanitizeUserInput(text);
281 + return `<a href="${safeUrl}" target="${linkTarget}">${safeText}</a>`;
282 + });
266 283
267 - // Replace standalone URLs not already in an <a> tag
268 - var urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
269 - replacedText = replacedText.replace(urlPattern, '$1<a href="$2" target="' + linkTarget + '">$2</a>');
284 + // Process phone numbers (tel:)
285 + const phonePattern = /\[([^\]]+)\]\((tel:[\d+]+)\)/g;
286 + processedText = processedText.replace(phonePattern, (match, text, phone) => {
287 + const safePhone = encodeURI(phone);
288 + const safeText = sanitizeUserInput(text);
289 + return `<a href="${safePhone}">${safeText}</a>`;
290 + });
270 291
271 - // Replace "www." prefixed URLs not already in an <a> tag
272 - var wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
273 - replacedText = replacedText.replace(wwwPattern, '$1<a href="http://$2" target="' + linkTarget + '">$2</a>');
292 + // Process standalone URLs
293 + const urlPattern = /(^|[^">])(https?:\/\/[^\s<]+)/gim;
294 + processedText = processedText.replace(urlPattern, (match, prefix, url) => {
295 + const safeUrl = encodeURI(url);
296 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
297 + });
274 298
275 - return replacedText;
299 + // Process www. URLs
300 + const wwwPattern = /(^|[^">])(www\.[\S]+(\b|$))(?![^<]*<\/a>)/gim;
301 + processedText = processedText.replace(wwwPattern, (match, prefix, url) => {
302 + const safeUrl = encodeURI(`http://${url}`);
303 + return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${url}</a>`;
304 + });
305 +
306 + return processedText;
276 307 }
277 308
278 -
279 309 function scrollElementToTop(element) {
280 310 var chatBox = $('#chat-box');
281 311 var elementTop = element.position().top + chatBox.scrollTop();
282 312 chatBox.animate({ scrollTop: elementTop }, 500);
@@ -379,10 +409,10 @@
379 409 if (response.data && response.data.filename) {
380 410 showActivePdf(response.data.filename);
381 411 activePdfFile = response.data.filename;
382 412 }
383 -
384 - // Add redirect check here
413 +
414 + // Add redirect check here
385 415 if (response.redirect_url) {
386 416 let responseText = response.text || '';
387 417 if (responseText) {
388 418 replaceLastMessage("bot", responseText);
@@ -392,8 +422,9 @@
392 422 }, 1500);
393 423 return;
394 424 }
395 425
426 +
396 427 // Check for live agent response
397 428 if (response.success && response.data && response.data.status === 'waiting_for_agent') {
398 429 updateChatModeIndicator('agent');
399 430 return;
@@ -442,11 +473,17 @@
442 473 }
443 474 });
444 475 }
445 476
477 +// Sanitize only user input
478 +function sanitizeUserInput(text) {
479 + const div = document.createElement('div');
480 + div.textContent = text;
481 + return div.innerHTML;
482 +}
483 +
484 +// Modified appendMessage function that only sanitizes user content
446 485 function appendMessage(sender, messageText = '', messageHtml = '', images = [], isTemporary = false) {
447 - //console.log("Appending message. Sender:", sender, "Content:", messageText);
448 -
449 486 try {
450 487 // Determine styles based on sender type
451 488 let messageClass, bgColor, fontColor;
452 489
@@ -453,42 +490,52 @@
453 490 if (sender === "user") {
454 491 messageClass = "user-message";
455 492 bgColor = userMessageBgColor;
456 493 fontColor = userMessageFontColor;
457 - } else if (sender === "agent") {
458 - messageClass = "agent-message";
459 - bgColor = liveAgentMessageBgColor;
460 - fontColor = liveAgentMessageFontColor;
461 - } else {
494 + // Only sanitize user input
495 + messageText = sanitizeUserInput(messageText);
496 + } else if (sender === "agent") {
497 + messageClass = "agent-message";
498 + bgColor = liveAgentMessageBgColor;
499 + fontColor = liveAgentMessageFontColor;
500 + } else {
462 501 messageClass = "bot-message";
463 502 bgColor = botMessageBgColor;
464 503 fontColor = botMessageFontColor;
465 504 }
466 505
467 - const messageDiv = $('<div>')
468 - .addClass(messageClass)
469 - .css({
470 - 'background': bgColor,
471 - 'color': fontColor,
472 - });
506 + const messageDiv = $('<div>')
507 + .addClass(messageClass)
508 + .css({
509 + 'background': bgColor,
510 + 'color': fontColor,
511 + 'margin-bottom': '1em'
512 + });
473 513
474 - // Add CSS for paragraphs
475 - messageDiv.css({
476 - 'margin-bottom': '1em'
477 - });
514 + // Process the message content based on sender
515 + let fullMessage;
516 + if (sender === "user") {
517 + // For user messages, apply linkify after sanitization
518 + fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
519 + } else {
520 + // For bot/agent messages, preserve HTML
521 + fullMessage = messageText;
522 + }
478 523
479 - // Format and process the message content
480 - let fullMessage = linkify(formatBoldText(convertNewlinesToBreaks(formatCodeBlocks(messageText))));
481 -
482 524 // Add images if provided
483 525 if (images && images.length > 0) {
484 526 fullMessage += '<div class="image-gallery">';
485 527 images.forEach(img => {
528 + // Ensure image URLs and titles are properly escaped
529 + const safeTitle = sanitizeUserInput(img.title);
530 + const safeUrl = encodeURI(img.image_url);
531 + const safeThumbnail = encodeURI(img.thumbnail_url);
532 +
486 533 fullMessage += `
487 534 <div style="margin-bottom: 10px;">
488 - <strong>${img.title}</strong><br>
489 - <a href="${img.image_url}" target="_blank">
490 - <img src="${img.thumbnail_url}" alt="${img.title}" style="max-width: 100px; height: auto; margin: 5px;" />
535 + <strong>${safeTitle}</strong><br>
536 + <a href="${safeUrl}" target="_blank">
537 + <img src="${safeThumbnail}" alt="${safeTitle}" style="max-width: 100px; height: auto; margin: 5px;" />
491 538 </a>
492 539 </div>`;
493 540 });
494 541 fullMessage += '</div>';
@@ -494,23 +541,20 @@
494 541 fullMessage += '</div>';
495 542 }
496 543
497 544 // Append HTML content if provided
498 - if (messageHtml) {
545 + if (messageHtml && sender !== "user") {
499 546 fullMessage += '<br><br>' + messageHtml;
500 547 }
501 548
502 549 messageDiv.html(fullMessage);
503 550
504 - // Add a class for temporary messages if needed
505 551 if (isTemporary) {
506 552 messageDiv.addClass('temporary-message');
507 553 }
508 554
509 - // Append the message to the chat box
510 - messageDiv.hide().appendTo('#chat-box').fadeIn(300, function () {
555 + messageDiv.hide().appendTo('#chat-box').fadeIn(300, function() {
511 556 if (sender === "bot") {
512 - // After bot's message is displayed, scroll the last user message to the top
513 557 const lastUserMessage = $('#chat-box').find('.user-message').last();
514 558 if (lastUserMessage.length) {
515 559 scrollElementToTop(lastUserMessage);
516 560 }
@@ -517,13 +561,13 @@
517 561 }
518 562 });
519 563
520 564 if (messageText.id) {
521 - lastSeenMessageId = messageText.id;
522 - hideNotification();
523 - }
565 + lastSeenMessageId = messageText.id;
566 + hideNotification();
567 + }
524 568 } catch (error) {
525 - console.error("Error rendering message with images:", error);
569 + console.error("Error rendering message:", error);
526 570 }
527 571 }
528 572
529 573
@@ -1091,10 +1135,18 @@
1091 1135 document.getElementById('word-upload').click();
1092 1136 });
1093 1137 }
1094 1138
1139 +function addSafeEventListener(elementId, eventType, handler) {
1140 + const element = document.getElementById(elementId);
1141 + if (element) {
1142 + element.addEventListener(eventType, handler);
1143 + }
1144 +}
1145 +
1146 +
1095 1147 // PDF file input change handler
1096 -document.getElementById('pdf-upload').addEventListener('change', async function(e) {
1148 +addSafeEventListener('pdf-upload', 'change', async function(e) {
1097 1149 const file = e.target.files[0];
1098 1150
1099 1151 if (!file || file.type !== 'application/pdf') {
1100 1152 alert('Please select a valid PDF file.');
@@ -1166,9 +1218,9 @@
1166 1218 }
1167 1219 });
1168 1220
1169 1221 // Word file input change handler
1170 -document.getElementById('word-upload').addEventListener('change', async function(e) {
1222 +addSafeEventListener('word-upload', 'change', async function(e) {
1171 1223 const file = e.target.files[0];
1172 1224
1173 1225 if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
1174 1226 alert('Please select a valid Word document (.docx).');
@@ -1401,21 +1453,24 @@
1401 1453 document.addEventListener('DOMContentLoaded', function() {
1402 1454 checkInitialDocumentStatus();
1403 1455 });
1404 1456
1405 -// Style all toolbar elements
1406 1457 const toolbarElements = [
1407 1458 '#mxchat-chatbot .toolbar-btn svg',
1408 1459 '#mxchat-chatbot .active-pdf-name',
1409 1460 '#mxchat-chatbot .active-word-name',
1410 1461 '#mxchat-chatbot .remove-pdf-btn svg',
1411 - '#mxchat-chatbot .remove-word-btn svg'
1462 + '#mxchat-chatbot .remove-word-btn svg',
1463 + '#mxchat-chatbot .toolbar-perplexity svg'
1412 1464 ];
1413 -$(toolbarElements.join(', ')).css({
1414 - 'fill': toolbarIconColor,
1415 - 'color': toolbarIconColor
1465 +
1466 +toolbarElements.forEach(selector => {
1467 + $(selector).css({
1468 + 'fill': toolbarIconColor,
1469 + 'stroke': toolbarIconColor,
1470 + 'color': toolbarIconColor
1471 + });
1416 1472 });
1417 -
1418 1473
1419 1474
1420 1475 // Ensure essential elements are defined
1421 1476 const emailForm = document.getElementById('email-collection-form');