PageSourceSearch

https://cdn2.luatvietnam.vn/js/app.js?v=20260224

js luatvietnam.vn collected 2026-09-24 08:37:50 UTC 52,488 bytes, 1,486 lines download raw bytes

1const app = (function () {
2    const appConfigs = {
3        lazyLoadSelectorName: ".lazyload, img[data-src]",
4        RootPathLawJudg: "/ban-an/",
5        RootPathService: "/khac/",
6        RootPathDoc: "/van-ban/",
7        RootPathLuatVietNam: "/",
8        googleClientId:
9            "430060160652-v6547hpru6paae2boas196b4j7l1qcnr.apps.googleusercontent.com",
10        reCaptchaSiteKey: "6LfhkewpAAAAANgBivSXiilt6-5VbNfvFETkGL2N",
11        cacheKeyExpiry: 3600 * 24 * 365,
12        darkModeExpiry: 3600 * 24 * 30,
13        darkModeKey: "luatvietnam.vn_ban_an_night_mode",
14    };
15
16    const trustedHostnames = new Set([
17        "localhost",
18        "betaluat.icsoft.vn",
19        "luat.icsoft.vn",
20        "luatvietnam.vn",
21    ]);
22
23    var messageTimer;
24    const init = () => {
25        handleEvents();
26        handleMessageListener();
27        initAuthPopupHeightObserver();
28        //updateRole();
29        lazyLoadImage();
30    };
31    const baseEventHandler = (event) => {
32        event.preventDefault();
33        event.stopPropagation();
34    };
35
36    // Hàm tiện ích ghi log qua ClientLogger (nếu có)
37    const logClientError = (message, error) => {
38        if (window.ClientLogger && typeof window.ClientLogger.error === 'function') {
39            window.ClientLogger.error(message, error);
40        }
41    };
42    const logClientWarn = (message) => {
43        if (window.ClientLogger && typeof window.ClientLogger.warn === 'function') {
44            window.ClientLogger.warn(message);
45        }
46    };
47    const logClientInfo = (message) => {
48        if (window.ClientLogger && typeof window.ClientLogger.info === 'function') {
49            window.ClientLogger.info(message);
50        }
51    };
52
53    const handleEvents = () => {
54        $(document).on("click", ".btn-submit", function (event) {
55            app.baseEventHandler(event);
56            const form = $(this).closest("form"),
57                action = $(form).data("action");
58
59            if (form) {
60                if (!form.valid()) {
61                    console.warn('Form không hợp lệ trước khi submit', { formId: form.attr('id') });
62
63                    return;
64                }
65
66                if (!form.attr("submitted")) {
67                    if (action) {
68                        addRecaptchaV3ToForm(
69                            "6Ld_KKgUAAAAALzeQ_4iQZKotSGsnX0EjtP6IjEw",
70                            form,
71                            action
72                        )
73                            .then(function () {
74                                form.submit();
75                            })
76                            .catch(function (error) {
77                                console.warn('Lỗi khi thêm reCAPTCHA vào form', error);
78                            });
79                    } else {
80                        form.submit();
81                    }
82                }
83            }
84        });
85        $(document).on("click", "#gopyv2", function (event) {
86            event.preventDefault();
87            const self = $(this);
88            app.showPopup({
89                url: `${app.appConfigs.RootPathLuatVietNam}Ajax/PartialGopY2`,
90                dataType: "html",
91                type: "GET",
92                data: {},
93                popupId: "GopYv2",
94                target: self,
95                onComplete: function () {
96                    app.validatorUnobtrusiveParse($("#FormGopYv2"));
97                },
98            });
99        });
100
101        $(document).on("click", "#ycbsvb", function (event) {
102            event.preventDefault();
103            const self = $(this);
104            app.showPopup({
105                url: `${app.appConfigs.RootPathLuatVietNam}van-ban/yeu-cau-bo-sung.html`,
106                dataType: "html",
107                type: "GET",
108                data: {},
109                popupId: "_ycbsvb",
110                target: self,
111                onComplete: function () {
112                    app.validatorUnobtrusiveParse($("#_ycbosungvb_form"));
113                },
114            });
115        });
116        $(document).on("click", ".btn-more-menu", function (event) {
117            event.preventDefault();
118            const self = $(this),
119                parent = self.closest(".col-mega"),
120                menuItems = parent.find(".ul-mega"),
121                hiddenItems = menuItems.find("li.target-hidden"),
122                availableItems = menuItems.find('li[data-available="1"]'),
123                initialItems = availableItems.length;
124
125            if (hiddenItems.length) {
126                hiddenItems.removeClass("target-hidden");
127                self.text("Thu gọn");
128            } else {
129                menuItems.find("li").addClass("target-hidden");
130
131                availableItems.slice(0, initialItems).removeClass("target-hidden");
132
133                if (availableItems.length < initialItems) {
134                    menuItems
135                        .find("li:not([data-available])")
136                        .slice(0, initialItems - availableItems.length)
137                        .removeClass("target-hidden");
138                }
139
140                self.text("Xem thêm");
141            }
142        });
143        $(document).on("keyup change", ".input-menulvvb", function () {
144            var self = $(this),
145                searchTerm = self.val().trim();
146
147            function normalizeString(str) {
148                return str
149                    .normalize("NFD")
150                    .replace(/[\u0300-\u036f]/g, "")
151                    .toLowerCase();
152            }
153
154            function resetToInitialState() {
155                const parent = self.closest(".col-mega"),
156                    menuItems = parent.find(".ul-menu-list li");
157                var isExpanded = parent.find(".btn-more-menu").text() === "Thu gọn";
158                menuItems.each(function () {
159                    var li = $(this);
160                    li.removeClass("highlight target-hidden");
161
162                    if (!li.data("available") && !isExpanded) {
163                        li.addClass("target-hidden");
164                    }
165                });
166            }
167
168            function highlightText(element, keyword) {
169                var text = element.text();
170                var normalizedText = normalizeString(text);
171                var normalizedKeyword = normalizeString(keyword);
172
173                if (normalizedKeyword.length > 0) {
174                    var keywordIndex = normalizedText.indexOf(normalizedKeyword);
175                    if (keywordIndex !== -1) {
176                        var originalKeyword = text.substr(keywordIndex, keyword.length);
177                        var highlightedText = text.replace(
178                            new RegExp(originalKeyword, "gi"),
179                            function (match) {
180                                return '<span class="highlight">' + match + "</span>";
181                            }
182                        );
183                        element.html(highlightedText);
184                    } else {
185                        element.html(text);
186                    }
187                } else {
188                    element.html(text);
189                }
190            }
191
192            function highlightAndShowKeyword(keyword) {
193                var keywordExists = keyword.length > 0;
194
195                $(".ul-menu-list li").each(function () {
196                    var li = $(this);
197                    var link = li.find("a");
198
199                    if (link.length) {
200                        link.each(function () {
201                            highlightText($(this), keyword);
202                        });
203                    } else {
204                        highlightText(li, keyword);
205                    }
206
207                    if (keywordExists && li.find(".highlight").length === 0) {
208                        li.addClass("target-hidden");
209                    } else {
210                        li.removeClass("target-hidden");
211                    }
212                });
213
214                if (!keywordExists) {
215                    resetToInitialState();
216                }
217            }
218
219            highlightAndShowKeyword(searchTerm);
220        });
221
222        $(document)
223            .off("click", "[data-toggle-target]")
224            .on("click", "[data-toggle-target]", function (e) {
225                e.preventDefault();
226                var self = $(this),
227                    target = self.attr("data-toggle-target");
228
229                if (typeof target != typeof undefined) {
230                    $(target).toggleClass("target-expanded").css("display", "");
231                }
232            });
233
234        $(document).on("click", ".tabs-login-item", function () {
235            const tab = $(this).data("tab");
236
237            setActiveSSOTab(tab);
238
239            setTimeout(updateAuthIframeHeight, 100);
240        });
241
242        $(document).on("click", ".quen-pass", function (event) {
243            app.baseEventHandler(event);
244            const self = $(this);
245            if (!self.attr("clicked")) {
246                app.showPopup({
247                    url: "/khach-hang/sso/quen-mat-khau",
248                    dataType: "html",
249                    type: "GET",
250                    popupId: "_forgotPassword",
251                    target: self,
252                    onComplete: function () {
253                        app.validatorUnobtrusiveParse($("#_forgotPasswordForm"));
254                    },
255                });
256            }
257        });
258
259        $(document).on("click", '[data-role="auth-switch-login-code"]', function (e) {
260            e.preventDefault();
261
262            const authContainer = $(this).closest(".auth-popup-wrapper");
263            if (!authContainer.length) return;
264
265            authContainer
266                .find('[data-role="auth-form"]')
267                .addClass("target-hidden");
268
269            authContainer
270                .find('[data-role="auth-form-login-code"]')
271                .removeClass("target-hidden");
272
273            if (window.parent) {
274                window.parent.postMessage(
275                    {
276                        action: "authTabSwitch",
277                        tabName: 'loginCode',
278                    },
279                    "*"
280                );
281            }
282        });
283
284        $(document).on("click", '[data-role="auth-back-to-login"]', function (e) {
285            e.preventDefault();
286
287            const authContainer = $(this).closest(".auth-popup-wrapper");
288            if (!authContainer.length) return;
289
290            authContainer
291                .find('[data-role="auth-form"]')
292                .removeClass("target-hidden");
293
294            authContainer
295                .find('[data-role="auth-form-login-code"]')
296                .addClass("target-hidden");
297
298            if (window.parent) {
299                window.parent.postMessage(
300                    {
301                        action: "authTabSwitch",
302                        tabName: 'login',
303                    },
304                    "*"
305                );
306            }
307        });
308
309    };
310
311    const onBegin = (element) => {
312        const target = $(element);
313        if (target) {
314            if (target.is("form")) {
315                target.attr("submitted", "");
316                let btnSubmit = target.find(".btn-submit").first();
317                if (!btnSubmit.length) {
318                    btnSubmit = target.find(".text-submit").first();
319                }
320                if (btnSubmit.length > 0) {
321                    const ogAttr = btnSubmit.attr("og");
322                    if (!ogAttr) {
323                        btnSubmit.attr("og", btnSubmit.html());
324                    }
325                    btnSubmit.html(
326                        '<svg class="loading" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>'
327                    );
328                }
329            } else {
330                const ogAttr = target.attr("og");
331                if (!ogAttr) {
332                    target.attr("og", target.html());
333                }
334                target.html(
335                    '<svg class="loading" fill="#fff" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>'
336                );
337            }
338        }
339    };
340
341    const onComplete = (element, reset = true, scrollTo) => {
342        const target = $(element);
343        let timeOut;
344        if (target) {
345            if (target.is("form")) {
346                let btnSubmit = target.find(".btn-submit").first();
347                if (!btnSubmit.length) {
348                    btnSubmit = target.find(".text-submit").first();
349                }
350                if (btnSubmit.length > 0 && reset) {
351                    if (timeOut) {
352                        clearTimeout(timeOut);
353                    }
354                    timeOut = setTimeout(() => {
355                        target.removeAttr("submitted");
356                        const ogAttr = btnSubmit.attr("og");
357                        if (ogAttr) {
358                            btnSubmit.html(ogAttr);
359                        }
360                        timeout = null;
361                    }, 500);
362                }
363            } else {
364                if (timeOut) {
365                    clearTimeout(timeOut);
366                }
367                timeOut = setTimeout(() => {
368                    const ogAttr = target.attr("og");
369                    if (ogAttr) {
370                        target.html(ogAttr);
371                    }
372                    timeout = null;
373                }, 500);
374            }
375        }
376        if (scrollTo) {
377            const scrollToElement = $(`#${scrollTo}`);
378            if (scrollToElement) {
379                $("html,body").animate({ scrollTop: 0 }, 600);
380            }
381        }
382    };
383
384    const onSuccess = (element, xhr) => {
385        if (xhr != null && xhr.message) {
386            showMessage({
387                autoClose: true,
388                buttons: [
389                    {
390                        Name: "Đóng",
391                        ClassName: "btn",
392                        ClickEvent: function () {
393                            $("#_message").remove();
394
395                            if ($(this).attr("clicked")) {
396                                return;
397                            }
398
399                            if (xhr != null && !xhr.succeeded) {
400                                return;
401                            }
402
403                            if (
404                                xhr != null &&
405                                xhr.callback != null &&
406                                xhr.callback.length > 0
407                            ) {
408                                eval(b64DecodeUnicode(xhr.callback));
409                            }
410
411                            $(this).attr("clicked", true);
412
413                            if (xhr != null && xhr.succeeded && xhr.returnUrl != null) {
414                                window.location.href = xhr.returnUrl;
415                                return;
416                            }
417                        },
418                    },
419                ],
420                messages: [xhr.message],
421            });
422        } else if (xhr != null && xhr.succeeded && xhr.returnUrl != null) {
423            window.location.href = xhr.returnUrl;
424            return;
425        }
426        if (xhr != null && !xhr.succeeded) {
427            onComplete(element, true);
428        }
429    };
430
431    const onLoginSuccess = (element, xhr) => {
432        if (xhr != null) {
433            const {
434                message,
435                callback,
436                callback2,
437                succeeded,
438                isAgreedPrivacyPolicy,
439                returnUrl,
440            } = xhr;
441
442            if (succeeded === true && isAgreedPrivacyPolicy === false) {
443                window.parent.postMessage(
444                    {
445                        action: "requirePrivacyPolicy",
446                        returnUrl: returnUrl || window.location.href,
447                    },
448                    "*"
449                );
450
451                return;
452            }
453
454            if (message) {
455
456                //if (callback2) {
457                //    handleCallback(callback2);
458                //    return;
459                //}
460
461                if (message.indexOf("hiện đang đăng nhập") > -1) {
462                    showMessage({
463                        autoClose: true,
464                        buttons: [
465                            {
466                                Name: "Tiếp tục",
467                                ClassName: "btn",
468                                ClickEvent: function () {
469                                    if ($(this).attr("clicked")) {
470                                        return;
471                                    }
472
473                                    if (!succeeded) {
474                                        return;
475                                    }
476
477                                    if (callback != null && callback.length > 0) {
478                                        eval(b64DecodeUnicode(callback));
479                                    }
480
481                                    //if (callback2) {
482                                    //    handleCallback(callback2);
483                                    //    return;
484                                    //}
485
486                                    $(this).attr("clicked", true);
487
488                                    if (succeeded) {
489                                        window.parent.postMessage({ succeeded: true }, "*");
490                                        return;
491                                    }
492
493                                    $("#login").find("form").submit();
494                                },
495                            },
496                        ],
497                        messages: [message],
498                    });
499                } else {
500                    showMessage({
501                        autoClose: true,
502                        buttons: [
503                            {
504                                Name: "Đóng",
505                                ClassName: "btn",
506                                ClickEvent: function () {
507                                    if ($(this).attr("clicked")) {
508                                        return;
509                                    }
510
511                                    if (!succeeded) {
512                                        return;
513                                    }
514
515                                    if (callback != null && callback.length > 0) {
516                                        eval(b64DecodeUnicode(callback));
517                                    }
518
519                                    //if (callback2) {
520                                    //    handleCallback(callback2);
521                                    //    return;
522                                    //}
523
524                                    $(this).attr("clicked", true);
525
526                                    if (succeeded) {
527                                        window.parent.postMessage({ succeeded: true }, "*");
528                                        return;
529                                    }
530                                },
531                            },
532                        ],
533                        messages: [message],
534                    });
535                }
536
537                return;
538            }
539
540            if (succeeded) {
541                window.parent.postMessage(
542                    {
543                        succeeded: true,
544                        returnUrl: returnUrl || window.location.href,
545                    },
546                    "*"
547                );
548
549                return;
550            }
551
552            if (!succeeded) {
553                onComplete(element, true);
554            }
555        }
556    };
557    const onLoginByOpenAuthSuccess = function (succeeded, message) {
558        if (message) {
559            showMessage({
560                autoClose: true,
561                buttons: [
562                    {
563                        Name: "Đóng",
564                        ClassName: "btn",
565                        ClickEvent: function () {
566                            if ($(this).attr("clicked")) {
567                                return;
568                            }
569
570                            if (!succeeded) {
571                                return;
572                            }
573
574                            $(this).attr("clicked", true);
575
576                            if (succeeded) {
577                                window.parent.postMessage({ succeeded: true }, "*");
578                                return;
579                            }
580                        },
581                    },
582                ],
583                messages: [message],
584            });
585            return;
586        }
587
588        if (succeeded) {
589            window.parent.postMessage({ succeeded: true }, "*");
590            return;
591        }
592    };
593
594    const callbackHandlers = {
595        customerRegisterSuccess: (id, userName, email, phone, returnUrl) => {
596            try {
597                const analytics = window.AppAnalytics;
598
599                if (analytics) {
600                    if (id) {
601                        analytics.identifyUser({ id, userName, email, phone });
602                    }
603
604                    analytics.trackEvent("trial_registered", {
605                        source: "auth_popup",
606                        page_path: window.location.pathname,
607                        page_location: window.location.href,
608                    });
609                }
610            } catch (e) {
611                logClientError('Analytics track register success failed', e);
612            }
613
614            if (returnUrl && returnUrl.trim().length > 0) {
615                postMessageToParent("redirect", { redirectUrl: returnUrl });
616
617                return;
618            }
619
620            setActiveSSOTab("login", true);
621        },
622        forgotPassword: () => $("#_forgotPassword")?.remove(),
623    };
624
625    const handleCallback = (callBackRaw) => {
626        if (!callBackRaw) return;
627
628        try {
629            const parsed =
630                typeof callBackRaw === "string" ? JSON.parse(callBackRaw) : callBackRaw;
631
632            const name = parsed?.Name;
633            const args = parsed?.Args || [];
634
635            const fn = callbackHandlers[name];
636            if (typeof fn === "function") {
637                fn(...args);
638            }
639        } catch (err) {
640            console.error('Lỗi khi parse callback', err);
641        }
642    };
643
644    const b64DecodeUnicode = (str) => {
645        return decodeURIComponent(
646            atob(str)
647                .split("")
648                .map(function (c) {
649                    return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
650                })
651                .join("")
652        );
653    };
654
655    const debounce = (func, delay = 500) => {
656        let timeOut;
657        return function executedFunc(...args) {
658            if (timeOut) {
659                clearTimeout(timeOut);
660            }
661            timeOut = setTimeout(() => {
662                func(...args);
663                timeOut = null;
664            }, delay);
665        };
666    };
667
668    const responseInValid = (response) => {
669        let responseObj;
670        try {
671            responseObj = JSON.parse(response);
672        } catch (e) { }
673        if (
674            responseObj &&
675            responseObj.Message ==
676            "Có sự cố trong quá trình xử lý yêu cầu, quý khách vui lòng liên hệ Chăm sóc khách hàng để được hỗ trợ!"
677        ) {
678            return responseObj;
679        }
680        return undefined;
681    };
682
683    const showPopup = (options) => {
684        let timeOut;
685        let defaults = {
686            popupId: undefined,
687            url: "",
688            type: "GET",
689            dataType: "html",
690            onComplete: {},
691        };
692        options = $.extend(defaults, options);
693        if (!options.popupId) {
694            options.popupId = Math.random().toString(36).substr(2, 9);
695        }
696        fetchData(options).then((response) => {
697            if (response && response.length > 0) {
698                const responseObj = responseInValid(response);
699                if (responseObj != null && responseObj.Message) {
700                    showMessage({ autoClose: true, messages: [responseObj.Message] });
701                } else {
702                    if (timeOut) {
703                        clearTimeout(timeOut);
704                    }
705                    timeOut = setTimeout(() => {
706                        $(`#${options.popupId}`).remove();
707                        //$('body').css({ 'overflow': 'hidden' })
708                        $("body").append(
709                            `<div id="${options.popupId}" class="popup-bg target-hidden target-expanded">${response}</div>`
710                        );
711                        if (typeof options.onComplete === "function") {
712                            options.onComplete();
713                        }
714                    }, 100);
715                }
716            }
717        });
718    };
719
720    const showMessage = (options) => {
721        var defaults = {
722            id: "_message",
723            title: "Thông báo",
724            buttons: [
725                {
726                    Name: "Đóng",
727                    ClassName: "btn",
728                    ClickEvent: {},
729                },
730            ],
731            callback: {},
732            success: true,
733            messages: [],
734            autoClose: true,
735            timeClose: 5,
736            returnUrl: "",
737        };
738
739        options = $.extend(defaults, options);
740
741        options.messages = $.isArray(options.messages) ? options.messages : [];
742        let popup = `<div id="${options.id}" class="popup-bg target-hidden"> 
743            <div class="popup-wrapper popup-340"> <div class="popup-body"> <div class="popup-content"> <div class="form-pad16"> <div class="form-group text-center"> <div class="text-successful color-red">${options.title}</div> </div> <div class="form-group text-center text-content">`;
744
745        if (options.messages.length > 0) {
746            popup += '<div class="form-group text-center text-content">';
747            for (var i = 0; i < options.messages.length; i++) {
748                popup += "<p>" + options.messages[i] + "</p>";
749            }
750            popup += `</div>
751            <div class="form-group text-center gap-8">`;
752            if (options.buttons.length > 0) {
753                for (var i = 0; i < options.buttons.length; i++) {
754                    if (options.buttons[i].Name == "Đóng") {
755                        popup += `<div class="btn-item"><span class="${options.buttons[i].ClassName}" data-toggle-target="#${options.id}" data-close="true">Đóng</span></div>`;
756                    } else {
757                        popup += `<div class="btn-item"><span class="${options.buttons[i].ClassName}">${options.buttons[i].Name}</span></div>`;
758                    }
759                }
760            }
761            popup += "</div>";
762        }
763        if (messageTimer) {
764            clearInterval(messageTimer);
765        }
766        if (options.success) {
767            $(`#${options.id}`).remove();
768            $("body").append(popup);
769            for (var i = 0; i < options.buttons.length; i++) {
770                var el = options.buttons[i];
771                var event = el.ClickEvent;
772                $(`#${options.id}`).on(
773                    "click",
774                    `.${el.ClassName}`,
775                    $.isFunction(event) ? event : $.noop
776                );
777            }
778        }
779        if (options.autoClose && options.buttons.length == 1) {
780            var autoCloseAfter = options.timeClose,
781                button = $(`.${options.buttons[0].ClassName}`, $(`#${options.id}`)),
782                buttonText = button.text();
783            messageTimer = setInterval(function () {
784                if (autoCloseAfter > 0) {
785                    button.text(`${buttonText} (${autoCloseAfter})`);
786                } else {
787                    button.text(buttonText);
788                }
789                if (autoCloseAfter == 0) {
790                    clearInterval(messageTimer);
791
792                    if (!button.attr("clicked")) {
793                        button.trigger("click");
794
795                        $(`#${options.id}`).remove();
796                    }
797                }
798                autoCloseAfter--;
799            }, 1000);
800        }
801        setTimeout(function () {
802            $(`#${options.id}`).addClass("target-expanded").css("display", "");
803        }, 300);
804    };
805
806    const fetchData = (options) => {
807        let timeOut,
808            defaults = {
809                target: undefined,
810                beforeSend: function () {
811                    if (this.target) {
812                        const ogAttr = this.target.attr("og");
813                        if (!ogAttr) {
814                            this.target.attr("og", this.target.html());
815                        }
816                        this.target.attr("clicked", true);
817                        this.target.html(
818                            '<svg class="loading" aria-hidden="true" focusable="false" role="presentation" class="icon icon-spinner" viewBox="0 0 20 20"><path d="M7.229 1.173a9.25 9.25 0 1 0 11.655 11.412 1.25 1.25 0 1 0-2.4-.698 6.75 6.75 0 1 1-8.506-8.329 1.25 1.25 0 1 0-.75-2.385z" fill="#919EAB"/></svg>'
819                        );
820                    }
821                },
822                callback: function () {
823                    if (this.target) {
824                        if (timeOut) {
825                            clearTimeout(timeOut);
826                        }
827                        timeOut = setTimeout(() => {
828                            const ogAttr = this.target.attr("og");
829                            if (ogAttr) {
830                                this.target.removeAttr("clicked");
831                                this.target.html(ogAttr);
832                                this.target.removeAttr("og");
833                            }
834                            timeOut = null;
835                        }, 200);
836                    }
837                },
838            };
839
840        options = $.extend(defaults, options);
841
842        if (options.target && options.target.attr("clicked")) {
843            return Promise.resolve();
844        }
845
846        return new Promise(function (resolve, reject) {
847            $.ajax(options)
848                .done(resolve)
849                .fail(function (jqXHR, textStatus, errorThrown) {
850                    // Ghi log lỗi Ajax
851                    logClientError('Lỗi Ajax: ' + options.url, {
852                        status: jqXHR.status,
853                        statusText: jqXHR.statusText,
854                        errorThrown: errorThrown
855                    });
856                    reject(jqXHR, textStatus, errorThrown);
857                })
858                .always(options.callback || {});
859        });
860    };
861
862    const handleCustomerStatistics = () => {
863        return new Promise(function (resolver, reject) {
864            let timeOut = setTimeout(function () {
865                fetchData({
866                    url: `${app.appConfigs.APIVersion}customer/get/statistics`,
867                    dataType: "json",
868                    type: "GET",
869                }).then((response) => {
870                    if (response.data) {
871                        $("#saved-documents-total").text(
872                            `(${response.data.savedDocumentTotal})`
873                        );
874                        $("#monitor-document-effects-total").text(
875                            `(${response.data.monitorDocumentEffectsTotal})`
876                        );
877                    }
878                });
879                clearTimeout(timeOut);
880                resolver();
881            }, 3000);
882        });
883    };
884
885    const initAuthPopupHeightObserver = () => {
886        updateAuthIframeHeight();
887        observeAuthPopupResize();
888        observeTabContentMutation();
889    };
890
891    const updateAuthIframeHeight = () => {
892        const popupWrapper = document.querySelector(".auth-popup-wrapper");
893        if (!popupWrapper) return;
894
895        const height = popupWrapper.scrollHeight;
896        window.parent.postMessage(
897            {
898                action: "authPopupUpdateHeight",
899                height: height,
900            },
901            "*"
902        );
903    };
904
905    const observeAuthPopupResize = () => {
906        const popupWrapper = document.querySelector(".auth-popup-wrapper");
907        if (!popupWrapper) return;
908
909        const resizeObserver = new ResizeObserver(() => {
910            updateAuthIframeHeight();
911        });
912
913        resizeObserver.observe(popupWrapper);
914    };
915
916    const observeTabContentMutation = () => {
917        const contentWrapper = document.querySelector(
918            ".tabs-login-content-wrapper"
919        );
920        if (!contentWrapper) return;
921
922        const observer = new MutationObserver(() => {
923            updateAuthIframeHeight();
924        });
925
926        observer.observe(contentWrapper, {
927            childList: true,
928            subtree: true,
929            attributes: true,
930            characterData: true,
931        });
932    };
933
934    const postMessageToParent = (action, data = {}) => {
935        if (window.parent) {
936            window.parent.postMessage(
937                {
938                    action: action,
939                    ...data,
940                },
941                "*"
942            );
943        }
944    };
945
946    const handleMessageListener = () => {
947        $(window).on("message", function (event) {
948            const { origin, data } = event.originalEvent;
949
950            if (!isTrustedOrigin(origin, trustedHostnames)) {
951                console.warn('Nhận message từ origin không tin cậy: ' + origin);
952                return;
953            }
954
955            if (!data) return;
956
957            switch (data?.action) {
958                case "requestAuthHeight":
959                    const iframe = $("#authPopup ._iframe");
960                    if (iframe.length && data.height) {
961                        iframe.css("height", data.height);
962                    }
963                    break;
964
965                case "authTabChange":
966                    const tabName = data?.tab;
967                    if (tabName) {
968                        setActiveSSOTab(tabName);
969                    }
970
971                case "authClosePopup":
972                    $("body").css("overflow", "");
973                    break;
974
975                case "authTabSwitch": {
976                    const $iframe = $("#authPopup ._iframe");
977                    const TAB_HEIGHTS = { login: 500, register: 642, loginCode: 300, };
978                    const newHeight = TAB_HEIGHTS[data.tabName];
979
980                    if ($iframe.length && newHeight) {
981                        $iframe[0].style.height = newHeight + "px";
982                    }
983                    break;
984                }
985
986                case "redirect":
987                    if (data.redirectUrl) {
988                        window.location.href = data.redirectUrl;
989                    }
990
991                    break;
992
993                default:
994                    console.warn('Nhận message không xác định từ iframe', { action: data?.action, origin });
995                    break;
996            }
997        });
998    };
999
1000    const isTrustedOrigin = (origin, trustedHostnames) => {
1001        try {
1002            const hostname = new URL(origin).hostname;
1003            return trustedHostnames.has(hostname);
1004        } catch (error) {
1005            console.warn("Invalid origin:", origin);
1006            return false;
1007        }
1008    };
1009
1010    const setActiveSSOTab = (tabName, resetForm = false) => {
1011        const tabItem = $(`.tabs-login-item[data-tab="${tabName}"]`);
1012        const tabContent = $(`#${tabName}`);
1013
1014        // Active tab item
1015        if (tabItem.length) {
1016            $(".tabs-login-item").removeClass("active");
1017            tabItem.addClass("active");
1018        }
1019
1020        // Active tab content
1021        if (tabContent.length) {
1022            $(".tabs-login-content").removeClass("active");
1023            tabContent.addClass("active");
1024        }
1025
1026        if (resetForm) {
1027            $(".tabs-login-content form").each(function () {
1028                this.reset();
1029
1030                // Đặt lại xác thực của jQuery Validate nếu đã được áp dụng
1031                const validator = $(this).data("validator");
1032                if (validator) {
1033                    validator.resetForm();
1034                }
1035            });
1036        }
1037
1038        if (window.parent) {
1039            window.parent.postMessage(
1040                {
1041                    action: "authTabSwitch",
1042                    tabName: tabName,
1043                },
1044                "*"
1045            );
1046        }
1047    };
1048
1049    function setCookie(name, value, minutes, secure = true, sameSite = true) {
1050        var expires = "";
1051        if (minutes) {
1052            var date = new Date();
1053            date.setTime(date.getTime() + minutes * 60 * 1000);
1054            expires = "; expires=" + date.toUTCString();
1055        }
1056        var secureFlag = secure ? "; secure" : "";
1057        var sameSiteFlag = sameSite ? "; SameSite=Strict" : "";
1058        document.cookie =
1059            name + "=" + value + expires + "; path=/" + secureFlag + sameSiteFlag;
1060    }
1061    function getCookie(name) {
1062        var nameEQ = name + "=";
1063        var cookies = document.cookie.split(";");
1064        for (var i = 0; i < cookies.length; i++) {
1065            var cookie = cookies[i];
1066            while (cookie.charAt(0) == " ") {
1067                cookie = cookie.substring(1, cookie.length);
1068            }
1069            if (cookie.indexOf(nameEQ) == 0) {
1070                return cookie.substring(nameEQ.length, cookie.length);
1071            }
1072        }
1073        return null;
1074    }
1075    function isMobile() {
1076        return (
1077            "ontouchstart" in document.documentElement &&
1078            /mobi/i.test(navigator.userAgent)
1079        );
1080    }
1081    const validatorUnobtrusiveParse = (form) => {
1082        if (form) {
1083            form.removeData("validator");
1084            form.removeData("unobtrusiveValidation");
1085            $.validator.unobtrusive.parse(form);
1086        }
1087    };
1088    const storage = {
1089        getItem: function (key, optionalCallback) {
1090            if (!this.supportsLocalStorage()) {
1091                return null;
1092            }
1093
1094            var callback = function (data) {
1095                data = typeof data !== "undefined" ? data : null;
1096
1097                return typeof optionalCallback === "function"
1098                    ? optionalCallback(data)
1099                    : data;
1100            };
1101
1102            var value = localStorage.getItem(key);
1103
1104            if (value !== null) {
1105                value = JSON.parse(value);
1106
1107                if (value.hasOwnProperty("__expiry")) {
1108                    var expiry = value.__expiry;
1109                    var now = Date.now();
1110
1111                    if (now >= expiry) {
1112                        this.removeItem(key);
1113
1114                        return callback();
1115                    } else {
1116                        return callback(value.__data);
1117                    }
1118                } else {
1119                    return callback(value);
1120                }
1121            } else {
1122                return callback();
1123            }
1124        },
1125        setItem: function (key, value, expiry) {
1126            if (
1127                !this.supportsLocalStorage() ||
1128                typeof value === "undefined" ||
1129                key === null ||
1130                value === null
1131            ) {
1132                return false;
1133            }
1134
1135            if (typeof expiry === "number") {
1136                value = {
1137                    __data: value,
1138                    __expiry: Date.now() + parseInt(expiry) * 1000,
1139                };
1140            }
1141
1142            try {
1143                localStorage.setItem(key, JSON.stringify(value));
1144
1145                return true;
1146            } catch (e) {
1147                console.log(
1148                    "Không thể lưu dữ liệu " + key + " trong localStorage: " + e.name
1149                );
1150                return false;
1151            }
1152        },
1153        removeItem: function (key) {
1154            if (this.supportsLocalStorage()) {
1155                localStorage.removeItem(key);
1156            }
1157        },
1158        clear: function () {
1159            if (this.supportsLocalStorage()) {
1160                localStorage.clear();
1161            }
1162        },
1163        supportsLocalStorage: function () {
1164            try {
1165                localStorage.setItem("_", "_");
1166                localStorage.removeItem("_");
1167
1168                return true;
1169            } catch (e) {
1170                return false;
1171            }
1172        },
1173    };
1174    const lazyLoadImage = () => {
1175        const images = document.querySelectorAll(appConfigs.lazyLoadSelectorName);
1176
1177        const lazyLoad = (target) => {
1178            const io = new IntersectionObserver((entries, observer) => {
1179                entries.forEach((entry) => {
1180                    if (entry.isIntersecting) {
1181                        const img = entry.target;
1182                        const src = img.getAttribute("data-src");
1183                        if (src) {
1184                            img.setAttribute("src", src);
1185                        }
1186                        img.classList.add("fade-in");
1187                        observer.disconnect();
1188                    }
1189                });
1190            });
1191
1192            io.observe(target);
1193        };
1194
1195        images.forEach((image) => {
1196            lazyLoad(image);
1197        });
1198    };
1199
1200    const loadScript = (src, async = true, type = "text/javascript") => {
1201        return new Promise((resolve, reject) => {
1202            try {
1203                const el = document.createElement("script");
1204                const container = document.head || document.body;
1205
1206                el.type = type;
1207                el.async = async;
1208                el.src = src;
1209
1210                el.addEventListener("load", () => {
1211                    resolve({ status: true });
1212                });
1213
1214                el.addEventListener("error", () => {
1215                    reject({
1216                        status: false,
1217                        message: `Failed to load the script ${src}`,
1218                    });
1219                });
1220
1221                container.appendChild(el);
1222            } catch (err) {
1223                reject(err);
1224            }
1225        });
1226    };
1227
1228    const loadScriptV2 = (src, options = {}) => {
1229        const { async = true, type = "text/javascript", defer = false } = options;
1230        return new Promise((resolve, reject) => {
1231            // Kiểm tra xem script đã tồn tại chưa để tránh tải trùng
1232            const existingScript = document.querySelector(`script[src="${src}"]`);
1233            if (existingScript) {
1234                resolve({ status: true, message: "Script already loaded" });
1235                return;
1236            }
1237
1238            try {
1239                const el = document.createElement("script");
1240                const container = document.head || document.body;
1241
1242                // Thiết lập các thuộc tính
1243                el.src = src;
1244                el.type = type;
1245                el.async = async;
1246                el.defer = defer;
1247
1248                // Xử lý sự kiện load
1249                el.addEventListener("load", () => {
1250                    resolve({ status: true, message: "Script loaded successfully" });
1251                });
1252
1253                // Xử lý sự kiện error chi tiết hơn
1254                el.addEventListener("error", (error) => {
1255                    console.error('Không thể tải script: ' + src, error);
1256
1257                    reject({
1258                        status: false,
1259                        message: `Failed to load the script ${src}`,
1260                        error,
1261                    });
1262                });
1263
1264                // Thêm script vào container
1265                container.appendChild(el);
1266            } catch (err) {
1267                reject(err);
1268            }
1269        });
1270    };
1271
1272    const loadStyle = (src) => {
1273        return new Promise((resolve, reject) => {
1274            const link = document.createElement("link");
1275            link.rel = "stylesheet";
1276            link.href = src;
1277
1278            link.onload = () => resolve(src);
1279
1280            link.onerror = () => {
1281                console.error('Không thể tải CSS: ' + src);
1282                reject(new Error(`Failed to load CSS file: ${src}`));
1283            };
1284
1285            document.head.appendChild(link);
1286        });
1287    };
1288    function addRecaptchaV3ToForm(reCAPTCHASiteKey, formSelector, action) {
1289        return new Promise((resolve, reject) => {
1290            grecaptcha.ready(function () {
1291                grecaptcha
1292                    .execute(reCAPTCHASiteKey, { action: action })
1293                    .then(function (token) {
1294                        var recaptchaResponse = $(formSelector).find(
1295                            'input[name="RecaptchaResponse"]'
1296                        );
1297
1298                        if (recaptchaResponse.length === 0) {
1299                            recaptchaResponse = $("<input>").attr({
1300                                type: "hidden",
1301                                name: "RecaptchaResponse",
1302                                id: "g-recaptcha-response",
1303                            });
1304
1305                            $(formSelector).append(recaptchaResponse);
1306                        }
1307
1308                        recaptchaResponse.val(token);
1309
1310                        resolve();
1311                    })
1312                    .catch(function (error) {
1313                        reject(error);
1314                    });
1315            });
1316        });
1317    }
1318    function loadGoogleRecaptcha() {
1319        loadScriptV2(
1320            "https://www.google.com/recaptcha/api.js?render=6Ld_KKgUAAAAALzeQ_4iQZKotSGsnX0EjtP6IjEw",
1321            { async: true, defer: true }
1322        )
1323            .then((data) => {
1324                console.log("reCAPTCHA script loaded successfully");
1325            })
1326            .catch((err) => {
1327                console.error(err);
1328            });
1329    }
1330    function updateRole() {
1331        if (
1332            typeof IsAuthenticated != typeof undefined &&
1333            IsAuthenticated != null &&
1334            IsAuthenticated
1335        ) {
1336            var hostName = "/";
1337            if (window.location.hostname.indexOf("localhost") > -1) {
1338                hostName = "https://localhost:44361/";
1339            }
1340            var url = hostName + "khach-hang/api/v1/customer/refresh-info";
1341
1342            fetch(url, {
1343                method: "POST",
1344                headers: {
1345                    "Content-Type": "application/json",
1346                },
1347            }).catch((error) => {
1348                console.error('Lỗi refresh thông tin người dùng', error);
1349            });
1350        }
1351    }
1352    $(document).on("click", ".ic-hidden-pass", function () {
1353        var password = $(this).closest(".form-group").find("input").first();
1354        $(this).remove();
1355        password
1356            .closest(".form-group")
1357            .first()
1358            .append('<i class="iconlvn-eye-open ic-show-pass"></i>');
1359        password.prop("type", "text");
1360    });
1361    $(document).on("click", ".ic-show-pass", function () {
1362        var password = $(this).closest(".form-group").find("input").first();
1363        $(this).remove();
1364        password
1365            .closest(".form-group")
1366            .first()
1367            .append('<i class="iconlvn-eye-close ic-hidden-pass"></i>');
1368        password.prop("type", "password");
1369    });
1370    var $jQval = $.validator,
1371        adapters,
1372        data_validation = "unobtrusiveValidation";
1373
1374    function isMethodProxySafe(method) {
1375        return method === "GET" || method === "POST";
1376    }
1377
1378    function asyncOnBeforeSend(xhr, method) {
1379        if (!isMethodProxySafe(method)) {
1380            xhr.setRequestHeader("X-HTTP-Method-Override", method);
1381        }
1382    }
1383
1384    function asyncOnSuccess(element, data, contentType) {
1385        var mode;
1386
1387        if (contentType.indexOf("application/x-javascript") !== -1) {
1388            // jQuery already executes JavaScript for us
1389            return;
1390        }
1391
1392        mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase();
1393        $(element.getAttribute("data-ajax-update")).each(function (i, update) {
1394            var top;
1395
1396            switch (mode) {
1397                case "BEFORE":
1398                    top = update.firstChild;
1399                    $("<div />")
1400                        .html(data)
1401                        .contents()
1402                        .each(function () {
1403                            update.insertBefore(this, top);
1404                        });
1405                    break;
1406                case "AFTER":
1407                    $("<div />")
1408                        .html(data)
1409                        .contents()
1410                        .each(function () {
1411                            update.appendChild(this);
1412                        });
1413                    break;
1414                default:
1415                    $(update).html(data);
1416                    break;
1417            }
1418        });
1419    }
1420    function asyncRequest(element, options) {
1421        var confirm, loading, method, duration;
1422
1423        confirm = element.getAttribute("data-ajax-confirm");
1424        if (confirm && !window.confirm(confirm)) {
1425            return;
1426        }
1427
1428        loading = $(element.getAttribute("data-ajax-loading"));
1429        duration = element.getAttribute("data-ajax-loading-duration") || 0;
1430
1431        options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" });
1432
1433        method = options.type.toUpperCase();
1434        if (!isMethodProxySafe(method)) {
1435            options.type = "POST";
1436            options.data.push({ name: "X-HTTP-Method-Override", value: method });
1437        }
1438
1439        $.ajax(options);
1440    }
1441
1442    function validate(form) {
1443        var validationInfo = $(form).data(data_validation);
1444        return (
1445            !validationInfo || !validationInfo.validate || validationInfo.validate()
1446        );
1447    }
1448    //$("form[data-ajax=true]").on("submit", function (evt) {
1449
1450    //    evt.preventDefault();
1451    //    if (!validate(this)) {
1452    //        return;
1453    //    }
1454    //    asyncRequest(this, {
1455    //        url: this.action,
1456    //        type: this.method || "GET",
1457    //        data: []
1458    //    });
1459    //});
1460    init();
1461
1462    return {
1463        appConfigs,
1464        baseEventHandler,
1465        debounce,
1466        showPopup,
1467        showMessage,
1468        onBegin,
1469        onComplete,
1470        onSuccess,
1471        onLoginSuccess,
1472        lazyLoadImage,
1473        b64DecodeUnicode,
1474        validatorUnobtrusiveParse,
1475        fetchData,
1476        setCookie,
1477        getCookie,
1478        loadScript,
1479        loadStyle,
1480        storage,
1481        handleCustomerStatistics,
1482        isMobile,
1483        loadGoogleRecaptcha,
1484        responseInValid,
1485    };
1486})();

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.