fix: 补齐书签 subgroups 渲染并对齐 README
- 修复 subgroups 生成但不展示的问题(模板渲染 + 前端结构导出) - 更新 README:配置示例/完全替换策略/多层级说明与 helper 文档,减少重复说明
This commit is contained in:
15
README.md
15
README.md
@@ -16,7 +16,7 @@
|
||||
|
||||
📋 静态一键部署 | ⚡ 自动化构建 | 🔖 支持书签导入
|
||||
|
||||
> MeNav是一个轻量级、高度可定制的个人导航网站生成器,让您轻松创建属于自己的导航主页。无需数据库和后端服务,完全静态部署,支持一键Fork部署到GitHub Pages,还可以从浏览器书签一键导入网站。配合 [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展,更支持书签自动同步和导航站自动更新。MeNav is a lightweight, highly customizable personal navigation website generator. One-click deployment to GitHub Pages, automated build, bookmark import support, and more.
|
||||
> MeNav 是一个轻量级、高度可定制的个人导航网站生成器,让您轻松创建属于自己的导航主页。无需数据库和后端服务,完全静态部署,支持一键 Fork 部署到 GitHub Pages,还可以从浏览器书签一键导入网站。配合 [MarksVault](https://github.com/rbetree/MarksVault) 浏览器扩展,更支持书签自动同步和导航站自动更新。
|
||||
|
||||
如果觉得项目有用,欢迎⭐Star/Fork支持,谢谢!
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
## 目录
|
||||
|
||||
- [预览](#预览--preview)
|
||||
- [预览](#预览)
|
||||
- [功能特点](#功能特点)
|
||||
- [近期更新](#近期更新)
|
||||
- [技术栈](#技术栈)
|
||||
@@ -50,7 +50,7 @@
|
||||
- [配置指南](#设置配置文件)
|
||||
- [书签导入](#书签导入功能)
|
||||
- [常见问题](#常见问题)
|
||||
- [Star-History](#Star-History)
|
||||
- [Star-History](#star-history)
|
||||
|
||||
## 快速预览
|
||||
|
||||
@@ -460,14 +460,7 @@ MeNav 支持从浏览器导入书签,快速批量添加网站链接;也支
|
||||
|
||||
<details>
|
||||
<summary>如何使用MarksVault扩展自动同步书签?</summary>
|
||||
MarksVault浏览器扩展与MeNav的集成相当简单:
|
||||
|
||||
1. 首先,从[GitHub仓库](https://github.com/rbetree/MarksVault)下载并安装MarksVault扩展
|
||||
2. 打开扩展,进入同步设置:
|
||||
- 设置GitHub令牌(需要有对目标仓库的写入权限)
|
||||
- 配置目标仓库:填写您的用户名和fork的MeNav仓库名
|
||||
- 确认bookmarks文件夹路径(默认即可)
|
||||
3. 使用扩展的任务功能,自动推送书签到项目
|
||||
MarksVault 扩展集成的完整说明请见:[`bookmarks/README.md`](bookmarks/README.md) 的 “MarksVault 扩展集成” 章节。
|
||||
</details>
|
||||
|
||||
## Star-History
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
|
||||
## 配置加载优先级(完全替换)
|
||||
|
||||
书签页配置遵循项目的“完全替换”策略:系统只会使用找到的最高优先级配置,不会把默认配置与用户配置合并。
|
||||
书签页配置同样遵循项目的“完全替换”策略:系统只会选择一套配置目录加载,不会把 `user` 与 `_default` 混合合并。
|
||||
|
||||
优先级(高 → 低):
|
||||
- 若存在 `config/user/`:书签页配置应位于 `config/user/pages/bookmarks.yml`(通常由导入脚本生成)
|
||||
- 否则:使用 `config/_default/pages/bookmarks.yml`(默认示例)
|
||||
|
||||
1. `config/user/pages/bookmarks.yml`(用户配置,通常由导入脚本生成)
|
||||
2. `config/_default/pages/bookmarks.yml`(默认配置)
|
||||
> 提示:一旦创建 `config/user/`,`config/_default/` 会被完全忽略,因此不要指望从默认配置“兜底补齐缺失项”。
|
||||
|
||||
## MarksVault 扩展集成
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
```bash
|
||||
npm run import-bookmarks
|
||||
```
|
||||
- 若 `config/user/` 不存在,导入脚本会先从 `config/_default/` 初始化一份用户配置(因为配置采用“完全替换”策略,需要完整配置才能正常生成站点)。
|
||||
(可选)若希望生成结果保持确定性(便于版本管理,减少时间戳导致的无意义 diff):
|
||||
```bash
|
||||
MENAV_BOOKMARKS_DETERMINISTIC=1 npm run import-bookmarks
|
||||
|
||||
@@ -59,7 +59,7 @@ MeNav 配置系统采用“完全替换”策略(不合并),按以下优
|
||||
2. **字段与结构的权威参考**:
|
||||
- 全局配置:[`_default/site.yml`](_default/site.yml)
|
||||
- 页面配置:[`_default/pages/`](_default/pages/)
|
||||
3. **多层级嵌套书签示例**:[`_default/pages/bookmarks-four-level.yml`](_default/pages/bookmarks-four-level.yml)(2~4 层结构均有覆盖)
|
||||
3. **多层级嵌套书签示例**:[`_default/pages/bookmarks-four-level.yml`](_default/pages/bookmarks-four-level.yml)(示例展示到 `groups`;`subgroups` 可参考下方说明或由导入脚本生成)
|
||||
|
||||
## 模块化配置文件
|
||||
|
||||
@@ -69,7 +69,7 @@ MeNav 配置系统采用“完全替换”策略(不合并),按以下优
|
||||
|
||||
- 网站标题、描述和关键词
|
||||
- 作者信息和版权声明
|
||||
- 字体配置和主题设置
|
||||
- 字体配置、图标模式等全局设置
|
||||
- 全局元数据和站点参数
|
||||
- 个人资料和社交媒体链接
|
||||
- 导航菜单配置(侧边栏导航项、页面标题和图标、页面顺序和可见性)
|
||||
@@ -151,6 +151,22 @@ MeNav 配置系统采用“完全替换”策略(不合并),按以下优
|
||||
4. `subgroups`:子分组
|
||||
5. `sites`:站点(叶子节点)
|
||||
|
||||
若你需要第 4 层(`subgroups`),结构示例(片段):
|
||||
|
||||
```yaml
|
||||
categories:
|
||||
- name: 示例分类
|
||||
subcategories:
|
||||
- name: 示例子分类
|
||||
groups:
|
||||
- name: 示例分组
|
||||
subgroups:
|
||||
- name: 示例子分组
|
||||
sites:
|
||||
- name: 示例站点
|
||||
url: https://example.com
|
||||
```
|
||||
|
||||
#### 向后兼容性
|
||||
|
||||
- 原有二层结构(`categories -> sites`)无需修改即可继续使用
|
||||
@@ -159,12 +175,16 @@ MeNav 配置系统采用“完全替换”策略(不合并),按以下优
|
||||
|
||||
## 配置优先级
|
||||
|
||||
配置项的优先级从高到低为:
|
||||
MeNav 配置系统采用“完全替换”策略:只会选择一套目录加载,不会把 `user` 与 `_default` 混合合并。
|
||||
|
||||
1. 用户页面配置 (`user/pages/*.yml`)
|
||||
2. 用户网站配置 (`user/site.yml`)
|
||||
3. 默认页面配置 (`_default/pages/*.yml`)
|
||||
4. 默认网站配置 (`_default/site.yml`)
|
||||
- 若存在 `config/user/`:只加载 `config/user/`,并**完全忽略** `config/_default/`
|
||||
- 否则:加载 `config/_default/`
|
||||
|
||||
在“同一套目录”内,各文件的关系是:
|
||||
|
||||
- `site.yml`:站点全局配置(包含 `navigation` 等)
|
||||
- `pages/*.yml`:各页面配置(文件名需与 `navigation.id` 对应)
|
||||
- `navigation.yml`:仅在 `site.yml` 未提供 `navigation` 时回退使用(兼容旧版本;推荐迁移到 `site.yml`)
|
||||
|
||||
## 配置示例
|
||||
|
||||
@@ -182,15 +202,20 @@ profile:
|
||||
subtitle: "我收藏的精选网站"
|
||||
description: "这是一个用于快速访问常用网站的个人导航页面。"
|
||||
|
||||
# 主题和样式设置
|
||||
theme:
|
||||
default: "light"
|
||||
toggleIcon: true
|
||||
|
||||
# 字体配置
|
||||
fonts:
|
||||
title: "Roboto, sans-serif"
|
||||
content: "Noto Sans SC, sans-serif"
|
||||
title:
|
||||
family: Roboto
|
||||
weight: 600
|
||||
source: google
|
||||
subtitle:
|
||||
family: Noto Sans SC
|
||||
weight: 500
|
||||
source: google
|
||||
body:
|
||||
family: Noto Sans SC
|
||||
weight: 400
|
||||
source: google
|
||||
|
||||
# 社交媒体链接
|
||||
social:
|
||||
|
||||
@@ -179,6 +179,14 @@ MeNav 的助手函数分为四类:
|
||||
{{json this}}
|
||||
```
|
||||
|
||||
#### extractDomain
|
||||
|
||||
从 URL 中提取“干净的域名”(不包含协议、路径与查询串),常用于站点描述兜底显示:
|
||||
|
||||
```handlebars
|
||||
{{extractDomain url}}
|
||||
```
|
||||
|
||||
### 条件判断函数
|
||||
|
||||
#### ifEquals / ifNotEquals
|
||||
@@ -311,6 +319,22 @@ MeNav 的助手函数分为四类:
|
||||
{{/each}}
|
||||
```
|
||||
|
||||
#### encodeURIComponent
|
||||
|
||||
对字符串做 URL 组件编码,常用于拼接第三方请求参数(例如 favicon 的 `url=` 参数):
|
||||
|
||||
```handlebars
|
||||
{{encodeURIComponent url}}
|
||||
```
|
||||
|
||||
#### add
|
||||
|
||||
数字加法,用于根据层级动态计算标题级别等场景:
|
||||
|
||||
```handlebars
|
||||
<h{{add level 1}}>...</h{{add level 1}}>
|
||||
```
|
||||
|
||||
### 核心函数
|
||||
|
||||
#### escapeHtml
|
||||
|
||||
@@ -398,13 +398,13 @@ function updateCategoryToggleIcon(state) {
|
||||
}
|
||||
}
|
||||
|
||||
window.MeNav.toggleCategory = function(categoryName, subcategoryName = null, groupName = null) {
|
||||
const selector = groupName
|
||||
? `[data-name="${categoryName}"] [data-name="${subcategoryName}"] [data-name="${groupName}"]`
|
||||
: subcategoryName
|
||||
? `[data-name="${categoryName}"] [data-name="${subcategoryName}"]`
|
||||
: `[data-name="${categoryName}"]`;
|
||||
|
||||
window.MeNav.toggleCategory = function(categoryName, subcategoryName = null, groupName = null, subgroupName = null) {
|
||||
let selector = `[data-name="${categoryName}"]`;
|
||||
|
||||
if (subcategoryName) selector += ` [data-name="${subcategoryName}"]`;
|
||||
if (groupName) selector += ` [data-name="${groupName}"]`;
|
||||
if (subgroupName) selector += ` [data-name="${subgroupName}"]`;
|
||||
|
||||
const element = document.querySelector(selector);
|
||||
if (element) {
|
||||
toggleNestedElement(element);
|
||||
@@ -500,6 +500,11 @@ function extractNestedData(element) {
|
||||
if (groups.length > 0) {
|
||||
data.groups = Array.from(groups).map(group => extractNestedData(group));
|
||||
}
|
||||
|
||||
const subgroups = element.querySelectorAll(':scope > .group-content > .subgroups-container > .group');
|
||||
if (subgroups.length > 0) {
|
||||
data.subgroups = Array.from(subgroups).map(subgroup => extractNestedData(subgroup));
|
||||
}
|
||||
|
||||
const sites = element.querySelectorAll(':scope > .category-content > .sites-grid > .site-card, :scope > .group-content > .sites-grid > .site-card');
|
||||
if (sites.length > 0) {
|
||||
|
||||
@@ -134,13 +134,13 @@ templates/
|
||||
|
||||
#### category.hbs - 分类容器组件
|
||||
|
||||
`category.hbs` 是支持多层级嵌套的核心组件,可以递归渲染分类和子分类结构。
|
||||
`category.hbs` 是多层级嵌套的核心组件,可渲染 `categories -> subcategories -> groups -> sites` 的结构;更深一层的 `subgroups` 由 `group.hbs` 负责渲染。
|
||||
|
||||
**功能特性**:
|
||||
- 支持无限层级的分类嵌套
|
||||
- 自动计算标题层级(h2, h3, h4...)
|
||||
- 根据层级自动应用对应的CSS类
|
||||
- 支持三种内容类型:子分类、分组、站点
|
||||
- 支持 2~4 层嵌套(`categories -> subcategories -> groups -> subgroups -> sites`,其中 `subgroups` 可选)
|
||||
- 自动计算标题层级(h2/h3/h4/h5)
|
||||
- 根据层级自动应用对应的 CSS 类(如 `category-level-2`、`group-level-4`)
|
||||
- 分类容器支持三种内容:子分类、分组、站点(分组内可继续包含子分组)
|
||||
|
||||
**递归渲染原理**:
|
||||
通过在模板内部调用自身实现递归渲染:
|
||||
@@ -171,6 +171,7 @@ templates/
|
||||
|
||||
**功能特性**:
|
||||
- 支持在分类内创建站点分组
|
||||
- 支持子分组(`subgroups`,用于第 4 层结构)
|
||||
- 自动应用层级样式
|
||||
- 支持展开/折叠功能
|
||||
- 与category.hbs保持一致的层级系统
|
||||
@@ -188,7 +189,7 @@ templates/
|
||||
|
||||
#### 多层级嵌套结构示例
|
||||
|
||||
典型的四层级结构:分类 → 子分类 → 分组 → 站点
|
||||
典型的(最多 4 层)结构:分类 → 子分类 → 分组 → 子分组 → 站点(`subgroups` 可选)
|
||||
|
||||
```yaml
|
||||
# 配置示例
|
||||
@@ -201,13 +202,16 @@ categories:
|
||||
groups:
|
||||
- name: "框架"
|
||||
icon: "fas fa-cubes"
|
||||
sites:
|
||||
- name: "React"
|
||||
url: "https://reactjs.org"
|
||||
subgroups:
|
||||
- name: "React生态"
|
||||
icon: "fab fa-react"
|
||||
- name: "Vue"
|
||||
url: "https://vuejs.org"
|
||||
icon: "fab fa-vuejs"
|
||||
sites:
|
||||
- name: "React"
|
||||
url: "https://reactjs.org"
|
||||
icon: "fab fa-react"
|
||||
- name: "Next.js"
|
||||
url: "https://nextjs.org"
|
||||
icon: "fas fa-triangle"
|
||||
```
|
||||
|
||||
对应的模板渲染:
|
||||
@@ -224,22 +228,32 @@ categories:
|
||||
<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="sites-grid">
|
||||
<!-- 渲染站点卡片 -->
|
||||
{{> site-card site}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<!-- 渲染站点卡片 -->
|
||||
{{> site-card site}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,16 +265,16 @@ categories:
|
||||
- **层级1 (level=1)**: 顶级分类,使用h2标题
|
||||
- **层级2 (level=2)**: 子分类,使用h3标题
|
||||
- **层级3 (level=3)**: 分组,使用h4标题
|
||||
- **层级4+**: 更深层级,继续递增标题层级
|
||||
- **层级4 (level=4)**: 子分组,使用h5标题(用于 4 层结构)
|
||||
|
||||
每个层级都有对应的CSS类:
|
||||
- `category-level-1`, `category-level-2`, ...
|
||||
- `group-level-1`, `group-level-2`, ...
|
||||
- `category-level-1`, `category-level-2`
|
||||
- `group-level-3`, `group-level-4`
|
||||
|
||||
这种设计确保了:
|
||||
1. 语义化的HTML结构
|
||||
2. 一致的视觉层级
|
||||
3. 可扩展的嵌套深度
|
||||
3. 可预测的嵌套深度(当前导入脚本与样式保证到 level=4)
|
||||
4. 灵活的样式定制
|
||||
|
||||
### 站点图标渲染(favicon/manual)
|
||||
@@ -271,22 +285,27 @@ categories:
|
||||
|
||||
```handlebars
|
||||
{{#if url}}
|
||||
<a href="{{url}}" class="site-card" title="{{name}} - {{#if description}}{{description}}{{else}}{{url}}{{/if}}" {{#if external}}target="_blank" rel="noopener"{{/if}}>
|
||||
<a href="{{url}}" class="site-card{{#if style}} site-card-{{style}}{{/if}}"
|
||||
{{#if external}}target="_blank" rel="noopener"{{/if}}
|
||||
data-type="site"
|
||||
data-name="{{name}}"
|
||||
data-url="{{url}}"
|
||||
data-icon="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"
|
||||
data-description="{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}">
|
||||
{{#ifEquals @root.icons.mode "favicon"}}
|
||||
{{#ifHttpUrl url}}
|
||||
<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={{encodeURIComponent url}}&size=32"
|
||||
alt="{{name}} favicon"
|
||||
loading="lazy"
|
||||
{{!-- 可选:降低引用者信息外泄 --}}
|
||||
{{!-- referrerpolicy="no-referrer" --}}
|
||||
style="opacity:0;"
|
||||
onload="this.style.opacity='1'; this.previousElementSibling.style.display='none';"
|
||||
onerror="this.style.display='none'; this.previousElementSibling.style.display='none'; this.nextElementSibling.style.display='inline-block';"
|
||||
/>
|
||||
<i class="fas fa-link icon-fallback" aria-hidden="true" style="display:none;"></i>
|
||||
<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={{encodeURIComponent url}}&size=32"
|
||||
alt="{{name}} 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>
|
||||
{{else}}
|
||||
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"></i>
|
||||
{{/ifHttpUrl}}
|
||||
@@ -294,8 +313,8 @@ categories:
|
||||
<i class="{{#if icon}}{{icon}}{{else}}fas fa-link{{/if}}"></i>
|
||||
{{/ifEquals}}
|
||||
<h3>{{#if name}}{{name}}{{else}}未命名站点{{/if}}</h3>
|
||||
<p>{{#if description}}{{description}}{{else}}{{url}}{{/if}}</p>
|
||||
</a>
|
||||
<p>{{#if description}}{{description}}{{else}}{{extractDomain url}}{{/if}}</p>
|
||||
</a>
|
||||
{{/if}}
|
||||
```
|
||||
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
</div>
|
||||
|
||||
<div class="group-content">
|
||||
{{#if subgroups}}
|
||||
<div class="subgroups-container" data-container="subgroups">
|
||||
{{#each subgroups}}
|
||||
{{> group level=4}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if sites}}
|
||||
<div class="sites-grid" data-container="sites">
|
||||
{{#if sites.length}}
|
||||
@@ -25,8 +33,12 @@
|
||||
<p class="empty-sites">暂无网站</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="empty-content">暂无网站</p>
|
||||
{{/if}}
|
||||
|
||||
{{#unless subgroups}}
|
||||
{{#unless sites}}
|
||||
<p class="empty-content">暂无网站</p>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +55,62 @@ test('parseBookmarks:解析书签栏、根目录书签与图标映射', () =>
|
||||
assert.equal(tools.sites[0].name, 'Google');
|
||||
});
|
||||
|
||||
test('templates:subgroups(第4层)应可渲染到页面', () => {
|
||||
const Handlebars = require('handlebars');
|
||||
const { registerAllHelpers } = require('../src/helpers');
|
||||
|
||||
const hbs = Handlebars.create();
|
||||
registerAllHelpers(hbs);
|
||||
|
||||
const category = fs.readFileSync(path.join(__dirname, '..', 'templates', 'components', 'category.hbs'), 'utf8');
|
||||
const group = fs.readFileSync(path.join(__dirname, '..', 'templates', 'components', 'group.hbs'), 'utf8');
|
||||
const siteCard = fs.readFileSync(path.join(__dirname, '..', 'templates', 'components', 'site-card.hbs'), 'utf8');
|
||||
const page = fs.readFileSync(path.join(__dirname, '..', 'templates', 'pages', 'bookmarks.hbs'), 'utf8');
|
||||
|
||||
hbs.registerPartial('category', category);
|
||||
hbs.registerPartial('group', group);
|
||||
hbs.registerPartial('site-card', siteCard);
|
||||
|
||||
const tpl = hbs.compile(page);
|
||||
|
||||
const html = tpl({
|
||||
title: '我的书签',
|
||||
subtitle: '测试 subgroups 渲染',
|
||||
icons: { mode: 'manual' },
|
||||
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', description: 'React官方' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.ok(html.includes('subgroups-container'), '应输出 subgroups-container 容器');
|
||||
assert.ok(html.includes('group-level-4'), '应输出 level=4 的 group 样式类');
|
||||
assert.ok(html.includes('React生态'), '应渲染子分组标题文本');
|
||||
});
|
||||
|
||||
test('generateBookmarksYaml:生成 YAML 且可被解析', () => {
|
||||
const bookmarks = {
|
||||
categories: [
|
||||
@@ -122,4 +178,3 @@ test('ensureUserConfigInitialized/ensureUserSiteYmlExists:可在空目录初
|
||||
process.chdir(originalCwd);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user