1 2/* 3 * ========================================================== 4 * MAIN SCRIPT 5 * ========================================================== 6 * 7 * Main Javascript file. This file is shared by frond-end and back-end. © 2017-2022 board.support. All rights reserved. 8 * 9 */ 10 11'use strict'; 12 13(function ($) { 14 15 var version = '3.5.2'; 16 var main; 17 var global; 18 var upload_target; 19 var admin = false; 20 var tickets = false; 21 var timeout = false; 22 var timeout_label_date = [false, false]; 23 var interval = false; 24 var timeout_debounce = []; 25 var previous_search; 26 var sb_current_user = false; 27 var chat; 28 var chat_editor; 29 var chat_textarea; 30 var chat_header; 31 var chat_status; 32 var chat_emoji; 33 var chat_scroll_area; 34 var label_date; 35 var label_date_items = false; 36 var label_date_show = false; 37 var label_date_history = [9999999, '']; 38 var document_title = document.title; 39 var CHAT_SETTINGS = {}; 40 var mobile = $(window).width() < 429; 41 var today = new Date(); 42 var bot_id; 43 var force_action = ''; 44 var dialogflow_human_takeover; 45 var agents_online = false; 46 var ND = 'undefined'; 47 var cookies_supported = true; 48 var utc_offset = (new Date()).getTimezoneOffset() * 60000; 49 var cloud_data = false; 50 var articles_page = false; 51 52 /* 53 * ---------------------------------------------------------- 54 * EXTERNAL PLUGINS 55 * ---------------------------------------------------------- 56 */ 57 58 // Auto Expand Scroll Area | Schiocco 59 $.fn.extend({ manualExpandTextarea: function () { var t = this[0]; t.style.height = "auto", t.style.maxHeight = "25px"; window.getComputedStyle(t); t.style.height = (t.scrollHeight > 350 ? 350 : t.scrollHeight) + "px", t.style.maxHeight = "", $(t).trigger("textareaChanged") }, autoExpandTextarea: function () { var t = this[0]; t.addEventListener("input", function (e) { $(t).manualExpandTextarea() }, !1) } }); 60 61 // autolink-js 62 (function () { var t = [].slice; String.prototype.autoLink = function () { var n, a, r, i, c, e, l; return e = /(^|[\s\n]|<[A-Za-z]*\/?>)((?:https?|ftp):\/\/[\-A-Z0-9+\u0026\u2019@#\/%?=()~_|!:,.;]*[\-A-Z0-9+\u0026@#\/%=~()_|])/gi, 0 < (c = 1 <= arguments.length ? t.call(arguments, 0) : []).length ? (i = c[0], n = i.callback, r = function () { var t; for (a in t = [], i) l = i[a], "callback" !== a && t.push(" " + a + "='" + l + "'"); return t }().join(""), this.replace(e, function (t, a, i) { return "" + a + (("function" == typeof n ? n(i) : void 0) || "<a href='" + i + "'" + r + ">" + i + "</a>") })) : this.replace(e, "$1<a href='$2'>$2</a>") } }).call(this); 63 64 /* 65 * ---------------------------------------------------------- 66 * # FUNCTIONS 67 * ---------------------------------------------------------- 68 */ 69 70 var SBF = { 71 72 // Main Ajax function 73 ajax: function (data, onSuccess = false) { 74 data['login-cookie'] = this.loginCookie(); 75 if (!('user_id' in data) && activeUser()) { 76 data['user_id'] = activeUser().id; 77 } 78 if (!('language' in data) && typeof SB_LANG != ND) { 79 data['language'] = SB_LANG; 80 } 81 if (cloud_data) { 82 data['cloud'] = cloud_data; 83 } 84 $.ajax({ 85 method: 'POST', 86 url: SB_AJAX_URL, 87 data: data 88 }).done((response) => { 89 let result; 90 if (Array.isArray(response)) { 91 result = response; 92 } else { 93 try { 94 result = JSON.parse(response); 95 } catch (e) { 96 SBChat.is_busy_update = false; 97 SBChat.busy(false); 98 console.log(response); 99 SBF.error(response.length > 500 ? response.substr(0, 500) + '... Check the console for more details.' : response, `SBF.ajax.${data['function']}`); 100 return; 101 } 102 } 103 if (result[0] == 'success') { 104 if (onSuccess) onSuccess(result[1]); 105 } else if (SBF.errorValidation(result)) { 106 if (onSuccess) onSuccess(result); 107 } else { 108 if (admin && result[1] == 'security-error') { 109 setTimeout(() => { SBF.reset() }, 1000); 110 } 111 SBChat.is_busy_update = false;
112 SBChat.busy(false); 113 SBF.error(result[1] + (SBF.null(result[2]) ? '' : '\nFunction name: ' + result[2]) + (SBF.null(result[3]) ? '' : '\nError message: ' + (typeof result[3] == 'string' ? result[3] : ('error' in result[3] && 'message' in result[3]['error'] ? `${result[3]['error']['message']} in function '${result[3]['error']['function']}'` : result[3]))), `SBF.ajax.${data['function']}`); 114 } 115 }); 116 }, 117 118 // Cors function 119 cors: function (method = 'GET', url, onSuccess) { 120 let xhr = new XMLHttpRequest(); 121 if ('withCredentials' in xhr) { 122 xhr.open(method, url, true); 123 } else if (typeof XDomainRequest != ND) { 124 xhr = new XDomainRequest(); 125 xhr.open(method, url); 126 } else { 127 return false; 128 } 129 xhr.onload = function () { 130 onSuccess(xhr.responseText); 131 }; 132 xhr.onerror = function () { 133 return false; 134 }; 135 xhr.send(); 136 }, 137 138 // Uploads 139 upload: function (form, onSuccess) { 140 if (cloud_data) form.append('cloud', cloud_data); 141 jQuery.ajax({ 142 url: SB_URL + '/include/upload.php', 143 cache: false, 144 contentType: false, 145 processData: false, 146 data: form, 147 type: 'POST', 148 success: function (response) { 149 onSuccess(response); 150 } 151 }); 152 }, 153 154 // UTC Time 155 UTC: function (datetime) { 156 return new Date(datetime).getTime() - utc_offset; 157 }, 158 159 // Check if a variable is null or empty 160 null: function (obj) { if (typeof (obj) !== ND && obj !== null && obj !== 'null' && obj !== false && (obj.length > 0 || typeof (obj) == 'number' || typeof (obj.length) == ND) && obj !== ND) return false; else return true; }, 161 162 // Deactivate and hide the elements 163 deactivateAll: function () { 164 global.find('.sb-popup, .sb-tooltip, .sb-list .sb-menu, .sb-select ul').sbActive(false); 165 }, 166 167 // Deselect the content of the target 168 deselectAll: function () { 169 if (window.getSelection) { window.getSelection().removeAllRanges(); } 170 else if (document.selection) { document.selection.empty(); } 171 }, 172 173 // Get URL parameters 174 getURL: function (name = false, url = false) { 175 if (url == false) { 176 url = location.search; 177 } 178 if (name == false) { 179 var c = url.split('?').pop().split('&'); 180 var p = {}; 181 for (var i = 0; i < c.length; i++) { 182 var d = c[i].split('='); 183 p[d[0]] = SBF.escape(d[1]); 184 } 185 return p; 186 } 187 if (url.indexOf('?') > 0) { 188 url = url.substr(0, url.indexOf('?')); 189 } 190 return SBF.escape(decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url) || [, ""])[1].replace(/\+/g, '%20') || "")); 191 }, 192 193 URL: function () { 194 return window.location.href.substr(0, window.location.href.indexOf('?')); 195 }, 196 197 // Convert a string to slug and inverse 198 stringToSlug: function (string) { 199 string = string.trim(); 200 string = string.toLowerCase(); 201 var from = "åà áãäâèéëêìÃïîòóöôùúüûñç·/_,:;"; 202 var to = "aaaaaaeeeeiiiioooouuuunc------"; 203 for (var i = 0, l = from.length; i < l; i++) { 204 string = string.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i)); 205 } 206 return string.replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-+/, '').replace(/-+$/, '').replace(/ /g, ''); 207 }, 208 209 slugToString: function (string) { 210 string = string.replace(/_/g, ' ').replace(/-/g, ' '); 211 return string.charAt(0).toUpperCase() + string.slice(1); 212 }, 213 214 // Random string 215 random: function () { 216 let chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 217 let result = ''; 218 for (var i = 5; i > 0; --i) result += chars[Math.floor(Math.random() * 62)]; 219 return result; 220 }, 221 222 // Check if a user type is an agent 223 isAgent: function (user_type) {
224 return user_type == 'agent' || user_type == 'admin' || user_type == 'bot'; 225 }, 226 227 // Get cors elapsed of a given date from now 228 beautifyTime: function (datetime, extended = false, future = false) { 229 let date; 230 if (datetime == '0000-00-00 00:00:00') return ''; 231 if (datetime.indexOf('-') > 0) { 232 let arr = datetime.split(/[- :]/); 233 date = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]); 234 } else { 235 let arr = datetime.split(/[. :]/); 236 date = new Date(arr[2], arr[1] - 1, arr[0], arr[3], arr[4], arr[5]); 237 } 238 let date_string = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds())); 239 let diff_days = Math.round(((new Date()) - date_string) / (1000 * 60 * 60 * 24)) * (future ? -1 : 1); 240 let days = [sb_('Sunday'), sb_('Monday'), sb_('Tuesday'), sb_('Wednesday'), sb_('Thursday'), sb_('Friday'), sb_('Saturday')]; 241 let time = date_string.toLocaleTimeString('en-EN', { hour: '2-digit', minute: '2-digit' }); 242 if (time.charAt(0) === '0' && (time.includes('PM') || time.includes('AM'))) time = time.substring(1); 243 if (diff_days < 1) { 244 return `<span>${sb_('Today')}</span>${extended ? ` <span>${time}</span>` : ''}`; 245 } else if (diff_days < 8) { 246 return `<span>${days[date_string.getDay()]}</span>${extended ? ` <span>${time}</span>` : ''}`; 247 } else { 248 return `<span>${date_string.toLocaleDateString()}</span>${extended ? ` <span>${time}</span>` : ''}`; 249 } 250 }, 251 252 // Get the unix timestamp value of a data string with format yyyy-mm-dd hh:mm:ss 253 unix: function (datetime) { 254 let arr = datetime.split(/[- :]/); 255 return Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]); 256 }, 257 258 // Generate a string containing the agent location and time 259 getLocationTimeString: function (details, onSuccess) { 260 if ('timezone' in details) { 261 let location = {}; 262 location['timezone'] = details['timezone']['value']; 263 location['country'] = 'country' in details ? details['country']['value'] : location['timezone'].split('/')[0].replace(/_/g, ' '); 264 location['city'] = 'city' in details ? details['city']['value'] : location['timezone'].split('/')[1].replace(/_/g, ' '); 265 SBF.cors('GET', 'https://worldtimeapi.org/api/timezone/' + location['timezone'], function (response) { 266 response = JSON.parse(response); 267 if (SBF.null(response) || !SBF.null(response['error'])) { 268 SBF.error(response, 'SBF.getLocationTimeString()'); 269 onSuccess(responseonSuccess); 270 } else { 271 let datetime = response['datetime'].replace('T', ' '); 272 onSuccess(`${new Date(datetime.substr(0, datetime.indexOf('.'))).toLocaleString([], { hour: '2-digit', minute: '2-digit' })} ${sb_('in')} ${location['city'] ? location['city'] : ''}${location['country'] ? ', ' + location['country'] : ''}`); 273 } 274 }); 275 } 276 }, 277 278 // Date string 279 dateDB: function (date) { 280 if (date == 'now') { 281 date = (new Date).toISOString().replace('T', ' '); 282 if (date.indexOf('.') > 0) { 283 date = date.substr(0, date.indexOf('.')); 284 } 285 return date; 286 } else { 287 return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; 288 } 289 }, 290 291 // Set and get users last activity 292 updateUsersActivity: function (user_id, return_user_id, onSuccess) { 293 if (SBPusher.active) { 294 onSuccess(SBPusher.online_ids.includes(return_user_id) ? 'online' : 'offline'); 295 } else { 296 SBF.ajax({ 297 function: 'update-users-last-activity', 298 user_id: user_id, 299 return_user_id: return_user_id, 300 check_slack: !admin && CHAT_SETTINGS['slack-active'] 301 }, (response) => { 302 if (response === 'online') { 303 onSuccess('online'); 304 } else { 305 onSuccess('offline'); 306 } 307 }); 308 } 309 }, 310 311 // Search functions 312 search: function (search, searchFunction) { 313 search = search.toLowerCase(); 314 if (search == previous_search) { 315 global.find('.sb-search-btn i').sbLoading(false); 316 return; 317 } 318 clearTimeout(timeout); 319 timeout = setTimeout(function () { 320 previous_search = search; 321 searchFunction(); 322 }, 200); 323 }, 324 325 searchClear: function (icon, onSuccess) { 326 let search = $(icon).next().val(); 327 if (search) { 328 $(icon).next().val(''); 329 onSuccess(); 330 } 331 }, 332
333 // Support Board error js reporting 334 error: function (message, function_name) { 335 if (admin && SBAdmin.is_logout) return; 336 if (message instanceof Error) message = message.message; 337 if (message[message.length - 1] == '.') message = message.slice(0, -1); 338 if (message.includes('Support Board Error')) message = message.replace('Support Board Error ', '').replace(']:', ']'); 339 if (admin && message && !function_name.includes('update-users-last-activity') && !function_name.startsWith('security-error')) SBAdmin.dialog(`[Error] [${function_name}] ${message}. Check the console for more details.`, 'info'); 340 global.find('.sb-loading').sbLoading(false); 341 SBF.event('SBError', { message: message, function_name: function_name }); 342 throw new Error(`Support Board Error [${function_name}]: ${message}.`); 343 }, 344 345 errorValidation: function (response, code = true) { 346 return Array.isArray(response) && response[0] === 'validation-error' && (code === true || response[1] == code); 347 }, 348 349 // Login 350 loginForm: function (button, area = false, onSuccess = false) { 351 button = $(button); 352 if (!button.sbLoading()) { 353 if (area === false) area = button.closest('.sb-rich-login'); else area = $(area); 354 let email = $.trim(area.find('#email input').val()); 355 let password = $.trim(area.find('#password input').val()); 356 if (email == '' || password == '') { 357 area.find('.sb-info').html(sb_('Please insert email and password.')).sbActive(true); 358 } else { 359 SBF.ajax({ 360 function: 'login', 361 email: email, 362 password: password 363 }, (response) => { 364 if (response && Array.isArray(response)) { 365 if (!admin && this.isAgent(response[0]['user_type'])) { 366 SBForm.showErrorMessage(area, 'You cannot sign in as an agent.'); 367 SBChat.scrollBottom(); 368 } else { 369 let user = new SBUser(response[0]); 370 user.set('conversation_id', SBChat.conversation ? SBChat.conversation.id : false); 371 this.loginCookie(response[1]); 372 this.event('SBLoginForm', user); 373 if (onSuccess) onSuccess(response); 374 } 375 if (SBF.setting('wp-users-system') == 'wp') { 376 SBApps.wordpress.ajax('wp-login', { user: email, password: password }); 377 } 378 } else { 379 area.find('.sb-info').html(sb_('Invalid email or password.')).sbActive(true); 380 if (!admin) SBChat.scrollBottom(); 381 } 382 button.sbLoading(false); 383 }); 384 area.find('.sb-info').html('').sbActive(false); 385 button.sbLoading(true); 386 } 387 } 388 }, 389 390 // Set the login cookie 391 loginCookie: function (value = false) { 392 if (CHAT_SETTINGS.cloud) { 393 if (value === false) return storage('login'); 394 storage('login', value); 395 } else { 396 if (value === false) return this.cookie('sb-login'); 397 this.cookie('sb-login', value, 3650, 'set'); 398 } 399 }, 400 401 // Login 402 login: function (email = '', password = '', user_id = '', token = '', onSuccess = false) { 403 SBF.ajax({ 404 function: 'login', 405 email: email, 406 password: password, 407 user_id: user_id, 408 token: token 409 }, (response) => { 410 if (response != false && Array.isArray(response)) { 411 this.loginCookie(response[1]); 412 if (onSuccess) onSuccess(response); 413 return true; 414 } else { 415 return false;
416 } 417 }); 418 }, 419 420 // Logout 421 logout: function (reload = true) { 422 SBChat.stopRealTime(); 423 this.cookie('sb-login', '', '', false); 424 this.cookie('sb-cloud', '', '', false); 425 storage('open-conversation', ''); 426 storage('login', ''); 427 SBChat.conversations = false; 428 activeUser(false); 429 if (typeof sb_beams_client !== ND) { 430 sb_beams_client.stop(); 431 } 432 SBF.ajax({ 433 function: 'logout' 434 }, () => { 435 SBF.event('SBLogout'); 436 if (reload) { 437 setTimeout(() => { location.reload() }, 500); 438 } 439 }); 440 }, 441 442 // Return the active user 443 activeUser: function () { 444 return activeUser(); 445 }, 446 447 // Get the active user 448 getActiveUser: function (database = false, onSuccess) { 449 let app_login = SBApps.login(); 450 if (!app_login && (storage('wp-login') || storage('whmcs-login') || storage('perfex-login') || storage('aecommerce-login'))) { 451 this.cookie('sb-login', '', '', 'delete'); 452 activeUser(false); 453 storage('login', ''); 454 storage('wp-login', ''); 455 storage('whmcs-login', ''); 456 storage('perfex-login', ''); 457 storage('aecommerce-login', ''); 458 } 459 SBF.ajax({ 460 function: 'get-active-user', 461 db: database, 462 login_app: JSON.stringify(app_login), 463 user_token: SBF.getURL('token') 464 }, (response) => { 465 if (!response) { 466 onSuccess(); 467 return false; 468 } else { 469 if ('cookie' in response) SBF.loginCookie(response['cookie']); 470 if ('user_type' in response) { 471 if (!admin && SBF.isAgent(response['user_type'])) { 472 let message = 'You are logged in as both agent and user. Logout or use another browser, Incognito or Private mode, to login as user. Force a logout by running the function SBF.reset() in the console.'; 473 if (!storage('double-login-alert')) { 474 storage('double-login-alert', true); 475 alert(message); 476 } 477 console.warn('Support Board: ' + message); 478 SBF.event('SBDoubleLoginError'); 479 } else { 480 activeUser(new SBUser(response, 'phone' in response ? { phone: response.phone } : {})); 481 SBPusher.start(); 482 if (app_login) { 483 storage(app_login[1] + '-login', true); 484 } 485 onSuccess(); 486 SBF.event('SBActiveUserLoaded', response); 487 } 488 } 489 } 490 }); 491 }, 492 493 // Clean 494 reset: function () { 495 let cookies = ['sb-login', 'sb-cloud', 'sb-dialogflow-disabled']; 496 for (var i = 0; i < cookies.length; i++) { 497 this.cookie(cookies[i], '', 0, false); 498 } 499 try { localStorage.removeItem('support-board') } catch (e) { } 500 this.logout(); 501 }, 502 503 // Lightbox 504 lightbox: function (content) { 505 let lightbox = $(admin ? global : main).find('.sb-lightbox-media'); 506 lightbox.sbActive(true).find(' > div').html(content); 507 if (admin) SBAdmin.open_popup = lightbox; 508 }, 509 510 // Manage the local storage 511 storage: function (key, value = ND) { 512 try { if (typeof localStorage == ND) return false } catch (e) { return false } 513 let settings = localStorage.getItem('support-board'); 514 if (settings === null) { 515 settings = {}; 516 } else { 517 settings = JSON.parse(settings); 518 } 519 if (value === ND) { 520 return key in settings ? settings[key] : false;
521 } else { 522 if (!value) { 523 delete settings[key]; 524 } else { 525 settings[key] = value; 526 } 527 localStorage.setItem('support-board', JSON.stringify(settings)); 528 } 529 }, 530 531 // Save the current time or check if the saved time is older than now plus the given hours 532 storageTime: function (key, hours = false) { 533 if (hours === false) { 534 storage(key, today.getTime()); 535 } else { 536 return storage(key) == false || (today.getTime() - storage(key)) > (3600000 * hours); 537 } 538 }, 539 540 // Set or get a cookie 541 cookie: function (name, value = false, expiration_days = false, action = 'get', seconds = false) { 542 let cookie_https = location.protocol == 'https:' ? 'SameSite=None;Secure;' : ''; 543 let settings = window[admin ? 'SB_ADMIN_SETTINGS' : 'CHAT_SETTINGS']; 544 let domain = settings && settings['cookie-domain'] ? 'domain=' + settings['cookie-domain'] + ';' : ''; 545 if (action == 'get') { 546 if (!cookies_supported) { 547 return this.storage(name); 548 } 549 let cookies = document.cookie.split(';'); 550 for (var i = 0; i < cookies.length; i++) { 551 var cookie = cookies[i]; 552 while (cookie.charAt(0) == ' ') { 553 cookie = cookie.substring(1); 554 } 555 if (cookie.indexOf(name) == 0) { 556 let value = cookie.substring(name.length + 1, cookie.length); 557 return this.null(value) ? false : value; 558 } 559 } 560 return false; 561 } else if (action == 'set') { 562 if (!cookies_supported) { 563 this.storage(name, value); 564 } else { 565 let date = new Date(); 566 date.setTime(date.getTime() + (expiration_days * (seconds ? 1 : 86400) * 1000)); 567 document.cookie = name + "=" + value + ";expires=" + date.toUTCString() + ";path=/;" + cookie_https + domain; 568 } 569 } else if (this.cookie(name)) { 570 if (!cookies_supported) { 571 this.storage(name, ''); 572 } else { 573 document.cookie = name + "=" + value + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;" + cookie_https + domain; 574 } 575 } 576 }, 577 578 // Return a front setting 579 setting: function (key) { 580 return typeof CHAT_SETTINGS !== ND && key in CHAT_SETTINGS ? CHAT_SETTINGS[key] : false; 581 }, 582 583 // Return the shortcode array 584 shortcode: function (shortcode) { 585 return SBRichMessages.shortcode(shortcode); 586 }, 587 588 // Events and webhooks 589 event: function (name, parameters) { 590 $(document).trigger(name, parameters); 591 let webhooks = admin ? (typeof SB_ADMIN_SETTINGS === ND ? false : SB_ADMIN_SETTINGS['webhooks']) : CHAT_SETTINGS['webhooks']; 592 let webhooks_list = { SBSMSSent: 'sms-sent', SBLoginForm: 'login', SBRegistrationForm: 'registration', SBUserDeleted: 'user-deleted', SBEmailSent: 'email-sent', SBNewMessagesReceived: 'new-message', SBNewConversationReceived: 'new-conversation', SBActiveConversationStatusUpdated: 'conversation-status-updated', SBSlackMessageSent: 'slack-message-sent', SBMessageDeleted: 'message-deleted', SBRichMessageSubmit: 'rich-message', SBNewEmailAddress: 'new-email-address' }; 593 if (webhooks && name in webhooks_list) { 594 if (webhooks !== true) { 595 if (!Array.isArray(webhooks)) webhooks = webhooks.replace(/ /g, '').split(','); 596 if (!webhooks.includes(webhooks_list[name])) return; 597 } 598 SBF.ajax({ 599 function: 'webhooks', 600 function_name: name, 601 parameters: parameters 602 }); 603 } 604 }, 605 606 // Translate a string 607 translate: function (string) { 608 if ((!admin && SBF.null(CHAT_SETTINGS)) || (admin && typeof SB_TRANSLATIONS === ND)) return string; 609 let translations = admin ? SB_TRANSLATIONS : CHAT_SETTINGS['translations'];
610 if (translations && string in translations) { 611 return translations[string] == '' ? string : translations[string]; 612 } else { 613 return string; 614 } 615 }, 616 617 // Escape a string 618 escape: function (string) { 619 if (!string) return string; 620 return string.replaceAll('<script', '<script').replaceAll('</script', '</script').replaceAll('javascript:', '').replaceAll('onclick=', '').replaceAll('onerror=', ''); 621 }, 622 623 // Visibility change function 624 visibilityChange: function (visibility = '') { 625 if (visibility == 'hidden') { 626 if (!admin) { 627 SBChat.stopRealTime(); 628 } 629 SBChat.tab_active = false; 630 } else { 631 if (activeUser() && !admin) { 632 SBChat.startRealTime(); 633 } 634 SBChat.tab_active = true; 635 clearInterval(interval); 636 document.title = document_title; 637 } 638 }, 639 640 // Convert a settings string to an Array 641 settingsStringToArray: function (string) { 642 if (this.null(string)) return []; 643 let result = []; 644 string = string.split(','); 645 for (var i = 0; i < string.length; i++) { 646 let values = string[i].split(':'); 647 result[values[0]] = values[1] == 'false' ? false : values[1] == 'true' ? true : values[1]; 648 } 649 return result; 650 }, 651 652 // Open a browser window 653 openWindow: function (link, width = 550, height = 350) { 654 let left = (screen.width / 2) - (width / 2); 655 let top = (screen.height / 2) - (height / 2); 656 window.open(link, 'targetWindow', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=' + width + ',height=' + height + ', top=' + top + ', left=' + left); 657 return false; 658 }, 659 660 // Convert a date to local time 661 convertUTCDateToLocalDate: function (date, UTCoffset = 0) { 662 date = new Date(date); // Y/m/d H:i:s 663 date = new Date(date.getTime() + UTCoffset * 3600000); 664 return new Date(date.getTime() + utc_offset * -1); 665 }, 666 667 // Load a js or css file 668 loadResource: function (url, script = false) { 669 let head = document.getElementsByTagName('head')[0]; 670 let resource = document.createElement(script ? 'script' : 'link'); 671 if (script) { 672 resource.src = url + '?v=' + version; 673 } else { 674 resource.id = 'support-board'; 675 resource.rel = 'stylesheet'; 676 resource.type = 'text/css'; 677 resource.href = url + '?v=' + version; 678 resource.media = 'all'; 679 } 680 head.appendChild(resource); 681 }, 682 683 // Debounce 684 debounce: function (bounceFunction, id, interval = 500) { 685 if (!(id in timeout_debounce)) { 686 timeout_debounce[id] = true; 687 bounceFunction(); 688 setTimeout(() => { 689 delete timeout_debounce[id]; 690 }, interval); 691 } 692 } 693 } 694 695 /* 696 * ---------------------------------------------------------- 697 * # PUSHER 698 * ---------------------------------------------------------- 699 */ 700 701 var SBPusher = { 702 channels: {}, 703 channels_presence: [], 704 active: false, 705 pusher: false, 706 started: false, 707 pusher_beams: false, 708 initialized: false, 709 online_ids: [], 710 init_push_notifications: false, 711 sw: false, 712 713 // Initialize Pusher 714 init: function (onSuccess = false) { 715 if (SBPusher.active) { 716 if (this.pusher) { 717 return onSuccess ? onSuccess() : true; 718 } else if (onSuccess) { 719 $(window).one('SBPusherInit', () => { 720 onSuccess(); 721 }); 722 } else return; 723 this.initialized = true; 724 $.getScript('https://js.pusher.com/7.0/pusher.min.js',
724() => { 725 this.pusher = new Pusher(admin ? SB_ADMIN_SETTINGS['pusher-key'] : CHAT_SETTINGS['pusher-key'], { cluster: admin ? SB_ADMIN_SETTINGS['pusher-cluster'] : CHAT_SETTINGS['pusher-cluster'], authEndpoint: SB_URL + '/include/pusher.php', auth: { params: { login: SBF.loginCookie() } } }); 726 SBF.event('SBPusherInit'); 727 }); 728 } 729 }, 730 731 // Initialize Push notifications 732 initPushNotifications: function () { 733 if (activeUser() || admin) { 734 $.getScript('https://js.pusher.com/beams/1.0/push-notifications-cdn.js', () => { 735 window.navigator.serviceWorker.ready.then((serviceWorkerRegistration) => { 736 this.pusher_beams = new PusherPushNotifications.Client({ 737 instanceId: admin ? SB_ADMIN_SETTINGS['push-notifications-id'] : CHAT_SETTINGS['push-notifications-id'], 738 serviceWorkerRegistration: serviceWorkerRegistration, 739 }); 740 this.pusher_beams.start().then(() => this.pusher_beams.setDeviceInterests(admin ? [SB_ACTIVE_AGENT['id'], 'agents'] : [activeUser().id, 'users'])).catch(console.error); 741 this.init_push_notifications = false; 742 }); 743 }); 744 } 745 }, 746 747 // Initialize service worker 748 initServiceWorker: function () { 749 if ('serviceWorker' in navigator) { 750 navigator.serviceWorker.register(admin || typeof SB_CLOUD_SW != 'undefined' ? SB_URL + '/sw.js?v=' + version : CHAT_SETTINGS['push-notifications-url'] + '?v=' + version).then((registration) => { 751 registration.update(); 752 this.sw = registration; 753 }).catch(function (error) { 754 console.warn(error); 755 }); 756 navigator.serviceWorker.onmessage = function (e) { 757 if (admin) { 758 if (e.data.conversation_id) { 759 SBAdmin.conversations.openConversation(e.data.conversation_id, e.data.user_id); 760 SBAdmin.conversations.update(); 761 } else if (e.data.user_id) { 762 SBAdmin.profile.show(e.data.user_id); 763 SBChat.audio.play(); 764 } 765 } else if (e.data == 'sb-open-chat') { 766 SBChat.open(); 767 } 768 }; 769 } 770 }, 771 772 // Start Pusher and Push notifications 773 start: function () { 774 if (!admin && !this.started && activeUser()) { 775 if (this.active) { 776 this.init(() => { 777 this.event('client-typing', (response) => { 778 if (response.user_id == SBChat.agent_id && SBChat.conversation && response.conversation_id == SBChat.conversation.id) { 779 SBChat.typing(-1, 'start'); 780 clearTimeout(timeout); 781 timeout = setTimeout(() => { SBChat.typing(-1, 'stop') }, 1000); 782 } 783 }); 784 this.event('new-message', (response) => { 785 if (response && activeUser() && !SBF.null(response.conversation_id) && (!activeUser().getConversationByID(response.conversation_id) && (!SBChat.conversation || SBChat.conversation.id != response.conversation_id))) { 786 SBChat.updateConversations(); 787 } else { 788 SBChat.update(); 789 } 790 }); 791 this.presence(); 792 this.started = true; 793 SBChat.automations.run_all(); 794 }); 795 } 796 if (CHAT_SETTINGS['push-notifications']) { 797 if (typeof Notification != ND && Notification.permission == 'granted') this.initPushNotifications(); 798 else this.init_push_notifications = true; 799 } 800 } 801 }, 802 803 // Subscribe to a channel 804 subscribe: function (channel_name, onSuccess = false) { 805 if (!this.pusher) return this.init(() => { this.subscribe(channel_name, onSuccess) });
806 channel_name = this.cloudChannelRename(channel_name); 807 let channel = this.pusher.subscribe(channel_name); 808 channel.bind('pusher:subscription_error', (error) => { 809 return console.log(error); 810 }); 811 channel.bind('pusher:subscription_succeeded', () => { 812 this.channels[channel_name] = channel; 813 if (onSuccess) onSuccess(); 814 }) 815 }, 816 817 // Add event listener for a channel 818 event: function (event, callback, channel = 'private-user-' + activeUser().id) { 819 if (!this.pusher) return this.init(() => { this.event(event, callback, channel) }); 820 let channel_original = channel; 821 channel = this.cloudChannelRename(channel); 822 if (channel in this.channels) { 823 this.channels[channel].unbind(event); 824 this.channels[channel].bind(event, (data) => { 825 callback(data); 826 }); 827 } else this.subscribe(channel_original, () => { this.event(event, callback, channel_original) }); 828 }, 829 830 // Trigger an event 831 trigger: function (event, data = {}, channel = 'private-user-' + activeUser().id) { 832 if (event.indexOf('client-') == 0) { 833 return this.channels[this.cloudChannelRename(channel)].trigger(event, data); 834 } else { 835 SBF.ajax({ 836 function: 'pusher-trigger', 837 channel: channel, 838 event: event, 839 data: data 840 }, (response) => { return response }); 841 } 842 }, 843 844 // Presence 845 presence: function (index = 1, onSuccess) { 846 if (!this.pusher) return this.init(() => { this.presence() }); 847 let channel = this.pusher.subscribe(this.cloudChannelRename('presence-' + index)); 848 channel.bind('pusher:subscription_succeeded', (members) => { 849 if (members.count > 98) return this.subscribe(index + 1); 850 members.each((member) => { 851 if (this.presenceCheck(member)) { 852 this.online_ids.push(member.id); 853 } 854 }); 855 SBChat.updateUsersActivity(); 856 if (onSuccess) onSuccess(); 857 }) 858 channel.bind('pusher:subscription_error', (error) => { 859 return console.log(error); 860 }); 861 channel.bind('pusher:member_added', (member) => { 862 if (this.presenceCheck(member)) { 863 this.presenceAdd(member.id); 864 } 865 if (admin) SBAdmin.users.onlineUserNotification(member); 866 }); 867 channel.bind('pusher:member_removed', (member) => { 868 this.presenceRemove(member.id); 869 }); 870 this.channels_presence.push(channel); 871 if (!admin && CHAT_SETTINGS['slack-active']) { 872 this.event('add-user-presence', (response) => { 873 this.presenceAdd(response.agent_id) 874 }); 875 SBF.ajax({ 876 function: 'slack-presence', 877 list: true 878 }, (response) => { 879 for (var i = 0; i < response.length; i++) { 880 this.presenceAdd(response[i]); 881 } 882 SBChat.updateUsersActivity(); 883 }); 884 } 885 }, 886 887 presenceCheck: function (member) { 888 let agent = SBF.isAgent(member.info.user_type); 889 return ((admin && !agent) || (!admin && agent)) && !this.online_ids.includes(member.id); 890 }, 891 892 presenceAdd: function (user_id) { 893 if (typeof user_id != ND && !this.online_ids.includes(user_id)) { 894 this.online_ids.push(user_id); 895 this.presenceUpdateAdmin(user_id); 896 SBChat.updateUsersActivity(); 897 } 898 }, 899 900 presenceRemove: function (user_id) { 901 if (typeof user_id == ND) return; 902 let index = this.online_ids.indexOf(user_id); 903 if (index !== -1) { 904 this.online_ids.splice(index, 1); 905 this.presenceUpdateAdmin(user_id); 906 SBChat.updateUsersActivity(); 907 } else if (admin) { 908 global.find(`.sb-conversation-busy[data-agent="${user_id}"]`).remove(); 909 } 910 }, 911 912 presenceUnsubscribe: function () { 913 for (var i = 0; i < this.channels_presence.length; i++) { 914 this.channels_presence[i].unsubscribe(this.cloudCh
914annelRename('presence-' + (i + 1))); 915 } 916 }, 917 918 presenceUpdateAdmin: function (user_id) { 919 if (admin) { 920 if (global.find('.sb-area-users.sb-active').length) SBAdmin.users.update(); 921 if (activeUser() && activeUser().id == user_id) SBAdmin.users.updateUsersActivity(); 922 } 923 }, 924 925 pushNotification: function (message) { 926 let icon = admin ? SB_ACTIVE_AGENT['profile_image'] : activeUser().image; 927 SBF.ajax({ 928 function: 'push-notification', 929 title: admin ? SB_ACTIVE_AGENT['full_name'] : activeUser().name, 930 message: (new SBMessage()).strip(message), 931 icon: icon.indexOf('user.svg') > 0 ? CHAT_SETTINGS['notifications-icon'] : icon, 932 interests: SBChat.getRecipientUserID(), 933 conversation_id: SBChat.conversation == false ? false : SBChat.conversation.id 934 }, (response) => { return response }); 935 }, 936 937 cloudChannelRename: function (channel) { 938 return (CHAT_SETTINGS.cloud || (admin && SB_ADMIN_SETTINGS.cloud)) ? channel + '-' + (admin ? SB_ADMIN_SETTINGS.cloud.cloud_user_id : CHAT_SETTINGS.cloud.cloud_user_id) : channel; 939 } 940 } 941 942 /* 943 * ---------------------------------------------------------- 944 * GLOBAL FUNCTIONS 945 * ---------------------------------------------------------- 946 */ 947 948 window.SBF = SBF; 949 window.SBPusher = SBPusher; 950 window.sb_current_user = sb_current_user; 951 952 /* 953 * ---------------------------------------------------------- 954 * JQUERY FUNCTIONS 955 * ---------------------------------------------------------- 956 */ 957 958 // Check if active 959 $.fn.sbActive = function (show = -1) { 960 if (show === -1) return $(this).hasClass('sb-active'); 961 $(this).setClass('sb-active', show); 962 return this; 963 }; 964 965 // Loading check, set and unset 966 $.fn.sbLoading = function (value = 'check') { 967 if (value == 'check') { 968 return $(this).hasClass('sb-loading'); 969 } else { 970 $(this).setClass('sb-loading', value); 971 } 972 return this; 973 } 974 975 // Popup 976 $.fn.sbTogglePopup = function (button = false) { 977 let showed = true; 978 if (admin) SBAdmin.open_popup = false; 979 if ($(this).sbActive()) { 980 $(this).sbActive(false); 981 global.removeClass('sb-popup-active'); 982 showed = false; 983 } else { 984 global.addClass('sb-popup-active'); 985 global.find('.sb-popup').sbActive(false); 986 if (button) $(this).css('left', $(button).offset().left + 15).sbActive(true); 987 if (admin) setTimeout(() => { SBAdmin.open_popup = this }, 500); 988 SBF.deselectAll(); 989 } 990 return showed; 991 }; 992 993 // Uploader 994 $.fn.sbUploadFiles = function (onSuccess) { 995 let files = $(this).prop('files'); 996 for (var i = 0; i < files.length; i++) { 997 let file = files[i]; 998 let form = new FormData(); 999 form.append('file', file); 1000 SBF.upload(form, onSuccess); 1001 } 1002 $(this).value = ''; 1003 } 1004 1005 // Set profile box 1006 $.fn.setProfile = function (name = false, profile_image = false) { 1007 if (SBF.null(name)) name = activeUser() ? activeUser().name : ''; 1008 if (SBF.null(profile_image)) profile_image = activeUser() ? activeUser().image : SB_URL + '/media/user.svg'; 1009 $(this).find('img').attr('src', profile_image); 1010 $(this).find('.sb-name').html(name); 1011 return this; 1012 } 1013 1014 // Add or remove a class 1015 $.fn.setClass = function (class_name, add = true) { 1016 if (add) { 1017 $(this).addClass(class_name); 1018 } else { 1019 $(this).removeClass(class_name); 1020 } 1021 return this; 1022 } 1023 1024 /* 1025 * ---------------------------------------------------------- 1026 * CLASSIC FUNCTIONS 1027 * ---------------------------------------------------------- 1028 */ 1029 1030 // Delta value 1031 function sbDelta(e) { 1032 let delta = e.originalEvent.wheelDelta; 1033 if (typeof delta == ND) { 1034 delta = e.originalEvent.deltaY; 1035 } 1036 if (typeof delta == ND) { 1037 delta = e.originalEvent.detail * -1; 1038 } 1039 return delta; 1040 } 1041 1042 // Check if an elemen is loading and set it status to loading 1043 function loading(element) { 1044 if ($(element).sbLoading()) return true; 1045 else $(element).sbLoading(true); 1046 return false;
1047 } 1048 1049 // Shortcut for local storage function 1050 function storage(key, value = ND) { 1051 return SBF.storage(key, value); 1052 } 1053 1054 // Support Board js translations 1055 function sb_(string) { 1056 return SBF.translate(string); 1057 } 1058 1059 // Access the global user variable 1060 function activeUser(value = -1) { 1061 if (value === -1) { 1062 return window.sb_current_user; 1063 } else { 1064 window.sb_current_user = value; 1065 } 1066 } 1067 1068 /* 1069 * ---------------------------------------------------------- 1070 * # USER 1071 * ---------------------------------------------------------- 1072 */ 1073 1074 class SBUser { 1075 constructor(details = {}, extra = {}) { 1076 this.details = details; 1077 this.extra = extra; 1078 this.conversations = []; 1079 this.processArray(details); 1080 } 1081 1082 get id() { 1083 return this.get('id') == '' ? this.get('user_id') : this.get('id'); 1084 } 1085 1086 get type() { 1087 return this.get('user_type'); 1088 } 1089 1090 get name() { 1091 return 'first_name' in this.details ? this.details['first_name'] + (this.details['last_name'] ? ' ' + this.details['last_name'] : '') : ''; 1092 } 1093 1094 get nameBeautified() { 1095 return 'last_name' in this.details && this.details['last_name'].charAt(0) != '#' ? this.name : CHAT_SETTINGS['visitor-default-name']; 1096 } 1097 1098 get image() { 1099 return this.get('profile_image'); 1100 } 1101 1102 get language() { 1103 let language = this.getExtra('language'); 1104 if (!language) language = this.getExtra('browser_language'); 1105 return !language ? '' : language['value'].toLowerCase(); 1106 } 1107 1108 get(id) { 1109 if (id in this.details && !SBF.null(this.details[id])) return this.details[id]; 1110 else return ''; 1111 } 1112 1113 getExtra(id) { 1114 if (id in this.extra && !SBF.null(this.extra[id])) return this.extra[id]; 1115 else return ''; 1116 } 1117 1118 set(id, value) { 1119 this.details[id] = value; 1120 } 1121 1122 setExtra(id, value) { 1123 this.extra[id] = value; 1124 } 1125 1126 // Initialization 1127 processArray(details) { 1128 if (details && 'details' in details) { 1129 for (var i = 0; i < details['details'].length; i++) { 1130 this.setExtra(details['details'][i]['slug'], details['details'][i]); 1131 } 1132 delete details['details']; 1133 this.details = details; 1134 } 1135 } 1136 1137 // Update the user details and extra details 1138 update(onSuccess) { 1139 if (this.id) { 1140 SBF.ajax({ 1141 function: 'get-user', 1142 user_id: this.id, 1143 extra: true 1144 }, (response) => { 1145 this.processArray(response); 1146 onSuccess(); 1147 }); 1148 } else { 1149 SBF.error('Missing user ID', 'SBUser.update'); 1150 } 1151 } 1152 1153 // Get user conversations 1154 getConversations(onSuccess = false, exclude_id) { 1155 if (this.id) { 1156 SBF.ajax({ 1157 function: 'get-user-conversations', 1158 user_id: this.id, 1159 exclude_id: exclude_id, 1160 agent: SBF.isAgent(this.type) 1161 }, (response) => { 1162 let conversations = []; 1163 for (var i = 0; i < response.length; i++) { 1164 let status = response[i]['conversation_status_code']; 1165 if (status != 3 || !CHAT_SETTINGS['close-chat']) { 1166 let conversation = new SBConversation([new SBMessage(response[i])], { id: response[i]['conversation_id'], conversation_status_code: status, department: response[i]['department'], agent_id: response[i]['agent_id'], title: response[i]['title'] }); 1167 conversations.push(conversation); 1168 } 1169 } 1170 this.conversations = conversations; 1171 if (onSuccess) onSuccess(conversations); 1172 }); 1173 } else { 1174 SBF.error('Missing user ID', 'SBUser.getConversations'); 1175 } 1176 } 1177 1178 // Get conversations code 1179 getConversationsCode(conversations = false) { 1180 let code = ''; 1181 let active_conversation_id = SBChat.conversation ? SBChat.conversation.id : -1; 1182 if (!conversations) conversations = this.conversations; 1183 for (var i = 0; i < conversations.length; i++) { 1184 if (conversations[i] instanceof SBConversation) { 1185 code += `<li ${active_conversation_id == conversations[i].id ? 'class="sb-active" ' : ''}
1185data-conversation-status="${conversations[i].get('conversation_status_code')}" data-conversation-id="${conversations[i].id}" data-department="${conversations[i].get('department')}">${conversations[i].getCode()}</li>`; 1186 } else { 1187 SBF.error('Conversation not of type SBConversation', 'SBUser.getConversationsCode'); 1188 } 1189 } 1190 return code; 1191 } 1192 1193 // Get single conversation 1194 getFullConversation(conversation_id = false, onSuccess = false) { 1195 if (conversation_id !== false) { 1196 SBF.ajax({ 1197 function: 'get-conversation', 1198 conversation_id: conversation_id 1199 }, (response) => { 1200 let messages = []; 1201 if (response) { 1202 if (response === 'agent-not-authorized') { 1203 window.location.href = SBF.URL(); 1204 return; 1205 } 1206 for (var i = 0; i < response['messages'].length; i++) { 1207 messages.push(new SBMessage(response['messages'][i])); 1208 } 1209 } 1210 if (onSuccess) onSuccess(new SBConversation(messages, response ? response['details'] : false)); 1211 }); 1212 } else { 1213 SBF.error('Missing conversation ID', 'SBUser.getFullConversation'); 1214 } 1215 } 1216 1217 getConversationByID(conversation_id, index = false) { 1218 for (var i = 0; i < this.conversations.length; i++) { 1219 if (this.conversations[i].id == conversation_id) { 1220 return index ? i : this.conversations[i]; 1221 } 1222 } 1223 return false; 1224 } 1225 1226 // Add a new conversation 1227 addConversation(conversation) { 1228 if (conversation instanceof SBConversation) { 1229 let conversation_id = conversation.id; 1230 let is_new = true; 1231 for (var i = 0; i < this.conversations.length; i++) { 1232 if (this.conversations[i].id == conversation_id) { 1233 this.conversations[i] = conversation; 1234 is_new = false; 1235 break; 1236 } 1237 } 1238 if (is_new) { 1239 this.conversations.unshift(conversation); 1240 } 1241 return is_new; 1242 } else { 1243 SBF.error('Conversation not of type SBConversation', 'SBUser.addConversation'); 1244 } 1245 } 1246 1247 // Remove a conversation 1248 removeConversation(conversation_id) { 1249 let index = this.getConversationByID(conversation_id, true); 1250 if (index !== false) this.conversations.splice(index, 1); 1251 } 1252 1253 // Get the last conversation 1254 getLastConversation() { 1255 return this.isConversationsEmpty() ? false : this.conversations[this.conversations.length - 1]; 1256 } 1257 1258 // Check if the conversation array is empty 1259 isConversationsEmpty() { 1260 return this.conversations.length == 0; 1261 } 1262 1263 // Check if the extra array is empty 1264 isExtraEmpty() { 1265 return Object.keys(this.extra).length === 0 && this.extra.constructor === Object; 1266 } 1267 1268 // Delete the user 1269 delete(onSuccess) { 1270 if (this.id) { 1271 SBF.ajax({ 1272 function: 'delete-user', 1273 user_id: this.id 1274 }, () => { 1275 SBF.event('SBUserDeleted', this.id); 1276 onSuccess(); 1277 return true; 1278 }); 1279 } else { 1280 SBF.error('Missing user ID', 'SBUser.delete'); 1281 } 1282 } 1283 } 1284 window.SBUser = SBUser; 1285 1286 /* 1287 * ---------------------------------------------------------- 1288 * # MESSAGE 1289 * ---------------------------------------------------------- 1290 */ 1291 1292 class SBMessage { 1293 constructor(details = {}) { 1294 this.details = details; 1295 if ('first_name' in this.details) { 1296 this.details['full_name'] = this.details['first_name'] + ' ' + this.details['last_name']; 1297 } 1298 let payload = this.get('payload'); 1299 if (payload) { 1300 try { 1301 var json = JSON.parse(this.get('payload').replace("\\'", "'")); 1302 if (json && typeof json === 'object') { 1303 payload = json; 1304 } else { 1305 payload = {}; 1306 } 1307 } catch (e) { 1308 payload = {}; 1309 } 1310 } else { 1311 payload = {}; 1312 } 1313 this.set('payload', payload); 1314 } 1315 1316 get id() { 1317 return this.get('id'); 1318 } 1319 1320 get attachments() { 1321 return !SBF.null(this.details['attachments']) ? JSON.parse(this.details['attachments']) : []; 1322 } 1323 1324 get message() { 1325 return this.get('message'); 1326 } 1327 1328 get(id) { 1329 if (id in this.details && !SBF.null(this.details[id])) return this.details[id]; 1330 else return ''; 1331 } 1332 1333 set(id, value) { 1334 this.details[id] = value; 1335 } 1336 1337 payload(key = false, value = false) { 1338 let payload = this.get('payload'); 1339 if (key !== false && value !== false) { 1340 payload[key] = value; 1341 this.set('payload', payload); 1342 } else if (key !== false) { 1343 return key in payload ? payload.key : ('id' in payload && payload.id == key ? payload : false); 1344 } 1345 return payload; 1346 } 1347 1348 getCode(translation = false) { 1349 let agent = SBF.isAgent(this.details['user_type']); 1350 let admin_menu = admin ? SBAdmin.conversations.messageMenu(agent) : ''; 1351 let message = translation ? this.get('translation') : (!this.message && 'preview' in this.payload ? this.payload.preview : this.message); 1352 let attachments = this.attachments; 1353 let attachments_code = ''; 1354 let media_code = ''; 1355 let thumb = (admin && SB_ADMIN_SETTINGS['show-profile-images']) || (!admin && ((agent && !CHAT_SETTINGS['hide-agents-thumb']) || (!agent && CHAT_SETTINGS['display-users-thumb']))) ? `<div class="sb-thumb"><img src="${this.details['profile_image']}"><div class="sb-tooltip"><div>${this.details['full_name']}</div></div></div>` : ''; 1356 let css = (!agent ? 'sb-right' : '') + (thumb == '' ? '' : ' sb-thumb-active'); 1357 let type = ''; 1358 if (!message && !attachments.length) return ''; 1359 1360 // Rich Messages 1361 if (agent) { 1362 let shortcodes = message.match(/\[.+?\]/g) || []; 1363 let rich = false;
1364 let count = shortcodes.length; 1365 for (var i = 0; i < count; i++) { 1366 let settings = SBRichMessages.shortcode(shortcodes[i]); 1367 let rich_message = SBRichMessages.generate(settings[1], settings[0]); 1368 if (rich_message) { 1369 if (message.charAt(0) != '[') { 1370 rich_message = rich_message.replace('sb-rich-message', 'sb-rich-message sb-margin-top'); 1371 } 1372 if (message.charAt(message.length - 1) != ']') { 1373 rich_message = rich_message.replace('sb-rich-message', 'sb-rich-message sb-margin-bottom'); 1374 } 1375 message = message.replace(shortcodes[i], '{{RM}}'); 1376 message = this.render(message).replace('{{RM}}', rich_message); 1377 rich = true; 1378 type = `data-type="${settings[0]}"`; 1379 } 1380 } 1381 if (rich) { 1382 css += ' sb-rich-cnt'; 1383 if (count > 1) { 1384 type = 'data-type="multiple"'; 1385 } 1386 } 1387 if (!count) message = this.render(message); 1388 } else message = this.render(message); 1389 1390 // Attachments 1391 if (attachments.length) { 1392 attachments_code = '<div class="sb-message-attachments">'; 1393 for (var i = 0; i < attachments.length; i++) { 1394 let url = attachments[i][1]; 1395 if (/.jpg|.jpeg|.png|.gif/.test(url)) { 1396 media_code += `<div class="sb-image${url.includes('.png') ? ' sb-image-png' : ''}"><img src="${url}" /></div>`; 1397 } else { 1398 attachments_code += `<a rel="noopener" target="_blank" href="${url}">${attachments[i][0]}</a>`; 1399 } 1400 } 1401 attachments_code += '</div>'; 1402 } 1403 1404 // Message creation 1405 return `<div data-id="${this.details['id']}" class="${css}" ${type}>${thumb}<div class="sb-cnt"><div class="sb-message${media_code && !message ? ' sb-message-media' : ''}">${message}${media_code}</div>${attachments_code}<div class="sb-time">${SBF.beautifyTime(this.details['creation_time'], true)}</div></div>${admin_menu}</div>`; 1406 } 1407 1408 render(message = false) { 1409 if (message === false) message = '' + this.details['message']; 1410 let len = message.length; 1411 1412 // Code block 1413 let codes = message.match(/```([\s\S]*)```/) || []; 1414 for (var i = 0; i < codes.length; i++) { 1415 message = message.replace(codes[i], '[code-' + i + ']'); 1416 } 1417 1418 // Breakline 1419 message = message.replace(/(?:\r\n|\r|\n)/g, '<br>'); 1420 1421 // Bold 1422 message = message.replace(/\*([^\**]+)\*/g, "<b>$1</b>"); 1423 1424 // Italic 1425 message = message.replace(/\__([^\____]+)\__/g, "<i>$1</i>"); 1426 1427 // Strikethrough 1428 message = message.replace(/\~([^\~~]+)\~/g, "<del>$1</del>"); 1429 1430 // Code 1431 message = message.replace(/\`([^\``]+)\`/g, "<code>$1</code>"); 1432 1433 // Unicode 1434 let false_positives = ['cede', 'ubbed']; 1435 for (var i = 0; i < false_positives.length; i++) { 1436 message = message.replaceAll(false_positives[i], `[U${i}]`); 1437 } 1438 message = message.replaceAll('uccede', '[U1]'); 1439 message = message.replace(/u([0-9a-f]{4,5})/mi, '&#x$1;'); 1440 for (var i = 0; i < false_positives.length; i++) { 1441 message = message.replaceAll(`[U${i}]`, false_positives[i]); 1442 } 1443 1444 // Single emoji 1445 if (((len == 6 || len == 5) && message.startsWith('&#x')) || len < 3 && message.match(/(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/)) { 1446 message = `<span class="emoji-large">${message}</span>`;
1447 } 1448 1449 // Links 1450 if (message.includes('http')) { 1451 message = message.autoLink({ target: '_blank' }); 1452 } 1453 1454 // Code block restore 1455 for (var i = 0; i < codes.length; i++) { 1456 message = message.replace('[code-' + i + ']', '<pre>' + $.trim($.trim(codes[i]).substr(3, codes[i].length - 6).replace(/(?:\r\n|\r|\n)/g, '<br>'))) + '</pre>'; 1457 } 1458 1459 return message.replace(/&lt;/g, '<'); 1460 } 1461 1462 strip(message = false) { 1463 if (message === false) message = '' + this.details['message']; 1464 let replaces = [message.match(/\*([^\**]+)\*/g), message.match(/\__([^\____]+)\__/g), message.match(/\~([^\~~]+)\~/g), message.match(/\`([^\``]+)\`/g)]; 1465 let chars = ['*', '__', '~', '`']; 1466 for (var i = 0; i < replaces.length; i++) { 1467 let items = replaces[i]; 1468 if (!SBF.null(items)) { 1469 for (var y = 0; y < items.length; y++) { 1470 message = message.replace(items[y], items[y].replaceAll(chars[i], '')); 1471 } 1472 } 1473 } 1474 return message; 1475 } 1476 } 1477 window.SBMessage = SBMessage; 1478 1479 /* 1480 * ---------------------------------------------------------- 1481 * # CONVERSATION 1482 * ---------------------------------------------------------- 1483 */ 1484 1485 class SBConversation { 1486 constructor(messages, details) { 1487 this.details = SBF.null(details) ? {} : details; 1488 if (Array.isArray(messages)) { 1489 this.messages = []; 1490 if (messages.length) { 1491 if (messages[0] instanceof SBMessage) { 1492 this.messages = messages; 1493 } else { 1494 SBF.error('Messages not of type SBMessage', 'SBConversation.constructor'); 1495 } 1496 } 1497 } else { 1498 SBF.error('Message array not of type Array', 'SBConversation.constructor'); 1499 } 1500 } 1501 1502 get id() { 1503 return this.get('id') == '' ? this.get('conversation_id') : this.get('id'); 1504 } 1505 1506 get(id) { 1507 if (id in this.details && !SBF.null(this.details[id])) { 1508 return this.details[id]; 1509 } 1510 if (id == 'title') { 1511 if (!SBF.null(this.details['first_name'])) { 1512 return this.details['first_name'] + ' ' + this.details['last_name']; 1513 } else if (this.messages.length) { 1514 return this.messages[0].get('full_name'); 1515 } 1516 } 1517 return ''; 1518 } 1519 1520 set(id, value) { 1521 this.details[id] = value; 1522 } 1523 1524 getMessage(id) { 1525 for (var i = 0; i < this.messages.length; i++) { 1526 if (this.messages[i].id == id) { 1527 this.messages[i].set('index', i); 1528 return this.messages[i]; 1529 } 1530 } 1531 return false; 1532 } 1533 1534 getLastMessage() { 1535 let index = this.messages.length - 1; 1536 for (var i = index; i > -1; i--) { 1537 if (this.messages[i].message || this.messages[i].attachments.length) return this.messages[i]; 1538 } 1539 return false; 1540 } 1541 1542 getLastUserMessage(index = false, agent = false) { 1543 if (index === false) index = this.messages.length - 1; 1544 for (var i = index; i > -1; i--) { 1545 let message = this.messages[i]; 1546 let user_type = message.get('user_type'); 1547 if (!message.message && !message.attachments.length) continue; 1548 if ((!agent && !SBF.isAgent(user_type)) || (agent === true && (user_type == 'agent' || user_type == 'admin')) || (agent == 'bot' && user_type == 'bot') || (agent == 'no-bot' && user_type != 'bot') || (agent == 'all' && SBF.isAgent(user_type))) { 1549 this.messages[i].set('index', i - 1); 1550 return this.messages[i]; 1551 } 1552 } 1553 return false;
1554 } 1555 1556 getUserMessages(user_type = 'user') { 1557 let results = []; 1558 let checks = user_type == 'user' ? ['visitor', 'lead', 'user'] : (user_type == 'agents' ? ['agent', 'admin'] : ['bot']); 1559 for (var i = 0; i < this.messages.length; i++) { 1560 if (checks.includes(this.messages[i].get('user_type'))) { 1561 results.push(this.messages[i]); 1562 } 1563 } 1564 return results; 1565 } 1566 1567 updateMessage(id, message) { 1568 if (message instanceof SBMessage) { 1569 for (var i = 0; i < this.messages.length; i++) { 1570 if (this.messages[i].id == id) { 1571 this.messages[i] = message; 1572 return true; 1573 } 1574 } 1575 } else { 1576 SBF.error('Message not of type SBMessage', 'SBConversation.updateMessage'); 1577 } 1578 return false; 1579 } 1580 1581 addMessages(messages) { 1582 if (Array.isArray(messages)) { 1583 for (var i = 0; i < messages.length; i++) { 1584 if (messages[i] instanceof SBMessage) { 1585 this.messages.push(messages[i]); 1586 } 1587 } 1588 } else { 1589 if (messages instanceof SBMessage) { 1590 this.messages.push(messages); 1591 } else { 1592 SBF.error('Messages not of type SBMessage', 'SBConversation.addMessages()'); 1593 } 1594 } 1595 return this; 1596 } 1597 1598 getCode() { 1599 let count = this.messages.length; 1600 if (count) { 1601 let message = this.messages[count - 1]; 1602 let text = message.message; 1603 if (text.indexOf('[') !== false) { 1604 let shortcodes = text.match(/\[.+?\]/g) || []; 1605 if (shortcodes.length) { 1606 let shortcode = SBRichMessages.shortcode(shortcodes[0]); 1607 text = text.replace(shortcodes[0], sb_(SBF.null(shortcode[1]['message']) ? (SBF.null(shortcode[1]['title']) ? '' : shortcode[1]['title']) : shortcode[1]['message'])); 1608 } 1609 } 1610 let title = this.get('title'); 1611 if (!title || (tickets && CHAT_SETTINGS['tickets-conversations-title-user'])) title = SBF.isAgent(message.get('user_type')) ? message.get('full_name') : sb_('You'); 1612 return `<div class="sb-conversation-item" data-user-id="${message.get('user_id')}"><img src="${message.get('profile_image')}"><div><span class="sb-name">${title}</span><span class="sb-time">${SBF.beautifyTime(message.get('creation_time'))}</span></div><div class="sb-message">${text.length > 114 ? text.substr(0, 114) + ' ...' : text}</div></div>`; 1613 } 1614 return ''; 1615 } 1616 1617 deleteMessage(id) { 1618 for (var i = 0; i < this.messages.length; i++) { 1619 if (this.messages[i].id == id) { 1620 this.messages.splice(i, 1); 1621 return true; 1622 } 1623 } 1624 return false; 1625 } 1626 1627 searchMessages(search, exact_match = false) { 1628 let results = []; 1629 for (var i = 0; i < this.messages.length; i++) { 1630 let message = this.messages[i].message; 1631 if ((exact_match && message == search) || (!exact_match && message.includes(search))) { 1632 results.push[messages[i]]; 1633 } 1634 } 1635 return results; 1636 } 1637 1638 getAttachments() { 1639 let list = []; 1640 for (var i = 0; i < this.messages.length; i++) { 1641 let attachments = this.messages[i].attachments; 1642 for (var j = 0; j < attachments.length; j++) { 1643 let link = attachments[j][1]; 1644 list.push([attachments[j][0], link, link.substr(link.lastIndexOf('.') + 1), this.messages[i].id]); 1645 } 1646 } 1647 return list; 1648 } 1649 } 1650 window.SBConversation = SBConversation; 1651 1652 /* 1653 * ---------------------------------------------------------- 1654 * # CHAT 1655 * ---------------------------------------------------------- 1656 */ 1657 1658 var SBChat = { 1659 1660 // Variables 1661 rich_messages_list: ['chips', 'buttons', 'select', 'inputs', 'table', 'list'], 1662 emoji_options: { range: 0, range_limit: 47, list: [], list_now: [], touch: false }, 1663 initialized: false, 1664 editor_listening: false, 1665 conversation: false, 1666 is_busy: false, 1667 is_busy_update: false, 1668 is_busy_populate: false, 1669 chat_open: false, 1670 real_time: false, 1671 agent_id: -1, 1672 agent_online: false, 1673 user_online: false, 1674 expanded: false, 1675 main_header: true, 1676 start_header: false, 1677 desktop_notifications: false, 1678 flash_notifications: false, 1679 id_last_message: 0, 1680 id_last_message_conversation: 0, 1681 datetime_last_message_conversation: '2000-01-01 00:00:00', 1682 audio: false, 1683 audio_out: false, 1684 tab_active: true, 1685 notifications: [], 1686 typing_settings: { typing: false, sent: false, timeout: false }, 1687 email_sent: false, 1688 dashboard: false, 1689 articles: false, 1690 articles_categories: false, 1691 slack_channel: [-1, -1], 1692 skip: false, 1693 queue_interval: false, 1694 departments: false, 1695 default_department: null, 1696 default_agent: null, 1697 offline_message_set: false, 1698 1699 // Send a message 1700 sendMessage: function (user_id = -1, message = '', attachments = [], onSuccess = false, payload = false, conversation_status_code = false) { 1701 let is_dialogflow_human_takeover = dialogflow_human_takeover && SBApps.dialogflow.active(); 1702 let is_return = false;
1703 1704 // Check settings and contents 1705 if (!activeUser() && !admin) { 1706 this.addUserAndLogin(() => { 1707 return this.sendMessage(user_id, message, attachments, onSuccess, payload); 1708 }, true); 1709 return; 1710 } 1711 if (!this.conversation) { 1712 let last_conversation = admin ? false : activeUser().getLastConversation(); 1713 if (last_conversation && force_action != 'new-conversation' && (!SBChat.default_department || SBChat.default_department == last_conversation.get('department')) && (!SBChat.default_agent || SBChat.default_agent == last_conversation.get('agent_id'))) { 1714 this.openConversation(last_conversation.id); 1715 this.setConversation(last_conversation); 1716 force_action = false; 1717 } else { 1718 this.newConversation(conversation_status_code, user_id, '', [], (admin && SB_ACTIVE_AGENT['department'] ? SB_ACTIVE_AGENT['department'] : null), null, () => { 1719 return this.sendMessage(user_id, message, attachments, onSuccess, payload); 1720 }); 1721 return; 1722 } 1723 } 1724 this.calculateLabelDateFirst(); 1725 if (user_id == -1) user_id = admin ? SB_ACTIVE_AGENT['id'] : activeUser().id; 1726 let is_user = user_id != bot_id; 1727 if (!message && !attachments.length) { 1728 message = chat_textarea.val().trim(); 1729 chat_editor.find('.sb-attachments > div').each(function () { 1730 attachments.push([$(this).attr('data-name'), $(this).attr('data-value')]); 1731 }); 1732 if (admin && SBAdmin.must_translate) { 1733 SBApps.dialogflow.translate([message], activeUser().language, (response) => { 1734 if (response.length) { 1735 if (payload) { 1736 payload['original-message'] = message; 1737 } else { 1738 payload = { 'original-message': message }; 1739 } 1740 if (response[0].translatedText) message = response[0].translatedText; 1741 } 1742 this.sendMessage(user_id, message, attachments, onSuccess, payload, conversation_status_code); 1743 }); 1744 is_return = true; 1745 } 1746 } 1747 this.busy(true); 1748 if (is_user) { 1749 chat_textarea.val('').css('height', ''); 1750 chat_editor.find('.sb-attachments').html(''); 1751 } 1752 this.activateBar(false); 1753 1754 if (is_return) return; 1755 if (conversation_status_code === false && user_id == bot_id) { 1756 conversation_status_code = 'skip'; 1757 } 1758 if (!admin && is_user && !is_dialogflow_human_takeover) { 1759 conversation_status_code = 2; 1760 } 1761 1762 // Send message 1763 if (message || attachments.length || payload) { 1764 SBF.ajax({ 1765 function: 'send-message', 1766 user_id: user_id, 1767 conversation_id: this.conversation.id, 1768 message: message, 1769 attachments: attachments, 1770 conversation_status_code: conversation_status_code, 1771 queue: !admin && CHAT_SETTINGS['queue'] && is_user, 1772 payload: payload, 1773 recipient_id: admin ? activeUser().id : false 1774 }, (response) => { 1775 let send_slack = admin || !is_dialogflow_human_takeover || response.human_takeover_active 1776 1777 // Update the dashboard conversations area 1778 if (!admin && user_id == bot_id) { 1779 if (this.dashboard) { 1780 this.updateConversations(); 1781 } else if (!this.chat_open) { 1782 this.updateNotifications('add', this.conversation.id); 1783 } 1784 } 1785 1786 // Update the chat current conversation 1787 if ((admin && !this.user_online) || (!admin && !this.agent_online)) { 1788 this.update(); 1789 } 1790 1791 // Follow up and offline messages 1792 if (!admin && is_user && !dialogflow_human_takeover) { 1793 this.followUp(); 1794 this.offlineMessage(); 1795 } 1796 1797 // Dialogflow 1798 if (!admin && is_user && (!payload || (payload['id'] != 'sb-human-takeover' && SBF.null(payload['ski
1798p-dialogflow'])))) { 1799 SBApps.dialogflow.message(message, attachments); 1800 } 1801 1802 // Slack and visitor to lead 1803 if (!admin && is_user && activeUser().type == 'visitor') { 1804 SBF.ajax({ function: 'update-user-to-lead', user_id: user_id }, () => { 1805 activeUser().set('user_type', 'lead'); 1806 if (CHAT_SETTINGS['slack-active'] && send_slack) { 1807 this.slackMessage(user_id, activeUser().name, activeUser().image, message, attachments); 1808 } 1809 }); 1810 } else if (send_slack && !this.skip) { 1811 if (admin && SB_ADMIN_SETTINGS['slack-active']) { 1812 this.slackMessage(activeUser().id, SB_ACTIVE_AGENT['full_name'], SB_ACTIVE_AGENT['profile_image'], message, attachments); 1813 } else if (CHAT_SETTINGS['slack-active']) { 1814 this.slackMessage(activeUser().id, (is_user ? activeUser().name : CHAT_SETTINGS['bot-name']), (is_user ? activeUser().image : CHAT_SETTINGS['bot-image']), message, attachments); 1815 } 1816 } 1817 1818 // Language detection 1819 if (is_user && CHAT_SETTINGS['language-detection'] && this.conversation && message.split(' ').length > 1 && !SBF.storage('language-detection-completed')) { 1820 SBF.ajax({ function: 'google-language-detection-update-user', user_id: user_id, string: message, token: SBApps.dialogflow.token }); 1821 SBF.storage('language-detection-completed', true); 1822 } 1823 1824 // Articles 1825 if (this.articles && !admin && CHAT_SETTINGS['articles'] && !CHAT_SETTINGS['office-hours'] && !this.isInitDashboard()) { 1826 setTimeout(() => { 1827 if (this.conversation) { 1828 this.sendMessage(bot_id, '[articles]'); 1829 this.scrollBottom(); 1830 this.articles = false; 1831 } 1832 }, 5000); 1833 } 1834 1835 // Queue 1836 if (response['queue']) { 1837 this.queue(this.conversation.id); 1838 } 1839 1840 // Events 1841 let message_response = { user_id: user_id, conversation_id: this.conversation.id, conversation_status_code: conversation_status_code, message_id: response.id, message: message, attachments: attachments }; 1842 SBF.event('SBMessageSent', message_response); 1843 if (tickets) SBTickets.onMessageSent(); 1844 if (onSuccess) onSuccess(message_response); 1845 if (response.notifications.length) SBF.event('SBNotificationsSent', response.notifications); 1846 1847 // Miscellaneous 1848 if (this.skip) this.skip = false; 1849 this.busy(false); 1850 }); 1851 1852 // Display the message as sending in progress 1853 if (is_user) { 1854 message = SBF.escape(message); 1855 chat.append((new SBMessage({ id: 'sending', profile_image: (admin ? SB_ACTIVE_AGENT['profile_image'] : activeUser().image), full_name: activeUser().name, creation_time: '0000-00-00 00:00:00', message: message, user_type: (admin ? 'agent' : 'user') })).getCode().replace('<div class="sb-time"></div>', `<div class="sb-time">${sb_('Sending')}<i></i></div>`)); 1856 if (!this.dashboard) this.scrollBottom(); 1857 } 1858 1859 // Sounds 1860 if (this.audio && (!admin && this.chat_open && is_user && ['a', 'aa'].includes(CHAT_SETTINGS['chat-sound'])) || (admin && SB_ADMIN_SETTINGS['sounds'] == 'a')) { 1861 this.audio_out.play(); 1862 } 1863 } else this.busy(false); 1864 }, 1865 1866 // [Deprecated] This function will be removed soon 1867 sendBotMessage: function (message = '', attachments = []) { 1868 return SBApps.dialogflow.message(message, attachments); 1869 }, 1870 1871 // Update message 1872 updateMessage: function (message_id, message = '') { 1873 SBF.ajax({ 1874 function: 'update-message', 1875 message_id: message_id, 1876 message: message 1877 }); 1878 }, 1879 1880 // Email notifications 1881 sendEmail: function (message, attachments, send_to_active_user = false) { 1882 let recipient_id = send_to_active_user ? activeUser().id : this.getRecipientUserID(); 1883 if (!admin && !isNaN(recipient_id) && this.agent_online) { 1884 return false;
1885 } 1886 SBF.ajax({ 1887 function: 'create-email', 1888 recipient_id: recipient_id, 1889 sender_name: admin ? (send_to_active_user ? SB_ACTIVE_AGENT['full_name'] : activeUser().name) : (send_to_active_user ? CHAT_SETTINGS['bot-name'] : activeUser().name), 1890 sender_profile_image: admin ? (send_to_active_user ? SB_ACTIVE_AGENT['profile_image'] : activeUser().name) : (send_to_active_user ? CHAT_SETTINGS['bot-image'] : activeUser().image), 1891 message: message, 1892 attachments: attachments, 1893 department: this.conversation ? this.conversation.get('department') : false, 1894 conversation_id: this.conversation ? this.conversation.id : false 1895 }, () => { 1896 SBF.event('SBEmailSent', { recipient_id: recipient_id, message: message, attachments: attachments }); 1897 }); 1898 }, 1899 1900 // SMS notifications 1901 sendSMS: function (message) { 1902 let recipient_id = this.getRecipientUserID(); 1903 if (!admin && !isNaN(recipient_id) && this.agent_online) return false; 1904 SBF.ajax({ 1905 function: 'send-sms', 1906 to: recipient_id, 1907 message: message, 1908 conversation_id: this.conversation ? this.conversation.id : false 1909 }, (response) => { 1910 if (response['status'] == 'sent' || response['status'] == 'queued') { 1911 SBF.event('SBSMSSent', { recipient_id: this.getRecipientUserID(), message: message, response: response }); 1912 } else if (response.message) { 1913 SBF.error(response.message, 'SBChat.sendSMS'); 1914 } 1915 }); 1916 }, 1917 1918 // Push notifications. [Deprecated. This function will be removed soon.] 1919 pushNotification: function (message) { 1920 SBPusher.pushNotification(message); 1921 }, 1922 1923 // Desktop notifications 1924 desktopNotification: function (title, message, icon, conversation_id = false, user_id = false) { 1925 if (Notification.permission !== 'granted') { 1926 Notification.requestPermission(); 1927 } else { 1928 let notify = SBPusher.sw.showNotification(title, { 1929 body: (new SBMessage()).strip(message), 1930 icon: icon.indexOf('user.svg') > 0 ? CHAT_SETTINGS['notifications-icon'] : icon 1931 }); 1932 notify.onclick = () => { 1933 if (admin) { 1934 if (conversation_id) { 1935 SBAdmin.conversations.openConversation(conversation_id, user_id == false ? activeUser().id : user_id); 1936 SBAdmin.conversations.update(); 1937 } else if (user_id) { 1938 SBAdmin.profile.show(user_id); 1939 } 1940 } else { 1941 this.start(); 1942 } 1943 window.focus(); 1944 } 1945 } 1946 }, 1947 1948 // Returns the recipient user ID 1949 getRecipientUserID: function () { 1950 return admin ? activeUser().id : (this.lastAgent(false) ? this.lastAgent(false)['user_id'] : (SBF.null(this.conversation.get('agent_id')) ? (SBF.null(this.conversation.get('department')) ? 'agents' : 'department-' + this.conversation.get('department')) : this.conversation.get('agent_id'))); 1951 }, 1952 1953 // Editor submit message 1954 submit: function () { 1955 if (!this.is_busy) { 1956 this.sendMessage(); 1957 if (CHAT_SETTINGS['cron-email-piping-active']) { 1958 setInterval(function () { 1959 SBF.ajax({ function: 'email-piping' }); 1960 }, 60000); 1961 CHAT_SETTINGS['cron-email-piping-active'] = false; 1962 } 1963 if (SBPusher.init_push_notifications) { 1964 SBPusher.initPushNotifications(); 1965 } 1966 } 1967 }, 1968 1969 // Initialize the chat 1970 initChat: function () { 1971 if (admin) return; 1972 SBF.getActiveUser(true, () => { 1973 let active = activeUser() !== false; 1974 let user_type = active ? activeUser().type : false;
1975 if (!tickets && CHAT_SETTINGS['popup']['active'] && !storage('popup') && (!mobile || !CHAT_SETTINGS['popup-mobile-hidden'])) { 1976 this.popup(); 1977 } 1978 SBChat.automations.run_all(); 1979 if (!tickets && CHAT_SETTINGS['privacy'] && !CHAT_SETTINGS['registration-required'] && !storage('privacy-approved')) { 1980 this.privacy(); 1981 return; 1982 } 1983 if (typeof Notification !== ND && !CHAT_SETTINGS['push-notifications-users'] && (CHAT_SETTINGS['desktop-notifications'] == 'all' || (CHAT_SETTINGS['desktop-notifications'] == 'users') || (admin && CHAT_SETTINGS['desktop-notifications'] == 'agents'))) { 1984 this.desktop_notifications = true; 1985 } 1986 if (CHAT_SETTINGS['flash-notifications'] == 'all' || (CHAT_SETTINGS['flash-notifications'] == 'users') || (admin && CHAT_SETTINGS['flash-notifications'] == 'agents')) { 1987 this.flash_notifications = true; 1988 } 1989 if (this.registration(true) && !tickets) { 1990 this.registration(); 1991 if (!active && CHAT_SETTINGS['visitors-registration']) { 1992 this.addUserAndLogin(); 1993 } 1994 return; 1995 } 1996 if (!active && (typeof SB_WP_WAITING_LIST !== ND || CHAT_SETTINGS['visitors-registration'] || CHAT_SETTINGS['welcome'] || CHAT_SETTINGS['subscribe'] || tickets) && (!tickets || !CHAT_SETTINGS['tickets-registration-required'])) { 1997 this.addUserAndLogin(() => { 1998 this.welcome(); 1999 this.subscribe(); 2000 SBApps.woocommerce.waitingList(); 2001 this.finalizeInit(); 2002 }); 2003 } else if (!this.conversation && active) { 2004 this.populateConversations(); 2005 } else { 2006 this.finalizeInit(); 2007 } 2008 if (CHAT_SETTINGS['header-name'] && active && user_type == 'user' && !tickets) { 2009 chat_header.find('.sb-title').html(`${sb_('Hello')} ${activeUser().nameBeautified}!`); 2010 } 2011 this.welcome(); 2012 this.subscribe(); 2013 if (!SBPusher.active) { 2014 setInterval(() => { 2015 this.updateConversations(); 2016 this.updateUsersActivity(); 2017 }, 10200); 2018 } 2019 SBApps.woocommerce.waitingList(); 2020 this.scrollBottom(true); 2021 }); 2022 }, 2023 2024 finalizeInit: function () { 2025 if (!this.initialized) { 2026 main.attr('style', ''); 2027 if (!admin && !tickets) { 2028 if (this.isInitDashboard()) { 2029 this.showDashboard(); 2030 } 2031 if (!mobile && window.innerHeight < 760) { 2032 main.find(' > .sb-body').css('max-height', (window.innerHeight - 130) + 'px'); 2033 } 2034 } 2035 this.initialized = true; 2036 if (!admin) { 2037 if (activeUser() && !this.registration(true)) { 2038 if (storage('open-conversation')) this.openConversation(storage('open-conversation')); 2039 if (SBF.getURL('conversation')) this.openConversation(SBF.getURL('conversation')); 2040 } 2041 if (!this.chat_open && ((!mobile && storage('chat-open')) || SBF.getURL('chat') == 'open') || SBF.getURL('conversation')) { 2042 setTimeout(() => { this.start(); }, 500); 2043 } 2044 if (CHAT_SETTINGS['woocommerce-returning-visitor']) { 2045 if (storage('returning-visitor') === false) { 2046 SBF.storageTime('returning-visitor'); 2047 } else if (SBF.storageTime('returning-visitor', 24) && !storage('returning-visitor-processed')) { 2048 setTimeout(() => { 2049 SBF.ajax({ 2050 function: 'woocommerce-returning-visitor' 2051 }, () => { 2052 storage('returning-visitor-processed', true); 2053 }); 2054 }, 15000); 2055 } 2056 } 2057 if (CHAT_SETTINGS['timetable-type']) SBChat.offlineMessage(); 2058 if (CHAT_SETTINGS['queue-human-takeover'] && SBApps.dialogflow.humanTakeoverActive()) { 2059 CHAT_SETTINGS['queue'] = true; 2060 } 2061 } 2062 if (tickets) SBTickets.init(); 2063 SBF.event('SBInit'); 2064 } 2065 }, 2066 2067 // Initialize the chat settings and open the chat 2068 start: function () { 2069 if (this.initialized) { 2070 this.populate(); 2071 this.headerAgent(); 2072 this.updateUsersActivity(); 2073 this.startRealTime(); 2074 this.chat_open = true; 2075 this.popup(true); 2076 if (this.conversation) this.updateNotifications('remove', this.conversation.id); 2077 main.sbActive(true); 2078 $('body').addClass('sb-chat-open'); 2079 if (CHAT_SETTINGS['welcome-trigger'] == 'open') { 2080 this.welcome(); 2081 } 2082 SBApps.martfury.privateChat(); 2083 this.calculateLabelDates(); 2084 } 2085 }, 2086 2087 // Open or close the chat 2088 open: function (open = true) { 2089 if (open && !this.chat_open) { 2090 this.start(); 2091 this.chat_open = true; 2092 this.startRealTime(); 2093 main.sbActive(true); 2094 $('body').addClass('sb-chat-open'); 2095 storage('chat-open', true); 2096 if (mobile) history.pushState({ 'chat-open': true }, '', ''); 2097 SBF.event('SBChatOpen'); 2098 } else if (!open && this.chat_open) { 2099 main.sbActive(false); 2100 this.stopRealTime(); 2101 this.chat_open = false;
2102 storage('chat-open', false); 2103 $('body').removeClass('sb-chat-open'); 2104 SBF.event('SBChatClose'); 2105 } 2106 }, 2107 2108 // Get a full conversation and display it in the chat 2109 openConversation: function (conversation_id) { 2110 activeUser().getFullConversation(conversation_id, (response) => { 2111 if (!response['id']) { 2112 storage('open-conversation', ''); 2113 return false; 2114 } 2115 this.setConversation(response); 2116 this.hideDashboard(); 2117 this.populate(); 2118 this.main_header = false; 2119 if (storage('queue') == conversation_id) { 2120 this.queue(conversation_id); 2121 } 2122 if ((this.chat_open || tickets)) { 2123 this.updateNotifications('remove', conversation_id); 2124 } 2125 if (tickets) SBTickets.activateConversation(response); 2126 storage('open-conversation', conversation_id); 2127 SBF.event('SBConversationOpen', response); 2128 }); 2129 }, 2130 2131 // Update the active conversation with the latest messages 2132 update: function () { 2133 if (this.conversation) { 2134 if (this.is_busy_update) return; 2135 let last_message = this.conversation.getLastMessage(); 2136 let is_update = false; 2137 SBF.ajax({ 2138 function: 'get-new-messages', 2139 conversation_id: this.conversation.id, 2140 datetime: this.datetime_last_message_conversation, 2141 last_id: this.id_last_message_conversation 2142 }, (response) => { 2143 let count = response.length; 2144 this.is_busy_update = false; 2145 if (this.conversation) { 2146 if (Array.isArray(response) && count > 0 && (!last_message || last_message.id != response[count - 1]['id'] || last_message.message != response[count - 1]['message'] || last_message.payload != response[count - 1]['payload'] || last_message.attachments != response[count - 1]['attachments'])) { 2147 let code = ''; 2148 let messages = []; 2149 let id_check = []; 2150 let dialogflow_activation = false; 2151 2152 // Generate and add the new messages 2153 this.calculateLabelDateFirst(); 2154 for (var i = 0; i < count; i++) { 2155 if (!id_check.includes(response[i]['id'])) { 2156 let message = new SBMessage(response[i]); 2157 let payload = message.payload(); 2158 this.id_last_message_conversation = message.id; 2159 this.datetime_last_message_conversation = message.get('creation_time'); 2160 2161 // Payload 2162 if (!['boolean', 'string'].includes(typeof payload)) { 2163 if ('event' in payload) { 2164 let event = payload['event']; 2165 if ((event == 'delete-message' && this.conversation.getMessage(message.id) !== false) || (!admin && message.message == '' && !message.attachments.length)) { 2166 this.deleteMessage(message.id); 2167 } 2168 if (event == 'woocommerce-update-cart' && !admin) { 2169 SBApps.woocommerce.updateCart(payload['action'], payload['id']); 2170 } 2171 if (!SBApps.dialogflow.active() && (event == 'conversation-status-update-3' || event == 'conversation-status-update-4' || event == 'activate-bot')) { 2172 SBApps.dialogflow.active('activate'); 2173 dialogflow_activation = true; 2174 } 2175 if (CHAT_SETTINGS['close-chat'] && event == 'conversation-status-update-3') { 2176 this.closeChat(false); 2177 return; 2178 } 2179 } 2180 if ('human-takeover' in payload && CHAT_SETTINGS['queue-human-takeover']) { 2181 CHAT_SETTINGS['queue'] = true; 2182 SBChat.queue(SBChat.conversation.id); 2183 } 2184 } 2185 2186 // Message 2187 if (this.conversation.getMessage(response[i]['id'])) { 2188 this.conversation.updateMessage(message.id, message); 2189 chat.find(`[data-id="${message.id}"]`).replaceWith(message.getCode()); 2190 is_update = true; 2191 } else { 2192 this.conversation.addMessages(message); 2193 code += message.getCode(); 2194 } 2195 messages.push(message); 2196 id_check.push(message.id); 2197 } 2198 } 2199 chat.append(code); 2200 2201 // Update status code 2202 let last_message = this.conversation.getLastMessage(); 2203 let user_type = last_message.get('user_type'); 2204 let is_agent = SBF.isAgent(user_type);
2205 if (!admin && is_agent && user_type != 'bot') { 2206 if (this.chat_open) { 2207 if (last_message.message.indexOf('sb-rich-success') == -1) this.setConversationStatus(0); 2208 if (CHAT_SETTINGS['follow']['active']) clearTimeout(timeout); 2209 } 2210 if (!dialogflow_activation) SBApps.dialogflow.active(false); 2211 } 2212 2213 // Queue 2214 if (storage('queue') == this.conversation.id && is_agent && user_type != 'bot') { 2215 this.queue('clear'); 2216 } 2217 2218 // Miscellaneous 2219 chat.find('[data-id="sending"]').remove(); 2220 this.headerAgent(); 2221 if (!this.dashboard && !is_update) { 2222 this.scrollBottom(); 2223 setTimeout(() => { this.scrollBottom() }, 300); 2224 } 2225 if (this.dashboard || !this.chat_open) { 2226 this.updateNotifications('add', this.conversation.id); 2227 } 2228 this.typing(-1, 'stop'); 2229 this.busy(false); 2230 if (this.audio && (count != 1 || !(SBF.null(messages[0].message) && SBF.null(messages[0].attachments))) && ((!admin && this.chat_open && (is_agent || user_type == 'bot') && ['aa', 'ia'].includes(CHAT_SETTINGS['chat-sound'])) || (admin && !SBF.isAgent(user_type) && ['a', 'i', 'ic'].includes(SB_ADMIN_SETTINGS['sounds'])))) { 2231 this.audio.play(); 2232 } 2233 SBF.event('SBNewMessagesReceived', messages); 2234 if (tickets) SBTickets.onNewMessageReceived(messages[0]); 2235 } 2236 } 2237 }); 2238 this.is_busy_update = true; 2239 setTimeout(() => { this.is_busy_update = false }, 5000); 2240 } else { 2241 this.updateConversations(); 2242 } 2243 }, 2244 2245 // Update the user conversations list with the latest conversations and messages 2246 updateConversations: function () { 2247 if (activeUser()) { 2248 SBF.ajax({ 2249 function: 'get-new-user-conversations', 2250 datetime: this.id_last_message 2251 }, (response) => { 2252 if (response.length) { 2253 this.id_last_message = response[0]['id']; 2254 for (var i = 0; i < response.length; i++) { 2255 let conversation_id = response[i]['conversation_id']; 2256 let message = new SBMessage(response[i]); 2257 let status_code = response[i]['conversation_status_code']; 2258 let conversation = new SBConversation([message], { id: conversation_id, conversation_status_code: status_code, department: response[i]['department'], title: response[i]['title'] }); 2259 let is_new = activeUser().addConversation(conversation); 2260 2261 // Red notifications 2262 if (response[i]['user_id'] != activeUser().id && (this.conversation.id != conversation_id || !this.chat_open)) { 2263 this.updateNotifications('add', conversation_id); 2264 if (CHAT_SETTINGS['auto-open']) { 2265 this.start(); 2266 } 2267 } 2268 2269 // Payload 2270 let payload = message.payload(); 2271 if (typeof payload !== 'boolean' && 'event' in payload) { 2272 let event = payload['event']; 2273 if (event == 'open-chat') { 2274 if (this.conversation.id != conversation_id || this.dashboard) { 2275 this.openConversation(conversation_id); 2276 } 2277 setTimeout(() => { this.open() }, 500); 2278 } 2279 if (message.message == '' && !message.attachments.length) { 2280 continue; 2281 } 2282 } 2283 2284 if (!this.tab_active) { 2285 2286 // Desktop notifications 2287 if (this.desktop_notifications) { 2288 SBChat.desktopNotification(message.get('full_name'), message.message, message.get('profile_image')); 2289 } 2290 2291 // Flash notifications 2292 if (this.flash_notifications) { 2293 this.flashNotification(); 2294 } 2295 2296 // Sound notifications 2297 if (this.audio && ['a', 'aa', 'i'].includes(CHAT_SETTINGS['chat-sound']) && (!this.chat_open || !this.tab_active || this.dashboard) && !(SBF.null(message.message) && SBF.null(message.attachments))) { 2298 this.audio.play(); 2299 } 2300 } 2301 if (is_new) { 2302 SBF.event('SBNewConversationReceived', conversation); 2303 if (tickets) SBTickets.onNewConversationReceived(conversation); 2304 } 2305 } 2306 main.find('.sb-user-conversations').html(activeUser().getConversationsCode()); 2307 main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3); 2308 } 2309 }); 2310 } 2311 }, 2312 2313 // Generate the conversation code and display it 2314 populate: function () { 2315 if (this.conversation) { 2316 let code = ''; 2317 let notify = chat.find(' > .sb-notify-message'); 2318 let last_date = false;
2319 for (var i = 0; i < this.conversation.messages.length; i++) { 2320 let message = this.conversation.messages[i]; 2321 let current_date = SBF.beautifyTime(message.get('creation_time')); 2322 if (current_date != last_date) { 2323 code += `<div class="sb-label-date">${current_date}</div>`; 2324 last_date = current_date; 2325 } 2326 code += message.getCode(); 2327 } 2328 chat.html((notify.length ? notify[0].outerHTML : '') + code); 2329 if (!this.dashboard) { 2330 this.scrollBottom(); 2331 this.calculateLabelDates(); 2332 } 2333 } else if (activeUser() && !activeUser().isConversationsEmpty()) { 2334 this.showDashboard(); 2335 } 2336 }, 2337 2338 // Populate the dashboard with all conversations 2339 populateConversations: function (onSuccess = false) { 2340 if (!this.is_busy_populate && activeUser()) { 2341 this.is_busy_populate = true; 2342 setTimeout(() => { this.is_busy_populate = false }, 5000); 2343 activeUser().getConversations((response) => { 2344 let count = response.length; 2345 if (count) { 2346 let now = Date.now(); 2347 this.id_last_message = response[0]['messages'][0].id; 2348 for (var i = 0; i < count; i++) { 2349 if (response[i].get('conversation_status_code') == 1 && (!this.conversation || this.conversation.id != response[i].id)) { 2350 this.updateNotifications('add', response[i].id); 2351 } 2352 if ((now - SBF.UTC(response[i]['messages'][0].get('creation_time'))) < 6000) this.open(); 2353 } 2354 main.removeClass('sb-no-conversations'); 2355 main.find('.sb-user-conversations').html(activeUser().getConversationsCode()); 2356 main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3); 2357 } 2358 if ((!this.initialized || force_action == 'open-conversation') && count == 1 && !this.isInitDashboard() && !storage('open-conversation')) { 2359 this.openConversation(activeUser().conversations[0].id); 2360 if (force_action == 'open-conversation') force_action = ''; 2361 } 2362 if (onSuccess) { 2363 onSuccess(response); 2364 } 2365 this.finalizeInit(); 2366 }); 2367 } 2368 }, 2369 2370 // Create a new conversation and optionally send the first message 2371 newConversation: function (status_code, user_id = -1, message = '', attachments = [], department = null, agent_id = null, onSuccess = false) { 2372 if (activeUser()) { 2373 SBF.ajax({ 2374 function: 'new-conversation', 2375 status_code: status_code, 2376 title: tickets ? main.find('.sb-ticket-title input').val() : null, 2377 department: SBF.null(department) ? this.default_department : department, 2378 agent_id: SBF.null(agent_id) ? this.default_agent : agent_id, 2379 source: tickets ? 'tk' : '' 2380 }, (response) => { 2381 if (SBF.errorValidation(response, 'user-not-found')) { 2382 this.addUserAndLogin(() => { 2383 this.newConversation(status_code, user_id, message, attachments, department, agent_id, onSuccess); 2384 }); 2385 return; 2386 } 2387 let conversation = new SBConversation([], response['details']); 2388 this.setConversation(conversation); 2389 if (message || attachments.length) { 2390 this.sendMessage(user_id, message, attachments); 2391 } 2392 if (user_id != bot_id) { 2393 setTimeout(() => { this.queue(conversation.id) }, 1000); 2394 } 2395 activeUser().conversations.push(conversation); 2396 if (onSuccess) onSuccess(conversation); 2397 }); 2398 2399 } else { 2400 SBF.error('activeUser() not setted', 'SBChat.newConversation'); 2401 } 2402 }, 2403 2404 // Set an existing conversation as active conversation 2405 setConversation: function (conversation) { 2406 if (conversation instanceof SBConversation) { 2407 this.conversation = conversation; 2408 this.id_last_message_conversation = !this.conversation.getLastMessage() ? 0 : this.conversation.getLastMessage().id; 2409 this.datetime_last_message_conversation = this.conversation.getLastMessage() == false ? '2000-01-01 00:00:00' : this.conversation.getLastMessage().get('creation_time'); 2410 if (conversation.id != this.conversation.id) { 2411 this.queue(conversation.id); 2412 } 2413 storage('open-conversation', conversation.id); 2414 SBF.event('SBActiveConversationChanged', conversation); 2415 } else { 2416 SBF.error('Value not of type SBConversation', 'SBChat.setConversation'); 2417 } 2418 }, 2419 2420 // Manage all the queue functionalities 2421 queue: function (conversation_id) { 2422 if (conversation_id == 'clear') { 2423 main.removeClass('sb-notify-active sb-queue-active'); 2424 chat.find(' > .sb-notify-message').remove(); 2425 clearInterval(this.queue_interval); 2426 this.queue_interval = false;
2427 storage('queue', ''); 2428 return; 2429 } 2430 if (!admin && CHAT_SETTINGS['queue']) { 2431 SBF.ajax({ 2432 function: 'queue', 2433 conversation_id: conversation_id, 2434 department: this.conversation.get('department') 2435 }, (response) => { 2436 chat.find(' > .sb-notify-message').remove(); 2437 let position = response[0]; 2438 if (position == 0) { 2439 this.queue('clear'); 2440 } else { 2441 let time = (!CHAT_SETTINGS['queue-response-time'] ? 5 : parseInt(CHAT_SETTINGS['queue-response-time'])) * position; 2442 let text = sb_(!CHAT_SETTINGS['queue-message'] ? 'Please wait for an agent. You are number {position} in the queue. Your waiting time is approximately {minutes} minutes.' : CHAT_SETTINGS['queue-message']).replace('{position}', '<b>' + position + '</b>').replace('{minutes}', '<b>' + time + '</b>'); 2443 if (response[1]) chat.prepend(`<div class="sb-notify-message sb-rich-cnt"><div class="sb-cnt"><div class="sb-message">${text}</div></div></div>`); 2444 if (this.queue_interval === false) { 2445 this.queue_interval = setInterval(() => { this.queue(conversation_id) }, 10100); 2446 if (response[1]) main.addClass('sb-notify-active sb-queue-active'); 2447 storage('queue', conversation_id); 2448 } 2449 } 2450 SBF.event('SBQueueUpdate', position); 2451 }); 2452 } 2453 }, 2454 2455 // Get the departments details and generate the department code 2456 getDepartmentCode(department_id, onSuccess) { 2457 if (this.departments) { 2458 if (department_id == 'all') { 2459 let code = ''; 2460 for (var key in this.departments) { 2461 this.getDepartmentCode(this.departments[key]['id'], (response) => { code += response; }); 2462 }; 2463 onSuccess(code); 2464 } else { 2465 onSuccess(`<div data-color="${this.departments[department_id]['color']}">${this.departments[department_id]['image'] == '' ? `<span></span>` : `<img src="${this.departments[department_id]['image']}" />`}<div>${this.departments[department_id]['name']}<div></div>`); 2466 } 2467 } else { 2468 SBF.ajax({ 2469 function: 'get-departments' 2470 }, (response) => { 2471 if (response) { 2472 this.departments = response; 2473 this.getDepartmentCode(department_id, onSuccess); 2474 } 2475 }); 2476 } 2477 }, 2478 2479 // Start and stop the real time check of new messages 2480 startRealTime: function () { 2481 if (SBPusher.active) return; 2482 this.stopRealTime(); 2483 this.real_time = setInterval(() => { 2484 this.update(); 2485 this.typing(admin ? (activeUser() ? activeUser().id : -1) : this.agent_id, 'check'); 2486 }, 1000); 2487 }, 2488 2489 stopRealTime: function () { 2490 clearInterval(this.real_time); 2491 }, 2492 2493 // Check if the agent is online and set the online status of the active user 2494 updateUsersActivity: function () { 2495 if (activeUser()) { 2496 SBF.updateUsersActivity(activeUser().id, this.agent_id, (response) => { 2497 if (!this.typing_settings['typing']) { 2498 if (response == 'online' || this.agent_id == bot_id) { 2499 $(chat_status).addClass('sb-status-online').html(sb_('Online')); 2500 this.agent_online = this.agent_id != bot_id; 2501 } else { 2502 $(chat_status).removeClass('sb-status-online').html(sb_('Away')); 2503 this.agent_online = false; 2504 } 2505 } 2506 }); 2507 } 2508 }, 2509 2510 // Show the loading icon and put the chat in busy mode 2511 busy: function (value) { 2512 chat_editor.find('.sb-loader').sbActive(value); 2513 this.is_busy = value; 2514 SBF.event('SBBusy', value); 2515 }, 2516 2517 // Manage the agent header 2518 headerAgent: function () { 2519 if (!admin && !tickets && !this.dashboard && this.conversation && (this.agent_id == -1 || (this.conversation.getLastMessage() && SBF.isAgent(this.conversation.getLastMessage().get('user_type')) && this.conversation.getLastMessage().get('user_id') != this.agent_id))) { 2520 let agent = this.lastAgent(); 2521 if (agent) { 2522 this.agent_id = agent['user_id']; 2523 this.headerReset(); 2524 chat_header.addClass('sb-header-agent').attr('data-agent-id', this.agent_id).html(`<div class="sb-dashboard-btn sb-icon-arrow-left"></div><div class="sb-profile"><img src="${agent['profile_image']}" /><div><span class="sb-name">${agent['full_name']}</span><span class="sb-status">${sb_('Away')}</span></div><i class="sb-icon sb-icon-close ${!mobile && CHAT_SETTINGS['close-chat'] ? 'sb-close-chat' : 'sb-responsive-close-btn'}"></i></div><div class="sb-label-date-top"></div>`); 2525 chat_status = chat_header.find('.sb-status'); 2526 this.updateUsersActivity(); 2527 label_date = chat_header.find('.sb-label-date-top'); 2528 if (SBF.storageTime('header-animation', 1)) { 2529 this.headerAnimation(); 2530 } 2531 } 2532 } 2533 }, 2534 2535 headerReset: function () { 2536 if (this.start_header == false) { 2537 this.start_header = [chat_header.html(), chat_header.attr('class')]; 2538 } 2539 chat_header.removeClass('sb-header-main sb-header-brand sb-header-agent sb-header-minimal'); 2540 this.main_header = false;
2541 }, 2542 2543 headerAnimation: function () { 2544 chat_header.addClass('sb-header-animation'); 2545 setTimeout(() => { chat_header.removeClass('sb-header-animation') }, 8000); 2546 SBF.storageTime('header-animation'); 2547 }, 2548 2549 // Return the last agent of the active conversation 2550 lastAgent: function (bot = true) { 2551 let agent = false; 2552 if (this.conversation) { 2553 let message = this.conversation.getLastUserMessage(false, bot ? 'all' : true); 2554 if (message) { 2555 agent = { user_id: message.get('user_id'), full_name: message.get('full_name'), profile_image: message.get('profile_image') }; 2556 } 2557 } 2558 return agent; 2559 }, 2560 2561 // Scroll the chat to the bottom 2562 scrollBottom: function (top = false) { 2563 label_date_show = false; 2564 setTimeout(() => { label_date_show = true }, 1000); 2565 setTimeout(() => { 2566 chat_scroll_area.scrollTop(top ? 0 : chat_scroll_area[0].scrollHeight); 2567 this.scrollHeader(); 2568 }, 20); 2569 }, 2570 2571 // Check if the chat is at bottom 2572 isBottom: function () { 2573 return chat_scroll_area[0].scrollTop === (chat_scroll_area[0].scrollHeight - chat_scroll_area[0].offsetHeight); 2574 }, 2575 2576 // Dashboard header animation 2577 scrollHeader: function () { 2578 if (this.main_header && this.dashboard) { 2579 let scroll = chat_scroll_area.scrollTop(); 2580 if (scroll > -1 && scroll < 1000) { 2581 chat_header.find('.sb-content').css({ 'opacity': (1 - (scroll / 500)), 'top': (scroll / 10 * -1) + 'px' }); 2582 }; 2583 } 2584 }, 2585 2586 // Display the dashboard area 2587 showDashboard: function () { 2588 if (!admin && !tickets) { 2589 main.addClass('sb-dashboard-active'); 2590 chat_header.removeClass('sb-header-agent'); 2591 this.hidePanel() 2592 if (this.start_header) chat_header.html(this.start_header[0]).addClass(this.start_header[1]); 2593 chat_scroll_area.find(' > div').sbActive(false); 2594 main.find('.sb-dashboard').sbActive(true); 2595 this.populateConversations(); 2596 this.conversation = false; 2597 this.agent_id = -1; 2598 this.stopRealTime(); 2599 this.dashboard = true; 2600 this.main_header = true; 2601 this.scrollBottom(true); 2602 SBF.event('SBDashboard'); 2603 } 2604 }, 2605 2606 // Hide the dashboard area 2607 hideDashboard: function () { 2608 if (!admin && !tickets) { 2609 chat.sbActive(true); 2610 main.removeClass('sb-dashboard-active').find('.sb-dashboard').sbActive(false); 2611 this.dashboard = false; 2612 this.headerAgent(); 2613 this.scrollHeader(0); 2614 if (this.chat_open) { 2615 this.startRealTime(); 2616 } 2617 SBF.event('SBDashboardClosed'); 2618 } 2619 }, 2620 2621 // Show a chat panel 2622 showPanel: function (name, title) { 2623 if (tickets) return SBTickets.showPanel(name, title); 2624 let panel = chat_scroll_area.find(' > .sb-panel-' + name); 2625 if (panel.length) { 2626 chat_scroll_area.find(' > div').sbActive(false); 2627 panel.sbActive(true); 2628 if (!this.start_header) this.start_header = [chat_header.html(), chat_header.attr('class')]; 2629 chat_header.attr('class', 'sb-header sb-header-panel').html(`${sb_(title)}<div class="sb-dashboard-btn sb-icon-close"></div>`); 2630 main.addClass('sb-panel-active'); 2631 this.dashboard = true; 2632 } 2633 SBF.event('SBPanelActive', name); 2634 }, 2635 2636 hidePanel: function () { 2637 main.removeClass('sb-panel-active'); 2638 chat_header.removeClass('sb-header-panel'); 2639 }, 2640 2641 // Clear the conversation area and the active conversation 2642 clear: function () { 2643 this.conversation = false;
2644 chat.html(''); 2645 }, 2646 2647 // Update the red notification counter of the chat 2648 updateNotifications: function (action = 'add', conversation_id) { 2649 if (action == 'add' && !this.notifications.includes(conversation_id)) { 2650 this.notifications.push(conversation_id); 2651 if (!this.dashboard && this.conversation && this.conversation.id != conversation_id) this.headerAnimation(); 2652 } 2653 if (action == 'remove') { 2654 for (var i = 0; i < this.notifications.length; i++) { 2655 if (this.notifications[i] == conversation_id) { 2656 this.notifications.splice(i, 1); 2657 if (this.conversation.get('conversation_status_code') != 0 && ['1', '2', 1, 2].includes(this.conversation.get('conversation_status_code'))) { 2658 this.setConversationStatus(0); 2659 } 2660 break; 2661 } 2662 } 2663 } 2664 let count = this.notifications.length; 2665 main.find('.sb-chat-btn span').attr('data-count', count).html(count > -1 ? count : 0); 2666 SBF.event('SBNotificationsUpdate', { action: action, conversation_id: conversation_id }); 2667 }, 2668 2669 // Set the active conversation status 2670 setConversationStatus: function (status_code) { 2671 if (this.conversation) { 2672 SBF.ajax({ 2673 function: 'update-conversation-status', 2674 conversation_id: this.conversation.id, 2675 status_code: status_code 2676 }, () => { 2677 this.conversation.set('conversation_status_code', status_code); 2678 SBF.event('SBActiveConversationStatusUpdated', { conversation_id: this.conversation.id, status_code: status_code }); 2679 }); 2680 return true; 2681 } 2682 return false; 2683 }, 2684 2685 // Typing status 2686 typing: function (user_id = -1, action = 'check') { 2687 if (this.conversation) { 2688 let valid = this.agent_online || (admin && this.user_online); 2689 if (action == 'check' && !SBPusher.active && user_id != -1 && user_id != bot_id && valid) { 2690 SBF.ajax({ 2691 function: 'is-typing', 2692 user_id: user_id, 2693 conversation_id: this.conversation.id 2694 }, (response) => { 2695 if (response && !this.typing_settings['typing']) { 2696 this.typing(-1, 'start'); 2697 } else if (!response && this.typing_settings['typing']) { 2698 this.typing(-1, 'stop'); 2699 } 2700 }); 2701 } else if (action == 'set' && valid) { 2702 let source = this.conversation.get('source'); 2703 if (source) { 2704 source = source == 'fb' ? [source, activeUser().getExtra('facebook-id')['value'], this.conversation.get('extra')] : (source == 'tw' ? [source, activeUser().getExtra('twitter-id')['value']] : false); 2705 } 2706 if (SBPusher.active) { 2707 SBF.debounce(() => { 2708 SBPusher.trigger('client-typing', { user_id: admin ? SB_ACTIVE_AGENT['id'] : activeUser().id, conversation_id: this.conversation.id }); 2709 if (source) SBF.ajax({ function: 'set-typing', source: source }); 2710 }, '#2'); 2711 } else { 2712 if (!this.typing_settings['sent']) { 2713 this.typing_settings['sent'] = true; 2714 SBF.ajax({ 2715 function: 'set-typing', 2716 user_id: user_id, 2717 conversation_id: this.conversation.id, 2718 source: source 2719 }); 2720 this.typing(user_id, 'set'); 2721 } else { 2722 clearTimeout(this.typing_settings['timeout']); 2723 this.typing_settings['timeout'] = setTimeout(() => { 2724 SBF.ajax({ 2725 function: 'set-typing', 2726 user_id: user_id, 2727 conversation_id: -1 2728 }, () => { 2729 this.typing_settings['sent'] = false;
2730 }); 2731 }, 2000); 2732 } 2733 } 2734 } else if (action == 'start' || action == 'stop') { 2735 let start = action == 'start'; 2736 if (!admin && chat_status) { 2737 if (start) { 2738 $(chat_status).addClass('sb-status-typing').html(sb_('Typing')); 2739 } else { 2740 let online = this.agent_online || this.agent_id == bot_id; 2741 $(chat_status).removeClass('sb-status-typing').html(sb_(online ? 'Online' : 'Away')); 2742 if (online) $(chat_status).addClass('sb-status-online'); 2743 } 2744 } 2745 this.typing_settings['typing'] = start; 2746 SBF.event('SBTyping', start); 2747 } 2748 } 2749 }, 2750 2751 // Articles 2752 showArticles: function (id = -1) { 2753 let panel = tickets ? main.find('.sb-panel-main .sb-panel') : chat_scroll_area.find(' > .sb-panel-articles'); 2754 let code = ''; 2755 panel.html('').sbLoading(true); 2756 this.showPanel('articles', CHAT_SETTINGS['articles-title'] ? CHAT_SETTINGS['articles-title'] : 'Help Center'); 2757 this.getArticles(id, (articles) => { 2758 if (id == -1) { 2759 let ids = []; 2760 for (var y = 0; y < this.articles_categories.length; y++) { 2761 let category = this.articles_categories[y]; 2762 let code_articles_category = ''; 2763 for (var i = 0; i < articles.length; i++) { 2764 let article = articles[i]; 2765 if (!ids.includes(article.id) && (!category || SBF.null(article.parent_category) || article.parent_category.includes(category.id))) { 2766 code_articles_category += `<div data-id="${article.id}"><div>${article.title}</div><span>${article.content}</span></div>`; 2767 ids.push(article.id); 2768 } 2769 } 2770 if (code_articles_category) code += (category ? `<div data-id="${category.id}" class="sb-title">${sb_(category.title)}</div>` : '') + code_articles_category; 2771 } 2772 panel.html(`<div class="sb-articles">${code}</div>`); 2773 } else if (articles) { 2774 panel.html(this.getArticleCode(articles)); 2775 } 2776 panel.sbLoading(false); 2777 SBF.event('SBArticles', { id: id, articles: articles }); 2778 }); 2779 }, 2780 2781 getArticles: function (id = -1, onSuccess = false, category = true) { 2782 if (this.articles === false || id != -1) { 2783 SBF.ajax({ 2784 function: 'get-articles', 2785 categories: category, 2786 articles_language: typeof SB_LANG != ND ? SB_LANG[0] : false, 2787 id: id 2788 }, (response) => { 2789 if (id == -1) { 2790 this.articles = response[0]; 2791 this.articles_categories = response[1].length ? response[1] : ['']; 2792 } 2793 onSuccess(id == -1 ? response[0] : response); 2794 }); 2795 } else { 2796 if (onSuccess) { 2797 if (category && category !== true && category != 'true') { 2798 let articles = []; 2799 for (var i = 0; i < this.articles.length; i++) { 2800 if (('categories' in this.articles[i] && this.articles[i].categories.includes(category)) || ('parent_category' in this.articles[i] && category == this.articles[i].parent_category)) articles.push(this.articles[i]); 2801 } 2802 onSuccess(articles); 2803 } else onSuccess(this.articles); 2804 } else { 2805 return this.articles; 2806 } 2807 } 2808 }, 2809 2810 searchArticles: function (search, button, target) { 2811 if (search) { 2812 $(button).sbLoading(true); 2813 SBF.ajax({ 2814 function: 'search-articles', 2815 articles_language: typeof SB_LANG != ND ? SB_LANG[0] : false, 2816 search: search 2817 }, (articles) => { 2818 let code = ''; 2819 let count = articles.length; 2820 if (count == 0) { 2821 code += `<p class="sb-no-results">${sb_('No articles found.')}</p>`; 2822 } else { 2823 for (var i = 0; i < articles.length; i++) { 2824 code += `<div data-id="${articles[i]['id']}"><div>${articles[i]['title']}</div><span>${articles[i]['content']}</span></div>`; 2825 } 2826 } 2827 $(target).html(code); 2828 $(button).sbLoading(false); 2829 }); 2830 } 2831 }, 2832 2833 setArticleRating: function (article_id, rating, onSuccess = false) { 2834 SBF.ajax({ 2835 function: 'article-ratings', 2836 article_id: article_id, 2837 rating: rating 2838 }, (response) => { 2839 if (onSuccess) onSuccess(response); 2840 }); 2841 }, 2842 2843 articleRatingOnClick: function (button) { 2844 let article = $(button).closest('.sb-article'); 2845 if (!article[0].hasAttribute('data-user-rating')) { 2846 $(button).parent().sbLoading(); 2847 let rating = $(button).attr('data-rating') == 'positive' ? 1 : -1; 2848 let article_id = $(button).closest('.sb-article').attr('data-id'); 2849 SBChat.setArticleRating(article_id, rating, () => { 2850 SBF.storage('article-rating-' + article_id, rating); 2851 article.attr('data-user-rating', rating); 2852 $(button).parent().sbLoading(false); 2853 }); 2854 } 2855 }, 2856 2857 getArticleCode: function (article) {
2858 let user_rating = SBF.storage('article-rating-' + article['id']); 2859 let code = ''; 2860 if (this.articles_categories && !SBF.null(article.categories)) { 2861 for (var i = 0; i < this.articles_categories.length; i++) { 2862 let category_id = this.articles_categories[i].id; 2863 if (article.categories.includes(category_id) || article.parent_category == category_id) { 2864 code += `<span data-id="${category_id}">${sb_(this.articles_categories[i].title)}</span>`; 2865 } 2866 } 2867 } 2868 return `<div data-id="${article.id}"${user_rating ? ` data-user-rating="${user_rating}"` : ''} class="sb-article"><div class="sb-title">${article.title}<div class="sb-close sb-icon-close"></div></div><div class="sb-content">${article.content.replace(/(?:\r\n|\r|\n)/g, '<br>')}</div>${SBF.null(article.link) ? '' : `<a href="${article.link}" target="_blank" class="sb-btn-text"><i class="sb-icon-plane"></i>${sb_('Read more')}</a>`}${code ? `<div class="sb-article-category-links">${code}</div>` : ''}<div class="sb-rating"><span>${sb_('Rate and review')}</span><div><i data-rating="positive" class="sb-submit sb-icon-like"><span>${sb_('Helpful')}</span></i><i data-rating="negative" class="sb-submit sb-icon-dislike"><span>${sb_('Not helpful')}</span></i></div></div></div>`; 2869 }, 2870 2871 initArticlesPage: function () { 2872 let code_articles = ''; 2873 let code_categories = ''; 2874 let cache; 2875 let panel_main; 2876 let categories = {}; 2877 let category_names = {}; 2878 articles_page = $('body').find('#sb-articles'); 2879 if (!articles_page.length) articles_page = $('body'); 2880 articles_page.sbLoading(true); 2881 this.getArticles(-1, () => { 2882 for (var i = 0; i < this.articles.length; i++) { 2883 let article = this.articles[i]; 2884 code_articles += `<div data-id="${article.id}"><div>${article.title}</div><span>${article.content}</span></div>`; 2885 if (!SBF.null(article.parent_category)) { 2886 if (!(article.parent_category in categories)) categories[article.parent_category] = []; 2887 if (!SBF.null(article.categories)) { 2888 for (var j = 0; j < article.categories.length; j++) { 2889 if (!categories[article.parent_category].includes(article.categories[j])) categories[article.parent_category].push(article.categories[j]); 2890 } 2891 } 2892 } 2893 } 2894 for (var i = 0; i < this.articles_categories.length; i++) { 2895 category_names[this.articles_categories[i].id] = this.articles_categories[i].title; 2896 } 2897 for (var key in categories) { 2898 code_categories += `<div class="sb-parent-category"><span data-id="${key}">${sb_(category_names[key])}</span><div class="sb-ul">`; 2899 for (var i = 0; i < categories[key].length; i++) { 2900 code_categories += `<span data-id="${categories[key][i]}">${sb_(category_names[categories[key][i]])}</span>`; 2901 } 2902 code_categories += '</div></div>'; 2903 } 2904 articles_page.html(`<div class="sb-articles-page${CHAT_SETTINGS.rtl ? ' sb-rtl' : ''}"><div class="sb-panel-main sb-articles">${code_articles}</div><div class="sb-panel-side"><div class="sb-title">${CHAT_SETTINGS['articles-title'] ? CHAT_SETTINGS['articles-title'] : 'Help Center'}</div><div class="sb-input sb-input-btn"><input placeholder="${sb_('Search for articles...')}" autocomplete="off"><div class="sb-submit-articles sb-icon-arrow-right"></div></div><div class="sb-title">${sb_('Categories')}</div><div class="sb-article-categories"><div><span data-id="true" class="sb-active">${sb_('All articles')}</span></div>${code_categories}</div></div></div>`); 2905 articles_page.sbLoading(false); 2906 panel_main = articles_page.find('.sb-panel-main'); 2907 if (SBF.getURL('article')) articles_page.find(`.sb-articles > [data-id="${SBF.getURL('article')}"]`).click(); 2908 }); 2909 2910 articles_page.on('keydown', '.sb-panel-side input', function (e) { 2911 if (e.which == 13) $(this).next().click(); 2912 }); 2913 2914 articles_page.on('click', '.sb-articles > [data-id]', function () { 2915 cache = panel_main.html(); 2916 panel_main.sbLoading(true); 2917 SBChat.getArticles($(this).attr('data-id'), (article) => { 2918 panel_main.removeClass('sb-articles').html(SBChat.getArticleCode(article)); 2919 panel_main.sbLoading(false); 2920 }); 2921 }); 2922 2923 articles_page.on('click', '.sb-article [data-rating]', function () { 2924 SBChat.articleRatingOnClick(this); 2925 }); 2926 2927 articles_page.on('click', '.sb-article .sb-title .sb-close', function () { 2928 articles_page.find('.sb-panel-main').addClass('sb-articles').html(cache); 2929 }); 2930 2931 articles_page.on('click', '.sb-submit-articles', function () { 2932 let search = $(this).parent().find('input').val(); 2933 if (search) { 2934 cache = panel_main.html(); 2935 panel_main.html(''); 2936 SBChat.searchArticles(search, this, panel_main); 2937 } else { 2938 panel_main.html(cache); 2939 } 2940 panel_main.addClass('sb-articles'); 2941 }); 2942 2943 articles_page.on('click', '.sb-article-categories [data-id]', function () { 2944 if (loading(panel_main)) return; 2945 let code = ''; 2946 cache = panel_main.html(); 2947 articles_page.find('.sb-article-categories [data-id]').sbActive(false); 2948 $(this).sbActive(true); 2949 SBChat.getArticles(-1, (articles) => { 2950 for (var i = 0; i < articles.length; i++) { 2951 code += `<div data-id="${articles[i].id}"><div>${articles[i]['title']}</div><span>${articles[i].content}</span></div>`; 2952 } 2953 panel_main.addClass('sb-articles').html(code ? code : `<p class="sb-no-results">${sb_('No articles found.')}</p>`); 2954 panel_main.sbLoading(false); 2955 }, $(this).attr('data-id')); 2956 }); 2957 }, 2958 2959 // Emoji 2960 categoryEmoji: function (category) { 2961 let list = this.emoji_options['list']; 2962 if (category == 'all') { 2963 this.emoji_options['list_now'] = list; 2964 } else { 2965 this.emoji_options['list_now'] = []; 2966 for (var i = 0; i < list.length; i++) { 2967 if (list[i]['category'].startsWith(category)) { 2968 this.emoji_options['list_now'].push(list[i]); 2969 } 2970 } 2971 } 2972 this.emoji_options['range'] = 0; 2973 this.populateEmoji(0); 2974 this.populateEmojiBar(); 2975 }, 2976 2977 mouseWheelEmoji: function (e) { 2978 let range = this.emoji_options['range']; 2979 if (sbDelta(e) > 0 || (mobile && typeof e.originalEvent.changedTouches !== ND && this.emoji_options['touch'] < e.originalEvent.changedTouches[0].clientY)) { 2980 range -= (range < 1 ? 0 : 1); 2981 } else { 2982 range += (range > this.emoji_options['range_limit'] ? 0 : 1); 2983 } 2984 chat_emoji.find('.sb-emoji-bar > div').sbActive(false).eq(range).sbActive(true); 2985 this.emoji_options['range'] = range; 2986 this.populateEmoji(range); 2987 e.preventDefault(); 2988 }, 2989 2990 insertEmoji: function (emoji) { 2991 if (emoji.indexOf('.svg') > 0) { 2992 emoji = $.parseHTML(emoji)[0]['alt']; 2993 } 2994 this.insertText(emoji); 2995 chat_emoji.sbTogglePopup(); 2996 }, 2997 2998 showEmoji: function (button) { 2999 if (chat_emoji.sbTogglePopup(button)) { 3000 if (!admin) { 3001 chat_emoji.css({ left: chat_editor.offset().left + (tickets ? 68 : 20), top: chat_editor.offset().top - window.scrollY - (tickets ? chat_editor.height() - 330 : 304) }); 3002 } 3003 if (chat_emoji.find('.sb-emoji-list > ul').html() == '') { 3004 jQuery.ajax({ 3005 method: 'POST', 3006 url: SB_AJAX_URL, 3007 data: { 3008 function: 'emoji' 3009 } 3010 }).done((response) => { 3011 this.emoji_options['list'] = JSON.parse(response); 3012 this.emoji_options['list_now'] = this.emoji_options['list']; 3013 this.populateEmoji(0); 3014 this.populateEmojiBar(); 3015 }); 3016 } 3017 SBF.deselectAll(); 3018 } 3019 }, 3020 3021 populateEmoji: function (range) { 3022 let code = ''; 3023 let per_page = mobile ? 42 : 48; 3024 let limit = range * per_page + per_page; 3025 let list_now = this.emoji_options['list_now']; 3026 if (limit > list_now.length) limit = list_now.length; 3027 this.emoji_options['range_limit'] = list_now.length / per_page - 1; 3028 this.emoji_options['range'] = range; 3029 for (var i = (range * per_page); i < limit; i++) { 3030 code += `<li>${list_now[i]['char']}</li>`; 3031 } 3032 chat_emoji.find('.sb-emoji-list').html(`<ul>${code}</ul>`); 3033 }, 3034 3035 populateEmojiBar: function () { 3036 let code = '<div class="sb-active"></div>'; 3037 let per_page = mobile ? 42 : 49; 3038 for (var i = 0; i < this.emoji_options['list_now'].length / per_page - 1; i++) { 3039 code += '<div></div>'; 3040 } 3041 this.emoji_options['range'] = 0; 3042 chat_emoji.find('.sb-emoji-bar').html(code); 3043 }, 3044 3045 clickEmojiBar: function (item) { 3046 let range = $(item).index(); 3047 this.populateEmoji(range); 3048 this.emoji_options['range'] = range; 3049 chat_emoji.find('.sb-emoji-bar > div').sbActive(false).eq(range).sbActive(true); 3050 }, 3051 3052 searchEmoji: function (search) { 3053 SBF.search(search, () => { 3054 if (search.length > 1) { 3055 let list = this.emoji_options['list']; 3056 let list_now = []; 3057 for (var i = 0; i < list.length; i++) { 3058 if (list[i]['category'].toLowerCase().includes(search) || list[i]['name'].toLowerCase().includes(search)) { 3059 list_now.push(list[i]); 3060 } 3061 } 3062 this.emoji_options['list_now'] = list_now; 3063 } else { 3064 this.emoji_options['list_now'] = this.emoji_options['list']; 3065 } 3066 this.emoji_options['range'] = 0; 3067 this.populateEmoji(0); 3068 this.populateEmojiBar(); 3069 }); 3070 }, 3071 3072 // Editor methods 3073 textareaChange: function (textarea) { 3074 let value = $(textarea).val(); 3075 3076 // Saved replies 3077 if (admin) { 3078 SBAdmin.conversations.savedReplies(textarea, value); 3079 } 3080 3081 // Typing 3082 if (value) { 3083 this.typing((admin && !SBPusher.active ? SB_ACTIVE_AGENT['id'] : activeUser().id), 'set'); 3084 this.activateBar(); 3085 } else { 3086 this.activateBar(false); 3087 } 3088 }, 3089 3090 insertText: function (text) { 3091 let textarea = $(chat_textarea.get(0)); 3092 let index = 0; 3093 if (this.dashboard) return false;
3094 if ('selectionStart' in textarea.get(0)) { 3095 index = textarea.get(0).selectionStart; 3096 } else if ('selection' in document) { 3097 textarea.focus(); 3098 let selection = document.selection.createRange(); 3099 var selection_length = document.selection.createRange().text.length; 3100 selection.moveStart('character', -textarea.value.length); 3101 index = selection.text.length - selection_length; 3102 } 3103 textarea.val(textarea.val().substr(0, index) + text + textarea.val().substr(index)); 3104 textarea.focus(); 3105 textarea.manualExpandTextarea(); 3106 this.activateBar(); 3107 }, 3108 3109 enabledAutoExpand: function () { 3110 if (chat_textarea.length) chat_textarea.autoExpandTextarea(); 3111 }, 3112 3113 // Privacy message 3114 privacy: function () { 3115 SBF.ajax({ 3116 function: 'get-block-setting', 3117 value: 'privacy' 3118 }, (response) => { 3119 chat_scroll_area.append(`<div class="sb-privacy sb-init-form" data-decline="${response['decline']}"><div class="sb-title">${response['title']}</div><div class="sb-text">${response['message']}</div>` + (response['link'] ? `<a target="_blank" href="${response['link']}">${response['link-name']}</a>` : '') + `<div class="sb-buttons"><a class="sb-btn sb-approve">${response['btn-approve']}</a><a class="sb-btn sb-decline">${response['btn-decline']}</a></div></div>`); 3120 this.finalizeInit(); 3121 SBF.event('SBPrivacy'); 3122 }); 3123 if (!this.dashboard) this.showDashboard(); 3124 this.dashboard = true; 3125 main.addClass('sb-init-form-active'); 3126 }, 3127 3128 // Popup message 3129 popup: function (close = false, content = false) { 3130 if (close) { 3131 let popup = main.find('.sb-popup-message'); 3132 let id = popup.attr('data-id'); 3133 storage('popup' + (SBF.null(id) ? '' : id), true); 3134 popup.remove(); 3135 return; 3136 } 3137 setTimeout(() => { 3138 if (!this.chat_open) { 3139 if (content == false) content = CHAT_SETTINGS['popup']; 3140 main.find('.sb-popup-message').remove(); 3141 main.append(`<div data-id="${'id' in content ? content['id'] : ''}" class="sb-popup-message">` + ('image' in content && content['image'] ? `<img src="${content['image']}" />` : '') + ('title' in content && content['title'] ? `<div class="sb-top">${content['title']}</div>` : '') + `<div class="sb-text">${content['message']}</div><div class="sb-icon-close"></div></div>`); 3142 SBF.event('SBPopup', content); 3143 } 3144 }, 1000); 3145 }, 3146 3147 // Follow up message 3148 followUp: function () { 3149 if (this.followUpCheck()) { 3150 if (timeout) clearTimeout(timeout); 3151 timeout = setTimeout(() => { 3152 if (this.followUpCheck()) { 3153 let settings = CHAT_SETTINGS['follow']; 3154 this.sendMessage(bot_id, `[email id="sb-follow-up-form" title="${settings['title']}" message="${settings['message']}" placeholder="${settings['placeholder']}" name="${settings['name']}" last-name="${settings['last-name']}" phone="${settings['phone']}" phone-required="${settings['phone-required']}" success="${settings['success']}"]`); 3155 this.scrollBottom(); 3156 SBF.storageTime('email'); 3157 SBF.event('SBFollowUp'); 3158 } 3159 }, SBF.null(CHAT_SETTINGS['follow']['delay']) ? (CHAT_SETTINGS['office-hours'] || agents_online ? 15000 : (SBApps.dialogflow.active() ? 8000 : 5000)) : parseInt(CHAT_SETTINGS['follow']['delay'])); 3160 } 3161 }, 3162 3163 followUpCheck: function () { 3164 return !admin && this.conversation && CHAT_SETTINGS['follow']['active'] && activeUser() && !activeUser().get('email') && SBF.storageTime('email', 24) && (CHAT_SETTINGS['office-hours'] || !CHAT_SETTINGS['follow']['disable-office-hours']); 3165 }, 3166 3167 // Welcome message 3168 welcome: function () { 3169 if ((CHAT_SETTINGS['welcome-trigger'] != 'open' || this.chat_open) && (CHAT_SETTINGS['office-hours'] || !CHAT_SETTINGS['welcome-disable-office-hours']) && CHAT_SETTINGS['welcome'] && !storage('welcome') && activeUser()) { 3170 SBF.ajax({ 3171 function: 'get-block-setting', 3172 value: 'welcome' 3173 }, (response) => { 3174 setTimeout(() => { 3175 if (CHAT_SETTINGS['dialogflow-welcome']) { 3176 if (this.conversation === false) { 3177 this.newConversation(3, -1, '', [], null, null, function () { SBApps.dialogflow.welcome(response['open'], response['sound']) }); 3178 } else { 3179 SBApps.dialogflow.welcome(response['open'], response['sound']); 3180 } 3181 } else { 3182 this.sendMessage(bot_id, response['message'], [], false, false, 3); 3183 if (response['open']) { 3184 this.start(); 3185 } 3186 if (response['sound']) { 3187 this.audio.play(); 3188 } 3189 } 3190 this.skip = true; 3191 SBF.event('SBWelcomeMessage'); 3192 }, parseInt(tickets ? 0 : CHAT_SETTINGS['welcome-delay'])); 3193 storage('welcome', true); 3194 }); 3195 } 3196 }, 3197 3198 // Subscribe message 3199 subscribe: function () { 3200 if (CHAT_SETTINGS['subscribe'] && !storage('subscribe') && activeUser() && SBF.null(activeUser().get('email')) && activeUser()) { 3201 setTimeout(() => { 3202 SBF.ajax({ 3203 function: 'get-block-setting', 3204 value: 'subscribe' 3205 }, (response) => { 3206 this.sendMessage(bot_id, response['message'], [], false, { event: 'open-chat' }, 3); 3207 if (response['sound'] && this.audio) { 3208 this.audio.play(); 3209 } 3210 this.skip = true; 3211 storage('subscribe', true); 3212 }); 3213 }, parseInt(CHAT_SETTINGS['subscribe-delay'])); 3214 } 3215 }, 3216 3217 // Offline timetable message 3218 offlineMessage: function () { 3219 if (!admin && !this.offline_message_set && CHAT_SETTINGS['timetable'] && (!CHAT_SETTINGS['office-hours'] || (!agents_online && !CHAT_SETTINGS['timetable-disable-agents'])) && SBF.storageTime('timetable', 1)) { 3220 let message = CHAT_SETTINGS['timetable-message']; 3221 switch (CHAT_SETTINGS['timetable-type']) { 3222 case 'header': 3223 if (message[0]) chat_header.find('.sb-title').html(message[0]); 3224 chat_header.find('.sb-text').html(message[1]); 3225 this.offline_message_set = true; 3226 break; 3227 case 'info':
3228 chat.prepend(`<div class="sb-notify-message sb-rich-cnt"><div class="sb-cnt"><div class="sb-message">${message[0] ? `<b>${message[0]}</b> ` : ''}${message[1]}</div></div></div>`); 3229 main.addClass('sb-notify-active'); 3230 this.offline_message_set = true; 3231 break; 3232 default: 3233 setTimeout(() => { 3234 if (this.conversation) { 3235 this.sendMessage(bot_id, CHAT_SETTINGS['timetable-hide'] ? `${message[0] ? `*${message[0]}*\n` : ''}${message[1]}` : '[timetable]'); 3236 this.scrollBottom(); 3237 this.offline_message_set = true; 3238 SBF.storageTime('timetable'); 3239 } 3240 }, 5000); 3241 } 3242 } 3243 }, 3244 3245 // Send Slack message 3246 slackMessage: function (user_id, full_name, profile_image, message, attachments = []) { 3247 if (!this.conversation || (!message && !attachments.length)) return false; 3248 let conversation_id = this.conversation.id; 3249 SBF.ajax({ 3250 function: 'send-slack-message', 3251 user_id: user_id, 3252 full_name: full_name, 3253 profile_image: profile_image, 3254 conversation_id: conversation_id, 3255 message: message, 3256 attachments: attachments, 3257 channel: this.slack_channel[0] == activeUser().id ? this.slack_channel[1] : false 3258 }, (response) => { 3259 this.slack_channel = [activeUser().id, response[1]]; 3260 SBF.event('SBSlackMessageSent', { message: message, conversation_id: conversation_id, slack_channel: response[1] }); 3261 }); 3262 }, 3263 3264 // Delete message 3265 deleteMessage: function (message_id) { 3266 SBF.ajax({ 3267 function: 'delete-message', 3268 message_id: message_id 3269 }, () => { 3270 if (this.conversation) this.conversation.deleteMessage(message_id); 3271 chat.find(`[data-id="${message_id}"]`).remove(); 3272 SBF.event('SBMessageDeleted', message_id); 3273 }); 3274 }, 3275 3276 // Registration form 3277 registration: function (check = false, type = CHAT_SETTINGS['registration-required']) { 3278 if (check) return CHAT_SETTINGS['registration-required'] && (!CHAT_SETTINGS['registration-offline'] || !agents_online) && (!CHAT_SETTINGS['registration-timetable'] || !CHAT_SETTINGS['office-hours']) && (activeUser() === false || ['visitor', 'lead'].includes(activeUser().type)); 3279 chat_scroll_area.append(SBRichMessages.generate({}, CHAT_SETTINGS['registration-link'] ? 'login' : type, 'sb-init-form')); 3280 if (!this.dashboard) this.showDashboard(); 3281 this.dashboard = true; 3282 this.finalizeInit(); 3283 main.addClass('sb-init-form-active'); 3284 }, 3285 3286 // Display the send button 3287 activateBar: function (show = true) { 3288 chat_editor.find('.sb-bar').sbActive(show); 3289 }, 3290 3291 // Shortcut for add user and login function 3292 addUserAndLogin: function (onSuccess = false, lead = false) { 3293 SBF.ajax({ 3294 function: 'add-user-and-login',
3295 settings: { user_type: lead ? 'lead' : 'visitor' } 3296 }, (response) => { 3297 SBF.loginCookie(response[1]); 3298 activeUser(new SBUser(response[0])); 3299 SBPusher.start(); 3300 if (!SBPusher.active) SBChat.automations.run_all(); 3301 if (onSuccess) { 3302 onSuccess(response); 3303 } 3304 }); 3305 }, 3306 3307 // Check if the dashboard must be showed 3308 isInitDashboard: function () { 3309 return CHAT_SETTINGS['init-dashboard'] || (activeUser() && activeUser().conversations.length > 1); 3310 }, 3311 3312 // Upload response 3313 uploadResponse: function (response) { 3314 response = JSON.parse(response); 3315 if (response[0] == 'success') { 3316 if (response[1] == 'extension_error') { 3317 let message = 'The file you are trying to upload has an extension that is not allowed.'; 3318 if (admin) SBAdmin.dialog(message, 'info'); else alert(message); 3319 } else if ($(upload_target).hasClass('sb-input-image')) { 3320 $(upload_target).find('.image').attr('data-value', '').css('background-image', ''); 3321 setTimeout(() => { 3322 $(upload_target).find('.image').attr('data-value', response[1]).css('background-image', `url("${response[1]}?v=${SBF.random()}")`).append('<i class="sb-icon-close"></i>'); 3323 upload_target = false;
3324 }, 500); 3325 } else { 3326 let name = response[1].substr(response[1].lastIndexOf('/') + 1); 3327 chat_editor.find('.sb-attachments').append(`<div data-name="${name}" data-value="${response[1]}">${name}<i class="sb-icon-close"></i></div>`); 3328 SBChat.activateBar(); 3329 } 3330 } else { 3331 SBF.error(response[1], 'sb-upload-files.change'); 3332 } 3333 this.busy(false); 3334 }, 3335 3336 // Archive a conversation and close it 3337 closeChat: function (ajax = true) { 3338 let id = this.conversation.id; 3339 SBChat.clear(); 3340 if (ajax) { 3341 SBF.ajax({ 3342 function: 'update-conversation-status', 3343 conversation_id: id, 3344 status_code: 3 3345 }, () => { 3346 close(); 3347 }); 3348 } else { 3349 close(); 3350 } 3351 function close() { 3352 main.find(`li[data-conversation-id="${id}"]`).remove(); 3353 force_action = 'new-conversation'; 3354 SBChat.clear(); 3355 storage('open-conversation', ''); 3356 activeUser().removeConversation(id); 3357 if (!main.hasClass('sb-dashboard-disabled')) { 3358 SBChat.showDashboard(); 3359 } 3360 } 3361 }, 3362 3363 // Automations 3364 automations: { 3365 history: [], 3366 busy: [], 3367 scroll_position_intervals: {}, 3368 timeout_queue: [], 3369 3370 run_all: function () { 3371 let automations = CHAT_SETTINGS['automations']; 3372 for (var i = 0; i < automations.length; i++) { 3373 let automation = automations[i]; 3374 let conditions = automation.conditions; 3375 let count = conditions.length; 3376 let valid = count == 0; 3377 let browsing_time = false; 3378 let scroll_position = false; 3379 let server_conditions = false; 3380 for (var j = 0; j < conditions.length; j++) { 3381 let criteria = conditions[j][1]; 3382 valid = false; 3383 switch (conditions[j][0]) { 3384 case 'browsing_time': 3385 valid = true; 3386 browsing_time = criteria; 3387 break; 3388 case 'scroll_position': 3389 valid = true; 3390 scroll_position = criteria; 3391 break; 3392 case 'include_urls': 3393 case 'exclude_urls': 3394 case 'referring': 3395 let url = conditions[j][0] == 'referring' ? document.referrer : window.location.href; 3396 let checks = conditions[j][2].split(','); 3397 let include = conditions[j][0] != 'exclude_urls'; 3398 if (!include) valid = true; 3399 url = url.replace('https://', '').replace('http://', '').replace('www.', ''); 3400 for (var y = 0; y < checks.length; y++) { 3401 checks[y] = $.trim(checks[y].replace('https://', '').replace('http://', '').replace('www.', '')); 3402 if ((criteria == 'contains' && url.indexOf(checks[y]) != -1) || (criteria == 'does-not-contain' && url.indexOf(checks[y]) == -1) || (criteria == 'is-exactly' && checks[y] == url) || (criteria == 'is-not' && checks[y] != url)) { 3403 valid = include; 3404 break; 3405 } 3406 } 3407 break; 3408 case 'custom_variable': 3409 let variable = criteria.split('='); 3410 if (variable[0] in window && window[variable[0]] == variable[1]) { 3411 valid = true; 3412 } 3413 break; 3414 case 'returning_visitor': 3415 case 'user_type': 3416 case 'cities': 3417 case 'languages': 3418 case 'countries': 3419 valid = activeUser(); 3420 server_conditions = true; 3421 break; 3422 case 'user_phone': 3423 valid = activeUser() && !SBF.null(activeUser().getExtra('phone')); 3424 break; 3425 case 'user_email': 3426 valid = activeUser() && !SBF.null(activeUser().get('email')); 3427 break; 3428 default: 3429 valid = true; 3430 } 3431 if (!valid) break; 3432 } 3433 if (['messages', 'emails', 'sms'].includes(automation.type) && !activeUser()) valid = false;
3434 if (valid) { 3435 if (server_conditions) { 3436 if (!(automation.id in this.busy)) { 3437 SBF.ajax({ 3438 function: 'automations-validate', 3439 automation: automation 3440 }, (response) => { 3441 if (response !== false) { 3442 this.run_all_final(automation, scroll_position, browsing_time); 3443 } 3444 delete this.busy[automation.id]; 3445 }); 3446 this.busy[automation.id] = true; 3447 } 3448 } else { 3449 this.run_all_final(automation, scroll_position, browsing_time); 3450 } 3451 } 3452 } 3453 }, 3454 3455 run_all_final: function (automation, scroll_position, browsing_time) { 3456 if (scroll_position) { 3457 this.scroll_position_intervals[automation.id] = setInterval(() => { 3458 if ($(window).scrollTop() > parseInt(scroll_position)) { 3459 if (browsing_time) setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000); 3460 else this.run(automation); 3461 clearInterval(this.scroll_position_intervals[automation.id]); 3462 } 3463 }, 1000); 3464 } else if (browsing_time) { 3465 if (!this.timeout_queue.includes(automation.id)) { 3466 setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000); 3467 this.timeout_queue.push(automation.id); 3468 } 3469 } else this.run(automation); 3470 }, 3471 3472 run: function (automation) { 3473 if (this.history.includes(automation.id)) return; 3474 switch (automation.type) { 3475 case 'messages': 3476 case 'emails': 3477 case 'sms': 3478 if (!(automation.id in this.busy)) { 3479 if (automation.type == 'messages' && SBChat.chat_open) { 3480 let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false; 3481 if (last_message && ((Date.now() - 600000) < SBF.unix(last_message.get('creation_time')))) return; 3482 } 3483 SBF.ajax({ 3484 function: 'automations-run', 3485 automation: automation 3486 }, (response) => { 3487 if (response !== false) { 3488 this.history.push(automation.id); 3489 if (automation.type == 'messages' && !SBPusher.active) SBChat.updateConversations(); 3490 } 3491 delete this.busy[automation.id]; 3492 }); 3493 this.busy[automation.id] = true; 3494 } 3495 break; 3496 case 'popups': 3497 if (!storage('popup' + automation.id)) { 3498 setTimeout(() => { 3499 if (!SBChat.chat_open) { 3500 SBChat.popup(false, { id: automation.id, image: automation.profile_image, title: automation.title, message: automation.message }); 3501 this.history.push(automation.id); 3502 } else if (automation.fallback) { 3503 let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false; 3504 if (!last_message || ((Date.now() - 600000) > SBF.unix(last_message.get('creation_time')))) { 3505 SBChat.sendMessage(bot_id, (SBF.null(automation.title) ? '' : `*${automation.title}*\n`) + automation.message, [], false, false, 0); 3506 storage('popup' + automation.id, true); 3507 this.history.push(automation.id); 3508 } 3509 } 3510 }, 1000); 3511 } 3512 break; 3513 case 'design': 3514 if (automation.background) { 3515 chat_header.css('background-image', `url("${automation.background}")`); 3516 } 3517 if (automation.brand) { 3518 chat_header.find('.sb-brand img').attr('src', automation.brand); 3519 } 3520 if (automation.title) { 3521 chat_header.find('.sb-title').html(automation.title); 3522 } 3523 if (automation.message) { 3524 chat_header.find('.sb-text').html(automation.message); 3525 } 3526 if (automation.icon) { 3527 main.find('.sb-chat-btn .sb-icon').attr('src', automation.icon); 3528 } 3529 if (automation.color_1 || automation.color_2 || automation.color_3) { 3530 SBF.ajax({ function: 'chat-css', color_1: automation.color_1, color_2: automation.color_2, color_3: automation.color_3 }, (response) => { 3531 global.append(`<style>${response}</style>`); 3532 }); 3533 } 3534 this.history.push(automation.id); 3535 break; 3536 case 'more': 3537 if (automation.department) { 3538 SBChat.default_department = automation.department; 3539 } 3540 if (automation.agent) { 3541 SBChat.default_agent = automation.agent; 3542 } 3543 break; 3544 } 3545 } 3546 }, 3547 3548 // More 3549 flashNotification: function () { 3550 clearInterval(interval); 3551 interval = setInterval(function () { 3552 document.title = document.title == document_title ? sb_('New message...') : document_title; 3553 }, 2000); 3554 }, 3555 3556 calculateLabelDates: function () { 3557 if (admin || this.chat_open) label_date_items = chat.find('.sb-label-date'); 3558 }, 3559 3560 calculateLabelDateFirst: function () { 3561 if (!this.conversation.messages.length) chat.append(`<div class="sb-label-date"><span>${sb_('Today')}</span></div>`); 3562 } 3563 } 3564 window.SBChat = SBChat; 3565 3566 /* 3567 * ---------------------------------------------------------- 3568 * # RICH MESSAGES 3569 * ---------------------------------------------------------- 3570 */ 3571 3572 var SBRichMessages = { 3573 rich_messsages: { 3574 'email': '', 3575 'button': '', 3576 'video': '', 3577 'image': '', 3578 'woocommerce-button': '', 3579 'chips': '<div class="sb-buttons">[options]</div>', 3580 'buttons': '<div class="sb-buttons">[options]</div>', 3581 'select': '<div class="sb-select"><p></p><ul>[options]</ul></div>', 3582 'list': '<div class="sb-text-list">[values]</div>', 3583 'list-image': '<div class="sb-image-list">[values]</div>', 3584 'table': '<table><tbody>[header][values]</tbody></table>',
3585 'inputs': '<div class="sb-form">[values]</div>', 3586 'rating': `<div class="sb-rating" data-success-negative="[success-negative]"><span>[label]</span><div><i data-rating="positive" class="sb-submit sb-icon-like"><span>[positive]</span></i><i data-rating="negative" class="sb-submit sb-icon-dislike"><span>[negative]</span></i></div></div>`, 3587 'card': '<div class="sb-card">[settings]</div>', 3588 'share': '<div class="sb-social-buttons">[settings]</div>', 3589 'slider': '<div class="sb-slider"><div>[items]</div></div><div class="sb-slider-arrow sb-icon-arrow-left[class]"></div><div class="sb-slider-arrow sb-icon-arrow-right sb-active[class]"></div>', 3590 'slider-images': '<div class="sb-slider sb-slider-images"><div>[items]</div></div><div class="sb-slider-arrow sb-icon-arrow-left[class]"></div><div class="sb-slider-arrow sb-icon-arrow-right sb-active[class]"></div>', 3591 'phone': '' 3592 }, 3593 cache: {}, 3594 3595 // Generate a rich message 3596 generate: function (settings, name, css = '') { 3597 let content; 3598 let next = true; 3599 let id = 'id' in settings ? settings['id'] : SBF.random(); 3600 let render = new SBMessage({}); 3601 3602 // Check if the rich message exist 3603 if (name in this.rich_messsages) { 3604 content = this.rich_messsages[name]; 3605 } else if (name in this.cache) { 3606 content = this.cache[name]; 3607 } else if (admin || (!SBF.null(CHAT_SETTINGS) && 'rich-messages' in CHAT_SETTINGS && CHAT_SETTINGS['rich-messages'].includes(name))) { 3608 if (!('id' in settings)) id = name; 3609 content = '<div class="sb-rich-loading sb-loading"></div>'; 3610 SBF.ajax({ 3611 function: 'get-rich-message', 3612 name: name, 3613 settings: settings 3614 }, (response) => { 3615 response = render.render(this.initInputs(response)); 3616 if (name == 'timetable') response = this.timetable(response); 3617 main.find(`.sb-rich-message[id="${id}"]`).html(`<div class="sb-content">${response}</div>`); 3618 this.cache[name] = response; 3619 SBChat.scrollBottom(SBChat.dashboard); 3620 }); 3621 next = false; 3622 } else { 3623 return false; 3624 } 3625 3626 // Generate the rich message 3627 let disabled = 'disabled' in settings; 3628 if (next) { 3629 let options; 3630 let code = ''; 3631 switch (name) { 3632 case 'rating': 3633 content = content.replace('[label]', sb_(SBF.null(settings['label']) ? 'Rate and review' : settings['label'])).replace('[success-negative]', SBF.null(settings['success-negative']) ? '' : sb_(settings['success-negative'])).replace('[positive]', sb_(SBF.null(settings['label-positive']) ? 'Helpful' : settings['label-positive'])).replace('[negative]', sb_(SBF.null(settings['label-negative']) ? 'Not helpful' : settings['label-negative'])); 3634 break; 3635 case 'email': 3636 let inputs = []; 3637 let email = activeUser().get('email'); 3638 let default_name = activeUser().get('last_name').charAt(0) == '#'; 3639 if (settings['name'] == 'true') inputs.push(['first_name', settings['last-name'] == 'true' ? 'First name' : 'Name', default_name ? '' : (settings['last-name'] == 'true' ? activeUser().get('first_name') : activeUser().name), 'text', true]); 3640 if (settings['last-name'] == 'true') inputs.push(['last_name', 'Last name', default_name ? '' : activeUser().get('last_name'), 'text', true]); 3641 for (var i = 0; i < inputs.length; i++) { 3642 content += `<div id="${inputs[i][0]}" data-type="text" class="sb-input sb-input-text"><span class="${inputs[i][2] == '' ? '' : 'sb-active sb-filled'}">${sb_(inputs[i][1])}</span><input value="${inputs[i][2]}" autocomplete="false" type="${inputs[i][3]}" ${inputs[i][4] ? 'required' : ''}></div>`; 3643 }
3644 if (settings['phone'] == 'true') { 3645 let phone = activeUser().getExtra('phone'); 3646 let phone_codes = admin ? [] : CHAT_SETTINGS['phone-codes']; 3647 let select = ''; 3648 let single = phone_codes.length === 1; 3649 if (single) { 3650 select = `<input disabled value="${phone_codes[0]}">`; 3651 } else { 3652 for (var i = 0; i < phone_codes.length; i++) { 3653 select += `<option value="+${phone_codes[i]}">+${phone_codes[i]}</option>`; 3654 } 3655 select = `<select><option value="">+00</option>${select}</select>`; 3656 } 3657 content += `<div id="phone" data-type="select-input" class="sb-input sb-input-select-input"><span class="${phone == '' ? '' : 'sb-active sb-filled'}">${sb_('Phone')}</span><div>${select}</div><input value="${admin ? '' : phone}" pattern="[0-9]+" autocomplete="false" type="tel" ${settings['phone-required'] != 'false' ? 'required' : ''}></div>`; 3658 } 3659 content += `<div id="email" data-type="email" class="sb-input sb-input-btn"><span class="${email == '' ? '' : 'sb-active sb-filled'}">${sb_(SBF.null(settings.placeholder) ? 'Email' : settings['placeholder'])}</span><input value="${email}" autocomplete="off" type="email" required><div class="sb-submit sb-icon-arrow-right"></div></div>`; 3660 break; 3661 case 'image': 3662 content = `<div class="sb-image"><img src="${settings['url']}"></div>`; 3663 break; 3664 case 'video': 3665 content = `<iframe${'height' in settings ? ` height="${settings['height']}"` : ''} src="${settings['type'] == 'youtube' ? '//www.youtube.com/embed/' : '//player.vimeo.com/video/'}${settings['id']}" allowfullscreen></iframe>`; 3666 break; 3667 case 'select': 3668 options = settings['options'].split(','); 3669 for (var i = 0; i < options.length; i++) { 3670 code += `<li data-value="${SBF.stringToSlug(options[i])}">${sb_(options[i])}</li>`; 3671 } 3672 content = content.replace('[options]', code); 3673 break; 3674 case 'chips': 3675 case 'buttons': 3676 options = settings['options'].split(','); 3677 for (var i = 0; i < options.length; i++) { 3678 code += `<div class="sb-btn sb-submit">${sb_(options[i])}</div>`; 3679 } 3680 content = content.replace('[options]', code); 3681 break; 3682 case 'button': 3683 content = `<a href="${settings['link'].replace(/<i>/g, '_').replace(/<\/i>/g, '_')}"${'target' in settings ? ' target="_blank"' : ''} class="sb-rich-btn sb-btn${settings['style'] == 'link' ? '-text' : ''}">${sb_(settings['name'])}</a>`; 3684 break; 3685 case 'list': 3686 options = settings['values'].split(','); 3687 let list = name == 'list'; 3688 let list_double = list && options.length && options[0].indexOf(':') > 0; 3689 if (list && !list_double) { 3690 content = content.replace('sb-text-list', 'sb-text-list sb-text-list-single'); 3691 } 3692 for (var i = 0; i < options.length; i++) { 3693 code += list_double ? `<div><div>${sb_(options[i].split(':')[0])}</div><div>${sb_(options[i].split(':')[1])}</div></div>` : `<div>${$.trim(sb_(options[i]))}</div>`; 3694 } 3695 content = content.replace('[values]', code); 3696 break; 3697 case 'list-image': 3698 options = settings['values'].split(','); 3699 for (var i = 0; i < options.length; i++) { 3700 let item = options[i].replace('://', '///').split(':'); 3701 code += `<div><div class="sb-thumb" style="background-image:url('${item[0].replace('///', '://')}')"></div><div class="sb-list-title">${item[1]}</div><div>${item[2]}</div></div>`; 3702 } 3703 content = content.replace('[values]', code); 3704 break; 3705 case 'table': 3706 options = settings['header'].split(','); 3707 code += '<tr>'; 3708 for (var i = 0; i < options.length; i++) { 3709 code += `<th>${options[i]}</th>`; 3710 } 3711 code += '</tr>'; 3712 content = content.replace('[header]', code); 3713 code = ''; 3714 options = settings['values'].split(','); 3715 for (var i = 0; i < options.length; i++) {
3716 let tds = options[i].split(':'); 3717 code += '<tr>'; 3718 for (var i = 0; i < tds.length; i++) { 3719 code += `<td>${tds[i]}</td>`; 3720 } 3721 code += '</tr>'; 3722 } 3723 content = content.replace('[values]', code); 3724 break; 3725 case 'inputs': 3726 options = settings['values'].split(','); 3727 for (var i = 0; i < options.length; i++) { 3728 if (disabled && options[i] == '') continue; 3729 code += `<div id="${SBF.stringToSlug(options[i])}" data-type="text" class="sb-input sb-input-text"><span>${sb_(options[i])}</span><input autocomplete="false" type="text" required></div>`; 3730 } 3731 code += '<div class="sb-btn sb-submit">' + sb_('button' in settings ? settings['button'] : 'Send now') + '</div>'; 3732 content = content.replace('[values]', code); 3733 break; 3734 case 'card': 3735 code = `${'image' in settings ? `<div class="sb-card-img" style="background-image:url('${settings['image']}')"></div>` : ''}<div class="sb-card-header">${settings['header']}</div>${'extra' in settings ? `<div class="sb-card-extra">${settings['extra']}</div>` : ''}${'description' in settings ? `<div class="sb-card-description">${settings['description']}</div>` : ''}${'link' in settings ? `<a class="sb-card-btn" href="${settings['link']}"${'target' in settings ? ' target="_blank"' : ''}>${sb_(settings['link-text'])}</a>` : ''}`; 3736 content = content.replace('[settings]', code); 3737 break; 3738 case 'share': 3739 let channels = 'channels' in settings ? settings['channels'].replace(/ /g, '').split(',') : ['fb', 'tw', 'li', 'wa', 'pi']; 3740 let link = ''; 3741 for (var i = 0; i < channels.length; i++) { 3742 switch (channels[i]) { 3743 case 'fb': 3744 link = 'www.facebook.com/sharer.php?u='; 3745 break; 3746 case 'tw': 3747 link = 'twitter.com/intent/tweet?url='; 3748 break; 3749 case 'li': 3750 link = 'www.linkedin.com/sharing/share-offsite/?url='; 3751 break; 3752 case 'wa': 3753 link = 'web.whatsapp.com/send?text='; 3754 break; 3755 case 'pi': 3756 link = 'www.pinterest.com/pin/create/button/?url='; 3757 break; 3758 } 3759 code += `<div class="sb-${channels[i]} sb-icon-social-${channels[i]}" data-link="https://${link}${settings['link']}"></div>`; 3760 } 3761 content = content.replace('[settings]', code); 3762 break; 3763 case 'slider': 3764 let count = 0; 3765 for (var i = 1; i < 16; i++) { 3766 if (('header-' + i) in settings) { 3767 code += `<div>${('image-' + i) in settings ? `<div class="sb-card-img" style="background-image:url('${settings['image-' + i]}')"></div>` : ''}<div class="sb-card-header">${settings['header-' + i]}</div>${('extra-' + i) in settings ? `<div class="sb-card-extra">${settings['extra-' + i]}</div>` : ''}${('description-' + i) in settings ? `<div class="sb-card-description">${settings['description-' + i]}</div>` : ''}${('link-' + i) in settings ? `<a class="sb-card-btn" href="${settings['link-' + i]}"${'target' in settings ? ' target="_blank"' : ''}>${sb_(settings['link-text-' + i])}</a>` : ''}</div>`; 3768 count++; 3769 } else { 3770 break; 3771 } 3772 } 3773 content = content.replace('[items]', code).replace(/\[class\]/g, count == 1 ? ' sb-hide' : ''); 3774 break; 3775 case 'slider-images': 3776 if ('images' in settings) { 3777 let images = settings['images'].split(','); 3778 for (var i = 0; i < images.length; i++) { 3779 code += `<div class="sb-card-img" data-value="${images[i]}" style="background-image:url('${images[i]}')"></div>`; 3780 } 3781 content = content.replace(/\[class\]/g, images.length == 1 ? ' sb-hide' : ''); 3782 } 3783 content = content.replace('[items]', code); 3784 break; 3785 case 'woocommerce-button': 3786 settings['settings'] = `checkout:${settings['checkout']},coupon:${settings['coupon']}`; 3787 content = `<a href="#" data-ids="${settings['ids']}" class="sb-rich-btn sb-btn">${settings['name']}</a>`; 3788 break; 3789 } 3790 } 3791 return `<div id="${id}" data-type="${name}"${disabled ? 'disabled="true"' : ''}${'settings' in settings ? ` data-settings="${settings['settings']}"` : ''}class="sb-rich-message sb-rich-${name} ${css}">` + ('title' in settings ? `<div class="sb-top">
3791${render.render(sb_(settings['title']))}</div>` : '') + ('message' in settings ? `<div class="sb-text">${render.render(sb_(settings['message']))}</div>` : '') + `<div class="sb-content">${content}</div><div data-success="${'success' in settings ? settings['success'].replace(/"/g, '') : ''}" class="sb-info"></div></div>`; 3792 }, 3793 3794 // Function of built-in rich messages 3795 submit: function (area, type, element) { 3796 if (!admin && !loading(element) && !this.is_busy) { 3797 let error = ''; 3798 let shortcode = ''; 3799 let parameters = {}; 3800 let success = $(area).find('[data-success]').length ? $(area).find('[data-success]').attr('data-success') : ''; 3801 let rich_message_id = $(area).closest('.sb-rich-message').attr('id'); 3802 let message_id = $(area).closest('[data-id]').attr('data-id'); 3803 let message = ''; 3804 let payload = { 'rich-messages': {} }; 3805 let user_settings = activeUser() == false ? { 'profile_image': ['', ''], 'first_name': ['', ''], 'last_name': ['', ''], 'email': ['', ''], 'password': ['', ''], 'user_type': ['', ''] } : { 'profile_image': [activeUser().image, ''], 'first_name': [activeUser().get('first_name'), ''], 'last_name': [activeUser().get('last_name'), ''], 'email': [activeUser().get('email'), ''], 'password': ['', ''], 'user_type': ['', ''] }; 3806 let settings = {}; 3807 let input = $(element); 3808 let dialogflow_response = ''; 3809 let dialogflow_parameters = false; 3810 let active_conversation = SBChat.conversation !== false; 3811 let settings_extra = {}; 3812 let payload_settings = {}; 3813 3814 if (SBF.null(message_id)) { 3815 message_id = -1; 3816 } else { 3817 let item = SBChat.conversation.getMessage(message_id); 3818 message = item.message; 3819 payload = item.payload(); 3820 if (!('rich-messages' in payload)) { 3821 payload['rich-messages'] = {}; 3822 } 3823 } 3824 if (!$(element).hasClass('sb-btn') && !$(element).hasClass('sb-select') && !$(element).hasClass('sb-submit')) { 3825 input = $(element).closest('.sb-btn,.sb-select'); 3826 } 3827 $(area).find('.sb-info').html('').sbActive(false); 3828 3829 switch (type) { 3830 case 'email': 3831 let is_last_name = 'last_name' in settings; 3832 settings = SBForm.getAll(area); 3833 if ('first_name' in settings) { 3834 user_settings['user_type'] = ['user', '']; 3835 if (!is_last_name) user_settings['last_name'] = ['', '']; 3836 } 3837 if ('phone' in settings) { 3838 settings_extra = { 'phone': [settings['phone'][0], 'Phone'] }; 3839 } 3840 $.extend(user_settings, settings); 3841 error = 'Please fill in all required fields and make sure the email is valid.'; 3842 if (success) success = sb_(success).replace('{user_email}', user_settings['email'][0]).replace('{user_name_}', user_settings['first_name'][0] + (is_last_name ? (' ' + user_settings['last_name'][0]) : '')); 3843 payload['rich-messages'][rich_message_id] = { type: type, result: settings }; 3844 payload['event'] = 'update-user'; 3845 parameters = { function: 'update-user-and-message', settings: user_settings, settings_extra: settings_extra, payload: payload }; 3846 dialogflow_parameters = { settings: user_settings, settings_extra: settings_extra }; 3847 break; 3848 case 'registration': 3849 settings_extra = SBForm.getAll(area.find('.sb-form-extra')); 3850 $.extend(user_settings, SBForm.getAll(area.find('.sb-form-main'))); 3851 payload_settings = $.extend({}, user_settings); 3852 if (success) { 3853 success = sb_(success); 3854 }
3855 if (CHAT_SETTINGS['registration-details']) { 3856 success += '[list values="'; 3857 for (var key in user_settings) { 3858 let value = user_settings[key][0].replace(/:|,/g, ''); 3859 if (value) { 3860 if (key == 'profile_image') { 3861 value = value.substr(value.lastIndexOf('/') + 1); 3862 } 3863 if (key == 'password' || key == 'password-check') { 3864 value = '********'; 3865 payload_settings[key] = '********'; 3866 } 3867 success += user_settings[key][1] == '' ? '' : `${sb_(user_settings[key][1].replace(/:|,/g, ''))}:${value},`; 3868 } 3869 } 3870 for (var key in settings_extra) { 3871 if (settings_extra[key][0]) { 3872 success += `${sb_(settings_extra[key][1].replace(/:|,/g, ''))}:${settings_extra[key][0].replace(/:|,/g, '')},`; 3873 } 3874 } 3875 success = success.slice(0, -1) + '"]'; 3876 } 3877 user_settings['user_type'] = ['user', '']; 3878 payload['rich-messages'][rich_message_id] = { type: type, result: { user: payload_settings, extra: settings_extra } }; 3879 payload['event'] = 'update-user'; 3880 parameters = { function: activeUser() ? 'update-user-and-message' : 'add-user-and-login', settings: user_settings, settings_extra: settings_extra, payload: payload }; 3881 error = SBForm.getRegistrationErrorMessage(area); 3882 dialogflow_parameters = { settings: user_settings, settings_extra: settings_extra }; 3883 break; 3884 case 'rating': 3885 let rating = $(element).attr('data-rating'); 3886 let agent = SBChat.conversation.getLastUserMessage(false, true); 3887 success = sb_(rating == 'positive' ? success : $(element).closest('.sb-rating').attr('data-success-negative')); 3888 settings = { conversation_id: SBChat.conversation.id, agent_id: agent ? agent.get('user_id') : bot_id, user_id: activeUser().id, message: area.find('textarea').val(), rating: (rating == 'positive' ? 1 : -1) }; 3889 payload['rich-messages'][rich_message_id] = { type: type, result: settings }; 3890 parameters = { function: 'set-rating', payload: payload, settings: settings }; 3891 dialogflow_response = rating; 3892 break; 3893 case 'chips': 3894 case 'select': 3895 case 'buttons': 3896 settings = SBF.escape($(element).html()); 3897 if (success) success = sb_(success) + ` *${settings}*`; 3898 payload['rich-messages'][rich_message_id] = { type: type, result: settings }; 3899 parameters = { function: 'update-message', payload: payload }; 3900 dialogflow_response = settings; 3901 if (type == 'chips') { 3902 SBChat.sendMessage(activeUser().id, settings, [], false, { id: rich_message_id, event: 'chips-click', result: settings }, rich_message_id == 'sb-human-takeover' && input.index() == 0 ? 2 : false); 3903 if (rich_message_id == 'sb-human-takeover' && $(element).index() == 0) { 3904 SBApps.dialogflow.humanTakeover(); 3905 } 3906 $(element).closest('.sb-content').remove(); 3907 } 3908 break; 3909 case 'inputs': 3910 settings = SBForm.getAll(area); 3911 error = 'All fields are required.'; 3912 if (success) { 3913 success = sb_(success) + ' [list values="'; 3914 for (var key in settings) { 3915 success += `${sb_(settings[key][1].replace(/:|,/g, ''))}:${settings[key][0].replace(/:|,/g, '')},`; 3916 } 3917 success = success.slice(0, -1) + '"]'; 3918 } 3919 payload['rich-messages'][rich_message_id] = { type: type, result: settings }; 3920 parameters = { function: 'update-message', payload: payload }; 3921 dialogflow_parameters = { settings: settings }; 3922 break; 3923 } 3924 3925 shortcode = message.substr(message.indexOf('[' + type)) 3926 shortcode = shortcode.substr(0, shortcode.indexOf(']') + 1); 3927 3928 if (error && SBForm.errors(area)) {
3929 SBForm.showErrorMessage(area, error); 3930 input.sbLoading(false); 3931 if (SBChat.dashboard || (active_conversation && SBChat.conversation.getLastMessage().id == message_id)) { 3932 SBChat.scrollBottom(); 3933 } 3934 return false; 3935 } 3936 if (!success && type != 'registration') { 3937 let shortcode_settings = this.shortcode(shortcode); 3938 let title = 'title' in shortcode_settings[1] ? `title="${shortcode_settings[1]['title']}"` : ''; 3939 let message = 'message' in shortcode_settings[1] ? `message="${shortcode_settings[1]['message']}"` : ''; 3940 let value = ''; 3941 if (['inputs', 'email'].includes(type)) { 3942 for (var key in settings) { 3943 value += settings[key][0] + ','; 3944 } 3945 value = `values="${value.slice(0, -1)}"` 3946 } else { 3947 value = `options="${settings}"`; 3948 } 3949 success = `[${type == 'email' ? 'inputs' : type} ${title} ${message} ${value} disabled="true"]`; 3950 } 3951 if (message_id != -1) { 3952 $.extend(parameters, { 3953 message_id: message_id, 3954 message: message ? message.replace(shortcode, success) : success, 3955 payload: payload 3956 }); 3957 } 3958 SBF.ajax(parameters, (response) => { 3959 if (response != false && !SBF.errorValidation(response)) { 3960 switch (type) { 3961 case 'email': 3962 for (var key in user_settings) { 3963 activeUser().set(key, user_settings[key][0]); 3964 } 3965 for (var key in settings_extra) { 3966 activeUser().setExtra(key, settings_extra[key][0]); 3967 } 3968 SBF.loginCookie(response[1]); 3969 if (rich_message_id == 'sb-subscribe-form') { 3970 SBF.ajax({ 3971 function: 'subscribe-email', 3972 email: activeUser().get('email') 3973 }); 3974 } 3975 SBChat.automations.run_all(); 3976 SBF.event('SBNewEmailAddress', { id: rich_message_id, name: activeUser().name, email: activeUser().get('email') }); 3977 break; 3978 case 'registration': 3979 SBF.loginCookie(response[1]); 3980 user_settings['id'] = [response[0].id]; 3981 if (!activeUser()) { 3982 activeUser(new SBUser(response[0])); 3983 for (var key in settings_extra) { 3984 activeUser().setExtra(key, settings_extra[key][0]); 3985 } 3986 SBPusher.start(); 3987 SBChat.initChat(); 3988 if ((!CHAT_SETTINGS['init-dashboard'] || !main.find('.sb-departments-list').length) && success) SBChat.sendMessage(bot_id, success, [], false, false, 3); 3989 } else { 3990 for (var key in user_settings) { 3991 activeUser().set(key, user_settings[key][0]); 3992 } 3993 for (var key in settings_extra) { 3994 activeUser().setExtra(key, settings_extra[key][0]); 3995 } 3996 SBChat.automations.run_all(); 3997 } 3998 if (SBChat.dashboard) { 3999 main.removeClass('sb-init-form-active'); 4000 $(area).remove(); 4001 if (!SBChat.isInitDashboard()) SBChat.hideDashboard(); 4002 } 4003 if (CHAT_SETTINGS['wp-registration'] && 'email' in user_settings && 'password' in user_settings) { 4004 SBApps.wordpress.ajax('wp-registration', { user_id: response[0].id, first_name: response[0].first_name, last_name: response[0].last_name, password: user_settings.password[0], email: user_settings.email[0] }); 4005 } else if (CHAT_SETTINGS['wp-users-system'] == 'wp') { 4006 SBApps.wordpress.ajax('wp-login', { user: user_settings.email[0], password: user_settings.password[0] }); 4007 } 4008 delete this.cache['registration']; 4009 setTimeout(() => { 4010 SBF.event('SBRegistrationForm', { id: rich_message_id, conversation_id: SBChat.conversation ? SBChat.conversation.id : false, user: user_settings, extra: payload['rich-messages'][rich_message_id]['result']['extra'] }); 4011 }, 5000); 4012 break; 4013 } 4014 if (message_id == -1) { 4015 $(element).closest('.sb-rich-message').html(success); 4016 } else if ((!('type' in payload) || payload['type'] != 'close-message') && !dialogflow_human_takeover) { 4017 SBChat.setConversationStatus(2); 4018 } 4019 if (!['login', 'chips', 'rating'].includes(type) && (CHAT_SETTINGS['dialogflow-send-user-details'] || !['email', 'registration'].includes(type))) { 4020 SBApps.dialogflow.message(`${rich_message_id}
4020${dialogflow_response ? ('|' + dialogflow_response) : ''}`, [], false, dialogflow_parameters); 4021 } 4022 if (CHAT_SETTINGS['slack-active'] && (!dialogflow_human_takeover || SBApps.dialogflow.humanTakeoverActive())) { 4023 SBChat.slackMessage(activeUser().id, activeUser().name, activeUser().image, success); 4024 } 4025 if (SBPusher.active) SBChat.update(); 4026 if (type != 'registration' && type != 'email') SBF.event('SBRichMessageSubmit', { result: response, data: payload['rich-messages'][rich_message_id], id: rich_message_id }); 4027 } else { 4028 SBForm.showErrorMessage(area, SBForm.getRegistrationErrorMessage(response, 'response')); 4029 if (SBChat.dashboard) { 4030 SBChat.scrollBottom(); 4031 } 4032 input.sbLoading(false); 4033 } 4034 }); 4035 } 4036 }, 4037 4038 // Return the shortcode name and the shortcode settings 4039 shortcode: function (shortcode) { 4040 if (shortcode.indexOf(' ') < 0) { 4041 return [shortcode.slice(1, -1), {}]; 4042 } 4043 let result = {}; 4044 let shortcode_name = shortcode.substr(1, shortcode.indexOf(' ') - 1); 4045 shortcode = shortcode.slice(1, -1).substr(shortcode_name.length + 1); 4046 let settings = shortcode.split('" '); 4047 for (var i = 0; i < settings.length; i++) { 4048 if (settings[i].includes('=')) { 4049 let item = [settings[i].substr(0, settings[i].indexOf('=')), settings[i].substr(settings[i].indexOf('=') + 2)]; 4050 result[$.trim(item[0])] = item[1].replace(/"/g, ''); 4051 } 4052 } 4053 return [shortcode_name, result]; 4054 }, 4055 4056 // Init the rich message inputs 4057 initInputs: function (code) { 4058 code = $($.parseHTML('<div>' + code + '</div>')); 4059 code.find('.sb-input input').each(function () { 4060 if ($(this).val()) { 4061 $(this).siblings().addClass('sb-active sb-filled'); 4062 } 4063 }); 4064 return code.html(); 4065 }, 4066 4067 // Timetable shortcode 4068 timetable: function (code) { 4069 let table = $($.parseHTML(`<div>${code}</div>`)); 4070 let offset = table.find('[data-offset]').attr('data-offset'); 4071 offset = SBF.null(offset) ? 0 : parseInt(offset); 4072 table.find('[data-time]').each(function () { 4073 let times = $(this).attr('data-time').split('|'); 4074 code = '' 4075 for (var i = 0; i < times.length; i++) { 4076 if (times[i] == 'closed') { 4077 code += sb_('Closed'); 4078 break; 4079 } else if (times[i]) { 4080 let hm = times[i].split(':'); 4081 let time = SBF.convertUTCDateToLocalDate(`01/01/2000 ${hm[0]}:${hm[1]}`, offset); 4082 code += time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + (i == 0 || i == 2 ? `<span>${sb_('to')}</span>` : i == 1 && times[i + 1] ? `<br />` : ''); 4083 } 4084 } 4085 table.find(' > div > span').html(`<i class="sb-icon-clock"></i> ${sb_('Time zone')} ${Intl.DateTimeFormat().resolvedOptions().timeZone}`); 4086 $(this).html(code); 4087 }); 4088 return table.html(); 4089 }, 4090 4091 // Slider 4092 sliderChange: function (id, direction = 'left') { 4093 let slider = chat.find(`#${id}`); 4094 if (slider.length && !slider.hasClass('sb-moving')) { 4095 let items = slider.find('.sb-slider > div > div'); 4096 let item = items.eq(0); 4097 let width = Math.ceil(item.closest('.sb-slider').width()); 4098 let negative = (direction == 'right' ? -1 : 1); 4099 let margin = parseFloat(parseFloat(parseFloat(item.css('margin-left')) + (width * negative)).toFixed(2)) + (-0.5 * negative); 4100 let check = width * (items.length - 1) * -1; 4101 if (margin < 1 && margin >= check) { 4102 item.css('margin-left', margin + 'px'); 4103 slider.addClass('sb-moving'); 4104 setTimeout(() => { slider.removeClass('sb-moving'); }, 1200); 4105 } 4106 slider.find('.sb-icon-arrow-right').sbActive(!(check > (margin - 15) && check < (margin + 15))); 4107 slider.find('.sb-icon-arrow-left').sbActive(margin < -10); 4108 } 4109 } 4110 } 4111 4112 /* 4113 * ---------------------------------------------------------- 4114 * FORM METHODS 4115 * ---------------------------------------------------------- 4116 */ 4117 4118 var SBForm = { 4119 4120 // Get all settings 4121 getAll: function (area) { 4122 let settings = {}; 4123 $(area).find('.sb-input[id]').each((i, element) => { 4124 settings[$(element).attr('id')] = this.get(element); 4125 }); 4126 return settings; 4127 }, 4128 4129 // Get a single setting 4130 get: function (input) { 4131 input = $(input); 4132 let type = input.data('type'); 4133 let name = sb_(SBF.escape(input.find(' > span').html())); 4134 switch (type) { 4135 case 'image': 4136 let url = input.find('.image').attr('data-value'); 4137 return [SBF.null(url) ? '' : url, name]; 4138 case 'select': 4139 return [SBF.escape(input.find('select').val()), name]; 4140 case 'select-input': 4141 let select = input.find('select,input[disabled]'); 4142 let prepend = select.val(); 4143 return [SBF.escape((select.is('select') || select.is('input') ? prepend : input.find('> div').html()) + input.find('input[type="tel"]').val()), name]; 4144 default: 4145 let target = input.find('input'); 4146 return [SBF.escape(target.length ? target.val() : input.find('[data-value]').attr('data-value')), name]; 4147 } 4148 }, 4149 4150 // Set a single setting 4151 set: function (item, value) { 4152 item = $(item); 4153 if (item.length) { 4154 let type = item.data('type'); 4155 switch (type) { 4156 case 'image': 4157 if (value == '') { 4158 item.find('.image').removeAttr('data-value').removeAttr('style'); 4159 } else { 4160 item.find('.image').attr('data-value', value).css('background-image', `url("${value}")`); 4161 } 4162 break; 4163 case 'select': 4164 item.find('select').val(value); 4165 break; 4166 default: 4167 item.find('input,textarea').val(value); 4168 break; 4169 } 4170 return true; 4171 } 4172 return false;
4173 }, 4174 4175 // Clear all the input values 4176 clear: function (area) { 4177 $(area).find('.sb-input,.sb-input-setting').each((i, element) => { 4178 this.set(element, ''); 4179 $(element).find('input, select, textarea').removeClass('sb-error'); 4180 }); 4181 this.set($(area).find('#user_type'), 'user'); 4182 }, 4183 4184 // Check for errors on user input 4185 errors: function (area) { 4186 let errors = false; 4187 let items = $(area).find('input, select, textarea').removeClass('sb-error'); 4188 items.each(function (i) { 4189 let value = $.trim($(this).val()); 4190 let type = $(this).attr('type'); 4191 let required = $(this).prop('required'); 4192 if ((required && value == '') || ((required || value) && ((type == 'password' && (value.length < 8 || (items.length > (i + 1) && items.eq(i + 1).attr('type') == 'password' && items.eq(i + 1).val() != value))) || (type == 'email' && (value.indexOf('@') < 0 || value.indexOf('.') < 0 || /;|:|\/|\\|,|#|"|!|=|\+|\*|{|}|[|]|£|\$|â¬|~|'|>|<|\^|&/.test(value))) || (type == 'tel' && value && ($(this).parent().find('select').val() == '' || isNaN(value) || value.includes('+') || value.length < 5))))) { 4193 errors = true; 4194 $(this).addClass('sb-error'); 4195 } 4196 }); 4197 items = $(area).find('[data-required]').removeClass('sb-error'); 4198 items.each(function () { 4199 if (SBF.null($(this).attr('data-value'))) { 4200 $(this).addClass('sb-error'); 4201 errors = true; 4202 } 4203 }); 4204 return errors; 4205 }, 4206 4207 // Display a error message 4208 showErrorMessage: function (area, message) { 4209 $(area).find('.sb-info').html(sb_(message)).sbActive(true); 4210 clearTimeout(timeout); 4211 timeout = setTimeout(function () { 4212 $(area).find('.sb-info').sbActive(false); 4213 }, 7000); 4214 }, 4215 4216 // Display a success message 4217 showSuccessMessage: function (area, message) { 4218 $(area).find('.sb-info').remove(); 4219 $(area).addClass('sb-success').find('.sb-content').html(`<div class="sb-text">${message}</div>`); 4220 }, 4221 4222 // Return the registration error message 4223 getRegistrationErrorMessage(area_or_response, type = 'validation') { 4224 if (type == 'response') return SBF.errorValidation(area_or_response, 'duplicate-email') ? 'This email is already in use. Please use another email.' : (SBF.errorValidation(area_or_response, 'duplicate-phone') ? 'This phone number is already in use. Please use another number.' : 'Error. Please check your information and try again.') 4225 let name = $(area_or_response).find('#last_name').length ? 'First name, last name' : 'Name'; 4226 let phone = $(area_or_response).find('#phone [required]').length ? ', phone number' : ''; 4227 let email = $(area_or_response).find('#email').length ? ', email' : ''; 4228 let password = $(area_or_response).find('#password').length ? ', and a password (8 character minimum)' : ''; 4229 return `${name}${email}${phone}${password} ${(email + phone + password) == '' ? 'is' : 'are'} required.`; 4230 } 4231 } 4232 window.SBForm = SBForm; 4233 4234 /* 4235 * ---------------------------------------------------------- 4236 * # APPS 4237 * ---------------------------------------------------------- 4238 */ 4239 4240 var SBApps = { 4241 4242 // Get the login data 4243 login: function () { 4244 if (this.is('wp') && typeof SB_WP_ACTIVE_USER != ND && CHAT_SETTINGS['wp-users-system'] == 'wp') { 4245 return [[SB_WP_ACTIVE_USER, typeof SB_WP_AVATAR != ND ? SB_WP_AVATAR : ''], 'wp']; 4246 } 4247 if (typeof SB_PERFEX_ACTIVE_USER != ND) { 4248 return [[SB_PERFEX_ACTIVE_USER, SB_PERFEX_CONTACT_ID], 'perfex']; 4249 } 4250 if (typeof SB_WHMCS_ACTIVE_USER != ND) { 4251 return [SB_WHMCS_ACTIVE_USER, 'whmcs']; 4252 }
4253 if (typeof SB_AECOMMERCE_ACTIVE_USER != ND) { 4254 return [SB_AECOMMERCE_ACTIVE_USER, 'aecommerce']; 4255 } 4256 if (typeof SB_DEFAULT_USER != ND) { 4257 return [SB_DEFAULT_USER, 'default']; 4258 } 4259 return false; 4260 }, 4261 4262 // Check if an app is installed and active 4263 is: function (name) { 4264 if (admin) return SBAdmin.apps.is(name); 4265 if (name == 'wordpress' || name == 'wp') return CHAT_SETTINGS['wp']; 4266 return name in CHAT_SETTINGS ? CHAT_SETTINGS[name] : false; 4267 }, 4268 4269 wordpress: { 4270 4271 // Ajax 4272 ajax: function (action, data, onSuccess = false) { 4273 if (typeof SB_WP_AJAX_URL == ND) return; 4274 $.ajax({ 4275 method: 'POST', 4276 url: SB_WP_AJAX_URL, 4277 data: $.extend({ action: 'sb_wp_ajax', type: action }, data) 4278 }).done((response) => { 4279 if (onSuccess) { 4280 onSuccess(response); 4281 } 4282 }); 4283 } 4284 }, 4285 4286 woocommerce: { 4287 4288 // Update the cart 4289 updateCart: function (action, product_id, onSuccess = false) { 4290 SBApps.wordpress.ajax(action, { product_id: product_id }, onSuccess); 4291 }, 4292 4293 // Waiting list 4294 waitingList: function (action = 'request', product_id = false) { 4295 if (typeof SB_WP_WAITING_LIST !== ND && (action != 'request' || SB_WP_WAITING_LIST && SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID, 24)) && activeUser()) { 4296 SBF.ajax({ 4297 function: 'woocommerce-waiting-list', 4298 product_id: product_id === false ? SB_WP_PAGE_ID : product_id, 4299 conversation_id: SBChat.conversation.id, 4300 action: action, 4301 token: this.token 4302 }, (response) => { 4303 if (response) { 4304 SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID); 4305 if (action == 'request' && (!SBChat.chat_open || SBChat.dashboard)) { 4306 SBChat.updateConversations(); 4307 } 4308 } 4309 }); 4310 } 4311 } 4312 }, 4313 4314 dialogflow: { 4315 token: storage('dialogflow-token'), 4316 typing_enabled: false, 4317 project_id: false, 4318 4319 // Send a message to the bot and get the reply 4320 message: function (message = '', attachments = [], delay = false, parameters = false) { 4321 if (this.active()) { 4322 let human_takeover_active = SBApps.dialogflow.humanTakeoverActive(); 4323 if (!human_takeover_active || this.typing_enabled) { 4324 timeout = setTimeout(() => { 4325 SBChat.typing(-1, 'start'); 4326 setTimeout(() => { 4327 SBChat.typing(-1, 'stop'); 4328 }, 10000); 4329 }, 1000); 4330 } 4331 setTimeout(() => { 4332 SBF.ajax({ 4333 function: 'dialogflow-message', 4334 conversation_id: SBChat.conversation ? SBChat.conversation.id : false, 4335 message: message, 4336 attachments: attachments, 4337 parameters: parameters, 4338 token: this.token, 4339 dialogflow_language: storage('dialogflow-language') ? storage('dialogflow-language') : SB_LANG, 4340 project_id: this.project_id 4341 }, (response) => { 4342 if (response === false) return; 4343 if ('human_takeover' in response) { 4344 SBChat.offlineMessage(); 4345 SBChat.followUp(); 4346 return this.active(false); 4347 } 4348 if ('language_detection' in response && !storage('dialogflow-language')) { 4349 storage('dialogflow-language', [response.language_detection]); 4350 } 4351 if (!SBF.errorValidation(response)) { 4352 let query_result = 'queryResult' in response['response'] ? response['response']['queryResult'] : false;
4353 let is_unknow = query_result && (query_result['action'] == 'input.unknown' || ('match' in query_result && query_result['match']['matchType'] == 'NO_MATCH')); 4354 let messages = 'messages' in response && Array.isArray(response['messages']) ? response['messages'] : []; 4355 SBChat.typing(-1, 'stop'); 4356 clearTimeout(timeout); 4357 if (this.token != response['token']) { 4358 this.token = response['token']; 4359 storage('dialogflow-token', response['token']); 4360 } 4361 if (is_unknow) { 4362 if (human_takeover_active) this.typing_enabled = false; 4363 } else this.typing_enabled = true; 4364 if (query_result) { 4365 4366 // Actions 4367 if ('action' in query_result) { 4368 let action = query_result['action']; 4369 if (action == 'end') { 4370 this.active(false); 4371 } 4372 SBF.event('SBBotAction', action); 4373 } 4374 4375 // Payload 4376 for (var i = 0; i < messages.length; i++) { 4377 if ('payload' in messages[i]) { 4378 let payloads = ['human-takeover', 'redirect', 'woocommerce-update-cart', 'woocommerce-checkout', 'open-article', 'transcript', 'department', 'agent', 'send-email', 'disable-bot', 'rich-message']; 4379 let payload = messages[i]['payload']; 4380 if (SBF.null(payload)) payload = []; 4381 if (payloads[0] in payload && payload[payloads[0]] === true) { 4382 this.humanTakeover(); 4383 } 4384 if (payloads[1] in payload) { 4385 setTimeout(function () { 4386 'new-window' in payload && payload['new-window'] ? window.open(payload[payloads[1]]) : document.location = payload[payloads[1]]; 4387 }, 500); 4388 } 4389 if (payloads[2] in payload) { 4390 let payload_value = payload[payloads[2]]; 4391 SBApps.woocommerce.updateCart(payload_value[0], payload_value[1], (response) => { 4392 if (response) { 4393 SBF.event('SBWoocommerceCartUpdated', { 'action': payload_value[0], 'product': payload_value[1] }); 4394 } 4395 }); 4396 } 4397 if (payloads[3] in payload && payload[payloads[3]] === true) { 4398 SBApps.wordpress.ajax('url', { url_name: 'checkout' }, (response) => { 4399 setTimeout(() => document.location = response, 500); 4400 }); 4401 } 4402 if (payloads[4] in payload) { 4403 SBChat.showArticles(payload[payloads[4]]); 4404 } 4405 if (payloads[5] in payload && payload[payloads[5]]) { 4406 SBF.ajax({ 4407 function: 'transcript', 4408 conversation_id: SBChat.conversation.id, 4409 type: 'txt' 4410 }, (response) => { 4411 if (payload[payloads[5]] == 'email' && activeUser().get('email')) { 4412 SBChat.sendEmail('message' in payload ? payload.message : '', [[response, response]], true); 4413 } else window.open(response); 4414 }); 4415 } 4416 if (payloads[6] in payload) { 4417 SBF.ajax({ 4418 function: 'update-conversation-department', 4419 conversation_id: SBChat.conversation.id, 4420 department: payload[payloads[6]], 4421 message: SBChat.conversation.getLastUserMessage().message 4422 }); 4423 } 4424 if (payloads[7] in payload) { 4425 SBF.ajax({ 4426 function: 'update-conversation-agent', 4427 conversation_id: SBChat.conversation.id, 4428 agent_id: payload[payloads[7]], 4429 message: SBChat.conversation.getLastUserMessage().message 4430 }); 4431 } 4432 if (payloads[8] in payload) { 4433 let email = payload[payloads[8]]; 4434 SBChat.sendEmail(email.message, email.attachments, email.recipient == 'active_user'); 4435 } 4436 if (payloads[9] in payload) { 4437 this.active(false); 4438 SBF.cookie('sb-dialogflow-disabled', true, 300, 'set', true); 4439 } 4440 if (payloads[10] in payload) { 4441 SBChat.sendMessage(bot_id, payload[payloads[10]]); 4442 } 4443 SBF.event('SBBotPayload', payload); 4444 } 4445 4446 // More 4447 if (!storage('dialogflow-language') && 'languageCode' in query_result && (!SB_LANG || query_result.languageCode != SB_LANG[0])) { 4448 storage('dialogflow-language', [query_result.languageCode]); 4449 } 4450 } 4451 4452 // Diagnostic info 4453 if ('diagnosticInfo' in query_result) { 4454 let info = query_result['diagnosticInfo']; 4455 4456 // End conversation 4457 if ('end_conversation' in info && info['end_conversation']) { 4458 this.active(false); 4459 } 4460 } 4461 } 4462 4463 // Slack 4464 if (CHAT_SETTINGS['slack-active'] && messages && (!dialogflow_human_takeover || human_takeover_active)) { 4465 for (var i = 0; i < messages.length; i++) { 4466 SBChat.slackMessage(activeUser().id, CHAT_SETTINGS['bot-name'], CHAT_SETTINGS['bot-image'], messages[i]['message'], messages[i]['attachments']); 4467 } 4468 } 4469 4470 SBF.event('SBBotMessage', { response: response, message: message }); 4471 } 4472 }); 4473 }, delay !== false ? delay : (CHAT_SETTINGS['bot-delay'] == 0 ? 2000 : parseInt(CHAT_SETTINGS['bot-delay']))); 4474 } 4475 }, 4476 4477 // Check if Dialogflow is active or deactivate it 4478 active: function (active = true) { 4479 let name = 'human-takeover-' + SBChat.conversation.id; 4480 if (active === false) { 4481 SBF.storageTime(name); 4482 return false;
4483 } 4484 if (active == 'activate') { 4485 SBF.cookie('sb-dialogflow-disabled', 0, 0, 'delete'); 4486 SBF.storage(name, ''); 4487 } 4488 return CHAT_SETTINGS['dialogflow-active'] && !admin && !SBF.cookie('sb-dialogflow-disabled') && (SBF.storageTime(name, 240) || !SBChat.agent_online) && (!CHAT_SETTINGS['bot-office-hours'] || !CHAT_SETTINGS['office-hours']); 4489 }, 4490 4491 // Trigger the welcome Intent 4492 welcome: function (open = false, sound = false) { 4493 SBF.ajax({ 4494 function: 'dialogflow-message', 4495 message: '', 4496 conversation_id: SBChat.conversation.id, 4497 token: this.token, 4498 event: 'Welcome', 4499 dialogflow_language: storage('dialogflow-language') ? storage('dialogflow-language') : SB_LANG 4500 }, () => { 4501 if (open) SBChat.start(); 4502 if (sound) SBChat.audio.play(); 4503 }); 4504 }, 4505 4506 // Start the human takeover process 4507 humanTakeover: function () { 4508 SBF.ajax({ 4509 function: 'dialogflow-human-takeover', 4510 conversation_id: SBChat.conversation.id 4511 }, () => { 4512 SBChat.offlineMessage(); 4513 SBChat.followUp(); 4514 this.active(false); 4515 if (CHAT_SETTINGS['queue-human-takeover']) { 4516 CHAT_SETTINGS['queue'] = true; 4517 SBChat.queue(SBChat.conversation.id); 4518 } 4519 }); 4520 }, 4521 4522 // Check if human takeover is active 4523 humanTakeoverActive: function () { 4524 return !SBF.storageTime('human-takeover-' + (SBChat.conversation ? SBChat.conversation.id : storage('open-conversation')), 240); 4525 }, 4526 4527 // Translate a string 4528 translate: function (strings, language_code, onSuccess) { 4529 SBF.ajax({ 4530 function: 'google-translate', 4531 strings: strings, 4532 language_code: language_code, 4533 token: this.token 4534 }, (response) => { 4535 this.token = response[1] 4536 onSuccess(response[0]); 4537 }); 4538 } 4539 }, 4540 4541 aecommerce: { 4542 4543 // Update the user cart 4544 cart: function () { 4545 if (SBApps.is('aecommerce') && typeof SB_AECOMMERCE_CART != ND && typeof SB_AECOMMERCE_ACTIVE_USER != ND && storage('aecommerce') != JSON.stringify(SB_AECOMMERCE_CART)) { 4546 SBF.ajax({ 4547 function: 'aecommerce-cart', 4548 cart: SB_AECOMMERCE_CART 4549 }, () => { 4550 storage('aecommerce', JSON.stringify(SB_AECOMMERCE_CART)); 4551 }); 4552 } 4553 } 4554 }, 4555 4556 martfury: { 4557 4558 privateChat: function () { 4559 let store = $('#tab-vendor > h4,.ps-product__vendor > a'); 4560 if (store.length) { 4561 store = store.html().toLowerCase(); 4562 for (var i = 0; i < CHAT_SETTINGS.martfury.length; i++) { 4563 if (store == CHAT_SETTINGS.martfury[i]['martfury-linking-store'].toLowerCase()) { 4564 let agent_id = CHAT_SETTINGS.martfury[i]['martfury-linking-agent']; 4565 let conversations = SBF.activeUser() ? SBF.activeUser().conversations : []; 4566 SBChat.default_agent = agent_id; 4567 for (var j = 0; j < conversations.length; j++) { 4568 if (conversations[j].get('agent_id') == agent_id) { 4569 SBChat.openConversation(conversations[j].id); 4570 return; 4571 } 4572 } 4573 SBChat.clear(); 4574 SBChat.hideDashboard(); 4575 } 4576 } 4577 } 4578 } 4579 } 4580 } 4581 window.SBApps = SBApps; 4582 4583 /* 4584 * ---------------------------------------------------------- 4585 * # INIT 4586 * ---------------------------------------------------------- 4587 */ 4588 4589 $(document).ready(function () { 4590 main = $('.sb-admin, .sb-admin-start'); 4591 if (main.length) { 4592 admin = true; 4593 initialize(); 4594 return; 4595 } 4596 let url_full; 4597 let url; 4598 let init = false;
4599 if (typeof SB_INIT_URL != ND) { 4600 if (SB_INIT_URL.indexOf('.js') < 0) { 4601 SB_INIT_URL += '/js/main.js?v=' + version; 4602 } 4603 url_full = SB_INIT_URL; 4604 } else { 4605 let scripts = document.getElementsByTagName('script'); 4606 let checks = ['init.js', 'main.js', 'min/init.min.js', 'min/main.min.js']; 4607 for (var i = 0; i < scripts.length; i++) { 4608 let source = scripts[i].src; 4609 if (scripts[i].id == 'sbinit') { 4610 url_full = source; 4611 init = init ? init : url_full.includes('init.'); 4612 break; 4613 } else { 4614 for (var j = 0; j < checks.length; j++) { 4615 if (source.includes('/supportboard/js/' + checks[j])) { 4616 url_full = source; 4617 init = init ? init : url_full.includes('init.'); 4618 break; 4619 } 4620 } 4621 } 4622 } 4623 } 4624 let parameters = SBF.getURL(false, url_full); 4625 if ('url' in parameters) { 4626 url_full = parameters['url']; 4627 } 4628 if (typeof SB_DISABLED != ND && SB_DISABLED) { 4629 return; 4630 } 4631 if (init) { 4632 initialize(); 4633 return; 4634 } 4635 if (typeof SB_TICKETS != ND || ('mode' in parameters && parameters['mode'] == 'tickets')) { 4636 tickets = true; 4637 parameters['mode'] = 'tickets'; 4638 } 4639 if ('cloud' in parameters) { 4640 cloud_data = parameters['cloud']; 4641 } 4642 let min = url_full.lastIndexOf('main.min.js'); 4643 url = url_full.substr(0, url_full.lastIndexOf('main.js') > 0 ? (url_full.lastIndexOf('main.js') - 4) : (min - 8)); 4644 let url_chat = url + '/include/init.php' + ('lang' in parameters ? '?lang=' + parameters['lang'] : '') + ('mode' in parameters ? '&mode=' + parameters['mode'] : '') + (cloud_data ? '&cloud=' + cloud_data : ''); 4645 SBF.cors('GET', url_chat.replace('.php&', '.php?'), (response) => { 4646 let target = 'body'; 4647 if (tickets && $('#sb-tickets').length) { 4648 target = '#sb-tickets'; 4649 } 4650 $(target).append(response); 4651 SBF.loadResource(url + '/css/' + (tickets ? 'tickets' : 'main') + '.css'); 4652 if (tickets) { 4653 $.getScript(url + '/apps/tickets/tickets' + (min ? '.min' : '') + '.js?v=' + version, () => { 4654 initialize(); 4655 }); 4656 } else initialize(); 4657 }); 4658 }); 4659 4660 function initialize() { 4661 main = $('.sb-admin, .sb-chat, .sb-tickets'); 4662 4663 // Initalize the chat and the user 4664 if (main.length && typeof SB_AJAX_URL != ND) { 4665 chat = main.find('.sb-list'); 4666 chat_editor = main.find('.sb-editor'); 4667 chat_textarea = chat_editor.find('textarea'); 4668 chat_scroll_area = main.find(admin || tickets ? '.sb-list' : '> div > .sb-scroll-area'); 4669 label_date = main.find('.sb-label-date-top'); 4670 chat_header = chat_scroll_area.find('.sb-header'); 4671 chat_emoji = chat_editor.find('.sb-emoji'); 4672 chat_status = tickets ? main.find('.sb-profile-agent .sb-status') : null; 4673 SBChat.enabledAutoExpand(); 4674 SBChat.audio = main.find('#sb-audio').get(0); 4675 SBChat.audio_out = main.find('#sb-audio-out').get(0); 4676 4677 // Check if cookies works 4678 SBF.cookie('sb-check', 'ok', 1, 'set'); 4679 if (SBF.cookie('sb-check') != 'ok') { 4680 cookies_supported = false; 4681 console.warn('Support Board: cookies not available.'); 4682 } else { 4683 SBF.cookie('sb-check', false, false, false); 4684 } 4685 if (!admin) { 4686 SBF.ajax({ 4687 function: 'get-front-settings', 4688 current_url: window.location.href 4689 }, (response) => { 4690 CHAT_SETTINGS = response;
4691 if (typeof SB_LOCAL_SETTINGS != ND) { 4692 $.extend(CHAT_SETTINGS, SB_LOCAL_SETTINGS); 4693 } 4694 bot_id = CHAT_SETTINGS['bot-id']; 4695 dialogflow_human_takeover = CHAT_SETTINGS['dialogflow-human-takeover']; 4696 agents_online = CHAT_SETTINGS['agents-online']; 4697 SBPusher.active = CHAT_SETTINGS['pusher']; 4698 if (typeof SB_REGISTRATION_REQUIRED != ND) { 4699 CHAT_SETTINGS['registration-required'] = SB_REGISTRATION_REQUIRED; 4700 CHAT_SETTINGS['tickets-registration-required'] = SB_REGISTRATION_REQUIRED; 4701 } 4702 if (typeof SB_ARTICLES_PAGE != ND && SB_ARTICLES_PAGE) { 4703 SBChat.initArticlesPage(); 4704 } 4705 if ((!tickets || !CHAT_SETTINGS['tickets-manual-init']) && ((tickets && !CHAT_SETTINGS['tickets-manual-init']) || (!CHAT_SETTINGS['chat-manual-init'] && (!CHAT_SETTINGS['disable-offline'] || agents_online) && (!CHAT_SETTINGS['disable-office-hours'] || CHAT_SETTINGS['office-hours']) && (!CHAT_SETTINGS['chat-login-init'] || SBApps.login())))) { 4706 SBChat.initChat(); 4707 } 4708 if (CHAT_SETTINGS['cron']) { 4709 setTimeout(function () { 4710 SBF.ajax({ function: 'cron-jobs' }); 4711 }, 10000); 4712 } 4713 if (CHAT_SETTINGS['cron-email-piping']) { 4714 setTimeout(function () { 4715 SBF.ajax({ function: 'email-piping' }); 4716 }, 8000); 4717 } 4718 if (CHAT_SETTINGS['push-notifications-users']) { 4719 SBPusher.initServiceWorker(); 4720 } 4721 if (tickets) { 4722 if (CHAT_SETTINGS['tickets-default-department']) SBChat.default_department = CHAT_SETTINGS['tickets-default-department']; 4723 if (CHAT_SETTINGS['dialogflow-disable-tickets']) CHAT_SETTINGS['dialogflow-active'] = false; 4724 } 4725 if (SBApps.is('martfury')) { 4726 let session = false; 4727 setInterval(function () { 4728 if (activeUser()) { 4729 let current = SBF.cookie('XSRF-TOKEN'); 4730 if (current && current != session) { 4731 SBF.ajax({ function: 'martfury-session' }); 4732 session = current; 4733 } 4734 } 4735 }, 3000); 4736 } 4737 SBApps.aecommerce.cart(); 4738 SBF.event('SBReady'); 4739 }); 4740 if (SBChat.audio) SBChat.audio.volume = 0.6; 4741 if (SBChat.audio_out) SBChat.audio_out.volume = 0.6; 4742 } else { 4743 SBF.event('SBReady'); 4744 } 4745 $(chat_editor).on('keydown', 'textarea', function (e) { 4746 if (e.which == 13 && (!tickets || CHAT_SETTINGS['tickets-enter-button']) && (!admin || (!e.ctrlKey && !e.shiftKey))) { 4747 SBChat.submit(); 4748 e.preventDefault; 4749 return false; 4750 } 4751 if (admin && e.which == 13 && e.ctrlKey) { 4752 SBChat.insertText('\n'); 4753 } 4754 }); 4755 $(main).on('keydown', '.sb-dashboard-articles input', function (e) { 4756 if (e.which == 13) $(this).next().click(); 4757 }); 4758 if (typeof SB_DEFAULT_DEPARTMENT !== ND) { 4759 SBChat.default_department = SB_DEFAULT_DEPARTMENT; 4760 } 4761 if (typeof SB_DEFAULT_AGENT !== ND) { 4762 SBChat.default_agent = SB_DEFAULT_AGENT; 4763 } 4764 if (typeof SB_DIALOGFLOW_AGENT !== ND) { 4765 SBApps.dialogflow.project_id = SB_DIALOGFLOW_AGENT; 4766 } 4767 } else { 4768 SBF.event('SBReady'); 4769 } 4770 4771 // Disable real-time if browser tab not active 4772 document.addEventListener('visibilitychange', function () { 4773 SBF.visibilityChange(document.visibilityState); 4774 }, false); 4775 4776 $(main).on('click', function () { 4777 if (!SBChat.tab_active) { 4778 SBF.visibilityChange(); 4779 } 4780 }); 4781 4782 // Set the global container for both admin and front 4783 global = main; 4784 if (admin) { 4785 main = main.find('.sb-conversation'); 4786 } 4787 4788 // Scroll detection 4789 $(chat_scroll_area).on('scroll', function () { 4790 let scroll = chat_scroll_area.scrollTop(); 4791 let count = label_date_items.length; 4792 SBChat.scrollHeader(); 4793 if (label_date_show) { 4794 label_date.sbActive(true); 4795 clearTimeout(timeout_label_date[0]); 4796 timeout_label_date[0] = setTimeout(() => { label_date.sbActive(false) }, 1500); 4797 } 4798 if (count) { 4799 if (SBChat.isBottom()) { 4800 label_date.html($(label_date_items[count - 1]).html()); 4801 } else { 4802 for (var i = 0; i < count; i++) { 4803 let top = label_date_items[i].getBoundingClientRect().top; 4804 if (top > 100 && top < 150) { 4805 let label = $(label_date_items[label_date_history[0] > scroll && i > 0 ? i - 1 : i]).html(); 4806 if (label != label_date_history[1]) { 4807 label_date.html(label); 4808 label_date_history[1] = label; 4809 } 4810 break; 4811 } 4812 } 4813 } 4814 } 4815 label_date_history[0] = scroll; 4816 }); 4817 4818 // Show the message menu 4819 $(chat).on('click', '.sb-menu-btn', function () { 4820 let menu = $(this).parent().find('.sb-menu'); 4821 let active = $(menu).sbActive(); 4822 SBF.deactivateAll(); 4823 if (!active) { 4824 $(menu).sbActive(true); 4825 SBF.deselectAll(); 4826 if (admin) SBAdmin.open_popup = menu; 4827 } 4828 }); 4829 4830 // Mobile 4831 if (mobile) { 4832 $(chat_editor).on('click', '.sb-textarea', function () { 4833 main.addClass('sb-header-hidden'); 4834 $(this).find('textarea').focus(); 4835 if (SBChat.isBottom()) { 4836 SBChat.scrollBottom(); 4837 setTimeout(() => { 4838 SBChat.scrollBottom(); 4839 }, 200); 4840 } 4841 }); 4842 4843 $(chat_editor).on('focusout', '.sb-textarea', function () { 4844 setTimeout(() => { 4845 main.removeClass('sb-header-hidden'); 4846 }, 300); 4847 }); 4848 4849 $(chat_editor).on('click', '.sb-submit', function () { 4850 chat_textarea.blur(); 4851 }); 4852 4853 window.addEventListener('popstate', function () { 4854 if (SBChat.chat_open) SBChat.open(false); 4855 }); 4856 } 4857 4858 // Hide the message menu 4859 $(chat).on('click', '.sb-menu li', function () { 4860 $(this).parent().sbActive(false); 4861 }); 4862 4863 // Send a message 4864 $(chat_editor).on('click', '.sb-submit', function () { 4865 SBChat.submit(); 4866 }); 4867 4868 // Open the chat 4869 $('body').on('click', '.sb-chat-btn,.sb-responsive-close-btn,#sb-open-chat,.sb-open-chat', function () { 4870 SBChat.open(!SBChat.chat_open); 4871 }); 4872 4873 // Show the dashboard 4874 $(main).on('click', '.sb-dashboard-btn', function () { 4875 SBChat.showDashboard(); 4876 if (chat_scroll_area.find(' > .sb-panel-articles > .sb-article').length) SBChat.showArticles(); 4877 storage('open-conversation', 0); 4878 force_action = false;
4879 }); 4880 4881 // Open a conversation from the dashboard 4882 $(main).on('click', '.sb-user-conversations li', function () { 4883 SBChat.openConversation($(this).attr('data-conversation-id')); 4884 }); 4885 4886 // Start a new conversation from the dashboard 4887 $(main).on('click', '.sb-btn-new-conversation,.sb-departments-list > div,.sb-agents-list > div', function () { 4888 let id = $(this).data('id'); 4889 if (!SBF.null(id)) { 4890 if ($(this).parent().hasClass('sb-departments-list')) { 4891 SBChat.default_department = parseInt(id); 4892 } else { 4893 SBChat.default_agent = parseInt(id); 4894 } 4895 } 4896 force_action = 'new-conversation'; 4897 SBChat.clear(); 4898 SBChat.hideDashboard(); 4899 }); 4900 4901 // Displays all conversations in the dashboard 4902 $(main).on('click', '.sb-btn-all-conversations', function () { 4903 main.find('.sb-dashboard-conversations').removeClass('sb-conversations-hidden'); 4904 }); 4905 4906 // Events uploader 4907 $(chat_editor).on('click', '.sb-btn-attachment', function () { 4908 if (!SBChat.is_busy) { 4909 chat_editor.find('.sb-upload-files').val('').click(); 4910 } 4911 }); 4912 4913 $(chat_editor).on('click', '.sb-attachments > div > i', function (e) { 4914 $(this).parent().remove(); 4915 if (chat_textarea.val() == '' && chat_editor.find('.sb-attachments > div').length == 0) { 4916 SBChat.activateBar(false); 4917 } 4918 e.preventDefault(); 4919 return false; 4920 }); 4921 4922 $(chat_editor).on('change', '.sb-upload-files', function (data) { 4923 SBChat.busy(true); 4924 $(this).sbUploadFiles(function (response) { 4925 SBChat.uploadResponse(response); 4926 }); 4927 SBF.event('SBAttachments'); 4928 }); 4929 4930 $(chat_editor).on('dragover', function (e) { 4931 $(this).addClass('sb-drag'); 4932 clearTimeout(timeout); 4933 e.preventDefault(); 4934 e.stopPropagation(); 4935 }); 4936 4937 $(chat_editor).on('dragleave', function (e) { 4938 timeout = setTimeout(() => { 4939 $(this).removeClass('sb-drag'); 4940 }, 200); 4941 e.preventDefault(); 4942 e.stopPropagation(); 4943 }); 4944 4945 $(chat_editor).on('drop', function (e) { 4946 let files = e.originalEvent.dataTransfer.files; 4947 e.preventDefault(); 4948 e.stopPropagation(); 4949 if (files.length > 0) { 4950 for (var i = 0; i < files.length; ++i) { 4951 let form = new FormData(); 4952 form.append('file', files[i]); 4953 SBF.upload(form, function (response) { SBChat.uploadResponse(response) }); 4954 } 4955 } 4956 $(this).removeClass('sb-drag'); 4957 return false; 4958 }); 4959 4960 // Articles 4961 $(main).on('click', '.sb-btn-all-articles:not([onclick])', function () { 4962 SBChat.showArticles(); 4963 }); 4964 4965 $(main).on('click', '.sb-articles > div:not(.sb-title)', function () { 4966 SBChat.showArticles($(this).attr('data-id')); 4967 }); 4968 4969 $(main).on('click', '.sb-dashboard-articles .sb-input-btn .sb-submit-articles', function () { 4970 SBChat.searchArticles($(this).parent().find('input').val(), this, $(this).parent().next()); 4971 }); 4972 4973 $(global).on('click', '.sb-article [data-rating]', function () { 4974 SBChat.articleRatingOnClick(this); 4975 }); 4976 4977 $(chat).on('click', '.sb-rich-button a', function (e) { 4978 let link = $(this).attr('href'); 4979 if (link.indexOf('#') === 0) { 4980 if (link.indexOf('#article-') === 0) { 4981 SBChat.showArticles(link.replace('#article-', '')); 4982 e.preventDefault(); 4983 return false; 4984 } 4985 } 4986 }); 4987 4988 // Lightbox 4989 $(global).on('click', '.sb-lightbox-media > i', function () { 4990 global.find('.sb-lightbox-media').sbActive(false); 4991 if (admin) SBAdmin.open_popup = false;
4992 return false; 4993 }); 4994 4995 $(main).on('click', '.sb-image', function () { 4996 SBF.lightbox($(this).html()); 4997 }); 4998 4999 $(main).on('click', '.sb-slider-images .sb-card-img', function () { 5000 SBF.lightbox(`<img src="${$(this).attr('data-value')}" />`); 5001 }); 5002 5003 // Event: on conversation loaded 5004 $(document).on('SBConversationLoaded', function () { 5005 if (storage('queue')) { 5006 SBChat.queue(storage('queue')); 5007 } 5008 }); 5009 5010 // Events emoji 5011 $(chat_editor).on('click', '.sb-btn-emoji', function () { 5012 SBChat.showEmoji(this); 5013 }); 5014 5015 $(chat_emoji).on('click', '.sb-emoji-list li', function (e) { 5016 SBChat.insertEmoji($(this).html()); 5017 if (mobile) clearTimeout(timeout); 5018 }); 5019 5020 $(chat_emoji).find('.sb-emoji-list').on('touchend', function (e) { 5021 timeout = setTimeout(() => { 5022 SBChat.mouseWheelEmoji(e); 5023 }, 50); 5024 }); 5025 5026 $(chat_emoji).find('.sb-emoji-list').on('mousewheel DOMMouseScroll', function (e) { 5027 SBChat.mouseWheelEmoji(e); 5028 }); 5029 5030 $(chat_emoji).find('.sb-emoji-list').on('touchstart', function (e) { 5031 SBChat.emoji_options['touch'] = e.originalEvent.touches[0].clientY; 5032 }); 5033 5034 $(chat_emoji).on('click', '.sb-emoji-bar > div', function () { 5035 SBChat.clickEmojiBar(this); 5036 }); 5037 5038 $(chat_emoji).on('click', '.sb-select li', function () { 5039 SBChat.categoryEmoji($(this).data('value')); 5040 }); 5041 5042 $(chat_emoji).find('.sb-search-btn input').on('change keyup paste', function () { 5043 SBChat.searchEmoji($(this).val()); 5044 }); 5045 5046 // Textarea 5047 $(chat_textarea).on('keyup', function () { 5048 SBChat.textareaChange(this); 5049 }); 5050 5051 // Privacy message 5052 $(main).on('click', '.sb-privacy .sb-approve', function () { 5053 storage('privacy-approved', true); 5054 $(this).closest('.sb-privacy').remove(); 5055 main.removeClass('sb-init-form-active'); 5056 chat_header.find(' > div').css({ 'opacity': 1, 'top': 0 }); 5057 SBChat.initChat(); 5058 if (tickets) { 5059 SBTickets.showPanel('new-ticket'); 5060 } else if (!SBChat.isInitDashboard()) { 5061 SBChat.hideDashboard(); 5062 } 5063 }); 5064 5065 $(main).on('click', '.sb-privacy .sb-decline', function () { 5066 let privacy = $(this).closest('.sb-privacy'); 5067 $(privacy).find('.sb-text').html($(privacy).attr('data-decline')); 5068 $(privacy).find('.sb-decline').remove(); 5069 SBChat.scrollBottom(true); 5070 }); 5071 5072 // Popup message 5073 $(main).on('click', '.sb-popup-message .sb-icon-close', function () { 5074 SBChat.popup(true); 5075 }); 5076 5077 // Rich messages and inputs 5078 $(main).on('click', '.sb-rich-message .sb-submit,.sb-rich-message .sb-select ul li', function () { 5079 let message = $(this).closest('.sb-rich-message'); 5080 if (!message[0].hasAttribute('disabled')) { 5081 SBRichMessages.submit(message, message.attr('data-type'), this); 5082 }; 5083 }); 5084 5085 $(main).on('click', '.sb-rich-message .sb-input > span', function () { 5086 $(this).sbActive(true); 5087 $(this).siblings().focus(); 5088 }); 5089 5090 $(main).on('focus focusout click', '.sb-rich-message .sb-input input,.sb-rich-message .sb-input select', function (e) { 5091 switch (e.type) { 5092 case 'focusin': 5093 case 'focus': 5094 $(this).siblings().sbActive(true); 5095 break; 5096 case 'focusout': 5097 if ($(this).val() == '') { 5098 $(this).siblings().sbActive(false); 5099 } else { 5100 $(this).siblings().addClass('sb-filled sb-active'); 5101 } 5102 break; 5103 case 'click': 5104 $(this).siblings().removeClass('sb-filled'); 5105 break; 5106 } 5107 }); 5108 5109 $(main).on('click', '.sb-slider-arrow', function () { 5110 SBRichMessages.sliderChange($(this).closest('[id]').attr('id'), $(this).hasClass('sb-icon-arrow-right') ? 'right' : 'left'); 5111 }); 5112 5113 $(main).on('change', '.sb-rich-message [data-type="select"] select', function () { 5114 $(this).siblings().sbActive(true); 5115 }); 5116 5117 $(main).on('click', '[data-type="select-input"] > div', function () { 5118 $(this).prev().sbActive(true); 5119 $(this).next().addClass('sb-focus'); 5120 }); 5121 5122 $(main).on('focusout', '[data-type="select-input"] input,[data-type="select-input"] select', function () { 5123 let cnt = $(this).closest('.sb-input'); 5124 if (cnt.find('input[type="tel"]').val() + cnt.find('select').val() == '') { 5125 cnt.find('span').sbActive(false); 5126 } 5127 cnt.find('.sb-focus').removeClass('sb-focus'); 5128 }); 5129 5130 // Registration and Login 5131 $(main).on('click', '.sb-rich-registration .sb-login-area', function () { 5132 let init = main.hasClass('sb-init-form-active'); 5133 $(this).closest('.sb-rich-registration').replaceWith(SBRichMessages.generate({}, 'login', init ? 'sb-init-form' : '')); 5134 SBChat.scrollBottom(init); 5135 }); 5136 5137 $(main).on('click', '.sb-rich-login .sb-registration-area', function () { 5138 if (CHAT_SETTINGS['registration-link']) { 5139 document.location = CHAT_SETTINGS['registration-link']; 5140 } else { 5141 let init = main.hasClass('sb-init-form-active'); 5142 $(this).closest('.sb-rich-login').replaceWith(SBRichMessages.generate({}, 'registration', init ? 'sb-init-form' : '')); 5143 SBChat.scrollBottom(init); 5144 } 5145 }); 5146 5147 $(main).on('click', '.sb-rich-login .sb-submit-login', function () { 5148 SBF.loginForm(this, false, (response) => { 5149 let area = $(this).closest('.sb-rich-login'); 5150 activeUser(new SBUser(response[0])); 5151 if (area.hasClass('sb-init-form')) { 5152 main.removeClass('sb-init-form-active'); 5153 area.remove(); 5154 force_action = 'open-conversation'; 5155 SBChat.initChat(); 5156 SBPusher.start(); 5157 if (!SBChat.isInitDashboard()) { 5158 SBChat.hideDashboard(); 5159 } 5160 } else { 5161 area = area.closest('[data-id]'); 5162 let message = SBChat.conversation.getMessage(
5162area.attr('data-id')); 5163 let text = `${sb_('Logged in as')} *${activeUser().name}*`; 5164 message.set('message', text); 5165 SBChat.updateMessage(message.id, text); 5166 area.replaceWith(message.getCode()); 5167 SBPusher.started = false; 5168 SBPusher.start(); 5169 } 5170 }); 5171 }); 5172 5173 // Social share buttons 5174 $(chat).on('click', '.sb-social-buttons div', function () { 5175 SBF.openWindow($(this).attr('data-link')); 5176 }); 5177 5178 // Archive chat 5179 $(main).on('click', '.sb-close-chat', function () { 5180 SBChat.closeChat(); 5181 }); 5182 5183 // WooCommerce 5184 $(chat).on('click', '.sb-rich-woocommerce-button a, [href="#"].sb-card-btn', function (e) { 5185 let settings = SBF.settingsStringToArray($(this).closest('.sb-rich-message').attr('data-settings')); 5186 let checkout = settings['link-type'] == 'checkout' || settings['checkout']; 5187 let product_ids = $(this)[0].hasAttribute('data-ids') ? $(this).attr('data-ids').split(',') : [settings['id'].split('|')[$(this).parent().index()]]; 5188 if (product_ids.length) { 5189 if (loading(this)) return; 5190 SBApps.wordpress.ajax('button-purchase', { product_ids: product_ids, checkout: checkout, coupon: settings['coupon'] }, (response) => { 5191 if (checkout) { 5192 document.location = response; 5193 } else { 5194 $(this).addClass('sb-icon-check').sbLoading(false); 5195 } 5196 }); 5197 } 5198 e.preventDefault(); 5199 return false; 5200 }); 5201 5202 $(chat).on('click', '#sb-waiting-list .sb-submit', function () { 5203 if ($(this).index() == 0) { 5204 setTimeout(() => { SBApps.woocommerce.waitingList('submit') }, 1000); 5205 } 5206 }); 5207 5208 $(document).on('SBNewEmailAddress', function (e, response) { 5209 if (response['id'] == 'sb-waiting-list-email') { 5210 SBApps.woocommerce.waitingList('submit'); 5211 } 5212 }); 5213 5214 /* 5215 * ---------------------------------------------------------- 5216 * COMPONENTS 5217 * ---------------------------------------------------------- 5218 */ 5219 5220 // Search 5221 $(global).on('click', '.sb-search-btn > i', function () { 5222 let parent = $(this).parent(); 5223 let active = $(parent).sbActive(); 5224 if (active) { 5225 setTimeout(() => { $(parent).find('input').val('') }, 50); 5226 setTimeout(() => { $(parent).find('input').trigger('change') }, 550); 5227 }; 5228 $(parent).sbActive(!active); 5229 $(parent).find('input').focus(); 5230 global.find('.sb-select ul').sbActive(false); 5231 }); 5232 5233 // Select 5234 $(global).on('click', '.sb-select', function () { 5235 let ul = $(this).find('ul'); 5236 let active = ul.hasClass('sb-active'); 5237 $(global).find('.sb-select ul').sbActive(false); 5238 ul.setClass('sb-active', !active); 5239 if (admin) SBAdmin.open_popup = active ? false : this; 5240 }); 5241 5242 $(global).on('click', '.sb-select li', function () { 5243 let select = $(this).closest('.sb-select'); 5244 let value = $(this).data('value'); 5245 var item = $(select).find(`[data-value="${value}"]`); 5246 $(select).find('li').sbActive(false); 5247 $(select).find('p').attr('data-value', value).html($(item).html()); 5248 $(item).sbActive(true); 5249 }); 5250 5251 // Image uploader 5252 $(global).on('click', '.sb-input-image .image', function () { 5253 upload_target = $(this).parent(); 5254 chat_editor.find('.sb-upload-files').click(); 5255 }); 5256 5257 $(global).on('click', '.sb-input-image .image > .sb-icon-close', function (e) { 5258 $(this).parent().removeAttr('data-value').css('background-image', ''); 5259 e.preventDefault(); 5260 return false;
5261 }); 5262 } 5263 5264}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.