From 89c1c0330bf97e9d2812ada2d753151ea15717af Mon Sep 17 00:00:00 2001 From: rbetree Date: Fri, 16 Jan 2026 02:25:03 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E7=BB=9F=E4=B8=80=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 ConfigError/TemplateError/BuildError/FileError 与 wrapAsyncError,统一错误输出 - generator 入口接入 wrapAsyncError,确保命令行执行路径一致 - 兜底逻辑使用 instanceof,保留 BuildError/TemplateError 上下文信息 - 合并格式化提交(仅缩进/换行调整) --- .github/workflows/deploy.yml | 1 - scripts/build-runtime.js | 9 +- src/bookmark-processor.js | 34 +++-- src/generator.js | 4 +- src/generator/cache/articles.js | 1 - src/generator/cache/projects.js | 5 +- src/generator/config/index.js | 33 +++-- src/generator/config/resolver.js | 3 +- src/generator/html/404.js | 3 +- src/generator/html/components.js | 1 - src/generator/html/page-data.js | 18 ++- src/generator/main.js | 50 +++++-- src/generator/utils/errors.js | 127 ++++++++++++++++++ src/generator/utils/html.js | 1 - src/generator/utils/pageMeta.js | 1 - src/generator/utils/sites.js | 4 +- src/runtime/app/index.js | 10 +- src/runtime/app/routing.js | 14 +- src/runtime/app/search.js | 19 ++- src/runtime/app/search/highlight.js | 5 +- src/runtime/app/searchEngines.js | 1 - src/runtime/app/ui.js | 1 - src/runtime/index.js | 1 - src/runtime/menav/addElement.js | 36 +++-- src/runtime/menav/index.js | 3 +- src/runtime/menav/updateElement.js | 5 +- src/runtime/nested/index.js | 4 +- src/runtime/shared.js | 1 - src/runtime/tooltip.js | 1 - test/404-fallback.node-test.js | 1 - ...p1-7-extension-config-minimal.node-test.js | 5 +- 31 files changed, 313 insertions(+), 89 deletions(-) create mode 100644 src/generator/utils/errors.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 66fccfa..f1fd3e0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -106,7 +106,6 @@ jobs: git commit -m "chore(bookmarks): 导入书签并写回用户配置" git push "https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }}.git" HEAD:${{ github.ref_name }} # --- 书签处理步骤结束 --- - # --- 网站构建和部署步骤 --- # 同步时效性数据(best-effort):projects 仓库信息、articles RSS 聚合 # 说明: diff --git a/scripts/build-runtime.js b/scripts/build-runtime.js index ad9b8c4..9ba2dea 100644 --- a/scripts/build-runtime.js +++ b/scripts/build-runtime.js @@ -44,7 +44,8 @@ async function main() { logLevel: 'info', }); - const outputs = result && result.metafile && result.metafile.outputs ? result.metafile.outputs : null; + const outputs = + result && result.metafile && result.metafile.outputs ? result.metafile.outputs : null; const outKey = outputs ? Object.keys(outputs).find((k) => k.endsWith('dist/script.js')) : ''; const bytes = outKey && outputs && outputs[outKey] ? outputs[outKey].bytes : 0; if (bytes) { @@ -53,10 +54,12 @@ async function main() { console.log('✅ runtime bundle 完成:dist/script.js'); } } catch (error) { - console.error('❌ runtime bundle 失败(禁止回退旧产物):', error && error.message ? error.message : error); + console.error( + '❌ runtime bundle 失败(禁止回退旧产物):', + error && error.message ? error.message : error + ); process.exitCode = 1; } } main(); - diff --git a/src/bookmark-processor.js b/src/bookmark-processor.js index 62e817d..ccf7bb3 100644 --- a/src/bookmark-processor.js +++ b/src/bookmark-processor.js @@ -1,6 +1,7 @@ const fs = require('fs'); const path = require('path'); const yaml = require('js-yaml'); +const { FileError, wrapAsyncError } = require('./generator/utils/errors'); // 书签文件夹路径 - 使用相对路径 const BOOKMARKS_DIR = 'bookmarks'; @@ -821,8 +822,11 @@ async function main() { // 验证文件是否确实被创建 if (!fs.existsSync(MODULAR_OUTPUT_FILE)) { - console.error(`[ERROR] 文件未能创建: ${MODULAR_OUTPUT_FILE}`); - process.exit(1); + throw new FileError('文件未能创建', MODULAR_OUTPUT_FILE, [ + '检查目录权限是否正确', + '确认磁盘空间是否充足', + '尝试手动创建目录: mkdir -p config/user/pages', + ]); } console.log('[SUCCESS] 文件保存成功'); @@ -846,27 +850,33 @@ async function main() { console.log('[INFO] 导航配置无需更新\n'); } } catch (writeError) { - console.error(`[ERROR] 写入文件时出错:`, writeError); - console.error('[ERROR] 错误堆栈:', writeError.stack); - process.exit(1); + throw new FileError('写入文件时出错', MODULAR_OUTPUT_FILE, [ + '检查文件路径是否正确', + '确认目录权限是否正确', + `错误详情: ${writeError.message}`, + ]); } console.log('========================================'); console.log('[SUCCESS] 书签处理完成!'); console.log('========================================'); } catch (error) { - console.error('[FATAL] 处理书签文件时发生错误:', error); - console.error('[ERROR] 错误堆栈:', error.stack); - process.exit(1); + // 如果是自定义错误,直接抛出 + if (error instanceof FileError) { + throw error; + } + // 否则包装为 FileError + throw new FileError('处理书签文件时发生错误', null, [ + '检查书签 HTML 文件格式是否正确', + '确认配置目录结构是否完整', + `错误详情: ${error.message}`, + ]); } } // 启动处理 if (require.main === module) { - main().catch((err) => { - console.error('Unhandled error in bookmark processing:', err); - process.exit(1); - }); + wrapAsyncError(main)(); } module.exports = { diff --git a/src/generator.js b/src/generator.js index df567e2..7213ed1 100644 --- a/src/generator.js +++ b/src/generator.js @@ -1,14 +1,14 @@ // 生成端薄入口:保持对外导出稳定,内部实现位于 src/generator/main.js const impl = require('./generator/main'); +const { wrapAsyncError } = require('./generator/utils/errors'); module.exports = impl; if (require.main === module) { if (typeof impl.main === 'function') { - impl.main(); + wrapAsyncError(impl.main)(); } else { console.error('generator main() 未导出,无法直接执行。'); process.exitCode = 1; } } - diff --git a/src/generator/cache/articles.js b/src/generator/cache/articles.js index e582a9f..e62a7f2 100644 --- a/src/generator/cache/articles.js +++ b/src/generator/cache/articles.js @@ -156,4 +156,3 @@ module.exports = { tryLoadArticlesFeedCache, buildArticlesCategoriesByPageCategories, }; - diff --git a/src/generator/cache/projects.js b/src/generator/cache/projects.js index cf211b8..41dcaff 100644 --- a/src/generator/cache/projects.js +++ b/src/generator/cache/projects.js @@ -4,7 +4,9 @@ const path = require('path'); function tryLoadProjectsRepoCache(pageId, config) { if (!pageId) return null; - const cacheDirFromEnv = process.env.PROJECTS_CACHE_DIR ? String(process.env.PROJECTS_CACHE_DIR) : ''; + const cacheDirFromEnv = process.env.PROJECTS_CACHE_DIR + ? String(process.env.PROJECTS_CACHE_DIR) + : ''; const cacheDirFromConfig = config && config.site && config.site.github && config.site.github.cacheDir ? String(config.site.github.cacheDir) @@ -132,4 +134,3 @@ module.exports = { applyRepoMetaToCategories, buildProjectsMeta, }; - diff --git a/src/generator/config/index.js b/src/generator/config/index.js index a04c774..a466907 100644 --- a/src/generator/config/index.js +++ b/src/generator/config/index.js @@ -1,8 +1,15 @@ const fs = require('node:fs'); const { loadModularConfig } = require('./loader'); const { ensureConfigDefaults, validateConfig } = require('./validator'); -const { prepareRenderData, MENAV_EXTENSION_CONFIG_FILE, getSubmenuForNavItem, resolveTemplateNameForPage, buildExtensionConfig } = require('./resolver'); +const { + prepareRenderData, + MENAV_EXTENSION_CONFIG_FILE, + getSubmenuForNavItem, + resolveTemplateNameForPage, + buildExtensionConfig, +} = require('./resolver'); const { assignCategorySlugs } = require('./slugs'); +const { ConfigError } = require('../utils/errors'); function loadConfig() { let config = { @@ -18,16 +25,17 @@ function loadConfig() { if (hasUserModularConfig) { if (!fs.existsSync('config/user/site.yml')) { - console.error('[ERROR] 检测到 config/user/ 目录,但缺少 config/user/site.yml。'); - console.error( - '[ERROR] 由于配置采用"完全替换"策略,系统不会从 config/_default/ 补齐缺失配置。' - ); - console.error('[ERROR] 解决方法:先完整复制 config/_default/ 到 config/user/,再按需修改。'); - process.exit(1); + throw new ConfigError('检测到 config/user/ 目录,但缺少 config/user/site.yml', [ + '由于配置采用"完全替换"策略,系统不会从 config/_default/ 补齐缺失配置', + '解决方法:先完整复制 config/_default/ 到 config/user/,再按需修改', + '参考文档: config/README.md', + ]); } if (!fs.existsSync('config/user/pages')) { - console.warn('[WARN] 检测到 config/user/ 目录,但缺少 config/user/pages/。部分页面内容可能为空。'); + console.warn( + '[WARN] 检测到 config/user/ 目录,但缺少 config/user/pages/。部分页面内容可能为空。' + ); console.warn('[WARN] 建议:复制 config/_default/pages/ 到 config/user/pages/,再按需修改。'); } @@ -35,10 +43,11 @@ function loadConfig() { } else if (hasDefaultModularConfig) { config = loadModularConfig('config/_default'); } else { - console.error('[ERROR] 未找到可用配置:缺少 config/user/ 或 config/_default/。'); - console.error('[ERROR] 本版本已不再支持旧版单文件配置(config.yml / config.yaml)。'); - console.error('[ERROR] 解决方法:使用模块化配置目录(建议从 config/_default/ 复制到 config/user/ 再修改)。'); - process.exit(1); + throw new ConfigError('未找到可用配置:缺少 config/user/ 或 config/_default/', [ + '本版本已不再支持旧版单文件配置(config.yml / config.yaml)', + '解决方法:使用模块化配置目录(建议从 config/_default/ 复制到 config/user/ 再修改)', + '参考文档: config/README.md', + ]); } config = ensureConfigDefaults(config); diff --git a/src/generator/config/resolver.js b/src/generator/config/resolver.js index 48acb8b..d19307c 100644 --- a/src/generator/config/resolver.js +++ b/src/generator/config/resolver.js @@ -27,8 +27,7 @@ function resolveTemplateNameForPage(pageId, config) { if (!pageId) return 'page'; const pageConfig = config && config[pageId] ? config[pageId] : null; - const explicit = - pageConfig && pageConfig.template ? String(pageConfig.template).trim() : ''; + const explicit = pageConfig && pageConfig.template ? String(pageConfig.template).trim() : ''; if (explicit) return explicit; const candidatePath = path.join(process.cwd(), 'templates', 'pages', `${pageId}.hbs`); diff --git a/src/generator/html/404.js b/src/generator/html/404.js index 0143f36..f5d61d3 100644 --- a/src/generator/html/404.js +++ b/src/generator/html/404.js @@ -2,7 +2,8 @@ const { escapeHtml } = require('../utils/html'); // 生成 GitHub Pages 的 404 回跳页:将 / 形式的路径深链接转换为 /?page= function generate404Html(config) { - const siteTitle = config && config.site && typeof config.site.title === 'string' ? config.site.title : 'MeNav'; + const siteTitle = + config && config.site && typeof config.site.title === 'string' ? config.site.title : 'MeNav'; const safeTitle = escapeHtml(siteTitle); return ` diff --git a/src/generator/html/components.js b/src/generator/html/components.js index 34e85ca..50a1e2f 100644 --- a/src/generator/html/components.js +++ b/src/generator/html/components.js @@ -205,4 +205,3 @@ module.exports = { generatePageContent, generateSearchResultsPage, }; - diff --git a/src/generator/html/page-data.js b/src/generator/html/page-data.js index 52c95c5..bf1cd94 100644 --- a/src/generator/html/page-data.js +++ b/src/generator/html/page-data.js @@ -1,8 +1,15 @@ const fs = require('fs'); const path = require('path'); const { getSubmenuForNavItem, assignCategorySlugs } = require('../config'); -const { tryLoadArticlesFeedCache, buildArticlesCategoriesByPageCategories } = require('../cache/articles'); -const { tryLoadProjectsRepoCache, applyRepoMetaToCategories, buildProjectsMeta } = require('../cache/projects'); +const { + tryLoadArticlesFeedCache, + buildArticlesCategoriesByPageCategories, +} = require('../cache/articles'); +const { + tryLoadProjectsRepoCache, + applyRepoMetaToCategories, + buildProjectsMeta, +} = require('../cache/projects'); const { getPageConfigUpdatedAtMeta } = require('../utils/pageMeta'); function prepareNavigationData(pageId, config) { @@ -32,7 +39,12 @@ function resolveTemplateName(pageId, data) { let templateName = explicitTemplate || pageId; if (!explicitTemplate) { - const inferredTemplatePath = path.join(process.cwd(), 'templates', 'pages', `${templateName}.hbs`); + const inferredTemplatePath = path.join( + process.cwd(), + 'templates', + 'pages', + `${templateName}.hbs` + ); if (!fs.existsSync(inferredTemplatePath)) { templateName = 'page'; } diff --git a/src/generator/main.js b/src/generator/main.js index fa53817..841fc76 100644 --- a/src/generator/main.js +++ b/src/generator/main.js @@ -3,14 +3,29 @@ const fs = require('fs'); const path = require('path'); const { execFileSync } = require('child_process'); -const { loadHandlebarsTemplates, getDefaultLayoutTemplate, renderTemplate } = require('./template/engine'); +const { + loadHandlebarsTemplates, + getDefaultLayoutTemplate, + renderTemplate, +} = require('./template/engine'); const { MENAV_EXTENSION_CONFIG_FILE, loadConfig, getSubmenuForNavItem } = require('./config'); -const { generateNavigation, generateCategories, generateSocialLinks } = require('./html/components'); +const { + generateNavigation, + generateCategories, + generateSocialLinks, +} = require('./html/components'); const { generate404Html } = require('./html/404'); const { generateFontLinks, generateFontCss } = require('./html/fonts'); const { preparePageData } = require('./html/page-data'); const { collectSitesRecursively } = require('./utils/sites'); +const { + BuildError, + ConfigError, + FileError, + TemplateError, + wrapAsyncError, +} = require('./utils/errors'); /** * 渲染单个页面 @@ -219,7 +234,10 @@ function copyStaticFiles(config) { if (config.site.favicon) { try { if (fs.existsSync(`assets/${config.site.favicon}`)) { - fs.copyFileSync(`assets/${config.site.favicon}`, `dist/${path.basename(config.site.favicon)}`); + fs.copyFileSync( + `assets/${config.site.favicon}`, + `dist/${path.basename(config.site.favicon)}` + ); } else if (fs.existsSync(config.site.favicon)) { fs.copyFileSync(config.site.favicon, `dist/${path.basename(config.site.favicon)}`); } else { @@ -270,20 +288,36 @@ function main() { stdio: 'inherit', }); } catch (error) { - console.error('Error bundling runtime script:', error); - process.exit(1); + throw new BuildError('构建运行时脚本失败', { + 脚本路径: 'scripts/build-runtime.js', + 错误信息: error.message, + }); } // 复制静态文件 copyStaticFiles(config); } catch (e) { - console.error('Error in main function:', e); - process.exit(1); + // 如果是自定义错误,直接抛出,保留上下文/路径信息 + if ( + e instanceof ConfigError || + e instanceof TemplateError || + e instanceof BuildError || + e instanceof FileError + ) { + throw e; + } + + // 否则包装为 BuildError(避免直接暴露底层异常) + throw new BuildError('构建过程中发生错误', { + 错误类型: e.name || 'Error', + 错误信息: e.message || '未知错误', + }); } } if (require.main === module) { - main(); + // 使用 wrapAsyncError 包装主函数,自动处理错误 + wrapAsyncError(main)(); } // 导出供测试使用的函数 diff --git a/src/generator/utils/errors.js b/src/generator/utils/errors.js new file mode 100644 index 0000000..c3d6032 --- /dev/null +++ b/src/generator/utils/errors.js @@ -0,0 +1,127 @@ +/** + * 自定义错误类 - 配置相关错误 + */ +class ConfigError extends Error { + constructor(message, suggestions = []) { + super(message); + this.name = 'ConfigError'; + this.suggestions = suggestions; + } +} + +/** + * 自定义错误类 - 模板相关错误 + */ +class TemplateError extends Error { + constructor(message, templatePath = null) { + super(message); + this.name = 'TemplateError'; + this.templatePath = templatePath; + } +} + +/** + * 自定义错误类 - 构建相关错误 + */ +class BuildError extends Error { + constructor(message, context = {}) { + super(message); + this.name = 'BuildError'; + this.context = context; + } +} + +/** + * 自定义错误类 - 文件操作相关错误 + */ +class FileError extends Error { + constructor(message, filePath = null, suggestions = []) { + super(message); + this.name = 'FileError'; + this.filePath = filePath; + this.suggestions = suggestions; + } +} + +/** + * 统一错误处理器 - 专业紧凑版(中文) + * @param {Error} error - 错误对象 + * @param {number} exitCode - 退出码,默认为 1 + */ +function handleError(error, exitCode = 1) { + // 错误标题行 + console.error(`\n✖ ${error.name}: ${error.message}`); + + // 文件路径(如果有) + if (error.filePath || error.templatePath) { + const path = error.filePath || error.templatePath; + console.error(` 位置: ${path}`); + } + + // 上下文信息(如果有) + if (error.context && Object.keys(error.context).length > 0) { + console.error('│'); + for (const [key, value] of Object.entries(error.context)) { + console.error(`│ ${key}: ${value}`); + } + } + + // 修复建议(如果有) + if (error.suggestions && error.suggestions.length > 0) { + console.error('│'); + console.error('➜ 解决方案:'); + error.suggestions.forEach((suggestion, index) => { + console.error(` ${index + 1}. ${suggestion}`); + }); + } + + // DEBUG 提示(仅在非 DEBUG 模式下显示) + if (process.env.DEBUG) { + console.error('\n堆栈跟踪:'); + console.error(error.stack); + } else { + console.error('\n(设置 DEBUG=1 查看堆栈跟踪)'); + } + + console.error(); // 空行结束 + process.exit(exitCode); +} + +/** + * 包装异步函数,自动处理未捕获的错误 + * @param {Function} fn - 异步函数 + * @returns {Function} 包装后的函数 + */ +function wrapAsyncError(fn) { + return async (...args) => { + try { + return await fn(...args); + } catch (error) { + // 如果是自定义错误,直接使用 handleError + if ( + error instanceof ConfigError || + error instanceof TemplateError || + error instanceof BuildError || + error instanceof FileError + ) { + handleError(error); + } else { + // 否则包装为 BuildError + handleError( + new BuildError(error.message || '未知错误', { + 原始错误类型: error.name || 'Error', + }) + ); + } + } + }; +} + +module.exports = { + ConfigError, + TemplateError, + BuildError, + FileError, + handleError, + wrapAsyncError, +}; diff --git a/src/generator/utils/html.js b/src/generator/utils/html.js index d59186b..ef2f8f5 100644 --- a/src/generator/utils/html.js +++ b/src/generator/utils/html.js @@ -14,4 +14,3 @@ function escapeHtml(unsafe) { module.exports = { escapeHtml, }; - diff --git a/src/generator/utils/pageMeta.js b/src/generator/utils/pageMeta.js index 918788b..3e79775 100644 --- a/src/generator/utils/pageMeta.js +++ b/src/generator/utils/pageMeta.js @@ -98,4 +98,3 @@ function getPageConfigUpdatedAtMeta(pageId) { module.exports = { getPageConfigUpdatedAtMeta, }; - diff --git a/src/generator/utils/sites.js b/src/generator/utils/sites.js index 90de9a7..a5eae3f 100644 --- a/src/generator/utils/sites.js +++ b/src/generator/utils/sites.js @@ -17,7 +17,8 @@ function collectSitesRecursively(node, output) { if (Array.isArray(node.subcategories)) node.subcategories.forEach((child) => collectSitesRecursively(child, output)); - if (Array.isArray(node.groups)) node.groups.forEach((child) => collectSitesRecursively(child, output)); + if (Array.isArray(node.groups)) + node.groups.forEach((child) => collectSitesRecursively(child, output)); if (Array.isArray(node.subgroups)) node.subgroups.forEach((child) => collectSitesRecursively(child, output)); @@ -32,4 +33,3 @@ module.exports = { normalizeUrlKey, collectSitesRecursively, }; - diff --git a/src/runtime/app/index.js b/src/runtime/app/index.js index bf66266..9e2ce99 100644 --- a/src/runtime/app/index.js +++ b/src/runtime/app/index.js @@ -7,11 +7,16 @@ function detectHomePageId() { // 1) 优先从生成端注入的配置数据读取(保持与实际导航顺序一致) try { const config = - window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; + window.MeNav && typeof window.MeNav.getConfig === 'function' + ? window.MeNav.getConfig() + : null; const injectedHomePageId = config && config.data && config.data.homePageId ? String(config.data.homePageId).trim() : ''; if (injectedHomePageId) return injectedHomePageId; - const nav = config && config.data && Array.isArray(config.data.navigation) ? config.data.navigation : null; + const nav = + config && config.data && Array.isArray(config.data.navigation) + ? config.data.navigation + : null; const firstId = nav && nav[0] && nav[0].id ? String(nav[0].id).trim() : ''; if (firstId) return firstId; } catch (error) { @@ -109,4 +114,3 @@ document.addEventListener('DOMContentLoaded', () => { initRouting(state, dom, { ui, search }); }); - diff --git a/src/runtime/app/routing.js b/src/runtime/app/routing.js index 1f7d7d0..e07045f 100644 --- a/src/runtime/app/routing.js +++ b/src/runtime/app/routing.js @@ -49,7 +49,8 @@ module.exports = function initRouting(state, dom, api) { state.pages = document.querySelectorAll('.page'); // 方案 A:用 ?page= 作为页面深链接(兼容 GitHub Pages 静态托管) - const normalizeText = (value) => String(value === null || value === undefined ? '' : value).trim(); + const normalizeText = (value) => + String(value === null || value === undefined ? '' : value).trim(); const isValidPageId = (pageId) => { const id = normalizeText(pageId); @@ -164,7 +165,9 @@ module.exports = function initRouting(state, dom, api) { const escapedId = escapeSelector(categoryId); targetCategory = targetPage.querySelector(`#${escapedId}`) || - targetPage.querySelector(`[data-type="category"][data-id="${escapeAttrValue(categoryId)}"]`); + targetPage.querySelector( + `[data-type="category"][data-id="${escapeAttrValue(categoryId)}"]` + ); } // 回退:旧逻辑按文本包含匹配(兼容旧页面/旧数据) @@ -190,7 +193,8 @@ module.exports = function initRouting(state, dom, api) { const desiredPosition = containerRect.height / 4; // 计算需要滚动的位置 - const scrollPosition = contentElement.scrollTop + rect.top - containerRect.top - desiredPosition; + const scrollPosition = + contentElement.scrollTop + rect.top - containerRect.top - desiredPosition; // 执行滚动 contentElement.scrollTo({ @@ -234,7 +238,8 @@ module.exports = function initRouting(state, dom, api) { // 支持 ?page= 直接打开对应页面;无效时回退到首页 const rawPageIdFromUrl = getRawPageIdFromUrl(); const validatedPageIdFromUrl = getPageIdFromUrl(); - const initialPageId = validatedPageIdFromUrl || (isValidPageId(state.homePageId) ? state.homePageId : 'home'); + const initialPageId = + validatedPageIdFromUrl || (isValidPageId(state.homePageId) ? state.homePageId : 'home'); setActiveNavByPageId(initialPageId); showPage(initialPageId); @@ -400,4 +405,3 @@ module.exports = function initRouting(state, dom, api) { return { showPage }; }; - diff --git a/src/runtime/app/search.js b/src/runtime/app/search.js index 18bb22e..a63695a 100644 --- a/src/runtime/app/search.js +++ b/src/runtime/app/search.js @@ -48,18 +48,25 @@ module.exports = function initSearch(state, dom) { // 兼容不同页面/卡片样式:优先取可见文本,其次回退到 data-*(确保 projects repo 卡片也能被搜索) const dataTitle = card.dataset?.name || card.getAttribute('data-name') || ''; - const dataDescription = card.dataset?.description || card.getAttribute('data-description') || ''; + const dataDescription = + card.dataset?.description || card.getAttribute('data-description') || ''; const titleText = - card.querySelector('h3')?.textContent || card.querySelector('.repo-title')?.textContent || dataTitle; + card.querySelector('h3')?.textContent || + card.querySelector('.repo-title')?.textContent || + dataTitle; const descriptionText = - card.querySelector('p')?.textContent || card.querySelector('.repo-desc')?.textContent || dataDescription; + card.querySelector('p')?.textContent || + card.querySelector('.repo-desc')?.textContent || + dataDescription; const title = String(titleText || '').toLowerCase(); const description = String(descriptionText || '').toLowerCase(); const url = card.href || card.getAttribute('href') || '#'; const icon = - card.querySelector('i.icon-fallback')?.className || card.querySelector('i')?.className || ''; + card.querySelector('i.icon-fallback')?.className || + card.querySelector('i')?.className || + ''; // 将卡片信息添加到索引中 state.searchIndex.items.push({ @@ -111,7 +118,9 @@ module.exports = function initSearch(state, dom) { // 使用更高效的搜索算法 const matchedItems = state.searchIndex.items.filter((item) => { - return item.searchText.includes(searchTerm) || PinyinMatch.match(item.searchText, searchTerm); + return ( + item.searchText.includes(searchTerm) || PinyinMatch.match(item.searchText, searchTerm) + ); }); // 按页面分组结果 diff --git a/src/runtime/app/search/highlight.js b/src/runtime/app/search/highlight.js index 925c052..dde08c1 100644 --- a/src/runtime/app/search/highlight.js +++ b/src/runtime/app/search/highlight.js @@ -47,7 +47,9 @@ module.exports = function highlightSearchTerm(card, searchTerm) { while ((match = regex.exec(rawText)) !== null) { if (match.index > lastIndex) { - fragment.appendChild(document.createTextNode(rawText.substring(lastIndex, match.index))); + fragment.appendChild( + document.createTextNode(rawText.substring(lastIndex, match.index)) + ); } const span = document.createElement('span'); @@ -87,4 +89,3 @@ module.exports = function highlightSearchTerm(card, searchTerm) { console.error('Error highlighting search term'); } }; - diff --git a/src/runtime/app/searchEngines.js b/src/runtime/app/searchEngines.js index c3e4850..6a6338a 100644 --- a/src/runtime/app/searchEngines.js +++ b/src/runtime/app/searchEngines.js @@ -22,4 +22,3 @@ module.exports = { url: 'https://duckduckgo.com/?q=', }, }; - diff --git a/src/runtime/app/ui.js b/src/runtime/app/ui.js index 119e96e..4aed01a 100644 --- a/src/runtime/app/ui.js +++ b/src/runtime/app/ui.js @@ -178,4 +178,3 @@ module.exports = function initUi(state, dom) { initSidebarState, }; }; - diff --git a/src/runtime/index.js b/src/runtime/index.js index efb32aa..69f3549 100644 --- a/src/runtime/index.js +++ b/src/runtime/index.js @@ -15,4 +15,3 @@ require('./app'); // tooltip 独立模块:内部会按需监听 DOMContentLoaded require('./tooltip'); - diff --git a/src/runtime/menav/addElement.js b/src/runtime/menav/addElement.js index 1761c92..9000228 100644 --- a/src/runtime/menav/addElement.js +++ b/src/runtime/menav/addElement.js @@ -17,7 +17,9 @@ module.exports = function addElement(type, parentId, data) { const pageEl = parent.closest('.page'); const pageId = pageEl && pageEl.id ? String(pageEl.id).trim() : ''; const cfg = - window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; + window.MeNav && typeof window.MeNav.getConfig === 'function' + ? window.MeNav.getConfig() + : null; let templateName = ''; @@ -27,12 +29,16 @@ module.exports = function addElement(type, parentId, data) { : null; const templateFromMap = - pageTemplates && pageId && pageTemplates[pageId] ? String(pageTemplates[pageId]).trim() : ''; + pageTemplates && pageId && pageTemplates[pageId] + ? String(pageTemplates[pageId]).trim() + : ''; // 兼容旧版:cfg.data[pageId].template const legacyPageConfig = cfg && cfg.data && pageId ? cfg.data[pageId] : null; const templateFromLegacy = - legacyPageConfig && legacyPageConfig.template ? String(legacyPageConfig.template).trim() : ''; + legacyPageConfig && legacyPageConfig.template + ? String(legacyPageConfig.template).trim() + : ''; if (templateFromMap) { templateName = templateFromMap; @@ -55,16 +61,22 @@ module.exports = function addElement(type, parentId, data) { const siteIcon = data.icon || 'fas fa-link'; const siteDescription = data.description || (data.url ? menavExtractDomain(data.url) : ''); const siteFaviconUrl = data && data.faviconUrl ? String(data.faviconUrl).trim() : ''; - const siteForceIconModeRaw = data && data.forceIconMode ? String(data.forceIconMode).trim() : ''; + const siteForceIconModeRaw = + data && data.forceIconMode ? String(data.forceIconMode).trim() : ''; const siteForceIconMode = - siteForceIconModeRaw === 'manual' || siteForceIconModeRaw === 'favicon' ? siteForceIconModeRaw : ''; + siteForceIconModeRaw === 'manual' || siteForceIconModeRaw === 'favicon' + ? siteForceIconModeRaw + : ''; const safeSiteUrl = menavSanitizeUrl(siteUrl, 'addElement(site).url'); const safeSiteIcon = menavSanitizeClassList(siteIcon, 'addElement(site).icon'); newSite.setAttribute('href', safeSiteUrl); newSite.title = siteName + (siteDescription ? ' - ' + siteDescription : ''); - newSite.setAttribute('data-tooltip', siteName + (siteDescription ? ' - ' + siteDescription : '')); + newSite.setAttribute( + 'data-tooltip', + siteName + (siteDescription ? ' - ' + siteDescription : '') + ); if (/^https?:\/\//i.test(safeSiteUrl)) { newSite.target = '_blank'; newSite.rel = 'noopener'; @@ -185,8 +197,11 @@ module.exports = function addElement(type, parentId, data) { // favicon 模式:优先加载 faviconUrl;否则按 url 生成 try { const cfg = - window.MeNav && typeof window.MeNav.getConfig === 'function' ? window.MeNav.getConfig() : null; - const iconsMode = cfg && cfg.icons && cfg.icons.mode ? String(cfg.icons.mode).trim() : 'favicon'; + window.MeNav && typeof window.MeNav.getConfig === 'function' + ? window.MeNav.getConfig() + : null; + const iconsMode = + cfg && cfg.icons && cfg.icons.mode ? String(cfg.icons.mode).trim() : 'favicon'; const iconsRegion = cfg && cfg.icons && cfg.icons.region ? String(cfg.icons.region).trim() : 'com'; @@ -319,7 +334,10 @@ module.exports = function addElement(type, parentId, data) { // 添加内容(用 DOM API 构建,避免 innerHTML 注入) const titleEl = document.createElement('h2'); const iconEl = document.createElement('i'); - iconEl.className = menavSanitizeClassList(data.icon || 'fas fa-folder', 'addElement(category).icon'); + iconEl.className = menavSanitizeClassList( + data.icon || 'fas fa-folder', + 'addElement(category).icon' + ); titleEl.appendChild(iconEl); titleEl.appendChild(document.createTextNode(' ' + String(data.name || '未命名分类'))); diff --git a/src/runtime/menav/index.js b/src/runtime/menav/index.js index b93f8ab..536ac0b 100644 --- a/src/runtime/menav/index.js +++ b/src/runtime/menav/index.js @@ -44,7 +44,8 @@ function findDefaultElement(type, id) { // 全局 MeNav 对象 - 用于浏览器扩展 const existing = window.MeNav && typeof window.MeNav === 'object' ? window.MeNav : {}; -const events = existing.events && typeof existing.events === 'object' ? existing.events : createMenavEvents(); +const events = + existing.events && typeof existing.events === 'object' ? existing.events : createMenavEvents(); window.MeNav = Object.assign(existing, { version: menavDetectVersion(), diff --git a/src/runtime/menav/updateElement.js b/src/runtime/menav/updateElement.js index f3d3bd5..c714479 100644 --- a/src/runtime/menav/updateElement.js +++ b/src/runtime/menav/updateElement.js @@ -107,7 +107,10 @@ module.exports = function updateElement(type, id, newData) { if (newData.icon) { const iconElement = element.querySelector('i'); if (iconElement) { - iconElement.className = menavSanitizeClassList(newData.icon, 'updateElement(nav-item).icon'); + iconElement.className = menavSanitizeClassList( + newData.icon, + 'updateElement(nav-item).icon' + ); } element.setAttribute( 'data-icon', diff --git a/src/runtime/nested/index.js b/src/runtime/nested/index.js index 4f61dfc..c6ee558 100644 --- a/src/runtime/nested/index.js +++ b/src/runtime/nested/index.js @@ -180,7 +180,9 @@ function registerNestedApi() { if (!activePage) return; const allElements = getCollapsibleNestedContainers(activePage); - const collapsedElements = allElements.filter((element) => element.classList.contains('collapsed')); + const collapsedElements = allElements.filter((element) => + element.classList.contains('collapsed') + ); if (allElements.length === 0) return; // 如果收起的数量 >= 总数的一半,执行展开;否则执行收起 diff --git a/src/runtime/shared.js b/src/runtime/shared.js index 1406771..47fba40 100644 --- a/src/runtime/shared.js +++ b/src/runtime/shared.js @@ -139,4 +139,3 @@ module.exports = { menavDetectVersion, menavUpdateAppHeight, }; - diff --git a/src/runtime/tooltip.js b/src/runtime/tooltip.js index 6a751ff..940ccb6 100644 --- a/src/runtime/tooltip.js +++ b/src/runtime/tooltip.js @@ -112,4 +112,3 @@ document.addEventListener('DOMContentLoaded', () => { hoverMedia.addListener(syncTooltipEnabled); } }); - diff --git a/test/404-fallback.node-test.js b/test/404-fallback.node-test.js index 175a2d3..34f26d9 100644 --- a/test/404-fallback.node-test.js +++ b/test/404-fallback.node-test.js @@ -13,4 +13,3 @@ test('P1-5:404.html 回跳应将 / 转为 ?page=(并支持仓库前 assert.ok(html.includes('segments.length === 2'), '应支持仓库站点 //'); assert.ok(html.includes('l.replace(target)'), '应使用 location.replace 执行回跳'); }); - diff --git a/test/p1-7-extension-config-minimal.node-test.js b/test/p1-7-extension-config-minimal.node-test.js index 60badd8..81c4ee3 100644 --- a/test/p1-7-extension-config-minimal.node-test.js +++ b/test/p1-7-extension-config-minimal.node-test.js @@ -20,9 +20,7 @@ test('P1-7:页面内不应注入整站 configJSON,应仅保留扩展元信 const config = loadConfig(); const html = generateHTML(config); - const match = html.match( - /