/**
* 統一導航加載器 v7 - 移除所有複製代碼
* 在所有頁面的
末尾一次性加載
*
* 功能:
* - 從Firebase Firestore動態加載導航菜單
* - 支持LocalStorage快取(24小時)
* - 自動構建桌面和行動版本導航
* - 提供降級支持
*/
(function () {
'use strict';
const CK = 'nav_v7';
const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24小時
// 清除舊版本cache
['nav_cache', 'nav_cache_v2', 'nav_cache_v3', 'nav_cache_v4', 'nav_cache_v5', 'nav_v5', 'nav_v6']
.forEach(k => { try { localStorage.removeItem(k); } catch(e) {} });
function isIndex() {
const p = window.location.pathname;
return p.endsWith('index.html') || p === '/' || p.endsWith('/');
}
// ── XSS 防線:escape HTML 與 URL ──
function escHtml(s) {
return String(s == null ? '' : s)
.replace(/&/g, '&').replace(//g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
function safeUrl(u) {
const s = String(u == null ? '' : u).trim();
if (/^\s*(javascript|data|vbscript):/i.test(s)) return '#';
return s;
}
function fixLink(link) {
if (!link) return '#';
const safe = safeUrl(link);
return safe.startsWith('#') && !isIndex() ? 'index.html' + safe : safe;
}
function buildNavHTML(items) {
if (!items || !items.length) return { desktop: '', mobile: '' };
const desktopHTML = items.map(item => {
if (item.type === 'dropdown' && item.subItems?.length > 0) {
const subHTML = item.subItems.map(sub =>
`${escHtml(sub.title)}`
).join('');
return `
`;
}
return `${escHtml(item.title)}`;
}).join('');
const mobileHTML = items.map(item => {
if (item.type === 'dropdown' && item.subItems?.length > 0) {
const subHTML = item.subItems.map(sub =>
`${escHtml(sub.title)}`
).join('');
return `
`;
}
return `${escHtml(item.title)}`;
}).join('');
return { desktop: desktopHTML, mobile: mobileHTML };
}
function loadCachedNav() {
try {
const cached = JSON.parse(localStorage.getItem(CK));
if (cached && (Date.now() - cached.timestamp) < CACHE_EXPIRY) {
return cached.items;
}
} catch (e) {}
return null;
}
function saveNav(items) {
try {
localStorage.setItem(CK, JSON.stringify({
items: items,
timestamp: Date.now()
}));
} catch (e) {}
}
function renderNav(items) {
const { desktop, mobile } = buildNavHTML(items);
const desktopContainer = document.getElementById('dynamic-nav-container');
const mobileContainer = document.getElementById('dynamic-mobile-nav');
if (desktopContainer) desktopContainer.innerHTML = desktop;
if (mobileContainer) mobileContainer.innerHTML = mobile;
}
// 立即用cache渲染(無閃爍)
const cached = loadCachedNav();
if (cached) {
document.addEventListener('DOMContentLoaded', () => renderNav(cached));
}
// 暴露API給Firebase module使用
window.sharedNav = {
load: async function(getPublicCol, getDocs, query, getDocsFromServer) {
try {
const fetcher = getDocsFromServer || getDocs;
const snap = await fetcher(query(getPublicCol("nav_menus")));
const items = [];
snap.forEach(doc => {
const data = doc.data();
if (data.status !== 'draft') items.push(data);
});
if (items.length > 0) {
items.sort((a, b) => (a.sortOrder || 99) - (b.sortOrder || 99));
saveNav(items);
renderNav(items);
}
} catch (e) {
console.error('Failed to load nav from server:', e);
// 回退到cached版本
const fallback = loadCachedNav();
if (fallback) renderNav(fallback);
}
}
};
})();