Files
aifo-portal-web/js/layout.js
T
2026-07-03 20:34:27 +08:00

243 lines
7.3 KiB
JavaScript

/**
* Loads shared nav/footer and applies active menu + copyright year.
* Requires pages to be served over HTTP (see server.js).
*/
(function () {
var NAV_URL = "/components/nav.html";
var FOOTER_URL = "/components/footer.html";
var FOOT_CSS_ID = "aifo-foot-styles";
var NAV_CSS_ID = "aifo-nav-styles";
var NAV_CSS_URL = "/assets/css/nav.css";
var AIFO_BTN_CSS_ID = "aifo-btn-styles";
var AIFO_BTN_CSS_URL = "/assets/css/aifo-btn.css";
var ICONFONT_CSS_ID = "aifo-iconfont-styles";
var ICONFONT_CSS_URL = "/assets/vendor/icon/iconfont.css";
var FLAG_ICONS_CSS_ID = "aifo-flag-icons-styles";
var FLAG_ICONS_CSS_URL = "/assets/flagicon/css/flag-icons.min.css";
var FOOT_JS_URL = "/js/footer.js";
var footJsLoading = false;
var footJsLoaded = false;
function normalizePath(pathname) {
if (!pathname) return "/";
var p = pathname.split("?")[0].split("#")[0];
try {
p = decodeURIComponent(p);
} catch (e) {
/* ignore */
}
if (p.endsWith("/index.html")) {
p = p.slice(0, -"/index.html".length) || "/";
}
if (p.length > 1 && p.endsWith("/")) {
p = p.slice(0, -1);
}
return p.toLowerCase();
}
function hrefToPath(href) {
if (!href || href === "#" || href.indexOf("javascript:") === 0) return null;
try {
var url = new URL(href, window.location.origin);
if (url.origin !== window.location.origin) return null;
return normalizePath(url.pathname);
} catch (e) {
return null;
}
}
function pathsMatch(current, target) {
if (!target) return false;
if (current === target) return true;
if (
(current === "/" || current === "/index.html") &&
(target === "/" || target === "/index.html")
) {
return true;
}
return false;
}
function highlightActiveNav(container) {
var current = normalizePath(window.location.pathname);
var links = container.querySelectorAll("a[href]");
links.forEach(function (anchor) {
var target = hrefToPath(anchor.getAttribute("href"));
if (!pathsMatch(current, target)) return;
anchor.classList.add("active");
var item = anchor.closest(".nav-item");
if (item) item.classList.add("active");
if (anchor.classList.contains("dropdown-item")) {
var parentItem = anchor.closest(".nav-item.dropdown");
if (parentItem) {
parentItem.classList.add("active");
var toggle = parentItem.querySelector(".nav-link.dropdown-toggle");
if (toggle) toggle.classList.add("active");
}
}
});
}
function updateCopyrightYear(root) {
var year = String(new Date().getFullYear());
root.querySelectorAll("#current-year").forEach(function (el) {
el.textContent = year;
});
}
function fetchText(url) {
return fetch(url, { credentials: "same-origin" }).then(function (res) {
if (!res.ok) throw new Error("Failed to load " + url + " (" + res.status + ")");
return res.text();
});
}
function injectHtml(containerId, html) {
var el = document.getElementById(containerId);
if (!el) return null;
el.innerHTML = html;
return el;
}
function ensureFooterStyles() {
if (document.getElementById(FOOT_CSS_ID)) return;
var link = document.createElement("link");
link.id = FOOT_CSS_ID;
link.rel = "stylesheet";
link.href = "/assets/css/foot.css";
document.head.appendChild(link);
}
function ensureFlagIconsStyles() {
if (document.getElementById(FLAG_ICONS_CSS_ID)) return;
var link = document.createElement("link");
link.id = FLAG_ICONS_CSS_ID;
link.rel = "stylesheet";
link.href = FLAG_ICONS_CSS_URL;
document.head.appendChild(link);
}
function ensureNavStyles() {
if (!document.getElementById(ICONFONT_CSS_ID)) {
var iconLink = document.createElement("link");
iconLink.id = ICONFONT_CSS_ID;
iconLink.rel = "stylesheet";
iconLink.href = ICONFONT_CSS_URL;
document.head.appendChild(iconLink);
}
if (!document.getElementById(NAV_CSS_ID)) {
var navLink = document.createElement("link");
navLink.id = NAV_CSS_ID;
navLink.rel = "stylesheet";
navLink.href = NAV_CSS_URL;
document.head.appendChild(navLink);
}
if (!document.getElementById(AIFO_BTN_CSS_ID)) {
var btnLink = document.createElement("link");
btnLink.id = AIFO_BTN_CSS_ID;
btnLink.rel = "stylesheet";
btnLink.href = AIFO_BTN_CSS_URL;
document.head.appendChild(btnLink);
}
}
function loadFooterScript(callback) {
if (footJsLoaded) {
callback();
return;
}
if (footJsLoading) {
document.addEventListener("aifo:footer-js-ready", callback, { once: true });
return;
}
footJsLoading = true;
var script = document.createElement("script");
script.src = FOOT_JS_URL;
script.onload = function () {
footJsLoaded = true;
footJsLoading = false;
document.dispatchEvent(new CustomEvent("aifo:footer-js-ready"));
callback();
};
script.onerror = function () {
footJsLoading = false;
console.error("[layout.js] Failed to load " + FOOT_JS_URL);
};
document.body.appendChild(script);
}
var navIconsJsLoading = false;
var navIconsJsLoaded = false;
function loadNavIconsScript() {
if (navIconsJsLoaded || navIconsJsLoading) return;
navIconsJsLoading = true;
var script = document.createElement("script");
script.src = "/js/nav-icons.js";
script.onload = function () {
navIconsJsLoaded = true;
navIconsJsLoading = false;
};
script.onerror = function () {
navIconsJsLoading = false;
console.error("[layout.js] Failed to load /js/nav-icons.js");
};
document.body.appendChild(script);
}
function initFooterModule(root) {
ensureFooterStyles();
updateCopyrightYear(root);
loadFooterScript(function () {
if (typeof window.initAifoFooter === "function") {
window.initAifoFooter(root);
}
});
}
function initLayout() {
var navContainer = document.getElementById("nav-container");
var footerContainer = document.getElementById("footer-container");
if (!navContainer && !footerContainer) return;
var tasks = [];
if (navContainer) tasks.push(fetchText(NAV_URL));
else tasks.push(Promise.resolve(null));
if (footerContainer) tasks.push(fetchText(FOOTER_URL));
else tasks.push(Promise.resolve(null));
Promise.all(tasks)
.then(function (parts) {
var navHtml = parts[0];
var footerHtml = parts[1];
if (navHtml) {
ensureNavStyles();
ensureFlagIconsStyles();
var navRoot = injectHtml("nav-container", navHtml);
if (navRoot) highlightActiveNav(navRoot);
loadNavIconsScript();
}
if (footerHtml) {
var footerRoot = injectHtml("footer-container", footerHtml);
if (footerRoot) initFooterModule(footerRoot);
}
if (navHtml && window.jQuery && window.jQuery.fn.dropdown) {
window.jQuery("#nav-container [data-toggle=\"dropdown\"]").dropdown();
}
document.dispatchEvent(new CustomEvent("aifo:layout-ready"));
})
.catch(function (err) {
console.error("[layout.js]", err);
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initLayout);
} else {
initLayout();
}
})();