PageSourceSearch

https://app.appec.it/files/js/normalize.js?v=2.8.7

js appec.it collected 2026-10-02 01:04:40 UTC 310,247 bytes, 7,464 lines download raw bytes

1
2
3// TRANSLATE
4let translate = {};
5let language = {};
6let otherTranslate = {};
7let publicConsts = {};
8let stateNames = {};
9let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches && ($('html[data-darkmode="LIGHT"]').length <= 0) || ($('html[data-darkmode="DARK"]').length > 0);
10window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
11    isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches && ($('html[data-darkmode="LIGHT"]').length <= 0) || ($('html[data-darkmode="DARK"]').length > 0);
12    setAppDarkMode();
13    changeTheme($('html').data('color'));
14});
15function toFloatInput(price) {
16    return numberFormat(price.toString().replace(',', '').replace('.', ','), 2, ',', '.');
17}
18function numberFormat(price, decimalPlaces, decimalSeparator, thousandsSeparator) {
19    let value = parseFloat(price.replace('.', '').replace(',', '.'));
20    if (isNaN(value)) {
21        return "0,00";
22    }
23    let negativ = false;
24    if (value < 0) {
25        negativ = true;
26        value *= -1;
27    }
28    let formatted = value.toFixed(decimalPlaces);
29    let number = Math.floor(formatted);
30    let decimal = Math.floor((formatted - number).toFixed(decimalPlaces) * Math.pow(10, decimalPlaces));
31    decimal = decimal.toString();
32    while (decimal.toString().length < decimalPlaces) {
33        decimal = '0' + decimal;
34    }
35    number = number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator);
36    number = number + decimalSeparator + decimal.toString().padEnd(decimalPlaces, "0");
37    return (negativ ? '-' : '') + number;
38}
39$.scrollLock = (function scrollLockSimple() {
40    var locked = false;
41    var $body;
42    var previous;
43
44    function lock() {
45        if (!$body)
46            $body = $('body');
47        previous = $body.css('overflow');
48        $body.css('overflow', 'hidden');
49        locked = true;
50    }
51
52    function unlock() {
53        $body.css('overflow', 'visible');
54        locked = false;
55    }
56    return function scrollLock(on) {
57        if (arguments.length) {
58            if (on)
59                lock();
60            else
61                unlock();
62        } else {
63            if (locked)
64                unlock();
65            else
66                lock();
67        }
68    };
69}());
70
71let vh = window.innerHeight * 0.01;
72document.documentElement.style.setProperty('--vh', `${vh}px`);
73window.addEventListener('resize', () => {
74    let vh = window.innerHeight * 0.01;
75    document.documentElement.style.setProperty('--vh', `${vh}px`);
76});
77
78function setAppDarkMode() {
79    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
80        var messageObj = { function: 'darkmode', isDarkMode: isDarkMode };
81        var stringifiedMessageObj = JSON.stringify(messageObj);
82        webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
83    }
84}
85function getTranslate() {
86    return new Promise((resolve, reject) => {
87        let lang = $('html').data('my-lang');
88        if (lang == 'bz')
89            lang = 'de';
90        $.getJSON("translate/" + lang + ".json?t=" + new Date().getTime(), function (data) {
91            language = $('html').data('my-lang');
92            translate = data;
93            loadOtherLangs(lang);
94            resolve(true);
95        }).fail(function (e) {
96            console.log("error translate");
97            resolve(false);
98        });
99    });
100
101}
102// translate wird erst im window.load-Handler geladen - Klicks davor (oder nach
103// fehlgeschlagenem Laden) liefen auf ein leeres translate-Objekt.
104async function ensureTranslate() {
105    if (Object.keys(translate).length === 0) {
106        await getTranslate();
107    }
108}
109function getPublicConsts() {
110    return new Promise((resolve, reject) => {
111        $.getJSON("getpublicconsts", function (data) {
112            publicConsts = data;
113            resolve(true);
114        }).fail(function (e) {
115            console.log("error publicconsts");
116            resolve(false);
117        });
118    });
119}
120function getStateNames() {
121    return new Promise((resolve, reject) => {
122        $.getJSON("getstatenames", function (data) {
123            stateNames = data;
124            resolve(true);
125        }).fail(function (e) {
126            console.log("error statenames");
127            resolve(false);
128        });
129    });
130
131}
132function getStateTranslate(state) {
133    if (stateNames[state] !== undefined) {
134        return stateNames[state];
135    }
136    if (translate[state] !== undefined) {
137        return translate[state];
138    }
139    // z. B. STATE_TEST_1_OK: nicht in stateNames, nur unter translate['STATES']
140    if (translate['STATES'] !== undefined && translate['STATES'][state] !== undefined) {
141        let name = translate['STATES'][state];
142        if (language == 'bz' && otherTranslate['it'] !== undefined && otherTranslate['it']['STATES'] !== undefined && otherTranslate['it']['STATES'][state] !== undefined) {
143            name += ' - ' + otherTranslate['it']['STATES'][state];
144        }
145        return name;
146    }
147    return state;
148}
149function loadOtherLangs(currLang) {
150    const allLang = ['de', 'it', 'en'];
151    allLang.forEach(function (lang) {
152        if (lang != currLang) {
153            $.getJSON("translate/" + lang + ".json?t=" + new Date().getTime(), function (data) {
154                otherTranslate[lang] = data
155            }).fail(function (e) {
156                console.log("error translate");
157            });
158        }
159    });
160}
161// GENERAL 
162function trackEventGoogleAnalytics(eventName, parameter = {}) {
163    try {
164        if (typeof gtag !== 'undefined') {
165            gtag('event', eventName, parameter);
166        }
167    } catch (error) {
168        console.log(error);
169    }
170
171}
172function trackEventFacebook(eventName, parameter = {}) {
173    try {
174        if (typeof fbq !== 'undefined') {
175            fbq('trackCustom', eventName, parameter);
176            return true;
177        }
178    } catch (error) {
179        console.log(error);
180    }
181    return false;
182}
183async function scrollToEl(el) {
184    return new Promise(function (resolve, reject) {
185        let top = $('.menu-top').outerHeight();
186        if ($('.my-breadcrumbs-wrapper').length > 0)
187            top += $('.my-breadcrumbs-wrapper').outerHeight();
188        $([document.documentElement, document.body]).animate({
189            scrollTop: el.offset().top - top
190        }, 500).promise().done(function () {
191            resolve(true);
192        });
193    });
194}
195const levenshteinDistance = (str1 = '', str2 = '') => {
196    const track = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
197    for (let i = 0; i <= str1.length; i += 1) {
198        track[0][i] = i;
199    }
200    for (let j = 0; j <= str2.length; j += 1) {
201        track[j][0] = j;
202    }
203    for (let j = 1; j <= str2.length; j += 1) {
204        for (let i = 1; i <= str1.length; i += 1) {
205            const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
206            track[j][i] = Math.min(
207                track[j][i - 1] + 1,
208                track[j - 1][i] + 1,
209                track[j - 1][i - 1] + indicator,
210            );
211        }
212    }
213    return track[str2.length][str1.length];
214};
215initZoomist();
216function initZoomist() {
217    if ($('.zoomist-img').length > 0) {
218        $('.zoomist-img').each(function (idx, zoomistElement) {
219            const myZoomist = new Zoomist(zoomistElement, {
220                zoomRatio: 0.1,
221                fill: "contain",
222                draggable: true,
223                wheelable: true,
224                pinchable: true,
225                bounds: true,
226                minRatio: 1,
227                maxRatio: 6,
228                slider: false,
229                // slider: {
230                //     direction: "horizontal"
231                // },
232                zoomer: false
233            })
234            myZoomist.on('zoom', function (ratio) {
235                if (ratio <= 1) {
236                    myZoomist.reset();
237
238                }
239            });
240            myZoomist.on('dragStart', function (transform, event) {
241                if (myZoomist.getZoomRatio() <= 1) {
242                    myZoomist.reset()
243                }
244            });
245            myZoomist.on('drag', function (transform, event) {
246                if (myZoomist.getZoomRatio() <= 1) {
247                    myZoomist.reset()
248                }
249            });
250        })
251
252    }
253}
254$('.focus-after-load').find('[name]').focus();
255initColor();
256$(window).on('load', async function () {
257    await getPublicConsts();
258    await getTranslate();
259    await updateGb(null, false);
260    await saveFormCheckBeforeUnload();
261    await initLocalStorageToForm();
262    await initParamsToForm();
263    await getStateNames();
264    initDirIDParam();
265    initMultipleTestSign();
266    initMultipleInputValue();
267    updateCartButton();
268    initToasts();
269    initTriggerClick();
270    if (typeof initMyHandsontable !== "undefined") {
271        initMyHandsontable();
272    }
273    if (typeof initializeTabulator !== "undefined") {
274        initializeTabulator();
275    }
276    showCookieBanner();
277});
278
279function iosAcceptTracking() {
280    $('script[type="text/plain"][cookie-consent="tracking"],script[type="text/plain"][cookie-consent="targeting"]').each(function () {
281        var $script = $(this);
282        var newScript = document.createElement('script');
283        newScript.type = 'text/javascript';
284        if ($script.attr('src')) {
285            newScript.src = $script.attr('src');
286            newScript.async = true;
287        } else {
288            newScript.text = $script.html();
289        }
290        document.head.appendChild(newScript);
291        $script.remove();
292    });
293}
294async function showCookieBannerMobileApp() {
295    if ($('html').data('plt-android')) {
296        if (!translate['SETTINGS']) {
297            await getTranslate();
298        }
299        if (typeof cookieconsent === 'undefined') {
300            return;
301        }
302        cookieconsent.run({
303            "notice_banner_type": "simple",
304            "consent_type": "express",
305            "palette": "light",
306            "language": language,
307            "website_name": "APPEC",
308            "cookies_policy_url": translate['SETTINGS']['PRIVACY_LINK'],
309            "cookie_duration": 365
310        });
311    }
312}
313function showCookieBanner() {
314    // cookieconsent.js wird z.B. von Firefox-Trackingschutz/Adblockern blockiert
315    if (typeof cookieconsent === 'undefined' || !translate['SETTINGS']) {
316        return;
317    }
318    if (typeof webkit === 'undefined') {
319        cookieconsent.run({
320            "notice_banner_type": "simple",
321            "consent_type": "express",
322            "palette": "light",
323            "language": language,
324            "website_name": "APPEC",
325            "cookies_policy_url": translate['SETTINGS']['PRIVACY_LINK'],
326            "cookie_duration": 365
327        });
328    }
329}
330function initToasts() {
331    showSaved();
332    removeRefId();
333    showUpdated();
334    showDisconnected();
335    showAccountConfirmed();
336    showAccountDeleted();
337    showNoArticleLeft();
338    showPositiveMultipleTestAdded();
339}
340window.onpopstate = history.onpushstate = function (e) {
341    initDirIDParam();
342}
343function initMultipleInputValue() {
344    let IDs = localStorage.getItem('multiple_test_data');
345    let multipleTestData = JSON.parse(IDs);
346    if ($('input[name="multiple_test_data"]').length > 0 && multipleTestData.length > 0) {
347        $('input[name="multiple_test_data"]').val(IDs);
348        $('form.check-before-unload').data('serialize', $('form.check-before-unload').seri
348alize());
349    }
350}
351function initMultipleTestSign() {
352    let url_str = window.location.href;
353    let url = new URL(url_str);
354    let search_params = url.searchParams;
355    let multipletestMode = false;
356    let prevtest_id = 0;
357    let multipleTestDataKey = 0;
358    search_params.forEach(function (val, key) {
359        if (key == 'multipletest') {
360            multipletestMode = true;
361        }
362        if (key == 'key') {
363            multipleTestDataKey = val;
364        }
365        if (key == 'prevtest_id') {
366            prevtest_id = val;
367        }
368    });
369    if (multipletestMode) {
370        let sign = localStorage.getItem('sign_img');
371        if (sign) {
372            $('input[name="sign_image"]').val(sign);
373            if ($('.sign-img').length > 0) {
374                $('.sign-img').attr('src', sign);
375            } else {
376                let imgEl = document.createElement('img');
377                imgEl.setAttribute('src', sign);
378                imgEl.className = 'sign-img';
379                $('.sign-container').append(imgEl);
380            }
381        }
382        let IDs = localStorage.getItem('multiple_test_data');
383        let multipleTestData = JSON.parse(IDs);
384        let prevMultipleTestDataKey = multipleTestDataKey - 1;
385        if (multipleTestData.length > prevMultipleTestDataKey && prevMultipleTestDataKey >= 0) {
386            if (prevtest_id > 0) {
387                multipleTestData[prevMultipleTestDataKey]['test_id'] = prevtest_id;
388                multipleTestData[prevMultipleTestDataKey]['test_added'] = true;
389                localStorage.setItem('multiple_test_data', JSON.stringify(multipleTestData));
390                window.history.pushState('', document.title, removeParam('prevtest_id', url_str));
391            }
392            if (multipleTestData[prevMultipleTestDataKey]['test_added'] && multipleTestData[prevMultipleTestDataKey]['test_id'] > 0) {
393                $('#prev-test').removeClass('hide-me').find('a').attr('href', 'articles/edittest?multipletest=1&id=' + multipleTestData[prevMultipleTestDataKey]['test_id'] + ' &key=' + prevMultipleTestDataKey);
394            } else {
395                $('#prev-test').removeClass('hide-me').find('a').attr('href', 'articles/edittest?multipletest=1&article_id=' + multipleTestData[prevMultipleTestDataKey]['id'] + '&key=' + prevMultipleTestDataKey);
396            }
397        }
398        resetFormCheckBeforeUnload();
399    }
400}
401$(document).on('input', 'input[type="text"]', function (event) {
402    // Sonst wird special Char in DB gespeichert was zu fehler führt, hier eventuell weitere special Char entfernen
403    // console.log($(this).val($(this).val().replaceAll('"', "").replaceAll("'", "")));
404});
405function initDatePicker() {
406    if (translate['DATEPICKER']['DATE_FORMAT'] !== undefined) {
407        $('input.datepicker').Zebra_DatePicker({
408            days: translate['DATEPICKER']['WEEKDAYS'],
409            format: translate['DATEPICKER']['DATE_FORMAT'],
410            lang_clear_date: translate['DATEPICKER']['CLEAR_DATE'],
411            months: translate['DATEPICKER']['MONTHS'],
412            open_on_focus: false,
413            readonly_element: $('html').data('plt-ios'),
414            show_other_months: false,
415            show_clear_date: true,
416            show_select_today: translate['DATEPICKER']['DATE_TODAY'],
417            strict: true,
418            //default_position: 'below',
419            show_icon: false,
420            onSelect: function (value) {
421                this.change()
422            },
423            onClear: function (value) {
424                this.change()
425            }
426        });
427    }
428}
429function checkDate(str) {
430    var date_regex = /^(0[1-9]|1\d|2\d|3[01])\.(0[1-9]|1[0-2])\.(19|20|21)\d{2}$/;
431    return (date_regex.test(str));
432}
433$(document).on('focus', 'input[type="text"].datepicker', function (event) {
434    if ($(this).data('Zebra_DatePicker') == undefined) {
435        initDatePicker();
436    }
437    $(this).parents('.input-container').removeClass('empty');
438});
439
440$(document).on('blur', 'input[type="text"].datepicker', function (event) {
441    const val = $(this).val();
442    if (val) {
443        $(this).parents('.input-container').removeClass('empty');
444    } else {
445        $(this).parents('.input-container').addClass('empty');
446    }
447});
448$(document).on('change', 'input[type="text"].datepicker', function (event) {
449    $(this).parents('.my-input').removeClass('error');
450    const val = $(this).val();
451    if (val) {
452        $(this).parents('.input-container').removeClass('empty')
453        if (!checkDate(val)) {
454            let inputWrapperEl = $(this).parents('.my-input');
455            inputWrapperEl.addClass('error');
456            inputWrapperEl.find('.input-error').text(translate['DATEPICKER']['INVALID_DATE']);
457            $(this).focus();
458        } else {
459            if ($(this).attr('name') === 'from_date') {
460                $('input[name="to_date"]').data('Zebra_DatePicker').update({ direction: [val, moment().format("DD.MM.YYYY")] });
461            }
462        }
463    } else {
464        $(this).parents('.input-container').addClass('empty');
465        if ($(this).attr('name') === 'from_date') {
466            $('input[name="to_date"]').data('Zebra_DatePicker').update({ direction: [false, moment().format("DD.MM.YYYY")] });
467        }
468    }
469});
470function initColor() {
471    let tmpColor = $('html').data('color');
472    if (/^#[0-9A-F]{6}$/i.test(tmpColor)) {
473        const hex = tmpColor;
474        const r = parseInt(hex.replace('#', '').substr(0, 2), 16);
475        const g = parseInt(hex.replace('#', '').substr(2, 2), 16);
476        const b = parseInt(hex.replace('#', '').substr(4, 2), 16);
477        if (!(r > 230 && g > 230 && b > 230)) {
478            changeTheme(tmpColor);
479        }
480    } else {
481        changeTheme('#3880ff');
482    }
483}
484function initLocalStorageToForm() {
485    return new Promise(async (resolve, reject) => {
486        if ($('form.save-in-local-storage').length > 0) {
487            if ($('form.save-in-local-storage [name="edit"]').val() != '1') {
488                if ($('form.save-in-local-storage [name="is_ref_id"]').length <= 0) {
489                    let tmpData = localStorage.getItem($('form.save-in-local-storage').attr('id'));
490                    if (tmpData) {
491                        let data = JSON.parse(tmpData);
492                        if ($('form.save-in-local-storage').attr('id') == 'editmodel-form') {
493                            let tmpArr = Object.entries(data).map(arr => arr[0]).filter(x => x.includes('checklist-id'));
494                            for (let idx = 0; idx < tmpArr.length; idx++) {
495                                const key = tmpArr[idx];
496                                let checklist = await addChecklist(false, false);
497                                let tmpkey = 'question-' + key.replace(/^\D+/g, '') + '[]';
498                                let tmpkeyType = 'type-' + key.replace(/^\D+/g, '') + '[]';
499                                if (data[tmpkey]) {
500                                    let tmpQuestions = data[tmpkey];
501                                    let tmpTypes = [];
502                                    if (data[tmpkeyType]) {
503                                        tmpTypes = data[tmpkeyType];
504                                    }
505                                    for (let idx2 = 0; idx2 < tmpQuestions.length; idx2++) {
506                                        let type = 'radio';
507                                        if (tmpTypes[idx2]) {
508                                            type = tmpTypes[idx2];
509                                        }
510                                        await addNewQuestion($(checklist).find('.list'), tmpQuestions[idx2], type, false);
511                                    }
512                                }
513                            }
514                        }
515                        Object.entries(data).forEach(function ([key, val], idx) {
516                            if (key != 'edit' && key != 'parent_dir_name' && key != 'parent_dir_id' && !key.includes('question-') && !key.includes('type-')) {
517                                let inputEl = $('form.save-in-local-storage [name="' + key + '"]');
518                                if (inputEl.length > 0) {
519                                    inputEl.val(val).change();
520                                    if (typeof val === 'string' || val instanceof String) {
521                                        if (val.startsWith('data:image/')) {
522                                            inputEl.parent().find('.prev-img').css('background-image', 'url(' + val + ')');
523                                        }
524                                    }
525                                }
526                            }
527                        })
528                    }
529                }
530            }
531        }
532        resolve(true);
533    });
534}
535function initParamsToForm() {
536    return new Promise((resolve, reject) => {
537        let url_str = window.location.href;
538        let url = new URL(url_str);
539        let search_params = url.searchParams;
540        let params = {};
541        search_params.forEach(function (val, key) {
542            params[key] = val;
543            $('[name="' + key + '"]').val(val);
544            if ($('[name="' + key + '"]').length > 0) {
545                window.history.pushState('', document.title, removeParam(key, url_str));
546                url_str = window.location.href;
547            }
548        });
549        resolve(true);
550    });
551}
552function initTriggerClick() {
553    let url_str = window.location.href;
554    let url = new URL(url_str);
555    let search_params = url.searchParams;
556    if (search_params.has('triggerclick')) {
557        const triggerID = '#' + search_params.get('triggerclick');
558        $(triggerID).trigger('click');
559        window.history.pushState('', document.title, removeParam("triggerclick", url_str));
560    }
561}
562function showSaved() {
563    let url_str = window.location.href;
564    let url = new URL(url_str);
565    let search_params = url.searchParams;
566    if (search_params.has('saved')) {
567        window.history.pushState('', document.title, removeParam("saved", url_str));
568        presentToast(translate['SAVED'], 'fa-cloud', 'success', 2000);
569    }
570}
571function removeRefId() {
572    let url_str = window.location.href;
573    let url = new URL(url_str);
574    let search_params = url.searchParams;
575    if (search_params.has('ref_id')) {
576        window.history.pushState('', document.title, removeParam("ref_id", url_str));
577    }
578}
579function showUpdated() {
580    let url_str = window.location.href;
581    let url = new URL(url_str);
582    let search_params = url.searchParams;
583    if (search_params.has('updated')) {
584        window.history.pushState('', document.title, removeParam("updated", url_str));
585        presentToast(translate['MODEL']['UPDATED'], 'fa-cloud-arrow-down', 'success', 2000);
586    }
587}
588function showDisconnected() {
589    let url_str = window.location.href;
590    let url = new URL(url_str);
591    let search_params = url.searchParams;
592    if (search_params.has('disconnected')) {
593        window.history.pushState('', document.title, removeParam("disconnected", url_str));
594        presentToast(translate['MODEL']['DISCONNECTED'], 'fa-check', 'success', 2000);
595    }
596}
597function showAccountDeleted() {
598    let url_str = window.location.href;
599    let url = new URL(url_str);
600    let search_params = url.searchParams;
601    if (search_params.has('shownoarticlesleft')) {
602        window.history.pushState('', document.title, removeParam("shownoarticlesleft", url_str));
603        getAjaxPopUp('noarticlesleft');
604    }
605}
606function showNoArticleLeft() {
607    let url_str = window.location.href;
608    let url = new URL(url_str);
609    let search_params = url.searchParams;
610    if (search_params.has('accountdeleted')) {
611        window.history.pushState('', document.title, removeParam("accountdeleted", url_str));
612        presentToast(translate['SETTINGS']['ACCOUNT_DELTED_SUCCESSFUL'], 'fa-circle-check', 'success');
613    }
614}
615function showPositiveMultipleTestAdded() {
616    let url_str = window.location.href;
617    let url = new URL(url_str);
618    let search_params = url.searchParams;
619    if (search_params.has('positivemultipletestadded')) {
620        let count = 0;
621        if (search_params.has('count')) {
622            count = search_params.get('count');
623            window.history.pushState('', document.title, removeParam("count", url_str));
624            url_str = window.location.href;
625        }
626        window.history.pushState('', document.title, removeParam("positivemultipletestadded", url_str));
627        presentToast(translate['ARTICLE']['TOAST_POSITIVE_MULTIPLE_TEST_ADDED'].replace('##', count), 'fa-circle-check', 'success');
628    }
629}
630
631function showAccountConfirmed() {
632    let url_str = window.location.href;
633    let url = new URL(url_str);
634    let search_params = url.searchParams;
635    if (search_params.has('accountconfirmed')) {
636        if (search_params.get('accountconfirmed') == '1') {
637            presentToast(translate['SETTINGS']['ACCOUNT_CONFIRMED'], 'fa-circle-check', 'success', 10000);
638        } else {
639            presentToast(translate[search_params.get('error')], 'fa-circle-exclamation', 'warning', 10000);
640            url_str = removeParam("error", url_str);
641        }
642        url_str = removeParam("accountconfirmed", url_str);
643        window.history.pushState('', document.title, url_str);
644    }
645}
646function removeParam(key, sourceURL) {
647    var rtn = sourceURL.split("?")[0],
648        param,
649        params_arr = [],
650        queryString = (sourceURL.indexOf("?") !== -1) ? sourceURL.split("?")[1] : "";
651    if (queryString !== "") {
652        params_arr = queryString.split("&");
653        for (var i = params_arr.length - 1; i >= 0; i -= 1) {
654            param = params_arr[i].split("=")[0];
655            if (param === key) {
656                params_arr.splice(i, 1);
657            }
658        }
659        if (params_arr.length) rtn = rtn + "?" + params_arr.join("&");
660    }
661    return rtn;
662}
663function replaceUrlParam(paramName, paramValue) {
664    let url = window.location.href;
665    if (paramValue == null) {
666        paramValue = '';
667    }
668    var pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)');
669    if (url.search(pattern) >= 0) {
670        url = url.replace(pattern, '$1' + paramValue + '$2');
671    } else {
672        url = url.replace(/[?#]$/, '');
673        url = url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue;
674    }
675    window.history.pushState('', document.title, url);
676}
677$(document).on('click', '.open-link', function (e) {
678    e.preventDefault();
679    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
680        try {
681            let messageObj = { function: 'open_external_link', url: $(this).attr('href') };
682            let stringifiedMessageObj = JSON.stringify(messageObj);
683            webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
684        } catch (error) {
685            console.log(error);
686        }
687    } else {
688        window.open($(this).attr('href'), '_blank');
689    }
690});
691$(document).on('click', 'a[target="_blank"]', function (e) {
692    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
693        e.preventDefault();
694        try {
695            let messageObj = { function: 'open_external_link', url: $(this).attr('href') };
696            let stringifiedMessageObj = JSON.stringify(messageObj);
697            webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
698        } catch (error) {
699            console.log(error);
700        }
701    }
702});
703$(document).on('click', '[data-openlink]', function (e) {
704    if (!$('.my-table-list').hasClass('long-pressed')) {
705        let link = $(this).data('openlink');
706        if (e.ctrlKey || e.metaKey) {
707            window.open(link, '_blank');
708        } else {
709            window.open(link, '_self');
710        }
711    }
712});
713$(document).on('click', '.action-links a:not(.preview-attachment-in-lightbox):not([data-dir]):not(.get-bill-pdf), .preview-attachment-in-lightbox[download]', async function (e) {
714    e.preventDefault();
715    e.stopImmediatePropagation();
716    const openInNewTab = e.metaKey || e.ctrlKey;
717    if (typeof $(this).attr('download') == "undefined") {
718        let link = $(this).attr('href');
719        window.open(link, openInNewTab ? '_blank' : '_self');
720        if ($(this).attr('id') == 'export-test') {
721            exporttest($(this));
722        }
723    } else {
724        await showLoading();
725        $.ajax({
726            url: $(this).attr('href') + '&for_ajax=1',
727            success: async function (result) {
728                try {
729                    let data = JSON.parse(result);
730                    if (typeof data === 'object') {
731                        await downloadFile(data['base64'], data['filename'], data['mimetype'])
732                    } else {
733                        await hideLoading();
734                        console.log(result);
735                    }
736                } catch (err) {
737                    await hideLoading();
738                    console.log(err);
739                    console.log(result);
740                }
741            },
742            error: function (error) {
743                console.log(error);
744            }
745        });
746    }
747});
748$(document).on('click', '.my-table-list .list-item', function (e) {
749    if ($('.my-table-list').hasClass('long-pressed')) {
750        return;
751    }
752    if ($(e.target).closest('.select-col, .action-links').length > 0) {
753        return;
754    }
755    let firstActionLink = $(this).find('.action-links a').first();
756    if (firstActionLink.length > 0) {
757        firstActionLink[0].click();
758    }
759});
760
761
762
763function setCookie(cname, cvalue, exdays = 0) {
764    if (exdays == 0) {
765        document.cookie = cname + "=" + cvalue + ";path=/";
766    } else {
767        const d = new Date();
768        d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
769        let expires = "expires=" + d.toUTCString();
770        document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
771    }
772}
773
774function deleteCookie(cname) {
775    setCookie(cname, null, -100);
776    document.cookie = cname + "=" + ";expires=Thu, 01 Jan 1970 00:00:01 GMT";
777}
778
779function getCookie(cname) {
780    let name = cname + "=";
781    let ca = document.cookie.split(';');
782    for (let i = 0; i < ca.length; i++) {
783        let c = ca[i];
784        while (c.charAt(0) == ' ') {
785            c = c.substring(1);
786        }
787        if (c.indexOf(name) == 0) {
788            return c.substring(name.length, c.length);
789        }
790    }
791    return "";
792}
793let touchstartX = 0;
794let touchendX = 0;
795let canSwipeMenu = false;
796let hasMoved = false; // <-- neu
797
798$('body').on('touchstart', function (event) {
799    touchstartX = event.touches[0].clientX;
800    hasMoved = false; // reset bei jedem neuen Touchstart
801
802    if (!$('.menu-left').hasClass('open')) {
803        if (touchstartX < 70) {
804            canSwipeMenu = true;
805        }
806    } else {
807        canSwipeMenu = true;
808    }
809});
810
811$('body').on('touchmove', function (event) {
812    if (canSwipeMenu) {
813        touchendX = event.touches[0].clientX;
814        hasMoved = true; // nur wenn es wirklich bewegt wurde
815    }
816});
817
818$('body').on('touchend', function (event) {
819    if (canSwipeMenu && hasMoved) { // <-- nur wenn tatsächlich geswiped
820        let diff = touchstartX - touchendX;
821
822        if (!$('.menu-left').hasClass('open')) {
823            if (diff < -200) {
824                $('.menu-left').toggleClass('open');
825            }
826        } else {
827            if (diff > 200) {
828                $('.menu-left').toggleClass('open');
829            }
830        }
831    }
832
833    // Reset
834    canSwipeMenu = false;
835    hasMoved = false;
836});
837
838$(document).on('keydown', function (e) {
839    let key = e.keyCode ? e.keyCode : e.which;
840    if ($('.my-popup').is(':visible')) {
841        if (key == 27) {
842            $('.my-popup').find('.close-popup').trigger('click');
843            $('.my-popup').find('.close-and-remove-popup').trigger('click');
844        }
845    }
846    if ($('#popup-pdf').is(':visible')) {
847        if (key == 27) {
848            $('#popup-pdf').find('.close').trigger('click');
849        }
850    }
851    if ($('.my-action-sheet').is(':visible')) {
852        if (key == 27) {
853            closeMyActionSheet($('.my-action-sheet'));
854        }
855    }//cmd+s ctrl+s
856    if ((e.ctrlKey || e.metaKey) && e.which == 83) {
857        $('.ctrl-save').trigger('click');
858        e.preventDefault();
859        return false;
860    }
861});
862async function showLoading(text = translate['LOADING']) {
863    $('#my-loading span.info-text').html(text);
864    if (!$('#my-loading').is(':visible')) {
865        await $('#my-loading').fadeIn(300).delay(500).promise();
866    }
867    return true;
868}
869async function hideLoading(noAnimation = false) {
870    if (!noAnimation) {
871        await $('#my-loading').fadeOut(300).promise();
872    } else {
873        await $('#my-loading').fadeOut(0).promise();
874    }
875    return true;
876}
877
878$(window).resize(function () {
879    resetToastsPostions();
880    if ($('.my-breadcrumbs').length > 0) {
881        changeBreadcrumbsView();
882    }
883    if ($('#sign_canvas').length > 0) {
884        saveResizeAndRedisplaySignCanvas();
885    }
886    if ($('#canvas_container').length > 0) {
887        initPDFViewer();
888    }
889    if ($('.filter-wrapper').length > 0 || $('.my-breadcrumbs-wrapper').length > 0) {
890        setFilterWrapperSticky();
891    }
892});
893async function getSVG(link) {
894    return new Promise((resolve, reject) => {
895        $.ajax({
896            url: link,
897            success: function (data) {
898                resolve(new XMLSerializer().serializeToString(data.documentElement));
899            },
900            error: function (error) {
901                console.log(error);
902            }
903        });
904    })
905}
906$(document).on('click', '.delete-filters', function (e) {
907    deleteCookie('article_filter');
908    deleteCookie('tests_from_article_filter');
909    deleteCookie('test_filter');
910    deleteCookie('model_filter');
911    deleteCookie('central_model_filter');
912    deleteCookie('manufacture_filter');
913    deleteCookie('model_category_filter');
914    deleteCookie('central_model_category_filter');
915    deleteCookie('user_filter');
916    deleteCookie('usergroup_filter');
917    deleteCookie('main_bill_filter');
918    localStorage.removeItem('editarticle-form');
919    localStorage.removeItem('editdir-form');
920    localStorage.removeItem('editmanufacture-form');
921    localStorage.removeItem('editmodel_category-form');
922    localStorage.removeItem('editmodel-form');
923    localStorage.removeItem('sign_img');
924});
925// END - GENERAL
926// ACCORDION
927$(document).on('click', '.my-accordion', function (e) {
928    let $this = $(this);
929    let id = $this.data('target');
930    if ($('#' + id).hasClass('hide-me')) {
931        $('#' + id).hide().promise().done(function () {
932            $('#' + id).removeClass('hide-me').promise().done(function () {
933                $('#' + id).slideDown(500);
934                $this.addClass('shown');
935            });
936        });
937    } else {
938        $('#' + id).slideUp(500).promise().done(function () {
939            $('#' + id).addClass('hide-me');
940            $this.removeClass('shown');
941        });
942    }
943})
944// END - ACCORDION
945// TOOLTIP
946$(document).on('mouseover', '.my-tooltip', function (e) {
947    $this = $(this);
948    let toolTipText = $this.attr('title');
949    $this.data('title', toolTipText);
950    $this.attr('title', '');
951    let offset = $(this).offset();
952    let windowWith = $(window).width() - 16;
953    let toolTipEl = document.createElement('div');
954    toolTipEl.className = 'my-tooltip-content ' + $this.data('color') + ($this.hasClass('top') ? ' top' : '') + ($this.hasClass('left') ? ' left' : '');
955    toolTipEl.textContent = toolTipText;
956    $(toolTipEl).css('top', e.originalEvent.clientY);
957    if ($this.hasClass('left')) {
958        $(toolTipEl).css('right', windowWith - offset.left);
959        $(toolTipEl).css('max-width', offset.left);
960    } else {
961        $(toolTipEl).css('left', offset.left);
962        $(toolTipEl).css('max-width', (windowWith - offset.left));
963    }
964    $('body').append(toolTipEl);
965
966}).on('mouseout', '.my-tooltip', function () {
967    $this = $(this);
968    $this.attr('title', $this.data('title'));
969    $('.my-tooltip-content').remove();
970});
971$(document).on('click', '.my-tooltip', function (e) {
972    e.preventDefault();
973    e.stopImmediatePropagation();
974});
975$(document).on('touchstart', function (e) {
976    $('.my-tooltip').each(function (idx, el) {
977        $this = $(el);
978        $this.attr('title', $this.data('title'));
979        $('.my-tooltip-content').remove();
980    });
981});
982// END - TOOLTIP
983// BREADCRUMBS
984if ($('.my-breadcrumbs').length > 0) {
985    changeBreadcrumbsView();
986}
987function changeBreadcrumbsView() {
988    const breadcrumbs = $('.my-breadcrumbs');
989    let width = breadcrumbs.find('.breadcrumb').map((idx, el) => $(el).outerWidth()).toArray().reduce((partialSum, a) => partialSum + a, 0);
990    if (width > breadcrumbs.width() && !$('.my-breadcrumbs').hasClass('mobil')) {
991        $('.my-breadcrumbs').addClass('mobil');
992    } else {
993        width = breadcrumbs.find('.breadcrumb').map((idx, el) => {
994            if (idx < (breadcrumbs.find('.breadcrumb').length - 1)) {
995                return $(el).outerWidth() + $(el).find('span').outerWidth()
996            } else {
997                return $(el).outerWidth()
998            }
999        }).toArray().reduce((partialSum, a) => partialSum + a, 0);
1000        if (width < breadcrumbs.width()) {
1001            $('.my-breadcrumbs').removeClass('mobil');
1002        }
1003        let tmpWidth = breadcrumbs.find('.breadcrumb').map((idx, el) => $(el).outerWidth()).toArray().reduce((partialSum, a) => partialSum + a, 0);
1004        let spanWidth = breadcrumbs.find('.breadcrumb:last-of-type span').outerWidth();
1005        let maxSpanWidth = breadcrumbs.width() - (tmpWidth - spanWidth);
1006        breadcrumbs.find('.breadcrumb:last-of-type span').css('max-width', maxSpanWidth);
1007    }
1008}
1009// END - BREADCRUMBS
1010// EXPORT
1011async function hideLoadingFromApp() {
1012    return new Promise(async (resolve, reject) => {
1013        let hideLoading = $('html').attr('data-hide-loading');
1014        if ((hideLoading !== '' && hideLoading !== undefined)) {
1015            $('html').attr('data-hide-loading', '');
1016            $('#my-loading').fadeOut(300);
1017            resolve(true);
1018        } else {
1019            setTimeout(async () => {
1020                resolve(await hideLoadingFromApp());
1021            }, 300);
1022        }
1023    });
1024}
1025
1026async function mergePdfs(files) {
1027    const mergedPdf = await PDFLib.PDFDocument.create();
1028    const imageFiles = [];
1028 // sammeln der Bildpfade
1029
1030    for (let file of files) {
1031        if (file instanceof Uint8Array) {
1032            // pdfMake PDF
1033            const pdf = await PDFLib.PDFDocument.load(file);
1034            const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
1035            copiedPages.forEach(p => mergedPdf.addPage(p));
1036
1037        } else if (typeof file === 'string') {
1038            const ext = file.split('.').pop().toLowerCase();
1039
1040            if (ext === 'pdf') {
1041                // PDF vom Server laden
1042                const pdfBytes = await fetch(file).then(res => res.arrayBuffer());
1043                const pdf = await PDFLib.PDFDocument.load(pdfBytes);
1044                const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
1045                copiedPages.forEach(p => mergedPdf.addPage(p));
1046            } else if (['jpg', 'jpeg', 'png'].includes(ext)) {
1047                // Bild merken, wird später in Galerie eingefügt
1048                imageFiles.push(file);
1049            }
1050        }
1051    }
1052
1053    // Bilder als Galerie einfügen
1054    if (imageFiles.length > 0) {
1055        await addImagesAsGallery(mergedPdf, imageFiles, 42.5); // 42.5pt ≈ 15mm Rand
1056    }
1057
1058    return await mergedPdf.saveAsBase64({ dataUri: true });
1059}
1060async function mergePdfs(pdfsToMerges) {
1061    await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_7']);
1062    const mergedPdf = await PDFLib.PDFDocument.create();
1063    try {
1064        for (let index = 0; index < pdfsToMerges.length; index++) {
1065            let pdf = await PDFLib.PDFDocument.load(pdfsToMerges[index]);
1066            const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
1067            copiedPages.forEach(p => mergedPdf.addPage(p));
1068        }
1069    } catch (error) {
1070        console.log(error);
1071    }
1072    return await mergedPdf.saveAsBase64({ dataUri: true });
1073}
1074
1075async function mergePdfsAndImages(files, asBytes = false) {
1076    const mergedPdf = await PDFLib.PDFDocument.create();
1077    const imageFiles = [];
1078
1079    for (let file of files) {
1080        try {
1081            if (file instanceof Uint8Array) {
1082                const pdf = await PDFLib.PDFDocument.load(file);
1083                const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
1084                copiedPages.forEach(p => mergedPdf.addPage(p));
1085
1086            } else if (typeof file === 'string') {
1087                const ext = file.split('.').pop().toLowerCase();
1088
1089                if (ext === 'pdf') {
1090                    const pdfBytes = await fetch(file).then(res => res.arrayBuffer());
1091                    const pdf = await PDFLib.PDFDocument.load(pdfBytes);
1092                    const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
1093                    copiedPages.forEach(p => mergedPdf.addPage(p));
1094
1095                } else if (['jpg', 'jpeg', 'png'].includes(ext)) {
1096                    imageFiles.push(file);
1097                }
1098            } else {
1099                console.warn('mergePdfs: unbekannter Dateityp', file);
1100            }
1101        } catch (error) {
1102            console.error('mergePdfs error:', error, file);
1103        }
1104    }
1105
1106    if (imageFiles.length > 0) {
1107        await addImagesAsGallery(mergedPdf, imageFiles, 42.5);
1108    }
1109
1110    if (asBytes) {
1111        return await mergedPdf.save();
1112    }
1113    return await mergedPdf.saveAsBase64({ dataUri: true });
1114}
1115// Bild fuer die Galerie laden. Grosse Fotos werden auf maxPx verkleinert (JPEG), damit die PDFs nicht riesig werden.
1116async function getGalleryImage(file, maxPx = 1600) {
1117    const ext = file.split('.').pop().toLowerCase();
1118    const blob = await fetch(file).then(res => res.blob());
1119    try {
1120        const bitmap = await createImageBitmap(blob);
1121        if (Math.max(bitmap.width, bitmap.height) > maxPx) {
1122            const scale = maxPx / Math.max(bitmap.width, bitmap.height);
1123            const canvas = document.createElement('canvas');
1124            canvas.width = Math.round(bitmap.width * scale);
1125            canvas.height = Math.round(bitmap.height * scale);
1126            const ctx = canvas.getContext('2d');
1127            ctx.fillStyle = '#ffffff';
1128            ctx.fillRect(0, 0, canvas.width, canvas.height);
1129            ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
1130            bitmap.close();
1131            const jpgBlob = await new Promise(resolve => canvas.toBlob(resolve, 'image/jpeg', 0.85));
1132            if (jpgBlob) {
1133                return { bytes: await jpgBlob.arrayBuffer(), isPng: false };
1134            }
1135        } else {
1136            bitmap.close();
1137        }
1138    } catch (error) {
1139        console.log(error);
1140    }
1141    return { bytes: await blob.arrayBuffer(), isPng: ext === 'png' };
1142}
1143async function addImagesAsGallery(mergedPdf, imageFiles, pageMargin = 42.5) {
1144    const pageWidth = 595.28;
1145    const pageHeight = 841.89;
1146    const availableWidth = pageWidth - pageMargin * 2;
1147    const availableHeight = pageHeight - pageMargin * 2;
1148
1149    let page = mergedPdf.addPage([pageWidth, pageHeight]);
1150    let x = pageMargin;
1151    let y = pageHeight - pageMargin;
1152    let rowHeight = 0;
1153    const spacing = 10;
1154
1155    for (const file of imageFiles) {
1156        const galleryImage = await getGalleryImage(file);
1157        const image = galleryImage.isPng ? await mergedPdf.embedPng(galleryImage.bytes) : await mergedPdf.embedJpg(galleryImage.bytes);
1158
1159        const imgWidth = image.width;
1160        const imgHeight = image.height;
1161
1162        const maxWidth = availableWidth / 2 - spacing;
1163        const scale = maxWidth / imgWidth;
1164        const drawWidth = imgWidth * scale;
1165        const drawHeight = imgHeight * scale;
1166
1167        if (x + drawWidth > availableWidth + pageMargin) {
1168            x = pageMargin;
1169            y -= rowHeight + spacing;
1170            rowHeight = 0;
1171        }
1172
1173        if (y - drawHeight < pageMargin) {
1174            page = mergedPdf.addPage([pageWidth, pageHeight]);
1175            x = pageMargin;
1176            y = pageHeight - pageMargin;
1177            rowHeight = 0;
1178        }
1179
1180        page.drawImage(image, { x, y: y - drawHeight, width: drawWidth, height: drawHeight });
1181
1182        x += drawWidth + spacing;
1183        rowHeight = Math.max(rowHeight, drawHeight);
1184    }
1185}
1186
1187function base64ToArrayBuffer(base64) {
1188    var binary_string = window.atob(base64);
1189    var len = binary_string.length;
1190    var bytes = new Uint8Array(len);
1191    for (var i = 0; i < len; i++) {
1192        bytes[i] = binary_string.charCodeAt(i);
1193    }
1194    return bytes.buffer;
1195}
1196async function exporttest(self) {
1197    await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED']);
1198    const id = self.data('id');
1199    $.ajax({
1200        type: 'GET',
1201        url: 'gettestdataforexport',
1202        cache: false,
1203        data: {
1204            id: id
1205        },
1206        success: async function (result) {
1207            try {
1208                let data = JSON.parse(result);
1209                if (typeof data === 'object') {
1210                    let base64 = await testToPDF(data['testData'], data['data']['logo'], true);
1211                    await downloadFile(base64, data['data']['filename'], data['data']['mimetype'])
1212                } else {
1213                    await hideLoading();
1214                    console.log(result);
1215                }
1216            } catch (err) {
1217                await hideLoading();
1218                console.log(err);
1219                console.log(result);
1220            }
1221        }
1222    });
1223}
1224// base64: Base64-Data-URL oder Blob. keepLoading: Ladeanzeige nicht ausblenden (Zwischendateien beim Export in Teilen)
1225async function downloadFile(base64, filename, mimetype, keepLoading = false) {
1226    if (base64 instanceof Blob && ($('html').data('plt-android') || $('html').data('plt-ios'))) {
1227        base64 = await toBase64(base64);
1228    }
1229    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
1230        try {
1231            var messageObj = { function: 'downloadFile', base64: base64, filename: filename, type: mimetype };
1232            var stringifiedMessageObj = JSON.stringify(messageObj);
1233            webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
1234            await hideLoadingFromApp();
1235        } catch (error) {
1236            console.log(error);
1237        }
1238    } else {
1239        if (!keepLoading) {
1240            await hideLoading();
1241        }
1242        $('#loading-bar').hide();
1243        let a = document.createElement("a");
1244        let blobUrl = null;
1245        if (base64 instanceof Blob) {
1246            blobUrl = URL.createObjectURL(base64);
1247            a.href = blobUrl;
1248        } else {
1249            a.href = base64;
1250        }
1251        a.download = filename;
1252        a.click();
1253        if (blobUrl) {
1254            setTimeout(() => URL.revokeObjectURL(blobUrl), 60000);
1255        }
1256    }
1257}
1258$(document).on('click', '#export-test', async function () {
1259    exporttest($(this));
1260});
1261function getTitlePagePDF() {
1262    return new Promise(async (resolve, reject) => {
1263        const tabelListEl = $('.my-table-list');
1264        const tableName = tabelListEl.data('table-name');
1265        let res = await getAjaxPopUp('titlepagepopup', { tableName: tableName });
1266        if (res) {
1267
1268            await initPDFViewer();
1269            let popup = $('#title-page-popup');
1270            let id = popup.attr('id');
1271            $('#' + id + ' .bold').on('click', async function () {
1272                const base64 = $('#canvas_container').attr('data-src')
1273                await hidePopUp(popup);
1274                popup.remove();
1275                resolve(base64);
1276            });
1277            $('#' + id + ' .close-popup').on('click', async function () {
1278                await hidePopUp(popup);
1279                popup.remove();
1280                reject('dismiss');
1281            });
1282            $('#' + id + ' .upload').on('click', async function () {
1283                await getAjaxPopUp('uploadnewtitlepage');
1284                $('#upload-pdf-popup #my-drop-container input[type="file"]').on("change", async function () {
1285                    let $this = $(this);
1286                    let pdf = $this[0].files[0];
1287                    if (pdf.size < 5000000 && pdf.type === "application/pdf") {
1288                        const pdfBase64 = await toBase64(pdf);
1289                        $.ajax({
1290                            type: 'POST',
1291                            url: 'uploadtitlepage',
1292                            cache: false,
1293                            data: {
1294                                pdfBase64: pdfBase64,
1295                                tableName: tableName
1296                            },
1297                            success: async function (result) {
1298                                presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_UPLOADED'], 'fa-save', 'success');
1299                                let tmppopup = $('#upload-pdf-popup');
1300                                await hidePopUp(tmppopup);
1301                                changeCanvasContainerSrc(pdfBase64);
1302                                tmppopup.remove();
1303                            }
1304                        });
1305                    } else {
1306                        presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_TO_BIG'], 'fa-triangle-exclamation', 'warning');
1307                    }
1308                });
1309                $('#upload-pdf-popup .close-popup').on('click', async function () {
1310                    let tmppopup = $('#upload-pdf-popup');
1311                    await hidePopUp(tmppopup);
1312                    tmppopup.remove();
1313                });
1314
1315                $("#my-drop-container").on("dragenter", function (e) {
1316                    e.preventDefault();
1317                    e.stopPropagation();
1318                    $(this).addClass('drag-over');
1319                });
1320                $("#my-drop-container").on("dragleave", function (e) {
1321                    e.preventDefault();
1322                    e.stopPropagation();
1323                    $(this).removeClass('drag-over');
1324                });
1325                $('body #my-drop-container').on("drop dragdrop", async function (e) {
1326                    e.stopPropagation();
1327                    e.preventDefault();
1328                    $(this).removeClass('drag-over');
1329                    let pdf = e.originalEvent.dataTransfer.files[0];
1330                    if (pdf.size < 5000000 && pdf.type === "application/pdf") {
1331                        const pdfBase64 = await toBase64(pdf);
1332                        $.ajax({
1333                            type: 'POST',
1334                            url: 'uploadtitlepage',
1335                            cache: false,
1336                            data: {
1337                                pdfBase64: pdfBase64,
1338                                tableName: tableName
1339                            },
1340                            success: async function (result) {
1341                                presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_UPLOADED'], 'fa-save', 'success');
1342                                let tmppopup = $('#upload-pdf-popup');
1343                                changeCanvasContainerSrc(pdfBase64);
1344                                await hidePopUp(tmppopup);
1345                                tmppopup.remove();
1346                            }
1347                        });
1348                    } else {
1349                        presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_TO_BIG'], 'fa-triangle-exclamation', 'warning');
1350                    }
1351                });
1352            });
1353            $('#' + id + ' .generate').on('click', async function () {
1354                let data = {};
1355                data['date_formate'] = $('#date_format').val();
1356                data['tableName'] = $('.my-table-list').data('table-name');
1357                data['postData'] = $('#export-popup').find('form').serializeArray();
1358                data['ids'] = getExportIds();
1359
1360                getAjaxPopUp('generatenewtitlepage', data);
1361            });
1362            $('#' + id + ' .delete').on('click', async function () {
1363                $.ajax({
1364                    type: 'POST',
1365                    url: 'uploadtitlepage',
1366                    cache: false,
1367                    data: {
1368                        pdfBase64: null,
1369                        delete: true,
1370                        tableName: tableName
1371                    },
1372                    success: async function (result) {
1373                        try {
1374                            let data = JSON.parse(result);
1375                            if (typeof data === 'object') {
1376                                if (data['delete']) {
1377                                    presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_DELETED'], 'fa-tras
1377h', 'success');
1378                                    if (data['base64']) {
1379                                        changeCanvasContainerSrc(data['base64']);
1380                                    }
1381                                }
1382                            }
1383                        } catch (error) {
1384                            console.log(result);
1385                            console.log(error);
1386                        }
1387                    }
1388                });
1389            });
1390
1391        }
1392    });
1393}
1394function changeCanvasContainerSrc(src) {
1395    $('#canvas_container').attr('data-src', src);
1396    initPDFViewer();
1397}
1398pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
1399let initPDFViewerRunning = false;
1400async function initPDFViewer() {
1401    return new Promise(async (resolve, reject) => {
1402        if (initPDFViewerRunning == false) {
1403            initPDFViewerRunning = true;
1404            let link = $("#canvas_container").attr('data-src');
1405            let pdf = await pdfjsLib.getDocument(link).promise;
1406            let numPages = pdf.numPages;
1407            $('#canvas_container .inner').children().remove();
1408            for (let currPage = 1; currPage <= numPages; currPage++) {
1409                let page = await pdf.getPage(currPage);
1410                let viewport = page.getViewport({ scale: 1 });
1411                let canvas = document.createElement("canvas");
1412                canvas.style.display = "block";
1413                let context = canvas.getContext('2d');
1414                let width = $('#canvas_container .inner').width();
1415                let height = viewport.height / (viewport.width / width);
1416                canvas.width = width * 4;
1417                canvas.height = height * 4;
1418                viewport = page.getViewport({ scale: (width * 4 / viewport.width) });
1419                page.render({ canvasContext: context, viewport: viewport });
1420                $('#canvas_container .inner').append(canvas);
1421                $('#canvas_container').height($('#canvas_container .inner').height() / 4)
1422            }
1423            initPDFViewerRunning = false;
1424        }
1425        resolve(true)
1426    });
1427}
1428
1429function toBase64(file) {
1430    return new Promise((resolve, reject) => {
1431        const reader = new FileReader();
1432        reader.readAsDataURL(file);
1433        reader.onload = () => resolve(reader.result);
1434        reader.onerror = error => reject(error);
1435    });
1436}
1437function toDataUrl(url) {
1438    return new Promise((resolve, reject) => {
1439        var xhr = new XMLHttpRequest();
1440        xhr.onload = function () {
1441            var reader = new FileReader();
1442            reader.onloadend = function () {
1443                resolve(reader.result);
1444            }
1445            reader.readAsDataURL(xhr.response);
1446        };
1447        xhr.open('GET', url);
1448        xhr.responseType = 'blob';
1449        xhr.send();
1450    });
1451}
1452// PDF-Export in Teilen: Eine Datei ("Teil") wird abgeschlossen und heruntergeladen, sobald eine dieser Grenzen erreicht ist.
1453// Dadurch bleibt der Speicherbedarf konstant, egal wie viele Eintraege exportiert werden.
1454const EXPORT_TABLE_CHUNK_ROWS = 1000; // Zeilen der Zusammenfassung pro pdfMake-Dokument (gerade Zahl wegen der Zebra-Streifen)
1455const EXPORT_TEST_BATCH = 50; // Pruefungen pro Serveranfrage
1456function getExportLimits() {
1457    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
1458        return { maxBytes: 30000000, maxPages: 1000, maxTests: 500 };
1459    }
1460    // navigator.deviceMemory gibt es nur in Chrome/Edge (max. 8). Sonst vorsichtig sein.
1461    if (!(navigator.deviceMemory >= 8)) {
1462        return { maxBytes: 30000000, maxPages: 1000, maxTests: 500 };
1463    }
1464    return { maxBytes: 60000000, maxPages: 2000, maxTests: 1000 };
1465}
1466function isExportHeapHigh() {
1467    return !!performance.memory && performance.memory.usedJSHeapSize > performance.memory.jsHeapSizeLimit * 0.7;
1468}
1469let exportCancelled = false;
1470$(document).on('click', '#my-loading-cancel', function () {
1471    exportCancelled = true;
1472    $(this).prop('disabled', true).find('span').text(translate['EXPORT_DATA']['CANCELLING']);
1473});
1474function checkExportCancelled() {
1475    if (exportCancelled) {
1476        throw new Error('EXPORT_CANCELLED');
1477    }
1478}
1479function exportYield() {
1480    return new Promise(resolve => setTimeout(resolve, 0));
1481}
1482async function createExportPart() {
1483    return { doc: await PDFLib.PDFDocument.create(), pages: 0, bytes: 0, tests: 0 };
1484}
1485async function addPdfToExportPart(part, source, tests = 0) {
1486    const pdf = await PDFLib.PDFDocument.load(source, { updateMetadata: false });
1487    const copiedPages = await part.doc.copyPages(pdf, pdf.getPageIndices());
1488    copiedPages.forEach(p => part.doc.addPage(p));
1489    part.pages += copiedPages.length;
1490    part.bytes += (typeof source === 'string') ? Math.round(source.length * 0.75) : source.length;
1491    part.tests += tests;
1492}
1493function shouldCloseExportPart(part, limits) {
1494    return part.pages >= limits.maxPages || part.bytes >= limits.maxBytes || part.tests >= limits.maxTests || (part.bytes > 5000000 && isExportHeapHigh());
1495}
1496async function fetchExportTestDatas(ids, dateFormat) {
1497    let lastError = null;
1498    for (let attempt = 0; attempt < 3; attempt++) {
1499        try {
1500            const result = await $.ajax({
1501                type: 'POST',
1502                url: 'gettestdataforexport',
1503                cache: false,
1504                data: { ids: ids, date_format: dateFormat }
1505            });
1506            const parsed = JSON.parse(result);
1507            if (parsed && Array.isArray(parsed['testDatas'])) {
1508                return parsed['testDatas'];
1509            }
1510            lastError = new Error('invalid response');
1511        } catch (error) {
1512            lastError = error;
1513        }
1514        await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
1515    }
1516    throw lastError;
1517}
1518async function exportPdfInParts(data, titlePage, with_attachments) {
1519    const limits = getExportLimits();
1520    const testIds = data['test_ids'] || [];
1521    const hasTable = !!data['dataArray'] && data['dataArray'].length > 1;
1522    if (!hasTable && testIds.length == 0) {
1523        presentToast(translate['EXPORT_DATA']['NO_DATA_FOR_EXPORT'], 'fa-triangle-exclamation', 'warning');
1524        await hideLoading();
1525        return;
1526    }
1527    const header = hasTable ? data['dataArray'][0] : null;
1528    const rows = hasTable ? data['dataArray'].slice(1) : [];
1529    data['dataArray'] = null;
1530
1531    // Grobe Schaetzung, damit der Nutzer vorab weiss, was auf ihn zukommt
1532    const estimatedPages = (rows.length / 12) + (testIds.length * 2);
1533    const estimatedParts = Math.max(1, Math.ceil(Math.max(estimatedPages / limits.maxPages, testIds.length / limits.maxTests)));
1534    if (estimatedParts > 1) {
1535        await hideLoading();
1536        let message = translate['EXPORT_DATA']['SPLIT_MESSAGE'].replace('##', testIds.length || rows.length).replace('%%', estimatedParts);
1537        if ($('html').data('plt-android') || $('html').data('plt-ios')) {
1538            message += translate['EXPORT_DATA']['SPLIT_MESSAGE_APP'];
1539        }
1540        if (!await submitFormAfterConfirm(translate['EXPORT_DATA']['SPLIT_TITLE'], message)) {
1541            return;
1542        }
1543    }
1544
1545    exportCancelled = false;
1546    clearPdfImageCache();
1547    $('#my-loading-cancel').prop('disabled', false).show().find('span').text(translate['DISMISS']);
1548
1549    let partNumber = 1;
1550    let downloaded = 0;
1551    let part = null;
1552    let testsStartTime = 0;
1553    const progress = function (extraLine = '') {
1554        let lines = [translate['TEST']['FILE_WILL_BE_EXPORTED']];
1555        if (estimatedParts > 1) {
1556            lines.push(translate['EXPORT_DATA']['PART_PROGRESS'].replace('##', partNumber).replace('%%', Math.max(estimatedParts, partNumber)));
1557        }
1558        if (extraLine) {
1559            lines.push(extraLine);
1560        }
1561        return lines.join('<br />');
1562    };
1563    const getEta = function (done) {
1564        if (done < 3) {
1565            return '';
1566        }
1567        const remainingSeconds = (Date.now() - testsStartTime) / done * (testIds.length - done) / 1000;
1568        return (remainingSeconds >= 60) ? translate['EXPORT_DATA']['TIME_REMAINING'].replace('##', Math.ceil(remainingSeconds / 60)) : '';
1569    };
1570    const flushPart = async function (isLast) {
1571        downloaded++;
1572        let filename = data['filename'];
1573        if (!(isLast && downloaded == 1)) {
1574            filename = filename.replace(/\.pdf$/i, '') + '_' + translate['EXPORT_DATA']['PART'] + '_' + downloaded + '.pdf';
1575        }
1576        await showLoading(progress(translate['EXPORT_DATA']['SAVING_PART']));
1577        const bytes = await part.doc.save();
1578        part.doc = null;
1579        await downloadFile(new Blob([bytes], { type: data['mimetype'] }), filename, data['mimetype'], !isLast);
1580        if (!isLast) {
1581            await new Promise(resolve => setTimeout(resolve, 1000)); // Browser mehrere Downloads nacheinander erlauben
1582            partNumber++;
1583            part = await createExportPart();
1584        }
1585    };
1586
1587    try {
1588        part = await createExportPart();
1589        if (titlePage) {
1590            await addPdfToExportPart(part, titlePage);
1591        }
1592
1593        // 1. Zusammenfassung (Tabelle) in Stuecken
1594        for (let start = 0; start < rows.length; start += EXPORT_TABLE_CHUNK_ROWS) {
1595            checkExportCancelled();
1596            const chunkEnd = Math.min(start + EXPORT_TABLE_CHUNK_ROWS, rows.length);
1597            await showLoading(progress(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_3'].replace('##', start).replace('%%', rows.length)));
1598            const chunkBytes = await tableListToPDFMaker([header.slice()].concat(rows.slice(start, chunkEnd)), data['logo'], data['title'], data['ref_date'], data['show_ref_date'], {
1599                continuation: start > 0,
1600                quiet: true,
1601                asBytes: true,
1602                onProgress: (row) => showLoading(progress(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_5'].replace('##', start + row).replace('%%', rows.length)))
1603            });
1604            for (let k = start; k < chunkEnd; k++) {
1605                rows[k] = null;
1606            }
1607            await addPdfToExportPart(part, chunkBytes);
1608            if ((chunkEnd < rows.length || testIds.length > 0) && shouldCloseExportPart(part, limits)) {
1609                await flushPart(false);
1610            }
1611            await exportYield();
1612        }
1613
1614        // 2. Einzelne Pruefungen: in kleinen Paketen vom Server holen, einzeln als PDF bauen und sofort anhaengen
1615        testsStartTime = Date.now();
1616        for (let start = 0; start < testIds.length; start += EXPORT_TEST_BATCH) {
1617            checkExportCancelled();
1618            const testDatas = await fetchExportTestDatas(testIds.slice(start, start + EXPORT_TEST_BATCH), data['date_format']);
1619            for (let k = 0; k < testDatas.length; k++) {
1620                checkExportCancelled();
1621                const done = start + k;
1622                await showLoading(progress(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_4'].replace('##', done).replace('%%', testIds.length) + '<br />' + getEta(done)));
1623                const testBytes = await testToPDF(testDatas[k], data['logo'], with_attachments, { asBytes: true });
1624                testDatas[k] = null;
1625                await addPdfToExportPart(part, testBytes, 1);
1626                if ((done + 1 < testIds.length) && shouldCloseExportPart(part, limits)) {
1627                    await flushPart(false);
1628                }
1629            }
1630            await exportYield();
1631        }
1632
1633        await flushPart(true);
1634        if (downloaded > 1) {
1635            presentToast(translate['EXPORT_DATA']['FINISHED_PARTS'].replace('##', downloaded), 'fa-save', 'success');
1636        }
1637    } catch (error) {
1638        if (error && error.message === 'EXPORT_CANCELLED') {
1639            presentToast(translate['EXPORT_DATA']['CANCELLED'], 'fa-ban', 'warning');
1640        } else {
1641            console.log(error);
1642            presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'warning');
1643        }
1644        await hideLoading();
1645    } finally {
1646        if (part) {
1647            part.doc = null;
1648        }
1649        $('#my-loading-cancel').hide();
1650        clearPdfImageCache();
1651    }
1652}
1653$(document).on('click', '#confirm-export', async function (e) {
1654    let popup = $(this).parents('.my-popup');
1655    const tableName = getListTableName();
1656    let ids = getExportIds();
1657    let titlePage = '';
1658    let with_attachments = false;
1659    if (popup.find('select[name="file_type"]').val() == 'PDF') {
1660        if (popup.find('input[name="with_title_page"]').prop('checked')) {
1661            try {
1662                titlePage = await getTitlePagePDF();
1663            } catch (error) {
1664                return;
1665            }
1666        }
1667        if (popup.find('input[name="with_attachments"]').prop('checked')) {
1668            with_attachments = true;
1669        }
1670    }
1671    await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED_STEP_1']);
1672    popup.find('.close-and-remove-popup').trigger('click');
1673    $.ajax({
1674        type: 'POST',
1675        url: 'export',
1676        cache: false,
1677        data: {
1678            data: JSON.stringify(popup.find('form').serializeArray()),
1679            tableName: tableName,
1680            ids: ids,
1681            from_cart: $('#cart-popup').length > 0 ? 1 : 0
1682        },
1683        success: async function (result) {
1684            try {
1685                let data = JSON.parse(result);
1686                let base64 = '';
1687                if (data['type'] == 'PDF') {
1688                    if (tableName != 'main_bill') {
1689                        await exportPdfInParts(data, titlePage, with_attachments);
1690                        return;
1691                    }
1692                    if (data['dataArray'].length > 1) {
1693                        await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED']);
1694                        base64 = await tableListToPDFMaker(data['dataArray'], data['logo'], data['title'], data['ref_date'], data['show_ref_date']);
1695                        if (data['summary'] == '1') {
1696                            data['detailsPdfs'] = data['detailsPdfs'].map(x => base64ToArrayBuffer(x));
1697                            data['detailsPdfs'].unshift(base64);
1698                            base64 = await mergePdfs(data['detailsPdfs']);
1699                        } else if (data['summary'] == '0') {
1700                            data['detailsPdfs'] = data['detailsPdfs'].map(x => base64ToArrayBuffer(x));
1701                            base64 = await mergePdfs(data['detailsPdfs']);
1702                        }
1703                    }
1704                } else {
1705                    await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED']);
1706                    base64 = data['base64'];
1707                }
1708                if (base64) {
1709                    if (titlePage) {
1710                        base64 = await mergePdfs([titlePage, base64]);
1711                    }
1712                    await downloadFile(base64, data['filename'], data['mimetype']);
1713                } else {
1714                    presentToast(translate['EXPORT_DATA']['NO_DATA_FOR_EXPORT'], 'fa-triangle-exclamation', 'warning');
1715                    await hideLoading();
1716                }
1717
1718            } catch (err) {
1719                await hideLoading();
1720                console.log(err);
1721                console.log(result);
1722            }
1723        },
1724    });
1725});
1726// END - EXPORT
1727// REFRESHER
1728var mouseY = 0;
1729var startMouseY = 0;
1730var offsetRefresh = 0;
1731$(document).on('mousedown touchstart', function (ev) {
1732    if (ev.pageY === undefined) {
1733        mouseY = ev.touches[0].pageY;
1734    } else {
1735        mouseY = ev.pageY;
1736    }
1737    startMouseY = mouseY;
1738    $(document).on('mousemove touchmove', function (e) {
1739        if (mouseY < ($(window).height() * 0.10)) {
1740            let pageY = 0;
1741            if (e.pageY === undefined) {
1742                pageY = e.touches[0].pageY;
1743            } else {
1744                pageY = e.pageY;
1745            }
1746            if (pageY > mouseY) {
1747                offsetRefresh = pageY - startMouseY;
1748                let percentage = 100 / ($(window).height() * 0.25) * offsetRefresh;
1749                if (offsetRefresh < ($(window).height() * 0.25)) {
1750                    $('.refresher i').removeClass('rotate');
1751                    $('.refresher').css('top', offsetRefresh / 2.5 + 'px');
1752                    $('.refresher').css('opacity', percentage / 100);
1753                    $('.refresher i').css('transform', 'rotate(' + (3.6 * percentage) + 'deg)');
1754                    $('body').css('margin-top', offsetRefresh / 3 + 'px');
1755                } else {
1756                    $('.refresher i').addClass('rotate');
1757                }
1758            } else {
1759                $(document).unbind("mousemove");
1760            }
1761        }
1762    });
1763});
1764$('html').on('mouseup touchend', function (ev) {
1765    if (offsetRefresh > ($(window).height() * 0.25)) {
1766        location.reload();
1767    }
1768    $('.refresher').css('top', '0px');
1769    $('.refresher').css('opacity', '0');
1770    $('.refresher i').css('transform', 'rotate(0deg)');
1771    $('body').css('margin-top', '0px');
1772    $(document).unbind("mousemove");
1773});
1774$('html').on('mouseleave', function (ev) {
1775    if (offsetRefresh > ($(window).height() * 0.25)) {
1776        location.reload();
1777    }
1778    $('.refresher').css('top', '0px');
1779    $('.refresher').css('opacity', '0');
1780    $('.refresher i').css('transform', 'rotate(0deg)');
1781    $('body').css('margin-top', '0px');
1782    $(document).unbind("mousemove");
1783});
1784
1785// FORM FUNCTIONS 
1786function getFormData($form) {
1787    var unindexed_array = $form.serializeArray();
1788    var indexed_array = {};
1789
1790    $.map(unindexed_array, function (n, i) {
1791        if (n['name'].includes('[]')) {
1792            if (indexed_array[n['name']]) {
1793                indexed_array[n['name']].push(n['value']);
1794            } else {
1795                indexed_array[n['name']] = [n['value']];
1796            }
1797        } else {
1798            indexed_array[n['name']] = n['value'];
1799        }
1800    });
1801
1802    return indexed_array;
1803}
1804$(document).on('focus', 'input[readonly].remove-read-only', function () {
1805    if ($(this).attr('readonly')) {
1806        $(this).prop('readonly', false);
1807        $(this).blur();
1808        $(this).focus();
1809    }
1810});
1811
1812$(document).on('input', '.my-input input[type="search"]', function () {
1813    updateClearSearch($(this));
1814});
1815if ($('.my-input input[type="search"]').length > 0) {
1816    $('.my-input input[type="search"]').each(function (idx, el) {
1817        updateClearSearch($(el));
1818    });
1819}
1820function updateClearSearch(el) {
1821    if (el.length > 0) {
1822        let value = el.val();
1823        if (value.length > 0) {
1824            el.parents('.my-input').find('.clear-search').show();
1825        } else {
1826            el.parents('.my-input').find('.clear-search').hide();
1827        }
1828    }
1829}
1830
1831$(document).on('input', '.my-input input', function () {
1832    $(this).parents('.my-input').removeClass('error');
1833});
1834$(document).on('input', '.my-input.time-input input[type="text"]', function () {
1835    setTimeInputValues($(this).parents('.my-input'));
1836});
1837$(document).on('input', '.my-input select', function () {
1838    $(this).parents('.my-input').removeClass('error');
1839});
1840$(document).on('input', '.my-input.time-input select', function () {
1841    setTimeInputValues($(this).parents('.my-input'));
1842});
1843function setTimeInputValues(inputEl) {
1844    let val = inputEl.find('input[type="text"]').val();
1845    val = val.replace(/[^0-9]+/g, '');
1846    const divisor = inputEl.find('select option:selected').data('divisor');
1847    const minuteVal = divisor * val;
1848    inputEl.find('input[type="hidden"]').val(minuteVal);
1849    inputEl.find('input[type="text"]').val(val);
1850    if (minuteVal > 2147483647) {
1851        let errorMessage = inputEl.find('.input-error').data('error1');
1852        errorMessage = errorMessage.replace('#', (Math.floor(2147483647 / divisor) + ' ' + inputEl.find('select option:selected').text()));
1853        inputEl.find('.input-error').text(errorMessage);
1854        inputEl.addClass('error');
1855    }
1856}
1857async function saveFormCheckBeforeUnload() {
1858    $('form.check-before-unload').data('serialize', $('form.check-before-unload').seri
1858alize());
1859}
1860let skipBeforeUnload = false;
1861$(window).bind('pagehide beforeunload', function (e) {
1862    if (skipBeforeUnload) {
1863        skipBeforeUnload = false;
1864        return;
1865    }
1866    if ($('.my-table-list').length > 0) {
1867        if ($('.my-table-list').data('filter-article_id') !== undefined) {
1868            delete filter['article_id'];
1869            updateFilter('article_id', "");
1870        }
1871    }
1872
1873    if ($('form.check-before-unload').length > 0) {
1874        if ($('form.check-before-unload').serialize() != $('form.check-before-unload').data('serialize')) {
1875            if ($('form.save-in-local-storage').length > 0) {
1876                if ($('form.save-in-local-storage [name="edit"]').val() != '1') {
1877                    if ($('form.save-in-local-storage [name="is_ref_id"]').length <= 0) {
1878                        localStorage.setItem($('form.save-in-local-storage').attr('id'), JSON.stringify(getFormData($('form.save-in-local-storage'))));
1879                    } else {
1880                        return true;
1881                    }
1882                }
1883            } else {
1884                return true;
1885            }
1886        }
1887    }
1888    $('#loading-bar').show();
1889    e = null;
1890});
1891
1892$(document).on('click', '.popup-select', async function () {
1893    const inputEl = $(this);
1894    const popupSelectType = inputEl.data('popup-select');
1895    if (popupSelectType == 'parent_dir_id') {
1896        const currentId = inputEl.find('input[type="hidden"].main-val').val();
1897        const targetDirID = $('input[name="current_id"]').val();
1898        openmoveelementstodirpopup({ dir_id: currentId, targetDirID: targetDirID });
1899        $(document).off('click', '#move-elements-to-dir-popup #confirm_move').on('click', '#move-elements-to-dir-popup #confirm_move', async function (e) {
1900            let dirID = getCurrentDirFromMoveElementsPopup();
1901            let dirName = getCurrentDirNameFromMoveElementsPopup();
1902            inputEl.find('input[type="hidden"].main-val').val(dirID).change();
1903            inputEl.find('input[type="text"]').val(dirName).change();
1904            inputEl.removeClass('error');
1905            hidePopUp($('#move-elements-to-dir-popup'));
1906            $('#move-elements-to-dir-popup').remove();
1907        });
1908    } else {
1909        await getPopUpSelectValue(inputEl);
1910    }
1911});
1912$(document).on('keydown', '.my-input', function (e) {
1913    if (!$(this).hasClass('textarea') && e.target.tagName.toLowerCase() !== 'textarea') {
1914        if (e.key === "Enter") {
1915            e.preventDefault();
1916        }
1917    }
1918});
1919async function getPopUpSelectValue(inputEl) {
1920    return new Promise(async (resolve, reject) => {
1921        const currentId = inputEl.find('input[type="hidden"].main-val').val();
1922        const popupSelectType = inputEl.data('popup-select');
1923        const title = inputEl.find('label').text();
1924        const triggerClick = inputEl.attr('id');
1925        await getAjaxPopUp('getpopupselect', { 'type': popupSelectType, 'currentId': currentId, 'title': title });
1926        $('#select-popup #search').focus();
1927        setTimeout(() => {
1928            if ($('#select-popup input:checked').length > 0) {
1929                $('#select-popup .popup-body').animate({
1930                    scrollTop: $('#select-popup input:checked').parents('.element').position().top - ($('#select-popup .popup-header').outerHeight() + $('#select-popup .search-wrapper').outerHeight() + $(window).height() * 0.05)
1931                }, 200);
1932            }
1933        }, 300);
1934
1935        $('#select-popup #confirm').click(async function (e) {
1936            let id = $('#select-popup input:checked').val();
1937            inputEl.find('input[type="hidden"].main-val').val(id).change();
1938            if (inputEl.find('input[type="hidden"].random-id').length > 0)
1939                inputEl.find('input[type="hidden"].random-id').val($('#select-popup input:checked').parents('.element').find('.bg-img').data('random-id')).change();
1940            else if ($('.prev-img').length > 0) {
1941                if ($('.prev-img + input[type="hidden"]').val() == '' || $('.prev-img + input[type="hidden"]').length == 0) {
1942                    $('.prev-img').css('background-image', $('#select-popup input:checked').parents('.element').find('.bg-img.change-img').css('background-image'));
1943                    inputEl.find('input[type="hidden"].img-value').val($('#select-popup input:checked').parents('.element').find('.bg-img.change-img').css('background-image')).change();
1944                }
1945            }
1946            inputEl.find('input[type="text"]').val($('#select-popup input:checked').parents('.element').find('.name').text()).change();
1947            $('#select-popup').find('.close-and-remove-popup').trigger('click');
1948            inputEl.removeClass('error');
1949            resolve(id);
1950        });
1951        let justCheckedOne = $('#select-popup .element input[type="radio"]:checked').length > 0;
1952        $('#select-popup .element input[type="radio"]').change(async function (e) {
1953            if (!justCheckedOne) {
1954                $('#select-popup #confirm').trigger('click');
1955            }
1956        });
1957        $('#select-popup #add_new').click(async function (e) {
1958            const functionName = $(this).data('function-name')
1959            window[functionName]('?redirect=' + encodeURI(window.location.pathname.replace($('base').attr('href'), '') + '?triggerclick=' + triggerClick));
1960        });
1961        $('#select-popup .clear-search').click(function (e) {
1962            $(this).parents('.my-input').find('input[type="search"]').val('');
1963            let allElements = $('#select-popup .elements .element');
1964            allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#select-popup .elements");
1965            $('#select-popup .popup-body').animate({
1966                scrollTop: 0
1967            }, 200);
1968            if (!isTouchDevice()) {
1969                $(this).parents('.my-input').find('input[type="search"]').focus();
1970            }
1971        });
1972        $('#select-popup #search').on('input', function (e) {
1973            const val = $(this).val().toLowerCase();
1974            let allElements = $('#select-popup .elements .element');
1975            if (val.length > 0) {
1976                allElements.each((idx, el) => {
1977                    const name = $(el).find('.name').text().toLowerCase();
1978                    if (name.includes(val)) {
1979                        $(el).attr('data-order', 0);
1980                    } else {
1981                        $(el).attr('data-order', levenshteinDistance(val, name));
1982                    }
1983                });
1984                allElements.sort((a, b) => $(a).attr("data-order") - $(b).attr("data-order")).appendTo("#select-popup .elements");
1985            } else {
1986                allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#select-popup .elements");
1987            }
1988            $('#select-popup .popup-body').animate({
1989                scrollTop: 0
1990            }, 200);
1991        })
1992    });
1993}
1994function resetFormCheckBeforeUnload() {
1995    $('form.check-before-unload').data('serialize', $('form.check-before-unload').seri
1995alize());
1996    if ($('form.save-in-local-storage').length > 0) {
1997        if ($('form.save-in-local-storage [name="edit"]').val() != '1') {
1998            if ($('form.save-in-local-storage [name="is_ref_id"]').length <= 0) {
1999                localStorage.removeItem($('form.save-in-local-storage').attr('id'));
2000            }
2001        }
2002    }
2003    return true;
2004}
2005
2006function submitForm(form) {
2007    $(form).find('input[type="submit"]').prop('disabled', true);
2008    form.submit();
2009    return true;
2010}
2011
2012$('input[type="color"]').on('input', function (e) {
2013    let tmpColor = $(e.target).val();
2014    if (/^#[0-9A-F]{6}$/i.test(tmpColor)) {
2015        const hex = tmpColor;
2016        const r = parseInt(hex.replace('#', '').substr(0, 2), 16);
2017        const g = parseInt(hex.replace('#', '').substr(2, 2), 16);
2018        const b = parseInt(hex.replace('#', '').substr(4, 2), 16);
2019        if (!(r > 230 && g > 230 && b > 230)) {
2020            changeTheme(tmpColor);
2021            $('#color-value').val(tmpColor);
2022        }
2023    } else {
2024        $(e.target).val('#3880ff');
2025        $('#color-value').val('#3880ff');
2026        changeTheme('#3880ff');
2027    }
2028});
2029$('#color-value').on('input', function () {
2030    let tmpColor = $(this).val();
2031    if (/^#[0-9A-F]{6}$/i.test(tmpColor)) {
2032        const hex = tmpColor;
2033        const r = parseInt(hex.replace('#', '').substr(0, 2), 16);
2034        const g = parseInt(hex.replace('#', '').substr(2, 2), 16);
2035        const b = parseInt(hex.replace('#', '').substr(4, 2), 16);
2036        if (!(r > 230 && g > 230 && b > 230)) {
2037            changeTheme(tmpColor);
2038            $('input[type="color"]').val(tmpColor);
2039        }
2040    } else if (tmpColor == '') {
2041        $('input[type="color"]').val('#3880ff');
2042        changeTheme('#3880ff');
2043    } else {
2044        $('input[type="color"]').val('#3880ff');
2045        changeTheme('#3880ff');
2046    }
2047});
2048$('#color-value').on('blur', function () {
2049    let tmpColor = $(this).val();
2050    if (/^#[0-9A-F]{6}$/i.test(tmpColor)) {
2051        const hex = tmpColor;
2052        const r = parseInt(hex.replace('#', '').substr(0, 2), 16);
2053        const g = parseInt(hex.replace('#', '').substr(2, 2), 16);
2054        const b = parseInt(hex.replace('#', '').substr(4, 2), 16);
2055        if (!(r > 230 && g > 230 && b > 230)) {
2056            changeTheme(tmpColor);
2057            $('input[type="color"]').val(tmpColor);
2058        }
2059    } else if (tmpColor == '') {
2060        $('input[type="color"]').val('#3880ff');
2061        $(this).val('#3880ff');
2062        changeTheme('#3880ff');
2063    } else {
2064        $('input[type="color"]').val('#3880ff');
2065        $(this).val('#3880ff');
2066        changeTheme('#3880ff');
2067    }
2068});
2069function changeTheme(colorString = "#3880ff") {
2070    let color = tinycolor(colorString);
2071    let colorContrast = tinycolor(color.isLight() ? '#000000' : '#ffffff');
2072    let colorShade = tinycolor(colorString).darken(8).toString();
2073    let colorTint = tinycolor(colorString).lighten(8).toString();
2074    let colorLight = tinycolor(colorString).lighten(35).toString();
2075    let colorMedium = tinycolor(colorString).lighten(15).toString();
2076    if ((color._r < 150 && color._g < 150 && color._b < 150)) {
2077        colorLight = tinycolor(colorString).lighten(50).toString();
2078        colorMedium = tinycolor(colorString).lighten(25).toString();
2079    }
2080    if ((color._r < 80 && color._g < 80 && color._b < 80)) {
2081        colorLight = tinycolor(colorString).lighten(70).toString();
2082        colorMedium = tinycolor(colorString).lighten(35).toString();
2083    }
2084    if (isDarkMode) {
2085        colorLight = tinycolor(colorString).darken(40).toString();
2086        colorMedium = tinycolor(colorString).darken(20).toString();
2087        if ((color._r > 150 && color._g > 150 && color._b > 150)) {
2088            colorLight = tinycolor(colorString).darken(50).toString();
2089            colorMedium = tinycolor(colorString).darken(25).toString();
2090        }
2091        if ((color._r > 80 && color._g > 80 && color._b > 80)) {
2092            colorLight = tinycolor(colorString).darken(70).toString();
2093            colorMedium = tinycolor(colorString).darken(35).toString();
2094        }
2095    }
2096    document.documentElement.style.setProperty('--color-primary-medium', colorMedium);
2097    document.documentElement.style.setProperty('--color-primary-light', colorLight);
2098    document.documentElement.style.setProperty('--color-primary', color);
2099    document.documentElement.style.setProperty('--color-primary-contrast', colorContrast);
2100    document.documentElement.style.setProperty('--color-primary-shade', colorShade);
2101    document.documentElement.style.setProperty('--color-primary-tint', colorTint);
2102    // document.querySelector('meta[name="theme-color"]').setAttribute("content", color.hex());
2103    // document.querySelector('meta[name="msapplication-navbutton-color"]').setAttribute("content", color.hex());
2104    // document.querySelector('meta[name="apple-mobile-web-app-status-bar-style"]').setAttribute("content", color.hex());
2105}
2106$(document).on('click', '.my-input .multiple-input .input-item .remove', function (e) {
2107    if ($(this).parents('.input-item').find('.value').attr('data-col-name') != undefined) {
2108        let name = $(this).parents('.input-item').find('.value').text();
2109        let colname = $(this).parents('.input-item').find('.value').attr('data-col-name');
2110        let newOption = document.createElement('option');
2111        newOption.value = colname;
2112        newOption.textContent = name;
2113        $(this).parents('.my-input').find('.tmp-input select').append(newOption);
2114        let options = $(this).parents('.my-input').find('.tmp-input select option:not([value=""])');
2115        let arr = options.map(function (_, o) { return { t: $(o).text(), v: o.value }; }).get();
2116        arr.sort(function (o1, o2) { return o1.t > o2.t ? 1 : o1.t < o2.t ? -1 : 0; });
2117        options.each(function (i, o) {
2118            o.value = arr[i].v;
2119            $(o).text(arr[i].t);
2120        });
2121        $(this).parents('.my-input').find('.tmp-input').show();
2122    }
2123    let tmpEl = $(this).parents('.multiple-input');
2124    $(this).parents('.input-item').remove();
2125    updateMultipleInputValue(tmpEl);
2126});
2127$('.my-input #add_value').click(function () {
2128    addValueToMultipleInput($(this).parents('.my-input'));
2129});
2130function addValueToMultipleInput(myInputEL) {
2131    let tmpInputEl = myInputEL.find('.tmp-input');
2132    let inputEl = '';
2133    if (tmpInputEl.prop("tagName") == 'INPUT') {
2134        inputEl = myInputEL.find('input.tmp-input');
2135    } else {
2136        inputEl = myInputEL.find('select');
2137    }
2138    const val = inputEl.val();
2139    let error = false;
2140    if (inputEl.attr('type') == 'email') {
2141        re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
2142        if (!re.test(val.trim())) {
2143            error = true;
2144        }
2145    }
2146    if (!error) {
2147        let newInputItem = document.createElement('div');
2148        newInputItem.className = 'input-item';
2149
2150        let newInputItemValue = document.createElement('span');
2151        newInputItemValue.className = 'value';
2152        if (tmpInputEl.prop("tagName") == 'INPUT') {
2153            newInputItemValue.textContent = val.trim();
2154        } else {
2155            newInputItemValue.setAttribute('data-col-name', val);
2156            newInputItemValue.textContent = inputEl.find('option[value="' + val + '"]').text();
2157        }
2158        newInputItem.appendChild(newInputItemValue);
2159
2160        let newInputItemRemove = document.createElement('span');
2161        newInputItemRemove.className = 'remove';
2162        newInputItemRemove.innerHTML = '<i class="fa-solid fa-xmark"></i>';
2163        newInputItem.appendChild(newInputItemRemove);
2164        tmpInputEl.before(newInputItem);
2165        if (tmpInputEl.prop("tagName") == 'INPUT') {
2166            inputEl.val('');
2167        } else {
2168            inputEl.find('option[value="' + val + '"]').remove();
2169            if (inputEl.find('option').length <= 1) {
2170                tmpInputEl.hide();
2171            }
2172        }
2173        $(this).parents('.my-input').find('#add_value').addClass('hide-me');
2174        updateMultipleInputValue(inputEl.parents('.multiple-input'));
2175    } else {
2176        let inputWrapperEl = inputEl.parents('.my-input');
2177        inputWrapperEl.addClass('error');
2178        inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
2179        inputEl.focus();
2180    }
2181}
2182$('.my-input .multiple-input input.tmp-input').on('keyup', function (e) {
2183    const val = $(this).val();
2184    if (val.length > 0) {
2185        $(this).parents('.my-input').find('#add_value').removeClass('hide-me');
2186    } else {
2187        $(this).parents('.my-input').find('#add_value').addClass('hide-me');
2188    }
2189});
2190$('.my-input .multiple-input input.tmp-input').on('keydown', function (e) {
2191    const val = $(this).val();
2192    if (e.key === "Backspace") {
2193        if (val.length == 0) {
2194            $(this).parents('.multiple-input').find('.input-item:last-of-type').remove();
2195            updateMultipleInputValue($(this).parents('.multiple-input'));
2196        }
2197    } else if (e.key === "Enter") {
2198        e.preventDefault();
2199        addValueToMultipleInput($(this).parents('.my-input'));
2200    }
2201});
2202$(document).on('change', '.my-input .multiple-input .tmp-input select', function (e) {
2203    addValueToMultipleInput($(this).parents('.my-input'));
2204})
2205function updateMultipleInputValue(el) {
2206    let tmpInputEl = el.find('.tmp-input');
2207    let get_only_select = tmpInputEl.data('get_only_select');
2208    let values = [];
2209    if (tmpInputEl.prop("tagName") == 'INPUT') {
2210        el.find('.input-item').each((idx, item) => {
2211            values.push($(item).find('.value').text());
2212        });
2213    } else {
2214        if (get_only_select === true) {
2215            el.find('.input-item').each((idx, item) => {
2216                values.push($(item).find('.value').data('col-name'));
2217            });
2218        } else {
2219            values = el.find('select option').map((idx, el) => ($(el).val() !== '') ? $(el).val() : null).toArray()
2220        }
2221    }
2222    el.find('input[type="hidden"]').val(values.join(","));
2223}
2224$(document).on('click', '.my-input label+i,.my-input label+svg', function (e) {
2225    $(this).parents('.my-input').find('label').trigger('click');
2226});
2227
2228$('input[type="range"]').on('input', function (e) {
2229    let target = $(e.target);
2230    let value = Math.round(target.val() * 100);
2231    target.css('--value', value + '%');
2232    target.find('+label').html(target.find('+label').data('text').replace('#', value));
2233});
2234
2235let currentImageEl = null;
2236$('.my-image button').click(async function (e) {
2237    let target = $(this).parents('.my-image');
2238    target.removeClass('error');
2239    const onlySvg = target.hasClass('only-svg');
2240    currentImageEl = target;
2241    let result = await getImage(target, onlySvg);
2242    if (result !== null && result != 'remove') {
2243        target.find('.prev-img').css('background-image', 'url(' + result + ')');
2244        target.find('input.img-value').val(result);
2245        target.find('input.for-delete').val('0');
2246    } else if (result === 'remove') {
2247        target.find('.prev-img').css('background-image', 'url(' + target.find('.prev-img').attr('data-default') + ')');
2248        target.find('input.img-value').val('');
2249        target.find('input.for-delete').val('1');
2250    }
2251});
2252
2253function imageInputSetData(target, imageData) {
2254    target.find('.prev-img').css('background-image', 'url(' + imageData + ')');
2255    target.find('input.img-value').val(imageData);
2256    target.find('input.for-delete').val('0');
2257}
2258$('.login-form input').on('keydown', function (e) {
2259    if (e.key === "Enter") {
2260        $('.login-form button[type="submit"]').trigger('click');
2261    }
2262});
2263// END - FORM FUNCTIONS 
2264
2265// NFC 
2266let lastFocusedElement;
2267$(document).on('focus', '.field-for-focus', function (e) {
2268    lastFocusedElement = $(this);
2269});
2270$(document).on('click', '#scan-nfc', async function (e) {
2271    let nfc_tag = await getNFC();
2272    let target = $(this).data('target');
2273    writeNFCOrQRCodeToEl(nfc_tag, target);
2274});
2275function normalizeScannedCode(code) {
2276    if (code.includes('Ser. No.')) {
2277        code = code.replace(/(\r\n|\n|\r)/gm, "");
2278        code = code.replace('Ser. No.: ', '');
2279        code = code.substr(0, code.indexOf(' Model'));
2280    }
2281    return code;
2282}
2283function writeNFCOrQRCodeToEl(code, target) {
2284    if (code !== '') {
2285        code = normalizeScannedCode(code);
2286        if (code !== '') {
2287            if (target == 'search') {
2288                $('.my-table-list-filter-search').val(code);
2289                $('.my-auto-complete').addClass('hide-me');
2290                updateFilter('searchStr', encodeURI($('.my-table-list-filter-search').val()));
2291                updateClearSearch($('.my-table-list-filter-search'));
2292                getTableList(false, true);
2293            }
2294            if (target == 'focus-or-nfc-id') {
2295                let el;
2296                if (lastFocusedElement) {
2297                    el = lastFocusedElement;
2298                } else {
2299                    if ($('[name="nfc_id"]').length > 0) {
2300                        el = $('[name="nfc_id"]');
2301                    }
2302                }
2303                if (el) {
2304                    let splittedCode = code.split('');
2305                    writeSplittedCodeToEl(el, splittedCode);
2306                }
2307            }
2308            if (target == 'focus-or-customer_number') {
2309                let el;
2310                if (lastFocusedElement) {
2311                    el = lastFocusedElement;
2312                } else {
2313                    if ($('[name="customer_number"]').length > 0) {
2314                        el = $('[name="customer_number"]');
2315                    }
2316                }
2317                if (el) {
2318                    let splittedCode = code.split('');
2319                    writeSplittedCodeToEl(el, splittedCode);
2320                }
2321            }
2322            if (target == 'hot-cell') {
2323                let splittedCode = code.split('');
2324                writeSplittedCodeToHot(splittedCode);
2325            }
2326        }
2327    }
2328}
2329let writeSplittedCell = null;
2330async function writeSplittedCodeToHot(splittedCode, index = 0) {
2331    if (typeof writeSplittedCodeToTabulator === 'function' && typeof table !== 'undefined' && table !== null) {
2332        writeSplittedCodeToTabulator(splittedCode, index);
2333        return;
2334    }
2335    if (hot !== undefined) {
2336        if (lastHotCell !== undefined) {
2337            if (lastHotCell.col >= 0 && lastHotCell.row >= 0) {
2338                if (writeSplittedCell == null)
2339                    writeSplittedCell = lastHotCell;
2340                if (splittedCode.length > 0) {
2341                    await hot.setDataAtCell(writeSplittedCell.row, writeSplittedCell.col, splittedCode.filter((x, i) => i <= index).join(''));
2342                    if (index < splittedCode.length) {
2343                        setTimeout(() => {
2344                            writeSplittedCodeToHot(splittedCode, index + 1)
2345                        }, 40);
2346                    } else {
2347                        writeSplittedCell = null;
2348                    }
2349                }
2350            } else {
2351                presentToast(translate['TABULATOR_JS']['NO_CELL_SELECTED'], 'fa-triangle-exclamation', 'warning')
2352            }
2353        } else {
2354            presentToast(translate['TABULATOR_JS']['NO_CELL_SELECTED'], 'fa-triangle-exclamation', 'warning')
2355        }
2356    }
2357}
2358function writeSplittedCodeToEl(el, splittedCode) {
2359    if (splittedCode.length > 0) {
2360        let currVal = el.val() + splittedCode[0];
2361        el.focus();
2362        el.val(currVal);
2363        splittedCode.splice(0, 1);
2364        setTimeout(() => {
2365            writeSplittedCodeToEl(el, splittedCode)
2366        }, 40);
2367    }
2368}
2369$(document).on('click', '.nfc_unsubscribe', async function (e) {
2370    return new Promise(async (resolve, reject) => {
2371        if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2372            try {
2373                var messageObj = { function: 'unsubscribeNFC' };
2374                var stringifiedMessageObj = JSON.stringify(messageObj);
2375                webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
2376            } catch (error) {
2377            }
2378        }
2379        resolve(null);
2380    })
2381})
2382async function getNFC() {
2383    await showLoading();
2384    return new Promise(async (resolve, reject) => {
2385        if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2386            try {
2387                var messageObj = { function: 'scanNFC' };
2388                var stringifiedMessageObj = JSON.stringify(messageObj);
2389                webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
2390                if ($('html').data('plt-ios')) {
2391                    hideLoading();
2392                    let res = await getNFCFromApp();
2393                    if (res === false || res == '') {
2394                        await openNFCPopUp();
2395                    }
2396                    resolve(res);
2397                } else {
2398                    await openNFCPopUp();
2399                    hideLoading();
2400                    resolve(await getNFCFromApp());
2401                }
2402            } catch (error) {
2403                resolve(null);
2404            }
2405        } else {
2406            hideLoading();
2407            getAjaxPopUp('scannfc', { nfc_available: false });
2408        }
2409    });
2410}
2411async function openNFCPopUp() {
2412    return new Promise(async (resolve, reject) => {
2413        let nfc_available = $('html').attr('data-nfc-available');
2414        let error = $('html').attr('data-error');
2415        if ((nfc_available !== '' && nfc_available !== undefined) || (error !== '' && error !== undefined)) {
2416            if (nfc_available !== '' && nfc_available !== undefined) {
2417                getAjaxPopUp('scannfc', { nfc_available: (nfc_available === 'true') });
2418                resolve(true);
2419            } else if (error !== '' && error !== undefined) {
2420                $('html').attr('data-error', '');
2421                let errorMessage = translate[error];
2422                if (errorMessage == undefined) {
2423                    errorMessage = translate['NFC_QR']['NFC_ERROR'];
2424                }
2425                presentToast(errorMessage, 'fa-triangle-exclamation', 'warning');
2426                resolve(true);
2427            }
2428        } else {
2429            setTimeout(async () => {
2430                resolve(openNFCPopUp());
2431            }, 300);
2432        }
2433    });
2434}
2435async function getNFCFromApp() {
2436    return new Promise(async (resolve, reject) => {
2437        let nfc_tag = $('html').attr('data-nfc');
2438        let error = $('html').attr('data-error');
2439        if ((nfc_tag !== '' && nfc_tag !== undefined) || (error !== '' && error !== undefined)) {
2440            if (nfc_tag !== '' && nfc_tag !== undefined) {
2441                $('#scannfc-popup').find('.close-and-remove-popup').trigger('click');
2442                $('html').attr('data-nfc', '');
2443                resolve(nfc_tag);
2444            } else if (error !== '' && error !== undefined) {
2445                $('html').attr('data-error', '');
2446                let errorMessage = translate[error];
2447                if (errorMessage == undefined) {
2448                    errorMessage = translate['NFC_QR']['NFC_ERROR'];
2449                }
2450                presentToast(errorMessage, 'fa-triangle-exclamation', 'warning');
2451                resolve('');
2452            }
2453        } else {
2454            setTimeout(async () => {
2455                resolve(await getNFCFromApp());
2456            }, 300);
2457        }
2458    });
2459}
2460// END - NFC
2461// QR 
2462$(document).on('click', '#scan-qrcode', async function (e) {
2463    let qr_code = await getQRCode();
2464    let target = $(this).data('target');
2465    writeNFCOrQRCodeToEl(qr_code, target);
2466});
2467async function getQRCode() {
2468    return new Promise(async (resolve, reject) => {
2469        if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2470            try {
2471                await closeWebQr();
2472                var messageObj = { function: 'scanQR' };
2473                var stringifiedMessageObj = JSON.stringify(messageObj);
2474                webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
2475                resolve(await getQRFromApp());
2476            } catch (error) {
2477                console.error(error);
2478            }
2479        } else {
2480            resolve(await openWebQr());
2481        }
2482    });
2483}
2484
2485var qrCanScann = true;
2486var qrScannerActive = false;
2487
2488// QR/Barcode-Scanner-Engine: app/files/js/qr-scanner.js (zxing-wasm, Open Source).
2489// Erkennt invertierte Codes (z.B. weißer QR-Code auf grauem/dunklem Hintergrund)
2490// automatisch bei jedem Frame mit, kein separater "Invertieren"-Modus mehr nötig.
2491// onCode: Wenn gesetzt, bleibt die Kamera nach jedem Scan offen und onCode wird pro Code aufgerufen
2492async function openWebQr(onCode = null) {
2493    return new Promise(async (resolve, reject) => {
2494        qrCanScann = true;
2495        await showLoading();
2496        await getAjaxPopUp('getwebqr');
2497
2498        $('#useQRFromApp').on('click', async function (e) {
2499            await closeWebQr(false);
2500            if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2501                try {
2502                    var messageObj = { function: 'scanQR' };
2503                    var stringifiedMessageObj = JSON.stringify(messageObj);
2504                    webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
2505                    resolve(await getQRFromApp());
2506                } catch (error) {
2507                    resolve(null);
2508                }
2509            }
2510        });
2511        try {
2512            await window.QrScanner.start(document.getElementById('qr-video'), {
2513                onResult: async (decodedText) => {
2514                    if (decodedText.length !== 0 && qrCanScann) {
2515                        $('#qr-shaded-region').addClass('scanned');
2516                        beep();
2517                        qrCanScann = false;
2518                        if (onCode) {
2519                            await onCode(decodedText);
2520                            $('#qr-shaded-region').removeClass('scanned');
2521                            qrCanScann = true;
2522                            return;
2523                        }
2524                        closeWebQr();
2525                        resolve(decodedText);
2526                    }
2527                },
2528                onError: (err) => {
2529                    console.error('Fehler beim Scannen:', err);
2530                }
2531            });
2532            qrScannerActive = true;
2533
2534            $('#webqr-popup').addClass('show');
2535            if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2536                $('#useQRFromApp').removeClass('hide-me');
2537            }
2538            hideLoading();
2539        } catch (err) {
2540            hideLoading();
2541            console.error(`Fehler beim Starten des Scanners: ${err}`);
2542            if (err.toString().toLowerCase().includes('permission')) {
2543                presentToast(translate['ATTACHMENT']['WEB_PICUTRE_NO_PERMISSION'], 'fa-triangle-exclamation', 'warning');
2544            } else {
2545                presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'danger');
2546            }
2547            if ($('html').data('plt-android') || $('html').data('plt-ios')) {
2548                try {
2549                    await closeWebQr();
2550                    var messageObj = { function: 'scanQR' };
2551                    var stringifiedMessageObj = JSON.stringify(messageObj);
2552                    webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
2553                    resolve(await getQRFromApp());
2554                } catch (error) {
2555                    console.error(error);
2556                }
2557            } else {
2558                closeWebQr();
2559            }
2560        }
2561    });
2562}
2563$(document).on('click', '.close-webqr', function (e) {
2564    closeWebQr();
2565});
2566function beep() {
2567    let snd = new Audio("data:audio/wav;base64,//uQRAAAAWMSLwUIYAAsYkXgoQwAEaYLWfkWgAI0wWs/ItAAAGDgYtAgAyN+QWaAAihwMWm4G8QQRDiMcCBcH3Cc+CDv/7xA4Tvh9Rz/y8QADBwMWgQAZG/ILNAARQ4GLTcDeIIIhxGOBAuD7hOfBB3/94gcJ3w+o5/5eIAIAAAVwWgQAVQ2ORaIQwEMAJiDg95G4nQL7mQVWI6GwRcfsZAcsKkJvxgxEjzFUgfHoSQ9Qq7KNwqHwuB13MA4a1q/DmBrHgPcmjiGoh//EwC5nGPEmS4RcfkVKOhJf+WOgoxJclFz3kgn//dBA+ya1GhurNn8zb//9NNutNuhz31f////9vt///z+IdAEAAAK4LQIAKobHItEIYCGAExBwe8jcToF9zIKrEdDYIuP2MgOWFSE34wYiR5iqQPj0JIeoVdlG4VD4XA67mAcNa1fhzA1jwHuTRxDUQ//iYBczjHiTJcIuPyKlHQkv/LHQUYkuSi57yQT//uggfZNajQ3Vmz+Zt//+mm3Wm3Q576v////+32///5/EOgAAADVghQAAAAA//uQZAUAB1WI0PZugAAAAAoQwAAAEk3nRd2qAAAAACiDgAAAAAAABCqEEQRLCgwpBGMlJkIz8jKhGvj4k6jzRnqasNKIeoh5gI7BJaC1A1AoNBjJgbyApVS4IDlZgDU5WUAxEKDNmmALHzZp0Fkz1FMTmGFl1FMEyodIavcCAUHDWrKAIA4aa2oCgILEBupZgHvAhEBcZ6joQBxS76AgccrFlczBvKLC0QI2
2567cBoCFvfTDAo7eoOQInqDPBtvrDEZBNYN5xwNwxQRfw8ZQ5wQVLvO8OYU+mHvFLlDh05Mdg7BT6YrRPpCBznMB2r//xKJjyyOh+cImr2/4doscwD6neZjuZR4AgAABYAAAABy1xcdQtxYBYYZdifkUDgzzXaXn98Z0oi9ILU5mBjFANmRwlVJ3/6jYDAmxaiDG3/6xjQQCCKkRb/6kg/wW+kSJ5//rLobkLSiKmqP/0ikJuDaSaSf/6JiLYLEYnW/+kXg1WRVJL/9EmQ1YZIsv/6Qzwy5qk7/+tEU0nkls3/zIUMPKNX/6yZLf+kFgAfgGyLFAUwY//uQZAUABcd5UiNPVXAAAApAAAAAE0VZQKw9ISAAACgAAAAAVQIygIElVrFkBS+Jhi+EAuu+lKAkYUEIsmEAEoMeDmCETMvfSHTGkF5RWH7kz/ESHWPAq/kcCRhqBtMdokPdM7vil7RG98A2sc7zO6ZvTdM7pmOUAZTnJW+NXxqmd41dqJ6mLTXxrPpnV8avaIf5SvL7pndPvPpndJR9Kuu8fePvuiuhorgWjp7Mf/PRjxcFCPDkW31srioCExivv9lcwKEaHsf/7ow2Fl1T/9RkXgEhYElAoCLFtMArxwivDJJ+bR1HTKJdlEoTELCIqgEwVGSQ+hIm0NbK8WXcTEI0UPoa2NbG4y2K00JEWbZavJXkYaqo9CRHS55FcZTjKEk3NKoCYUnSQ0rWxrZbFKbKIhOKPZe1cJKzZSaQrIyULHDZmV5K4xySsDRKWOruanGtjLJXFEmwaIbDLX0hIPBUQPVFVkQkDoUNfSoDgQGKPekoxeGzA4DUvnn4bxzcZrtJyipKfPNy5w+9lnXwgqsiyHNeSVpemw4bWb9psYeq//uQZBoABQt4yMVxYAIAAAkQoAAAHvYpL5m6AAgAACXDAAAAD59jblTirQe9upFsmZbpMudy7Lz1X1DYsxOOSWpfPqNX2WqktK0DMvuGwlbNj44TleLPQ+Gsfb+GOWOKJoIrWb3cIMeeON6lz2umTqMXV8Mj30yWPpjoSa9ujK8SyeJP5y5mOW1D6hvLepeveEAEDo0mgCRClOEgANv3B9a6fikgUSu/DmAMATrGx7nng5p5iimPNZsfQLYB2sDLIkzRKZOHGAaUyDcpFBSLG9MCQALgAIgQs2YunOszLSAyQYPVC2YdGGeHD2dTdJk1pAHGAWDjnkcLKFymS3RQZTInzySoBwMG0QueC3gMsCEYxUqlrcxK6k1LQQcsmyYeQPdC2YfuGPASCBkcVMQQqpVJshui1tkXQJQV0OXGAZMXSOEEBRirXb
2567VRQW7ugq7IM7rPWSZyDlM3IuNEkxzCOJ0ny2ThNkyRai1b6ev//3dzNGzNb//4uAvHT5sURcZCFcuKLhOFs8mLAAEAt4UWAAIABAAAAAB4qbHo0tIjVkUU//uQZAwABfSFz3ZqQAAAAAngwAAAE1HjMp2qAAAAACZDgAAAD5UkTE1UgZEUExqYynN1qZvqIOREEFmBcJQkwdxiFtw0qEOkGYfRDifBui9MQg4QAHAqWtAWHoCxu1Yf4VfWLPIM2mHDFsbQEVGwyqQoQcwnfHeIkNt9YnkiaS1oizycqJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
2568    snd.play();
2569}
2570async function closeWebQr(anim = true) {
2571    if (anim)
2572        await hidePopUp($('#webqr-popup'));
2573    if (qrScannerActive) {
2574        window.QrScanner.stop();
2575        qrScannerActive = false;
2576    }
2577    $('#webqr-popup').remove();
2578}
2579async function getQRFromApp() {
2580    return new Promise(async (resolve, reject) => {
2581        let qr_code = $('html').attr('data-qr');
2582        let error = $('html').attr('data-error');
2583        if ((qr_code !== '' && qr_code !== undefined) || (error !== '' && error !== undefined)) {
2584            if (qr_code !== '' && qr_code !== undefined) {
2585                $('html').attr('data-qr', '');
2586                resolve(qr_code);
2587            } else if (error !== '' && error !== undefined) {
2588                $('html').attr('data-error', '');
2589                console.log(error);
2590                let errorMessage = translate['NFC_QR'][error];
2591                if (errorMessage == undefined) {
2592                    resolve(await openWebQr());
2593                } else {
2594                    presentToast(errorMessage, 'fa-triangle-exclamation', 'warning');
2595                }
2596                resolve('');
2597            }
2598        } else {
2599            setTimeout(async () => {
2600                resolve(await getQRFromApp());
2601            }, 300);
2602        }
2603    });
2604}
2605// END - QR
2606
2607// ATTACHMENT
2608let pdfDoc = null;
2609let pdfViewer = null;
2610let eventBus = null;
2611let linkService = null;
2612let currentScale = 1;  // wird nach pagesinit gesetzt
2613let minScale = 1;      // min Zoom = volle Breite
2614let currentPage = 1;
2615
2616// Öffnet Base64-PDF
2617async function openBase64Pdf(base64) {
2618    const base64Data = base64.split(',')[1];
2619    const pdfBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
2620
2621    const loadingTask = pdfjsLib.getDocument({ data: pdfBytes });
2622    pdfDoc = await loadingTask.promise;
2623
2624    eventBus = new pdfjsViewer.EventBus();
2625    linkService = new pdfjsViewer.PDFLinkService({ eventBus });
2626
2627    pdfViewer = new pdfjsViewer.PDFViewer({
2628        container: document.getElementById('viewerContainer'),
2629        viewer: document.getElementById('pdfViewer'),
2630        eventBus: eventBus,
2631        linkService: linkService
2632    });
2633
2634    linkService.setViewer(pdfViewer);
2635    pdfViewer.setDocument(pdfDoc);
2636    linkService.setDocument(pdfDoc, null);
2637
2638    // Vollbreite erst nach Seiteninitialisierung
2639    eventBus.on('pagesinit', function () {
2640        pdfViewer.currentPageNumber = currentPage;
2641        pdfViewer.currentScaleValue = 'page-fit'; // volle Breite
2642        currentScale = pdfViewer.currentScale;
2643        minScale = currentScale; // minScale merken
2644        $('#pageNumber').val(currentPage);
2645    });
2646
2647    $.scrollLock(true);
2648    $('#popup-pdf').fadeIn(300);
2649}
2650
2651// Toolbar Zoom
2652$('#zoomIn').click(() => {
2653    currentScale = Math.min(currentScale + 0.2, 5);
2654    pdfViewer.currentScale = currentScale;
2655});
2656
2657$('#zoomOut').click(() => {
2658    currentScale = Math.max(currentScale - 0.2, minScale);
2659    pdfViewer.currentScale = currentScale;
2660});
2661if ($('#viewerContainer').length > 0) {
2662    const container = document.getElementById('viewerContainer');
2663    // Touch-Pinch Zoom
2664    let lastDistance = null;
2665    let pinchCenterStart = null;
2666    let pinchZoomRAF = null;
2667    let startScroll = null;
2668
2669    container.addEventListener('touchstart', function (e) {
2670        if (e.touches.length === 2) {
2671            const midX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
2672            const midY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
2673            pinchCenterStart = { x: midX, y: midY };
2674            startScroll = { left: container.scrollLeft, top: container.scrollTop };
2675        }
2676    });
2677
2678    container.addEventListener('touchmove', function (e) {
2679        if (e.touches.length === 2 && pinchCenterStart && startScroll) {
2680            e.preventDefault();
2681            const dx = e.touches[0].clientX - e.touches[1].clientX;
2682            const dy = e.touches[0].clientY - e.touches[1].clientY;
2683            const distance = Math.sqrt(dx * dx + dy * dy);
2684
2685            if (lastDistance) {
2686                const delta = (distance - lastDistance) * 0.0015;
2687                const oldScale = currentScale;
2688                currentScale += delta;
2689                currentScale = Math.max(minScale, Math.min(5, currentScale));
2690
2691                if (!pinchZoomRAF) {
2692                    pinchZoomRAF = requestAnimationFrame(() => {
2693                        pdfViewer.currentScale = currentScale;
2694                        const rect = container.getBoundingClientRect();
2695                        const offsetX = pinchCenterStart.x - rect.left;
2696                        const offsetY = pinchCenterStart.y - rect.top;
2697                        container.scrollLeft = startScroll.left + (offsetX * (currentScale / oldScale - 1));
2698                        container.scrollTop = startScroll.top + (offsetY * (currentScale / oldScale - 1));
2699                        pinchZoomRAF = null;
2700                    });
2701                }
2702            }
2703            lastDistance = distance;
2704        }
2705    });
2706
2707    container.addEventListener('touchend', function (e) {
2708        if (e.touches.length < 2) {
2709            lastDistance = null;
2710            pinchCenterStart = null;
2711            startScroll = null;
2712            if (pinchZoomRAF) cancelAnimationFrame(pinchZoomRAF);
2713            pinchZoomRAF = null;
2714        }
2715    });
2716
2717    // Trackpad / Ctrl+Wheel Zoom
2718    let wheelZoomRAF = null;
2719
2720    container.addEventListener('wheel', function (e) {
2721        if (e.ctrlKey) {
2722            e.preventDefault();
2723
2724            const rect = container.getBoundingClientRect();
2725            const offsetX = e.clientX - rect.left + container.scrollLeft;
2726            const offsetY = e.clientY - rect.top + container.scrollTop;
2727
2728            const oldScale = currentScale;
2729            currentScale += e.deltaY * -0.0025; // kleiner = sanfter
2730            currentScale = Math.max(minScale, Math.min(5, currentScale));
2731
2732            // Verzögertes Update über requestAnimationFrame
2733            if (!wheelZoomRAF) {
2734                wheelZoomRAF = requestAnimationFrame(() => {
2735                    pdfViewer.currentScale = currentScale;
2736
2737                    const scaleRatio = currentScale / oldScale;
2738                    container.scrollLeft = offsetX * scaleRatio - (e.clientX - rect.left);
2739                    container.scrollTop = offsetY * scaleRatio - (e.clientY - rect.top);
2740
2741                    wheelZoomRAF = null;
2742                });
2743            }
2744        }
2745    });
2746}
2747// Close Button
2748$('#popup-pdf .close').click(() => {
2749    $('#popup-pdf').fadeOut(300).promise().done(function () {
2750        if (pdfViewer) {
2751            pdfViewer.cleanup();
2752            pdfViewer = null;
2753        }
2754    })
2755    $.scrollLock(false);
2756});
2757$(document).on('click', '.preview-attachment-in-lightbox', async function () {
2758
2759    let href = $(this).data('href');
2760    if (typeof href == "undefined") {
2761        let images = $(this).data('images')
2762        if (images) {
2763            images = images.split('#');
2764            let imageDatas = [];
2765            images.forEach(function (src, index) {
2766                imageDatas.push({ src: src, thumb: src });
2767            });
2768            lg = $(this).get(0);
2769            const dynamicGallery = lightGallery(lg, { download: !($('html').data('plt-android') || $('html').data('plt-ios')), dynamic: true, dynamicEl: imageDatas });
2770        }
2771    } else {
2772        await showLoading();
2773        $.ajax({
2774            url: href + '&for_ajax=1',
2775            success: async function (result) {
2776                try {
2777                    let data = JSON.parse(result);
2778                    if (data && data.base64) {
2779                        await openBase64Pdf(data.base64);
2780                    }
2781                } catch (err) {
2782                    console.error(err);
2783                } finally {
2784                    await hideLoading();
2785                }
2786            },
2787            error: function (err) { console.error(err); }
2788        });
2789    }
2790});
2791function addFilesToList(files) {
2792    let indexItem = $('#my-drop-container .upload-item').length;
2793    Array.prototype.forEach.call(files, async (file, index) => {
2794        try {
2795            let base64 = await readFileAsync(file);
2796            let re = /(?:\.([^.]+))?$/;
2797            let filename_extension = re.exec(file.name)[1];
2798            let grid = document.createElement('div');
2799            grid.className = 'grid-container upload-item';
2800            let wrapperImage = document.createElement('div');
2801            wrapperImage.className = 'wrapper-images';
2802            let wrapperGridValues = document.createElement('div');
2803            wrapperGridValues.className = 'wrapper-values';
2804            let gridValues = document.createElement('div');
2805            gridValues.className = 'grid-container';
2806            let wrapperName = document.createElement('div');
2807            wrapperName.className = 'col-12';
2808            let wrapperFilename = document.createElement('div');
2809            wrapperFilename.className = 'col-12';
2810            let wrapperInfo = document.createElement('div');
2811            wrapperInfo.className = 'my-attachment-infos';
2812
2813            let wrapperInfoFileSize = document.createElement('div');
2814            wrapperInfoFileSize.className = 'filesize';
2815            wrapperInfoFileSize.innerHTML = '<i class="fa-solid fa-database"></i> ' + humanFileSize(file.size);
2816            wrapperInfo.appendChild(wrapperInfoFileSize);
2817
2818            let wrapperInfoFileType = document.createElement('div');
2819            wrapperInfoFileType.className = 'filetype';
2820            wrapperInfoFileType.innerHTML = '<i class="fa-solid fa-file-circle-question"></i> ' + file.type;
2821            wrapperInfo.appendChild(wrapperInfoFileType);
2822
2823            if (["image/jpeg", "image/gif", "image/png", "image/jpg"].includes(file.type)) {
2824                let i = new Image();
2825                i.onload = function () {
2826                    let wrapperInfoImageSize = document.createElement('div');
2827                    wrapperInfoImageSize.className = 'imagesize';
2828                    wrapperInfoImageSize.innerHTML = '<i class="fa-solid fa-up-right-and-down-left-from-center"></i> ' + i.width + " x " + i.height + ' px';
2829                    wrapperInfo.appendChild(wrapperInfoImageSize);
2830                };
2831                i.src = base64;
2832            }
2833
2834            let thumbnail = document.createElement('div');
2835            thumbnail.className = 'thumbnail';
2836            let thumbnailChess = document.createElement('div');
2837            thumbnailChess.className = 'chess';
2838            let thumbnailBgImg = document.createElement('div');
2839            thumbnailBgImg.className = 'bg-img';
2840            let loadingSvg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
2841            loadingSvg.style.height = '100%';
2842            loadingSvg.style.width = '100%';
2843            loadingSvg.setAttribute("viewBox", "0 0 25 25");
2844
2845            let loadingSvgPath = document.createElementNS("http://www.w3.org/2000/svg", 'path');
2846            loadingSvgPath.setAttribute("fill", "none");
2847            loadingSvgPath.setAttribute("d", "M0 0 25 0 25 25 0 25 Z");
2848            if (file.type.includes("image/")) {
2849                thumbnailBgImg.style.backgroundImage = "url('" + base64 + "')";
2850            } else if (file.type.includes("video/")) {
2851                thumbnailBgImg.innerHTML = '<i class="fa-solid fa-video"></i>';
2852            } else if (file.type.includes("application/pdf")) {
2853                thumbnailBgImg.innerHTML = '<i class="fa-solid fa-file-pdf"></i>';
2854            } else if (file.type.includes("application/zip")) {
2855                thumbnailBgImg.innerHTML = '<i class="fa-solid fa-file-zipper"></i>';
2856            } else {
2857                thumbnailBgImg.innerHTML = '<i class="fa-solid fa-file"></i>';
2858            }
2859            thumbnailChess.appendChild(thumbnailBgImg);
2860            loadingSvg.appendChild(loadingSvgPath);
2861            thumbnailChess.appendChild(loadingSvg);
2862            thumbnail.appendChild(thumbnailChess);
2863            wrapperImage.appendChild(thumbnail);
2864
2865            let inputName = document.createElement('div');
2866            inputName.className = 'col-12';
2867            let inputNameMyInput = document.createElement('div');
2868            inputNameMyInput.className = 'my-input with-icon';
2869            let inputNameInputContainer = document.createElement('div');
2870            inputNameInputContainer.className = 'input-container';
2871            let inputNameInputError = document.createElement('div');
2872            inputNameInputError.className = 'input-error';
2873            inputNameInputError.setAttribute('data-error1', translate["ATTACHMENT"]["ERROR"]["NAME"]);
2874            let inputNameInput = document.createElement('input');
2875            inputNameInput.type = "text";
2876            inputNameInput.setAttribute('maxlength', "150");
2877            inputNameInput.setAttribute('name', "name_" + indexItem);
2878            inputNameInput.setAttribute('id', "name_" + indexItem);
2879            inputNameInput.value = file.name.replace(/\.[^/.]+$/, "").replaceAll('_', ' ').replaceAll('-', ' ');
2880            let inputNameLabel = document.createElement('label');
2881            inputNameLabel.setAttribute('for', "name_" + indexItem);
2882            inputNameLabel.innerHTML = translate["ATTACHMENT"]["NAME"];
2883            let inputNameIcon = document.createElement('i');
2884            inputNameIcon.className = 'fa-solid fa-signature';
2885
2886            inputName.appendChild(inputNameMyInput);
2887            inputNameMyInput.appendChild(inputNameInputContainer);
2888            inputNameMyInput.appendChild(inputNameInputError);
2889            inputNameInputContainer.appendChild(inputNameInput);
2890            inputNameInputContainer.appendChild(inputNameLabel);
2891            inputNameInputContainer.appendChild(inputNameIcon);
2892
2893            let inputFileName = document.createElement('div');
2894            inputFileName.className = 'col-12';
2895            let inputFileNameMyInput = document.createElement('div');
2896            inputFileNameMyInput.className = 'my-input with-icon';
2897            let inputFileNameInputContainer = document.createElement('div');
2898            inputFileNameInputContainer.className = 'input-container';
2899            let inputFileNameInputError = document.createElement('div');
2900            inputFileNameInputError.className = 'input-error';
2901            inputFileNameInputError.setAttribute('data-error1', translate["ATTACHMENT"]["ERROR"]["NAME"]);
2902            let inputFileNameInput = document.createElement('input');
2903            inputFileNameInput.type = "text";
2904            inputFileNameInput.setAttribute('maxlength', "150");
2905            inputFileNameInput.setAttribute('name', "filename_" + indexItem);
2906            inputFileNameInput.setAttribute('id', "filename_" + indexItem);
2907            inputFileNameInput.value = secureFileName(file.name);
2908            let inputFileNameLabel = document.createElement('label');
2909            inputFileNameLabel.setAttribute('for', "filename_" + indexItem);
2910            inputFileNameLabel.innerHTML = translate["ATTACHMENT"]["FILENAME"];
2911            let inputFileNameIcon = document.createElement('i');
2912            inputFileNameIcon.className = 'fa-solid fa-file';
2913
2914            inputFileName.appendChild(inputFileNameMyInput);
2915            inputFileNameMyInput.appendChild(inputFileNameInputContainer);
2916            inputFileNameMyInput.appendChild(inputFileNameInputError);
2917            inputFileNameInputContainer.appendChild(inputFileNameInput);
2918            inputFileNameInputContainer.appendChild(inputFileNameLabel);
2919            inputFileNameInputContainer.appendChild(inputFileNameIcon);
2920
2921            let removeEl = document.createElement('div');
2922            removeEl.className = 'remove-item';
2923            let removeElI = document.createElement('i');
2924            removeElI.className = 'fa-solid fa-times';
2925
2926            let base64Input = document.createElement('input');
2927            base64Input.setAttribute('type', 'hidden');
2928            base64Input.setAttribute('name', 'base64_' + indexItem);
2929            base64Input.value = base64;
2930
2931            let fileextensionInput = document.createElement('input');
2932            fileextensionInput.setAttribute('type', 'hidden');
2933            fileextensionInput.setAttribute('name', 'file_extension_' + indexItem);
2934            fileextensionInput.value = filename_extension;
2935
2936            removeEl.appendChild(removeElI);
2937            grid.appendChild(fileextensionInput);
2938            grid.appendChild(base64Input);
2939            grid.appendChild(wrapperImage);
2940            grid.appendChild(wrapperGridValues);
2941            grid.appendChild(removeEl);
2942            wrapperGridValues.appendChild(gridValues);
2943            gridValues.appendChild(wrapperName);
2944            wrapperName.appendChild(inputName);
2945            gridValues.appendChild(wrapperFilename);
2946            wrapperFilename.appendChild(inputFileName);
2947            gridValues.appendChild(wrapperInfo);
2948
2949            if (file.size >= publicConsts['MAX_FILE_SIZE']) {
2950                let overlayError = document.createElement('div');
2951                overlayError.className = 'overlay-error';
2952                let overlayErrorInner = document.createElement('div');
2953                overlayErrorInner.className = 'overlay-error-inner';
2954                overlayErrorInner.innerText = translate['ATTACHMENT']['ER
2954ROR']['MAX_FILE_SIZE'] + ' ' + humanFileSize(publicConsts['MAX_FILE_SIZE']);
2955                wrapperGridValues.appendChild(overlayError);
2956                overlayError.appendChild(overlayErrorInner);
2957                grid.className = 'grid-container upload-item uploaderror';
2958            }
2959            if (!["image/jpeg", "image/gif", "image/png", "image/jpg", "application/pdf"].includes(file.type)) {
2960                let overlayError = document.createElement('div');
2961                overlayError.className = 'overlay-error';
2962                let overlayErrorInner = document.createElement('div');
2963                overlayErrorInner.className = 'overlay-error-inner';
2964                overlayErrorInner.innerText = translate['ATTACHMENT']['WRONG_FILE_TYPE'];
2965                wrapperGridValues.appendChild(overlayError);
2966                overlayError.appendChild(overlayErrorInner);
2967                grid.className = 'grid-container upload-item uploaderror';
2968            }
2969
2970            $('#my-drop-container .attachment-file-list').append(grid);
2971
2972            if ($('#my-drop-container .upload-item').length > 0) {
2973                $('#remove_all_items').removeClass('hide-me');
2974            } else {
2975                $('#remove_all_items').addClass('hide-me');
2976            }
2977        } catch (err) {
2978            console.log(err);
2979        }
2980    });
2981}
2982$(document).on('click', '#my-drop-container .upload-item .remove-item', function () {
2983    $(this).parents('.upload-item').remove();
2984    if ($('#my-drop-container .upload-item').length > 0) {
2985        $('#remove_all_items').removeClass('hide-me');
2986    } else {
2987        $('#remove_all_items').addClass('hide-me');
2988    }
2989});
2990let onUpload = false;
2991$('#upload_now').click(async function () {
2992    if (!onUpload) {
2993        onUpload = true;
2994        let error = false;
2995        let index = 0;
2996        while ($('#my-drop-container .upload-item').get(index) !== undefined) {
2997            let myItem = $($('#my-drop-container .upload-item').get(index));
2998            if (myItem.find('input[name^="name"]').val().trim().length <= 0) {
2999                let inputEl = myItem.find('input[name^="name"]');
3000                let inputWrapperEl = inputEl.parents('.my-input');
3001                inputWrapperEl.addClass('error');
3002                inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
3003                if (!error)
3004                    inputEl.focus();
3005                error = true;
3006            }
3007            if (myItem.find('input[name^="filename"]').val().trim().length <= 0) {
3008                let inputEl = myItem.find('input[name^="filename"]');
3009                let inputWrapperEl = inputEl.parents('.my-input');
3010                inputWrapperEl.addClass('error');
3011                inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
3012                if (!error)
3013                    inputEl.focus();
3014                error = true;
3015            }
3016            index++;
3017        }
3018        if (!error) {
3019            index = 0;
3020            while ($('#my-drop-container .upload-item').get(index) !== undefined) {
3021                let myItem = $($('#my-drop-container .upload-item').get(index));
3022                if (!myItem.hasClass('uploaded') && !myItem.hasClass('uploaderror')) {
3023                    if (index == 0) {
3024                        scrollToEl(myItem);
3025                    }
3026                    await uploadFile(myItem);
3027                }
3028                index++;
3029            }
3030        }
3031        onUpload = false;
3032    } else {
3033        scrollToEl($('#my-drop-container .upload-item:not(.uploaded)'));
3034    }
3035});
3036$('#remove_all_items').click(function () {
3037    $('#my-drop-container .upload-item').remove();
3038});
3039
3040function uploadFile(item) {
3041    return new Promise((resolve, reject) => {
3042        $(window).scrollTop(item.offset().top - $('.gap-top').height() - 50);
3043        let base64 = item.find('input[name^="base64"]').val();
3044        let name = item.find('input[name^="name"]').val();
3045        let filename = item.find('input[name^="filename"]').val();
3046        let type = $('#my-drop-container').data('type');
3047        let parent_id = $('#my-drop-container').data('parent_id');
3048        $.ajax({
3049            xhr: function () {
3050                let xhr = new window.XMLHttpRequest();
3051                xhr.upload.addEventListener("progress", function (evt) {
3052                    if (evt.lengthComputable) {
3053                        let percentComplete = 100 - (evt.loaded / evt.total) * 100;
3054                        item.css('--uploadPercentage', percentComplete);
3055                    }
3056                }, false);
3057                return xhr;
3058            },
3059            type: 'POST',
3060            url: 'uploadfile',
3061            data: {
3062                base64: base64,
3063                name: name,
3064                filename: filename,
3065                parent_id: parent_id,
3066                type: type
3067            },
3068            cache: false,
3069            success: function (result) {
3070                try {
3071                    let data = JSON.parse(result);
3072                    if (typeof data === 'object') {
3073                        if (data.done) {
3074                            item.addClass('uploaded');
3075                        } else {
3076                            item.addClass('uploaderror');
3077                            let overlayError = document.createElement('div');
3078                            overlayError.className = 'overlay-error';
3079                            let overlayErrorInner = document.createElement('div');
3080                            overlayErrorInner.className = 'overlay-error-inner';
3081                            overlayErrorInner.innerHTML = data.error;
3082                            overlayError.appendChild(overlayErrorInner);
3083                            item.find('.wrapper-values').append(overlayError);
3084                        }
3085                    } else {
3086                        if (result == 'NO_RIGHTS') {
3087                            presentToast(translate['NO_RIGTHS']["ERROR"]["NO_RIGHTS"], 'fa-triangle-exclamation', 'warning');
3088                        } else {
3089                            console.log(data);
3090                        }
3091                    }
3092                } catch (err) {
3093                    if (result == 'NO_RIGHTS') {
3094                        presentToast(translate['NO_RIGTHS']["ERROR"]["NO_RIGHTS"], 'fa-triangle-exclamation', 'warning');
3095                    } else {
3096                        console.log(err, result)
3097                    }
3098                }
3099                resolve(true);
3100            },
3101        });
3102    });
3103}
3104$(document).on('input', '#my-drop-container input[name*="filename"], #editattachment input[name="filenmae"]', function () {
3105    $(this).val(secureFileName($(this).val(), false));
3106})
3107$(document).on('blur', '#my-drop-container input[name*="filename"], #editattachment input[name="filenmae"]', function () {
3108    $(this).val(secureFileName($(this).val(), true));
3109})
3110function secureFileName(name, withDefault = true) {
3111    name = name.replace(/\.[^/.]+$/g, "");
3112    name = name.replaceAll('ä', 'ae');
3113    name = name.replaceAll('.', '-');
3114    name = name.replaceAll('Ä', 'ae');
3115    name = name.replaceAll('ö', 'oe');
3116    name = name.replaceAll('Ö', 'oe');
3117    name = name.replaceAll('ü', 'ue');
3118    name = name.replaceAll('Ü', 'ue');
3119    name = name.replaceAll('ß', 'ss');
3120    name = name.replaceAll(' ', '-');
3121    name = name.replace(/([\s])\1+/g, '-');
3122    name = name.replace(/[^A-Za-z0-9_\- ]/g, '');
3123    while (name.includes('--')) {
3124        name = name.replaceAll('--', '-');
3125    }
3126    name = name.toLocaleLowerCase();
3127    name = name.substring(0, 150);
3128    if (withDefault)
3129        name = (name.length == 0) ? 'file' : name;
3130    return name;
3131}
3132if ($("#my-drop-container").length > 0) {
3133    initDragAndDrop();
3134}
3135async function initDragAndDrop(targetEL = null) {
3136    return new Promise((resolve, reject) => {
3137
3138        $(document).on("dragenter", "#my-drop-container", function (e) {
3139            e.preventDefault();
3140            e.stopPropagation();
3141            $(this).addClass('drag-over');
3142        });
3143        $(document).on("dragleave", "#my-drop-container", function (e) {
3144            e.preventDefault();
3145            e.stopPropagation();
3146            $(this).removeClass('drag-over');
3147        });
3148        $("body").on("drop dragdrop", "#my-drop-container", async function (e) {
3149            e.stopPropagation();
3150            e.preventDefault();
3151            $(this).removeClass('drag-over');
3152            let file = e.originalEvent.dataTransfer.files[0];
3153            if (targetEL !== null) {
3154                resolve(await changeFileImgValue(file, targetEL));
3155            } else {
3156                addFilesToList(e.originalEvent.dataTransfer.files);
3157            }
3158        });
3159        $(document).on("change", '#my-drop-container input[type="file"]', async function (e) {
3160            let $this = $(this);
3161            let file = $this[0].files[0];
3162            if (targetEL !== null) {
3163                resolve(await changeFileImgValue(file, targetEL));
3164            } else {
3165                addFilesToList($this[0].files);
3166            }
3167        });
3168    });
3169}
3170function readFileAsync(file) {
3171    return new Promise((resolve, reject) => {
3172        let reader = new FileReader();
3173        reader.onload = () => {
3174            resolve(reader.result);
3175        };
3176        reader.onerror = reject;
3177        reader.readAsDataURL(file);
3178    });
3179}
3180function humanFileSize(bytes, si = false, dp = 2) {
3181    const thresh = si ? 1000 : 1024;
3182    if (Math.abs(bytes) < thresh) {
3183        return bytes + ' B';
3184    }
3185    const units = si
3186        ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
3187        : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
3188    let u = -1;
3189    const r = 10 ** dp;
3190    do {
3191        bytes /= thresh;
3192        ++u;
3193    } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
3194
3195    return (bytes.toFixed(dp) + ' ' + units[u]).replace('.', ',');
3196}
3197$("html").on("drop", function (e) {
3198    e.preventDefault();
3199    e.stopPropagation();
3200});
3201$("html").on("dragover", function (e) {
3202    e.preventDefault();
3203    e.stopPropagation();
3204});
3205
3206$(document).on('click', '#select-popup .clear-search', function (e) {
3207    $(this).parents('.my-input').find('input[type="search"]').val('');
3208    let allElements = $('#select-popup .elements .element');
3209    allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#select-popup .elements");
3210    $('#select-popup .popup-body').animate({
3211        scrollTop: 0
3212    }, 200);
3213    if (!isTouchDevice()) {
3214        $(this).parents('.my-input').find('input[type="search"]').focus();
3215    }
3216});
3217$(document).on('input', '#select-popup #search', function (e) {
3218    const val = $(this).val().toLowerCase();
3219    let allElements = $('#select-popup .elements .element');
3220    if (val.length > 0) {
3221        allElements.each((idx, el) => {
3222            const name = $(el).find('.name').text().toLowerCase();
3223            if (name.includes(val)) {
3224                $(el).attr('data-order', 0);
3225            } else {
3226                $(el).attr('data-order', levenshteinDistance(val, name));
3227            }
3228        });
3229        allElements.sort((a, b) => $(a).attr("data-order") - $(b).attr("data-order")).appendTo("#select-popup .elements");
3230    } else {
3231        allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#select-popup .elements");
3232    }
3233    $('#select-popup .popup-body').animate({
3234        scrollTop: 0
3235    }, 200);
3236})
3237// END-RESOURCES
3238// IMAGE
3239async function getImage(targetEL, onlySvg = false) {
3240    return new Promise((resolve, reject) => {
3241        let onlySvg = targetEL.hasClass('only-svg');
3242        let buttons = [];
3243        if (!onlySvg) {
3244            if ($('html').data('plt-android') || $('html').data('plt-ios')) {
3245                buttons.push({
3246                    icon: '<i class="fa-solid fa-camera"></i>',
3247                    text: translate['ATTACHMENT']['TAKE_PHOTO'],
3248                    style: 'default',
3249                    handler: async () => {
3250                        closeMyActionSheet($('#image-action-sheet'));
3251                        resolve(await takeImageWithCamera(targetEL));
3252                    }
3253                });
3254            } else {
3255                try {
3256                    if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
3257                        buttons.push({
3258                            icon: '<i class="fa-solid fa-camera"></i>',
3259                            text: translate['ATTACHMENT']['TAKE_PHOTO'],
3260                            style: 'default',
3261                            handler: async () => {
3262                                closeMyActionSheet($('#image-action-sheet'));
3263                                resolve(await takeImageWithCamera(targetEL));
3264                            }
3265                        });
3266                    }
3267                } catch (error) { }
3268            }
3269        } else {
3270            buttons.push({
3271                icon: '<i class="fa-solid fa-cloud"></i>',
3272                text: translate['ATTACHMENT']['SELECT_ON_SERVER'],
3273                style: 'default',
3274                handler: async () => {
3275                    closeMyActionSheet($('#image-action-sheet'));
3276                    resolve(await getSVGFromServer(targetEL));
3277                }
3278            });
3279        }
3280
3281        buttons.push({
3282            icon: '<i class="fa-solid fa-image"></i>',
3283            text: translate['ATTACHMENT']['SELECT_ON_DEVICE'],
3284            style: 'default',
3285            handler: async () => {
3286                closeMyActionSheet($('#image-action-sheet'));
3287                resolve(await getImageFromDevice(targetEL, onlySvg));
3288            }
3289        });
3290        // Bild löschen
3291        buttons.push({
3292            icon: '<i class="fa-solid fa-trash"></i>',
3293            text: translate['ATTACHMENT']['DELETE_IMAGE'],
3294            style: 'default',
3295            handler: (e) => {
3296                closeMyActionSheet($('#image-action-sheet'));
3297                resolve('remove');
3298            }
3299        });
3300        // Abbrechen
3301        buttons.push({
3302            icon: '<i class="fa-solid fa-ban"></i>',
3303            text: translate['DISMISS'],
3304            style: 'default',
3305            handler: () => {
3306                closeMyActionSheet($('#image-action-sheet'));
3307                resolve(null);
3308            }
3309        });
3310        myActionSheet(translate['ATTACHMENT']['CHANGE_IMAGE'], 'image-action-sheet', buttons);
3311    });
3312}
3313async function getSVGFromServer(targetEL) {
3314    return new Promise(async (resolve, reject) => {
3315        await getAjaxPopUp('getsvgfromserver');
3316        $('#svg-from-server-popup #confirm-svg-icon').click(async function (e) {
3317            let icon = $('#svg-from-server-popup input[name="icon"]:checked').val();
3318            if (icon) {
3319                let svg = await getSVG(icon);
3320                let base64 = "data:image/svg+xml;base64," + window.btoa(svg);
3321                $('#svg-from-server-popup').find('.close-and-remove-popup').trigger('click');
3322                resolve(base64);
3323            } else {
3324                presentToast(translate['MODEL_CATEGORY']['ERROR']['IMAGE'], 'fa-triangle-exclamation', 'warning');
3325            }
3326        });
3327        myActionSheet(translate['ATTACHMENT']['CHANGE_IMAGE'], 'image-action-sheet', buttons);
3328    });
3329}
3330async function takeImageWithCamera(targetEL) {
3331    return new Promise(async (resolve, reject) => {
3332        if ($('html').data('memory-full')) {
3333            getAjaxPopUp('nomemoryleft');
3334        } else {
3335            if ($('html').data('plt-android') || $('html').data('plt-ios')) {
3336                try {
3337                    var messageObj = { function: 'takeImage' };
3338                    var stringifiedMessageObj = JSON.stringify(messageObj);
3339                    webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
3340                    resolve(await getImageFromApp(targetEL));
3341                } catch (error) {
3342                    resolve(null);
3343                }
3344            } else {
3345                // Browser
3346                await getAjaxPopUp('getcamerapopup');
3347                resolve(await getImageFromCamera(targetEL));
3348            }
3349        }
3350    });
3351}
3352async function getImageFromDevice(targetEL, onlySvg = false) {
3353    return new Promise(async (resolve, reject) => {
3354        if ($('html').data('memory-full')) {
3355            getAjaxPopUp('nomemoryleft');
3356        } else {
3357            if (($('html').data('plt-android') || $('html').data('plt-ios')) && !onlySvg) {
3358                try {
3359                    var messageObj = { function: 'takeImageFromLibrary' };
3360                    var stringifiedMessageObj = JSON.stringify(messageObj);
3361                    webkit.messageHandlers.cordova_iab.postMessage(stringifiedMessageObj);
3362                    resolve(await getImageFromApp(targetEL));
3363                } catch (error) {
3364                    resolve(null);
3365                }
3366            } else {
3367                await getAjaxPopUp('getuploadimagepopup', { onlySvg: onlySvg });
3368                resolve(await initDragAndDropImg(targetEL, onlySvg));
3369            }
3370        }
3371    });
3372}
3373
3374let myVideo;
3375function getImageFromCamera(targetEL) {
3376    return new Promise((resolve, reject) => {
3377        myVideo = document.getElementById('myVideoStreamForCamera');
3378        let myCanvas = document.getElementById('myCanvasForCamera');
3379        let myImage = document.getElementById('myPrevImage');
3380        let popup = $('#myVideoStreamForCamera').parents('.my-popup');
3381        try {
3382            if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
3383                navigator.mediaDevices.getUserMedia({ video: { width: { min: 1280 }, height: { min: 720 } }, audio: false }).then(stream => {
3384                    myVideo.srcObject = stream;
3385                    popup.find('#take-picture').removeClass('hide-me');
3386                    $(document).on('click', ('#' + popup.attr('id') + ' #take-picture'), function (e) {
3387                        myCanvas.width = myVideo.videoWidth;
3388                        myCanvas.height = myVideo.videoHeight;
3389                        myCanvas.getContext("2d").drawImage(myVideo, 0, 0);
3390                        popup.find('#take-picture').addClass('hide-me');
3391                        popup.find('#retake-picture').removeClass('hide-me');
3392                        popup.find('#rotate-picture').removeClass('hide-me');
3393                        popup.find('#confirm-image').removeClass('hide-me');
3394                        let image = myCanvas.toDataURL('image/png');
3395                        popup.find('.video-wrapper').addClass('hide-me');
3396                        popup.find('.cropper-wrapper').removeClass('hide-me');
3397                        myImage.src = image;
3398                        myImage.onload = async function () {
3399                            resolve(await initCropper(myImage, targetEL));
3400                        };
3401                    });
3402                }).catch(err => {
3403                    if (err.toString().toLowerCase().includes('permission')) {
3404                        presentToast(translate['ATTACHMENT']['WEB_PICUTRE_NO_PERMISSION'], 'fa-triangle-exclamation', 'warning');
3405                    } else {
3406                        presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'danger');
3407                    }
3408                    closePhotoPopUp();
3409                    resolve(null);
3410                });
3411            } else {
3412                presentToast(translate['ATTACHMENT']['WEB_PICUTRE_NOT_SUPPORTED'], 'fa-ban', 'warning');
3413                closePhotoPopUp();
3414                resolve(null);
3415            }
3416        } catch (error) {
3417            presentToast(translate['ATTACHMENT']['WEB_PICUTRE_NOT_SUPPORTED'], 'fa-ban', 'warning');
3418            closePhotoPopUp();
3419            resolve(null);
3420        }
3421    });
3422}
3423
3424$(document).on('click', '.close-camera-popup', function (e) {
3425    closePhotoPopUp();
3426});
3427async function closePhotoPopUp(popup = null) {
3428    if (popup == null) {
3429        popup = $('#myVideoStreamForCamera').parents('.my-popup');
3430    }
3431    await hidePopUp(popup);
3432    popup.remove();
3433    if (myVideo) {
3434        myVideo.srcObject.getTracks().forEach(function (track) {
3435            track.stop();
3436        });
3437    }
3438}
3439let cropper = null;
3440
3441async function initCropper(myImage, targetEL) {
3442    let ratio = 1;
3443    if (targetEL.data('ratio') !== '' && targetEL.data('ratio') !== undefined) {
3444        ratio = targetEL.data('ratio');
3445    }
3446    $('.cropper-wrapper').css('--ratio', ratio);
3447    return new Promise((resolve, reject) => {
3448        setTimeout(() => {
3449            let width = myImage.naturalWidth;
3450            let height = myImage.naturalHeight;
3451            let y = width - height;
3452            let x = height - width;
3453            if (y > 0) {
3454                y = y / 2 * -1;
3455            } else {
3456                y = 0;
3457            }
3458            if (x > 0) {
3459                x = x / 2 * -1;
3460            } else {
3461                x = 0;
3462            }
3463            cropper = new Cropper(myImage, {
3464                autoCropArea: 1,
3465                viewMode: 0,
3466                aspectRatio: ratio,
3467                autoCrop: true,
3468                zoomable: false,
3469                movable: false,
3470                minContainerWidth: 100,
3471                minContainerHeight: 100,
3472                data: {
3473                    x: x,
3474                    y: y,
3475                    width: width,
3476                    height: height
3477                }
3478            });
3479            // Handler aus früheren initCropper()-Aufrufen entfernen - sonst
3480            // sammeln sie sich pro Bild an und feuern mehrfach (teils auf
3481            // einen bereits zerstörten Cropper).
3482            $(document).off('click.cropper');
3483            $(document).on('click.cropper', '#confirm-image', function (e) {
3484                // getCroppedCanvas() liefert null, solange das Bild noch nicht
3485                // geladen ist (ready === false) oder der Cropper zerstört wurde.
3486                if (!cropper || !cropper.ready) {
3487                    return;
3488                }
3489                const canvas = cropper.getCroppedCanvas({
3490                    width: 1200
3491                });
3492                if (!canvas) {
3493                    presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'danger');
3494                    return;
3495                }
3496                let popup = $(this).parents('.my-popup');
3497                let image = canvas.toDataURL('image/png');
3498                closePhotoPopUp(popup);
3499                resolve(image);
3500            });
3501            $(document).on('click.cropper', '#retake-picture', async function (e) {
3502                let popup = $(this).parents('.my-popup');
3503                let myCanvas = document.getElementById('myCanvasForCamera');
3504                if (myCanvas) {
3505                    const context = myCanvas.getContext('2d');
3506                    context.clearRect(0, 0, myCanvas.width, myCanvas.height);
3507                    popup.find('#take-picture').removeClass('hide-me');
3508                    popup.find('#rotate-picture').addClass('hide-me');
3509                    popup.find('#retake-picture').addClass('hide-me');
3510                    popup.find('#confirm-image').addClass('hide-me');
3511                    popup.find('.video-wrapper').removeClass('hide-me');
3512                    popup.find('.cropper-wrapper').addClass('hide-me');
3513                } else {
3514                    closePhotoPopUp(popup);
3515                    resolve(null);
3516                    currentImageEl.find('button').trigger('click');
3517                }
3518                if (cropper) {
3519                    cropper.destroy();
3520                    cropper = null;
3521                }
3522            });
3523
3524            $(document).on('click.cropper', '#rotate-picture', async function (e) {
3525                if (cropper && cropper.ready) {
3526                    cropper.rotate(90);
3527                }
3528            });
3529            $(document).on('click.cropper', '#get-other-picture', function (e) {
3530                let popup = $(this).parents('.my-popup');
3531                popup.find('#get-other-picture').addClass('hide-me');
3532                popup.find('#rotate-picture').addClass('hide-me');
3533                popup.find('#confirm-image').addClass('hide-me');
3534                popup.find('#my-drop-container').removeClass('hide-me');
3535                popup.find('.cropper-wrapper').addClass('hide-me');
3536                if (cropper) {
3537                    cropper.destroy();
3538                    cropper = null;
3539                }
3540            });
3541        }, 100);
3542
3543    });
3544}
3545async function getImageFromApp(targetEL) {
3546    return new Promise(async (resolve, reject) => {
3547        let image = $('html').attr('data-image');
3548        let error = $('html').attr('data-error');
3549        if ((image !== '' && image !== undefined) || (error !== '' && error !== undefined)) {
3550            if (image !== '' && image !== undefined) {
3551                image = image.replace('data:image/png;base64,data:image/png;base64', 'data:image/png;base64');
3552                await getAjaxPopUp('getcropperpopup');
3553                $('#cropper-popup #rotate-picture').removeClass('hide-me');
3554                $('#cropper-popup #retake-picture').removeClass('hide-me');
3555                $('#cropper-popup #confirm-image').removeClass('hide-me');
3556                $('#cropper-popup .cropper-wrapper').removeClass('hide-me');
3557                $('html').attr('data-image', '');
3558                let myImage = document.getElementById('myimage');
3559                myImage.src = image;
3560                myImage.onload = async function () {
3561                    resolve(await initCropper(myImage, targetEL));
3562                }
3563            } else if (error !== '') {
3564                $('html').attr('data-error', '');
3565                presentToast(translate['ATTACHMENT'][error], 'fa-triangle-exclamation', 'warning');
3566            }
3567        } else {
3568            setTimeout(async () => {
3569                resolve(await getImageFromApp(targetEL));
3570            }, 300);
3571        }
3572    });
3573}
3574async function initDragAndDropImg(targetEL, onlySvg = false) {
3575    return new Promise((resolve, reject) => {
3576
3577        $(document).on("dragenter", "#my-drop-container", function (e) {
3578            e.preventDefault();
3579            e.stopPropagation();
3580            $(this).addClass('drag-over');
3581        });
3582        $(document).on("dragleave", "#my-drop-container", function (e) {
3583            e.preventDefault();
3584            e.stopPropagation();
3585            $(this).removeClass('drag-over');
3586        });
3587        $("body").on("drop dragdrop", "#my-drop-container", async function (e) {
3588            e.stopPropagation();
3589            e.preventDefault();
3590            $(this).removeClass('drag-over');
3591            let file = e.originalEvent.dataTransfer.files[0];
3592            resolve(await changeFileImgValue(file, targetEL, onlySvg));
3593        });
3594        $(document).on("change", '#upload-image-popup #my-drop-container input[type="file"]', async function () {
3595            let $this = $(this);
3596            let file = $this[0].files[0];
3597            resolve(await changeFileImgValue(file, targetEL, onlySvg));
3598        });
3599    });
3600}
3601async function changeFileImgValue(file, targetEL, onlySvg = false) {
3602    return new Promise((resolve, reject) => {
3603        let imagesTypes = ["image/jpeg", "image/png", "image/jpg", "image/svg+xml"];
3604        if (onlySvg)
3605            imagesTypes = ["image/svg+xml"];
3606        const maxFileSizeInKB = 5000;
3607        var reader = new FileReader();
3608        reader.onload = function () {
3609            let image = reader.result;
3610            if (file.type == "image/svg+xml") {
3611                closePhotoPopUp($('#myimage').parents('.my-popup'));
3612                resolve(image);
3613            } else {
3614                let myImage = document.getElementById('myimage');
3615                myImage.src = image;
3616                $('#upload-image-popup #get-other-picture').removeClass('hide-me');
3617                $('#upload-image-popup #rotate-picture').removeClass('hide-me');
3618                $('#upload-image-popup #confirm-image').removeClass('hide-me');
3619                $('#upload-image-popup #my-drop-container').addClass('hide-me');
3620                $('#upload-image-popup .cropper-wrapper').removeClass('hide-me');
3621                myImage.onload = async function () {
3622                    resolve(await initCropper(myImage, targetEL));
3623                }
3624            }
3625
3626        }
3627        if (imagesTypes.includes(file.type)) {
3628            if (file.size / 1000 < maxFileSizeInKB) {
3629                reader.readAsDataURL(file);
3630            } else {
3631                presentToast(translate['ATTACHMENT']['MAX_FILE_SIZE'], 'fa-triangle-exclamation', 'warning');
3632            }
3633        } else {
3634            if (onlySvg) {
3635                presentToast(translate['ATTACHMENT']['WRONG_FILE_TYPE_ONLY_SVG'], 'fa-triangle-exclamation', 'warning');
3636            } else {
3637                presentToast(translate['ATTACHMENT']['WRONG_FILE_TYPE_IMG'], 'fa-triangle-exclamation', 'warning');
3638            }
3639        }
3640    });
3641}
3642$("html").on("drop", function (e) {
3643    e.preventDefault();
3644    e.stopPropagation();
3645});
3646$("html").on("dragover", function (e) {
3647    e.preventDefault();
3648    e.stopPropagation();
3649});
3650// END - IMAGE
3651
3652// ACTION SHEET
3653$(document).on("click", ".my-action-sheet", function (e) {
3654    if ($(e.target).hasClass('my-action-sheet')) {
3655        closeMyActionSheet($(this));
3656    }
3657})
3658function myActionSheet(title, id, buttons) {
3659    let myActionSheet = document.createElement('div');
3660    myActionSheet.setAttribute('id', id);
3661    myActionSheet.className = 'my-action-sheet';
3662
3663    let myActionSheetInner = document.createElement('div');
3664    myActionSheetInner.className = 'inner';
3665    myActionSheet.appendChild(myActionSheetInner);
3666
3667    if (title != '') {
3668        let myActionSheetTitle = document.createElement('div');
3669        myActionSheetTitle.className = 'title';
3670        myActionSheetTitle.textContent = title;
3671        myActionSheetInner.appendChild(myActionSheetTitle);
3672    }
3673    let myActionSheetList = document.createElement('ul');
3674    buttons.forEach((data, index) => {
3675        let myActionSheetListItem = document.createElement('li');
3676        myActionSheetListItem.className = data['style'];
3677        myActionSheetListItem.innerHTML = data['icon'] + '<span>' + data['text'] + '</span>';
3678        myActionSheetListItem.addEventListener('click', data['handler']);
3679        myActionSheetList.appendChild(myActionSheetListItem);
3680    });
3681    myActionSheetInner.appendChild(myActionSheetList);
3682    $('body').append(myActionSheet);
3683    $('#' + id).fadeIn(500).addClass('shown');
3684    return $('#' + id);
3685}
3686
3687function closeMyActionSheet(el) {
3688    el.removeClass('shown').fadeOut(500).promise().done(function () {
3689        el.remove();
3690    });
3691}
3692
3693// END - ACTION SHEET
3694
3695// POPUP FUNCTIONS 
3696
3697function showPopUp(popupEl, animationTime = 500) {
3698    return new Promise(function (resolve, reject) {
3699        popupEl.fadeIn(animationTime).promise().done(function () {
3700            resolve(true);
3701        });
3702    });
3703}
3704
3705function hidePopUp(popupEl) {
3706    return new Promise(function (resolve, reject) {
3707        popupEl.fadeOut(500).promise().done(function () {
3708            resolve(true);
3709        });
3710    });
3711}
3712
3713$(document).on('click', '.my-popup .close-popup', function (e) {
3714    let popUpEl = $(this).parents('.my-popup');
3715    hidePopUp(popUpEl);
3716});
3717
3718$(document).on('click', '.my-popup .close-and-remove-popup', async function (e) {
3719    let popUpEl = $(this).parents('.my-popup');
3720    await hidePopUp(popUpEl);
3721    popUpEl.remove();
3722});
3723
3724$(document).on('click', '.my-popup .generate-titlepage-pdf', async function (e) {
3725    let popUpEl = $(this).parents('.my-popup');
3726    await generateTitlePage(popUpEl);
3727    popUpEl.remove();
3728});
3729
3730function getAjaxPopUp(action, data, animationTime = 500) {
3731    return new Promise(function (resolve, reject) {
3732        $('#loading-bar').show();
3733        $.ajax({
3734            type: 'POST',
3735            url: action,
3736            cache: false,
3737            data: {
3738                data: JSON.stringify(data)
3739            },
3740            success: function (result) {
3741                $('#loading-bar').hide();
3742                if (/<[a-z][\s\S]*>/i.test(result)) {
3743                    $('body').append(result).delay(10).promise().done(async function () {
3744                        await showPopUp($('#' + $(result).attr('id')), animationTime);
3745                        resolve(true);
3746                    });
3747                } else {
3748                    handleResult(result);
3749                    resolve(false)
3750                }
3751            },
3752        });
3753    })
3754}
3755
3756async function infoPopup(title, message, link) {
3757    return new Promise((resolve, reject) => {
3758        $('#loading-bar').show();
3759        $.ajax({
3760            type: 'POST',
3761            url: 'getinfopopup',
3762            data: {
3763                title: title,
3764                message: message,
3765                link: link,
3766            },
3767            cache: false,
3768            success: function (result) {
3769                $('#loading-bar').hide();
3770                if (/<[a-z][\s\S]*>/i.test(result)) {
3771                    $('body').append(result).delay(10).promise().done(function () {
3772                        let popupEl = $('#' + $(result).attr('id'));
3773                        showPopUp(popupEl);
3774                        resolve(true);
3775                    });
3776                } else {
3777                    if (result == 'NO_RIGHTS') {
3778                        presentToast(translate['NO_RIGTHS']["ERROR"]["NO_RIGHTS"], 'fa-triangle-exclamation', 'warning');
3779                    } else {
3780                        console.log(result)
3781                    }
3782                    resolve(false);
3783                }
3784            },
3785        });
3786    });
3787
3788}
3789
3790$('.submit-form-after-confirm').click(async function (e) {
3791    e.preventDefault();
3792    let el = $(this);
3793    if (await submitFormAfterConfirm($(el).data('title'), $(el).data('message'))) {
3794        el.unbind('click').click();
3795    }
3796    return false;
3797})
3798async function submitFormAfterConfirm(title, message) {
3799    return new Promise((resolve, reject) => {
3800        $('#loading-bar').show();
3801        $.ajax({
3802            type: 'POST',
3803            url: 'getconfirmpopup',
3804            data: {
3805                title: title,
3806                message: message,
3807            },
3808            cache: false,
3809            success: function (result) {
3810                $('#loading-bar').hide();
3811                if (/<[a-z][\s\S]*>/i.test(result)) {
3812                    $('body').append(result).delay(10).promise().done(function () {
3813                        let popupEl = $('#' + $(result).attr('id'));
3814                        popupEl.find('.my-link.bold').bind('click', () => {
3815                            resolve(true);
3816                            popupEl.find('.close-and-remove-popup').trigger('click');
3817                        });
3818                        popupEl.find('.my-link.close-and-remove-popup').bind('click', () => {
3819                            resolve(false);
3820                        });
3821                        showPopUp(popupEl);
3822
3823                    });
3824                } else {
3825                    handleResult(result);
3826                    resolve(false);
3827                }
3828            },
3829        });
3830    });
3831
3832}
3833async function getarticlenotokorinrepairpopup(noQuestions = false) {
3834    return new Promise(async (resolve, reject) => {
3835        $('#loading-bar').show();
3836        await getAjaxPopUp('getarticlenotokorinrepairpopup', { message: noQuestions ? translate['ARTICLE']['TEST_POSTIVE_OR_NEGATIVE'] : translate['ARTICLE']['MESSAGE_SET_ARTICLE_NOT_OK'] });
3837        $('#loading-bar').hide();
3838        let popupEl = $('#articlenotokorinrepair-popup');
3839        popupEl.find('.my-link.in_repair').bind('click', () => {
3840            resolve('in_repair');
3841            popupEl.find('.close-and-remove-popup').trigger('click');
3842        });
3843        popupEl.find('.my-link.not_ok').bind('click', () => {
3844            resolve('not_ok');
3845            popupEl.find('.close-and-remove-popup').trigger('click');
3846        });
3847        popupEl.find('.my-link.close-and-remove-popup').bind('click', () => {
3848            resolve(false);
3849        });
3850    });
3851
3852}
3853function convertRemToPixels(rem) {
3854    return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
3855}
3856async function getUserSignPopUp() {
3857    return new Promise((resolve, reject) => {
3858        $('#loading-bar').show();
3859        let width = 836;
3860        let widthVP = $(window).width();
3861        if (widthVP > 768) {
3862            if (widthVP < (836 + convertRemToPixels(6))) {
3863                width = widthVP - convertRemToPixels(6);
3864            }
3865        } else {
3866            width = widthVP - convertRemToPixels(4);
3867        }
3868        $.ajax({
3869            type: 'POST',
3870            url: 'getusersignpopup',
3871            cache: false,
3872            data: {
3873                canvas_width: Math.round(width)
3874            },
3875            success: function (result) {
3876                $('#loading-bar').hide();
3877                if (/<[a-z][\s\S]*>/i.test(result)) {
3878                    $('body').append(result).delay(10).promise().done(async function () {
3879                        let popupEl = $('#' + $(result).attr('id'));
3880                        popupEl.find('.my-link.bold').bind('click', () => {
3881                            let sign = $('#sign_canvas').get(0).toDataURL();
3882                            resolve(sign);
3883                            popupEl.find('.close-and-remove-popup').trigger('click');
3884                        });
3885                        popupEl.find('.my-link.close-and-remove-popup').bind('click', () => {
3886                            resolve(false);
3887                        });
3888                        await showPopUp(popupEl);
3889                        signPaths = [];
3890                        saveResizeAndRedisplaySignCanvas();
3891
3892                    });
3893                } else {
3894                    handleResult(result);
3895                    resolve(false);
3896                }
3897            },
3898        });
3899    });
3900}
3901
3902async function generateTitlePage(popUpEl) {
3903    let data = getFormData(popUpEl.find('form'));
3904    let base64 = await generateTitlePagePDF(data);
3905    $.ajax({
3906        type: 'POST',
3907        url: 'uploadtitlepage',
3908        cache: false,
3909        data: {
3910            pdfBase64: base64,
3911            tableName: $('.my-table-list').data('table-name')
3912        },
3913        success: async function (result) {
3914            presentToast(translate['EXPORT_DATA']['TITLEPAGE']['FILE_GENERATED'], 'fa-save', 'success');
3915            await hidePopUp(popUpEl);
3916            changeCanvasContainerSrc(base64);
3917            popUpEl.remove();
3918        }
3919    });
3920}
3921$(document).on('input', '#generate-titlepage-form #dirText', function () {
3922    const val = $(this).val();
3923    const newlineArr = val.split(/[\r\n]+/);
3924    let strArr = [];
3925    for (let index = 0; index < newlineArr.length; index++) {
3926        if (index < 5) {
3927            strArr.push(newlineArr[index].substr(0, 65));
3928        }
3929    }
3930    $(this).val(strArr.join("\r\n"));
3931});
3932
3933// END-POPUP FUNCTIONS 
3934
3935// SIGN FUNCTIONS
3936
3937
3938jQuery.event.special.touchmove = {
3939    setup: function (_, ns, handle) {
3940        this.addEventListener("touchmove", handle, { passive: !ns.includes("noPreventDefault") });
3941    }
3942};
3943let signPaths = [];
3944
3945$(document).on('mousedown touchstart', '#sign_canvas', function (e) {
3946    let drawingSign = false;
3947    let saveXSign = 0;
3948    let saveYSign = 0;
3949    let timestamp = null;
3950    drawingSign = true;
3951    canSwipeMenu = false;
3952    const canvasPosition = $(this).get(0).getBoundingClientRect();
3953    saveXSign = e.offsetX;
3954    saveYSign = e.offsetY;
3955    $(document).on('mousemove touchmove', '#sign_canvas', function (e) {
3956        if (!drawingSign)
3957            return;
3958
3959        const canvasPosition = $(this).get(0).getBoundingClientRect();
3960        let ctx = $(this).get(0).getContext('2d');
3961        let currentX = 0;
3962        let currentY = 0;
3963        if (e.offsetX && e.offsetY) {
3964            currentX = e.offsetX;
3965            currentY = e.offsetY;
3966        } else {
3967            if (e.touches) {
3968                currentX = e.touches[0].clientX - canvasPosition.x;
3969                currentY = e.touches[0].clientY - canvasPosition.y;
3970            }
3971        }
3972
3973        if (timestamp === null) {
3974            timestamp = Date.now();
3975        }
3976        const now = Date.now();
3977        const dt = now - timestamp;
3978        const dx = saveXSign - currentX;
3979        const dy = saveYSign - currentY;
3980        const speedX = Math.abs(Math.round(dx / dt * 100));
3981        const speedY = Math.abs(Math.round(dy / dt * 100));
3982        let speed = speedX;
3983        if (speedY > speedX) {
3984            speed = speedY;
3985        }
3986        const maxSpeed = 200;
3987        speed = (speed > maxSpeed) ? maxSpeed : speed;
3988        const speedFactor = 1 - (100 / maxSpeed * speed / 100);
3989
3990        ctx.lineCap = 'round';
3991        ctx.lineJoin = 'round';
3992        ctx.strokeStyle = '#000';
3993        let signScaleFactor = 100 / 863 * $('#sign_canvas').get(0).width / 100;
3994        let lineWidth = (4 + (3 * speedFactor)) * signScaleFactor;
3995        ctx.lineWidth = (lineWidth >= 1) ? lineWidth : 1;
3996
3997        let path = new Path2D();
3998        path.moveTo(saveXSign, saveYSign);
3999        path.lineTo(currentX, currentY);
4000        path.closePath();
4001
4002        ctx.stroke(path);
4003        signPaths.push({ width: $('#sign_canvas').get(0).width, path: path, speedFactor: speedFactor });
4004
4005        saveXSign = currentX;
4006        saveYSign = currentY;
4007        timestamp = now;
4008    });
4009    $(document).on('mouseup touchend', '#sign_canvas', function (e) {
4010        drawingSign = false;
4011        canSwipeMenu = true;
4012    });
4013});
4014function saveResizeAndRedisplaySignCanvas() {
4015    let el = $('#sign_canvas').get(0);
4016    el.width = $('#sign_canvas').parent().width();
4017    el.height = $('#sign_canvas').parent().width() * 0.5;
4018    let ctx = el.getContext('2d');
4019    ctx.clearRect(0, 0, el.width, el.height);
4020
4021    ctx.lineJoin = 'round';
4022    ctx.strokeStyle = '#000';
4023    let signScaleFactorLine = 100 / 863 * el.width / 100;
4024
4025    signPaths.forEach((path) => {
4026        let lineWidth = (4 + (3 * path.speedFactor)) * signScaleFactorLine;
4027        ctx.lineWidth = (lineWidth >= 1) ? lineWidth : 1;
4028        let signScaleFactor = 100 / path.width * el.width / 100;
4029        ctx.scale(signScaleFactor, signScaleFactor);
4030        ctx.stroke(path['path'])
4031        ctx.scale((1 / signScaleFactor), (1 / signScaleFactor));
4032    });
4033}
4034$(document).on('click', '#user-sign-popup .clear', function (e) {
4035    let ctx = $('#sign_canvas').get(0).getContext('2d');
4036    ctx.clearRect(0, 0, $('#sign_canvas').get(0).width, $('#sign_canvas').get(0).height);
4037    signPaths = [];
4038});
4039
4040
4041// END-SIGN FUNCTIONS
4042
4043// TOAST FUNCTIONS
4044
4045$('.show-toast').click(function () {
4046    presentToast($(this).data('text'), $(this).data('icon'), $(this).data('color'), $(this).data('duration'));
4047});
4048
4049if ($('.show-toast-now').length > 0) {
4050    $('.show-toast-now').each(function (idx, el) {
4051        presentToast($(el).data('text'), $(el).data('icon'), $(el).data('color'), $(el).data('duration'));
4052    });
4053}
4054
4055$(document).on('click', '.my-toast', function () {
4056    let id = $(this).attr('id');
4057    removeToast(id);
4058})
4059
4060function presentToast(message = '', icon = 'cloud-done', color = 'success', duration = 10000) {
4061    let id = 'toast-' + new Date().getTime();
4062    let html = '<div id="' + id + '" class="my-toast' + ((color !== '') ? ' ' + color : '') + '">' + ((icon !== '') ? '<i class="fa-solid ' + icon + '"></i>' : '') + ((message !== '') ? '<span>' + message + '</span>' : '') + '</div>';
4063    $('body').append(html).delay(100).promise().done(function () {
4064        $('#' + id).addClass('show');
4065        resetToastsPostions();
4066    });
4067    if (duration > 0) {
4068        setTimeout(() => {
4069            removeToast(id);
4070        }, duration);
4071    }
4072    return id;
4073}
4074
4075function resetToastsPostions() {
4076    let bottom = 16;
4077    if ($('.my-fixed-buttons.right-bottom').length > 0) {
4078        bottom += $('.my-fixed-buttons.right-bottom').outerHeight() + 16;
4079    }
4080    $('.my-toast.show').each(function (idx, el) {
4081        $(el).css('bottom', bottom);
4082        bottom += $(el).outerHeight() + 16;
4083    });
4084}
4085
4086function removeToast(id) {
4087    let tmptoast = $('#' + id);
4088    if (tmptoast.length > 0) {
4089        let bottom = 16;
4090        if ($('.my-fixed-buttons.right-bottom').length > 0) {
4091            bottom += $('.my-fixed-buttons.right-bottom').outerHeight() + 16;
4092        }
4093        $('.my-toast.show').each(function (idx, el) {
4094            if ($(el).attr('id') !== id) {
4095                $(el).css('bottom', bottom);
4096                bottom += $(el).outerHeight() + 16;
4097            }
4098        });
4099        $(tmptoast).removeClass('show').delay(300).promise().done(function () {
4100            $(tmptoast).remove();
4101        });
4102    }
4103}
4104
4105
4106// END-TOAST FUNCTIONS
4107
4108// MENU FUNCTIONS
4109
4110$(document).on('click', '.remove-agb-pop-up', function (e) {
4111    let popUpEl = $('#agb-popup');
4112    popUpEl.remove();
4113});
4114
4115
4116$('.menu-top .menu-button').click(function () {
4117    $('.menu-left').toggleClass('open');
4118});
4119
4120$('.my-content').click(function () {
4121    $('.menu-left').removeClass('open');
4122});
4123
4124$('.menu-left .menu-point .main-point .arrow').click(function (e) {
4125    e.preventDefault();
4126    if ($(this).parents('.menu-point').hasClass('open')) {
4127        $(this).parents('.menu-point').find('.sub-point').slideUp({
4128            duration: 300
4129        });
4130        $(this).parents('.menu-point').removeClass('open');
4131    } else {
4132        $(this).parents('.menu-point').find('.sub-point').slideDown({
4133            complete: function () {
4134                $(this).css({
4135                    display: "flex"
4136                })
4137            },
4138            start: function () {
4139                $(this).css({
4140                    display: "flex"
4141                })
4142            },
4143            duration: 300
4144        });
4145        $(this).parents('.menu-point').addClass('open');
4146    }
4147
4148});
4149
4150$('.menu-left .account-wrapper').click(function (e) {
4151    $(this).toggleClass('sub-menu-shown').find('.sub-menu').slideToggle(300);
4152});
4153
4154$('#addaccount').click(async function () {
4155    $('#addaccount').prop('disabled', true);
4156    await getAjaxPopUp('addaccount');
4157    $('#addaccount').prop('disabled', false);
4158});
4159
4160// END-MENU FUNCTIONS
4161
4162// TAB FUNCTIONS
4163
4164$('.my-tabs .tab').click(function () {
4165    const tabs = $(this).parents('.my-tabs');
4166    const target = $($(this).data('target'));
4167    tabs.find('.tab').removeClass('active');
4168    $(this).addClass('active');
4169    tabs.find('.content').removeClass('active');
4170    target.addClass('active');
4171});
4172
4173$(window).on('load', function () {
4174    let container = $('.my-tab-nav-wrapper');
4175    let activeTab = container.find('.active');
4176    if (activeTab.length) {
4177        $('.my-content').addClass('with-tabs');
4178        $('.my-fixed-buttons').addClass('with-tabs');
4179        container.animate(
4180            {
4181                scrollLeft: (activeTab.position().left + container.scrollLeft() - container.offset().left - 20)
4182            },
4183            150
4184        );
4185    }
4186    let activeMenuPoint = $('.menu-point.active');
4187    let containerMenu = $('.menu-wrapper');
4188    if (activeMenuPoint.length > 0) {
4189        let diff = (activeMenuPoint.offset().top + activeMenuPoint.outerHeight()) - containerMenu.outerHeight();
4190        if (activeMenuPoint.length) {
4191            if (diff > 0) {
4192                $('.menu-wrapper').animate(
4193                    {
4194                        scrollTop: diff
4195                    },
4196                    150
4197                );
4198            }
4199        }
4200    }
4201});
4202
4203// END-TAB FUNCTIONS
4204
4205// SETTING FUNCTIONS
4206
4207$('#economic_unit_id').on('change', function () {
4208    const val = $(this).val();
4209    switch (val) {
4210        case "1":
4211            $('[data-for="PRIVATE"]').removeClass('hide-me');
4212            $('[data-for="COMPANY"]').addClass('hide-me');
4213            $('[data-for="COMPANY_AND_ITALY"]').addClass('hide-me');
4214            if ($('select[name="country"]').val() == 'ITALY') {
4215                $('[data-for="PRIVATE_AND_ITALY"]').removeClass('hide-me');
4216            } else {
4217                $('[data-for="PRIVATE_AND_ITALY"]').addClass('hide-me');
4218            }
4219            break;
4220        default:
4221            $('[data-for="PRIVATE"]').addClass('hide-me');
4222            $('[data-for="PRIVATE_AND_ITALY"]').addClass('hide-me');
4223            $('[data-for="COMPANY"]').removeClass('hide-me');
4224            if ($('select[name="country"]').val() == 'ITALY') {
4225                $('[data-for="COMPANY_AND_ITALY"]').removeClass('hide-me');
4226            } else {
4227                $('[data-for="COMPANY_AND_ITALY"]').addClass('hide-me');
4228            }
4229            break;
4230    }
4231});
4232if ($('#settings-form select[name="country"]').length > 0 && $('#settings-form select[name="province"] option').length < 2) {
4233    updateProvices();
4234}
4235$('#settings-form #country').on('change', function () {
4236    const val = $(this).val();
4237    $('input[name="province_hidden"]').val('');
4238    switch (val) {
4239        case "ITALY":
4240            if ($('select[name="economic_unit_id"]').val() != '1') {
4241                $('[data-for="COMPANY_AND_ITALY"]').removeClass('hide-me');
4242            } else {
4243                $('[data-for="COMPANY_AND_ITALY"]').addClass('hide-me');
4244            }
4245            if ($('select[name="economic_unit_id"]').val() == '1') {
4246                $('[data-for="PRIVATE_AND_ITALY"]').removeClass('hide-me');
4247            } else {
4248                $('[data-for="PRIVATE_AND_ITALY"]').addClass('hide-me');
4249            }
4250            break;
4251        default:
4252            $('[data-for="COMPANY_AND_ITALY"]').addClass('hide-me');
4253            $('[data-for="PRIVATE_AND_ITALY"]').addClass('hide-me');
4254            break;
4255    }
4256    updateProvices();
4257});
4258function updateProvices() {
4259    let currentProvice = $('select[name="province"]').data('current-val')
4260    let provinces = $('select[name="country"] option:selected').data('provinces');
4261    $('select[name="province"] option').not(':first').remove();
4262    if (provinces.length > 0) {
4263        $('.province-container.select').removeClass('hide-me');
4264        $('.province-container.input').addClass('hide-me');
4265        $('.province-container.select [name="province_hidden"]').attr('name', 'province');
4266        $('.province-container.select [id="province_hidden"]').attr('id', 'province');
4267        $('.province-container.select [for="province_hidden"]').attr('for', 'province');
4268        $('.province-container.input [name="province"]').attr('name', 'province_hidden');
4269        $('.province-container.input [id="province"]').attr('id', 'province_hidden');
4270        $('.province-container.input [for="province"]').attr('for', 'province_hidden');
4271        $('input[name="province_hidden"]').val('');
4272        provinces.forEach(function (province) {
4273            let optionEl = document.createElement('option');
4274            optionEl.value = province['name'] + ' - ' + province['region'] + ' (' + province['code'] + ')';
4275            optionEl.textContent = province['name'] + ' - ' + province['region'] + ' (' + province['code'] + ')';
4276            $('select[name="province"]').append(optionEl);
4277        });
4278        if ($('select[name="province"] option[value="' + currentProvice + '"]').length > 0) {
4279            $('select[name="province"]').val(currentProvice);
4280        } else {
4281            $('select[name="province"]').val('');
4282        }
4283    } else {
4284        $('.province-container.select').addClass('hide-me');
4285        $('.province-container.input').removeClass('hide-me');
4286        $('.province-container.input [name="province_hidden"]').attr('name', 'province');
4287        $('.province-container.input [id="province_hidden"]').attr('id', 'province');
4288        $('.province-container.input [for="province_hidden"]').attr('for', 'province');
4289        $('.province-container.select [name="province"]').attr('name', 'province_hidden');
4290        $('.province-container.select [id="province"]').attr('id', 'province_hidden');
4291        $('.province-container.select [for="province"]').attr('for', 'province_hidden');
4292        $('select[name="province_hidden"]').val('');
4293    }
4294}
4295
4296$('input[name="package_id"]').change(function (e) {
4297    e.preventDefault();
4298    if ($(this).prop('checked')) {
4299        $('input[name="package_id"]:checked').prop('checked', false);
4300        $(this).prop('checked', true);
4301    } else {
4302        if ($('form').data('old-package-id') == '') {
4303            $('input[name="package_id"]:checked').prop('checked', false);
4304        } else {
4305            $(this).prop('checked', true);
4306        }
4307    }
4308    if ($('input[name="package_id"]:checked').val() > 0) {
4309        $('.show-with-package').removeClass('hide-me');
4310    } else {
4311        $('.show-with-package').addClass('hide-me');
4312    }
4313    updateGb();
4314});
4315$('#paymethod_tabs .tab').click(function (e) {
4316    const value = $(this).data('id');
4317    $('input[name="paymethod_id"]').val(value);
4318});
4319$('#reenter-coupon').click(function (e) {
4320    $('.coupon-wrapper').remove();
4321    $('.coupon-input-wrapper').removeClass('hide-me');
4322    $(this).addClass('hide-me');
4323    $('input[name="remove-redeemed-coupon"]').val('1');
4324    $('input[name="coupon_code"]').val('');
4325});
4326$('#open-contact-form').click(function (e) {
4327    getAjaxPopUp('ownpackagepopup');
4328    $(document).on('click', '#send-own-package-request', function (e) {
4329        error = false;
4330        let popupEl = $(this).parents('.my-popup');
4331        let val = parseInt(popupEl.find('#number_articles').val());
4332        let note = popupEl.find('#note').val();
4333        if (val <= 5000 || isNaN(val)) {
4334            let inputEl = popupEl.find('#number_articles');
4335            let inputWrapperEl = inputEl.parents('.my-input');
4336            inputWrapperEl.addClass('error');
4337            inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
4338            if (!error)
4339                inputEl.focus();
4340            error = true;
4341        }
4342        if (!error) {
4343            $('#loading-bar').show();
4344            $.ajax({
4345                type: 'POST',
4346                url: 'sendownpackagerequest',
4347                data: {
4348                    number_articles: val,
4349                    note: note,
4350                },
4351                cache: false,
4352                success: function (result) {
4353                    $('#loading-bar').hide();
4354                    if (result == '1') {
4355                        popupEl.find('.close-and-remove-popup').trigger('click');
4356                        presentToast(translate['SETTINGS']['OWN_PACKAGES_REQUEST_SENDED'], 'fa-paper-plane', 'success')
4357                    } else {
4358                        handleResult(result);
4359                        resolve(false);
4360                    }
4361                },
4362            });
4363        }
4364    });
4365});
4366$('#delete-account').click(async function () {
4367    let res = await submitFormAfterConfirm('', translate['SETTINGS']['DELETE_ACCOUNT_QUESTION']);
4368    if (res) {
4369        let deleteAccountCode = Math.floor((Math.random() * 100000));
4370        await getAjaxPopUp('deleteaccountpopup', { code: deleteAccountCode });
4371        $(document).on('click', '#confirm-delete-account', function () {
4372            let popupEl = $(this).parents('.my-popup');
4373            let code = parseInt(popupEl.find('#delete_code').val());
4374            error = false;
4375            if (code != deleteAccountCode) {
4376                let inputEl = popupEl.find('#delete_code');
4377                let inputWrapperEl = inputEl.parents('.my-input');
4378                inputWrapperEl.addClass('error');
4379                inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
4380                if (!error)
4381                    inputEl.focus();
4382                error = true;
4383            }
4384            if (!error) {
4385                let deleteinput = document.createElement("input");
4386                deleteinput.name = "delete-account";
4387                deleteinput.type = "hidden";
4388                deleteinput.value = "1";
4389                $('#settings-form').append(deleteinput);
4390                resetFormCheckBeforeUnload();
4391                $('#settings-form').submit();
4392            }
4393        });
4394    }
4395});
4396$('#add-gb').click(function () {
4397    updateGb(true);
4398});
4399$('#remove-gb').click(function () {
4400    updateGb(false);
4401});
4402async function updateGb(add = null, setMin = true) {
4403    if ($('#settings-form #memory').length > 0) {
4404        let value = parseInt($('#memory').val());
4405        let currentMemory = parseInt($('#memory').data('current-memory'));
4406
4407        let steps = $('#memory').data('steps');
4408
4409        const currentMemoryInGB = currentMemory / 1024 / 1024 / 1024;
4410        const minVal = Math.ceil(currentMemoryInGB);
4411
4412        let keys = Object.keys(steps).map(Number).sort((a, b) => a - b);
4413
4414        let index = keys.findIndex(k => k >= value);
4415        if (index === -1) index = keys.length - 1;
4416
4417        if (add !== null) {
4418            if (add) {
4419                if (index < keys.length - 1) {
4420                    value = keys[index + 1];
4421                } else {
4422                    presentToast(translate['SETTINGS']['MEMORY']['MAX_VALUE'], 'fa-triangle-exclamation', 'warning', 6000);
4423                    return;
4424                }
4425            } else {
4426                if (index > 0) {
4427                    let newVal = keys[index - 1];
4428                    if (newVal < minVal) {
4429                        return;
4430                    }
4431                    value = newVal;
4432                } else {
4433                    return;
4434                }
4435            }
4436        }
4437
4438        if (value < minVal && setMin) {
4439            value = keys.find(k => k >= minVal) || minVal;
4440        }
4441
4442        $('#memory').val(value);
4443        $('.current-memory-package .value').text(value);
4444
4445        const currentMemoryInPercentage = (currentMemoryInGB / value) * 100;
4446        $('.memory-range').css('--currentMemory', currentMemoryInPercentage + '%');
4447        $('.memory-range').parents('.memory-wrapper').removeClass('full');
4448        $('.memory-range').parents('.memory-wrapper').removeClass('half-full');
4449        $('.memory-range').parents('.memory-wrapper').removeClass('empty');
4450        if (currentMemoryInPercentage >= 90) {
4451            $('.memory-range').parents('.memory-wrapper').addClass('full');
4452        } else if (currentMemoryInPercentage >= 50) {
4453            $('.memory-range').parents('.memory-wrapper').addClass('half-full');
4454        } else {
4455            $('.memory-range').parents('.memory-wrapper').addClass('empty');
4456        }
4457
4458        let price = steps[value] ?? 0;
4459        $('.price-memory span').text(toFloatInput(price));
4460
4461        let packagePrice = parseFloat($('input[name="package_id"]:checked').data('price'));
4462        let total = price + packagePrice;
4463        $('#total .value span').text(toFloatInput(total));
4464    }
4465}
4466// END-SETTING FUNCTIONS
4467// TABLE-LIST FUNCTIONS
4468
4469if ($('.my-table-list').length > 0) {
4470    initTableList();
4471}
4472let infiniteLoadElementsForTableListInit = false;
4473async function initTableList() {
4474    await initTableDataAttrFilter();
4475    if (getCurrentAction() != 'articles') {
4476        await getTableList();
4477        loadNextItemsForTableList();
4478    }
4479}
4480function getFilter() {
4481    let filter = {};
4482    try {
4483        const tableName = $('.my-table-list').data('table-name');
4484        let jsonFilter = getCookie(tableName + '_filter')
4485        if (jsonFilter) {
4486            filter = JSON.parse(jsonFilter);
4487        }
4488    } catch (error) {
4489        //löscht cookie das fehler nicht nochmal kommt
4490        const tableName = $('.my-table-list').data('table-name');
4491        const cookieName = tableName + '_filter';
4492        deleteCookie(cookieName);
4493        $('.my-table-list-filter-search').val('');
4494        updateClearSearch($('.my-table-list-filter-search'));
4495        $('#articles .badges .my-badge.state').removeClass('not-active');
4496        updateFilterCounter();
4497        updateSortForTableList($('.my-table-list').data('init-sort').split('-')[0], $('.my-table-list').data('init-sort').split('-')[1]);
4498        removeDirIdParam();
4499        getTableList();
4500        console.log(error);
4501    }
4502    return filter;
4503}
4504function updateFilter(fname, fvalue) {
4505    try {
4506        let filter = {};
4507        const tableName = $('.my-table-list').data('table-name');
4508        const cookieName = tableName + '_filter';
4509        let jsonFilter = getCookie(cookieName);
4510        if (jsonFilter) {
4511            filter = JSON.parse(jsonFilter);
4512        }
4513        filter[fname] = fvalue;
4514        setCookie(cookieName, JSON.stringify(filter));
4515        updateFilterCounter();
4516    } catch (error) {
4517        console.log(error);
4518    }
4519
4520}
4521if ($('.my-table-list').length > 0)
4522    updateFilterCounter();
4523
4524function updateFilterCounter() {
4525    let filter = {};
4526    const tableName = $('.my-table-list').data('table-name');
4527    const cookieName = tableName + '_filter';
4528    let jsonFilter = getCookie(cookieName);
4529    try {
4530        if (jsonFilter) {
4531            filter = JSON.parse(jsonFilter);
4532        }
4533        if (tableName == 'attachment') {
4534            delete filter['parent_id'];
4535            delete filter['type'];
4536        }
4537        if ($('.my-table-list').data('filter-article_id') !== undefined) {
4538            delete filter['article_id'];
4539        }
4540        if (Object.values(filter).filter(x => (x != '' && x != [] && x != null && x != undefined && x != $('.my-table-list').attr('data-init-sort'))).length > 0) {
4541            $('#clear_filter').css('display', 'inline-flex');
4542        } else {
4543            $('#clear_filter').css('display', 'none');
4544        }
4545        delete filter['searchStr'];
4546        delete filter['orderBy'];
4547        let count = Object.values(filter).filter(x => (x != '' && x != [] && x != null && x != undefined && x != $('.my-table-list').attr('data-init-sort'))).length;
4548        if (count == 0)
4549            count = '';
4550        $('.my-buttons-list #filter .info').text(count);
4551    } catch (error) {
4552        console.log(error);
4553    }
4554}
4555$('#clear_filter').click(function () {
4556    const tableName = $('.my-table-list').data('table-name');
4557    const cookieName = tableName + '_filter';
4558    deleteCookie(cookieName);
4559    $('.my-table-list-filter-search').val('');
4560    updateClearSearch($('.my-table-list-filter-search'));
4561    $('#articles .badges .my-badge.state').removeClass('not-active');
4562    initTableDataAttrFilter(true);
4563    updateSortForTableList($('.my-table-list').data('init-sort').split('-')[0], $('.my-table-list').data('init-sort').split('-')[1]);
4564    updateFilterCounter();
4565    removeDirIdParam();
4566    getTableList();
4567});
4568$('#sort-table-list').on('change', function () {
4569    let direction = 'asc';
4570    if ($('#sort-table-list-order').prop('checked'))
4571        direction = 'desc';
4572    updateSortForTableList($(this).val(), direction);
4573});
4574$('#sort-table-list-order').on('change', function () {
4575    let direction = 'asc';
4576    if ($(this).prop('checked'))
4577        direction = 'desc';
4578    updateSortForTableList($('#sort-table-list').val(), direction);
4579});
4580function updateSortForTableList(newSortColName, direction = '') {
4581    let filter = getFilter();
4582    let currentSort = filter['orderBy'];
4583    if (currentSort == '' || currentSort == undefined)
4584        currentSort = $('.my-table-list').attr('data-init-sort');
4585    const currentSortColName = currentSort.split('-')[0];
4586    const currentSortDirection = currentSort.split('-')[1];
4587    let newSortDirection = 'asc';
4588    if (direction == '') {
4589        if (currentSortColName == newSortColName) {
4590            if (currentSortDirection == 'asc') {
4591                newSortDirection = 'desc';
4592            }
4593        }
4594    } else {
4595        newSortDirection = direction;
4596    }
4597    $('#sort-table-list-order').prop('checked', newSortDirection == 'desc');
4598    if ($('#sort-table-list [value="' + newSortColName + '"]').length > 0) {
4599        $('#sort-table-list').val(newSortColName);
4600    } else {
4601        $('#sort-table-list').val($('#sort-table-list option:first-of-type').val());
4602    }
4603    $('.my-table-list').find('.sort-col').removeClass('asc').removeClass('desc');
4604    $('.my-table-list').find('.sort-col[data-col-name="' + newSortColName + '"]').addClass(newSortDirection);
4605    updateFilter('orderBy', newSortColName + '-' + newSortDirection);
4606    getTableList();
4607}
4608$('.my-table-list .sort-col').click(function () {
4609    updateSortForTableList($(this).data('col-name'));
4610});
4611$('.my-buttons-list #filter').click(function (e) {
4612    let tableName = $('.my-table-list').data('table-name');
4613    if (tableName == 'tests_from_article')
4614        tableName = 'test';
4615    getAjaxPopUp(tableName + 'filterpopup');
4616});
4617function updateCols(tableListName, columns) {
4618    return new Promise((resolve, reject) => {
4619        $('.my-table-list .row .cell').removeClass('hide-me');
4620        columns.forEach((value, idx) => {
4621            $('.my-table-list .row .cell[data-col-name="' + value + '"]').addClass('hide-me');
4622        });
4623        $.ajax({
4624            type: 'POST',
4625            url: 'updatetablelistcolumssettings',
4626            cache: false,
4627            data: {
4628                tableName: tableListName,
4629                columns: columns
4630            },
4631            success: function (result) {
4632                if (result == '1') {
4633                    resolve(true);
4634                } else {
4635                    handleResult(result);
4636                    resolve(false);
4637                }
4638            }
4639        });
4640    })
4641
4642}
4643$(document).on('click', '#confirm-filter', async function () {
4644    const popupEL = $(this).parents('.my-popup');
4645    let data = popupEL.find('form').serializeArray();
4646    let tmpIdx = data.findIndex((el, idx) => el.name == 'cols_val');
4647    if (tmpIdx >= 0) {
4648        let columns = [];
4649        if (popupEL.find('#cols_val').val() !== '')
4650            columns = popupEL.find('#cols_val').val().split(',');
4651        let tableListName = popupEL.find('#cols_val').data('for');
4652        await updateCols(tableListName, columns);
4653        data.splice(tmpIdx, 1);
4654    }
4655    tmpIdx = data.findIndex((el, idx) => el.name == 'cols');
4656    if (tmpIdx >= 0) {
4657        data.splice(tmpIdx, 1);
4658    }
4659    data.forEach((value, idx) => {
4660        if (value.name == 'from_date' || value.name == 'to_date' || value.name == 'ref_date') {
4661            if (!checkDate(value.value)) {
4662                value.value = '';
4663            } else {
4664                let tmpdate = moment(value.value, "DD.MM.YYYY");
4665                value.value = tmpdate.format("YYYY-MM-DD");
4666            }
4667        }
4668        updateFilter(value.name, value.value);
4669    });
4670    popupEL.find('.close-and-remove-popup').trigger('click');
4671    getTableList();
4672});
4673$(document).on('click', '#table-list-edit-element', function (e) {
4674    let link = $(this).data('link');
4675    let id = $('.list-item .select-col input[type="checkbox"]:checked').val().replace(/\D/g, '');
4676    window.open(link + id, '_self');
4677});
4678$(document).on('click', '#table-list-duplicate', function (e) {
4679    let link = $(this).data('link');
4680    let linkArr = link.split('/');
4681    let name = linkArr[linkArr.length - 1].substr(0, linkArr[linkArr.length - 1].indexOf('?'))
4682    let id = $('.list-item .select-col input[type="checkbox"]:checked').val().replace(/\D/g, '');
4683    localStorage.removeItem(name + '-form');
4684    window.open(link + id, '_self');
4685});
4686function initTableDataAttrFilter(afterClear = false) {
4687    return new Promise((resolve, reject) => {
4688        const tabelListEl = $('.my-table-list');
4689        if (tabelListEl.length > 0) {
4690            Object.keys(tabelListEl.data()).forEach(function (key, idx) {
4691                if (!afterClear || key != 'filterdir_id') {
4692                    if (key.startsWith('filter')) {
4693                        const splitted = key.split('filter')[1].toLowerCase();
4694                        updateFilter(splitted, tabelListEl.data(key));
4695                    }
4696                }
4697            })
4698        }
4699        resolve(true);
4700    });
4701}
4702function handleVisibilityChange() {
4703    if (document.visibilityState === "visible") {
4704        initTableDataAttrFilter();
4705    }
4706}
4707document.addEventListener("visibilitychange", handleVisibilityChange, false);
4708let tableListRequest = null;
4709function getTableList(add = false, jumpInOnOneResult = false) {
4710    const tabelListEl = $('.my-table-list');
4711    if (tabelListEl.length == 0) {
4712        return Promise.resolve();
4713    }
4714    if (tableListRequest != null) {
4715        tableListRequest.abort();
4716    }
4717    if (!add) {
4718        tabelListEl.find('.table-list-inner .list-item').remove();
4719    }
4720    return new Promise((resolve, reject) => {
4721        tableListGetCalled = true;
4722        const tableName = tabelListEl.data('table-name');
4723        let filter = getFilter();
4724        let ids = [];
4725        if (add) {
4726            ids = tabelListEl.find('.list-item').map((idx, el) => $(el).data('id')).toArray()
4727        }
4728        $('#loading-bar').show();
4729        tableListRequest = $.ajax({
4730            type: 'POST',
4731            url: 'gettablelist',
4732            cache: false,
4733            data: {
4734                tableName: tableName,
4735                ids: ids,
4736                filter: filter
4737            },
4738            success: function (result) {
4739                $('#loading-bar').hide();
4740                if (!add) {
4741                    tabelListEl.find('.table-list-inner .list-item').remove();
4742                    updateMyButtonsList();
4743                }
4744
4745                if (/<[a-z][\s\S]*>/i.test(result)) {
4746                    infiniteLoadElementsForTableListInit = false;
4747                    tabelListEl.show();
4748                    $('.my-hint').addClass('hide-me');
4749                    tabelListEl.find('.table-list-inner').append(result);
4750                    if ($('.head-row-clone-wrapper').hasClass('active')) {
4751                        rebuildTableHeadRowClone();
4752                    }
4753                    if (!add || tabelListEl.find('#count').text() > 0) {
4754                        $('#table-list-item-count').text(tabelListEl.find('#count').text());
4755                        $('#table-list-item-count_state_1_to_old').text(tabelListEl.find('#count_state_1_to_old').text());
4756                        $('#table-list-item-count_state_2_overdue').text(tabelListEl.find('#count_state_2_overdue').text());
4757                        $('#table-list-item-count_state_3_upcoming').text(tabelListEl.find('#count_state_3_upcomping').text());
4758                        $('#table-list-item-count_state_4_inspected').text(tabelListEl.find('#count_state_4_inspected').text());
4759                        $('#table-list-item-count_state_6_in_stock').text(tabelListEl.find('#count_state_6_in_stock').text());
4760                        $('#table-list-item-count_state_7_not_be_checked').text(tabelListEl.find('#count_state_7_not_be_checked').text());
4761                        $('#table-list-item-count_state_8_not_found').text(tabelListEl.find('#count_state_8_not_found').text());
4762                        $('#table-list-item-count_state_5_in_repair').text(tabelListEl.find('#count_state_5_in_repair').text());
4763                        $('#table-list-item-count_state_9_discarded').text(tabelListEl.find('#count_state_9_discarded').text());
4764                    }
4765                    tabelListEl.find('#count').remove();
4766                    tabelListEl.find('#count_state_1_to_old').remove();
4767                    tabelListEl.find('#count_state_2_overdue').remove();
4768                    tabelListEl.find('#count_state_3_upcomping').remove();
4769                    tabelListEl.find('#count_state_4_inspected').remove();
4770                    tabelListEl.find('#count_state_6_in_stock').remove();
4771                    tabelListEl.find('#count_state_7_not_be_checked').remove();
4772                    tabelListEl.find('#count_state_8_not_found').remove();
4773                    tabelListEl.find('#count_state_5_in_repair').remove();
4774                    tabelListEl.find('#count_state_9_discarded').remove();
4775
4776                    setTimeout(() => {
4777                        tabelListEl.find('.list-item').removeClass('added');
4778                    }, 100);
4779                    if (jumpInOnOneResult) {
4780                        if (tabelListEl.attr('id') == 'article-table-list') {
4781                            if (filter['searchStr'].length > 0 && $('#table-list-item-count').text() == 1 && tabelListEl.find('.list-item').length == 1) {
4782                                if (tabelListEl.find('.list-item').attr('id').includes('article')) {
4783                                    window.open(tabelListEl.find('.list-item').data('openlink'), '_self');
4784                                    return;
4785                                }
4786                            }
4787                        }
4788                    }
4789                } else {
4790                    if (result == 'empty') {
4791                        if (tabelListEl.find('.list-item').length == 0) {
4792                            tabelListEl.hide();
4793                            $('#table-list-item-count').text(0);
4794                            $('#table-list-item-count_state_1_to_old').text(0);
4795                            $('#table-list-item-count_state_2_overdue').text(0);
4796                            $('#table-list-item-count_state_3_upcomping').text(0);
4797                            $('#table-list-item-count_state_4_inspected').text(0);
4798                            $('#table-list-item-count_state_6_in_stock').text(0);
4799                            $('#table-list-item-count_state_7_not_be_checked').text(0);
4800                            $('#table-list-item-count_state_8_not_found').text(0);
4801                            $('#table-list-item-count_state_5_in_repair').text(0);
4802                            $('#table-list-item-count_state_9_discarded').text(0);
4803                            $('.my-hint').removeClass('hide-me');
4804                        }
4805                    } else {
4806                        if (result != 'null')
4807                            handleResult(result);
4808                    }
4809                }
4810                tableListGetCalled = false;
4811            },
4812        });
4813    });
4814}
4815function handleResult(result, err = null, showToast = true) {
4816    if (typeof result == 'string') {
4817        if (result == 'NO_RIGHTS' && showToast) {
4818            presentToast(translate['NO_RIGTHS']["ERROR"]["NO_RIGHTS"], 'fa-triangle-exclamation', 'warning');
4819        } else if (result.includes('<script type="text/javascript">window.location.replace("')) {
4820            window.location.reload();
4821        } else {
4822            presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'warning');
4823            console.log(result, err);
4824            sendErrorToServer({
4825                type: 'js-error',
4826                message: err?.message ?? null,
4827                result: result,
4828                ua: navigator.userAgent ? navigator.userAgent : null,
4829            });
4830        }
4831    } else {
4832        console.log(result, err);
4833        sendErrorToServer({
4834            type: 'js-error',
4835            message: err?.message ?? null,
4836            result: result,
4837            ua: navigator.userAgent ? navigator.userAgent : null,
4838        });
4839    }
4840}
4841// Die Aktionen der Artikelliste funktionieren auch im Prüfkorb. Dort sind alle Artikel des Korbs die Auswahl.
4842function getSelectedListCheckboxes() {
4843    if ($('#cart-popup').length > 0) {
4844        return $('#cart-popup .cart-item input[type="checkbox"]:checked');
4845    }
4846    return $('.list-item .select-col input[type="checkbox"]:checked');
4847}
4848function getListTableName() {
4849    if ($('#cart-popup').length > 0) {
4850        return 'article';
4851    }
4852    return $('.my-table-list').data('table-name');
4853}
4854function getExportIds() {
4855    if ($('#cart-popup').length > 0) {
4856        return getSelectedListCheckboxes().map((idx, el) => $(el).val()).toArray();
4857    }
4858    if ($('.my-table-list').find('.list-item .select-col input[type="checkbox"]:checked').length < $('.my-table-list').find('.list-item .select-col input[type="checkbox"]').length) {
4859        return $('.my-table-list').find('.list-item .select-col input[type="checkbox"]:checked').map((idx, el) => $(el).val()).toArray();
4860    }
4861    return [];
4862}
4863$(document).on('click', '#table-list-select-all', async function () {
4864    $('.my-table-list .select-col input[type="checkbox"]').prop('checked', true);
4865    updateMyButtonsList();
4866});
4867$(document).on('click', '#table-list-deselect-all', async function () {
4868    $('.my-table-list .list-item .select-col input[type="checkbox"]').prop('checked', false);
4869    updateMyButtonsList();
4870});
4871$(document).on('click', '#table-list-export', async function () {
4872    const tabelListEl = $('.my-table-list');
4873    let ids = [];
4874    getSelectedListCheckboxes().each(function () {
4875        ids.push(this.value);
4876    });
4877    const tableName = getListTableName();
4878    await getAjaxPopUp('exportpopup', { tableName: tableName, ids: ids, fromCart: $('#cart-popup').length > 0 });
4879    initForPDFSelect();
4880});
4881
4882// Generischer "Mehrfach bearbeiten"-Einstieg für Modelle/Hersteller/
4883// Kategorien (Ziel-URL/Typ kommen aus data-link/data-type, siehe
4884// models.tmpl.php/manufactures.tmpl.php/model_categories.tmpl.php) - anders
4885// als bei Artikeln (gemischte Liste mit Ordnern, eigener Handler oben) ist
4886// hier jede Liste homogen, ein Filtern nach id-Präfix ist nicht nötig.
4887$(document).on('click', '#table-list-multiple-edit', function () {
4888    let ids = [];
4889    let tablelistName = $('.my-table-list').attr('id');
4890    getSelectedListCheckboxes().each(function () {
4891        if (tablelistName == 'article-table-list') {
4892            if (this.value.startsWith('article')) {
4893                ids.push(this.value.replace(/\D/g, ''));
4894            }
4895        } else {
4896            ids.push(this.value.replace(/\D/g, ''));
4897        }
4898    });
4899    if (ids.length == 0) {
4900        return;
4901    }
4902    let form = $('<form>', { method: 'POST', action: $(this).data('link') });
4903    console.log({ method: 'POST', action: $(this).data('link') });
4904
4905    ids.forEach(function (id) {
4906        form.append($('<input>', { type: 'hidden', name: 'ids[]', value: id }));
4907    });
4908    $('body').append(form);
4909    form.submit();
4910});
4911$(document).on('click', '#table-list-delete', async function () {
4912    const tabelListEl = $('.my-table-list');
4913    let ids = [];
4914    let someDirsNotDeleted = false;
4915    getSelectedListCheckboxes().each(function () {
4916        if ($('#article-table-list').length > 0 && this.value.includes('dir')) {
4917            if ($('.list-item[data-id="' + this.value + '"]').find('.state').hasClass('state_10_empty')) {
4918                ids.push(this.value);
4919            } else {
4920                someDirsNotDeleted = true;
4921            }
4922        } else {
4923            ids.push(this.value);
4924        }
4925    });
4926    const tableName = getListTableName();
4927    let title = translate[tableName.toLocaleUpperCase()]['DELETE_TITLE'];
4928    let message = translate[tableName.toLocaleUpperCase()]['DELETE_MESSAGE'];
4929    if (title === undefined) {
4930        title = translate[tableName.toLocaleUpperCase()]['DELETE_TITLE'];
4931    }
4932    if (message === undefined) {
4933        message = translate[tableName.toLocaleUpperCase()]['DELETE_MESSAGE'];
4934    }
4935    if (ids.some(x => (x.toString().startsWith('dir')))) {
4936        title = translate['ARTICLE']['DELETE_TITLE_DIRS_AND_ARTICLES'];
4937        message = translate['ARTICLE']['DELETE_MESSAGE_DIRS_AND_ARTICLES'];
4938    }
4939    if (await submitFormAfterConfirm(title, message)) {
4940        showLoading(translate['LOADING']);
4941        $.ajax({
4942            type: 'POST',
4943            url: 'deletefromtablelist',
4944            cache: false,
4945            data: {
4946                tableName: tableName,
4947                ids: ids
4948            },
4949            success: function (result) {
4950                hideLoading();
4951                try {
4952                    let data = JSON.parse(result);
4953                    if (typeof data === 'object') {
4954                        let deletedIds = data['deletedIds'];
4955                        let deleteItemCounts = data['deleteItemCounts'];
4956                        if (deletedIds.length == 0 || deletedIds.length != ids.length || someDirsNotDeleted) {
4957                            let str = translate[tableName.toLocaleUpperCase()]['CAN_NOT_DELETE_EVERY_ITEM'];
4958                            presentToast(str, 'fa-triangle-exclamation', 'warning');
4959                        }
4960                        if (deletedIds.length > 0) {
4961                            presentToast(translate['SUCCESSFULLY_DELETED'], 'fa-trash', 'success', 2000);
4962                        }
4963                        let articleCount = 0;
4964                        deletedIds.forEach((id) => {
4965                            if (tableName == 'article') {
4966                                if (String(tabelListEl.find('.list-item[data-id="' + id + '"]').data('id')).startsWith('article')) {
4967                                    articleCount++;
4968                                }
4969                            }
4970                            tabelListEl.find('.list-item[data-id="' + id + '"]').remove();
4971                        });
4972                        if (tableName == 'article') {
4973                            deleteItemCounts = articleCount;
4974                            let articleLeft = parseInt($('#open-action-sheet-add-article .info').text());
4975                            if (isNaN(articleLeft)) {
4976                                articleLeft = 0;
4977                            }
4978                            articleLeft += articleCount;
4979                            $('#open-action-sheet-add-article .info').text(articleLeft);
4980                            if (articleLeft > 0) {
4981                                $('#open-action-sheet-add-article').attr('data-no-articles-left', false);
4982                            }
4983                        }
4984                        let currCount = $('#table-list-item-count').text();
4985                        currCount -= deleteItemCounts;
4986                        $('#table-list-item-count').text(currCount)
4987                        updateMyButtonsList();
4988                        updateArticleData();
4989                        if (tabelListEl.find('.list-item').length == 0) {
4990                            tabelListEl.hide();
4991                            $('.my-hint').removeClass('hide-me');
4992                        }
4993                    } else {
4994                        if (result == 'IDS_EMPTY') {
4995                            let str = translate['CAN_NOT_DELETE_EVERY_ITEM_' + tableName.toLocaleUpperCase()];
4996                            if (str === undefined) {
4997                                str = translate[tableName.toLocaleUpperCase()]['CAN_NOT_DELETE_EVERY_ITEM'];
4998                            }
4999                            presentToast(str, 'fa-triangle-exclamation', 'warning');
5000                        } else {
5001                            handleResult(result);
5002                        }
5003                    }
5004                } catch (err) {
5005                    if (result == 'IDS_EMPTY') {
5006                        let str = translate['CAN_NOT_DELETE_EVERY_ITEM_' + tableName.toLocaleUpperCase()];
5007                        if (str === undefined) {
5008                            str = translate[tableName.toLocaleUpperCase()]['CAN_NOT_DELETE_EVERY_ITEM'];
5009                        }
5010                        presentToast(str, 'fa-triangle-exclamation', 'warning');
5011                    } else {
5012                        handleResult(result);
5013                    }
5014                }
5015            }
5016        });
5017    }
5018});
5019function functionDeleteFromTableList(ids) {
5020    ids.forEach(function (id) {
5021        $('.list-item[data-id="' + id + '"] .select-col input[type="checkbox"]').prop('checked', true);
5022    });
5023    $('#table-list-delete').trigger('click');
5024}
5025function updateArticleData() {
5026    if ($('section#article').length > 0) {
5027        $.ajax({
5028            type: 'GET',
5029            url: 'getarticledataafterdeletetests',
5030            cache: false,
5031            data: {
5032                id: $('input[name="article_id"]').val()
5033            },
5034            success: function (result) {
5035                try {
5036                    let data = JSON.parse(result);
5037                    if (data['state']) {
5038                        $('.my-badge.state').attr("class").split(/\s+/).forEac
5038h((el) => {
5039                            if (el.startsWith('state_')) {
5040                                $('.my-badge.state').removeClass(el);
5041                            }
5042                        })
5043                        $('.my-badge.state').addClass(data['state'].toLowerCase()).text(getStateTranslate(data['state']));
5044                        if (data['date_next_control']) {
5045                            $('input[name="date_next_control"]').val(data['date_next_control']);
5046                            if (['STATE_6_IN_STOCK', 'STATE_7_NOT_BE_CHECKED', 'STATE_8_NOT_FOUND', 'STATE_5_IN_REPAIR', 'STATE_9_DISCARDED', 'STATE_10_EMPTY'].indexOf(data['state']) >= 0) {
5047                                $('input[name="date_next_control"]').parents('.col-12').addClass('hide-me');
5048                            } else {
5049                                $('input[name="date_next_control"]').parents('.col-12').removeClass('hide-me');
5050                            }
5051                        }
5052                        if (data['date_not_ok']) {
5053                            $('input[name="date_not_ok"]').val(data['date_not_ok']);
5054                            if (['STATE_9_DISCARDED'].indexOf(data['state']) >= 0) {
5055                                $('input[name="date_not_ok"]').parents('.col-12').addClass('hide-me');
5056                            } else {
5057                                $('input[name="date_not_ok"]').parents('.col-12').removeClass('hide-me');
5058                            }
5059                        }
5060                    }
5061                } catch (error) {
5062                    handleResult(result);
5063                }
5064            }
5065        });
5066    }
5067}
5068function loadNextItemsForTableList() {
5069    pixelsFromWindowBottomToBottom = 0 + $(document).height() - $(window).scrollTop() - $(window).height();
5070    if (pixelsFromWindowBottomToBottom < 300) {
5071        if (!infiniteLoadElementsForTableListInit)
5072            getTableList(true);
5073        infiniteLoadElementsForTableListInit = true;
5074    }
5075}
5076function setFilterWrapperSticky() {
5077    let rem = 1;
5078    if ($(window).width() < 767) {
5079        rem = 0.5;
5080    }
5081    let filterHeight = 0;
5082    if ($('.filter-wrapper').length > 0) {
5083        if ($(window).scrollTop() > ($('.filter-wrapper-line').offset().top - $('.menu-top').outerHeight() - convertRemToPixels(rem))) {
5084            $('.filter-wrapper-line').height($('.filter-wrapper:not(.sticky)').outerHeight());
5085            if ($('.my-breadcrumbs-wrapper').length > 0) {
5086                if ($('.my-breadcrumbs').is(':visible')) {
5087                    $('.filter-wrapper-line').height($('.filter-wrapper:not(.sticky)').outerHeight() + $('.my-breadcrumbs-wrapper:not(.sticky)').outerHeight());
5088                } else {
5089                    $('.filter-wrapper').addClass('box-shadow');
5090                }
5091            } else {
5092                $('.filter-wrapper').addClass('box-shadow');
5093            }
5094            $('.filter-wrapper').addClass('sticky');
5095            filterHeight = parseInt($('.filter-wrapper.sticky').css('top').match(/\d+/)[0]) + $('.filter-wrapper.sticky').outerHeight() - convertRemToPixels(0.25);
5096        } else {
5097            $('.filter-wrapper-line').height(0);
5098            $('.filter-wrapper').removeClass('sticky');
5099            $('.filter-wrapper').removeClass('box-shadow');
5100            filterHeight = 0;
5101            $('.my-breadcrumbs-wrapper.sticky').css('top', '');
5102
5103        }
5104    }
5105    if ($(window).width() < 767) {
5106        rem = 0.75;
5107    }
5108    if ($('.my-breadcrumbs-wrapper').length > 0) {
5109        if ($('.my-breadcrumbs').is(':visible')) {
5110            if ($(window).scrollTop() > ($('.my-breadcrumbs-wrapper-line').offset().top - $('.menu-top').outerHeight() - convertRemToPixels(rem))) {
5111                $('.my-breadcrumbs-wrapper-line').height($('.my-breadcrumbs-wrapper:not(.sticky)').outerHeight());
5112                $('.my-breadcrumbs-wrapper').addClass('sticky');
5113                if (filterHeight > 0) {
5114                    $('.my-breadcrumbs-wrapper.sticky').css('top', filterHeight);
5115                }
5116            } else {
5117                $('.my-breadcrumbs-wrapper-line').height(0);
5118                $('.my-breadcrumbs-wrapper').removeClass('sticky');
5119            }
5120        }
5121    }
5122}
5123function getStickyStackedTop() {
5124    let top = $('.menu-top').length > 0 ? $('.menu-top').outerHeight() : 0;
5125    if ($('.my-breadcrumbs-wrapper').hasClass('sticky')) {
5126        top = parseFloat($('.my-breadcrumbs-wrapper').css('top')) + $('.my-breadcrumbs-wrapper').outerHeight();
5127    } else if ($('.filter-wrapper').hasClass('sticky')) {
5128        top = parseFloat($('.filter-wrapper').css('top')) + $('.filter-wrapper').outerHeight();
5129    }
5130    return top;
5131}
5132function getOrCreateTableHeadRowClone() {
5133    let $wrapper = $('.head-row-clone-wrapper');
5134    if ($wrapper.length === 0) {
5135        let $origRow = $('.my-table-list .head-row');
5136        $wrapper = $('<div class="head-row-clone-wrapper"><div class="table-list-inner"></div></div>');
5137        $('body').append($wrapper);
5138        $wrapper.on('change', '.select-col input[type="checkbox"]', function () {
5139            $origRow.find('.select-col input[type="checkbox"]').prop('checked', $(this).is(':checked')).trigger('change');
5140        });
5141        $wrapper.on('click', '.sort-col', function (e) {
5142            e.preventDefault();
5143            let colName = $(this).data('col-name');
5144            $origRow.find('.sort-col[data-col-name="' + colName + '"]').trigger('click');
5145        });
5146    }
5147    return $wrapper;
5148}
5149function rebuildTableHeadRowClone() {
5150    let $origRow = $('.my-table-list .head-row');
5151    if ($origRow.length === 0) {
5152        return;
5153    }
5154    let $wrapper = getOrCreateTableHeadRowClone();
5155    $wrapper.find('.table-list-inner').html($origRow.clone(false).prop('outerHTML'));
5156    let $cloneRow = $wrapper.find('.head-row');
5157    let widths = [];
5158    $origRow.find('> .cell').each(function () {
5159        widths.push($(this).outerWidth());
5160    });
5161    $cloneRow.find('> .cell').each(function (i) {
5162        $(this).css({ width: widths[i], minWidth: widths[i], maxWidth: widths[i], boxSizing: 'border-box' });
5163    });
5164    syncTableHeadRowCheckbox();
5165    updateTableHeadRowTransform();
5166}
5167function syncTableHeadRowCheckbox() {
5168    let $origCheckbox = $('.my-table-list .head-row .select-col input[type="checkbox"]');
5169    let $cloneCheckbox = $('.head-row-clone-wrapper .head-row .select-col input[type="checkbox"]');
5170    if ($origCheckbox.length > 0 && $cloneCheckbox.length > 0) {
5171        $cloneCheckbox.prop('checked', $origCheckbox.is(':checked'));
5172    }
5173}
5174function updateTableHeadRowTransform() {
5175    let $tableList = $('.my-table-list');
5176    $('.head-row-clone-wrapper').css('top', getStickyStackedTop())
5177        .find('.table-list-inner').css('transform', 'translateX(' + (-$tableList.scrollLeft()) + 'px)');
5178}
5179let tableHeadRowWasActive = false;
5180function setTableHeadRowSticky() {
5181    let $origRow = $('.my-table-list .head-row');
5182    if ($origRow.length === 0 || !$origRow.is(':visible')) {
5183        $('.head-row-clone-wrapper').removeClass('active');
5184        tableHeadRowWasActive = false;
5185        return;
5186    }
5187    let isActive = $origRow[0].getBoundingClientRect().top < getStickyStackedTop();
5188    if (isActive) {
5189        if (!tableHeadRowWasActive) {
5190            rebuildTableHeadRowClone();
5191        } else {
5192            updateTableHeadRowTransform();
5193        }
5194        $('.head-row-clone-wrapper').addClass('active');
5195    } else {
5196        $('.head-row-clone-wrapper').removeClass('active');
5197    }
5198    tableHeadRowWasActive = isActive;
5199}
5200let tableHeadRowHorizontalRAF = null;
5201$('.my-table-list').on('scroll', function () {
5202    if (!$('.head-row-clone-wrapper').hasClass('active') || tableHeadRowHorizontalRAF) {
5203        return;
5204    }
5205    tableHeadRowHorizontalRAF = requestAnimationFrame(function () {
5206        updateTableHeadRowTransform();
5207        tableHeadRowHorizontalRAF = null;
5208    });
5209});
5210$(document).on('wheel', '.head-row-clone-wrapper', function (e) {
5211    let oe = e.originalEvent;
5212    let deltaX = oe.deltaX;
5213    if (deltaX === 0 && oe.shiftKey) {
5214        deltaX = oe.deltaY;
5215    }
5216    if (deltaX !== 0 && Math.abs(deltaX) >= Math.abs(oe.deltaY)) {
5217        e.preventDefault();
5218        $('.my-table-list').scrollLeft($('.my-table-list').scrollLeft() + deltaX);
5219    }
5220});
5221$(window).on('resize', function () {
5222    if ($('.head-row-clone-wrapper').hasClass('active')) {
5223        rebuildTableHeadRowClone();
5224    }
5225});
5226if ($('.filter-wrapper').length > 0 || $('.my-breadcrumbs-wrapper').length > 0) {
5227    setFilterWrapperSticky();
5228}
5229if ($('.my-table-list').length > 0) {
5230    setTableHeadRowSticky();
5231}
5232let mainScrollRAF = null;
5233$(window).scroll(function (e) {
5234    if (mainScrollRAF) {
5235        return;
5236    }
5237    mainScrollRAF = requestAnimationFrame(function () {
5238        mainScrollRAF = null;
5239        if ($('.filter-wrapper').length > 0 || $('.my-breadcrumbs-wrapper').length > 0) {
5240            setFilterWrapperSticky();
5241        }
5242        if ($('.my-table-list').length > 0) {
5243            loadNextItemsForTableList();
5244            setTableHeadRowSticky();
5245        }
5246    });
5247});
5248function markMultipleListItems(currEl) {
5249    if ($('.list-item .select-col input[type="checkbox"]:checked').length > 0) {
5250        let index = $('.list-item .select-col input[type="checkbox"]').index(currEl);
5251        let indexes = [];
5252        $('.list-item .select-col input[type="checkbox"]:checked').each(function (i, e) {
5253            indexes.push($('.list-item .select-col input[type="checkbox"]').index(e));
5254        });
5255        indexes.sort((a, b) => {
5256            return Math.abs(index - a) - Math.abs(index - b);
5257        });
5258        let nearstIndex = indexes[0];
5259        if (nearstIndex > index) {
5260            for (let i = nearstIndex; i > index; i--) {
5261                $('.row.list-item').eq(i).find('.select-col input[type="checkbox"]:not(:disabled)').prop('checked', true)
5262            }
5263        } else {
5264            for (let i = nearstIndex; i < index; i++) {
5265                $('.row.list-item').eq(i).find('.select-col input[type="checkbox"]:not(:disabled)').prop('checked', true)
5266            }
5267        }
5268    }
5269}
5270$(document).on('click', '.list-item .select-col input[type="checkbox"]', function (e) {
5271    e.preventDefault();
5272    e.stopImmediatePropagation();
5273});
5274$(document).on('mouseup', '.list-item .select-col input[type="checkbox"]', function (e) {
5275    e.preventDefault();
5276    $(this).prop('checked', !$(this).prop('checked'));
5277    updateMyButtonsList();
5278
5279});
5280$(document).on('mousedown', '.list-item .select-col input[type="checkbox"]', function (e) {
5281    if (e.shiftKey && $('.list-item .select-col input[type="checkbox"]:checked').length > 0) {
5282        if (e.shiftKey) {
5283            markMultipleListItems($(this));
5284        }
5285    }
5286});
5287
5288$(document).on('change', '.my-table-list .list-item .select-col input[type="checkbox"]', function () {
5289    updateMyButtonsList();
5290});
5291function updateMyButtonsList() {
5292    const thisCheckedCheckboxes = $('.my-table-list .list-item .select-col input[type="checkbox"]:checked');
5293    const thisCheckboxes = $('.my-table-list .list-item .select-col input[type="checkbox"]');
5294    if (thisCheckedCheckboxes.length > 0) {
5295        $('.my-buttons-list .table-list-checkbox-show').removeClass('hide-me');
5296        $('.my-buttons-list .table-list-checkbox-hide').addClass('hide-me');
5297        if (thisCheckedCheckboxes.length === thisCheckboxes.length) {
5298            $('#table-list-selected-count').text('(' + translate['ALL'] + ')');
5299        } else {
5300            $('#table-list-selected-count').text('(' + thisCheckedCheckboxes.length + ')');
5301        }
5302    } else {
5303        $('.my-table-list').removeClass('long-pressed');
5304        $('.my-buttons-list .table-list-checkbox-show').addClass('hide-me');
5305        $('.my-buttons-list .table-list-checkbox-hide').removeClass('hide-me');
5306        $('#table-list-selected-count').text('');
5307    }
5308    if (thisCheckedCheckboxes.length == 1 && $('.my-table-list .list-item .select-col input[type="checkbox"]:checked').parents('.list-item').attr('id').startsWith('dir')) {
5309        $('.my-buttons-list .my-drop-down .option.show-select-only-one-dir').removeClass('hide-me')
5310    } else {
5311        $('.my-buttons-list .my-drop-down .option.show-select-only-one-dir').addClass('hide-me')
5312    }
5313    if (thisCheckedCheckboxes.length == 1 && $('.my-table-list .list-item .select-col input[type="checkbox"]:checked').parents('.list-item').attr('id').startsWith('article')) {
5314        $('.my-buttons-list .my-drop-down .option.show-select-only-one-article').removeClass('hide-me')
5315    } else {
5316        $('.my-buttons-list .my-drop-down .option.show-select-only-one-article').addClass('hide-me')
5317    }
5318    if ($('.my-table-list .list-item .select-col input[type="checkbox"]:checked').parents('.list-item').toArray().some(x => $(x).attr('id').startsWith('article'))) {
5319        $('.my-buttons-list .my-drop-down .option.show-select-article').removeClass('hide-me')
5320    } else {
5321        $('.my-buttons-list .my-drop-down .option.show-select-article:not(.show-select-dir)').addClass('hide-me');
5322    }
5323    if ($('.my-table-list .list-item .select-col input[type="checkbox"]:checked').parents('.list-item').toArray().some(x => $(x).attr('id').startsWith('dir'))) {
5324        $('.my-buttons-list .my-drop-down .option.show-select-dir').removeClass('hide-me')
5325    } else {
5326        $('.my-buttons-list .my-drop-down .option.show-select-dir:not(.show-select-article)').addClass('hide-me');
5327    }
5328    if (thisCheckedCheckboxes.length == 1) {
5329        $('.my-buttons-list .my-drop-down .option.show-select-only-one').removeClass('hide-me')
5330    } else {
5331        $('.my-buttons-list .my-drop-down .option.show-select-only-one').addClass('hide-me')
5332    }
5333    if (thisCheckedCheckboxes.length != thisCheckboxes.length || thisCheckedCheckboxes.length <= 0) {
5334        $('.my-table-list .head-row .select-col input[type="checkbox"]').prop('checked', false);
5335    }
5336    syncTableHeadRowCheckbox();
5337}
5338$('.my-table-list .head-row .select-col input[type="checkbox"]').change(function () {
5339    if ($(this).prop('checked'))
5340        $('.my-table-list .list-item .select-col input[type="checkbox"]').prop('checked', true);
5341    else
5342        $('.my-table-list .list-item .select-col input[type="checkbox"]').prop('checked', false);
5343    updateMyButtonsList();
5344});
5345let autoCompleteRequest = null;
5346function getAutoComplete() {
5347    $('#loading-bar').hide();
5348    if (autoCompleteRequest != null) {
5349        autoCompleteRequest.abort();
5350    }
5351    return new Promise(function (resolve) {
5352        const val = $('.my-table-list-filter-search').val();
5353        if (val.length > 0) {
5354            $('#loading-bar').show();
5355            const filter = getFilter();
5356            const tableName = $('.my-table-list').data('table-name');
5357            autoCompleteRequest = $.ajax({
5358                type: 'POST',
5359                url: 'getautocomplete',
5360                cache: false,
5361                data: {
5362                    tableName: tableName,
5363                    filter: filter,
5364                    currentSearchString: val
5365                },
5366                success: function (result) {
5367                    try {
5368                        let data = JSON.parse(result);
5369                        $('.my-auto-complete .results').children().remove();
5370                        data.forEach((result, idx) => {
5371                            let resultEl = document.createElement('div');
5372                            resultEl.className = 'result';
5373                            resultEl.setAttribute('data-search-str', result['search_str']);
5374
5375                            let resultType = document.createElement('span');
5376                            resultType.className = 'type';
5377                            resultType.textContent = translate['AUTOCOMPLETE'][result['type']];
5378
5379                            let resultValue = document.createElement('span');
5380                            resultValue.className = 'word';
5381                            resultValue.textContent = result['word'] + ((resultType.textContent !== '') ? ' - ' : '');
5382
5383                            resultEl.appendChild(resultValue);
5384                            resultEl.appendChild(resultType);
5385                            $('.my-auto-complete .results').append(resultEl);
5386                        });
5387                        $('.my-auto-complete').removeClass('hide-me');
5388                    } catch (error) {
5389                        handleResult(result, error);
5390                    }
5391                    $('#loading-bar').hide();
5392                    autoCompleteRequest = null;
5393                    resolve(true);
5394                }
5395            });
5396        } else {
5397            $('.my-auto-complete .results').children().remove();
5398            $('.my-auto-complete').addClass('hide-me');
5399            resolve(true);
5400        }
5401    });
5402}
5403$('.my-table-list-filter-search').on('input', function (e) {
5404    getAutoComplete();
5405});
5406$('.my-table-list-filter-search').on('focus', function (e) {
5407    if ($(this).val().length > 0 && $('.my-auto-complete .result').length > 0)
5408        $('.my-auto-complete').removeClass('hide-me');
5409});
5410$('.my-table-list-filter-search').on('blur', function (e) {
5411    $('.my-auto-complete').addClass('hide-me');
5412});
5413function isTouchDevice() {
5414    return (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
5415}
5416$('.clear-search').click(function (e) {
5417    $(this).parents('.my-input').find('.my-table-list-filter-search').val('');
5418    updateFilter('searchStr', encodeURI($('.my-table-list-filter-search').val()));
5419    updateClearSearch($('.my-table-list-filter-search'));
5420    getTableList(false);
5421    $('.my-auto-complete').addClass('hide-me');
5422    if (!isTouchDevice()) {
5423        $(this).parents('.my-input').find('.my-table-list-filter-search').focus();
5424    }
5425});
5426$(document).on('mousedown', '.my-auto-complete .result', function (e) {
5427    $('.my-table-list-filter-search').val($(this).data('search-str')).blur();
5428    $('.my-auto-complete').addClass('hide-me');
5429    updateFilter('searchStr', encodeURI($('.my-table-list-filter-search').val()));
5430    getTableList(false, true);
5431    e.preventDefault();
5432});
5433$('.my-table-list-filter-search').on('keydown', function (e) {
5434    let autoCompleteEl = $('.my-auto-complete');
5435    if (e.key === "Enter") {
5436        if (autoCompleteEl.is(':visible')) {
5437            if (autoCompleteEl.find('.result.active').length > 0) {
5438                $('.my-table-list-filter-search').val(autoCompleteEl.find('.result.active').data('search-str'));
5439            }
5440        }
5441        updateFilter('searchStr', encodeURI($('.my-table-list-filter-search').val()));
5442        getTableList(false, true);
5443        $('.my-auto-complete').addClass('hide-me');
5444    } else if (e.key === "ArrowDown") {
5445        e.preventDefault();
5446        getNextResult(1);
5447    } else if (e.key === "ArrowUp") {
5448        e.preventDefault();
5449        getNextResult(-1);
5450    } else if (e.key === "Tab") {
5451        e.preventDefault();
5452        if (autoCompleteEl.find('.result.active').length > 0) {
5453            $('.my-table-list-filter-search').val(autoCompleteEl.find('.result.active').data('search-str'));
5454        }
5455    } else if (e.key === "ArrowRight") {
5456        if (autoCompleteEl.find('.result.active').length > 0) {
5457            $('.my-table-list-filter-search').val(autoCompleteEl.find('.result.active').data('search-str'));
5458        }
5459    } else if (e.key === 'Escape') {
5460        e.preventDefault();
5461        $('.my-auto-complete').addClass('hide-me');
5462    }
5463});
5464function getNextResult(factor) {
5465    let autoCompleteEl = $('.my-auto-complete');
5466    let l = autoCompleteEl.find('.result').length;
5467    if (autoCompleteEl.find('.result.active').length > 0) {
5468        let currentIndex = autoCompleteEl.find('.result').index(autoCompleteEl.find('.result.active'));
5469        let nextIndex = currentIndex + factor;
5470        autoCompleteEl.find('.result.active').removeClass('active');
5471        if (nextIndex >= 0 && nextIndex < l) {
5472            $(autoCompleteEl.find('.result').get(nextIndex)).addClass('active');
5473        } else if (nextIndex < 0) {
5474            $(autoCompleteEl.find('.result').get((l - 1))).addClass('active');
5475        } else if (nextIndex >= l) {
5476            $(autoCompleteEl.find('.result').get(0)).addClass('active');
5477        }
5478    } else {
5479        if (factor > 0) {
5480            $(autoCompleteEl.find('.result').get(0)).addClass('active');
5481        } else {
5482            $(autoCompleteEl.find('.result').get((l - 1))).addClass('active');
5483        }
5484    }
5485}
5486
5487// END-TABLE-LIST FUNCTIONS
5488
5489// USER FUNCTIONS
5490$('#edituser-form input[name="admin"]').on("change", function (e) {
5491    if ($(this).prop('checked')) {
5492        $('#edituser-form [name="usergroup_id"]').parents('.my-input').addClass('hide-me');
5493        $('#edituser-form [name="can_see_all_folders"]').parents('.my-input').addClass('hide-me');
5494        $('#user-dir-select').addClass('hide-me');
5495
5496    } else {
5497        $('#edituser-form [name="usergroup_id"]').parents('.my-input').removeClass('hide-me');
5498        $('#edituser-form [name="can_see_all_folders"]').parents('.my-input').removeClass('hide-me');
5499        if ($('#edituser-form select[name="can_see_all_folders"]').val() === '0') {
5500            $('#user-dir-select').removeClass('hide-me');
5501        }
5502    }
5503});
5504$('#edituser-form select[name="can_see_all_folders"], #editusergroup-form input[name="can_see_all_folders"]').on("change", function (e) {
5505    let val = 0;
5506    if ($(this).get(0).nodeName == 'INPUT') {
5507        val = $(this).prop('checked');
5508    } else {
5509        val = $(this).val();
5510    }
5511    if (val == 0) {
5512        $('#user-dir-select').removeClass('hide-me');
5513    } else {
5514        $('#user-dir-select').addClass('hide-me');
5515    }
5516});
5517$('#edituser-form [readonly]:not(.remove-read-only)').on("click", function (e) {
5518    presentToast(translate['USER']['CAN_NOT_EDIT_HAS_OTHER_ACCOUNTS'], 'fa-triangle-exclamation', 'warning');
5519});
5520$('#user-dir-select input[type="checkbox"]').change(function () {
5521    let val = $(this).val().split('-')[1];
5522    let id = $(this).data('id');
5523    if (!$(this).prop("checked") && val == '1') {
5524        $(this).val(id + '-0');
5525        $(this).prop("checked", true);
5526        // $(this).parents('.input-box').find('+ .sub-cat input[type="checkbox"]').each(function (idx, el) {
5527        //     $(el).prop('checked', true).val($(el).data('id') + '-0');
5528        // })
5529    } else if ($(this).prop("checked") && val == '0') {
5530        $(this).val('1');
5531        $(this).prop("checked", false);
5532    } else if (!$(this).prop("checked") && val == '0') {
5533        $(this).val(id + '-1');
5534        // $(this).parents('.input-box').find('+ .sub-cat input[type="checkbox"]').each(function (idx, el) {
5535        //     $(el).prop('checked', false).val($(el).data('id') + '-1');
5536        // })
5537    } else {
5538        $(this).parents('.list-item').find('> .input-box input[type="checkbox"]').each(function (idx, el) {
5539            $(el).prop('checked', true).val($(el).data('id') + '-1');
5540        })
5541    }
5542});
5543function updateChecklistUserDirsParents() {
5544    $('#user-dir-select.my-input.checklist .list-item').each((idx, el) => {
5545        const subCatEl = $(el).find('.sub-cat');
5546        const checkboxes = subCatEl.find('input[type="checkbox"]:not(.parent)');
5547        const leng = checkboxes.length;
5548        if (leng > 0) {
5549            let count = 0;
5550            checkboxes.each((idx, el) => {
5551                if ($(el).prop('checked') && $(el).val('1'))
5552                    count++;
5553            });
5554            if (count == leng) {
5555                $(el).find('>.input-box input[type="checkbox"]').prop('checked', true).val('1');
5556            }
5557        }
5558    });
5559}
5560// END-USER FUNCTIONS
5561// USERGROUP FUNCTIONS
5562if ($('#usergroup-rights.my-input.checklist').length > 0) {
5563    updateChecklistParents();
5564}
5565function updateChecklistParents() {
5566    $('#usergroup-rights.my-input.checklist .list-item').each((idx, el) => {
5567        const subCatEl = $(el).find('.sub-cat');
5568        const checkboxes = subCatEl.find('input[type="checkbox"]:not(.parent)');
5569        const leng = checkboxes.length;
5570        if (leng > 0) {
5571            let count = 0;
5572            checkboxes.each((idx, el) => {
5573                if ($(el).prop('checked'))
5574                    count++;
5575            });
5576            if (count == leng) {
5577                $(el).find('>.input-box input[type="checkbox"]').prop('checked', true);
5578            } else {
5579                $(el).find('>.input-box input[type="checkbox"]').prop('checked', false);
5580            }
5581        }
5582    });
5583}
5584$('#usergroup-rights.my-input.checklist input[type="checkbox"]').change(function (e) {
5585    if ($(this).hasClass('parent')) {
5586        $(this).closest('.list-item').find('input[type="checkbox"]').prop('checked', $(this).prop('checked'));
5587    }
5588    updateChecklistParents();
5589});
5590// END-USERGROUP FUNCTIONS  
5591// BILL FUNCTIONS  
5592async function downloadBill(el) {
5593    let id = el.data('id');
5594    await showLoading(translate['TEST']['FILE_WILL_BE_EXPORTED']);
5595    $.ajax({
5596        type: 'POST',
5597        url: 'getbillbase64',
5598        cache: false,
5599        data: {
5600            id: id
5601        },
5602        success: async function (result) {
5603            try {
5604                let data = JSON.parse(result);
5605                let base64 = data['base64'];
5606                let fileName = data['filename'];
5607                await downloadFile(base64, fileName, 'application/pdf');
5608            } catch (err) {
5609                ha
5609ndleResult(result, err);
5610            }
5611        },
5612    });
5613}
5614$(document).on('click', '.my-table-list .get-bill-pdf', function (e) {
5615    console.log('Ss');
5616
5617    if ($(e.target).prop("tagName") !== 'INPUT')
5618        downloadBill($(this));
5619})
5620$(document).on('change', '#export-popup #file_type', function () {
5621    initForPDFSelect();
5622});
5623function initForPDFSelect() {
5624    const val = $('#export-popup #file_type').val();
5625    if (val == 'PDF') {
5626        $('#export-popup .for-pdf').removeClass('hide-me');
5627    } else {
5628        $('#export-popup .for-pdf').addClass('hide-me');
5629    }
5630    if (val == 'EXCEL') {
5631        $('#export-popup .for-excel').removeClass('hide-me');
5632    } else {
5633        $('#export-popup .for-excel').addClass('hide-me');
5634    }
5635    if (val == 'EXCEL' || val == 'CSV') {
5636        $('#export-popup #date_format').val('d.m.Y');
5637    }
5638}
5639// END-BILL FUNCTIONS  
5640// MODELCATEGORY FUNCTIONS  
5641
5642$(document).on('click', '#open-action-sheet-add-model_category', async function () {
5643    openActionSheetAddModelCategory();
5644});
5645async function openActionSheetAddModelCategory(redirect = '') {
5646    let buttons = [];
5647    buttons.push({
5648        icon: '<i class="fa-solid fa-cloud-arrow-down"></i>',
5649        text: translate['MODEL_CATEGORY']['IMPORT_FROM_DB'],
5650        style: 'default',
5651        handler: async () => {
5652            window.open('model_categories/central_model_categories' + redirect, '_self');
5653            closeMyActionSheet($('#icon-action-sheet'));
5654        }
5655    });
5656    const icon = await getSVG('resources/images/icons/add-model_category.svg');
5657    buttons.push({
5658        icon: icon,
5659        text: translate['MODEL_CATEGORY']['ADD_OWN'],
5660        style: 'default',
5661        handler: (e) => {
5662            window.open('model_categories/editmodel_category' + redirect, '_self');
5663            closeMyActionSheet($('#icon-action-sheet'));
5664        }
5665    });
5666    buttons.push({
5667        icon: '<i class="fa-solid fa-table-list"></i>',
5668        text: translate['MODEL_CATEGORY']['ADD_MULTIPLE'],
5669        style: 'default',
5670        handler: (e) => {
5671            window.open('model_categories/editmultiple?type=model_category&new=1', '_self');
5672            closeMyActionSheet($('#icon-action-sheet'));
5673        }
5674    });
5675    // Abbrechen
5676    buttons.push({
5677        icon: '<i class="fa-solid fa-ban"></i>',
5678        text: translate['DISMISS'],
5679        style: 'default',
5680        handler: () => {
5681            closeMyActionSheet($('#icon-action-sheet'));
5682        }
5683    });
5684    myActionSheet(translate['ADD'], 'icon-action-sheet', buttons);
5685};
5686// END-MODELCATEGORY FUNCTIONS  
5687// MANUFACTURE FUNCTIONS  
5688
5689$(document).on('click', '#open-action-sheet-add-manufacture', async function () {
5690    openActionSheetAddManufacture();
5691});
5692async function openActionSheetAddManufacture(redirect = '') {
5693    let buttons = [];
5694    buttons.push({
5695        icon: '<i class="fa-solid fa-cloud-arrow-down"></i>',
5696        text: translate['MANUFACTURE']['IMPORT_FROM_DB'],
5697        style: 'default',
5698        handler: async () => {
5699            window.open('manufactures/central_manufactures' + redirect, '_self');
5700            closeMyActionSheet($('#icon-action-sheet'));
5701        }
5702    });
5703    const icon = await getSVG('resources/images/icons/add-manufacture.svg')
5704    buttons.push({
5705        icon: icon,
5706        text: translate['MANUFACTURE']['ADD_OWN'],
5707        style: 'default',
5708        handler: (e) => {
5709            window.open('manufactures/editmanufacture' + redirect, '_self');
5710            closeMyActionSheet($('#icon-action-sheet'));
5711        }
5712    });
5713    buttons.push({
5714        icon: '<i class="fa-solid fa-table-list"></i>',
5715        text: translate['MANUFACTURE']['ADD_MULTIPLE'],
5716        style: 'default',
5717        handler: (e) => {
5718            window.open('manufactures/editmultiple?type=manufacture&new=1', '_self');
5719            closeMyActionSheet($('#icon-action-sheet'));
5720        }
5721    });
5722    // Abbrechen
5723    buttons.push({
5724        icon: '<i class="fa-solid fa-ban"></i>',
5725        text: translate['DISMISS'],
5726        style: 'default',
5727        handler: () => {
5728            closeMyActionSheet($('#icon-action-sheet'));
5729        }
5730    });
5731    myActionSheet(translate['ADD'], 'icon-action-sheet', buttons);
5732}
5733// END-MANUFACTURE FUNCTIONS  
5734// MODEL FUNCTIONS  
5735$(document).on('click', '#disconnect-from-db', async function () {
5736    if (await submitFormAfterConfirm(translate['MODEL']['DISCONNECT_FROM_CENTRAL_DB_TITLE'], translate['MODEL']['DISCONNECT_FROM_CENTRAL_DB_MESSAGE'])) {
5737        const id = $(this).data('id');
5738        const tableName = $(this).data('table-name');
5739        $('#loading-bar').show();
5740        $.ajax({
5741            type: 'POST',
5742            url: 'disconnectfromcentraldb',
5743            cache: false,
5744            data: {
5745                id: id,
5746                tableName: tableName
5747            },
5748            success: async function (result) {
5749                $('#loading-bar').hide();
5750                if (result == 'success') {
5751                    $('#disconnect-from-db').remove();
5752                    presentToast(translate['MODEL']['DISCONNECTED'], 'fa-check', 'success', 2000);
5753                } else {
5754                    console.log(result);
5755                }
5756            }
5757        });
5758    }
5759});
5760$(document).on('click', '#open-action-sheet-add-model', async function () {
5761    openActionSheetAddModel();
5762});
5763async function openActionSheetAddModel(redirect = '') {
5764    await ensureTranslate();
5765    let buttons = [];
5766    buttons.push({
5767        icon: '<i class="fa-solid fa-cloud-arrow-down"></i>',
5768        text: translate['MODEL']['IMPORT_FROM_DB'],
5769        style: 'default',
5770        handler: async () => {
5771            window.open('models/central_models' + redirect, '_self');
5772            closeMyActionSheet($('#icon-action-sheet'));
5773        }
5774    });
5775    const icon = await getSVG('resources/images/icons/add-model.svg')
5776    buttons.push({
5777        icon: icon,
5778        text: translate['MODEL']['ADD_OWN'],
5779        style: 'default',
5780        handler: (e) => {
5781            window.open('models/editmodel' + redirect, '_self');
5782            closeMyActionSheet($('#icon-action-sheet'));
5783        }
5784    });
5785    buttons.push({
5786        icon: '<i class="fa-solid fa-table-list"></i>',
5787        text: translate['MODEL']['ADD_MULTIPLE'],
5788        style: 'default',
5789        handler: (e) => {
5790            window.open('models/editmultiple?type=model&new=1', '_self');
5791            closeMyActionSheet($('#icon-action-sheet'));
5792        }
5793    });
5794    // Abbrechen
5795    buttons.push({
5796        icon: '<i class="fa-solid fa-ban"></i>',
5797        text: translate['DISMISS'],
5798        style: 'default',
5799        handler: () => {
5800            closeMyActionSheet($('#icon-action-sheet'));
5801        }
5802    });
5803    myActionSheet(translate['ADD'], 'icon-action-sheet', buttons);
5804}
5805$('#editmodel input[name="model_category_random_id"]').bind("change", function () {
5806    let id = $(this).val();
5807    let path = $('#model_image .prev-img').data('path-to-model-category-img');
5808    if (id != '') {
5809        $.ajax({
5810            url: path + id + '.svg',
5811            type: 'HEAD',
5812            success: function () {
5813                if (!$('#model_image .prev-img').css('background-image').includes('data:image')) {
5814                    $('#model_image .prev-img').css('background-image', 'url(' + path + id + '.svg)');
5815                }
5816                $('#model_image .prev-img').attr('data-default', path + id + '.svg');
5817            }
5818        });
5819    }
5820});
5821$('#editarticle input[name="model_img_val"]').bind("change", function () {
5822    let bgCss = $(this).val();
5823    if (bgCss != '') {
5824        $('.prev-img').css('background-image', bgCss);
5825    }
5826});
5827$(document).on('click', '#checklists .checklist .toggle-questions', function () {
5828    $(this).parents('.checklist').toggleClass('open');
5829    $(this).parents('.checklist').find('.questions').slideToggle(300);
5830});
5831function updateQuestions(list) {
5832    list.find('.question').each(function (idx, el) {
5833        $(el).find('.number').text((idx + 1) + '.');
5834    })
5835}
5836function updateChecklists(list) {
5837    $('#checklists .checklist').each(function (idx, el) {
5838        $(el).find('[name*="checklist-id-"]').attr('name', ('checklist-id-' + idx));
5839        $(el).find('[name*="checklist-name-"]').attr('name', ('checklist-name-' + idx));
5840        $(el).find('[id*="checklist-name-"]').attr('id', ('checklist-name-' + idx));
5841        $(el).find('[for*="checklist-name-"]').attr('for', ('checklist-name-' + idx));
5842        $(el).find('[name*="checklist-controllinterval-"]').attr('name', ('checklist-controllinterval-' + idx));
5843        $(el).find('[name*="checklist-controllinterval_input-"]').attr('name', ('checklist-controllinterval_input-' + idx));
5844        $(el).find('[name*="time_unit_controllinterval_input-"]').attr('name', ('checklist-time_unit_controllinterval_input-' + idx));
5845        $(el).find('[id*="checklist-controllinterval-"]').attr('id', ('checklist-controllinterval-' + idx));
5846        $(el).find('[id*="checklist-controllinterval_input-"]').attr('id', ('checklist-controllinterval_input-' + idx));
5847        $(el).find('[id*="time_unit_controllinterval_input-"]').attr('id', ('checklist-time_unit_controllinterval_input-' + idx));
5848        $(el).find('[name*="question-"]').attr('name', ('question-' + idx + '[]'));
5849        $(el).find('[id*="question-"]').attr('id', ('question-' + idx + '[]'));
5850        $(el).find('[name*="type-"]').attr('name', ('type-' + idx + '[]'));
5851        $(el).find('[id*="type-"]').attr('id', ('type-' + idx + '[]'));
5852    });
5853}
5854$(document).on('click', '#checklists .remove-checklist', function () {
5855    let checklistEl = $(this).parents('.checklist');
5856    checklistEl.remove();
5857    updateChecklists();
5858});
5859
5860$(document).on('click', '#checklists .questions .remove-question', function () {
5861    let questionEl = $(this).parents('.question');
5862    let list = $(this).parents('.list');
5863    questionEl.remove();
5864    updateQuestions(list);
5865});
5866
5867if ($('.questions .list').length > 0) {
5868    $('.questions .list').each(function (idx, el) {
5869        new Sortable(el, {
5870            filter: '.my-button',
5871            handle: '.move-icon',
5872            onUpdate: function (evt) {
5873                updateQuestions($(evt.item).parents('.list'));
5874            },
5875        });
5876    });
5877}
5878async function addChecklist(scroll = true, withQuestions = true) {
5879    let checklist = document.createElement('div');
5880    checklist.className = 'checklist';
5881
5882    let checklistInfo = document.createElement('div');
5883    checklistInfo.className = 'infos';
5884    checklist.appendChild(checklistInfo);
5885
5886    let iconShow = document.createElement('i');
5887    iconShow.className = 'fa-solid fa-plus icon-show toggle-questions';
5888    checklistInfo.appendChild(iconShow);
5889
5890    let iconHide = document.createElement('i');
5891    iconHide.className = 'fa-solid fa-minus icon-hide toggle-questions';
5892    checklistInfo.appendChild(iconHide);
5893
5894    let inputChecklistId = document.createElement('input');
5895    inputChecklistId.setAttribute('type', 'hidden');
5896    inputChecklistId.setAttribute('name', 'checklist-id-');
5897    inputChecklistId.setAttribute('value', '');
5898    checklistInfo.appendChild(inputChecklistId);
5899
5900    let inputs = document.createElement('div');
5901    inputs.className = 'inputs';
5902    checklistInfo.appendChild(inputs);
5903
5904    let nameInput = document.createElement('div');
5905    nameInput.className = 'my-input with-icon';
5906    inputs.appendChild(nameInput);
5907
5908    let nameInputContainer = document.createElement('div');
5909    nameInputContainer.className = 'input-container';
5910    nameInput.appendChild(nameInputContainer);
5911
5912    let nameInputEl = document.createElement('input');
5913    nameInputEl.setAttribute('type', 'text');
5914    nameInputEl.setAttribute('name', 'checklist-name-');
5915    nameInputEl.setAttribute('id', 'checklist-name-');
5916    nameInputEl.setAttribute('placeholder', ' ');
5917    nameInputEl.setAttribute('value', '');
5918    nameInputEl.setAttribute('maxlength', '200');
5919    nameInputContainer.appendChild(nameInputEl);
5920
5921    let nameInputLabel = document.createElement('label');
5922    nameInputLabel.setAttribute('for', 'checklist-name-');
5923    nameInputLabel.innerText = translate['MODEL']['NAME'];
5924    nameInputContainer.appendChild(nameInputLabel);
5925
5926    let nameInputIcon = document.createElement('i');
5927    nameInputIcon.className = 'fa-solid fa-hashtag';
5928    nameInputContainer.appendChild(nameInputIcon);
5929
5930    let nameInputError = document.createElement('div');
5931    nameInputError.className = 'input-error';
5932    nameInputError.setAttribute('data-error1', translate['MODEL']['ERROR']['NAME']);
5933    nameInput.appendChild(nameInputError);
5934
5935    let controllIntervalInput = document.createElement('div');
5936    controllIntervalInput.className = 'my-input time-input with-icon';
5937    inputs.appendChild(controllIntervalInput);
5938
5939    let controllIntervalInputContainer = document.createElement('div');
5940    controllIntervalInputContainer.className = 'input-container';
5941    controllIntervalInput.appendChild(controllIntervalInputContainer);
5942
5943    let controllIntervalInputEl = document.createElement('input');
5944    controllIntervalInputEl.setAttribute('type', 'hidden');
5945    controllIntervalInputEl.setAttribute('name', 'checklist-controllinterval-');
5946    controllIntervalInputEl.setAttribute('id', 'checklist-controllinterval-');
5947    controllIntervalInputEl.setAttribute('placeholder', ' ');
5948    controllIntervalInputEl.setAttribute('value', '');
5949    controllIntervalInputContainer.appendChild(controllIntervalInputEl);
5950
5951    let controllIntervalInputEl2 = document.createElement('input');
5952    controllIntervalInputEl2.setAttribute('type', 'text');
5953    controllIntervalInputEl2.setAttribute('name', 'checklist-controllinterval_input-');
5954    controllIntervalInputEl2.setAttribute('id', 'checklist-controllinterval_input-');
5955    controllIntervalInputEl2.setAttribute('placeholder', ' ');
5956    controllIntervalInputEl2.setAttribute('value', '');
5957    controllIntervalInputContainer.appendChild(controllIntervalInputEl2);
5958
5959    let controllIntervalSelect = document.createElement('select');
5960    controllIntervalSelect.setAttribute('name', 'time_unit_controllinterval_input-');
5961    controllIntervalSelect.setAttribute('id', 'time_unit_controllinterval_input-');
5962    controllIntervalInputContainer.appendChild(controllIntervalSelect);
5963
5964    let timeUnits = [];
5965    timeUnits.push({ name: 'year', title: 'TIME_UNIT_YEAR', divisor_to_min: 525960 });
5966    timeUnits.push({ name: 'month', title: 'TIME_UNIT_MONTH', divisor_to_min: 43830 });
5967    timeUnits.push({ name: 'week', title: 'TIME_UNIT_WEEK', divisor_to_min: 10080 });
5968    timeUnits.push({ name: 'day', title: 'TIME_UNIT_DAY', divisor_to_min: 1440 });
5969    //timeUnits.push({ name: 'hour', title: 'TIME_UNIT_HOUR', divisor_to_min: 60 });
5970    //timeUnits.push({ name: 'minute', title: 'TIME_UNIT_MINUTE', divisor_to_min: 1 });
5971    timeUnits.forEach(timeUnit => {
5972        let controllIntervalOption = document.createElement('option');
5973        if (timeUnit.title == 'TIME_UNIT_MONTH') {
5974            controllIntervalOption.setAttribute('selected', 'selected');
5975        }
5976        controllIntervalOption.setAttribute('value', timeUnit.name);
5977        controllIntervalOption.setAttribute('data-divisor', timeUnit.divisor_to_min);
5978        controllIntervalOption.innerText = translate['TIME_UNIT_TITLES'][timeUnit.title];
5979        controllIntervalSelect.appendChild(controllIntervalOption);
5980    });
5981
5982    let controllIntervalLabel = document.createElement('label');
5983    controllIntervalLabel.setAttribute('for', 'storage_time_input-');
5984    controllIntervalLabel.innerText = translate['MODEL']['CHECKLIST_CONTROLLINTERVAL'];
5985    controllIntervalInputContainer.appendChild(controllIntervalLabel);
5986
5987    controllIntervalInputContainer.insertAdjacentHTML('beforeend', await getSVG('resources/images/icons/checklist.svg'));
5988
5989    let controllIntervalIcon = document.createElement('i');
5990    controllIntervalIcon.className = 'select-icon fa-solid fa-angle-down';
5991    controllIntervalInputContainer.appendChild(controllIntervalIcon);
5992
5993    let controllIntervalError = document.createElement('div');
5994    controllIntervalError.className = 'input-error';
5995    controllIntervalError.setAttribute('data-error1', translate['MODEL']['ERROR']['MAX_INT_VALUE']);
5996    controllIntervalError.setAttribute('data-error2', translate['MODEL']['ERROR']['CHECKLIST_CONTROLLINTERVAL']);
5997    controllIntervalInput.appendChild(controllIntervalError);
5998
5999    let removeChecklist = document.createElement('button');
6000    removeChecklist.setAttribute('type', 'button');
6001    removeChecklist.className = 'remove-checklist danger my-icon-button';
6002    checklistInfo.appendChild(removeChecklist);
6003
6004    let removeChecklistIcon = document.createElement('i');
6005    removeChecklistIcon.className = 'fa-solid fa-trash';
6006    removeChecklist.appendChild(removeChecklistIcon);
6007
6008    let questions = document.createElement('div');
6009    questions.className = 'questions';
6010    checklist.appendChild(questions);
6011
6012    let questionsList = document.createElement('div');
6013    questionsList.className = 'list';
6014    questions.appendChild(questionsList);
6015
6016    let addQuestion = document.createElement('button');
6017    addQuestion.setAttribute('type', 'button');
6018    addQuestion.setAttribute('id', 'add-question');
6019    addQuestion.className = 'my-button';
6020    questions.appendChild(addQuestion);
6021
6022    addQuestion.insertAdjacentHTML('beforeend', await getSVG('resources/images/icons/add-question.svg'));
6023
6024    let addQuestionSpan = document.createElement('span');
6025    addQuestionSpan.innerText = translate['MODEL']['ADD_QUESTION'];
6026    addQuestion.appendChild(addQuestionSpan);
6027
6028    $('#checklists').append(checklist);
6029    if (withQuestions) {
6030        addNewQuestion($(checklist).find('.list'), translate['MODEL']['STANDART_QUESTION_1'] + (language == 'bz' ? '\n\n' + otherTranslate['it']['MODEL']['STANDART_QUESTION_1'] : ''), 'radio', false);
6031        addNewQuestion($(checklist).find('.list'), translate['MODEL']['STANDART_QUESTION_2'] + (language == 'bz' ? '\n\n' + otherTranslate['it']['MODEL']['STANDART_QUESTION_2'] : ''), 'radio', false);
6032
6033        new Sortable(questionsList, {
6034            filter: '.my-button',
6035            handle: '.move-icon',
6036            onUpdate: function (evt) {
6037                updateQuestions($(evt.item).parents('.list'));
6038            },
6039        });
6040    }
6041    updateChecklists();
6042    $('#checklists .checklist:last-of-type .icon-show').trigger('click');
6043    if (scroll) {
6044        scrollToEl($('#checklists .checklist:last-of-type'));
6045    }
6046    return checklist;
6047}
6048$(document).on('click', '#add-checklist', async function () {
6049    addChecklist();
6050});
6051
6052async function addNewQuestion(listEl, text = '', type = 'radio', scrollAfterAppend = true) {
6053    let question = document.createElement('div');
6054    question.className = 'question';
6055
6056    let toolbar = document.createElement('div');
6057    toolbar.className = 'toolbar';
6058    question.appendChild(toolbar);
6059
6060    let title = document.createElement('div');
6061    title.className = 'title';
6062    toolbar.appendChild(title);
6063
6064    let number = document.createElement('span');
6065    number.className = 'number';
6066    number.innerText = '0.';
6067    title.appendChild(number);
6068
6069    let titleText = document.createElement('span');
6070    titleText.innerHTML = '&nbsp;' + translate['MODEL']['QUESTION'];
6071    title.appendChild(titleText);
6072
6073    let selectTypDiv = document.createElement('div');
6074    selectTypDiv.className = 'my-select-button checklist-type no-max-width small';
6075    let selectTyp = document.createElement('select');
6076    selectTyp.setAttribute('name', 'type-');
6077    selectTyp.setAttribute('id', 'type-');
6078    selectTypDiv.appendChild(selectTyp);
6079
6080    Object.entries(translate['MODEL']['QUESTION_TYPES']).forEach((data) => {
6081        let option = document.createElement('option');
6082        option.setAttribute('value', data[0].toLocaleLowerCase());
6083        option.innerText = data[1];
6084        selectTyp.appendChild(option);
6085        if (data[0].toLocaleLowerCase() == type)
6086            selectTyp.value = data[0].toLocaleLowerCase();
6087    });
6088
6089    let selectIcon = document.createElement('i');
6090    selectIcon.className = 'select-icon fa-solid fa-angle-down';
6091    selectTypDiv.appendChild(selectIcon);
6092
6093    title.appendChild(selectTypDiv);
6094
6095    let removeQuestion = document.createElement('button');
6096    removeQuestion.setAttribute('type', 'button');
6097    removeQuestion.className = 'remove-question danger my-icon-button';
6098    toolbar.appendChild(removeQuestion);
6099
6100    let removeQuestionIcon = document.createElement('i');
6101    removeQuestionIcon.className = 'fa-solid fa-trash-can';
6102    removeQuestion.appendChild(removeQuestionIcon);
6103
6104    let move = document.createElement('button');
6105    move.setAttribute('type', 'button');
6106    move.className = 'my-icon-button move-icon';
6107    toolbar.appendChild(move);
6108
6109    let moveIcon = document.createElement('i');
6110    moveIcon.className = 'fa-solid fa-arrows-up-down-left-right';
6111    move.appendChild(moveIcon);
6112
6113    let textareaWrapper = document.createElement('div');
6114    textareaWrapper.className = 'textarea-wrapper';
6115    question.appendChild(textareaWrapper);
6116
6117    let textarea = document.createElement('textarea');
6118    textarea.setAttribute('name', 'question-');
6119    textarea.setAttribute('id', 'question-');
6120    textarea.setAttribute('placeholder', '...');
6121    textarea.value = text;
6122    textareaWrapper.appendChild(textarea);
6123
6124    listEl.append(question);
6125    updateQuestions(listEl);
6126    updateChecklists();
6127    if (scrollAfterAppend)
6128        scrollToEl(listEl.find('.question:last-of-type'));
6129    return true;
6130}
6131
6132$(document).on('click', '#add-question', async function () {
6133    addNewQuestion($(this).parents('.questions').find('.list'));
6134});
6135
6136
6137// END-MODEL FUNCTIONS  
6138// ARTICLE FUNCTIONS  
6139
6140function getParentDirs(dirID) {
6141    if ($('#article-table-list').length > 0) {
6142        $('.my-breadcrumbs').children().remove();
6143        if (dirID) {
6144            $('.my-breadcrumbs').removeClass('hide-me');
6145            return new Promise((resolve, reject) => {
6146                $.ajax({
6147                    type: 'POST',
6148                    url: 'getparentdirs',
6149                    cache: false,
6150                    data: {
6151                        dir_id: dirID
6152                    },
6153                    success: function (result) {
6154                        try {
6155                            let data = JSON.parse(result);
6156                            if (typeof data === 'object') {
6157                                data.forEach(function (breadcrumb) {
6158                                    let breadcrumbEl = document.createElement('a');
6159                                    breadcrumbEl.setAttribute('data-dir', breadcrumb['link']);
6160                                    breadcrumbEl.className = 'breadcrumb';
6161                                    breadcrumbEl.innerHTML = breadcrumb['icon'];
6162                                    let breadcrumbElName = document.createElement('span');
6163                                    breadcrumbElName.textContent = breadcrumb['name'];
6164                                    breadcrumbEl.appendChild(breadcrumbElName);
6165                                    $('.my-breadcrumbs').append(breadcrumbEl);
6166                                });
6167                                if (data.length > 1) {
6168                                    let backActionDataDir = data[(data.length - 2)]['link'];
6169                                    $('.back-button').attr('data-dir', backActionDataDir).removeClass('hide-me');
6170                                }
6171                                changeBreadcrumbsView();
6172                                resolve(true);
6173                            } else {
6174                                handleResult(result);
6175                                resolve(false);
6176                            }
6177                        } catch (error) {
6178                            handleResult(result, error);
6179                            resolve(false);
6180                        }
6181                    }
6182                });
6183            })
6184        } else {
6185            $('.my-breadcrumbs').addClass('hide-me');
6186            $('.back-button').attr('data-dir', '').addClass('hide-me');
6187            let url_str = window.location.href;
6188            let url = new URL(url_str);
6189            let search_params = url.searchParams;
6190            if (search_params.has('dir_id')) {
6191                window.history.pushState('', document.title, removeParam("dir_id", url_str));
6192            }
6193        }
6194    }
6195}
6196function removeDirIdParam() {
6197    getParentDirs('');
6198}
6199$(document).on('click', '.list-item a[data-dir], .list-item[data-dir], .breadcrumb[data-dir], .back-button[data-dir]', function (e) {
6200    if ($(e.target).prop("tagName") !== 'INPUT') {
6201        $('.my-tooltip-content').remove();
6202        let dirID = $(this).attr('data-dir');
6203        $('.back-button').removeClass('hide-me').attr('data-dir', dirID);
6204        replaceUrlParam('dir_id', dirID);
6205        $('.my-table-list-filter-search').val('');
6206        updateClearSearch($('.my-table-list-filter-search'));
6207        updateFilter('searchStr', '');
6208        updateFilter('dir_id', dirID);
6209        getParentDirs(dirID);
6210        getTableList();
6211    }
6212});
6213function getCurrentAction() {
6214    let url = window.location.href;
6215    url = url.substring(url.lastIndexOf('/') + 1);
6216    if (url.includes('?')) {
6217        url = url.substring(0, url.indexOf('?'));
6218    }
6219    return url;
6220}
6221async function initDirIDParam() {
6222    if (getCurrentAction() == 'articles') {
6223        let url_str = window.location.href;
6224        let url = new URL(url_str);
6225        let search_params = url.searchParams;
6226        if (search_params.has('states')) {
6227            let statesParam = search_params.get('states').split(',').map(s => s.trim()).filter(s => s.length > 0);
6228            if (statesParam.length > 0) {
6229                updateFilter('states', statesParam);
6230                $('#articles .badges .my-badge.state').each(function () {
6231                    $(this).toggleClass('not-active', statesParam.indexOf($(this).data('state')) === -1);
6232                });
6233            }
6234        }
6235        if (search_params.has('dir_id')) {
6236            $('.back-button').removeClass('hide-me').attr('data-dir', search_params.get('dir_id'));
6237            getParentDirs(search_params.get('dir_id'));
6238            $('.my-table-list-filter-search').val('');
6239            updateClearSearch($('.my-table-list-filter-search'));
6240            updateFilter('searchStr', '');
6241            updateFilter('dir_id', search_params.get('dir_id'));
6242            replaceUrlParam('dir_id', search_params.get('dir_id'));
6243            await getTableList();
6244            loadNextItemsForTableList
6245        } else {
6246            let filter = getFilter();
6247            if (filter['dir_id']) {
6248                $('.back-button').removeClass('hide-me').attr('data-dir', filter['dir_id']);
6249                getParentDirs(filter['dir_id']);
6250                $('.my-table-list-filter-search').val('');
6251                updateClearSearch($('.my-table-list-filter-search'));
6252                updateFilter('searchStr', '');
6253                updateFilter('dir_id', filter['dir_id']);
6254                replaceUrlParam('dir_id', filter['dir_id']);
6255                await getTableList();
6256                loadNextItemsForTableList
6257            } else {
6258                getParentDirs('');
6259                updateFilter('dir_id', '');
6260                await getTableList();
6261                loadNextItemsForTableList
6262            }
6263        }
6264    }
6265}
6266$('#articles .badges .my-badge.state').click(function () {
6267    let clickedState = $(this).data('state');
6268    let filter = getFilter();
6269    let states = [];
6270    if (filter['states'] !== undefined) {
6271        states = filter['states'];
6272    }
6273    if (states.length == 0) {
6274        $('#articles .badges .my-badge.state').addClass('not-active');
6275        states.push(clickedState);
6276        $(this).removeClass('not-active');
6277    } else {
6278        if ($(this).hasClass('not-active')) {
6279            $(this).removeClass('not-active');
6280            states.push(clickedState);
6281        } else {
6282            $(this).addClass('not-active');
6283            const index = states.indexOf(clickedState);
6284            if (index !== -1) {
6285                states.splice(index, 1);
6286            }
6287        }
6288        if (states.length == 0 || states.length == $('#articles .badges .my-badge.state').length) {
6289            $('#articles .badges .my-badge.state').removeClass('not-active');
6290            states = [];
6291        }
6292    }
6293    updateFilter('states', states);
6294    getTableList();
6295});
6296$(document).on('change', '#show_articles_from_dir', function () {
6297    if ($(this).val() == 1) {
6298        $('#order_by_dirs [value="1"]').prop('disabled', false);
6299    } else {
6300        $('#order_by_dirs').val('0');
6301        $('#order_by_dirs [value="1"]').prop('disabled', true);
6302    }
6303});
6304async function openmoveelementstodirpopup(data, animationTime = null) {
6305    await getAjaxPopUp('moveelementstodirpopup', data, animationTime);
6306    $('#move-elements-to-dir-popup #move_dir_tabs .my-tab-nav').on('click', function (e) {
6307        e.preventDefault();
6308        const tab = $(this).data('tab');
6309        $('#move-elements-to-dir-popup #move_dir_tabs .my-tab-nav').removeClass('active');
6310        $(this).addClass('active');
6311        $('#move-elements-to-dir-popup .tab-content').removeClass('active');
6312        $('#move-elements-to-dir-popup .tab-content[data-tab-content="' + tab + '"]').addClass('active');
6313        $('#move-elements-to-dir-popup .popup-body').animate({
6314            scrollTop: 0
6315        }, 200);
6316        if (tab === 'search' && !isTouchDevice()) {
6317            $('#move-elements-to-dir-popup #search').focus();
6318        }
6319    });
6320    $('#move-elements-to-dir-popup #search').on('input', function (e) {
6321        const val = $(this).val().toLowerCase();
6322        let allElements = $('#move-elements-to-dir-popup #forsearch .dir');
6323        if (val.length > 0) {
6324            allElements.each((idx, el) => {
6325                const name = $(el).find('.name').text().toLowerCase();
6326                if (name.includes(val)) {
6327                    $(el).attr('data-order', 0);
6328                } else {
6329                    $(el).attr('data-order', levenshteinDistance(val, name));
6330                }
6331            });
6332            allElements.sort((a, b) => $(a).attr("data-order") - $(b).attr("data-order")).appendTo("#move-elements-to-dir-popup #forsearch");
6333        } else {
6334            allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#move-elements-to-dir-popup #forsearch");
6335        }
6336        $('#move-elements-to-dir-popup .popup-body').animate({
6337            scrollTop: 0
6338        }, 200);
6339    });
6340
6341    $('#move-elements-to-dir-popup .clear-search').click(function (e) {
6342        $(this).parents('.my-input').find('input[type="search"]').val('');
6343        let allElements = $('#move-elements-to-dir-popup .elements .element');
6344        allElements.sort((a, b) => $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase())).appendTo("#move-elements-to-dir-popup #forsearch");
6345        $('#move-elements-to-dir-popup .popup-body').animate({
6346            scrollTop: 0
6347        }, 200);
6348        if (!isTouchDevice()) {
6349            $(this).parents('.my-input').find('input[type="search"]').focus();
6350        }
6351    });
6352}
6353$(document).on('click', '#table-list-move-to-dir', function () {
6354    let filter = getFilter();
6355    let dirID = null;
6356    if (filter['dir_id']) {
6357        dirID = filter['dir_id'];
6358    }
6359    openmoveelementstodirpopup({ dir_id: dirID });
6360    $(document).off('click', '#move-elements-to-dir-popup #confirm_move').on('click', '#move-elements-to-dir-popup #confirm_move', function (e) {
6361        let dirID = getCurrentDirFromMoveElementsPopup();
6362        let ids = [];
6363        getSelectedListCheckboxes().each(function () {
6364            ids.push(this.value);
6365        });
6366        showLoading(translate['ARTICLE']['FOLDERS_WILL_BE_MOVED']);
6367        $.ajax({
6368            type: 'POST',
6369            url: 'moveelementstodir',
6370            cache: false,
6371            data: {
6372                dir_id: dirID,
6373                ids: ids
6374            },
6375            success: async function (result) {
6376                $('#loading-bar').hide();
6377                try {
6378                    let data = JSON.parse(result);
6379                    if (typeof data === 'object') {
6380                        hideLoading();
6381                        hidePopUp($('#move-elements-to-dir-popup'));
6382                        $('#move-elements-to-dir-popup').remove();
6383                        if (data['success']) {
6384                            getTableList();
6385                        }
6386                        if (data['not_all_moved']) {
6387                            presentToast(translate['ARTICLE']['NOT_ALL_ELEMENTS_CAN_BE_MOVED'], 'fa-triangle-exclamation', 'warning');
6388                        }
6389                    }
6390                } catch (err) {
6391                    hideLoading();
6392                    handleResult(result, err);
6393                }
6394            }
6395        });
6396    });
6397});
6398$(document).on('click', '#move-elements-to-dir-popup .move-to-dir', async function (e) {
6399    e.preventDefault();
6400    let id = $(this).data('id');
6401    if (id === '') {
6402        id = null;
6403    }
6404
6405    moveToNextDir(id);
6406});
6407async function moveToNextDir(dir_id) {
6408    $('#move-elements-to-dir-popup').addClass('old');
6409    const targetDirID = $('input[name="current_id"]').val();
6410    openmoveelementstodirpopup({ dir_id: dir_id, targetDirID: targetDirID }, 0);
6411    hidePopUp($('#move-elements-to-dir-popup.old').remove());
6412    showPopUp($('#move-elements-to-dir-popup'), 0);
6413}
6414$(document).on('click', '#move-elements-to-dir-popup .dir', async function (e) {
6415    let id = $(this).find('.move-to-dir').data('id');
6416    $('#move-elements-to-dir-popup .dir').removeClass('active');
6417    $(this).addClass('active');
6418});
6419function getCurrentDirFromMoveElementsPopup() {
6420    let dirID = null;
6421    let activeDir = $('#move-elements-to-dir-popup .dir.active');
6422    if (activeDir.length > 0) {
6423        dirID = $(activeDir).find('.move-to-dir').data('id');
6424    } else {
6425        activeDir = $('#move-elements-to-dir-popup .parent_dir .move-to-dir');
6426        if (activeDir.length > 0) {
6427            dirID = activeDir.data('currentid');
6428        }
6429    }
6430    if (dirID == null || dirID == '') {
6431        dirID = 0;
6432    }
6433    return dirID;
6434}
6435function getCurrentDirNameFromMoveElementsPopup() {
6436    let dirName = translate['ARTICLE']['ALL_TITLE'];
6437    let activeDir = $('#move-elements-to-dir-popup .dir.active');
6438    if (activeDir.length > 0) {
6439        dirName = $(activeDir).find('.name').text();
6440    } else {
6441        activeDir = $('#move-elements-to-dir-popup .parent_dir .name');
6442        if (activeDir.length > 0) {
6443            dirName = activeDir.text();
6444        }
6445    }
6446    return dirName;
6447}
6448
6449$(document).on('click', '.add-new-dir', async function () {
6450    let dirID = getCurrentDirFromMoveElementsPopup();
6451    await getAjaxPopUp('adddirpopup', { dir_id: dirID });
6452});
6453
6454$(document).on('click', '#open-action-sheet-add-article', async function (e) {
6455    if (!$(e.target).hasClass('info')) {
6456        let buttons = [];
6457        let canEditArticle = $(this).data('can-edit-article');
6458        let canEditDir = $(this).data('can-edit-dir');
6459        let noArticlesLeft = $('#open-action-sheet-add-article').attr('data-no-articles-left') != 'false';
6460        if (canEditArticle) {
6461            const iconArticle = await getSVG('resources/images/icons/add-article.svg')
6462            buttons.push({
6463                icon: iconArticle,
6464                text: translate['ARTICLE']['ADD_TITLE'],
6465                style: 'default',
6466                handler: async () => {
6467                    if (noArticlesLeft) {
6468                        getAjaxPopUp('noarticlesleft');
6469                    } else {
6470                        let filter = getFilter();
6471                        let link = 'articles/editarticle';
6472                        if (filter['dir_id']) {
6473                            link = link + '?parent_dir=' + filter['dir_id'];
6474                        }
6475                        window.open(link, '_self');
6476                    }
6477                    closeMyActionSheet($('#icon-action-sheet'));
6478                }
6479            });
6480            const iconAddMultipleArticle = await getSVG('resources/images/icons/add-multiple-articles.svg')
6481            buttons.push({
6482                icon: iconAddMultipleArticle,
6483                text: translate['ARTICLE']['ADD_MULTIPLE'],
6484                style: 'default',
6485                handler: async () => {
6486                    if (noArticlesLeft) {
6487                        getAjaxPopUp('noarticlesleft');
6488                    } else {
6489                        let link = 'articles/editmultiple?type=article&new=1';
6490                        window.open(link, '_self');
6491                    }
6492                    closeMyActionSheet($('#icon-action-sheet'));
6493                }
6494            });
6495        }
6496        if (canEditDir) {
6497            const iconDir = await getSVG('resources/images/icons/add-dir.svg')
6498            buttons.push({
6499                icon: iconDir,
6500                text: translate['DIR']['ADD_TITLE'],
6501                style: 'default',
6502                handler: (e) => {
6503                    let filter = getFilter();
6504                    let link = 'articles/editdir';
6505                    if (filter['dir_id']) {
6506                        link = link + '?parent_dir=' + filter['dir_id'];
6507                    }
6508                    window.open(link, '_self');
6509                    closeMyActionSheet($('#icon-action-sheet'));
6510                }
6511            });
6512        }
6513        // Abbrechen
6514        buttons.push({
6515            icon: '<i class="fa-solid fa-ban"></i>',
6516            text: translate['DISMISS'],
6517            style: 'default',
6518            handler: () => {
6519                closeMyActionSheet($('#icon-action-sheet'));
6520            }
6521        });
6522        myActionSheet(translate['ADD'], 'icon-action-sheet', buttons);
6523    }
6524});
6525function addDirAsync(form) {
6526    let formData = new FormData(form);
6527    let error = false;
6528    $('.my-input').removeClass('error');
6529    let name = formData.get("name").trim();
6530    let parent_dir = formData.get("parent_dir").trim();;
6531    if (name.length < 1) {
6532        let inputEl = $(form).find("[name='name']");
6533        let inputWrapperEl = inputEl.parents('.my-input');
6534        inputWrapperEl.addClass('error');
6535        inputWrapperEl.find('.input-error').text(inputWrapperEl.find('.input-error').data('error1'));
6536        if (!error)
6537            inputEl.focus();
6538        error = true;
6539    }
6540    if (!error) {
6541        $('#loading-bar').show();
6542        $.ajax({
6543            type: 'POST',
6544            url: 'adddirasync',
6545            cache: false,
6546            data: {
6547                name: name,
6548                parent_dir: parent_dir
6549            },
6550            success: async function (result) {
6551                trackEventGoogleAnalytics("Ordner hinzugefügt");
6552                $('#loading-bar').hide();
6553                try {
6554                    let data = JSON.parse(result);
6555                    if (typeof data === 'object') {
6556                        if (data['success']) {
6557                            const newDir = data['data'];
6558                            moveToNextDir(newDir['id']);
6559                            await hidePopUp($('#add-dir'));
6560                            $('#add-dir').remove();
6561                        }
6562                    } else {
6563                        handleResult(result);
6564                    }
6565                } catch (err) {
6566                    handleResult(result, err);
6567                }
6568
6569            }
6570        });
6571    }
6572    return false;
6573}
6574$(document).on('change', '#editarticle-form input[name="date_of_manufacture"]', function () {
6575    const val = $(this).val();
6576    if (val) {
6577        if (checkDate(val)) {
6578            let dateMomentObject = moment(val, "DD.MM.YYYY");
6579            let tmpDateOfManufacture = dateMomentObject.toDate();
6580            let tmpDateOfPurchase = checkDate($('input[name="date_of_purchase"]').val()) ? moment($('input[name="date_of_purchase"]').val(), "DD.MM.YYYY").toDate() : null;
6581            let tmpDateFirstUse = checkDate($('input[name="date_first_use"]').val()) ? moment($('input[name="date_first_use"]').val(), "DD.MM.YYYY").toDate() : null;
6582            let editModus = $('input[name="edit"]').val();
6583            if (editModus != '1') {
6584                if (tmpDateOfPurchase == null) {
6585                    $('input[name="date_of_purchase"]').val(dateMomentObject.format("DD.MM.YYYY")).change();
6586                    tmpDateOfPurchase = tmpDateOfManufacture;
6587                }
6588                if (tmpDateFirstUse == null) {
6589                    $('input[name="date_first_use"]').val(dateMomentObject.format("DD.MM.YYYY")).change();
6590                    tmpDateFirstUse = tmpDateOfPurchase;
6591                }
6592                if (tmpDateOfManufacture > tmpDateOfPurchase) {
6593                    $('input[name="date_of_purchase"]').val(dateMomentObject.format("DD.MM.YYYY")).change();
6594                }
6595                if (tmpDateOfManufacture > tmpDateFirstUse) {
6596                    $('input[name="date_first_use"]').val(dateMomentObject.format("DD.MM.YYYY")).change();
6597                }
6598            }
6599        }
6600    }
6601
6602});
6603$(document).on('change', '#editarticle-form input[name="date_of_purchase"]', function () {
6604    const val = $(this).val();
6605    if (val) {
6606        if (checkDate(val)) {
6607            let dateMomentObject = moment(val, "DD.MM.YYYY")
6608            let tmpDateOfPurchase = dateMomentObject.toDate();
6609            let tmpDateFirstUse = checkDate($('input[name="date_first_use"]').val()) ? moment($('input[name="date_first_use"]').val(), "DD.MM.YYYY").toDate() : null;
6610            let editModus = $('input[name="edit"]').val();
6611            if (editModus != '1') {
6612                if (tmpDateFirstUse == null) {
6613                    $('input[name="date_first_use"]').val(dateMomentObject.format("DD.MM.YYYY"));
6614                    tmpDateFirstUse = tmpDateOfPurchase;
6615                }
6616                if (tmpDateOfPurchase > tmpDateFirstUse) {
6617                    $('input[name="date_first_use"]').val(dateMomentObject.format("DD.MM.YYYY"));
6618                }
6619            }
6620        }
6621    }
6622});
6623$(document).on('change', '#editarticle-form input[name="not_ok"]', function () {
6624    let checked = $(this).prop('checked');
6625    if (checked) {
6626        $('#editarticle-form .show-on-not-ok').removeClass('hide-me');
6627    } else {
6628        $('#editarticle-form .show-on-not-ok').addClass('hide-me');
6629    }
6630});
6631$('#open_add_test_popup').click(function () {
6632    getAjaxPopUp('addtestpopup', { article_id: $(this).data('article_id') })
6633});
6634if ($('#show-add-test-popup').length > 0) {
6635    let multipleModus = $('#skip-test').length > 0;
6636    let testSite = $('#set-not-found').length > 0;
6637    getAjaxPopUp('addtestpopup', { article_id: $('#show-add-test-popup').data('article_id'), multipleModus: multipleModus, testSite: testSite });
6638}
6639$(document).on('click', '.confirm_add_test', function () {
6640    let article_id = $('input[name="article_id"]').val();
6641    if (article_id === undefined)
6642        article_id = $('#open_add_test_popup').data('article_id');
6643    if (article_id === undefined)
6644        article_id = $('#show-add-test-popup').data('article_id');
6645
6646    const values = $('#add-test-popup input[name="checklist_for_tests[]"]:checked').map((idx, el) => $(el).val()).toArray();
6647    let link = 'articles/article/tests/edittest?article_id=' + article_id + '&checklist_ids=' + values.join(',');
6648    if ($('#show-add-test-popup').length > 0) {
6649        replaceUrlParam('checklist_ids', values.join(','))
6650        link = location.href;
6651    }
6652    window.open(link, '_self');
6653});
6654$(document).on('click', '.trigger-back-button', function () {
6655    if ($('.back-button').length > 0) {
6656        if ($('.back-button').attr('href') != '') {
6657            window.open($('.back-button').attr('href'), '_self');
6658        }
6659    }
6660});
6661
6662// END-ARTICLE FUNCTIONS  
6663
6664// TEST FUNCTIONS  
6665$('.check-all-in-checklist i').click(function () {
6666    const val = $(this).data('for');
6667    const id = $(this).parents('.check-all-in-checklist').data('id');
6668    $('.my-checklist[data-id="' + id + '"]').find('input[value="' + val + '"]').prop('checked', true).change();
6669});
6670$('.my-checklist[data-id] input').change(function () {
6671    $(this).parents('.my-checklist').find('.checklist-title').removeClass('error');
6672})
6673$(document).on('click', '#add-postitive-multiple-tests', async function () {
6674    let ids = [];
6675    getSelectedListCheckboxes().each(function () {
6676        ids.push(this.value);
6677    });
6678    let showSign = $(this).data('show_sign') == 1;
6679    let message = translate['ARTICLE']['INFO_POSITIVE_MULTIPLE_TESTS'].replace('##STATES##', '<span class="my-badge small active state state_2_overdue">' + getStateTranslate('STATE_2_OVERDUE') + '</span>, <span class="my-badge small active state state_3_upcoming">' + getStateTranslate('STATE_3_UPCOMING') + '</span> ' + translate['ARTICLE']['INFO_COMMENT_FOR_POSITIVE_MULTIPLE_TESTS_AND'] + ' <span class="my-badge small active state state_4_inspected">' + getStateTranslate('STATE_4_INSPECTED') + '</span>');
6680    let confirm = await submitFormAfterConfirm(translate['ARTICLE']['ADD_POSITIVE_MULTIPLE_TEST'], message);
6681    if (confirm) {
6682        let withSign = false;
6683        let sign = '';
6684        if (showSign) {
6685            withSign = await submitFormAfterConfirm('', translate['TEST']['MESSAGE_SIGN']);
6686            if (withSign) {
6687                sign = await getUserSignPopUp();
6688            }
6689        }
6690        let comment = await getCommentForPositiveMultipleTest();
6691        showLoading(translate['ARTICLE']['ADD_POSITIVE_MULTIPLE_TEST_LOADING']);
6692
6693        $.ajax({
6694            type: 'POST',
6695            url: 'addpositivemultipletests',
6696            cache: false,
6697            data: {
6698                sign: sign,
6699                comment: comment,
6700                ids: ids
6701            },
6702            success: async function (result) {
6703                hideLoading();
6704                try {
6705                    let data = JSON.parse(result);
6706                    if (typeof data === 'object') {
6707                        if (data['success'] == true) {
6708                            trackEventGoogleAnalytics("Test hinzugefügt", { count: data['count'] });
6709                            window.location.replace(location.href + (location.href.includes('?') ? ('&positivemultipletestadded=1&count=' + data['count']) : ('?positivemultipletestadded=1&count=' + data['count'])));
6710                        } else {
6711                            presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'warning');
6712                        }
6713                    } else {
6714                        handleResult(result, err);
6715                    }
6716                } catch (err) {
6717                    handleResult(result, err);
6718                }
6719            }
6720        });
6721    }
6722});
6723$(document).on('click', '#change-multi-date-first-use', async function () {
6724    let ids = [];
6725    getSelectedListCheckboxes().each(function () {
6726        ids.push(this.value);
6727    });
6728    let res = await getAjaxPopUp('changemultidatefirstusepopup');
6729    if (res) {
6730        $(document).on('click', '.confirm-change-multi-date-first-use', async function () {
6731            $('#loading-bar').show();
6732            let date_first_use = $('#changemultidatefirstusepopup #date_first_use').val();
6733            await hidePopUp($('#changemultidatefirstusepopup'));
6734            $.ajax({
6735                type: 'POST',
6736                url: 'changemultidatefirstuse',
6737                cache: false,
6738                data: {
6739                    ids: ids,
6740                    date_first_use: date_first_use
6741                },
6742                success: async function (result) {
6743                    $('#loading-bar').hide();
6744                    try {
6745                        let data = JSON.parse(result);
6746                        if (typeof data === 'object') {
6747                            if (data['success'] == true) {
6748                                window.location.replace(location.href + (location.href.includes('?') ? ('&saved=1') : ('?saved=1')));
6749                            } else {
6750                                presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'warning');
6751                            }
6752                        } else {
6753                            handleResult(result, err);
6754                        }
6755                    } catch (err) {
6756                        handleResult(result, err);
6757                    }
6758                }
6759            });
6760        });
6761    }
6762});
6763$(document).on('click', '#mark-as-not-found, #mark-as-found', async function () {
6764    let value = $(this).attr('id') == 'mark-as-not-found';
6765
6766    let ids = [];
6767    getSelectedListCheckboxes().each(function () {
6768        ids.push(this.value);
6769    });
6770    let message = translate['ARTICLE']['SIGN_ALL_MARKED_ARTICLES_AS_NOT_FOUND'].replace('##STATES##',
6771        '<span class="my-badge small active state state_1_to_old">' + getStateTranslate('STATE_1_TO_OLD') + '</span>, ' +
6772        '<span class="my-badge small active state state_2_overdue">' + getStateTranslate('STATE_2_OVERDUE') + '</span>, ' +
6773        '<span class="my-badge small active state state_3_upcoming">' + getStateTranslate('STATE_3_UPCOMING') + '</span>, ' +
6774        '<span class="my-badge small active state state_4_inspected">' + getStateTranslate('STATE_4_INSPECTED') + '</span>, ' +
6775        '<span class="my-badge small active state state_6_in_stock">' + getStateTranslate('STATE_6_IN_STOCK') + '</span> ' +
6776        '<span class="my-badge small active state state_5_in_repair">' + getStateTranslate('STATE_5_IN_REPAIR') + '</span> ' +
6777        translate['ARTICLE']['INFO_COMMENT_FOR_POSITIVE_MULTIPLE_TESTS_AND'] +
6778        ' <span class="my-badge small active state state_7_not_be_checked">' + getStateTranslate('STATE_7_NOT_BE_CHECKED') + '</span>');
6779    if (!value) {
6780        message = translate['ARTICLE']['SIGN_ALL_MARKED_ARTICLES_AS_FOUND'].replace('##STATES##',
6781            '<span class="my-badge small active state state_8_not_found">' + getStateTranslate('STATE_8_NOT_FOUND') + '</span>');
6782    }
6783    let confirm = await submitFormAfterConfirm('', message);
6784    if (confirm) {
6785        showLoading(translate['LOADING']);
6786        $.ajax({
6787            type: 'POST',
6788            url: 'markasnotfound',
6789            cache: false,
6790            data: {
6791                value: value,
6792                ids: ids
6793            },
6794
6795            success: async function (result) {
6796                hideLoading();
6797                try {
6798                    let data = JSON.parse(result);
6799                    if (typeof data === 'object') {
6800                        if (data['success'] == true) {
6801                            getTableList();
6802                            if (value) {
6803                                presentToast(translate['ARTICLE']['MARKED_AS_NOT_FOUND_SUCCESSFULLY'].replace('##', data['count']), 'fa-magnifying-glass', 'success');
6804                            } else {
6805                                presentToast(translate['ARTICLE']['MARKED_AS_FOUND_SUCCESSFULLY'].replace('##', data['count']), 'fa-magnifying-glass', 'success');
6806                            }
6807                        } else {
6808                            presentToast(translate['ERROR_GENERAL'], 'fa-triangle-exclamation', 'warning');
6809                        }
6810                    } else {
6811                        handleResult(result);
6812                    }
6813                } catch (err) {
6814                    handleResult(result, err);
6815                }
6816            }
6817        });
6818    }
6819});
6820async function getCommentForPositiveMultipleTest() {
6821    return new Promise(async function (resolve) {
6822        let comment = '';
6823        await getAjaxPopUp('getcommentforpositivemultipletestpopup');
6824        $('#comment-for-positive-multiple-test-popup .confirm-comment-for-positive-multiple-test').click(function () {
6825            let comment = $('#comment-for-positive-multiple-test-popup #comment').val();
6826            resolve(comment);
6827        });
6828    })
6829}
6830$(document).on('click', '#add-multiple-tests', async function (e) {
6831    $('#loading-bar').show();
6832    const openInNewTab = e.metaKey || e.ctrlKey;
6833    let ids = [];
6834    getSelectedListCheckboxes().each(function () {
6835        ids.push(this.value);
6836    });
6837    let showSign = $(this).data('show_sign') == 1;
6838    $.ajax({
6839        type: 'POST',
6840        url: 'getallarticleidsformultipletest',
6841        cache: false,
6842        data: {
6843            ids: ids
6844        },
6845        success: async function (result) {
6846            $('#loading-bar').hide();
6847            try {
6848                let data = JSON.parse(result);
6849                if (typeof data === 'object') {
6850                    if (data.ids.length > 0) {
6851                        startMultipleTest(data.ids, showSign, openInNewTab);
6852                    }
6853                } else {
6854                    handleResult(result, err);
6855                }
6856            } catch (err) {
6857                handleResult(result, err);
6858            }
6859        }
6860    });
6861});
6862async function startMultipleTest(ids, showSign, openInNewTab) {
6863    let multipleTestData = [];
6864    ids.forEach(id => {
6865        multipleTestData.push({
6866            id: id,
6867            test_added: false,
6868            test_id: 0
6869        });
6870    })
6871    localStorage.setItem('multiple_test_data', JSON.stringify(multipleTestData));
6872    let withSign = false;
6873    if (showSign) {
6874        withSign = await submitFormAfterConfirm('', translate['TEST']['MESSAGE_SIGN']);
6875    }
6876    if (withSign) {
6877        let sign = await getUserSignPopUp();
6878        if (sign) {
6879            localStorage.setItem('sign_img', sign);
6880        } else {
6881            localStorage.removeItem('sign_img');
6882        }
6883    } else {
6884        localStorage.removeItem('sign_img');
6885    }
6886    window.open('articles/edittest?multipletest=1&article_id=' + ids[0] + '&key=0', openInNewTab ? '_blank' : '_self');
6887}
6888// CART FUNCTIONS
6889function getCart() {
6890    try {
6891        let cart = JSON.parse(localStorage.getItem('test_cart'));
6892        if (Array.isArray(cart)) {
6893            return cart;
6894        }
6895    } catch (err) {
6896    }
6897    return [];
6898}
6899function setCart(cart) {
6900    localStorage.setItem('test_cart', JSON.stringify(cart));
6901    updateCartButton();
6902}
6903function addToCart(ids) {
6904    let cart = getCart();
6905    let added = 0;
6906    ids.forEach(id => {
6907        id = parseInt(id);
6908        if (!cart.includes(id)) {
6909            cart.push(id);
6910            added++;
6911        }
6912    });
6913    setCart(cart);
6914    return added;
6915}
6916function removeFromCart(id) {
6917    setCart(getCart().filter(cartId => cartId != id));
6918}
6919function updateCartButton() {
6920    let cart = getCart();
6921    $('#cart-button').find('.cart-count').text(cart.length).toggleClass('hide-me', cart.length == 0);
6922    let article_id = $('#toggle-cart').data('article_id');
6923    if (article_id) {
6924        let inCart = cart.includes(parseInt(article_id));
6925        $('#toggle-cart').find('.add').toggleClass('hide-me', inCart);
6926        $('#toggle-cart').find('.remove').toggleClass('hide-me', !inCart);
6927    }
6928}
6929$(document).on('click', '#add-to-cart', function () {
6930    $('#loading-bar').show();
6931    let ids = [];
6932    getSelectedListCheckboxes().each(function () {
6933        ids.push(this.value);
6934    });
6935    $.ajax({
6936        type: 'POST',
6937        url: 'getarticleidsforcart',
6938        cache: false,
6939        data: {
6940            ids: ids
6941        },
6942        success: function (result) {
6943            $('#loading-bar').hide();
6944            try {
6945                let data = JSON.parse(result);
6946                if (typeof data === 'object') {
6947                    if (data.ids.length > 0) {
6948                        let added = addToCart(data.ids);
6949                        presentToast(translate['CART']['ADDED'].replace('##COUNT##', added), 'fa-basket-shopping', 'success', 3000);
6950                    } else {
6951                        presentToast(translate['CART']['NO_TESTABLE'], 'fa-triangle-exclamation', 'warning', 5000);
6952                    }
6953                } else {
6954                    handleResult(result);
6955                }
6956            } catch (err) {
6957                handleResult(result, err);
6958            }
6959        }
6960    });
6961});
6962$(document).on('click', '#toggle-cart', function () {
6963    let article_id = parseInt($(this).data('article_id'));
6964    if (getCart().includes(article_id)) {
6965        removeFromCart(article_id);
6966    } else {
6967        addToCart([article_id]);
6968        presentToast(translate['CART']['ADDED'].replace('##COUNT##', 1), 'fa-basket-shopping', 'success', 3000);
6969    }
6970});
6971$(document).on('click', '#cart-button', async function () {
6972    if ($('#cart-popup').length == 0) {
6973        await getAjaxPopUp('getcart', { ids: getCart() });
6974        syncCartWithPopup();
6975        focusCartInput();
6976    }
6977});
6978function syncCartWithPopup() {
6979    // Entfernt Artikel, die es nicht mehr gibt oder die der User nicht mehr sehen darf
6980    let existingIds = $('#cart-popup .cart-item').map((idx, el) => parseInt($(el).data('id'))).toArray();
6981    setCart(getCart().filter(id => existingIds.includes(id)));
6982    updateCartPopupCount();
6983}
6984function updateCartPopupCount() {
6985    let count = getCart().length;
6986    $('#cart-popup .cart-empty').toggleClass('hide-me', count > 0);
6987    $('#cart-popup .cart-actions-dropdown').toggleClass('hide-me', count == 0);
6988}
6989// Eingabefeld für HID-NFC-Leser am Desktop: Der Leser tippt die ID wie eine Tastatur und schickt meist ein Enter
6990function focusCartInput() {
6991    if (!$('html').data('plt-android') && !$('html').data('plt-ios') && !('ontouchstart' in window)) {
6992        $('#cart-code-input').focus();
6993    }
6994}
6995async function submitCartCodeInput() {
6996    let input = $('#cart-code-input');
6997    let code = input.val().trim();
6998    input.val('');
6999    if (code !== '') {
7000        await addCodeToCart(normalizeScannedCode(code));
7001    }
7002    focusCartInput();
7003}
7004$(document).on('keydown', '#cart-code-input', function (e) {
7005    if (e.key == 'Enter') {
7006        e.preventDefault();
7007        submitCartCodeInput();
7008    }
7009});
7010$(document).on('click', '#cart-popup .cart-code-submit', function () {
7011    submitCartCodeInput();
7012});
7013$(document).on('click', '#cart-popup', function () {
7014    focusCartInput();
7015});
7016$(document).on('click', '#cart-popup .cart-remove', function () {
7017    removeFromCart($(this).parents('.cart-item').data('id'));
7018    $(this).parents('.cart-item').remove();
7019    updateCartPopupCount();
7020});
7021$(document).on('click', '#cart-popup .cart-clear', async function () {
7022    if (getCart().length == 0) {
7023        return;
7024    }
7025    let confirm = await submitFormAfterConfirm(translate['CART']['CLEAR'], translate['CART']['CLEAR_MESSAGE']);
7026    if (confirm) {
7027        setCart([]);
7028        $('#cart-popup').find('.close-and-remove-popup').first().trigger('click');
7029    }
7030});
7031// Nach Aktionen, die Artikel verändern, wird die Liste im Prüfkorb neu geladen
7032$(document).ajaxSuccess(function (event, xhr, settings) {
7033    if ($('#cart-popup').length > 0 && ['markasnotfound', 'moveelementstodir', 'deletefromtablelist'].includes(settings.url)) {
7034        refreshCartPopup();
7035    }
7036});
7037function refreshCartPopup() {
7038    $.ajax({
7039        type: 'POST',
7040        url: 'getcart',
7041        cache: false,
7042        data: {
7043            data: JSON.stringify({ ids: getCart() })
7044        },
7045        success: function (result) {
7046            if (/<[a-z][\s\S]*>/i.test(result)) {
7047                $('#cart-popup .cart-list').html($('<div>').html(result).find('.cart-list').html());
7048                syncCartWithPopup();
7049            }
7050        }
7051    });
7052}
7053$(document).on('click', '#cart-popup .cart-scan', function () {
7054    scanCodesToCart(getNFC, '.nfc_unsubscribe');
7055});
7056$(document).on('click', '#cart-popup .cart-scan-qr', function () {
7057    if ($('html').data('plt-android') || $('html').data('plt-ios')) {
7058        // Der native Scanner der App schließt sich nach jedem Scan, deshalb wird er pro Code neu gestartet.
7059        // Pause danach, damit der native Scanner sauber schließt, der Ergebnis-Toast sichtbar bleibt,
7060        // und erst danach die Kamera wieder öffnet (statt direkt zurück in den nächsten Scan zu laufen).
7061        scanCodesToCart(getQRCode, '.close-webqr', 2500);
7062        return;
7063    }
7064    // Im Browser bleibt die Kamera offen, bis der User sie schließt
7065    let lastCode = '';
7066    let lastTime = 0;
7067    openWebQr(async function (code) {
7068        // Derselbe Code bleibt meist noch ein paar Sekunden im Bild
7069        if (code === lastCode && Date.now() - lastTime < 3000) {
7070            lastTime = Date.now();
7071            return;
7072        }
7073        lastCode = code;
7074        lastTime = Date.now();
7075        await addCodeToCart(normalizeScannedCode(code));
7076    });
7077});
7078async function scanCodesToCart(getCode, cancelSelector, pauseAfterScanMs = 0) {
7079    let code = await getCodeOrCancel(getCode, cancelSelector);
7080    while (code) {
7081        await addCodeToCart(normalizeScannedCode(code));
7082        // Das Web-QR-Popup wird beim Schließen animiert, das nächste darf erst danach geöffnet werden
7083        while ($('#webqr-popup').length > 0) {
7084            await new Promise(resolve => setTimeout(resolve, 100));
7085        }
7086        if (pauseAfterScanMs) {
7087            await new Promise(resolve => setTimeout(resolve, pauseAfterScanMs));
7088        }
7089        code = await getCodeOrCancel(getCode, cancelSelector);
7090    }
7091}
7092// Bricht der User das Scan-Popup ab, bleibt das Promise von getNFC() bzw. getQRCode() offen. Deshalb wird zusätzlich auf den Abbruch gewartet.
7093// e.originalEvent gibt es nur bei echten Klicks, die Scan-Funktionen schließen das Popup per trigger bzw. per Funktion.
7094function getCodeOrCancel(getCode, cancelSelector) {
7095    return new Promise((resolve, reject) => {
7096        $(document).on('click.cartscan', cancelSelector, function (e) {
7097            if (e.originalEvent) {
7098                $(document).off('click.cartscan');
7099                resolve(null);
7100            }
7101        });
7102        getCode().then(code => {
7103            $(document).off('click.cartscan');
7104            resolve(code);
7105        });
7106    });
7107}
7108function addCodeToCart(code) {
7109    return new Promise((resolve, reject) => {
7110        $.ajax({
7111            type: 'POST',
7112            url: 'getarticleidbycode',
7113            cache: false,
7114            data: {
7115                code: code
7116            },
7117            success: async function (result) {
7118                try {
7119                    let data = JSON.parse(result);
7120                    if (data['status'] == 'found') {
7121                        if (getCart().includes(parseInt(data['id']))) {
7122                            presentToast(translate['CART']['ALREADY_IN'].replace('##NAME##', data['name']), 'fa-basket-shopping', 'warning', 3000);
7123                        } else {
7124                            addToCart([data['id']]);
7125                            $('#cart-popup .cart-list').append(data['html']);
7126                            updateCartPopupCount();
7127                            presentToast(translate['CART']['ADDED_ARTICLE'].replace('##NAME##', data['name']), 'fa-basket-shopping', 'success', 3000);
7128                        }
7129                    } else {
7130                        presentToast(translate['CART']['SCAN_' + data['status'].toUpperCase()], 'fa-triangle-exclamation', 'warning', 5000);
7131                    }
7132                } catch (err) {
7133                    handleResult(result, err);
7134                }
7135                resolve(true);
7136            }
7137        });
7138    });
7139}
7140// END-CART FUNCTIONS
7141$(document).on('click', '#skip-test', function (e) {
7142    let url_str = window.location.href;
7143    let url = new URL(url_str);
7144    let search_params = url.searchParams;
7145    let multipleTestDataKey = 0;
7146    search_params.forEach(function (val, key) {
7147        if (key == 'key') {
7148            multipleTestDataKey = val;
7149        }
7150    });
7151    let IDs = localStorage.getItem('multiple_test_data');
7152    let multipleTestData = JSON.parse(IDs);
7153    multipleTestDataKey = parseInt(multipleTestDataKey) + 1;
7154    if (multipleTestData.length > multipleTestDataKey) {
7155        if (multipleTestData[multipleTestDataKey]['test_added'] && multipleTestData[multipleTestDataKey]['test_id'] > 0) {
7156            window.open('articles/edittest?multipletest=1&id=' + multipleTestData[multipleTestDataKey]['test_id'] + '&key=' + multipleTestDataKey, '_self');
7157        } else {
7158            window.open('articles/edittest?multipletest=1&article_id=' + multipleTestData[multipleTestDataKey].id + '&key=' + multipleTestDataKey, '_self');
7159        }
7160    } else {
7161        let cookieData = getCookie('article_filter');
7162        let filter = JSON.parse(cookieData);
7163        localStorage.removeItem('multiple_test_data');
7164        localStorage.removeItem('sign_img');
7165        if (filter['dir_id']) {
7166            window.open('articles?dir_id=' + filter['dir_id'], '_self');
7167        } else {
7168            window.open('articles', '_self');
7169        }
7170    }
7171});
7172$(document).on('click', '#set-not-found', function (e) {
7173    let val = $(this).data('val');
7174    let article_id = $(this).data('article_id');
7175    $.ajax({
7176        type: 'POST',
7177        url: 'setarticlenotfound',
7178        cache: false,
7179        data: {
7180            val: val,
7181            article_id: article_id
7182        },
7183        success: async function (result) {
7184            try {
7185                let data = JSON.parse(result);
7186                if (typeof data === 'object') {
7187                    if (data['success'] === true) {
7188                        if (data['not_found'] === true) {
7189                            $('#set-not-found[data-val="false"]').removeClass('hide-me');
7190                            $('#set-not-found[data-val="true"]').addClass('hide-me');
7191                            $('button[type="submit"]').addClass('hide-me');
7192                        } else {
7193                            $('#set-not-found[data-val="true"]').removeClass('hide-me');
7194                            $('#set-not-found[data-val="false"]').addClass('hide-me');
7195                            $('button[type="submit"]').removeClass('hide-me');
7196                        }
7197                        if ($('.my-badge.state').length > 0) {
7198                            $('.my-badge.state').attr("class").split(/\s+/).forEac
7198h((el) => {
7199                                if (el.startsWith('state_')) {
7200                                    $('.my-badge.state').removeClass(el);
7201                                }
7202                            })
7203                            $('.my-badge.state').addClass(data['state'].toLowerCase()).text(getStateTranslate(data['state']));
7204                        }
7205                        presentToast(translate['ARTICLE']['STATE_CHANGED_TO_AFTER_SET_NOT_FOUND'] + getStateTranslate(data['state']), 'fa-floppy-disk', 'light', 2000);
7206                    }
7207                } else {
7208                    handleResult(result);
7209                }
7210            } catch (err) {
7211                handleResult(result, err);
7212            }
7213        }
7214    });
7215})
7216// END-TEST FUNCTIONS  
7217
7218
7219// IMPORT FUNCTIONS  
7220async function importItems(ids) {
7221    const tableName = $('.my-table-list').data('table-name');
7222    if (await submitFormAfterConfirm(translate['MODEL']['IMPORT_ELEMENT_CONFIRM_TITLE'], translate['MODEL']['IMPORT_ELEMENT_CONFIRM_TEXT'])) {
7223        $('#loading-bar').show();
7224        $.ajax({
7225            type: 'POST',
7226            url: 'importfromtablelist',
7227            cache: false,
7228            data: {
7229                tableName: tableName,
7230                ids: ids
7231            },
7232            success: async function (result) {
7233                switch (tableName) {
7234                    case 'central_model':
7235                        trackEventGoogleAnalytics("Modell importiert");
7236                        break;
7237                    case 'central_manufacture':
7238                        trackEventGoogleAnalytics("Hersteller importiert");
7239                        break;
7240                    case 'central_model_category':
7241                        trackEventGoogleAnalytics("Kategorie importiert");
7242                        break;
7243                }
7244                $('#loading-bar').hide();
7245                try {
7246                    let data = JSON.parse(result);
7247                    if (typeof data === 'object') {
7248                        data.forEach((element) => {
7249                            $('.my-table-list').find('.list-item[data-id="' + element['central_db_id'] + '"] .thumbnail .icon').removeClass('hide-me');
7250                        });
7251                        if (data.length != ids.length) {
7252                            presentToast(translate['CAN_NOT_IMPORT_EVERY_ITEM_' + tableName.toLocaleUpperCase()], 'fa-triangle-exclamation', 'warning');
7253                        }
7254                        if (data.length > 0) {
7255                            presentToast(translate['MODEL']['SUCCESSFULLY_IMPORTED'], 'fa-cloud-arrow-down', 'success', 2000);
7256                        }
7257                        $('.my-table-list .list-item .select-col input[type="checkbox"]').prop('checked', false);
7258                        updateMyButtonsList();
7259                        let url_str = window.location.href;
7260                        let url = new URL(url_str);
7261                        let search_params = url.searchParams;
7262                        if (search_params.has('redirect')) {
7263                            if (data[0]) {
7264                                let link = '';
7265                                switch (tableName) {
7266                                    case 'central_model':
7267                                        link = decodeURI(search_params.get('redirect'));
7268                                        link += '&model_id=' + data[0]['id'] + '&model_name=' + encodeURI(data[0]['name']);
7269                                        window.open(link, '_self');
7270                                        break;
7271                                    case 'central_manufacture':
7272                                        link = decodeURI(search_params.get('redirect'));
7273                                        link += '&manufacture_id=' + data[0]['id'] + '&manufacture_name=' + encodeURI(data[0]['name']);
7274                                        window.open(link, '_self');
7275                                        break;
7276                                    case 'central_model_category':
7277                                        link = decodeURI(search_params.get('redirect'));
7278                                        link += '&model_category_id=' + data[0]['id'] + '&model_category_name=' + encodeURI(data[0]['name']);
7279                                        window.open(link, '_self');
7280                                        break;
7281                                }
7282                            }
7283                        }
7284                    } else {
7285                        handleResult(result);
7286                    }
7287                } catch (err) {
7288                    handleResult(result, err);
7289                }
7290            },
7291        });
7292    }
7293}
7294$(document).on('click', '.my-table-list .import-item', function (e) {
7295    if ($(e.target).prop("tagName") !== 'INPUT')
7296        importItems([$(this).data('id')]);
7297});
7298$(document).on('click', '#import-items', async function () {
7299    var ids = [];
7300    getSelectedListCheckboxes().each(function () {
7301        ids.push(this.value);
7302    });
7303    importItems(ids);
7304});
7305$('#article .show_short_or_long').click(function () {
7306    if ($(this).hasClass('short')) {
7307        $(this).find('.input').text($(this).find('.input').data('long'));
7308        $(this).removeClass('short').addClass('long');
7309    } else {
7310        $(this).find('.input').text($(this).find('.input').data('short'));
7311        $(this).removeClass('long').addClass('short')
7312    }
7313})
7314// END-IMPORT FUNCTIONS  
7315window.onerror = function (message, source, lineno, colno, error) {
7316    sendErrorToServer({
7317        type: 'js-error',
7318        message: message,
7319        source: source,
7320        lineno: lineno,
7321        colno: colno,
7322        stack: error ? error.stack : null,
7323        ua: navigator.userAgent ? navigator.userAgent : null,
7324    });
7325};
7326window.onunhandledrejection = function (event) {
7327    sendErrorToServer({
7328        type: 'unhandled-promise',
7329        message: event.reason ? event.reason.message : 'Unknown',
7330        stack: event.reason ? event.reason.stack : null,
7331        ua: navigator.userAgent ? navigator.userAgent : null
7332    });
7333};
7334function sendErrorToServer(errorData) {
7335    $.ajax({
7336        type: 'POST',
7337        url: 'log_js_error',
7338        cache: false,
7339        data: errorData
7340    });
7341}
7342
7343// CALENDAR FUNCTIONS
7344if ($('#my-calendar').length > 0) {
7345    let myCalendarEl = document.getElementById('my-calendar');
7346    let myCalendarOptions = $('#my-calendar').data('calendar');
7347    let calendarUrlParams = new URLSearchParams(window.location.search);
7348    if (!calendarUrlParams.has('view')) {
7349        let savedCalendarView = localStorage.getItem('calendarView');
7350        if (savedCalendarView) {
7351            myCalendarOptions.initialView = savedCalendarView;
7352        }
7353    }
7354    if (!calendarUrlParams.has('day') && !calendarUrlParams.has('month') && !calendarUrlParams.has('year')) {
7355        let savedCalendarDate = localStorage.getItem('calendarDate');
7356        if (savedCalendarDate) {
7357            myCalendarOptions.initialDate = savedCalendarDate;
7358        }
7359    }
7360    let myCalendar = new FullCalendar.Calendar(myCalendarEl, myCalendarOptions);
7361    myCalendar.render();
7362
7363    let touchStartXCal = 0;
7364    let touchCurrentXCal = 0;
7365    let isSwipingCal = false;
7366
7367    const SWIPE_THRESHOLD = 180;
7368
7369    myCalendarEl.addEventListener('touchstart', (e) => {
7370        if (e.touches.length !== 1) return;
7371
7372        touchStartXCal = e.touches[0].clientX;
7373        touchCurrentXCal = touchStartXCal;
7374        isSwipingCal = true;
7375    }, { passive: true });
7376
7377    myCalendarEl.addEventListener('touchmove', (e) => {
7378        if (!isSwipingCal) return;
7379
7380        touchCurrentXCal = e.touches[0].clientX;
7381    }, { passive: true });
7382
7383    myCalendarEl.addEventListener('touchend', () => {
7384        if (!isSwipingCal) return;
7385
7386        const diffX = touchCurrentXCal - touchStartXCal;
7387
7388        if (Math.abs(diffX) > SWIPE_THRESHOLD) {
7389            if (diffX > 0) {
7390                myCalendar.prev();
7391            } else {
7392                myCalendar.next();
7393            }
7394        }
7395
7396        isSwipingCal = false;
7397    });
7398
7399    myCalendar.on('datesSet', function (info) {
7400        let currdate = moment(myCalendar.getDate());
7401        replaceUrlParam('day', currdate.format("DD"));
7402        replaceUrlParam('month', currdate.format("MM"));
7403        replaceUrlParam('year', currdate.format("YYYY"));
7404        replaceUrlParam('view', info.view.type);
7405        localStorage.setItem('calendarView', info.view.type);
7406        localStorage.setItem('calendarDate', currdate.format("YYYY-MM-DD"));
7407    });
7408
7409    $(document).on('click', '.fc-filterButton-button', function () {
7410        getAjaxPopUp('calendarfilterpopup');
7411    });
7412
7413    $(document).on('click', '#confirm-filter-calendar', async function () {
7414        const popupEL = $(this).parents('.my-popup');
7415        popupEL.find('form').submit();
7416    });
7417
7418    myCalendar.on('eventClick', function (info) {
7419        var eventObj = info.event;
7420        let datetime = moment(eventObj.startStr);
7421        let desc = "";
7422        if (eventObj.extendedProps.description)
7423            desc = eventObj.extendedProps.description
7424        let message = datetime.format(eventObj.allDay ? 'DD.MM.YYYY' : 'DD.MM.YYYY HH:mm') + "<br /><br /><strong>" + eventObj.title + "</strong><br />" + desc + "<br />";
7425        infoPopup(eventObj.extendedProps.category, message, eventObj.url);
7426        info.jsEvent.preventDefault();
7427    });
7428
7429    // myCalendar.on('dateClick', function (info) {
7430    //     let dateStr = info.dateStr;
7431    //     let dateOnly = dateStr.length <= 10;
7432    //     if (dateOnly) {
7433    //         dateStr = dateStr + ' 19:30';
7434    //     }
7435    //     let datetime = moment(dateStr);
7436    //     let allDay = info.allDay;
7437    //     let buttons = [];
7438
7439    //     if (buttons.length > 0)
7440    //         myActionSheet(translate['CALENDAR']['ADD'], 'add-calender-action-sheet', buttons);
7441
7442    // });
7443}
7444
7445$('#addicstocalendar').click(function () {
7446    let icsLink = $(this).data('ics_link');
7447    infoPopup(translate['CALENDAR']['ADD_ICS_TO_CALENDER_TITLE'], translate['CALENDAR']['ADD_ICS_TO_CALENDER_MESSAGE'].replace('#ICS_LINK#', icsLink));
7448})
7449function copyIcsLink(el) {
7450    const text = el.textContent;
7451    navigator.clipboard.writeText(text).then(() => {
7452        el.style.backgroundColor = 'var(--color-success)';
7453        el.style.color = 'var(--color-success-contrast)';
7454        el.textContent = '📋 Link kopiert!';
7455        setTimeout(() => {
7456            el.textContent = text;
7457            el.style.backgroundColor = 'var(--color-light)';
7458            el.style.color = 'var(--color-font)';
7459        }, 1500);
7460    }).catch(() => {
7461        alert('Kopieren fehlgeschlagen. Bitte manuell kopieren.');
7462    });
7463}
7464// END-CALENDAR FUNCTIONS

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.