@x-withu/page-withu 1.1.2 → 1.1.3

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/docs/setup.md CHANGED
@@ -10,6 +10,8 @@
10
10
  export default {
11
11
  // 站点标题,会显示在页面顶部
12
12
  title: "PageWithU",
13
+ // 浏览器标签页图标,本地路径相对项目根目录,例如 "./content/logo.svg"
14
+ favicon: "/src/assets/bulb.svg",
13
15
  // 作者名,会显示在页脚版权信息中
14
16
  author: "Your Name",
15
17
  // 版权年份,默认使用当前年份
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@x-withu/page-withu",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A lightweight personal homepage generator.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -82,6 +82,13 @@ const domainsTitle = domainsFrontmatter.title || 'Domains'
82
82
  const sites = domainsFrontmatter.sites || []
83
83
  const tabTitle = userConfig.tabTitle || userConfig.title || 'PageWithU'
84
84
  const favicon = userConfig.favicon === '/src/assets/bulb.svg' ? defaultFavicon : userConfig.favicon || defaultFavicon
85
+ const externalUrlPattern = /^[a-z][a-z\d+.-]*:/i
86
+
87
+ function resolveFaviconHref(value) {
88
+ if (value === defaultFavicon || externalUrlPattern.test(value) || value.startsWith('//')) return value
89
+ const localPath = value.replace(/^\.\//, '')
90
+ return withBasePath(localPath.startsWith('/') ? localPath : `/${localPath}`)
91
+ }
85
92
 
86
93
  function applyDocumentMeta() {
87
94
  document.title = tabTitle
@@ -91,7 +98,7 @@ function applyDocumentMeta() {
91
98
  icon.rel = 'icon'
92
99
  document.head.appendChild(icon)
93
100
  }
94
- icon.href = favicon === defaultFavicon || !favicon.startsWith('/') ? favicon : withBasePath(favicon)
101
+ icon.href = resolveFaviconHref(favicon)
95
102
  }
96
103
 
97
104
  applyDocumentMeta()
@@ -77,6 +77,49 @@
77
77
  --color-tag-bg: rgba(110, 168, 254, 0.15);
78
78
  --color-tag-text: #6ea8fe;
79
79
  }
80
+
81
+ :root:not([data-theme="light"]) main > header {
82
+ border: 1px solid rgba(255, 255, 255, 0.1);
83
+ background: rgba(18, 18, 18, 0.78);
84
+ box-shadow: 0 14px 38px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.08);
85
+ }
86
+
87
+ :root:not([data-theme="light"]) .site-title,
88
+ :root:not([data-theme="light"]) .site-title:hover {
89
+ color: #fff;
90
+ }
91
+
92
+ :root:not([data-theme="light"]) .nav-item {
93
+ color: rgba(255, 255, 255, 0.68);
94
+ }
95
+
96
+ :root:not([data-theme="light"]) .nav-item:hover,
97
+ :root:not([data-theme="light"]) .nav-item.active {
98
+ color: #fff;
99
+ text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
100
+ }
101
+
102
+ :root:not([data-theme="light"]) .theme-toggle {
103
+ color: rgba(255, 255, 255, 0.68);
104
+ }
105
+
106
+ :root:not([data-theme="light"]) .theme-toggle:hover {
107
+ background: rgba(255, 255, 255, 0.1);
108
+ color: #fff;
109
+ text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
110
+ }
111
+
112
+ :root:not([data-theme="light"]) .theme-toggle .icon-sun {
113
+ display: none;
114
+ }
115
+
116
+ :root:not([data-theme="light"]) .theme-toggle .icon-moon {
117
+ display: none;
118
+ }
119
+
120
+ :root:not([data-theme="light"]) .theme-toggle .icon-system {
121
+ display: block;
122
+ }
80
123
  }
81
124
 
82
125
  /* Explicit dark mode override */
@@ -184,8 +227,7 @@ main > header {
184
227
  -webkit-backdrop-filter: blur(18px) saturate(1.35);
185
228
  }
186
229
 
187
- [data-theme="dark"] main > header,
188
- :root:not([data-theme="light"]) main > header {
230
+ [data-theme="dark"] main > header {
189
231
  border: 1px solid rgba(255, 255, 255, 0.1);
190
232
  background: rgba(18, 18, 18, 0.78);
191
233
  box-shadow: 0 14px 38px rgba(0, 0, 0, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.08);
@@ -210,8 +252,7 @@ main > header::before {
210
252
  background: none;
211
253
  }
212
254
 
213
- [data-theme="dark"] .site-title,
214
- :root:not([data-theme="light"]) .site-title {
255
+ [data-theme="dark"] .site-title {
215
256
  color: #fff;
216
257
  }
217
258
 
@@ -220,8 +261,7 @@ main > header::before {
220
261
  background: none;
221
262
  }
222
263
 
223
- [data-theme="dark"] .site-title:hover,
224
- :root:not([data-theme="light"]) .site-title:hover {
264
+ [data-theme="dark"] .site-title:hover {
225
265
  color: #fff;
226
266
  }
227
267
 
@@ -248,7 +288,7 @@ main > header::before {
248
288
  padding: 6px 0;
249
289
  font-size: 0.9rem;
250
290
  font-weight: 650;
251
- color: var(--color-subtext);
291
+ color: var(--color-subtext-light, #8a9ba8);
252
292
  text-decoration: none;
253
293
  border-radius: 0;
254
294
  transition: color 0.2s ease, text-shadow 0.2s ease;
@@ -256,8 +296,7 @@ main > header::before {
256
296
  white-space: nowrap;
257
297
  }
258
298
 
259
- [data-theme="dark"] .nav-item,
260
- :root:not([data-theme="light"]) .nav-item {
299
+ [data-theme="dark"] .nav-item {
261
300
  color: rgba(255, 255, 255, 0.68);
262
301
  }
263
302
 
@@ -269,9 +308,7 @@ main > header::before {
269
308
  }
270
309
 
271
310
  [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 {
311
+ [data-theme="dark"] .nav-item.active {
275
312
  color: #fff;
276
313
  text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
277
314
  }
@@ -516,8 +553,7 @@ footer p {
516
553
  color: var(--color-subtext);
517
554
  }
518
555
 
519
- [data-theme="dark"] .theme-toggle,
520
- :root:not([data-theme="light"]) .theme-toggle {
556
+ [data-theme="dark"] .theme-toggle {
521
557
  color: rgba(255, 255, 255, 0.68);
522
558
  }
523
559
 
@@ -527,8 +563,7 @@ footer p {
527
563
  text-shadow: none;
528
564
  }
529
565
 
530
- [data-theme="dark"] .theme-toggle:hover,
531
- :root:not([data-theme="light"]) .theme-toggle:hover {
566
+ [data-theme="dark"] .theme-toggle:hover {
532
567
  background: rgba(255, 255, 255, 0.1);
533
568
  color: #fff;
534
569
  text-shadow: 0 0 18px rgba(255, 255, 255, 0.28);
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { createRequire } from 'node:module'
3
3
  import { tmpdir } from 'node:os'
4
- import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
4
+ import { copyFileSync, existsSync, 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
 
@@ -23,6 +23,18 @@ const userContentDir = resolve(projectRoot, 'content')
23
23
  const cacheKey = createHash('sha256').update(projectRoot).digest('hex').slice(0, 12)
24
24
  const cacheDir = resolve(tmpdir(), `page-withu-${cacheKey}`)
25
25
  const basePath = process.env.BASE_PATH || '/'
26
+ const externalUrlPattern = /^[a-z][a-z\d+.-]*:/i
27
+
28
+ function resolveProjectAssetPath(path) {
29
+ if (!path || path === '/src/assets/bulb.svg' || externalUrlPattern.test(path) || path.startsWith('//') || path.startsWith('/')) return null
30
+ const relativePath = path.replace(/^\.\//, '')
31
+ return {
32
+ relativePath,
33
+ sourcePath: join(projectRoot, relativePath),
34
+ outputPath: join(resolve(projectRoot, 'dist'), relativePath),
35
+ href: withBasePath(`/${relativePath}`),
36
+ }
37
+ }
26
38
 
27
39
  function slugify(text) {
28
40
  return text
@@ -160,6 +172,27 @@ function markdown(deps) {
160
172
  }
161
173
  }
162
174
 
175
+ function projectAssets(userConfig) {
176
+ return {
177
+ name: 'project-assets',
178
+ configureServer(server) {
179
+ const favicon = resolveProjectAssetPath(userConfig.favicon)
180
+ if (!favicon) return
181
+
182
+ server.middlewares.use((req, res, next) => {
183
+ const pathname = decodeURI((req.url || '').split('?')[0])
184
+ if (pathname !== `/${favicon.relativePath}` || !existsSync(favicon.sourcePath)) {
185
+ next()
186
+ return
187
+ }
188
+
189
+ if (favicon.sourcePath.endsWith('.svg')) res.setHeader('Content-Type', 'image/svg+xml')
190
+ res.end(readFileSync(favicon.sourcePath))
191
+ })
192
+ },
193
+ }
194
+ }
195
+
163
196
  function staticHtmlRoutes(userConfig) {
164
197
  return {
165
198
  name: 'static-html-routes',
@@ -179,7 +212,7 @@ function staticHtmlRoutes(userConfig) {
179
212
 
180
213
  const pageSize = userConfig.pagination?.pageSize || 5
181
214
  const totalPages = Math.max(1, Math.ceil(blogPosts.length / pageSize))
182
- const routes = ['domains.html', 'blog.html']
215
+ const routes = ['index.html', 'domains.html', 'blog.html']
183
216
  for (let page = 2; page <= totalPages; page += 1) routes.push(`blog/page/${page}.html`)
184
217
  for (const slug of blogPosts) routes.push(`blog/${slug}.html`)
185
218
 
@@ -188,6 +221,19 @@ function staticHtmlRoutes(userConfig) {
188
221
  mkdirSync(dirname(file), { recursive: true })
189
222
  writeFileSync(file, index)
190
223
  }
224
+
225
+ const favicon = resolveProjectAssetPath(userConfig.favicon)
226
+ if (favicon && existsSync(favicon.sourcePath)) {
227
+ mkdirSync(dirname(favicon.outputPath), { recursive: true })
228
+ copyFileSync(favicon.sourcePath, favicon.outputPath)
229
+
230
+ for (const route of routes) {
231
+ const file = join(dist, route)
232
+ if (!existsSync(file)) continue
233
+ const html = readFileSync(file, 'utf8').replace(/<link rel="icon"[^>]*href="[^"]*"[^>]*>/, `<link rel="icon" href="${favicon.href}">`)
234
+ writeFileSync(file, html)
235
+ }
236
+ }
191
237
  },
192
238
  }
193
239
  }
@@ -225,7 +271,7 @@ export default defineConfig(async () => {
225
271
  '@page-withu/user-content': userContentDir,
226
272
  },
227
273
  },
228
- plugins: [vueModule.default(), markdown(deps), staticHtmlRoutes(userConfig)],
274
+ plugins: [vueModule.default(), markdown(deps), projectAssets(userConfig), staticHtmlRoutes(userConfig)],
229
275
  server: {
230
276
  port: 5500,
231
277
  fs: {