feat(templates):新增 Markdown 内容页支持

新增 template: content:构建期使用 markdown-it 将本地Markdown 渲染为 HTML(禁用 raw HTML/图片),并按MeNav的URLscheme白名单策略对链接做安全降级
This commit is contained in:
rbetree
2026-01-20 17:43:06 +08:00
parent f773b9e290
commit 280d376bac
13 changed files with 642 additions and 0 deletions

View File

@@ -12,6 +12,8 @@ const {
} = 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');
@@ -83,6 +85,48 @@ function applyBookmarksData(data, pageId) {
}
}
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',
]);
}
// 本期仅支持读取本地 markdown 文件
const fs = require('node:fs');
const path = require('node:path');
// 允许用户以相对 repo root 的路径引用推荐约定content/*.md
// 同时允许绝对路径(便于本地调试/临时文件),但不会自动复制资源到 dist。
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';
@@ -158,6 +202,10 @@ function preparePageData(pageId, config) {
applyBookmarksData(data, pageId);
}
if (templateName === 'content') {
applyContentData(data, pageId, config);
}
applyHomePageTitles(data, pageId, config);
if (Array.isArray(data.categories) && data.categories.length > 0) {