Skip to content

Commit 41ab1a7

Browse files
committed
Update
1 parent dd6090d commit 41ab1a7

File tree

8 files changed

+283
-1
lines changed

8 files changed

+283
-1
lines changed

config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ images:
88
- name: fragment
99
context: ./images/fragment
1010
image: raineblog/mkdocs-docker-fragment
11+
- name: rspress
12+
context: ./images/rspress
13+
image: raineblog/mkdocs-rspress-docker

images/rspress/Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# syntax=docker/dockerfile:1
2+
FROM node:alpine
3+
RUN apk add --no-cache \
4+
bash tini tar zstd \
5+
git git-fast-import \
6+
ca-certificates curl wget
7+
RUN git config --system --add safe.directory /__w/*
8+
WORKDIR /app
9+
COPY templates/ /app/
10+
RUN npm install
11+
ENTRYPOINT ["/sbin/tini", "--"]
12+
CMD ["/usr/local/bin/rspress-build"]

images/rspress/bin/rspress-build

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
set -e
3+
4+
cp /app/package.json .
5+
cp -r /app/styles .
6+
7+
cp /app/rspress.config.ts .
8+
cp /app/tsconfig.json .
9+
10+
rspress build "$@"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "whk",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"build": "rspress build",
8+
"dev": "rspress dev",
9+
"preview": "rspress preview"
10+
},
11+
"dependencies": {
12+
"@rspress/core": "^2.0.2",
13+
"rspress-plugin-reading-time": "^1.0.0"
14+
},
15+
"devDependencies": {
16+
"@rspress/plugin-rss": "^2.0.3",
17+
"@rspress/plugin-sitemap": "^2.0.3",
18+
"@types/node": "^22.8.1",
19+
"@types/react": "^19.2.13",
20+
"@types/react-dom": "^19.2.3",
21+
"react": "^19.2.4",
22+
"react-dom": "^19.2.4"
23+
}
24+
}
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import path from 'path';
2+
import fs from 'fs';
3+
import { defineConfig } from '@rspress/core';
4+
import { pluginRss } from '@rspress/plugin-rss';
5+
import { pluginSitemap } from '@rspress/plugin-sitemap';
6+
import readingTime from 'rspress-plugin-reading-time';
7+
8+
// 读取配置文件
9+
const readConfig = (name: string) => {
10+
const filePath = path.join(__dirname, 'config', `${name}.json`);
11+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
12+
};
13+
14+
const projectConfig = readConfig('project');
15+
const navConfig = readConfig('nav');
16+
const extraConfig = readConfig('extra');
17+
18+
// 解析 nav.json 为 Rspress 格式
19+
function parseNavAndSidebar(config: any[]) {
20+
const nav: any[] = [];
21+
const sidebar: Record<string, any[]> = {};
22+
23+
// 辅助函数:处理链接,将 xxx/index.md 或 xxx.md 转换为正确的路由
24+
const normalizeLink = (link: string) => {
25+
let normalized = link.replace(/\.md$/, '');
26+
if (normalized.endsWith('/index')) {
27+
normalized = normalized.slice(0, -6);
28+
}
29+
return `/${normalized}`;
30+
};
31+
32+
config.forEach((item) => {
33+
// 处理导航栏
34+
const navItem: any = {
35+
text: item.title,
36+
};
37+
38+
// 寻找导航项对应的链接 (通常是第一个子页面)
39+
const findFirstLink = (children: any[]): string | undefined => {
40+
for (const child of children) {
41+
if (typeof child === 'string') return normalizeLink(child);
42+
if (typeof child === 'object') {
43+
const keys = Object.keys(child);
44+
if (keys.length > 0 && Array.isArray(child[keys[0]])) {
45+
return findFirstLink(child[keys[0]]);
46+
}
47+
}
48+
}
49+
return undefined;
50+
};
51+
52+
if (item.children) {
53+
const link = findFirstLink(item.children);
54+
if (link) {
55+
navItem.link = link;
56+
}
57+
58+
// 处理侧边栏
59+
const sidebarItems = item.children.map((child: any) => {
60+
if (typeof child === 'string') {
61+
// 如果 nav.json 中直接写了路径,也需要处理(虽然 Rspress 侧边栏支持字符串路径,但我们统一样式)
62+
return {
63+
text: path.basename(child, '.md'),
64+
link: normalizeLink(child),
65+
};
66+
} else {
67+
// 处理嵌套结构,例如 {"分类名": ["path/to/doc.md"]}
68+
const sectionTitle = Object.keys(child)[0];
69+
return {
70+
text: sectionTitle,
71+
collapsible: true,
72+
collapsed: false,
73+
items: child[sectionTitle].map((sub: string) => {
74+
let text = path.basename(sub, '.md');
75+
if (text === 'index') {
76+
// 如果是 index.md,则取其父目录名作为标题(或者根据实际需求调整)
77+
const parts = sub.split('/');
78+
text = parts[parts.length - 2] || 'Index';
79+
}
80+
return {
81+
text: text,
82+
link: normalizeLink(sub),
83+
};
84+
}),
85+
};
86+
}
87+
});
88+
89+
// 如果有子项,则将其分配给对应的侧边栏分组
90+
if (link) {
91+
const parts = link.split('/').filter(Boolean);
92+
const prefix = parts.length > 0 ? `/${parts[0]}/` : '/';
93+
sidebar[prefix] = sidebarItems;
94+
}
95+
}
96+
97+
nav.push(navItem);
98+
});
99+
100+
return { nav, sidebar };
101+
}
102+
103+
const { nav, sidebar } = parseNavAndSidebar(navConfig);
104+
105+
// 支持的社交链接图标
106+
const SUPPORTED_SOCIAL_ICONS = [
107+
'lark', 'discord', 'facebook', 'github', 'instagram', 'linkedin',
108+
'slack', 'x', 'youtube', 'wechat', 'qq', 'juejin', 'zhihu',
109+
'bilibili', 'weibo', 'gitlab', 'X', 'bluesky', 'npm'
110+
];
111+
112+
export default defineConfig({
113+
root: path.join(__dirname, 'docs'),
114+
title: projectConfig.info.site_name,
115+
description: projectConfig.info.site_description,
116+
lang: 'zh',
117+
logoText: 'rspress',
118+
icon: projectConfig.theme.favicon.startsWith('.')
119+
? projectConfig.theme.favicon
120+
: `/${projectConfig.theme.favicon}`,
121+
logo: projectConfig.theme.logo.startsWith('.')
122+
? projectConfig.theme.logo
123+
: `/${projectConfig.theme.logo}`,
124+
llms: true,
125+
globalStyles: path.join(__dirname, 'styles/custom.css'),
126+
themeConfig: {
127+
nav,
128+
sidebar,
129+
llmsUI: false,
130+
enableContentAnimation: true,
131+
enableAppearanceAnimation: true,
132+
enableScrollToTop: true,
133+
footer: {
134+
message: projectConfig.info.copyright,
135+
},
136+
socialLinks: (extraConfig.social || []).map((s: any) => {
137+
let icon = s.icon;
138+
139+
// 如果已经是对象格式 { svg: '...' }
140+
if (typeof icon === 'object' && icon !== null && icon.svg) {
141+
return {
142+
icon: icon,
143+
mode: 'link',
144+
content: s.link,
145+
};
146+
}
147+
148+
// 处理旧的 fontawesome 路径或 url 编码路径
149+
if (typeof icon === 'string' && icon.includes('/')) {
150+
const parts = icon.split('/');
151+
icon = parts[parts.length - 1].replace('x-twitter', 'x');
152+
}
153+
154+
// 如果是自定义 SVG 字符串
155+
if (typeof icon === 'string' && icon.trim().startsWith('<svg')) {
156+
return {
157+
icon: { svg: icon },
158+
mode: 'link',
159+
content: s.link,
160+
};
161+
}
162+
163+
// 仅保留支持的内置图标
164+
if (typeof icon === 'string' && SUPPORTED_SOCIAL_ICONS.includes(icon)) {
165+
return {
166+
icon: icon,
167+
mode: 'link',
168+
content: s.link,
169+
};
170+
}
171+
172+
return null;
173+
}).filter(Boolean),
174+
editLink: {
175+
docRepoBaseUrl: `${projectConfig.info.repo_url}/blob/main/docs/`,
176+
},
177+
},
178+
plugins: [
179+
readingTime({
180+
defaultLocale: 'zh-CN',
181+
}),
182+
pluginRss({
183+
siteUrl: projectConfig.info.repo_url,
184+
feed: {
185+
id: 'blog',
186+
test: '/blog/',
187+
item: (item: any, page: any) => {
188+
if (!item.date) {
189+
// 尝试从路径中匹配日期 (例如 2026-01-26)
190+
const dateMatch = page.routePath.match(/(\d{4}-\d{2}-\d{2})/);
191+
if (dateMatch) {
192+
item.date = new Date(dateMatch[1]);
193+
} else {
194+
// 如果无法匹配,默认使用当前时间,避免 toISOString() 报错
195+
item.date = new Date();
196+
}
197+
}
198+
return item;
199+
},
200+
},
201+
}),
202+
pluginSitemap({
203+
siteUrl: projectConfig.info.repo_url,
204+
}),
205+
],
206+
});

images/rspress/templates/styles/custom.css

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"compilerOptions": {
3+
"lib": ["DOM", "ES2020"],
4+
"jsx": "react-jsx",
5+
"target": "ES2020",
6+
"noEmit": true,
7+
"skipLibCheck": true,
8+
"useDefineForClassFields": true,
9+
10+
/* modules */
11+
"module": "ESNext",
12+
"moduleDetection": "force",
13+
"moduleResolution": "bundler",
14+
"verbatimModuleSyntax": true,
15+
"resolveJsonModule": true,
16+
"allowImportingTsExtensions": true,
17+
"isolatedModules": true,
18+
19+
/* type checking */
20+
"strict": true,
21+
"noUnusedLocals": true,
22+
"noUnusedParameters": true
23+
},
24+
"include": ["docs", "theme", "rspress.config.ts"],
25+
"mdx": {
26+
"checkMdx": true
27+
}
28+
}

images/starlight/Dockerfile

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)