diff --git a/README.md b/README.md index 57a18a3..a7c3d97 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,11 @@ - [快速开始](#快速开始) - [部署方式](#部署方式) - [快速部署到GitHub Pages](#快速部署到github-pages) + - [部署到服务器](#部署到服务器) +- [设置配置文件](#设置配置文件) + - [使用单文件配置](#使用单文件配置) + - [使用模块化配置](#使用模块化配置) - [书签导入功能](#书签导入功能) - - [使用方法](#使用方法) - - [自动化工作流程详解](#自动化工作流程详解) - - [书签配置自定义](#书签配置自定义) -- [模板说明](#模板说明) -- [配置文件结构](#配置文件结构) -- [设置网站字体](#设置网站字体) -- [设置网站图标](#设置网站图标) -- [添加新的网站链接](#添加新的网站链接) - [贡献](#贡献) - [许可证](#许可证) @@ -40,8 +36,18 @@ ## 近期更新 +
+点击查看/隐藏更新日志 + ### 2025/05/02 +**1. 模块化配置** +- ✅ 支持将配置拆分为多个文件,便于管理和维护 +- ✅ 引入配置目录结构,分离页面配置 +- ✅ 保持向后兼容性,同时支持传统配置文件 + +### 2025/05/01 + **1. 页面布局优化** - ✅ 优化了内容区域和侧边栏的间距,确保各种分辨率下内容不会贴近边缘 - ✅ 卡片与边框始终保持合理间距,避免在窄屏设备上与滚动条贴边 @@ -65,6 +71,8 @@ - ✅ 生成配置文件,无需手动录入即可批量导入网站链接 - ✅ 与GitHub Actions集成,全自动化的导入和部署流程 +
+ ## 技术栈 - HTML5 + CSS3 @@ -86,8 +94,22 @@ menav/ │ └── index.html # HTML骨架模板文件 ├── dist/ # 生成的静态网站(由generator.js生成) ├── bookmarks/ # 书签导入目录 -├── config.yml # 默认配置文件 -└── config.user.yml # 用户自定义配置文件 +├── config/ # 模块化配置目录 +│ ├── _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. 修改配置 -- 复制 `config.yml` 为 `config.user.yml` -- 在 `config.user.yml` 中根据你的需求修改网站内容: - - 修改网站基本信息 - - 添加/修改导航链接 - - 自定义社交媒体链接 - - 更新个人项目展示 - - 添加友情链接等 - + - 可以选择使用单文件配置或模块化配置(见[设置配置文件](#设置配置文件)) + - 自定义网站内容、导航链接、社交媒体链接等 4. 本地预览 ```bash @@ -138,9 +154,9 @@ npm run dev - 勾选 "Issues" 3. 启用Actions: - - 进入fork后的仓库 - - 点击顶部的 "Actions" 标签页 - - 点击绿色按钮 "I understand my workflows, go ahead and enable them" + - 进入fork后的仓库 + - 点击顶部的 "Actions" 标签页 + - 点击绿色按钮 "I understand my workflows, go ahead and enable them" 4. 配置Pages: - 进入仓库的 Settings -> Pages @@ -150,20 +166,16 @@ npm run dev #### 第二步:自定义配置 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" 重新运行 +### 部署到服务器 -## 书签导入功能 +如果您想部署到自己的Web服务器,只需要以下几个步骤: -### 使用方法 - -1. **从浏览器导出书签** - - 在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" - # 更多网站... - # 更多分类... +1. 构建静态网站: +```bash +npm run build ``` -您可以: -- 修改分类和网站的名称和描述 -- 调整图标(使用Font Awesome图标类) -- 重新组织书签分类结构 -- 添加或删除特定书签 +2. 复制构建结果: + - 所有生成的静态文件都位于 `dist` 目录中 + - 将 `dist` 目录中的所有文件复制到您的Web服务器根目录 -**提示**: -- 尽管自动处理会清理原始HTML文件,但修改后的 `bookmarks.user.yml` 会被保留,您可以随时手动编辑它来更新书签内容 -- 如果项目中同时存在 `bookmarks.yml` 和 `bookmarks.user.yml`,系统会合并两者的内容,以 `bookmarks.user.yml` 中的配置为优先 -- 这种设计允许您在更新源仓库时保留自己的书签配置 +3. 配置Web服务器: + - 确保服务器配置为提供静态文件 + - 对于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骨架模板: - - 使用占位符 (如 `{{SITE_TITLE}}`, `{{NAVIGATION}}`, `{{HOME_CONTENT}}`) 标记动态内容的位置 - - 只包含页面结构、CSS引用和基本Javascript引用 +4. 更新配置: + - 如果您想在服务器上更新网站,只需重复上述步骤1-2 + - 或者设置自动部署流程,例如使用GitLab CI/CD或Jenkins -2. 工作原理: - - `generator.js` 读取配置文件 (优先使用 `config.user.yml`) - - 将配置内容注入到模板中的占位符位置 - - 生成最终的静态HTML网站 +## 设置配置文件 -3. 优点: - - 清晰分离结构与内容 - - 用户只需修改配置文件,不需要编辑HTML - - 便于更新与维护 +MeNav支持两种配置方式:单文件配置和模块化配置(推荐)。 +在加载配置时遵循以下优先级顺序: -### 配置文件结构 +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 # 网站基本信息 site: @@ -294,9 +253,9 @@ site: # 字体设置 fonts: title: # 标题字体 - family: 字体名称 # 可以是Web安全字体或Google Fonts - weight: 字重值 # 如400、500、600等 - source: 字体来源 # "google"或"system" + family: 字体名称 + weight: 字重值 + source: 字体来源 subtitle: # 副标题字体 family: 字体名称 weight: 字重值 @@ -319,67 +278,21 @@ navigation: id: 页面ID active: 是否激活 +# 类别 +categories: + - name: 分类名称 + icon: 分类图标 + sites: + - name: 网站名称 + url: 网站地址 + icon: 网站图标 + description: 网站描述 + # 更多配置... ``` -### 设置网站字体 - -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` 中相应的分类下添加新站点: - +3. **添加网站链接**: + 在 `config.user.yml` 中的 categories 部分添加新站点: ```yaml categories: - name: 分类名称 @@ -391,6 +304,87 @@ categories: 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 diff --git a/config.yml b/config.yml index 2c9d553..e099835 100644 --- a/config.yml +++ b/config.yml @@ -38,7 +38,6 @@ navigation: - name: 书签 icon: fas fa-bookmark id: bookmarks - active: false social: - name: GitHub url: https://github.com diff --git a/config/_default/navigation.yml b/config/_default/navigation.yml new file mode 100644 index 0000000..f8ae948 --- /dev/null +++ b/config/_default/navigation.yml @@ -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 \ No newline at end of file diff --git a/config/_default/pages/articles.yml b/config/_default/pages/articles.yml new file mode 100644 index 0000000..5d2585a --- /dev/null +++ b/config/_default/pages/articles.yml @@ -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: "#" \ No newline at end of file diff --git a/config/_default/pages/bookmarks.yml b/config/_default/pages/bookmarks.yml new file mode 100644 index 0000000..8e6b00d --- /dev/null +++ b/config/_default/pages/bookmarks.yml @@ -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" \ No newline at end of file diff --git a/config/_default/pages/friends.yml b/config/_default/pages/friends.yml new file mode 100644 index 0000000..4eb544c --- /dev/null +++ b/config/_default/pages/friends.yml @@ -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 \ No newline at end of file diff --git a/config/_default/pages/home.yml b/config/_default/pages/home.yml new file mode 100644 index 0000000..5807701 --- /dev/null +++ b/config/_default/pages/home.yml @@ -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: 算法刷题平台 \ No newline at end of file diff --git a/config/_default/pages/projects.yml b/config/_default/pages/projects.yml new file mode 100644 index 0000000..786c1cb --- /dev/null +++ b/config/_default/pages/projects.yml @@ -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: "#" \ No newline at end of file diff --git a/config/_default/site.yml b/config/_default/site.yml new file mode 100644 index 0000000..a2ea023 --- /dev/null +++ b/config/_default/site.yml @@ -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 \ No newline at end of file diff --git a/src/bookmark-processor.js b/src/bookmark-processor.js index 4bbda16..805239b 100644 --- a/src/bookmark-processor.js +++ b/src/bookmark-processor.js @@ -6,8 +6,12 @@ const yaml = require('js-yaml'); const BOOKMARKS_DIR = 'bookmarks'; // 输出配置文件路径 - 使用相对路径 const OUTPUT_FILE = 'bookmarks.user.yml'; +// 模块化输出配置文件路径 +const MODULAR_OUTPUT_FILE = 'config/user/pages/bookmarks.yml'; // 默认书签配置文件路径 - 使用相对路径 const DEFAULT_BOOKMARKS_FILE = 'bookmarks.yml'; +// 模块化默认书签配置文件路径 +const MODULAR_DEFAULT_BOOKMARKS_FILE = 'config/_default/pages/bookmarks.yml'; // 图标映射,根据URL关键字匹配合适的图标 const ICON_MAPPING = { @@ -209,20 +213,114 @@ ${yamlString}`; // 更新现有config.yml中的导航,添加书签页面 function updateConfigWithBookmarks() { + // 传统配置文件 const configFile = 'config.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 { - 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 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) { + // 确保navigation数组存在 + if (!config.navigation) { + config.navigation = []; + } + // 添加书签页面到导航 config.navigation.push({ name: '书签', @@ -238,11 +336,14 @@ function updateConfigWithBookmarks() { quotingType: '"' }); - fs.writeFileSync(targetConfigFile, updatedYaml, 'utf8'); - console.log(`Updated ${targetConfigFile} with bookmarks navigation`); + fs.writeFileSync(filePath, updatedYaml, 'utf8'); + return true; } + + return false; // 无需更新 } 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() { console.log('Starting bookmark processing...'); 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(); @@ -288,39 +390,58 @@ async function main() { console.log(yaml.split('\n').slice(0, 5).join('\n') + '\n...'); try { - // 确保目标目录存在 + // 确保传统目标目录存在 const outputDir = path.dirname(OUTPUT_FILE); if (!fs.existsSync(outputDir) && outputDir !== '.') { console.log(`Creating output directory: ${outputDir}`); 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'); + // 保存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)) { const stats = fs.statSync(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 permissions: ${stats.mode.toString(8)}`); - - // 列出当前目录内容以确认 - console.log('Current directory contains:'); - fs.readdirSync('.').forEach(file => { - console.log(`- ${file}`); - }); + success = true; } 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); } // 更新导航 updateConfigWithBookmarks(); } catch (writeError) { - console.error(`ERROR writing file ${OUTPUT_FILE}:`, writeError); + console.error(`ERROR writing files:`, writeError); process.exit(1); } } catch (error) { diff --git a/src/generator.js b/src/generator.js index 907b73a..0680cc7 100644 --- a/src/generator.js +++ b/src/generator.js @@ -15,48 +15,199 @@ function escapeHtml(unsafe) { .replace(/'/g, "'"); } -// 读取配置文件 -function loadConfig() { - let config = null; - - try { - // 优先尝试读取用户配置 - if (fs.existsSync('config.user.yml')) { - const userConfigFile = fs.readFileSync('config.user.yml', 'utf8'); - config = yaml.load(userConfigFile); - console.log('Using user configuration from config.user.yml'); - } - // 如果没有用户配置,则使用默认配置 - else { - const defaultConfigFile = fs.readFileSync('config.yml', 'utf8'); - config = yaml.load(defaultConfigFile); - console.log('No user configuration found, using default config.yml'); +/** + * 从文件合并配置到主配置对象 + * @param {Object} config 主配置对象 + * @param {string} filePath 配置文件路径 + */ +function mergeConfigFromFile(config, filePath) { + if (fs.existsSync(filePath)) { + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const fileConfig = yaml.load(fileContent); + + // 提取文件名(不含扩展名)作为配置键 + const configKey = path.basename(filePath, path.extname(filePath)); + + // 如果是site或navigation文件,直接合并到主配置 + 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 { 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'); 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')) { const bookmarksFile = fs.readFileSync('bookmarks.yml', 'utf8'); bookmarksConfig = yaml.load(bookmarksFile); - console.log('Using default bookmarks configuration from bookmarks.yml'); + bookmarksSource = 'bookmarks.yml'; } // 添加书签页面配置 if (bookmarksConfig) { config.bookmarks = bookmarksConfig; + console.log(`Using bookmarks configuration from ${bookmarksSource}`); // 确保导航中有书签页面 const hasBookmarksNav = config.navigation.some(nav => nav.id === 'bookmarks');