1/* ========================================================= 2 MMOS Brand â global JS overrides for all Frappe apps 3 Injected via web_include_js / app_include_js 4 ========================================================= */ 5 6(function () { 7 "use strict"; 8 9 const ROOT = "https://onekeyco.com"; 10 const MAIL = "https://mail.onekeyco.com/mail/"; 11 const DOCS = "https://onekeyco.com/docs"; 12 const SUPPORT = "https://onekeyco.com/support"; 13 14 // ââ Link rewriting ââââââââââââââââââââââââââââââââââââââ 15 const REWRITE_MAP = [ 16 { match: /frappe\.io/i, replace: ROOT }, 17 { match: /frappeframework/i, replace: ROOT }, 18 { match: /erpnext\.com/i, replace: ROOT }, 19 { match: /discuss\.frappe/i, replace: SUPPORT }, 20 { match: /github\.com\/frappe/i, replace: DOCS }, 21 { match: /frappemail\.com/i, replace: MAIL }, 22 ]; 23 24 function mapHref(href) { 25 if (!href || href.startsWith("#") || href.startsWith("mailto:")) return href; 26 try { 27 const u = new URL(href, window.location.origin); 28 const h = u.hostname.toLowerCase(); 29 if (h.endsWith("onekeyco.com") || h === window.location.hostname) return href; 30 if (u.pathname.startsWith("/mail")) return MAIL; 31 for (const rule of REWRITE_MAP) { 32 if (rule.match.test(href)) return rule.replace; 33 } 34 } catch (_) {} 35 return href; 36 } 37 38 function rewriteLinks(root) { 39 (root || document).querySelectorAll("a[href]").forEach(function (a) { 40 const href = a.getAttribute("href"); 41 if (!href) return; 42 if (a.dataset.mmosHref === href) return; 43 const next = mapHref(href); 44 if (next && next !== href) a.setAttribute("href", next); 45 a.dataset.mmosHref = a.getAttribute("href") || href; 46 }); 47 } 48 49 function deferRewrite(root) { 50 if (window.requestIdleCallback) { 51 requestIdleCallback(function () { rewriteLinks(root); }, { timeout: 500 }); 52 return; 53 } 54 setTimeout(function () { rewriteLinks(root); }, 60); 55 } 56 57 // ââ Desk help menu override âââââââââââââââââââââââââââââ 58 function patchHelpMenu() { 59 if (!window.frappe || !frappe.ui || !frappe.ui.toolbar) return; 60 const origHelp = frappe.ui.toolbar.show_help; 61 if (origHelp && !origHelp._mmos_patched) { 62 frappe.ui.toolbar.show_help = function () { 63 origHelp.apply(this, arguments); 64 deferRewrite(); 65 setTimeout(function () { 66 const menu = document.querySelector(".navbar-help .dropdown-menu"); 67 if (menu) rewriteLinks(menu); 68 }, 100); 69 }; 70 frappe.ui.toolbar.show_help._mmos_patched = true; 71 } 72 // Also patch frappe.help_feedback_link 73 if (window.frappe) frappe.help_feedback_link = SUPPORT; 74 } 75 76 // ââ Boot info override ââââââââââââââââââââââââââââââââââ 77 function applyBootBranding() { 78 if (!window.frappe || !frappe.boot || !frappe.boot.mmos_branding) return; 79 const b = frappe.boot.mmos_branding; 80 if (b.brand_name) { 81 frappe.boot.app_name = b.brand_name; 82 document.title = document.title.replace(/\b(Frappe|ERPNext)\b/g, b.brand_name); 83 } 84 frappe.help_feedback_link = SUPPORT; 85 } 86 87 // ââ Navbar brand text âââââââââââââââââââââââââââââââââââ 88 function patchNavbarBrand() { 89 document.querySelectorAll(".navbar-brand .navbar-home span, .brand-name").forEach(function (el) { 90 if (el.textContent.trim() === "Frappe" || el.textContent.trim() === "ERPNext") { 91 el.textContent = "MMOS"; 92 } 93 }); 94 } 95 96 // ââ Login page ââââââââââââââââââââââââââââââââââââââââââ 97 function patchLoginPage() { 98 document.querySelectorAll(".login-footer, .login-content footer").forEach(function (el) { 99 el.innerHTML = '<small>Powered by <a href="' + ROOT + '">MMOS Platform</a></small>'; 100 }); 101 } 102 103 // ââ Modal accessibility guard ââââââââââââââââââââââââââ 104 function patchModalFocus() { 105 const handler = function (e) { 106 const modal = e && e.target; 107 const active = document.activeElement; 108 if (modal && active && modal.contains(active) && typeof active.blur === "function") { 109 active.blur(); 110 } 111 }; 112 document.addEventListener("hide.bs.modal", handler); 113 document.addEventListener("hidden.bs.modal", handler); 114 } 115 116 // ââ MutationObserver for Vue/React SPAs ââââââââââââââââ 117 function startObserver() { 118 if (!window.MutationObserver || !document.body) return; 119 new MutationObserver(function (mutations) { 120 mutations.forEach(function (m) {
121 m.addedNodes.forEach(function (node) { 122 if (node.nodeType === 1) deferRewrite(node); 123 }); 124 }); 125 }).observe(document.body, { childList: true, subtree: true }); 126 } 127 128 // ââ Init ââââââââââââââââââââââââââââââââââââââââââââââââ 129 function init() { 130 rewriteLinks(); 131 patchLoginPage(); 132 patchNavbarBrand(); 133 applyBootBranding(); 134 patchHelpMenu(); 135 startObserver(); 136 patchModalFocus(); 137 } 138 139 if (document.readyState === "loading") { 140 document.addEventListener("DOMContentLoaded", init); 141 } else { 142 init(); 143 } 144 145 // Frappe desk fully booted 146 document.addEventListener("frappe:ready", function () { 147 applyBootBranding(); 148 patchHelpMenu(); 149 deferRewrite(); 150 }); 151 152 // Desk page navigation (frappe.router) 153 setTimeout(function () { 154 if (window.frappe && frappe.router && frappe.router.on) { 155 frappe.router.on("change", function () { 156 setTimeout(function () { deferRewrite(); patchNavbarBrand(); }, 120); 157 }); 158 } 159 }, 2000); 160})(); 161/* ââ MMOS cookie banner (auto-iniettato solo dove esistono pagine legali) ââ */ 162(function () { 163 "use strict"; 164 function ready(fn) { 165 if (document.readyState !== "loading") fn(); 166 else document.addEventListener("DOMContentLoaded", fn); 167 } 168 ready(function () { 169 try { 170 if (localStorage.getItem("mmos_cookie_consent")) return; 171 // gating: mostra il banner solo se la pagina ha un link alle pagine legali 172 var hasLegal = document.querySelector('a[href="/privacy"], a[href$="/privacy"], a[href$="/privacy.html"]'); 173 if (!hasLegal) return; 174 if (document.querySelector(".mmos-cookie")) return; 175 var el = document.createElement("div"); 176 el.className = "mmos-cookie"; 177 el.innerHTML = '<p>Usiamo solo cookie tecnici e funzionali. Dettagli nella ' + 178 '<a href="/cookie">Cookie Policy</a> e nella <a href="/privacy">Privacy Policy</a>.</p>' + 179 '<div class="mmos-cookie-actions">' + 180 '<button class="mmos-cookie-ok" type="button">Accetta</button>' + 181 '<button class="mmos-cookie-no" type="button">Rifiuta</button></div>'; 182 document.body.appendChild(el); 183 setTimeout(function () { el.classList.add("is-visible"); }, 1000); 184 function close(v) { return function () { localStorage.setItem("mmos_cookie_consent", v); el.classList.remove("is-visible"); }; } 185 el.querySelector(".mmos-cookie-ok").addEventListener("click", close("accepted")); 186 el.querySelector(".mmos-cookie-no").addEventListener("click", close("rejected")); 187 } catch (e) {} 188 }); 189})(); 190 191/* ââ Relabel del bottone social 'Login with Frappe' â MMOS ID ââ 192 Scoped a /login e safe: se non c'è il bottone (siti senza SSO frappe) non fa nulla. 193 Sposta il bottone in cima e lo evidenzia. */ 194(function () { 195 if (!/^\/login/.test(location.pathname)) return; 196 function relabel() { 197 var btn = document.querySelector('a.btn-frappe'); 198 if (btn && !btn.dataset.mmosid) { 199 btn.dataset.mmosid = '1'; 200 btn.textContent = 'ð Accedi con MMOS ID'; 201 btn.style.fontWeight = '600'; 202 } 203 } 204 var obs = new MutationObserver(relabel); 205 obs.observe(document.documentElement, { childList: true, subtree: true }); 206 document.addEventListener('DOMContentLoaded', relabel); 207 relabel(); 208})();
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.