@sugarat/theme 0.2.11 → 0.2.13

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/node.js CHANGED
@@ -202,6 +202,7 @@ var tabsPlugin = (md) => {
202
202
 
203
203
  // src/utils/node/index.ts
204
204
  var import_node_child_process = require("child_process");
205
+ var import_node_path = __toESM(require("path"));
205
206
 
206
207
  // src/utils/client/index.ts
207
208
  function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
@@ -287,6 +288,27 @@ function aliasObjectToArray(obj) {
287
288
  replacement
288
289
  }));
289
290
  }
291
+ function joinPath(base, path4) {
292
+ return `${base}${path4}`.replace(/\/+/g, "/");
293
+ }
294
+ function isBase64ImageURL(url) {
295
+ const regex = /^data:image\/[a-z]+;base64,/;
296
+ return regex.test(url);
297
+ }
298
+ function getFirstImagURLFromMD(content, route) {
299
+ const url = content.match(/!\[.*\]\((.*)\)/)?.[1]?.replace(/['"]/g, "");
300
+ const isHTTPSource = url && url.startsWith("http");
301
+ if (!url) {
302
+ return "";
303
+ }
304
+ if (isHTTPSource || isBase64ImageURL(url)) {
305
+ return url;
306
+ }
307
+ const paths = joinPath("/", route).split("/");
308
+ paths.splice(paths.length - 1, 1);
309
+ const relativePath = url.startsWith("/") ? url : import_node_path.default.join(paths.join("/") || "", url);
310
+ return joinPath("/", relativePath);
311
+ }
290
312
 
291
313
  // src/utils/node/mdPlugins.ts
292
314
  function getMarkdownPlugins(cfg) {
@@ -338,7 +360,7 @@ function patchOptimizeDeps(config) {
338
360
 
339
361
  // src/utils/node/theme.ts
340
362
  var import_node_fs = __toESM(require("fs"));
341
- var import_node_path = __toESM(require("path"));
363
+ var import_node_path2 = __toESM(require("path"));
342
364
  var import_node_process = __toESM(require("process"));
343
365
  var import_fast_glob = __toESM(require("fast-glob"));
344
366
  var import_gray_matter = __toESM(require("gray-matter"));
@@ -361,14 +383,14 @@ function getArticles(cfg) {
361
383
  if (route.startsWith("./")) {
362
384
  route = route.replace(
363
385
  new RegExp(
364
- `^\\.\\/${import_node_path.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path.default.sep}`, "g"), "/")}`
386
+ `^\\.\\/${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
365
387
  ),
366
388
  ""
367
389
  );
368
390
  } else {
369
391
  route = route.replace(
370
392
  new RegExp(
371
- `^${import_node_path.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path.default.sep}`, "g"), "/")}`
393
+ `^${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
372
394
  ),
373
395
  ""
374
396
  );
@@ -399,7 +421,7 @@ function getArticles(cfg) {
399
421
  ]);
400
422
  const wordCount = 100;
401
423
  meta.description = meta.description || getTextSummary(fileContent, wordCount);
402
- meta.cover = meta.cover ?? (fileContent.match(/[!]\[.*?\]\((https:\/\/.+)\)/)?.[1] || "");
424
+ meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, `/${route}`);
403
425
  if (meta.publish === false) {
404
426
  meta.hidden = true;
405
427
  meta.recommend = false;
@@ -417,15 +439,18 @@ function patchVPThemeConfig(cfg, vpThemeConfig = {}) {
417
439
  }
418
440
 
419
441
  // src/utils/node/vitePlugins.ts
420
- var import_node_path2 = __toESM(require("path"));
442
+ var import_node_path3 = __toESM(require("path"));
421
443
  var import_node_child_process2 = require("child_process");
422
444
  var import_node_process2 = __toESM(require("process"));
445
+ var import_node_fs2 = require("fs");
446
+ var import_node_buffer = require("buffer");
423
447
  var import_vitepress_plugin_pagefind = require("vitepress-plugin-pagefind");
424
448
  var import_vitepress_plugin_rss = require("vitepress-plugin-rss");
425
449
  function getVitePlugins(cfg) {
426
450
  const plugins = [];
427
451
  const buildEndFn = [];
428
452
  plugins.push(inlineBuildEndPlugin(buildEndFn));
453
+ plugins.push(coverImgTransform());
429
454
  if (cfg && cfg.search !== false) {
430
455
  const ops = cfg.search instanceof Object ? cfg.search : {};
431
456
  plugins.push(
@@ -468,6 +493,42 @@ function inlineBuildEndPlugin(buildEndFn) {
468
493
  }
469
494
  };
470
495
  }
496
+ function coverImgTransform() {
497
+ let blogConfig;
498
+ let vitepressConfig;
499
+ let assetsDir;
500
+ return {
501
+ name: "@sugarat/theme-plugin-cover-transform",
502
+ apply: "build",
503
+ enforce: "pre",
504
+ configResolved(config) {
505
+ vitepressConfig = config.vitepress;
506
+ assetsDir = vitepressConfig.assetsDir;
507
+ blogConfig = config.vitepress.site.themeConfig.blog;
508
+ },
509
+ async generateBundle(_, bundle) {
510
+ const assetsMap = Object.entries(bundle).filter(([key]) => {
511
+ return key.startsWith(assetsDir);
512
+ }).map(([_2, value]) => {
513
+ return value;
514
+ });
515
+ for (const page of blogConfig.pagesData) {
516
+ const { cover } = page.meta;
517
+ if (!cover?.startsWith("/")) {
518
+ continue;
519
+ }
520
+ try {
521
+ const realPath = import_node_path3.default.join(vitepressConfig.root, cover);
522
+ const fileBuffer = (0, import_node_fs2.readFileSync)(realPath);
523
+ const matchAsset = assetsMap.find((v) => import_node_buffer.Buffer.compare(fileBuffer, v.source));
524
+ page.meta.cover = joinPath("/", matchAsset.fileName);
525
+ } catch (e) {
526
+ vitepressConfig.logger.warn(e?.message);
527
+ }
528
+ }
529
+ }
530
+ };
531
+ }
471
532
 
472
533
  // src/node.ts
473
534
  function getThemeConfig(cfg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sugarat/theme",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
5
5
  "author": "sugar",
6
6
  "license": "MIT",
@@ -51,6 +51,7 @@
51
51
  "@documate/vue": "^0.3.1",
52
52
  "@element-plus/icons-vue": "^2.1.0",
53
53
  "element-plus": "^2.3.4",
54
+ "javascript-stringify": "^2.1.0",
54
55
  "pagefind": "1.0.3",
55
56
  "sass": "^1.56.1",
56
57
  "typescript": "^4.8.2",
@@ -64,7 +64,7 @@ const showTime = computed(() => {
64
64
  <div
65
65
  v-if="cover"
66
66
  class="cover-img"
67
- :style="`background-image: url(${cover});`"
67
+ :style="`background-image: url(${withBase(`${cover}`)});`"
68
68
  />
69
69
  </div>
70
70
  <!-- 底部补充描述 -->
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable global-require */
2
2
  /* eslint-disable prefer-rest-params */
3
3
  import { spawn, spawnSync } from 'node:child_process'
4
+ import path from 'node:path'
4
5
  import { formatDate } from '../client'
5
6
 
6
7
  export function clearMatterContent(content: string) {
@@ -118,3 +119,33 @@ export function withBase(base: string, path: string) {
118
119
  ? path
119
120
  : joinPath(base, path)
120
121
  }
122
+
123
+ function isBase64ImageURL(url: string) {
124
+ // Base64 图片链接的格式为 data:image/[image format];base64,[Base64 编码的数据]
125
+ const regex = /^data:image\/[a-z]+;base64,/
126
+ return regex.test(url)
127
+ }
128
+
129
+ /**
130
+ * 从文档内容中提取封面
131
+ * @param content 文档内容
132
+ */
133
+ export function getFirstImagURLFromMD(content: string, route: string) {
134
+ const url = content.match(/!\[.*\]\((.*)\)/)?.[1]?.replace(/['"]/g, '')
135
+ const isHTTPSource = url && url.startsWith('http')
136
+ if (!url) {
137
+ return ''
138
+ }
139
+
140
+ if (isHTTPSource || isBase64ImageURL(url)) {
141
+ return url
142
+ }
143
+
144
+ // TODO: 其它协议,待补充
145
+
146
+ const paths = joinPath('/', route).split('/')
147
+ paths.splice(paths.length - 1, 1)
148
+ const relativePath = url.startsWith('/') ? url : path.join(paths.join('/') || '', url)
149
+
150
+ return joinPath('/', relativePath)
151
+ }
@@ -6,7 +6,7 @@ import glob from 'fast-glob'
6
6
  import matter from 'gray-matter'
7
7
  import type { Theme } from '../../composables/config/index'
8
8
  import { formatDate } from '../client'
9
- import { getDefaultTitle, getFileBirthTime, getTextSummary } from './index'
9
+ import { getDefaultTitle, getFileBirthTime, getFirstImagURLFromMD, getTextSummary } from './index'
10
10
 
11
11
  export function patchDefaultThemeSideBar(cfg?: Partial<Theme.BlogConfig>) {
12
12
  return cfg?.blog !== false && cfg?.recommend !== false
@@ -105,7 +105,7 @@ export function getArticles(cfg?: Partial<Theme.BlogConfig>) {
105
105
  // 获取封面图
106
106
  meta.cover
107
107
  = meta.cover
108
- ?? (fileContent.match(/[!]\[.*?\]\((https:\/\/.+)\)/)?.[1] || '')
108
+ ?? (getFirstImagURLFromMD(fileContent, `/${route}`))
109
109
 
110
110
  // 是否发布 默认发布
111
111
  if (meta.publish === false) {
@@ -1,13 +1,17 @@
1
1
  import path from 'node:path'
2
2
  import { execSync } from 'node:child_process'
3
3
  import process from 'node:process'
4
+ import { readFileSync } from 'node:fs'
5
+ import { Buffer } from 'node:buffer'
4
6
  import type { SiteConfig } from 'vitepress'
7
+
5
8
  import {
6
9
  chineseSearchOptimize,
7
10
  pagefindPlugin
8
11
  } from 'vitepress-plugin-pagefind'
9
12
  import { RssPlugin } from 'vitepress-plugin-rss'
10
13
  import type { Theme } from '../../composables/config/index'
14
+ import { joinPath } from './index'
11
15
 
12
16
  export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
13
17
  const plugins: any[] = []
@@ -16,7 +20,8 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
16
20
  const buildEndFn: any[] = []
17
21
  // 执行自定义的 buildEnd 钩子
18
22
  plugins.push(inlineBuildEndPlugin(buildEndFn))
19
-
23
+ // 处理cover image的路径(暂只支持自动识别的文章首图)
24
+ plugins.push(coverImgTransform())
20
25
  // 内置简化版的pagefind
21
26
  if (cfg && cfg.search !== false) {
22
27
  const ops = cfg.search instanceof Object ? cfg.search : {}
@@ -118,3 +123,44 @@ export function inlineBuildEndPlugin(buildEndFn: any[]) {
118
123
  }
119
124
  }
120
125
  }
126
+
127
+ // TODO: 支持frontmatter中的相对路径图片自动处理
128
+ export function coverImgTransform() {
129
+ let blogConfig: Theme.BlogConfig
130
+ let vitepressConfig: SiteConfig
131
+ let assetsDir: string
132
+ return {
133
+ name: '@sugarat/theme-plugin-cover-transform',
134
+ apply: 'build',
135
+ enforce: 'pre',
136
+ configResolved(config: any) {
137
+ vitepressConfig = config.vitepress
138
+ assetsDir = vitepressConfig.assetsDir
139
+ blogConfig = config.vitepress.site.themeConfig.blog
140
+ },
141
+ async generateBundle(_: any, bundle: Record<string, any>) {
142
+ const assetsMap = Object.entries(bundle).filter(([key]) => {
143
+ return key.startsWith(assetsDir)
144
+ }).map(([_, value]) => {
145
+ return value
146
+ })
147
+ for (const page of blogConfig.pagesData) {
148
+ const { cover } = page.meta
149
+ // 是否相对路径引用
150
+ if (!cover?.startsWith('/')) {
151
+ continue
152
+ }
153
+ try {
154
+ // 寻找构建后的
155
+ const realPath = path.join(vitepressConfig.root, cover)
156
+ const fileBuffer = readFileSync(realPath)
157
+ const matchAsset = assetsMap.find(v => Buffer.compare(fileBuffer, v.source))
158
+ page.meta.cover = joinPath('/', matchAsset.fileName)
159
+ }
160
+ catch (e: any) {
161
+ vitepressConfig.logger.warn(e?.message)
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }