From 6474fa3635e459b83790ca4b3700b89c9f91fe1b Mon Sep 17 00:00:00 2001 From: Zuoling Rong Date: Wed, 7 May 2025 14:58:38 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=AE=8C=E6=88=90Handlebars?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E7=B3=BB=E7=BB=9F=E5=9F=BA=E7=A1=80=E9=9B=86?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/generator.js | 362 ++++++++++++++++++++++++---- templates/components/category.hbs | 12 + templates/components/navigation.hbs | 21 ++ templates/components/site-card.hbs | 7 + templates/layouts/default.hbs | 119 +++++++++ templates/pages/home.hbs | 10 + 6 files changed, 487 insertions(+), 44 deletions(-) create mode 100644 templates/components/category.hbs create mode 100644 templates/components/navigation.hbs create mode 100644 templates/components/site-card.hbs create mode 100644 templates/layouts/default.hbs create mode 100644 templates/pages/home.hbs diff --git a/src/generator.js b/src/generator.js index 9a46839..ae57f17 100644 --- a/src/generator.js +++ b/src/generator.js @@ -1,6 +1,113 @@ const fs = require('fs'); const yaml = require('js-yaml'); const path = require('path'); +const Handlebars = require('handlebars'); + +// 注册Handlebars实例和辅助函数 +const handlebars = Handlebars.create(); + +// 加载和注册Handlebars模板的函数 +function loadHandlebarsTemplates() { + const templatesDir = path.join(process.cwd(), 'templates'); + + // 检查基本模板目录是否存在 + if (!fs.existsSync(templatesDir)) { + console.warn('Templates directory not found. Using fallback HTML generation.'); + return false; + } + + // 加载布局模板 + const layoutsDir = path.join(templatesDir, 'layouts'); + if (fs.existsSync(layoutsDir)) { + fs.readdirSync(layoutsDir).forEach(file => { + if (file.endsWith('.hbs')) { + const layoutName = path.basename(file, '.hbs'); + const layoutPath = path.join(layoutsDir, file); + const layoutContent = fs.readFileSync(layoutPath, 'utf8'); + handlebars.registerPartial(layoutName, layoutContent); + console.log(`Registered layout template: ${layoutName}`); + } + }); + } + + // 加载组件模板 + const componentsDir = path.join(templatesDir, 'components'); + if (fs.existsSync(componentsDir)) { + fs.readdirSync(componentsDir).forEach(file => { + if (file.endsWith('.hbs')) { + const componentName = path.basename(file, '.hbs'); + const componentPath = path.join(componentsDir, file); + const componentContent = fs.readFileSync(componentPath, 'utf8'); + handlebars.registerPartial(componentName, componentContent); + console.log(`Registered component template: ${componentName}`); + } + }); + } + + // 识别并注册默认布局模板 + const defaultLayoutPath = path.join(layoutsDir, 'default.hbs'); + if (fs.existsSync(defaultLayoutPath)) { + console.log('Default layout template found and registered.'); + return true; + } else { + console.warn('Default layout template not found. Using fallback HTML generation.'); + return false; + } +} + +// 渲染Handlebars模板函数 +function renderTemplate(templateName, data, useLayout = true) { + const templatePath = path.join(process.cwd(), 'templates', 'pages', `${templateName}.hbs`); + + // 检查模板是否存在 + if (!fs.existsSync(templatePath)) { + console.warn(`Template ${templateName}.hbs not found. Using fallback HTML generation.`); + return null; + } + + try { + const templateContent = fs.readFileSync(templatePath, 'utf8'); + const template = handlebars.compile(templateContent); + + // 渲染页面内容 + const pageContent = template(data); + + // 如果不使用布局或者默认布局不存在,直接返回页面内容 + if (!useLayout) { + return pageContent; + } + + // 使用布局模板 + const defaultLayoutPath = path.join(process.cwd(), 'templates', 'layouts', 'default.hbs'); + if (fs.existsSync(defaultLayoutPath)) { + try { + // 准备布局数据,包含页面内容 + const layoutData = { + ...data, + body: pageContent + }; + + // 加载默认布局模板 + const layoutContent = fs.readFileSync(defaultLayoutPath, 'utf8'); + const layoutTemplate = handlebars.compile(layoutContent); + + // 渲染完整页面 + return layoutTemplate(layoutData); + } catch (layoutError) { + console.error(`Error rendering layout for ${templateName}:`, layoutError); + // 如果布局渲染失败,尝试返回页面内容 + return pageContent; + } + } else { + // 如果找不到布局模板,返回页面内容 + console.warn('Default layout template not found. Returning page content only.'); + return pageContent; + } + } catch (error) { + console.error(`Error rendering template ${templateName}:`, error); + return null; + } +} // HTML转义函数,防止XSS攻击 function escapeHtml(unsafe) { @@ -644,44 +751,73 @@ function processTemplate(template, config) { const googleFontsLink = generateGoogleFontsLink(config); const fontVariables = generateFontVariables(config); - // 生成所有页面的HTML - let allPagesHTML = ''; - - // 确保按照导航顺序生成页面 - if (config.navigation && Array.isArray(config.navigation)) { - // 按照导航中的顺序生成页面 - config.navigation.forEach(navItem => { - const pageId = navItem.id; + // 如果Handlebars模板系统可用,优先使用Handlebars渲染 + const defaultLayoutPath = path.join(process.cwd(), 'templates', 'layouts', 'default.hbs'); + if (fs.existsSync(defaultLayoutPath)) { + try { + // 准备导航数据,添加submenu字段 + const navigationData = config.navigation.map(nav => { + const navItem = { ...nav }; + + // 根据页面ID获取对应的子菜单项(分类) + if (nav.id === 'home' && Array.isArray(config.categories)) { + navItem.submenu = config.categories; + } + // 书签页面添加子菜单(分类) + else if (nav.id === 'bookmarks' && config.bookmarks && Array.isArray(config.bookmarks.categories)) { + navItem.submenu = config.bookmarks.categories; + } + // 项目页面添加子菜单 + else if (nav.id === 'projects' && config.projects && Array.isArray(config.projects.categories)) { + navItem.submenu = config.projects.categories; + } + // 文章页面添加子菜单 + else if (nav.id === 'articles' && config.articles && Array.isArray(config.articles.categories)) { + navItem.submenu = config.articles.categories; + } + // 友链页面添加子菜单 + else if (nav.id === 'friends' && config.friends && Array.isArray(config.friends.categories)) { + navItem.submenu = config.friends.categories; + } + // 通用处理:任意自定义页面的子菜单生成 + else if (config[nav.id] && config[nav.id].categories && Array.isArray(config[nav.id].categories)) { + navItem.submenu = config[nav.id].categories; + } + + return navItem; + }); - // 跳过搜索结果页 - if (pageId === 'search-results') { - return; - } + // 准备模板数据 + const templateData = { + site: config.site, + navigation: generateNavigation(config.navigation, config), + navigationData: navigationData, + social: config.social, + categories: config.categories, + profile: config.profile, + googleFontsLink: googleFontsLink, + fontVariables: fontVariables, + currentYear: currentYear, + socialLinks: generateSocialLinks(config.social), + searchResults: generateSearchResultsPage(config) + }; - let pageContent = ''; - let isActive = pageId === 'home' ? ' active' : ''; + // 加载默认布局模板 + const layoutContent = fs.readFileSync(defaultLayoutPath, 'utf8'); + const layoutTemplate = handlebars.compile(layoutContent); - // 根据页面ID生成对应内容 - if (pageId === 'home') { - pageContent = generateHomeContent(config); - } else if (config[pageId]) { - pageContent = generatePageContent(pageId, config[pageId]); - } else { - pageContent = `
-

页面未配置

-

请配置 ${pageId} 页面

-
`; - } - - // 添加页面HTML - allPagesHTML += ` - -
-${pageContent} -
`; - }); + // 渲染模板 + return layoutTemplate(templateData); + } catch (error) { + console.error('Error using Handlebars template:', error); + console.log('Falling back to placeholder replacement method.'); + // 出错时回退到原始占位符替换方法 + } } + // 生成所有页面的HTML + let allPagesHTML = generateAllPagesHTML(config); + // 创建替换映射 const replacements = { '{{SITE_TITLE}}': escapeHtml(config.site.title), @@ -706,6 +842,96 @@ ${pageContent} return processedTemplate; } +// 生成所有页面的HTML +function generateAllPagesHTML(config) { + let allPagesHTML = ''; + + // 准备Handlebars渲染所需的通用数据 + const templateData = { + site: config.site, + navigation: config.navigation, + navigationData: config.navigation, + social: config.social, + categories: config.categories, + profile: config.profile, + googleFontsLink: generateGoogleFontsLink(config), + fontVariables: generateFontVariables(config), + currentYear: new Date().getFullYear(), + socialLinks: generateSocialLinks(config.social), + searchResults: generateSearchResultsPage(config) + }; + + // 确保按照导航顺序生成页面 + if (config.navigation && Array.isArray(config.navigation)) { + // 按照导航中的顺序生成页面 + config.navigation.forEach(navItem => { + const pageId = navItem.id; + + // 跳过搜索结果页 + if (pageId === 'search-results') { + return; + } + + let pageContent = ''; + let isActive = pageId === 'home' ? ' active' : ''; + + // 首先尝试使用模板渲染 + const pageTemplatePath = path.join(process.cwd(), 'templates', 'pages', `${pageId}.hbs`); + if (fs.existsSync(pageTemplatePath)) { + // 准备页面特定数据 + const pageTemplateData = { ...templateData }; + + // 添加页面特定数据 + if (pageId === 'home') { + // home页面不需要额外数据,已经包含了categories + } else if (config[pageId]) { + // 其他页面可能有自己的配置 + Object.assign(pageTemplateData, config[pageId]); + if (config[pageId].categories) { + pageTemplateData.categories = config[pageId].categories; + } + } + + try { + // 使用Handlebars直接编译模板(不使用布局) + const templateContent = fs.readFileSync(pageTemplatePath, 'utf8'); + const template = handlebars.compile(templateContent); + pageContent = template(pageTemplateData); + console.log(`Rendered ${pageId} page using Handlebars template.`); + } catch (error) { + console.error(`Error rendering ${pageId} template:`, error); + // 回退到原始生成逻辑 + pageContent = null; + } + } + + // 如果模板渲染失败或模板不存在,使用原始生成逻辑 + if (!pageContent) { + // 根据页面ID生成对应内容 + if (pageId === 'home') { + pageContent = generateHomeContent(config); + } else if (config[pageId]) { + pageContent = generatePageContent(pageId, config[pageId]); + } else { + pageContent = `
+

页面未配置

+

请配置 ${pageId} 页面

+
`; + } + } + + // 添加页面HTML + allPagesHTML += ` + +
+${pageContent} +
`; + }); + } + + return allPagesHTML; +} + // 调试函数 function debugConfig(config) { console.log('==== DEBUG INFO ===='); @@ -731,19 +957,64 @@ function main() { fs.mkdirSync('dist', { recursive: true }); } - // 读取模板文件 - const templatePath = 'templates/index.html'; + // 初始化Handlebars模板系统 + const handlebarsAvailable = loadHandlebarsTemplates(); + + // 准备Handlebars渲染所需的通用数据 + const templateData = { + site: config.site, + navigation: config.navigation, + navigationData: config.navigation, + social: config.social, + categories: config.categories, + profile: config.profile, + googleFontsLink: generateGoogleFontsLink(config), + fontVariables: generateFontVariables(config), + currentYear: new Date().getFullYear(), + socialLinks: generateSocialLinks(config.social), + searchResults: generateSearchResultsPage(config) + }; + let htmlContent = ''; - if (fs.existsSync(templatePath)) { - // 读取模板并处理 - const template = fs.readFileSync(templatePath, 'utf8'); - htmlContent = processTemplate(template, config); - console.log(`Using template from ${templatePath} and injecting content`); + // 尝试使用Handlebars模板渲染 + if (handlebarsAvailable) { + console.log('Handlebars templates are available.'); + + // 渲染逻辑:先尝试使用页面模板和默认布局渲染 + const renderedContent = renderTemplate('home', templateData); + + if (renderedContent) { + // 使用模板成功渲染 + htmlContent = renderedContent; + console.log('Successfully rendered using Handlebars templates.'); + } else { + // 模板渲染失败,回退到传统模板处理 + console.log('Failed to render with Handlebars templates, using traditional template processing.'); + + const templatePath = 'templates/index.html'; + if (fs.existsSync(templatePath)) { + const template = fs.readFileSync(templatePath, 'utf8'); + htmlContent = processTemplate(template, config); + } else { + // 如果没有任何模板,使用纯生成的HTML + htmlContent = generateHTML(config); + console.log('No template files found, using generated HTML.'); + } + } } else { - // 如果没有模板文件,使用生成的HTML - htmlContent = generateHTML(config); - console.log('No template file found, using generated HTML'); + // Handlebars不可用,使用传统模板处理 + console.log('Handlebars templates are not available, using traditional template processing.'); + + const templatePath = 'templates/index.html'; + if (fs.existsSync(templatePath)) { + const template = fs.readFileSync(templatePath, 'utf8'); + htmlContent = processTemplate(template, config); + } else { + // 如果没有任何模板,使用纯生成的HTML + htmlContent = generateHTML(config); + console.log('No template files found, using generated HTML.'); + } } // 生成HTML @@ -766,5 +1037,8 @@ module.exports = { generateHTML, copyStaticFiles, generateNavigation, - generateCategories + generateCategories, + loadHandlebarsTemplates, + renderTemplate, + generateAllPagesHTML }; diff --git a/templates/components/category.hbs b/templates/components/category.hbs new file mode 100644 index 0000000..f68a4be --- /dev/null +++ b/templates/components/category.hbs @@ -0,0 +1,12 @@ +
+

{{name}}

+
+ {{#if sites.length}} + {{#each sites}} + {{> site-card}} + {{/each}} + {{else}} +

暂无网站

+ {{/if}} +
+
\ No newline at end of file diff --git a/templates/components/navigation.hbs b/templates/components/navigation.hbs new file mode 100644 index 0000000..b2a7f8f --- /dev/null +++ b/templates/components/navigation.hbs @@ -0,0 +1,21 @@ +{{#each this}} + +{{/each}} \ No newline at end of file diff --git a/templates/components/site-card.hbs b/templates/components/site-card.hbs new file mode 100644 index 0000000..379b3ef --- /dev/null +++ b/templates/components/site-card.hbs @@ -0,0 +1,7 @@ +{{#if url}} + + +

{{#if name}}{{name}}{{else}}未命名站点{{/if}}

+

{{description}}

+
+{{/if}} \ No newline at end of file diff --git a/templates/layouts/default.hbs b/templates/layouts/default.hbs new file mode 100644 index 0000000..61134a1 --- /dev/null +++ b/templates/layouts/default.hbs @@ -0,0 +1,119 @@ + + + + + + {{site.title}} + + + {{{googleFontsLink}}} + + + + + + + + +
+
+ +
+ + +
+ + +
+ + + + + +
+ +
+ +
+ + {{{body}}} + + +
+
+

搜索结果

+

在所有页面中找到的匹配项

+
+ {{#each navigation}} + + {{/each}} +
+
+ + + +
+ + + \ No newline at end of file diff --git a/templates/pages/home.hbs b/templates/pages/home.hbs new file mode 100644 index 0000000..e11468d --- /dev/null +++ b/templates/pages/home.hbs @@ -0,0 +1,10 @@ +
+
+

{{profile.title}}

+

{{profile.subtitle}}

+

{{profile.description}}

+
+ {{#each categories}} + {{> category}} + {{/each}} +
\ No newline at end of file