@x-withu/page-withu 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/bin/page-withu.js +8 -63
- package/docs/develop.md +42 -30
- package/package.json +21 -10
- package/{template → src/app}/index.html +1 -1
- package/{template → src/app}/src/app.vue +18 -11
- package/{template/src/components/blog-detail.vue → src/app/src/components/blog_detail.vue} +8 -6
- package/{template → src/app}/src/styles/main.css +264 -47
- package/{template → src}/vite.config.js +7 -6
- /package/{template → src/app}/src/assets/bulb.svg +0 -0
- /package/{template/src/components/blog-list.vue → src/app/src/components/blog_list.vue} +0 -0
- /package/{template → src/app}/src/components/site_card.vue +0 -0
- /package/{template → src/app}/src/components/theme_toggle.vue +0 -0
- /package/{template → src/app}/src/scripts/main.js +0 -0
- /package/{template → src/defaults}/config.js +0 -0
- /package/{template → src/defaults}/content/blog/hello-world.md +0 -0
- /package/{template → src/defaults}/content/domains.md +0 -0
- /package/{template → src/defaults}/content/index.md +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>
|
|
5
|
-
|
|
5
|
+
一款轻量的个人主页生成器
|
|
6
6
|
</strong>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
@@ -41,15 +41,14 @@
|
|
|
41
41
|
# 安装 page-withu
|
|
42
42
|
npm install -g @x-withu/page-withu
|
|
43
43
|
|
|
44
|
-
#
|
|
44
|
+
# 创建项目
|
|
45
45
|
page-withu new my-homepage
|
|
46
|
-
|
|
47
46
|
cd my-homepage
|
|
48
47
|
|
|
49
48
|
# 本地预览
|
|
50
49
|
page-withu serve
|
|
51
50
|
|
|
52
|
-
#
|
|
51
|
+
# 生成静态网站
|
|
53
52
|
page-withu build
|
|
54
53
|
```
|
|
55
54
|
|
package/bin/page-withu.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
3
|
import fs from 'node:fs/promises';
|
|
5
4
|
import { constants as fsConstants } from 'node:fs';
|
|
6
5
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { homedir } from 'node:os';
|
|
8
6
|
import path from 'node:path';
|
|
9
7
|
import { fileURLToPath } from 'node:url';
|
|
10
8
|
|
|
@@ -13,33 +11,18 @@ const pkg = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.
|
|
|
13
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
12
|
const __dirname = path.dirname(__filename);
|
|
15
13
|
const packageRoot = path.resolve(__dirname, '..');
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'highlight.js': '^11.11.1',
|
|
23
|
-
'katex': '^0.16.45',
|
|
24
|
-
'markdown-it': '^14.1.0',
|
|
25
|
-
'markdown-it-emoji': '^3.0.0',
|
|
26
|
-
'markdown-it-footnote': '^4.0.0',
|
|
27
|
-
'mermaid': '^11.15.0',
|
|
28
|
-
'vite': '^6.3.1',
|
|
29
|
-
'vue': '^3.5.13',
|
|
30
|
-
};
|
|
31
|
-
const runtimeKey = createHash('sha256').update(JSON.stringify(runtimeDependencies)).digest('hex').slice(0, 12);
|
|
32
|
-
const runtimeDir = path.join(homedir(), '.page-withu', 'runtime', `${pkg.version}-${runtimeKey}`);
|
|
14
|
+
const sourceDir = path.join(packageRoot, 'src');
|
|
15
|
+
const defaultsDir = path.join(sourceDir, 'defaults');
|
|
16
|
+
const viteConfigPath = path.join(sourceDir, 'vite.config.js');
|
|
17
|
+
const packageRequire = createRequire(import.meta.url);
|
|
18
|
+
const vitePackageDir = path.dirname(packageRequire.resolve('vite/package.json'));
|
|
19
|
+
const viteBin = path.join(vitePackageDir, 'bin/vite.js');
|
|
33
20
|
|
|
34
21
|
const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
|
|
35
22
|
const green = (text) => `\x1b[32m${text}\x1b[0m`;
|
|
36
23
|
const red = (text) => `\x1b[31m${text}\x1b[0m`;
|
|
37
24
|
const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
|
|
38
25
|
|
|
39
|
-
function npmCommand() {
|
|
40
|
-
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
26
|
async function exists(filePath) {
|
|
44
27
|
try {
|
|
45
28
|
await fs.access(filePath, fsConstants.F_OK);
|
|
@@ -64,39 +47,6 @@ function run(command, args, options = {}) {
|
|
|
64
47
|
});
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
async function ensureRuntime() {
|
|
68
|
-
const marker = path.join(runtimeDir, '.ready');
|
|
69
|
-
if (await exists(marker)) return runtimeDir;
|
|
70
|
-
|
|
71
|
-
process.stdout.write('Preparing Page With U runtime...\n');
|
|
72
|
-
try {
|
|
73
|
-
await fs.mkdir(runtimeDir, { recursive: true });
|
|
74
|
-
await fs.writeFile(path.join(runtimeDir, 'package.json'), `${JSON.stringify({
|
|
75
|
-
private: true,
|
|
76
|
-
type: 'module',
|
|
77
|
-
dependencies: runtimeDependencies,
|
|
78
|
-
allowScripts: {
|
|
79
|
-
esbuild: true,
|
|
80
|
-
},
|
|
81
|
-
}, null, 2)}\n`);
|
|
82
|
-
await run(npmCommand(), ['install', '--fund=false', '--audit=false'], {
|
|
83
|
-
cwd: runtimeDir,
|
|
84
|
-
stdio: 'ignore',
|
|
85
|
-
});
|
|
86
|
-
await fs.writeFile(marker, new Date().toISOString());
|
|
87
|
-
process.stdout.write(`${green('Page With U runtime is ready.')}\n`);
|
|
88
|
-
return runtimeDir;
|
|
89
|
-
} catch (err) {
|
|
90
|
-
process.stderr.write(`${red('Failed to prepare Page With U runtime.')}\n`);
|
|
91
|
-
throw err;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function resolveRuntimeFile(runtimeRoot, specifier) {
|
|
96
|
-
const runtimeRequire = createRequire(path.join(runtimeRoot, 'package.json'));
|
|
97
|
-
return runtimeRequire.resolve(specifier);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
50
|
async function detectProject(projectDir) {
|
|
101
51
|
const required = ['config.js', 'content/index.md', 'content/domains.md'];
|
|
102
52
|
const missing = [];
|
|
@@ -121,8 +71,8 @@ async function createProject(projectName = 'my-homepage') {
|
|
|
121
71
|
|
|
122
72
|
try {
|
|
123
73
|
await fs.mkdir(fullPath, { recursive: true });
|
|
124
|
-
await fs.copyFile(path.join(
|
|
125
|
-
await fs.cp(path.join(
|
|
74
|
+
await fs.copyFile(path.join(defaultsDir, 'config.js'), path.join(fullPath, 'config.js'));
|
|
75
|
+
await fs.cp(path.join(defaultsDir, 'content'), path.join(fullPath, 'content'), { recursive: true });
|
|
126
76
|
await fs.mkdir(path.join(fullPath, 'content/blog'), { recursive: true });
|
|
127
77
|
|
|
128
78
|
process.stdout.write(`${green(`Successfully created PageWithU project in ${targetDir}!`)}\n`);
|
|
@@ -146,9 +96,6 @@ async function runVite(command, args) {
|
|
|
146
96
|
process.exit(1);
|
|
147
97
|
}
|
|
148
98
|
|
|
149
|
-
const runtimeRoot = await ensureRuntime();
|
|
150
|
-
const vitePackageDir = path.dirname(resolveRuntimeFile(runtimeRoot, 'vite/package.json'));
|
|
151
|
-
const viteBin = path.join(vitePackageDir, 'bin/vite.js');
|
|
152
99
|
const viteArgs = command === 'serve'
|
|
153
100
|
? ['--config', viteConfigPath, ...args]
|
|
154
101
|
: ['build', '--config', viteConfigPath, ...args];
|
|
@@ -158,9 +105,7 @@ async function runVite(command, args) {
|
|
|
158
105
|
cwd: projectRoot,
|
|
159
106
|
env: {
|
|
160
107
|
...process.env,
|
|
161
|
-
NODE_PATH: path.join(runtimeRoot, 'node_modules'),
|
|
162
108
|
PAGE_WITHU_PROJECT_ROOT: projectRoot,
|
|
163
|
-
PAGE_WITHU_RUNTIME_ROOT: runtimeRoot,
|
|
164
109
|
},
|
|
165
110
|
});
|
|
166
111
|
} catch (err) {
|
package/docs/develop.md
CHANGED
|
@@ -9,23 +9,26 @@ Page With U 的目标是保持轻量、清晰、容易定制。开发时建议
|
|
|
9
9
|
- 优先修改已有文件,不要为了小功能引入复杂架构。
|
|
10
10
|
- 模板默认配置要适合大多数用户开箱即用。
|
|
11
11
|
- 文档中的用户流程要始终和 CLI 行为保持一致。
|
|
12
|
-
-
|
|
13
|
-
- 不要引入大型 UI 框架,样式优先维护在 `
|
|
14
|
-
- Markdown 能力优先在 `
|
|
12
|
+
- 修改包内运行时或默认内容后,需要用源码目录或新生成项目执行一次 `page-withu build`。
|
|
13
|
+
- 不要引入大型 UI 框架,样式优先维护在 `src/app/src/styles/main.css`。
|
|
14
|
+
- Markdown 能力优先在 `src/vite.config.js` 中集中处理,避免分散到多个组件。
|
|
15
15
|
|
|
16
16
|
项目结构:
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
19
|
page-withu/
|
|
20
20
|
├── bin/
|
|
21
|
-
│ └── page-withu.js
|
|
22
|
-
├── docs/
|
|
23
|
-
├──
|
|
24
|
-
│ ├──
|
|
25
|
-
│ ├──
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
├──
|
|
21
|
+
│ └── page-withu.js # CLI 入口
|
|
22
|
+
├── docs/ # 使用、部署、开发文档
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── app/ # 包内 Vue 运行时应用
|
|
25
|
+
│ │ ├── index.html
|
|
26
|
+
│ │ └── src/
|
|
27
|
+
│ ├── defaults/ # 新项目默认配置和内容
|
|
28
|
+
│ │ ├── config.js
|
|
29
|
+
│ │ └── content/
|
|
30
|
+
│ └── vite.config.js # 包内 Markdown 渲染与静态路由生成
|
|
31
|
+
├── package.json # page-withu CLI 与运行时依赖配置
|
|
29
32
|
└── README.md
|
|
30
33
|
```
|
|
31
34
|
|
|
@@ -70,8 +73,8 @@ page-withu new my-homepage
|
|
|
70
73
|
调整 CLI 行为时,通常需要同步修改:
|
|
71
74
|
|
|
72
75
|
- `bin/page-withu.js` 中的 commander command。
|
|
73
|
-
- 必要时修改 `
|
|
74
|
-
-
|
|
76
|
+
- 必要时修改 `src/defaults/config.js` 的默认字段。
|
|
77
|
+
- 必要时修改包内运行时中读取配置的 Vue 组件。
|
|
75
78
|
- 必要时更新 `README.md` 和 `docs/setup.md`。
|
|
76
79
|
|
|
77
80
|
测试脚手架生成流程:
|
|
@@ -86,30 +89,36 @@ node /path/to/page-withu/bin/page-withu.js build
|
|
|
86
89
|
|
|
87
90
|
如果构建成功,说明内容型项目可以通过包内运行时正常生成。
|
|
88
91
|
|
|
89
|
-
##
|
|
92
|
+
## 开发包内运行时与默认内容
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
包内 Vue 运行时应用位于:
|
|
92
95
|
|
|
93
96
|
```text
|
|
94
|
-
|
|
97
|
+
src/app/
|
|
95
98
|
```
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
新项目默认配置和内容位于:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
src/defaults/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
启动运行时开发服务:
|
|
98
107
|
|
|
99
108
|
```bash
|
|
100
|
-
cd
|
|
101
|
-
node
|
|
109
|
+
cd src/defaults
|
|
110
|
+
node ../../bin/page-withu.js serve
|
|
102
111
|
```
|
|
103
112
|
|
|
104
113
|
常见开发位置:
|
|
105
114
|
|
|
106
|
-
- `
|
|
107
|
-
- `
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
110
|
-
- `
|
|
111
|
-
- `
|
|
112
|
-
- `
|
|
115
|
+
- `src/app/src/app.vue`:主页面路由、布局、浏览器标签页标题和 favicon。
|
|
116
|
+
- `src/app/src/components/blog_list.vue`:博客列表、分页、搜索、标签和时间线。
|
|
117
|
+
- `src/app/src/components/blog_detail.vue`:文章详情、ToC、图片放大、Mermaid 渲染。
|
|
118
|
+
- `src/app/src/styles/main.css`:全局样式、亮暗模式、响应式布局、Markdown 样式。
|
|
119
|
+
- `src/vite.config.js`:Markdown 插件、callout、代码高亮、静态 `.html` 路由生成。
|
|
120
|
+
- `src/defaults/config.js`:站点默认配置。
|
|
121
|
+
- `src/defaults/content/`:默认内容样例。
|
|
113
122
|
|
|
114
123
|
修改 Markdown 渲染逻辑后,建议检查这些能力:
|
|
115
124
|
|
|
@@ -118,11 +127,11 @@ node ../bin/page-withu.js serve
|
|
|
118
127
|
- Mermaid、LaTeX、脚注、Emoji 是否能正常渲染。
|
|
119
128
|
- callout 是否在亮色和暗色模式下都可读。
|
|
120
129
|
|
|
121
|
-
|
|
130
|
+
构建默认内容项目:
|
|
122
131
|
|
|
123
132
|
```bash
|
|
124
|
-
cd
|
|
125
|
-
node
|
|
133
|
+
cd src/defaults
|
|
134
|
+
node ../../bin/page-withu.js build
|
|
126
135
|
```
|
|
127
136
|
|
|
128
137
|
如果改动影响脚手架生成后的项目,还需要用 CLI 重新生成一个临时项目并构建。
|
|
@@ -150,7 +159,10 @@ npm run pack:check
|
|
|
150
159
|
确认包中至少包含:
|
|
151
160
|
|
|
152
161
|
- `bin/page-withu.js`
|
|
153
|
-
- `
|
|
162
|
+
- `src/app/`
|
|
163
|
+
- `src/defaults/config.js`
|
|
164
|
+
- `src/defaults/content/`
|
|
165
|
+
- `src/vite.config.js`
|
|
154
166
|
- `docs/`
|
|
155
167
|
- `package.json`
|
|
156
168
|
- `README.md`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-withu/page-withu",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "A lightweight
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "A lightweight personal homepage generator.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/Explorer-Dong/page-withu"
|
|
@@ -12,21 +12,32 @@
|
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin/",
|
|
15
|
-
"template/config.js",
|
|
16
|
-
"template/content/",
|
|
17
|
-
"template/src/",
|
|
18
|
-
"template/index.html",
|
|
19
|
-
"template/vite.config.js",
|
|
20
15
|
"docs/",
|
|
16
|
+
"src/app/",
|
|
17
|
+
"src/defaults/config.js",
|
|
18
|
+
"src/defaults/content/",
|
|
19
|
+
"src/vite.config.js",
|
|
21
20
|
"README.md"
|
|
22
21
|
],
|
|
23
22
|
"scripts": {
|
|
24
23
|
"pack:check": "npm pack --dry-run"
|
|
25
24
|
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@traptitech/markdown-it-katex": "^3.6.0",
|
|
27
|
+
"@vitejs/plugin-vue": "^5.2.3",
|
|
28
|
+
"gray-matter": "^4.0.3",
|
|
29
|
+
"highlight.js": "^11.11.1",
|
|
30
|
+
"katex": "^0.16.45",
|
|
31
|
+
"markdown-it": "^14.1.0",
|
|
32
|
+
"markdown-it-emoji": "^3.0.0",
|
|
33
|
+
"markdown-it-footnote": "^4.0.0",
|
|
34
|
+
"mermaid": "^11.15.0",
|
|
35
|
+
"vite": "^6.3.1",
|
|
36
|
+
"vue": "^3.5.13"
|
|
37
|
+
},
|
|
26
38
|
"keywords": [
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"generator",
|
|
39
|
+
"static site generator",
|
|
40
|
+
"lightweight",
|
|
30
41
|
"vue",
|
|
31
42
|
"vite"
|
|
32
43
|
],
|
|
@@ -41,12 +41,18 @@
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
<footer>
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
<div class="footer-content">
|
|
45
|
+
<div class="footer-meta">
|
|
46
|
+
<p>© {{ userConfig.year }} {{ userConfig.author }}</p>
|
|
47
|
+
<div class="footer-links">
|
|
48
|
+
<template v-for="(link, index) in userConfig.footerLinks" :key="link.label">
|
|
49
|
+
<a :href="link.url">{{ link.label }}</a>
|
|
50
|
+
<span v-if="index < userConfig.footerLinks.length - 1" class="separator">·</span>
|
|
51
|
+
</template>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
<p class="made-with-page-withu">Made with <a href="https://github.com/Explorer-Dong/page-withu" target="_blank"
|
|
55
|
+
rel="noreferrer">PageWithU</a></p>
|
|
50
56
|
</div>
|
|
51
57
|
</footer>
|
|
52
58
|
</main>
|
|
@@ -57,8 +63,8 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
57
63
|
import userConfig from '@page-withu/user-config'
|
|
58
64
|
import ThemeToggle from './components/theme_toggle.vue'
|
|
59
65
|
import SiteCard from './components/site_card.vue'
|
|
60
|
-
import BlogList from './components/
|
|
61
|
-
import BlogDetail from './components/
|
|
66
|
+
import BlogList from './components/blog_list.vue'
|
|
67
|
+
import BlogDetail from './components/blog_detail.vue'
|
|
62
68
|
import { frontmatter as domainsFrontmatter } from '@page-withu/user-content/domains.md'
|
|
63
69
|
import { sections as aboutSections } from '@page-withu/user-content/index.md'
|
|
64
70
|
import defaultFavicon from './assets/bulb.svg'
|
|
@@ -67,13 +73,14 @@ const blogModules = import.meta.glob('@page-withu/user-content/blog/*.md', { eag
|
|
|
67
73
|
const validPages = ['about', 'domains', 'blog']
|
|
68
74
|
const basePath = import.meta.env.BASE_URL.replace(/\/$/, '')
|
|
69
75
|
const navItems = [
|
|
70
|
-
{ page: '
|
|
71
|
-
{ page: '
|
|
76
|
+
{ page: 'about', label: '主页' },
|
|
77
|
+
{ page: 'domains', label: '项目' },
|
|
78
|
+
{ page: 'blog', label: '博客' },
|
|
72
79
|
]
|
|
73
80
|
|
|
74
81
|
const domainsTitle = domainsFrontmatter.title || 'Domains'
|
|
75
82
|
const sites = domainsFrontmatter.sites || []
|
|
76
|
-
const tabTitle = userConfig.tabTitle || userConfig.title || '
|
|
83
|
+
const tabTitle = userConfig.tabTitle || userConfig.title || 'PageWithU'
|
|
77
84
|
const favicon = userConfig.favicon === '/src/assets/bulb.svg' ? defaultFavicon : userConfig.favicon || defaultFavicon
|
|
78
85
|
|
|
79
86
|
function applyDocumentMeta() {
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
<a class="blog-back" :href="blogHref" @click.prevent="$emit('back')">← Back to all posts</a>
|
|
4
4
|
<div :class="['blog-article-layout', { 'without-toc': !toc.length }]">
|
|
5
5
|
<div class="blog-article-main">
|
|
6
|
-
<
|
|
6
|
+
<div class="blog-article-header">
|
|
7
7
|
<h1 class="markdown-body">{{ post.title }}</h1>
|
|
8
8
|
<div class="blog-meta">
|
|
9
9
|
<time :datetime="post.date">{{ formatDate(post.date) }}</time>
|
|
10
10
|
<span v-for="tag in post.tags" :key="tag" class="tag">{{ tag }}</span>
|
|
11
11
|
</div>
|
|
12
|
-
</
|
|
12
|
+
</div>
|
|
13
13
|
|
|
14
14
|
<div ref="contentRef" class="markdown-body" @click="openImage" v-html="post.html"></div>
|
|
15
15
|
|
|
@@ -35,10 +35,12 @@
|
|
|
35
35
|
<button class="blog-toc-close" type="button" aria-label="Close table of contents"
|
|
36
36
|
@click="tocOpen = false">×</button>
|
|
37
37
|
</div>
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
<div class="blog-toc-links">
|
|
39
|
+
<a v-for="item in toc" :key="item.id" :href="`#${item.id}`"
|
|
40
|
+
:class="[`toc-level-${item.level}`, { active: activeHeading === item.id }]" @click="tocOpen = false">
|
|
41
|
+
{{ item.title }}
|
|
42
|
+
</a>
|
|
43
|
+
</div>
|
|
42
44
|
</aside>
|
|
43
45
|
</div>
|
|
44
46
|
|
|
@@ -165,63 +165,115 @@ main {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
/* ===== Header ===== */
|
|
168
|
-
header {
|
|
168
|
+
main > header {
|
|
169
|
+
position: sticky;
|
|
170
|
+
top: 16px;
|
|
171
|
+
z-index: 80;
|
|
169
172
|
display: flex;
|
|
170
173
|
align-items: center;
|
|
171
174
|
justify-content: space-between;
|
|
172
|
-
gap:
|
|
175
|
+
gap: 24px;
|
|
176
|
+
width: min(100%, 720px);
|
|
177
|
+
margin: 0 auto;
|
|
178
|
+
padding: 6px 8px 6px 20px;
|
|
179
|
+
border: 1px solid var(--color-card-border);
|
|
180
|
+
border-radius: 24px;
|
|
181
|
+
background: rgba(255, 255, 255, 0.85);
|
|
182
|
+
box-shadow: 0 14px 38px rgba(0, 0, 0, 0.05), inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
|
183
|
+
backdrop-filter: blur(18px) saturate(1.35);
|
|
184
|
+
-webkit-backdrop-filter: blur(18px) saturate(1.35);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
[data-theme="dark"] main > header,
|
|
188
|
+
:root:not([data-theme="light"]) main > header {
|
|
189
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
190
|
+
background: rgba(18, 18, 18, 0.78);
|
|
191
|
+
box-shadow: 0 14px 38px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main > header::before {
|
|
195
|
+
content: none;
|
|
173
196
|
}
|
|
174
197
|
|
|
175
198
|
.site-title {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
flex: 0 0 auto;
|
|
200
|
+
max-width: 180px;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
color: var(--color-text);
|
|
203
|
+
text-overflow: ellipsis;
|
|
204
|
+
white-space: nowrap;
|
|
205
|
+
font-size: 0.95rem;
|
|
206
|
+
font-weight: 800;
|
|
179
207
|
text-decoration: none;
|
|
180
208
|
margin: 0;
|
|
181
209
|
padding: 0;
|
|
182
210
|
background: none;
|
|
183
211
|
}
|
|
184
212
|
|
|
213
|
+
[data-theme="dark"] .site-title,
|
|
214
|
+
:root:not([data-theme="light"]) .site-title {
|
|
215
|
+
color: #fff;
|
|
216
|
+
}
|
|
217
|
+
|
|
185
218
|
.site-title:hover {
|
|
186
|
-
color: var(--color-
|
|
219
|
+
color: var(--color-link);
|
|
187
220
|
background: none;
|
|
188
221
|
}
|
|
189
222
|
|
|
223
|
+
[data-theme="dark"] .site-title:hover,
|
|
224
|
+
:root:not([data-theme="light"]) .site-title:hover {
|
|
225
|
+
color: #fff;
|
|
226
|
+
}
|
|
227
|
+
|
|
190
228
|
.header-actions {
|
|
191
229
|
display: flex;
|
|
192
230
|
align-items: center;
|
|
193
|
-
gap:
|
|
231
|
+
gap: 24px;
|
|
232
|
+
min-width: 0;
|
|
194
233
|
}
|
|
195
234
|
|
|
196
235
|
/* ===== Navigation Bar ===== */
|
|
197
236
|
.nav-bar {
|
|
198
237
|
display: flex;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
238
|
+
align-items: center;
|
|
239
|
+
gap: 24px;
|
|
240
|
+
min-width: 0;
|
|
241
|
+
padding: 0;
|
|
242
|
+
border: none;
|
|
243
|
+
border-radius: 0;
|
|
244
|
+
background: transparent;
|
|
204
245
|
}
|
|
205
246
|
|
|
206
247
|
.nav-item {
|
|
207
|
-
padding:
|
|
248
|
+
padding: 6px 0;
|
|
208
249
|
font-size: 0.9rem;
|
|
209
|
-
font-weight:
|
|
250
|
+
font-weight: 650;
|
|
210
251
|
color: var(--color-subtext);
|
|
211
252
|
text-decoration: none;
|
|
212
|
-
border-radius:
|
|
213
|
-
transition: color 0.2s ease,
|
|
253
|
+
border-radius: 0;
|
|
254
|
+
transition: color 0.2s ease, text-shadow 0.2s ease;
|
|
214
255
|
background: none;
|
|
256
|
+
white-space: nowrap;
|
|
215
257
|
}
|
|
216
258
|
|
|
217
|
-
.nav-item
|
|
218
|
-
|
|
219
|
-
|
|
259
|
+
[data-theme="dark"] .nav-item,
|
|
260
|
+
:root:not([data-theme="light"]) .nav-item {
|
|
261
|
+
color: rgba(255, 255, 255, 0.68);
|
|
220
262
|
}
|
|
221
263
|
|
|
264
|
+
.nav-item:hover,
|
|
222
265
|
.nav-item.active {
|
|
223
|
-
color: var(--color-
|
|
224
|
-
background:
|
|
266
|
+
color: var(--color-text);
|
|
267
|
+
background: none;
|
|
268
|
+
text-shadow: none;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[data-theme="dark"] .nav-item:hover,
|
|
272
|
+
[data-theme="dark"] .nav-item.active,
|
|
273
|
+
:root:not([data-theme="light"]) .nav-item:hover,
|
|
274
|
+
:root:not([data-theme="light"]) .nav-item.active {
|
|
275
|
+
color: #fff;
|
|
276
|
+
text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
|
|
225
277
|
}
|
|
226
278
|
|
|
227
279
|
/* ===== Section Titles ===== */
|
|
@@ -398,6 +450,17 @@ footer p {
|
|
|
398
450
|
margin-bottom: 6px;
|
|
399
451
|
}
|
|
400
452
|
|
|
453
|
+
.footer-content {
|
|
454
|
+
display: flex;
|
|
455
|
+
align-items: center;
|
|
456
|
+
justify-content: space-between;
|
|
457
|
+
gap: 16px;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.footer-meta {
|
|
461
|
+
min-width: 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
401
464
|
.footer-links {
|
|
402
465
|
display: flex;
|
|
403
466
|
align-items: center;
|
|
@@ -419,40 +482,75 @@ footer p {
|
|
|
419
482
|
color: var(--color-separator);
|
|
420
483
|
}
|
|
421
484
|
|
|
485
|
+
.made-with-page-withu {
|
|
486
|
+
flex-shrink: 0;
|
|
487
|
+
margin: 0;
|
|
488
|
+
color: var(--color-footer-text);
|
|
489
|
+
font-size: 0.85rem;
|
|
490
|
+
text-align: right;
|
|
491
|
+
white-space: nowrap;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.made-with-page-withu a {
|
|
495
|
+
color: var(--color-footer-link);
|
|
496
|
+
font-weight: 600;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.made-with-page-withu a:hover {
|
|
500
|
+
color: var(--color-footer-link-hover);
|
|
501
|
+
background: var(--color-footer-link-hover-bg);
|
|
502
|
+
}
|
|
503
|
+
|
|
422
504
|
/* ===== Theme Toggle ===== */
|
|
423
505
|
.theme-toggle {
|
|
424
|
-
width:
|
|
425
|
-
height:
|
|
426
|
-
border:
|
|
506
|
+
width: 34px;
|
|
507
|
+
height: 34px;
|
|
508
|
+
border: none;
|
|
427
509
|
border-radius: 50%;
|
|
428
|
-
background:
|
|
510
|
+
background: transparent;
|
|
429
511
|
cursor: pointer;
|
|
430
512
|
display: flex;
|
|
431
513
|
align-items: center;
|
|
432
514
|
justify-content: center;
|
|
433
|
-
transition: background-color 0.
|
|
434
|
-
|
|
515
|
+
transition: background-color 0.2s ease, transform 0.15s ease, color 0.2s ease;
|
|
516
|
+
color: var(--color-subtext);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
[data-theme="dark"] .theme-toggle,
|
|
520
|
+
:root:not([data-theme="light"]) .theme-toggle {
|
|
521
|
+
color: rgba(255, 255, 255, 0.68);
|
|
435
522
|
}
|
|
436
523
|
|
|
437
524
|
.theme-toggle:hover {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
525
|
+
background: var(--color-link-hover-bg);
|
|
526
|
+
color: var(--color-text);
|
|
527
|
+
text-shadow: none;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
[data-theme="dark"] .theme-toggle:hover,
|
|
531
|
+
:root:not([data-theme="light"]) .theme-toggle:hover {
|
|
532
|
+
background: rgba(255, 255, 255, 0.1);
|
|
533
|
+
color: #fff;
|
|
534
|
+
text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
|
|
441
535
|
}
|
|
442
536
|
|
|
443
537
|
.theme-toggle:active {
|
|
444
|
-
transform: scale(0.
|
|
538
|
+
transform: scale(0.92);
|
|
445
539
|
}
|
|
446
540
|
|
|
447
541
|
.theme-toggle svg {
|
|
448
|
-
width:
|
|
449
|
-
height:
|
|
542
|
+
width: 18px;
|
|
543
|
+
height: 18px;
|
|
450
544
|
fill: none;
|
|
451
|
-
stroke:
|
|
545
|
+
stroke: currentColor;
|
|
452
546
|
stroke-width: 2;
|
|
453
547
|
stroke-linecap: round;
|
|
454
548
|
stroke-linejoin: round;
|
|
455
|
-
transition:
|
|
549
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.theme-toggle:hover svg {
|
|
553
|
+
transform: rotate(15deg);
|
|
456
554
|
}
|
|
457
555
|
|
|
458
556
|
.theme-toggle .icon-sun {
|
|
@@ -553,6 +651,39 @@ footer p {
|
|
|
553
651
|
padding: 16px;
|
|
554
652
|
overflow-x: auto;
|
|
555
653
|
margin-bottom: 1em;
|
|
654
|
+
scrollbar-width: thin;
|
|
655
|
+
scrollbar-color: transparent transparent;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.markdown-body pre:hover {
|
|
659
|
+
scrollbar-color: color-mix(in srgb, var(--color-subtext) 45%, transparent) transparent;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.markdown-body pre::-webkit-scrollbar {
|
|
663
|
+
height: 4px;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.markdown-body pre::-webkit-scrollbar-button {
|
|
667
|
+
display: none;
|
|
668
|
+
width: 0;
|
|
669
|
+
height: 0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.markdown-body pre::-webkit-scrollbar-track {
|
|
673
|
+
background: transparent;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.markdown-body pre::-webkit-scrollbar-thumb {
|
|
677
|
+
background: transparent;
|
|
678
|
+
border-radius: 999px;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.markdown-body pre:hover::-webkit-scrollbar-thumb {
|
|
682
|
+
background: color-mix(in srgb, var(--color-subtext) 42%, transparent);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.markdown-body pre::-webkit-scrollbar-thumb:hover {
|
|
686
|
+
background: color-mix(in srgb, var(--color-subtext) 62%, transparent);
|
|
556
687
|
}
|
|
557
688
|
|
|
558
689
|
.markdown-body pre code {
|
|
@@ -1007,12 +1138,53 @@ footer p {
|
|
|
1007
1138
|
flex-direction: column;
|
|
1008
1139
|
gap: 8px;
|
|
1009
1140
|
max-height: calc(100vh - 48px);
|
|
1010
|
-
padding: 0
|
|
1141
|
+
padding: 0 4px 16px 16px;
|
|
1011
1142
|
border-left: 1px solid var(--color-footer-border);
|
|
1012
|
-
overflow
|
|
1143
|
+
overflow: hidden;
|
|
1013
1144
|
font-size: 0.84rem;
|
|
1014
1145
|
}
|
|
1015
1146
|
|
|
1147
|
+
.blog-toc-links {
|
|
1148
|
+
display: flex;
|
|
1149
|
+
flex-direction: column;
|
|
1150
|
+
gap: 8px;
|
|
1151
|
+
min-height: 0;
|
|
1152
|
+
overflow-y: auto;
|
|
1153
|
+
scrollbar-width: thin;
|
|
1154
|
+
scrollbar-color: transparent transparent;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.blog-toc-links:hover {
|
|
1158
|
+
scrollbar-color: color-mix(in srgb, var(--color-subtext) 45%, transparent) transparent;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
.blog-toc-links::-webkit-scrollbar {
|
|
1162
|
+
width: 4px;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.blog-toc-links::-webkit-scrollbar-button {
|
|
1166
|
+
display: none;
|
|
1167
|
+
width: 0;
|
|
1168
|
+
height: 0;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.blog-toc-links::-webkit-scrollbar-track {
|
|
1172
|
+
background: transparent;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.blog-toc-links::-webkit-scrollbar-thumb {
|
|
1176
|
+
background: transparent;
|
|
1177
|
+
border-radius: 999px;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.blog-toc-links:hover::-webkit-scrollbar-thumb {
|
|
1181
|
+
background: color-mix(in srgb, var(--color-subtext) 42%, transparent);
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.blog-toc-links::-webkit-scrollbar-thumb:hover {
|
|
1185
|
+
background: color-mix(in srgb, var(--color-subtext) 62%, transparent);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1016
1188
|
.blog-toc-header {
|
|
1017
1189
|
display: flex;
|
|
1018
1190
|
align-items: center;
|
|
@@ -1204,14 +1376,34 @@ footer p {
|
|
|
1204
1376
|
gap: 28px;
|
|
1205
1377
|
}
|
|
1206
1378
|
|
|
1207
|
-
header {
|
|
1208
|
-
|
|
1209
|
-
|
|
1379
|
+
main > header {
|
|
1380
|
+
top: 12px;
|
|
1381
|
+
gap: 18px;
|
|
1382
|
+
width: 100%;
|
|
1383
|
+
padding: 8px 10px 8px 18px;
|
|
1210
1384
|
}
|
|
1211
1385
|
|
|
1212
1386
|
.header-actions {
|
|
1213
|
-
|
|
1214
|
-
|
|
1387
|
+
flex: 1;
|
|
1388
|
+
width: auto;
|
|
1389
|
+
justify-content: flex-end;
|
|
1390
|
+
gap: 18px;
|
|
1391
|
+
min-width: 0;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.nav-bar {
|
|
1395
|
+
gap: 18px;
|
|
1396
|
+
overflow-x: auto;
|
|
1397
|
+
scrollbar-width: none;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
.nav-bar::-webkit-scrollbar {
|
|
1401
|
+
display: none;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
.theme-toggle {
|
|
1405
|
+
width: 38px;
|
|
1406
|
+
height: 38px;
|
|
1215
1407
|
}
|
|
1216
1408
|
}
|
|
1217
1409
|
|
|
@@ -1227,12 +1419,13 @@ footer p {
|
|
|
1227
1419
|
}
|
|
1228
1420
|
|
|
1229
1421
|
.site-title {
|
|
1230
|
-
|
|
1422
|
+
max-width: 120px;
|
|
1423
|
+
font-size: 0.9rem;
|
|
1231
1424
|
}
|
|
1232
1425
|
|
|
1233
1426
|
.nav-item {
|
|
1234
|
-
padding:
|
|
1235
|
-
font-size: 0.
|
|
1427
|
+
padding: 7px 0;
|
|
1428
|
+
font-size: 0.86rem;
|
|
1236
1429
|
}
|
|
1237
1430
|
|
|
1238
1431
|
.about-section {
|
|
@@ -1322,6 +1515,7 @@ footer p {
|
|
|
1322
1515
|
box-shadow: var(--shadow-card);
|
|
1323
1516
|
transform: translate(-50%, -50%);
|
|
1324
1517
|
display: none;
|
|
1518
|
+
overflow: hidden;
|
|
1325
1519
|
}
|
|
1326
1520
|
|
|
1327
1521
|
.blog-toc.open {
|
|
@@ -1329,12 +1523,11 @@ footer p {
|
|
|
1329
1523
|
}
|
|
1330
1524
|
|
|
1331
1525
|
.blog-toc-header {
|
|
1332
|
-
position: sticky;
|
|
1333
|
-
top: -16px;
|
|
1334
1526
|
margin: -16px -16px 4px;
|
|
1335
1527
|
padding: 16px;
|
|
1336
1528
|
background: var(--color-card-bg);
|
|
1337
1529
|
border-bottom: 1px solid var(--color-footer-border);
|
|
1530
|
+
flex-shrink: 0;
|
|
1338
1531
|
}
|
|
1339
1532
|
|
|
1340
1533
|
.blog-toc-close {
|
|
@@ -1377,3 +1570,27 @@ footer p {
|
|
|
1377
1570
|
height: 36px;
|
|
1378
1571
|
}
|
|
1379
1572
|
}
|
|
1573
|
+
|
|
1574
|
+
@media (max-width: 520px) {
|
|
1575
|
+
main > header {
|
|
1576
|
+
padding-left: 14px;
|
|
1577
|
+
gap: 14px;
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
.site-title {
|
|
1581
|
+
max-width: 80px;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.header-actions {
|
|
1585
|
+
gap: 14px;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
.nav-bar {
|
|
1589
|
+
gap: 14px;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
.theme-toggle {
|
|
1593
|
+
width: 32px;
|
|
1594
|
+
height: 32px;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
@@ -5,11 +5,12 @@ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
|
5
5
|
import { dirname, join, resolve } from 'node:path'
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const sourceRoot = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const appRoot = resolve(sourceRoot, 'app')
|
|
9
10
|
const projectRoot = resolve(process.env.PAGE_WITHU_PROJECT_ROOT || process.cwd())
|
|
10
|
-
const
|
|
11
|
-
const dependencyRoot =
|
|
12
|
-
const runtimeRequire =
|
|
11
|
+
const packageRoot = resolve(sourceRoot, '..')
|
|
12
|
+
const dependencyRoot = resolve(packageRoot, 'node_modules')
|
|
13
|
+
const runtimeRequire = createRequire(import.meta.url)
|
|
13
14
|
const loadRuntimeModule = async (specifier) => import(pathToFileURL(runtimeRequire.resolve(specifier)).href)
|
|
14
15
|
const runtimeModuleAlias = Object.fromEntries([
|
|
15
16
|
'vue',
|
|
@@ -214,7 +215,7 @@ export default defineConfig(async () => {
|
|
|
214
215
|
const userConfig = (await import(`${pathToFileURL(userConfigFile).href}?t=${Date.now()}`)).default
|
|
215
216
|
|
|
216
217
|
return {
|
|
217
|
-
root:
|
|
218
|
+
root: appRoot,
|
|
218
219
|
base: basePath,
|
|
219
220
|
cacheDir,
|
|
220
221
|
resolve: {
|
|
@@ -228,7 +229,7 @@ export default defineConfig(async () => {
|
|
|
228
229
|
server: {
|
|
229
230
|
port: 5500,
|
|
230
231
|
fs: {
|
|
231
|
-
allow: [
|
|
232
|
+
allow: [appRoot, projectRoot, dependencyRoot],
|
|
232
233
|
},
|
|
233
234
|
},
|
|
234
235
|
build: {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|