@x-withu/page-withu 1.1.0 → 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 +147 -65
- package/docs/develop.md +42 -30
- package/package.json +15 -21
- 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 +40 -15
- /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,31 +1,58 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import { constants as fsConstants } from 'node:fs';
|
|
3
5
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { Command } from 'commander';
|
|
5
|
-
import fs from 'fs-extra';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
import ora from 'ora';
|
|
10
8
|
|
|
11
|
-
const pkg = await fs.
|
|
9
|
+
const pkg = JSON.parse(await fs.readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
|
12
10
|
|
|
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
|
-
const
|
|
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'));
|
|
20
19
|
const viteBin = path.join(vitePackageDir, 'bin/vite.js');
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
const cyan = (text) => `\x1b[36m${text}\x1b[0m`;
|
|
22
|
+
const green = (text) => `\x1b[32m${text}\x1b[0m`;
|
|
23
|
+
const red = (text) => `\x1b[31m${text}\x1b[0m`;
|
|
24
|
+
const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
|
|
25
|
+
|
|
26
|
+
async function exists(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(filePath, fsConstants.F_OK);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function run(command, args, options = {}) {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const child = spawn(command, args, { stdio: 'inherit', ...options });
|
|
38
|
+
child.on('error', reject);
|
|
39
|
+
child.on('exit', (code, signal) => {
|
|
40
|
+
if (signal) {
|
|
41
|
+
process.kill(process.pid, signal);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (code === 0) resolve();
|
|
45
|
+
else reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
22
49
|
|
|
23
50
|
async function detectProject(projectDir) {
|
|
24
51
|
const required = ['config.js', 'content/index.md', 'content/domains.md'];
|
|
25
52
|
const missing = [];
|
|
26
53
|
|
|
27
54
|
for (const item of required) {
|
|
28
|
-
if (!await
|
|
55
|
+
if (!await exists(path.join(projectDir, item))) missing.push(item);
|
|
29
56
|
}
|
|
30
57
|
|
|
31
58
|
return missing;
|
|
@@ -35,26 +62,26 @@ async function createProject(projectName = 'my-homepage') {
|
|
|
35
62
|
const targetDir = projectName.trim() || 'my-homepage';
|
|
36
63
|
const fullPath = path.resolve(process.cwd(), targetDir);
|
|
37
64
|
|
|
38
|
-
if (await
|
|
39
|
-
|
|
65
|
+
if (await exists(fullPath)) {
|
|
66
|
+
process.stderr.write(red(`\nError: Directory ${targetDir} already exists.\n`));
|
|
40
67
|
process.exit(1);
|
|
41
68
|
}
|
|
42
69
|
|
|
43
|
-
|
|
70
|
+
process.stdout.write('Creating project...\n');
|
|
44
71
|
|
|
45
72
|
try {
|
|
46
|
-
await fs.
|
|
47
|
-
await fs.
|
|
48
|
-
await fs.
|
|
49
|
-
await fs.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
await fs.mkdir(fullPath, { recursive: true });
|
|
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 });
|
|
76
|
+
await fs.mkdir(path.join(fullPath, 'content/blog'), { recursive: true });
|
|
77
|
+
|
|
78
|
+
process.stdout.write(`${green(`Successfully created PageWithU project in ${targetDir}!`)}\n`);
|
|
79
|
+
process.stdout.write('\nNext steps:\n');
|
|
80
|
+
process.stdout.write(cyan(` cd ${targetDir}\n`));
|
|
81
|
+
process.stdout.write(cyan(' page-withu serve\n'));
|
|
82
|
+
process.stdout.write(cyan(' page-withu build\n\n'));
|
|
56
83
|
} catch (err) {
|
|
57
|
-
|
|
84
|
+
process.stderr.write(`${red('Failed to create project.')}\n`);
|
|
58
85
|
console.error(err);
|
|
59
86
|
process.exit(1);
|
|
60
87
|
}
|
|
@@ -64,52 +91,107 @@ async function runVite(command, args) {
|
|
|
64
91
|
const projectRoot = process.cwd();
|
|
65
92
|
const missing = await detectProject(projectRoot);
|
|
66
93
|
if (missing.length) {
|
|
67
|
-
|
|
68
|
-
|
|
94
|
+
process.stderr.write(`${red('Current directory does not look like a PageWithU project.')}\n`);
|
|
95
|
+
process.stderr.write(`${yellow(`Missing: ${missing.join(', ')}`)}\n`);
|
|
69
96
|
process.exit(1);
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
const viteArgs = command === 'serve'
|
|
73
100
|
? ['--config', viteConfigPath, ...args]
|
|
74
101
|
: ['build', '--config', viteConfigPath, ...args];
|
|
75
|
-
const child = spawn(process.execPath, [viteBin, ...viteArgs], {
|
|
76
|
-
cwd: projectRoot,
|
|
77
|
-
stdio: 'inherit',
|
|
78
|
-
env: {
|
|
79
|
-
...process.env,
|
|
80
|
-
PAGE_WITHU_PROJECT_ROOT: projectRoot,
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
102
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
try {
|
|
104
|
+
await run(process.execPath, [viteBin, ...viteArgs], {
|
|
105
|
+
cwd: projectRoot,
|
|
106
|
+
env: {
|
|
107
|
+
...process.env,
|
|
108
|
+
PAGE_WITHU_PROJECT_ROOT: projectRoot,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error(err.message);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function printMainHelp() {
|
|
118
|
+
console.log(`Usage: page-withu [options] [command]
|
|
119
|
+
|
|
120
|
+
CLI to scaffold, preview, and build a lightweight personal homepage
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
-V, --version output the version number
|
|
124
|
+
-h, --help display help for command
|
|
125
|
+
|
|
126
|
+
Commands:
|
|
127
|
+
new [project-name] create a new PageWithU project with the default content and config
|
|
128
|
+
serve [args...] start local preview for the current PageWithU project
|
|
129
|
+
build [args...] build dist assets for the current PageWithU project
|
|
130
|
+
help [command] display help for command`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function printCommandHelp(command) {
|
|
134
|
+
if (command === 'new') {
|
|
135
|
+
console.log(`Usage: page-withu new [options] [project-name]
|
|
136
|
+
|
|
137
|
+
create a new PageWithU project with the default content and config
|
|
138
|
+
|
|
139
|
+
Options:
|
|
140
|
+
-h, --help display help for command`);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (command === 'serve') {
|
|
144
|
+
console.log(`Usage: page-withu serve [options] [args...]
|
|
145
|
+
|
|
146
|
+
start local preview for the current PageWithU project
|
|
147
|
+
|
|
148
|
+
Options:
|
|
149
|
+
-h, --help display help for command`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (command === 'build') {
|
|
153
|
+
console.log(`Usage: page-withu build [options] [args...]
|
|
154
|
+
|
|
155
|
+
build dist assets for the current PageWithU project
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
-h, --help display help for command`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
printMainHelp();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function main(argv) {
|
|
165
|
+
const [command, ...args] = argv;
|
|
166
|
+
|
|
167
|
+
if (!command || command === '--help' || command === '-h') {
|
|
168
|
+
printMainHelp();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (command === '--version' || command === '-V') {
|
|
172
|
+
console.log(pkg.version);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (command === 'help') {
|
|
176
|
+
printCommandHelp(args[0]);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
180
|
+
printCommandHelp(command);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (command === 'new') {
|
|
184
|
+
await createProject(args[0]);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (command === 'serve' || command === 'build') {
|
|
188
|
+
await runVite(command, args);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
process.stderr.write(`Unknown command: ${command}\n\n`);
|
|
193
|
+
printMainHelp();
|
|
194
|
+
process.exit(1);
|
|
91
195
|
}
|
|
92
196
|
|
|
93
|
-
|
|
94
|
-
.name('page-withu')
|
|
95
|
-
.description('CLI to scaffold, preview, and build a lightweight personal homepage')
|
|
96
|
-
.version(pkg.version);
|
|
97
|
-
|
|
98
|
-
program
|
|
99
|
-
.command('new [project-name]')
|
|
100
|
-
.description('create a new PageWithU project with the default content and config')
|
|
101
|
-
.action(createProject);
|
|
102
|
-
|
|
103
|
-
program
|
|
104
|
-
.command('serve [args...]')
|
|
105
|
-
.description('start local preview for the current PageWithU project')
|
|
106
|
-
.allowUnknownOption(true)
|
|
107
|
-
.action((args = []) => runVite('serve', args));
|
|
108
|
-
|
|
109
|
-
program
|
|
110
|
-
.command('build [args...]')
|
|
111
|
-
.description('build dist assets for the current PageWithU project')
|
|
112
|
-
.allowUnknownOption(true)
|
|
113
|
-
.action((args = []) => runVite('build', args));
|
|
114
|
-
|
|
115
|
-
program.parse();
|
|
197
|
+
await main(process.argv.slice(2));
|
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,32 +12,19 @@
|
|
|
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
|
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"homepage",
|
|
28
|
-
"blog",
|
|
29
|
-
"generator",
|
|
30
|
-
"vue",
|
|
31
|
-
"vite"
|
|
32
|
-
],
|
|
33
|
-
"author": "Explorer-Dong",
|
|
34
|
-
"license": "MIT",
|
|
35
25
|
"dependencies": {
|
|
36
26
|
"@traptitech/markdown-it-katex": "^3.6.0",
|
|
37
27
|
"@vitejs/plugin-vue": "^5.2.3",
|
|
38
|
-
"chalk": "^5.3.0",
|
|
39
|
-
"commander": "^12.1.0",
|
|
40
|
-
"fs-extra": "^11.2.0",
|
|
41
28
|
"gray-matter": "^4.0.3",
|
|
42
29
|
"highlight.js": "^11.11.1",
|
|
43
30
|
"katex": "^0.16.45",
|
|
@@ -45,8 +32,15 @@
|
|
|
45
32
|
"markdown-it-emoji": "^3.0.0",
|
|
46
33
|
"markdown-it-footnote": "^4.0.0",
|
|
47
34
|
"mermaid": "^11.15.0",
|
|
48
|
-
"ora": "^8.0.1",
|
|
49
35
|
"vite": "^6.3.1",
|
|
50
36
|
"vue": "^3.5.13"
|
|
51
|
-
}
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"static site generator",
|
|
40
|
+
"lightweight",
|
|
41
|
+
"vue",
|
|
42
|
+
"vite"
|
|
43
|
+
],
|
|
44
|
+
"author": "Explorer-Dong",
|
|
45
|
+
"license": "MIT"
|
|
52
46
|
}
|
|
@@ -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
|
+
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
2
3
|
import { tmpdir } from 'node:os'
|
|
3
|
-
import { defineConfig } from 'vite'
|
|
4
|
-
import vue from '@vitejs/plugin-vue'
|
|
5
|
-
import MarkdownIt from 'markdown-it'
|
|
6
|
-
import mk from '@traptitech/markdown-it-katex'
|
|
7
|
-
import footnote from 'markdown-it-footnote'
|
|
8
|
-
import { full as emoji } from 'markdown-it-emoji'
|
|
9
|
-
import matter from 'gray-matter'
|
|
10
|
-
import hljs from 'highlight.js'
|
|
11
4
|
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
|
12
5
|
import { dirname, join, resolve } from 'node:path'
|
|
13
6
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
14
7
|
|
|
15
|
-
const
|
|
8
|
+
const sourceRoot = dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const appRoot = resolve(sourceRoot, 'app')
|
|
16
10
|
const projectRoot = resolve(process.env.PAGE_WITHU_PROJECT_ROOT || process.cwd())
|
|
11
|
+
const packageRoot = resolve(sourceRoot, '..')
|
|
12
|
+
const dependencyRoot = resolve(packageRoot, 'node_modules')
|
|
13
|
+
const runtimeRequire = createRequire(import.meta.url)
|
|
14
|
+
const loadRuntimeModule = async (specifier) => import(pathToFileURL(runtimeRequire.resolve(specifier)).href)
|
|
15
|
+
const runtimeModuleAlias = Object.fromEntries([
|
|
16
|
+
'vue',
|
|
17
|
+
'katex/dist/katex.min.css',
|
|
18
|
+
'highlight.js/styles/github.css',
|
|
19
|
+
'mermaid',
|
|
20
|
+
].map((specifier) => [specifier, runtimeRequire.resolve(specifier)]))
|
|
17
21
|
const userConfigFile = resolve(projectRoot, 'config.js')
|
|
18
22
|
const userContentDir = resolve(projectRoot, 'content')
|
|
19
23
|
const cacheKey = createHash('sha256').update(projectRoot).digest('hex').slice(0, 12)
|
|
@@ -73,7 +77,7 @@ function callouts(md) {
|
|
|
73
77
|
})
|
|
74
78
|
}
|
|
75
79
|
|
|
76
|
-
function createMarkdownRenderer() {
|
|
80
|
+
function createMarkdownRenderer({ MarkdownIt, mk, footnote, emoji, hljs }) {
|
|
77
81
|
const toc = []
|
|
78
82
|
let md
|
|
79
83
|
md = new MarkdownIt({
|
|
@@ -127,8 +131,9 @@ function renderMarkdown(md, content) {
|
|
|
127
131
|
return { html, toc: env.toc }
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
function markdown() {
|
|
131
|
-
const { md } = createMarkdownRenderer()
|
|
134
|
+
function markdown(deps) {
|
|
135
|
+
const { md } = createMarkdownRenderer(deps)
|
|
136
|
+
const { matter } = deps
|
|
132
137
|
return {
|
|
133
138
|
name: 'vite-plugin-markdown',
|
|
134
139
|
transform(code, id) {
|
|
@@ -187,24 +192,44 @@ function staticHtmlRoutes(userConfig) {
|
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
|
|
195
|
+
const { defineConfig } = await loadRuntimeModule('vite')
|
|
196
|
+
|
|
190
197
|
export default defineConfig(async () => {
|
|
198
|
+
const [vueModule, MarkdownItModule, mkModule, footnoteModule, emojiModule, matterModule, hljsModule] = await Promise.all([
|
|
199
|
+
loadRuntimeModule('@vitejs/plugin-vue'),
|
|
200
|
+
loadRuntimeModule('markdown-it'),
|
|
201
|
+
loadRuntimeModule('@traptitech/markdown-it-katex'),
|
|
202
|
+
loadRuntimeModule('markdown-it-footnote'),
|
|
203
|
+
loadRuntimeModule('markdown-it-emoji'),
|
|
204
|
+
loadRuntimeModule('gray-matter'),
|
|
205
|
+
loadRuntimeModule('highlight.js'),
|
|
206
|
+
])
|
|
207
|
+
const deps = {
|
|
208
|
+
MarkdownIt: MarkdownItModule.default,
|
|
209
|
+
mk: mkModule.default,
|
|
210
|
+
footnote: footnoteModule.default,
|
|
211
|
+
emoji: emojiModule.full,
|
|
212
|
+
matter: matterModule.default,
|
|
213
|
+
hljs: hljsModule.default,
|
|
214
|
+
}
|
|
191
215
|
const userConfig = (await import(`${pathToFileURL(userConfigFile).href}?t=${Date.now()}`)).default
|
|
192
216
|
|
|
193
217
|
return {
|
|
194
|
-
root:
|
|
218
|
+
root: appRoot,
|
|
195
219
|
base: basePath,
|
|
196
220
|
cacheDir,
|
|
197
221
|
resolve: {
|
|
198
222
|
alias: {
|
|
223
|
+
...runtimeModuleAlias,
|
|
199
224
|
'@page-withu/user-config': userConfigFile,
|
|
200
225
|
'@page-withu/user-content': userContentDir,
|
|
201
226
|
},
|
|
202
227
|
},
|
|
203
|
-
plugins: [
|
|
228
|
+
plugins: [vueModule.default(), markdown(deps), staticHtmlRoutes(userConfig)],
|
|
204
229
|
server: {
|
|
205
230
|
port: 5500,
|
|
206
231
|
fs: {
|
|
207
|
-
allow: [
|
|
232
|
+
allow: [appRoot, projectRoot, dependencyRoot],
|
|
208
233
|
},
|
|
209
234
|
},
|
|
210
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
|