PageSourceSearch

https://sms-fly.ua/js/func.main.js?1779355243

js sms-fly.ua collected 2026-10-05 05:31:26 UTC 104,462 bytes, 2,469 lines download raw bytes

1//Загружаем глобальные классы
2$(function () {
3    if ( typeof loaderfly === 'undefined') loaderfly = new Loader();
4    mainObj.blur = false;
5
6    //Система предотвращающая запросы в неактивных окнах
7    window.onblur = () => {
8        mainObj.blur = true
9        //Что б не слетала авторизация, раз в 15 минут будем отключать для возобновления запросов
10        setInterval(()=>{
11            mainObj.blur = false
12            setTimeout(()=>{ //Спустя 90 сек, вернем подмену
13                mainObj.blur = true
14            }, 1000*90)
15        }, 1000*60*15)
16    }
17    window.onfocus = () => {mainObj.blur = false}
18    //
19
20    //Антикликерная система
21    mainObj.freezeClic = false;
22    document.addEventListener("click", e=>{if (mainObj.freezeClic) {e.stopPropagation(); e.preventDefault();}}, true);
23});//
24
25mainObj = {}; //Объект с параметрами хешем и т.п.
26
27const gsmCodePoints = new Set([
28    0x000a, 0x000c, 0x000d,
29    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
30    0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
31    0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d,
32    0x004e, 0x004f,
33    0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
34    0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
35    0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e,
36    0x00a1, 0x00a3, 0x00a4, 0x00a5, 0x00a7,
37    0x00bf,
38    0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c9,
39    0x00d1, 0x00d6, 0x00d8, 0x00dc, 0x00df,
40    0x00e0, 0x00e4, 0x00e5, 0x00e6, 0x00e8, 0x00e9, 0x00ec,
41    0x00f1, 0x00f2, 0x00f6, 0x00f8, 0x00f9, 0x00fc,
42    0x0393, 0x0394, 0x0398, 0x039b, 0x039e, 0x03a0, 0x03a3, 0x03a6, 0x03a8, 0x03a9,
43    0x20ac,
44]);
45
46function isGsmMessage(message) {
47    for (const s of message) {
48        const codePoint = s.codePointAt(0);
49        if (codePoint && !gsmCodePoints.has(codePoint)) {
50            return false;
51        }
52    }
53    return true;
54}
55
56function validator_fly(type, value) {
57    let returnobj = {
58            valid: false,
59            value: value,
60            message: ''
61        },
62        tvalue = value.toString().trim()
63    switch ( type ) {
64        case 'sendername' :
65            if ( /[^a-zA-Z0-9 _&\-\.\+]/i.test(tvalue) ) {
66                returnobj.message = langt('L_SENDERNAME_Ispolzovan_nedopustimyy_simvol');
67            }
68
69            if ( !/\D/i.test(tvalue) ) {
70                returnobj.message = langt('L_SENDERNAME_Nedopustimo_ispolzovat_odni_tsifry')
71            }
72
73            if ( tvalue.length < 2 || tvalue.length > 11 ) {
74                returnobj.message = `${langt('L_SENDERNAME_Nepravilnoye_kolichestvo_simvolov')}: ${tvalue.length}.`
75            }
76
77            if ( returnobj.message === '' ) {
78                returnobj.value = tvalue
79                returnobj.valid = true
80            }
81
82            break;
83        case 'user_email':
84            let reg = /^[-._a-z0-9]+@(?:[a-z0-9][-a-z0-9]+\.)+[a-z]{2,8}$/i;
85
86            if ( reg.test(tvalue) ) {
87                returnobj.value = tvalue
88                returnobj.valid = true
89            } else {
90                returnobj.message = langt('L_GENERAL_Nekkorektnyj_email')
91            }
92
93            break;
94        case 'oblast':
95            if ( tvalue > 0 ) {
96                returnobj.value = tvalue
97                returnobj.valid = true
98            } else {
99                returnobj.message = langt('L_GENERAL_Oblast_ne_vybrana')
100            }
101            break;
102        case 'accepter':
103            let svalue = tvalue.toString();
104
105            if ( /1|true|on/i.test(svalue) ) {
106                returnobj.value = tvalue
107                returnobj.valid = true
108            } else {
109                //returnobj.message = langt('L_GENERAL_Ne_vybrano') //не нужно ибо чекбокс не красиво выглядит
110            }
111            break;
112        case 'organization_name':
113        case 'vid_deyatelnosti':
114        case 'im':
115            if ( tvalue.length < 2 ) {
116                returnobj.message = `${langt('L_SENDERNAME_Nepravilnoye_kolichestvo_simvolov')}: ${tvalue.length}.`
117            } else {
118                returnobj.value = tvalue
119                returnobj.valid = true
120            }
121            break;
122        case 'telegram':
123            if ( tvalue.length < 3 ) {
124                returnobj.message = langt('L_GENERAL_Nevernoye_kolichestvo_simvolov')
125            } else {
126                returnobj.value = tvalue
127                returnobj.valid = true
128            }
129            break;
130        case 'ip':
131            //Разделим на части по-разделителям
132            returnobj.valid = true
133            let validList = [], falseList = [],
134                IPs = tvalue.split(/[,; |]+/)
135            for (const IP of IPs) {
136                if (IP == '') continue //Пустышки пропускаем
137                if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(IP) &&
138                    !/^(192\.168)|(100\.64)|(100\.27)|(127\.)|(172\.1[6-9]\.)|(172\.2\d\.)|(172\.3[0-1]\.)/.test(IP)) {
139                        validList.push(IP)
140                } else {
141                    falseList.push(IP)
142                    returnobj.valid = false
143                }
144            }
145
146            if ( returnobj.valid ) {
147                returnobj.value = validList.join(';')
148            } else {
149                returnobj.message = langt('L_GENERAL_Proverte_IP') +': ' + falseList.join(', ')
150            }
151            break
152        default: returnobj.message = 'Unknown type'
153    }
154
155    return returnobj
156}
157
158//Функция-переводчик
159function langt (key, uplow) {
160    if ( window.hasOwnProperty('paramsGlobal') && paramsGlobal.hasOwnProperty('i18l_ext') ) Object.assign(i18l, paramsGlobal.i18l_ext) //Подгружаем дополнительные языковые ключи, которые добавлены php функцией loadLang()
161
162    if (i18l.hasOwnProperty(key)) {
163        if (uplow === 'U') {
164            return i18l[key].toUpperCase();
165        } else if (uplow === 'L') {
166            return i18l[key].toLowerCase();
167        } else {
168            return i18l[key]
169        }
170    }  else {
171        var keys = key.split(';;');
172        if (keys.length === 3) {
173            switch (paramsGlobal['locale']) {
174                case 'en': return  keys[0];
175                case 'ru': return  keys[1];
176                default: return keys[2];
177            }
178        }
179        console.log('Локаль для ключа "' + key + '" не найдена!');
180        return key;
181    }
182}
183
184function langt_with_replace (key, replaces) {
185    if ( window.hasOwnProperty('paramsGlobal') && paramsGlobal.hasOwnProperty('i18l_ext') ) Object.assign(i18l, paramsGlobal.i18l_ext) //Подгружаем дополнительные языковые ключи, которые добавлены php функцией loadLang()
186
187    if (i18l.hasOwnProperty(key)) {
188        const translate = i18l[key] // "Привет [[имя]], добро пожаловать в [[город]]!"
189
190        const regex = /\[\[(.*?)\]\]/g;
191        let result = translate;
192
193        const matches = [...translate.matchAll(regex)];
194
195        for (const match of matches) {
196            const template = match[0]; // [[key]]
197            const key = match[1];      // key
198            result = result.replace(template, replaces.hasOwnProperty(key) ? replaces[key] : key);
199        }
200
201        return result;
202    }  else {
203        console.log('Локаль для ключа "' + key + '" не найдена!');
204        return key;
205    }
206}
207
208function filterDate(value, type = 'datetime') {
209    if (value === null || value === '0000-00-00' || value === undefined) return '';
210    value += '-0-0-0-0-0-0'; //Что б проходили не полные даты
211
212    let arr = value.split(/[\-\+ :T]/),
213        date = new Date(arr[0],arr[1] - 1, arr[2], arr[3] , arr[4] ,arr[5]);
214
215    let options = {}
216    if (type.includes('date')) {
217        options.year = 'numeric'
218        options.month = 'numeric'
219        options.day = 'numeric'
220    }
221    if (type.includes('time')) {
222        options.hour = 'numeric'
223        options.minute = 'numeric'
224    }
225    if (type.includes('full')) {
226        options.second = 'numeric'
227    }
228
229    let formatter = new Intl.DateTimeFormat('ru-RU', options).formatToParts(new Date(date))
230    return formatter.map(({type, value})=>value = (value === ', ')?' ':value ).join('')
231}
232
233function sleep(milliseconds) {
234    var date = Date.now(), currentDate = null;
235    do {
236        currentDate = Date.now();
237    } while (currentDate - date < milliseconds);
238}
239
240//MD5 hash
241function MD5(d){result = M(V(Y(X(d),8*d.length)));return result.toLowerCase()};function M(d){for(var _,m="0123456789ABCDEF",f="",r=0;r<d.length;
241r++)_=d.charCodeAt(r),f+=m.charAt(_>>>4&15)+m.charAt(15&_);return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<<m%32;return _}function V(d){for(var _="",m=0;m<32*d.length;m+=8)_+=String.fromCharCode(d[m>>5]>>>m%32&255);return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n<d.length;n+=16){var h=m,t=f,g=r,e=i;f=md5_ii(f=md5_ii(f=md5_ii(f=md5_ii(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_hh(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_gg(f=md5_ff(f=md5_ff(f=md5_ff(f=md5_ff(f,r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+0],7,-680876936),f,r,d[n+1],12,-389564586),m,f,d[n+2],17,606105819),i,m,d[n+3],22,-1044525330),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+4],7,-176418897),f,r,d[n+5],12,1200080426),m,f,d[n+6],17,-1473231341),i,m,d[n+7],22,-45705983),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+8],7,1770035416),f,r,d[n+9],12,-1958414417),m,f,d[n+10],17,-42063),i,m,d[n+11],22,-1990404162),r=md5_ff(r,i=md5_ff(i,m=md5_ff(m,f,r,i,d[n+12],7,1804603682),f,r,d[n+13],12,-40341101),m,f,d[n+14],17,-1502002290),i,m,d[n+15],22,1236535329),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+1],5,-165796510),f,r,d[n+6],9,-1069501632),m,f,d[n+11],14,643717713),i,m,d[n+0],20,-373897302),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+5],5,-701558691),f,r,d[n+10],9,38016083),m,f,d[n+15],14,-660478335),i,m,d[n+4],20,-405537848),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+9],5,568446438),f,r,d[n+14],9,-1019803690),m,f,d[n+3],14,-187363961),i,m,d[n+8],20,1163531501),r=md5_gg(r,i=md5_gg(i,m=md5_gg(m,f,r,i,d[n+13],5,-1444681467),f,r,d[n+2],9,-51403784),m,f,d[n+7],14,1735328473),i,m,d[n+12],20,-1926607734),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+5],4,-378558),f,r,d[n+8],11,-2022574463),m,f,d[n+11],16,1839030562),i,m,d[n+14],23,-35309556),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+1],4,-1530992060),f,r,d[n+4],11,1272893353),m,f,d[n+7],16,-155497632),i,m,d[n+10],23,-1094730640),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+13],4,681279174),f,r,d[n+0],11,-358537222),m,f,d[n+3],16,-722521979),i,m,d[n+6],23,76029189),r=md5_hh(r,i=md5_hh(i,m=md5_hh(m,f,r,i,d[n+9],4,-640364487),f,r,d[n+12],11,-421815835),m,f,d[n+15],16,530742520),i,m,d[n+2],23,-995338651),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+0],6,-198630844),f,r,d[n+7],10,1126891415),m,f,d[n+14],15,-1416354905),i,m,d[n+5],21,-57434055),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+12],6,1700485571),f,r,d[n+3],10,-1894986606),m,f,d[n+10],15,-1051523),i,m,d[n+1],21,-2054922799),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+8],6,1873313359),f,r,d[n+15],10,-30611744),m,f,d[n+6],15,-1560198380),i,m,d[n+13],21,1309151649),r=md5_ii(r,i=md5_ii(i,m=md5_ii(m,f,r,i,d[n+4],6,-145523070),f,r,d[n+11],10,-1120210379),m,f,d[n+2],15,718787259),i,m,d[n+9],21,-343485551),m=safe_add(m,h),f=safe_add(f,t),r=safe_add(r,g),i=safe_add(i,e)}return Array(m,f,r,i)}function md5_cmn(d,_,m,f,r,i){return safe_add(bit_rol(safe_add(safe_add(_,d),safe_add(f,i)),r),m)}function md5_ff(d,_,m,f,r,i,n){return md5_cmn(_&m|~_&f,d,_,r,i,n)}function md5_gg(d,_,m,f,r,i,n){return md5_cmn(_&f|m&~f,d,_,r,i,n)}function md5_hh(d,_,m,f,r,i,n){return md5_cmn(_^m^f,d,_,r,i,n)}function md5_ii(d,_,m,f,r,i,n){return md5_cmn(m^(_|~f),d,_,r,i,n)}function safe_add(d,_){var m=(65535&d)+(65535&_);return(d>>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
242
243// возвращает cookie с именем name, если есть, если нет, то undefined
244function getCookie(name) {
245    var matches = document.cookie.match(new RegExp(
246        "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
247    ));
248    return matches ? decodeURIComponent(matches[1]) : undefined;
249}
250// Возвращает случайное число между min (включительно) и max (не включая max)
251function getRandomArbitrary(min, max) {
252    return Math.random() * (max - min) + min;
253}
254// Возвращает случайное целое число между min (включительно) и max (не включая max)
255function getRandomInt(min, max) {
256    return Math.floor(Math.random() * (max - min)) + min;
257}
258//Отложеный старт при изменении значения
259function startAfter(pausems, fn) {
260    let timeIdHash = MD5(pausems.toString() + fn.toString());
261    if ( mainObj.hasOwnProperty(timeIdHash) ) clearTimeout(mainObj[timeIdHash])
262    mainObj[timeIdHash] = setTimeout(fn, pausems)
263}
264//Глубокое копирование объекта
265function objCopy(obj) {
266    if (obj == null) return null;
267
268    if (typeof obj !== "object") {
269        return obj;
270    };
271
272    var copy = obj.constructor();
273    for (var key in obj) {
274        if (typeof obj[key] === "object") {
275            copy[key] = objCopy(obj[key]);
276        } else {
277            copy[key] = obj[key];
278        }
279    }
280    return copy;
281}
282
283//Сумма элементов массива или объекта
284function sumObject(elem) {
285    var count = 0;
286    if (Array.isArray(elem)) {
287        for (var i = 0; i < elem.length; i++) {
288            count += sumObject(elem[i]);
289        }
290    } else if (typeof elem === "object") {
291        for (var index in elem) {
292            count += sumObject(elem[index]);
293        }
294    } else {
295        return Number(elem);
296    }
297
298    return count;
299}
300
301async function checkPhone (phone_number) {
302    let req = new Promise(resolve => jsReqF('checkPhone', {phone: phone_number}, r => resolve(r.dataout), true))
303
304    return await req
305}
306
307//Функция контроля изменений в объекте по таймеру
308// function assignObj(obj, timer, fn) {
309//     var old_obj = objCopy(obj);
310//     setInterval(function () {
311//         if (JSON.stringify(old_obj) !== JSON.stringify(obj)) {
312//             old_obj = objCopy(obj);
313//             fn(); //Если объект изменен то вызываем колбек
314//         }
315//     },timer); //
316// }
317//Функция контроля изменений в объекте по таймеру
318function assignObj(obj, timer, fn) {
319    let old_obj = JSON.stringify(obj);
320
321    setInterval(() => {
322        let new_obj = JSON.stringify(obj);
323        if ( new_obj !== old_obj ) {
324            //console.log(old_obj, ' --- ', new_obj);
325            old_obj = new_obj;
326            fn(); //Если объект изменен то вызываем колбек
327        }
328    }, timer); //
329}
330
331//Запрос баланса с обновлением в шапке
332function getBalance() {
333    jsReq('ClientMain',{does: 'getBalance'}, function () {
334        var balance = this.dataout['balance'],
335            price = this.dataout['price'];
336        $('.balance-realbalance').text(Number(balance['realbalance']).toFixed(2));
337        $('.balance-count-ai').find('span').text(Math.floor(balance['realbalance']/price['AI']));
338        $('.balance-count-noai').find('span').text(Math.floor(balance['realbalance']/price['NOAI']));
339    })
340}
341
342function headerExt(actionUrl) {
343    if (actionUrl === undefined) {
344        actionUrl = (paramsGlobal.REQUEST_URI !== undefined)? paramsGlobal.REQUEST_URI :'/contacts/';
345    };
346
347    var f = document.createElement('form');
348    f.method = 'post';
349    f.action = actionUrl;
350
351    var i = document.createElement('input');
352    i.name = 'paramsJSON';
353    i.value = JSON.stringify(paramsGlobal);
354
355    f.appendChild(i);
356
357    $('body').append(f);
358
359    f.submit();
360}
361
362function showTooltip($this, message, sec) {
363    let show = new Boolean($this.val())
364
365    if ( show.valueOf() ) {
366        let jq_tooltip = $(".confirm-block"), jq_backbutton = $('.back-button')
367
368        if (sec === 999) {
369            jq_tooltip.find('span').css({'color':'red '})
370        }
371
372        sec = (sec === undefined) ? 5000 : sec*1000;
373        jq_tooltip.find('span').html(message);
374        jq_backbutton.hide()
375        jq_tooltip.fadeIn()
376        setTimeout( () => {
377            jq_tooltip.fadeOut(()=>{
378                jq_backbutton.show()
379            })
380        }, sec)
381    }
382}
383
384function showPhpTooltip(message, sec) {
385
386    let jq_tooltip = $(".confirm-block"), jq_backbutton = $('.back-button')
387
388    if (sec === 999) jq_tooltip.find('span').css({'color':'red'})
389
390    sec = (sec === undefined) ? 5000 : sec*1000;
391    jq_tooltip.find('span').text(message);
392    jq_backbutton.hide()
393    jq_tooltip.fadeIn()
394    setTimeout( () => {
395        jq_tooltip.fadeOut(()=>{
396            jq_backbutton.show()
397        })
398    }, sec)
399}
400
401//Класс загрузки картиок
402function UploadImgFile($link) {
403
404    var $input = $('.imgFile'), //Если инпута нет или больше одного создастся новый
405        $filelabel = $('<i>', {class:"file-name"}), //Метка с указанием имени и размера
406        $delfile = $('<i>', {class:"smsic-close m-l10"}), //Удалить текущий выбор
407        limitsize = 5242880, //Ограничение на размер файла
408        uploadFile = null; //Содержит инфо о текущем выбранном файле
409
410    if ($input.length !== 1) {
411        $input = $('<input>', {type:"file", name:"uploadFile", hidden:"true", accept:'image/jpeg,image/png,image/gif,application/pdf'})
412    }
413
414    $link.click(function () {
415        $input.trigger('click');
416    });
417
418    $input.change(function () {
419        var tmpFile = $input.prop('files')[0];
420
421        if ( (tmpFile.type !== 'image/jpeg') && (tmpFile.type !== 'image/png') && (tmpFile.type !== 'image/gif') && (tmpFile.type !== 'application/pdf') ) {
422            alertModal(langt('L_GENERAL_Nedopustimyy_tip_fayla')+'!');
423            console.log(tmpFile.type);
424            return;
425        }
426
427        if (tmpFile.size > limitsize) {
428            alertModal(langt('L_GENERAL_Prevyshen_maksimalno_dopustimyy_razmer_fayla')+' '+Math.round(limitsize/1024/1024)+" Mb");
429            return;
430        }
431        uploadFile = tmpFile;
432        $filelabel.text(uploadFile.name + " ("+(uploadFile.size/1024).toFixed(2)+" Kb)");
433        $filelabel.insertAfter($link);
434        $delfile.insertAfter($filelabel);
435
436        $delfile.click(function () {
437            uploadFile = null;
438            $input[0].value = "";
439            $filelabel.remove();
440            $delfile.remove();
441        });
442    });
443
444    this.getInfo = function () {
445        return uploadFile;
446    };
447
448    //Асинхронная загрузка файла, пока не используем
449    this.upload = function (fn) {
450        var form = new FormData();
451        form.append('do', 'uploadImgFile');
452        form.append('uploadFile', uploadFile);
453
454        // отправляем через xhr
455        var xhr = new XMLHttpRequest();
456        xhr.onload = function() {
457            try {
458                var response = JSON.parse(xhr.responseText);
459                fn.apply(response);
460
461                //Убираем инфу
462                uploadFile = null;
463                $input[0].value = "";
464                $filelabel.remove();
465                $delfile.remove();
466            } catch(err) {
467                console.log(response);
468                alert(langt('L_GENERAL_Oshibka_zagruzki_fayla_poprobuyte_yeshche_raz')+"!");
469            }
470
471        };
472        xhr.open("post", "/responder2.php", false);
473        xhr.send(form);
474    };
475}
476
477//Класс загрузки файлов
478function UploadUserFile($link) {
479    let $filelabel = $('<i>', {class:"file-name"}), //Метка с указанием имени и размера
480        $delfile = $('<i>', {class:"smsic-close m-l10"}), //Удалить текущий выбор
481        limitsize = 5242880, //Ограничение на размер файла
482        allowedType = ['image/bmp', 'text/csv', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
483                        'image/gif', 'image/jpeg', 'image/png', 'application/pdf', 'application/rtf', 'image/tiff', 'application/zip','application/x-zip-compressed',
484                        'text/plain', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
485        $input = $('<input>', {type:"file", name:"uploadFile", hidden:"true", accept:`${allowedType.join(',')}`}),
486        uploadFile = null; //Содержит инфо о текущем выбранном файле
487
488    $link.after($input) //Добавим input после линка
489    $link.click(()=>$input.trigger('click')) //Выбор файла
490
491    $input.change(()=>{
492        let tmpFile = $input.prop('files')[0];
493
494        if ( !allowedType.includes(tmpFile.type) ) {
495            alertModal(langt('L_SUPPORT_Razmer_fayla_ne_boleye'))
496            //     console.log(tmpFile.type);
497            return
498        }
499
500        if (tmpFile.size > limitsize) {
501            alertModal(langt('L_GENERAL_Prevyshen_maksimalno_dopustimyy_razmer_fayla')+' '+Math.round(limitsize/1024/1024)+" Mb");
502            return
503        }
504        uploadFile = tmpFile
505        $filelabel.text(uploadFile.name + " ("+(uploadFile.size/1024).toFixed(2)+" Kb)");
506        $filelabel.insertAfter($link)
507        $delfile.insertAfter($filelabel)
508
509        $delfile.click(function () {
510            uploadFile = null
511            $input[0].value = ""
512            $filelabel.remove()
513            $delfile.remove()
514        })
515    })
516
517    this.getInfo = function () {
518        return uploadFile
519    }
520
521    //Асинхронная загрузка файла
522    this.upload = async fn => {
523        let form = new FormData();
524        form.append('do', 'uploadUserFile')
525        form.append('uploadFile', uploadFile)
526
527        let req = await fetch("/responder2.php", {
528            method: 'post',
529            body: form
530        })
531
532        let response = await req.json()
533        try {
534            if (typeof fn === 'function') fn.apply(response)
535            //Убираем инфу
536            uploadFile = null;
537            $input[0].value = "";
538            $filelabel.remove();
539            $delfile.remove();
540        } catch (e) {
541            console.log(response);
542            alert(langt('L_GENERAL_Oshibka_zagruzki_fayla_poprobuyte_yeshche_raz')+"!");
543        }
544    }
545}
546
547//Класс анимации ожидания
548function Loader() {
549    var queue = [];
550
551    $flyloader = $('<div>', {class: "loader-wrapper d-none"});
552    $flyloader.append('<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div>');
553    $('body').append($flyloader);
554
555    this.show = function (timeout) {
556        var timeID = setTimeout(function () {
557            $flyloader.removeClass('d-none');
558        },timeout);
559
560        queue.push(timeID);
561
562        return timeID;
563    };
564
565    this.hide = function (timeID) {
566        var pos = queue.indexOf(timeID);
567        if (pos > -1) {
568            queue.splice(pos, 1);
569            clearTimeout(timeID);
570        }
571
572        if (queue.length === 0) {
573            $flyloader.addClass('d-none');
574        }
575    }
576}
577
578function jsReq(module, data, callback, cached = false, dev) {
579    try {
580        if (flynoauth && module !== 'checkPhone') return; //Пользователь не авторизован
581    } catch (e) {
582        //Все ок
583    }
584    //Используем кеширование в запросах
585    let hash = MD5(module+JSON.stringify(data));
586
587    if (!mainObj.hasOwnProperty('jsReqCache')) {
588        mainObj.jsReqCache = {}
589    }
590
591    if (cached || mainObj.blur) {
592        if (mainObj.jsReqCache.hasOwnProperty(hash)) {
593            if (dev) console.log(`${dev} (cache): `, mainObj.jsReqCache[hash]);
594            callback.call(objCopy(mainObj.jsReqCache[hash]));
595            return;
596        }
597    }
598    //
599
600    var curlocation = window.location.href,
601        timeID = loaderfly.show(1000),
602        waitID = setTimeout(()=>{
603            console.log('Timeout exit...');
604            loaderfly.hide(timeID);
605        },60000),
606        req = new JsHttpRequest();
607
608    mainObj.freezeClic = true; //Преотвратим все клики пока идут запросы
609    req.open(null, '/responder2.php', true);
610    req.send({'do': module, 'params': data});
611    try {
612        req.onreadystatechange = function () {
613            if (req.readyState == 4) {
614                if (req.responseJS == "SESSION_ERROR") {
615                    window.location = curlocation;
616                }
617                if (dev) console.log(`${dev}: `,req.responseJS);
618
619                mainObj.jsReqCache[hash] = objCopy(req.responseJS);
620                callback.call(req.responseJS);
621            }
622            loaderfly.hide(timeID);
623            clearTimeout(waitID);
624            mainObj.freezeClic = false; //После запроса включем возможность кликов
625        };
626    } catch (e) {
627        console.log('jsReq: Bad request!');
628        loaderfly.hide(timeID);
629        mainObj.freezeClic = false; //После запроса включем возможность кликов
630    }
631}
632
633async function jsReqF(module, data, callback, cached = false, dev) {
634    try {
635        if (flynoauth && module !== 'checkPhone') return; //Пользователь не авторизован
636    } catch (e) {
637        //Все ок
638    }
639    //Используем кеширование в запросах
640    let hash = MD5(module+JSON.stringify(data));
641
642    if (!mainObj.hasOwnProperty('jsReqCache')) {
643        mainObj.jsReqCache = {}
644    }
645
646    if (cached || mainObj.blur) {
647        if (mainObj.jsReqCache.hasOwnProperty(hash)) {
648            if (dev) console.log(`${dev} (cache): `, mainObj.jsReqCache[hash])
649
650            if (typeof callback === 'function') callback(objCopy(mainObj.jsReqCache[hash]))
651            return;
652        }
653    }
654    //
655
656    var curlocation = window.location.href,
657        timeID = loaderfly.show(1000),
658        waitID = setTimeout(()=>{
659            console.log('Timeout exit...');
660            loaderfly.hide(timeID);
661        },60000);
662
663    let formData = new FormData()
664    formData.append('do', module)
665    formData.append('params', JSON.stringify(data))
666
667    mainObj.freezeClic = true; //Преотвратим все клики пока идут запросы
668    const response = await fetch('/responder2.php', {
669        method: 'POST',
670        body: formData
671    });
672
673    try {
674        const result = await response.json();
675        if (result === "SESSION_ERROR") {
676            window.location = curlocation;
677        }
678        if (dev) console.log(`${dev}: `,result);
679
680        mainObj.jsReqCache[hash] = objCopy(result);
681
682        if (typeof callback === 'function') callback(result)
683    } catch (error) {
684        console.log(error)
685    }
686
687    mainObj.freezeClic = false; //После запроса включем возможность кликов
688    loaderfly.hide(timeID);
689    clearTimeout(waitID);
690}
691
692function CheckerClass(label) {
693    //Универсальный класс выбора элементов
694    //label - префикс класса, для нескольких списков на странице
695    //елементы должны иметь атрибут "data-"+label+"id" в котором ид елемента (например ид группы, контакта и т.д.)
696    //check - используеться при построении дом, все остальное работает в автоматическом режиме
697    //getSelected() - возращает массив выбранных элементов
698    //{label}-yes и {label}-no - классы визуализации выбранных по свойству css display
699
700    var jq_toggle = $('.'+label+'__toggle'),
701        selected_obj = {}, self = this;
702
703    this.getSelected = function () {
704        var ids = [];
705        for (key in selected_obj) {
706            if (selected_obj[key]) ids.push(key);
707        };
708        return ids;
709    };
710
711    this.setSelected = function (arr) {
712        this.reset();
713        for (let i = 0; i < arr.length; i++) {
714            selected_obj[arr[i]] = true;
715        }
716    };
717
718    this.check = function (id) {
719        jq_toggle.prop("checked", false); //Выключаем toggle
720
721        if (!selected_obj.hasOwnProperty(id)) {
722            selected_obj[id] = false;
723        };
724
725        startAfter(100, reEvents);
726
727        return selected_obj[id];
728    };
729
730    this.reset = function () {
731        selected_obj = {};
732        setToggleTitle();
733        $('.'+label+'__item').prop('checked', false);
734    };
735
736    reEvents = function () {
737        var checkitem = $('.'+label+'__item');
738
739        checkitem.unbind('change'); //Очищаем хвосты
740        checkitem.change(function () {
741            jq_toggle.prop("checked", false); //Выключаем toggle
742            var jq_elem = $(this),
743                id = jq_elem.attr("data-"+label+"id");
744
745            selected_obj[id] = !selected_obj[id];
746            setToggleTitle();
747        });
748        checkitem.css('cursor','pointer');
749    };
750
751    setToggleTitle = function () {
752        var count = self.getSelected().length;
753        jq_toggle.attr("title","Выбрано: " + count);
754
755        //Показываем нужный блок в зависимости от выбранности
756        if (count > 0) {
757            $('.'+label+'-no').css('display', 'none');
758            $('.'+label+'-yes').fadeIn();
759        } else {
760            $('.'+label+'-yes').css('display', 'none');
761            $('.'+label+'-no').fadeIn();
762        }
763    };
764
765    //Событие кнопки выбрать все
766    jq_toggle.change(function () {
767        $('.'+label+'__item').each(function (index, elem) {
768            var id = $(elem).attr("data-"+label+"id");
769
770            if (selected_obj.hasOwnProperty(id)) {
771                selected_obj[id] = jq_toggle.prop("checked");
772                $(elem).prop("checked", jq_toggle.prop("checked"));
773            }
774        });
775
776        setToggleTitle();
777    });
778
779    jq_toggle.css('cursor','pointer');
780
781    //Считываем текущие статусы
782    $('.'+label+'__item').each(function (index, elem) {
783        var id = $(elem).attr("data-"+label+"id");
784
785        selected_obj[id] = $(elem).prop("checked");
786    });
787
788    self.check();//
789};
790
791function smartSearch(target, searchText) {
792    target = target.toLowerCase();
793    searchText = searchText.toLowerCase();
794
795    var lat = "qwertyuiop[]asdfghjkl;'zxcvbnm,./";
796    var ru = "йцукенгшщзхъфывапролджэячсмитьбю.";
797
798    if (ru.indexOf(searchText[0]) == -1) {
799        if (target.indexOf(searchText) > -1) return true;
800        return false;
801    } else {
802        var searchTextRu = searchText;
803        var searchTextLat = "";
804
805        for (var i=0; i < searchTextRu.length; i++) {
806            searchTextLat += lat[ru.indexOf(searchTextRu[i])];
807        }
808        if (target.indexOf(searchTextRu) > -1) return true;
809        if (target.indexOf(searchTextLat) > -1) return true;
810        return false;
811    }
812}
813
814//Функция помещает текст-шаблон в позицию курсора
815function addTextToTextarea(areaId, text, callback) {
816    var txtarea = document.getElementById(areaId);
817    if (!txtarea) return;
818
819    var scrollPos = txtarea.scrollTop;
820    var strPos = 0;
821    var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
822        "ff" : (document.selection ? "ie" : false ) );
823    if (br == "ie") {
824        txtarea.focus();
825        var range = document.selection.createRange();
826        range.moveStart ('character', -txtarea.value.length);
827        strPos = range.text.length;
828    } else if (br == "ff") {
829        strPos = txtarea.selectionStart;
830    }
831
832    var front = (txtarea.value).substring(0, strPos);
833    var back = (txtarea.value).substring(strPos, txtarea.value.length);
834    txtarea.value = front + text + back;
835    strPos = strPos + text.length;
836    if (br == "ie") {
837        txtarea.focus();
838        var ieRange = document.selection.createRange();
839        ieRange.moveStart ('character', -txtarea.value.length);
840        ieRange.moveStart ('character', strPos);
841        ieRange.moveEnd ('character', 0);
842        ieRange.select();
843    } else if (br == "ff") {
844        txtarea.selectionStart = strPos;
845        txtarea.selectionEnd = strPos;
846        txtarea.focus();
847    }
848
849    txtarea.scrollTop = scrollPos;
850
851    callback.call(true);
852}
853
854//Функция помещает текст-шаблон в позицию курсора, в отличии от предыдущей передаем dom елемент сразу
855function addTextToTextareaByElem(txtarea, text) {
856    if (!txtarea) return;
857
858    var scrollPos = txtarea.scrollTop;
859    var strPos = 0;
860    var br = ((txtarea.selectionStart || txtarea.selectionStart == '0') ?
861        "ff" : (document.selection ? "ie" : false ) );
862    if (br == "ie") {
863        txtarea.focus();
864        var range = document.selection.createRange();
865        range.moveStart ('character', -txtarea.value.length);
866        strPos = range.text.length;
867    } else if (br == "ff") {
868        strPos = txtarea.selectionStart;
869    }
870
871    var front = (txtarea.value).substring(0, strPos);
872    var back = (txtarea.value).substring(strPos, txtarea.value.length);
873    txtarea.value = front + text + back;
874    strPos = strPos + text.length;
875    if (br == "ie") {
876        txtarea.focus();
877        var ieRange = document.selection.createRange();
878        ieRange.moveStart ('character', -txtarea.value.length);
879        ieRange.moveStart ('character', strPos);
880        ieRange.moveEnd ('character', 0);
881        ieRange.select();
882    } else if (br == "ff") {
883        txtarea.selectionStart = strPos;
884        txtarea.selectionEnd = strPos;
885        txtarea.focus();
886    }
887
888    txtarea.scrollTop = scrollPos;
889    $(txtarea).trigger('keyup');
890}
891
892function addButton(text, callback, addclass, parent) {
893    parent = (parent === undefined)? $('#modalfooter') : $(parent);
894    addclass = (addclass === undefined)? '' : addclass;
895
896    $('<button>',{type: 'button', class: 'btn btn-primary modalbutton ' + addclass, text: text, click: callback}).appendTo(parent);
897}
898
899function timeToStr(offset) { //Делаем функцию для красивого заполнение временных инпутов
900    var reformat = function(val) {
901        if (val < 10) return "0" + String(val);
902        return String(val);
903    } ;
904    //Offset - сдвиг в секундах
905    offset = (offset == undefined)?0:offset;
906
907    var now = new Date();
908    now.setTime(now.getTime() + 1000 * offset);
909
910    var
911        Ye = reformat(now.getFullYear()),
912        Mo = reformat(now.getMonth()+1),
913        Da = reformat(now.getDate()),
914        Ho = reformat(now.getHours()),
915        Mi = reformat(now.getMinutes()),
916        Se = reformat(now.getSeconds());
917
918    var datesep = '-', timesep = ':';
919
920    return Ye + datesep + Mo + datesep + Da + " " + Ho + timesep + Mi + timesep + Se;
921}
922
923function Paginator(parent, limit_by_page, limit_links) {
924    let self = this,
925        current_page = 1,
926        limit = 10,
927        links_limit = limit_links,
928        content = [],
929        content_length = 0,
930        content_pages = 0,
931        jq_parent = parent,
932        jq_renderblock = jq_parent.find('.pag-renderblock'),
933        jq_pagesblock = jq_parent.find('.pag-pagesblock');
934
935    if (limit_by_page === 'itemsperpage') {
936        limit = (!paramsGlobal.hasOwnProperty('itemsperpage'))?10:paramsGlobal.itemsperpage;
937    } else if (limit_by_page === 'contactsperpage') {
938        limit = (!paramsGlobal.hasOwnProperty('contactsperpage'))?50:paramsGlobal.contactsperpage;
939    } else if (limit_by_page > 0) {
940        limit = limit_by_page
941    }
942
943    this.trigger = function (page) {
944        console.log("Не установлен обработчик!");
945    };
946
947    this.setCurrentPage = function (page) {
948        if (page !== undefined) {
949            current_page = Number(page);
950        }
951    };
952
953    this.getCurrentPage = function () {
954        return current_page;
955    };
956
957    this.getRenderBlock = function () {
958        return jq_renderblock;
959    };
960
961    this.getPageContent = function () {
962        showLinks();
963        jq_parent.find(`[data-page = "${current_page}"]`).addClass('page-active');
964
965        return content;
966    };
967
968    this.getLimit = function () {
969        return limit;
970    }
971
972    this.setContent = function(data_array) {
973        if ( (self.getCurrentPage() > 1) && (data_array.length === 0) ) { //Если удалили последний елемент на странице, возращаемся на предыдущую страницу
974            self.trigger(self.getCurrentPage() - 1)
975            return
976        }
977
978        content = data_array;
979    };
980
981    this.setContentLength = function(data_length) {
982        content_length = data_length;
983        content_pages = Math.floor((content_length-0.1)/limit+1);
984    };
985
986    var showLinks = function () {
987        jq_pagesblock.empty()
988        if ( content_pages <= 1 ) return true; //Если страница одна - ничего не выводим
989        let pullpages = new Set([current_page]);
990
991        for (let i = 1; i < links_limit ; i++) {
992            let setSize = Math.min(content_pages, links_limit)
993            if ( pullpages.size >= setSize) break //Размер сета не может бытьь больше количеествва страниц
994            if ( current_page + i <= content_pages ) {
995                pullpages.add( current_page + i )
996            }
997            if ( pullpages.size >= setSize ) break //см выше
998            if ( current_page - i > 0) {
999                pullpages.add( current_page - i )
1000            }
1001        }
1002
1003        pullpages = new Set([...pullpages].sort((a,b) => (a-b)))
1004
1005        for (let page of pullpages) {
1006            jq_pagesblock.append(`<a class="page-link" data-page="${page}" tabindex="0">${page}</a>`)
1007        }
1008
1009        if ( !pullpages.has(1) || !pullpages.has(content_pages) ) {
1010            if ( !pullpages.has(1) ) {
1011                let cp = ( current_page-1 > 0 ) ? current_page-1 : 1
1012                jq_pagesblock.prepend(`<a tabindex="0" title="${cp}" class="page-link" data-page="${cp}"><</a>`);
1013                jq_pagesblock.prepend(`<a tabindex="0" title="1" class="page-link" data-page="1"><<</a>`);
1014            }
1015
1016            if ( !pullpages.has(content_pages) ) {
1017                let cp = ( current_page+1 <= content_pages ) ? current_page+1 : content_pages
1018                jq_pagesblock.append(`<a tabindex="0" title="${cp}" class="page-link" data-page="${cp}">></a>`);
1019                jq_pagesblock.append(`<a tabindex="0" title="${content_pages}" class="page-link" data-page="${content_pages}">>></a>`);
1020            }
1021        }
1022        setEvents();
1023    };
1024
1025    var setEvents = function () {
1026        jq_parent.find('.page-link').click(function (e) {
1027            self.trigger(Number(e.target.dataset.page));
1028        })
1029    }
1030}
1031
1032function ModalClass(mainclass) {
1033    var self = this;
1034    self.modalID = Math.floor(Math.random() * 1000000);
1035    self.parentElem = null; //Элемент вызвавший форму
1036
1037    var buildDOM = function () {
1038        var html_skeleton =`
1039            <div class="modal d-none modal-wrapper-${self.modalID}">
1040                <div class="modal-dialog">
1041                    <div class="modal-content">
1042                        <div class="modal-header">
1043                            <h4 class="modal-title"></h4>
1044                        </div>
1045                        <div class="modal-body">
1046                        </div>
1047                        <div class="modal-footer">
1048                        </div>
1049                        <a class="modal-close-button button type3" style="width: 50px"><i class="smsic-close"></i></a>
1050                    </div>
1051                </div>
1052            </div>
1053        `;
1054
1055        $('section').append(html_skeleton);
1056    }();
1057
1058    self.jq_wrapper = $('.modal-wrapper-'+self.modalID);
1059
1060    if (mainclass !== undefined) self.jq_wrapper.addClass(mainclass)
1061
1062    self.show = function() {
1063        //Скоем все модалки если они открыты
1064        $('.modal').addClass('d-none');
1065        $('.section-content, .addwidget, footer, .pencil').addClass('modal-show');
1066        self.jq_wrapper.removeClass('d-none');
1067        if (typeof self.afterShow === "function") self.afterShow();
1068
1069        //В модалях запрещаем назад-вперед
1070        window.onpopstate =  function(e) {
1071            history.pushState({},'')
1072            self.hide()
1073        }
1074        history.pushState({},'')//
1075    };
1076
1077    self.hide = function () {
1078        $('.section-content, .addwidget, footer, .pencil').removeClass('modal-show');
1079        self.jq_wrapper.addClass('d-none');
1080
1081        //Отменяем запрет переходов
1082        window.onpopstate = null
1083        history.go(-1)//
1084    };
1085
1086    self.setCSS = function (obj, elemstr) {
1087        switch (elemstr) {
1088            case 'body': self.jq_wrapper.find('.modal-body').css(obj); break;
1089            case 'footer': self.jq_wrapper.find('.modal-footer').css(obj); break;
1090            default: self.jq_wrapper.find('.modal-content').css(obj);
1091        }
1092    };
1093
1094    self.setBodyCSS = function (obj) {
1095        self.jq_wrapper.find('.modal-body').css(obj);
1096    };
1097
1098    self.setTitle = function (html) {
1099        self.jq_wrapper.find('.modal-title').html(html);
1100    };
1101
1102    self.setBody = function (html, addtype) {
1103        if (addtype == 'add') {
1104            self.jq_wrapper.find('.modal-body').append(html);
1105        } else {
1106            self.jq_wrapper.find('.modal-body').html(html);
1107        }
1108    };
1109
1110    self.setFooter = function (html, addtype) {
1111        if (addtype == 'add') {
1112            self.jq_wrapper.find('.modal-footer').append(html);
1113        } else {
1114            self.jq_wrapper.find('.modal-footer').html(html);
1115        }
1116    };
1117
1118    self.setFooterCSS = function (obj) {
1119        self.jq_wrapper.find('.modal-footer').css(obj);
1120    };
1121
1122    self.addButton = function (text, callback, addclass) {
1123        addclass = (addclass === undefined)? '' : addclass;
1124
1125        var button = $('<button>',{type: 'button', class: 'button type1 modalbutton ' + addclass, text: text, click: callback});
1126
1127        self.setFooter(button, 'add');
1128    };
1129
1130    self.replaceButton = function (text, callback, addclass) {
1131        addclass = (addclass === undefined)? '' : addclass;
1132
1133        var button = $('<button>',{type: 'button', class: 'button type1 modalbutton ' + addclass, text: text, click: callback});
1134
1135        self.setFooter(button);
1136    };
1137
1138    self.setCloseEvent = function (callback) {
1139        self.jq_wrapper.find('.modal-close-button').unbind('click')
1140        self.jq_wrapper.find('.modal-close-button').click(function () {
1141            if (typeof  callback === "function") {
1142                callback();
1143            }
1144        });
1145    };
1146
1147    self.setErrorText = function (text, showtime) {
1148        showtime = (showtime === undefined) ? 3000:showtime;
1149
1150        let p = $(`<p class="error" style="text-align: center">${text}</p>`);
1151        self.jq_wrapper.find('.modal-footer').after(p);
1152        setTimeout(function () {
1153            p.fadeOut(function () {
1154                p.remove();
1155            });
1156        },showtime)
1157    }
1158
1159    var setEvent = function() {
1160        self.jq_wrapper.find('.modal-close-button').click(function () {
1161            self.hide();
1162        });
1163    }();
1164}
1165
1166//Класс для использования шаблонов в интерфейсе, наследует ModalClass
1167function TextTemplateClass(type = 'smsTemplate') {
1168    ModalClass.apply(this,arguments);
1169    this.jq_wrapper.addClass('texttemplateclass')
1170    var self = this, editstatus = false, maxPosition = 0, editingTemplateID = '';
1171
1172    self.afterShow = function () {
1173        scrollModalToTop();
1174    }
1175
1176    function scrollModalToTop() {
1177        if (self.jq_wrapper[0] && typeof self.jq_wrapper[0].scrollIntoView === 'function') {
1178            self.jq_wrapper[0].scrollIntoView({block: 'start'});
1179        }
1180        self.jq_wrapper.find('.modal-body').scrollTop(0);
1181    }
1182
1183    function findTemplateLi(templateID) {
1184        return self.jq_wrapper.find('ul.template-list li').filter(function () {
1185            return $(this).attr('data-template-id') === templateID;
1186        }).first();
1187    }
1188
1189    function resolveNewTemplateID(templates, previousTemplateIDs) {
1190        if (!templates || !previousTemplateIDs) return '';
1191
1192        for (const templateID in templates) {
1193            if (previousTemplateIDs.indexOf(templateID) === -1) return templateID;
1194        }
1195
1196        return '';
1197    }
1198
1199    function activateTemplate(templateID, scrollToTemplate) {
1200        let li = findTemplateLi(templateID);
1201        if (!li.length) return;
1202
1203        self.jq_wrapper.find('[name="text-template"]').prop('checked', false);
1204        self.jq_wrapper.find('.do-addtocamp').css('display', 'none');
1205
1206        li.find('[name="text-template"]').prop('checked', true);
1207        li.find('.do-addtocamp').css('display', 'inline');
1208
1209        if (self.templates[templateID]) {
1210            self.jq_wrapper.find('.demo-phone-text').text(self.templates[templateID].text || '');
1211        }
1212
1213        if (scrollToTemplate && li[0] && typeof li[0].scrollIntoView === 'function') {
1214            li[0].scrollIntoView({behavior: 'smooth', block: 'center'});
1215        }
1216    }
1217
1218    function setEditingTemplate(templateID) {
1219        self.jq_wrapper.find('.do-edit i:first-child').removeClass('smsic-close').addClass('smsic-edit');
1220
1221        editingTemplateID = templateID || '';
1222        editstatus = !!editingTemplateID;
1223
1224        if (editingTemplateID) {
1225            findTemplateLi(editingTemplateID).find('.do-edit i:first-child').removeClass('smsic-edit').addClass('smsic-close');
1226        }
1227    }
1228
1229    self.setTitle(langt('L_GENERAL_Vybor_shablona'));
1230    //Получаем шаблоны при объявлении класса и потом работаем только с этими данными
1231    jsReqF('CampaignMain', {does: 'getTemplates', type}, r => {
1232        self.templates = r.dataout || {};
1233        buildDOM();
1234    })
1235
1236    addEditStatus = function(status) {
1237        if (status === 'edit') {
1238            self.jq_wrapper.find('.addform-title').text(langt('L_GENERAL_Redaktor_shablona'));
1239            self.jq_wrapper.find('.do-add').addClass('d-none');
1240            self.jq_wrapper.find('.do-edit-submit').removeClass('d-none');
1241        } else {
1242            self.jq_wrapper.find('.addform-title').text(langt('L_GENERAL_Dobavit_novyy'));
1243            self.jq_wrapper.find('.do-edit-submit').addClass('d-none');
1244            self.jq_wrapper.find('.do-add').removeClass('d-none');
1245            self.jq_wrapper.find('textarea').val('');
1246        }
1247        self.jq_wrapper.find('textarea').trigger('keyup'); //Что б посчиталось в счетчиках
1248        self.jq_wrapper.find('.do-edit-submit').unbind('click');
1249    },
1250    buildDOM = function (options = {}) {
1251        maxPosition = 0;
1252        var content = `
1253            <div class="template-wrapper">
1254                <div class="template-block d-none">
1255                    <ul class="template-list">
1256                        <li class="d-none">
1257                            <label><input type="radio" name="text-template"><span>${langt('L_GENERAL_Tut_budet_shablon')}</span></label>
1258                            <div class="add-block">
1259                                <button class="button type1 do-addtocamp">${langt('L_GENERAL_Vybrat')}</button>
1260                            </div>
1261                            <div>
1262                                <button class="button type0 do-edit"><i class="smsic-edit"></i></button>
1263                                <button class="button type0 do-delete"><i class="smsic-delete"></i></button>
1264                            </div>
1265                        </li>
1266                    </ul>
1267
1268                </div>
1269                <div class="help-block">
1270                    <textarea rows="8" placeholder="${langt('L_GENERAL_Tekst_soobshcheniya')}..."></textarea>
1271                    <div class="counter-wrapper"></div>
1272                    <button class="button type1 do-add">${langt('L_GENERAL_Dobavit_novyy')}</button>
1273                    <button class="button type1 do-edit-submit d-none">${langt('L_GENERAL_Sokhranit')}</button>
1274                </div>
1275            </div>`;
1276        self.setBody(content);
1277
1278        self.textCalc = new TextMessageClass(self.jq_wrapper);
1279        //По дефолту часть с шаблонами у нас скрыта если там шаблонов нет
1280        if (Object.keys(self.templates).length > 0) {
1281            self.jq_wrapper.find('.template-block').removeClass('d-none')
1282        } else {
1283            self.jq_wrapper.find('.template-block').addClass('d-none')
1284        }
1285
1286        let templateIDs = Object.keys(self.templates),
1287            activeTemplateID = (self.templates[options.activeTemplateID]) ? options.activeTemplateID : templateIDs[0];
1288
1289        for (const templateID of templateIDs) {
1290            maxPosition = ( maxPosition < Number(self.templates[templateID].position) ) ? Number(self.templates[templateID].position) : maxPosition
1291
1292            addLi(templateID, self.templates[templateID].text, templateID === activeTemplateID)
1293        }
1294
1295        if (activeTemplateID && self.templates[activeTemplateID]) {
1296            self.jq_wrapper.find('.demo-phone-text').text(self.templates[activeTemplateID].text || '');
1297        }
1298
1299        setEvents();
1300
1301        if (options.scrollToActive && activeTemplateID) {
1302            activateTemplate(activeTemplateID, true);
1303        } else if (!self.jq_wrapper.hasClass('d-none')) {
1304            scrollModalToTop();
1305        }
1306    },
1307    rebuild = function(options = {}) {
1308        jsReqF('CampaignMain', {does: 'getTemplates', type}, r => {
1309            let templates = r.dataout || {},
1310                activeTemplateID = options.activeTemplateID || resolveNewTemplateID(templates, options.previousTemplateIDs),
1311                scrollToActive = options.scrollToActive && !!activeTemplateID;
1312
1313            self.templates = templates
1314            self.setBody('')
1315            buildDOM({
1316                activeTemplateID: activeTemplateID,
1317                scrollToActive: scrollToActive
1318            })
1319        })
1320    },
1321    addLi = function(templateID, text, checked) {
1322        var ul = self.jq_wrapper.find('ul.template-list'),
1323            li = self.jq_wrapper.find('li.d-none').clone();
1324        li.attr('data-template-id', templateID);
1325        li.find('span').text(text);
1326        li.find('input').val(templateID);
1327        li.find('input').prop('checked', checked);
1328
1329        if (checked) {
1330            li.find('.do-addtocamp').css('display', 'inline');
1331        }
1332
1333        li.removeClass('d-none');
1334
1335        ul.append(li);
1336    },
1337    setEvents = function () {
1338        //Вставка в родителя
1339        self.jq_wrapper.find('.do-addtocamp').click(function () {
1340            let templateID = self.jq_wrapper.find('[name="text-template"]:checked').val(),
1341                template = self.templates[templateID]
1342
1343            addTextToTextareaByElem(self.parentElem[0], template.text);
1344
1345            //Проведем расчет рейтинга темплейтов и обновим позиции в отображении
1346            let counter = Number(self.templates[templateID].position) + 1,
1347                workedElement = Number(self.templates[templateID].position), //Текущий выбранный шаблон
1348                templates = [];
1349
1350            for (const templateIDKey in self.templates) {
1351                let curposition = Number(self.templates[templateIDKey].position);
1352                if ( templateIDKey === templateID ) { //Текущему добавляем единицу
1353                    //console.log(`ID: ${templateIDKey} CP: ${curposition} NP: ${counter} LY: current`)
1354                    self.templates[templateIDKey].position = counter
1355                } else {
1356                    let lymbda = Math.round((curposition - workedElement)/10) //Вычисляем лямбду
1357                    if ( (curposition - lymbda) > (counter + 1) ) { //Для вышестоящих шаблонов
1358                        //console.log(`ID: ${templateIDKey} CP: ${curposition} NP: ${curposition - lymbda} LY: ${lymbda}`)
1359                        self.templates[templateIDKey].position = curposition - lymbda //Даем новое позиционинрование с учетом лямбды
1360                    } else {
1361                        //console.log(`ID: ${templateIDKey} CP: ${curposition} NP: LOW`)
1362                        continue
1363                    }
1364                }
1365                templates.push(self.templates[templateIDKey]) //Собираем перечень шаблонов с измененными позициями
1366            }
1367
1368            jsReqF('CampaignMain',{does: 'editTemplate', templates, type}, ()=>rebuild()); //Последний выбранный подымим в позиции
1369            //
1370
1371            self.hide();
1372        });
1373
1374        //Клик по радио, отображаем кнопку выбора
1375        self.jq_wrapper.find('[name="text-template"]').change(e => {
1376            if (e.target.checked) {
1377                activateTemplate($(e.target).val(), false);
1378            }
1379        })
1380
1381        //Набор в тексбоксе
1382        self.jq_wrapper.find('textarea').keyup(function () {
1383            self.jq_wrapper.find('.demo-phone-text').text(self.jq_wrapper.find('textarea').val());
1384        });
1385
1386        //Редактирование шаблона
1387        self.jq_wrapper.find('.do-edit').click(function (event) {
1388            let li = $(event.target).closest('li'),
1389                templateID = li.find('input').val(),
1390                template = self.templates[templateID],
1391                isClosingCurrent = editingTemplateID === templateID
1392
1393            if (!template) return
1394
1395            activateTemplate(templateID, false);
1396
1397            if (isClosingCurrent) {
1398                setEditingTemplate('');
1399                addEditStatus('add');
1400                activateTemplate(templateID, true);
1401                return;
1402            }
1403
1404            setEditingTemplate(templateID);
1405            self.jq_wrapper.find('textarea').val(template.text);
1406            addEditStatus('edit'); //Делаем редактор
1407
1408
1409            self.jq_wrapper.find('.do-edit-submit').click(function () {
1410                var newtext = self.jq_wrapper.find('textarea').val();
1411                if ( newtext === '' ) return
1412
1413                jsReqF('CampaignMain',{does: 'editTemplate', templateID: template.templateID, text: newtext, type}, r => {
1414                    li.find('input + span').text(newtext)
1415
1416                    //Что б корректны были следующие выборы после редактирования
1417                    self.templates[templateID].text = newtext
1418
1419                    setEditingTemplate('');
1420                    addEditStatus('add'); //Возращаем добавление
1421                    activateTemplate(templateID, true);
1422                });
1423            });
1424
1425            //Спозиционируемся на редактируемом блоке
1426            self.jq_wrapper.find('textarea')[0].scrollIntoView({
1427                behavior: 'smooth',
1428                block: 'center'
1429            })
1430            //
1431        });
1432
1433        //Удаление шаблона
1434        self.jq_wrapper.find('.do-delete').click(function (event) {
1435            let li = $(event.target).closest('li'),
1436                templateID = li.find('input').val();
1437
1438            jsReqF('CampaignMain',{does: 'deleteTemplate', templateID: self.templates[templateID].templateID, type}, r =>li.remove());
1439        });
1440
1441        //Добавляем новый с возратов в форму
1442        self.jq_wrapper.find('.do-add').click(function () {
1443            if ( self.jq_wrapper.find('textarea').val() === '' ) return
1444            let templateText = self.jq_wrapper.find('textarea').val(),
1445                previousTemplateIDs = Object.keys(self.templates);
1446
1447            jsReqF('CampaignMain',{does: 'addTemplate', text: templateText, position: 0, type}, r => rebuild({
1448                previousTemplateIDs: previousTemplateIDs,
1449                scrollToActive: !!r.dataout
1450            }));
1451        })
1452
1453        //Переключалки видимости
1454        self.jq_wrapper.find('.ttoggled').click(function () {
1455            var toggledClass = $(this).attr('data-toggle');
1456            $('.'+toggledClass).toggleClass('d-none');
1457        });
1458    };
1459}
1460
1461TextTemplateClass.prototype = Object.create(ModalClass.prototype);
1462
1463//Класс селектора групп
1464function GroupSelectorClass(selected) {
1465    ModalClass.apply(this,arguments);
1466    var self = this;
1467    self.ifSelected = null;
1468    //Если передан массив выбранных групп
1469    if (Array.isArray(selected)) {
1470        self.selected = selected;
1471    } else {
1472        self.selected = [];
1473    }//
1474
1475    self.setTitle(langt('L_GENERAL_Vybor_grupp'));
1476    self.addButton(langt('L_GENERAL_Gotovo'), () => self.hide())
1477    jsReq('ContactsMain', {does: 'getGroups', limit: 9999, page: 1}, function () {
1478        if (Number(this.dataout.shift()) > 1000) console.log('Групп больше 9999');
1479        self.groups = this.dataout;
1480        self.buildDOM();
1481    });
1482}
1483
1484GroupSelectorClass.prototype = Object.create(ModalClass.prototype);
1485
1486GroupSelectorClass.prototype.buildDOM = function () {
1487    //Заполняем группы
1488    var html_items = $('<div>');
1489
1490    for (var index in this.groups) {
1491        var item = this.groups[index];
1492        let checked = (this.selected.indexOf(item.groupID) > -1) ? 'checked' : '';
1493        let p = $('<p class="group-block-modal"><input class="grouplist" id="groupList'+item.groupID+'" data-record-id="" type="checkbox" '+checked+'><label for="groupList'+item.groupID+'"></label><span></span></p>');
1494
1495        p.find('span').text(item.counter)
1496        p.find('.grouplist').attr('data-record-id', item.groupID)
1497        p.find('label').text(item.groupName)
1498
1499        if (item.default == 1) { //Дефолтную группу в верх списка
1500            html_items.prepend($('<hr>'));
1501            html_items.prepend(p)
1502        } else {
1503            html_items.append(p)
1504        }
1505    }
1506    html_items.append($('<hr>'));
1507
1508    this.setBodyCSS({'max-height': "65vh", "max-width": "600px", overflow: "auto", margin: "auto"});
1509    this.setBody(html_items);
1510    this.setEvents();
1511};
1512
1513GroupSelectorClass.prototype.setEvents = function () {
1514    var self = this;
1515
1516    //Добавить-убрать группу в self.selected
1517    this.jq_wrapper.find('.grouplist').click(function (e) {
1518        var curelem = e.target;
1519        if (curelem.checked) {
1520            self.selected.push(curelem.dataset.recordId);
1521        } else {
1522            self.selected.splice(self.selected.indexOf(curelem.dataset.recordId), 1);
1523        }
1524
1525        try { //Это виджет? тогда выбираем
1526            self.parentElem.parent().parent().find('.countgroup').text(self.selected.length);
1527        } catch (e) { //Это херня изза старой версии создания рассылки
1528            self.ifSelected.call(self.selected);
1529        }
1530    });
1531};
1532
1533//Класс селектора альфаимен
1534function AlfanameSelectorClass(selected) {
1535    ModalClass.apply(this,arguments);
1536    var self = this,
1537        ailistID = [];
1538    //Если передан массив выбранных групп
1539    if (Array.isArray(selected)) {
1540        self.selected = selected;
1541    } else {
1542        self.selected = [];
1543    }//
1544
1545    jsReq('ClientMain', {does: 'getAlphaNames', status: 'ALL', type: 'ALL', images: 'OFF'}, function () {
1546        self.ai = this.dataout;
1547        buildDOM();
1548    });
1549
1550    buildDOM = function () {
1551        var ailist = '';
1552        self.setBodyCSS({'min-height':'300px', 'margin': 'auto', width: '600px'});
1553
1554        //Заполняем имена
1555        for (let index in self.ai) {
1556            let ai = self.ai[index];
1557            ailist += `
1558                <li class="filter-item" data-filterinfo="${ai['alphaName']} (${ai['type']})" style="width: 200px">
1559                    <label class="cursor-pointer"><input data-nameid="${ai['nameID']}" class="aicheck" type="checkbox" checked="checked">&nbsp;&nbsp;${ai['alphaName']} (${ai['type']})</label>
1560                </li>
1561            `;
1562            ailistID.push(Number(ai['nameID']));
1563        }
1564
1565        var html = `
1566            <div class="f-a-c f-j-b">
1567                <a class="selectall" tabindex="0">${langt('L_GENERAL_Vybrat_vse')}</a>
1568                <input class="filter-ai" placeholder="${langt('L_GENERAL_Filtr_po_imenam')}...">
1569                <a class="deselectall" tabindex="0">${langt('L_GENERAL_Snyat_vydeleniye')}</a>
1570            </div>
1571            <hr>
1572            <ul style="column-count: 3; padding-top: 30px; overflow: auto">
1573                ${ailist}    
1574            </ul>
1575        `;
1576
1577        self.selected = ailistID.slice();
1578        self.setBody(html);
1579        self.addButton(langt('L_GENERAL_Gotovo'), function () {
1580            self.hide();
1581        });
1582        setEvents();
1583    };
1584
1585    setEvents = function () {
1586        $('.filter-ai').keyup(function () {
1587            var filterval = this.value;
1588
1589            startAfter(1000, function () {
1590                var pattern = RegExp(filterval,'i');
1591                self.jq_wrapper.find('.filter-item').each(function () {
1592                    console.log(this);
1593                    if (!pattern.test($(this).data('filterinfo'))) {
1594                        $(this).css('display','none');
1595                    } else {
1596                        $(this).css('display','block');
1597                    };
1598                });
1599            });
1600        });
1601
1602        $('.selectall').click(function () {
1603            var $aielems = self.jq_wrapper.find('input');
1604            $aielems.each(function () {
1605                $(this).prop( "checked", true );
1606            });
1607            self.selected = ailistID.slice();
1608            console.log(self.selected);
1609        });
1610
1611        $('.deselectall').click(function () {
1612            var $aielems = self.jq_wrapper.find('input');
1613            $aielems.each(function () {
1614                $(this).prop( "checked", false );
1615            });
1616            self.selected = [];
1617            console.log(self.selected);
1618        });
1619
1620        $('.aicheck').change(function () {
1621            if ( $(this).prop("checked") ) {
1622                self.selected.push($(this).data('nameid'));
1623                //console.log('check',self.selected);
1624            } else {
1625                (self.selected.indexOf($(this).data('nameid')) > -1) && self.selected.splice(self.selected.indexOf($(this).data('nameid')),1);
1626                //console.log('uncheck',self.selected);
1627            }
1628        });
1629    };
1630}
1631
1632AlfanameSelectorClass.prototype = Object.create(ModalClass.prototype);
1633
1634//Класс эмодзиков
1635function Emodzi(target, container) {
1636    let app = {
1637        emojiis: {
1638            top50: '😂😍🤣😊🙏💕😭🔥😘👍🥰😎😁😉😅🙄😜😁👌👏💔💖💙😢💪🤗💜😎😇🌹🎉💞✨🤷😱😌🌸🙌😋🦠💊💰💵💶💳😈💩💣🥂🌍',
1639            smileys : '😀😃😄😁😆😅😂🤣😊😇🙂🙃😉😌😍🥰😘😗😙😚😋😛😝😜🤪🤨🧐🤓😎🤩🥳😏😒😞😔😟😕🙁😣😖😫😩🥺😢😭😤😠😡🤬🤯😳🥵🥶😱😨😰😥😓🤗🤔🤭🤫🤥😶😐😑😬🙄😯😦😧😮😲🥱😴🤤😪😵🤐🥴🤢🤮🤧😷🤒🤕🤑🤠😈👿👹👺🤡💩👻💀👽👾🤖🎃😺😸😹😻😼😽🙀😿😾',
1640            gestures : '👋🤚🖐✋🖖👌🤏✌🤞🤟🤘🤙👈👉👆🖕👇☝👍👎✊👊🤛🤜👏🙌👐🤲🤝🙏✍💅🤳💪🦾🦵🦿🦶👣👂🦻👃🧠🦷🦴👀👅👄💋🩸',
1641            clothing: '🧳🌂🧵🧶👓🥽🥼🦺👔👕👖🧣🧤🧥🧦👗👘🥻🩱🩲🩳👙👚👛👜👝🎒👞👟🥾🥿👠👡🩰👢👑👒🎩🎓🧢💄💍💼',
1642            nature: '🐶🐱🐭🐹🐰🦊🐻🐼🐻🐨🐯🦁🐮🐷🐽🐸🐵🙈🙉🙊🐒🐔🐧🐦🐤🐣🐥🦆🦅🦉🦇🐺🐗🐴🦄🐝🐛🦋🐌🐞🐜🦟🦗🦂🐢🐍🦎🦖🦕🐙🦑🦐🦞🦀🐡🐠🐟🐬🐳🐋🦈🐊🐅🐆🦓🦍🦧🐘🦛🦏🐪🐫🦒🦘🐃🐂🐄🐎🐖🐏🐑🦙🐐🦌🐕🐩🦮🐕🦺🐈🐈🐓🦃🦚
1642🦜🦢🦩🐇🦝🦨🦡🦦🦥🐁🐀🦔🐾🐉🐲🌵🎄🌲🌳🌴🌱🌿🍀🎍🎋🍃🍂🍁🍄🐚🌾💐🌷🌹🥀🌺🌸🌼🌻🌞🌝🌛🌜🌚🌕🌖🌗🌘🌑🌒🌓🌔🌙🌎🌍🌏🪐💫⭐🌟✨⚡💥🔥🌈⛅⛄💨💧💦☔🌊',
1643            food: '🍏🍎🍐🍊🍋🍌🍉🍇🍓🍈🍒🍑🥭🍍🥥🥝🍅🍆🥑🥦🥬🥒🌽🥕🧄🧅🥔🍠🥐🥯🍞🥖🥨🧀🥚🍳🧈🥞🧇🥓🥩🍗🍖🦴🌭🍔🍟🍕🥪🥙🧆🌮🌯🥗🥘🥫🍝🍜🍲🍛🍣🍱🥟🦪🍤🍙🍚🍘🍥🥠🥮🍢🍡🍧🍨🍦🥧🧁🍰🎂🍮🍭🍬🍫🍿🍩🍪🌰🥜🍯🥛🍼☕🍵🧃🥤🍶🍺🍻🥂🍷🥃🍸🍹🧉🍾🧊🥄🍴🥣🥡🥢🧂',
1644            activity: '⚽🏀🏈⚾🥎🎾🏐🏉🥏🎱🪀🏓🏸🏒🏑🥍🏏🥅⛳🪁🏹🎣🤿🥊🥋🎽🛹🛷🥌🎿🏂🪂🏋🤼🤸⛹🤺🤾🏌🏇🧘🏄🏊🤽🚣🧗🚵🚴🏆🥉🏅🎫🎪🤹🎭🩰🎨🎬🎤🎧🎼🎹🥁🎷🎺🎸🪕🎻🎲🎯🎳🎮🎰🧩',
1645            travel: '🚗🚕🚙🚌🚎🚓🚑🚒🚐🚚🚛🚜🦯🦽🦼🛴🚲🛵🏍🛺🚨🚔🚍🚘🚖🚡🚠🚟🚃🚋🚞🚝🚄🚅🚈🚂🚆🚇🚊🚉🛫🛬💺🚀🛸🚁🛶⛵🚤🛳⛴🚢⚓⛽🚧🚦🚥🚏🗿🗽🗼🏰🏯🏟🎡🎢🎠⛲🌋⛺🏠🏡🏭🏢🏬🏣🏤🏥🏦🏨🏪🏫🏩💒⛪🕌🕍🛕🕋🗾🎑🌅🌄🌠🎇🎆🌇🌆🌃🌌🌉🌁',
1646            objects: '⌚📱📲💻💽💾💿📀📼📷📸📹🎥📞📟📠📺📻🧭⏱⏲⏰⌛⏳📡🔋🔌💡🔦🪔🧯💸💵💴💶💷💰💳💎🧰🔧🔨🔩🧱🧲🔫💣🧨🪓🔪🚬🏺🔮📿🧿💈🔭🔬🩹🩺💊💉🩸🧬🦠🧫🧪🧹🧺🧻🚽🚰🚿🛁🛀🧼🪒🧽🧴🔑🚪🪑🛌🧸🛒🎁🎈🎏🎀🎊🎉🎎🏮🎐🧧📩📨📧💌📥📤📦📪📫📬📭📮📯📜📃📄📑🧾📊📈📉📆📅📇📋📁📂📰📓📔📒📕📗📘📙📚📖🧷🔗📐📏🧮📌📍📝🔍🔎🔏🔐🔒🔓',
1647        },
1648        wrapper: document.createElement('div'),
1649        button: document.createElement('div'),
1650        selector: document.createElement('div'),
1651        caution: document.createElement('div'),
1652        textarea: target,
1653        container: container,
1654    }
1655
1656    app.container.append(app.button)
1657    app.wrapper.style.display = 'none' //Скрываем по умолчанию
1658
1659    //Нарисуем кнопку
1660    app.button.classList = 'emo-button'
1661    app.button.innerHTML = '<span class="emojii-button" style="cursor: pointer">🙂</span>'
1662    app.button.onclick = e => {
1663        if ( e.target.constructor.name === 'HTMLLIElement' ) return
1664        app.wrapper.style.display = (app.wrapper.style.display === 'none') ? 'block' : 'none'
1665    }
1666
1667    app.wrapper.classList = 'emoji-wrapper'
1668    app.button.append(app.wrapper)
1669
1670    //Загрузим разделы
1671    let ulbuttons = document.createElement('ul')
1672    ulbuttons.classList = 'emoji-paragraph'
1673    for (const appKey in app.emojiis) {
1674        let li = document.createElement('li')
1675        li.classList = appKey
1676        let litext = langt('L_EMODZI_'+appKey)
1677        if  (appKey === 'top50') li.className = 'active'
1678
1679        li.innerText = litext
1680        ulbuttons.appendChild(li)
1681
1682        li.addEventListener('click', ()=>{
1683            let ulli = document.querySelectorAll('.emoji-paragraph>li')
1684            for (let oldli of ulli) {
1685                oldli.className = ''
1686            }
1687            li.className = 'active'
1688            let emodzilist = document.querySelectorAll(`.emoji-list`)
1689            for (const paragraph of emodzilist) {
1690                paragraph.style.display = ( paragraph.className.includes(appKey) ) ? 'flex' : 'none'
1691            }
1692        })
1693    }
1694
1695    app.wrapper.append(ulbuttons)
1696    //Заполним емодзи
1697    for (const appKey in app.emojiis) {
1698        let ul = document.createElement('ul')
1699        if ( appKey !== 'top50' ) ul.style.display = 'none'
1700        ul.className = `emoji-list ${appKey}`
1701        for (let char of app.emojiis[appKey]) {
1702            let li = document.createElement('li')
1703            li.innerText = char
1704            li.title = char.length + ' симв.'
1705            ul.appendChild(li)
1706
1707            li.addEventListener('click', () => {
1708                addTextToTextareaByElem(app.textarea, char)
1709            })
1710        }
1711        app.wrapper.append(ul)
1712    }
1713
1714    //Предупреждение и его анимация
1715    app.caution.className = 'emodzi-caution'
1716    let $span = $('<span>', {text: langt('L_EMODZI_Vnimaniye_Otobrazheniye_emodzi')})
1717    $(app.caution).append($span)
1718    app.wrapper.append(app.caution)
1719
1720
1721    //Закрываем окно при внешнем клике
1722    addEventListener('click', e=>{
1723        if (e.target.classList[0] === 'emojii-button' || e.target.constructor.name === 'HTMLLIElement') return
1724        app.wrapper.style.display = 'none'
1725    })
1726}
1727
1728//Класс обработчиков и калькуляций textarea, $parentElem - jq ссылка на родительский елемент-контейнер
1729function TextMessageClass($parentElem) {
1730    var self = this;
1731    self.parentElem = $parentElem;
1732    self.ID = Math.floor(Math.random() * 1000000);
1733    self.isViber = self.parentElem.hasClass('viber-text') || self.parentElem.hasClass('rcs-text') //Необходимо для скрытия ненужных блоков
1734
1735    self.textarea = self.parentElem.find('textarea')
1736
1737    var fastlink_html = `
1738            <a tabindex="0" class="fastlink" title="${langt('L_GENERAL_Nomer_telefona')}">%PHONE%</a>
1739            <a tabindex="0" class="fastlink" title="${langt('L_GENERAL_Familiya_poluchatelya')}">%FAM%</a>
1740            <a tabindex="0" class="fastlink" title="${langt('L_GENERAL_Imya_poluchatelya')}">%IM%</a>
1741            <a tabindex="0" class="fastlink" title="${langt('L_GENERAL_Otchestvo_poluchatelya')}">%OTCH%</a><br/>
1741`;
1742    self.parentElem.find('.fastlink-wrapper').append(fastlink_html);
1743
1744    self.parentElem.find('.fastlink').click(function () {
1745        addTextToTextareaByElem(self.textarea[0], this.innerHTML);
1746    });
1747
1748
1749    const   sympartcounter = ( self.isViber ) ? '' : `<div><span class="text1 sympartcounter">150</span><span class="text2">${langt('L_GENERAL_simvolov_do')}</span></div>`,
1750            countmes = ( self.isViber ) ? '' : `<div><span class="text1 smscounter">2</span><span class="text2">${langt('L_GENERAL_chasti')}</span></div>`
1751
1752    const counter_html = `
1753           <div class="block-count">
1754                <div class="block-emodzi m-r10"></div> 
1755                <div><span class="text1 symcounter">150</span><span class="text2">${langt('L_GENERAL_simvolov')}</span></div>
1756                ${sympartcounter}
1757                ${countmes}
1758           </div>
1759           <div class="block-price" style="display: none">
1760               <span>${langt('L_GENERAL_Stoimost')}</span><span class="fullCostcounter">0.25</span>
1761           </div>
1762            <div class="block-buttons">
1763                <button class="text-stat-button charTypedraftCyr" type="button" name="charTypedraft_${self.ID}">${langt('L_GENERAL_CYR')}</button><button class="text-stat-button charTypedraftLat" type="button" name="charTypedraft_${self.ID}">${langt('L_GENERAL_LAT')}</button>
1764            </div>`;
1765
1766    self.parentElem.find('.counter-wrapper').append(counter_html);
1767    //self.parentElem.append($counter_html);
1768
1769    //Назначаем события
1770    self.textarea.keyup(function () {
1771        self.symbolsCount();
1772    });
1773    self.parentElem.find('.charTypedraftCyr').click(function () {
1774        self.translit("cyr");
1775        self.textarea.trigger('keyup');
1776    });
1777    self.parentElem.find('.charTypedraftLat').click(function () {
1778        self.translit("lat");
1779        self.textarea.trigger('keyup');
1780    });
1781
1782    self.textarea.on('symbolsCount', function () {
1783        self.symbolsCount();
1784    });
1785
1786    self.symbolsCount();
1787
1788    if ( !Boolean(paramsGlobal.ISMOBILE) ) new Emodzi(self.textarea[0], self.parentElem.find('.block-emodzi'))
1789}
1790
1791TextMessageClass.prototype.symbolsCount = function() {
1792    let $symcounter = this.parentElem.find('.symcounter'),
1793        $sympartcounter = this.parentElem.find('.sympartcounter'),
1794        $smscounter = this.parentElem.find('.smscounter')
1795
1796    // Align frontend counter with backend normalization and GSM charset detection.
1797    var inputStr = this.textarea.val()
1798        .replace(/\t/g, " ")
1799        .replace(/\r\n/g, "\n");
1800    var gsmPattern = /^[\x0A\x0C\x0D\x20-\x5F\x61-\x7E\xA0\xA1\xA3-\xA5\xA7\xBF\xC4-\xC6\xC7\xC9\xD1\xD6\xD8\xDC\xDF\xE0\xE4-\xE9\xEC\xF1\xF2\xF6\xF8\xF9\xFC\u0393\u0394\u0398\u039B\u039E\u03A0\u03A3\u03A6\u03A8\u03A9\u20AC]*$/u;
1801    var isGsm = gsmPattern.test(inputStr);
1802    var strlength = inputStr.length;
1803    let basecount = 160
1804
1805    if (!isGsm) {
1806        this.parentElem.find('.charTypedraftCyr').addClass('active');
1807        this.parentElem.find('.charTypedraftLat').removeClass('active');
1808
1809        basecount = (strlength > 70) ? 67 : 70
1810    } else {
1811        this.parentElem.find('.charTypedraftLat').addClass('active');
1812        this.parentElem.find('.charTypedraftCyr').removeClass('active');
1813
1814        var tbc = (inputStr.match(/[\{\}\\[\]~|€^]/gu) || []).length;
1815        strlength = strlength + tbc;
1816
1817        basecount = (strlength > 160) ? 153 : basecount
1818    }
1819
1820    $symcounter.text(strlength) //Количество символов
1821    $smscounter.text( (( (strlength - basecount) > 0 ) ? Math.ceil(strlength / basecount) : 1) ) //Количество частей
1822    //Подсчет знаков до конца
1823    if (strlength > 0 && strlength%basecount === 0) { //если знаков равно части сообщения 0
1824        $sympartcounter.text(0)
1825    } else { //0 и остальные случаи
1826        $sympartcounter.text(basecount-strlength%basecount)
1827    }//
1828
1829    var smsParts = $smscounter.text();
1830    var smsAICost = 1.289;
1831    var smsCost = smsParts * smsAICost;
1832    $('.fullCostcounter').text(smsCost);
1833};
1834
1835
1836
1837TextMessageClass.prototype.translit = function (layout) {
1838    // -- TRANSLIT --------------------- //
1839    var tra = new Array();
1840    var abc2 = new Array();
1841    var abc1 = new Array();
1842    tra['a'] = new Array('J+', 'j+', 'J', 'j', '', '');
1843    abc2['a'] = new Array('Jа', 'jа', 'Я', 'я', 'а', 'a');
1844    tra['b'] = new Array('', '');
1845    abc2['b'] = new Array('б', 'b');
1846    tra['v'] = new Array('', '');
1847    abc2['v'] = new Array('в', 'v');
1848    tra['g'] = new Array('', '');
1849    abc2['g'] = new Array('г', 'g');
1850    tra['d'] = new Array('', '');
1851    abc2['d'] = new Array('д', 'd');
1852    tra['e'] = new Array('', '');
1853    abc2['e'] = new Array('е', 'e');
1854    tra['o'] = new Array('J+', 'j+', 'J', 'j', '', '');
1855    abc2['o'] = new Array('Jо', 'jо', 'Ё', 'ё', 'о', 'o');
1856    tra['h'] = new Array('сh+', 'Сh+', 'Сh', 'ш+', 'к+', 'с+', 'е+', 'з+', 'К+', 'Ш+', 'Е+', 'сh', 'С+', 'ц+', 'Ц+', 'З+', 'Е', 'ш', 'с', 'ц', 'к', 'е', 'З', 'Ш', 'С', 'Ц', 'К', 'з', '');
1857    abc2['h'] = new Array('сhh', 'Сhh', 'Щ', 'шh', 'кh', 'сh', 'еh', 'зh', 'Кh', 'Шh', 'Еh', 'щ', 'Сh', 'цh', 'Цh', 'Зh', 'Э', 'щ', 'ш', 'ч', 'х', 'э', 'Ж', 'Щ', 'Ш', 'Ч', 'Х', 'ж', 'h');
1858    tra['z'] = new Array('', '');
1859    abc2['z'] = new Array('з', 'z');
1860    tra['i'] = new Array('', '');
1861    abc2['i'] = new Array('и', 'i', 'і');
1862    tra['j'] = new Array('J+', 'j+', 'J', 'j', '');
1863    abc2['j'] = new Array('Jj', 'jj', 'Й', 'й', 'j');
1864    tra['k'] = new Array('', '');
1865    abc2['k'] = new Array('к', 'k');
1866    tra['l'] = new Array('', '');
1867    abc2['l'] = new Array('л', 'l');
1868    tra['m'] = new Array('', '');
1869    abc2['m'] = new Array('м', 'm');
1870    tra['n'] = new Array('', '');
1871    abc2['n'] = new Array('н', 'n');
1872    tra['p'] = new Array('', '');
1873    abc2['p'] = new Array('п', 'p');
1874    tra['r'] = new Array('', '');
1875    abc2['r'] = new Array('р', 'r');
1876    tra['s'] = new Array('', '');
1877    abc2['s'] = new Array('с', 's');
1878    tra['t'] = new Array('', '');
1879    abc2['t'] = new Array('т', 't');
1880    tra['u'] = new Array('J+', 'j+', 'J', 'j', '', '');
1881    abc2['u'] = new Array('Jу', 'jу', 'Ю', 'ю', 'у', 'u');
1882    tra['f'] = new Array('', '');
1883    abc2['f'] = new Array('ф', 'f');
1884    tra['c'] = new Array('', '');
1885    abc2['c'] = new Array('ц', 'c');
1886    tra['\"'] = new Array('ъ+', 'ъ', '', '');
1887    abc2['\"'] = new Array('ъъ', 'Ъ', 'ъ', '\"');
1888    tra['y'] = new Array('', '');
1889    abc2['y'] = new Array('ы', 'y');
1890    tra['\''] = new Array('ь+', 'ь', '', '');
1891    abc2['\''] = new Array('ьь', 'Ь', 'ь', '\'');
1892    tra['A'] = new Array('J+', 'J', '', '');
1893    abc2['A'] = new Array('JА', 'Я', 'А', 'A');
1894    tra['B'] = new Array('', '');
1895    abc2['B'] = new Array('Б', 'B');
1896    tra['V'] = new Array('', '');
1897    abc2['V'] = new Array('В', 'V');
1898    tra['G'] = new Array('', '');
1899    abc2['G'] = new Array('Г', 'G');
1900    tra['D'] = new Array('', '');
1901    abc2['D'] = new Array('Д', 'D');
1902    tra['E'] = new Array('', '');
1903    abc2['E'] = new Array('Е', 'E');
1904    tra['O'] = new Array('J+', 'J', '', '');
1905    abc2['O'] = new Array('JО', 'Ё', 'О', 'O');
1906    tra['H'] = new Array('СH+', 'Ц+', 'З+', 'С+', 'СH', 'Ш+', 'К+', 'Е+', 'Е', 'К', 'Ц', 'С', 'Ш', 'З', '');
1907    abc2['H'] = new Array('СHH', 'ЦH', 'ЗH', 'СH', 'Щ', 'ШH', 'КH', 'ЕH', 'Э', 'Х', 'Ч', 'Ш', 'Щ', 'Ж', 'H');
1908    tra['Z'] = new Array('', '');
1909    abc2['Z'] = new Array('З', 'Z');
1910    tra['I'] = new Array('', '');
1911    abc2['I'] = new Array('И', 'I');
1912    tra['J'] = new Array('J+', 'J', '');
1913    abc2['J'] = new Array('JJ', 'Й', 'J');
1914    tra['K'] = new Array('', '');
1915    abc2['K'] = new Array('К', 'K');
1916    tra['L'] = new Array('', '');
1917    abc2['L'] = new Array('Л', 'L');
1918    tra['M'] = new Array('', '');
1919    abc2['M'] = new Array('М', 'M');
1920    tra['N'] = new Array('', '');
1921    abc2['N'] = new Array('Н', 'N');
1922    tra['P'] = new Array('', '');
1923    abc2['P'] = new Array('П', 'P');
1924    tra['R'] = new Array('', '');
1925    abc2['R'] = new Array('Р', 'R');
1926    tra['S'] = new Array('', '');
1927    abc2['S'] = new Array('С', 'S');
1928    tra['T'] = new Array('', '');
1929    abc2['T'] = new Array('Т', 'T');
1930    tra['U'] = new Array('J+', 'J', '', '');
1931    abc2['U'] = new Array('JУ', 'Ю', 'У', 'U');
1932    tra['F'] = new Array('', '');
1933    abc2['F'] = new Array('Ф', 'F');
1934    tra['C'] = new Array('', '');
1935    abc2['C'] = new Array('Ц', 'C');
1936    tra['Y'] = new Array('', '');
1937    abc2['Y'] = new Array('Ы', 'Y');
1938    abc1['а'] = 'a';
1939    abc1['б'] = 'b';
1940    abc1['в'] = 'v';
1941    abc1['г'] = 'g';
1942    abc1['д'] = 'd';
1943    abc1['е'] = 'e';
1944    abc1['ё'] = 'jo';
1945    abc1['ж'] = 'zh';
1946    abc1['з'] = 'z';
1947    abc1['и'] = 'i';
1948    abc1['й'] = 'jj';
1949    abc1['к'] = 'k';
1950    abc1['л'] = 'l';
1951    abc1['м'] = 'm';
1952    abc1['н'] = 'n';
1953    abc1['о'] = 'o';
1954    abc1['п'] = 'p';
1955    abc1['р'] = 'r';
1956    abc1['с'] = 's';
1957    abc1['т'] = 't';
1958    abc1['у'] = 'u';
1959    abc1['ф'] = 'f';
1960    abc1['х'] = 'kh';
1961    abc1['ц'] = 'c';
1962    abc1['ч'] = 'ch';
1963    abc1['ш'] = 'sh';
1964    abc1['щ'] = 'shh';
1965    abc1['ъ'] = '\"';
1966    abc1['ы'] = 'y';
1967    abc1['ь'] = '\'';
1968    abc1['э'] = 'eh';
1969    abc1['ю'] = 'ju';
1970    abc1['я'] = 'ja';
1971    abc1['А'] = 'A';
1972    abc1['Б'] = 'B';
1973    abc1['В'] = 'V';
1974    abc1['Г'] = 'G';
1975    abc1['Д'] = 'D';
1976    abc1['Е'] = 'E';
1977    abc1['Ё'] = 'Jo';
1978    abc1['Ж'] = 'Zh';
1979    abc1['З'] = 'Z';
1980    abc1['И'] = 'I';
1981    abc1['Й'] = 'Jj';
1982    abc1['К'] = 'K';
1983    abc1['Л'] = 'L';
1984    abc1['М'] = 'M';
1985    abc1['Н'] = 'N';
1986    abc1['О'] = 'O';
1987    abc1['П'] = 'P';
1988    abc1['Р'] = 'R';
1989    abc1['С'] = 'S';
1990    abc1['Т'] = 'T';
1991    abc1['У'] = 'U';
1992    abc1['Ф'] = 'F';
1993    abc1['Ð¥'] = 'Kh';
1994    abc1['Ц'] = 'C';
1995    abc1['Ч'] = 'Ch';
1996    abc1['Ш'] = 'Sh';
1997    abc1['Щ'] = 'Shh';
1998    abc1['Ъ'] = '\"\"';
1999    abc1['Ы'] = 'Y';
2000    abc1['Ь'] = '\'\'';
2001    abc1['Э'] = 'Eh';
2002    abc1['Ю'] = 'Ju';
2003    abc1['Я'] = 'Ja';
2004    abc1['є'] = 'e';
2005    abc1['Є'] = 'E';
2006    abc1['і'] = 'i';
2007    abc1['І'] = 'I';
2008    abc1['ї'] = 'i';
2009    abc1['Ї'] = 'I';
2010    abc1['№'] = '#';
2011
2012    var txt = this.textarea.val();
2013    txt = txt.replace(/\t/g, ' ');
2014    if (layout == "cyr") {
2015        var thestringlat = txt;
2016        var thestringcyr = "";
2017        for (kk = 0; kk < thestringlat.length; kk++) {
2018            var curtxt = thestringcyr + thestringlat.substr(kk, 1);
2019            var pretxt = curtxt.substr(0, curtxt.length - 1);
2020            var last = curtxt.substr(curtxt.length - 1, 1);
2021            var lat = tra[last];
2022            var rus = abc2[last];
2023            if (lat) {
2024                for (var ii = 0; ii < lat.length; ii++) {
2025                    var pos = pretxt.length > lat[ii].length ? (pretxt.length - lat[ii].length) : 0;
2026                    if ( lat[ii] == pretxt.substr(pos, pretxt.length - pos) ) {
2027                        thestringcyr = pretxt.substr(0, pretxt.length - lat[ii].length) + rus[ii];
2028                        break;
2029                    }
2030                }
2031            } else {
2032                thestringcyr = curtxt;
2033            }
2034        }
2035        this.textarea.val(thestringcyr);
2036    } else {
2037        var preval = ""; var postval="";
2038        txtnew = "";
2039        var symb = "";
2040        for (kk = 0; kk < txt.length; kk++) {
2041            symb = txt.substr(kk, 1);
2042            symb = abc1[symb] ? abc1[symb] : symb;
2043            txtnew = txtnew.substr(0, txtnew.length) + symb;
2044        }
2045        this.textarea.val(preval + txtnew + postval);
2046    }
2047
2048    this.symbolsCount();
2049}
2050
2051function ClientInfoClass() {
2052    var self = this;
2053    self.ai = [];
2054
2055    this.getInfo = function (callback) {
2056        jsReq('ClientMain', {does: 'getAlphaNames'}, function () {
2057            self.ai = objCopy(this.dataout);
2058
2059            callback();
2060        });
2061    };
2062}
2063
2064//Устанавливает статусы дисаблед псевдомассиву элементов js
2065function setDisabledStatus(elements, status) {
2066    for (let i = 0; i < elements.length; i ++) {
2067        let elem = elements[i];
2068
2069        if (status) {
2070            elem.removeAttribute('disabled')
2071        } else {
2072            elem.setAttribute('disabled', 'disabled')
2073        }
2074    }
2075}
2076
2077function HeaderLinks() {
2078    ModalClass.apply(this, arguments);
2079    var self = this,
2080        jq_linkswrapper =  $('.headerlinkcontainer'),
2081        settings = null,
2082        maxwidth = Number(jq_linkswrapper.css('max-width').replace('px', '')),
2083        countbutton = Math.trunc(maxwidth / 49);
2084        addbutton = `<div class="headerlinkadd cursor-pointer">+</div>`,
2085        scrollbutton = `<div class="headerlinkscroll" title="Все кнопки"><i class="smsic-down-dir mini"></i><div class="scrollcontent"></div></div>`;
2086
2087    self.setCSS({
2088        'display': 'flex',
2089        'flex-direction': 'column',
2090        'align-items': 'center'
2091    }, 'body' );
2092
2093    self.setCSS({
2094        'text-align': 'center',
2095        'margin-top': '30px'
2096    }, 'footer' );
2097
2098    self.linkadm = `
2099        <div class="headerlink linkadm" title="${langt('L_GENERAL_Administrirovaniye')}" data-link="#"><i class="smsic-settings"></i>
2100            <div class="linkadm-content">
2101                <a href="/Panel/Codes/">
2101Регистрации</a>
2102                <a href="/Panel/Clients/">Клиенты</a>
2103                <a href="/Panel/AllReports/">Рассылки</a>
2104                <a href="/Panel/Anames/">Альфаимена</a>
2105                <hr>
2106                <a href="/Panel/Payment/">Заказы</a>
2107                <a href="/Panel/Zvonki/">Звонки</a>
2108                <a href="/Panel/PaymentM/">Платежи</a>
2109                <hr>
2110                <a href="/Panel/Limits/MerchantsP24">Мерчанты P24</a>
2111                <a href="/Panel/Limits/Cards">Карты P24</a>
2112                <a href="/Panel/Limits/Organizations">Безнал</a>
2113                <a href="/Panel/Limits/Merchants">Ecommers</a>
2114                <hr>
2115                <a href="/support/manager/">Тикеты</a>
2116                <a href="/Test">Тест</a>
2117                <a href="/Test/Errors/">Ошибки Nginx</a>
2118            </div>
2119        </div>`; //Администрирование
2120    if ( paramsGlobal.hasOwnProperty('infocounters') ) {//Количество пользователей
2121        self.linkusers = `<div class="headerlink linkadm linkusers" title="Пользователей на сайте" data-link="#">${paramsGlobal.infocounters.users}
2122            <div class="linkadm-content">
2123                <ul style="font-size: 1rem">
2124                    <li><strong>В последние 30 мин</strong></li>
2125                    <li>Пользователей: <span style="text-align: right">${paramsGlobal.infocounters.users}</span></li>
2126                    <li>С начала дня: <span style="text-align: right">${paramsGlobal.infocounters.todaycount}</span></li>
2127                    <li>Гостей: <span style="text-align: right">${paramsGlobal.infocounters.guest}</span></li>
2128                    <li>Менеджеров: ${paramsGlobal.infocounters.managers}</li>
2129                </ul>
2130            </div>
2131        </div>`
2132
2133        let color = (paramsGlobal.infocounters.queue.count < 20) ? 'lightgreen' : 'orange'
2134        if (paramsGlobal.infocounters.queue.count > 100) color = 'red'
2135        let widthunset = (paramsGlobal.infocounters.queue.count > 999) ? 'width: unset':'';
2136
2137        self.linkqueue = `<div class="headerlink linkadm linkqueue" title="Рассылок в очереди" data-link="#" style="color: ${color}; ${widthunset}">${paramsGlobal.infocounters.queue.count}
2138            <div class="linkadm-content">
2139                <ul style="font-size: 1rem">
2140                    <li><strong>Последний SEND:</strong></li>
2141                    <li>${paramsGlobal.infocounters.queue.lasttime || 'Очереди нет.'}</li>
2142                </ul>
2143            </div>
2144        </div>`
2145    }
2146    self.link1 = `<div class="headerlink link1" title="${langt('L_GENERAL_Napisat_v_podderzhku')}" data-link="/support/newticket"><i class="smsic-mail"></i></div>`; //Написать в поддержку
2147    self.link2 = `<div class="headerlink link2" title="${langt('L_GENERAL_Otpravit_kvitantsiyu')}" data-link="/client/balance/notification/"><i class="smsic-attach"></i></div>`; //Отправить квитанцию
2148    self.link3 = `<div class="headerlink link3" title="${langt('L_GENERAL_Otpravit_sms')}" data-link="/campaigns/sms/create"><i class="smsic-chat"></i></div>`; //Отправить смс
2149    self.link4 = `<div class="headerlink link4 add-viber" title="${langt('L_GENERAL_Otpravit_Viber')}" data-link="viber"><i class="smsic-paper-plane"></i></div>`; //Отправить Viber
2150    self.link5 = `<div class="headerlink link5" title="${langt('L_GENERAL_Sozdat_gruppu')}" data-link="/contacts/"><i class="smsic-group"></i></div>`; //Создать группу
2151
2152    var getLinks = function (callback) {
2153        jsReq('ClientMain', {does: 'getClientInfo'}, function () {
2154            var responce = this.dataout;
2155            try {
2156                settings = JSON.parse(responce.settings);
2157            } catch (e) {}
2158
2159            if ( (settings === null) || (!settings.hasOwnProperty('links')) ) {
2160                settings = {}; settings.links = [];
2161            }
2162
2163            callback();
2164            buildLinks();
2165            //setEvents();
2166        });
2167    };
2168
2169    var buildLinks = function () {
2170        $('.linkon, .headerlinkadd, .headerlinkscroll').remove();
2171        if (settings.hasOwnProperty('links')) {
2172            let linkscount = 0, scrolladd = false;
2173            for (var i = 0; i < settings.links.length; i++) {
2174                var link = settings.links[i];
2175                if ( paramsGlobal['ClientType'] < 3 && ['linkadm','linkusers','linkqueue'].includes(link) ) continue;
2176
2177                linkscount++;
2178                if ( linkscount > countbutton - 3 ) {
2179                    if (!scrolladd) {
2180                        jq_linkswrapper.append(scrollbutton);
2181                        scrolladd = true;
2182                    }
2183                    jq_linkswrapper.find('.scrollcontent').append(self[link])
2184                    jq_linkswrapper.find('.'+link).addClass('linkon');
2185                } else {
2186                    jq_linkswrapper.append(self[link]);
2187                    jq_linkswrapper.find('.'+link).addClass('linkon');
2188                }
2189            }
2190            jq_linkswrapper.append(addbutton);
2191        }
2192
2193        setEvents();
2194    };
2195
2196    var setEvents = function() {
2197        $(".headerlinkadd").click(function () {
2198            self.show();
2199        });
2200
2201        $('.linkon').click(function () {
2202            let link = $(this).data('link');
2203            if (link === 'viber') { //Для вайбера своя лабуда
2204                let params = {
2205                    does: 'getAlphaNames',
2206                    type: 'VIBER',
2207                    images: 'OFF'
2208                }
2209                jsReq('ClientMain', params, function () {
2210                    if (!this.dataout || this.dataout.length === 0) {
2211                        alertModal({
2212                            title: 'У вас нет активного имени',
2213                            body: 'Для создания рассылки Вам необходимо активное имя отправителя для Viber. <br><br> Зарегистрировать и узнать статус имен можно на странице "Имя отправителя".<br><br>',
2214                            button: 'Перейти к именам',
2215                            action: () => {
2216                                location = '/sendername/'
2217                            }
2218                        })
2219                    } else {
2220                        location = '/campaigns/viber/create/'
2221                    }
2222                })
2223            } else {
2224                location = link+'/'
2225            }
2226        });
2227    };
2228
2229    var buildLabel = function(id, text) {
2230        var $label = $('<label>'),
2231            $input = $('<input type="checkbox">'),
2232            $icon = $(self[id]).find('i');
2233
2234        if ( $icon[0] === undefined) $icon = $('<i>', {class: 'smsic-settings'}) //Если не найдена иконка, например в счетчике
2235
2236        $label.text(text);
2237        $icon.css({'font-size': '30px'});
2238        $label.prepend($icon);
2239
2240        $input.attr('id', id);
2241        try {
2242            $input.attr("checked", (settings.links.indexOf(id) > -1) );
2243        } catch (e) {
2244
2245        }
2246
2247        $label.prepend($input);
2248        $label.css({
2249            'user-select': 'none',
2250            'cursor': 'pointer',
2251            'width': '230px',
2252            'display': 'flex',
2253            'align-items': 'center',
2254            'color': '#000'
2255        });
2256        return $label.prop('outerHTML');
2257    };
2258
2259    getLinks(function () {
2260        let adminLink = '';
2261        if ( paramsGlobal['ClientType']>1 ) {
2262            adminLink += buildLabel('linkadm', langt('L_GENERAL_Administrirovaniye'))
2263                        +buildLabel('linkusers', 'Счетчик пользователей сайта')
2264                        +buildLabel('linkqueue', 'Текущая очередь рассылок')
2265                        +"<hr>";
2266        }
2267        self.setTitle(langt('L_GENERAL_Dobavleniye_funktsiy'));//Добавление функций
2268        //self.setBody('<p>'+langt('L_GENERAL_Vyberite_funktsii_kotoryye_vy_chasto_polzuyetes')+':</p>' + //Выберите функции, которые вы часто пользуетесь
2269        self.setBody(
2270            adminLink + //Администрирование если ClientType > 1
2271            buildLabel('link1',langt('L_GENERAL_Napisat_v_podderzhku')) + //Написать в поддержку
2272            buildLabel('link2',langt('L_GENERAL_Otpravit_kvitantsiyu')) + //Отправить квитанцию
2273            buildLabel('link3',langt('L_GENERAL_Otpravit_sms')) + //Отправить смс
2274            buildLabel('link4',langt('L_GENERAL_Otpravit_Viber')) + //Отправить Viber
2275            buildLabel('link5',langt('L_GENERAL_Sozdat_gruppu')) ); //Создать группу
2276        self.addButton(langt('L_GENERAL_Primenit'), function () {
2277            settings.links = [];
2278            self.jq_wrapper.find('input').each(function (ind, elem) {
2279                if ($(elem).prop('checked')) {
2280                    settings.links.push(elem.id);
2281                }
2282            });
2283            jsReq('ClientMain', {does: 'updateClient', fields: {settings: JSON.stringify(settings)}}, function () {
2284                buildLinks()
2285            });
2286            self.hide();
2287        }); //Добавить
2288    });
2289}
2290
2291HeaderLinks.prototype = Object.create(ModalClass.prototype);
2292
2293function NotifySystem() {
2294    var self = this,
2295        jq_notifywrapper = $('.notifywrapper'),
2296        jq_notifylink = $('.notifylink'),
2297        jq_notifycounter = $('.notifycounter'),
2298        clientNotify = [],
2299        getClientNotify = function () {
2300            jsReq('ClientMain', {does:'getClientNotify', reading : "N"}, function () {
2301                clientNotify = this.dataout.slice();
2302                buildNotify();
2303            });
2304        },
2305        setEvents = function () {
2306            //Пометка о прочтении
2307            $('.notifyread').click(function (e) {
2308                var jq_li = $(this).closest('li'),
2309                    notifyID = jq_li.data('notifyid');
2310
2311                jsReq('ClientMain', {does: 'markNotify', notifyID: notifyID, type: 'readed'}, function () {
2312                    var newCount = Number(jq_notifycounter.text())-1;
2313                    jq_notifycounter.text((newCount == 0) ? "" : newCount);
2314                    jq_notifylink.trigger('click');
2315                });
2316
2317            });
2318
2319            //Класс для ссылки, пееред переходом помечаем о прочтении
2320            $('.silentread').click(function (e) {
2321                e.preventDefault();
2322                var jq_li = $(this).closest('li'),
2323                    notifyID = jq_li.data('notifyid'),
2324                    self = this;
2325
2326                jsReq('ClientMain', {does: 'markNotify', notifyID: notifyID, type: 'readed'}, function () {
2327                    var newCount = Number(jq_notifycounter.text())-1;
2328                    jq_notifycounter.text((newCount == 0) ? "" : newCount);
2329                    location = $(self).prop('href');
2330                });
2331            });
2332
2333            //Помечаем все прочитанным
2334            $('.set-mark-notify').click(e => jsReq('ClientMain', {does: 'markNotify', notifyID: -1, type: 'readed'}, () => getClientNotify()))
2335        },
2336        buildNotify = function () {
2337            jq_notifywrapper.find('.notify:not(.template)').remove();
2338
2339            jq_notifycounter.text(clientNotify.length > 0 ? clientNotify.length : '');
2340
2341            if (clientNotify.length > 0) {
2342                jq_notifylink.find('i').addClass('newmess');
2343            } else {
2344                jq_notifylink.find('i').removeClass('newmess');
2345                var jq_li_blank = $('.template.notify').clone();
2346                jq_li_blank.removeClass('template');
2347                jq_li_blank.css('display','list-item');
2348                jq_li_blank.find('.date-block').remove();
2349                jq_li_blank.find('.notifytext').html("<span style='font-size: 15px'>"+langt('L_GENERAL_Novykh_uvedomleniy_net')+"</span>");
2350                jq_notifywrapper.prepend(jq_li_blank);
2351            }
2352
2353            for (var i = 0; i < clientNotify.length; i++) {
2354                var notify = clientNotify[i],
2355                    jq_li = $('.template.notify').clone();
2356
2357                if ( (notify.readOnly == "Y") || (notify.type == "system") || (notify.type == "manager") )  { //Ридонли невозможно сделать прочитанным
2358                    jq_li.find(".notifyread").prop('disabled','disabled');
2359                }
2360                jq_li.attr('data-notifyid', notify.notifyID);
2361                jq_li.removeClass('template');
2362                jq_li.css('display','list-item');
2363                jq_li.find('.startdatatime').text(notify.startDateTime);
2364                jq_li.find('.notifytext').text(notify.text);
2365                jq_li.find('.action-block').html(notify.link);
2366                jq_notifywrapper.prepend(jq_li);
2367            }
2368
2369            if (clientNotify.length > 1) {
2370                let jq_li_blank = $('.template.notify').clone();
2371                jq_li_blank.removeClass('template');
2372                jq_li_blank.css('display','list-item');
2373                jq_li_blank.find('.date-block').remove();
2374                jq_li_blank.find('.notifytext').html(`<li><a tabindex="0" class="set-mark-notify">${langt('L_GENERAL_Prochitat_vse')}</a></li>`);
2375                jq_notifywrapper.prepend(jq_li_blank);
2376            }
2377
2378            setEvents();
2379        };
2380
2381    jq_notifylink.click(function () {
2382        for (let i = 0; i < clientNotify.length; i++) { //Помечаем все уведомления в колокольчике как увиденное
2383            let notify = clientNotify[i];
2384            if ( (notify['viewed'] === null) || (notify['viewed'] === 'N') ) {
2385                jsReq('ClientMain', {does: 'markNotify', notifyID: notify['notifyID'], type: 'viewed'}, () => {});
2386            }
2387        }//
2388        getClientNotify();
2389    });
2390
2391    getClientNotify();
2392    setInterval(function () {
2393        getClientNotify();
2394    },60000);
2395}
2396
2397function alertModal(props) {
2398    let modal = new ModalClass();
2399    let closeAlert = () => {
2400        modal.hide()
2401        modal.jq_wrapper.remove()
2402    }
2403
2404    if (props === undefined) {
2405        modal.setTitle('Coming soon...');
2406    }
2407
2408    if ( typeof props === 'object') {
2409        if (props.hasOwnProperty('mainclass')) modal.jq_wrapper.addClass(props.mainclass)
2410        if (props.hasOwnProperty('title')) modal.setTitle(props.title)
2411        if (props.hasOwnProperty('body')) modal.setBody(`<h3>${props.body}</h3>`)
2412        if (props.hasOwnProperty('footer')) modal.setFooter(props.footer)
2413        if (props.hasOwnProperty('bodyonly')) modal.setBody(props.bodyonly)
2414        if (props.hasOwnProperty('button')) modal.addButton(props.button, function () {
2415            if ( props.hasOwnProperty('action') ) {
2416                if (typeof props.action === 'function') {
2417                    props.action(()=>closeAlert())
2418                } else {
2419                    closeAlert()
2420                }
2421            }
2422        }, ( props.hasOwnProperty('checkButton')) ? 'the-phone-relation' : '' );
2423        if (props.hasOwnProperty('close_event')) {
2424            modal.setCloseEvent(props.close_event)
2425        } else {
2426            modal.setCloseEvent(closeAlert)
2427        }
2428    } else {
2429        modal.setTitle(props)
2430        modal.addButton('OK', closeAlert)
2431        modal.setCloseEvent(closeAlert)
2432    }
2433    modal.setBodyCSS({'text-align': 'center'})
2434
2435    modal.show();
2436}
2437
2438function showErrorMessage (text, elem, showtime) {
2439    showtime = (showtime === undefined) ? 3000:showtime;
2440
2441    let p = $(`<p class="error" style="text-align: center">${text}</p>`);
2442    $(elem).after(p);
2443    setTimeout(function () {
2444        p.fadeOut(function () {
2445            p.remove();
2446        });
2447    },showtime)
2448}
2449
2450function textToClipboard(text) {
2451    text = text.trim()
2452
2453    try {
2454        navigator.clipboard.writeText(text)
2455        console.log('Navigator in')
2456    } catch {
2457        const textarea = document.createElement("textarea")
2458        textarea.value = text
2459        document.body.appendChild(textarea)
2460        textarea.select()
2461        try {
2462            document.execCommand("copy")
2463        } finally {
2464            document.body.removeChild(textarea)
2465        }
2466    }
2467}
2468
2469

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.