@sugarat/theme 0.4.12 → 0.5.0

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.
Files changed (38) hide show
  1. package/node.d.ts +16 -74
  2. package/node.js +148 -51
  3. package/node.mjs +753 -0
  4. package/package.json +10 -6
  5. package/src/components/BlogAlert.vue +10 -9
  6. package/src/components/BlogApp.vue +0 -2
  7. package/src/components/BlogArticleAnalyze.vue +8 -9
  8. package/src/components/BlogAuthor.vue +6 -5
  9. package/src/components/BlogBackToTop.vue +8 -10
  10. package/src/components/BlogButtonAfterArticle.vue +5 -14
  11. package/src/components/BlogCommentWrapper.vue +11 -28
  12. package/src/components/BlogDocCover.vue +3 -3
  13. package/src/components/BlogFooter.vue +3 -1
  14. package/src/components/BlogFriendLink.vue +4 -3
  15. package/src/components/BlogHomeBanner.vue +7 -6
  16. package/src/components/BlogHomeHeaderAvatar.vue +3 -3
  17. package/src/components/BlogHomeOverview.vue +3 -3
  18. package/src/components/BlogHomeTags.vue +5 -5
  19. package/src/components/BlogHotArticle.vue +4 -7
  20. package/src/components/BlogItem.vue +1 -1
  21. package/src/components/BlogList.vue +7 -6
  22. package/src/components/BlogRecommendArticle.vue +11 -14
  23. package/src/components/BlogSidebar.vue +9 -15
  24. package/src/components/CommentArtalk.vue +7 -5
  25. package/src/components/CommentGiscus.vue +7 -8
  26. package/src/components/Icon.vue +33 -0
  27. package/src/composables/config/blog.ts +203 -87
  28. package/src/composables/config/index.ts +12 -79
  29. package/src/hooks/useOml2d.ts +15 -8
  30. package/src/index.ts +3 -0
  31. package/src/node.ts +5 -1
  32. package/src/styles/index.scss +6 -6
  33. package/src/utils/node/hot-reload-plugin.ts +31 -1
  34. package/src/utils/node/index.ts +0 -2
  35. package/src/utils/node/mdPlugins.ts +4 -0
  36. package/src/utils/node/theme.ts +15 -18
  37. package/src/utils/node/vitePlugins.ts +122 -33
  38. package/src/components/BlogPopover.vue +0 -290
@@ -1,45 +1,13 @@
1
1
  /* eslint-disable ts/no-namespace */
2
- import type { ElButton } from 'element-plus'
3
- import type { DefaultTheme, Route } from 'vitepress'
2
+ import type { DefaultTheme } from 'vitepress'
4
3
  import type { RSSOptions } from 'vitepress-plugin-rss'
5
4
  import type { Mapping, Repo } from '@giscus/vue'
6
5
  import type { Options as Oml2dOptions } from 'oh-my-live2d'
7
- import type { Ref } from 'vue'
8
6
  import type { PagefindConfig } from 'vitepress-plugin-pagefind'
7
+ import type { AnnouncementOptions } from 'vitepress-plugin-announcement'
9
8
 
10
9
  type RSSPluginOptions = RSSOptions
11
10
 
12
- // TODO: 重构 lint 问题
13
- export declare namespace BlogPopover {
14
- export interface Title {
15
- type: 'title'
16
- content: string
17
- style?: string
18
- }
19
-
20
- export interface Text {
21
- type: 'text'
22
- content: string
23
- style?: string
24
- }
25
-
26
- export interface Image {
27
- type: 'image'
28
- src: string
29
- style?: string
30
- }
31
-
32
- export interface Button {
33
- type: 'button'
34
- link: string
35
- content: string
36
- style?: string
37
- props?: InstanceType<typeof ElButton>['$props']
38
- }
39
-
40
- export type Value = Title | Text | Image | Button
41
- }
42
-
43
11
  export type ThemeableImage =
44
12
  | string
45
13
  | { src: string; alt?: string }
@@ -293,50 +261,6 @@ export namespace Theme {
293
261
  html?: string
294
262
  }
295
263
 
296
- /**
297
- * 公告
298
- */
299
- export interface Popover {
300
- title: string
301
- /**
302
- * 细粒度的时间控制
303
- * 默认展示时间,-1 只展示1次,其它数字为每次都展示,一定时间后自动消失,0为不自动消失
304
- * 配置改变时,会重新触发展示
305
- */
306
- duration: number
307
- /**
308
- * 移动端自动最小化
309
- * @default false
310
- */
311
- mobileMinify?: boolean
312
- body?: BlogPopover.Value[]
313
- footer?: BlogPopover.Value[]
314
- /**
315
- * 手动重新打开
316
- * @default true
317
- */
318
- reopen?: boolean
319
- /**
320
- * 是否打开闪烁提示,通常需要和 reopen 搭配使用
321
- * @default true
322
- */
323
- twinkle?: boolean
324
- /**
325
- * 设置展示图标,svg
326
- * @recommend https://iconbuddy.app/search?q=fire
327
- */
328
- icon?: string
329
- /**
330
- * 设置关闭图标,svg
331
- * @recommend https://iconbuddy.app/search?q=fire
332
- */
333
- closeIcon?: string
334
- /**
335
- * 自定义展示策略
336
- * @param to 切换到的目标路由
337
- */
338
- onRouteChanged?: (to: Route, show: Ref<boolean>) => void
339
- }
340
264
  export interface FriendLink {
341
265
  nickname: string
342
266
  des: string
@@ -425,6 +349,7 @@ export namespace Theme {
425
349
  | 'el-red'
426
350
  export interface BlogConfig {
427
351
  blog?: false
352
+ locales?: Record<string, Omit<BlogConfig, 'locales' | 'pagesData' | 'search' | 'popover' | 'RSS'> & { pagesData?: PageData[] }>
428
353
  /**
429
354
  * 展示日期格式化
430
355
  */
@@ -436,6 +361,11 @@ export namespace Theme {
436
361
  */
437
362
  themeColor?: ThemeColor
438
363
  pagesData: PageData[]
364
+ /**
365
+ * @deprecated 请使用 VitePress 官方 srcDir 替代
366
+ *
367
+ * @doc https://vitepress.dev/zh/reference/site-config#srcdir
368
+ */
439
369
  srcDir?: string
440
370
  author?: string
441
371
  hotArticle?: HotArticle | false
@@ -462,7 +392,7 @@ export namespace Theme {
462
392
  * el-alert
463
393
  */
464
394
  alert?: Alert
465
- popover?: Popover
395
+ popover?: AnnouncementOptions
466
396
  friend?: FriendLink[] | FriendConfig
467
397
  authorList?: Omit<FriendLink, 'avatar'>[]
468
398
  /**
@@ -535,6 +465,9 @@ export namespace Theme {
535
465
  * 渲染时替换图片地址
536
466
  */
537
467
  imageStyle?: ImageStyleConfig
468
+ groupIcon?: {
469
+ customIcon: Record<string, string>
470
+ }
538
471
  }
539
472
 
540
473
  export type FormatShowDate = {
@@ -1,4 +1,4 @@
1
- import { onMounted } from 'vue'
1
+ import { onMounted, watch } from 'vue'
2
2
  import type { Options } from 'oh-my-live2d'
3
3
  import { useOml2dOptions } from '../composables/config/blog'
4
4
 
@@ -37,13 +37,13 @@ const defaultOptions: Options = {
37
37
  }
38
38
  export function useOml2d() {
39
39
  const oml2dOptions = useOml2dOptions()
40
- onMounted(async () => {
41
- if (oml2dOptions) {
40
+ const init = async () => {
41
+ if (oml2dOptions.value) {
42
42
  const { loadOml2d } = await import('oh-my-live2d')
43
43
  loadOml2d({
44
44
  ...defaultOptions,
45
45
  ...oml2dOptions,
46
- models: oml2dOptions?.models?.map(model => ({
46
+ models: oml2dOptions?.value?.models?.map(model => ({
47
47
  ...defaultModelOptions,
48
48
  ...model,
49
49
  stageStyle: {
@@ -57,27 +57,34 @@ export function useOml2d() {
57
57
  })),
58
58
  tips: {
59
59
  ...defaultOptions.tips,
60
- ...oml2dOptions.tips,
60
+ ...oml2dOptions?.value.tips,
61
61
  style: {
62
62
  // @ts-expect-error
63
63
  ...defaultOptions?.tips?.style,
64
64
  // @ts-expect-error
65
- ...oml2dOptions?.tips?.style
65
+ ...oml2dOptions?.value.tips?.style
66
66
  },
67
67
  mobileStyle: {
68
68
  // @ts-expect-error
69
69
  ...defaultOptions?.tips?.mobileStyle,
70
70
  // @ts-expect-error
71
- ...oml2dOptions?.tips?.mobileStyle
71
+ ...oml2dOptions?.value.tips?.mobileStyle
72
72
  },
73
73
  copyTips: {
74
74
  // @ts-expect-error
75
75
  ...defaultOptions?.tips?.copyTips,
76
76
  // @ts-expect-error
77
- ...oml2dOptions?.tips?.copyTips
77
+ ...oml2dOptions?.value.tips?.copyTips
78
78
  }
79
79
  }
80
80
  })
81
81
  }
82
+ }
83
+ // TODO: destroy
84
+ // watch(oml2dOptions, () => {
85
+ // init()
86
+ // })
87
+ onMounted(() => {
88
+ init()
82
89
  })
83
90
  }
package/src/index.ts CHANGED
@@ -35,6 +35,9 @@ import UserWorksPage from './components/UserWorks.vue'
35
35
  // 内置一些特殊的主题色
36
36
  import './styles/theme/inline-theme.var.css'
37
37
 
38
+ // 导入group icons
39
+ import 'virtual:group-icons.css'
40
+
38
41
  export const BlogTheme: Theme = {
39
42
  ...DefaultTheme,
40
43
  Layout: withConfigProvider(BlogApp),
package/src/node.ts CHANGED
@@ -46,7 +46,7 @@ export function getThemeConfig(cfg: Partial<Theme.BlogConfig> = {}) {
46
46
  return {
47
47
  themeConfig: {
48
48
  blog: {
49
- pagesData,
49
+ pagesData, // 插件里补全
50
50
  ...cfg
51
51
  },
52
52
  // 补充一些额外的配置用于继承
@@ -63,6 +63,10 @@ export function defineConfig(config: UserConfig<Theme.Config>): any {
63
63
  return config
64
64
  }
65
65
 
66
+ export function defineLocaleConfig(cfg: Omit<Theme.BlogConfig, 'locales' | 'pagesData'>) {
67
+ return cfg
68
+ }
69
+
66
70
  // 重新导包 tabsMarkdownPlugin 导出CJS格式支持
67
71
  export { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
68
72
 
@@ -102,12 +102,12 @@ html.dark {
102
102
  }
103
103
 
104
104
  // sidebar
105
- @media (min-width: 1440px) {
106
- aside.VPSidebar {
107
- padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 16px)) / 2)) !important;
108
- padding-right: 10px !important;
109
- }
110
- }
105
+ // @media (min-width: 1440px) {
106
+ // aside.VPSidebar {
107
+ // // padding-left: max(32px, calc((100% - (var(--vp-layout-max-width) - 16px)) / 2)) !important;
108
+ // // padding-right: 10px !important;
109
+ // }
110
+ // }
111
111
 
112
112
  .VPDoc .content main {
113
113
  img {
@@ -1,5 +1,7 @@
1
+ import fs from 'fs'
1
2
  import type { PluginOption } from 'vite'
2
3
  import type { SiteConfig } from 'vitepress'
4
+ import { grayMatter } from '@sugarat/theme-shared'
3
5
  import type { Theme } from '../../composables/config/index'
4
6
  import { getArticleMeta } from './theme'
5
7
  import { debounce, isEqual } from './index'
@@ -21,6 +23,7 @@ export function themeReloadPlugin() {
21
23
  server.restart()
22
24
  }, 500)
23
25
  server.watcher.on('add', async (path) => {
26
+ // TODO: rewrite 和 动态路由兼容
24
27
  const route = generateRoute(path)
25
28
  const meta = await getArticleMeta(path, route, blogConfig?.timeZone)
26
29
  blogConfig.pagesData.push({
@@ -32,10 +35,37 @@ export function themeReloadPlugin() {
32
35
 
33
36
  server.watcher.on('change', async (path: string) => {
34
37
  const route = generateRoute(path)
38
+ const fileContent = await fs.promises.readFile(path, 'utf-8')
39
+ const { data: frontmatter } = grayMatter(fileContent, {
40
+ excerpt: true,
41
+ })
35
42
  const meta = await getArticleMeta(path, route, blogConfig?.timeZone)
36
43
  const matched = blogConfig.pagesData.find(v => v.route === route)
37
44
 
38
- if (matched && !isEqual(matched.meta, meta, ['date', 'description'])) {
45
+ // 自动生成的部分属性不参与比较,避免刷新频繁
46
+ const excludeKeys = ['date', 'description'].filter(key => !frontmatter[key])
47
+ // 主题不关心的属性不参与比较,避免刷新频繁
48
+ const inlineKeys = [
49
+ // vitepress 默认主题 https://vitepress.dev/zh/reference/frontmatter-config
50
+ 'lang',
51
+ 'outline',
52
+ 'head',
53
+ 'layout',
54
+ 'hero',
55
+ 'features',
56
+ 'navbar',
57
+ 'sidebar',
58
+ 'aside',
59
+ 'lastUpdated',
60
+ 'editLink',
61
+ 'footer',
62
+ 'pageClass',
63
+ // 本主题扩展 https://theme.sugarat.top/config/frontmatter.html
64
+ 'hiddenCover',
65
+ 'readingTime',
66
+ 'buttonAfterArticle'
67
+ ]
68
+ if (matched && !isEqual(matched.meta, meta, inlineKeys.concat(excludeKeys))) {
39
69
  matched.meta = meta
40
70
  restart()
41
71
  }
@@ -31,8 +31,6 @@ export function getFirstImagURLFromMD(content: string, route: string) {
31
31
  return url
32
32
  }
33
33
 
34
- // TODO: 其它协议,待补充
35
-
36
34
  const paths = joinPath('/', route).split('/')
37
35
  paths.splice(paths.length - 1, 1)
38
36
  const relativePath = url.startsWith('/') ? url : path.join(paths.join('/') || '', url)
@@ -3,6 +3,7 @@ import { createRequire } from 'module'
3
3
  import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
4
4
  import type { UserConfig } from 'vitepress'
5
5
  import timeline from 'vitepress-markdown-timeline'
6
+ import { groupIconMdPlugin } from 'vitepress-plugin-group-icons'
6
7
  import type { Theme } from '../../composables/config/index'
7
8
  import { aliasObjectToArray } from './index'
8
9
 
@@ -30,6 +31,9 @@ export function getMarkdownPlugins(cfg?: Partial<Theme.BlogConfig>) {
30
31
  if (cfg?.timeline !== false) {
31
32
  markdownPlugin.push(timeline)
32
33
  }
34
+
35
+ markdownPlugin.push(groupIconMdPlugin)
36
+
33
37
  return markdownPlugin
34
38
  }
35
39
 
@@ -1,9 +1,6 @@
1
- /* eslint-disable prefer-rest-params */
2
1
  import fs from 'node:fs'
3
2
  import path from 'node:path'
4
- import process from 'node:process'
5
- import glob from 'fast-glob'
6
- import { getDefaultTitle, getFileLastModifyTime, getTextSummary, grayMatter, normalizePath } from '@sugarat/theme-shared'
3
+ import { getDefaultTitle, getFileLastModifyTime, getTextSummary, getVitePressPages, grayMatter, normalizePath, renderDynamicMarkdown } from '@sugarat/theme-shared'
7
4
  import type { SiteConfig } from 'vitepress'
8
5
  import type { Theme } from '../../composables/config/index'
9
6
  import { formatDate } from '../client'
@@ -29,8 +26,8 @@ export function getPageRoute(filepath: string, srcDir: string) {
29
26
  }
30
27
 
31
28
  const defaultTimeZoneOffset = new Date().getTimezoneOffset() / -60
32
- export async function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset) {
33
- const fileContent = await fs.promises.readFile(filepath, 'utf-8')
29
+ export async function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset, baseContent?: string) {
30
+ const fileContent = baseContent || await fs.promises.readFile(filepath, 'utf-8')
34
31
 
35
32
  const { data: frontmatter, excerpt, content } = grayMatter(fileContent, {
36
33
  excerpt: true,
@@ -70,6 +67,7 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
70
67
  = meta.description || getTextSummary(content, 100) || excerpt
71
68
 
72
69
  // 获取封面图
70
+ // TODO: 耦合信息优化
73
71
  meta.cover
74
72
  = meta.cover
75
73
  ?? (getFirstImagURLFromMD(fileContent, route))
@@ -81,19 +79,18 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
81
79
  }
82
80
  return meta as Theme.PageMeta
83
81
  }
82
+
84
83
  export async function getArticles(cfg: Partial<Theme.BlogConfig>, vpConfig: SiteConfig) {
85
- const srcDir
86
- = cfg?.srcDir || vpConfig.srcDir.replace(vpConfig.root, '').replace(/^\//, '')
87
- || process.argv.slice(2)?.[1]
88
- || '.'
89
- const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'], absolute: true })
84
+ const pages = getVitePressPages(vpConfig)
85
+ const metaResults = pages.reduce((prev, value) => {
86
+ const { page, route, originRoute, filepath, isDynamic, dynamicRoute } = value
90
87
 
91
- const metaResults = files.reduce((prev, curr) => {
92
- const route = getPageRoute(curr, vpConfig.srcDir)
93
- const metaPromise = getArticleMeta(curr, route, cfg?.timeZone)
88
+ const metaPromise = (isDynamic && dynamicRoute)
89
+ ? getArticleMeta(filepath, originRoute, cfg?.timeZone, renderDynamicMarkdown(filepath, dynamicRoute.params, dynamicRoute.content))
90
+ : getArticleMeta(filepath, originRoute, cfg?.timeZone)
94
91
 
95
92
  // 提前获取,有缓存取缓存
96
- prev[curr] = {
93
+ prev[page] = {
97
94
  route,
98
95
  metaPromise
99
96
  }
@@ -105,8 +102,8 @@ export async function getArticles(cfg: Partial<Theme.BlogConfig>, vpConfig: Site
105
102
 
106
103
  const pageData: Theme.PageData[] = []
107
104
 
108
- for (const file of files) {
109
- const { route, metaPromise } = metaResults[file]
105
+ for (const page of pages) {
106
+ const { route, metaPromise } = metaResults[page.page]
110
107
  const meta = await metaPromise
111
108
  if (meta.layout === 'home') {
112
109
  continue
@@ -142,5 +139,5 @@ export function patchVPThemeConfig(
142
139
  }
143
140
 
144
141
  export function checkConfig(cfg?: Partial<Theme.BlogConfig>) {
145
- // TODO:保留
142
+ // 保留
146
143
  }
@@ -1,6 +1,3 @@
1
- import path from 'node:path'
2
- import { existsSync, readFileSync } from 'node:fs'
3
- import { Buffer } from 'node:buffer'
4
1
  import type { HeadConfig, SiteConfig } from 'vitepress'
5
2
  import {
6
3
  pagefindPlugin
@@ -8,6 +5,8 @@ import {
8
5
  import { RssPlugin } from 'vitepress-plugin-rss'
9
6
  import type { PluginOption } from 'vite'
10
7
  import { joinPath } from '@sugarat/theme-shared'
8
+ import { AnnouncementPlugin } from 'vitepress-plugin-announcement'
9
+ import { groupIconVitePlugin } from 'vitepress-plugin-group-icons'
11
10
  import type { Theme } from '../../composables/config/index'
12
11
  import { _require } from './mdPlugins'
13
12
  import { themeReloadPlugin } from './hot-reload-plugin'
@@ -16,7 +15,7 @@ import { getArticles } from './theme'
16
15
  export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
17
16
  const plugins: any[] = []
18
17
 
19
- // 处理cover image的路径(暂只支持自动识别的文章首图)
18
+ // 处理 cover image 的路径(暂只支持自动识别的文章首图)
20
19
  plugins.push(coverImgTransform())
21
20
 
22
21
  // 处理自定义主题色
@@ -26,10 +25,10 @@ export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
26
25
  // 自动重载首页
27
26
  plugins.push(themeReloadPlugin())
28
27
 
29
- // 主题pageData生成
28
+ // 主题 pageData生成
30
29
  plugins.push(providePageData(cfg))
31
30
 
32
- // 内置简化版的pagefind
31
+ // 内置 pagefind
33
32
  if (cfg && cfg.search !== false) {
34
33
  const ops = cfg.search instanceof Object ? cfg.search : {}
35
34
  plugins.push(
@@ -39,7 +38,7 @@ export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
39
38
  )
40
39
  }
41
40
 
42
- // 内置支持Mermaid
41
+ // 内置支持 Markdown 流程图 Mermaid
43
42
  if (cfg?.mermaid !== false) {
44
43
  const { MermaidPlugin } = _require('vitepress-plugin-mermaid')
45
44
  plugins.push(inlineInjectMermaidClient())
@@ -50,6 +49,16 @@ export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
50
49
  if (cfg?.RSS) {
51
50
  ;[cfg?.RSS].flat().forEach(rssConfig => plugins.push(RssPlugin(rssConfig)))
52
51
  }
52
+
53
+ // 内置支持 全局公告
54
+ if (cfg?.popover) {
55
+ plugins.push(AnnouncementPlugin(cfg.popover))
56
+ }
57
+
58
+ // 内置支持 group icon
59
+
60
+ plugins.push(groupIconVitePlugin(cfg?.groupIcon))
61
+
53
62
  return plugins
54
63
  }
55
64
 
@@ -101,58 +110,138 @@ export function inlineBuildEndPlugin(buildEndFn: any[]) {
101
110
  }
102
111
  }
103
112
 
104
- // TODO: 支持frontmatter中的相对路径图片自动处理
113
+ // 支持frontmatter中的相对路径图片自动处理
105
114
  export function coverImgTransform() {
106
115
  let blogConfig: Theme.BlogConfig
107
116
  let vitepressConfig: SiteConfig
108
117
  let assetsDir: string
118
+
119
+ const relativeMetaName: (keyof Theme.PageMeta)[] = ['cover']
120
+ const relativeMeta: Theme.PageMeta[] = []
121
+ const relativeMetaMap: Record<string, Theme.PageMeta> = {}
122
+ const viteAssetsMap: Record<string, string> = {}
123
+ const relativePathMap: Record<string, string> = {}
109
124
  return {
110
125
  name: '@sugarat/theme-plugin-cover-transform',
111
126
  apply: 'build',
112
- enforce: 'pre',
127
+ // enforce: 'pre',
113
128
  configResolved(config: any) {
114
129
  vitepressConfig = config.vitepress
115
130
  assetsDir = vitepressConfig.assetsDir
116
131
  blogConfig = config.vitepress.site.themeConfig.blog
132
+
133
+ const pagesData = [...blogConfig.pagesData]
134
+ // 兼容国际化
135
+ if (vitepressConfig.site.locales && Object.keys(vitepressConfig.site.locales).length > 1 && blogConfig?.locales) {
136
+ Object.values(blogConfig?.locales).map(v => v.pagesData)
137
+ .filter(v => !!v)
138
+ .forEach(v => pagesData.push(...v))
139
+ }
140
+ // 提取所有相对路径的属性
141
+ pagesData.forEach((v) => {
142
+ relativeMetaName.forEach((k) => {
143
+ const value = v.meta[k]
144
+ if (value && typeof value === 'string' && value.startsWith('/')) {
145
+ const absolutePath = `${vitepressConfig.srcDir}${value}`
146
+
147
+ // 复用已经映射后的值
148
+ if (relativeMetaMap[absolutePath]) {
149
+ Object.assign(v.meta, { [k]: relativeMetaMap[absolutePath][k] })
150
+ return
151
+ }
152
+
153
+ relativePathMap[value] = absolutePath
154
+ relativePathMap[absolutePath] = value
155
+ relativeMeta.push(v.meta)
156
+ relativeMetaMap[absolutePath] = v.meta
157
+ }
158
+ })
159
+ })
117
160
  },
118
- async generateBundle(_: any, bundle: Record<string, any>) {
161
+ moduleParsed(info) {
162
+ if (!relativePathMap[info.id]) {
163
+ return
164
+ }
165
+ const asset = info.code?.match(/export default "(.*)"/)?.[1]
166
+ if (!asset) {
167
+ return
168
+ }
169
+
170
+ viteAssetsMap[info.id] = asset
171
+ viteAssetsMap[asset] = info.id
172
+
173
+ // 换成 ViteAssets,影响输出 HTML
174
+ relativeMeta.forEach((meta) => {
175
+ relativeMetaName.forEach((k) => {
176
+ const value = meta[k]
177
+ if (!value || !relativePathMap[value as string]) {
178
+ return
179
+ }
180
+ const viteAsset = viteAssetsMap[relativePathMap[value as string]]
181
+ if (viteAsset) {
182
+ Object.assign(meta, { [k]: viteAsset })
183
+ }
184
+ })
185
+ })
186
+ },
187
+ generateBundle(_: any, bundle: Record<string, any>) {
188
+ // 换成 最终输出路径,影响 CSR 内容
119
189
  const assetsMap = Object.entries(bundle).filter(([key]) => {
120
190
  return key.startsWith(assetsDir)
121
191
  }).map(([_, value]) => {
122
192
  return value
123
- })
124
- for (const page of blogConfig.pagesData) {
125
- const { cover } = page.meta
126
- // 是否相对路径引用
127
- if (!cover?.startsWith?.('/')) {
128
- continue
129
- }
130
- try {
131
- // 寻找构建后的
132
- const realPath = path.join(vitepressConfig.root, cover)
133
- if (!existsSync(realPath)) {
134
- continue
193
+ }).filter(v => v.type === 'asset')
194
+
195
+ if (!assetsMap.length) {
196
+ return
197
+ }
198
+
199
+ relativeMeta.forEach((meta) => {
200
+ relativeMetaName.forEach((k) => {
201
+ const value = meta[k]
202
+ if (!value || !viteAssetsMap[value as string]) {
203
+ return
135
204
  }
136
- const fileBuffer = readFileSync(realPath)
137
- const matchAsset = assetsMap.find(v => Buffer.compare(fileBuffer, v.source) === 0)
205
+ const absolutePath = viteAssetsMap[value as string]
206
+ const matchAsset = assetsMap.find(v => joinPath(`${vitepressConfig.srcDir}/`, v.originalFileName) === absolutePath)
138
207
  if (matchAsset) {
139
- page.meta.cover = joinPath('/', matchAsset.fileName)
208
+ Object.assign(meta, { [k]: joinPath('/', matchAsset.fileName) })
140
209
  }
141
- }
142
- catch (e: any) {
143
- vitepressConfig.logger.warn(e?.message)
144
- }
145
- }
210
+ })
211
+ })
146
212
  }
147
- }
213
+ } as PluginOption
148
214
  }
149
215
 
150
216
  export function providePageData(cfg: Partial<Theme.BlogConfig>) {
151
217
  return {
152
218
  name: '@sugarat/theme-plugin-provide-page-data',
153
219
  async config(config: any) {
154
- const pagesData = await getArticles(cfg, config.vitepress)
155
- config.vitepress.site.themeConfig.blog.pagesData = pagesData
220
+ const vitepressConfig: SiteConfig = config.vitepress
221
+ const pagesData = await getArticles(cfg, vitepressConfig)
222
+ if (vitepressConfig.site.locales && Object.keys(vitepressConfig.site.locales).length > 1) {
223
+ if (!vitepressConfig.site.themeConfig.blog.locales) {
224
+ vitepressConfig.site.themeConfig.blog.locales = {}
225
+ }
226
+ // 兼容国际化
227
+ const localeKeys = Object.keys(vitepressConfig.site.locales)
228
+ localeKeys.forEach((localeKey) => {
229
+ if (!vitepressConfig.site.themeConfig.blog.locales[localeKey]) {
230
+ vitepressConfig.site.themeConfig.blog.locales[localeKey] = {}
231
+ }
232
+
233
+ vitepressConfig.site.themeConfig.blog.locales[localeKey].pagesData = pagesData.filter((v) => {
234
+ const { route } = v
235
+ const isRoot = localeKey === 'root'
236
+ if (isRoot) {
237
+ return !localeKeys.filter(v => v !== 'root').some(k => route.startsWith(`/${k}`))
238
+ }
239
+ return route.startsWith(`/${localeKey}`)
240
+ })
241
+ })
242
+ return
243
+ }
244
+ vitepressConfig.site.themeConfig.blog.pagesData = pagesData
156
245
  },
157
246
  } as PluginOption
158
247
  }