1/* NghienDeutsch website â language switch (mặc Äá»nh theo IP), giá Pro Äá»ng, nav */ 2(function () { 3 var LANGS = ["vi", "en", "de"]; 4 var WORKER = "https://nghienducchua-proxy.thoatran21012.workers.dev"; 5 var _proPrice = null; // { vnd:'99.000â«', eur:'â 3,99 ⬠/ tháng' } lấy từ /pay-info 6 var _plans = []; 7 var _releaseLinks = null; 8 var _installCopy = null; 9 10 function fmtVnd(x) { return String(Math.round(Number(x) || 0)).replace(/\B(?=(\d{3})+(?!\d))/g, ".") + "â«"; } 11 function fmtEur(x) { return (Number(x) || 0).toFixed(2).replace(".", ",") + " â¬"; } 12 13 function storedLang() { 14 var q = new URLSearchParams(location.search).get("lang"); 15 if (LANGS.indexOf(q) >= 0) return q; 16 var s = localStorage.getItem("nd_lang"); 17 if (LANGS.indexOf(s) >= 0) return s; 18 return null; 19 } 20 function pickLangByCountry(c) { 21 c = String(c || "").toUpperCase(); 22 if (c === "DE" || c === "AT" || c === "CH") return "de"; 23 if (c === "VN") return "vi"; 24 return "en"; 25 } 26 function curLang() { return storedLang() || document.documentElement.lang || "vi"; } 27 function localizedPlan(plan, lang) { 28 var all = plan && plan.i18n || {}; 29 var content = all[lang] || all.vi || {}; 30 return { 31 display_name: content.display_name || plan.display_name || plan.name, 32 description: content.description || "", 33 features: Array.isArray(content.features) && content.features.length ? content.features : (plan.features || []), 34 }; 35 } 36 37 // Chữ trang Cà i Äặt/Cáºp nháºt mà Admin sá»a trong Váºn hà nh & bảo trì. Tải từ 38 // /install-copy; khóa nà o Admin Äá» trá»ng thì giữ nguyên chữ Äóng gói trong i18n.js, 39 // nên mất mạng hay chưa cấu hình gì thì trang vẫn Äá»§ chữ. 40 var _installCopy = null; 41 42 function applyLang(lang) { 43 var packed = (window.I18N && window.I18N[lang]) || {}; 44 var dict = packed; 45 var override = _installCopy && _installCopy[lang]; 46 if (override) { 47 dict = {}; 48 for (var pk in packed) if (Object.prototype.hasOwnProperty.call(packed, pk)) dict[pk] = packed[pk]; 49 for (var ok in override) if (Object.prototype.hasOwnProperty.call(override, ok)) dict[ok] = override[ok]; 50 } 51 document.documentElement.lang = lang; 52 var page = location.pathname.split("/").pop() || "index.html"; 53 // Bảng tra cứu: Má»I trang phải có title + description riêng. Trưá»c Äây viết 54 // bằng chuá»i ba ngôi nên trang má»i (update.html) rÆ¡i vá» meta cá»§a trang chá»§,
55 // và privacy/terms cÅ©ng dùng description cá»§a trang chá»§. 56 var META = { 57 "index.html": ["meta.home.title", "meta.home.description"], 58 "install.html": ["meta.install.title", "meta.install.description"], 59 "update.html": ["meta.update.title", "meta.update.description"], 60 "privacy.html": ["meta.privacy.title", "meta.privacy.description"], 61 "terms.html": ["meta.terms.title", "meta.terms.description"], 62 }; 63 var meta = META[page] || META["index.html"]; 64 if (dict[meta[0]]) document.title = dict[meta[0]]; 65 var description = document.querySelector('meta[name="description"]'); 66 if (description && dict[meta[1]]) description.setAttribute("content", dict[meta[1]]); 67 document.querySelectorAll("[data-i18n]").forEach(function (el) { 68 var k = el.getAttribute("data-i18n"); 69 if (dict[k] != null) el.textContent = dict[k]; 70 }); 71 document.querySelectorAll("[data-i18n-html]").forEach(function (el) { 72 var k = el.getAttribute("data-i18n-html"); 73 if (dict[k] != null) el.innerHTML = dict[k]; 74 }); 75 localStorage.setItem("nd_lang", lang); 76 var lbl = document.querySelector(".lang-switch"); 77 if (lbl) lbl.textContent = lang.toUpperCase() + " â¾"; 78 applyProPrice(); // giá Äá»ng phải Äặt SAU i18n (i18n vừa ghi Äè lại textContent) 79 applyInstallCopy(lang); // chữ hưá»ng dẫn Admin sá»a, Äè lên bản dá»±ng sẵn 80 syncCompareLabels(); // bảng so sánh xếp dạng thẻ trên Äiá»n thoại cần tên cá»t 81 if (_releaseLinks) applyInstallLinks(_releaseLinks); 82 enhanceChromeUrlChips(); // chạy SAU i18n vì innerHTML vừa dá»±ng lại các <code> 83 enhanceLinkboxCopy(); // nút sao chép link dà i (Cách 2) 84 } 85 86 // Chữ hưá»ng dẫn cà i Äặt do Admin nháºp (Worker /version â install_copy). Admin gõ 87 // chữ thuần nên KHÃNG dùng innerHTML á» Äây: má»i thứ dá»±ng bằng createTextNode, còn 88 // các Äá»a chá» dạng chrome:// | edge:// | about: ÄÆ°á»£c bá»c <code> Äá» enhanceChromeUrlChips 89 // biến tiếp thà nh chip bấm-Äá»-copy. Má»t tà i khoản admin bá» chiếm cÅ©ng không nhúng 90 // ÄÆ°á»£c thẻ script và o trang công khai. 91 var SCHEME_TOKEN = /((?:chrome|edge|brave|opera|vivaldi|about|moz-extension):\/?\/?[^\s,;)]+)/g; 92 function renderCopyInto(el, text) { 93 while (el.firstChild) el.removeChild(el.firstChild); 94 String(text).split(SCHEME_TOKEN).forEach(function (part, i) { 95 if (!part) return; 96 // split() vá»i capture group: phần tá» lẻ luôn là token khá»p regex. 97 if (i % 2 === 1) { 98 var code = document.createElement("code"); 99 code.textContent = part.replace(/[.,;:]+$/, ""); 100 el.appendChild(code); 101 var tail = part.match(/[.,;:]+$/); 102 if (tail) el.appendChild(document.createTextNode(tail[0])); 103 } else { 104 el.appendChild(document.createTextNode(part)); 105 } 106 }); 107 } 108 // Dưá»i 640px bảng so sánh xếp lại thà nh thẻ, má»i ô â phải tá»± nói nó thuá»c cá»t nà o. 109 // Chép nhãn từ <thead> thay vì viết cứng Äá» không phải dá»ch lần thứ hai â Äá»i ngôn 110 // ngữ là nhãn Äá»i theo. 111 function syncCompareLabels() { 112 document.querySelectorAll("table.compare").forEach(function (table) { 113 var heads = table.querySelectorAll("thead th"); 114 if (heads.length < 2) return; 115 table.querySelectorAll("tbody tr").forEach(function (row) { 116 row.querySelectorAll("td").forEach(function (cell, i) { 117 if (i === 0 || !heads[i]) return; 118 cell.setAttribute("data-label", (heads[i].textContent || "").trim()); 119 if (heads[i].classList.contains("us")) cell.setAttribute("data-us", "1"); 120 }); 121 }); 122 }); 123 } 124 125 function applyInstallCopy(lang) { 126 // Không fallback sang vi: nếu Admin má»i sá»a tiếng Viá»t mà DeepL chưa ká»p dá»ch 127 // EN/DE, giữ nguyên bản dá»±ng sẵn trong i18n.js thay vì hiá»n tiếng Viá»t trên 128 // trang English (Äúng lá»i ngôn ngữ lẫn trong hình 1). 129 var copy = _installCopy && _installCopy[lang]; 130 if (!copy) return;
131 Object.keys(copy).forEach(function (key) { 132 var text = copy[key]; 133 if (!text) return; 134 document.querySelectorAll('[data-i18n="' + key + '"],[data-i18n-html="' + key + '"]').forEach(function (el) { 135 renderCopyInto(el, text); 136 }); 137 }); 138 } 139 140 // Trang web KHÃNG thá» má» link chrome:// (Chrome chặn vì bảo máºt). Thay vì link 141 // chết, biến má»i <code>chrome://â¦</code> thà nh chip bấm-Äá»-copy: user dán và o 142 // thanh Äá»a chá» là xong. i18n dá»±ng lại innerHTML má»i lần Äá»i ngôn ngữ nên hà m 143 // nà y chạy lại sau Äó và bá» qua chip Äã tạo (data-copy-ready). 144 // 145 // edge:// và about:debugging bá» chặn y há»t, nên Cách 3 (Firefox) và Cách 4 (Edge) 146 // cÅ©ng cần chip â không riêng gì Chrome. 147 var COPY_LABEL = { vi: "Sao chép", en: "Copy", de: "Kopieren" }; 148 var COPIED_LABEL = { vi: "Äã sao chép!", en: "Copied!", de: "Kopiert!" }; 149 var CHIP_SCHEMES = /^(?:chrome|edge|brave|opera|vivaldi):\/\/|^about:/; 150 function enhanceChromeUrlChips() { 151 var lang = curLang(); 152 var codes = document.querySelectorAll(".legal code"); 153 codes.forEach(function (code) { 154 var url = (code.textContent || "").trim(); 155 if (!CHIP_SCHEMES.test(url)) return; // chá» Äá»a chá» trình duyá»t chặn 156 if (code.closest && code.closest(".copy-chip")) return; // <code> bên trong chip â bá» qua 157 var chip = document.createElement("button"); 158 chip.type = "button"; 159 chip.className = "copy-chip"; 160 chip.setAttribute("data-copy-ready", "1"); 161 chip.title = COPY_LABEL[lang] || COPY_LABEL.vi; 162 var codeInner = document.createElement("code"); 163 codeInner.textContent = url; 164 var ico = document.createElement("span"); 165 ico.className = "copy-ico"; 166 ico.setAttribute("aria-hidden", "true"); 167 ico.textContent = "â§"; 168 chip.appendChild(codeInner); 169 chip.appendChild(ico); 170 chip.addEventListener("click", function () { 171 copyText(url).then(function (ok) { 172 if (!ok) return; 173 chip.classList.add("copied"); 174 var prev = ico.textContent; 175 ico.textContent = "â"; 176 chip.title = COPIED_LABEL[lang] || COPIED_LABEL.vi; 177 setTimeout(function () { 178 chip.classList.remove("copied"); 179 ico.textContent = prev; 180 chip.title = COPY_LABEL[lang] || COPY_LABEL.vi; 181 }, 1600); 182 }); 183 }); 184 code.parentNode.replaceChild(chip, code); 185 }); 186 } 187 function copyText(text) { 188 if (navigator.clipboard && navigator.clipboard.writeText) { 189 return navigator.clipboard.writeText(text).then(function () { return true; }, function () { return legacyCopy(text); }); 190 } 191 return Promise.resolve(legacyCopy(text)); 192 } 193 // Nút sao chép trong .pg-linkbox: lấy link từ phần tá» mà data-copy-from trá» tá»i, 194 // nên không nhân bản URL và o HTML lần thứ hai (sai má»t chá» là lá»ch nhau ngay). 195 function enhanceLinkboxCopy() { 196 var lang = curLang(); 197 document.querySelectorAll(".pg-linkbox__copy[data-copy-from]").forEach(function (btn) { 198 if (btn.getAttribute("data-copy-ready")) return; 199 btn.setAttribute("data-copy-ready", "1"); 200 var target = document.getElementById(btn.getAttribute("data-copy-from")); 201 if (!target) { btn.hidden = true; return; } 202 var ico = btn.querySelector("span") || btn; 203 btn.title = COPY_LABEL[lang] || COPY_LABEL.vi; 204 btn.addEventListener("click", function () { 205 copyText(target.getAttribute("href") || target.textContent || "").then(function (ok) { 206 if (!ok) return; 207 btn.classList.add("copied"); 208 ico.textContent = "â"; 209 btn.title = COPIED_LABEL[lang] || COPIED_LABEL.vi; 210 setTimeout(function () { 211 btn.classList.remove("copied"); 212 ico.textContent = "â§"; 213 btn.title = COPY_LABEL[lang] || COPY_LABEL.vi; 214 }, 1600); 215 }); 216 }); 217 }); 218 } 219 function legacyCopy(text) { 220 try { 221 var ta = document.createElement("textarea"); 222 ta.value = text; ta.setAttribute("readonly", ""); 223 ta.style.position = "absolute"; ta.style.left = "-9999px"; 224 document.body.appendChild(ta); ta.select(); 225 var ok = document.execCommand("copy"); 226 document.body.removeChild(ta); 227 return ok; 228 } catch (_) { return false; } 229 } 230 231 // Giá Pro Äá»ng từ admin (/pay-info): ghi Äè sá» niêm yết cứng khi có dữ liá»u. 232 function applyProPrice() { 233 if (!_proPrice) return; 234 var v = document.getElementById("land-pro-vnd"); 235 var e = document.getElementById("land-pro-eur"); 236 if (v && _proPrice.vnd) v.textContent = _proPrice.vnd; 237 if (e && _proPrice.eur) e.textContent = _proPrice.eur; 238 applyPlanCatalog(); 239 } 240 function applyPlanCatalog() { 241 var wrap = document.querySelector("#pricing .plans"); 242 if (!wrap || !_plans.length) return; 243 var lang = curLang(); 244 var perMonth = { vi: "/tháng", en: "/month", de: "/Monat" }[lang] || "/tháng"; 245 _plans.forEach(function (plan) { 246 if (!plan || !plan.name) return; 247 var card = wrap.querySelector('[data-plan="' + plan.name + '"]'); 248 if (!card) { 249 card = document.createElement("div"); 250 card.className = "plan"; 251 card.setAttribute("data-plan", plan.name); 252 card.innerHTML = '<h3></h3><p class="plan-description" style="color:var(--ink-soft);font-size:14px"></p><div class="price"><span></span><small></small></div><div class="price-eur"></div><ul></ul><a class="btn btn-primary"></a>'; 253 wrap.appendChild(card); 254 } 255 var content = localizedPlan(plan, lang); 256 var title = card.querySelector("h3"); 257 var description = card.querySelector(".plan-description"); 258 var primary = card.querySelector(".price span"); 259 var unit = card.querySelector(".price small"); 260 var secondary = card.querySelector(".price-eur"); 261 var list = card.querySelector("ul"); 262 var action = card.querySelector("a.btn"); 263 var price = plan.price || {}; 264 if (title) title.textContent = content.display_name; 265 if (description && content.description) description.textContent = content.description; 266 if (primary) primary.textContent = lang === "vi" ? fmtVnd(price.VND || 0) : fmtEur(price.EUR || 0); 267 if (unit) unit.textContent = plan.name === "free" ? ({ vi: "/mãi mãi", en: "/forever", de: "/für immer" }[lang] || "/mãi mãi") : perMonth; 268 if (secondary) secondary.textContent = plan.name === "free" ? "\u00a0" : (lang === "vi" ? "â " + fmtEur(price.EUR || 0) + " " + perMonth : "â " + fmtVnd(price.VND || 0) + " " + perMonth); 269 if (list && Array.isArray(content.features) && content.features.length) { 270 list.innerHTML = "";
271 content.features.forEach(function (feature) { 272 var li = document.createElement("li"); 273 li.textContent = String(feature); 274 list.appendChild(li); 275 }); 276 } 277 if (action) { 278 action.href = plan.name === "free" ? "install.html" : "app/?plan=" + encodeURIComponent(plan.name); 279 action.textContent = plan.name === "free" 280 ? ({ vi: "Cà i Äặt miá» n phÃ", en: "Install free", de: "Kostenlos installieren" }[lang] || "Cà i Äặt miá» n phÃ") 281 : ({ vi: "Chá»n gói", en: "Choose plan", de: "Plan wählen" }[lang] || "Chá»n gói"); 282 } 283 }); 284 } 285 function loadProPrice() { 286 if (!document.getElementById("land-pro-vnd")) return; // chá» trang có bảng giá 287 fetch(WORKER + "/pay-info", { method: "POST", headers: { "Content-Type": "application/json" }, body: "{}" }) 288 .then(function (r) { return r.json(); }) 289 .then(function (d) { 290 _plans = (d && Array.isArray(d.plans)) ? d.plans : []; 291 var pro = (d && d.price_table && d.price_table.pro) || {}; 292 if (!pro.EUR && !pro.VND && !_plans.length) return; 293 var perMonth = { vi: " / tháng", en: " / month", de: " / Monat" }[curLang()] || " / tháng"; 294 _proPrice = { 295 vnd: pro.VND ? fmtVnd(pro.VND) : null, 296 eur: pro.EUR ? "â " + fmtEur(pro.EUR) + perMonth : null, 297 }; 298 applyProPrice(); 299 applyPlanCatalog(); 300 }) 301 .catch(function () {}); 302 } 303 304 var DEFAULT_CONTACTS = [ 305 { type: "email", label: "", value: "[email protected]" }, 306 ]; 307 308 function renderContacts(box, contacts) { 309 var seen = {}; 310 var list = (Array.isArray(contacts) ? contacts : []).filter(function (ct) { 311 if (!ct || !String(ct.value || "").trim()) return false; 312 var key = String(ct.type || "text") + ":" + String(ct.value).trim().toLowerCase(); 313 if (seen[key]) return false; 314 seen[key] = true; 315 return true; 316 }); 317 if (!list.length) list = DEFAULT_CONTACTS; 318 box.innerHTML = ""; 319 list.forEach(function (ct) { 320 var value = String(ct.value || "").trim(); 321 var type = ct.type || (/@/.test(value) ? "email" : "text"); 322 var node = document.createElement(type === "email" || type === "phone" ? "a" : "div"); 323 if (type === "email") node.href = "mailto:" + value; 324 if (type === "phone") node.href = "tel:" + value.replace(/[^+\d]/g, ""); 325 node.textContent = (ct.label ? String(ct.label).trim() + ": " : "") + value; 326 box.appendChild(node); 327 }); 328 } 329 330 // Kontakt Äá»ng á» footer: lấy từ admin (/pay-info contacts). HTML giữ má»t 331 // Äá»a chá» dá»± phòng Äá» footer vẫn dùng ÄÆ°á»£c nếu mạng hoặc Worker tạm lá»i. 332 function loadContacts() { 333 var box = document.getElementById("foot-contacts"); 334 if (!box) return; 335 fetch(WORKER + "/pay-info", { method: "POST", cache: "no-store", headers: { "Content-Type": "application/json" }, body: "{}" }) 336 .then(function (r) { 337 if (!r.ok) throw new Error("contact-config-unavailable"); 338 return r.json(); 339 }) 340 .then(function (d) { 341 var list = (d && Array.isArray(d.contacts)) ? d.contacts : []; 342 renderContacts(box, list); 343 }) 344 .catch(function () { renderContacts(box, DEFAULT_CONTACTS); }); 345 } 346 347 function applyInstallLinks(release) { 348 release = release || {}; 349 _releaseLinks = release; 350 var chromeUrl = String(release.chrome_store_url || "").trim(); 351 var manualUrl = String(release.manual_download_url || release.download_url || "").trim(); 352 var cws = document.getElementById("install-cws-btn"); 353 var manualBtn = document.getElementById("install-manual-btn"); 354 var manualLink = document.getElementById("install-manual-link"); 355 // Phiên bản má»i nhất trong mục "Cáºp nháºt": lấy từ /version (Admin là nguá»n 356 // chuẩn) Äá» hưá»ng dẫn không bao giá» ghi cứng má»t sá» cÅ©. 357 var latestVer = document.getElementById("install-latest-version"); 358 var stable = String(release.stable || release.version || "").trim(); 359 if (latestVer && stable) latestVer.textContent = stable; 360 if (cws) { 361 // Ưu tiên link Chrome Web Store;
361 khi chưa lên CWS nhưng Äã có bản cà i 362 // (Google Drive/GitHub qua manual_download_url) thì nút vẫn HOẠT Äá»NG và 363 // bá» nhãn "(sắp có)" â bản cà i Äã sẵn sà ng nên không còn "bald verfügbar". 364 var liveUrl = chromeUrl || manualUrl; 365 if (liveUrl) { 366 cws.href = liveUrl; 367 cws.target = "_blank"; 368 cws.rel = "noopener"; 369 cws.removeAttribute("aria-disabled"); 370 var dict = (window.I18N && window.I18N[curLang()]) || {}; 371 if (dict["install.m1btn_live"]) cws.textContent = dict["install.m1btn_live"]; 372 } else { 373 cws.href = "#"; 374 cws.setAttribute("aria-disabled", "true"); 375 } 376 } 377 if (manualUrl) { 378 if (manualBtn) manualBtn.href = manualUrl; 379 if (manualLink) { 380 manualLink.href = manualUrl; 381 manualLink.textContent = manualUrl; 382 } 383 } 384 385 // ââ Nút theo từng trình duyá»t âââââââââââââââââââââââââââââââââââââââââââ 386 // Nguyên tắc: KHÃNG BAO GIá» hiá»n link chết â bấm và o link rá»ng thì ngưá»i dùng 387 // tưá»ng sản phẩm há»ng. à Admin trá»ng thì nút nà o có ÄÆ°á»ng dá»± phòng dùng ÄÆ°á»£c sẽ 388 // chuyá»n sang ÄÆ°á»ng Äó (Firefox â gói trên Google Drive, Edge â Chrome Web Store); 389 // nút nà o không có gì thay thế thì bỠẩn. 390 var firefoxStore = String(release.firefox_store_url || "").trim(); 391 var edgeStore = String(release.edge_store_url || "").trim(); 392 var ffBtn = document.getElementById("install-firefox-btn"); 393 var edgeBtn = document.getElementById("install-edge-btn"); 394 var ffSoon = document.getElementById("install-firefox-soon"); 395 396 function wire(el, url) { 397 if (!el) return false; 398 if (url) { 399 el.href = url; 400 el.target = "_blank"; 401 el.rel = "noopener"; 402 el.hidden = false; 403 el.removeAttribute("aria-disabled"); 404 return true; 405 } 406 el.href = "#"; 407 el.hidden = true; 408 el.setAttribute("aria-disabled", "true"); 409 return false; 410 } 411 412 // Firefox: nút LUÃN dùng ÄÆ°á»£c, cùng nguyên tắc vá»i nút Chrome á» trên 413 // (chromeUrl || manualUrl). Hai trạng thái, và nhãn phải khá»p viá»c nút tháºt sá»± là m: 414 // ⢠Admin Äã dán link addons.mozilla.org â má» trang AMO, nhãn "Thêm và o Firefox". 415 // ⢠Chưa có â tải bản Firefox từ Google Drive, nhãn "Tải bản Firefox". 416 // Vì sao KHÃNG ẩn nút khi ô AMO trá»ng (khác Edge): bưá»c 1 trong chÃnh thẻ nà y viết 417 // "tải bản Firefox á» TRÃN". Ẩn nút thì chẳng còn gì á» trên Äá» bấm và ngưá»i dùng bế 418 // tắc, dù ÄÆ°á»ng cà i thá»§ công vẫn chạy tá»t. Nút nà y cÅ©ng không Äi qua wire() vì 419 // wire() ẩn nút khi không có URL â á» Äây HTML Äã có href Drive cứng là m lưá»i an toà n 420 // cho trưá»ng hợp /version không gá»i ÄÆ°á»£c. 421 if (ffBtn) { 422 var ffDict = (window.I18N && window.I18N[curLang()]) || {}; 423 var ffUrl = firefoxStore || manualUrl; 424 if (ffUrl) ffBtn.href = ffUrl; 425 ffBtn.target = "_blank"; 426 ffBtn.rel = "noopener"; 427 ffBtn.hidden = false; 428 ffBtn.removeAttribute("aria-disabled"); 429 var ffKey = firefoxStore ? "install.bfirefox" : "install.bfirefox_dl"; 430 if (ffDict[ffKey]) ffBtn.textContent = ffDict[ffKey]; 431 } 432 // Edge cà i ÄÆ°á»£c cả từ Chrome Web Store, nên khi Admin chưa Äiá»n link Edge riêng 433 // thì nút vẫn dùng ÄÆ°á»£c bằng link Chromium thay vì bỠẩn. 434 wire(edgeBtn, edgeStore || chromeUrl || manualUrl); 435 // Dòng "Äang chá» duyá»t trên addons.mozilla.org" chỠẩn khi Admin Äà có link AMO. 436 // Trưá»c Äây nó ẩn theo "nút có sá»ng không", mà giá» nút luôn sá»ng nhá» link Google 437 // Drive â bám và o Äó thì dòng nhắc biến mất trong khi bản AMO vẫn chưa có. 438 if (ffSoon) ffSoon.hidden = !!firefoxStore; 439 // Dòng "Äang chá» duyá»t trên Edge Add-ons" chá» hiá»n khi CHƯA có link Edge riêng â 440 // lúc Äó nút Äang dùng link Chromium, nên ngưá»i dùng cần biết vì sao. 441 var edgeSoon = document.getElementById("install-edge-soon"); 442 if (edgeSoon) edgeSoon.hidden = !!edgeStore; 443 444 } 445 446 function loadReleaseLinks() { 447 if (!document.getElementById("install-cws-btn") && !document.getElementById("install-manual-btn")) return; 448 // Chá» hai trang nà y dùng chữ ghi Äè â trang chá»§ không cần gá»i thêm request. 449 if (/install\.html$|update\.html$/.test(location.pathname)) { 450 fetch(WORKER + "/install-copy", { cache: "no-store" }) 451 .then(function (r) { return r.ok ? r.json() : null; }) 452 .then(function (d) { 453 if (!d) return; 454 _installCopy = d; 455 applyLang(curLang()); // vẽ lại bằng chữ cá»§a Admin 456 }) 457 .catch(function () { /* giữ chữ Äóng gói */ }); 458 } 459 460 fetch(WORKER + "/version", { cache: "no-store" }) 461 .then(function (r) { 462 if (!r.ok) throw new Error("release-config-unavailable"); 463 return r.json(); 464 }) 465 .then(function (d) { 466 _installCopy = (d && d.install_copy) || null; 467 applyInstallLinks(d && d.extension); 468 // /version vá» sau lần applyLang Äầu tiên nên phải vẽ lại chữ á» Äây. 469 applyInstallCopy(curLang()); 470 enhanceChromeUrlChips(); 471 }) 472 .catch(function () {}); 473 } 474 475 document.addEventListener("DOMContentLoaded", function () { 476 var s = storedLang(); 477 applyLang(s || "vi"); 478 loadProPrice(); 479 loadContacts(); 480 loadReleaseLinks(); 481 setInterval(loadReleaseLinks, 60000); 482 // Chưa có lá»±a chá»n rõ rà ng â dùng ngôn ngữ theo IP (quá»c gia từ Cloudflare edge). 483 if (!s && !localStorage.getItem("nd_lang_userset")) { 484 fetch(WORKER + "/version").then(function (r) { return r.json(); }).then(function (d) { 485 if (localStorage.getItem("nd_lang_userset")) return; 486 applyLang(pickLangByCountry(d && d.country)); 487 }).catch(function () {}); 488 } 489 490 var ls = document.querySelector(".lang-switch"); 491 if (ls) ls.addEventListener("click", function () { 492 localStorage.setItem("nd_lang_userset", "1"); // ngưá»i dùng tá»± chá»n â không ghi Äè bằng IP nữa 493 var cur = curLang(); 494 var next = LANGS[(LANGS.indexOf(cur) + 1) % LANGS.length]; 495 applyLang(next); 496 }); 497 498 var nt = document.querySelector(".nav-toggle"); 499 var nl = document.querySelector(".nav-links"); 500 if (nt && nl) { 501 nt.addEventListener("click", function () { nl.classList.toggle("open"); }); 502 nl.querySelectorAll("a").forEach(function (a) { a.addEventListener("click", function () { nl.classList.remove("open"); }); }); 503 } 504 }); 505})();
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.