PageSourceSearch

https://www.portofmiddlesbrough.com/wp-content/plugins/supportboard/supportboard/js/main.js?lang=en&ver=3.5.2

js portofmiddlesbrough.com collected 2026-10-02 23:04:58 UTC 257,196 bytes, 5,264 lines download raw bytes

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', '&lt;script').replaceAll('</script', '&lt;/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(/&amp;lt;/g, '&lt;');
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.