PageSourceSearch

https://paymento.io/core/modules/25da1d5bae/supportboard/js/main.js?lang=en

js paymento.io collected 2026-09-24 18:50:34 UTC 345,369 bytes, 7,149 lines download raw bytes

1
2/*
3 * ==========================================================
4 * MAIN SCRIPT
5 * ==========================================================
6 *
7 * Main JavaScript file. © 2017-2026 board.support. All rights reserved.
8 * 
9 */
10
11'use strict';
12
13(function ($) {
14
15    var version = '3.9.1';
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_typing = 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 chat_overlay_panel;
35    var label_date_items = false;
36    var label_date_history = [9999999, ''];
37    var label_date_timeout = [false, false];
38    var document_title = document.title;
39    var CHAT_SETTINGS = {};
40    var mobile = $(window).width() < 465;
41    var bot_id;
42    var force_action = '';
43    var dialogflow_human_takeover;
44    var agents_online = false;
45    var ND = 'undefined';
46    var cookies_supported = true;
47    var utc_offset_user = (new Date()).getTimezoneOffset() * 60000;
48    var cloud_data = false;
49    var articles_page = false;
50    var ajax_calls;
51    var audio_mp3;
52    var audio_recorder_dom;
53    var audio_recorder_dom_time;
54    var audio_recorder;
55    var audio_recorder_chunks = [];
56    var audio_recorder_time = [0, false];
57    var audio_recorder_time_player = [0, false];
58    var audio_recorder_stream;
59    var prevent_focusout = false;
60    var init_push_notifications = false;
61    var conversation_update_last_check = 0;
62    var is_shopify = typeof Shopify !== ND;
63    var call_timer_interval;
64    var call_bar;
65    var call_seconds = 0;
66    var call_data = false;
67
68    /*
69    * ----------------------------------------------------------
70    * EXTERNAL SCRIPTS
71    * ----------------------------------------------------------
72    */
73
74    // Auto Expand Scroll Area | Schiocco
75    $.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) } });
76
77    // Autolink-js
78    (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):\/\/[\w\-\u0080-\uFFFF+&@#\/%?=()~_|!:,.;]*[\w\-\u0080-\uFFFF+&@#\/%=~_|])/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);
79
80    /*
81    * ----------------------------------------------------------
82    * FUNCTIONS
83    * ----------------------------------------------------------
84    */
85
86    var SBF = {
87        visibility_status: 'visible',
88        loop_prevention: false,
89
90        // Main Ajax function
91        ajax: function (data, onSuccess = false) {
92            if (ajax_calls) {
93                ajax_calls[0].push(data);
94                ajax_calls[1].push(onSuccess);
95            } else {
96                ajax_calls = [[data], [onSuccess]];
97                setTimeout(() => {
98                    let onSuccessCalls = ajax_calls[1];
99                    let data_auto = { 'login-cookie': SBF.loginCookie(), current_url: window.location.href }
100                    if (activeUser()) {
101                        data_auto.user_id = activeUser().id;
102                    }
103                    if (typeof SB_LANG != ND) {
104                        data_auto.language = SB_LANG;
105                    }
106                    if (cloud_data) {
107                        data_auto.cloud = cloud_data;
108                    }
109                    if (location.search.includes('debug')) {
110                        data_auto.debug = true;
111                    }
112                    $.ajax({
113                        method: 'POST',
114                        url: SB_AJAX_URL,
115                        data: $.extend({ function: 'ajax_calls', calls: ajax_calls[0] }, data_auto)
116                    }).done((response) => {
117                        let result;
118                        if (Array.isArray(response)) {
119                            result = response;
120                        }
120 else if (response === 'invalid-session') {
121                            setTimeout(() => { SBF.reset() }, 1000);
122                        } else if (admin && SB_ADMIN_SETTINGS.cloud && response.includes('no-credits') && !response.includes('"value":"no-credits')) {
123                            return SBCloud.creditsAlertQuota();
124                        } else {
125                            try {
126                                result = typeof response === 'string' || response instanceof String ? JSON.parse(response) : response;
127                            } catch (e) {
128                                this.ajax_error(response, data);
129                                return;
130                            }
131                        }
132                        for (var i = 0; i < result.length; i++) {
133                            let result_sub = result[i];
134                            if (!Array.isArray(result_sub) && !result_sub.includes('["error')) {
135                                result_sub = result;
136                            }
137                            if (result_sub[0] == 'success') {
138                                onSuccess = onSuccessCalls[i];
139                                if (onSuccess) {
140                                    onSuccess(result_sub[1]);
141                                }
142                            } else if (SBF.errorValidation(result_sub)) {
143                                if (onSuccess) {
144                                    onSuccess(result_sub);
145                                }
146                            } else {
147                                if (admin) {
148                                    if (result_sub[1] == 'security-error') {
149                                        setTimeout(() => { SBF.reset() }, 1000);
150                                    }
151                                    SBAdmin.conversations.busy = false;
152                                }
153                                SBChat.is_busy_update = false;
154                                SBChat.busy(false);
155                                if (result_sub[1] == 'login-data-error' && !this.loop_prevention) {
156                                    return;
157                                }
158                                let result_sub_arr = JSON.parse(result_sub);
159                                if (Array.isArray(result_sub_arr) && result_sub_arr[0] == 'error' && result_sub_arr[2] && result_sub_arr[3]) {
160                                    SBF.error(result_sub_arr[3], result_sub_arr[2]);
161                                } else {
162                                    SBF.error(JSON.stringify(result_sub).replace(/\\/g, "").replace(/\"/g, "").replace(/\[/g, "").replace(/\]/g, "").replace('error,', ''), data.function);
163                                }
164                            }
165                        }
166                    }).fail((jqXHR, textStatus, error) => {
167                        if (error) {
168                            this.ajax_error('HTTP CURL ERROR');
169                            console.log(error);
170                        }
171                    });
172                    ajax_calls = false;
173                }, 100);
174            }
175        },
176
177        ajax_error: function (response, data = false) {
178            if (admin) {
179                SBAdmin.conversations.busy = false;
180                SBApps.dialogflow.smart_reply_busy = false;
181            }
182            if (SBApps.dialogflow.busy) {
183                SBApps.dialogflow.busy = false;
184                if (!admin && SBChat.conversation) {
185                    SBF.ajax({ function: 'open-ai-send-fallback-message', conversation_id: SBChat.conversation.id });
186                    SBChat.typing(-1, 'stop');
187                }
188            }
189            SBChat.is_busy_update = false;
190            SBChat.busy(false);
191            console.log(response);
192            SBF.error(response.length > 500 ? response.substr(0, 500) + '... Check the console for more details.' : response, `SBF.ajax.${data ? data.function : ''}`);
193        },
194
195        // Cors function
196        cors: function (method = 'GET', url, onSuccess) {
197            let xhr = new XMLHttpRequest();
198            if ('withCredentials' in xhr) {
199                xhr.open(method, url, true);
200            } else if (typeof XDomainRequest != ND) {
201                xhr = new XDomainRequest();
202                xhr.open(method, url);
203            } else {
204                return false;
205            }
206            xhr.onload = function () {
207                onSuccess(xhr.responseText);
208            };
209            xhr.onerror = function () {
210                return false;
211            };
212            xhr.send();
213        },
214
215        // Uploads
216        upload: function (form, onSuccess) {
217            if (cloud_data) {
218                form.append('cloud', cloud_data);
219            }
220            jQuery.ajax({
221                url: SB_URL + '/include/upload.php',
222                cache: false,
223                contentType: false,
224                processData: false,
225                data: form,
226                type: 'POST',
227                success: function (response) {
228                    onSuccess(response);
229                }
230            });
231        },
232
233        // Get file type
234        getFileType: function (url_or_name) {
235            let file_name = String(url_or_name).split('#')[0].split('?')[0].split('/').pop();
236            let file_extension = file_name.includes('.') ? file_name.split('.').pop().toLowerCase() : '';
237            if (/jpg|jpeg|png|gif|webp/.test(file_extension)) {
238                return 'image';
239            }
240            if (/mp3|ogg|wav|aac/.test(file_extension)) {
241                return 'audio';
242            }
243            if (/mp4|mkv|vob|3gp|webm/.test(file_extension)) {
244                return 'video';
245            }
246            return 'file';
247        },
248
249        // Get file icon
250        getFileIcon: function (url_or_name) {
251            let file_extension = url_or_name.split('.').pop().split('?')[0].toLowerCase();
252            let icons = {
253                audio: ['mp3', 'wav', 'ogg', 'm4a'],
254                video: ['mp4', 'avi', 'mov', 'mkv', 'webm', 'wmv'],
255                pdf: ['pdf'],
256                doc: ['doc', 'docx', 'rtf', 'odt'],
257                ppt: ['ppt', 'pptx', 'key', 'odp'],
258                xls: ['xls', 'xlsx', 'csv', 'ods'],
259                archive: ['zip', 'rar', '7z', 'tar', 'gz', 'bz2'],
260                image: ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg', 'tiff', 'heic', 'psd', 'ai'],
261                text: ['txt', 'log', 'md', 'json']
262            };
263            for (let key in icons) {
264                if (icons[key].includes(file_extension)) {
265                    return key;
266                }
267            }
268            return 'default';
269        },
270
271        // UTC Time
272        UTC: function (datetime) {
273            return new Date(datetime).getTime() - utc_offset_user;
274        },
275
276        // Check if a variable is null or empty
277        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; },
278
279        // Deactivate and hide the elements
280        deactivateAll: function () {
281            if (global) {
282                global.find('.sb-popup, .sb-tooltip, .sb-list .sb-menu, .sb-select ul').sbActive(false);
283            }
284        },
285
286        // Deselect the content of the target
287        deselectAll: function () {
288            if (window.getSelection) {
289                window.getSelection().removeAllRanges();
290            } else if (document.selection) {
291                document.selection.empty();
292            }
293        },
294
295        // Get URL parameters
296        getURL: function (name = false, url = false) {
297            if (!url) {
298                url = location.search;
299            }
300            if (name == false) {
301                var c = url.split('?').pop().split('&');
302                var p = {};
303                for (var i = 0; i < c.length; i++) {
304                    var d = c[i].split('=');
305                    p[d[0]] = SBF.escape(d[1]);
306                }
307                return p;
308            }
309            if (url.indexOf('?') > 0) {
310                url = url.substr(0, url.indexOf('?'));
311            }
312            return SBF.escape(decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(url) || [, ""])[1].replace(/\+/g, '%20') || ""));
313        },
314
315        URL: function () {
316            let url = window.location.href;
317            return url.includes('?') ? url.substring(0, url.indexOf('?')) : url;
318        },
319
320        // Convert a string to slug and inverse
321        stringToSlug: function (string) {
322            let map = {
323                'ก': 'k', 'ข': 'kh', 'ฃ': 'kh', 'ค': 'kh', 'ฅ': 'kh', 'ฆ': 'kh', 'ง': 'ng',
324                'จ': 'ch', 'ฉ': 'ch', 'ช': 'ch', 'ซ': 's', 'ฌ': 'ch', 'ญ': 'y', 'ฎ': 'd',
325                'ฏ': 't', 'ฐ': 'th', 'ฑ': 'th', 'ฒ': 'th', 'ณ': 'n', 'ด': 'd', 'ต': 't',
326                'ถ': 'th', 'ท': 'th', 'ธ': 'th', 'น': 'n', 'บ': 'b', 'ป': 'p', 'ผ': 'ph',
327                'ฝ': 'f', 'พ': 'ph', 'ฟ': 'f', 'ภ': 'ph', 'ม': 'm', 'ย': 'y', 'ร': 'r',
328                'ล': 'l', 'ว': 'w', 'ศ': 's', 'ษ': 's', 'ส': 's', 'ห': 'h', 'ฬ': 'l',
329                'อ': 'o', 'ฮ': 'h', 'ะ': 'a', 'ั': 'a', 'า': 'a', 'ำ': 'am', 'ิ': 'i',
330                'ี': 'i', 'ึ': 'ue', 'ื': 'ue', 'ุ': 'u', 'ู': 'u', 'เ': 'e', 'แ': 'ae',
331                'โ': 'o', 'ใ': 'ai', 'ไ': 'ai', 'ا': 'a', 'ب': 'b', 'ت': 't', 'ث': 'th', 'ج': 'j', 'ح': 'h', 'خ': 'kh',
332                'د': 'd', 'ذ': 'dh', 'ر': 'r', 'ز': 'z', 'س': 's', 'ش': 'sh', 'ص': 's',
333                'ض': 'd', 'ط': 't', 'ظ': 'z', 'ع': 'a', 'غ': 'gh', 'ف': 'f', 'ق': 'q',
334                'ك': 'k', 'ل': 'l', 'م': 'm', 'ن': 'n', 'ه': 'h', 'و': 'w', 'ي': 'y',
335                '你': 'ni', '好': 'hao', '世': 'shi', '界': 'jie', '我': 'wo', '是': 'shi',
336                '中': 'zhong', '国': 'guo', '人': 'ren', '谢': 'xie', '再': 'zai', '见': 'jian'
337            };
338            let from = "åàáãäâèéëêìíïîòóöôùúüûñç·/_,:;";
339            let to = "aaaaaaeeeeiiiioooouuuunc------";
340            string = string.trim().toLowerCase().split('').map(char => map[char] || char).join('');
341            for (var i = 0, l = from.length; i < l; i++) {
342                string = string.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
343            }
344            return string.replace(/[^a-z0-9 -]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-+/, '').replace(/-+$/, '').replace(/ /g, '');
345        },
346
347        slugToString: function (string) {
348            string = string.replace(/_/g, ' ').replace(/-/g, ' ');
349            return string.charAt(0).toUpperCase() + string.slice(1);
350        },
351
352        // Random string
353        random: function () {
354            let chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
355            let result = '';
356            for (var i = 5; i > 0; --i) result += chars[Math.floor(Math.random() * 62)];
357            return result;
358        },
359
360        // Check if a user type is an agent
361        isAgent: function (user_type) {
362            return user_type == 'agent' || user_type == 'admin' || user_type == 'bot';
363        },
364
365        // Beautify the time
366        beautifyTime: function (datetime, extended = false, future = false) {
367            let date;
368            if (datetime == '0000-00-00 00:00:00') {
369                return '';
370            }
371            if (datetime.indexOf('-') > 0) {
372                let arr = datetime.split(/[- :]/);
373                date = new Date(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
374            } else {
375                let arr = datetime.split(/[. :]/);
376                date = new Date(arr[2], arr[1] - 1, arr[0], arr[3], arr[4], arr[5]);
377            }
378            let now = new Date();
379            let date_string = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds()));
380            let diff_days = ((now - date_string) / 86400000) * (future ? -1 : 1);
381            let days = [sb_('Sunday'), sb_('Monday'), sb_('Tuesday'), sb_('Wednesday'), sb_('Thursday'), sb_('Friday'), sb_('Saturday')];
382            let time = date_string.toLocaleTimeString(navigator.language, { hour: '2-digit', minute: '2-digit' });
383            if (time.charAt(0) === '0' && (time.includes('PM') || time.includes('AM'))) {
384                time = time.substring(1);
385            }
386            if (diff_days < 1 && now.getDate() == date_string.getDate()) {
387                return extended ? `<span>${sb_('Today')}</span> <span>${time}</span>` : `<span data-today>${time}</span>`;
388            } else if (diff_days < 6) {
389                return `<span>${days[date_string.getDay()]}</span>${extended ? ` <span>${time}</span>` : ''}`;
390            } else {
391                return `<span>${date_string.toLocaleDateString(undefined, { year: '2-digit', month: '2-digit', day: '2-digit' })}</span>${extended ? ` <span>${time}</span>` : ''}`;
392            }
393        },
394
395        // Get the unix timestamp value of a date string with format yyyy-mm-dd hh:mm:ss
396        unix: function (datetime) {
397            let arr = datetime.split(/[- :]/);
398            return Date.UTC(arr[0], arr[1] - 1, arr[2], arr[3], arr[4], arr[5]);
399        },
400
401        // Generate a string containing the agent location and time
402        getLocationTimeString: function (details, onSuccess) {
403            if (details.timezone) {
404                let location = {};
405                location.timezone = details.timezone.value;
406                location.country = details.country ? details.country.value : location.timezone.split('/')[0].replace(/_/g, ' ');
407                location.city = details.city ? details.city.value : location.timezone.split('/')[1].replace(/_/g, ' ');
408                onSuccess(`${new Intl.DateTimeFormat(undefined, { timeZone: location.timezone, hour: '2-digit', minute: '2-digit' }).format(new Date())} ${sb_('in')} ${location.city ? location.city : ''}${location.country ? ', ' + location.country : ''}`);
409            }
410        },
411
412        // Date string
413        dateDB: function (date) {
414            if (date == 'now') {
415                date = (new Date).toISOString().replace('T', ' ');
416                if (date.indexOf('.') > 0) {
417                    date = date.substr(0, date.indexOf('.'));
418                }
419                return date;
420            } else {
421                return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
422            }
423        },
424
425        // Convert seconds to second and minutes
426        getMinutesSeconds: function (seconds) {
427            let minutes = Math.floor(seconds / 60);
428            seconds = seconds - minutes * 60;
429            return (minutes ? minutes : '0') + ':' + (seconds < 10 ? '0' + seconds : seconds);
430        },
431
432        // Convert a date to local time
433        convertUTCDateToLocalDate: function (datetime, utc_offset = 0) {
434            datetime = new Date(datetime); // Y/m/d H:i:s
435            datetime = new Date(datetime.getTime() + utc_offset * 3600000);
436            return new Date(datetime.getTime() + utc_offset_user * -1);
437        },
438
439        // Calls
440        call: {
441            rtc: false,
442            recorder: false,
443            mixed_stream: false,
444            local_stream: false,
445
446            answer: function (conversation_id = false) {
447                SBApps.whatsapp.call.answer(conversation_id ? conversation_id : SBChat.conversation.id, (response) => {
448                    call_bar.find('#sb-call-answer').sbLoading(false);
449                    if (response.success) {
450                        this.startCall();
451                    } else {
452                        SBF.error(JSON.stringify(response), 'SBConversations.call.answer');
453                    }
454                });
455            },
456
457            decline: function (is_terminate = false, conversation_id = false, onSuccess = false) {
458                SBApps.whatsapp.call.decline(conversation_id ? conversation_id : SBChat.conversation.id, is_terminate, (response) => {
459                    call_bar.find('#sb-call-decline').sbLoading(false);
460                    call_bar.sbActive(false);
461                    this.end();
462                    if (response.success) {
463                        clearInterval(call_timer_interval);
464                        call_bar.removeClass('sb-call-active');
465                    } else {
466                        SBF.error(JSON.stringify(response), 'SBConversations.call.decline');
467                    }
468                    if (onSuccess) {
469                        onSuccess(response);
470                    }
471                });
472            },
473
474            startCall: function () {
475                call_seconds = 0;
476                clearInterval(call_timer_interval);
477                call_bar.addClass('sb-call-active');
478                call_timer_interval = setInterval(() => {
479                    call_seconds++;
480                    call_bar.find('#call-timer').html(SBF.getMinutesSeconds(call_seconds));
481                }, 1000);
482                storage('call-active', [SBChat.conversation.id, admin ? SBAdmin.conversations.active_calls[SBChat.conversation.id] : call_data]);
483            },
484
485            start: function (channel, user_id, onSuccess) {
486                this.initRTC(() => {
487                    if (channel == 'whatsapp') {
488                        SBApps.whatsapp.call.start(this.rtc.localDescription.sdp, user_id, (response) => {
489                            if (onSuccess) {
490                                onSuccess(response);
491                            }
492                        });
493                    }
494                });
495            },
496
497            initRTC: function (onSuccess, sdp = false) {
498                (async () => {
499                    const chunks = [];
500                    this.rtc = new RTCPeerConnection(sdp ? {}
500 : { iceServers: [] });
501                    this.audio_ctx = new AudioContext();
502                    this.destination = this.audio_ctx.createMediaStreamDestination();
503                    this.mixed_stream = this.destination.stream;
504                    this.recorder = new MediaRecorder(this.mixed_stream, { mimeType: 'audio/webm;codecs=opus' });
505                    this.recorder.ondataavailable = e => {
506                        if (e.data.size) {
507                            chunks.push(e.data);
508                        }
509                    }
510                    this.recorder.onstop = () => {
511                        const form = new FormData();
512                        const blob = new Blob(chunks, { type: this.recorder.mimeType });
513                        if (call_bar.hasClass('sb-call-ringing')) {
514                            return;
515                        }
516                        form.append('file', new File([blob], `call_${SBChat.conversation.id}_${Date.now()}.webm`));
517                        const audio = new Audio();
518                        audio.src = URL.createObjectURL(blob);
519                        audio.addEventListener('loadedmetadata', () => {
520                            SBF.upload(form, (response) => {
521                                response = JSON.parse(response);
522                                if (response[0] === 'success') {
523                                    setTimeout(() => {
524                                        SBF.ajax({
525                                            function: 'whatsapp-call-save-audio',
526                                            conversation_id: SBChat.conversation.id,
527                                            audio_file_url: response[1],
528                                            duration: audio.duration
529                                        });
530                                    }, 1000);
531                                } else {
532                                    SBF.error(response[1], 'call.initRTC');
533                                }
534                                URL.revokeObjectURL(audio.src);
535                            });
536                        });
537                    }
538                    this.rtc.ontrack = (event) => {
539                        const remoteStream = event.streams[0];
540                        const audioEl = global.find('#sb-audio-call')[0];
541                        audioEl.srcObject = remoteStream;
542                        audioEl.autoplay = true;
543                        const remoteSource = this.audio_ctx.createMediaStreamSource(remoteStream);
544                        remoteSource.connect(this.destination);
545                    }
546                    this.local_stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } });
547                    this.audio_ctx.createMediaStreamSource(this.local_stream).connect(this.destination);
548                    this.local_stream.getTracks().forEach(track => {
549                        this.rtc.addTrack(track, this.local_stream);
550                    });
551                    if (sdp) {
552                        await this.rtc.setRemoteDescription({ type: 'offer', sdp });
553                    }
554                    const local_desc = sdp ? await this.rtc.createAnswer() : await this.rtc.createOffer({ offerToReceiveAudio: true });
555                    await this.rtc.setLocalDescription(local_desc);
556                    if (!sdp) {
557                        this.rtc.onicecandidate = () => { };
558                        await new Promise(r => setTimeout(r, 500));
559                    }
560                    if (this.audio_ctx.state !== 'running') {
561                        await this.audio_ctx.resume();
562                    }
563                    this.recorder.start();
564                    this.stopRecording = () => {
565                        if (this.recorder && this.recorder.state !== 'inactive') {
566                            this.recorder.stop();
567                        }
568                    }
569                    onSuccess(local_desc);
570                })();
571            },
572
573            show: function (show = true, is_ringing = false) {
574                call_bar.removeClass('sb-call-active sb-call-ringing');
575                if (is_ringing) {
576                    call_bar.addClass('sb-call-ringing');
577                }
578                setTimeout(() => {
579                    call_bar.sbActive(show);
580                }, 300);
581            },
582
583            end: function () {
584                if (this.recorder && this.recorder.state !== 'inactive') {
585                    this.recorder.stop();
586                }
587                if (this.local_stream) {
588                    this.local_stream.getTracks().forEach(t => t.stop());
589                    this.local_stream = null;
590                }
591                if (this.mixed_stream) {
592                    this.mixed_stream.getTracks().forEach(t => t.stop());
593                    this.mixed_stream = null;
594                }
595                if (this.rtc) {
596                    this.rtc.getSenders().forEach(s => s.track && s.track.stop());
597                    this.rtc.getReceivers().forEach(r => r.track && r.track.stop());
598                    this.rtc.close();
599                    this.rtc = null;
600                }
601                const audio = global.find('#sb-audio-call')[0];
602                if (audio) {
603                    audio.srcObject = null;
604                    audio.pause();
605                }
606            },
607
608            mute: function (mute = true) {
609                if (this.local_stream) {
610                    this.local_stream.getAudioTracks().forEach(track => {
611                        track.enabled = !mute;
612                    });
613                }
614            }
615        },
616
617        // Set and get users last activity
618        updateUsersActivity: function (user_id, return_user_id, onSuccess) {
619            if (SBPusher.active) {
620                onSuccess((admin && SB_ADMIN_SETTINGS.bot_id == return_user_id) || (!admin && CHAT_SETTINGS.bot_id == return_user_id) ? 'online' : (SBPusher.online_ids.includes(return_user_id) ? 'online' : 'offline'));
621            } else {
622                SBF.ajax({
623                    function: 'update-users-last-activity',
624                    user_id: user_id,
625                    return_user_id: return_user_id,
626                    check_slack: !admin && CHAT_SETTINGS.slack_active
627                }, (response) => {
628                    if (response === 'online') {
629                        onSuccess('online');
630                    } else {
631                        onSuccess('offline');
632                    }
633                });
634            }
635        },
636
637        // Search functions
638        search: function (search, searchFunction) {
639            search = search.toLowerCase();
640            if (search == previous_search) {
641                global.find('.sb-search-btn i').sbLoading(false);
642                return;
643            }
644            clearTimeout(timeout);
645            timeout = setTimeout(function () {
646                previous_search = search;
647                searchFunction();
648            }, 1000);
649        },
650
651        searchClear: function (icon, onSuccess) {
652            let search = $(icon).next().val();
653            if (search) {
654                $(icon).next().val('');
655                onSuccess();
656            }
657        },
658
659        // Support Board error JS reporting
660        error: function (message, function_name) {
661            let is_full_error = message.includes(function_name);
662            if (admin && SBAdmin.is_logout) {
663                return;
664            }
665            if (message instanceof Error) {
666                message = message.message;
667            }
668            if (message[message.length - 1] == '.') {
669                message = message.slice(0, -1);
670            }
671            if (admin) {
672                if (message && !function_name.includes('update-users-last-activity') && !function_name.startsWith('security-error')) {
673                    SBAdmin.infoPanel(`<pre>${message.includes(']:') ? 'Error: ' + message.substr(message.indexOf(']:') + 2) : message}. Check the console for more details.</pre>`, 'info', false, 'error', '', true);
674                    global.find('.sb-lightbox-overlay').sbActive(false);
675                }
676                SBApps.dialogflow.smart_reply_busy = false;
677            }
678            global.find('.sb-loading').sbLoading(false);
679            SBChat.busy(false);
680            SBF.event('SBError', { message: message, function_name: function_name });
681            throw new Error(is_full_error ? message : `Support Board Error [${function_name}]: ${message}.`);
682        },
683
684        errorValidation: function (response, code = true) {
685            return Array.isArray(response) && response[0] === 'validation-error' && (code === true || response[1] == code);
686        },
687
688        // Login
689        loginForm: function (button, area = false, onSuccess = false, isRecursion = true) {
690            button = $(button);
691            if (!button.sbLoading()) {
692                if (area === false) {
693                    area = button.closest('.sb-rich-login');
694                } else {
695                    area = $(area);
696                }
697                let email = $.trim(area.find('#email input').val());
698                let password = $.trim(area.find('#password input').val());
699                if (!email || !password) {
700                    area.find('.sb-info').html(sb_('Please insert email and password.')).sbActive(true);
701                    SBChat.scrollBottom();
702                } else {
703                    SBF.ajax({
704                        function: 'login',
705                        email: email,
706                        password: password
707                    }, (response) => {
708                        if (response && Array.isArray(response)) {
709                            if (!admin && this.isAgent(response[0].user_t
709ype)) {
710                                SBForm.showErrorMessage(area, 'You cannot sign in as an agent.');
711                                SBChat.scrollBottom();
712                            } else {
713                                let user = new SBUser(response[0]);
714                                user.set('conversation_id', SBChat.conversation ? SBChat.conversation.id : false);
715                                this.loginCookie(response[1]);
716                                this.event('SBLoginForm', user);
717                                if (onSuccess) {
718                                    onSuccess(response);
719                                }
720                            }
721                            if (SBF.setting('wp-users-system') == 'wp') {
722                                SBApps.wordpress.ajax('wp-login', { user: email, password: password });
723                            }
724                        } else if (admin && SBApps.is('wordpress')) {
725                            return SBApps.wordpress.ajax('wp-login-admin', { user: email, password: password }, () => {
726                                button.sbLoading(false);
727                                if (isRecursion) {
728                                    this.loginForm(button, area, onSuccess, false);
729                                } else {
730                                    area.find('.sb-info').html(sb_(response === 'ip-ban' ? 'Too many login attempts. Please retry again in a few hours.' : 'Invalid email or password.')).sbActive(true);
731                                }
732                            });
733                        } else {
734                            area.find('.sb-info').html(sb_(response === 'ip-ban' ? 'Too many login attempts. Please retry again in a few hours.' : 'Invalid email or password.')).sbActive(true);
735                            if (!admin) {
736                                SBChat.scrollBottom();
737                            }
738                        }
739                        button.sbLoading(false);
740                    });
741                    area.find('.sb-info').html('').sbActive(false);
742                    button.sbLoading(true);
743                }
744            }
745        },
746
747        // Set the login cookie
748        loginCookie: function (value = false) {
749            if (value === false) {
750                return this.cookie('sb-login') ? this.cookie('sb-login') : storage('login');
751            }
752            if (CHAT_SETTINGS.cloud) {
753                storage('login', value);
754            } else {
755                this.cookie('sb-login', value, 3650, 'set');
756            }
757        },
758
759        // Login
760        login: function (email = '', password = '', user_id = '', token = '', onSuccess = false) {
761            SBF.ajax({
762                function: 'login',
763                email: email,
764                password: password,
765                user_id: user_id,
766                token: token
767            }, (response) => {
768                if (response != false && Array.isArray(response)) {
769                    this.loginCookie(response[1]);
770                    if (onSuccess) {
771                        onSuccess(response);
772                    }
773                    return true;
774                } else {
775                    return false;
776                }
777            });
778        },
779
780        // Logout
781        logout: function (reload = true) {
782            SBChat.stopRealTime();
783            this.cookie('sb-login', '', '', false);
784            this.cookie('sb-cloud', '', '', false);
785            storage('open-conversation', '');
786            storage('login', '');
787            SBChat.conversations = false;
788            activeUser(false);
789            if (typeof sb_beams_client !== ND) {
790                sb_beams_client.stop();
791            }
792            if (typeof SB_AJAX_URL !== ND) {
793                SBF.ajax({
794                    function: 'logout'
795                }, () => {
796                    SBF.event('SBLogout');
797                    if (reload) {
798                        setTimeout(() => { location.reload() }, 500);
799                    }
800                });
801            }
802        },
803
804        // Return the active user
805        activeUser: function () {
806            return activeUser();
807        },
808
809        // Get the active user
810        getActiveUser: function (database = false, onSuccess) {
811            let app_login = SBApps.login();
812            let token = SBF.getURL('token');
813            if (!app_login && (storage('wp-login') || storage('whmcs-login') || storage('perfex-login') || storage('aecommerce-login'))) {
814                this.cookie('sb-login', '', '', 'delete');
815                activeUser(false);
816                storage('login', '');
817                storage('wp-login', '');
818                storage('whmcs-login', '');
819                storage('perfex-login', '');
820                storage('aecommerce-login', '');
821            }
822            if (token) {
823                history.replaceState(null, '', SBF.URL());
824            }
825            SBF.ajax({
826                function: 'get-active-user',
827                db: database,
828                login_app: JSON.stringify(app_login),
829                user_token: token
830            }, (response) => {
831                if (!response) {
832                    onSuccess();
833                    return false;
834                } else {
835                    if (response.cookie) {
836                        SBF.loginCookie(response.cookie);
837                    }
838                    if (response.user_type) {
839                        if (!admin && SBF.isAgent(response.user_type)) {
840                            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.';
841                            if (!storage('double-login-alert')) {
842                                storage('double-login-alert', true);
843                                alert(message);
844                            }
845                            console.warn('Support Board: ' + message);
846                            SBF.event('SBDoubleLoginError');
847                        } else {
848                            activeUser(new SBUser(response, response.phone ? { phone: response.phone } : {}));
849                            SBPusher.start();
850                            if (app_login) {
851                                storage(app_login[1] + '-login', true);
852                            }
853                            onSuccess();
854                            SBF.event('SBActiveUserLoaded', response);
855                        }
856                    }
857                }
858            });
859        },
860
861        // Clean
862        reset: function () {
863            let cookies = ['sb-login', 'sb-cloud'];
864            for (var i = 0; i < cookies.length; i++) {
865                this.cookie(cookies[i], '', 0, false);
866            }
867            try { localStorage.removeItem('support-board') } catch (e) { }
868            this.logout();
869        },
870
871        // Lightbox
872        lightbox: function (content) {
873            let lightbox = $(admin ? global : main).find('.sb-lightbox-media');
874            lightbox.sbActive(true).find(' > div').html(content);
875            if (admin) {
876                SBAdmin.open_popup = lightbox;
877            }
878        },
879
880        // Manage the local storage
881        storage: function (key, value = ND) {
882            try { if (typeof localStorage == ND) return false } catch (e) { return false }
883            let settings = localStorage.getItem('support-board');
884            if (settings === null) {
885                settings = {};
886            } else {
887                settings = JSON.parse(settings);
888            }
889            if (value === ND) {
890                return key in settings ? settings[key] : false;
891            } else {
892                if (!value) {
893                    delete settings[key];
894                } else {
895                    settings[key] = value;
896                }
897                localStorage.setItem('support-board', JSON.stringify(settings));
898            }
899        },
900
901        // Save the current time or check if the saved time is older than the given hours
902        storageTime: function (key, hours = false) {
903            let today = new Date();
904            if (hours === false) {
905                storage(key, today.getTime());
906            } else {
907                if (storage(key) == false) {
908                    return true;
909                }
910                if ((today.getTime() - storage(key)) > (3600000 * hours)) {
911                    storage(key, false);
912                    return true;
913                }
914                return false;
915            }
916        },
917
918        // Set or get a cookie
919        cookie: function (name, value = false, expiration_days = false, action = 'get', seconds = false) {
920            let cookie_https = location.protocol == 'https:' ? 'SameSite=None;Secure;' : '';
921            let settings = window[admin ? 'SB_ADMIN_SETTINGS' : 'CHAT_SETTINGS'];
922            let domain = settings && settings.cookie_domain ? 'domain=' + settings.cookie_domain + ';' : '';
923            if (action == 'get') {
924                if (!cookies_supported) {
925                    return this.storage(name);
926                }
927                let cookies = document.cookie.split(';');
928                for (var i = 0; i < cookies.length; i++) {
929                    var cookie = cookies[i];
930                    while (cookie.charAt(0) == ' ') {
931                        cookie = cookie.substring(1);
932                    }
933                    if (cookie.indexOf(name) == 0) {
934                        let value = cookie.substring(name.length + 1, cookie.length);
935                        return this.null(value) ? false : value;
936                    }
937                }
938                return false;
939            } else if (action == 'set') {
940                if (!cookies_supported) {
941                    this.storage(name, value);
942                } else {
943                    let date = new Date();
944                    date.setTime(date.getTime() + (expiration_days * (seconds ? 1 : 86400) * 1000));
945                    document.cookie = name + "=" + value + ";expires=" + date.toUTCString() + ";path=/;" + cookie_https + domain;
946                }
947            } else if (this.cookie(name)) {
948                if (!cookies_supported) {
949                    this.storage(name, '');
950                } else {
951                    document.cookie = name + "=" + value + ";expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;" + cookie_https + domain;
952                }
953            }
954        },
955
956        // Return a front setting or set it
957        setting: function (key, value = -1) {
958            if (value !== -1) {
959                if (typeof CHAT_SETTINGS !== ND) CHAT_SETTINGS[key] = value;
960            } else return typeof CHAT_SETTINGS !== ND && key in CHAT_SETTINGS ? CHAT_SETTINGS[key] : false;
961        },
962
963        // Return the shortcode array
964        shortcode: function (shortcode) {
965            return SBRichMessages.shortcode(shortcode);
966        },
967
968        // Events and webhooks
969        event: function (name, parameters) {
970            $(document).trigger(name, parameters);
971            let webhooks = admin ? (typeof SB_ADMIN_SETTINGS === ND ? false : SB_ADMIN_SETTINGS.webhooks) : CHAT_SETTINGS.webhooks;
972            let webhooks_list = { SBGetUser: 'get-user', SBSMSSent: 'sms-sent', SBLoginForm: 'login', SBRegistrationForm: 'registration', SBUserDeleted: 'user-deleted', SBNewMessagesReceived: 'new-messages', SBNewConversationReceived: 'new-conversation', SBSlackMessageSent: 'slack-message-sent', SBMessageDeleted: 'message-deleted', SBRichMessageSubmit: 'rich-message', SBNewEmailAddress: 'new-email-address' };
973            if (webhooks && name in webhooks_list) {
974                if (webhooks !== true) {
975                    if (!Array.isArray(webhooks)) {
976                        webhooks = webhooks.replace(/ /g, '').split(',');
977                    }
978                    if (!webhooks.includes(webhooks_list[name])) {
979                        return;
980                    }
981                }
982                SBF.ajax({
983                    function: 'webhooks',
984                    function_name: name,
985                    parameters: parameters
986                });
987            }
988        },
989
990        // Translate a string
991        translate: function (string) {
992            if ((!admin && SBF.null(CHAT_SETTINGS)) || (admin && typeof SB_TRANSLATIONS === ND)) {
993                return string;
994            }
995            let translations = admin ? SB_TRANSLATIONS : CHAT_SETTINGS.translations;
996            if (translations && translations[string]) {
997                return translations[string] ? translations[string] : string;
998            } else {
999                return string;
1000            }
1001        },
1002
1003        // Escape a string
1004        escape: function (string) {
1005            return string ? string.replace(/</ig, '&lt;').replace(/javascript:|onclick|onerror|ontoggle|onmouseover|onload|oncontextmenu|ondblclick|onmousedown|onmouseenter|onmouseleave|onmousemove|onmouseout|onmouseup/ig, '') : '';
1006        },
1007
1008        // Remove the Support Board syntax from a string
1009        strip: function (message) {
1010            message = message.replace('```', '');
1011            let patterns = [/\*([^\**]+)\*/, /\__([^\____]+)\__/, /\~([^\~~]+)\~/, /\`([^\``]+)\`/];
1012            for (var i = 0; i < 2; i++) {
1013                patterns.forEach(pattern => {
1014                    message = message.replace(pattern, (match) => match.replace(/[\*\_\~\`]/g, ''));
1015                });
1016            }
1017            return message.replace(/\\,/g, ',').replace(/\\:/g, ':');;
1018        },
1019
1020        // Visibility change function
1021        visibilityChange: function (visibility = '') {
1022            this.visibility_status = visibility;
1023            let is_admin = admin && typeof SBAdmin !== ND;
1024            if (visibility == 'hidden') {
1025                if (!admin) {
1026                    SBChat.stopRealTime();
1027                }
1028                SBChat.tab_active = false;
1029                this.visibility_was_hidden = true;
1030            } else {
1031                if (activeUser() && !admin) {
1032                    SBChat.startRealTime();
1033                }
1034                SBChat.tab_active = true;
1035                clearInterval(interval);
1036                clearInterval(SBChat.audio_interval);
1037                if (SBChat.conversation) {
1038                    SBChat.conversation.updateMessagesStatus();
1039                    if (SBChat.chat_open || admin) {
1040                        SBChat.updateNotifications(SBChat.conversation.id);
1041                    }
1042                    if (is_admin) {
1043                        setTimeout(() => {
1044                            SBAdmin.conversations.notificationsCounterReset(SBChat.conversation.id);
1045                        }, 2000);
1046                    }
1047                }
1048                if (is_admin) {
1049                    if ((Date.now() - (mobile ? 60000 : 180000)) > conversation_update_last_check) {
1050                        SBAdmin.conversations.update();
1051                        conversation_update_last_check = Date.now();
1052                    }
1053                    if (mobile) {
1054                        SBChat.update();
1055                    }
1056                }
1057                document.title = document_title;
1058                this.serviceWorker.closeNotifications();
1059            }
1060        },
1061
1062        // Convert a settings string to an Array
1063        settingsStringToArray: function (string) {
1064            if (this.null(string)) {
1065                return [];
1066            }
1067            let result = [];
1068            string = string.split(',');
1069            for (var i = 0; i < string.length; i++) {
1070                let values = string[i].split(':');
1071                result[values[0]] = values[1] == 'false' ? false : values[1] == 'true' ? true : values[1];
1072            }
1073            return result;
1074        },
1075
1076        // Open a browser window
1077        openWindow: function (link, width = 550, height = 350) {
1078            let left = (screen.width / 2) - (width / 2);
1079            let top = (screen.height / 2) - (height / 2);
1080            window.open(link, 'targetWindow', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=' + width + ',height=' + height + ', top=' + top + ', left=' + left);
1081            return false;
1082        },
1083
1084        // Load a JS or CSS file
1085        loadResource: function (src, is_js = false, onLoad = false, content = false) {
1086            let resource = document.createElement(is_js ? 'script' : 'link');
1087            if (src) {
1088                let id = 'sb-' + SBF.stringToSlug(src.split('/').pop());
1089                if (document.getElementById(id)) {
1090                    if (onLoad) {
1091                        onLoad();
1092                    }
1093                    return;
1094                }
1095                if (is_js) {
1096                    resource.src = src;
1097                } else {
1098                    resource.href = src;
1099                }
1100                resource.type = is_js ? 'text/javascript' : 'text/css';
1101                resource.id = id;
1102            } else {
1103                resource.innerHTML = content;
1104            }
1105            if (onLoad) {
1106                resource.onload = function () {
1107                    onLoad();
1108                }
1109            }
1110            if (!is_js) {
1111                resource.rel = 'stylesheet';
1112            }
1113            document.head.appendChild(resource);
1114        },
1115
1116        // Debounce
1117        debounce: function (bounceFunction, id, interval = 500) {
1118            if (!(id in timeout_debounce)) {
1119                timeout_debounce[id] = true;
1120                bounceFunction();
1121                setTimeout(() => {
1122                    delete timeout_debounce[id];
1123                }, interval);
1124            }
1125        },
1126
1127        // Push Notifications
1128        serviceWorker: {
1129            sw: false,
1130            timeout: false,
1131
1132            init: function () {
1133                if (navigator.serviceWorker) {
1134                    navigator.serviceWorker.register(admin || typeof SB_CLOUD_SW != 'undefined' ? SB_URL.replace('/script', '') + '/sw.js?v=' + version : CHAT_SETTINGS.push_notifications_url + '?v=' + version).then((registration) => {
1135                        registration.update();
1136                        this.sw = registration;
1137                    }).catch(function (error) {
1138                        console.warn(error);
1139                    });
1140                }
1141            },
1142
1143            initPushNotifications: function () {
1144                if ((admin && (SB_ADMIN_SETTINGS.push_notifications_provider == 'pusher' || SB_ADMIN_SETTINGS.push_notifications_provider != 'one
1144signal')) || (!admin && (CHAT_SETTINGS.push_notifications_provider == 'pusher' || CHAT_SETTINGS.push_notifications_provider != 'pushalert'))) { // Deprecated: remove || CHAT_SETTINGS.push_notifications_provider != '' and || SB_ADMIN_SETTINGS.push_notifications_provider != 'pushalert'
1145                    SBPusher.initPushNotifications();
1146                } else {
1147                    $.getScript('https://cdn.onesignal.com/sdks/web/v16/OneSignalSDK.page.js', () => {
1148                        window.OneSignalDeferred = window.OneSignalDeferred || [];
1149                        OneSignalDeferred.push((OneSignal) => {
1150                            OneSignal.init({
1151                                appId: SB_ADMIN_SETTINGS.push_notifications_id
1152                            });
1153                        });
1154                        OneSignalDeferred.push((OneSignal) => {
1155                            OneSignal.User.PushSubscription.addEventListener('change', (event) => {
1156                                if (event.current.optedIn) {
1157                                    let external_id = (admin && SB_ADMIN_SETTINGS.cloud ? SB_ADMIN_SETTINGS.cloud.cloud_user_id + '-' : (!admin && CHAT_SETTINGS.cloud ? CHAT_SETTINGS.cloud.cloud_user_id + '-' : '')) + (admin ? SB_ACTIVE_AGENT.id : activeUser().id);
1158                                    if (external_id == 1) {
1159                                        external_id = 'SB-1';
1160                                    }
1161                                    OneSignal.User.addTag('user_type', admin ? 'agents' : 'users');
1162                                    OneSignal.login(external_id);
1163                                }
1164                                SBF.event('SBPushNotificationSubscription', event.current);
1165                            });
1166                        });
1167                        init_push_notifications = false;
1168                    });
1169                }
1170            },
1171
1172            closeNotifications: function (index = 0) {
1173                if (this.sw) {
1174                    this.sw.getNotifications().then((notifications) => {
1175                        if (notifications.length) {
1176                            for (let i = 0; i < notifications.length; i += 1) {
1177                                notifications[i].close();
1178                            }
1179                        } else if (index < 300 && SBF.visibility_status == 'visible') {
1180                            setTimeout(() => {
1181                                this.closeNotifications(index + 1);
1182                            }, 10);
1183                        }
1184                    });
1185                }
1186            },
1187
1188            pushNotification: function (message, interests = false) {
1189                let icon = admin ? SB_ACTIVE_AGENT.profile_image : activeUser().image;
1190                SBF.ajax({
1191                    function: 'push-notification',
1192                    title: admin ? SB_ACTIVE_AGENT.full_name : activeUser().name,
1193                    message: SBF.strip(message),
1194                    icon: icon.indexOf('user.svg') > 0 ? CHAT_SETTINGS.notifications_icon : icon,
1195                    interests: interests ? interests : SBChat.getRecipientUserID(),
1196                    conversation_id: SBChat.conversation ? SBChat.conversation.id : false
1197                }, (response) => {
1198                    return response
1199                });
1200            }
1201        },
1202
1203        beautifyAttachmentName: function (name) {
1204            let index = name.indexOf('_');
1205            return index !== -1 ? name.substring(index + 1) : name;
1206        },
1207
1208        audio: {
1209            play: function (audio_element, parent = false) {
1210                audio_element = $(audio_element);
1211                let audio = audio_element.parent().find('audio').get(0);
1212                let stopped = audio_element.hasClass('sb-icon-play');
1213                let audio_time = audio_element.parent().find('.sb-player
1213-time');
1214                $(parent ? parent : chat).find('audio').each(function () {
1215                    $(this).get(0).pause();
1216                    $(this).unbind('ended');
1217                    $(this).parent().find('.sb-player-btn').removeClass('sb-icon-pause').addClass('sb-icon-play');
1218                });
1219                if (stopped) {
1220                    audio.play();
1221                    audio_element.removeClass('sb-icon-play');
1222                } else {
1223                    audio.pause();
1224                }
1225                $(audio).unbind('ended');
1226                $(audio).bind('ended', () => {
1227                    audio_element.removeClass('sb-icon-pause').addClass('sb-icon-play');
1228                });
1229                audio_element.addClass('sb-icon-' + (stopped ? 'pause' : 'play'));
1230                if (audio.ontimeupdate) {
1231                    audio.removeEventListener('timeupdate', audio.ontimeupdate);
1232                    audio.removeEventListener('loadedmetadata', audio.onloadedmetadata);
1233                }
1234                if (audio_time) {
1235                    audio.ontimeupdate = () => {
1236                        audio_time.find('span:first-child').html(SBF.getMinutesSeconds(audio.currentTime.toFixed(0)));
1237                    }
1238                    audio.onloadedmetadata = () => {
1239                        audio_time.find('span:last-child').html(SBF.getMinutesSeconds(audio.duration.toFixed(0)));
1240                    }
1241                    audio.addEventListener('timeupdate', audio.ontimeupdate);
1242                    audio.addEventListener('timeupdate', audio.onloadedmetadata);
1243                }
1244            },
1245
1246            speed: function (audio_element) {
1247                let parent = $(audio_element).parent();
1248                if (parent.find('.sb-player-btn').hasClass('sb-icon-pause')) {
1249                    let element = $(audio_element).find('.sb-player-speed-number');
1250                    let speed = parseFloat(element.html());
1251                    speed += 0.5;
1252                    if (speed > 2) {
1253                        speed = 1;
1254                    }
1255                    parent.find('audio').get(0).playbackRate = speed;
1256                    element.html(speed);
1257                }
1258            },
1259
1260            download: function (audio_element) {
1261                window.open($(audio_element).parent().find('audio source').attr('src'));
1262            }
1263
1264        }
1265    }
1266
1267    /*
1268    * ----------------------------------------------------------
1269    * PUSHER
1270    * ----------------------------------------------------------
1271    */
1272
1273    var SBPusher = {
1274        channels: {},
1275        channels_presence: [],
1276        active: false,
1277        pusher: false,
1278        started: false,
1279        pusher_beams: false,
1280        initialized: false,
1281        online_ids: [],
1282        beams_loaded: false,
1283
1284        // Initialize Pusher
1285        init: function (onSuccess = false) {
1286            if (SBPusher.active) {
1287                if (this.pusher) {
1288                    return onSuccess ? onSuccess() : true;
1289                } else if (onSuccess) {
1290                    $(window).one('SBPusherInit', () => {
1291                        onSuccess();
1292                    });
1293                } else {
1294                    return;
1295                }
1296                this.initialized = true;
1297                if (typeof Pusher === ND) {
1298                    $.getScript('https://js.pusher.com/8.2.0/pusher.min.js', () => {
1299                        window.Pusher = Pusher;
1300                        this.init_2();
1301                    }, true);
1302                } else {
1303                    this.init_2();
1304                }
1305            }
1306        },
1307
1308        init_2: function () {
1309            this.pusher = new Pusher(admin ? SB_ADMIN_SETTINGS.pusher_key : CHAT_SETTINGS.pusher_key, {
1310                cluster: admin ? SB_ADMIN_SETTINGS.pusher_cluster : CHAT_SETTINGS.pusher_cluster,
1311                channelAuthorization: {
1312                    endpoint: SB_URL + '/include/pusher.php',
1313                    params: {
1314                        login: SBF.loginCookie(),
1315                        cloud_user_id: CHAT_SETTINGS.cloud ? CHAT_SETTINGS.cloud.cloud_user_id : false
1316                    }
1317                }
1318            });
1319            SBF.event('SBPusherInit');
1320        },
1321
1322        // Initialize Push notifications
1323        initPushNotifications: function () {
1324            if (activeUser() || admin) {
1325                if (this.beams_loaded) {
1326                    this.initPushNotifications_2();
1327                } else {
1328                    $.getScript('https://js.pusher.com/beams/2.0.0-beta.0/push-notifications-cdn.js', () => {
1329                        this.initPushNotifications_2();
1330                    }, true);
1331                }
1332            }
1333        },
1334
1335        initPushNotifications_2: function () {
1336            window.navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
1337                this.pusher_beams = new PusherPushNotifications.Client({
1338                    instanceId: admin ? SB_ADMIN_SETTINGS.push_notifications_id : CHAT_SETTINGS.push_notifications_id,
1339                    serviceWorkerRegistration: serviceWorkerRegistration,
1340                });
1341                SBF.serviceWorker.closeNotifications();
1342                this.pusher_beams.start().then(() => this.pusher_beams.setDeviceInterests(admin ? [SB_ACTIVE_AGENT.id, 'agents'] : [activeUser().id, 'users'])).catch(console.error);
1343                init_push_notifications = false;
1344            });
1345        },
1346
1347        // Start Pusher and Push notifications
1348        start: function () {
1349            if (!admin && !this.started && activeUser()) {
1350                if (this.active) {
1351                    this.init(() => {
1352                        this.event('client-typing', (response) => {
1353                            if (response.user_id == SBChat.agent_id && SBChat.conversation && response.conversation_id == SBChat.conversation.id) {
1354                                SBChat.typing(-1, 'start');
1355                                clearTimeout(timeout_typing);
1356                                timeout_typing = setTimeout(() => { SBChat.typing(-1, 'stop') }, 1000);
1357                            }
1358                        });
1359                        this.event('new-message', (response) => {
1360                            if (response && activeUser() && response.conversation_id && (!activeUser().getConversationByID(response.conversation_id) || !SBChat.conversation || SBChat.conversation.id != response.conversation_id)) {
1361                                SBChat.updateConversations();
1362                            } else {
1363                                if (response && response.action && ['message_delete', 'message_update'].includes(response.action)) {
1364                                    SBChat.id_last_message_conversation = response.message_id - 1;
1365                                }
1366                                SBChat.update();
1367                            }
1368                        });
1369                        this.presence(1, () => {
1370                            this.started = true;
1371                            SBChat.automations.runAll();
1372                        });
1373                    });
1374                }
1375                if (CHAT_SETTINGS.push_notifications_users) {
1376                    if (CHAT_SETTINGS.push_notifications_provider == 'pusher' || CHAT_SETTINGS.push_notifications_provider != 'onesignal') { // Deprecated: remove || CHAT_SETTINGS.push_notifications != ''
1377                        if (typeof Notification != ND && Notification.permission == 'granted') {
1378                            this.initPushNotifications();
1379                        } else {
1380                            init_push_notifications = true;
1381                        }
1382                    }
1383                }
1384            }
1385        },
1386
1387        // Subscribe to a channel
1388        subscribe: function (channel_name, onSuccess = false) {
1389            if (!this.pusher) {
1390                return this.init(() => { this.subscribe(channel_name, onSuccess) });
1391            }
1392            channel_name = this.cloudChannelRename(channel_name);
1393            let channel = this.pusher.subscribe(channel_name);
1394            channel.bind('pusher:subscription_error', (error) => {
1395                return console.log(error);
1396            });
1397            channel.bind('pusher:subscription_succeeded', () => {
1398                this.channels[channel_name] = channel;
1399                if (onSuccess) onSuccess();
1400            })
1401        },
1402
1403        // Add event listener for a channel
1404        event: function (event, callback, channel = 'private-user-' + activeUser().id) {
1405            if (!this.pusher) {
1406                return this.init(() => { this.event(event, callback, channel) });
1407            }
1408            let channel_original = channel;
1409            channel = this.cloudChannelRename(channel);
1410            if (channel in this.channels) {
1411                this.channels[channel].unbind(event);
1412                this.channels[channel].bind(event, (data) => {
1413                    callback(data);
1414                });
1415            } else {
1416                this.subscribe(channel_original, () => { this.event(event, callback, channel_original) });
1417            }
1418        },
1419
1420        // Trigger an event
1421        trigger: function (event, data = {}, channel = 'private-user-' + activeUser().id) {
1422            if (event.indexOf('client-') == 0) {
1423                return this.channels[this.cloudChannelRename(channel)].trigger(event, data);
1424            } else {
1425                SBF.ajax({
1426                    function: 'pusher-trigger',
1427                    channel: channel,
1428                    event: event,
1429                    data: data
1430                }, (response) => {
1431                    return response
1432                });
1433            }
1434        },
1435
1436        // Presence  
1437        presence: function (index = 1, onSuccess) {
1438            if (!this.pusher) {
1439                return this.init(() => { this.presence() });
1440            }
1441            let channel = this.pusher.subscribe(this.cloudChannelRename('presence-' + index));
1442            channel.bind('pusher:subscription_succeeded', (members) => {
1443                if (members.count > 98) {
1444                    return this.subscribe(index + 1);
1445                }
1446                members.each((member) => {
1447                    if (this.presenceCheck(member)) {
1448                        this.online_ids.push(member.id);
1449                    }
1450                });
1451                SBChat.updateUsersActivity();
1452                if (onSuccess) {
1453                    onSuccess();
1454                }
1455            })
1456            channel.bind('pusher:subscription_error', (error) => {
1457                return console.log(error);
1458            });
1459            channel.bind('pusher:member_added', (member) => {
1460                if (this.presenceCheck(member)) {
1461                    this.presenceAdd(member.id);
1462                }
1463                if (admin && SBF.storageTime('online-user-notification-' + member.id, 24)) {
1464                    SBAdmin.users.onlineUserNotification(member);
1465                    SBF.storageTime('online-user-notification-' + member.id);
1466                }
1467            });
1468            channel.bind('pusher:member_removed', (member) => {
1469                this.presenceRemove(member.id);
1470            });
1471            this.channels_presence.push(channel);
1472            if (!admin && CHAT_SETTINGS.slack_active) {
1473                this.event('add-user-presence', (response) => {
1474                    this.presenceAdd(response.agent_id)
1475                });
1476                SBF.ajax({
1477                    function: 'slack-presence',
1478                    list: true
1479                }, (response) => {
1480                    for (var i = 0; i < response.length; i++) {
1481                        this.presenceAdd(response[i]);
1482                    }
1483                    SBChat.updateUsersActivity();
1484                });
1485            }
1486        },
1487
1488        presenceCheck: function (member) {
1489            let agent = SBF.isAgent(member.info.user_type);
1490            return ((admin && !agent) || (!admin && agent)) && !this.online_ids.includes(member.id);
1491        },
1492
1493        presenceAdd: function (user_id) {
1494            if (typeof user_id != ND && !this.online_ids.includes(user_id)) {
1495                this.online_ids.push(user_id);
1496                this.presenceUpdateAdmin(user_id);
1497                SBChat.updateUsersActivity(user_id);
1498            }
1499        },
1500
1501        presenceRemove: function (user_id) {
1502            if (typeof user_id == ND) {
1503                return;
1504            }
1505            let index = this.online_ids.indexOf(user_id);
1506            if (index !== -1) {
1507                this.online_ids.splice(index, 1);
1508                this.presenceUpdateAdmin(user_id);
1509                SBChat.updateUsersActivity();
1510            } else if (admin) {
1511                global.find(`.sb-conversation-busy[data-agent="${user_id}"]`).remove();
1512            }
1513        },
1514
1515        presenceUnsubscribe: function () {
1516            for (var i = 0; i < this.channels_presence.length; i++) {
1517                this.channels_presence[i].unsubscribe(this.cloudCh
1517annelRename('presence-' + (i + 1)));
1518            }
1519        },
1520
1521        presenceUpdateAdmin: function (user_id) {
1522            if (admin) {
1523                if (global.find('.sb-area-users.sb-active').length) {
1524                    SBAdmin.users.update();
1525                }
1526                if (activeUser() && activeUser().id == user_id) {
1527                    SBAdmin.users.updateUsersActivity();
1528                }
1529            }
1530        },
1531
1532        cloudChannelRename: function (channel) {
1533            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;
1534        }
1535    }
1536
1537    /*
1538    * ----------------------------------------------------------
1539    * GLOBAL FUNCTIONS
1540    * ----------------------------------------------------------
1541    */
1542
1543    window.SBF = SBF;
1544    window.SBPusher = SBPusher;
1545    window.sb_current_user = sb_current_user;
1546
1547    /*
1548    * ----------------------------------------------------------
1549    * JQUERY FUNCTIONS
1550    * ----------------------------------------------------------
1551    */
1552
1553    $.fn.sbActive = function (show = -1) {
1554        if (show === -1) {
1555            return $(this).hasClass('sb-active');
1556        }
1557        $(this).setClass('sb-active', show);
1558        return this;
1559    };
1560
1561    $.fn.sbLoading = function (value = 'check') {
1562        if (value == 'check') {
1563            return $(this).hasClass('sb-loading');
1564        } else {
1565            $(this).setClass('sb-loading', value);
1566        }
1567        return this;
1568    }
1569
1570    $.fn.sbTogglePopup = function (button = false) {
1571        let showed = true;
1572        if (admin) SBAdmin.open_popup = false;
1573        if ($(this).sbActive()) {
1574            $(this).sbActive(false);
1575            global.removeClass('sb-popup-active');
1576            showed = false;
1577        } else {
1578            global.addClass('sb-popup-active');
1579            global.find('.sb-popup').sbActive(false);
1580            if (button) $(this).css('left', $(button).offset().left + 15).sbActive(true);
1581            if (admin) setTimeout(() => { SBAdmin.open_popup = this }, 500);
1582            SBF.deselectAll();
1583        }
1584        return showed;
1585    };
1586
1587    $.fn.sbUploadFiles = function (onSuccess, index = false) {
1588        let files = $(this).prop('files');
1589        for (var i = (index === false ? 0 : index); i < (index === false ? files.length : index + 1); i++) {
1590            let file = files[i];
1591            let size_mb = file.size / (1024 ** 2);
1592            let max_size = admin ? SB_ADMIN_SETTINGS.max_file_size : CHAT_SETTINGS.max_file_size;
1593            if (size_mb > max_size) {
1594                let message = sb_('Maximum upload size is {R}MB. File size: {R2}MB.').replace('{R}', max_size).replace('{R2}', size_mb.toFixed(2));
1595                SBChat.is_busy_update = false;
1596                SBChat.busy(false);
1597                if (admin) {
1598                    SBAdmin.conversations.busy = false;
1599                    SBAdmin.infoPanel(message, 'info');
1600                } else {
1601                    alert(message);
1602                }
1603            }
1604            let form = new FormData();
1605            form.append('file', file);
1606            SBF.upload(form, onSuccess);
1607        }
1608        $(this).value = '';
1609    }
1610
1611    $.fn.setProfile = function (name = false, profile_image = false) {
1612        if (SBF.null(name)) {
1613            name = activeUser() ? activeUser().name : '';
1614        }
1615        if (SBF.null(profile_image)) {
1616            profile_image = activeUser() ? activeUser().image : SB_URL + '/media/user.svg';
1617        }
1618        if (name) {
1619            $(this).removeClass('sb-profile-empty');
1620        }
1621        $(this).find('img').attr('src', profile_image);
1622        $(this).find('.sb-name').html(name);
1623        return this;
1624    }
1625
1626    $.fn.setClass = function (class_name, add = true) {
1627        if (add) {
1628            $(this).addClass(class_name);
1629        } else {
1630            $(this).removeClass(class_name);
1631        }
1632        return this;
1633    }
1634
1635    /*
1636    * ----------------------------------------------------------
1637    * FUNCTIONS
1638    * ----------------------------------------------------------
1639    */
1640
1641    function sbDelta(e) {
1642        let delta = e.originalEvent.wheelDelta;
1643        if (typeof delta == ND) {
1644            delta = e.originalEvent.deltaY;
1645        }
1646        if (typeof delta == ND) {
1647            delta = e.originalEvent.detail * -1;
1648        }
1649        return delta;
1650    }
1651
1652    function loading(element) {
1653        if ($(element).sbLoading()) {
1654            return true;
1655        } else {
1656            $(element).sbLoading(true);
1657        }
1658        return false;
1659    }
1660
1661    function storage(key, value = ND) {
1662        return SBF.storage(key, value);
1663    }
1664
1665    function sb_(string) {
1666        return SBF.translate(string);
1667    }
1668
1669    function activeUser(value = -1) {
1670        if (value === -1) {
1671            return window.sb_current_user;
1672        } else {
1673            window.sb_current_user = value;
1674        }
1675    }
1676
1677    function setAudio() {
1678        let volume = admin ? SB_ADMIN_SETTINGS.sound.volume : CHAT_SETTINGS.sound.volume;
1679        if (SBChat.audio && volume) {
1680            SBChat.audio.volume = volume;
1681        }
1682    }
1683
1684    /* 
1685    * ----------------------------------------------------------
1686    * USER
1687    * ----------------------------------------------------------
1688    */
1689
1690    class SBUser {
1691        constructor(details = {}, extra = {}) {
1692            this.details = details;
1693            this.extra = extra;
1694            this.conversations = [];
1695            this.processArray(details);
1696        }
1697
1698        get id() {
1699            return this.get('id') ? this.get('id') : this.get('user_id');
1700        }
1701
1702        get type() {
1703            return this.get('user_type');
1704        }
1705
1706        get email() {
1707            return this.get('email');
1708        }
1709
1710        get name() {
1711            return this.details.first_name ? this.details.first_name + (this.details.last_name ? ' ' + this.details.last_name : '') : '';
1712        }
1713
1714        get nameBeautified() {
1715            let default_name = admin ? SB_ADMIN_SETTINGS.visitor_default_name : CHAT_SETTINGS.visitor_default_name;
1716            return !default_name || (this.details.last_name && this.details.last_name.charAt(0) != '#') ? this.name : default_name;
1717        }
1718
1719        get image() {
1720            return this.get('profile_image');
1721        }
1722
1723        get language() {
1724            let language = this.getExtra('language');
1725            if (!language) {
1726                language = this.getExtra('browser_language');
1727            }
1728            return language ? language.value.toLowerCase() : '';
1729        }
1730
1731        get(id) {
1732            if (id in this.details && !SBF.null(this.details[id])) {
1733                return this.details[id];
1734            }
1735            return '';
1736        }
1737
1738        getExtra(id) {
1739            if (id in this.extra && !SBF.null(this.extra[id])) {
1740                return this.extra[id];
1741            }
1742            return '';
1743        }
1744
1745        set(id, value) {
1746            this.details[id] = value;
1747        }
1748
1749        setExtra(id, value) {
1750            this.extra[id] = value;
1751        }
1752
1753        // Initialization
1754        processArray(details) {
1755            if (details && details.details) {
1756                for (var i = 0; i < details.details.length; i++) {
1757                    this.setExtra(details.details[i].slug, details.details[i]);
1758                }
1759                delete details.details;
1760                this.details = details;
1761            }
1762        }
1763
1764        // Get user details and extra details
1765        update(onSuccess) {
1766            if (this.id) {
1767                SBF.ajax({
1768                    function: 'get-user',
1769                    user_id: this.id,
1770                    extra: true
1771                }, (response) => {
1772                    this.processArray(response);
1773                    onSuccess(response);
1774                    SBF.event('SBGetUser', this);
1775                });
1776            } else {
1777                SBF.error('Missing user ID', 'SBUser.update');
1778            }
1779        }
1780
1781        // Get user conversations
1782        getConversations(onSuccess = false, exclude_id) {
1783            if (this.id) {
1784                SBF.ajax({
1785                    function: 'get-user-conversations',
1786                    user_id: this.id,
1787                    exclude_id: exclude_id,
1788                    agent: SBF.isAgent(this.type)
1789                }, (response) => {
1790                    if (!SBF.errorValidation(response)) {
1791                        let conversations = [];
1792                        for (var i = 0; i < response.length; i++) {
1793                            if (SBChat.isConversationAllowed(response[i].source, response[i].conversation_status_code, response[i].payload)) {
1794                                conversations.push(new SBConversation([new SBMessage(response[i])], response[i]));
1795                            }
1796                        }
1797                        this.conversations = conversations;
1798                        if (onSuccess) {
1799                            onSuccess(conversations);
1800                        }
1801                    }
1802                });
1803            } else {
1804                SBF.error('Missing user ID', 'SBUser.getConversations');
1805            }
1806        }
1807
1808        // Get conversations code
1809        getConversationsCode(conversations = false) {
1810            let code = '';
1811            let active_conversation_id = SBChat.conversation ? SBChat.conversation.id : -1;
1812            if (!conversations) {
1813                conversations = this.conversations;
1814            }
1815            for (var i = 0; i < conversations.length; i++) {
1816                if (conversations[i] instanceof SBConversation) {
1817                    if (!admin && !SBChat.isConversationAllowed(conversations[i].get('source'), conversations[i].status_code, conversations[i].getLastMessage() ? JSON.stringify(conversations[i].getLastMessage().payload()) : false)) {
1818                        continue;
1819                    }
1820                    let red_notifications = 0;
1821                    let is_active_conversation = active_conversation_id == conversations[i].id;
1822                    if (!admin && !is_active_conversation) {
1823                        for (var j = 0; j < SBChat.notifications.length; j++) {
1824                            if (SBChat.notifications[j][0] == conversations[i].id) {
1825                                red_notifications++;
1826                            }
1827                        }
1828                    }
1829                    code += `<li ${is_active_conversation ? 'class="sb-active" ' : ''}data-conversation-status="${is_active_conversation ? 0 : conversations[i].status_code}
1829" data-conversation-id="${conversations[i].id}" data-department="${conversations[i].get('department')}">${conversations[i].getCode()}${red_notifications ? '<span data-count="' + red_notifications + '">' + red_notifications + '</span>' : ''}</li>`;
1830                } else {
1831                    SBF.error('Conversation not of type SBConversation', 'SBUser.getConversationsCode');
1832                }
1833            }
1834            return code;
1835        }
1836
1837        // Get single conversation
1838        getFullConversation(conversation_id = false, onSuccess = false) {
1839            if (conversation_id !== false) {
1840                SBF.ajax({
1841                    function: 'get-conversation',
1842                    conversation_id: conversation_id
1843                }, (response) => {
1844                    let messages = [];
1845                    if (response) {
1846                        if (response === 'agent-not-authorized') {
1847                            window.location.href = SBF.URL();
1848                            return;
1849                        }
1850                        for (var i = 0; i < response.messages.length; i++) {
1851                            messages.push(new SBMessage(response.messages[i]));
1852                        }
1853                    }
1854                    if (onSuccess) {
1855                        onSuccess(new SBConversation(messages, response ? response.details : false));
1856                    }
1857                });
1858            } else {
1859                SBF.error('Missing conversation ID', 'SBUser.getFullConversation');
1860            }
1861        }
1862
1863        getConversationByID(conversation_id, index = false) {
1864            for (var i = 0; i < this.conversations.length; i++) {
1865                if (this.conversations[i].id == conversation_id) {
1866                    return index ? i : this.conversations[i];
1867                }
1868            }
1869            return false;
1870        }
1871
1872        // Add a new conversation
1873        addConversation(conversation) {
1874            if (conversation instanceof SBConversation) {
1875                let conversation_id = conversation.id;
1876                let is_new = true;
1877                for (var i = 0; i < this.conversations.length; i++) {
1878                    if (this.conversations[i].id == conversation_id) {
1879                        this.conversations[i] = conversation;
1880                        is_new = false;
1881                        break;
1882                    }
1883                }
1884                if (is_new) {
1885                    this.conversations.unshift(conversation);
1886                }
1887                return is_new;
1888            } else {
1889                SBF.error('Conversation not of type SBConversation', 'SBUser.addConversation');
1890            }
1891        }
1892
1893        // Remove a conversation
1894        removeConversation(conversation_id) {
1895            let index = this.getConversationByID(conversation_id, true);
1896            if (index !== false) {
1897                this.conversations.splice(index, 1);
1898            }
1899        }
1900
1901        // Get the last conversation
1902        getLastConversation() {
1903            if (this.isConversationsEmpty()) {
1904                return false;
1905            }
1906            for (var i in this.conversations) {
1907                let conversation = this.conversations[i];
1908                if (SBChat.isConversationAllowed(conversation.get('source'), conversation.status_code, JSON.stringify(conversation.getLastMessage().payload()))) {
1909                    return conversation;
1910                }
1911            }
1912        }
1913
1914        // Check if the conversation array is empty
1915        isConversationsEmpty() {
1916            if (!this.conversations.length) {
1917                return true;
1918            }
1919            for (var i in this.conversations) {
1920                let conversation = this.conversations[i];
1921                if (SBChat.isConversationAllowed(conversation.get('source'), conversation.status_code, JSON.stringify(conversation.getLastMessage().payload()))) {
1922                    return false;
1923                }
1924            }
1925            return true;
1926        }
1927
1928        // Check if the extra array is empty
1929        isExtraEmpty() {
1930            return Object.keys(this.extra).length === 0 && this.extra.constructor === Object;
1931        }
1932
1933        // Delete the user
1934        delete(onSuccess) {
1935            if (this.id) {
1936                SBF.ajax({
1937                    function: 'delete-user',
1938                    user_id: this.id
1939                }, () => {
1940                    SBF.event('SBUserDeleted', this.id);
1941                    onSuccess();
1942                    return true;
1943                });
1944            } else {
1945                SBF.error('Missing user ID', 'SBUser.delete');
1946            }
1947        }
1948    }
1949    window.SBUser = SBUser;
1950
1951    /* 
1952    * ----------------------------------------------------------
1953    * MESSAGE
1954    * ----------------------------------------------------------
1955    */
1956
1957    class SBMessage {
1958        constructor(details = {}) {
1959            this.details = Object.assign({}, details);
1960            let keys = ['message_status_code', 'message_id', 'message_profile_image', 'message_first_name', 'message_last_name', 'message_user_id', 'message_user_type'];
1961            let keys_delete = ['source', 'extra', 'title', 'tags', 'agent_id', 'department', 'last_update_time', 'conversation_creation_time', 'conversation_id', 'conversation_status_code', 'conversation_user_id'];
1962            for (var i = 0; i < keys.length; i++) {
1963                if (details[keys[i]]) {
1964                    this.details[keys[i].replace('message_', '')] = details[keys[i]];
1965                }
1966                delete this.details[keys[i]];
1967            }
1968            if (this.details.first_name) {
1969                this.details.full_name = this.details.first_name + (this.details.last_name ? ' ' + this.details.last_name : '');
1970            }
1971            if (details.last_update_time) {
1972                this.details.creation_time = details.last_update_time;
1973            }
1974            for (var i = 0; i < keys_delete.length; i++) {
1975                delete this.details[keys_delete[i]];
1976            }
1977            let payload = this.get('payload');
1978            if (payload) {
1979                if (typeof payload === 'string') {
1980                    try {
1981                        var json = JSON.parse(this.get('payload').replace("\\'", "'"));
1982                        if (json && typeof json === 'object') {
1983                            payload = json;
1984                        } else {
1985                            payload = {};
1986                        }
1987                    } catch (e) {
1988                        payload = {};
1989                    }
1990                }
1991            } else {
1992                payload = {};
1993            }
1994            this.set('payload', payload);
1995        }
1996
1997        get id() {
1998            return this.get('id');
1999        }
2000
2001        get attachments() {
2002            return !SBF.null(this.details.attachments) ? JSON.parse(this.details.attachments) : [];
2003        }
2004
2005        get message() {
2006            return admin ? (this.payload('translation') && this.payload('translation-language') == SB_ADMIN_SETTINGS.active_agent_language ? this.payload('translation') : (this.payload('original-message-language') == SB_ADMIN_SETTINGS.active_agent_language ? this.payload('original-message') : this.get('message'))) : this.get('message');
2007        }
2008
2009        get(id) {
2010            if (id in this.details && !SBF.null(this.details[id])) {
2011                return this.details[id];
2012            }
2013            return '';
2014        }
2015
2016        set(id, value) {
2017            this.details[id] = value;
2018        }
2019
2020        payload(key = false, value = false) {
2021            let payload = this.get('payload');
2022            if (key !== false && value !== false) {
2023                payload[key] = value;
2024                this.set('payload', payload);
2025            } else if (key !== false) {
2026                return key in payload ? payload[key] : (payload.id && payload.id == key ? payload : false);
2027            }
2028            return ['boolean', 'string'].includes(typeof payload) ? [] : payload;
2029        }
2030
2031        getCode() {
2032            let agent = SBF.isAgent(this.details.user_type);
2033            let message = this.message;
2034            let attachments = this.attachments;
2035            let reply = this.payload('reply');
2036            let admin_menu = admin ? SBAdmin.conversations.messageMenu(agent, message, !reply && !agent && (!SBChat.conversation || ['tg', 'fb', 'w
2036a', ''].includes(SBChat.conversation.get('source')))) : '';
2037            let attachments_code = '';
2038            let media_code = '';
2039            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 loading="lazy" src="${this.details.profile_image}"><div class="sb-tooltip"><div>${this.details.full_name}</div></div></div>` : '';
2040            let css = ((admin && agent) || (!admin && !agent) ? 'sb-right' : '') + (thumb ? ' sb-thumb-active' : '');
2041            let type = '';
2042            let name = (!admin && agent && CHAT_SETTINGS.sender_name) || (admin && SB_ADMIN_SETTINGS.sender_name == 'chat-admin') ? `<span class="sb-agent-name">${this.get('full_name')}</span>` : '';
2043            let delivery_failed = admin ? this.payload('delivery_failed') : false;
2044
2045            if (!message && !attachments.length) {
2046                if (admin) {
2047                    message = SBAdmin.conversations.getInfoMessage(this);
2048                }
2049                if (!message) {
2050                    return '';
2051                } else {
2052                    css += ' sb-info-message';
2053                }
2054            }
2055            if (reply && SBChat.conversation && SBChat.conversation.getMessage(reply)) {
2056                reply = SBChat.conversation.getMessage(reply);
2057                let is_agent = SBF.isAgent(reply.get('user_type'));
2058                let text = reply.message;
2059                if (!text) {
2060                    text = '<div class="sb-message-attachments">';
2061                    attachments.forEach((attachment) => {
2062                        text += `<a>${attachment[0]}</a>`;
2063                    });
2064                    text += '</div>';
2065                }
2066                reply = `<div class="sb-reply-message${is_agent ? ' sb-reply-agent' : ''}"><span>${(is_agent && admin) || (!is_agent && !admin) ? sb_('You') : reply.get('full_name')}</span> ${text}</div>`;
2067            } else {
2068                reply = '';
2069            }
2070
2071            // Rich Messages
2072            if (agent) {
2073                message = message.replace(/\n/g, '<br>');
2074                message = message.replace(/`([\s\S]*?)`/g, (match) => {
2075                    return match.replace(/\[/g, '&#91;');
2076                });
2077                let shortcodes = message.match(/\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\]/g) || [];
2078                let is_rich_message = false;
2079                let count = shortcodes.length;
2080                for (var i = 0; i < count; i++) {
2081                    let settings = SBRichMessages.shortcode(shortcodes[i]);
2082                    if (settings[0]) {
2083                        if (settings[0] == 'action') {
2084                            message = message.replace(shortcodes[i], '');
2085                        } else {
2086                            let rich_message = SBRichMessages.generate(settings[1], settings[0]);
2087                            if (rich_message) {
2088                                message = message.replace(shortcodes[i], rich_message);
2089                                is_rich_message = true;
2090                                type = `data-type="${settings[0]}"`;
2091                            }
2092                        }
2093                    }
2094                }
2095                if (is_rich_message) {
2096                    css += ' sb-rich-cnt';
2097                    if (count > 1) {
2098                        type = 'data-type="multiple"';
2099                    }
2100                }
2101            } else if (message.includes('[rating ')) {
2102                let settings = SBRichMessages.shortcode(message);
2103                message = SBRichMessages.generate(settings[1], settings[0]);
2104            }
2105            let matches = message.includes('data-success') ? [...message.matchAll(/data-success="([^"]*)"/g)].map(match => match[1]) : [];
2106            for (var i = 0; i < matches.length; i++) {
2107                message = message.replace(matches[i], '{R' + i + '}');
2108            }
2109            message = this.render(message);
2110            for (var i = 0; i < matches.length; i++) {
2111                message = message.replace('{R' + i + '}', matches[i]);
2112            }
2113
2114            // Attachments
2115            if (attachments.length) {
2116                attachments_code = '<div class="sb-message-attachments">';
2117                for (var i = 0; i < attachments.length; i++) {
2118                    let url = attachments[i][1];
2119                    let url_and_name = url + attachments[i][0];
2120                    if (SBF.getFileType(url_and_name) == 'image') {
2121                        let size = '';
2122                        if (attachments[i].length > 2) {
2123                            size = attachments[i][2].split('|');
2124                            size = `width="${size[0]}" style="aspect-ratio: ${size[0]} / ${size[1]}"`;
2125                        }
2126                        media_code += `<div class="sb-image${url_and_name.includes('.png') ? ' sb-image-png' : (url.includes('sticker_') ? ' sb-image-sticker' : '')}"><img loading="lazy" src="${url}" ${size}/></div>`;
2127                    } else if (SBF.getFileType(url_and_name) == 'audio' || url.includes('voice_message') || url.includes('audioclip')) {
2128                        if ((admin && !SB_ADMIN_SETTINGS.speech_recognition) || (!admin && !CHAT_SETTINGS.speech_recognition)) {
2129                            message = '';
2130                        }
2131                        attachments_code += `<div class="sb-player">
2131<div class="sb-player-btn sb-icon-play"></div><div class="sb-player-speed"><div class="sb-player-speed-number">1</div><div class="sb-icon-close"></div></div><div class="sb-player-download sb-icon-arrow-down"></div><audio><source src="${url}" type="audio/mpeg"></audio></div>`;
2132                    } else if (url) {
2133                        attachments_code += `<a rel="noopener" target="_blank" data-type="${SBF.getFileIcon(url)}" href="${url}">${SBF.beautifyAttachmentName(attachments[i][0])}</a>`;
2134                    }
2135                }
2136                attachments_code += '</div>';
2137            }
2138
2139            // Message creation
2140            let message_text = (delivery_failed ? SBAdmin.conversations.getDeliveryFailedMessage(delivery_failed) : '') +  (name + message + media_code).trim();
2141            return !message_text && !attachments_code ? '' : `<div data-id="${this.details.id}" class="${css}" ${type}>${thumb}${reply}<div class="sb-cnt"><div class="sb-message${media_code && !message ? ' sb-message-media' : ''}"${delivery_failed ? ' style="opacity:.7"' : ''}>${message_text}</div>${attachments_code}<div class="sb-time">${SBF.beautifyTime(this.details.creation_time, true)}${admin && agent && this.details.status_code == 2 ? '<i class="sb-icon-check"></i>' : ''}</div></div>${admin_menu}</div>`;
2142        }
2143
2144        render(message = false) {
2145            if (message === false) {
2146                message = '' + this.details.message;
2147            }
2148            let len = message.length;
2149
2150            // Code block
2151            let codes = message.match(/```([\s\S]*?)```/g) || [];
2152            for (var i = 0; i < codes.length; i++) {
2153                message = message.replace(codes[i], '[code-' + i + ']');
2154            }
2155
2156            // Breakline
2157            message = message.replace(/(?:\r\n|\r|\n)/g, '<br>');
2158
2159            // Bold
2160            message = message.replace(/\*([^\**]+)\*/g, "<b>$1</b>");
2161
2162            // Italic
2163            message = message.replace(/__(.+?)__/g, "<i>$1</i>");
2164
2165            // Strikethrough
2166            message = message.replace(/\~([^\~~]+)\~/g, "<del>$1</del>");
2167
2168            // Code
2169            message = message.replace(/\`([^\``]+)\`/g, "<code>$1</code>");
2170
2171            // Single emoji
2172            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])/)) {
2173                message = `<span class="emoji-large">${message}</span>`;
2174            }
2175
2176            // Links
2177            if (message.includes('](http')) {
2178                let temp = message.split('[');
2179                message = '';
2180                for (var i = 0; i < temp.length; i++) {
2181                    if (temp[i].includes('](http')) {
2182                        temp[i] = temp[i].substring(temp[i].indexOf('](') + 2, temp[i].indexOf(')'));
2183                    }
2184                    message += temp[i];
2185                }
2186            }
2187            if (message.includes('www.')) {
2188                message = message.replaceAll('www.', 'https://www.').replaceAll('https://https:', 'https:').replaceAll('http://https:', 'http:');
2189            }
2190            let replace = [['href="http', '[L1]'], ['src="http', '[L2]'], ['url("http', '[L3]'], ['url(\'http', '[L4]'], ['extra="http', '[L5]'], ['data-link="http', '[L6]'], ['data-value="http', '[L7]']];
2191            for (var i = 0; i < replace.length; i++) {
2192                message = message.replaceAll(replace[i][0], replace[i][1]);
2193            }
2194            if (message.includes('http')) {
2195                message = message.autoLink({
2196                    target: '_blank',
2197                    callback: function (url) {
2198                        return url.includes('#sb-') ? `<a href="${url.split('#sb-')[0]}" target="_blank">${url.split('#sb-')[1].replaceAll('--', ' ')}</a>` : null;
2199                    }
2200                });
2201            }
2202            for (var i = 0; i < replace.length; i++) {
2203                message = message.replaceAll(replace[i][1], replace[i][0]);
2204            }
2205
2206            // Code block restore
2207            for (var i = 0; i < codes.length; i++) {
2208                message = message.replace('[code-' + i + ']', '<pre>' + $.trim($.trim(codes[i].replace(/```<br>/g, '```').replace(/<br>```/g, '```')).replace(/```/g, '').replace(/(?:\r\n|\r|\n)/g, '<br>')) + '</pre>');
2209            }
2210
2211            return message.replace(/&amp;lt;/g, '&lt;');
2212        }
2213
2214        strip(message = false) {
2215            return SBF.strip(message === false ? '' + this.details.message : message);
2216        }
2217    }
2218    window.SBMessage = SBMessage;
2219
2220    /* 
2221    * ----------------------------------------------------------
2222    * CONVERSATION
2223    * ----------------------------------------------------------
2224    */
2225
2226    class SBConversation {
2227        constructor(messages, details) {
2228            this.details = SBF.null(details) ? {} : details;
2229            if (Array.isArray(messages)) {
2230                this.messages = [];
2231                if (messages.length) {
2232                    if (messages[0] instanceof SBMessage) {
2233                        this.messages = messages;
2234                    } else {
2235                        SBF.error('Messages not of type SBMessage', 'SBConversation.constructor');
2236                    }
2237                }
2238            } else {
2239                SBF.error('Message array not of type Array', 'SBConversation.constructor');
2240            }
2241            let keys = ['conversation_id', 'conversation_user_id', 'conversation_first_name', 'conversation_last_name', 'conversation_profile_image', 'conversation_user_type', 'conversation_creation_time', 'conversation_status_code'];
2242            let keys_delete = ['payload'];
2243            for (var i = 0; i < keys.length; i++) {
2244                if (details[keys[i]]) {
2245                    this.details[keys[i].replace('conversation_', '')] = details[keys[i]];
2246                }
2247                delete this.details[keys[i]];
2248            }
2249            for (var i = 0; i < keys_delete.length; i++) {
2250                delete this.details[keys_delete[i]];
2251            }
2252            if (details) {
2253                this.details.tags = 'tags' in details ? (typeof details.tags === 'string' ? details.tags.split(',') : details.tags) : [];
2254            }
2255        }
2256
2257        get id() {
2258            return this.get('id');
2259        }
2260
2261        get status_code() {
2262            return this.get('status_code');
2263        }
2264
2265        get(id) {
2266            if (id in this.details && !SBF.null(this.details[id])) {
2267                return this.details[id];
2268            }
2269            if (id == 'title') {
2270                if (this.details.title) {
2271                    return this.details.title;
2272                } else if (this.details.first_name) {
2273                    return this.details.first_name + ' ' + this.details.last_name;
2274                } else if (this.messages.length) {
2275                    return this.messages[0].get('full_name');
2276                }
2277            }
2278            return '';
2279        }
2280
2281        set(id, value) {
2282            this.details[id] = value;
2283        }
2284
2285        getMessage(id) {
2286            for (var i = 0; i < this.messages.length; i++) {
2287                if (this.messages[i].id == id) {
2288                    this.messages[i].set('index', i);
2289                    return this.messages[i];
2290                }
2291            }
2292            return false;
2293        }
2294
2295        getLastMessage(include_hidden_messages = false) {
2296            let index = this.messages.length - 1;
2297            if (include_hidden_messages) {
2298                return index > -1 ? this.messages[index] : false;
2299            }
2300            for (var i = index; i > -1; i--) {
2301                if (this.messages[i].message || this.messages[i].attachments.length || this.messages[i].payload('preview')) {
2302                    return this.messages[i];
2303                }
2304            }
2305            return false;
2306        }
2307
2308        getLastUserMessage(index = false, agent = false) {
2309            if (index === false) {
2310                index = this.messages.length - 1;
2311            }
2312            for (var i = index; i > -1; i--) {
2313                let message = this.messages[i];
2314                let user_type = message.get('user_type');
2315                if ((message.message || message.attachments.length) && ((!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)))) {
2316                    this.messages[i].set('index', i - 1);
2317                    return this.messages[i];
2318                }
2319            }
2320            return false;
2321        }
2322
2323        getNextMessage(message_id, user_type = false) {
2324            let count = this.messages.length;
2325            for (var i = 0; i < count; i++) {
2326                if (this.messages[i].id == message_id && i < (count - 1)) {
2327                    for (var j = i + 1; j < count; j++) {
2328                        let message = this.messages[j];
2329                        let message_user_type = message.get('user_type');
2330                        let next_message = this.messages[i + 1];
2331                        if (!user_type || (user_type == 'agent' && SBF.isAgent(message_user_type)) || (user_type == 'user' && !SBF.isAgent(message_user_type))) {
2332                            return next_message;
2333                        }
2334                    }
2335                    break;
2336                }
2337            }
2338            return false;
2339        }
2340
2341        getUserMessages(user_type = 'user') {
2342            let results = [];
2343            let checks = user_type == 'user' ? ['visitor', 'lead', 'user'] : (user_type == 'agents' ? ['agent', 'admin'] : ['bot']);
2344            for (var i = 0; i < this.messages.length; i++) {
2345                if (checks.includes(this.messages[i].get('user_type'))) {
2346                    this.messages[i].set('index', i);
2347                    results.push(this.messages[i]);
2348                }
2349            }
2350            return results;
2351        }
2352
2353        updateMessage(id, message) {
2354            if (message instanceof SBMessage) {
2355                for (var i = 0; i < this.messages.length; i++) {
2356                    if (this.messages[i].id == id) {
2357                        this.messages[i] = message;
2358                        return true;
2359                    }
2360                }
2361            } else {
2362                SBF.error('Message not of type SBMessage', 'SBConversation.updateMessage');
2363            }
2364            return false;
2365        }
2366
2367        addMessages(messages) {
2368            if (Array.isArray(messages)) {
2369                for (var i = 0; i < messages.length; i++) {
2370                    if (messages[i] instanceof SBMessage) {
2371                        this.messages.push(messages[i]);
2372                    }
2373                }
2374            } else {
2375                if (messages instanceof SBMessage) {
2376                    this.messages.push(messages);
2377                } else {
2378                    SBF.error('Messages not of type SBMessage', 'SBConversation.addMessages()');
2379                }
2380            }
2381            return this;
2382        }
2383
2384        getCode(text_only = false) {
2385            let message = this.getLastMessage();
2386            if (message) {
2387                let text = message.message;
2388                if (!text && message.payload('preview')) {
2389                    text = message.payload('preview');
2390                }
2391                if (admin) {
2392                    text = message.payload().preview ? message.payload().preview : text;
2393                }
2394                text = text.replace(/(\r\n|\n|\r)/gm, ' ');
2395                if (text.indexOf('[') !== false) {
2396                    let shortcodes = text.match(/\[.+?\]/g) || [];
2397                    if (shortcodes.length) {
2398                        let shortcode = SBRichMessages.shortcode(shortcodes[0]);
2399                        if (shortcode[0]) {
2400                            text = text.replace(shortcodes[0], shortcode[0] == 'action' ? '' : sb_(shortcode[1].message ? shortcode[1].message : (shortcode[1].title ? shortcode[1].title : (shortcode[1].name && shortcode[1].name != 'false' && shortcode[1].name != 'true' ? shortcode[1].name : (shortcode[1].link ? shortcode[1].link : (shortcode[1].values ? shortcode[1].values.replaceAll(',', ', ').replaceAll('  ', ' ') : (shortcode[1].options ? shortcode[1].options.replaceAll(',', ', ').replaceAll('  ', ' ') : sb_(SBF.slugToString(shortcode[0])))))))));
2401                        }
2402                    }
2403                }
2404                if (!text && message.attachments.length) {
2405                    for (var i = 0; i < message.attachments.length; i++) {
2406                        text += message.attachments[i][0] + ' ';
2407                    }
2408                }
2409                text = SBF.strip(text);
2410                if (text.length > 114) {
2411                    text = text.substr(0, 114) + ' ...';
2412                }
2413                if (text_only) {
2414                    return text;
2415                }
2416                let title = this.get('title');
2417                if (!title || (activeUser() && activeUser().name == title) || (tickets && CHAT_SETTINGS.tickets_conversations_title_user)) {
2418                    title = activeUser() && message.get('user_id') == activeUser().id ? sb_('You') : message.get('full_name');
2419                }
2420                if (SBF.getFileType(title) == 'image') {
2421                    title = `<img src="${title}" />
2421${title.includes('t=') ? ` <span>${new URL(title).searchParams.get('t')}</span>` : ''}`;
2422                }
2423                return `<div class="sb-conversation-item" data-user-id="${this.get('user_id')}"><img loading="lazy" 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}</div></div>`;
2424            }
2425            return '';
2426        }
2427
2428        deleteMessage(id) {
2429            for (var i = 0; i < this.messages.length; i++) {
2430                if (this.messages[i].id == id) {
2431                    this.messages.splice(i, 1);
2432                    return true;
2433                }
2434            }
2435            return false;
2436        }
2437
2438        searchMessages(search, exact_match = false) {
2439            let results = [];
2440            for (var i in this.messages) {
2441                let message = this.messages[i].message;
2442                let payload = JSON.stringify(this.messages[i].payload());
2443                if ((exact_match && (message == search || payload == search)) || (!exact_match && (message.includes(search) || payload.includes(search)))) {
2444                    this.messages[i].set('index', i);
2445                    results.push(this.messages[i]);
2446                }
2447            }
2448            return results;
2449        }
2450
2451        getAttachments() {
2452            let list = [];
2453            for (var i = 0; i < this.messages.length; i++) {
2454                let attachments = this.messages[i].attachments;
2455                for (var j = 0; j < attachments.length; j++) {
2456                    let link = attachments[j][1];
2457                    list.push([attachments[j][0], link, link.substr(link.lastIndexOf('.') + 1), this.messages[i].id]);
2458                }
2459            }
2460            return list;
2461        }
2462
2463        updateMessagesStatus(ids = false) {
2464            if (ids) {
2465                for (var i = 0; i < this.messages.length; i++) {
2466                    let id = this.messages[i].id;
2467                    if (ids.includes(id)) {
2468                        let div = chat.find(`[data-id="${id}"] .sb-time`);
2469                        if (!div.find('i').length) {
2470                            div.append('<i class="sb-icon-check"></i>');
2471                        }
2472                    }
2473                }
2474            } else if (!admin && SBF.visibility_status == 'visible') {
2475                ids = [];
2476                for (var i = 0; i < this.messages.length; i++) {
2477                    let message = this.messages[i];
2478                    if (SBF.isAgent(message.get('user_type')) && message.get('status_code') != 2) {
2479                        ids.push(message.id);
2480                        message.set('status_code', 2);
2481                    }
2482                }
2483                if (ids.length) {
2484                    SBF.ajax({ function: 'update-messages-status', message_ids: ids });
2485                }
2486            }
2487        }
2488    }
2489    window.SBConversation = SBConversation;
2490
2491    /* 
2492    * ----------------------------------------------------------
2493    * CHAT
2494    * ----------------------------------------------------------
2495    */
2496
2497    var SBChat = {
2498        emoji_options: { range: 0, range_limit: 47, list: [], list_now: [], touch: false },
2499        initialized: false,
2500        editor_listening: false,
2501        conversation: false,
2502        is_busy: false,
2503        is_busy_update: false,
2504        is_busy_populate: false,
2505        chat_open: false,
2506        real_time: false,
2507        agent_id: -1,
2508        agent_online: false,
2509        user_online: false,
2510        expanded: false,
2511        main_header: true,
2512        start_header: false,
2513        desktop_notifications: false,
2514        flash_notifications: false,
2515        id_last_message: 0,
2516        id_last_message_conversation: 0,
2517        datetime_last_message_conversation: '2000-01-01 00:00:00',
2518        audio: false,
2519        audio_interval: false,
2520        tab_active: true,
2521        notifications: storage('notifications') ? storage('notifications') : [],
2522        typing_settings: { typing: false, sent: false, timeout: false },
2523        email_sent: false,
2524        dashboard: false,
2525        articles: false,
2526        articles_allowed_ids: false,
2527        articles_category: false,
2528        skip: false,
2529        queue_interval: false,
2530        departments: false,
2531        default_department: null,
2532        default_agent: null,
2533        default_tags: null,
2534        offline_message_set: false,
2535        label_date: false,
2536        label_date_show: false,
2537
2538        // Send a message
2539        sendMessage: function (user_id = -1, message = '', attachments = [], onSuccess = false, payload = false, conversation_status_code = false) {
2540            let is_dialogflow_human_takeover = dialogflow_human_takeover && SBApps.dialogflow.active();
2541            let is_return = false;
2542            let conversation = this.conversation;
2543            let reply_id = chat_editor.find('> [data-reply]').attr('data-reply');
2544            let edit_message_id = chat_editor.find('> [data-edit-message]').attr('data-edit-message');
2545            if (!payload) {
2546                payload = {};
2547            }
2548
2549            // Check settings and contents
2550            if (!activeUser() && !admin) {
2551                this.addUserAndLogin(() => {
2552                    return this.sendMessage(user_id, message, attachments, onSuccess, payload);
2553                }, true);
2554                this.busy(true);
2555                return;
2556            }
2557            if (!conversation) {
2558                let last_conversation = admin ? false : activeUser().getLastConversation();
2559                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'))) {
2560                    this.openConversation(last_conversation.id);
2561                    this.setConversation(last_conversation);
2562                    force_action = false;
2563                } else {
2564                    this.newConversation(conversation_status_code, user_id, '', [], (admin && SB_ACTIVE_AGENT.department ? SB_ACTIVE_AGENT.department : null), null, () => {
2565                        return this.sendMessage(user_id, message, attachments, onSuccess, payload);
2566                    });
2567                    this.busy(true);
2568                    return;
2569                }
2570            }
2571            this.calculateLabelDateFirst();
2572            if (user_id == -1) {
2573                user_id = admin ? SB_ACTIVE_AGENT.id : activeUser().id;
2574            }
2575            let is_user = user_id != bot_id;
2576            if (!message && !attachments.length) {
2577                message = chat_textarea.val().trim();
2578                chat_editor.find('.sb-attachments > div').each(function () {
2579                    let attachment = [$(this).attr('data-name'), $(this).attr('data-value')];
2580                    if ($(this).attr('data-size')) {
2581                        attachment.push($(this).attr('data-size'));
2582                    }
2583                    attachments.push(attachment);
2584                });
2585                if (admin && SBAdmin.must_translate && message) {
2586                    SBApps.dialogflow.translate([message], activeUser().language, (response) => {
2587                        if (response.length) {
2588                            let language = admin ? SB_ADMIN_SETTINGS.active_agent_language : activeUser().language;
2589                            if (!payload) {
2590                                payload = {};
2591                            }
2592                            payload['original-message'] = message;
2593                            payload['original-message-language'] = language;
2594                            if (response[0]) {
2595                                message = response[0];
2596                            }
2597                        }
2598                        this.sendMessage(user_id, message, attachments, onSuccess, payload, conversation_status_code);
2599                    });
2600                    is_return = true;
2601                }
2602            }
2603            this.busy(true);
2604            if (is_user) {
2605                chat_textarea.val('').css('height', '');
2606                chat_editor.find('.sb-attachments').html('');
2607            }
2608            chat_editor.sbActive(false);
2609            if (is_return) {
2610                return;
2611            }
2612            if (conversation_status_code === false && user_id == bot_id) {
2613                conversation_status_code = 'skip';
2614            }
2615            if (!admin && is_user && !is_dialogflow_human_takeover) {
2616                conversation_status_code = 2;
2617            }
2618            if (reply_id) {
2619                payload.reply = message;
2620            }
2621
2622            // Send message
2623            if (message || attachments.length || payload) {
2624                let message_response = { user_id: user_id, user: activeUser(), conversation_id: conversation.id, conversation: conversation, conversation_status_code: conversation_status_code, attachments: attachments, payload: payload };
2625                SBF.ajax({
2626                    function: edit_message_id ? 'update-message' : 'send-message',
2627                    user_id: user_id,
2628                    conversation_id: conversation.id,
2629                    message: message,
2630                    attachments: attachments,
2631                    conversation_status_code: conversation_status_code,
2632                    queue: !admin && CHAT_SETTINGS.queue && is_user,
2633                    payload: payload,
2634                    recipient_id: admin ? activeUser().id : false,
2635                    message_id: edit_message_id
2636                }, (response) => {
2637                    let send_slack = admin || !is_dialogflow_human_takeover || response.human_takeover_active
2638
2639                    // Update the dashboard conversations area
2640                    if (!admin && user_id == bot_id) {
2641                        if (this.dashboard) {
2642                            this.updateConversations();
2643                        } else if (!this.chat_open) {
2644                            this.updateNotifications(conversation.id, response.id);
2645                        }
2646                    }
2647
2648                    // Update the chat current conversation
2649                    if ((admin && !this.user_online) || (!admin && !this.agent_online)) {
2650                        this.update();
2651                    }
2652
2653                    // Follow up and offline messages
2654                    if (!admin && is_user && !dialogflow_human_takeover) {
2655                        this.followUp();
2656                        this.offlineMessage();
2657                    }
2658
2659                    // Chatbot
2660                    if (!admin && is_user && (!payload || payload.id != 'sb-human-takeover')) {
2661                        SBApps.dialogflow.message(message, attachments);
2662                    }
2663
2664                    // Slack and visitor to lead
2665                    if (!admin && is_user && activeUser().type == 'visitor') {
2666                        SBF.ajax({ function: 'update-user-to-lead', user_id: user_id }, () => {
2667                            activeUser().set('user_type', 'lead');
2668                            if (CHAT_SETTINGS.slack_active && send_slack) {
2669                                SBApps.slack.message(user_id, activeUser().name, activeUser().image, message, attachments);
2670                            }
2671                        });
2672                    } else if (send_slack && !this.skip) {
2673                        if (admin && SB_ADMIN_SETTINGS.slack_active) {
2674                            SBApps.slack.message(activeUser().id, SB_ACTIVE_AGENT.full_name, SB_ACTIVE_AGENT.profile_image, message, attachments);
2675                        } else if (CHAT_SETTINGS.slack_active) {
2676                            SBApps.slack.message(activeUser().id, (is_user ? activeUser().name : CHAT_SETTINGS.bot_name), (is_user ? activeUser().image : CHAT_SETTINGS.bot_image), message, attachments);
2677                        }
2678                    }
2679
2680                    // Language detection
2681                    if (is_user && CHAT_SETTINGS.language_detection && conversation && message.split(' ').length && message.length > 3 && !SBF.storage('language-detection-completed')) {
2682                        SBF.ajax({ function: 'google-language-detection-update-user', user_id: user_id, string: message, token: SBApps.dialogflow.token }, (response) => {
2683                            if (response) {
2684                                CHAT_SETTINGS.translations = response;
2685                            }
2686                        });
2687                        SBF.storage('language-detection-completed', true);
2688                    }
2689
2690                    // Articles
2691                    if (this.articles && !admin && CHAT_SETTINGS.articles && !CHAT_SETTINGS.office_hours && !this.isInitDashboard()) {
2692                        setTimeout(() => {
2693                            if (this.conversation && conversation.id == this.conversation.id) {
2694                                this.sendMessage(bot_id, '[articles]');
2695                                this.scrollBottom();
2696                                this.articles = false;
2697                            }
2698                        }, 5000);
2699                    }
2700
2701                    // Queue
2702                    if (response.queue) {
2703                        this.queue(this.conversation.id);
2704                    }
2705
2706                    // Events
2707                    message_response.message = response.message;
2708                    message_response.message_id = response.id;
2709                    SBF.event(edit_message_id ? 'SBMessageUpdated' : 'SBMessageSent', message_response);
2710                    if (tickets) {
2711                        SBTickets.onMessageSent();
2712                    }
2713                    if (onSuccess) {
2714                        onSuccess(message_response);
2715                    }
2716                    if (response.notifications && response.notifications.length) {
2717                        SBF.event('SBNotificationsSent', response.notifications);
2718                    }
2719
2720                    // Miscellaneous
2721                    if (this.skip) {
2722                        this.skip = false;
2723                    }
2724                    if (admin) {
2725                        SBAdmin.conversations.previous_editor_text = false;
2726                    }
2727                    if (edit_message_id) {
2728                        chat.find(`[data-id="${edit_message_id}"] .sb-message`).html(message);
2729                        SBChat.cancelEdit();
2730                    }
2731
2732                    this.busy(false);
2733                });
2734
2735                // Display the message as sending in progress
2736                if (is_user && !edit_message_id) {
2737                    message = SBF.escape(message);
2738                    chat.append((new SBMessage({ id: 'sending', profile_image: (admin ? SB_ACTIVE_AGENT.profile_image : activeUser().image), full_name: admin ? SB_ACTIVE_AGENT.full_name : activeUser().name, creation_time: '0000-00-00 00:00:00', message: message.replaceAll('<', '&lt;'), user_type: (admin ? 'agent' : 'user') })).getCode().replace('<div class="sb-time"></div>', `<div class="sb-time">${sb_('Sending')}<i></i></div>`));
2739                }
2740                if (!this.dashboard && (is_user || this.isBottom())) {
2741                    this.scrollBottom();
2742                }
2743            } else {
2744                this.busy(false);
2745            }
2746        },
2747
2748        // Update message
2749        updateMessage: function (message_id, message = '') {
2750            SBF.ajax({
2751                function: 'update-message',
2752                message_id: message_id,
2753                message: message
2754            });
2755        },
2756
2757        // Email notifications
2758        sendEmail: function (message, attachments, send_to_active_user = false, onSuccess = false) {
2759            let recipient_id = send_to_active_user ? (send_to_active_user === true ? activeUser().id : send_to_active_user) : this.getRecipientUserID();
2760            if (!admin && !isNaN(recipient_id) && this.agent_online) {
2761                return false;
2762            }
2763            SBF.ajax({
2764                function: 'create-email',
2765                recipient_id: recipient_id,
2766                sender_name: admin ? (send_to_active_user ? SB_ACTIVE_AGENT.full_name : activeUser().name) : (send_to_active_user ? CHAT_SETTINGS.bot_name : activeUser().name),
2767                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),
2768                message: message,
2769                attachments: attachments,
2770                department: this.conversation ? this.conversation.get('department') : false,
2771                conversation_id: this.conversation ? this.conversation.id : false
2772            }, (response) => {
2773                if (onSuccess) onSuccess(response);
2774            });
2775        },
2776
2777        // SMS notifications
2778        sendSMS: function (message) {
2779            let recipient_id = this.getRecipientUserID();
2780            if (!admin && !isNaN(recipient_id) && this.agent_online) return false;
2781            SBF.ajax({
2782                function: 'send-sms',
2783                to: recipient_id,
2784                message: message,
2785                conversation_id: this.conversation ? this.conversation.id : false
2786            }, (response) => {
2787                if (response.status == 'sent' || response.status == 'queued') {
2788                    SBF.event('SBSMSSent', { recipient_id: this.getRecipientUserID(), message: message, response: response });
2789                } else if (response.message) {
2790                    SBF.error(response.message, 'SBChat.sendSMS');
2791                }
2792            });
2793        },
2794
2795        // Desktop notifications
2796        desktopNotification: function (title, message, icon, conversation_id = false, user_id = false) {
2797            if (Notification.permission !== 'granted') {
2798                Notification.requestPermission();
2799            } else {
2800                let notify = SBF.serviceWorker.sw.showNotification(title, {
2801                    body: SBF.strip(message),
2802                    icon: icon.indexOf('user.svg') > 0 ? CHAT_SETTINGS.notifications_icon : icon
2803                });
2804                notify.onclick = () => {
2805                    if (admin) {
2806                        if (conversation_id) {
2807                            SBAdmin.conversations.openConversation(conversation_id, user_id == false ? activeUser().id : user_id);
2808                            SBAdmin.conversations.update();
2809                        } else if (user_id) {
2810                            SBAdmin.profile.show(user_id);
2811                        }
2812                    } else {
2813                        this.start();
2814                    }
2815                    window.focus();
2816                }
2817            }
2818        },
2819
2820        // Return the recipient user ID 
2821        getRecipientUserID: function () {
2822            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')));
2823        },
2824
2825        // Editor submit message
2826        submit: function () {
2827            if (!this.is_busy) {
2828                if (audio_recorder_dom && audio_recorder_dom.sbActive()) {
2829                    let button = audio_recorder_dom.find('.sb-btn-mic');
2830                    audio_recorder_dom.sbActive(false);
2831                    if (button.hasClass('sb-icon-pause')) {
2832                        button.click();
2833                    }
2834                    setTimeout(() => {
2835                        let form = new FormData();
2836                        let source = this.conversation ? this.conversation.get('source') : false;
2837                        if (source == 'wa' || audio_recorder_chunks.length) {
2838                            form.append('file', new File([source == 'wa' ? SBAudioRecorder.blob() : new Blob(audio_recorder_chunks, { type: source == 'ig' ? 'audio/wav' : 'audio/mp3' })], 'voice_message.' + (source == 'ig' ? 'wav' : 'mp3')));
2839                            SBF.upload(form, (response) => {
2840                                SBChat.uploadResponse(response);
2841                                this.submit();
2842                            });
2843                        } else {
2844                            this.submit();
2845                        }
2846                        audio_recorder_dom.find('.sb-icon-close').click();
2847                    }, 100);
2848                    return;
2849                }
2850                this.sendMessage();
2851                if (CHAT_SETTINGS.cron_email_piping_active) {
2852                    setTimeout(() => {
2853                        SBF.ajax({ function: 'email-piping' });
2854                        CHAT_SETTINGS.cron_email_piping_active = true;
2855                    }, 60000);
2856                    CHAT_SETTINGS.cron_email_piping_active = false;
2857                }
2858                if (init_push_notifications) {
2859                    SBF.serviceWorker.initPushNotifications();
2860                }
2861                if (admin) {
2862                    SBAdmin.conversations.setStatus(1, false, true);
2863                }
2864                SBChat.cancelReply();
2865            }
2866        },
2867
2868        // Initialize the chat
2869        initChat: function () {
2870            if (admin) return;
2871            SBF.getActiveUser(true, () => {
2872                let active = activeUser() !== false;
2873                let user_type = active ? activeUser().type : false;
2874                if (!tickets && CHAT_SETTINGS.popup && !storage('popup') && (!mobile || !CHAT_SETTINGS.popup_mobile_hidden)) {
2875                    this.popup();
2876                }
2877                SBChat.automations.runAll();
2878                if (!tickets && CHAT_SETTINGS.privacy && !CHAT_SETTINGS.registration_required && !storage('privacy-approved')) {
2879                    this.privacy();
2880                    return;
2881                }
2882                if (typeof Notification !== ND && !CHAT_SETTINGS.push_notifications_users && (['all', 'users'].includes(CHAT_SETTINGS.desktop_notifications) || (admin && CHAT_SETTINGS.desktop_notifications == 'agents'))) {
2883                    this.desktop_notifications = true;
2884                }
2885                if (['all', 'users'].includes(CHAT_SETTINGS.flash_notifications) || (admin && CHAT_SETTINGS.flash_notifications == 'agents')) {
2886                    this.flash_notifications = true;
2887                }
2888                if (this.registration(true) && !tickets) {
2889                    this.registration();
2890                    if (!active && CHAT_SETTINGS.visitors_registration) {
2891                        this.addUserAndLogin();
2892                    }
2893                    return;
2894                }
2895                if (!active && (typeof SB_WP_WAITING_LIST !== ND || CHAT_SETTINGS.visitors_registration || CHAT_SETTINGS.welcome || tickets || CHAT_SETTINGS.flow_on_load) && (!tickets || !CHAT_SETTINGS.tickets_registration_required)) {
2896                    this.addUserAndLogin(() => {
2897                        this.welcome();
2898                        SBApps.dialogflow.flowOnLoad();
2899                        SBApps.woocommerce.waitingList();
2900                        this.finalizeInit();
2901                    });
2902                } else if (!this.conversation && active) {
2903                    this.populateConversations();
2904                } else {
2905                    this.finalizeInit();
2906                }
2907                if (CHAT_SETTINGS.header_name && active && user_type == 'user' && !tickets) {
2908                    chat_header.find('.sb-title').html(sb_(CHAT_SETTINGS.header_name).replace('{user_name}', activeUser().nameBeautified));
2909                }
2910                this.welcome();
2911                if (!SBPusher.active) {
2912                    setInterval(() => {
2913                        this.updateConversations();
2914                        this.updateUsersActivity();
2915                    }, 10200);
2916                }
2917                SBApps.dialogflow.flowOnLoad();
2918                SBApps.woocommerce.waitingList();
2919                this.scrollBottom(true);
2920            });
2921        },
2922
2923        finalizeInit: function () {
2924            if (!this.initialized) {
2925                main.attr('style', '');
2926                if (!admin && !tickets) {
2927                    if (this.isInitDashboard()) {
2928                        this.showDashboard();
2929                    }
2930                    if (!mobile && window.innerHeight < 760) {
2931                        main.find(' > .sb-body').css('max-height', (window.innerHeight - 130) + 'px');
2932                    }
2933                }
2934                this.initialized = true;
2935                if (!admin) {
2936                    if (activeUser() && !this.registration(true)) {
2937                        if (SBF.getURL('conversation')) {
2938                            this.openConversation(SBF.getURL('conversation'), () => {
2939                                if (SBF.getURL('rating')) {
2940                                    setTimeout(() => { SBRichMessages.rating(); }, 600);
2941                                    let agent = SBChat.conversation.getLastUserMessage(false, true);
2942                                    SBF.ajax({
2943                                        function: 'set-rating',
2944                                        conversation_id: SBF.getURL('conversation'),
2945                                        agent_id: agent ? agent.get('user_id') : false,
2946                                        user_id: activeUser().id,
2947                                        rating: SBF.getURL('rating')
2948                                    });
2949                                }
2950                            });
2951                        } else if (storage('open-conversation')) {
2952                            this.openConversation(storage('open-conversation'));
2953                        }
2954                    }
2955                    if (!this.chat_open && ((!mobile && storage('chat-open')) || SBF.getURL('chat') == 'open') || SBF.getURL('conversation')) {
2956                        setTimeout(() => { this.start(); }, 500);
2957                    }
2958
2959                    if (CHAT_SETTINGS.woocommerce_returning_visitor) {
2960                        if (storage('returning-visitor') === false) {
2961                            SBF.storageTime('returning-visitor');
2962                        } else if (SBF.storageTime('returning-visitor', 24) && !storage('returning-visitor-processed')) {
2963                            setTimeout(() => {
2964                                SBF.ajax({
2965                                    function: 'woocommerce-returning-visitor'
2966                                }, () => {
2967                                    storage('returning-visitor-processed', true);
2968                                });
2969                            }, 15000);
2970                        }
2971                    }
2972                    if (CHAT_SETTINGS.timetable_type) {
2973                        SBChat.offlineMessage();
2974                    }
2975                    if (CHAT_SETTINGS.queue_human_takeover && SBApps.dialogflow.humanTakeoverActive()) {
2976                        CHAT_SETTINGS.queue = true;
2977                    }
2978                    $(window).on('resize', function () {
2979                        if (!mobile && window.innerHeight < 760) {
2980                            main.find(' > .sb-body').css('max-height', (window.innerHeight - 130) + 'px');
2981                        }
2982                    });
2983                    SBApps.dialogflow.flowOnLoad();
2984                }
2985                if (tickets) {
2986                    SBTickets.init();
2987                }
2988                SBF.event('SBInit');
2989            }
2990        },
2991
2992        // Initialize the chat settings and open the chat
2993        start: function () {
2994            if (this.initialized) {
2995                this.populate();
2996                this.headerAgent();
2997                this.updateUsersActivity();
2998                this.startRealTime();
2999                this.popup(true);
3000                if (this.conversation) {
3001                    this.updateNotifications(this.conversation.id);
3002                }
3003                main.sbActive(true);
3004                $('body').addClass('sb-chat-open');
3005                this.chat_open = true;
3006                if (CHAT_SETTINGS.welcome_trigger == 'open' && !this.registration(true)) {
3007                    this.welcome();
3008                }
3009                SBApps.martfury.privateChat();
3010                this.calculateLabelDates();
3011            }
3012        },
3013
3014        // Open or close the chat
3015        open: function (open = true) {
3016            if (open && !this.chat_open) {
3017                this.start();
3018                this.chat_open = true;
3019                this.startRealTime();
3020                main.sbActive(true);
3021                $('body').addClass('sb-chat-open');
3022                storage('chat-open', true);
3023                if (this.conversation) {
3024                    storage('last-open-message', this.conversation.getLastMessage().id);
3025                }
3026                if (mobile) {
3027                    history.pushState({ 'chat-open': true }, '', '');
3028                }
3029                SBF.event('SBChatOpen');
3030            } else if (!open && this.chat_open) {
3031                main.sbActive(false);
3032                this.stopRealTime();
3033                this.chat_open = false;
3034                storage('chat-open', false);
3035                $('body').removeClass('sb-chat-open');
3036                SBF.event('SBChatClose');
3037            }
3038        },
3039
3040        // Get a full conversation and display it in the chat
3041        openConversation: function (conversation_id, onSuccess = false) {
3042            activeUser().getFullConversation(conversation_id, (response) => {
3043                if (!response.id || !SBChat.isConversationAllowed(response.get('source'), response.status_code, JSON.stringify(response && response.getLastMessage() ? response.getLastMessage().payload() : {}))) {
3044                    storage('open-conversation', '');
3045                    return false;
3046                }
3047                this.setConversation(response);
3048                this.hideDashboard();
3049                this.populate();
3050                this.main_header = false;
3051                if (storage('chat-open') && !mobile) {
3052                    SBChat.open();
3053                }
3054                if (storage('queue') == conversation_id) {
3055                    this.queue(conversation_id);
3056                }
3057                if (this.chat_open || tickets) {
3058                    this.updateNotifications(conversation_id);
3059                }
3060                if (tickets) {
3061                    SBTickets.activateConversation(response);
3062                }
3063                if (onSuccess) {
3064                    onSuccess(response);
3065                }
3066                storage('open-conversation', conversation_id);
3067                SBF.event('SBConversationOpen', response);
3068            });
3069        },
3070
3071        // Update the active conversation with the latest messages
3072        update: function () {
3073            if (this.conversation) {
3074                if (this.is_busy_update) return;
3075                let last_message = this.conversation.getLastMessage(true);
3076                let is_update = false;
3077                SBF.ajax({
3078                    function: 'get-new-messages',
3079                    conversation_id: this.conversation.id,
3080                    datetime: this.datetime_last_message_conversation,
3081                    last_id: this.id_last_message_conversation
3082                }, (response) => {
3083                    let count = response.length;
3084                    this.is_busy_update = false;
3085                    if (this.conversation) {
3086                        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)) {
3087                            let code = '';
3088                            let messages = [];
3089                            let id_check = [];
3090                            let is_activation = false;
3091
3092                            // Generate and add the new messages
3093                            this.calculateLabelDateFirst();
3094                            for (var i = 0; i < count; i++) {
3095                                if (!id_check.includes(response[i].id) && (!admin || this.conversation.id == response[i].conversation_id)) {
3096                                    let message = new SBMessage(response[i]);
3097                                    let payload = message.payload();
3098                                    this.id_last_message_conversation = message.id;
3099                                    this.datetime_last_message_conversation = message.get('creation_time');
3100
3101                                    // Payload
3102                                    if (payload.event) {
3103                                        let event = payload.event;
3104                                        if ((event == 'delete-message' && this.conversation.getMessage(message.id) !== false) || (!admin && !message.message && !message.attachments.length && !payload)) {
3105                                            this.deleteMessage(message.id);
3106                                        }
3107                                        if (event == 'woocommerce-update-cart' && !admin) {
3108                                            SBApps.woocommerce.updateCart(payload.action, payload.id);
3109                                        }
3110                                        if (event == 'woocommerce-checkout' && !admin) {
3111                                            SBApps.wordpress.ajax('url', { url_name: 'checkout' }, (response) => {
3112                                                setTimeout(() => document.location = response, 500);
3113                                            });
3114                                        }
3115                                        if (!SBApps.dialogflow.active() && (event == 'conversation-status-update-3' || event == 'conversation-status-update-4' || event == 'activate-bot')) {
3116                                            SBApps.dialogflow.active('activate');
3117                                            is_activation = true;
3118                                        }
3119                                        if (event == 'conversation-status-update-3') {
3120                                            this.conversationArchived();
3121                                        }
3122                                    }
3123                                    if (payload['human-takeover'] && CHAT_SETTINGS.queue_human_takeover) {
3124                                        CHAT_SETTINGS.queue = true;
3125                                        SBChat.queue(SBChat.conversation.id);
3126                                    }
3127                                    if (payload['human-takeover-fallback']) {
3128                                        SBApps.dialogflow.typing_enabled = false;
3129                                    }
3130
3131                                    // Message
3132                                    if (this.conversation.getMessage(response[i].id)) {
3133                                        this.conversation.updateMessage(message.id, message);
3134                                        chat.find(`[data-id="${message.id}"]`).replaceWith(message.getCode());
3135                                        is_update = true;
3136                                    } else {
3137                                        if (message.message || message.attachments.length) {
3138                                            chat.find(`[data-id="sending"]`).remove();
3139                                        }
3140                                        if (this.conversation.id == response[i].conversation_id) {
3141                                            this.conversation.addMessages(message);
3142                                            code += message.getCode();
3143                                            is_update = false;
3144                                        }
3145                                    }
3146                                    this.conversation.updateMessagesStatus();
3147                                    messages.push(message);
3148                                    id_check.push(message.id);
3149                                    if (this.chat_open) {
3150                                        storage('last-open-message', message.id);
3151                                    }
3152                                    if (!admin && ((this.dashboard || !this.chat_open || !this.tab_active) && (message.get('user_id') != activeUser().id) && (message.message || message.attachments.length))) {
3153                                        this.updateNotifications(this.conversation.id, message.id);
3154                                    }
3155                                }
3156                            }
3157                            chat.append(code);
3158
3159                            // Update status code
3160                            let last_message = this.conversation.getLastMessage();
3161                            let user_type = last_message ? last_message.get('user_type') : false;
3162                            let is_agent = SBF.isAgent(user_type);
3163                            let is_agent_human = is_agent && user_type != 'bot';
3164                            if (!admin && is_agent_human) {
3165                                if (this.chat_open) {
3166                                    if (last_message && !last_message.message.includes('sb-rich-success') && (!response[0].payload || !response[0].payload.includes('conversation-status-update'))) {
3167                                        this.setConversationStatus(0);
3168                                    }
3169                                    if (CHAT_SETTINGS.follow) {
3170                                        clearTimeout(timeout);
3171                                    }
3172                                }
3173                                if (!is_activation) {
3174                                    SBApps.dialogflow.active(false);
3175                                }
3176                            }
3177
3178                            // Queue
3179                            if (storage('queue') == this.conversation.id && is_agent_human) {
3180                                this.queue('clear');
3181                            }
3182
3183                            // Flash notifications
3184                            if (messages.length && (!(SBF.null(messages[0].message) && SBF.null(messages[0].attachments)) || count != 1)) {
3185                                if (!admin && !this.tab_active) {
3186                                    this.flashNotification();
3187                                }
3188
3189                                // Sound notifications
3190                                if (this.audio && ((!admin && is_agent && CHAT_SETTINGS.sound) || (admin && !is_agent && SB_ADMIN_SETTINGS.sound))) {
3191                                    this.playSound();
3192                                }
3193                            }
3194
3195                            // Miscellaneous
3196                            this.headerAgent();
3197                            if (!is_update && !this.dashboard) {
3198                                this.scrollBottom();
3199                                setTimeout(() => { this.scrollBottom() }, 300);
3200                            }
3201                            if (CHAT_SETTINGS.auto_open && (this.dashboard || !this.chat_open)) {
3202                                this.open();
3203                            }
3204                            if (is_agent_human) {
3205                                this.typing(-1, 'stop');
3206                            }
3207                            this.busy(false);
3208                            SBF.event('SBNewMessagesReceived', { messages: messages, conversation_id: this.conversation.id });
3209                            if (tickets) {
3210                                SBTickets.onNewMessageReceived(messages[0], this.conversation.id);
3211                            }
3212                        }
3213                    }
3214                });
3215                this.is_busy_update = true;
3216                setTimeout(() => { this.is_busy_update = false }, 5000);
3217            } else {
3218                this.updateConversations();
3219            }
3220        },
3221
3222        // Update the user conversations list with the latest conversations and messages
3223        updateConversations: function () {
3224            if (activeUser()) {
3225                SBF.ajax({
3226                    function: 'get-new-user-conversations',
3227                    datetime: this.id_last_message
3228                }, (response) => {
3229                    if (response.length) {
3230                        this.id_last_message = response[0].message_id;
3231                        if (this.chat_open) {
3232                            storage('last-open-message', this.id_last_message);
3233                        }
3234                        for (var i = 0; i < response.length; i++) {
3235                            let status_code = response[i].conversation_status_code;
3236                            if (!SBChat.isConversationAllowed(response[i].source, status_code, response[i].payload)) {
3237                                continue;
3238                            }
3239                            let conversation_id = response[i].conversation_id;
3240                            let message = new SBMessage(response[i]);
3241                            let conversation = new SBConversation([message], response[i]);
3242                            let is_new = activeUser().addConversation(conversation);
3243
3244                            // Red notifications
3245                            if (response[i].message_user_id != activeUser().id && (this.conversation.id != conversation_id || !this.chat_open) && (message.message || message.attachments.length)) {
3246                                this.updateNotifications(conversation_id, message.id);
3247                                if (CHAT_SETTINGS.auto_open) {
3248                                    this.open();
3249                                }
3250                            }
3251
3252                            // Payload
3253                            let payload = message.payload();
3254                            if (typeof payload !== 'boolean' && payload.event) {
3255                                let event = payload.event;
3256                                if (event == 'open-chat') {
3257                                    if (this.conversation.id != conversation_id || this.dashboard) {
3258                                        this.openConversation(conversation_id);
3259                                    }
3260                                    if (!mobile) {
3261                                        setTimeout(() => { this.open() }, 500);
3262                                    }
3263                                }
3264                                if (!message.message && !message.attachments.length) {
3265                                    continue;
3266                                }
3267                            }
3268
3269                            if (!this.tab_active) {
3270
3271                                // Desktop notifications
3272                                if (this.desktop_notifications) {
3273                                    SBChat.desktopNotification(message.get('full_name'), message.message, message.get('profile_image'));
3274                                }
3275
3276                                // Flash notifications
3277                                if (this.flash_notifications) {
3278                                    this.flashNotification();
3279                                }
3280
3281                                // Sound notifications
3282                                if (!admin && this.audio && CHAT_SETTINGS.sound && (!this.chat_open || this.dashboard || this.conversation.id != conversation_id) && !(SBF.null(message.message) && SBF.null(message.attachments))) {
3283                                    this.playSound();
3284                                }
3285                            }
3286                            if (is_new) {
3287                                SBF.event('SBNewConversationReceived', conversation);
3288                            }
3289                            if (tickets) {
3290                                SBTickets.onConversationReceived(conversation);
3291                            }
3292                            if (!this.conversation && is_new) {
3293                                this.openConversation(conversation.id);
3294                            }
3295                        }
3296                        if (this.conversation) {
3297                            this.conversation.updateMessagesStatus();
3298                        }
3299                        main.find('.sb-user-conversations').html(activeUser().getConversationsCode());
3300                        main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3);
3301                    }
3302                });
3303            }
3304        },
3305
3306        // Generate the conversation code and display it
3307        populate: function () {
3308            if (this.conversation) {
3309                let code = '';
3310                let notify = chat.find(' > .sb-notify-message');
3311                let last_date = false;
3312                let conversation_id = this.conversation.id;
3313                for (var i = 0; i < this.conversation.messages.length; i++) {
3314                    let message = this.conversation.messages[i];
3315                    let current_date = SBF.beautifyTime(message.get('creation_time'));
3316                    if (current_date.includes('today')) {
3317                        current_date = `<span>${sb_('Today')}</span>`;
3318                    }
3319                    if (current_date != last_date && (message.message || message.attachments.length || (admin && ['terminate_call', 'incoming_call', 'call_permission_request', 'call_response', 'conversation-department-update', 'conversation-agent-update', 'conversation-status-update'].includes(message.payload('event'))))) {
3320                        code += `<div class="sb-label-date">${current_date}</div>`;
3321                        last_date = current_date;
3322                    }
3323                    code += message.getCode();
3324                }
3325                chat.html((notify.length ? notify[0].outerHTML : '') + code);
3326                if (!this.dashboard) {
3327                    this.scrollBottom();
3328                    this.calculateLabelDates();
3329                    if ((admin && SB_ADMIN_SETTINGS.notify_email_cron) || (!admin && CHAT_SETTINGS.notify_email_cron)) {
3330                        let last_message = this.conversation.getLastUserMessage(false, !admin);
3331                        if (last_message && last_message.id != storage('email-cron-' + conversation_id)) {
3332                            SBF.ajax({ function: 'remove-email-cron', conversation_id: conversation_id });
3333                            storage('email-cron-' + conversation_id, last_message.id);
3334                        }
3335                    }
3336                }
3337            } else if (activeUser() && !activeUser().isConversationsEmpty()) {
3338                if (CHAT_SETTINGS.disable_dashboard) {
3339                    this.openConversation(activeUser().conversations[0].id);
3340                } else {
3341                    this.showDashboard();
3342                }
3343            }
3344        },
3345
3346        // Populate the dashboard with all conversations
3347        populateConversations: function (onSuccess = false) {
3348            if (!this.is_busy_populate && activeUser()) {
3349                this.is_busy_populate = true;
3350                setTimeout(() => { this.is_busy_populate = false }, 5000);
3351                activeUser().getConversations((response) => {
3352                    let count = response.length;
3353                    let converstion_ids = [];
3354                    if (count) {
3355                        let now = Date.now();
3356                        let last_message = response[0].messages[0];
3357                        this.id_last_message = last_message.id;
3358                        for (var i = 0; i < count; i++) {
3359                            converstion_ids.push(response[i].id);
3360                            if (!tickets && (response[i].status_code == 1 && storage('last-open-message') < last_message.id) && (!this.conversation || this.conversation.id != response[i].id)) {
3361                                this.updateNotifications(response[i].id, last_message.id);
3362                            }
3363                            if (!mobile && (now - SBF.UTC(response[i].messages[0].get('creation_time'))) < 6000) {
3364                                this.open();
3365                            }
3366                        }
3367                        main.find('.sb-user-conversations').html(activeUser().getConversationsCode());
3368                        main.find('.sb-dashboard-conversations').setClass('sb-conversations-hidden', main.find('.sb-user-conversations > li').length > 3);
3369                    }
3370                    main.setClass('sb-no-conversations', !count);
3371                    if ((!this.initialized || force_action == 'open-conversation') && count == 1 && !this.isInitDashboard() && !storage('open-conversation')) {
3372                        this.openConversation(activeUser().getLastConversation().id);
3373                        if (force_action == 'open-conversation') {
3374                            force_action = '';
3375                        }
3376                    }
3377                    for (var i = 0; i < SBChat.notifications.length; i++) {
3378                        this.updateNotifications(SBChat.notifications[i][0], converstion_ids.includes(SBChat.notifications[i][0]) ? SBChat.notifications[i][1] : false);
3379                    }
3380                    if (onSuccess) {
3381                        onSuccess(response);
3382                    }
3383                    this.finalizeInit();
3384                    SBF.event('SBPopulateConversations', { conversations: response });
3385                });
3386            }
3387        },
3388
3389        // Create a new conversation and optionally send the first message
3390        newConversation: function (status_code, user_id = - 1, message = '', attachments = [], department = null, agent_id = null, onSuccess = false) {
3391            if (activeUser()) {
3392                SBF.ajax({
3393                    function: 'new-conversation',
3394                    status_code: status_code,
3395                    title: tickets ? main.find('.sb-ticket-title input').val() : null,
3396                    department: SBF.null(department) ? this.default_department : department,
3397                    agent_id: SBF.null(agent_id) ? this.default_agent : agent_id,
3398                    tags: this.default_tags,
3399                    source: tickets ? 'tk' : ''
3400                }, (response) => {
3401                    if (SBF.errorValidation(response, 'user-not-found')) {
3402                        this.addUserAndLogin(() => {
3403                            this.newConversation(status_code, user_id, message, attachments, department, agent_id, onSuccess);
3404                        });
3405                        return;
3406                    }
3407                    let conversation = new SBConversation([], response.details);
3408                    this.setConversation(conversation);
3409                    if (message || attachments.length) {
3410                        this.sendMessage(user_id, message, attachments);
3411                    }
3412                    if (user_id != bot_id) {
3413                        setTimeout(() => { this.queue(conversation.id) }, 1000);
3414                    }
3415                    if (onSuccess) {
3416                        onSuccess(conversation);
3417                    }
3418                });
3419
3420            } else {
3421                SBF.error('activeUser() not setted', 'SBChat.newConversation');
3422            }
3423        },
3424
3425        // Set an existing conversation as active conversation
3426        setConversation: function (conversation) {
3427            if (conversation instanceof SBConversation) {
3428                let conversations = activeUser().conversations;
3429                let is_new = true;
3430                this.conversation = conversation;
3431                this.id_last_message_conversation = !this.conversation.getLastMessage(true) ? 0 : this.conversation.getLastMessage(true).id;
3432                this.datetime_last_message_conversation = this.conversation.getLastMessage(true) == false ? '2000-01-01 00:00:00' : this.conversation.getLastMessage(true).get('creation_time');
3433                if (conversation.id != this.conversation.id) {
3434                    this.queue(conversation.id);
3435                }
3436                for (var i = 0; i < conversations.length; i++) {
3437                    if (conversations[i].id == conversation.id) {
3438                        conversations[i] = conversation;
3439                        is_new = false;
3440                        break;
3441                    }
3442                }
3443                if (is_new) {
3444                    conversations.push(conversation);
3445                }
3446                storage('open-conversation', conversation.id);
3447                SBApps.dialogflow.typing_enabled = true;
3448                this.headerAgent();
3449                SBF.event('SBActiveConversationChanged', conversation);
3450            } else {
3451                SBF.error('Value not of type SBConversation', 'SBChat.setConversation');
3452            }
3453        },
3454
3455        // Manage all the queue functionalities
3456        queue: function (conversation_id) {
3457            if (conversation_id == 'clear') {
3458                main.removeClass('sb-notify-active sb-queue-active');
3459                chat.find(' > .sb-notify-message').remove();
3460                clearInterval(this.queue_interval);
3461                this.queue_interval = false;
3462                storage('queue', '');
3463                if (CHAT_SETTINGS.queue_sound && !SBChat.tab_active) {
3464                    SBChat.playSound(999);
3465                }
3466                return;
3467            }
3468            if (!admin && CHAT_SETTINGS.queue) {
3469                SBF.ajax({
3470                    function: 'queue',
3471                    conversation_id: conversation_id,
3472                    department: this.conversation.get('department')
3473                }, (response) => {
3474                    chat.find(' > .sb-notify-message').remove();
3475                    let position = response[0];
3476                    if (position == 0) {
3477                        this.queue('clear');
3478                    } else {
3479                        let time = (!CHAT_SETTINGS.queue_response_time ? 5 : parseInt(CHAT_SETTINGS.queue_response_time)) * position;
3480                        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>');
3481                        if (response[1]) {
3482                            chat.prepend(`<div class="sb-notify-message sb-rich-cnt"><div class="sb-cnt"><div class="sb-message">${text}</div></div></div>`);
3483                        }
3484                        if (this.queue_interval === false) {
3485                            this.queue_interval = setInterval(() => { this.queue(conversation_id) }, 10100);
3486                            if (response[1]) {
3487                                main.addClass('sb-notify-active sb-queue-active');
3488                            }
3489                            storage('queue', conversation_id);
3490                        }
3491                    }
3492                    SBF.event('SBQueueUpdate', position);
3493                });
3494            }
3495        },
3496
3497        // Get the departments details and generate the department code
3498        getDepartmentCode(department_id, onSuccess) {
3499            if (this.departments) {
3500                if (department_id == 'all') {
3501                    let code = '';
3502                    for (var key in this.departments) {
3503                        this.getDepartmentCode(this.departments[key].id, (response) => { code += response; });
3504                    }
3505                    onSuccess(code);
3506                } else {
3507                    onSuccess(`<div data-color="${this.departments[department_id].color}">${this.departments[department_id].image ? `<img loading="lazy" src="${this.departments[department_id].image}" />` : ''}<div>${this.departments[department_id].name}<div></div>`);
3508                }
3509            } else {
3510                SBF.ajax({
3511                    function: 'get-departments'
3512                }, (response) => {
3513                    if (response) {
3514                        this.departments = response;
3515                        this.getDepartmentCode(department_id, onSuccess);
3516                    }
3517                });
3518            }
3519        },
3520
3521        // Start and stop the real time check of new messages
3522        startRealTime: function () {
3523            if (SBPusher.active) return;
3524            this.stopRealTime();
3525            this.real_time = setInterval(() => {
3526                this.update();
3527                this.typing(admin ? (activeUser() ? activeUser().id : -1) : this.agent_id, 'check');
3528            }, 1000);
3529        },
3530
3531        stopRealTime: function () {
3532            clearInterval(this.real_time);
3533        },
3534
3535        // Check if the agent is online and set the online status of the active user
3536        updateUsersActivity: function (presence_add_user_id = false) {
3537            SBF.updateUsersActivity(activeUser() ? activeUser().id : false, this.agent_id, (response) => {
3538                if (!this.typing_settings.typing) {
3539                    if (response == 'online' || this.agent_id == bot_id || ((!this.conversation || (this.conversation && !this.dashboard)) && this.agent_id == -1 && (agents_online || (presence_add_user_id && (!activeUser() || presence_add_user_id != activeUser().id))))) {
3540                        $(chat_status).addClass('sb-status-online').html(sb_('Online'));
3541                        this.agent_online = this.agent_id != bot_id;
3542                    } else {
3543                        $(chat_status).removeClass('sb-status-online').html(sb_('Away'));
3544                        this.agent_online = false;
3545                    }
3546                }
3547            });
3548        },
3549
3550        // Show the loading icon and put the chat in busy mode
3551        busy: function (value) {
3552            if (chat_editor) {
3553                chat_editor.find('.sb-loader').sbActive(value);
3554            }
3555            this.is_busy = value;
3556            SBF.event('SBBusy', value);
3557        },
3558
3559        // Manage the agent header
3560        headerAgent: function (is_default_chatbot = false) {
3561            if (!admin && !tickets && !this.dashboard && (this.agent_id == -1 || (this.conversation && this.conversation.getLastMessage() && SBF.isAgent(this.conversation.getLastMessage().get('user_type')) && this.conversation.getLastMessage().get('user_id') != this.agent_id))) {
3562                let agent = this.lastAgent(false);
3563                let is_continue = true;
3564                agent = agent && SBApps.dialogflow.humanTakeoverActive() && SBPusher.active && !SBPusher.presenceCheck({ info: { user_type: 'agent' }, id: agent.user_id }) ? agent : this.lastAgent();
3565                if (!agent && (!this.conversation || (this.conversation && !this.dashboard))) {
3566                    if (CHAT_SETTINGS.is_chatbot_active) {
3567                        is_default_chatbot = true;
3568                    } else {
3569                        is_continue = !chat_header.hasClass('sb-header-agent');
3570                        if (is_continue) {
3571                            agent = chat_header.hasClass('sb-header-type-agents') ? { user_id: chat_header.find('.sb-profiles > div:first-child').attr('data-id'), full_name: chat_header.find('.sb-profiles > div:first-child > span').text().trim(), profile_image: chat_header.find('img').eq(0).attr('src') } : { user_id: -1, full_name: chat_header.find('.sb-title').text().trim(), profile_image: chat_header.find('img').attr('src') };
3572                        }
3573                    }
3574                }
3575                if (!agent && is_default_chatbot) {
3576                    agent = { user_id: CHAT_SETTINGS.bot_id, full_name: CHAT_SETTINGS.bot_name, profile_image: CHAT_SETTINGS.bot_image };
3577                }
3578                if (agent) {
3579                    let replace_agent_animation = this.agent_id != agent.user_id && chat_header.hasClass('sb-header-agent');
3580                    this.headerReset();
3581                    this.agent_id = agent.user_id;
3582                    chat_header.addClass('sb-header-agent').attr('data-agent-id', this.agent_id);
3583                    if (replace_agent_animation) {
3584                        chat_header.find('.sb-profile').addClass('sb-replace-agent-animation');
3585                        setTimeout(() => {
3586                            chat_header.find('img').attr('src', agent['profile_image']);
3587                            chat_header.find('.sb-name').html(agent['full_name']);
3588                        }, 500);
3589                    } else {
3590                        chat_header.html(`<div class="sb-dashboard-btn sb-icon-arrow-left"></div><div class="sb-profile">${agent.profile_image ? `<img loading="lazy" 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 ${CHAT_SETTINGS.close_chat ? 'sb-close-chat' : 'sb-responsive-close-btn'}"></i>${mobile ? '' : '<i class="sb-icon sb-icon-arrows-' + (main.hasClass('sb-chat-large') ? 'in' : 'out') + ' sb-enlarge-chat-widget"></i>'}</div><div class="sb-label-date-top"></div>`);
3591                    }
3592                    chat_status = chat_header.find('.sb-status');
3593                    this.updateUsersActivity();
3594                    this.label_date = chat_header.find('.sb-label-date-top');
3595                    if (SBF.storageTime('header-animation', 1)) {
3596                        this.headerAnimation();
3597                    }
3598                } else if (is_continue) {
3599                    this.headerReset();
3600                    chat_header.html(this.start_header[0]).addClass(this.start_header[1]);
3601                }
3602            }
3603        },
3604
3605        headerReset: function () {
3606            if (this.start_header == false) {
3607                this.start_header = [chat_header.html(), chat_header.attr('class')];
3608            }
3609            chat_header.removeClass('sb-header-main sb-header-brand sb-header-agent sb-header-minimal');
3610            this.main_header = false;
3611        },
3612
3613        headerAnimation: function () {
3614            chat_header.addClass('sb-header-animation');
3615            setTimeout(() => { chat_header.removeClass('sb-header-animation') }, 8000);
3616            SBF.storageTime('header-animation');
3617        },
3618
3619        // Return the last agent of the active conversation
3620        lastAgent: function (bot = true) {
3621            let agent = false;
3622            if (this.conversation) {
3623                let message = this.conversation.getLastUserMessage(false, bot ? 'all' : true);
3624                if (message) {
3625                    agent = { user_id: message.get('user_id'), full_name: message.get('full_name'), profile_image: message.get('profile_image') };
3626                }
3627            }
3628            return agent;
3629        },
3630
3631        // Scroll the chat to the bottom
3632        scrollBottom: function (top = false) {
3633            setTimeout(() => {
3634                chat_scroll_area.scrollTop(top ? 0 : chat_scroll_area[0].scrollHeight);
3635                this.scrollHeader();
3636            }, 20);
3637        },
3638
3639        // Check if the chat is at bottom
3640        isBottom: function () {
3641            return chat_scroll_area[0].scrollTop === (chat_scroll_area[0].scrollHeight - chat_scroll_area[0].offsetHeight);
3642        },
3643
3644        // Enlarge or shrink the chat widget
3645        resize: function (is_enlarge = true) {
3646            let height = window.innerHeight - 130;
3647            let button = chat_header.find('.sb-enlarge-chat-widget');
3648            main.setClass('sb-chat-large', is_enlarge);
3649            main.find(' > .sb-body').css('max-height', (is_enlarge ? height : (height > 650 ? 650 : height)) + 'px');
3650            button.removeClass('sb-icon-arrows-in sb-icon-arrows-out');
3651            button.addClass(is_enlarge ? 'sb-icon-arrows-in' : 'sb-icon-arrows-out');
3652        },
3653
3654        // Dashboard header animation
3655        scrollHeader: function () {
3656            if (this.main_header && this.dashboard) {
3657                let scroll = chat_scroll_area.scrollTop();
3658                if (scroll > -1 && scroll < 1000) {
3659                    chat_header.find('.sb-content').css({ 'opacity': (1 - (scroll / 500)), 'top': (scroll / 10 * -1) + 'px' });
3660                };
3661            }
3662        },
3663
3664        // Display the dashboard area 
3665        showDashboard: function () {
3666            if (!admin && !tickets) {
3667                main.addClass('sb-dashboard-active');
3668                chat_header.removeClass('sb-header-agent');
3669                this.hidePanel()
3670                if (this.start_header) {
3671                    chat_header.html(this.start_header[0]).addClass(this.start_header[1]);
3672                }
3673                chat_scroll_area.find(' > div').sbActive(false);
3674                main.find('.sb-dashboard').sbActive(true);
3675                this.populateConversations();
3676                this.conversation = false;
3677                this.agent_id = -1;
3678                this.stopRealTime();
3679                this.dashboard = true;
3680                this.main_header = true;
3681                this.scrollBottom(true);
3682                this.resize(false);
3683                SBF.event('SBDashboard');
3684            }
3685        },
3686
3687        // Hide the dashboard area
3688        hideDashboard: function () {
3689            if (!admin && !tickets) {
3690                chat.sbActive(true);
3691                main.removeClass('sb-dashboard-active').find('.sb-dashboard').sbActive(false);
3692                this.dashboard = false;
3693                this.headerAgent();
3694                this.scrollHeader(0);
3695                if (this.chat_open) {
3696                    this.startRealTime();
3697                }
3698                SBF.event('SBDashboardClosed');
3699            }
3700        },
3701
3702        // Show a chat panel
3703        showPanel: function (name, title) {
3704            if (tickets) {
3705                return SBTickets.showPanel(name, title);
3706            }
3707            let panel = chat_scroll_area.find(' > .sb-panel-' + name);
3708            if (panel.length) {
3709                chat_scroll_area.find(' > div').sbActive(false);
3710                panel.sbActive(true);
3711                if (!this.start_header) {
3712                    this.start_header = [chat_header.html(), chat_header.attr('class')];
3713                }
3714                chat_header.attr('class', 'sb-header sb-header-panel').html(`<span>${sb_(title)}</span>${mobile ? '' : '<i class="sb-icon sb-icon-arrows-' + (main.hasClass('sb-chat-large') ? 'in' : 'out') + ' sb-enlarge-chat-widget sb-enlarge-chat-widget-panel"></i>'}<div class="sb-dashboard-btn sb-icon-close"></div>`);
3715                main.addClass('sb-panel-active');
3716                this.dashboard = true;
3717            }
3718            SBF.event('SBPanelActive', name);
3719        },
3720
3721        hidePanel: function () {
3722            main.removeClass('sb-panel-active');
3723            chat_header.removeClass('sb-header-panel');
3724        },
3725
3726        // Clear the conversation area and the active conversation
3727        clear: function () {
3728            this.conversation = false;
3729            chat.html('');
3730        },
3731
3732        // Update the red notification counter of the chat
3733        updateNotifications: function (conversation_id, message_id = false) {
3734            let check = false;
3735            if (message_id) {
3736                for (var i = 0; i < this.notifications.length; i++) {
3737                    if (this.notifications[i][0] == conversation_id && message_id == this.notifications[i][1]) {
3738                        check = true;
3739                    }
3740                }
3741                if (!check) {
3742                    this.notifications.push([conversation_id, message_id]);
3743                    if (!this.dashboard && this.conversation && this.conversation.id != conversation_id) {
3744                        this.headerAnimation();
3745                    }
3746                }
3747            } else {
3748                let active_notifications = [];
3749                for (var i = 0; i < this.notifications.length; i++) {
3750                    if (this.notifications[i][0] == conversation_id) {
3751                        check = true;
3752                    } else {
3753                        active_notifications.push(this.notifications[i]);
3754                    }
3755                }
3756                if (!admin && check && ['0', 0].includes(this.conversation.status_code)) {
3757                    this.setConversationStatus(1, true);
3758                }
3759                this.notifications = active_notifications;
3760            }
3761            let count = this.notifications.length;
3762            storage('notifications', this.notifications);
3763            main.find('.sb-chat-btn span').attr('data-count', count).html(count > -1 ? count : 0);
3764            SBF.event('SBNotificationsUpdate', { conversation_id: conversation_id, message_id: message_id });
3765        },
3766
3767        // Set the active conversation status
3768        setConversationStatus: function (status_code, prevent_2_to_1 = false) {
3769            if (this.conversation) {
3770                SBF.ajax({
3771                    function: 'update-conversation-status',
3772                    conversation_id: this.conversation.id,
3773                    status_code: status_code,
3774                    prevent_2_to_1: prevent_2_to_1
3775                }, () => {
3776                    this.conversation.set('status_code', status_code);
3777                    SBF.event('SBActiveConversationStatusUpdated', { conversation_id: this.conversation.id, status_code: status_code });
3778                });
3779                return true;
3780            }
3781            return false;
3782        },
3783
3784        // Typing status
3785        typing: function (user_id = -1, action = 'check') {
3786            if (this.conversation) {
3787                let valid = this.agent_online || (admin && this.user_online);
3788                if (action == 'check' && !SBPusher.active && user_id != -1 && user_id != bot_id && valid) {
3789                    SBF.ajax({
3790                        function: 'is-typing',
3791                        user_id: user_id,
3792                        conversation_id: this.conversation.id
3793                    }, (response) => {
3794                        if (response && !this.typing_settings.typing) {
3795                            this.typing(-1, 'start');
3796                        } else if (!response && this.typing_settings.typing) {
3797                            this.typing(-1, 'stop');
3798                        }
3799                    });
3800                } else if (action == 'set' && valid) {
3801                    let source = this.conversation.get('source');
3802                    clearTimeout(timeout_typing);
3803                    if (source) {
3804                        source = source == 'fb' ? [source, activeUser().getExtra('facebook-id').value, this.conversation.get('extra')] : (source == 'tw' ? [source, activeUser().getExtra('twitter-id').value] : false);
3805                    }
3806                    if (SBPusher.active) {
3807                        SBF.debounce(() => {
3808                            SBPusher.trigger('client-typing', { user_id: admin ? SB_ACTIVE_AGENT.id : activeUser().id, conversation_id: this.conversation.id });
3809                            if (source) {
3810                                SBF.ajax({ function: 'set-typing', source: source });
3811                            }
3812                        }, '#2');
3813                    } else {
3814                        if (!this.typing_settings.sent) {
3815                            this.typing_settings.sent = true;
3816                            SBF.ajax({
3817                                function: 'set-typing',
3818                                user_id: user_id,
3819                                conversation_id: this.conversation.id,
3820                                source: source
3821                            });
3822                            this.typing(user_id, 'set');
3823                        } else {
3824                            clearTimeout(this.typing_settings.timeout);
3825                            this.typing_settings.timeout = setTimeout(() => {
3826                                SBF.ajax({
3827                                    function: 'set-typing',
3828                                    user_id: user_id,
3829                                    conversation_id: -1
3830                                }, () => {
3831                                    this.typing_settings.sent = false;
3832                                });
3833                            }, 2000);
3834                        }
3835                    }
3836                } else if (action == 'start' || action == 'stop') {
3837                    let start = action == 'start';
3838                    if (!admin && chat_status) {
3839                        if (start) {
3840                            let agent = this.lastAgent();
3841                            $(chat_status).addClass('sb-status-typing').html(sb_('Typing'));
3842                            if (!agent && CHAT_SETTINGS.is_chatbot_active) {
3843                                agent = { user_id: CHAT_SETTINGS.bot_id, full_name: CHAT_SETTINGS.bot_name, profile_image: CHAT_SETTINGS.bot_image };
3844                            }
3845                            if (agent && !chat.find('[data-id="typing"]').length) {
3846                                chat.append((new SBMessage({ id: 'typing', profile_image: agent.profile_image, full_name: agent.full_name, user_type: 'agent', creation_time: '0000-00-00 00:00:00', message: ' ' })).getCode());
3847                                this.scrollBottom();
3848                            }
3849                        } else {
3850                            let online = this.agent_online || this.agent_id == bot_id;
3851                            clearTimeout(timeout_typing);
3852                            $(chat_status).removeClass('sb-status-typing').html(sb_(online ? 'Online' : 'Away'));
3853                            if (online) {
3854                                $(chat_status).addClass('sb-status-online');
3855                            }
3856                            chat.find('[data-id="typing"]').remove();
3857                        }
3858                    }
3859                    this.typing_settings.typing = start;
3860                    SBF.event('SBTyping', start);
3861                }
3862            }
3863        },
3864
3865        // Articles
3866        showArticles: function (id = false, is_category = false) {
3867            let panel = tickets ? main.find('.sb-panel-main .sb-panel') : chat_scroll_area.find(' > .sb-panel-articles');
3868            panel.html('').sbLoading(true);
3869            this.showPanel('articles', CHAT_SETTINGS.articles_title ? CHAT_SETTINGS.articles_title : 'Help Center');
3870            if (!id && CHAT_SETTINGS.articles_categories) {
3871                this.getArticleCategories((categories) => {
3872                    this.showArticles_(categories, panel, { categories: categories });
3873                }, 'parent');
3874            } else {
3875                this.getArticles(is_category ? false : id, (articles) => {
3876                    if (id && !is_category) {
3877                        panel.html(this.getArticleCode(articles[0]));
3878                        panel.sbLoading(false);
3879                        SBF.event('SBArticles', { id: id, articles: articles });
3880                    } else {
3881                        this.showArticles_(articles, panel, { id: id, articles: articles });
3882                    }
3883                }, is_category ? id : false);
3884            }
3885        },
3886
3887        showArticles_: function (items, panel, payload) {
3888            let code = '';
3889            let language = typeof SB_LANG != ND ? SB_LANG[0] : false;
3890            for (var i = 0; i < items.length; i++) {
3891                let is_article = 'content' in items[i];
3892                code += `<div data-id="${items[i].id}"${is_article ? '' : ' data-is-category="true"'}><div>${is_article ? items[i].title : (language && items[i].languages && items[i].languages[language] ? items[i].languages[language].title : items[i].title)}</div><span>${is_article ? items[i].content : (language && items[i].languages && items[i].languages[language] ? items[i].languages[language].description : (items[i].description ? items[i].description : ''))}</span></div>`;
3893            }
3894            panel.html(`<div class="sb-articles">${code}</div>`);
3895            panel.sbLoading(false);
3896            SBF.event('SBArticles', payload);
3897        },
3898
3899        getArticles: function (id = false, onSuccess = false, category = false, count = false) {
3900            SBF.ajax({
3901                function: 'get-articles',
3902                categories: this.articles_category ? this.articles_category : category,
3903                id: id ? id : this.articles_allowed_ids,
3904                count: count,
3905                return_categories: this.articles_category ? true : false,
3906                full: id,
3907                skip_language: true
3908            }, (response) => {
3909                onSuccess(response);
3910            });
3911        },
3912
3913        getArticleCategories: function (onSuccess = false, category_type = false) {
3914            SBF.ajax({
3915                function: 'get-articles-categories',
3916                category_type: category_type
3917            }, (response) => {
3918                onSuccess(response);
3919            });
3920        },
3921
3922        searchArticles: function (search, button, target) {
3923            if (search) {
3924                $(button).sbLoading(true);
3925                SBF.ajax({
3926                    function: 'search-articles',
3927                    search: search
3928                }, (articles) => {
3929                    let code = '';
3930                    let count = articles.length;
3931                    if (count == 0) {
3932                        code += `<p class="sb-no-results">${sb_('No articles found.')}</p>`;
3933                    } else {
3934                        for (var i = 0; i < articles.length; i++) {
3935                            code += `<div data-id="${articles[i].id}"><div>${articles[i].title}</div><span>${articles[i].content}</span></div>`;
3936                        }
3937                    }
3938                    $(target).html(code);
3939                    $(button).sbLoading(false);
3940                });
3941            }
3942        },
3943
3944        setArticleRating: function (article_id, rating, onSuccess = false) {
3945            SBF.ajax({
3946                function: 'article-ratings',
3947                article_id: article_id,
3948                rating: rating
3949            }, (response) => {
3950                if (onSuccess) onSuccess(response);
3951            });
3952        },
3953
3954        articleRatingOnClick: function (button) {
3955            let article = $(button).closest('.sb-article');
3956            if (!article[0].hasAttribute('data-user-rating')) {
3957                $(button).parent().sbLoading();
3958                let rating = $(button).attr('data-rating') == 'positive' ? 1 : -1;
3959                let article_id = $(button).closest('.sb-article').attr('data-id');
3960                SBChat.setArticleRating(article_id, rating, () => {
3961                    SBF.storage('article-rating-' + article_id, rating);
3962                    article.attr('data-user-rating', rating);
3963                    $(button).parent().sbLoading(false);
3964                });
3965            }
3966        },
3967
3968        getArticleCode: function (article) {
3969            let user_rating = SBF.storage('article-rating-' + article.id);
3970            let code = '';
3971            if (this.articles_categories && !SBF.null(article.categories)) {
3972                for (var i = 0; i < this.articles_categories.length; i++) {
3973                    let category_id = this.articles_categories[i].id;
3974                    if (article.categories.includes(category_id) || article.parent_category == category_id) {
3975                        code += `<span data-id="${category_id}">${sb_(this.articles_categories[i].title)}</span>`;
3976                    }
3977                }
3978            }
3979            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>${article.link ? `<div class="sb-read-more-cnt"><a href="${article.link}" target="_blank" class="sb-btn">${sb_('Read more')}</a></div>` : ''}${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>`;
3980        },
3981
3982        initArticlesPage: function () {
3983            let query_article_id = SBF.getURL('article_id');
3984            let query_category = SBF.getURL('category');
3985            let query_search = SBF.getURL('search');
3986            if (CHAT_SETTINGS.articles_url_rewrite) {
3987                let url_parts = location.href.replace(CHAT_SETTINGS.articles_url_rewrite, '').split('/');
3988                if (url_parts[url_parts.length - 2] == 'category') {
3989                    query_category = url_parts[url_parts.length - 1];
3990                }
3991                if ((url_parts.length == 2 && !url_parts[0] && url_parts[1]) || (url_parts.length == 1 && url_parts[0])) {
3992                    query_article_id = url_parts[url_parts.length - 1];
3993                }
3994                CHAT_SETTINGS.articles_page_url
3995            }
3996            articles_page = $('body').find('#sb-articles');
3997            if (!articles_page.length) {
3998                articles_page = $('body');
3999            }
4000            if (articles_page.sbLoading()) {
4001                let url = SB_URL + '/include/articles.php' + (query_category ? '?category=' + query_category : (query_article_id ? '?article_id=' + query_article_id : (query_search ? '?search=' + query_search : '')));
4002                if (cloud_data) {
4003                    url += (url.includes('?') ? '&' : '?') + 'cloud=' + cloud_data;
4004                }
4005                if (SBF.getURL('lang')) {
4006                    url += (url.includes('?') ? '&' : '?') + 'lang=' + SBF.getURL('lang');
4007                }
4008                SBF.loadResource(SB_URL + '/css/articles.css');
4009                SBF.cors('GET', url, (html) => {
4010                    articles_page.html(html);
4011                    articles_page.sbLoading(false);
4012                    this.initSingleArticlePage();
4013                });
4014            }
4015            articles_page.on('keydown', '.sb-panel-side input', function (e) {
4016                if (e.which == 13) {
4017                    $(this).next().click();
4018                }
4019            });
4020
4021            articles_page.on('click', '.sb-articles > [data-id]', function () {
4022                cache = panel_main.html();
4023                panel_main.sbLoading(true);
4024                SBChat.getArticles($(this).attr('data-id'), (article) => {
4025                    panel_main.removeClass('sb-articles').html(SBChat.getArticleCode(article));
4026                    panel_main.sbLoading(false);
4027                });
4028            });
4029
4030            articles_page.on('click', '.sb-article [data-rating]', function () {
4031                SBChat.articleRatingOnClick(this);
4032            });
4033
4034            articles_page.on('click', '.sb-article .sb-title .sb-close', function () {
4035                articles_page.find('.sb-panel-main').addClass('sb-articles').html(cache);
4036            });
4037
4038            articles_page.on('click', '.sb-submit-articles', function () {
4039                let search = $(this).parent().find('input').val();
4040                if (search) {
4041                    cache = panel_main.html();
4042                    panel_main.html('');
4043                    SBChat.searchArticles(search, this, panel_main);
4044                } else {
4045                    panel_main.html(cache);
4046                }
4047                panel_main.addClass('sb-articles');
4048            });
4049
4050            articles_page.on('click', '.sb-article-categories [data-id]', function () {
4051                if (loading(panel_main)) return;
4052                let code = '';
4053                cache = panel_main.html();
4054                articles_page.find('.sb-article-categories [data-id]').sbActive(false);
4055                $(this).sbActive(true);
4056                SBChat.getArticles(-1, (articles) => {
4057                    for (var i = 0; i < articles.length; i++) {
4058                        code += `<div data-id="${articles[i].id}"><div>${articles[i].title}</div><span>${articles[i].content}</span></div>`;
4059                    }
4060                    panel_main.addClass('sb-articles').html(code ? code : `<p class="sb-no-results">${sb_('No articles found.')}</p>`);
4061                    panel_main.sbLoading(false);
4062                }, $(this).attr('data-id'));
4063            });
4064        },
4065
4066        initSingleArticlePage: function () {
4067            if (typeof SB_ARTICLE_ID === ND) return;
4068            let user_rating = SBF.storage('article-rating-' + SB_ARTICLE_ID);
4069            const article = $('.sb-article');
4070            const nav = $('.sb-articles-nav');
4071            const links = nav.find('a[href^="#"]');
4072            const sections = [];
4073            const offset = (nav[0].getBoundingClientRect().top + 40) + 'px';
4074            if (user_rating) {
4075                article.attr('data-user-rating', user_rating);
4076            }
4077
4078            article.on('click', '.sb-rating-ext [data-rating]', function (e) {
4079                SBChat.articleRatingOnClick(this); e.preventDefault(); return false;
4080            });
4081
4082            nav.on('click', '.sb-articles-nav-mobile-btn', function () {
4083                $(this).parent().toggleClass('sb-active');
4084            });
4085
4086            nav.on('click', 'a', function () {
4087                if (window.innerWidth < 768) {
4088                    nav.removeClass('sb-active');
4089                }
4090                history.replaceState(null, '', $(this).attr('href'));
4091            });
4092
4093            nav.css('--offset', offset);
4094            $('.sb-articles-list-nav').css('--offset', offset);
4095            links.eq(0).addClass('sb-active');
4096            links.each((i, e) => {
4097                const section = article.find('#' + $(e).attr('href').slice(1));
4098                if (section.length) {
4099                    sections.push({ link: $(e), section: section[0] });
4100                }
4101            });
4102            const observer = new IntersectionObserver((entries) => {
4103                entries.forEach(entry => {
4104                    if (entry.isIntersecting) {
4105                        links.removeClass('sb-active');
4106                        const current = sections.find(s => s.section === entry.target);
4107                        if (current) {
4108                            current.link.addClass('sb-active');
4109                        }
4110                    }
4111                });
4112            }, { rootMargin: '-40% 0px -50% 0px', threshold: 0 });
4113            sections.forEach(s => observer.observe(s.section));
4114            links.each((i, e) => {
4115                $(e).on('click', function (event) {
4116                    event.preventDefault();
4117                    const target = article.find('#' + $(this).attr('href').slice(1));
4118                    if (target.length) {
4119                        window.scrollTo({ top: target[0].offsetTop - 80, behavior: 'smooth' });
4120                    }
4121                });
4122            });
4123
4124        },
4125
4126        // Emoji
4127        categoryEmoji: function (category) {
4128            let list = this.emoji_options.list;
4129            if (category == 'all') {
4130                this.emoji_options.list_now = list;
4131            } else {
4132                this.emoji_options.list_now = [];
4133                for (var i = 0; i < list.length; i++) {
4134                    if (list[i].category.startsWith(category)) {
4135                        this.emoji_options.list_now.push(list[i]);
4136                    }
4137                }
4138            }
4139            this.emoji_options.range = 0;
4140            this.populateEmoji(0);
4141            this.populateEmojiBar();
4142        },
4143
4144        mouseWheelEmoji: function (e) {
4145            let range = this.emoji_options.range;
4146            if (sbDelta(e) > 0 || (mobile && typeof e.originalEvent.changedTouches !== ND && this.emoji_options.touch < e.originalEvent.changedTouches[0].clientY)) {
4147                range -= (range < 1 ? 0 : 1);
4148            } else {
4149                range += (range > this.emoji_options.range_limit ? 0 : 1);
4150            }
4151            chat_emoji.find('.sb-emoji-bar > div').sbActive(false).eq(range).sbActive(true);
4152            this.emoji_options.range = range;
4153            this.populateEmoji(range);
4154            e.preventDefault();
4155        },
4156
4157        insertEmoji: function (emoji) {
4158            if (emoji.indexOf('.svg') > 0) {
4159                emoji = $.parseHTML(emoji)[0].alt;
4160            }
4161            this.insertText(emoji);
4162            chat_emoji.sbTogglePopup();
4163        },
4164
4165        showEmoji: function (button) {
4166            if (chat_emoji.sbTogglePopup(button)) {
4167                if (!admin) {
4168                    chat_emoji.css({ left: chat_editor.offset().left + (tickets ? 68 : 20), top: chat_editor.offset().top - window.scrollY - (tickets ? chat_editor.height() - 330 : 304) });
4169                }
4170                if (!chat_emoji.find('.sb-emoji-list > ul').html()) {
4171                    jQuery.ajax({
4172                        method: 'POST',
4173                        url: SB_AJAX_URL,
4174                        data: {
4175                            function: 'emoji',
4176                            'login-cookie': SBF.loginCookie()
4177                        }
4178                    }).done((response) => {
4179                        this.emoji_options.list = JSON.parse(response);
4180                        this.emoji_options.list_now = this.emoji_options.list;
4181                        this.populateEmoji(0);
4182                        this.populateEmojiBar();
4183                    });
4184                }
4185                SBF.deselectAll();
4186            }
4187        },
4188
4189        populateEmoji: function (range) {
4190            let code = '';
4191            let per_page = mobile ? 42 : 48;
4192            let limit = range * per_page + per_page;
4193            let list_now = this.emoji_options.list_now;
4194            if (limit > list_now.length) limit = list_now.length;
4195            this.emoji_options.range_limit = list_now.length / per_page - 1;
4196            this.emoji_options.range = range;
4197            for (var i = (range * per_page); i < limit; i++) {
4198                code += `<li>${list_now[i].char}</li>`;
4199            }
4200            chat_emoji.find('.sb-emoji-list').html(`<ul>${code}</ul>`);
4201        },
4202
4203        populateEmojiBar: function () {
4204            let code = '<div class="sb-active"></div>';
4205            let per_page = mobile ? 42 : 49;
4206            for (var i = 0; i < this.emoji_options.list_now.length / per_page - 1; i++) {
4207                code += '<div></div>';
4208            }
4209            this.emoji_options.range = 0;
4210            chat_emoji.find('.sb-emoji-bar').html(code);
4211        },
4212
4213        clickEmojiBar: function (item) {
4214            let range = $(item).index();
4215            this.populateEmoji(range);
4216            this.emoji_options.range = range;
4217            chat_emoji.find('.sb-emoji-bar > div').sbActive(false).eq(range).sbActive(true);
4218        },
4219
4220        searchEmoji: function (search) {
4221            SBF.search(search, () => {
4222                if (search.length > 1) {
4223                    let list = this.emoji_options.list;
4224                    let list_now = [];
4225                    for (var i = 0; i < list.length; i++) {
4226                        if (list[i].category.toLowerCase().includes(search) || list[i].name.toLowerCase().includes(search)) {
4227                            list_now.push(list[i]);
4228                        }
4229                    }
4230                    this.emoji_options.list_now = list_now;
4231                } else {
4232                    this.emoji_options.list_now = this.emoji_options.list;
4233                }
4234                this.emoji_options.range = 0;
4235                this.populateEmoji(0);
4236                this.populateEmojiBar();
4237            });
4238        },
4239
4240        // Editor methods
4241        textareaChange: function (textarea) {
4242            let value = $(textarea).val();
4243
4244            // Saved replies
4245            if (admin) {
4246                SBAdmin.conversations.savedReplies(textarea, value);
4247                SBAdmin.apps.openAI.rewriteButton(value);
4248            }
4249
4250            // Typing
4251            if (value) {
4252                this.typing((admin && !SBPusher.active ? SB_ACTIVE_AGENT.id : activeUser().id), 'set');
4253            }
4254            chat_editor.sbActive(value);
4255        },
4256
4257        insertText: function (text) {
4258            let textarea = $(chat_textarea.get(0));
4259            let index = 0;
4260            if (this.dashboard || !text) return false;
4261            if (textarea.get(0).selectionStart) {
4262                index = textarea.get(0).selectionStart;
4263            } else if (document.selection) {
4264                textarea.focus();
4265                let selection = document.selection.createRange();
4266                var selection_length = document.selection.createRange().text.length;
4267                selection.moveStart('character', -textarea.value.length);
4268                index = selection.text.length - selection_length;
4269            }
4270            textarea.val(textarea.val().substr(0, index) + text + textarea.val().substr(index));
4271            textarea.focus();
4272            textarea.manualExpandTextarea();
4273            chat_editor.sbActive(true);
4274        },
4275
4276        enabledAutoExpand: function () {
4277            if (chat_textarea.length) {
4278                chat_textarea.autoExpandTextarea();
4279            }
4280        },
4281
4282        cancelReply: function () {
4283            chat_editor.find('[data-reply]').remove();
4284            chat.removeClass('sb-reply-active');
4285        },
4286
4287        cancelEdit: function () {
4288            chat_editor.find('[data-edit-message]').remove();
4289            chat.removeClass('sb-edit-message-active');
4290        },
4291
4292        // Privacy message
4293        privacy: function () {
4294            SBF.ajax({
4295                function: 'get-block-setting',
4296                value: 'privacy'
4297            }, (response) => {
4298                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.replace(/\n/g, '<br>')}</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>`);
4299                this.finalizeInit();
4300                SBF.event('SBPrivacy');
4301            });
4302            if (!this.dashboard) {
4303                this.showDashboard();
4304            }
4305            this.dashboard = true;
4306            main.addClass('sb-init-form-active');
4307        },
4308
4309        // Popup message 
4310        popup: function (close = false, content = false) {
4311            if (close) {
4312                let popup = main.find('.sb-popup-message');
4313                let id = popup.attr('data-id');
4314                storage('popup' + (SBF.null(id) ? '' : id), true);
4315                popup.remove();
4316                return;
4317            }
4318            setTimeout(() => {
4319                if (!this.chat_open) {
4320                    if (content == false) {
4321                        content = CHAT_SETTINGS.popup;
4322                    }
4323                    main.find('.sb-popup-message').remove();
4324                    main.append(`<div data-id="${content.id ? content.id : ''}" class="sb-popup-message">` + (content.image ? `<img loading="lazy" src="${content.image}" />` : '') + (content.title ? `<div class="sb-top">${content.title}</div>` : '') + `<div class="sb-text">${content.message}</div><div class="sb-icon-close"></div></div>`);
4325                    SBF.event('SBPopup', content);
4326                }
4327            }, 1000);
4328        },
4329
4330        // Follow up message
4331        followUp: function () {
4332            if (this.followUpCheck()) {
4333                SBF.ajax({
4334                    function: 'execute-bot-message',
4335                    conversation_id: this.conversation.id,
4336                    name: 'follow_up',
4337                    check: false
4338                }, (response) => {
4339                    if (response.settings.sound && this.audio) {
4340                        this.audio.play();
4341                    }
4342                    this.skip = true;
4343                    SBF.storageTime('email');
4344                    SBF.event('SBFollowUp');
4345                });
4346            }
4347        },
4348
4349        followUpCheck: function () {
4350            return !admin && this.conversation && CHAT_SETTINGS.follow && activeUser() && !activeUser().email && SBF.storageTime('email', 24);
4351        },
4352
4353        // Welcome message
4354        welcome: function () {
4355            let is_valid = !tickets && (CHAT_SETTINGS.welcome_trigger != 'open' || this.chat_open) && (CHAT_SETTINGS.office_hours || !CHAT_SETTINGS.welcome_disable_office_hours) && CHAT_SETTINGS.welcome && !storage('welcome') && activeUser();
4356            setTimeout(() => {
4357                if (is_valid) {
4358                    if (this.conversation && this.conversation.searchMessages('welcome-message')) {
4359                        return;
4360                    }
4361                    SBF.ajax({
4362                        function: 'get-block-setting',
4363                        value: 'welcome'
4364                    }, (response) => {
4365                        this.sendMessage(bot_id, response.message, [], false, { type: 'welcome-message' }, 3);
4366                        if (response.open && !mobile) {
4367                            this.start();
4368                        }
4369                        if (response.sound) {
4370                            this.audio.play();
4371                        }
4372                        this.skip = true;
4373                        SBF.event('SBWelcomeMessage');
4374
4375                    });
4376                }
4377            }, parseInt(tickets ? 0 : CHAT_SETTINGS.welcome_delay) * 1000);
4378            if (is_valid) {
4379                storage('welcome', true);
4380            }
4381        },
4382
4383        // Offline timetable message
4384        offlineMessage: function () {
4385            if (!admin && CHAT_SETTINGS.timetable && (!CHAT_SETTINGS.office_hours || (!agents_online && !CHAT_SETTINGS.timetable_disable_agents))) {
4386                let message = CHAT_SETTINGS.timetable_message;
4387                switch (CHAT_SETTINGS.timetable_type) {
4388                    case 'header':
4389                        if (!this.offline_message_set) {
4390                            if (message[0]) {
4391                                chat_header.find('.sb-title').html(message[0]);
4392                            }
4393                            chat_header.find('.sb-text').html(message[1]);
4394                            this.offline_message_set = true;
4395                        }
4396                        break;
4397                    case 'info':
4398                        if (!this.offline_message_set) {
4399                            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>`);
4400                            main.addClass('sb-notify-active');
4401                            this.offline_message_set = true;
4402                        }
4403                        break;
4404                    default:
4405                        setTimeout(() => {
4406                            if (this.conversation) {
4407                                let offline_message = CHAT_SETTINGS.timetable_hide ? `${message[0] ? `*${message[0]}*\n` : ''}${message[1]}` : '[timetable]';
4408                                let offline_message_sent = this.conversation.searchMessages(offline_message, true);
4409                                offline_message_sent = offline_message_sent.length ? offline_message_sent : false;
4410                                if (offline_message_sent) {
4411                                    let last_agent_message = this.conversation.getLastUserMessage(false, true);
4412                                    offline_message_sent = !last_agent_message || (last_agent_message.get('index') < offline_message_sent[offline_message_sent.length - 1].get('index') && Date.now() - 3600000) < SBF.unix(offline_message_sent[0].get('creation_time'));
4413                                }
4414                                if (!offline_message_sent) {
4415                                    this.sendMessage(bot_id, offline_message);
4416                                }
4417                            }
4418                        }, 5000);
4419                }
4420            }
4421        },
4422
4423        // Delete message
4424        deleteMessage: function (message_id) {
4425            SBF.ajax({
4426                function: 'delete-message',
4427                message_id: message_id
4428            }, () => {
4429                if (this.conversation) this.conversation.deleteMessage(message_id);
4430                chat.find(`[data-id="${message_id}"]`).remove();
4431                SBF.event('SBMessageDeleted', message_id);
4432            });
4433        },
4434
4435        // Registration form
4436        registration: function (check = false, type = CHAT_SETTINGS.registration_required) {
4437            if (check) {
4438                return CHAT_SETTINGS.registration_required && (!CHAT_SETTINGS.registration_offline || !agents_online) && (typeof SB_DEFAULT_USER == ND || !SB_DEFAULT_USER.email) && (!CHAT_SETTINGS.registration_timetable || !CHAT_SETTINGS.office_hours) && (activeUser() === false || ['visitor', 'lead'].includes(activeUser().type));
4439            }
4440            chat_scroll_area.append(SBRichMessages.generate({}, CHAT_SETTINGS.registration_link || (CHAT_SETTINGS.registration_required == 'registration-login') ? 'login' : type, 'sb-init-form'));
4441            if (!this.dashboard) {
4442                this.showDashboard();
4443            }
4444            this.dashboard = true;
4445            this.finalizeInit();
4446            main.addClass('sb-init-form-active');
4447        },
4448
4449        // Shortcut for add user and login function
4450        addUserAndLogin: function (onSuccess = false, lead = false) {
4451            let settings = typeof SB_DEFAULT_USER != ND && SB_DEFAULT_USER ? SB_DEFAULT_USER : {};
4452            settings.user_type = lead ? 'lead' : 'visitor';
4453            SBF.ajax({
4454                function: 'add-user-and-login',
4455                settings: settings,
4456                settings_extra: settings.extra
4457            }, (response) => {
4458                if (SBF.errorValidation(response)) {
4459                    if (response[1] == 'duplicate-email' || response[1] == 'duplicate-phone') {
4460                        delete SB_DEFAULT_USER.email;
4461                        delete SB_DEFAULT_USER.extra.phone;
4462                        return this.addUserAndLogin(onSuccess, lead);
4463                    }
4464                } else {
4465                    SBF.loginCookie(response[1]);
4466                    activeUser(new SBUser(response[0]));
4467                    SBPusher.start();
4468                    if (!SBPusher.active) {
4469                        SBChat.automations.runAll();
4470                    }
4471                    if (onSuccess) {
4472                        onSuccess(response);
4473                    }
4474                }
4475            });
4476        },
4477
4478        // Check if the dashboard must be showed
4479        isInitDashboard: function () {
4480            return CHAT_SETTINGS.init_dashboard || (activeUser() && activeUser().conversations.length > 1);
4481        },
4482
4483        // Upload response
4484        uploadResponse: function (response) {
4485            response = JSON.parse(response);
4486            if (response[0] == 'success') {
4487                if (response[1] == 'extension_error') {
4488                    let message = 'The file you are trying to upload has an extension that is not allowed.';
4489                    if (admin) {
4490                        SBAdmin.infoPanel(message, 'info');
4491                    } else {
4492                        alert(message);
4493                    }
4494                } else if ($(upload_target).hasClass('sb-input-image')) {
4495                    let image = $(upload_target).find('.image');
4496                    let image_url = image.attr('data-value');
4497                    if (image_url && !image_url.includes('media/user.svg')) {
4498                        SBF.ajax({ function: 'delete-file', path: image_url });
4499                    }
4500                    image.attr('data-value', '').css('background-image', '');
4501                    setTimeout(() => {
4502                        image.attr('data-value', response[1]).css('background-image', `url("${response[1]}?v=${SBF.random()}")`).append('<i class="sb-icon-close"></i>');
4503                        upload_target = false;
4504                    }, 500);
4505                } else {
4506                    let name = SBF.beautifyAttachmentName(response[1].substr(response[1].lastIndexOf('/') + 1));
4507                    chat_editor.find('.sb-attachments').append(`<div data-name="${name}" data-type="${SBF.getFileIcon(name)}" data-value="${response[1]}"${response.length > 2 ? ' data-size="' + response[2][0] + '|' + response[2][1] + '"' : ''}>${name}<i class="sb-icon-close"></i></div>`);
4508                    chat_editor.sbActive(true);
4509                }
4510            } else {
4511                SBF.error(response[1], 'sb-upload-files.change');
4512            }
4513            this.busy(false);
4514        },
4515
4516        // Archive a conversation and close it
4517        closeChat: function (update_conversation_status = true) {
4518            let id = this.conversation.id;
4519            SBChat.clear();
4520            if (update_conversation_status) {
4521                SBF.ajax({
4522                    function: 'update-conversation-status',
4523                    conversation_id: id,
4524                    status_code: 3
4525                }, () => {
4526                    this.closeChat_(id);
4527                });
4528            } else {
4529                this.closeChat_(id);
4530            }
4531
4532        },
4533
4534        closeChat_(id) {
4535            main.find(`li[data-conversation-id="${id}"]`).remove();
4536            force_action = 'new-conversation';
4537            SBChat.clear();
4538            storage('open-conversation', '');
4539            storage('welcome', '');
4540            storage('flow_on_load', '');
4541            activeUser().removeConversation(id);
4542            if (!CHAT_SETTINGS.disable_dashboard) {
4543                SBChat.showDashboard();
4544            }
4545        },
4546
4547        conversationArchived: function () {
4548            let is_close_chat = (!tickets && CHAT_SETTINGS.close_chat) || (tickets && CHAT_SETTINGS.tickets_close);
4549            if (is_close_chat && !CHAT_SETTINGS.rating) {
4550                return this.closeChat(false);
4551            }
4552            if (CHAT_SETTINGS.rating) {
4553                SBRichMessages.rating(is_close_chat);
4554            }
4555        },
4556
4557        // Automations
4558        automations: {
4559            history: [],
4560            busy: [],
4561            scroll_position_intervals: {},
4562            timeout_queue: [],
4563
4564            runAll: function () {
4565                let automations = CHAT_SETTINGS.automations;
4566                for (var i = 0; i < automations.length; i++) {
4567                    let automation = automations[i];
4568                    let conditions = automation.conditions;
4569                    let count = conditions.length;
4570                    let valid = count == 0;
4571                    let browsing_time = false;
4572                    let scroll_position = false;
4573                    let server_conditions = false;
4574                    for (var j = 0; j < conditions.length; j++) {
4575                        let criteria = conditions[j][1];
4576                        valid = false;
4577                        switch (conditions[j][0]) {
4578                            case 'browsing_time':
4579                                valid = true;
4580                                browsing_time = criteria;
4581                                break;
4582                            case 'scroll_position':
4583                                valid = true;
4584                                scroll_position = criteria;
4585                                break;
4586                            case 'referring':
4587                            case 'url':
4588                                let url = conditions[j][0] == 'referring' ? document.referrer : window.location.href;
4589                                let checks = conditions[j][2].replace(/https?:\/\/|www\./g, '').split(',');
4590                                url = url.replace(/https?:\/\/|www\./g, '');
4591                                for (var y = 0; y < checks.length; y++) {
4592                                    if (url.includes(checks[y])) {
4593                                        valid = criteria == 'contains';
4594                                        break;
4595                                    }
4596                                }
4597                                break;
4598                            case 'include_urls': // Deprecated whole block
4599                            case 'exclude_urls': // Deprecated whole block
4600                                let url2 = conditions[j][0] == 'referring' ? document.referrer : window.location.href;
4601                                let checks2 = conditions[j][2].replace(/https?:\/\/|www\./g, '').split(',');
4602                                let include = conditions[j][0] != 'exclude_urls';
4603                                if (!include) valid = true;
4604                                url2 = url2.replace(/https?:\/\/|www\./g, '');
4605                                for (var y = 0; y < checks2.length; y++) {
4606                                    checks2[y] = $.trim(checks2[y].replace('https://', '').replace('http://', '').replace('www.', ''));
4607                                    if ((criteria == 'contains' && url2.indexOf(checks2[y]) != -1) || (criteria == 'does-not-contain' && url2.indexOf(checks2[y]) == -1) || (criteria == 'is-exactly' && checks2[y] == url2) || (criteria == 'is-not' && checks2[y] != url2)) {
4608                                        valid = include;
4609                                        break;
4610                                    }
4611                                }
4612                                break;
4613                            case 'custom_variable':
4614                                let variable = criteria.split('=');
4615                                if (variable[0] in window && window[variable[0]] == variable[1]) {
4616                                    valid = true;
4617                                }
4618                                break;
4619                            case 'returning_visitor':
4620                            case 'user_type':
4621                            case 'cities':
4622                            case 'languages':
4623                            case 'countries':
4624                            case 'postal_code':
4625                            case 'website':
4626                            case 'company':
4627                            case 'creation_time':
4628                            case 'last_activity':
4629                                valid = activeUser();
4630                                server_conditions = true;
4631                                break;
4632                            case 'phone':
4633                                valid = activeUser() && activeUser().getExtra('phone');
4634                                break;
4635                            case 'email':
4636                                valid = activeUser() && activeUser().email;
4637                                break;
4638                            default:
4639                                valid = activeUser() && activeUser().getExtra(conditions[j][0]);
4640                        }
4641                        if (!valid) {
4642                            break;
4643                        }
4644                    }
4645                    if (['messages', 'emails', 'sms'].includes(automation.type) && !activeUser()) {
4646                        valid = false;
4647                    }
4648                    if (valid) {
4649                        if (server_conditions) {
4650                            if (!(automation.id in this.busy)) {
4651                                SBF.ajax({
4652                                    function: 'automations-validate',
4653                                    automation: automation
4654                                }, (response) => {
4655                                    if (response !== false) {
4656                                        this.runAll_final(automation, scroll_position, browsing_time);
4657                                    }
4658                                    delete this.busy[automation.id];
4659                                });
4660                                this.busy[automation.id] = true;
4661                            }
4662                        } else if (automation.type != 'messages' || !SBChat.registration(true)) {
4663                            this.runAll_final(automation, scroll_position, browsing_time);
4664                        }
4665                    }
4666                }
4667            },
4668
4669            runAll_final: function (automation, scroll_position, browsing_time) {
4670                if (scroll_position) {
4671                    this.scroll_position_intervals[automation.id] = setInterval(() => {
4672                        if ($(window).scrollTop() > parseInt(scroll_position)) {
4673                            if (browsing_time) {
4674                                setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000);
4675                            } else {
4676                                this.run(automation);
4677                            }
4678                            clearInterval(this.scroll_position_intervals[automation.id]);
4679                        }
4680                    }, 1000);
4681                } else if (browsing_time) {
4682                    if (!this.timeout_queue.includes(automation.id)) {
4683                        setTimeout(() => { this.run(automation) }, parseInt(browsing_time) * 1000);
4684                        this.timeout_queue.push(automation.id);
4685                    }
4686                } else this.run(automation);
4687            },
4688
4689            run: function (automation) {
4690                if (this.history.includes(automation.id)) return;
4691                switch (automation.type) {
4692                    case 'messages':
4693                    case 'emails':
4694                    case 'sms':
4695                        if ((!SBPusher.active || SBPusher.started) && !(automation.id in this.busy)) {
4696                            if (automation.type == 'messages' && SBChat.chat_open) {
4697                                let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false;
4698                                if (last_message && ((Date.now() - 600000) < SBF.unix(last_message.get('creation_time')))) {
4699                                    return;
4700                                }
4701                            }
4702                            SBF.ajax({
4703                                function: 'automations-run',
4704                                automation: automation
4705                            }, (response) => {
4706                                if (response !== false) {
4707                                    this.history.push(automation.id);
4708                                    if (automation.type == 'messages' && !SBPusher.active) SBChat.updateConversations();
4709                                }
4710                                delete this.busy[automation.id];
4711                            });
4712                            this.busy[automation.id] = true;
4713                        }
4714                        break;
4715                    case 'popups':
4716                        if (!storage('popup' + automation.id)) {
4717                            if (!SBChat.chat_open) {
4718                                setTimeout(() => {
4719                                    SBChat.popup(false, { id: automation.id, image: automation.profile_image, title: automation.title, message: automation.message });
4720                                }, 1000);
4721                                this.history.push(automation.id);
4722                            } else if (automation.fallback) {
4723                                let last_message = SBChat.conversation ? SBChat.conversation.getLastUserMessage(false, 'no-bot') : false;
4724                                if (!last_message || ((Date.now() - 600000) > SBF.unix(last_message.get('creation_time')))) {
4725                                    SBChat.sendMessage(bot_id, (SBF.null(automation.title) ? '' : `*${automation.title}*\n`) + automation.message, [], false, false, 0);
4726                                    storage('popup' + automation.id, true);
4727                                    this.history.push(automation.id);
4728                                }
4729                            }
4730                        }
4731                        break;
4732                    case 'design':
4733                        if (automation.background) {
4734                            chat_header.css('background-image', `url("${automation.background}")`);
4735                        }
4736                        if (automation.brand) {
4737                            chat_header.find('.sb-brand img').attr('src', automation.brand);
4738                        }
4739                        if (automation.title) {
4740                            chat_header.find('.sb-title').html(automation.title);
4741                        }
4742                        if (automation.message) {
4743                            chat_header.find('.sb-text').html(automation.message);
4744                        }
4745                        if (automation.icon) {
4746                            main.find('.sb-chat-btn .sb-icon').attr('src', automation.icon);
4747                        }
4748                        if (automation.color_1 || automation.color_2 || automation.color_3) {
4749                            SBF.ajax({ function: 'chat-css', color_1: automation.color_1, color_2: automation.color_2, color_3: automation.color_3 }, (response) => {
4750                                global.append(`<style>${response}</style>`);
4751                            });
4752                        }
4753                        this.history.push(automation.id);
4754                        break;
4755                    case 'more':
4756                        let parameters = {};
4757                        if (automation.department) {
4758                            SBChat.default_department = automation.department;
4759                            parameters = { function: 'update-conversation-department', department: automation.department };
4760                        }
4761                        if (automation.agent) {
4762                            SBChat.default_agent = automation.agent;
4763                            parameters = { function: 'update-conversation-agent', agent_id: automation.agent };
4764                        }
4765                        if (automation.tags) {
4766                            automation.tags = automation.tags.split(',');
4767                            SBChat.default_tags = automation.tags;
4768                            parameters = { function: 'update-tags', tags: automation.tags, add: true };
4769                        }
4770                        if (SBChat.conversation.id && (automation.tags || automation.agent || automation.department)) {
4771                            parameters.conversation_id = SBChat.conversation.id
4772                            SBF.ajax(parameters);
4773                        }
4774                        if (automation.articles || automation.articles_category) {
4775                            let articles_dashboard = main.find('.sb-dashboard-articles > .sb-articles');
4776                            SBChat.articles_allowed_ids = automation.articles;
4777                            SBChat.articles_category = automation.articles_category;
4778                            if (articles_dashboard) {
4779                                SBChat.getArticles(automation.articles, (response) => {
4780                                    let code = '';
4781                                    for (var i = 0; i < 2; i++) {
4782                                        code += `<div data-id="${response[i].id}"><div>${response[i].title}</div><span>${response[i].content}</span></div>`;
4783                                    }
4784                                    articles_dashboard.html(code);
4785                                }, automation.articles_category, 2);
4786                            }
4787                        }
4788                        this.history.push(automation.id);
4789                        break;
4790                }
4791            }
4792        },
4793
4794        // More
4795        flashNotification: function () {
4796            clearInterval(interval);
4797            interval = setInterval(function () {
4798                let count = admin ? SBAdmin.conversations.getNotificationsCounterCount() : SBChat.notifications.length;
4799                if (count) {
4800                    document.title = document.title == document_title ? '(' + count + ') ' + sb_('New message' + (count > 1 ? 's' : '')) : document_title;
4801                }
4802            }, 2000);
4803        },
4804
4805        calculateLabelDates: function () {
4806            if (admin || this.chat_open) {
4807                label_date_items = chat.find('.sb-label-date');
4808            }
4809        },
4810
4811        calculateLabelDateFirst: function () {
4812            if (!this.conversation.messages.length) {
4813                chat.append(`<div class="sb-label-date"><span>${sb_('Today')}</span></div>`);
4814            }
4815        },
4816
4817        playSound: function (repeat = false) {
4818            this.audio.play();
4819            let index = repeat ? repeat : (admin ? SB_ADMIN_SETTINGS.sound.repeat : CHAT_SETTINGS.sound.repeat);
4820            if (index && !this.tab_active) {
4821                clearInterval(this.audio_interval);
4822                this.audio_interval = setInterval(() => {
4823                    this.audio.play();
4824                    index--;
4825                    if (!index) {
4826                        clearInterval(this.audio_interval);
4827                    }
4828                }, this.audio.duration * 1000 + 1500);
4829            }
4830        },
4831
4832        isConversationAllowed: function (source, status_code, payload) {
4833            return (!CHAT_SETTINGS.tickets_hide || (tickets && source == 'tk') || (!tickets && source != 'tk')) && (![3, 4, '3', '4'].includes(status_code) || (payload && payload.includes('-message') || ((!tickets && !CHAT_SETTINGS.close_chat) || (tickets && !CHAT_SETTINGS.tickets_close))));
4834        }
4835    }
4836    window.SBChat = SBChat;
4837
4838    /* 
4839    * ----------------------------------------------------------
4840    * RICH MESSAGES
4841    * ----------------------------------------------------------
4842    */
4843
4844    var SBRichMessages = {
4845        rich_messsages: {
4846            email: '',
4847            button: '',
4848            video: '',
4849            image: '',
4850            woocommerce_button: '',
4851            rating: '',
4852            chips: '<div class="sb-buttons">[options]</div>',
4853            buttons: '<div class="sb-buttons">[options]</div>',
4854            select: '<div class="sb-select"><p></p><ul>[options]</ul></div>',
4855            list: '<div class="sb-text-list">[values]</div>',
4856            'list-image': '<div class="sb-image-list">[values]</div>',
4857            table: '<table><tbody>[header][values]</tbody></table>',
4858            inputs: '<div class="sb-form">[values]</div>',
4859            card: '<div class="sb-card">[settings]</div>',
4860            share: '<div class="sb-social-buttons">[settings]</div>',
4861            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>',
4862            '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>'
4863        },
4864        cache: {},
4865        duplicated_email: false,
4866
4867        // Generate a rich message
4868        generate: function (settings, name, css = '') {
4869            let content;
4870            let next = true;
4871            let id = settings.id ? settings.id : SBF.random();
4872            let render = new SBMessage({});
4873
4874            // Check if the rich message exist
4875            if (name in this.rich_messsages) {
4876                content = this.rich_messsages[name];
4877            } else if (name in this.cache) {
4878                content = this.cache[name];
4879            } else if (this.isShortcode(name)) {
4880                if (!settings.id) {
4881                    id = name;
4882                }
4883                content = '<div class="sb-rich-loading sb-loading"></div>';
4884                SBF.ajax({
4885                    function: 'get-rich-message',
4886                    name: name,
4887                    settings: settings
4888                }, (response) => {
4889                    response = this.initInputs(response);
4890                    if (name == 'timetable') {
4891                        response = this.timetable(response);
4892                    }
4893                    $(admin && SBAdmin.active_admin_area == 'chatbot' ? '.sb-playground' : main).find(`.sb-rich-message[id="${id}"]`).html(`<div class="sb-content">${response}</div>`);
4894                    this.cache[name] = response;
4895                    SBChat.scrollBottom(SBChat.dashboard);
4896                    SBF.event('SBRichMessageShown', { name: name, settings: settings, response: response });
4897                });
4898                next = false;
4899            } else {
4900                return false;
4901            }
4902
4903            // Generate the rich message
4904            let disabled = settings.disabled;
4905            if (next) {
4906                let options;
4907                let code = '';
4908                switch (name) {
4909                    case 'email':
4910                        let inputs = [];
4911                        let email = activeUser().email;
4912                        let default_name = activeUser().get('last_name').charAt(0) == '#';
4913                        if (settings['name'] == 'true') {
4914                            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]);
4915                        }
4916                        if (settings['last-name'] == 'true') {
4917                            inputs.push(['last_name', 'Last name', default_name ? '' : activeUser().get('last_name'), 'text', true]);
4918                        }
4919                        for (var i = 0; i < inputs.length; i++) {
4920                            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>`;
4921                        }
4922                        if (settings['phone'] == 'true') {
4923                            let phone = activeUser().getExtra('phone');
4924                            if (!this.cache.phone) {
4925                                this.cache.phone = true;
4926                                SBF.ajax({
4927                                    function: 'get-select-phone'
4928                                }, (response) => {
4929                                    this.cache.phone = response;
4930                                    chat.find('#phone .sb-select-phone').html(response);
4931                                });
4932                            }
4933                            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 class="sb-select-phone">${this.cache.phone ? this.cache.phone : ''}</div><input autocomplete="false" type="text" data-phone="true"${settings['phone-required'] != 'false' ? ' required' : ''}></div>`;
4934                        }
4935                        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><div id="otp" class="sb-input"><span>${sb_('One-time code')}</span><input autocomplete="false" type="text"></div>`;
4936                        break;
4937                    case 'image':
4938                        content = `<div class="sb-image"><img loading="lazy" src="${settings.url}"></div>`;
4939                        break;
4940                    case 'video':
4941                        content = `<iframe loading="lazy"${settings.height ? ` height="${settings.height}"` : ''} src="https://${settings.type == 'youtube' ? 'www.youtube.com/embed/' : 'player.vimeo.com/video/'}${settings.id}" allowfullscreen></iframe>`;
4942                        break;
4943                    case 'select':
4944                        options = settings && settings.options ? settings.options.replace(/\\,/g, '{R}').split(',') : [];
4945                        for (var i = 0; i < options.length; i++) {
4946                            let item = options[i].replace(/{R}/g, ',');
4947                            code += `<li data-value="${SBF.stringToSlug(item)}">${sb_(item)}</li>`;
4948                        }
4949                        content = content.replace('[options]', code);
4950                        break;
4951                    case 'chips':
4952                    case 'buttons':
4953                        options = settings && settings.options ? settings.options.replace(/\\,/g, '{R}').split(',') : [];
4954                        for (var i = 0; i < options.length; i++) {
4955                            code += `<div class="sb-btn sb-submit">${sb_(options[i].replace(/{R}/g, ',').split('|')[0])}</div>`;
4956                        }
4957                        content = content.replace('[options]', code);
4958                        break;
4959                    case 'button':
4960                        if (settings && settings.link) {
4961                            let action_link = settings.link.includes('calendly.com') ? 'calendly' : (settings.link.includes('tidycal.com') ? 'tidycal' : false);
4962                            content = `<a ${action_link ? `data-action="${action_link}" data-extra="${settings.link}|${settings.success ? settings.success.replaceAll('"', '\'') : ''}" ` : ''}href="${action_link ? '#' : settings.link.replace(/<i>/g, '_').replace(/<\/i>/g, '_')}"${settings.target && !action_link ? ' target="_blank"' : ''} class="sb-rich-btn sb-btn${settings.style == 'link' ? '-text' : ''}">${sb_(settings.name)}</a>`;
4963                        }
4964                        break;
4965                    case 'list':
4966                        if (settings.values) {
4967                            options = settings.values.replace(/\\,/g, '{R}').replace(/\\:/g, '{R2}').replace(/:\/\//g, '{R3}').split(',');
4968                            let list = name == 'list';
4969                            let list_double = list && options.length && options[0].indexOf(':') > 0;
4970                            if (list && !list_double) {
4971                                content = content.replace('sb-text-list', 'sb-text-list sb-text-list-single');
4972                            }
4973                            if (settings.numeric) {
4974                                content = content.replace('sb-text-list', 'sb-text-list sb-text-list-numeric');
4975                            }
4976                            for (var i = 0; i < options.length; i++) {
4977                                let item = options[i].replace(/{R}/g, ',');
4978                                let is_inner = item.substr(0, 1) === '-';
4979                                code += list_double && item.includes(':') ? `<div><div>${sb_(item.split(':')[0].replace(/{R2}/g, ':').replace(/{R3}/g, '://'))}</div><div>${sb_(item.split(':')[1].replace(/{R2}/g, ':').replace(/{R3}/g, '://'))}</div></div>` : `<div${is_inner ? ' data-inner="true"' : ''}>${$.trim(sb_((is_inner ? item.substr(1) : item).replace(/{R2}/g, ':').replace(/{R3}/g, '://')))}</div>`;
4980                            }
4981                            content = content.replace('[values]', code);
4982                        }
4983                        break;
4984                    case 'list-image':
4985                        if (settings.values) {
4986                            options = settings.values.split(',');
4987                            for (var i = 0; i < options.length; i++) {
4988                                let item = options[i].replace('://', '///').split(':');
4989                                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>`;
4990                            }
4991                            content = content.replace('[values]', code);
4992                        }
4993                        break;
4994                    case 'table':
4995                        if (settings.values) {
4996                            options = settings.header.split(',');
4997                            code += '<tr>';
4998                            for (var i = 0; i < options.length; i++) {
4999                                code += `<th>${options[i]}</th>`;
5000                            }
5001                            code += '</tr>';
5002                            content = content.replace('[header]', code);
5003                            code = '';
5004                            options = settings.values.split(',');
5005                            for (var i = 0; i < options.length; i++) {
5006                                let tds = options[i].split(':');
5007                                code += '<tr>';
5008                                for (var j = 0; j < tds.length; j++) {
5009                                    code += `<td>${tds[j]}</td>`;
5010                                }
5011                                code += '</tr>';
5012                            }
5013                            content = content.replace('[values]', code);
5014                        }
5015                        break;
5016                    case 'inputs':
5017                        if (settings.values) {
5018                            options = settings.values.split(',');
5019                            for (var i = 0; i < options.length; i++) {
5020                                if (disabled && !options[i]) continue;
5021                                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>`;
5022                            }
5023                            code += '<div class="sb-btn sb-submit">' + sb_(settings.button ? settings.button : 'Send now') + '</div>';
5024                            content = content.replace('[values]', code);
5025                        }
5026                        break;
5027                    case 'card':
5028                        code = `${settings.image ? `<div class="sb-card-img" style="background-image:url('${settings.image}')"></div>` : ''}<div class="sb-card-header">${settings.header}</div>${settings.extra ? `<div class="sb-card-extra">${settings.extra}</div>` : ''}${settings.description ? `<div class="sb-card-description">${settings.description}</div>` : ''}${settings.link ? `<a class="sb-card-btn" href="${settings.link}"${settings.target ? ' target="_blank"' : ''}>${sb_(settings['link-text'])}</a>` : ''}`;
5029                        content = content.replace('[settings]', code);
5030                        break;
5031                    case 'share':
5032                        let channels = settings.channels ? settings.channels.replace(/ /g, '').split(',') : ['fb', 'tw', 'li', 'wa', 'pi'];
5033                        let link = '';
5034                        for (var i = 0; i < channels.length; i++) {
5035                            switch (channels[i]) {
5036                                case 'fb':
5037                                    link = 'www.facebook.com/sharer.php?u=';
5038                                    break;
5039                                case 'tw':
5040                                    link = 'twitter.com/intent/tweet?url=';
5041                                    break;
5042                                case 'li':
5043                                    link = 'www.linkedin.com/sharing/share-offsite/?url=';
5044                                    break;
5045                                case 'wa':
5046                                    link = 'web.whatsapp.com/send?text=';
5047                                    break;
5048                                case 'pi':
5049                                    link = 'www.pinterest.com/pin/create/button/?url=';
5050                                    break;
5051                            }
5052                            code += `<div class="sb-${channels[i]} sb-icon-social-${channels[i]}" data-link="https://${link}${encodeURIComponent(settings[channels[i]])}"></div>`;
5053                        }
5054                        content = content.replace('[settings]', code);
5055                        break;
5056                    case 'slider':
5057                        let count = 0;
5058                        for (var i = 1; i < 16; i++) {
5059                            if (('header-' + i) in settings) {
5060                                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]}"${settings.target ? ' target="_blank"' : ''}>${sb_(settings['link-text-' + i])}</a>` : ''}</div>`;
5061                                count++;
5062                            } else {
5063                                break;
5064                            }
5065                        }
5066                        content = content.replace('[items]', code).replace(/\[class\]/g, count == 1 ? ' sb-hide' : '');
5067                        break;
5068                    case 'slider-images':
5069                        if (settings.images) {
5070                            let images = settings.images.split(',');
5071                            for (var i = 0; i < images.length; i++) {
5072                                code += `<div class="sb-card-img" data-value="${images[i]}" style="background-image:url('${images[i]}')"></div>`;
5073                            }
5074                            content = content.replace(/\[class\]/g, images.length == 1 ? ' sb-hide' : '');
5075                        }
5076                        content = content.replace('[items]', code);
5077                        break;
5078                    case 'woocommerce_button':
5079                        settings.settings = `checkout:${settings.checkout},coupon:${settings.coupon}`;
5080                        content = `<a href="#" data-ids="${settings.ids}" class="sb-rich-btn sb-btn">${settings.name}</a>`;
5081                        break;
5082                    case 'rating':
5083                        content = `<div class="sb-rating-message sb-rating-${settings.value == 1 ? 'positive' : 'negative'}"><div><i class="sb-icon-${settings.value == 1 ? 'like' : 'dislike'}"></i> ${sb_(settings.value == 1 ? 'Helpful' : 'Not helpful')}</div>${settings.message ? '<div>' + settings.message + '</div>' : ''}</div>`;
5084                        settings.message = false;
5085                        break;
5086                }
5087            }
5088            return `<div id="${id}" data-type="${name}"${disabled ? 'disabled="true"' : ''}${settings.settings ? ` data-settings="${settings.settings}"` : ''}class="sb-rich-message sb-rich-${name} ${css}">` + (settings.title ? `<div class="sb-top">${render.render(sb_(settings.title))}</div>` : '') + (settings.message ? `<div class="sb-text">${render.render(sb_(settings.message))}</div>` : '') + `<div class="sb-content">${content}</div>${name == 'email' ? `<div data-success="${settings.success ? settings.success.replace(/"/g, '') : ''}" class="sb-info"></div>` : ''}</div>`;
5089        },
5090
5091        // Function of built-in rich messages
5092        submit: function (area, type, element) {
5093            if (!admin && !loading(element) && !this.is_busy) {
5094                let error = '';
5095                let shortcode = '';
5096                let parameters = {};
5097                let success = $(area).find('[data-success]').length ? $(area).find('[data-success]').attr('data-success') : '';
5098                let rich_message_id = $(area).closest('.sb-rich-message').attr('id');
5099                let message_id = $(area).closest('[data-id]').attr('data-id');
5100                let message = '';
5101                let payload = { 'rich-messages': {} };
5102                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().email, password: '', user_type: '' };
5103                let settings = {};
5104                let input = $(element);
5105                let active_conversation = SBChat.conversation !== false;
5106                let settings_extra = {};
5107                let payload_settings = {};
5108                let otp = area.find('#otp');
5109                if (SBF.null(message_id)) {
5110                    message_id = -1;
5111                } else {
5112                    let item = SBChat.conversation.getMessage(message_id);
5113                    message = item.message;
5114                    payload = item.payload();
5115                    if (!payload['rich-messages']) {
5116                        payload['rich-messages'] = {};
5117                    }
5118                }
5119                if (!$(element).hasClass('sb-btn') && !$(element).hasClass('sb-select') && !$(element).hasClass('sb-submit')) {
5120                    input = $(element).closest('.sb-btn,.sb-select');
5121                }
5122                $(area).find('.sb-info').html('').sbActive(false);
5123                switch (type) {
5124                    case 'email':
5125                        settings = SBForm.getAll(area);
5126                        $.each(settings, function (key, value) {
5127                            settings[key] = value[0];
5128                        });
5129                        if (settings.first_name) {
5130                            user_settings.user_type = 'user';
5131                            if (!settings.last_name) {
5132                                user_settings.last_name = '';
5133                            }
5134                        }
5135                        if (settings.phone) {
5136                            settings_extra = { phone: [settings.phone, 'Phone'] };
5137                        }
5138                        $.extend(user_settings, settings);
5139                        error = 'Please fill in all required fields and make sure the email is valid.';
5140                        if (success) {
5141                            success = sb_(success).replace('{user_email}', user_settings.email).replace('{user_name_}', user_settings.first_name + (settings.last_name ? (' ' + user_settings.last_name) : ''));
5142                        }
5143                        if (otp.sbActive()) {
5144                            let otp_string = otp.attr('data-otp');
5145                            user_settings.otp = otp_string ? [otp_string, area.find('#otp input').val()] : false;
5146                        }
5147                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
5148                        payload['event'] = 'update-user';
5149                        parameters = { function: 'update-user-and-message', settings: user_settings, settings_extra: settings_extra, payload: payload, skip_otp: true };
5150                        break;
5151                    case 'registration':
5152                        settings = SBForm.getAll(area.find('.sb-form-main'));
5153                        settings_extra = SBForm.getAll(area.find('.sb-form-extra'));
5154                        $.each(settings, function (key, value) {
5155                            settings[key] = value[0];
5156                        });
5157                        $.extend(user_settings, settings);
5158                        payload_settings = $.extend({}, user_settings);
5159                        if (success) {
5160                            success = sb_(success);
5161                        }
5162                        if (CHAT_SETTINGS.registration_details) {
5163                            success += '[list values="';
5164                            for (var key in user_settings) {
5165                                let value = user_settings[key].replace(/:|,/g, '');
5166                                if (value) {
5167                                    if (key == 'profile_image') {
5168                                        value = value.substr(value.lastIndexOf('/') + 1);
5169                                    }
5170                                    if (['password', 'password-check', 'envato-purchase-code', 'otp'].includes(key)) {
5171                                        value = '********';
5172                                        payload_settings[key] = '********';
5173                                    } else {
5174                                        success += user_settings[key] ? `${sb_(SBF.slugToString(key.replace('first_name', 'name')))}:${value},` : '';
5175                                    }
5176                                }
5177                            }
5178                            for (var key in settings_extra) {
5179                                if (settings_extra[key][0]) {
5180                                    success += `${sb_(settings_extra[key][1].replace(/:|,/g, ''))}:${settings_extra[key][0].replace(/:|,/g, '')},`;
5181                                }
5182                            }
5183                            success = success.slice(0, -1) + '"]';
5184                        }
5185                        if (otp.sbActive()) {
5186                            let otp_string = otp.attr('data-otp');
5187                            user_settings.otp = otp_string ? [otp_string, area.find('#otp input').val()] : false;
5188                        }
5189                        user_settings.user_type = 'user';
5190                        payload['rich-messages'][rich_message_id] = { type: type, result: { user: payload_settings, extra: settings_extra } };
5191                        payload['event'] = 'update-user';
5192                        parameters = CHAT_SETTINGS.registration_otp && user_settings.email && !user_settings.otp ? { function: 'otp', email: user_settings.email } : { function: activeUser() ? 'update-user-and-message' : 'add-user-and-login', settings: user_settings, settings_extra: settings_extra, payload: payload };
5193                        error = SBForm.getRegistrationErrorMessage(area);
5194                        break;
5195                    case 'chips':
5196                    case 'select':
5197                    case 'buttons':
5198                        settings = SBF.escape($(element).html());
5199                        if (success) {
5200                            success = sb_(success) + ` *${settings}*`;
5201                        }
5202                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
5203                        parameters = { function: 'update-message', payload: payload };
5204                        if (type == 'chips') {
5205                            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);
5206                            if (rich_message_id == 'sb-human-takeover' && $(element).index() == 0) {
5207                                SBApps.dialogflow.humanTakeover();
5208                            }
5209                            $(element).closest('.sb-content').remove();
5210                        }
5211                        break;
5212                    case 'inputs':
5213                        settings = SBForm.getAll(area);
5214                        error = 'All fields are required.';
5215                        if (success) {
5216                            success = sb_(success) + ' [list values="';
5217                            for (var key in settings) {
5218                                success += `${sb_(settings[key][1].replace(/:|,/g, ''))}:${settings[key][0].replace(/:|,/g, '')},`;
5219                            }
5220                            success = success.slice(0, -1) + '"]';
5221                        }
5222                        payload['rich-messages'][rich_message_id] = { type: type, result: settings };
5223                        parameters = { function: 'update-message', payload: payload };
5224                        break;
5225                }
5226                shortcode = message.substr(message.indexOf('[' + type))
5227                shortcode = shortcode.substr(0, shortcode.indexOf(']') + 1);
5228                if (error && SBForm.errors(area)) {
5229                    SBForm.showErrorMessage(area, error);
5230                    input.sbLoading(false);
5231                    if (SBChat.dashboard || (active_conversation && SBChat.conversation.getLastMessage().id == message_id)) {
5232                        SBChat.scrollBottom();
5233                    }
5234                    return false;
5235                }
5236                if (!success && type != 'registration') {
5237                    let shortcode_settings = this.shortcode(shortcode);
5238                    let id = shortcode_settings[1].id ? `id="${shortcode_settings[1].id}"` : '';
5239                    let title = shortcode_settings[1].title ? `title="${shortcode_settings[1].title}"` : '';
5240                    let message = shortcode_settings[1].message ? `message="${shortcode_settings[1].message}"` : '';
5241                    let value = '';
5242                    if (['inputs', 'email'].includes(type)) {
5243                        for (var key in settings) {
5244                            if (key != 'otp') {
5245                                value += settings[key] + ',';
5246                            }
5247                        }
5248                        value = `values="${value.slice(0, -1)}"`
5249                    } else {
5250                        value = `options="${settings}"`;
5251                    }
5252                    success = `[${type == 'email' ? 'inputs' : type} ${id} ${title} ${message} ${value} disabled="true"]`;
5253                }
5254                if (message_id != -1) {
5255                    success = success.replace(/<br>/g, '\n');
5256                    $.extend(parameters, {
5257                        message_id: message_id,
5258                        message: message ? (type == 'chips' ? message.replace(']', ' disabled="true"]') : message.replace(shortcode, success)) : success,
5259                        payload: payload
5260                    });
5261                }
5262                SBF.ajax(parameters, (response) => {
5263                    if (response && !SBF.errorValidation(response)) {
5264                        switch (type) {
5265                            case 'email':
5266                                for (var key in user_settings) {
5267                                    activeUser().set(key, user_settings[key]);
5268                                }
5269                                for (var key in settings_extra) {
5270                                    activeUser().setExtra(key, settings_extra[key][0]);
5271                                }
5272                                SBF.loginCookie(response[1]);
5273                                if (response[0].user_change) {
5274                                    location.reload();
5275                                    return;
5276                                }
5277                                if (rich_message_id == 'sb-follow-up') {
5278                                    SBF.ajax({
5279                                        function: 'subscribe-email',
5280                                        email: activeUser().email
5281                                    });
5282                                }
5283                                SBChat.automations.runAll();
5284                                SBF.event('SBNewEmailAddress', { id: rich_message_id, name: activeUser().name, email: activeUser().email });
5285                                break;
5286                            case 'registration':
5287                                let otp = area.find('#otp');
5288                                if (CHAT_SETTINGS.registration_otp && !otp.sbActive()) {
5289                                    otp.attr('data-otp', response).sbActive(true);
5290                                    otp.find('input').attr('required', true).addClass('sb-error');
5291                                    SBForm.showErrorMessage(area, sb_('Please check your email for the one-time code.'));
5292                                    SBChat.scrollBottom();
5293                                    input.sbLoading(false);
5294                                    return;
5295                                }
5296                                SBF.loginCookie(response[1]);
5297                                user_settings.id = response[0].id;
5298                                if (!activeUser()) {
5299                                    activeUser(new SBUser(response[0]));
5300                                    for (var key in settings_extra) {
5301                                        activeUser().setExtra(key, settings_extra[key][0]);
5302                                    }
5303                                    if (this.duplicated_email && !CHAT_SETTINGS.init_dashboard) {
5304                                        force_action = 'open-conversation';
5305                                    }
5306                                    SBPusher.start();
5307                                    SBChat.initChat();
5308                                    if (!this.duplicated_email && (!CHAT_SETTINGS.init_dashboard || !main.find('.sb-departments-list').length) && success) {
5309                                        SBChat.sendMessage(bot_id, success, [], false, false, 3);
5310                                    }
5311                                } else {
5312                                    for (var key in user_settings) {
5313                                        activeUser().set(key, user_settings[key]);
5314                                    }
5315                                    for (var key in settings_extra) {
5316                                        activeUser().setExtra(key, settings_extra[key][0]);
5317                                    }
5318                                    SBChat.automations.runAll();
5319                                    SBChat.welcome();
5320                                }
5321                                if (SBChat.dashboard) {
5322                                    main.removeClass('sb-init-form-active');
5323                                    $(area).remove();
5324                                    if (!SBChat.isInitDashboard() && (!CHAT_SETTINGS.init_dashboard || !this.duplicated_email)) {
5325                                        SBChat.hideDashboard();
5326                                    }
5327                                }
5328                                if (CHAT_SETTINGS.wp_registration && user_settings.email && user_settings.password) {
5329                                    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, email: user_settings.email });
5330                                } else if (CHAT_SETTINGS.wp_users_system == 'wp') {
5331                                    SBApps.wordpress.ajax('wp-login', { user: user_settings.email, password: user_settings.password });
5332                                }
5333                                delete this.cache.registration;
5334                                setTimeout(() => {
5335                                    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'] });
5336                                }, 5000);
5337                                break;
5338                            case 'buttons':
5339                                SBChat.scrollBottom();
5340                                break;
5341                        }
5342                        if (message_id == -1) {
5343                            $(element).closest('.sb-rich-message').html(success);
5344                        } else {
5345                            input.sbLoading(false);
5346                            if ((!payload.type || payload.type != 'close-message') && !dialogflow_human_takeover) {
5347                                SBChat.setConversationStatus(2);
5348                            }
5349                        }
5350                        if (!['login', 'chips'].includes(type) && (CHAT_SETTINGS.dialogflow_send_user_details || !['email', 'registration'].includes(type))) {
5351                            SBApps.dialogflow.message(`${rich_message_id}
5351${dialogflow_response ? ('|' + dialogflow_response) : ''}`);
5352                        }
5353                        if (CHAT_SETTINGS.slack_active && (!dialogflow_human_takeover || SBApps.dialogflow.humanTakeoverActive())) {
5354                            SBApps.slack.message(activeUser().id, activeUser().name, activeUser().image, success);
5355                        }
5356                        if (SBPusher.active) {
5357                            SBChat.update();
5358                        }
5359                        if (type != 'registration' && type != 'email') {
5360                            SBF.event('SBRichMessageSubmit', { result: response, data: payload['rich-messages'][rich_message_id], id: rich_message_id });
5361                        }
5362                    } else {
5363                        if ((type == 'registration' || type == 'email') && SBF.errorValidation(response, 'duplicate-email')) {
5364                            SBF.ajax({
5365                                function: 'otp',
5366                                email: user_settings.email
5367                            }, (response) => {
5368                                let otp = area.find('#otp');
5369                                otp.attr('data-otp', response).sbActive(true);
5370                                otp.find('input').attr('required', true).addClass('sb-error');
5371                                SBForm.showErrorMessage(area, sb_('This email is already in use. Please check your email for the one-time code.'));
5372                                if (type == 'registration') {
5373                                    area.find('.sb-submit').html(sb_('Sign in'));
5374                                    SBChat.scrollBottom();
5375                                }
5376                            });
5377                            this.duplicated_email = true;
5378                        } else {
5379                            this.duplicated_email = false;
5380                            SBForm.showErrorMessage(area, SBForm.getRegistrationErrorMessage(response, 'response'));
5381                        }
5382                        if (SBChat.dashboard) {
5383                            SBChat.scrollBottom();
5384                        }
5385                        input.sbLoading(false);
5386                    }
5387                });
5388            }
5389        },
5390
5391        // Return the shortcode name and the shortcode settings
5392        shortcode: function (shortcode) {
5393            let result = {};
5394            let shortcode_name = shortcode.includes(' ') ? shortcode.substr(1, shortcode.indexOf(' ') - 1) : shortcode.slice(1, -1);
5395            if (/\?|"|'|`|\*/gi.test(shortcode_name)) {
5396                return [false, false];
5397            }
5398            shortcode = shortcode.slice(1, -1).substr(shortcode_name.length + 1);
5399            let settings = shortcode.split('" ');
5400            for (var i = 0; i < settings.length; i++) {
5401                if (settings[i].includes('=')) {
5402                    let item = [settings[i].substr(0, settings[i].indexOf('=')), settings[i].substr(settings[i].indexOf('=') + 2)];
5403                    result[$.trim(item[0])] = item[1].replace(/"/g, '');
5404                }
5405            }
5406            return [shortcode_name, result];
5407        },
5408
5409        // Init the rich message inputs
5410        initInputs: function (code) {
5411            code = $($.parseHTML('<div>' + code + '</div>'));
5412            code.find('.sb-input input').each(function () {
5413                if ($(this).val()) {
5414                    $(this).siblings().addClass('sb-active sb-filled');
5415                }
5416            });
5417            return code.html();
5418        },
5419
5420        // Timetable shortcode
5421        timetable: function (code) {
5422            let table = $($.parseHTML(`<div>${code}</div>`));
5423            let utc_offset = table.find('[data-offset]').attr('data-offset');
5424            utc_offset = SBF.null(utc_offset) ? 0 : parseFloat(utc_offset);
5425            table.find('[data-time]').each(function () {
5426                let times = $(this).attr('data-time').split('|');
5427                code = ''
5428                for (var i = 0; i < times.length; i++) {
5429                    if (!times[i] || times[i] == 'closed') { // Deparecated. Remove || times[i] == 'closed'
5430                        code += sb_('Closed');
5431                        break;
5432                    } else if (times[i]) {
5433                        let hm = times[i].split(':');
5434                        let time = SBF.convertUTCDateToLocalDate(`01/01/2000 ${hm[0]}:${hm[1]}`, utc_offset);
5435                        code += time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + (i == 0 || i == 2 ? `<span>${sb_('to')}</span>` : i == 1 && times[i + 1] ? `<br />` : '');
5436                    }
5437                }
5438                table.find(' > div > span').html(`<i class="sb-icon-clock"></i> ${sb_('Time zone')} ${Intl.DateTimeFormat().resolvedOptions().timeZone}`);
5439                $(this).html(code);
5440            });
5441            return table.html();
5442        },
5443
5444        // Slider
5445        sliderChange: function (id, direction = 'left') {
5446            let slider = chat.find(`#${id}`);
5447            if (slider.length && !slider.hasClass('sb-moving')) {
5448                let items = slider.find('.sb-slider > div > div');
5449                let item = items.eq(0);
5450                let width = Math.ceil(item.closest('.sb-slider').width());
5451                let negative = (direction == 'right' ? -1 : 1);
5452                let margin = parseFloat(parseFloat(parseFloat(item.css('margin-left')) + (width * negative)));
5453                let check = width * (items.length - 1) * -1;
5454                if (margin < 1 && margin >= check) {
5455                    item.css('margin-left', margin + 'px');
5456                    slider.addClass('sb-moving');
5457                    setTimeout(() => { slider.removeClass('sb-moving'); }, 1200);
5458                }
5459                slider.find('.sb-icon-arrow-right').sbActive(!(check > (margin - 15) && check < (margin + 15)));
5460                slider.find('.sb-icon-arrow-left').sbActive(margin < -10);
5461            }
5462        },
5463
5464        iframe: {
5465            script_loaded: false,
5466
5467            load: function (action, url, title, message_id) {
5468                url = url.split('|');
5469                if (SBRichMessages.iframe.script_loaded) {
5470                    if (action == 'calendly') {
5471                        this.load_(action, url[0], title);
5472                    } else {
5473                        chat_overlay_panel.sbActive(true).attr('data-id', action);
5474                    }
5475                } else {
5476                    let actions = { calendly: ['https://assets.calendly.com/assets/external/widget.js'], tidycal: ['https://asset-tidycal.b-cdn.net/js/embed.js'] }[action];
5477                    if (action == 'tidycal') {
5478                        this.load_(action, url[0], title);
5479                    }
5480                    SBF.loadResource(actions[0], true, () => {
5481                        SBRichMessages.iframe.script_loaded = true;
5482                        window.addEventListener('message', function (e) {
5483                            console.log(e);
5484                            if ((action == 'calendly' && e.origin === 'https://calendly.com' && e.data.event == 'calendly.event_scheduled') || (action == 'tidycal' && e.origin === 'https://tidycal.com' && e.data.data && e.data.data.booking)) {
5485                                SBChat.updateMessage(message_id, sb_(url[1] ? url[1] : 'Booking completed.'));
5486                                chat_overlay_panel.sbActive(false);
5487                            }
5488                        });
5489                        if (action == 'calendly') {
5490                            this.load_(action, url[0], title);
5491                        }
5492                    });
5493                }
5494            },
5495
5496            load_: function (action, url, title) {
5497                if (action == 'calendly') {
5498                    Calendly.initInlineWidget({
5499                        url: (url.includes('http') ? '' : 'https://') + url + '?hide_landing_page_details=1&hide_event_type_details=1&hide_gdpr_banner=1',
5500                        parentElement: chat_overlay_panel.find('> div').eq(1),
5501                        prefil: activeUser().type == 'user' ? { name: activeUser().name, email: activeUser().email } : {}
5502                    });
5503                }
5504                if (action == 'tidycal') {
5505                    chat_overlay_panel.find('> div').eq(1).html(`<div class="sb-scroll-area"><div class="tidycal-embed" data-path="${url.replace('https://tidycal.com/', '')}"></div></div>`);
5506                }
5507                chat_overlay_panel.find('> div:first-child > div').html(sb_(title));
5508                chat_overlay_panel.sbActive(true).attr('data-id', action);
5509            }
5510        },
5511
5512        rating: function (is_close_chat = false) {
5513            chat_overlay_panel.find('> div:first-child > div').html(sb_(CHAT_SETTINGS.rating_title));
5514            chat_overlay_panel.find('> div:last-child').html(`<div class="sb-text">${sb_(CHAT_SETTINGS.rating_text)}</div>${CHAT_SETTINGS.rating_message ? `<div class="sb-input sb-input-textarea"><textarea placeholder="${sb_('Add a message here...')}"></textarea></div>` : ''}<div class="sb-rating"><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>`);
5515            chat_overlay_panel.sbActive(true).attr('data-id', 'rating').attr('data-close-chat', is_close_chat ? 'true' : '');
5516        },
5517
5518        isShortcode: function (shortcode_name) {
5519            return shortcode_name in this.rich_messsages || (admin && SB_ADMIN_SETTINGS.rich_messages.includes(shortcode_name)) || (!admin && CHAT_SETTINGS.rich_messages.includes(shortcode_name));
5520        }
5521    }
5522    window.SBRichMessages = SBRichMessages;
5523
5524    /* 
5525    * ----------------------------------------------------------
5526    * FORM METHODS
5527    * ----------------------------------------------------------
5528    */
5529
5530    var SBForm = {
5531
5532        // Get all settings
5533        getAll: function (area) {
5534            let settings = {};
5535            $(area).find('.sb-input[id]').each((i, element) => {
5536                settings[$(element).attr('id')] = this.get(element);
5537            });
5538            return settings;
5539        },
5540
5541        // Get a single setting
5542        get: function (input) {
5543            input = $(input);
5544            let type = input.data('type');
5545            let name = sb_(SBF.escape(input.find(' > span').html()));
5546            switch (type) {
5547                case 'image':
5548                    let url = input.find('.image').attr('data-value');
5549                    return [SBF.null(url) ? '' : url, name];
5550                case 'select':
5551                    return [SBF.escape(input.find('select').val()), name];
5552                case 'select-input':
5553                    let select = input.find('select,input[disabled]');
5554                    return [SBF.escape((select.is('select') || select.is('input') ? select.val() : (input.find('.sb-select').length ? input.find('.sb-select > p').attr('data-value') : input.find('> div').html())) + input.find('> input').val()), name];
5555                default:
5556                    let target = input.find('input');
5557                    return [SBF.escape(target.length ? target.val() : input.find('[data-value]').attr('data-value')), name];
5558            }
5559        },
5560
5561        // Set a single setting
5562        set: function (item, value) {
5563            item = $(item);
5564            if (item.length) {
5565                let type = item.data('type');
5566                switch (type) {
5567                    case 'image':
5568                        if (value) {
5569                            item.find('.image').attr('data-value', value).css('background-image', `url("${value}")`);
5570                        } else {
5571                            item.find('.image').removeAttr('data-value').removeAttr('style');
5572                        }
5573                        break;
5574                    case 'select':
5575                        item.find('select').val(value);
5576                        break;
5577                    default:
5578                        item.find('input,textarea').val(value);
5579                        break;
5580                }
5581                return true;
5582            }
5583            return false;
5584        },
5585
5586        // Clear all the input values
5587        clear: function (area) {
5588            $(area).find('.sb-input,.sb-setting').each((i, element) => {
5589                this.set(element, '');
5590                $(element).find('input, select, textarea').removeClass('sb-error');
5591            });
5592            this.set($(area).find('#user_type'), 'user');
5593        },
5594
5595        // Check for errors on user input
5596        errors: function (area) {
5597            let errors = false;
5598            let items = $(area).find('input, select, textarea').removeClass('sb-error');
5599            items.each(function (i) {
5600                let value = $.trim($(this).val());
5601                let type = $(this).attr('type');
5602                let required = $(this).prop('required');
5603                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.includes('@') || !value.includes('.') || /;|:|\/|\\|,|#|"|!|=|\*|{|}|[|]|£|\$|€|~|'|>|<|\^|&/.test(value))) || ($(this).attr('data-phone') && value && ((!$(this).parent().find('select').val() && !$(this).prev().find('> div > p').attr('data-value') && !$(this).parent().find('div').html().trim().startsWith('+')) || isNaN(value) || value.includes('+') || value.length < 5))))) {
5604                    errors = true;
5605                    $(this).addClass('sb-error');
5606                }
5607            });
5608            items = $(area).find('[data-required]').removeClass('sb-error');
5609            items.each(function () {
5610                if (SBF.null($(this).attr('data-value'))) {
5611                    $(this).addClass('sb-error');
5612                    errors = true;
5613                }
5614            });
5615            return errors;
5616        },
5617
5618        // Display a error message
5619        showErrorMessage: function (area, message) {
5620            $(area).find('.sb-info').html(sb_(message)).sbActive(true);
5621            clearTimeout(timeout);
5622            timeout = setTimeout(function () {
5623                $(area).find('.sb-info').sbActive(false);
5624            }, 7000);
5625        },
5626
5627        // Display a success message
5628        showSuccessMessage: function (area, message) {
5629            $(area).find('.sb-info').remove();
5630            $(area).addClass('sb-success').find('.sb-content').html(`<div class="sb-text">${message}</div>`);
5631        },
5632
5633        // Return the registration error message
5634        getRegistrationErrorMessage(area_or_response, type = 'validation') {
5635            let error_text = '';
5636            if (type == 'response') {
5637                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.' : (SBF.errorValidation(area_or_response, 'invalid-envato-purchase-code') || SBF.errorValidation(area_or_response, 'invalid-boxcoin-license-key') ? 'Invalid license key.' : (SBF.errorValidation(area_or_response, 'invalid-otp') ? 'Invalid one-time code.' : 'Error. Please check your information and try again.')))
5638            }
5639            $(area_or_response).find('.sb-input:not(#password-check) [required]').each(function () {
5640                error_text += ', ' + $(this).closest('.sb-input').find('span').html() + ($(this).attr('type') == 'password' ? ' (' + sb_('8 characters minimum') + ')' : '');
5641            });
5642            return `${error_text.substring(2)} ${sb_((error_text.includes(',') ? 'are' : 'is') + ' required.')}`;
5643        }
5644    }
5645    window.SBForm = SBForm;
5646
5647    /* 
5648    * ----------------------------------------------------------
5649    * APPS
5650    * ----------------------------------------------------------
5651    */
5652
5653    var SBApps = {
5654
5655        // Get the login data 
5656        login: function () {
5657            if (typeof SB_DEFAULT_USER != ND && SB_DEFAULT_USER) {
5658                return [SB_DEFAULT_USER, 'default'];
5659            }
5660            if (this.is('wp') && typeof SB_WP_ACTIVE_USER != ND && CHAT_SETTINGS.wp_users_system == 'wp') {
5661                return [[SB_WP_ACTIVE_USER, typeof SB_WP_AVATAR != ND ? SB_WP_AVATAR : ''], 'wp'];
5662            }
5663            if (typeof SB_SHOPIFY_ACTIVE_USER != ND) {
5664                return [SB_SHOPIFY_ACTIVE_USER, 'shopify'];
5665            }
5666            if (typeof SB_PERFEX_ACTIVE_USER != ND) {
5667                return [[SB_PERFEX_ACTIVE_USER, SB_PERFEX_CONTACT_ID], 'perfex'];
5668            }
5669            if (typeof SB_WHMCS_ACTIVE_USER != ND) {
5670                return [SB_WHMCS_ACTIVE_USER, 'whmcs'];
5671            }
5672            if (typeof SB_AECOMMERCE_ACTIVE_USER != ND) {
5673                return [SB_AECOMMERCE_ACTIVE_USER, 'aecommerce'];
5674            }
5675            return false;
5676        },
5677
5678        // Check if an app is installed and active
5679        is: function (name) {
5680            if (admin) return SBAdmin.apps.is(name);
5681            if (name == 'wordpress' || name == 'wp') return CHAT_SETTINGS.wp;
5682            return name in CHAT_SETTINGS ? CHAT_SETTINGS[name] : false;
5683        },
5684
5685        shopify: {
5686
5687            startCartSynchronization: function () {
5688                setInterval(() => {
5689                    if (activeUser()) {
5690                        fetch('/cart.js').then((response) => response.json()).then((cart) => {
5691                            cart = {
5692                                total: cart.total_price,
5693                                currency: cart.currency,
5694                                items: cart.items.map(item => ({
5695                                    id: item.product_id,
5696                                    price: item.price,
5697                                    handle: item.handle,
5698                                    title: item.title,
5699                                    quantity: item.quantity
5700                                }))
5701                            };
5702                            let cart_string = JSON.stringify(cart);
5703                            if (storage('shopify-cart') != cart_string) {
5704                                storage('shopify-cart', cart_string);
5705                                SBF.ajax({
5706                                    function: 'shopify-cart-sync',
5707                                    cart: cart
5708                                });
5709                            }
5710                        });
5711                    }
5712                }, 10000);
5713            }
5714        },
5715
5716        wordpress: {
5717
5718            ajax: function (action, data, onSuccess = false) {
5719                if (typeof SB_WP_AJAX_URL == ND) {
5720                    return onSuccess ? onSuccess(false) : false;
5721                };
5722                $.ajax({
5723                    method: 'POST',
5724                    url: SB_WP_AJAX_URL,
5725                    data: $.extend({ action: 'sb_wp_ajax', type: action }, data)
5726                }).done((response) => {
5727                    if (onSuccess) {
5728                        onSuccess(response);
5729                    }
5730                });
5731            }
5732        },
5733
5734        dialogflow: {
5735            typing_enabled: true,
5736            project_id: false,
5737            busy: false,
5738
5739            message: function (message = '', attachments = [], audio = false) {
5740                if (message.length < 2 && !attachments.length) {
5741                    return;
5742                }
5743                if (!audio) {
5744                    for (var i = 0; i < attachments.length; i++) {
5745                        if (attachments[i][0].includes('voice_message')) {
5746                            audio = attachments[i][1];
5747                        }
5748                    }
5749                }
5750                if (!$('.sb-emoji-list').html().includes('<li>' + message + '</li>')) {
5751                    this.openAI(message, false, audio, attachments);
5752                }
5753            },
5754
5755            openAI: function (message, onSuccess = false, audio = false, attachments = []) {
5756                if (CHAT_SETTINGS.is_chatbot_active) {
5757                    SBChat.headerAgent(true);
5758                    if (SBChat.agent_id == bot_id && (!SBApps.dialogflow.humanTakeoverActive() || this.typing_enabled)) {
5759                        this.typing();
5760                    }
5761                    if (this.chatbotLimit()) {
5762                        return SBChat.sendMessage(bot_id, this.chatbotLimit());
5763                    }
5764                    setTimeout(() => {
5765                        SBF.ajax({
5766                            function: 'open-ai-message',
5767                            message: message,
5768                            conversation_id: SBChat.conversation ? SBChat.conversation.id : false,
5769                            audio: audio,
5770                            extra: { token: SBApps.dialogflow.token },
5771                            attachments: attachments,
5772                            context: CHAT_SETTINGS.open_ai_context_awareness ? document.title.toUpperCase() + '\n\n\n' + $('meta[name="description"]').attr('content') || '' : false
5773                        }, (response) => {
5774                            this.busy = false;
5775                            SBChat.typing(-1, 'stop');
5776                            SBF.event('SBOpenAIMessage', { response: response, message: message });
5777                            if (response && (response[0] || response[5])) {
5778                                if (CHAT_SETTINGS.chatbot_limit) {
5779                                    this.chatbot_limit++;
5780                                }
5781                                if (CHAT_SETTINGS.slack_active && message && (!dialogflow_human_takeover || SBApps.dialogflow.humanTakeoverActive())) {
5782                                    SBApps.slack.message(activeUser().id, CHAT_SETTINGS.bot_name, CHAT_SETTINGS.bot_image, response[1]);
5783                                }
5784                                if (response[3]) {
5785                                    SBChat.offlineMessage();
5786                                    SBChat.followUp();
5787                                }
5788
5789                                // Payload
5790                                if (response[5]) {
5791                                    if (response[5].redirect) {
5792                                        setTimeout(() => {
5793                                            document.location = response[5].redirect;
5794                                        }, 500);
5795                                    }
5796                                    if (response[5].open_article) {
5797                                        SBChat.showArticles(response[5].open_article);
5798                                    }
5799                                    if (response[5].event == 'conversation-status-update-3') {
5800                                        SBChat.conversationArchived();
5801                                    }
5802                                    if (response[5].login_cookie) {
5803                                        SBF.loginCookie(response[5].login_cookie[1]);
5804                                        activeUser(new SBUser(response[5].login_cookie[0]));
5805                                    }
5806                                }
5807                                this.chatbotLimit(false);
5808                            } else if (response[1] !== false) {
5809                                SBF.error(response[1].error ? response[1].error.message : response[1], 'SBApps.dialogflow.openAI');
5810                            }
5811                            if (onSuccess) {
5812                                onSuccess(response);
5813                            }
5814                        });
5815                        this.busy = true;
5816                    }, CHAT_SETTINGS.bot_delay == 0 ? 2000 : parseInt(CHAT_SETTINGS.bot_delay) * 1000);
5817                }
5818            },
5819
5820            flowOnLoad() {
5821                if (CHAT_SETTINGS.flow_on_load && !storage('flow-on-load') && activeUser()) {
5822                    if (SBChat.conversation) {
5823                        SBF.ajax({
5824                            function: 'run-flow-on-load',
5825                            message: CHAT_SETTINGS.flow_on_load,
5826                            conversation_id: SBChat.conversation.id
5827                        });
5828                    } else {
5829                        SBChat.newConversation(3, activeUser().id, '', [], null, null, () => {
5830                            SBF.ajax({
5831                                function: 'run-flow-on-load',
5832                                message: CHAT_SETTINGS.flow_on_load,
5833                                conversation_id: SBChat.conversation.id
5834                            });
5835                        });
5836                    }
5837                    storage('flow-on-load', true);
5838                }
5839            },
5840
5841            typing: function () {
5842                clearTimeout(timeout_typing);
5843                timeout_typing = setTimeout(() => {
5844                    SBChat.typing(-1, 'start');
5845                }, 1000);
5846            },
5847
5848            active: function (active = true) {
5849                if (active === false) {
5850                    SBChat.conversation.set('is_human_takeover', true);
5851                    return false;
5852                }
5853                if (active == 'activate') {
5854                    SBChat.conversation.set('is_human_takeover', false);
5855                }
5856                if (!admin && SBApps.dialogflow.humanTakeoverActive() && CHAT_SETTINGS.dialogflow_human_takeover_disable_chatbot) {
5857                    return false;
5858                }
5859                return CHAT_SETTINGS.is_chatbot_active && !admin && (!SBChat.conversation || !SBApps.dialogflow.humanTakeoverActive() || !SBChat.agent_online) && (!CHAT_SETTINGS.dialogflow_office_hours || !CHAT_SETTINGS.office_hours);
5860            },
5861
5862            humanTakeover: function () {
5863                SBF.ajax({
5864                    function: 'human-takeover',
5865                    conversation_id: SBChat.conversation.id
5866                }, () => {
5867                    SBChat.offlineMessage();
5868                    SBChat.followUp();
5869                    this.active(false);
5870                    if (CHAT_SETTINGS.queue_human_takeover) {
5871                        CHAT_SETTINGS.queue = true;
5872                        SBChat.queue(SBChat.conversation.id);
5873                    }
5874                });
5875            },
5876
5877            humanTakeoverActive: function () {
5878                return SBChat.conversation ? SBChat.conversation.get('is_human_takeover') : false;
5879            },
5880
5881            translate: function (strings, language_code, onSuccess, message_ids, conversation_id) {
5882                SBF.ajax({
5883                    function: 'google-translate',
5884                    strings: strings,
5885                    language_code: language_code,
5886                    message_ids: message_ids,
5887                    conversation_id: conversation_id
5888                }, (response) => {
5889                    onSuccess(response);
5890                });
5891            },
5892
5893            chatbotLimit: function (check = true) {
5894                if (CHAT_SETTINGS.chatbot_limit) {
5895                    let chatbot_limit = storage('chatbot_limit');
5896                    let now = (new Date()).getTime() / 1000;
5897                    if (!chatbot_limit) {
5898                        chatbot_limit = [];
5899                    }
5900                    if (check) {
5901                        let interval = now - CHAT_SETTINGS.chatbot_limit.interval;
5902                        let chatbot_limit_new = [];
5903                        for (var i = 0; i < chatbot_limit.length; i++) {
5904                            if (chatbot_limit[i] > interval) {
5905                                chatbot_limit_new.push(chatbot_limit[i]);
5906                            }
5907                        }
5908                        storage('chatbot_limit', chatbot_limit_new);
5909                        if (chatbot_limit_new.length >= CHAT_SETTINGS.chatbot_limit.quota) {
5910                            return CHAT_SETTINGS.chatbot_limit.message;
5911                        }
5912                    } else {
5913                        chatbot_limit.push(now);
5914                        storage('chatbot_limit', chatbot_limit);
5915                    }
5916                }
5917                return false;
5918            }
5919        },
5920
5921        woocommerce: {
5922
5923            // Update the cart
5924            updateCart: function (action, product_id, onSuccess = false) {
5925                SBApps.wordpress.ajax(action, { product_id: product_id }, onSuccess);
5926            },
5927
5928            // Waiting list
5929            waitingList: function (action = 'request', product_id = false) {
5930                if (typeof SB_WP_WAITING_LIST !== ND && (action != 'request' || SB_WP_WAITING_LIST && SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID, 24)) && activeUser()) {
5931                    SBF.ajax({
5932                        function: 'woocommerce-waiting-list',
5933                        product_id: product_id === false ? SB_WP_PAGE_ID : product_id,
5934                        conversation_id: SBChat.conversation.id,
5935                        action: action,
5936                        token: this.token
5937                    }, (response) => {
5938                        if (response) {
5939                            SBF.storageTime('waiting-list-' + SB_WP_PAGE_ID);
5940                            if (action == 'request' && (!SBChat.chat_open || SBChat.dashboard)) {
5941                                SBChat.updateConversations();
5942                            }
5943                        }
5944                    });
5945                }
5946            }
5947        },
5948
5949        aecommerce: {
5950
5951            cart: function () {
5952                if (SBApps.is('aecommerce') && typeof SB_AECOMMERCE_CART != ND && typeof SB_AECOMMERCE_ACTIVE_USER != ND && storage('aecommerce') != JSON.stringify(SB_AECOMMERCE_CART)) {
5953                    SBF.ajax({
5954                        function: 'aecommerce-cart',
5955                        cart: SB_AECOMMERCE_CART
5956                    }, () => {
5957                        storage('aecommerce', JSON.stringify(SB_AECOMMERCE_CART));
5958                    });
5959                }
5960            }
5961        },
5962
5963        martfury: {
5964
5965            privateChat: function () {
5966                let store = $('#tab-vendor > h4,.ps-product__vendor > a');
5967                if (store.length) {
5968                    store = store.html().toLowerCase();
5969                    for (var i = 0; i < CHAT_SETTINGS.martfury.length; i++) {
5970                        if (store == CHAT_SETTINGS.martfury[i]['martfury-linking-store'].toLowerCase()) {
5971                            let agent_id = CHAT_SETTINGS.martfury[i]['martfury-linking-agent'];
5972                            let conversations = SBF.activeUser() ? SBF.activeUser().conversations : [];
5973                            SBChat.default_agent = agent_id;
5974                            for (var j = 0; j < conversations.length; j++) {
5975                                if (conversations[j].get('agent_id') == agent_id) {
5976                                    SBChat.openConversation(conversations[j].id);
5977                                    return;
5978                                }
5979                            }
5980                            SBChat.clear();
5981                            SBChat.hideDashboard();
5982                        }
5983                    }
5984                }
5985            }
5986        },
5987
5988        whatsapp: {
5989
5990            call: {
5991
5992                answer: function (conversation_id, onSuccess) {
5993                    const incoming_call = admin ? SBAdmin.conversations.active_calls[conversation_id] : call_data;
5994                    if (!incoming_call) {
5995                        return;
5996                    }
5997                    SBF.call.initRTC((answer) => {
5998                        SBF.ajax({
5999                            function: 'whatsapp-call-answer',
6000                            phone_number_id: incoming_call.phone_number_id,
6001                            call_id: incoming_call.id,
6002                            sdp_answer: answer.sdp
6003                        }, () => {
6004                            SBF.ajax({
6005                                function: 'whatsapp-call-answer',
6006                                phone_number_id: incoming_call.phone_number_id,
6007                                call_id: incoming_call.id,
6008                                sdp_answer: answer.sdp,
6009                                is_accept: true
6010                            }, (response) => {
6011                                onSuccess(response);
6012                            });
6013                        });
6014                    }, incoming_call.session.sdp);
6015                },
6016
6017                decline: function (conversation_id, is_terminate, onSuccess) {
6018                    const incoming_call = admin ? SBAdmin.conversations.active_calls[conversation_id] : call_data;
6019                    if (!incoming_call) {
6020                        return;
6021                    }
6022                    SBF.ajax({
6023                        function: 'whatsapp-call-decline',
6024                        phone_number_id: incoming_call.phone_number_id,
6025                        call_id: incoming_call.id,
6026                        is_terminate: is_terminate
6027                    }, (response) => {
6028                        onSuccess(response);
6029                    });
6030                },
6031
6032                start: function (sdp, user_id, onSuccess) {
6033                    SBF.ajax({
6034                        function: 'whatsapp-call-start',
6035                        sdp: sdp,
6036                        user_id: user_id
6037                    }, (response) => {
6038                        if (response.error) {
6039                            SBF.error(response.error.message, 'SBApps.whatsapp.call.start');
6040                        } else if (response == 'request-sent' && admin) {
6041                            SBAdmin.infoPanel('Call permission request sent successfully. The user will receive a message asking for consent to receive voice calls.');
6042                        } else {
6043                            call_data = response;
6044                        }
6045                        onSuccess(response);
6046                    });
6047                }
6048            }
6049        },
6050
6051        slack: {
6052            slack_channel: [-1, -1],
6053
6054            message: function (user_id, full_name, profile_image, message, attachments = []) {
6055                if (!SBChat.conversation || (!message && !attachments.length)) {
6056                    return false;
6057                }
6058                let conversation_id = SBChat.conversation.id;
6059                SBF.ajax({
6060                    function: 'send-slack-message',
6061                    user_id: user_id,
6062                    full_name: full_name,
6063                    profile_image: profile_image,
6064                    conversation_id: conversation_id,
6065                    message: message,
6066                    attachments: attachments,
6067                    channel: this.slack_channel[0] == activeUser().id ? this.slack_channel[1] : false
6068                }, (response) => {
6069                    this.slack_channel = [activeUser().id, response[1]];
6070                    SBF.event('SBSlackMessageSent', { message: message, conversation_id: conversation_id, slack_channel: response[1] });
6071                });
6072            }
6073        }
6074    }
6075    window.SBApps = SBApps;
6076
6077    /*
6078    * ----------------------------------------------------------
6079    * INIT
6080    * ----------------------------------------------------------
6081    */
6082
6083    $(document).ready(function () {
6084        main = $('.sb-admin, .sb-admin-start');
6085        if (main.length) {
6086            admin = true;
6087            initialize();
6088            return;
6089        }
6090        let url_full;
6091        let url;
6092        let init = false;
6093        if (typeof SB_INIT_URL != ND) {
6094            if (!SB_INIT_URL.includes('.js')) {
6095                SB_INIT_URL += '/js/main.js?v=' + version;
6096            }
6097            url_full = SB_INIT_URL;
6098        } else {
6099            let scripts = document.getElementsByTagName('script');
6100            let checks = ['init.js', 'main.js', 'min/init.min.js', 'min/main.min.js'];
6101            for (var i = 0; i < scripts.length; i++) {
6102                let source = scripts[i].src;
6103                if (scripts[i].id == 'sbinit') {
6104                    url_full = source;
6105                    init = init ? init : url_full.includes('init.');
6106                    break;
6107                } else {
6108                    for (var j = 0; j < checks.length; j++) {
6109                        if (source && source.includes('/supportboard/js/' + checks[j])) {
6110                            url_full = source;
6111                            init = init ? init : url_full.includes('init.');
6112                            break;
6113                        }
6114                    }
6115                }
6116            }
6117        }
6118        let parameters = SBF.getURL(false, url_full);
6119        if (parameters.url) {
6120            url_full = parameters.url;
6121        }
6122        if (typeof SB_DISABLED != ND && SB_DISABLED) {
6123            return;
6124        }
6125        if (init) {
6126            initialize();
6127            return;
6128        }
6129        if (typeof SB_TICKETS != ND || (parameters.mode == 'tickets')) {
6130            tickets = true;
6131            parameters.mode = 'tickets';
6132        }
6133        if (parameters.cloud) {
6134            cloud_data = parameters.cloud;
6135        }
6136        let lang_optional = is_shopify && Shopify.locale != 'en' ? Shopify.locale : false;
6137        let min = url_full.lastIndexOf('main.min.js');
6138        url = url_full.substr(0, url_full.lastIndexOf('main.js') > 0 ? (url_full.lastIndexOf('main.js') - 4) : (min - 8));
6139        let url_chat = url + '/include/init.php' + (parameters.lang ? '?lang=' + parameters.lang : '') + (lang_optional ? '?lang_optional=' + lang_optional : '') + (parameters.mode ? '&mode=' + parameters.mode : '') + (cloud_data ? '&cloud=' + cloud_data : '');
6140        SBF.cors('GET', url_chat.replace('.php&', '.php?'), (response) => {
6141            let target = 'body';
6142            if (tickets && $('#sb-tickets').length) {
6143                target = '#sb-tickets';
6144            }
6145            $(target).append(response);
6146            SBF.loadResource(url + '/css/' + (tickets ? 'tickets' : 'main') + '.css');
6147            if (tickets) {
6148                SBF.loadResource(url + '/apps/tickets/tickets' + (min > 0 ? '.min' : '') + '.js?v=' + version, true, () => {
6149                    initialize();
6150                });
6151            } else {
6152                initialize();
6153            }
6154            if (parameters.lang) {
6155                SB_LANG = [parameters.lang, admin ? 'admin' : 'front'];
6156            }
6157        });
6158    });
6159
6160    function initialize() {
6161        main = $('.sb-admin, .sb-chat, .sb-tickets');
6162
6163        // Initialize the chat and the user
6164        if (main.length && typeof SB_AJAX_URL != ND) {
6165            chat = main.find('.sb-list').eq(0);
6166            chat_editor = main.find('.sb-editor');
6167            chat_textarea = chat_editor.find('textarea');
6168            chat_scroll_area = main.find(admin || tickets ? '.sb-list' : '> div > .sb-scroll-area');
6169            chat_header = chat_scroll_area.find('.sb-header');
6170            chat_emoji = chat_editor.find('.sb-emoji');
6171            chat_overlay_panel = main.find('.sb-overlay-panel');
6172            chat_status = tickets ? main.find('.sb-profile-agent .sb-status') : null;
6173            SBChat.enabledAutoExpand();
6174            SBChat.audio = main.find('#sb-audio').get(0);
6175            SBChat.label_date = main.find('.sb-label-date-top');
6176
6177            // Check if cookies works
6178            SBF.cookie('sb-check', 'ok', 1, 'set');
6179            if (SBF.cookie('sb-check') != 'ok') {
6180                cookies_supported = false;
6181                console.warn('Support Board: cookies not available.');
6182            } else {
6183                SBF.cookie('sb-check', false, false, false);
6184            }
6185            if (!admin) {
6186                SBF.ajax({
6187                    function: 'get-front-settings',
6188                    tickets: tickets,
6189                    popup: !storage('popup') && !tickets
6190                }, (response) => {
6191                    CHAT_SETTINGS = response;
6192                    if (typeof SB_LOCAL_SETTINGS != ND) {
6193                        $.extend(CHAT_SETTINGS, SB_LOCAL_SETTINGS);
6194                    }
6195                    bot_id = CHAT_SETTINGS.bot_id;
6196                    dialogflow_human_takeover = CHAT_SETTINGS.dialogflow_human_takeover;
6197                    agents_online = CHAT_SETTINGS.agents_online;
6198                    SBPusher.active = CHAT_SETTINGS.pusher;
6199                    if (CHAT_SETTINGS.language) {
6200                        SB_LANG = CHAT_SETTINGS.language != 'auto' ? [CHAT_SETTINGS.language, 'front'] : (is_shopify && Shopify.locale != 'en' ? Shopify.locale : false);
6201                    }
6202                    if (typeof SB_REGISTRATION_REQUIRED != ND) {
6203                        CHAT_SETTINGS.registration_required = SB_REGISTRATION_REQUIRED;
6204                        CHAT_SETTINGS.tickets_registration_required = SB_REGISTRATION_REQUIRED;
6205                    }
6206                    if (typeof SB_ARTICLES_PAGE != ND && SB_ARTICLES_PAGE) {
6207                        SBChat.automations.runAll();
6208                        SBChat.initArticlesPage();
6209                    }
6210                    if (typeof SB_ARTICLE_ID != ND) {
6211                        SBChat.initSingleArticlePage();
6212                    }
6213                    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())))) {
6214                        SBChat.initChat();
6215                    }
6216                    if (CHAT_SETTINGS.cron) {
6217                        setTimeout(function () {
6218                            SBF.ajax({ function: 'cron-jobs' });
6219                        }, 10000);
6220                    }
6221                    if (CHAT_SETTINGS.cron_email_piping) {
6222                        setTimeout(function () {
6223                            SBF.ajax({ function: 'email-piping' });
6224                        }, 8000);
6225                    }
6226                    if (CHAT_SETTINGS.push_notifications_users) {
6227                        SBF.serviceWorker.init();
6228                    }
6229                    if (tickets) {
6230                        if (CHAT_SETTINGS.tickets_default_department) {
6231                            SBChat.default_department = CHAT_SETTINGS.tickets_default_department;
6232                        }
6233                        if (CHAT_SETTINGS.dialogflow_disable_tickets) {
6234                            CHAT_SETTINGS.open_ai_active = false;
6235                        }
6236                    }
6237                    if (SBApps.is('martfury')) {
6238                        let session = false;
6239                        setInterval(function () {
6240                            if (activeUser()) {
6241                                let current = SBF.cookie('XSRF-TOKEN');
6242                                if (current && current != session) {
6243                                    SBF.ajax({ function: 'martfury-session' });
6244                                    session = current;
6245                                }
6246                            }
6247                        }, 3000);
6248                    }
6249                    if (is_shopify) {
6250                        SBApps.shopify.startCartSynchronization();
6251                        main.addClass('sb-shopify');
6252                    }
6253                    setAudio();
6254                    SBApps.aecommerce.cart();
6255                    setTimeout(() => {
6256                        SBF.event('SBReady');
6257                    }, 500);
6258                });
6259            } else {
6260                setAudio();
6261                SBF.event('SBReady');
6262            }
6263
6264            $(chat_editor).on('keydown', 'textarea', function (e) {
6265                if (!e) return;
6266                if (e.which == 13 && (!tickets || CHAT_SETTINGS.tickets_enter_button) && !mobile && !e.ctrlKey && !e.shiftKey) {
6267                    SBChat.submit();
6268                    e.preventDefault;
6269                    return false;
6270                }
6271                if (admin && e.which == 13 && e.ctrlKey) {
6272                    SBChat.insertText('\n');
6273                }
6274            });
6275
6276            $(main).on('keydown', '.sb-dashboard-articles input', function (e) {
6277                if (e.which == 13) {
6278                    $(this).next().click();
6279                }
6280            });
6281
6282            if (typeof SB_DEFAULT_DEPARTMENT !== ND) {
6283                SBChat.default_department = SB_DEFAULT_DEPARTMENT;
6284            }
6285            if (typeof SB_DEFAULT_AGENT !== ND) {
6286                SBChat.default_agent = SB_DEFAULT_AGENT;
6287            }
6288            if (typeof SB_DEFAULT_TAGS !== ND) {
6289                SBChat.default_tags = SB_DEFAULT_TAGS;
6290            }
6291            if (SBF.storage('call-active')) {
6292                if (admin) {
6293                    SBAdmin.conversations.active_calls[SBF.storage('call-active')[0]] = SBF.storage('call-active')[1];
6294                } else {
6295                    call_data = SBF.storage('call-active')[1];
6296                }
6297                SBF.call.decline(true, SBF.storage('call-active')[0], () => {
6298                    setTimeout(() => {
6299                        if (admin) {
6300                            SBAdmin.conversations.update();
6301                        } else {
6302                            SBChat.update();
6303                        }
6304                    }, 1000);
6305                });
6306                SBF.storage('call-active', false);
6307            }
6308        } else {
6309            SBF.event('SBReady');
6310        }
6311
6312        // Image from clipboard
6313        document.onpaste = function (pasteEvent) {
6314            let item = pasteEvent.clipboardData.items[0];
6315            if (item.type.indexOf('image') === 0) {
6316                var reader = new FileReader();
6317                reader.onload = function (event) {
6318                    let data = event.target.result.split(',')
6319                    let bytes = data[0].indexOf('base64') >= 0 ? atob(data[1]) : decodeURI(data[1])
6320                    let ia = new Uint8Array(bytes.length)
6321                    for (let i = 0; i < bytes.length; i++) {
6322                        ia[i] = bytes.charCodeAt(i)
6323                    }
6324                    let form = new FormData();
6325                    form.append('file', new Blob([ia], { type: data[0].split(':')[1].split(';')[0] }), 'image_print.jpg');
6326                    SBF.upload(form, function (response) { SBChat.uploadResponse(response) });
6327                };
6328                reader.readAsDataURL(item.getAsFile());
6329            }
6330        }
6331
6332        // Disable real-time if browser tab not active
6333        document.addEventListener('visibilitychange', function () {
6334            SBF.visibilityChange(document.visibilityState);
6335        }, false);
6336
6337        $(main).on('click', function () {
6338            if (!SBChat.tab_active) {
6339                SBF.visibilityChange();
6340            }
6341        });
6342
6343        // Set the global container for both admin and front
6344        global = main;
6345        if (admin) {
6346            main = main.find('.sb-conversation');
6347        }
6348        call_bar = global.find('#sb-call-bar');
6349
6350        // Scroll detection
6351        $(chat_scroll_area).on('scroll', function () {
6352            let scroll = chat_scroll_area.scrollTop();
6353            let count = label_date_items.length;
6354            SBChat.scrollHeader();
6355            if (!SBChat.label_date_show && count && label_date_items[count - 1].getBoundingClientRect().top > (chat[0].getBoundingClientRect().top + chat.outerHeight())) {
6356                SBChat.label_date_show = true;
6357                if (count > 1) {
6358                    SBChat.label_date.html($(label_date_items[count - 2]).html());
6359                }
6360            }
6361            if (SBChat.label_date_show) {
6362                SBChat.label_date.sbActive(true);
6363                clearTimeout(label_date_timeout[0]);
6364                label_date_timeout[0] = setTimeout(() => { SBChat.label_date.sbActive(false) }, 1500);
6365            }
6366            if (count) {
6367                if (SBChat.isBottom()) {
6368                    SBChat.label_date.html($(label_date_items[count - 1]).html());
6369                    SBChat.label_date_show = label_date_items.length && label_date_items[label_date_items.length - 1].getBoundingClientRect().top < 0;
6370                } else {
6371                    let label_date_top = SBChat.label_date[0].getBoundingClientRect().top;
6372                    for (var i = 0; i < count; i++) {
6373                        let top = label_date_items[i].getBoundingClientRect().top;
6374                        if ((top - 100) < label_date_top && (top + 100) > label_date_top) {
6375                            let label = $(label_date_items[label_date_history[0] > scroll && i > 0 ? i - 1 : i]).html();
6376                            if (label != label_date_history[1]) {
6377                                SBChat.label_date.html(label);
6378                                label_date_history[1] = label;
6379                            }
6380                            break;
6381                        }
6382                    }
6383                }
6384            }
6385            label_date_history[0] = scroll;
6386        });
6387
6388        // Show the message menu
6389        $(chat).on('click', '.sb-menu-btn', function () {
6390            let menu = $(this).parent().find('.sb-menu');
6391            let active = $(menu).sbActive();
6392            SBF.deactivateAll();
6393            if (!active) {
6394                $(menu).sbActive(true);
6395                SBF.deselectAll();
6396                if (admin) SBAdmin.open_popup = menu;
6397            }
6398        });
6399
6400        // Mobile
6401        if (mobile) {
6402            $(chat_editor).on('click', '.sb-textarea', function () {
6403                main.addClass('sb-header-hidden');
6404                $(this).find('textarea').get(0).focus();
6405                if (SBChat.isBottom()) {
6406                    SBChat.scrollBottom();
6407                    setTimeout(() => {
6408                        SBChat.scrollBottom();
6409                    }, 200);
6410                }
6411            });
6412
6413            $(chat_editor).on('focusout', '.sb-textarea', function () {
6414                setTimeout(() => {
6415                    main.removeClass('sb-header-hidden');
6416                }, 300);
6417            });
6418
6419            $(chat_editor).on('click', '.sb-submit', function () {
6420                chat_textarea.blur();
6421            });
6422
6423            window.addEventListener('popstate', function () {
6424                if (SBChat.chat_open) {
6425                    SBChat.open(false);
6426                }
6427            });
6428        }
6429
6430        // Hide the message menu
6431        $(chat).on('click', '.sb-menu li', function () {
6432            $(this).parent().sbActive(false);
6433        });
6434
6435        // Send a message
6436        $(chat_editor).on('click', '.sb-submit', function () {
6437            SBChat.submit();
6438        });
6439
6440        // Clear a message
6441        $(chat_editor).on('click', '.sb-clear-text', function () {
6442            chat_textarea.val('').trigger('change').focus();
6443            chat_editor.find('.sb-attachments').html('');
6444            chat_editor.sbActive(false);
6445        });
6446
6447        // Open the chat
6448        $('body').on('click', '.sb-chat-btn,.sb-responsive-close-btn, #sb-open-chat, .
6448sb-open-chat', function () {
6449            SBChat.open(!SBChat.chat_open);
6450        });
6451
6452        // Show the dashboard
6453        $(main).on('click', '.sb-dashboard-btn', function () {
6454            SBChat.showDashboard();
6455            if (chat_scroll_area.find(' > .sb-panel-articles > .sb-article').length) {
6456                SBChat.showArticles();
6457            }
6458            storage('open-conversation', 0);
6459            force_action = false;
6460        });
6461
6462        // Open a conversation from the dashboard
6463        $(main).on('click', '.sb-user-conversations li', function () {
6464            SBChat.openConversation($(this).attr('data-conversation-id'));
6465        });
6466
6467        // Start a new conversation from the dashboard
6468        $(main).on('click', '.sb-btn-new-conversation, .sb-departments-list > div, .sb-agents-list > div', function () {
6469            let id = $(this).data('id');
6470            let existing_conversation = false;
6471            if (!SBF.null(id)) {
6472                let is_departments = $(this).parent().hasClass('sb-departments-list');
6473                if (is_departments) {
6474                    SBChat.default_department = parseInt(id);
6475                } else {
6476                    SBChat.default_agent = parseInt(id);
6477                }
6478                if ($(this).parent().data('force-one')) {
6479                    let converstations = activeUser() ? activeUser().conversations : [];
6480                    for (var i = 0; i < converstations.length; i++) {
6481                        if ((is_departments && converstations[i].get('department') == id) || (!is_departments && converstations[i].get('agent_id') == id)) {
6482                            existing_conversation = true;
6483                            SBChat.openConversation(converstations[i].id);
6484                            break;
6485                        }
6486                    }
6487                }
6488            }
6489            if (!existing_conversation) {
6490                force_action = 'new-conversation';
6491                SBChat.clear();
6492                SBChat.hideDashboard();
6493            }
6494        });
6495
6496        // Displays all conversations in the dashboard
6497        $(main).on('click', '.sb-btn-all-conversations', function () {
6498            main.find('.sb-dashboard-conversations').removeClass('sb-conversations-hidden');
6499        });
6500
6501        // Events uploader
6502        $(chat_editor).on('click', '.sb-btn-attachment', function () {
6503            if (!SBChat.is_busy) {
6504                chat_editor.find('.sb-upload-files').val('').click();
6505            }
6506        });
6507
6508        $(chat_editor).on('click', '.sb-attachments > div > i', function (e) {
6509            $(this).parent().remove();
6510            if (!chat_textarea.val() && chat_editor.find('.sb-attachments > div').length == 0) {
6511                chat_editor.sbActive(false);
6512            }
6513            e.preventDefault();
6514            return false;
6515        });
6516
6517        $(chat_editor).on('change', '.sb-upload-files', function (data) {
6518            SBChat.busy(true);
6519            $(this).sbUploadFiles(function (response) {
6520                SBChat.uploadResponse(response);
6521            });
6522            SBF.event('SBAttachments');
6523        });
6524
6525        $(chat_editor).on('dragover', function (e) {
6526            $(this).addClass('sb-drag');
6527            clearTimeout(timeout);
6528            e.preventDefault();
6529            e.stopPropagation();
6530        });
6531
6532        $(chat_editor).on('dragleave', function (e) {
6533            timeout = setTimeout(() => {
6534                $(this).removeClass('sb-drag');
6535            }, 200);
6536            e.preventDefault();
6537            e.stopPropagation();
6538        });
6539
6540        $(chat_editor).on('drop', function (e) {
6541            let files = e.originalEvent.dataTransfer.files;
6542            e.preventDefault();
6543            e.stopPropagation();
6544            if (files.length > 0) {
6545                for (var i = 0; i < files.length; ++i) {
6546                    let form = new FormData();
6547                    form.append('file', files[i]);
6548                    SBF.upload(form, function (response) { SBChat.uploadResponse(response) });
6549                }
6550            }
6551            $(this).removeClass('sb-drag');
6552            return false;
6553        });
6554
6555        // Articles
6556        $(main).on('click', '.sb-articles > div, .sb-btn-all-articles:not([onclick])', function () {
6557            if ($(this).hasClass('sb-btn-all-articles')) {
6558                SBChat.showArticles();
6559            } else {
6560                SBChat.showArticles($(this).attr('data-id'), $(this).attr('data-is-category'));
6561                if (!mobile) {
6562                    SBChat.resize();
6563                }
6564            }
6565        });
6566
6567        $(main).on('click', '.sb-dashboard-articles .sb-input-btn .sb-submit-articles', function () {
6568            SBChat.searchArticles($(this).parent().find('input').val(), this, $(this).parent().next());
6569        });
6570
6571        $(global).on('click', '.sb-article [data-rating]', function () {
6572            SBChat.articleRatingOnClick(this);
6573        });
6574
6575        $(chat).on('click', '.sb-rich-button a', function (e) {
6576            let link = $(this).attr('href');
6577            if (link.indexOf('#') === 0) {
6578                if (link.indexOf('#article-') === 0) {
6579                    SBChat.showArticles(link.replace('#article-', ''));
6580                    e.preventDefault();
6581                    return false;
6582                }
6583            }
6584        });
6585
6586        $('body').on('keydown', '.sb-articles-header input', function (e) {
6587            if (e.which == 13 && !mobile && !e.ctrlKey && !e.shiftKey) {
6588                $(this).next().click();
6589            }
6590        });
6591
6592        // Calls
6593        $(global).on('click', '#sb-call-answer', function () {
6594            if (loading(this)) return;
6595            SBF.call.answer();
6596        });
6597
6598        $(global).on('click', '#sb-call-decline', function () {
6599            if (loading(this)) return;
6600            SBF.call.decline(call_bar.hasClass('sb-call-active') || call_bar.hasClass('sb-call-ringing'));
6601        });
6602
6603        $(global).on('click', '#sb-call-mute', function () {
6604            let is_muted = !$(this).hasClass('sb-btn-red');
6605            SBF.call.mute(!is_muted);
6606            $(this).setClass('sb-btn-red', is_muted);
6607        });
6608
6609        // Rating
6610        $(chat_overlay_panel).on('click', '[data-rating]', function () {
6611            let area = chat_overlay_panel.find('> div:last-child');
6612            let rating = $(this).attr('data-rating');
6613            let conversation = SBChat.conversation ? SBChat.conversation : activeUser().getLastConversation();
6614            let agent = conversation.getLastUserMessage(false, true);
6615            loading(area);
6616            SBF.ajax({
6617                function: 'set-rating',
6618                conversation_id: conversation.id,
6619                agent_id: agent ? agent.get('user_id') : false,
6620                user_id: activeUser().id,
6621                message: area.find('textarea').val(),
6622                rating: rating == 'positive' ? 1 : -1
6623            }, () => {
6624                if (chat_overlay_panel.attr('data-close-chat')) {
6625                    SBChat.closeChat();
6626                }
6627                SBChat.update();
6628                chat_overlay_panel.sbActive(false);
6629                area.sbLoading(false);
6630            });
6631        });
6632
6633        // Lightbox
6634        $(global).on('click', '.sb-lightbox-media > i', function () {
6635            global.find('.sb-lightbox-media').sbActive(false);
6636            if (admin) {
6637                SBAdmin.open_popup = false;
6638            }
6639            return false;
6640        });
6641
6642        $(main).on('click', '.sb-image', function () {
6643            SBF.lightbox($(this).html());
6644        });
6645
6646        $(main).on('click', '.sb-slider-images .sb-card-img', function () {
6647            SBF.lightbox(`<img loading="lazy" src="${$(this).attr('data-value')}" />`);
6648        });
6649
6650        // Event: on conversation loaded
6651        $(document).on('SBConversationLoaded', function () {
6652            if (storage('queue')) {
6653                SBChat.queue(storage('queue'));
6654            }
6655        });
6656
6657        // Events emoji
6658        $(chat_editor).on('click', '.sb-btn-emoji', function () {
6659            SBChat.showEmoji(this);
6660        });
6661
6662        $(chat_emoji).on('click', '.sb-emoji-list li', function (e) {
6663            SBChat.insertEmoji($(this).html());
6664            if (mobile) clearTimeout(timeout);
6665        });
6666
6667        $(chat_emoji).find('.sb-emoji-list').on('touchend', function (e) {
6668            timeout = setTimeout(() => {
6669                SBChat.mouseWheelEmoji(e);
6670            }, 50);
6671        });
6672
6673        $(chat_emoji).find('.sb-emoji-list').on('mousewheel DOMMouseScroll', function (e) {
6674            SBChat.mouseWheelEmoji(e);
6675        });
6676
6677        $(chat_emoji).find('.sb-emoji-list').on('touchstart', function (e) {
6678            SBChat.emoji_options.touch = e.originalEvent.touches[0].clientY;
6679        });
6680
6681        $(chat_emoji).on('click', '.sb-emoji-bar > div', function () {
6682            SBChat.clickEmojiBar(this);
6683        });
6684
6685        $(chat_emoji).on('click', '.sb-select li', function () {
6686            SBChat.categoryEmoji($(this).data('value'));
6687        });
6688
6689        $(chat_emoji).find('.sb-search-btn input').on('change keyup paste', function () {
6690            SBChat.searchEmoji($(this).val());
6691        });
6692
6693        // Textarea
6694        $(chat_textarea).on('keyup', function () {
6695            SBChat.textareaChange(this);
6696        });
6697
6698        $(chat_textarea).on('focus input click', function () {
6699            chat_textarea.parent().parent().addClass('sb-focus');
6700        });
6701
6702        $(chat_textarea).on('focusout', function () {
6703            chat_textarea.parent().parent().removeClass('sb-focus');
6704        });
6705
6706        // Privacy message
6707        $(main).on('click', '.sb-privacy .sb-approve', function () {
6708            storage('privacy-approved', true);
6709            $(this).closest('.sb-privacy').remove();
6710            main.removeClass('sb-init-form-active');
6711            chat_header.find(' > div').css({ opacity: 1, top: 0 });
6712            SBChat.initChat();
6713            if (tickets) {
6714                SBTickets.showPanel(SBF.setting('tickets_disable_first') ? '' : 'new-ticket');
6715            } else if (!SBChat.isInitDashboard()) {
6716                SBChat.hideDashboard();
6717            }
6718        });
6719
6720        $(main).on('click', '.sb-privacy .sb-decline', function () {
6721            let privacy = $(this).closest('.sb-privacy');
6722            $(privacy).find('.sb-text').html($(privacy).attr('data-decline'));
6723            $(privacy).find('.sb-decline').remove();
6724            SBChat.scrollBottom(true);
6725        });
6726
6727        // Popup message
6728        $(main).on('click', '.sb-popup-message .sb-icon-close', function () {
6729            SBChat.popup(true);
6730        });
6731
6732        // Rich messages and inputs
6733        $(main).on('click', '.sb-rich-message .sb-submit,.sb-rich-message:not(.sb-rich-registration) .sb-select ul li', function () {
6734            let message = $(this).closest('.sb-rich-message');
6735            if (!message[0].hasAttribute('disabled')) {
6736                SBRichMessages.submit(message, message.attr('data-type'), this);
6737            };
6738        });
6739
6740        $(main).on('click', '.sb-rich-message .sb-input > span', function () {
6741            $(this).sbActive(true);
6742            $(this).siblings().focus();
6743        });
6744
6745        $(main).on('focus focusout click', '.sb-rich-message .sb-input input,.sb-rich-message .sb-input select', function (e) {
6746            switch (e.type) {
6747                case 'focusin':
6748                case 'focus':
6749                    $(this).siblings().sbActive(true);
6750                    break;
6751                case 'focusout':
6752                    if ($(this).val()) {
6753                        $(this).siblings().addClass('sb-filled sb-active');
6754                    } else {
6755                        setTimeout(() => {
6756                            if (!prevent_focusout) {
6757                                $(this).siblings().sbActive(false);
6758                            }
6759                        }, 100);
6760                    }
6761                    break;
6762                case 'click':
6763                    $(this).siblings().removeClass('sb-filled');
6764                    break;
6765            }
6766        });
6767
6768        $(main).on('click', '.sb-input-select-input > div', function () {
6769            prevent_focusout = true;
6770            setTimeout(() => {
6771                prevent_focusout = false;
6772            }, 250);
6773        });
6774
6775        $(main).on('click', '.sb-slider-arrow', function () {
6776            SBRichMessages.sliderChange($(this).closest('[id]').attr('id'), $(this).hasClass('sb-icon-arrow-right') ? 'right' : 'left');
6777        });
6778
6779        $(main).on('change', '.sb-rich-message [data-type="select"] select', function () {
6780            $(this).siblings().sbActive(true);
6781        });
6782
6783        $(main).on('click', '[data-type="select-input"] > div', function () {
6784            $(this).prev().sbActive(true);
6785            $(this).next().addClass('sb-focus');
6786        });
6787
6788        $(main).on('focusout', '[data-type="select-input"] input,[data-type="select-input"] select', function () {
6789            let cnt = $(this).closest('.sb-input');
6790            if (cnt.find('> input').val() + cnt.find('select').val() == '') {
6791                cnt.find('span').sbActive(false);
6792            }
6793            cnt.find('.sb-focus').removeClass('sb-focus');
6794        });
6795
6796        // Registration and Login
6797        $(main).on('click', '.sb-rich-message .sb-login-area', function () {
6798            let init = main.hasClass('sb-init-form-active');
6799            $(this).closest('.sb-rich-message').replaceWith(SBRichMessages.generate({}, 'login', init ? 'sb-init-form' : ''));
6800            SBChat.scrollBottom(init);
6801        });
6802
6803        $(main).on('click', '.sb-forgot-password', function () {
6804            let init = main.hasClass('sb-init-form-active');
6805            $(this).closest('.sb-rich-login').replaceWith(SBRichMessages.generate({}, 'login-link', init ? 'sb-init-form' : ''));
6806            SBChat.scrollBottom(init);
6807        });
6808
6809        $(main).on('click', '.sb-rich-login .sb-registration-area', function () {
6810            if (CHAT_SETTINGS.registration_link) {
6811                document.location = CHAT_SETTINGS.registration_link;
6812            } else {
6813                let init = main.hasClass('sb-init-form-active');
6814                $(this).closest('.sb-rich-login').replaceWith(SBRichMessages.generate({}, 'registration', init ? 'sb-init-form' : ''));
6815                SBChat.scrollBottom(init);
6816            }
6817        });
6818
6819        $(main).on('click', '.sb-rich-login .sb-submit-login', function () {
6820            SBF.loginForm(this, false, (response) => {
6821                let area = $(this).closest('.sb-rich-login');
6822                activeUser(new SBUser(response[0]));
6823                if (area.hasClass('sb-init-form')) {
6824                    force_action = 'open-conversation';
6825                    SBChat.initChat();
6826                    SBPusher.start();
6827                    if (!SBChat.isInitDashboard()) {
6828                        SBChat.hideDashboard();
6829                    }
6830                    $(document).on('SBPopulateConversations', function () {
6831                        main.removeClass('sb-init-form-active');
6832                        area.remove();
6833                        $(document).off('SBPopulateConversations');
6834                    });
6835                } else {
6836                    area = area.closest('[data-id]');
6837                    let message = SBChat.conversation.getMessage(area.attr('data-id'));
6838                    let text = `${sb_('Logged in as')} *${activeUser().name}*`;
6839                    message.set('message', text);
6840                    SBChat.updateMessage(message.id, text);
6841                    area.replaceWith(message.getCode());
6842                    SBPusher.started = false;
6843                    SBPusher.start();
6844                }
6845            });
6846        });
6847
6848        $(main).on('click', '.sb-submit-login-link', function () {
6849            const area = $(this).closest('.sb-rich-login-link,.sb-panel');
6850            const email = area.find('input').val();
6851            if (email && email.includes('@') && email.includes('.') && !loading(this)) {
6852                SBF.ajax({
6853                    function: 'login-link',
6854                    email: email,
6855                    url: SBF.URL()
6856                }, () => {
6857                    area.sbActive(true);
6858                });
6859            }
6860        });
6861
6862        // Social share buttons
6863        $(chat).on('click', '.sb-social-buttons div', function () {
6864            SBF.openWindow($(this).attr('data-link'));
6865        });
6866
6867        // Archive chat
6868        $(main).on('click', '.sb-close-chat', function () {
6869            SBChat.closeChat();
6870        });
6871
6872        // Enlarge chat widget
6873        if (!mobile) {
6874            $(main).on('click', '.sb-enlarge-chat-widget', function () {
6875                SBChat.resize(!main.hasClass('sb-chat-large'));
6876            });
6877        }
6878
6879        // WooCommerce
6880        $(chat).on('click', '.sb-rich-woocommerce_button a, [href="#"].sb-card-btn', function (e) {
6881            let settings = SBF.settingsStringToArray($(this).closest('.sb-rich-message').attr('data-settings'));
6882            let checkout = settings['link-type'] == 'checkout' || settings.checkout;
6883            let product_ids = $(this)[0].hasAttribute('data-ids') ? $(this).attr('data-ids').split(',') : [
6883settings.id.split('|')[$(this).parent().index()]];
6884            if (product_ids.length) {
6885                if (loading(this)) return;
6886                SBApps.wordpress.ajax('button-purchase', { product_ids: product_ids, checkout: checkout, coupon: settings.coupon }, (response) => {
6887                    if (checkout) {
6888                        document.location = response;
6889                    } else {
6890                        $(this).addClass('sb-icon-check').sbLoading(false);
6891                    }
6892                });
6893            }
6894            e.preventDefault();
6895            return false;
6896        });
6897
6898        $(chat).on('click', '#sb-waiting-list .sb-submit', function () {
6899            if ($(this).index() == 0) {
6900                setTimeout(() => { SBApps.woocommerce.waitingList('submit') }, 1000);
6901            }
6902        });
6903
6904        $(document).on('SBNewEmailAddress', function (e, response) {
6905            if (response.id == 'sb-waiting-list-email') {
6906                SBApps.woocommerce.waitingList('submit');
6907            }
6908        });
6909
6910        // Audio player
6911        $(chat).on('click', '.sb-player-btn', function () {
6912            SBF.audio.play(this);
6913        });
6914
6915        $(chat).on('click', '.sb-player-speed', function () {
6916            SBF.audio.speed(this);
6917        });
6918
6919        $(chat).on('click', '.sb-player-download', function () {
6920            SBF.audio.download(this);
6921        });
6922
6923        // Audio recording
6924        $(chat_editor).on('click', '.sb-btn-audio-clip', function () {
6925            audio_mp3 = SBChat.conversation && SBChat.conversation.get('source') == 'wa';
6926            if (audio_mp3 && typeof SBAudioRecorder === ND) {
6927                return $.getScript(SB_URL + '/vendor/lame.min.js', () => { this.click(); }, true);
6928            }
6929            navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
6930                if (audio_mp3) {
6931                    SBAudioRecorder.init(stream);
6932                } else {
6933                    if (typeof SBAudioRecorder != ND) {
6934                        SBAudioRecorder.close();
6935                    }
6936                    audio_recorder_chunks = [];
6937                    audio_recorder = new MediaRecorder(stream);
6938                    audio_recorder.addEventListener('dataavailable', e => {
6939                        audio_recorder_chunks.push(e.data);
6940                    });
6941                }
6942                if (!audio_recorder_dom) {
6943                    audio_recorder_dom = chat_editor.find('#sb-audio-clip');
6944                    audio_recorder_dom_time = audio_recorder_dom.find('.sb-audio-clip-time');
6945                    audio_recorder_dom.on('click', '.sb-btn-mic', function () {
6946                        let is_pause = $(this).hasClass('sb-icon-pause');
6947                        if (is_pause) {
6948                            if (audio_mp3) {
6949                                SBAudioRecorder.pause();
6950                            } else {
6951                                audio_recorder.stop();
6952                            }
6953                        } else {
6954                            let button = audio_recorder_dom.find('.sb-btn-clip-player');
6955                            if (button.hasClass('sb-icon-pause')) {
6956                                button.click();
6957                            }
6958                            if (audio_mp3) {
6959                                SBAudioRecorder.resume();
6960                            } else {
6961                                audio_recorder.start();
6962                            }
6963                        }
6964                        audio_recorder_time[2] = !is_pause;
6965                        audio_recorder_dom.find('.sb-icon-play').sbActive(is_pause);
6966                        $(this).removeClass('sb-icon-' + (is_pause ? 'pause' : 'mic')).addClass('sb-icon-' + (is_pause ? 'mic' : 'pause'));
6967                    });
6968
6969                    audio_recorder_dom.on('click', '.sb-btn-clip-player', function () {
6970                        let is_pause = $(this).hasClass('sb-icon-pause');
6971                        if (is_pause) {
6972                            audio_recorder_time_player[3].pause();
6973                            audio_recorder_time_player[2] = false;
6974                        } else {
6975                            if (audio_recorder_time_player[3]) {
6976                                audio_recorder_time_player[3].play();
6977                                audio_recorder_time_player[2] = true;
6978                            } else {
6979                                let audio = new Audio(URL.createObjectURL(audio_mp3 ? SBAudioRecorder.blob(false) : new Blob(audio_recorder_chunks, { type: 'audio/webm' })));
6980                                audio_recorder_dom_time.html('0:00');
6981                                audio.play();
6982                                audio.onended = () => {
6983                                    audio_recorder_time_player[0] = 0;
6984                                    audio_recorder_time_player[2] = false;
6985                                    audio_recorder_time_player[3] = false;
6986                                    audio_recorder_dom_time.html('0:00');
6987                                    $(this).removeClass('sb-icon-pause').addClass('sb-icon-play');
6988                                }
6989                                audio_recorder_time_player[0] = 0;
6990                                audio_recorder_time_player[2] = true;
6991                                audio_recorder_time_player[3] = audio;
6992                            }
6993                        }
6994                        $(this).removeClass('sb-icon-' + (is_pause ? 'pause' : 'play')).addClass('sb-icon-' + (is_pause ? 'play' : 'pause'));
6995                    });
6996
6997                    audio_recorder_dom.on('click', '.sb-icon-close', function () {
6998                        audio_recorder_chunks = [];
6999                        clearInterval(audio_recorder_time[1]);
7000                        clearInterval(audio_recorder_time_player[1]);
7001                        audio_recorder_dom.sbActive(false);
7002                        chat_editor.sbActive(false).removeClass('sb-audio-message-active');
7003                        if (audio_mp3) {
7004                            SBAudioRecorder.pause();
7005                        } else {
7006                            audio_recorder.stop();
7007                        }
7008                        audio_recorder_stream.getTracks()[0].stop();
7009                    });
7010                }
7011                audio_recorder_dom_time.html('0:00');
7012                audio_recorder_stream = stream;
7013                clearInterval(audio_recorder_time[1]);
7014                clearInterval(audio_recorder_time_player[1]);
7015                audio_recorder_time = [0, setInterval(() => {
7016                    if (audio_recorder_time[2]) {
7017                        audio_recorder_time[0]++;
7018                        audio_recorder_dom_time.html(SBF.getMinutesSeconds(audio_recorder_time[0]));
7019                    }
7020                }, 1000), true];
7021                audio_recorder_time_player = [0, setInterval(() => {
7022                    if (audio_recorder_time_player[2]) {
7023                        audio_recorder_time_player[0]++;
7024                        audio_recorder_dom_time.html(SBF.getMinutesSeconds(audio_recorder_time_player[0]));
7025                    }
7026                }, 1000), false, false];
7027                if (!audio_mp3) {
7028                    audio_recorder.start();
7029                }
7030                audio_recorder_dom.find('.sb-btn-clip-player').removeClass('sb-icon-pause').addClass('sb-icon-play').sbActive(false);
7031                audio_recorder_dom.find('.sb-icon-mic').removeClass('sb-icon-mic').addClass('sb-icon-pause');
7032                audio_recorder_dom.sbActive(true);
7033                chat_textarea.val('').css('height', '');
7034                chat_editor.sbActive(true).addClass('sb-audio-message-active');
7035            }).catch(error => {
7036                alert(error);
7037            });
7038        });
7039
7040        // Overlay panel
7041        $(chat).on('click', '[data-action].sb-rich-btn', function (e) {
7042            SBRichMessages.iframe.load($(this).attr('data-action'), $(this).attr('data-extra'), $(this).html(), $(this).closest('[data-id]').attr('data-id'));
7043            e.preventDefault();
7044            return false;
7045        });
7046
7047        $(chat_overlay_panel).on('click', '> div:first-child i', function () {
7048            main.find('.sb-overlay-panel').sbActive(false);
7049            if (chat_overlay_panel.attr('data-close-chat')) {
7050                SBChat.closeChat();
7051            }
7052        });
7053
7054        // Phone 
7055        $(main).on('change input', '#phone > input', function () {
7056            let value = $(this).val().trim();
7057            if (!/^[0-9+]+$/.test(value)) {
7058                value = value.replace(/[^0-9+]/g, '');
7059                $(this).val(value);
7060            }
7061            if (value.length > 1 && value.indexOf('+') === 0) {
7062                let prefix = false;
7063                if (value.substring(0, 2) == '+1') {
7064                    prefix = value.substring(0, 2);
7065                }
7066                if (value.length > 3) {
7067                    prefix = value.substring(0, 3);
7068                }
7069                if (prefix) {
7070                    $(this).parent().find(`[data-value="${prefix}"]`).click();
7071                    $(this).parent().find(`.sb-select`).click();
7072                    $(this).val(value.replace(prefix, ''));
7073                }
7074            }
7075        });
7076
7077        /*
7078        * ----------------------------------------------------------
7079        * COMPONENTS
7080        * ----------------------------------------------------------
7081        */
7082
7083        // Search
7084        $(global).on('click', '.sb-search-btn > i', function () {
7085            let parent = $(this).parent();
7086            let active = $(parent).sbActive();
7087            if (active) {
7088                setTimeout(() => { $(parent).find('input').val('') }, 50);
7089                setTimeout(() => { $(parent).find('input').trigger('change') }, 550);
7090            };
7091            $(parent).sbActive(!active);
7092            $(parent).find('input').get(0).focus();
7093            global.find('.sb-select ul').sbActive(false);
7094        });
7095
7096        // Select
7097        $(global).on('click', '.sb-select', function (e) {
7098            if (!e.target || !$(e.target).is('input')) {
7099                let ul = $(this).find('ul');
7100                let active = ul.hasClass('sb-active');
7101                $(global).find('.sb-select ul').sbActive(false);
7102                ul.setClass('sb-active', !active);
7103                $(this).find('.sb-select-search').setClass('sb-active', !active);
7104                if (admin) {
7105                    SBAdmin.open_popup = active ? false : this;
7106                }
7107            }
7108        });
7109
7110        $(global).on('click', '.sb-select li', function () {
7111            let select = $(this).closest('.sb-select');
7112            let value = $(this).data('value');
7113            let item = $(select).find(`[data-value="${value}"]`);
7114            select.find('li').sbActive(false);
7115            select.find('p').attr('data-value', value).html($(item).html());
7116            item.sbActive(true);
7117        });
7118
7119        $(global).on('input', '.sb-select-search input', function (e) {
7120            let search = $(this).val();
7121            SBF.search(search, () => {
7122                let list = $(this).parent().parent().find('li');
7123                list.setClass('sb-hide', search.length);
7124                if (search.length) {
7125                    search = search.toLowerCase();
7126                    list.each(function () {
7127                        if ($(this).attr('data-value').includes(search) || $(this).attr('data-country').includes(search)) {
7128                            $(this).removeClass('sb-hide');
7129                        }
7130                    });
7131                }
7132            });
7133        });
7134
7135        // Image uploader
7136        $(global).on('click', '.sb-input-image .image', function () {
7137            upload_target = $(this).parent();
7138            chat_editor.find('.sb-upload-files').click();
7139        });
7140
7141        $(global).on('click', '.sb-input-image .image > .sb-icon-close', function (e) {
7142            SBF.ajax({ function: 'delete-file', path: $(this).parent().attr('data-value') });
7143            $(this).parent().removeAttr('data-value').css('background-image', '');
7144            e.preventDefault();
7145            return false;
7146        });
7147    }
7148
7149}(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.