@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.
- package/node.d.ts +16 -74
- package/node.js +148 -51
- package/node.mjs +753 -0
- package/package.json +10 -6
- package/src/components/BlogAlert.vue +10 -9
- package/src/components/BlogApp.vue +0 -2
- package/src/components/BlogArticleAnalyze.vue +8 -9
- package/src/components/BlogAuthor.vue +6 -5
- package/src/components/BlogBackToTop.vue +8 -10
- package/src/components/BlogButtonAfterArticle.vue +5 -14
- package/src/components/BlogCommentWrapper.vue +11 -28
- package/src/components/BlogDocCover.vue +3 -3
- package/src/components/BlogFooter.vue +3 -1
- package/src/components/BlogFriendLink.vue +4 -3
- package/src/components/BlogHomeBanner.vue +7 -6
- package/src/components/BlogHomeHeaderAvatar.vue +3 -3
- package/src/components/BlogHomeOverview.vue +3 -3
- package/src/components/BlogHomeTags.vue +5 -5
- package/src/components/BlogHotArticle.vue +4 -7
- package/src/components/BlogItem.vue +1 -1
- package/src/components/BlogList.vue +7 -6
- package/src/components/BlogRecommendArticle.vue +11 -14
- package/src/components/BlogSidebar.vue +9 -15
- package/src/components/CommentArtalk.vue +7 -5
- package/src/components/CommentGiscus.vue +7 -8
- package/src/components/Icon.vue +33 -0
- package/src/composables/config/blog.ts +203 -87
- package/src/composables/config/index.ts +12 -79
- package/src/hooks/useOml2d.ts +15 -8
- package/src/index.ts +3 -0
- package/src/node.ts +5 -1
- package/src/styles/index.scss +6 -6
- package/src/utils/node/hot-reload-plugin.ts +31 -1
- package/src/utils/node/index.ts +0 -2
- package/src/utils/node/mdPlugins.ts +4 -0
- package/src/utils/node/theme.ts +15 -18
- package/src/utils/node/vitePlugins.ts +122 -33
- package/src/components/BlogPopover.vue +0 -290
|
@@ -1,45 +1,13 @@
|
|
|
1
1
|
/* eslint-disable ts/no-namespace */
|
|
2
|
-
import type {
|
|
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?:
|
|
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 = {
|
package/src/hooks/useOml2d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/styles/index.scss
CHANGED
|
@@ -102,12 +102,12 @@ html.dark {
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
// sidebar
|
|
105
|
-
@media (min-width: 1440px) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/utils/node/index.ts
CHANGED
|
@@ -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
|
|
package/src/utils/node/theme.ts
CHANGED
|
@@ -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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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[
|
|
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
|
|
109
|
-
const { route, metaPromise } = metaResults[
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
137
|
-
const matchAsset = assetsMap.find(v =>
|
|
205
|
+
const absolutePath = viteAssetsMap[value as string]
|
|
206
|
+
const matchAsset = assetsMap.find(v => joinPath(`${vitepressConfig.srcDir}/`, v.originalFileName) === absolutePath)
|
|
138
207
|
if (matchAsset) {
|
|
139
|
-
|
|
208
|
+
Object.assign(meta, { [k]: joinPath('/', matchAsset.fileName) })
|
|
140
209
|
}
|
|
141
|
-
}
|
|
142
|
-
|
|
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
|
|
155
|
-
|
|
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
|
}
|