min

MeNav 模板目录

目录

模板系统概述

MeNav 项目使用 Handlebars 作为模板引擎,实现了组件化架构,将页面内容与逻辑分离。模板系统的核心优势:

目录结构

templates/
├── layouts/      # 布局模板 - 定义页面整体结构
│   └── default.hbs   # 默认布局
├── pages/        # 页面模板 - 对应不同页面内容
│   ├── page.hbs      # 通用页面模板(默认/回退模板;普通页面常用)
│   ├── content.hbs    # 内容页(Markdown 内容页:构建期渲染)
│   ├── projects.hbs  # 项目页(repo 风格卡片)
│   ├── articles.hbs  # 文章页(RSS 聚合/只读文章条目)
│   ├── bookmarks.hbs # 书签页
│   └── search-results.hbs # 搜索结果页(内置)
├── components/   # 组件模板 - 可复用的界面元素
│   ├── page-header.hbs  # 统一标题区(首页/非首页/书签更新时间/项目热力图)
│   ├── navigation.hbs   # 导航组件
│   ├── site-card.hbs    # 站点卡片组件
│   ├── category.hbs     # 分类组件
│   └── ...
└── README.md     # 本文档

模板类型

布局模板

布局模板定义了整个页面的HTML结构,包含头部、导航栏、内容区和底部等基本框架。

位置: templates/layouts/

主要布局:

示例:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>min</title>
    <!-- 其他头部元素 -->
</head>
<body>
    <div class="layout">
        <!-- 导航部分 -->
        <nav class="sidebar">
            
        </nav>

        <!-- 内容区域 -->
        <main class="content">
            
            <div class="page  active" id="">
                }
            </div>
            
        </main>
    </div>
</body>
</html>

页面模板

页面模板对应网站的不同页面,每个页面模板通常包含多个组件组合。

位置: templates/pages/

主要页面:

说明:MeNav 不再依赖 home.hbs 作为首页模板。 “首页/默认打开页”由 site.yml -> navigation第一项决定;首页可使用任意页面模板,具体取决于该页面配置(pages/<homePageId>.ymltemplate 字段与回退规则)。

内容页(template: content)

内容页用于承载“关于 / 帮助 / 使用说明 / 更新日志 / 迁移指南”等纯文本内容。

对应模板文件:templates/pages/content.hbs

配置写法示例见:config/README.md 的“内容页(template: content)”。

示例 (page.hbs):

<div class="page-template page-template-">
    
    
        
    
</div>

组件模板

组件是可复用的UI元素,用于在不同页面中重复使用。

位置: templates/components/

说明:生成器启动时会自动扫描 templates/components/ 下的所有 .hbs 并注册为 Handlebars partial(partial 名称=文件名去掉 .hbs)。因此新增组件后无需手动“注册步骤”,可直接通过 `` 引用。

主要组件:

示例 (site-card.hbs,精简展示关键结构):


<a href=""
   class="site-card site-card-"
   target="_blank" rel="noopener"
   data-type="site"
   data-name=""
   data-url="">
    <div class="site-card-icon">...</div>
    <div class="site-card-content">
        <h3>未命名站点</h3>
        <p></p>
    </div>
</a>

说明:

多层级嵌套模板组件

category.hbs - 分类容器组件

category.hbs 是多层级嵌套的核心组件,可渲染 categories -> subcategories -> groups -> sites 的结构;更深一层的 subgroupsgroup.hbs 负责渲染。

功能特性:

递归渲染原理: 通过在模板内部调用自身实现递归渲染:


    

level参数的作用:

使用示例:

<!-- 顶级分类 -->


<!-- 指定层级的分类 -->

group.hbs - 分组容器组件

group.hbs 是用于在分类内组织站点的组件,同样支持层级参数。

功能特性:

使用示例:

<!-- 在分类模板中使用 -->

    


<!-- 指定层级的分组 -->

多层级嵌套结构示例

典型的(最多 4 层)结构:分类 → 子分类 → 分组 → 子分组 → 站点(subgroups 可选)

# 配置示例
categories:
  - name: '技术'
    icon: 'fas fa-code'
    subcategories:
      - name: '前端开发'
        icon: 'fas fa-laptop-code'
        groups:
          - name: '框架'
            icon: 'fas fa-cubes'
            subgroups:
              - name: 'React生态'
                icon: 'fab fa-react'
                sites:
                  - name: 'React'
                    url: 'https://reactjs.org'
                    icon: 'fab fa-react'
                  - name: 'Next.js'
                    url: 'https://nextjs.org'
                    icon: 'fas fa-triangle'

对应的模板渲染:

<!-- category.hbs 会递归渲染 -->
<section class="category category-level-1" data-level="1">
  <div class="category-header">
    <h2><i class="fas fa-code"></i> 技术</h2>
  </div>
  <div class="category-content">
    <div class="subcategories-container">
      <!-- 递归调用 category.hbs,level=2 -->
      <section class="category category-level-2" data-level="2">
        <div class="category-header">
          <h3><i class="fas fa-laptop-code"></i> 前端开发</h3>
        </div>
	        <div class="category-content">
	          <div class="groups-container">
	            <!-- 使用 group.hbs,默认 level=3 -->
	            <div class="group group-level-3" data-level="3">
	              <div class="group-header">
	                <h4><i class="fas fa-cubes"></i> 框架</h4>
	              </div>
	              <div class="group-content">
	                <div class="subgroups-container">
	                  <!-- group.hbs 渲染 subgroups,level=4 -->
	                  <div class="group group-level-4" data-level="4">
	                    <div class="group-header">
	                      <h5><i class="fab fa-react"></i> React生态</h5>
	                    </div>
	                    <div class="group-content">
	                      <div class="sites-grid">
	                        <!-- 渲染站点卡片 -->
	                        
	                      </div>
	                    </div>
	                  </div>
	                </div>
	              </div>
	            </div>
	          </div>
	        </div>
      </section>
    </div>
  </div>
</section>

层级系统说明

每个层级都有对应的CSS类:

这种设计确保了:

  1. 语义化的HTML结构
  2. 一致的视觉层级
  3. 可预测的嵌套深度(当前导入脚本与样式保证到 level=4)
  4. 灵活的样式定制

站点图标渲染(favicon/manual)

当启用 icons.mode: favicon(默认)时,站点卡片会优先显示站点 favicon;当 URL 非 http/https、加载失败或网络受限,则自动回退到 Font Awesome 图标。相关助手:ifHttpUrl(条件)与 encodeURIComponent(工具)。

站点级覆盖(可选,写在每个 sites[] 节点上):

注意:用于根据站点 URL 生成 faviconV2 地址的模板 helper 已更名为 faviconV2Url,从而避免与站点字段 faviconUrl 同名冲突;自定义模板如需生成 faviconV2 地址,请使用 。如需强制读取站点字段 `faviconUrl`,也可使用(推荐在复杂上下文中显式读取字段)。

示例(与内置组件实现保持一致):


<a href="" class="site-card site-card-"
   target="_blank" rel="noopener"
   data-type="site"
   data-name=""
   data-url=""
   data-icon="fas fa-link"
   data-description="">
    
      
        <div class="icon-container">
          <i class="fas fa-circle-notch fa-spin icon-placeholder" aria-hidden="true"></i>
          <img
            class="favicon-icon"
            src="https://t3.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=&size=32&drop_404_icon=true"
            alt=" favicon"
            loading="lazy"
            onload="this.classList.add('loaded'); this.previousElementSibling.classList.add('hidden');"
            onerror="this.classList.add('error'); this.previousElementSibling.classList.add('hidden'); this.nextElementSibling.classList.add('visible');"
          />
          <i class="fas fa-link icon-fallback" aria-hidden="true"></i>
        </div>
      
        <i class="fas fa-link"></i>
      
    
      <i class="fas fa-link"></i>
    
    <h3>未命名站点</h3>
    <p></p>
</a>

提示:关于 icons.mode 的配置与隐私说明,请参见:

模板数据流

MeNav 模板系统的数据流如下:

  1. generator.js 加载配置文件并处理数据
  2. 数据通过 Handlebars 上下文传递给模板
  3. 布局模板 (layouts/default.hbs) 作为外层容器
  4. 页面模板 (pages/*.hbs) 填充布局中的内容区域
  5. 组件模板 (components/*.hbs) 在页面中通过 `` 引用

主要数据对象:

常见派生字段(由生成器注入,供模板差异化使用):

提示:页面模板是“页面内容片段”,不要包含 <!DOCTYPE html> 等整页骨架;整页骨架由 layouts/default.hbs 负责。

模板使用示例

布局模板使用

布局模板通常只有一个 default.hbs,会自动被系统使用。

页面模板使用

页面模板对应导航中的各个页面,有两种使用方式:

  1. 自动匹配:系统会尝试使用与页面ID同名的模板(例如:页面ID为 projects 时会使用 projects.hbs
  2. 显式指定:在页面配置中使用 template 字段指定要使用的模板

模板指定示例

config/user/pages/项目.yml 中:

title: '我的项目'
subtitle: '这里展示我的所有项目'
template: 'projects' # 使用 projects.hbs 模板而不是使用页面ID命名的模板
categories:
  - name: '网站项目'
    icon: 'fas fa-globe'
    sites:
      - name: '个人博客'
        # ... 其他字段

注意:当系统找不到指定的模板或与页面ID匹配的模板时,会自动使用通用模板 page.hbs

引用组件

在页面或其他组件中引用组件:



条件渲染

根据条件显示内容:


  <h2></h2>

  <h2>欢迎使用</h2>

循环渲染

循环渲染数据列表:


    <section class="category" id="">
        <h2><i class=""></i> </h2>
        <div class="sites-grid">
            
                
            
        </div>
    </section>

最佳实践

  1. 组件粒度 - 保持组件的适当粒度,既不过大也不过小
    • 过大:难以复用和维护
    • 过小:增加复杂性和引用管理难度
  2. 数据传递 - 使用合适的方式传递数据
    • 直接上下文:`` (继承父上下文)
    • 指定数据:`` (传递特定数据)
  3. 命名规范
    • 使用连字符命名:site-card.hbssearch-results.hbs
    • 使用描述性名称,体现组件用途
  4. 注释
    • 对复杂逻辑添加注释说明
    • 标注可选参数和默认行为

扩展指南

添加新页面

  1. templates/pages/ 创建新的 .hbs 文件
  2. config/user/site.ymlnavigation 部分添加页面配置(配置采用“完全替换”策略,推荐使用 user 配置)
  3. 页面内容可引用现有组件或创建新组件

示例:

<!-- templates/pages/about.hbs -->
<div class='about-page'>
  <h2>关于我</h2>
  <p></p>

  
    <div class='skills'>
      <h3>技能</h3>
      <ul>
        
          <li></li>
        
      </ul>
    </div>
  
</div>

添加新组件

  1. templates/components/ 创建新的 .hbs 文件
  2. 在页面或其他组件中引用

示例:

<!-- templates/components/skill-card.hbs -->
<div class='skill-card'>
  <h4></h4>
  <div class='skill-level' data-level=''>
    <div class='skill-bar' style='width: %'></div>
  </div>
</div>

使用新组件: