PageSourceSearch

https://gliver.ru/local/templates/gliver_new/js/app.js?178393903838262

js gliver.ru collected 2026-10-07 08:16:15 UTC 38,262 bytes, 967 lines download raw bytes

1$(document).ready(function () {
2    $.fancybox.defaults.hash = false;
3    $("[data-fancybox]").fancybox({
4        afterShow: function( instance, current ) {
5            console.log('current', current)
6            if (current.origSrc.match(/^.+vk.com\/clip/)) {
7                // Добавляем класс, если это клип
8                console.log('instance.$refs', instance.$refs)
9                instance.$refs.container.addClass('vk-clip');
10            }
11        }
12    });
13    $.fn.select2.amd.define('select2/i18n/ru',[],function () {
14        // Russian
15        return {
16            errorLoading: function () {
17                return 'Результат не может быть загружен.';
18            },
19            inputTooLong: function (args) {
20                var overChars = args.input.length - args.maximum;
21                var message = 'Пожалуйста, удалите ' + overChars + ' символ';
22                if (overChars >= 2 && overChars <= 4) {
23                    message += 'а';
24                } else if (overChars >= 5) {
25                    message += 'ов';
26                }
27                return message;
28            },
29            inputTooShort: function (args) {
30                var remainingChars = args.minimum - args.input.length;
31
32                var message = 'Пожалуйста, введите ' + remainingChars + ' или более символов';
33
34                return message;
35            },
36            loadingMore: function () {
37                return 'Загружаем ещё ресурсы…';
38            },
39            maximumSelected: function (args) {
40                var message = 'Вы можете выбрать ' + args.maximum + ' элемент';
41
42                if (args.maximum  >= 2 && args.maximum <= 4) {
43                    message += 'а';
44                } else if (args.maximum >= 5) {
45                    message += 'ов';
46                }
47
48                return message;
49            },
50            noResults: function () {
51                return 'Ничего не найдено';
52            },
53            searching: function () {
54                return 'Поиск…';
55            }
56        };
57    });
58    // UTM метки в куки
59    let searchParams = new URLSearchParams(window.location.search)
60    let expires = 60 * 60 * 24 * 14 // 14 дней
61    if (searchParams.has('utm_source')) {
62        BX.setCookie('utm_source', searchParams.get('utm_source'), {path: '/', expires: expires});
63    }
64    if (searchParams.has('utm_medium')) {
65        BX.setCookie('utm_medium', searchParams.get('utm_medium'), {path: '/', expires: expires});
66    }
67    if (searchParams.has('utm_campaign')) {
68        BX.setCookie('utm_campaign', searchParams.get('utm_campaign'), {path: '/', expires: expires});
69    }
70    if (searchParams.has('utm_term')) {
71        BX.setCookie('utm_term', searchParams.get('utm_term'), {path: '/', expires: expires});
72    }
73    if (searchParams.has('utm_content')) {
74        BX.setCookie('utm_content', searchParams.get('utm_content'), {path: '/', expires: expires});
75    }
76    if (searchParams.has('yclid')) {
77        BX.setCookie('yclid', searchParams.get('yclid'), {path: '/', expires: expires});
78    }
79
80    phoneMask();
81
82    $('input[type="tel"]').not('.js-phone-filter').focusout(function (e) {
83        let phone = $(this).val().replace(/[^.\d]+/g, "").substring(1);
84
85        if (phone === '' || phone.length < 10) {
86            $(this).addClass('error');
87            if (!$(this).next().hasClass('error-phone')) {
88                $(this).after('<span class="error-phone">Введите номер телефона полностью</span>');
89            }
90        } else {
91            $(this).removeClass('error');
92            if ($(this).next().hasClass('error-phone')) {
93                $(this).next().remove();
94            }
95        }
96    });
97
98    $(document).on('click', '.js-callback', function () {
99        let title = $(this).attr('data-title'),
100            type = $(this).attr('data-type'),
101            url = $(this).attr('data-url') ? $(this).attr('data-url') : location.href.split("?")[0];
102
103        if (!title) {
104            title = 'Заказать звонок';
105        }
106
107        $('#modalCallback .modal-title').text(title);
108        $('#modalCallback input[name="one_click"]').val('N');
109        $('#modalCallback input[name="form_name"]').val('Форма "' + title + '"');
110        if (type) {
111            $('#modalCallback input[name="type"]').val(type);
112        }
113        $('#modalCallback input[name="url"]').val(url);
114
115        $('.modal').modal('hide');
116        $('#modalCallback').modal();
117    });
118    
119    $(document).on('click', '#modalCallback .modal-close, #modalCallback .modal-overlay', function() {
120        $('#recaptcha-message').hide();
121        $('#recaptcha-v2-container').hide();
122    });
123
124    function ensureLegacyCallbackHidden($form, name, value, force) {
125        let $field = $form.find('input[name="' + name + '"]');
126
127        if (!$field.length) {
128            $field = $('<input>', {
129                type: 'hidden',
130                name: name
131            }).appendTo($form);
132        }
133
134        if (force || !$field.val()) {
135            $field.val(value);
136        }
137
138        return $field;
139    }
140
141    function ensureLegacyCallbackRecaptcha($form) {
142        if (!$form.find('.recaptcha-message').length) {
143            $('<div class="recaptcha-message" style="display:none;"><span class="recaptcha-message-text"></span></div>').appendTo($form);
144        }
145
146        if (!$form.find('.recaptcha-v2-container').length) {
147            $('<div class="recaptcha-v2-container" style="display:none;"><div class="recaptcha-v2"></div></div>').appendTo($form);
148        }
149    }
150
151    $(document).on('submit', '.oneClickCallback', function (e) {
152        let $form = $(this);
153
154        if ($form.hasClass('js-call-me')) {
155            return;
156        }
157
158        e.preventDefault();
159        e.stopImmediatePropagation();
160
161        let formType = $.trim($form.find('input[name="formtype"]').val() || ''),
162            formName = formType || 'Форма "Мне нужна консультация"';
163
164        ensureLegacyCallbackHidden($form, 'confirm', 'N', false);
165        ensureLegacyCallbackHidden($form, 'one_click', 'N', true);
166        ensureLegacyCallbackHidden($form, 'form_name', formName, true);
167        ensureLegacyCallbackHidden($form, 'type', 'form_call_ordering', false);
168        ensureLegacyCallbackHidden($form, 'url', location.href.split("?")[0], true);
169        ensureLegacyCallbackRecaptcha($form);
170
171        $form.addClass('js-call-me');
172        $form.trigger('submit');
173    });
174
175    // Отправка формы "Позвоните мне"
176    /*$(document).on('submit', '.js-call-me', function (e) {
177        e.preventDefault();
178
179        $('.js-call-me').removeClass('active');
180        let data = $(this).serializeArray().reduce(function (obj, item) {
181            obj[item.name] = item.value;
182            return obj;
183        }, {});
184
185        if (data['confirm'] === 'Y') {
186            let _this = $(this),
187                $button = _this.find('button[type="submit"], input[type="submit"]').first()
188
189            $button.btnLoading();
190            data['url'] = data['url'] ? data['url'] : location.href.split("?")[0];
191            data['title'] = document.title;
192
193            fbq('track', 'Lead');
194            VK.Goal('lead');
195
196            BX.ajax.runComponentAction('8up:catalog.element', 'sendCallbackForm', {
197                mode: 'class',
198                data: {data: data}
199            }).then(function (response) {
200                $button.btnLoading('hide');
201                $('.js-form-confirm').find('button[type="submit"]').btnLoading('hide');
202                if (response.data.ERROR) {
203                    alert('Возникла ошибка, попробуйте позже.');
204                } else {
205                    $('.modal').modal('hide');
206                    modalMessage('Спасибо, ваша заявка успешно отправлена. Менеджер свяжется с вами в ближайшее время.');
207                    _this.find("input[name='name']").val('');
208                    _this.find("input[name='phone']").val('');
209                    _this.find("input[name='confirm']").val('N');
210                    if (data['type']) {
211                        dataLayer.push({'event': data['type']});
212                    }
213                    if(window.location.pathname === '/mebel/divany/na-zakaz/') {
214                        dataLayer.push({'event': 'order_by_one_click'});
215                    }
216                    dataLayer.push({'event': 'form_call_ordering'});
217                }
218            });
219        } else {
220            $('#modalFormConfirm input[type="tel"]').val(data['phone']);
221            $('.modal').modal('hide');
222            $(this).addClass('active');
223            $('#modalFormConfirm').attr('data-form', '.js-call-me').modal();
224        }
225    });*/
226
227    // Отправка формы "Позвоните мне" (Версия каптчей)
228    $(document).on('submit', '.js-call-me', function (e) {
229        e.preventDefault();
230
231        $('.js-call-me').removeClass('active');
232        let data = $(this).serializeArray().reduce(function (obj, item) {
233            obj[item.name] = item.value;
234            return obj;
235        }, {});
236
237        if (data['confirm'] === 'Y') {
238            let _this = $(this),
239                $button = _this.find('button[type="submit"], input[type="submit"]').first()
240
241            $button.btnLoading();
242            data['url'] = data['url'] ? data['url'] : location.href.split("?")[0];
243            data['title'] = document.title;
244
245            window.GliverRecaptcha.execute('callback_form').then(function(token) {
246                    data['recaptcha_token'] = token;
247                    data['recaptcha_version'] = 'v3';
248
249                    fbq('track', 'Lead');
250                    VK.Goal('lead');
251
252                    BX.ajax.runComponentAction('8up:catalog.element', 'sendCallbackForm', {
253                        mode: 'class',
254                        data: {data: data}
255                    }).then(function (response) {
256                        $button.btnLoading('hide');
257                        $('.js-form-confirm').find('button[type="submit"]').btnLoading('hide');
258
259                        if (response.data.ERROR) {
260                            alert('Возникла ошибка, попробуйте позже.');
261                        } else if (response.data.SUSPICIOUS) {
262                            var isModalForm = _this.closest('#modalCallback').length > 0;
263                            var $messageContainer, $recaptchaContainer, recaptchaElementId;
264                            
265                            if (isModalForm) {
266                                $('#modalFormConfirm').modal('hide');
267                                $('#modalCallback').modal();
268                                recaptchaElementId = 'recaptcha-v2';
269                            }
270
271                            $messageContainer = _this.find('.recaptcha-message');
272                            $recaptchaContainer = _this.find('.recaptcha-v2-container');
273                            recaptchaElementId = _this.find('.recaptcha-v2')[0];
274
275                            $messageContainer.show();
276                            if (response.data.message) {
277                                $messageContainer.find('.recaptcha-message-text').text(response.data.message);
278                            }
279                            
280                            $recaptchaContainer.show();
281                            
282                            var formKey = _this.attr('id') || 'form_' + Math.random().toString(36).substr(2, 9);
283                            if (!window.recaptchaV2Rendered) {
284                                window.recaptchaV2Rendered = {};
285                            }
286                            
287                            if (!window.recaptchaV2Rendered[formKey]) {
288                                window.GliverRecaptcha.renderV2(recaptchaElementId, {
289                                    'sitekey': window.RECAPTCHA_V2_SITE_KEY,
290                                    'callback': function(v2Token) {
291                                        data['recaptcha_token'] = v2Token;
292                                        data['recaptcha_version'] = 'v2';
293                                        
294                                        $button.btnLoading();
295                                        BX.ajax.runComponentAction('8up:catalog.element', 'sendCallbackForm', {
296                                            mode: 'class',
297                                            data: {data: data}
298                                        }).then(function (response) {
299                                            $button.btnLoading('hide');
300                                            $recaptchaContainer.hide();
301                                            $messageContainer.hide();
302                                            window.GliverRecaptcha.reset(window.recaptchaV2Rendered[formKey]);
303                                            
304                                            if (response.data.ERROR) {
305                                                alert('Возникла ошибка, попробуйте позже.');
306                                            } else {
307                                                if (isModalForm) {
308                                                    $('.modal').modal('hide');
309                                                }
310                                                modalMessage('Спасибо, ваша заявка успешно отправлена. Менеджер свяжется с вами в ближайшее время.');
311                                                _this.find("input[name='name']").val('');
312                                                _this.find("input[name='phone']").val('');
313                                                _this.find("input[name='confirm']").val('N');
314                                                if (data['type']) {
315                                                    dataLayer.push({'event': data['type']});
316                                                }
317                                                if(window.location.pathname === '/mebel/divany/na-zakaz/') {
318                                                    dataLayer.push({'event': 'order_by_one_click'});
319                                                }
320                                                dataLayer.push({'event': 'form_call_ordering'});
321                                            }
322                                        });
323                                    }
324                                }).then(function(widgetId) {
325                                    window.recaptchaV2Rendered[formKey] = widgetId;
326                                }).catch(function() {
327                                    $button.btnLoading('hide');
328                                    alert('Не удалось загрузить reCAPTCHA. Попробуйте позже.');
329                                });
330                            }
331                        } else {
332                            $('.modal').modal('hide');
333                            modalMessage('Спасибо, ваша заявка успешно отправлена. Менеджер свяжется с вами в ближайшее время.');
334                            _this.find("input[name='name']").val('');
335                            _this.find("input[name='phone']").val('');
336                            _this.find("input[name='confirm']").val('N');
337                            if (data['type']) {
338                                dataLayer.push({'event': data['type']});
339                            }
340                            if(window.location.pathname === '/mebel/divany/na-zakaz/') {
341                                dataLayer.push({'event': 'order_by_one_click'});
342                            }
343                            dataLayer.push({'event': 'form_call_ordering'});
344                        }
345                    });
346            }).catch(function() {
347                $button.btnLoading('hide');
348                alert('Не удалось загрузить reCAPTCHA. Попробуйте позже.');
349            });
350        } else {
351            $('#modalFormConfirm input[type="tel"]').val(data['phone']);
352            $('.modal').modal('hide');
353            $(this).addClass('active');
354            $('#modalFormConfirm').attr('data-form', '.js-call-me').modal();
355        }
356    });
357
358    // Форма подтверждения телефона
359    $(document).on('submit', '.js-form-confirm', function (e) {
360        e.preventDefault();
361
362        if ($(this).find('.error-phone').length > 0) {
363            return false;
364        }
365
366        let phone = $(this).find('input[type="tel"]').val(),
367            $form = $($('#modalFormConfirm').attr('data-form') + '.active');
368
369        if (phone && $form.length > 0) {
370            $(this).find('button[type="submit"]').btnLoading();
371            $form.find('input[type="tel"]').val(phone);
372            $form.find('input[name="confirm"]').val('Y');
373            $form.submit();
374        }
375    });
376
377    // Каунтер
378    $(document).on('click', '.counter__button[data-quantity="plus"]', function (e) {
379        e.preventDefault();
380        let $input = $(this).closest('.counter').find('input'),
381            currentVal = parseInt($input.val());
382
383        if (!isNaN(currentVal)) {
384            $input.val(currentVal + 1);
385        } else {
386            $input.val(1);
387        }
388    });
389    $(document).on('click', '.counter__button[data-quantity="minus"]', function (e) {
390        e.preventDefault();
391        let $input = $(this).closest('.counter').find('input'),
392            currentVal = parseInt($input.val());
393
394        if (!isNaN(currentVal) && currentVal > 1) {
395            $input.val(currentVal - 1);
396        } else {
397            $input.val(1);
398        }
399    });
400    $(document).on('change', '.counter__input input', function (e) {
401        let value = $(this).val();
402        if(!value || value <= 0) {
403            $(this).val(1);
404        }
405    });
406
407    tippy(".js-mail-copy", {
408        trigger: "click",
409        theme: 'copy',
410        hideOnClick: false,
411        onShow(instance) {
412            setTimeout(() => {
413                instance.hide();
414            }, 2000);
415        }
416    });
417
418    $(document).on('click', '.js-mail-copy', function (e) {
419        let $temp = $("<input>"),
420            mail = $(this).closest('.gl-menu-contact-item-wrapper').find('.mailto-link').text();
421
422        $("body").append($temp);
423        $temp.val(mail).select();
424        document.execCommand("copy");
425        $temp.remove();
426        dataLayer.push({'event': 'copy_mail'});
427    });
428
429    $(window).scroll(function () {
430        setHeaderScrolled();
431    });
432    setHeaderScrolled();
433
434    function setHeaderScrolled() {
435        if ($(window).scrollTop() > 50) {
436            $('.header').addClass("scrolled");
437        } else {
438            $('.header').removeClass("scrolled");
439        }
440    }
441    
442    $(document).on('click', '.js-whatsapp-widget', function (e) {
443        const phone = $(this).data('phone');
444        const dt = new Date();
445        const padL = (nr, len = 2, chr = `0`) => `${nr}`.padStart(2, chr);
446        const dateString = `${
447            padL(dt.getDate())}.${
448            padL(dt.getMonth()+1)}.${
449            dt.getFullYear()} ${
450            padL(dt.getHours())}:${
451            padL(dt.getMinutes())}:${
452            padL(dt.getSeconds())}`;
453        
454        if (typeof window.ym === 'function') {
455            window.ym(32989379, 'reachGoal', `wa_${phone}`, {date_time: dateString});
456        }
457    });
458});
459
460$.fn.modal = function (param) {
461    let currentModal = $(this),
462        currentModalOverlay = currentModal.find(".modal-overlay");
463
464    if (param === 'hide') {
465        $(this).removeClass("is-visible");
466        $("body").removeClass("overflow-hidden");
467    } else {
468        let movedToBody = false;
469        if (currentModal.length && currentModal.hasClass("modal-web-form") && !currentModal.parent().is("body")) {
470            currentModal.appendTo("body");
471            movedToBody = true;
472        }
473        if (movedToBody) {
474            currentModal[0].offsetHeight;
475        }
476        const zIndex = Math.max.apply(Math, $('.modal.is-visible').map(function () {
477            return $(this).css("z-index");
478        }).get());
479        $(this).css('z-index', zIndex + 1);
480        if (param !== 'above') {
481            //$(".modal").not(this).removeClass("is-visible");
482        }
483        currentModal.addClass("is-visible");
484    
485        if (window.matchMedia("(max-width:991.98px)").matches) {
486            if (!currentModalOverlay.hasClass("desktop-only")) {
487                $("body").addClass("overflow-hidden");
488            }
489        } else if (window.matchMedia("(min-width:992px)").matches) {
490            if (!currentModalOverlay.hasClass("modal-overlay-mobile")) {
491                $("body").addClass("overflow-hidden");
492            }
493        }
494    }
495};
496
497File.prototype.convertToBase64 = function (callback) {
498    let reader = new FileReader();
499
500    reader.onloadend = function (e) {
501        callback(e.target.result, e.target.error);
502    };
503    reader.readAsDataURL(this);
504};
505
506const modalAlert = (message, title = 'Внимание') => {
507    $('#modalAlert .modal-title').text(title);
508    $('#modalAlert .modal-body').html(message);
509    $('#modalAlert').modal();
510}
511
512const modalMessage = (text) => {
513    $('#modalMessage .modal-body').html(text);
514    $('#modalMessage').modal();
515}
516
517const modalDebug = (text) => {
518    $('.modal').modal('hide');
519    $('#modalDebug .modal-body').html(text);
520    $('#modalDebug').modal();
521}
522
523const uploadFile = (file, inputImage = 'arFields[IMAGE]', inputPdf = 'arFields[PDF]', inputDoc = 'arFields[DOC]') => {
524    let filename = file.name,
525        idxDot = filename.lastIndexOf(".") + 1,
526        extFile = filename.substr(idxDot, filename.length).toLowerCase();
527    
528    const $btn = $(".upload-image").closest('form').find('.btn[type="submit"]');
529    if ($btn.length) {
530        $btn.btnLoading();
531    }
532    
533    if (extFile === "jpg" || extFile === "jpeg" || extFile === "png" || extFile === "gif") {
534        file.convertToBase64(function (base64) {
535            BX.ajax.runComponentAction('8up:calc.basket', 'saveImage', {
536                mode: 'class',
537                data: {base64: base64},
538            }).then(function (response) {
539                if (response.data.ERROR) {
540                    alert(response.data.ERROR);
541                } else {
542                    $(".upload-image").append(`<div class="upload-image__item">
543                                                    <img src="` + base64 + `" height="100%">
544                                                    <div class="upload-image__remove js-remove-image">×</div>
545                                                    <input class="js-image" t
545ype="hidden" name="` + inputImage + `[]" value="` + response.data.URL + `">
546                                                    <input class="js-image" type="hidden" name="arFields[IMAGE_NAME][]" value="` + filename + `">
547                                                </div>`);
548                }
549                if ($btn.length) {
550                    $btn.btnLoading('hide');
551                }
552            });
553        })
554
555    } else if (extFile === "pdf") {
556        let formData = new FormData();
557
558        formData.append('file', file);
559
560        BX.ajax.runComponentAction('8up:calc.custom.item', 'savePdf', {
561            mode: 'class',
562            data: formData,
563        }).then(function (response) {
564            if (response.data.ERROR) {
565                alert(response.data.ERROR);
566            } else {
567                $(".upload-image").append(`<div class="upload-image__item">
568                                                            <div class="bx-file-icon-container-medium icon-pdf">
569                                                                <div class="bx-file-icon-cover">
570                                                                    <div class="bx-file-icon-corner">
571                                                                        <div class="bx-file-icon-corner-fix"></div>
572                                                                    </div>
573                                                                    <div class="bx-file-icon-images"></div>
574                                                                </div>
575                                                                <div class="bx-file-icon-label"></div>
576                                                            </div>
577                                                            <div class="upload-image__title">` + filename + `</div>
578                                                            <div class="upload-image__remove js-remove-image">×</div>
579                                                            <input class="js-pdf" type="hidden" name="` + inputPdf + `[]" value="` + response.data.URL + `">
580                                                        </div>`);
581            }
582            if ($btn.length) {
583                $btn.btnLoading('hide');
584            }
585        });
586
587    } else if (extFile === "doc" || extFile === "docx") {
588        let formData = new FormData();
589    
590        formData.append('file', file);
591    
592        BX.ajax.runComponentAction('8up:calc.custom.item', 'saveDoc', {
593            mode: 'class',
594            data: formData,
595        }).then(function (response) {
596            if (response.data.ERROR) {
597                alert(response.data.ERROR);
598            } else {
599                $(".upload-image").append(`<div class="upload-image__item">
600                                                            <div class="bx-file-icon-container-medium icon-doc">
601                                                                <div class="bx-file-icon-cover">
602                                                                    <div class="bx-file-icon-corner">
603                                                                        <div class="bx-file-icon-corner-fix"></div>
604                                                                    </div>
605                                                                    <div class="bx-file-icon-images"></div>
606                                                                </div>
607                                                                <div class="bx-file-icon-label"></div>
608                                                            </div>
609                                                            <div class="upload-image__title">` + filename + `</div>
610                                                            <div class="upload-image__remove js-remove-image">×</div>
611                                                            <input class="js-pdf" type="hidden" name="` + inputDoc + `[]" value="` + response.data.URL + `">
612                                                        </div>`);
613            }
614            if ($btn.length) {
615                $btn.btnLoading('hide');
616            }
617        });
618    }
619    else {
620        alert('Неверный формат файла (Поддерживаемые форматы: jpg, jpeg, png, gif, pdf)');
621    }
622}
623
624const saveImage = (base64) => {
625    return BX.ajax.runComponentAction('dev:calc.basket', 'saveImage', {
626        mode: 'class',
627        data: {base64: base64},
628    }).then(function (response) {
629        if (response.data.ERROR) {
630            alert(response.data.ERROR);
631        } else {
632            return response.data;
633        }
634    });
635}
636
637const phoneMask = () => {
638    $('input[type="tel"]').inputmask({
639        mask: "+7 (9h9) 999-9999",
640        definitions: {
641            "h": {
642                validator: function (chrs, maskset, pos) {
643                    if (chrs === "9" && (maskset.buffer[4] === "8" || maskset.buffer[4] === "7")) {
644                        return {
645                            remove: [pos, pos - 1],
646                            insert: {
647                                pos: pos - 1,
648                                c: chrs
649                            },
650                            caret: pos
651                        }
652                    }
653                    if (chrs === "8" && (maskset.buffer[4] === "8" || maskset.buffer[4] === "7")) {
654                        return {
655                            remove: [pos, pos - 1],
656                            insert: {
657                                pos: pos - 1,
658                                c: chrs
659                            },
660                            caret: pos
661                        }
662                    }
663
664                    return {
665                        c: chrs
666                    }
667                }
668            }
669        },
670    });
671}
672
673$.fn.btnLoading = function (param) {
674    if (param === 'hide') {
675        $(this)
676            .removeClass('btn--loading')
677            .find(".icon-loading").remove();
678    } else {
679        $(this)
680            .addClass('btn--loading')
681            .append(`<div class="icon-loading"></div>`)
682    }
683};
684
685const confirmDialog = (title, message, yesCallback, noCallback, force = false) => {
686    if(force) {
687        yesCallback();
688    }
689    else {
690        $('#modalConfirm .modal-title').html(title);
691        $('#modalConfirm .modal-form__text-block span').html(message);
692        $('#modalConfirm').modal();
693
694        $('#modalConfirm .js-confirm-yes').off('click').click(async function (e) {
695            e.preventDefault();
696            if (typeof yesCallback === 'function') {
697                await yesCallback();
698            }
699            $('#modalConfirm').modal('hide');
700        });
701        $('#modalConfirm .js-confirm-no').off('click').click(async function () {
702            if (typeof noCallback === 'function') {
703                await noCallback();
704            }
705            $('#modalConfirm').modal('hide');
706        });
707    }
708}
709
710const html2PngWait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
711
712const html2PngElement = (query) => {
713    if (query instanceof Element) {
714        return query;
715    }
716
717    if (query && query.jquery) {
718        return query[0];
719    }
720
721    if (typeof query === 'string') {
722        return document.querySelector(query);
723    }
724
725    return null;
726};
727
728const html2PngWaitImages = async (element, timeout = 5000) => {
729    const images = Array.from(element.querySelectorAll('img'));
730
731    if (!images.length) {
732        return;
733    }
734
735    await Promise.all(images.map(img => new Promise(resolve => {
736        img.setAttribute('loading', 'eager');
737
738        if (!img.getAttribute('src') && img.dataset && img.dataset.src) {
739            img.setAttribute('src', img.dataset.src);
740        }
741
742        if (img.complete && img.naturalWidth > 0) {
743            resolve();
744            return;
745        }
746
747        const done = () => {
748            clearTimeout(timer);
749            img.removeEventListener('load', done);
750            img.removeEventListener('error', done);
751            resolve();
752        };
753
754        const timer = setTimeout(done, timeout);
755        img.addEventListener('load', done, {once: true});
756        img.addEventListener('error', done, {once: true});
757    })));
758};
759
760const html2PngBlobToDataUrl = (blob) => new Promise((resolve, reject) => {
761    const reader = new FileReader();
762    reader.onloadend = () => resolve(reader.result);
763    reader.onerror = reject;
764    reader.readAsDataURL(blob);
765});
766
767const html2PngInlineImages = async (element) => {
768    const images = Array.from(element.querySelectorAll('img'));
769
770    if (!images.length) {
771        return;
772    }
773
774    await Promise.all(images.map(async img => {
775        const src = img.currentSrc || img.getAttribute('src');
776
777        if (!src || src.indexOf('data:') === 0 || src.indexOf('blob:') === 0) {
778            return;
779        }
780
781        try {
782            const response = await fetch(new URL(src, window.location.href).href, {
783                cache: 'force-cache',
784                credentials: 'same-origin'
785            });
786
787            if (!response.ok) {
788                throw new Error('Не удалось загрузить изображение: ' + response.status);
789            }
790
791            img.setAttribute('src', await html2PngBlobToDataUrl(await response.blob()));
792        } catch (error) {
793            console.warn('Не удалось встроить изображение перед сохранением:', {
794                src: src,
795                error: error
796            });
797        }
798    }));
799};
800
801const html2PngHasLargeImage = (element) => {
802    return Array.from(element.querySelectorAll('img')).some(img => {
803        const width = img.naturalWidth || img.width || img.offsetWidth;
804        const height = img.naturalHeight || img.height || img.offsetHeight;
805
806        return width >= 100 && height >= 100;
807    });
808};
809
810const html2PngIsBlank = async (dataUrl, options = {}) => {
811    const img = new Image();
812
813    await new Promise((resolve, reject) => {
814        img.onload = resolve;
815        img.onerror = reject;
816        img.src = dataUrl;
817    });
818
819    const canvas = document.createElement('canvas');
820    const width = img.naturalWidth || img.width;
821    const height = img.naturalHeight || img.height;
822
823    if (!width || !height) {
824        return true;
825    }
826
827    canvas.width = width;
828    canvas.height = height;
829
830    const context = canvas.getContext('2d', {willReadFrequently: true});
831    context.drawImage(img, 0, 0);
832
833    const pixels = context.getImageData(0, 0, width, height).data;
834    const step = Math.max(4, Math.floor(Math.sqrt((width * height) / 2500)) * 4);
835    let checked = 0;
836    let visible = 0;
837    const minVisibleRatio = options.hasLargeImage ? 0.01 : 0.001;
838
839    for (let i = 0; i < pixels.length; i += step) {
840        const alpha = pixels[i + 3];
841        const red = pixels[i];
842        const green = pixels[i + 1];
843        const blue = pixels[i + 2];
844
845        checked++;
846
847        if (alpha > 8 && (red < 245 || green < 245 || blue < 245)) {
848            visible++;
849        }
850    }
851
852    return checked === 0 || visible / checked < minVisibleRatio;
853};
854
855const html2Png = async (query, isDownload = false) => {
856    const source = html2PngElement(query);
857
858    if (!source) {
859        console.error('Ошибка при сохранении изображения: элемент не найден', query);
860        return;
861    }
862
863    const attempts = 3;
864    let lastError = null;
865
866    for (let attempt = 1; attempt <= attempts; attempt++) {
867        const $wrapper = $('<div class="js-draw-saving" style="position: absolute; left: -10000px; top: 0;"></div>');
868        const $clone = $(source).clone();
869        $('body').append($wrapper);
870        $wrapper.append($clone);
871
872        const content = $wrapper.children()[0];
873
874        try {
875            if (document.fonts && document.fonts.ready) {
876                await document.fonts.ready;
877            }
878
879            await html2PngWaitImages(content);
880            await html2PngInlineImages(content);
881            await html2PngWaitImages(content);
882
883            if (content.offsetWidth === 0 || content.offsetHeight === 0) {
884                throw new Error('Размер области для сохранения равен 0');
885            }
886
887            const dataUrl = await domtoimage.toPng(content);
888
889            if (await html2PngIsBlank(dataUrl, {hasLargeImage: html2PngHasLargeImage(content)})) {
890                throw new Error('Получилось пустое изображение');
891            }
892
893            if (isDownload) {
894                const link = document.createElement('a');
895                link.download = "export_" + Date.now() + ".png";
896                link.href = dataUrl;
897                link.click();
898            }
899
900            return dataUrl;
901        } catch (error) {
902            lastError = error;
903            console.warn('Повтор сохранения изображения:', {
904                query: typeof query === 'string' ? query : source.className,
905                attempt: attempt,
906                error: error
907            });
908            await html2PngWait(200 * attempt);
909        } finally {
910            $wrapper.remove();
911        }
912    }
913
914    console.error('Ошибка при сохранении изображения:', lastError);
915    
916    /*return await html2canvas(document.querySelector('.js-draw-saving ' + query), {
917        scrollX: 0,
918        scrollY: -window.scrollY,
919        letterRendering: 1,
920        allowTaint: true,
921        useCORS: true
922    }).then(canvas => {
923        $('.js-draw-saving').remove();
924        let dataUrl = canvas.toDataURL("image/png");
925
926        if(isDownload) {
927            const a = document.createElement("a");
928            document.body.appendChild(a);
929            a.style = "display: none";
930            a.href = dataUrl;
931            a.download = "export_" + Date.now() + ".png";
932            a.click();
933            document.body.removeChild(a);
934        }
935
936        return dataUrl;
937    });*/
938
939    /*var node = document.querySelector(query);
940    var canvas = document.createElement("canvas");
941    canvas.height = node.offsetHeight + 30;
942    canvas.width = node.offsetWidth + 30;
943
944    console.log(node.outerHTML)
945
946    return await rasterizeHTML.drawHTML(node.outerHTML, canvas)
947        .then(function (renderResult) {
948            if (navigator.msSaveBlob) {
949                window.navigator.msSaveBlob(canvas.msToBlob(), name);
950            } else {
951                let dataUrl = canvas.toDataURL();
952                console.log(dataUrl)
953                if(isDownload) {
954                    const a = document.createElement("a");
955                    document.body.appendChild(a);
956                    a.style = "display: none";
957                    a.href = dataUrl;
958                    a.download = "export_" + Date.now() + ".png";
959                    a.click();
960                    document.body.removeChild(a);
961                }
962                return dataUrl;
963            }
964        });*/
965};
966
967var lazyLoadInstance = new LazyLoad({});

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.