fix(icons): faviconV2 加入 drop_404_icon=true 参数避免404占位图以触发回退
- helpers:faviconV2Url / faviconFallbackUrl 统一追加 drop_404_icon=true - runtime:新增站点的 faviconV2 com/cn URL 同步追加该参数 - docs:更新模板与 helper 文档示例 - test:新增用例防止参数回归
This commit is contained in:
@@ -56,10 +56,13 @@ MeNav 的助手函数分为四类:
|
|||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#ifHttpUrl url}}
|
{{#ifHttpUrl url}}
|
||||||
{{!-- 只有 http/https 才尝试加载 favicon --}}
|
{{! 只有 http/https 才尝试加载 favicon }}
|
||||||
<img src="https://t3.gstatic.com/faviconV2?url={{encodeURIComponent url}}&size=32" alt="{{name}} favicon" />
|
<img
|
||||||
|
src='https://t3.gstatic.com/faviconV2?url={{encodeURIComponent url}}&size=32&drop_404_icon=true'
|
||||||
|
alt='{{name}} favicon'
|
||||||
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<i class="fas fa-link"></i>
|
<i class='fas fa-link'></i>
|
||||||
{{/ifHttpUrl}}
|
{{/ifHttpUrl}}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,8 +82,13 @@ MeNav 的助手函数分为四类:
|
|||||||
对字符串进行 URL 组件编码(同名于浏览器 API,用作模板内联助手),适用于将动态 URL 参数安全拼接到查询串:
|
对字符串进行 URL 组件编码(同名于浏览器 API,用作模板内联助手),适用于将动态 URL 参数安全拼接到查询串:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{!-- 构造第三方 Favicon API 的 url 参数 --}}
|
{{! 构造第三方 Favicon API 的 url 参数 }}
|
||||||
<img src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&url={{encodeURIComponent url}}&size=32" alt="favicon" />
|
<img
|
||||||
|
src='https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&url={{encodeURIComponent
|
||||||
|
url
|
||||||
|
}}&size=32&drop_404_icon=true'
|
||||||
|
alt='favicon'
|
||||||
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
### 核心函数
|
### 核心函数
|
||||||
@@ -101,7 +109,7 @@ MeNav 的助手函数分为四类:
|
|||||||
用于生成内容的助手函数:
|
用于生成内容的助手函数:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{formatDate created "YYYY-MM-DD"}}
|
{{formatDate created 'YYYY-MM-DD'}}
|
||||||
{{limit description 100}}
|
{{limit description 100}}
|
||||||
{{json data}}
|
{{json data}}
|
||||||
```
|
```
|
||||||
@@ -111,10 +119,10 @@ MeNav 的助手函数分为四类:
|
|||||||
用于控制结构的助手函数:
|
用于控制结构的助手函数:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#ifEquals type "article"}}
|
{{#ifEquals type 'article'}}
|
||||||
<span class="badge">文章</span>
|
<span class='badge'>文章</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="badge">页面</span>
|
<span class='badge'>页面</span>
|
||||||
{{/ifEquals}}
|
{{/ifEquals}}
|
||||||
|
|
||||||
{{#each (range 1 5)}}
|
{{#each (range 1 5)}}
|
||||||
@@ -141,12 +149,16 @@ MeNav 的助手函数分为四类:
|
|||||||
格式化日期:
|
格式化日期:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{formatDate date "YYYY-MM-DD"}} {{!-- 2023-05-15 --}}
|
{{formatDate date 'YYYY-MM-DD'}}
|
||||||
{{formatDate date "YYYY年MM月DD日"}} {{!-- 2023年05月15日 --}}
|
{{! 2023-05-15 }}
|
||||||
{{formatDate date "YYYY-MM-DD HH:mm:ss"}} {{!-- 2023-05-15 14:30:00 --}}
|
{{formatDate date 'YYYY年MM月DD日'}}
|
||||||
|
{{! 2023年05月15日 }}
|
||||||
|
{{formatDate date 'YYYY-MM-DD HH:mm:ss'}}
|
||||||
|
{{! 2023-05-15 14:30:00 }}
|
||||||
```
|
```
|
||||||
|
|
||||||
支持的格式:
|
支持的格式:
|
||||||
|
|
||||||
- `YYYY`: 四位年份
|
- `YYYY`: 四位年份
|
||||||
- `MM`: 两位月份
|
- `MM`: 两位月份
|
||||||
- `DD`: 两位日期
|
- `DD`: 两位日期
|
||||||
@@ -159,7 +171,7 @@ MeNav 的助手函数分为四类:
|
|||||||
限制文本长度,超出部分显示省略号:
|
限制文本长度,超出部分显示省略号:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{limit "这是一段很长的文本内容" 5}} {{!-- 这是一段... --}}
|
{{limit '这是一段很长的文本内容' 5}} {{! 这是一段... }}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### toLowerCase / toUpperCase
|
#### toLowerCase / toUpperCase
|
||||||
@@ -167,8 +179,10 @@ MeNav 的助手函数分为四类:
|
|||||||
转换文本大小写:
|
转换文本大小写:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{toLowerCase "Hello"}} {{!-- hello --}}
|
{{toLowerCase 'Hello'}}
|
||||||
{{toUpperCase "world"}} {{!-- WORLD --}}
|
{{! hello }}
|
||||||
|
{{toUpperCase 'world'}}
|
||||||
|
{{! WORLD }}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### json
|
#### json
|
||||||
@@ -194,7 +208,7 @@ MeNav 的助手函数分为四类:
|
|||||||
比较两个值是否相等/不相等:
|
比较两个值是否相等/不相等:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#ifEquals status "active"}}
|
{{#ifEquals status 'active'}}
|
||||||
当前状态:活跃
|
当前状态:活跃
|
||||||
{{else}}
|
{{else}}
|
||||||
当前状态:非活跃
|
当前状态:非活跃
|
||||||
@@ -206,14 +220,17 @@ MeNav 的助手函数分为四类:
|
|||||||
通用条件比较:
|
通用条件比较:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#ifCond count ">" 0}}
|
{{#ifCond count '>' 0}}
|
||||||
有 {{count}} 个项目
|
有
|
||||||
|
{{count}}
|
||||||
|
个项目
|
||||||
{{else}}
|
{{else}}
|
||||||
没有项目
|
没有项目
|
||||||
{{/ifCond}}
|
{{/ifCond}}
|
||||||
```
|
```
|
||||||
|
|
||||||
支持的运算符:
|
支持的运算符:
|
||||||
|
|
||||||
- `==`, `===`, `!=`, `!==`
|
- `==`, `===`, `!=`, `!==`
|
||||||
- `<`, `<=`, `>`, `>=`
|
- `<`, `<=`, `>`, `>=`
|
||||||
- `&&`, `||`
|
- `&&`, `||`
|
||||||
@@ -287,8 +304,10 @@ MeNav 的助手函数分为四类:
|
|||||||
获取数组的第一个/最后一个元素:
|
获取数组的第一个/最后一个元素:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
第一项: {{first items}}
|
第一项:
|
||||||
最后一项: {{last items}}
|
{{first items}}
|
||||||
|
最后一项:
|
||||||
|
{{last items}}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### range
|
#### range
|
||||||
@@ -306,7 +325,7 @@ MeNav 的助手函数分为四类:
|
|||||||
从对象中选择指定的属性:
|
从对象中选择指定的属性:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{json (pick user "name" "email")}}
|
{{json (pick user 'name' 'email')}}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### keys
|
#### keys
|
||||||
@@ -382,7 +401,7 @@ module.exports = {
|
|||||||
toLowerCase,
|
toLowerCase,
|
||||||
toUpperCase,
|
toUpperCase,
|
||||||
json,
|
json,
|
||||||
formatNumber // 添加新函数
|
formatNumber, // 添加新函数
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,8 @@ function faviconV2Url(url, options) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const encodedUrl = encodeURIComponent(String(url));
|
const encodedUrl = encodeURIComponent(String(url));
|
||||||
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32`;
|
// drop_404_icon=true:缺失 favicon 时返回空 404,避免“小地球”占位图并可靠触发 <img onerror> 回退
|
||||||
|
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32&drop_404_icon=true`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -230,7 +231,8 @@ function faviconFallbackUrl(url, options) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const encodedUrl = encodeURIComponent(String(url));
|
const encodedUrl = encodeURIComponent(String(url));
|
||||||
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32`;
|
// drop_404_icon=true:缺失 favicon 时返回空 404,避免“小地球”占位图并可靠触发 <img onerror> 回退
|
||||||
|
return `https://${domain}/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodedUrl}&size=32&drop_404_icon=true`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,12 +240,13 @@ module.exports = function addElement(type, parentId, data) {
|
|||||||
if (urlToUse) {
|
if (urlToUse) {
|
||||||
// 根据 icons.region 配置决定优先使用哪个域名
|
// 根据 icons.region 配置决定优先使用哪个域名
|
||||||
const urls = [];
|
const urls = [];
|
||||||
|
// drop_404_icon=true:缺失 favicon 时返回空 404,避免占位图导致 onerror 不触发,从而可靠走回退逻辑
|
||||||
const comUrl = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(
|
const comUrl = `https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(
|
||||||
urlToUse
|
urlToUse
|
||||||
)}&size=32`;
|
)}&size=32&drop_404_icon=true`;
|
||||||
const cnUrl = `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(
|
const cnUrl = `https://t3.gstatic.cn/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${encodeURIComponent(
|
||||||
urlToUse
|
urlToUse
|
||||||
)}&size=32`;
|
)}&size=32&drop_404_icon=true`;
|
||||||
if (iconsRegion === 'cn') {
|
if (iconsRegion === 'cn') {
|
||||||
urls.push(cnUrl, comUrl);
|
urls.push(cnUrl, comUrl);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -53,9 +53,11 @@ templates/
|
|||||||
**位置**: `templates/layouts/`
|
**位置**: `templates/layouts/`
|
||||||
|
|
||||||
**主要布局**:
|
**主要布局**:
|
||||||
|
|
||||||
- `default.hbs` - 默认布局,定义整个页面框架
|
- `default.hbs` - 默认布局,定义整个页面框架
|
||||||
|
|
||||||
**示例**:
|
**示例**:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
@@ -91,6 +93,7 @@ templates/
|
|||||||
**位置**: `templates/pages/`
|
**位置**: `templates/pages/`
|
||||||
|
|
||||||
**主要页面**:
|
**主要页面**:
|
||||||
|
|
||||||
- `page.hbs` - 通用页面模板(默认/回退模板;普通页面常用)
|
- `page.hbs` - 通用页面模板(默认/回退模板;普通页面常用)
|
||||||
- `bookmarks.hbs` - 书签页
|
- `bookmarks.hbs` - 书签页
|
||||||
- `projects.hbs` - 项目页
|
- `projects.hbs` - 项目页
|
||||||
@@ -102,6 +105,7 @@ templates/
|
|||||||
> “首页/默认打开页”由 `site.yml -> navigation` 的**第一项**决定;首页可使用任意页面模板,具体取决于该页面配置(`pages/<homePageId>.yml` 的 `template` 字段与回退规则)。
|
> “首页/默认打开页”由 `site.yml -> navigation` 的**第一项**决定;首页可使用任意页面模板,具体取决于该页面配置(`pages/<homePageId>.yml` 的 `template` 字段与回退规则)。
|
||||||
|
|
||||||
**示例** (`page.hbs`):
|
**示例** (`page.hbs`):
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<div class="page-template page-template-{{pageId}}">
|
<div class="page-template page-template-{{pageId}}">
|
||||||
{{> page-header}}
|
{{> page-header}}
|
||||||
@@ -120,6 +124,7 @@ templates/
|
|||||||
> 说明:生成器启动时会自动扫描 `templates/components/` 下的所有 `.hbs` 并注册为 Handlebars partial(partial 名称=文件名去掉 `.hbs`)。因此新增组件后无需手动“注册步骤”,可直接通过 `{{> component-name}}` 引用。
|
> 说明:生成器启动时会自动扫描 `templates/components/` 下的所有 `.hbs` 并注册为 Handlebars partial(partial 名称=文件名去掉 `.hbs`)。因此新增组件后无需手动“注册步骤”,可直接通过 `{{> component-name}}` 引用。
|
||||||
|
|
||||||
**主要组件**:
|
**主要组件**:
|
||||||
|
|
||||||
- `page-header.hbs` - 统一页面标题区(首页/非首页/书签更新时间/项目热力图)
|
- `page-header.hbs` - 统一页面标题区(首页/非首页/书签更新时间/项目热力图)
|
||||||
- `navigation.hbs` - 导航菜单
|
- `navigation.hbs` - 导航菜单
|
||||||
- `site-card.hbs` - 站点卡片
|
- `site-card.hbs` - 站点卡片
|
||||||
@@ -128,6 +133,7 @@ templates/
|
|||||||
- `social-links.hbs` - 社交链接
|
- `social-links.hbs` - 社交链接
|
||||||
|
|
||||||
**示例** (`site-card.hbs`,精简展示关键结构):
|
**示例** (`site-card.hbs`,精简展示关键结构):
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#if url}}
|
{{#if url}}
|
||||||
<a href="{{url}}"
|
<a href="{{url}}"
|
||||||
@@ -146,6 +152,7 @@ templates/
|
|||||||
```
|
```
|
||||||
|
|
||||||
说明:
|
说明:
|
||||||
|
|
||||||
- `type=article`:用于 articles Phase 2 的只读文章条目卡片(仍保留 `data-*` 结构;扩展解析应以 `data-type="article"` 区分类型)
|
- `type=article`:用于 articles Phase 2 的只读文章条目卡片(仍保留 `data-*` 结构;扩展解析应以 `data-type="article"` 区分类型)
|
||||||
- `style=repo`:用于 projects 的代码仓库风卡片(展示 language/stars/forks 等只读元信息)
|
- `style=repo`:用于 projects 的代码仓库风卡片(展示 language/stars/forks 等只读元信息)
|
||||||
|
|
||||||
@@ -156,6 +163,7 @@ templates/
|
|||||||
`category.hbs` 是多层级嵌套的核心组件,可渲染 `categories -> subcategories -> groups -> sites` 的结构;更深一层的 `subgroups` 由 `group.hbs` 负责渲染。
|
`category.hbs` 是多层级嵌套的核心组件,可渲染 `categories -> subcategories -> groups -> sites` 的结构;更深一层的 `subgroups` 由 `group.hbs` 负责渲染。
|
||||||
|
|
||||||
**功能特性**:
|
**功能特性**:
|
||||||
|
|
||||||
- 支持 2~4 层嵌套(`categories -> subcategories -> groups -> subgroups -> sites`,其中 `subgroups` 可选)
|
- 支持 2~4 层嵌套(`categories -> subcategories -> groups -> subgroups -> sites`,其中 `subgroups` 可选)
|
||||||
- 自动计算标题层级(h2/h3/h4/h5)
|
- 自动计算标题层级(h2/h3/h4/h5)
|
||||||
- 根据层级自动应用对应的 CSS 类(如 `category-level-2`、`group-level-4`)
|
- 根据层级自动应用对应的 CSS 类(如 `category-level-2`、`group-level-4`)
|
||||||
@@ -163,6 +171,7 @@ templates/
|
|||||||
|
|
||||||
**递归渲染原理**:
|
**递归渲染原理**:
|
||||||
通过在模板内部调用自身实现递归渲染:
|
通过在模板内部调用自身实现递归渲染:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#each subcategories}}
|
{{#each subcategories}}
|
||||||
{{> category level=2}}
|
{{> category level=2}}
|
||||||
@@ -170,12 +179,14 @@ templates/
|
|||||||
```
|
```
|
||||||
|
|
||||||
**level参数的作用**:
|
**level参数的作用**:
|
||||||
|
|
||||||
- 用于跟踪当前嵌套层级
|
- 用于跟踪当前嵌套层级
|
||||||
- 控制标题标签的层级(h{{add level 1}})
|
- 控制标题标签的层级(h{{add level 1}})
|
||||||
- 应用对应的CSS类(category-level-{{level}})
|
- 应用对应的CSS类(category-level-{{level}})
|
||||||
- 传递给子组件以保持层级一致性
|
- 传递给子组件以保持层级一致性
|
||||||
|
|
||||||
**使用示例**:
|
**使用示例**:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!-- 顶级分类 -->
|
<!-- 顶级分类 -->
|
||||||
{{> category category}}
|
{{> category category}}
|
||||||
@@ -189,6 +200,7 @@ templates/
|
|||||||
`group.hbs` 是用于在分类内组织站点的组件,同样支持层级参数。
|
`group.hbs` 是用于在分类内组织站点的组件,同样支持层级参数。
|
||||||
|
|
||||||
**功能特性**:
|
**功能特性**:
|
||||||
|
|
||||||
- 支持在分类内创建站点分组
|
- 支持在分类内创建站点分组
|
||||||
- 支持子分组(`subgroups`,用于第 4 层结构)
|
- 支持子分组(`subgroups`,用于第 4 层结构)
|
||||||
- 自动应用层级样式
|
- 自动应用层级样式
|
||||||
@@ -196,6 +208,7 @@ templates/
|
|||||||
- 与category.hbs保持一致的层级系统
|
- 与category.hbs保持一致的层级系统
|
||||||
|
|
||||||
**使用示例**:
|
**使用示例**:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!-- 在分类模板中使用 -->
|
<!-- 在分类模板中使用 -->
|
||||||
{{#each groups}}
|
{{#each groups}}
|
||||||
@@ -213,27 +226,28 @@ templates/
|
|||||||
```yaml
|
```yaml
|
||||||
# 配置示例
|
# 配置示例
|
||||||
categories:
|
categories:
|
||||||
- name: "技术"
|
- name: '技术'
|
||||||
icon: "fas fa-code"
|
icon: 'fas fa-code'
|
||||||
subcategories:
|
subcategories:
|
||||||
- name: "前端开发"
|
- name: '前端开发'
|
||||||
icon: "fas fa-laptop-code"
|
icon: 'fas fa-laptop-code'
|
||||||
groups:
|
groups:
|
||||||
- name: "框架"
|
- name: '框架'
|
||||||
icon: "fas fa-cubes"
|
icon: 'fas fa-cubes'
|
||||||
subgroups:
|
subgroups:
|
||||||
- name: "React生态"
|
- name: 'React生态'
|
||||||
icon: "fab fa-react"
|
icon: 'fab fa-react'
|
||||||
sites:
|
sites:
|
||||||
- name: "React"
|
- name: 'React'
|
||||||
url: "https://reactjs.org"
|
url: 'https://reactjs.org'
|
||||||
icon: "fab fa-react"
|
icon: 'fab fa-react'
|
||||||
- name: "Next.js"
|
- name: 'Next.js'
|
||||||
url: "https://nextjs.org"
|
url: 'https://nextjs.org'
|
||||||
icon: "fas fa-triangle"
|
icon: 'fas fa-triangle'
|
||||||
```
|
```
|
||||||
|
|
||||||
对应的模板渲染:
|
对应的模板渲染:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!-- category.hbs 会递归渲染 -->
|
<!-- category.hbs 会递归渲染 -->
|
||||||
<section class="category category-level-1" data-level="1">
|
<section class="category category-level-1" data-level="1">
|
||||||
@@ -287,10 +301,12 @@ categories:
|
|||||||
- **层级4 (level=4)**: 子分组,使用h5标题(用于 4 层结构)
|
- **层级4 (level=4)**: 子分组,使用h5标题(用于 4 层结构)
|
||||||
|
|
||||||
每个层级都有对应的CSS类:
|
每个层级都有对应的CSS类:
|
||||||
|
|
||||||
- `category-level-1`, `category-level-2`
|
- `category-level-1`, `category-level-2`
|
||||||
- `group-level-3`, `group-level-4`
|
- `group-level-3`, `group-level-4`
|
||||||
|
|
||||||
这种设计确保了:
|
这种设计确保了:
|
||||||
|
|
||||||
1. 语义化的HTML结构
|
1. 语义化的HTML结构
|
||||||
2. 一致的视觉层级
|
2. 一致的视觉层级
|
||||||
3. 可预测的嵌套深度(当前导入脚本与样式保证到 level=4)
|
3. 可预测的嵌套深度(当前导入脚本与样式保证到 level=4)
|
||||||
@@ -301,6 +317,7 @@ categories:
|
|||||||
当启用 `icons.mode: favicon`(默认)时,站点卡片会优先显示站点 favicon;当 URL 非 http/https、加载失败或网络受限,则自动回退到 Font Awesome 图标。相关助手:`ifHttpUrl`(条件)与 `encodeURIComponent`(工具)。
|
当启用 `icons.mode: favicon`(默认)时,站点卡片会优先显示站点 favicon;当 URL 非 http/https、加载失败或网络受限,则自动回退到 Font Awesome 图标。相关助手:`ifHttpUrl`(条件)与 `encodeURIComponent`(工具)。
|
||||||
|
|
||||||
站点级覆盖(可选,写在每个 `sites[]` 节点上):
|
站点级覆盖(可选,写在每个 `sites[]` 节点上):
|
||||||
|
|
||||||
- `faviconUrl`:为单站点指定图标链接(优先级最高,失败回退到手动图标;本地路径建议以 `assets/` 开头,构建会复制到 `dist/` 同路径)
|
- `faviconUrl`:为单站点指定图标链接(优先级最高,失败回退到手动图标;本地路径建议以 `assets/` 开头,构建会复制到 `dist/` 同路径)
|
||||||
- `forceIconMode: favicon | manual`:强制该站点使用指定模式(不设置则跟随全局 `icons.mode`)
|
- `forceIconMode: favicon | manual`:强制该站点使用指定模式(不设置则跟随全局 `icons.mode`)
|
||||||
- 优先级:`faviconUrl` > `forceIconMode` > 全局 `icons.mode`
|
- 优先级:`faviconUrl` > `forceIconMode` > 全局 `icons.mode`
|
||||||
@@ -324,7 +341,7 @@ categories:
|
|||||||
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
|
<i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
|
||||||
<img
|
<img
|
||||||
class="favicon-icon"
|
class="favicon-icon"
|
||||||
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32"
|
src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url={{encodeURIComponent url}}&size=32&drop_404_icon=true"
|
||||||
alt="{{name}} favicon"
|
alt="{{name}} favicon"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
|
onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
|
||||||
@@ -345,6 +362,7 @@ categories:
|
|||||||
```
|
```
|
||||||
|
|
||||||
提示:关于 `icons.mode` 的配置与隐私说明,请参见:
|
提示:关于 `icons.mode` 的配置与隐私说明,请参见:
|
||||||
|
|
||||||
- `config/README.md` 的 `site.yml 常用字段`:[`../config/README.md#siteyml-常用字段`](../config/README.md#siteyml-%E5%B8%B8%E7%94%A8%E5%AD%97%E6%AE%B5)
|
- `config/README.md` 的 `site.yml 常用字段`:[`../config/README.md#siteyml-常用字段`](../config/README.md#siteyml-%E5%B8%B8%E7%94%A8%E5%AD%97%E6%AE%B5)
|
||||||
- 根目录 `README.md` 的“近期更新”:[`../README.md#近期更新`](../README.md#%E8%BF%91%E6%9C%9F%E6%9B%B4%E6%96%B0)
|
- 根目录 `README.md` 的“近期更新”:[`../README.md#近期更新`](../README.md#%E8%BF%91%E6%9C%9F%E6%9B%B4%E6%96%B0)
|
||||||
|
|
||||||
@@ -359,6 +377,7 @@ MeNav 模板系统的数据流如下:
|
|||||||
5. 组件模板 (`components/*.hbs`) 在页面中通过 `{{> component-name}}` 引用
|
5. 组件模板 (`components/*.hbs`) 在页面中通过 `{{> component-name}}` 引用
|
||||||
|
|
||||||
主要数据对象:
|
主要数据对象:
|
||||||
|
|
||||||
- `site` - 网站配置信息
|
- `site` - 网站配置信息
|
||||||
- `navigationData` - 导航菜单数据
|
- `navigationData` - 导航菜单数据
|
||||||
- `categories` - 分类和站点数据
|
- `categories` - 分类和站点数据
|
||||||
@@ -366,6 +385,7 @@ MeNav 模板系统的数据流如下:
|
|||||||
- `social` - 社交链接数据
|
- `social` - 社交链接数据
|
||||||
|
|
||||||
常见派生字段(由生成器注入,供模板差异化使用):
|
常见派生字段(由生成器注入,供模板差异化使用):
|
||||||
|
|
||||||
- `homePageId`:首页页面 ID(始终等于 `navigation` 第一项的 `id`)
|
- `homePageId`:首页页面 ID(始终等于 `navigation` 第一项的 `id`)
|
||||||
- `pageId`:当前页面 ID(用于 `.page-template-{{pageId}}` 等)
|
- `pageId`:当前页面 ID(用于 `.page-template-{{pageId}}` 等)
|
||||||
- `pageMeta.updatedAt/updatedAtSource`:仅 bookmarks 模板页用于“update: YYYY-MM-DD | from: ...”展示
|
- `pageMeta.updatedAt/updatedAtSource`:仅 bookmarks 模板页用于“update: YYYY-MM-DD | from: ...”展示
|
||||||
@@ -377,26 +397,29 @@ MeNav 模板系统的数据流如下:
|
|||||||
## 模板使用示例
|
## 模板使用示例
|
||||||
|
|
||||||
### 布局模板使用
|
### 布局模板使用
|
||||||
|
|
||||||
布局模板通常只有一个 `default.hbs`,会自动被系统使用。
|
布局模板通常只有一个 `default.hbs`,会自动被系统使用。
|
||||||
|
|
||||||
### 页面模板使用
|
### 页面模板使用
|
||||||
|
|
||||||
页面模板对应导航中的各个页面,有两种使用方式:
|
页面模板对应导航中的各个页面,有两种使用方式:
|
||||||
|
|
||||||
1. **自动匹配**:系统会尝试使用与页面ID同名的模板(例如:页面ID为 `projects` 时会使用 `projects.hbs`)
|
1. **自动匹配**:系统会尝试使用与页面ID同名的模板(例如:页面ID为 `projects` 时会使用 `projects.hbs`)
|
||||||
2. **显式指定**:在页面配置中使用 `template` 字段指定要使用的模板
|
2. **显式指定**:在页面配置中使用 `template` 字段指定要使用的模板
|
||||||
|
|
||||||
#### 模板指定示例
|
#### 模板指定示例
|
||||||
|
|
||||||
在 `config/user/pages/项目.yml` 中:
|
在 `config/user/pages/项目.yml` 中:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
title: "我的项目"
|
title: '我的项目'
|
||||||
subtitle: "这里展示我的所有项目"
|
subtitle: '这里展示我的所有项目'
|
||||||
template: "projects" # 使用 projects.hbs 模板而不是使用页面ID命名的模板
|
template: 'projects' # 使用 projects.hbs 模板而不是使用页面ID命名的模板
|
||||||
categories:
|
categories:
|
||||||
- name: "网站项目"
|
- name: '网站项目'
|
||||||
icon: "fas fa-globe"
|
icon: 'fas fa-globe'
|
||||||
sites:
|
sites:
|
||||||
- name: "个人博客"
|
- name: '个人博客'
|
||||||
# ... 其他字段
|
# ... 其他字段
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -467,14 +490,15 @@ categories:
|
|||||||
3. 页面内容可引用现有组件或创建新组件
|
3. 页面内容可引用现有组件或创建新组件
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!-- templates/pages/about.hbs -->
|
<!-- templates/pages/about.hbs -->
|
||||||
<div class="about-page">
|
<div class='about-page'>
|
||||||
<h2>关于我</h2>
|
<h2>关于我</h2>
|
||||||
<p>{{about.description}}</p>
|
<p>{{about.description}}</p>
|
||||||
|
|
||||||
{{#if about.skills}}
|
{{#if about.skills}}
|
||||||
<div class="skills">
|
<div class='skills'>
|
||||||
<h3>技能</h3>
|
<h3>技能</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{{#each about.skills}}
|
{{#each about.skills}}
|
||||||
@@ -492,17 +516,19 @@ categories:
|
|||||||
2. 在页面或其他组件中引用
|
2. 在页面或其他组件中引用
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
<!-- templates/components/skill-card.hbs -->
|
<!-- templates/components/skill-card.hbs -->
|
||||||
<div class="skill-card">
|
<div class='skill-card'>
|
||||||
<h4>{{name}}</h4>
|
<h4>{{name}}</h4>
|
||||||
<div class="skill-level" data-level="{{level}}">
|
<div class='skill-level' data-level='{{level}}'>
|
||||||
<div class="skill-bar" style="width: {{level}}%"></div>
|
<div class='skill-bar' style='width: {{level}}%'></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
```
|
```
|
||||||
|
|
||||||
使用新组件:
|
使用新组件:
|
||||||
|
|
||||||
```handlebars
|
```handlebars
|
||||||
{{#each skills}}
|
{{#each skills}}
|
||||||
{{> skill-card}}
|
{{> skill-card}}
|
||||||
|
|||||||
32
test/favicon-v2-drop-404-icon.node-test.js
Normal file
32
test/favicon-v2-drop-404-icon.node-test.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const { faviconV2Url, faviconFallbackUrl } = require('../src/helpers/utils');
|
||||||
|
|
||||||
|
test('faviconV2:应追加 drop_404_icon=true 以避免返回占位图', () => {
|
||||||
|
const optionsCom = { data: { root: { icons: { region: 'com' } } } };
|
||||||
|
const optionsCn = { data: { root: { icons: { region: 'cn' } } } };
|
||||||
|
|
||||||
|
const url = 'https://example.com';
|
||||||
|
|
||||||
|
const com = faviconV2Url(url, optionsCom);
|
||||||
|
const cn = faviconV2Url(url, optionsCn);
|
||||||
|
const fallbackCom = faviconFallbackUrl(url, optionsCom);
|
||||||
|
const fallbackCn = faviconFallbackUrl(url, optionsCn);
|
||||||
|
|
||||||
|
for (const out of [com, cn, fallbackCom, fallbackCn]) {
|
||||||
|
assert.ok(out.includes('drop_404_icon=true'), '生成的 URL 应包含 drop_404_icon=true');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('运行时新增站点:faviconV2 URL 也应包含 drop_404_icon=true', () => {
|
||||||
|
const repoRoot = path.resolve(__dirname, '..');
|
||||||
|
const runtimePath = path.join(repoRoot, 'src', 'runtime', 'menav', 'addElement.js');
|
||||||
|
const content = fs.readFileSync(runtimePath, 'utf8');
|
||||||
|
assert.ok(
|
||||||
|
content.includes('drop_404_icon=true'),
|
||||||
|
'src/runtime/menav/addElement.js 应追加 drop_404_icon=true'
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user