feat: 新增 icons.region 配置项&修改 favicon 加载超时机制&修复去除硬编码
- 新增 icons.region: com | cn 配置项,允许用户选择优先使用国内源或国外源 - com: 优先 gstatic.com,失败回退 gstatic.cn - cn: 优先 gstatic.cn,失败回退 gstatic.com - 修改 favicon 加载超时判断机制 - 自定义 faviconUrl: 5秒超时后显示回退图标 - 自动 favicon: 每次尝试3秒超时,最多等待6秒 - 更新配置文档和默认配置示例 - 去除卡片模板中的url硬编码 Issue: #31
This commit is contained in:
@@ -307,6 +307,8 @@ function ensureConfigDefaults(config) {
|
||||
result.icons = result.icons || {};
|
||||
// icons.mode: manual | favicon, 默认 favicon
|
||||
result.icons.mode = result.icons.mode || 'favicon';
|
||||
// icons.region: com | cn, 默认 com(优先使用 gstatic.com,失败后回退到 gstatic.cn)
|
||||
result.icons.region = result.icons.region || 'com';
|
||||
|
||||
// 站点基本信息默认值
|
||||
result.site.title = result.site.title || 'MeNav导航';
|
||||
|
||||
@@ -194,6 +194,48 @@ function add(a, b) {
|
||||
return numA + numB;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 icons.region 配置生成 favicon URL
|
||||
* @param {string} url 站点 URL
|
||||
* @param {Object} options Handlebars options 对象
|
||||
* @returns {string} favicon URL
|
||||
* @example {{faviconUrl url}}
|
||||
*/
|
||||
function faviconUrl(url, options) {
|
||||
if (!url) return '';
|
||||
|
||||
const region = options.data.root.icons?.region || 'com';
|
||||
const domain = region === 'cn' ? 't3.gstatic.cn' : 't3.gstatic.com';
|
||||
|
||||
try {
|
||||
const encodedUrl = encodeURIComponent(String(url));
|
||||
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 icons.region 配置生成 favicon 回退 URL
|
||||
* @param {string} url 站点 URL
|
||||
* @param {Object} options Handlebars options 对象
|
||||
* @returns {string} favicon 回退 URL
|
||||
* @example {{faviconFallbackUrl url}}
|
||||
*/
|
||||
function faviconFallbackUrl(url, options) {
|
||||
if (!url) return '';
|
||||
|
||||
const region = options.data.root.icons?.region || 'com';
|
||||
const domain = region === 'cn' ? 't3.gstatic.com' : 't3.gstatic.cn';
|
||||
|
||||
try {
|
||||
const encodedUrl = encodeURIComponent(String(url));
|
||||
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// 导出所有工具类助手函数
|
||||
module.exports = {
|
||||
slice,
|
||||
@@ -205,5 +247,7 @@ module.exports = {
|
||||
pick,
|
||||
keys,
|
||||
encodeURIComponent: encodeURIComponentHelper,
|
||||
add
|
||||
add,
|
||||
faviconUrl,
|
||||
faviconFallbackUrl
|
||||
};
|
||||
|
||||
@@ -392,14 +392,17 @@ window.MeNav = {
|
||||
contentWrapper.appendChild(descEl);
|
||||
|
||||
let iconsMode = 'favicon';
|
||||
let iconsRegion = 'com';
|
||||
try {
|
||||
const cfg =
|
||||
window.MeNav && typeof window.MeNav.getConfig === 'function'
|
||||
? window.MeNav.getConfig()
|
||||
: null;
|
||||
iconsMode = (cfg && (cfg.data?.icons?.mode || cfg.icons?.mode)) || 'favicon';
|
||||
iconsRegion = (cfg && (cfg.data?.icons?.region || cfg.icons?.region)) || 'com';
|
||||
} catch (e) {
|
||||
iconsMode = 'favicon';
|
||||
iconsRegion = 'com';
|
||||
}
|
||||
|
||||
const shouldUseCustomFavicon = Boolean(siteFaviconUrl);
|
||||
@@ -422,11 +425,23 @@ window.MeNav = {
|
||||
favicon.src = siteFaviconUrl;
|
||||
favicon.alt = `${siteName} favicon`;
|
||||
favicon.loading = 'lazy';
|
||||
|
||||
// 超时处理:5秒后如果还没加载成功,显示回退图标
|
||||
let loadTimeout = setTimeout(() => {
|
||||
if (!favicon.classList.contains('loaded')) {
|
||||
favicon.classList.add('error');
|
||||
placeholder.classList.add('hidden');
|
||||
fallback.classList.add('visible');
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
favicon.addEventListener('load', () => {
|
||||
clearTimeout(loadTimeout);
|
||||
favicon.classList.add('loaded');
|
||||
placeholder.classList.add('hidden');
|
||||
});
|
||||
favicon.addEventListener('error', () => {
|
||||
clearTimeout(loadTimeout);
|
||||
favicon.classList.add('error');
|
||||
placeholder.classList.add('hidden');
|
||||
fallback.classList.add('visible');
|
||||
@@ -437,8 +452,13 @@ window.MeNav = {
|
||||
iconContainer.appendChild(fallback);
|
||||
iconWrapper.appendChild(iconContainer);
|
||||
} else if (effectiveIconsMode === 'favicon' && siteUrl && /^https?:\/\//i.test(siteUrl)) {
|
||||
const faviconUrlPrimary = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`;
|
||||
const faviconUrlFallback = `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`;
|
||||
// 根据 icons.region 配置决定优先使用哪个域名
|
||||
const faviconUrlPrimary = iconsRegion === 'cn'
|
||||
? `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`
|
||||
: `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`;
|
||||
const faviconUrlFallback = iconsRegion === 'cn'
|
||||
? `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`
|
||||
: `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(siteUrl)}&size=32`;
|
||||
|
||||
const iconContainer = document.createElement('div');
|
||||
iconContainer.className = 'icon-container';
|
||||
@@ -457,14 +477,38 @@ window.MeNav = {
|
||||
favicon.alt = `${siteName} favicon`;
|
||||
favicon.loading = 'lazy';
|
||||
let faviconFallbackTried = false;
|
||||
let loadTimeout = null;
|
||||
|
||||
// 超时处理:3秒后如果还没加载成功,尝试回退 URL 或显示 Font Awesome 图标
|
||||
const startTimeout = () => {
|
||||
if (loadTimeout) clearTimeout(loadTimeout);
|
||||
loadTimeout = setTimeout(() => {
|
||||
if (!favicon.classList.contains('loaded')) {
|
||||
if (!faviconFallbackTried) {
|
||||
faviconFallbackTried = true;
|
||||
favicon.src = faviconUrlFallback;
|
||||
startTimeout(); // 为 fallback URL 也设置超时
|
||||
} else {
|
||||
favicon.classList.add('error');
|
||||
placeholder.classList.add('hidden');
|
||||
fallback.classList.add('visible');
|
||||
}
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
startTimeout();
|
||||
|
||||
favicon.addEventListener('load', () => {
|
||||
if (loadTimeout) clearTimeout(loadTimeout);
|
||||
favicon.classList.add('loaded');
|
||||
placeholder.classList.add('hidden');
|
||||
});
|
||||
favicon.addEventListener('error', () => {
|
||||
if (loadTimeout) clearTimeout(loadTimeout);
|
||||
if (!faviconFallbackTried) {
|
||||
faviconFallbackTried = true;
|
||||
favicon.src = faviconUrlFallback;
|
||||
startTimeout(); // 为 fallback URL 也设置超时
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user