更新模块化配置支持

This commit is contained in:
Zuoling Rong
2025-05-02 20:14:21 +08:00
parent 58227f9d53
commit 33d573698a
11 changed files with 717 additions and 235 deletions

370
README.md
View File

@@ -12,15 +12,11 @@
- [快速开始](#快速开始) - [快速开始](#快速开始)
- [部署方式](#部署方式) - [部署方式](#部署方式)
- [快速部署到GitHub Pages](#快速部署到github-pages) - [快速部署到GitHub Pages](#快速部署到github-pages)
- [部署到服务器](#部署到服务器)
- [设置配置文件](#设置配置文件)
- [使用单文件配置](#使用单文件配置)
- [使用模块化配置](#使用模块化配置)
- [书签导入功能](#书签导入功能) - [书签导入功能](#书签导入功能)
- [使用方法](#使用方法)
- [自动化工作流程详解](#自动化工作流程详解)
- [书签配置自定义](#书签配置自定义)
- [模板说明](#模板说明)
- [配置文件结构](#配置文件结构)
- [设置网站字体](#设置网站字体)
- [设置网站图标](#设置网站图标)
- [添加新的网站链接](#添加新的网站链接)
- [贡献](#贡献) - [贡献](#贡献)
- [许可证](#许可证) - [许可证](#许可证)
@@ -40,8 +36,18 @@
## 近期更新 ## 近期更新
<details>
<summary>点击查看/隐藏更新日志</summary>
### 2025/05/02 ### 2025/05/02
**1. 模块化配置**
- ✅ 支持将配置拆分为多个文件,便于管理和维护
- ✅ 引入配置目录结构,分离页面配置
- ✅ 保持向后兼容性,同时支持传统配置文件
### 2025/05/01
**1. 页面布局优化** **1. 页面布局优化**
- ✅ 优化了内容区域和侧边栏的间距,确保各种分辨率下内容不会贴近边缘 - ✅ 优化了内容区域和侧边栏的间距,确保各种分辨率下内容不会贴近边缘
- ✅ 卡片与边框始终保持合理间距,避免在窄屏设备上与滚动条贴边 - ✅ 卡片与边框始终保持合理间距,避免在窄屏设备上与滚动条贴边
@@ -65,6 +71,8 @@
- ✅ 生成配置文件,无需手动录入即可批量导入网站链接 - ✅ 生成配置文件,无需手动录入即可批量导入网站链接
- ✅ 与GitHub Actions集成全自动化的导入和部署流程 - ✅ 与GitHub Actions集成全自动化的导入和部署流程
</details>
## 技术栈 ## 技术栈
- HTML5 + CSS3 - HTML5 + CSS3
@@ -86,8 +94,22 @@ menav/
│ └── index.html # HTML骨架模板文件 │ └── index.html # HTML骨架模板文件
├── dist/ # 生成的静态网站由generator.js生成 ├── dist/ # 生成的静态网站由generator.js生成
├── bookmarks/ # 书签导入目录 ├── bookmarks/ # 书签导入目录
├── config.yml # 默认配置文件 ├── config/ # 模块化配置目录
└── config.user.yml # 用户自定义配置文件 │ ├── _default/ # 默认配置
│ │ ├── site.yml # 网站基本配置
│ │ ├── navigation.yml # 导航配置
│ │ └── pages/ # 页面配置
│ │ ├── home.yml
│ │ ├── projects.yml
│ │ ├── articles.yml
│ │ ├── friends.yml
│ │ └── bookmarks.yml
│ └── user/ # 用户自定义配置
│ ├── site.yml # 用户网站配置
│ ├── navigation.yml # 用户导航配置
│ └── pages/ # 用户页面配置
├── config.yml # 默认配置文件(传统格式)
└── config.user.yml # 用户自定义配置文件(传统格式)
``` ```
## 快速开始 ## 快速开始
@@ -109,14 +131,8 @@ npm install
``` ```
3. 修改配置 3. 修改配置
- 复制 `config.yml``config.user.yml` - 可以选择使用单文件配置或模块化配置(见[设置配置文件](#设置配置文件)
- `config.user.yml` 中根据你的需求修改网站内容: - 自定义网站内容、导航链接、社交媒体链接等
- 修改网站基本信息
- 添加/修改导航链接
- 自定义社交媒体链接
- 更新个人项目展示
- 添加友情链接等
4. 本地预览 4. 本地预览
```bash ```bash
@@ -138,9 +154,9 @@ npm run dev
- 勾选 "Issues" - 勾选 "Issues"
3. 启用Actions: 3. 启用Actions:
- 进入fork后的仓库 - 进入fork后的仓库
- 点击顶部的 "Actions" 标签页 - 点击顶部的 "Actions" 标签页
- 点击绿色按钮 "I understand my workflows, go ahead and enable them" - 点击绿色按钮 "I understand my workflows, go ahead and enable them"
4. 配置Pages: 4. 配置Pages:
- 进入仓库的 Settings -> Pages - 进入仓库的 Settings -> Pages
@@ -150,20 +166,16 @@ npm run dev
#### 第二步:自定义配置 #### 第二步:自定义配置
1. 创建个人配置文件: 1. 创建个人配置文件:
- 复制 `config.yml``config.user.yml` - 可以使用单文件配置或模块化配置
- `config.user.yml` 中修改配置 - 推荐使用模块化配置(见[使用模块化配置](#使用模块化配置)
- 提交 `config.user.yml` 到您的仓库这样GitHub Actions才能使用您的自定义配置进行构建 - 提交您的配置文件到仓库
2. 修改配置信息: 2. 等待自动部署:
- 修改网站基本信息 - GitHub Actions会自动检测您的更改
- 添加/修改导航链接 - 构建并部署您的网站
- 自定义社交媒体链接 - 部署完成后,您可以在 Settings -> Pages 中找到您的网站地址
- 更新个人项目展示
- 添加友情链接等
完成以上步骤后,系统会自动部署您的网站。部署完成后,您可以在 Settings -> Pages 中找到您的网站地址 > 重要提示: 请注意不要在配置文件中包含敏感信息,因为它将被提交到公开仓库
> 重要提示: 请务必使用 `config.user.yml` 进行配置,这样在同步上游更新时不会丢失您的个人设置。同时注意不要在配置文件中包含敏感信息,因为它将被提交到公开仓库。
#### 故障排除 #### 故障排除
@@ -175,114 +187,61 @@ npm run dev
- 找到失败的工作流 - 找到失败的工作流
- 点击 "Re-run all jobs" 重新运行 - 点击 "Re-run all jobs" 重新运行
### 部署到服务器
## 书签导入功能 如果您想部署到自己的Web服务器只需要以下几个步骤
### 使用方法 1. 构建静态网站:
```bash
1. **从浏览器导出书签** npm run build
- 在Chrome中: 打开书签管理器 -> 点击"更多"(三个点) -> 导出书签
- 在Firefox中: 打开书签库 -> 显示所有书签 -> 导入和备份 -> 导出书签为HTML
- 在Edge中: 设置 -> 收藏夹 -> 管理收藏夹 -> 导出为HTML文件
2. **导入书签到MeNav**
- 在项目根目录下创建 `bookmarks` 文件夹(如果不存在)
- 将导出的HTML书签文件放入 `bookmarks` 文件夹
- 提交并推送变更到仓库
- GitHub Actions将自动处理书签文件并生成书签配置
3. **书签处理流程**
- 系统会扫描 `bookmarks` 目录查找最新的HTML书签文件
- 解析书签文件中的链接和文件夹结构
- 自动为书签分配适当的图标
- 生成 `bookmarks.user.yml` 配置文件(而不是直接修改 `bookmarks.yml`
- 处理完成后会自动清空 `bookmarks` 目录(防止重复处理)
- 重新构建并部署网站
4. **注意事项**
- 每次只处理一个书签文件,如有多个文件,系统会选择最新的那个
- 书签导入是构建时的一次性操作,不会在浏览器中保存或同步您的书签
- 如果想要更新书签,可以直接编辑 `bookmarks.user.yml`,或重新导出书签文件并重新导入
- 系统会优先使用 `bookmarks.user.yml`,如果同时存在 `bookmarks.yml`,它们的内容会被合并(用户配置优先)
### 自动化工作流程详解
MeNav使用单一的GitHub Actions工作流处理书签导入与网站部署实现了无缝集成
1. **触发条件**:
- 当您推送任何更改到主分支(特别是向 `bookmarks` 目录添加HTML文件
- 手动触发工作流时
2. **书签处理步骤**:
- 自动检测 `bookmarks` 目录中的HTML文件
- 使用 `bookmark-processor.js` 脚本处理书签文件
- 生成/更新 `bookmarks.user.yml` 配置文件
- 提交更改(如有)并保存至仓库
- 自动清理已处理的HTML书签文件
3. **网站构建与部署**:
- 基于最新的配置(包括新生成的书签配置)构建网站
- 自动将构建结果部署到GitHub Pages
4. **故障排除**:
- 如果书签未正确显示请检查导出的HTML文件格式是否标准
- 可以通过GitHub Actions日志查看处理过程中的详细信息
- 确保书签文件是有效的HTML格式且包含正确的书签数据结构
### 书签配置自定义
处理后生成的 `bookmarks.user.yml` 文件可以手动编辑以进一步自定义:
```yaml
# 自动生成的书签配置示例(用户自定义版本)
title: 我的书签
subtitle: 从浏览器导入的书签收藏
categories:
- name: 开发工具
icon: fas fa-folder
sites:
- name: GitHub
url: https://github.com
icon: fab fa-github
description: "从书签导入: GitHub"
# 更多网站...
# 更多分类...
``` ```
您可以: 2. 复制构建结果:
- 修改分类和网站的名称和描述 - 所有生成的静态文件都位于 `dist` 目录中
- 调整图标使用Font Awesome图标类 - `dist` 目录中的所有文件复制到您的Web服务器根目录
- 重新组织书签分类结构
- 添加或删除特定书签
**提示**: 3. 配置Web服务器:
- 尽管自动处理会清理原始HTML文件但修改后的 `bookmarks.user.yml` 会被保留,您可以随时手动编辑它来更新书签内容 - 确保服务器配置为提供静态文件
- 如果项目中同时存在 `bookmarks.yml``bookmarks.user.yml`,系统会合并两者的内容,以 `bookmarks.user.yml` 中的配置为优先 - 对于Apache: 在网站根目录中已有正确的 .htaccess 文件
- 这种设计允许您在更新源仓库时保留自己的书签配置 - 对于Nginx: 添加以下配置到您的server块:
### 模板说明 ```nginx
server {
listen 80;
server_name your-domain.com;
root /path/to/dist;
index index.html;
本项目使用模板与配置文件分离的方式生成网站: location / {
try_files $uri $uri/ /index.html;
}
}
```
1. `templates/index.html` 是不包含具体内容的HTML骨架模板: 4. 更新配置:
- 使用占位符 (如 `{{SITE_TITLE}}`, `{{NAVIGATION}}`, `{{HOME_CONTENT}}`) 标记动态内容的位置 - 如果您想在服务器上更新网站只需重复上述步骤1-2
- 只包含页面结构、CSS引用和基本Javascript引用 - 或者设置自动部署流程例如使用GitLab CI/CD或Jenkins
2. 工作原理: ## 设置配置文件
- `generator.js` 读取配置文件 (优先使用 `config.user.yml`)
- 将配置内容注入到模板中的占位符位置
- 生成最终的静态HTML网站
3. 优点: MeNav支持两种配置方式单文件配置和模块化配置推荐
- 清晰分离结构与内容
- 用户只需修改配置文件,不需要编辑HTML
- 便于更新与维护
在加载配置时遵循以下优先级顺序:
### 配置文件结构 1. `config/user/` (模块化用户配置)
2. `config.user.yml`(单文件用户配置)
3. `config/_default/` (模块化默认配置)
4. `config.yml`(单文件默认配置)
`config.user.yml` 应包含以下主要部分: ### 使用单文件配置
单文件配置是最简单的配置方式,适合小型网站和快速开始。
1. **创建配置文件**:
- 复制 `config.yml``config.user.yml`
- 编辑 `config.user.yml` 文件
2. **配置文件结构**:
```yaml ```yaml
# 网站基本信息 # 网站基本信息
site: site:
@@ -294,9 +253,9 @@ site:
# 字体设置 # 字体设置
fonts: fonts:
title: # 标题字体 title: # 标题字体
family: 字体名称 # 可以是Web安全字体或Google Fonts family: 字体名称
weight: 字重值 # 如400、500、600等 weight: 字重值
source: 字体来源 # "google"或"system" source: 字体来源
subtitle: # 副标题字体 subtitle: # 副标题字体
family: 字体名称 family: 字体名称
weight: 字重值 weight: 字重值
@@ -319,67 +278,21 @@ navigation:
id: 页面ID id: 页面ID
active: 是否激活 active: 是否激活
# 类别
categories:
- name: 分类名称
icon: 分类图标
sites:
- name: 网站名称
url: 网站地址
icon: 网站图标
description: 网站描述
# 更多配置... # 更多配置...
``` ```
### 设置网站字体 3. **添加网站链接**:
`config.user.yml` 中的 categories 部分添加新站点:
1. 字体配置选项:
- `family`: 字体名称支持Web安全字体或Google Fonts
- `weight`: 字体粗细常用值如400(常规)、500(中等)、600(粗体)等
- `source`: 字体来源,可选"google"或"system"
2. 字体分类:
- `title`: 标题字体,用于大标题
- `subtitle`: 副标题字体,用于副标题
- `body`: 正文字体,用于普通文本
3. 使用Google字体示例:
```yaml
fonts:
body:
family: "Noto Sans SC" # Google提供的中文字体
weight: 400
source: "google"
```
4. 使用系统字体示例:
```yaml
fonts:
body:
family: "Segoe UI, system-ui, -apple-system, sans-serif"
weight: 400
source: "system"
```
### 设置网站图标
1. 准备图标文件:
- 支持.ico、.png等格式
- 建议尺寸为32x32或16x16像素
- 将图标文件放在assets目录下
- 例如: `assets/favicon.ico``assets/favicon.png`
2. 配置图标:
-`config.yml``config.user.yml`的site部分设置favicon
- 使用相对于仓库根目录的路径
- 例如: `favicon: favicon.ico`generator会自动从assets目录查找
- 也可以使用在线图标URL
3. 生成和部署:
- 运行 `npm run generate` 时会自动复制图标文件到dist目录
- 确保图标文件存在于assets目录中
- 部署后图标会自动显示在浏览器标签页
> 提示: 如果图标没有显示,请检查:
> 1. 图标文件是否存在于assets目录
> 2. 配置文件中的路径是否正确
> 3. 是否重新运行了生成命令
### 添加新的网站链接
`config.user.yml` 中相应的分类下添加新站点:
```yaml ```yaml
categories: categories:
- name: 分类名称 - name: 分类名称
@@ -391,6 +304,87 @@ categories:
description: 网站描述 description: 网站描述
``` ```
4. **设置网站字体**:
- `family`: 字体名称支持Web安全字体或Google Fonts
- `weight`: 字体粗细常用值如400(常规)、500(中等)、600(粗体)等
- `source`: 字体来源,可选"google"或"system"
例如使用Google字体:
```yaml
fonts:
body:
family: "Noto Sans SC" # Google提供的中文字体
weight: 400
source: "google"
```
5. **设置网站图标**:
- 支持.ico、.png等格式
- 建议尺寸为32x32或16x16像素
- 将图标文件放在assets目录下
- 在配置文件中设置:`favicon: favicon.ico`
### 使用模块化配置
模块化配置将配置分散到多个文件中,更易于管理和维护,推荐用于复杂网站。
#### 模块化配置目录结构
```
config/
├── _default/ # 默认配置
│ ├── site.yml # 网站基本信息、字体等
│ ├── navigation.yml # 导航菜单配置
│ └── pages/ # 页面配置
│ ├── home.yml # 首页配置
│ ├── projects.yml # 项目页配置
│ ├── articles.yml # 文章页配置
│ ├── friends.yml # 朋友页配置
│ └── bookmarks.yml # 书签页配置
└── user/ # 用户自定义配置(可选覆盖)
├── site.yml # 用户网站配置
├── navigation.yml # 用户导航配置
└── pages/ # 用户页面配置
├── home.yml # 用户首页配置
# 其他用户自定义页面...
```
#### 使用模块化配置的优势
1. **分离关注点**:每个页面和功能区域有专属配置文件
2. **简化编辑**:修改特定页面时只需编辑对应文件
3. **更好的版本控制**:减少合并冲突
4. **向后兼容**:仍然支持传统的 `config.yml``config.user.yml`
## 书签导入功能
MeNav支持从浏览器导入书签快速批量添加网站链接。
### 导入步骤
1. **从浏览器导出书签**
- 在Chrome中: 打开书签管理器 -> 点击"更多"(三个点) -> 导出书签
- 在Firefox中: 打开书签库 -> 显示所有书签 -> 导入和备份 -> 导出书签为HTML
- 在Edge中: 设置 -> 收藏夹 -> 管理收藏夹 -> 导出为HTML文件
2. **导入书签到MeNav**
- 在项目根目录下创建 `bookmarks` 文件夹(如果不存在)
- 将导出的HTML书签文件放入 `bookmarks` 文件夹
- 提交并推送变更到仓库
- 系统会自动处理书签文件并生成配置文件
3. **书签处理结果**
- 生成的书签配置会保存到 `bookmarks.user.yml`
- 如果使用模块化配置,也会同时保存到 `config/user/pages/bookmarks.yml`
- 书签会自动添加到导航菜单中
> 有关书签导入功能的更多信息,请参阅源代码中的相关注释。
## 贡献
欢迎通过 Issues 或 Pull Requests 的形式做出贡献。如果您有好的想法或发现了问题,请随时提出。
## 许可证 ## 许可证
AGPL-3.0 License AGPL-3.0 License

View File

@@ -38,7 +38,6 @@ navigation:
- name: 书签 - name: 书签
icon: fas fa-bookmark icon: fas fa-bookmark
id: bookmarks id: bookmarks
active: false
social: social:
- name: GitHub - name: GitHub
url: https://github.com url: https://github.com

View File

@@ -0,0 +1,16 @@
- name: 首页
icon: fas fa-home
id: home
active: true
- name: 项目
icon: fas fa-project-diagram
id: projects
- name: 文章
icon: fas fa-book
id: articles
- name: 朋友
icon: fas fa-users
id: friends
- name: 书签
icon: fas fa-bookmark
id: bookmarks

View File

@@ -0,0 +1,33 @@
title: 技术文章
subtitle: 分享我的技术文章和学习笔记
categories:
- name: 最新文章
icon: fas fa-pen
sites:
- name: Vue3最佳实践
icon: fab fa-vuejs
description: Vue3组合式API的使用技巧
url: "#"
- name: JavaScript进阶
icon: fab fa-js
description: JavaScript高级特性解析
url: "#"
- name: Git使用技巧
icon: fab fa-git-alt
description: Git常用命令和工作流
url: "#"
- name: Docker入门
icon: fab fa-docker
description: Docker基础知识和实践
url: "#"
- name: 学习笔记
icon: fas fa-book
sites:
- name: React Hooks
icon: fab fa-react
description: React Hooks最佳实践
url: "#"
- name: Node.js实战
icon: fab fa-node-js
description: Node.js服务端开发笔记
url: "#"

View File

@@ -0,0 +1,29 @@
title: 我的书签
subtitle: 从浏览器导入的书签收藏
categories:
- name: 技术资源
icon: fas fa-folder
sites:
- name: GitHub
url: https://github.com/
icon: fab fa-github
description: "从书签导入: GitHub"
- name: Stack Overflow
url: https://stackoverflow.com/
icon: fab fa-stack-overflow
description: "从书签导入: Stack Overflow"
- name: MDN Web Docs
url: https://developer.mozilla.org/
icon: fas fa-link
description: "从书签导入: MDN Web Docs"
- name: 社交媒体
icon: fas fa-folder
sites:
- name: Twitter
url: https://twitter.com/
icon: fab fa-twitter
description: "从书签导入: Twitter"
- name: LinkedIn
url: https://www.linkedin.com/
icon: fab fa-linkedin
description: "从书签导入: LinkedIn"

View File

@@ -0,0 +1,29 @@
title: 友情链接
subtitle: 优秀的博主和朋友们
categories:
- name: 技术博主
icon: fas fa-user-friends
sites:
- name: 小明的博客
icon: fas fa-code
description: 全栈开发工程师,分享技术心得
url: "#"
- name: 小红的前端
icon: fas fa-paint-brush
description: 专注前端开发与设计
url: "#"
- name: 小张的后端
icon: fas fa-server
description: 分享后端开发经验
url: "#"
- name: 技术社区
icon: fas fa-laptop-code
sites:
- name: GitHub
icon: fab fa-github
description: 开源代码托管平台
url: https://github.com
- name: Stack Overflow
icon: fab fa-stack-overflow
description: 程序员问答社区
url: https://stackoverflow.com

View File

@@ -0,0 +1,43 @@
categories:
- name: 常用网站
icon: fas fa-star
sites:
- name: Linux.do
url: https://linux.do/
icon: fab fa-linux
description: 新的理想型社区
- name: Google
url: https://www.google.com
icon: fab fa-google
description: 全球最大的搜索引擎
- name: GitHub
url: https://www.github.com
icon: fab fa-github
description: 代码托管平台
- name: Stack Overflow
url: https://stackoverflow.com
icon: fab fa-stack-overflow
description: 程序员问答社区
- name: ChatGPT
url: https://chat.openai.com
icon: fas fa-robot
description: AI智能助手
- name: 学习资源
icon: fas fa-graduation-cap
sites:
- name: 哔哩哔哩
url: https://www.bilibili.com
icon: fas fa-play-circle
description: 视频学习平台
- name: 知乎
url: https://www.zhihu.com
icon: fas fa-question-circle
description: 问答社区
- name: 掘金
url: https://juejin.cn
icon: fas fa-book
description: 高质量技术社区
- name: LeetCode
url: https://leetcode.cn
icon: fas fa-code
description: 算法刷题平台

View File

@@ -0,0 +1,29 @@
title: 我的项目
subtitle: 这里展示了我的一些个人项目和开源贡献
categories:
- name: 个人项目
icon: fas fa-code
sites:
- name: 个人导航站
icon: fas fa-compass
description: 一个简洁美观的个人导航页面
url: "#"
- name: Todo List
icon: fas fa-tasks
description: 基于Vue3的待办事项管理器
url: "#"
- name: 个人博客
icon: fas fa-blog
description: 使用Hexo搭建的技术博客
url: "#"
- name: 开源贡献
icon: fas fa-code-branch
sites:
- name: Project A
icon: fab fa-github
description: 开源项目贡献
url: "#"
- name: Project B
icon: fab fa-github
description: 开源项目贡献
url: "#"

38
config/_default/site.yml Normal file
View File

@@ -0,0 +1,38 @@
title: 我的导航
description: 个人网络导航站
author: Your Name
favicon: favicon.ico
logo_text: 导航站
fonts:
title:
family: Poppins
weight: 600
source: google
subtitle:
family: Quicksand
weight: 500
source: google
body:
family: Noto Sans SC
weight: 400
source: google
profile:
title: Hello,
subtitle: Welcome to My Navigation
description: 导航菜单
social:
- name: GitHub
url: https://github.com
icon: fab fa-github
- name: Telegram
url: https://t.me
icon: fab fa-telegram
- name: Twitter
url: https://twitter.com
icon: fab fa-twitter
- name: Steam
url: https://steam.com
icon: fab fa-steam

View File

@@ -6,8 +6,12 @@ const yaml = require('js-yaml');
const BOOKMARKS_DIR = 'bookmarks'; const BOOKMARKS_DIR = 'bookmarks';
// 输出配置文件路径 - 使用相对路径 // 输出配置文件路径 - 使用相对路径
const OUTPUT_FILE = 'bookmarks.user.yml'; const OUTPUT_FILE = 'bookmarks.user.yml';
// 模块化输出配置文件路径
const MODULAR_OUTPUT_FILE = 'config/user/pages/bookmarks.yml';
// 默认书签配置文件路径 - 使用相对路径 // 默认书签配置文件路径 - 使用相对路径
const DEFAULT_BOOKMARKS_FILE = 'bookmarks.yml'; const DEFAULT_BOOKMARKS_FILE = 'bookmarks.yml';
// 模块化默认书签配置文件路径
const MODULAR_DEFAULT_BOOKMARKS_FILE = 'config/_default/pages/bookmarks.yml';
// 图标映射根据URL关键字匹配合适的图标 // 图标映射根据URL关键字匹配合适的图标
const ICON_MAPPING = { const ICON_MAPPING = {
@@ -209,20 +213,114 @@ ${yamlString}`;
// 更新现有config.yml中的导航添加书签页面 // 更新现有config.yml中的导航添加书签页面
function updateConfigWithBookmarks() { function updateConfigWithBookmarks() {
// 传统配置文件
const configFile = 'config.yml'; const configFile = 'config.yml';
const userConfigFile = 'config.user.yml'; const userConfigFile = 'config.user.yml';
// 优先使用用户配置文件,如果存在 // 模块化配置文件
const targetConfigFile = fs.existsSync(userConfigFile) ? userConfigFile : configFile; const modularNavFile = 'config/_default/navigation.yml';
const modularUserNavFile = 'config/user/navigation.yml';
let navigationUpdated = false;
// 按优先级顺序尝试更新导航配置
// 1. 最高优先级: 模块化用户导航配置
if (fs.existsSync(modularUserNavFile)) {
navigationUpdated = updateNavigationFile(modularUserNavFile);
if (navigationUpdated) {
console.log(`Updated modular user navigation file: ${modularUserNavFile} (highest priority)`);
}
}
// 2. 次高优先级: 传统用户配置
if (!navigationUpdated && fs.existsSync(userConfigFile)) {
navigationUpdated = updateTraditionalConfig(userConfigFile);
if (navigationUpdated) {
console.log(`Updated legacy user config file: ${userConfigFile}`);
}
}
// 3. 次低优先级: 模块化默认导航
if (!navigationUpdated && fs.existsSync(modularNavFile)) {
navigationUpdated = updateNavigationFile(modularNavFile);
if (navigationUpdated) {
console.log(`Updated modular default navigation file: ${modularNavFile}`);
}
}
// 4. 最低优先级: 传统默认配置
if (!navigationUpdated && fs.existsSync(configFile)) {
navigationUpdated = updateTraditionalConfig(configFile);
if (navigationUpdated) {
console.log(`Updated legacy default config file: ${configFile} (lowest priority)`);
}
}
if (!navigationUpdated) {
console.log('Did not find any configuration file to update with bookmarks navigation');
}
}
// 更新单个导航配置文件(模块化版本)
function updateNavigationFile(filePath) {
try { try {
const configContent = fs.readFileSync(targetConfigFile, 'utf8'); const content = fs.readFileSync(filePath, 'utf8');
const navConfig = yaml.load(content);
// 检查是否已有书签页面
const hasBookmarksNav = Array.isArray(navConfig) &&
navConfig.some(nav => nav.id === 'bookmarks');
if (!hasBookmarksNav) {
// 添加书签导航项
if (!Array.isArray(navConfig)) {
console.log(`Warning: Navigation config in ${filePath} is not an array, cannot update`);
return false;
}
navConfig.push({
name: '书签',
icon: 'fas fa-bookmark',
id: 'bookmarks',
active: false
});
// 更新文件
const updatedYaml = yaml.dump(navConfig, {
indent: 2,
lineWidth: -1,
quotingType: '"'
});
fs.writeFileSync(filePath, updatedYaml, 'utf8');
return true;
}
return false; // 无需更新
} catch (error) {
console.error(`Error updating navigation file ${filePath}:`, error);
return false;
}
}
// 更新传统配置文件(整体配置)
function updateTraditionalConfig(filePath) {
try {
const configContent = fs.readFileSync(filePath, 'utf8');
const config = yaml.load(configContent); const config = yaml.load(configContent);
// 检查导航中是否已有书签页面 // 检查导航中是否已有书签页面
const hasBookmarksNav = config.navigation.some(nav => nav.id === 'bookmarks'); const hasBookmarksNav = config.navigation &&
Array.isArray(config.navigation) &&
config.navigation.some(nav => nav.id === 'bookmarks');
if (!hasBookmarksNav) { if (!hasBookmarksNav) {
// 确保navigation数组存在
if (!config.navigation) {
config.navigation = [];
}
// 添加书签页面到导航 // 添加书签页面到导航
config.navigation.push({ config.navigation.push({
name: '书签', name: '书签',
@@ -238,11 +336,14 @@ function updateConfigWithBookmarks() {
quotingType: '"' quotingType: '"'
}); });
fs.writeFileSync(targetConfigFile, updatedYaml, 'utf8'); fs.writeFileSync(filePath, updatedYaml, 'utf8');
console.log(`Updated ${targetConfigFile} with bookmarks navigation`); return true;
} }
return false; // 无需更新
} catch (error) { } catch (error) {
console.error('Error updating config with bookmarks navigation:', error); console.error(`Error updating config file ${filePath}:`, error);
return false;
} }
} }
@@ -250,7 +351,8 @@ function updateConfigWithBookmarks() {
async function main() { async function main() {
console.log('Starting bookmark processing...'); console.log('Starting bookmark processing...');
console.log(`Current working directory: ${process.cwd()}`); console.log(`Current working directory: ${process.cwd()}`);
console.log(`Output file will be: ${OUTPUT_FILE} (absolute: ${path.resolve(OUTPUT_FILE)})`); console.log(`Legacy output file will be: ${OUTPUT_FILE} (absolute: ${path.resolve(OUTPUT_FILE)})`);
console.log(`Modular output file will be: ${MODULAR_OUTPUT_FILE} (absolute: ${path.resolve(MODULAR_OUTPUT_FILE)})`);
// 获取最新的书签文件 // 获取最新的书签文件
const bookmarkFile = getLatestBookmarkFile(); const bookmarkFile = getLatestBookmarkFile();
@@ -288,39 +390,58 @@ async function main() {
console.log(yaml.split('\n').slice(0, 5).join('\n') + '\n...'); console.log(yaml.split('\n').slice(0, 5).join('\n') + '\n...');
try { try {
// 确保目标目录存在 // 确保传统目标目录存在
const outputDir = path.dirname(OUTPUT_FILE); const outputDir = path.dirname(OUTPUT_FILE);
if (!fs.existsSync(outputDir) && outputDir !== '.') { if (!fs.existsSync(outputDir) && outputDir !== '.') {
console.log(`Creating output directory: ${outputDir}`); console.log(`Creating output directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true }); fs.mkdirSync(outputDir, { recursive: true });
} }
// 保存YAML文件 // 确保模块化目标目录存在
console.log(`Writing to: ${OUTPUT_FILE}`); const modularOutputDir = path.dirname(MODULAR_OUTPUT_FILE);
if (!fs.existsSync(modularOutputDir)) {
console.log(`Creating modular output directory structure: ${modularOutputDir}`);
fs.mkdirSync(modularOutputDir, { recursive: true });
}
// 保存YAML到传统位置
console.log(`Writing to legacy location: ${OUTPUT_FILE}`);
fs.writeFileSync(OUTPUT_FILE, yaml, 'utf8'); fs.writeFileSync(OUTPUT_FILE, yaml, 'utf8');
// 保存YAML到模块化位置
console.log(`Writing to modular location: ${MODULAR_OUTPUT_FILE}`);
fs.writeFileSync(MODULAR_OUTPUT_FILE, yaml, 'utf8');
// 验证文件是否确实被创建 // 验证文件是否确实被创建
let success = false;
if (fs.existsSync(OUTPUT_FILE)) { if (fs.existsSync(OUTPUT_FILE)) {
const stats = fs.statSync(OUTPUT_FILE); const stats = fs.statSync(OUTPUT_FILE);
console.log(`Successfully saved bookmarks configuration to ${OUTPUT_FILE}`); console.log(`Successfully saved bookmarks configuration to ${OUTPUT_FILE}`);
console.log(`Verified file exists: ${OUTPUT_FILE}`);
console.log(`File size: ${stats.size} bytes`); console.log(`File size: ${stats.size} bytes`);
console.log(`File permissions: ${stats.mode.toString(8)}`); success = true;
// 列出当前目录内容以确认
console.log('Current directory contains:');
fs.readdirSync('.').forEach(file => {
console.log(`- ${file}`);
});
} else { } else {
console.error(`ERROR: File was not created: ${OUTPUT_FILE}`); console.error(`ERROR: Legacy file was not created: ${OUTPUT_FILE}`);
}
if (fs.existsSync(MODULAR_OUTPUT_FILE)) {
const stats = fs.statSync(MODULAR_OUTPUT_FILE);
console.log(`Successfully saved bookmarks configuration to ${MODULAR_OUTPUT_FILE}`);
console.log(`File size: ${stats.size} bytes`);
success = true;
} else {
console.error(`ERROR: Modular file was not created: ${MODULAR_OUTPUT_FILE}`);
}
if (!success) {
console.error('ERROR: No output files were created successfully');
process.exit(1); process.exit(1);
} }
// 更新导航 // 更新导航
updateConfigWithBookmarks(); updateConfigWithBookmarks();
} catch (writeError) { } catch (writeError) {
console.error(`ERROR writing file ${OUTPUT_FILE}:`, writeError); console.error(`ERROR writing files:`, writeError);
process.exit(1); process.exit(1);
} }
} catch (error) { } catch (error) {

View File

@@ -15,48 +15,199 @@ function escapeHtml(unsafe) {
.replace(/'/g, "&#039;"); .replace(/'/g, "&#039;");
} }
// 读取配置文件 /**
function loadConfig() { * 从文件合并配置到主配置对象
let config = null; * @param {Object} config 主配置对象
* @param {string} filePath 配置文件路径
try { */
// 优先尝试读取用户配置 function mergeConfigFromFile(config, filePath) {
if (fs.existsSync('config.user.yml')) { if (fs.existsSync(filePath)) {
const userConfigFile = fs.readFileSync('config.user.yml', 'utf8'); try {
config = yaml.load(userConfigFile); const fileContent = fs.readFileSync(filePath, 'utf8');
console.log('Using user configuration from config.user.yml'); const fileConfig = yaml.load(fileContent);
}
// 如果没有用户配置,则使用默认配置 // 提取文件名(不含扩展名)作为配置
else { const configKey = path.basename(filePath, path.extname(filePath));
const defaultConfigFile = fs.readFileSync('config.yml', 'utf8');
config = yaml.load(defaultConfigFile); // 如果是site或navigation文件直接合并到主配置
console.log('No user configuration found, using default config.yml'); if (configKey === 'site' || configKey === 'navigation') {
if (!config[configKey]) config[configKey] = {};
deepMerge(config[configKey], fileConfig);
} else {
// 其他配置直接合并到根级别
deepMerge(config, fileConfig);
}
console.log(`Loaded and merged configuration from ${filePath}`);
} catch (e) {
console.error(`Error loading configuration from ${filePath}:`, e);
}
}
}
/**
* 加载页面配置目录中的所有配置文件
* @param {Object} config 主配置对象
* @param {string} dirPath 页面配置目录路径
*/
function loadPageConfigs(config, dirPath) {
if (fs.existsSync(dirPath)) {
const files = fs.readdirSync(dirPath).filter(file =>
file.endsWith('.yml') || file.endsWith('.yaml'));
files.forEach(file => {
try {
const filePath = path.join(dirPath, file);
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileConfig = yaml.load(fileContent);
// 提取文件名(不含扩展名)作为配置键
const configKey = path.basename(file, path.extname(file));
// 将页面配置添加到主配置对象
config[configKey] = fileConfig;
console.log(`Loaded page configuration from ${filePath}`);
} catch (e) {
console.error(`Error loading page configuration from ${path.join(dirPath, file)}:`, e);
}
});
}
}
/**
* 深度合并两个对象
* @param {Object} target 目标对象
* @param {Object} source 源对象
* @returns {Object} 合并后的对象
*/
function deepMerge(target, source) {
if (!source) return target;
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] === 'object' && source[key] !== null) {
// 确保目标对象有这个属性
if (!target[key]) {
if (Array.isArray(source[key])) {
target[key] = [];
} else {
target[key] = {};
}
}
// 递归合并
if (Array.isArray(source[key])) {
// 对于数组,直接替换或添加
target[key] = source[key];
} else {
// 对于对象,递归合并
deepMerge(target[key], source[key]);
}
} else {
// 对于基本类型,直接替换
target[key] = source[key];
}
} }
} catch (e) {
console.error('Error loading configuration file:', e);
process.exit(1);
} }
// 尝试读取书签配置 return target;
}
// 读取配置文件
function loadConfig() {
// 初始化空配置对象
let config = {
site: {},
navigation: [],
fonts: {},
profile: {},
social: [],
categories: []
};
// 处理配置目录结构,按照优先级从低到高加载
// 4. 最低优先级: config.yml (传统默认配置)
if (fs.existsSync('config.yml')) {
const defaultConfigFile = fs.readFileSync('config.yml', 'utf8');
const defaultConfig = yaml.load(defaultConfigFile);
deepMerge(config, defaultConfig);
console.log('Loaded legacy default config.yml');
}
// 3. 其次优先级: config/_default/ 目录
if (fs.existsSync('config/_default')) {
console.log('Loading modular default configuration from config/_default/');
// 加载基础配置
mergeConfigFromFile(config, 'config/_default/site.yml');
mergeConfigFromFile(config, 'config/_default/navigation.yml');
// 加载页面配置
if (fs.existsSync('config/_default/pages')) {
loadPageConfigs(config, 'config/_default/pages');
}
}
// 2. 次高优先级: config.user.yml (传统用户配置)
if (fs.existsSync('config.user.yml')) {
const userConfigFile = fs.readFileSync('config.user.yml', 'utf8');
const userConfig = yaml.load(userConfigFile);
// 深度合并配置
deepMerge(config, userConfig);
console.log('Merged legacy user configuration from config.user.yml');
}
// 1. 最高优先级: config/user/ 目录
if (fs.existsSync('config/user')) {
console.log('Loading modular user configuration from config/user/ (highest priority)');
// 覆盖基础配置
mergeConfigFromFile(config, 'config/user/site.yml');
mergeConfigFromFile(config, 'config/user/navigation.yml');
// 覆盖页面配置
if (fs.existsSync('config/user/pages')) {
loadPageConfigs(config, 'config/user/pages');
}
}
// 处理书签文件(保持现有功能)
try { try {
let bookmarksConfig = null; let bookmarksConfig = null;
let bookmarksSource = null;
// 优先尝试读取用户书签配置 // 按照相同的优先级顺序处理书签配置
if (fs.existsSync('bookmarks.user.yml')) { // 1. 模块化用户书签配置 (最高优先级)
if (fs.existsSync('config/user/pages/bookmarks.yml')) {
const userBookmarksFile = fs.readFileSync('config/user/pages/bookmarks.yml', 'utf8');
bookmarksConfig = yaml.load(userBookmarksFile);
bookmarksSource = 'config/user/pages/bookmarks.yml';
}
// 2. 传统用户书签配置
else if (fs.existsSync('bookmarks.user.yml')) {
const userBookmarksFile = fs.readFileSync('bookmarks.user.yml', 'utf8'); const userBookmarksFile = fs.readFileSync('bookmarks.user.yml', 'utf8');
bookmarksConfig = yaml.load(userBookmarksFile); bookmarksConfig = yaml.load(userBookmarksFile);
console.log('Using user bookmarks configuration from bookmarks.user.yml'); bookmarksSource = 'bookmarks.user.yml';
} }
// 如果没有用户书签配置,则尝试读取默认书签配置 // 3. 模块化默认书签配置
else if (fs.existsSync('config/_default/pages/bookmarks.yml')) {
const defaultBookmarksFile = fs.readFileSync('config/_default/pages/bookmarks.yml', 'utf8');
bookmarksConfig = yaml.load(defaultBookmarksFile);
bookmarksSource = 'config/_default/pages/bookmarks.yml';
}
// 4. 传统默认书签配置 (最低优先级)
else if (fs.existsSync('bookmarks.yml')) { else if (fs.existsSync('bookmarks.yml')) {
const bookmarksFile = fs.readFileSync('bookmarks.yml', 'utf8'); const bookmarksFile = fs.readFileSync('bookmarks.yml', 'utf8');
bookmarksConfig = yaml.load(bookmarksFile); bookmarksConfig = yaml.load(bookmarksFile);
console.log('Using default bookmarks configuration from bookmarks.yml'); bookmarksSource = 'bookmarks.yml';
} }
// 添加书签页面配置 // 添加书签页面配置
if (bookmarksConfig) { if (bookmarksConfig) {
config.bookmarks = bookmarksConfig; config.bookmarks = bookmarksConfig;
console.log(`Using bookmarks configuration from ${bookmarksSource}`);
// 确保导航中有书签页面 // 确保导航中有书签页面
const hasBookmarksNav = config.navigation.some(nav => nav.id === 'bookmarks'); const hasBookmarksNav = config.navigation.some(nav => nav.id === 'bookmarks');