PageSourceSearch

https://media.vov.vn/sites/default/files/account/js/comment-vov.js

js vov.vn collected 2026-09-24 08:41:29 UTC 10,388 bytes, 210 lines download raw bytes

1(function ($, drupalSettings) {
2    function fromNow(
3        date,
4        nowDate = Date.now(),
5        rft = new Intl.RelativeTimeFormat(drupalSettings.path.currentLanguage || 'vi', {numeric: "auto"})
6    ) {
7        const SECOND = 1000;
8        const MINUTE = 60 * SECOND;
9        const HOUR = 60 * MINUTE;
10        const DAY = 24 * HOUR;
11        const WEEK = 7 * DAY;
12        const MONTH = 30 * DAY;
13        const YEAR = 365 * DAY;
14        const intervals = [
15            {ge: YEAR, divisor: YEAR, unit: "year"},
16            {ge: MONTH, divisor: MONTH, unit: "month"},
17            {ge: WEEK, divisor: WEEK, unit: "week"},
18            {ge: DAY, divisor: DAY, unit: "day"},
19            {ge: HOUR, divisor: HOUR, unit: "hour"},
20            {ge: MINUTE, divisor: MINUTE, unit: "minute"},
21            {ge: 30 * SECOND, divisor: SECOND, unit: "seconds"},
22            {ge: 0, divisor: 1, text: "just now"}
23        ];
24        const now = typeof nowDate === "object" ? nowDate.getTime() : new Date(nowDate).getTime();
25        const diff = now - (typeof date === "object" ? date : new Date(date)).getTime();
26        const diffAbs = Math.abs(diff);
27        for (const interval of intervals) {
28            if (diffAbs >= interval.ge) {
29                const x = Math.round(Math.abs(diff) / interval.divisor);
30                const isFuture = diff < 0;
31                return interval.unit ? rft.format(isFuture ? x : -x, interval.unit) : interval.text;
32            }
33        }
34    }
35
36    $(document).ready(function () {
37        var fieldComment = $("#vov-social-field-comment");
38        var vovAccountUrl = window.vovAccountLoginUrl;
39        let nid = drupalSettings.statistics.data.nid;
40        let lid = null;
41        if(!nid) return;
42        function listComment(loadMore = false) {
43            let url = `${vovAccountUrl}/get-all-comment?nid=${nid}`;
44            if (loadMore && lid) {
45                url += `&lid=${lid}`;
46            }
47            fetch(url)
48                .then(response => response.json())
49                .then(result => {
50                    if (loadMore) {
51                        fieldComment.append(initHtml(result.data));
52                    } else {
53                        fieldComment.html(initHtml(result.data));
54                    }
55                    if (result.data.length > 0) {
56                        lid = result.data[result.data.length - 1].cid;
57                        $("#load-more-comments").show();
58                    } else {
59                        $("#load-more-comments").hide();
60                    }
61                })
62                .catch(error => console.error("Lỗi khi tải bình luận!", error));
63        }
64
65        function listChildComment(cid) {
66            fetch(`${vovAccountUrl}/get-by-pid?pid=${cid}`)
67                .then(response => response.json())
68                .then(result => {
69                    if (result.data && result.data.length > 0) {
70                        $(`#comment-children-${cid}`).html(initHtml(result.data));
71                    }
72                })
73                .catch(error => console.error("Lỗi khi tải bình luận con!", error));
74        }
75
76        function resetForm(form) {
77            form[0].reset();
78            form.find("input[name='username']").hide();
79        }
80
81        function initHtml(data) {
82            let result = "";
83            data.forEach(element => {
84                let avaUrl = element.user_avatar || `https://ui-avatars.com/api/?length=1&name=${element.username}`;
85                result += `
86                    <div class="comment-item__custom">
87                        <div class="comment-item" id="vov-social-comment-${element.cid}">
88                            <div class="comment-item__text">${element.content}</div>
89                            <div class="comment-item__control">
90                                <div class="row">
91                                    <div class="col-md-6">
92                                        <div class="comment-item__author">
93                                            <div class="comment-item__avatar">
94                                                <img src="${avaUrl}" width="24" height="24">
95                                            </div>
96                                            <span class="comment-item__name">${element.username}</span>
97                                            <span class="comment-item__time">-&nbsp;${fromNow(element.created_at)}</span>
98                                        </div>
99                                    </div>
100                                    <div class="col-md-6 text-end">
101                                        <label class="text-vov-red ms-2 pointer reply-comment" data-cid="${element.cid}">Phản hồi</label>
102                                    </div>
103                                </div>
104                            </div>
105                            <div id="comment-children-${element.cid}" class="comment-children"></div>
106                            <div id="reply-form-${element.cid}" class="reply-form" style="display: none;">
107                                <form class="box-comment mt-3">
108                                    <div class="mt-2 comment-form">
109                                        <div class="d-flex flex-column gap-2 w-100">
110                                            <textarea id="commentText-${element.cid}" name="content" class="comment-box w-100 form-item__textarea" rows="1" placeholder="Thêm phản hồi..." required></textarea>
111                                            <input id="userName-${element.cid}" name="username" style="display: none" class="w-100 border px-3 py-2 rounded-2 mb-2" placeholder="Họ và tên..." />
112                                        </div>
113                                    </div>
114                                    <div class="comment-form">
115                                        <button type="submit" class="btn-vov-red btn m-auto">Gá»­i</button>
116                                    </div>
117                                </form>
118                            </div>
119                        </div>
120                    </div>
121                `;
122                listChildComment(element.cid);
123            });
124            return result;
125        }
126
127        $("body").on("click", ".reply-comment", function () {
128            let cid = $(this).data("cid");
129            $(`#reply-form-${cid}`).toggle();
130        });
131
132        $("body").on("input", "textarea[name='content']", function () {
133            let parent = $(this).closest(".box-comment");
134            let userNameInput = parent.find("input[name='username']");
135            if ($(this).val().trim().length > 0) {
136                userNameInput.show();
137            } else {
138                userNameInput.hide();
139            }
140        });
141
142        $("body").on("submit", ".box-comment", function (event) {
143            event.preventDefault();
144            let form = $(this);
145            let commentText = form.find("textarea[name='content']").val().trim();
146            let userName = form.find("input[name='username']").val().trim();
147            let currentComment = form.closest(".comment-item__custom");
148            let parentComment = currentComment.parent().closest(".comment-item__custom");
149            let parentId = parentComment.length ? parentComment.find(".reply-comment").first().data("cid") : currentComment.find(".reply-comment").first().data("cid");
150
151
152            if (!commentText) {
153                alert("Bình luận không thể để trống.");
154                return;
155            }
156            if (!userName) {
157                alert("Tên không thể để trống.");
158                return;
159            }
160            if (typeof grecaptcha === "undefined") {
161                console.error("Google reCAPTCHA chưa được tải!");
162            } else {
163                grecaptcha.ready(function () {
164                    grecaptcha.execute('6LeA1IAmAAAAAFKDrtl_4OAKuuAuF0be9j953jLT', {action: 'submit'}).then(function (token) {
165                        $.ajax({
166                            type: "post",
167                            url: vovAccountUrl + "/api/create-comment-new?lang=" + drupalSettings.path.currentLanguage,
168                            data: parentId ? form.serialize()+"&nid="+ nid + "&pid="+ parentId+ "&gresponse=" + token : form.serialize()+"&nid="+ nid + "&gresponse=" + token,
169                            success: function () {
170                                document.getElementById("modal-comment").style.display = "flex";
171                                let count = 3;
172                                const interval = setInterval(() => {
173                                    document.getElementById("time-count-down").textContent = count + "s";
174                                    if (count === 0) {
175                                        document.getElementById("modal-comment").style.display = "none";
176                                        clearInterval(interval);
177                                    }
178                                    count--;
179                                }, 1000);
180                                listComment();
181                                resetForm(form);
182                            },
183                            error: function (xhr) {
184                                let res = xhr.responseJSON;
185                                if (!res) {
186                                    try { res = JSON.parse(xhr.responseText); } catch (e) { res = null; }
187                                }
188                                let details = [];
189                                if (res && res.errors && Array.isArray(res.errors.content)) {
190                                    details = res.errors.content;
191                                }
192                                alert(details.length > 0 ? details.join("\n") : "Dữ liệu không hợp lệ.");
193                            },
194                        });
195                    });
196                });
197            }
198        });
199
200        $("body").on("click", "#load-more-comments", function () {
201            listComment(true);
202        });
203        $("body").on("click", "#close-modal-comment", function () {
204            document.getElementById('modal-comment').style.display = 'none'
205        });
206        if (fieldComment.length > 0) {
207            listComment();
208        }
209    });
210})(jQuery, drupalSettings);

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.