| const fs = require('fs'); |
| const path = require('path'); |
| const { getSubmenuForNavItem, assignCategorySlugs } = require('../config'); |
| const { |
| tryLoadArticlesFeedCache, |
| buildArticlesCategoriesByPageCategories, |
| } = require('../cache/articles'); |
| const { |
| tryLoadProjectsRepoCache, |
| tryLoadProjectsHeatmapCache, |
| applyRepoMetaToCategories, |
| buildProjectsMeta, |
| } = require('../cache/projects'); |
| const { getPageConfigUpdatedAtMeta } = require('../utils/pageMeta'); |
| const { createLogger, isVerbose } = require('../utils/logger'); |
| const { ConfigError } = require('../utils/errors'); |
| const { renderMarkdownToHtml } = require('./markdown'); |
|
|
| const log = createLogger('render'); |
|
|
| function prepareNavigationData(pageId, config) { |
| if (!Array.isArray(config.navigation)) { |
| log.warn('config.navigation 不是数组,已降级为空数组'); |
| return []; |
| } |
|
|
| return config.navigation.map((nav) => { |
| const navItem = { |
| ...nav, |
| isActive: nav.id === pageId, |
| active: nav.id === pageId, |
| }; |
|
|
| const submenu = getSubmenuForNavItem(navItem, config); |
| if (submenu) { |
| navItem.submenu = submenu; |
| } |
|
|
| return navItem; |
| }); |
| } |
|
|
| function resolveTemplateName(pageId, data) { |
| const explicitTemplate = typeof data.template === 'string' ? data.template.trim() : ''; |
| let templateName = explicitTemplate || pageId; |
|
|
| if (!explicitTemplate) { |
| const inferredTemplatePath = path.join( |
| process.cwd(), |
| 'templates', |
| 'pages', |
| `${templateName}.hbs` |
| ); |
| if (!fs.existsSync(inferredTemplatePath)) { |
| templateName = 'page'; |
| } |
| } |
|
|
| return templateName; |
| } |
|
|
| function applyProjectsData(data, pageId, config) { |
| data.siteCardStyle = 'repo'; |
| data.projectsMeta = buildProjectsMeta(config); |
|
|
| const heatmapCache = tryLoadProjectsHeatmapCache(pageId, config); |
| if (data.projectsMeta && data.projectsMeta.heatmap && heatmapCache) { |
| data.projectsMeta.heatmap.html = heatmapCache.html; |
| data.projectsMeta.heatmap.generatedAt = heatmapCache.meta.generatedAt; |
| data.projectsMeta.heatmap.sourceUrl = heatmapCache.meta.sourceUrl; |
| } |
|
|
| if (Array.isArray(data.categories)) { |
| const repoCache = tryLoadProjectsRepoCache(pageId, config); |
| if (repoCache && repoCache.map) { |
| applyRepoMetaToCategories(data.categories, repoCache.map); |
| } |
| } |
| } |
|
|
| function applyArticlesData(data, pageId, config) { |
| const cache = tryLoadArticlesFeedCache(pageId, config); |
| data.articlesItems = cache && Array.isArray(cache.items) ? cache.items : []; |
| data.articlesMeta = cache ? cache.meta : null; |
| data.articlesCategories = data.articlesItems.length |
| ? buildArticlesCategoriesByPageCategories(data.categories, data.articlesItems) |
| : []; |
| } |
|
|
| function applyBookmarksData(data, pageId) { |
| const updatedAtMeta = getPageConfigUpdatedAtMeta(pageId); |
| if (updatedAtMeta) { |
| data.pageMeta = { ...updatedAtMeta }; |
| } |
| } |
|
|
| function applyContentData(data, pageId, config) { |
| const content = data && data.content && typeof data.content === 'object' ? data.content : null; |
| const file = content && content.file ? String(content.file).trim() : ''; |
| if (!file) { |
| throw new ConfigError(`内容页缺少 content.file:${pageId}`, [ |
| `请在 config/*/pages/${pageId}.yml 中配置:`, |
| 'template: content', |
| 'content:', |
| ' file: path/to/file.md', |
| ]); |
| } |
|
|
| |
| const fs = require('node:fs'); |
| const path = require('node:path'); |
|
|
| |
| |
| const normalized = file.replace(/\\/g, '/'); |
| const absPath = path.isAbsolute(normalized) |
| ? path.normalize(normalized) |
| : path.join(process.cwd(), normalized.replace(/^\//, '')); |
| if (!fs.existsSync(absPath)) { |
| throw new ConfigError(`内容页 markdown 文件不存在:${pageId}`, [ |
| `检查路径是否正确:${file}`, |
| '提示:路径相对于仓库根目录(process.cwd())解析', |
| ]); |
| } |
|
|
| const markdownText = fs.readFileSync(absPath, 'utf8'); |
| const allowedSchemes = |
| config && |
| config.site && |
| config.site.security && |
| Array.isArray(config.site.security.allowedSchemes) |
| ? config.site.security.allowedSchemes |
| : null; |
|
|
| data.contentFile = normalized; |
| data.contentHtml = renderMarkdownToHtml(markdownText, { allowedSchemes }); |
| } |
|
|
| function convertTopLevelSitesToCategory(data, pageId, templateName) { |
| const isFriendsPage = pageId === 'friends' || templateName === 'friends'; |
| const isArticlesPage = pageId === 'articles' || templateName === 'articles'; |
|
|
| if ( |
| (isFriendsPage || isArticlesPage) && |
| (!Array.isArray(data.categories) || data.categories.length === 0) && |
| Array.isArray(data.sites) && |
| data.sites.length > 0 |
| ) { |
| const implicitName = isFriendsPage ? '全部友链' : '全部来源'; |
| data.categories = [ |
| { |
| name: implicitName, |
| icon: 'fas fa-link', |
| sites: data.sites, |
| }, |
| ]; |
| } |
| } |
|
|
| function applyHomePageTitles(data, pageId, config) { |
| const homePageId = |
| config.homePageId || |
| (Array.isArray(config.navigation) && config.navigation[0] ? config.navigation[0].id : null) || |
| 'home'; |
|
|
| data.homePageId = homePageId; |
|
|
| if (pageId === homePageId && config.profile) { |
| if (config.profile.title !== undefined) data.title = config.profile.title; |
| if (config.profile.subtitle !== undefined) data.subtitle = config.profile.subtitle; |
| } |
|
|
| data.isHome = pageId === homePageId; |
| data.homePageId = homePageId; |
| } |
|
|
| function preparePageData(pageId, config) { |
| const data = { |
| ...(config || {}), |
| currentPage: pageId, |
| pageId, |
| }; |
|
|
| data.navigation = prepareNavigationData(pageId, config); |
| data.socialLinks = Array.isArray(config.social) ? config.social : []; |
| data.navigationData = data.navigation; |
|
|
| if (config[pageId]) { |
| Object.assign(data, config[pageId]); |
| } |
|
|
| if (data.title === undefined) { |
| const navItem = Array.isArray(config.navigation) |
| ? config.navigation.find((nav) => nav.id === pageId) |
| : null; |
| if (navItem && navItem.name !== undefined) data.title = navItem.name; |
| } |
| if (data.subtitle === undefined) data.subtitle = ''; |
| if (!Array.isArray(data.categories)) data.categories = []; |
|
|
| const templateName = resolveTemplateName(pageId, data); |
|
|
| if (templateName === 'projects') { |
| applyProjectsData(data, pageId, config); |
| } |
|
|
| convertTopLevelSitesToCategory(data, pageId, templateName); |
|
|
| if (templateName === 'articles') { |
| applyArticlesData(data, pageId, config); |
| } |
|
|
| if (templateName === 'bookmarks') { |
| applyBookmarksData(data, pageId); |
| } |
|
|
| if (templateName === 'content') { |
| applyContentData(data, pageId, config); |
| } |
|
|
| applyHomePageTitles(data, pageId, config); |
|
|
| if (Array.isArray(data.categories) && data.categories.length > 0) { |
| assignCategorySlugs(data.categories, new Map()); |
| } |
|
|
| if (config[pageId] && config[pageId].template) { |
| if (isVerbose()) log.info(`页面 ${pageId} 使用指定模板`, { template: templateName }); |
| } |
|
|
| return { data, templateName }; |
| } |
|
|
| module.exports = { |
| preparePageData, |
| }; |
|
|