From 7a7bf361e35114f969cf95d36f38f72de89913c3 Mon Sep 17 00:00:00 2001 From: rbetree Date: Mon, 22 Dec 2025 00:19:44 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=B9=E9=BD=90=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E5=B9=B6=E5=8A=A0=E5=9B=BA=E4=B9=A6=E7=AD=BE?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) src/bookmark-processor.js:加固书签导入 - config/user/ 不存在时先从 config/_default/ 初始化一套完整用户配置(符合“完全替换”策略) - 优先更新 config/user/site.yml 的 navigation,确保包含 id: bookmarks;失败再兼容旧 navigation.yml - 无 .html 书签文件时由报错改为警告并跳过,不阻塞流程 - 导航更新日志按结果输出,不再无条件“已更新” 2) config/README.md:修正文档 - 明确配置加载为“完全替换、不合并” - 补充首次使用建议:复制 config/_default/ 到 config/user/ 3) package.json:许可证对齐 - license 从 MIT 改为 AGPL-3.0-only(与 LICENSE/README 对齐) 4) .gitignore:忽略工具目录 - 新增忽略 .serena/ --- .gitignore | 1 + config/README.md | 14 ++- package.json | 2 +- src/bookmark-processor.js | 181 +++++++++++++++++++++++++++++++------- 4 files changed, 159 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index b72fca8..1047664 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist/ .cursorindexingignore .cursor .spec-workflow/ +.serena/ # 系统文件 .DS_Store diff --git a/config/README.md b/config/README.md index ef8d8e8..1295416 100644 --- a/config/README.md +++ b/config/README.md @@ -39,14 +39,12 @@ config/ ## 配置加载机制 -MeNav 配置系统使用深度合并机制,按以下顺序加载和合并配置: +MeNav 配置系统采用“完全替换”策略(不合并),按以下优先级选择**唯一**的一套配置目录: -1. 加载 `_default` 目录中的所有配置(基础层) -2. 加载 `user` 目录中的配置(如果存在,覆盖同名配置项) -3. 深度合并所有配置,确保用户配置优先级高于默认配置 -4. 应用最终合并后的配置生成网站 +1. 若存在 `config/user/`,则只加载该目录下的配置,并**完全忽略** `config/_default/` +2. 否则加载 `config/_default/` 作为默认配置 -这种机制使用户只需配置想要自定义的部分,其余部分由默认配置提供。 +也就是说:`config/user/` 一旦存在,就需要包含一套完整的配置(例如 `site.yml` 与必要的 `pages/*.yml`),系统不会把缺失部分从默认配置补齐。 ## 模块化配置文件 @@ -170,10 +168,10 @@ categories: 3. **配置管理**: - 利用模块化结构分类管理配置 - - 只覆盖需要自定义的配置项 + - 首次使用建议先完整复制 `config/_default/` 到 `config/user/`,再按需修改 - 定期备份您的用户配置 4. **配置验证**: - 修改配置后先在本地构建测试 - 使用 `npm run dev` 预览更改效果 - - 确保 YAML 语法正确无误 \ No newline at end of file + - 确保 YAML 语法正确无误 diff --git a/package.json b/package.json index b4ff137..0e5c5ad 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "personal" ], "author": "Your Name", - "license": "MIT", + "license": "AGPL-3.0-only", "dependencies": { "js-yaml": "^4.1.0", "handlebars": "^4.7.8", diff --git a/src/bookmark-processor.js b/src/bookmark-processor.js index ba1b368..178d3c6 100644 --- a/src/bookmark-processor.js +++ b/src/bookmark-processor.js @@ -6,6 +6,8 @@ const yaml = require('js-yaml'); const BOOKMARKS_DIR = 'bookmarks'; // 模块化配置目录 const CONFIG_USER_DIR = 'config/user'; +// 模块化默认配置目录 +const CONFIG_DEFAULT_DIR = 'config/_default'; // 模块化页面配置目录 const CONFIG_USER_PAGES_DIR = path.join(CONFIG_USER_DIR, 'pages'); // 模块化输出配置文件路径 @@ -13,6 +15,109 @@ const MODULAR_OUTPUT_FILE = path.join(CONFIG_USER_PAGES_DIR, 'bookmarks.yml'); // 模块化默认书签配置文件路径 const MODULAR_DEFAULT_BOOKMARKS_FILE = 'config/_default/pages/bookmarks.yml'; +const USER_SITE_YML = path.join(CONFIG_USER_DIR, 'site.yml'); +const DEFAULT_SITE_YML = path.join(CONFIG_DEFAULT_DIR, 'site.yml'); +const LEGACY_USER_NAV_YML = path.join(CONFIG_USER_DIR, 'navigation.yml'); +const LEGACY_DEFAULT_NAV_YML = path.join(CONFIG_DEFAULT_DIR, 'navigation.yml'); + +function ensureUserConfigInitialized() { + if (fs.existsSync(CONFIG_USER_DIR)) { + return { initialized: false, source: 'existing' }; + } + + if (fs.existsSync(CONFIG_DEFAULT_DIR)) { + fs.cpSync(CONFIG_DEFAULT_DIR, CONFIG_USER_DIR, { recursive: true }); + console.log('[INFO] 检测到 config/user 不存在,已从 config/_default 初始化用户配置(完全替换策略需要完整配置)。'); + return { initialized: true, source: '_default' }; + } + + fs.mkdirSync(CONFIG_USER_DIR, { recursive: true }); + console.log('[WARN] 未找到默认配置目录 config/_default,已创建空的 config/user 目录;建议补齐 site.yml 与 pages/*.yml。'); + return { initialized: true, source: 'empty' }; +} + +function ensureUserSiteYmlExists() { + if (fs.existsSync(USER_SITE_YML)) { + return true; + } + + if (fs.existsSync(DEFAULT_SITE_YML)) { + if (!fs.existsSync(CONFIG_USER_DIR)) { + fs.mkdirSync(CONFIG_USER_DIR, { recursive: true }); + } + fs.copyFileSync(DEFAULT_SITE_YML, USER_SITE_YML); + console.log('[INFO] 未找到 config/user/site.yml,已从 config/_default/site.yml 复制一份。'); + return true; + } + + console.log('[WARN] 未找到可用的 site.yml,无法自动更新导航;请手动在 config/user/site.yml 添加 navigation(含 id: bookmarks)。'); + return false; +} + +function upsertBookmarksNavInSiteYml(siteYmlPath) { + try { + const raw = fs.readFileSync(siteYmlPath, 'utf8'); + const loaded = yaml.load(raw); + + if (!loaded || typeof loaded !== 'object') { + return { updated: false, reason: 'site_yml_not_object' }; + } + + const navigation = loaded.navigation; + + if (Array.isArray(navigation) && navigation.some(item => item && item.id === 'bookmarks')) { + return { updated: false, reason: 'already_present' }; + } + + if (navigation !== undefined && !Array.isArray(navigation)) { + return { updated: false, reason: 'navigation_not_array' }; + } + + const lines = raw.split(/\r?\n/); + const navLineIndex = lines.findIndex(line => /^navigation\s*:/.test(line)); + + const itemIndent = ' '; + const propIndent = `${itemIndent} `; + const snippet = [ + `${itemIndent}- name: 书签`, + `${propIndent}icon: fas fa-bookmark`, + `${propIndent}id: bookmarks`, + ]; + + if (navLineIndex === -1) { + // 不存在 navigation 段:直接追加一个新的块(尽量不破坏原文件结构) + const normalized = raw.endsWith('\n') ? raw : `${raw}\n`; + const spacer = normalized.trim().length === 0 ? '' : '\n'; + const updatedRaw = `${normalized}${spacer}navigation:\n${snippet.join('\n')}\n`; + fs.writeFileSync(siteYmlPath, updatedRaw, 'utf8'); + return { updated: true, reason: 'added_navigation_block' }; + } + + // 找到 navigation 块末尾(遇到下一个顶层 key 时结束) + let insertAt = lines.length; + for (let i = navLineIndex + 1; i < lines.length; i++) { + const line = lines[i]; + if (line.trim() === '' || /^\s*#/.test(line)) continue; + if (/^[A-Za-z0-9_-]+\s*:/.test(line)) { + insertAt = i; + break; + } + } + + const updatedLines = [...lines]; + // 在块末尾插入,确保块内至少有一个空行分隔更易读 + if (insertAt > 0 && updatedLines[insertAt - 1].trim() !== '') { + snippet.unshift(''); + } + updatedLines.splice(insertAt, 0, ...snippet); + + fs.writeFileSync(siteYmlPath, `${updatedLines.join('\n')}\n`, 'utf8'); + return { updated: true, reason: 'updated_navigation_block' }; + } catch (error) { + return { updated: false, reason: 'error', error }; + } +} + // 图标映射,根据URL关键字匹配合适的图标 const ICON_MAPPING = { 'github.com': 'fab fa-github', @@ -627,40 +732,42 @@ ${yamlString}`; // 更新导航以包含书签页面 function updateNavigationWithBookmarks() { - // 模块化配置文件 - const modularUserNavFile = path.join(CONFIG_USER_DIR, 'navigation.yml'); - const modularDefaultNavFile = 'config/_default/navigation.yml'; - - let navigationUpdated = false; - - // 按优先级顺序尝试更新导航配置 - - // 1. 首选: 模块化用户导航配置 - if (fs.existsSync(modularUserNavFile)) { - navigationUpdated = updateNavigationFile(modularUserNavFile); + // 1) 优先更新 site.yml(当前推荐方式) + if (ensureUserSiteYmlExists()) { + const result = upsertBookmarksNavInSiteYml(USER_SITE_YML); + if (result.updated) { + return { updated: true, target: 'site.yml', reason: result.reason }; + } + if (result.reason === 'already_present') { + return { updated: false, target: 'site.yml', reason: 'already_present' }; + } + if (result.reason === 'error') { + return { updated: false, target: 'site.yml', reason: 'error', error: result.error }; + } + // 如果 site.yml 无法更新(如 navigation 格式异常),继续尝试旧版 navigation.yml } - // 2. 其次: 模块化默认导航配置 - else if (fs.existsSync(modularDefaultNavFile)) { - // 如果用户导航文件不存在,我们需要先创建它,然后基于默认文件更新 + + // 2) 兼容旧版:独立 navigation.yml + if (fs.existsSync(LEGACY_USER_NAV_YML)) { + const updated = updateNavigationFile(LEGACY_USER_NAV_YML); + return { updated, target: 'navigation.yml', reason: updated ? 'updated' : 'already_present' }; + } + + if (fs.existsSync(LEGACY_DEFAULT_NAV_YML)) { try { - // 读取默认导航文件 - const defaultNavContent = fs.readFileSync(modularDefaultNavFile, 'utf8'); - const defaultNav = yaml.load(defaultNavContent); - - // 确保目录存在 + const defaultNavContent = fs.readFileSync(LEGACY_DEFAULT_NAV_YML, 'utf8'); if (!fs.existsSync(CONFIG_USER_DIR)) { fs.mkdirSync(CONFIG_USER_DIR, { recursive: true }); } - - // 写入用户导航文件 - fs.writeFileSync(modularUserNavFile, defaultNavContent, 'utf8'); - - // 更新新创建的文件 - navigationUpdated = updateNavigationFile(modularUserNavFile); + fs.writeFileSync(LEGACY_USER_NAV_YML, defaultNavContent, 'utf8'); + const updated = updateNavigationFile(LEGACY_USER_NAV_YML); + return { updated, target: 'navigation.yml', reason: updated ? 'updated' : 'already_present' }; } catch (error) { - console.error(`Error creating user navigation file:`, error); + return { updated: false, target: 'navigation.yml', reason: 'error', error }; } } + + return { updated: false, target: null, reason: 'no_navigation_config' }; } // 更新单个导航配置文件 @@ -716,7 +823,7 @@ async function main() { console.log('[步骤 1/5] 查找书签文件...'); const bookmarkFile = getLatestBookmarkFile(); if (!bookmarkFile) { - console.log('[ERROR] 未找到书签文件,处理终止'); + console.log('[WARN] 未找到书签文件,已跳过处理(将HTML书签文件放入 bookmarks/ 后再运行)。'); return; } console.log('[SUCCESS] 找到书签文件\n'); @@ -748,6 +855,9 @@ async function main() { // 保存文件 console.log('[步骤 5/5] 保存配置文件...'); try { + // 完全替换策略:若尚未初始化用户配置,则先从默认配置初始化一份完整配置 + ensureUserConfigInitialized(); + // 确保目标目录存在 if (!fs.existsSync(CONFIG_USER_PAGES_DIR)) { fs.mkdirSync(CONFIG_USER_PAGES_DIR, { recursive: true }); @@ -767,8 +877,19 @@ async function main() { // 更新导航 console.log('[附加步骤] 更新导航配置...'); - updateNavigationWithBookmarks(); - console.log('[SUCCESS] 导航配置已更新\n'); + const navUpdateResult = updateNavigationWithBookmarks(); + if (navUpdateResult.updated) { + console.log(`[SUCCESS] 导航配置已更新(${navUpdateResult.target})\n`); + } else if (navUpdateResult.reason === 'already_present') { + console.log('[INFO] 导航配置已包含书签入口,无需更新\n'); + } else if (navUpdateResult.reason === 'no_navigation_config') { + console.log('[WARN] 未找到可更新的导航配置文件;请确认 config/user/site.yml 存在且包含 navigation\n'); + } else if (navUpdateResult.reason === 'error') { + console.log('[WARN] 导航更新失败,请手动检查配置文件格式(详见错误信息)\n'); + console.error(navUpdateResult.error); + } else { + console.log('[INFO] 导航配置无需更新\n'); + } } catch (writeError) { console.error(`[ERROR] 写入文件时出错:`, writeError); @@ -793,4 +914,4 @@ if (require.main === module) { console.error('Unhandled error in bookmark processing:', err); process.exit(1); }); -} \ No newline at end of file +}