diff --git a/config/user/navigation.yml b/config/user/navigation.yml deleted file mode 100644 index aa483df..0000000 --- a/config/user/navigation.yml +++ /dev/null @@ -1,7 +0,0 @@ -- name: 首页 - icon: fas fa-home - id: home - active: true -- name: 书签 - icon: fas fa-bookmark - id: bookmarks \ No newline at end of file diff --git a/config/user/pages/bookmarks.yml b/config/user/pages/bookmarks.yml deleted file mode 100644 index 8e6b00d..0000000 --- a/config/user/pages/bookmarks.yml +++ /dev/null @@ -1,29 +0,0 @@ -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/user/pages/home.yml b/config/user/pages/home.yml deleted file mode 100644 index 8007cd7..0000000 --- a/config/user/pages/home.yml +++ /dev/null @@ -1,100 +0,0 @@ -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: 算法刷题平台 - - name: 开发工具 - icon: fas fa-tools - sites: - - name: VS Code - url: https://code.visualstudio.com - icon: fas fa-code - description: 强大的代码编辑器 - - name: Postman - url: https://www.postman.com - icon: fas fa-paper-plane - description: API调试工具 - - name: Git - url: https://git-scm.com - icon: fab fa-git-alt - description: 版本控制工具 - - name: Docker - url: https://www.docker.com - icon: fab fa-docker - description: 容器化平台 - - name: 设计资源 - icon: fas fa-palette - sites: - - name: Figma - url: https://www.figma.com - icon: fab fa-figma - description: 在线设计工具 - - name: Dribbble - url: https://dribbble.com - icon: fab fa-dribbble - description: 设计师社区 - - name: Behance - url: https://www.behance.net - icon: fab fa-behance - description: 创意设计平台 - - name: IconFont - url: https://www.iconfont.cn - icon: fas fa-icons - description: 图标资源库 - - name: 在线工具 - icon: fas fa-wrench - sites: - - name: JSON Editor - url: https://jsoneditoronline.org - icon: fas fa-code-branch - description: JSON在线编辑器 - - name: Can I Use - url: https://caniuse.com - icon: fas fa-browser - description: 浏览器兼容性查询 - - name: TinyPNG - url: https://tinypng.com - icon: fas fa-compress - description: 图片压缩工具 - - name: Carbon - url: https://carbon.now.sh - icon: fas fa-code - description: 代码图片生成器 \ No newline at end of file diff --git a/config/user/site.yml b/config/user/site.yml deleted file mode 100644 index c6f24e1..0000000 --- a/config/user/site.yml +++ /dev/null @@ -1,38 +0,0 @@ -title: 我的导航 -description: 个人网络导航站 -author: Your Name -favicon: favicon.ico -logo_text: 导航站11 - -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/package-lock.json b/package-lock.json index b12dbeb..3f91664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "ansi-styles": "^6.2.1", "color-convert": "^2.0.1", "color-name": "^2.0.0", + "handlebars": "^4.7.8", "has-flag": "^5.0.1", "js-yaml": "^4.1.0", "mime-db": "^1.52.0", @@ -522,6 +523,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmmirror.com/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-5.0.1.tgz", @@ -708,7 +730,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -731,6 +752,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -966,6 +993,15 @@ "dev": true, "license": "ISC" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -1045,6 +1081,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmmirror.com/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/update-check": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", @@ -1108,6 +1157,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..30d8411 --- /dev/null +++ b/src/README.md @@ -0,0 +1,103 @@ +# MeNav 源代码目录结构 + +## 目录概述 + +`src` 目录包含 MeNav 项目的所有源代码,按照功能和职责进行了明确的分层组织: + +- 根目录:核心功能实现 +- `helpers` 子目录:辅助功能实现 + +## 源代码结构分工 + +### 根目录函数(核心功能) + +根目录下的js文件实现了应用的核心功能,每个文件负责特定领域: + +- **generator.js**: 站点生成器的核心实现 + - 负责将配置文件转换为HTML网站 + - 处理模板渲染、配置解析和文件输出 + - 控制整个生成流程 + +- **script.js**: 客户端功能的核心实现 + - 处理用户界面交互逻辑 + - 实现搜索、导航、主题切换等功能 + - 管理页面状态和响应式布局 + +- **bookmark-processor.js**: 书签处理工具 + - 转换浏览器书签为MeNav配置格式 + - 提供书签导入功能 + +- **migrate-config.js**: 配置迁移工具 + - 处理配置文件格式升级 + - 确保向后兼容性 + +### helpers 目录函数(辅助功能) + +`helpers` 目录下的函数提供辅助性支持,主要服务于Handlebars模板系统: + +- **formatters.js**: 格式化函数 + - 日期格式化 + - 文本处理 + - 内容展示辅助 + +- **conditions.js**: 条件判断函数 + - 比较操作 + - 逻辑运算 + - 条件检查 + +- **utils.js**: 工具函数 + - 数组处理 + - 对象操作 + - 通用辅助方法 + +- **index.js**: 助手函数注册中心 + - 统一导入所有助手函数 + - 提供注册函数到Handlebars实例的方法 + - 定义核心HTML处理函数 + +## 函数职责分工 + +### 核心函数(根目录) + +根目录下的函数负责"做什么"——实现具体的业务逻辑和功能: + +- 应用入口和流程控制 +- 用户交互响应 +- 数据处理和转换 +- 文件生成和输出 + +这些函数通常: +- 直接面向最终用户需求 +- 控制程序主流程 +- 调用辅助函数完成特定任务 + +### 辅助函数(helpers目录) + +helpers目录下的函数负责"怎么做"——提供可重用的工具方法: + +- 为模板渲染提供扩展能力 +- 处理数据格式化和转换 +- 提供通用的条件判断逻辑 +- 实现常见的工具操作 + +这些函数通常: +- 高度重用性和通用性 +- 专注于单一职责 +- 被核心函数调用 + +## 扩展和修改指南 + +### 添加新的核心功能 + +如需添加新的核心功能,应在src根目录创建新的js文件或修改现有文件,确保: +- 文件名清晰反映功能用途 +- 保持单一责任原则 +- 适当调用辅助函数,避免重复实现通用功能 + +### 添加新的辅助函数 + +如需添加新的辅助函数,应在helpers目录下相应文件中添加,并在index.js中注册: +- 按功能类型放入正确的文件(formatters/conditions/utils) +- 编写清晰的JSDoc注释 +- 更新README.md文档 +- 在index.js中添加导出和注册 \ No newline at end of file diff --git a/src/bookmark-processor.js b/src/bookmark-processor.js index 11a2748..9ca4a49 100644 --- a/src/bookmark-processor.js +++ b/src/bookmark-processor.js @@ -74,7 +74,6 @@ function getLatestBookmarkFile() { try { // 确保书签目录存在 if (!fs.existsSync(BOOKMARKS_DIR)) { - console.log('Creating bookmarks directory'); fs.mkdirSync(BOOKMARKS_DIR, { recursive: true }); return null; } @@ -84,7 +83,6 @@ function getLatestBookmarkFile() { .filter(file => file.toLowerCase().endsWith('.html')); if (files.length === 0) { - console.log('No bookmark HTML files found'); return null; } @@ -97,7 +95,6 @@ function getLatestBookmarkFile() { // 找出最新的文件 fileStats.sort((a, b) => b.mtime - a.mtime); const latestFile = fileStats[0].file; - console.log(`Found latest bookmark file: ${latestFile}`); return path.join(BOOKMARKS_DIR, latestFile); } catch (error) { @@ -213,8 +210,6 @@ ${yamlString}`; // 更新导航以包含书签页面 function updateNavigationWithBookmarks() { - console.log('Checking navigation configuration for bookmarks page...'); - // 模块化配置文件 const modularUserNavFile = path.join(CONFIG_USER_DIR, 'navigation.yml'); const modularDefaultNavFile = 'config/_default/navigation.yml'; @@ -226,9 +221,6 @@ function updateNavigationWithBookmarks() { // 1. 首选: 模块化用户导航配置 if (fs.existsSync(modularUserNavFile)) { navigationUpdated = updateNavigationFile(modularUserNavFile); - if (navigationUpdated) { - console.log(`Updated modular user navigation file: ${modularUserNavFile}`); - } } // 2. 其次: 模块化默认导航配置 else if (fs.existsSync(modularDefaultNavFile)) { @@ -245,59 +237,13 @@ function updateNavigationWithBookmarks() { // 写入用户导航文件 fs.writeFileSync(modularUserNavFile, defaultNavContent, 'utf8'); - console.log(`Created user navigation file based on default: ${modularUserNavFile}`); // 更新新创建的文件 navigationUpdated = updateNavigationFile(modularUserNavFile); - if (navigationUpdated) { - console.log(`Updated newly created navigation file: ${modularUserNavFile}`); - } } catch (error) { console.error(`Error creating user navigation file:`, error); } } - // 3. 如果都不存在,创建一个基本的导航文件 - else { - try { - // 确保目录存在 - if (!fs.existsSync(CONFIG_USER_DIR)) { - fs.mkdirSync(CONFIG_USER_DIR, { recursive: true }); - } - - // 创建基本导航配置 - const basicNav = [ - { - name: "首页", - icon: "fas fa-home", - id: "home", - active: true - }, - { - name: "书签", - icon: "fas fa-bookmark", - id: "bookmarks", - active: false - } - ]; - - // 写入用户导航文件 - fs.writeFileSync( - modularUserNavFile, - yaml.dump(basicNav, { indent: 2, lineWidth: -1, quotingType: '"' }), - 'utf8' - ); - console.log(`Created basic navigation file: ${modularUserNavFile}`); - navigationUpdated = true; - } catch (error) { - console.error(`Error creating basic navigation file:`, error); - } - } - - if (!navigationUpdated) { - console.log('Did not update any navigation configuration with bookmarks page'); - } - - return navigationUpdated; } // 更新单个导航配置文件 @@ -344,31 +290,22 @@ function updateNavigationFile(filePath) { // 主函数 async function main() { - console.log('Starting bookmark processing...'); - // 获取最新的书签文件 const bookmarkFile = getLatestBookmarkFile(); if (!bookmarkFile) { - console.log('No bookmark file to process.'); return; } try { // 读取文件内容 - console.log(`Reading bookmark file: ${bookmarkFile}`); const htmlContent = fs.readFileSync(bookmarkFile, 'utf8'); // 解析书签 const bookmarks = parseBookmarks(htmlContent); - console.log(`Found ${bookmarks.categories.length} categories with bookmarks`); if (bookmarks.categories.length === 0) { console.error('ERROR: No bookmark categories found in the HTML file. Processing aborted.'); return; } - console.log('Categories found:'); - bookmarks.categories.forEach(cat => { - console.log(`- ${cat.name}: ${cat.sites.length} sites`); - }); // 生成YAML const yaml = generateBookmarksYaml(bookmarks); @@ -377,35 +314,23 @@ async function main() { return; } - // 显示将要写入的YAML前几行 - console.log('Generated YAML preview (first 5 lines):'); - console.log(yaml.split('\n').slice(0, 5).join('\n') + '\n...'); - try { // 确保目标目录存在 if (!fs.existsSync(CONFIG_USER_PAGES_DIR)) { - console.log(`Creating output directory structure: ${CONFIG_USER_PAGES_DIR}`); fs.mkdirSync(CONFIG_USER_PAGES_DIR, { recursive: true }); } // 保存YAML到模块化位置 - console.log(`Writing bookmarks configuration to: ${MODULAR_OUTPUT_FILE}`); fs.writeFileSync(MODULAR_OUTPUT_FILE, yaml, 'utf8'); // 验证文件是否确实被创建 - if (fs.existsSync(MODULAR_OUTPUT_FILE)) { - const stats = fs.statSync(MODULAR_OUTPUT_FILE); - console.log(`Successfully saved bookmarks configuration (${stats.size} bytes)`); - } else { + if (!fs.existsSync(MODULAR_OUTPUT_FILE)) { console.error(`ERROR: File was not created: ${MODULAR_OUTPUT_FILE}`); process.exit(1); } // 更新导航 updateNavigationWithBookmarks(); - - // 不再删除原始HTML文件,留给GitHub Actions处理 - console.log(`Processing complete. HTML files will be cleaned up by GitHub Actions workflow.`); } catch (writeError) { console.error(`ERROR writing file:`, writeError); process.exit(1); diff --git a/src/generator.js b/src/generator.js index 8dd0c9f..5c90829 100644 --- a/src/generator.js +++ b/src/generator.js @@ -28,7 +28,6 @@ function loadHandlebarsTemplates() { const layoutPath = path.join(layoutsDir, file); const layoutContent = fs.readFileSync(layoutPath, 'utf8'); handlebars.registerPartial(layoutName, layoutContent); - console.log(`Registered layout template: ${layoutName}`); } }); } else { @@ -44,23 +43,45 @@ function loadHandlebarsTemplates() { const componentPath = path.join(componentsDir, file); const componentContent = fs.readFileSync(componentPath, 'utf8'); handlebars.registerPartial(componentName, componentContent); - console.log(`Registered component template: ${componentName}`); } }); } else { throw new Error('Components directory not found. Cannot proceed without component templates.'); } - // 识别并注册默认布局模板 + // 识别并检查默认布局模板是否存在 const defaultLayoutPath = path.join(layoutsDir, 'default.hbs'); - if (fs.existsSync(defaultLayoutPath)) { - console.log('Default layout template found and registered.'); - return true; - } else { + if (!fs.existsSync(defaultLayoutPath)) { throw new Error('Default layout template not found. Cannot proceed without default layout.'); } } +/** + * 获取默认布局模板 + * @returns {Object} 包含模板路径和编译的模板函数 + */ +function getDefaultLayoutTemplate() { + const defaultLayoutPath = path.join(process.cwd(), 'templates', 'layouts', 'default.hbs'); + + // 检查默认布局模板是否存在 + if (!fs.existsSync(defaultLayoutPath)) { + throw new Error('Default layout template not found. Cannot proceed without default layout.'); + } + + try { + // 读取布局内容并编译模板 + const layoutContent = fs.readFileSync(defaultLayoutPath, 'utf8'); + const layoutTemplate = handlebars.compile(layoutContent); + + return { + path: defaultLayoutPath, + template: layoutTemplate + }; + } catch (error) { + throw new Error(`Error loading default layout template: ${error.message}`); + } +} + // 渲染Handlebars模板函数 function renderTemplate(templateName, data, useLayout = true) { const templatePath = path.join(process.cwd(), 'templates', 'pages', `${templateName}.hbs`); @@ -82,23 +103,16 @@ function renderTemplate(templateName, data, useLayout = true) { return pageContent; } - // 使用布局模板 - const defaultLayoutPath = path.join(process.cwd(), 'templates', 'layouts', 'default.hbs'); - if (!fs.existsSync(defaultLayoutPath)) { - throw new Error('Default layout template not found. Cannot proceed without layout.'); - } - try { + // 使用辅助函数获取默认布局模板 + const { template: layoutTemplate } = getDefaultLayoutTemplate(); + // 准备布局数据,包含页面内容 const layoutData = { ...data, body: pageContent }; - // 加载默认布局模板 - const layoutContent = fs.readFileSync(defaultLayoutPath, 'utf8'); - const layoutTemplate = handlebars.compile(layoutContent); - // 渲染完整页面 return layoutTemplate(layoutData); } catch (layoutError) { @@ -123,23 +137,31 @@ function escapeHtml(unsafe) { } /** - * 加载单个配置文件 + * 统一处理配置文件加载错误 + * @param {string} filePath 配置文件路径 + * @param {Error} error 错误对象 + */ +function handleConfigLoadError(filePath, error) { + console.error(`Error loading configuration from ${filePath}:`, error); +} + +/** + * 安全地加载YAML配置文件 * @param {string} filePath 配置文件路径 * @returns {Object|null} 配置对象,如果文件不存在或加载失败则返回null */ -function loadSingleConfig(filePath) { - if (fs.existsSync(filePath)) { - try { - const fileContent = fs.readFileSync(filePath, 'utf8'); - const fileConfig = yaml.load(fileContent); - console.log(`Loaded configuration from ${filePath}`); - return fileConfig; - } catch (e) { - console.error(`Error loading configuration from ${filePath}:`, e); - return null; - } - } +function safeLoadYamlConfig(filePath) { + if (!fs.existsSync(filePath)) { return null; + } + + try { + const fileContent = fs.readFileSync(filePath, 'utf8'); + return yaml.load(fileContent); + } catch (error) { + handleConfigLoadError(filePath, error); + return null; + } } /** @@ -163,34 +185,22 @@ function loadModularConfig(dirPath) { // 加载基础配置 const siteConfigPath = path.join(dirPath, 'site.yml'); - if (fs.existsSync(siteConfigPath)) { - try { - const fileContent = fs.readFileSync(siteConfigPath, 'utf8'); - const siteConfig = yaml.load(fileContent); - - // 将site.yml中的内容分配到正确的配置字段 - config.site = siteConfig; - - // 提取特殊字段到顶层配置 - if (siteConfig.fonts) config.fonts = siteConfig.fonts; - if (siteConfig.profile) config.profile = siteConfig.profile; - if (siteConfig.social) config.social = siteConfig.social; - - console.log(`Loaded site configuration from ${siteConfigPath}`); - } catch (e) { - console.error(`Error loading site configuration from ${siteConfigPath}:`, e); - } + const siteConfig = safeLoadYamlConfig(siteConfigPath); + if (siteConfig) { + // 将site.yml中的内容分配到正确的配置字段 + config.site = siteConfig; + + // 提取特殊字段到顶层配置 + if (siteConfig.fonts) config.fonts = siteConfig.fonts; + if (siteConfig.profile) config.profile = siteConfig.profile; + if (siteConfig.social) config.social = siteConfig.social; } + // 加载导航配置 const navConfigPath = path.join(dirPath, 'navigation.yml'); - if (fs.existsSync(navConfigPath)) { - try { - const fileContent = fs.readFileSync(navConfigPath, 'utf8'); - config.navigation = yaml.load(fileContent); - console.log(`Loaded navigation configuration from ${navConfigPath}`); - } catch (e) { - console.error(`Error loading navigation configuration from ${navConfigPath}:`, e); - } + const navConfig = safeLoadYamlConfig(navConfigPath); + if (navConfig) { + config.navigation = navConfig; } // 加载页面配置 @@ -200,11 +210,10 @@ function loadModularConfig(dirPath) { file.endsWith('.yml') || file.endsWith('.yaml')); files.forEach(file => { - try { - const filePath = path.join(pagesPath, file); - const fileContent = fs.readFileSync(filePath, 'utf8'); - const fileConfig = yaml.load(fileContent); - + const filePath = path.join(pagesPath, file); + const fileConfig = safeLoadYamlConfig(filePath); + + if (fileConfig) { // 提取文件名(不含扩展名)作为配置键 const configKey = path.basename(file, path.extname(file)); @@ -215,10 +224,6 @@ function loadModularConfig(dirPath) { // 将页面配置添加到主配置对象 config[configKey] = fileConfig; - - console.log(`Loaded page configuration from ${filePath}`); - } catch (e) { - console.error(`Error loading page configuration from ${path.join(pagesPath, file)}:`, e); } }); } @@ -254,15 +259,6 @@ function ensureConfigDefaults(config) { modeToggle: true }; - // 确保主题颜色设置存在 - if (!result.site.theme) { - result.site.theme = { - primary: '#4a89dc', - background: '#f5f7fa', - modeToggle: true - }; - } - // 用户资料默认值 result.profile = result.profile || {}; result.profile.title = result.profile.title || '欢迎使用'; @@ -300,42 +296,51 @@ function validateConfig(config) { return false; } - // 检查必要的顶级属性 - if (!config.site) { - console.warn('配置警告: 缺少site配置节点'); - } - - // 导航配置检查 - if (!Array.isArray(config.navigation)) { - console.warn('配置警告: navigation不是数组类型'); - } - - // 验证分类和站点结构 - if (!Array.isArray(config.categories)) { - console.warn('配置警告: categories不是数组类型'); - } else { - // 检查分类结构 - config.categories.forEach((category, index) => { - if (!category.name) { - console.warn(`配置警告: 第${index+1}个分类没有name属性`); - } - - if (!Array.isArray(category.sites)) { - console.warn(`配置警告: 分类 "${category.name || `#${index+1}`}" 的sites不是数组类型`); - } else { - // 检查站点URL - category.sites.forEach((site, siteIndex) => { - if (!site.url) { - console.warn(`配置警告: 分类 "${category.name || `#${index+1}`}" 中第${siteIndex+1}个站点缺少url属性`); - } - }); - } - }); - } + // 所有其他验证被移除,因为它们只是检查但没有实际操作 + // 配置默认值和数据修复已经在ensureConfigDefaults函数中处理 return true; } +/** + * 获取导航项的子菜单数据 + * @param {Object} navItem 导航项对象 + * @param {Object} config 配置对象 + * @returns {Array|null} 子菜单数据数组或null + */ +function getSubmenuForNavItem(navItem, config) { + if (!navItem || !navItem.id || !config) { + return null; + } + + // 首页页面添加子菜单(分类) + if (navItem.id === 'home' && Array.isArray(config.categories)) { + return config.categories; + } + // 书签页面添加子菜单(分类) + else if (navItem.id === 'bookmarks' && config.bookmarks && Array.isArray(config.bookmarks.categories)) { + return config.bookmarks.categories; + } + // 项目页面添加子菜单 + else if (navItem.id === 'projects' && config.projects && Array.isArray(config.projects.categories)) { + return config.projects.categories; + } + // 文章页面添加子菜单 + else if (navItem.id === 'articles' && config.articles && Array.isArray(config.articles.categories)) { + return config.articles.categories; + } + // 友链页面添加子菜单 + else if (navItem.id === 'friends' && config.friends && Array.isArray(config.friends.categories)) { + return config.friends.categories; + } + // 通用处理:任意自定义页面的子菜单生成 + else if (config[navItem.id] && config[navItem.id].categories && Array.isArray(config[navItem.id].categories)) { + return config[navItem.id].categories; + } + + return null; +} + /** * 准备渲染数据,添加模板所需的特殊属性 * @param {Object} config 配置对象 @@ -355,7 +360,7 @@ function prepareRenderData(config) { // 确保navigation是数组 if (!Array.isArray(renderData.navigation)) { renderData.navigation = []; - console.warn('Warning: navigation is not an array. Using empty array instead.'); + // 移除警告日志,数据处理逻辑保留 } // 添加导航项的活动状态标记和子菜单 @@ -368,30 +373,10 @@ function prepareRenderData(config) { active: index === 0 // 兼容原有逻辑 }; - // 为导航项添加子菜单 - // 首页页面添加子菜单(分类) - if (item.id === 'home' && Array.isArray(renderData.categories)) { - navItem.submenu = renderData.categories; - } - // 书签页面添加子菜单(分类) - else if (item.id === 'bookmarks' && renderData.bookmarks && Array.isArray(renderData.bookmarks.categories)) { - navItem.submenu = renderData.bookmarks.categories; - } - // 项目页面添加子菜单 - else if (item.id === 'projects' && renderData.projects && Array.isArray(renderData.projects.categories)) { - navItem.submenu = renderData.projects.categories; - } - // 文章页面添加子菜单 - else if (item.id === 'articles' && renderData.articles && Array.isArray(renderData.articles.categories)) { - navItem.submenu = renderData.articles.categories; - } - // 友链页面添加子菜单 - else if (item.id === 'friends' && renderData.friends && Array.isArray(renderData.friends.categories)) { - navItem.submenu = renderData.friends.categories; - } - // 通用处理:任意自定义页面的子菜单生成 - else if (renderData[item.id] && renderData[item.id].categories && Array.isArray(renderData[item.id].categories)) { - navItem.submenu = renderData[item.id].categories; + // 使用辅助函数获取子菜单 + const submenu = getSubmenuForNavItem(navItem, renderData); + if (submenu) { + navItem.submenu = submenu; } return navItem; @@ -428,22 +413,18 @@ function loadConfig() { // 根据优先级顺序选择最高优先级的配置 if (hasUserModularConfig) { // 1. 最高优先级: config/user/ 目录 - console.log('Using modular user configuration from config/user/ (highest priority)'); config = loadModularConfig('config/user'); } else if (hasDefaultModularConfig) { // 2. 次高优先级: config/_default/ 目录 - console.log('Using modular default configuration from config/_default/'); config = loadModularConfig('config/_default'); } else { // 3. 最低优先级: 旧版单文件配置 (config.yml or config.yaml) - console.log('Using legacy single-file configuration'); const legacyConfigPath = fs.existsSync('config.yml') ? 'config.yml' : 'config.yaml'; if (fs.existsSync(legacyConfigPath)) { try { const fileContent = fs.readFileSync(legacyConfigPath, 'utf8'); config = yaml.load(fileContent); - console.log(`Loaded legacy configuration from ${legacyConfigPath}`); } catch (e) { console.error(`Error loading configuration from ${legacyConfigPath}:`, e); } @@ -457,7 +438,7 @@ function loadConfig() { config = ensureConfigDefaults(config); if (!validateConfig(config)) { - console.warn('Configuration validation warnings found. Continuing with defaults.'); + // 移除警告日志,保留函数调用 } // 准备渲染数据 @@ -471,72 +452,15 @@ function generateNavigation(navigation, config) { return navigation.map(nav => { // 根据页面ID获取对应的子菜单项(分类) let submenuItems = ''; - - // 首页页面添加子菜单(分类) - if (nav.id === 'home' && Array.isArray(config.categories)) { + + // 使用辅助函数获取子菜单数据 + const submenu = getSubmenuForNavItem(nav, config); + + // 如果存在子菜单,生成HTML + if (submenu && Array.isArray(submenu)) { submenuItems = `
`; - } - // 书签页面添加子菜单(分类) - else if (nav.id === 'bookmarks' && config.bookmarks && Array.isArray(config.bookmarks.categories)) { - submenuItems = ` - `; - } - // 项目页面添加子菜单 - else if (nav.id === 'projects' && config.projects && Array.isArray(config.projects.categories)) { - submenuItems = ` - `; - } - // 文章页面添加子菜单 - else if (nav.id === 'articles' && config.articles && Array.isArray(config.articles.categories)) { - submenuItems = ` - `; - } - // 友链页面添加子菜单 - else if (nav.id === 'friends' && config.friends && Array.isArray(config.friends.categories)) { - submenuItems = ` - `; - } - // 通用处理:任意自定义页面的子菜单生成 - else if (config[nav.id] && config[nav.id].categories && Array.isArray(config[nav.id].categories)) { - submenuItems = ` - `; } - // 设置默认值 - const title = data.title || `${pageId} 页面`; - const subtitle = data.subtitle || ''; - const categories = data.categories || []; - - // 如果是书签页面,使用bookmarks配置 - if (pageId === 'bookmarks') { + // 首页使用profile数据,其他页面使用自身数据 + if (pageId === 'home') { + const profile = data.profile || {}; + + return ` +${escapeHtml(profile.description || '快速访问您的常用网站')}
+${escapeHtml(subtitle)}
-暂无数据
+{{else}} +{{profile.description}}
+{{description}}
+ +{{/if}} +``` + +## 模板数据流 + +MeNav 模板系统的数据流如下: + +1. `generator.js` 加载配置文件并处理数据 +2. 数据通过 Handlebars 上下文传递给模板 +3. 布局模板 (`layouts/default.hbs`) 作为外层容器 +4. 页面模板 (`pages/*.hbs`) 填充布局中的内容区域 +5. 组件模板 (`components/*.hbs`) 在页面中通过 `{{> component-name}}` 引用 + +主要数据对象: +- `site` - 网站配置信息 +- `navigationData` - 导航菜单数据 +- `categories` - 分类和站点数据 +- `profile` - 个人资料数据 +- `social` - 社交链接数据 + +## 模板使用示例 + +### 引用组件 + +在页面或其他组件中引用组件: + +```handlebars +{{> navigation navigationData}} +{{> site-card}} +``` + +### 条件渲染 + +根据条件显示内容: + +```handlebars +{{#if profile.title}} +{{about.description}}
+ + {{#if about.skills}} +