Skip to content

Commit 2b07cb0

Browse files
authored
Merge pull request #29 from devlive-community/dev
支持站点地图生成
2 parents c4236fc + 85e52e5 commit 2b07cb0

File tree

6 files changed

+165
-4
lines changed

6 files changed

+165
-4
lines changed

docs/content/setup/sitemap.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: 站点地图
3+
icon: map
4+
---
5+
6+
PageForge 支持站点地图,可以帮助你快速找到你的网站。默认是禁用的,需要在 `pageforge.yaml` 配置文件中启用。
7+
8+
## 启用站点地图
9+
10+
---
11+
12+
```yaml
13+
feature:
14+
sitemap:
15+
enable: true
16+
```
17+
18+
> 启用站点地图后,将在底部出现站点地图链接。
19+
20+
!!! danger "注意"
21+
22+
启用站点地图,必须要在 `pageforge.yaml` 文件中添加以下配置
23+
24+
```yaml
25+
site:
26+
baseUrl: https://pageforge.devlive.org
27+
```
28+
29+
!!!

docs/pageforge.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ site:
44
description: PageForge 是一款现代化的静态页面生成与部署平台,旨在帮助用户快速创建精美的静态网站,并一键部署到 GitHub Pages。 无论是个人博客、项目文档还是企业官网,PageForge 都能让你轻松实现高效构建、智能部署和即时上线。
55
logo: /assets/logo.svg
66
favicon: assets/logo.svg
7+
baseUrl: https://pageforge.devlive.org
78

89
cdn:
910
style: https://cdn.tailwindcss.com
@@ -50,6 +51,8 @@ feature:
5051
qq: true
5152
wechat: true
5253
weibo: true
54+
sitemap:
55+
enable: true
5356

5457
i18n:
5558
default: zh-CN
@@ -125,6 +128,7 @@ nav:
125128
- /setup/i18n
126129
- /setup/compress
127130
- /setup/share
131+
- /setup/sitemap
128132
- InlineTemplate:
129133
- /setup/template/home
130134
- /setup/template/enterprise

lib/directory-processor.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ class DirectoryProcessor {
186186
else {
187187
await this.processDirectory(sourceDir, '', '', this.config.sourcePath);
188188
}
189+
190+
// 生成站点地图
191+
if (isFeatureEnabled(this.config, 'sitemap')) {
192+
await this.fileProcessor.generateSitemap();
193+
}
189194
}
190195

191196
async processDirectory(sourceDir, baseDir = '', locale = '', rootSourceDir) {

lib/file-processor.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const {execSync} = require('child_process');
1919
const TemplateEngine = require("./template-engine");
2020
const {setContext} = require("./extension/marked/pageforge-ejs");
2121
const minify = require('html-minifier').minify;
22+
const SitemapGenerator = require('./sitemap-generator');
2223

2324
class FileProcessor {
2425
constructor(config, pages, sourcePath, outputPath) {
@@ -48,7 +49,7 @@ class FileProcessor {
4849

4950
// 合并用户配置
5051
this.minifyOptions = config.feature?.compress?.enable ?
51-
{ ...baseOptions, ...config.feature.compress.options } :
52+
{...baseOptions, ...config.feature.compress.options} :
5253
false;
5354
}
5455

@@ -61,6 +62,16 @@ class FileProcessor {
6162
return this.navigationCache.get(locale);
6263
}
6364

65+
// 构建站点地图数据
66+
async generateSitemap() {
67+
const sitemapGenerator = new SitemapGenerator(
68+
this.config,
69+
this.pages,
70+
(locale) => this.getCachedNavigation(locale)
71+
);
72+
sitemapGenerator.generate();
73+
}
74+
6475
// 解析元数据中的 EJS 模板
6576
parseMetadataTemplates(data, context) {
6677
const processed = {...data};
@@ -254,7 +265,8 @@ class FileProcessor {
254265

255266
if (!hasLocaleFile && isFeatureEnabled(this.config, 'i18n') && isDefaultLanguageFile) {
256267
console.log(`📄 未找到 ${locale} 语言文件,使用默认语言(${this.config.i18n?.default})文件: ${filename}`);
257-
} else {
268+
}
269+
else {
258270
console.log(`📄 正在跳过文件 ${filename} 不是当前语言文件`);
259271
return;
260272
}
@@ -304,7 +316,7 @@ class FileProcessor {
304316
let html = await this.templateEngine.renderWithLayout('layouts/page', pageData);
305317

306318
// 压缩 HTML
307-
if (this.config.feature?.compress?.enable !== false) {
319+
if (this.config.feature?.compress?.enable) {
308320
try {
309321
html = minify(html, this.minifyOptions);
310322
console.log(`🗜️ 压缩 ${relativePath} HTML 完成`);

lib/sitemap-generator.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const {isFeatureEnabled} = require('./utils');
4+
5+
class SitemapGenerator {
6+
constructor(config, pages, getNavigation) {
7+
this.config = config;
8+
this.pages = pages;
9+
this.getNavigation = getNavigation;
10+
this.baseUrl = config?.site?.baseUrl || '';
11+
}
12+
13+
// 获取语言代码
14+
getLanguageCode(lang) {
15+
return typeof lang === 'object' ? lang.key : lang;
16+
}
17+
18+
// 生成单个 URL 节点
19+
generateUrlNode(url, lang, metadata) {
20+
const normalizedUrl = url.startsWith('/') ? url.substring(1) : url;
21+
const isI18nEnabled = isFeatureEnabled(this.config, 'i18n');
22+
const langCode = this.getLanguageCode(lang);
23+
24+
// 当启用国际化时才添加语言前缀
25+
const fullUrl = isI18nEnabled ?
26+
`${langCode}/${normalizedUrl}` :
27+
normalizedUrl;
28+
29+
return ` <url>
30+
<loc>${this.baseUrl}/${fullUrl}</loc>${metadata?.gitInfo?.revision?.lastModifiedTime ? `
31+
<lastmod>${metadata.gitInfo.revision.lastModifiedTime}</lastmod>` : ''}
32+
<changefreq>weekly</changefreq>
33+
<priority>0.5</priority>
34+
</url>`;
35+
}
36+
37+
// 处理导航项
38+
processNavItem(item, lang, urls) {
39+
if (!item) {
40+
return;
41+
}
42+
43+
if (item.href) {
44+
// 将 /.html 转换为 /index.html
45+
const normalizedHref = item.href === '/.html' ? '/index.html' : item.href;
46+
47+
const metadata = this.pages.get(normalizedHref) || {};
48+
49+
const langCode = this.getLanguageCode(lang);
50+
const key = `${langCode}:${normalizedHref}`;
51+
urls.set(key, {
52+
lang: langCode,
53+
url: normalizedHref,
54+
metadata
55+
});
56+
}
57+
58+
// 递归处理子项
59+
if (Array.isArray(item.items)) {
60+
item.items.forEach(subItem => this.processNavItem(subItem, lang, urls));
61+
}
62+
}
63+
64+
// 收集所有 URL
65+
collectNavigationUrls() {
66+
const urls = new Map();
67+
const isI18nEnabled = isFeatureEnabled(this.config, 'i18n');
68+
69+
// 如果没有启用国际化,则只使用空数组作为默认语言
70+
const languages = isI18nEnabled
71+
? (this.config.languages || [this.config.i18n?.default || 'en'])
72+
: ['']; // 改为空字符串,表示没有语言前缀
73+
74+
// 收集每种语言的 URL
75+
for (const lang of languages) {
76+
const navItems = this.getNavigation(lang) || [];
77+
navItems.forEach(item => this.processNavItem(item, lang, urls));
78+
}
79+
80+
return urls;
81+
}
82+
83+
// 生成站点地图
84+
generate() {
85+
const urls = this.collectNavigationUrls();
86+
87+
let sitemap = `<?xml version="1.0" encoding="UTF-8"?>
88+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
89+
`;
90+
// 为每个语言版本生成独立的 URL 条目
91+
for (const [key, {lang, url, metadata}] of urls.entries()) {
92+
sitemap += this.generateUrlNode(url, lang, metadata);
93+
sitemap += '\n';
94+
}
95+
96+
sitemap += '</urlset>';
97+
98+
const outputPath = path.join(this.config.outputPath, 'sitemap.xml');
99+
fs.writeFileSync(outputPath, sitemap, 'utf8');
100+
console.log('✓ 生成站点地图完成');
101+
102+
return sitemap;
103+
}
104+
}
105+
106+
module.exports = SitemapGenerator;

templates/includes/footer.ejs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
alt="<%= siteData.site.title %>"
1111
class="h-12">
1212
</a>
13-
<a href="<%= pageData.language ? `/${pageData.language}` : '/' %>" class="text-xl font-bold text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-200 transition-colors">
13+
<a href="<%= pageData.language ? `/${pageData.language}` : '/' %>"
14+
class="text-xl font-bold text-gray-900 dark:text-white hover:text-gray-700 dark:hover:text-gray-200 transition-colors">
1415
<%= !siteData.site.hiddenTitle ? siteData.site.title : '' %>
1516
</a>
1617
</div>
@@ -53,6 +54,10 @@
5354
<div class="flex flex-col items-center sm:flex-row sm:items-center justify-between gap-4">
5455
<span class="text-sm text-gray-500 dark:text-gray-400">
5556
<%= locals.siteData?.footer?.copyright %> 使用 ❤️ <a href="https://pageforge.devlive.org" class="hover:text-blue-600" target="_blank">PageForge</a> 构建
57+
58+
<% if (locals.siteData?.feature?.sitemap?.enable) { %>
59+
<a href="/sitemap.xml" target="_blank" class="ml-3 dark:hover:text-white hover:underline hover:text-blue-600">站点地图</a>
60+
<% } %>
5661
</span>
5762
5863
<% if (locals.siteData?.footer?.social) { %>

0 commit comments

Comments
 (0)