valaxy 0.28.0-beta.7 → 0.28.1
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/client/components/.exclude/ValaxyDebug.vue +176 -0
- package/client/components/ValaxyOpenInEditor.vue +53 -0
- package/client/composables/app/useValaxyApp.ts +1 -7
- package/client/composables/categories.ts +3 -56
- package/client/composables/category-utils.ts +50 -0
- package/client/composables/index.ts +1 -0
- package/client/composables/outline/anchor.ts +1 -1
- package/client/composables/tags.ts +0 -5
- package/client/modules/components.ts +7 -0
- package/client/modules/valaxy.ts +28 -3
- package/dist/node/cli/index.mjs +2 -2
- package/dist/node/index.d.mts +15 -1
- package/dist/node/index.mjs +2 -2
- package/dist/shared/{valaxy.JIuR8V4d.d.mts → valaxy.6MW2qn5T.d.mts} +12 -4
- package/dist/shared/{valaxy.DAkHYbg0.mjs → valaxy.DFTOADvQ.mjs} +258 -108
- package/dist/types/index.d.mts +2 -2
- package/package.json +8 -9
- package/types/config.ts +0 -4
- package/types/frontmatter/page.ts +12 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, ref } from 'vue'
|
|
3
|
+
import { useRoute } from 'vue-router'
|
|
4
|
+
import { useFrontmatter } from '../../composables/common'
|
|
5
|
+
import { useScreenSize } from '../../composables/helper/useScreenSize'
|
|
6
|
+
import { useLayout } from '../../composables/layout'
|
|
7
|
+
import { useSiteConfig, useThemeConfig } from '../../config'
|
|
8
|
+
import ValaxySvgLogo from '../ValaxySvgLogo.vue'
|
|
9
|
+
|
|
10
|
+
const show = ref(true)
|
|
11
|
+
const expanded = ref<Record<string, boolean>>({
|
|
12
|
+
breakpoints: true,
|
|
13
|
+
route: false,
|
|
14
|
+
frontmatter: false,
|
|
15
|
+
config: false,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
function toggleSection(key: string) {
|
|
19
|
+
expanded.value[key] = !expanded.value[key]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Breakpoints
|
|
23
|
+
const screenSize = useScreenSize()
|
|
24
|
+
const breakpoints = computed(() => [
|
|
25
|
+
{ label: 'xs', value: screenSize.isXs.value },
|
|
26
|
+
{ label: 'sm', value: screenSize.isSm.value },
|
|
27
|
+
{ label: 'md', value: screenSize.isMd.value },
|
|
28
|
+
{ label: 'lg', value: screenSize.isLg.value },
|
|
29
|
+
{ label: 'xl', value: screenSize.isXl.value },
|
|
30
|
+
{ label: '2xl', value: screenSize.is2xl.value },
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
// Route
|
|
34
|
+
const route = useRoute()
|
|
35
|
+
const routeInfo = computed(() => ({
|
|
36
|
+
path: route.path,
|
|
37
|
+
name: route.name as string,
|
|
38
|
+
layout: route.meta?.layout || 'default',
|
|
39
|
+
query: Object.keys(route.query).length ? route.query : undefined,
|
|
40
|
+
params: Object.keys(route.params).length ? route.params : undefined,
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
// Frontmatter
|
|
44
|
+
const frontmatter = useFrontmatter()
|
|
45
|
+
const fmSummary = computed(() => {
|
|
46
|
+
const fm = frontmatter.value
|
|
47
|
+
if (!fm || !Object.keys(fm).length)
|
|
48
|
+
return null
|
|
49
|
+
return fm
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// Config
|
|
53
|
+
const siteConfig = useSiteConfig()
|
|
54
|
+
const themeConfig = useThemeConfig()
|
|
55
|
+
const layout = useLayout()
|
|
56
|
+
|
|
57
|
+
const configSummary = computed(() => ({
|
|
58
|
+
theme: siteConfig.value.lang ? undefined : undefined,
|
|
59
|
+
lang: siteConfig.value.lang,
|
|
60
|
+
title: siteConfig.value.title,
|
|
61
|
+
url: siteConfig.value.url,
|
|
62
|
+
layout: layout.value,
|
|
63
|
+
}))
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<div
|
|
68
|
+
v-if="show"
|
|
69
|
+
class="valaxy-debug fixed bottom-4 left-2 z-9999 max-h-[80vh] w-72 overflow-y-auto rounded-lg bg-black/80 p-3 text-xs text-white shadow-lg backdrop-blur-sm"
|
|
70
|
+
@click.stop
|
|
71
|
+
>
|
|
72
|
+
<!-- Header -->
|
|
73
|
+
<div class="mb-2 flex items-center justify-between border-b border-white/20 pb-2">
|
|
74
|
+
<span class="flex items-center gap-1 font-bold text-cyan-400">
|
|
75
|
+
<ValaxySvgLogo class="size-4" />
|
|
76
|
+
Valaxy Debug
|
|
77
|
+
</span>
|
|
78
|
+
<button
|
|
79
|
+
class="rounded px-1 text-white/60 transition hover:bg-white/20 hover:text-white"
|
|
80
|
+
title="Close"
|
|
81
|
+
@click="show = false"
|
|
82
|
+
>
|
|
83
|
+
✕
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<!-- Breakpoints -->
|
|
88
|
+
<div class="mb-1">
|
|
89
|
+
<button
|
|
90
|
+
class="w-full rounded px-1 py-0.5 text-left font-bold text-emerald-400 transition hover:bg-white/10"
|
|
91
|
+
@click="toggleSection('breakpoints')"
|
|
92
|
+
>
|
|
93
|
+
{{ expanded.breakpoints ? '▾' : '▸' }} Breakpoints
|
|
94
|
+
</button>
|
|
95
|
+
<div v-if="expanded.breakpoints" class="mt-1 flex flex-wrap gap-1 pl-3">
|
|
96
|
+
<span
|
|
97
|
+
v-for="bp in breakpoints"
|
|
98
|
+
:key="bp.label"
|
|
99
|
+
class="rounded px-1.5 py-0.5"
|
|
100
|
+
:class="bp.value ? 'bg-emerald-500/30 text-emerald-300' : 'bg-white/5 text-white/40'"
|
|
101
|
+
>
|
|
102
|
+
{{ bp.label }}
|
|
103
|
+
</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Route -->
|
|
108
|
+
<div class="mb-1">
|
|
109
|
+
<button
|
|
110
|
+
class="w-full rounded px-1 py-0.5 text-left font-bold text-blue-400 transition hover:bg-white/10"
|
|
111
|
+
@click="toggleSection('route')"
|
|
112
|
+
>
|
|
113
|
+
{{ expanded.route ? '▾' : '▸' }} Route
|
|
114
|
+
</button>
|
|
115
|
+
<div v-if="expanded.route" class="mt-1 space-y-0.5 pl-3">
|
|
116
|
+
<div><span class="text-white/50">path:</span> {{ routeInfo.path }}</div>
|
|
117
|
+
<div><span class="text-white/50">name:</span> {{ routeInfo.name }}</div>
|
|
118
|
+
<div><span class="text-white/50">layout:</span> {{ routeInfo.layout }}</div>
|
|
119
|
+
<div v-if="routeInfo.query">
|
|
120
|
+
<span class="text-white/50">query:</span> {{ JSON.stringify(routeInfo.query) }}
|
|
121
|
+
</div>
|
|
122
|
+
<div v-if="routeInfo.params">
|
|
123
|
+
<span class="text-white/50">params:</span> {{ JSON.stringify(routeInfo.params) }}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
|
|
128
|
+
<!-- Frontmatter -->
|
|
129
|
+
<div class="mb-1">
|
|
130
|
+
<button
|
|
131
|
+
class="w-full rounded px-1 py-0.5 text-left font-bold text-amber-400 transition hover:bg-white/10"
|
|
132
|
+
@click="toggleSection('frontmatter')"
|
|
133
|
+
>
|
|
134
|
+
{{ expanded.frontmatter ? '▾' : '▸' }} Frontmatter
|
|
135
|
+
</button>
|
|
136
|
+
<div v-if="expanded.frontmatter" class="mt-1 pl-3">
|
|
137
|
+
<pre v-if="fmSummary" class="max-h-48 overflow-auto whitespace-pre-wrap break-all rounded bg-white/5 p-1.5 text-white/80">{{ JSON.stringify(fmSummary, null, 2) }}</pre>
|
|
138
|
+
<span v-else class="text-white/40">No frontmatter</span>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<!-- Config -->
|
|
143
|
+
<div class="mb-1">
|
|
144
|
+
<button
|
|
145
|
+
class="w-full rounded px-1 py-0.5 text-left font-bold text-purple-400 transition hover:bg-white/10"
|
|
146
|
+
@click="toggleSection('config')"
|
|
147
|
+
>
|
|
148
|
+
{{ expanded.config ? '▾' : '▸' }} Config
|
|
149
|
+
</button>
|
|
150
|
+
<div v-if="expanded.config" class="mt-1 space-y-2 pl-3">
|
|
151
|
+
<div>
|
|
152
|
+
<div class="mb-0.5 text-white/50">
|
|
153
|
+
Site Config
|
|
154
|
+
</div>
|
|
155
|
+
<pre class="max-h-48 overflow-auto whitespace-pre-wrap break-all rounded bg-white/5 p-1.5 text-white/80">{{ JSON.stringify(configSummary, null, 2) }}</pre>
|
|
156
|
+
</div>
|
|
157
|
+
<div>
|
|
158
|
+
<div class="mb-0.5 text-white/50">
|
|
159
|
+
Theme Config
|
|
160
|
+
</div>
|
|
161
|
+
<pre class="max-h-48 overflow-auto whitespace-pre-wrap break-all rounded bg-white/5 p-1.5 text-white/80">{{ JSON.stringify(themeConfig, null, 2) }}</pre>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- Collapsed toggle button -->
|
|
168
|
+
<button
|
|
169
|
+
v-if="!show"
|
|
170
|
+
class="fixed bottom-4 left-2 z-9999 rounded-lg bg-black/60 px-2 py-1 text-xs text-white/60 shadow-lg backdrop-blur-sm transition hover:bg-black/80 hover:text-white"
|
|
171
|
+
title="Open Valaxy Debug"
|
|
172
|
+
@click="show = true"
|
|
173
|
+
>
|
|
174
|
+
<ValaxySvgLogo class="size-4" />
|
|
175
|
+
</button>
|
|
176
|
+
</template>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import { useData } from '../composables'
|
|
3
|
+
|
|
4
|
+
const { page } = useData()
|
|
5
|
+
|
|
6
|
+
const isDev = import.meta.env.DEV
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Open the current page source file in editor (dev mode only)
|
|
10
|
+
* Uses Vite's built-in `/__open-in-editor` endpoint
|
|
11
|
+
*/
|
|
12
|
+
function openInEditor() {
|
|
13
|
+
const filePath = page.value?.filePath
|
|
14
|
+
if (filePath) {
|
|
15
|
+
fetch(`${window.location.origin}/__open-in-editor?file=${encodeURIComponent(filePath)}`)
|
|
16
|
+
.catch((err) => {
|
|
17
|
+
console.error('[valaxy] Failed to open in editor:', err)
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<button
|
|
25
|
+
v-if="isDev"
|
|
26
|
+
class="valaxy-open-in-editor"
|
|
27
|
+
title="Open in Editor"
|
|
28
|
+
@click="openInEditor"
|
|
29
|
+
>
|
|
30
|
+
<slot>
|
|
31
|
+
<div i-ri-code-s-slash-line />
|
|
32
|
+
</slot>
|
|
33
|
+
</button>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.valaxy-open-in-editor {
|
|
38
|
+
display: inline-flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
opacity: 0.4;
|
|
42
|
+
transition: opacity 0.2s;
|
|
43
|
+
background: none;
|
|
44
|
+
border: none;
|
|
45
|
+
padding: 0;
|
|
46
|
+
color: inherit;
|
|
47
|
+
font-size: inherit;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.valaxy-open-in-editor:hover {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
</style>
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { definePerson, defineWebPage, defineWebSite, useSchemaOrg } from '@unhead/schema-org/vue'
|
|
2
2
|
import { useSeoMeta } from '@unhead/vue'
|
|
3
|
-
|
|
4
|
-
// TODO: add docs to override ValaxyApp
|
|
5
3
|
import { computed } from 'vue'
|
|
6
4
|
import { useI18n } from 'vue-i18n'
|
|
7
|
-
|
|
8
5
|
import { useRoute } from 'vue-router'
|
|
9
|
-
|
|
10
6
|
import { useFrontmatter, useLocale, useValaxyHead, useValaxyI18n } from '../../composables'
|
|
11
7
|
import { useTimezone } from '../../composables/global'
|
|
12
8
|
// https://github.com/vueuse/head
|
|
@@ -16,7 +12,6 @@ import { useSiteConfig } from '../../config'
|
|
|
16
12
|
|
|
17
13
|
export function useValaxyApp() {
|
|
18
14
|
const siteConfig = useSiteConfig()
|
|
19
|
-
// todo, allow user config
|
|
20
15
|
const fm = useFrontmatter()
|
|
21
16
|
|
|
22
17
|
const { locale } = useI18n()
|
|
@@ -32,7 +27,6 @@ export function useValaxyApp() {
|
|
|
32
27
|
}
|
|
33
28
|
|
|
34
29
|
// seo
|
|
35
|
-
// todo: get first image url from markdown
|
|
36
30
|
const siteUrl = computed(() => fm.value.url || siteConfig.value.url)
|
|
37
31
|
const description = computed(() => $tO(fm.value.excerpt) || $tO(fm.value.description) || $t(siteConfig.value.description))
|
|
38
32
|
|
|
@@ -43,7 +37,7 @@ export function useValaxyApp() {
|
|
|
43
37
|
ogLocaleAlternate: computed(() => siteConfig.value.languages.filter(l => l !== locale.value)),
|
|
44
38
|
ogSiteName: computed(() => $t(siteConfig.value.title)),
|
|
45
39
|
ogTitle: computed(() => $tO(fm.value.title) || $t(siteConfig.value.title)),
|
|
46
|
-
ogImage: computed(() => fm.value.ogImage || fm.value.cover || siteConfig.value.favicon),
|
|
40
|
+
ogImage: computed(() => fm.value.ogImage || fm.value.cover || fm.value.firstImage || siteConfig.value.favicon),
|
|
47
41
|
ogType: 'website',
|
|
48
42
|
ogUrl: siteUrl,
|
|
49
43
|
})
|
|
@@ -1,47 +1,11 @@
|
|
|
1
1
|
import type { MaybeRef } from 'vue'
|
|
2
2
|
import type { Post } from '../../types'
|
|
3
|
+
import type { CategoryList } from './category-utils'
|
|
3
4
|
import { computed, unref } from 'vue'
|
|
4
5
|
import { useSiteStore } from '../stores'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export interface BaseCategory {
|
|
10
|
-
/**
|
|
11
|
-
* 分类下的文章数量
|
|
12
|
-
*/
|
|
13
|
-
total: number
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @en
|
|
18
|
-
* Category list
|
|
19
|
-
*
|
|
20
|
-
* @zh
|
|
21
|
-
* 分类列表
|
|
22
|
-
*/
|
|
23
|
-
export interface CategoryList {
|
|
24
|
-
/**
|
|
25
|
-
* category name
|
|
26
|
-
*/
|
|
27
|
-
name: string
|
|
28
|
-
/**
|
|
29
|
-
* total posts
|
|
30
|
-
*/
|
|
31
|
-
total: number
|
|
32
|
-
children: Map<string, Post | CategoryList>
|
|
33
|
-
}
|
|
34
|
-
export type Category = CategoryList
|
|
35
|
-
export type Categories = Map<string, Post | CategoryList>
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* For theme development, you can use this function to determine whether the category is a category list.
|
|
39
|
-
* @todo write unit test
|
|
40
|
-
* @param category
|
|
41
|
-
*/
|
|
42
|
-
export function isCategoryList(category: any): category is CategoryList {
|
|
43
|
-
return category.children
|
|
44
|
-
}
|
|
7
|
+
export type { BaseCategory, Categories, Category, CategoryList } from './category-utils'
|
|
8
|
+
export { isCategoryList, removeItemFromCategory } from './category-utils'
|
|
45
9
|
|
|
46
10
|
/**
|
|
47
11
|
* get categories from posts
|
|
@@ -158,20 +122,3 @@ export function useCategories(category?: MaybeRef<string>, posts: Post[] = []) {
|
|
|
158
122
|
}
|
|
159
123
|
})
|
|
160
124
|
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* remove item from category
|
|
164
|
-
* @param categoryList
|
|
165
|
-
* @param categoryName
|
|
166
|
-
*/
|
|
167
|
-
export function removeItemFromCategory(categoryList: CategoryList, categoryName: string) {
|
|
168
|
-
if (isCategoryList(categoryList)) {
|
|
169
|
-
const categoryArr = categoryName.split('/')
|
|
170
|
-
categoryList.children.delete(categoryArr[0])
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* @deprecated use `useCategories` instead
|
|
176
|
-
*/
|
|
177
|
-
export const useCategory = useCategories
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Post } from '../../types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 基础分类
|
|
5
|
+
*/
|
|
6
|
+
export interface BaseCategory {
|
|
7
|
+
/**
|
|
8
|
+
* 分类下的文章数量
|
|
9
|
+
*/
|
|
10
|
+
total: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @en
|
|
15
|
+
* Category list
|
|
16
|
+
*
|
|
17
|
+
* @zh
|
|
18
|
+
* 分类列表
|
|
19
|
+
*/
|
|
20
|
+
export interface CategoryList {
|
|
21
|
+
/**
|
|
22
|
+
* category name
|
|
23
|
+
*/
|
|
24
|
+
name: string
|
|
25
|
+
/**
|
|
26
|
+
* total posts
|
|
27
|
+
*/
|
|
28
|
+
total: number
|
|
29
|
+
children: Map<string, Post | CategoryList>
|
|
30
|
+
}
|
|
31
|
+
export type Category = CategoryList
|
|
32
|
+
export type Categories = Map<string, Post | CategoryList>
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For theme development, you can use this function to determine whether the category is a category list.
|
|
36
|
+
* @param category
|
|
37
|
+
*/
|
|
38
|
+
export function isCategoryList(category: unknown): category is CategoryList {
|
|
39
|
+
return !!category && typeof category === 'object' && 'children' in category && category.children instanceof Map
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* remove item from category
|
|
44
|
+
* @param categoryList
|
|
45
|
+
* @param categoryName
|
|
46
|
+
*/
|
|
47
|
+
export function removeItemFromCategory(categoryList: CategoryList, categoryName: string) {
|
|
48
|
+
const categoryArr = categoryName.split('/')
|
|
49
|
+
categoryList.children.delete(categoryArr[0])
|
|
50
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ValaxySSGContext } from '../setups'
|
|
2
2
|
|
|
3
|
+
import { defineAsyncComponent } from 'vue'
|
|
3
4
|
import AppLink from '../components/AppLink.vue'
|
|
4
5
|
import ValaxyTranslate from '../components/builtin/ValaxyTranslate.vue'
|
|
5
6
|
|
|
@@ -10,4 +11,10 @@ import ValaxyTranslate from '../components/builtin/ValaxyTranslate.vue'
|
|
|
10
11
|
export function registerGlobalComponents(ctx: ValaxySSGContext) {
|
|
11
12
|
ctx.app.component('AppLink', AppLink)
|
|
12
13
|
ctx.app.component('VT', ValaxyTranslate)
|
|
14
|
+
|
|
15
|
+
// DEV-only: register ValaxyDebug component (tree-shaken in production)
|
|
16
|
+
if (import.meta.env.DEV) {
|
|
17
|
+
const ValaxyDebug = defineAsyncComponent(() => import('../components/.exclude/ValaxyDebug.vue'))
|
|
18
|
+
ctx.app.component('ValaxyDebug', ValaxyDebug)
|
|
19
|
+
}
|
|
13
20
|
}
|
package/client/modules/valaxy.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { PageDataPayload } from '../../types'
|
|
|
14
14
|
import type { ValaxySSGContext } from '../setups'
|
|
15
15
|
import { ensureSuffix } from '@antfu/utils'
|
|
16
16
|
import { useStorage } from '@vueuse/core'
|
|
17
|
+
import { nextTick, watch } from 'vue'
|
|
17
18
|
import { createI18n } from 'vue-i18n'
|
|
18
19
|
|
|
19
20
|
// @ts-expect-error virtual
|
|
@@ -52,11 +53,35 @@ export const i18n = createI18n({
|
|
|
52
53
|
})
|
|
53
54
|
|
|
54
55
|
export async function install({ app, router }: ValaxySSGContext, config: ComputedRef<ValaxyConfig<DefaultTheme.Config>>) {
|
|
55
|
-
const
|
|
56
|
-
|
|
56
|
+
const defaultLang = config?.value.siteConfig.lang || 'en'
|
|
57
|
+
|
|
58
|
+
// During SSR/SSG build **and** the initial client hydration pass we must
|
|
59
|
+
// keep the locale at `defaultLang` so that the rendered HTML matches on
|
|
60
|
+
// both sides — no hydration mismatch for any i18n-dependent attribute
|
|
61
|
+
// (title, class, text content, etc.).
|
|
62
|
+
//
|
|
63
|
+
// The stored user preference is restored **after** hydration is complete
|
|
64
|
+
// (router.isReady + nextTick) so Vue can patch the DOM normally.
|
|
65
|
+
i18n.global.locale.value = defaultLang
|
|
57
66
|
|
|
58
67
|
app.use(i18n)
|
|
59
|
-
|
|
68
|
+
|
|
69
|
+
router.isReady().then(async () => {
|
|
70
|
+
// Wait for the hydration to finish before restoring the stored locale.
|
|
71
|
+
await nextTick()
|
|
72
|
+
|
|
73
|
+
const storedLocale = useStorage('valaxy-locale', defaultLang)
|
|
74
|
+
|
|
75
|
+
// Apply the stored locale (if different from default)
|
|
76
|
+
if (storedLocale.value && storedLocale.value !== i18n.global.locale.value)
|
|
77
|
+
i18n.global.locale.value = storedLocale.value
|
|
78
|
+
|
|
79
|
+
// Keep i18n locale in sync when the stored value changes later
|
|
80
|
+
watch(storedLocale, (val) => {
|
|
81
|
+
if (val)
|
|
82
|
+
i18n.global.locale.value = val
|
|
83
|
+
})
|
|
84
|
+
|
|
60
85
|
handleHMR(router)
|
|
61
86
|
})
|
|
62
87
|
}
|
package/dist/node/cli/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import 'node:process';
|
|
2
2
|
import 'yargs';
|
|
3
3
|
import 'yargs/helpers';
|
|
4
|
-
export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.
|
|
4
|
+
export { c as cli, I as registerDevCommand, W as run, Z as startValaxyDev } from '../../shared/valaxy.DFTOADvQ.mjs';
|
|
5
5
|
import 'node:os';
|
|
6
6
|
import 'node:path';
|
|
7
7
|
import 'consola';
|
|
@@ -29,6 +29,7 @@ import 'feed';
|
|
|
29
29
|
import 'markdown-it';
|
|
30
30
|
import 'table';
|
|
31
31
|
import 'hookable';
|
|
32
|
+
import 'node:fs';
|
|
32
33
|
import 'node:child_process';
|
|
33
34
|
import 'node:v8';
|
|
34
35
|
import 'vite-ssg-sitemap';
|
|
@@ -57,7 +58,6 @@ import 'p-map';
|
|
|
57
58
|
import 'node:buffer';
|
|
58
59
|
import 'minisearch';
|
|
59
60
|
import 'lru-cache';
|
|
60
|
-
import 'node:fs';
|
|
61
61
|
import 'jiti';
|
|
62
62
|
import 'unocss';
|
|
63
63
|
import 'pascalcase';
|
package/dist/node/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ViteSSGOptions } from 'vite-ssg';
|
|
2
2
|
import * as vite from 'vite';
|
|
3
3
|
import { UserConfig, InlineConfig, ViteDevServer, PluginOption, Plugin } from 'vite';
|
|
4
|
-
import { D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon,
|
|
4
|
+
import { S as SiteConfig, D as DefaultTheme, R as RuntimeConfig, a as RedirectItem, V as ValaxyConfig, P as PartialDeep, b as ValaxyAddon, U as UserSiteConfig } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
5
5
|
import Vue from '@vitejs/plugin-vue';
|
|
6
6
|
import { Hookable } from 'hookable';
|
|
7
7
|
import { PluginVisualizerOptions } from 'rollup-plugin-visualizer';
|
|
@@ -445,6 +445,14 @@ interface ValaxyHooks {
|
|
|
445
445
|
'md:afterRender': (ctx: MdAfterRenderContext) => HookResult;
|
|
446
446
|
'build:before': () => HookResult;
|
|
447
447
|
'build:after': () => HookResult;
|
|
448
|
+
/**
|
|
449
|
+
* Called to compute statistics (word count, reading time) for a markdown route.
|
|
450
|
+
* Default implementation uses `presetStatistics`; addons/themes can hook to override.
|
|
451
|
+
*/
|
|
452
|
+
'statistics': (ctx: {
|
|
453
|
+
route: EditableTreeNode;
|
|
454
|
+
options: SiteConfig['statistics'];
|
|
455
|
+
}) => HookResult;
|
|
448
456
|
/**
|
|
449
457
|
* @experimental
|
|
450
458
|
* Called before content loaders start fetching.
|
|
@@ -768,6 +776,12 @@ interface ValaxyExtendConfig {
|
|
|
768
776
|
* @default true
|
|
769
777
|
*/
|
|
770
778
|
katex: boolean;
|
|
779
|
+
/**
|
|
780
|
+
* @description:en-US Auto-extract the first image from markdown content for Open Graph fallback
|
|
781
|
+
* @description:zh-CN 自动从 Markdown 内容中提取第一张图片,作为 Open Graph 的回退图片
|
|
782
|
+
* @default true
|
|
783
|
+
*/
|
|
784
|
+
extractFirstImage: boolean;
|
|
771
785
|
};
|
|
772
786
|
/**
|
|
773
787
|
* Enable MathJax3 math rendering (aligned with VitePress `markdown.math`).
|
package/dist/node/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.
|
|
1
|
+
export { A as ALL_ROUTE, E as EXCERPT_SEPARATOR, G as GLOBAL_STATE, P as PATHNAME_PROTOCOL_RE, V as ViteValaxyPlugins, b as build, c as cli, a as createServer, d as createValaxyPlugin, e as customElements, f as defaultSiteConfig, g as defaultValaxyConfig, h as defaultViteConfig, i as defineAddon, j as defineConfig, k as defineSiteConfig, l as defineTheme, m as defineValaxyAddon, n as defineValaxyConfig, o as defineValaxyTheme, p as encryptContent, q as generateClientRedirects, r as getGitTimestamp, s as getIndexHtml, t as getServerInfoText, u as isExternal, v as isInstalledGlobally, w as isKatexEnabled, x as isKatexPluginNeeded, y as isMathJaxEnabled, z as isPath, B as loadConfigFromFile, C as mergeValaxyConfig, D as mergeViteConfigs, F as postProcessForSSG, H as processValaxyOptions, I as registerDevCommand, J as resolveAddonsConfig, K as resolveImportPath, L as resolveImportUrl, M as resolveOptions, N as resolveSiteConfig, O as resolveSiteConfigFromRoot, Q as resolveThemeConfigFromRoot, R as resolveThemeValaxyConfig, S as resolveUserThemeConfig, T as resolveValaxyConfig, U as resolveValaxyConfigFromRoot, W as run, X as ssgBuild, Y as ssgBuildLegacy, Z as startValaxyDev, _ as toAtFS, $ as transformObject, a0 as version } from '../shared/valaxy.DFTOADvQ.mjs';
|
|
2
2
|
import 'node:path';
|
|
3
3
|
import 'fs-extra';
|
|
4
4
|
import 'consola/utils';
|
|
@@ -29,6 +29,7 @@ import 'feed';
|
|
|
29
29
|
import 'markdown-it';
|
|
30
30
|
import 'table';
|
|
31
31
|
import 'hookable';
|
|
32
|
+
import 'node:fs';
|
|
32
33
|
import 'node:child_process';
|
|
33
34
|
import 'node:v8';
|
|
34
35
|
import 'vite-ssg-sitemap';
|
|
@@ -57,7 +58,6 @@ import 'p-map';
|
|
|
57
58
|
import 'node:buffer';
|
|
58
59
|
import 'minisearch';
|
|
59
60
|
import 'lru-cache';
|
|
60
|
-
import 'node:fs';
|
|
61
61
|
import 'jiti';
|
|
62
62
|
import 'unocss';
|
|
63
63
|
import 'pascalcase';
|
|
@@ -167,6 +167,18 @@ interface PageFrontMatter extends BaseFrontMatter {
|
|
|
167
167
|
* @description 封面图片
|
|
168
168
|
*/
|
|
169
169
|
cover: string;
|
|
170
|
+
/**
|
|
171
|
+
* @description:en-US Open Graph image for SEO
|
|
172
|
+
* @description:zh-CN Open Graph 图片,用于 SEO
|
|
173
|
+
*/
|
|
174
|
+
ogImage: string;
|
|
175
|
+
/**
|
|
176
|
+
* @protected
|
|
177
|
+
* @tutorial ⚠️ DO NOT SET MANUALLY (auto-extracted from markdown content)
|
|
178
|
+
* @description:en-US First image URL extracted from markdown content
|
|
179
|
+
* @description:zh-CN 从 Markdown 内容中自动提取的第一张图片 URL
|
|
180
|
+
*/
|
|
181
|
+
firstImage: string;
|
|
170
182
|
/**
|
|
171
183
|
* display toc
|
|
172
184
|
* @description 是否显示目录
|
|
@@ -638,10 +650,6 @@ interface SiteConfig {
|
|
|
638
650
|
* @zh 是否启用
|
|
639
651
|
*/
|
|
640
652
|
enable: boolean;
|
|
641
|
-
/**
|
|
642
|
-
* @deprecated will be deprecated, use search.provider instead
|
|
643
|
-
*/
|
|
644
|
-
type?: SiteConfig['search']['provider'];
|
|
645
653
|
/**
|
|
646
654
|
* Search Type
|
|
647
655
|
* - algolia: Algolia Search
|
|
@@ -28,7 +28,8 @@ import { Feed } from 'feed';
|
|
|
28
28
|
import MarkdownIt from 'markdown-it';
|
|
29
29
|
import { table, getBorderCharacters } from 'table';
|
|
30
30
|
import { createHooks } from 'hookable';
|
|
31
|
-
import {
|
|
31
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
32
|
+
import { execFileSync, execSync, exec } from 'node:child_process';
|
|
32
33
|
import v8 from 'node:v8';
|
|
33
34
|
import generateSitemap from 'vite-ssg-sitemap';
|
|
34
35
|
import { createMarkdownItAsync, MarkdownItAsync } from 'markdown-it-async';
|
|
@@ -56,14 +57,13 @@ import pMap from 'p-map';
|
|
|
56
57
|
import { Buffer as Buffer$1 } from 'node:buffer';
|
|
57
58
|
import MiniSearch from 'minisearch';
|
|
58
59
|
import { LRUCache } from 'lru-cache';
|
|
59
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
60
60
|
import { createJiti } from 'jiti';
|
|
61
61
|
import { transformerDirectives, transformerVariantGroup, presetWind4, presetAttributify, presetIcons, presetTypography } from 'unocss';
|
|
62
62
|
import pascalCase from 'pascalcase';
|
|
63
63
|
import { convert } from 'html-to-text';
|
|
64
64
|
import VueRouter from 'vue-router/vite';
|
|
65
65
|
import { renderSSRHead } from '@unhead/vue/server';
|
|
66
|
-
import { intro, confirm, select, outro } from '@clack/prompts';
|
|
66
|
+
import { intro, confirm, isCancel, cancel, select, outro } from '@clack/prompts';
|
|
67
67
|
import net from 'node:net';
|
|
68
68
|
import * as readline from 'node:readline';
|
|
69
69
|
import qrcode from 'qrcode';
|
|
@@ -878,7 +878,8 @@ const defaultValaxyConfig = {
|
|
|
878
878
|
}
|
|
879
879
|
},
|
|
880
880
|
features: {
|
|
881
|
-
katex: true
|
|
881
|
+
katex: true,
|
|
882
|
+
extractFirstImage: true
|
|
882
883
|
},
|
|
883
884
|
math: false,
|
|
884
885
|
cdn: {
|
|
@@ -931,8 +932,9 @@ async function resolveValaxyConfigFromRoot(root, options) {
|
|
|
931
932
|
}
|
|
932
933
|
const mergeValaxyConfig = createDefu((obj, key, value) => {
|
|
933
934
|
if (isFunction(obj[key]) && isFunction(value)) {
|
|
935
|
+
const original = obj[key];
|
|
934
936
|
obj[key] = function(...args) {
|
|
935
|
-
|
|
937
|
+
original.call(this, ...args);
|
|
936
938
|
value.call(this, ...args);
|
|
937
939
|
};
|
|
938
940
|
return true;
|
|
@@ -1719,7 +1721,7 @@ async function setupMarkdownPlugins(md, options, base = "/") {
|
|
|
1719
1721
|
return md;
|
|
1720
1722
|
}
|
|
1721
1723
|
|
|
1722
|
-
const version = "0.28.
|
|
1724
|
+
const version = "0.28.1";
|
|
1723
1725
|
|
|
1724
1726
|
const GLOBAL_STATE = {
|
|
1725
1727
|
valaxyApp: void 0,
|
|
@@ -3107,12 +3109,68 @@ function clearHtmlTags(str) {
|
|
|
3107
3109
|
return str.replace(/<[^>]*>/g, "").replace(/\s+/g, " ");
|
|
3108
3110
|
}
|
|
3109
3111
|
|
|
3112
|
+
function count(content) {
|
|
3113
|
+
const cn = (content.match(/[\u4E00-\u9FA5]/g) || []).length;
|
|
3114
|
+
const en = (content.replace(/[\u4E00-\u9FA5]/g, "").match(/[\w\u0392-\u03C9\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u3040-\u309F\uAC00-\uD7AF\u0400-\u04FF]+|[\u00E4\u00C4\u00E5\u00C5\u00F6\u00D6]+/g) || []).length;
|
|
3115
|
+
return {
|
|
3116
|
+
cn,
|
|
3117
|
+
en
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
function readTime({ cn, en }, options) {
|
|
3121
|
+
const readingTime = cn / (options.speed.cn || 300) + en / (options.speed.en || 100);
|
|
3122
|
+
return readingTime < 1 ? 1 : Math.ceil(readingTime);
|
|
3123
|
+
}
|
|
3124
|
+
function wordCount({ cn, en }) {
|
|
3125
|
+
const num = cn + en;
|
|
3126
|
+
if (num < 1e3)
|
|
3127
|
+
return num.toString();
|
|
3128
|
+
return `${Math.round(num / 100) / 10}k`;
|
|
3129
|
+
}
|
|
3130
|
+
function statistics(content, options) {
|
|
3131
|
+
const countData = count(content);
|
|
3132
|
+
return {
|
|
3133
|
+
countData,
|
|
3134
|
+
wordCount: wordCount(countData),
|
|
3135
|
+
readingTime: readTime(countData, options.readTime)
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
function presetStatistics({
|
|
3139
|
+
route,
|
|
3140
|
+
options
|
|
3141
|
+
}) {
|
|
3142
|
+
const absolutePath = route.components.get("default") || "";
|
|
3143
|
+
if (existsSync(absolutePath)) {
|
|
3144
|
+
const file = readFileSync(absolutePath, "utf-8");
|
|
3145
|
+
const { wordCount: wordCount2, readingTime } = statistics(file, {
|
|
3146
|
+
readTime: {
|
|
3147
|
+
...options.readTime,
|
|
3148
|
+
speed: {
|
|
3149
|
+
cn: options.readTime?.speed?.cn || 300,
|
|
3150
|
+
en: options.readTime?.speed?.en || 100
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
const { frontmatter } = route.meta;
|
|
3155
|
+
if (frontmatter && typeof frontmatter === "object" && !Array.isArray(frontmatter)) {
|
|
3156
|
+
const fm = frontmatter;
|
|
3157
|
+
if (!fm.wordCount)
|
|
3158
|
+
fm.wordCount = wordCount2;
|
|
3159
|
+
if (!fm.readingTime)
|
|
3160
|
+
fm.readingTime = readingTime;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3110
3165
|
const buildHooks = [
|
|
3111
3166
|
"build:before",
|
|
3112
3167
|
"build:after"
|
|
3113
3168
|
];
|
|
3114
3169
|
function createValaxyNode(options) {
|
|
3115
3170
|
const hooks = createHooks();
|
|
3171
|
+
hooks.hook("statistics", ({ route, options: statsOptions }) => {
|
|
3172
|
+
presetStatistics({ route, options: statsOptions });
|
|
3173
|
+
});
|
|
3116
3174
|
if (typeof options.config.hooks === "object") {
|
|
3117
3175
|
Object.keys(options.config.hooks).forEach((name) => {
|
|
3118
3176
|
const hookName = name;
|
|
@@ -3394,10 +3452,24 @@ function inferDescription(frontmatter) {
|
|
|
3394
3452
|
return description;
|
|
3395
3453
|
return head && getHeadMetaContent(head, "description") || "";
|
|
3396
3454
|
}
|
|
3455
|
+
function extractFirstImage(code) {
|
|
3456
|
+
const mdImageMatch = code.match(/!\[.*?\]\((.+?)\)/);
|
|
3457
|
+
if (mdImageMatch)
|
|
3458
|
+
return mdImageMatch[1];
|
|
3459
|
+
const htmlImageMatch = code.match(/<img\s[^>]*?src=["'](.+?)["']/);
|
|
3460
|
+
if (htmlImageMatch)
|
|
3461
|
+
return htmlImageMatch[1];
|
|
3462
|
+
return void 0;
|
|
3463
|
+
}
|
|
3397
3464
|
async function generatePageData(code, id, options) {
|
|
3398
3465
|
const fileInfo = Valaxy.state.idMap.get(id);
|
|
3399
3466
|
const relativePath = path.relative(options.userRoot, id);
|
|
3400
3467
|
const fm = JSON.parse(JSON.stringify(fileInfo?.frontmatter));
|
|
3468
|
+
if (options.config.features?.extractFirstImage !== false && !fm.ogImage && !fm.cover) {
|
|
3469
|
+
const firstImage = extractFirstImage(code);
|
|
3470
|
+
if (firstImage)
|
|
3471
|
+
fm.firstImage = firstImage;
|
|
3472
|
+
}
|
|
3401
3473
|
const pageData = {
|
|
3402
3474
|
title: fm.title || fileInfo?.title || "",
|
|
3403
3475
|
titleTemplate: fm.titleTemplate,
|
|
@@ -3408,7 +3480,8 @@ async function generatePageData(code, id, options) {
|
|
|
3408
3480
|
relativePath,
|
|
3409
3481
|
filePath: id
|
|
3410
3482
|
};
|
|
3411
|
-
|
|
3483
|
+
if (options.config.siteConfig.lastUpdated)
|
|
3484
|
+
pageData.lastUpdated = await getGitTimestamp(id);
|
|
3412
3485
|
return pageData;
|
|
3413
3486
|
}
|
|
3414
3487
|
|
|
@@ -4285,59 +4358,6 @@ async function createValaxyPlugin(options, serverOptions = {}) {
|
|
|
4285
4358
|
];
|
|
4286
4359
|
}
|
|
4287
4360
|
|
|
4288
|
-
function count(content) {
|
|
4289
|
-
const cn = (content.match(/[\u4E00-\u9FA5]/g) || []).length;
|
|
4290
|
-
const en = (content.replace(/[\u4E00-\u9FA5]/g, "").match(/[\w\u0392-\u03C9\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u3040-\u309F\uAC00-\uD7AF\u0400-\u04FF]+|[\u00E4\u00C4\u00E5\u00C5\u00F6\u00D6]+/g) || []).length;
|
|
4291
|
-
return {
|
|
4292
|
-
cn,
|
|
4293
|
-
en
|
|
4294
|
-
};
|
|
4295
|
-
}
|
|
4296
|
-
function readTime({ cn, en }, options) {
|
|
4297
|
-
const readingTime = cn / (options.speed.cn || 300) + en / (options.speed.en || 100);
|
|
4298
|
-
return readingTime < 1 ? 1 : Math.ceil(readingTime);
|
|
4299
|
-
}
|
|
4300
|
-
function wordCount({ cn, en }) {
|
|
4301
|
-
const num = cn + en;
|
|
4302
|
-
if (num < 1e3)
|
|
4303
|
-
return num.toString();
|
|
4304
|
-
return `${Math.round(num / 100) / 10}k`;
|
|
4305
|
-
}
|
|
4306
|
-
function statistics(content, options) {
|
|
4307
|
-
const countData = count(content);
|
|
4308
|
-
return {
|
|
4309
|
-
countData,
|
|
4310
|
-
wordCount: wordCount(countData),
|
|
4311
|
-
readingTime: readTime(countData, options.readTime)
|
|
4312
|
-
};
|
|
4313
|
-
}
|
|
4314
|
-
function presetStatistics({
|
|
4315
|
-
route,
|
|
4316
|
-
options
|
|
4317
|
-
}) {
|
|
4318
|
-
const absolutePath = route.components.get("default") || "";
|
|
4319
|
-
if (existsSync(absolutePath)) {
|
|
4320
|
-
const file = readFileSync(absolutePath, "utf-8");
|
|
4321
|
-
const { wordCount: wordCount2, readingTime } = statistics(file, {
|
|
4322
|
-
readTime: {
|
|
4323
|
-
...options.readTime,
|
|
4324
|
-
speed: {
|
|
4325
|
-
cn: options.readTime?.speed?.cn || 300,
|
|
4326
|
-
en: options.readTime?.speed?.en || 100
|
|
4327
|
-
}
|
|
4328
|
-
}
|
|
4329
|
-
});
|
|
4330
|
-
const { frontmatter } = route.meta;
|
|
4331
|
-
if (frontmatter && typeof frontmatter === "object" && !Array.isArray(frontmatter)) {
|
|
4332
|
-
const fm = frontmatter;
|
|
4333
|
-
if (!fm.wordCount)
|
|
4334
|
-
fm.wordCount = wordCount2;
|
|
4335
|
-
if (!fm.readingTime)
|
|
4336
|
-
fm.readingTime = readingTime;
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
4361
|
async function getExcerptByType(excerpt = "", type = "html", mdIt) {
|
|
4342
4362
|
switch (type) {
|
|
4343
4363
|
case "ai":
|
|
@@ -4416,7 +4436,7 @@ async function createRouterPlugin(valaxyApp) {
|
|
|
4416
4436
|
const md = await fs.readFile(path, "utf-8");
|
|
4417
4437
|
const { data, excerpt, content } = matter(md, matterOptions);
|
|
4418
4438
|
const mdFm = data;
|
|
4419
|
-
const lastUpdated =
|
|
4439
|
+
const lastUpdated = valaxyConfig.siteConfig.lastUpdated;
|
|
4420
4440
|
delete mdFm.password;
|
|
4421
4441
|
if (mdFm.gallery_password) {
|
|
4422
4442
|
delete mdFm.gallery_password;
|
|
@@ -4454,8 +4474,7 @@ async function createRouterPlugin(valaxyApp) {
|
|
|
4454
4474
|
];
|
|
4455
4475
|
const routerFM = {
|
|
4456
4476
|
...mdFm,
|
|
4457
|
-
//
|
|
4458
|
-
// @TODO 添加文档和配置项,或者反过来允许用户自行优化
|
|
4477
|
+
// Normalize tags: ensure always an array for consistent consumption
|
|
4459
4478
|
tags: typeof mdFm.tags === "string" ? [mdFm.tags] : mdFm.tags,
|
|
4460
4479
|
// set default updated to date if not present
|
|
4461
4480
|
updated: mdFm.updated ?? mdFm.date
|
|
@@ -4480,7 +4499,7 @@ async function createRouterPlugin(valaxyApp) {
|
|
|
4480
4499
|
});
|
|
4481
4500
|
}
|
|
4482
4501
|
if (valaxyConfig.siteConfig.statistics.enable) {
|
|
4483
|
-
|
|
4502
|
+
await valaxyApp.hooks.callHook("statistics", {
|
|
4484
4503
|
options: valaxyConfig.siteConfig.statistics,
|
|
4485
4504
|
route
|
|
4486
4505
|
});
|
|
@@ -5401,17 +5420,21 @@ async function getPosts(params, options) {
|
|
|
5401
5420
|
return { data, content, excerpt, path: i };
|
|
5402
5421
|
});
|
|
5403
5422
|
const rawPosts = await Promise.all(readFilePromises);
|
|
5423
|
+
const draftPosts = [];
|
|
5404
5424
|
const filteredPosts = rawPosts.filter((p) => {
|
|
5405
5425
|
const { data } = p;
|
|
5406
5426
|
if (data.password)
|
|
5407
5427
|
return false;
|
|
5408
5428
|
if (data.draft) {
|
|
5429
|
+
draftPosts.push(p.path);
|
|
5409
5430
|
return false;
|
|
5410
5431
|
}
|
|
5411
5432
|
if (data.hide)
|
|
5412
5433
|
return false;
|
|
5413
5434
|
return true;
|
|
5414
5435
|
});
|
|
5436
|
+
if (draftPosts.length)
|
|
5437
|
+
consola.log(`[rss] Skipped ${draftPosts.length} draft post(s): ${draftPosts.join(", ")}`);
|
|
5415
5438
|
const posts = [];
|
|
5416
5439
|
for (const rawPost of filteredPosts) {
|
|
5417
5440
|
const { data, path, content, excerpt } = rawPost;
|
|
@@ -6080,58 +6103,185 @@ function registerCleanCommand(cli) {
|
|
|
6080
6103
|
);
|
|
6081
6104
|
}
|
|
6082
6105
|
|
|
6083
|
-
function
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6106
|
+
function getPnpmVersion() {
|
|
6107
|
+
try {
|
|
6108
|
+
return execSync("pnpm --version", { encoding: "utf-8" }).trim();
|
|
6109
|
+
} catch {
|
|
6110
|
+
return "not found";
|
|
6111
|
+
}
|
|
6112
|
+
}
|
|
6113
|
+
async function collectDebugInfo() {
|
|
6114
|
+
const info = {
|
|
6115
|
+
os: os.platform(),
|
|
6116
|
+
arch: os.arch(),
|
|
6117
|
+
node: process.version,
|
|
6118
|
+
pnpm: getPnpmVersion(),
|
|
6119
|
+
valaxy: version
|
|
6120
|
+
};
|
|
6121
|
+
try {
|
|
6122
|
+
const options = await resolveOptions({ userRoot: process.cwd() });
|
|
6123
|
+
info.userRoot = options.userRoot;
|
|
6124
|
+
info.theme = options.theme;
|
|
6125
|
+
info.themeVersion = options.config.themeConfig?.pkg?.version;
|
|
6126
|
+
info.addons = options.addons.filter((a) => a.enable).map((a) => ({
|
|
6127
|
+
name: a.name,
|
|
6128
|
+
version: a.pkg?.version || "unknown",
|
|
6129
|
+
global: a.global
|
|
6130
|
+
}));
|
|
6131
|
+
info.pages = options.pages.length;
|
|
6132
|
+
} catch {
|
|
6133
|
+
}
|
|
6134
|
+
return info;
|
|
6135
|
+
}
|
|
6136
|
+
function printFancy(info) {
|
|
6137
|
+
const lines = [];
|
|
6138
|
+
lines.push(`${colors.bold(colors.cyan("Environment"))}`);
|
|
6139
|
+
lines.push(` OS: ${colors.green(`${info.os} ${info.arch}`)}`);
|
|
6140
|
+
lines.push(` Node.js: ${colors.green(info.node)}`);
|
|
6141
|
+
lines.push(` Package Manager: ${colors.green(`pnpm ${info.pnpm}`)}`);
|
|
6142
|
+
lines.push(` Valaxy: ${colors.cyan(`v${info.valaxy}`)}`);
|
|
6143
|
+
if (info.theme) {
|
|
6144
|
+
lines.push("");
|
|
6145
|
+
lines.push(`${colors.bold(colors.cyan("Project"))}`);
|
|
6146
|
+
lines.push(` Root: ${colors.dim(info.userRoot)}`);
|
|
6147
|
+
lines.push(` Theme: ${colors.green(info.theme)} ${colors.blue(`v${info.themeVersion || "unknown"}`)}`);
|
|
6148
|
+
if (info.addons && info.addons.length > 0) {
|
|
6149
|
+
lines.push(` Addons:`);
|
|
6150
|
+
info.addons.forEach((addon, i) => {
|
|
6151
|
+
const prefix = i === info.addons.length - 1 ? "\u2514\u2500" : "\u251C\u2500";
|
|
6152
|
+
const globalTag = addon.global ? colors.cyan(" (global)") : "";
|
|
6153
|
+
lines.push(` ${prefix} ${colors.yellow(addon.name)} ${colors.blue(`v${addon.version}`)}${globalTag}`);
|
|
6154
|
+
});
|
|
6155
|
+
} else {
|
|
6156
|
+
lines.push(` Addons: ${colors.dim("none")}`);
|
|
6157
|
+
}
|
|
6158
|
+
lines.push(` Pages: ${colors.green(String(info.pages))}`);
|
|
6159
|
+
}
|
|
6160
|
+
consola.box({
|
|
6161
|
+
title: "\u{1F30C} Valaxy Debug Info",
|
|
6162
|
+
message: lines.join("\n"),
|
|
6163
|
+
style: {
|
|
6164
|
+
borderColor: "cyan"
|
|
6165
|
+
}
|
|
6089
6166
|
});
|
|
6090
6167
|
}
|
|
6168
|
+
function printPlain(info) {
|
|
6169
|
+
const lines = [];
|
|
6170
|
+
lines.push("## Environment");
|
|
6171
|
+
lines.push(`- OS: ${info.os} ${info.arch}`);
|
|
6172
|
+
lines.push(`- Node: ${info.node}`);
|
|
6173
|
+
lines.push(`- Package Manager: pnpm ${info.pnpm}`);
|
|
6174
|
+
lines.push(`- Valaxy: v${info.valaxy}`);
|
|
6175
|
+
if (info.theme) {
|
|
6176
|
+
lines.push("");
|
|
6177
|
+
lines.push("## Project");
|
|
6178
|
+
lines.push(`- Root: ${info.userRoot}`);
|
|
6179
|
+
lines.push(`- Theme: ${info.theme} (v${info.themeVersion || "unknown"})`);
|
|
6180
|
+
if (info.addons && info.addons.length > 0) {
|
|
6181
|
+
const addonStr = info.addons.map((a) => `${a.name} (v${a.version})${a.global ? " [global]" : ""}`).join(", ");
|
|
6182
|
+
lines.push(`- Addons: ${addonStr}`);
|
|
6183
|
+
} else {
|
|
6184
|
+
lines.push("- Addons: none");
|
|
6185
|
+
}
|
|
6186
|
+
lines.push(`- Pages: ${info.pages}`);
|
|
6187
|
+
}
|
|
6188
|
+
console.log(lines.join("\n"));
|
|
6189
|
+
}
|
|
6190
|
+
function registerDebugCommand(cli) {
|
|
6191
|
+
cli.command(
|
|
6192
|
+
"debug",
|
|
6193
|
+
"Display debug information for your Valaxy project",
|
|
6194
|
+
(args) => args.option("plain", {
|
|
6195
|
+
type: "boolean",
|
|
6196
|
+
default: false,
|
|
6197
|
+
describe: "Output plain text without colors (for pasting into issues)"
|
|
6198
|
+
}),
|
|
6199
|
+
async (args) => {
|
|
6200
|
+
const info = await collectDebugInfo();
|
|
6201
|
+
if (args.plain)
|
|
6202
|
+
printPlain(info);
|
|
6203
|
+
else
|
|
6204
|
+
printFancy(info);
|
|
6205
|
+
}
|
|
6206
|
+
);
|
|
6207
|
+
}
|
|
6091
6208
|
|
|
6092
6209
|
function registerDeployCommand(cli) {
|
|
6093
|
-
cli.command(
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
}
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6210
|
+
cli.command(
|
|
6211
|
+
"deploy [root]",
|
|
6212
|
+
"deploy your blog to the cloud",
|
|
6213
|
+
(args) => commonOptions(args).option("type", {
|
|
6214
|
+
type: "string",
|
|
6215
|
+
choices: ["gh-pages", "remote"],
|
|
6216
|
+
describe: "deploy type, overrides `deploy.type` in config"
|
|
6217
|
+
}).option("output", {
|
|
6218
|
+
alias: "o",
|
|
6219
|
+
type: "string",
|
|
6220
|
+
default: "dist",
|
|
6221
|
+
describe: "output dir"
|
|
6222
|
+
}).strict().help(),
|
|
6223
|
+
async ({ root, type, output }) => {
|
|
6224
|
+
intro("Deploying Your Blog");
|
|
6225
|
+
const shouldBuild = await confirm({
|
|
6226
|
+
message: "Do you want to build your blog before deploying?"
|
|
6227
|
+
});
|
|
6228
|
+
if (isCancel(shouldBuild)) {
|
|
6229
|
+
cancel("Operation cancelled.");
|
|
6230
|
+
process.exit(0);
|
|
6231
|
+
}
|
|
6232
|
+
if (shouldBuild) {
|
|
6233
|
+
await execBuild({ ssg: true, ssgEngine: "valaxy", root, output, log: "info" });
|
|
6234
|
+
}
|
|
6235
|
+
const options = await resolveOptions({ userRoot: root }, "build");
|
|
6236
|
+
const configDeployType = type ?? options.config.deploy?.type;
|
|
6237
|
+
const deployType = configDeployType ?? await select({
|
|
6238
|
+
message: "Where do you want to deploy?",
|
|
6239
|
+
options: [
|
|
6240
|
+
{ label: "GitHub Pages", value: "gh-pages", hint: "You need install `gh-pages` dependencies." },
|
|
6241
|
+
{ label: "Your Own Server", value: "remote" }
|
|
6242
|
+
]
|
|
6243
|
+
});
|
|
6244
|
+
if (isCancel(deployType)) {
|
|
6245
|
+
cancel("Operation cancelled.");
|
|
6246
|
+
process.exit(0);
|
|
6247
|
+
}
|
|
6248
|
+
if (deployType === "gh-pages") {
|
|
6249
|
+
let isGhPagesInstalled = false;
|
|
6250
|
+
try {
|
|
6251
|
+
await import('gh-pages');
|
|
6120
6252
|
isGhPagesInstalled = true;
|
|
6121
|
-
}
|
|
6122
|
-
|
|
6253
|
+
} catch {
|
|
6254
|
+
const installGhPages = await confirm({
|
|
6255
|
+
message: "Do you want to install `gh-pages` now?"
|
|
6256
|
+
});
|
|
6257
|
+
if (isCancel(installGhPages)) {
|
|
6258
|
+
cancel("Operation cancelled.");
|
|
6259
|
+
process.exit(0);
|
|
6260
|
+
}
|
|
6261
|
+
if (installGhPages) {
|
|
6262
|
+
try {
|
|
6263
|
+
await import('@antfu/install-pkg').then((i) => i.installPackage("gh-pages", { dev: true }));
|
|
6264
|
+
isGhPagesInstalled = true;
|
|
6265
|
+
} catch (e) {
|
|
6266
|
+
consola.error("Failed to install `gh-pages`:", e);
|
|
6267
|
+
}
|
|
6268
|
+
} else {
|
|
6269
|
+
outro("Please install `gh-pages` before deploying to GitHub Pages.");
|
|
6270
|
+
}
|
|
6123
6271
|
}
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6272
|
+
if (isGhPagesInstalled) {
|
|
6273
|
+
const { publish } = await import('gh-pages');
|
|
6274
|
+
await publish(output, {
|
|
6275
|
+
branch: "gh-pages",
|
|
6276
|
+
message: "chore: deploy by valaxy"
|
|
6277
|
+
});
|
|
6278
|
+
outro("Done!");
|
|
6279
|
+
}
|
|
6280
|
+
} else if (deployType === "remote") {
|
|
6281
|
+
outro("Remote deployment is not yet implemented.");
|
|
6132
6282
|
}
|
|
6133
6283
|
}
|
|
6134
|
-
|
|
6284
|
+
);
|
|
6135
6285
|
}
|
|
6136
6286
|
|
|
6137
6287
|
async function findFreePort(start) {
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { c as Post } from '../shared/valaxy.
|
|
2
|
-
export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.
|
|
1
|
+
import { c as Post } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
2
|
+
export { A as Album, B as BaseFrontMatter, D as DefaultTheme, E as ExcerptType, F as FuseListItem, d as Page, e as PageFrontMatter, P as PartialDeep, f as Photo, g as Pkg, h as PostFrontMatter, a as RedirectItem, i as RedirectRule, R as RuntimeConfig, S as SiteConfig, j as SocialLink, U as UserSiteConfig, k as UserValaxyConfig, b as ValaxyAddon, V as ValaxyConfig } from '../shared/valaxy.6MW2qn5T.mjs';
|
|
3
3
|
import { Header } from '@valaxyjs/utils';
|
|
4
4
|
import '@vueuse/integrations/useFuse';
|
|
5
5
|
import 'medium-zoom';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "valaxy",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.28.
|
|
4
|
+
"version": "0.28.1",
|
|
5
5
|
"description": "📄 Vite & Vue powered static blog generator.",
|
|
6
6
|
"author": {
|
|
7
7
|
"email": "me@yunyoujun.cn",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"types"
|
|
58
58
|
],
|
|
59
59
|
"engines": {
|
|
60
|
-
"node": "^
|
|
60
|
+
"node": "^18.0.0 || >=20.0.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@antfu/install-pkg": "^1.1.0",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"jiti": "^2.6.1",
|
|
96
96
|
"js-base64": "^3.7.8",
|
|
97
97
|
"js-yaml": "^4.1.1",
|
|
98
|
-
"katex": "^0.16.
|
|
98
|
+
"katex": "^0.16.42",
|
|
99
99
|
"lru-cache": "^11.2.7",
|
|
100
100
|
"markdown-it": "^14.1.1",
|
|
101
101
|
"markdown-it-anchor": "^9.2.0",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"markdown-it-emoji": "^3.0.0",
|
|
106
106
|
"markdown-it-footnote": "^4.0.0",
|
|
107
107
|
"markdown-it-image-figures": "^2.1.1",
|
|
108
|
-
"markdown-it-table-of-contents": "^1.
|
|
108
|
+
"markdown-it-table-of-contents": "^1.2.0",
|
|
109
109
|
"markdown-it-task-lists": "^2.1.1",
|
|
110
110
|
"medium-zoom": "^1.1.0",
|
|
111
111
|
"mermaid": "^11.13.0",
|
|
@@ -129,9 +129,8 @@
|
|
|
129
129
|
"unplugin-vue-components": "28.0.0",
|
|
130
130
|
"unplugin-vue-markdown": "^30.0.0",
|
|
131
131
|
"vanilla-lazyload": "^19.1.3",
|
|
132
|
-
"vite": "^8.0.
|
|
133
|
-
"vite-
|
|
134
|
-
"vite-plugin-vue-devtools": "^8.1.0",
|
|
132
|
+
"vite": "^8.0.2",
|
|
133
|
+
"vite-plugin-vue-devtools": "^8.1.1",
|
|
135
134
|
"vite-plugin-vue-layouts-next": "^2.1.0",
|
|
136
135
|
"vite-ssg": "^28.3.0",
|
|
137
136
|
"vite-ssg-sitemap": "^0.10.0",
|
|
@@ -140,8 +139,8 @@
|
|
|
140
139
|
"vue-i18n": "^11.3.0",
|
|
141
140
|
"vue-router": "^5.0.4",
|
|
142
141
|
"yargs": "^18.0.0",
|
|
143
|
-
"@valaxyjs/
|
|
144
|
-
"@valaxyjs/
|
|
142
|
+
"@valaxyjs/devtools": "0.28.1",
|
|
143
|
+
"@valaxyjs/utils": "0.28.1"
|
|
145
144
|
},
|
|
146
145
|
"devDependencies": {
|
|
147
146
|
"@mdit-vue/plugin-component": "^3.0.2",
|
package/types/config.ts
CHANGED
|
@@ -172,10 +172,6 @@ export interface SiteConfig {
|
|
|
172
172
|
* @zh 是否启用
|
|
173
173
|
*/
|
|
174
174
|
enable: boolean
|
|
175
|
-
/**
|
|
176
|
-
* @deprecated will be deprecated, use search.provider instead
|
|
177
|
-
*/
|
|
178
|
-
type?: SiteConfig['search']['provider']
|
|
179
175
|
/**
|
|
180
176
|
* Search Type
|
|
181
177
|
* - algolia: Algolia Search
|
|
@@ -109,6 +109,18 @@ export interface PageFrontMatter extends BaseFrontMatter {
|
|
|
109
109
|
* @description 封面图片
|
|
110
110
|
*/
|
|
111
111
|
cover: string
|
|
112
|
+
/**
|
|
113
|
+
* @description:en-US Open Graph image for SEO
|
|
114
|
+
* @description:zh-CN Open Graph 图片,用于 SEO
|
|
115
|
+
*/
|
|
116
|
+
ogImage: string
|
|
117
|
+
/**
|
|
118
|
+
* @protected
|
|
119
|
+
* @tutorial ⚠️ DO NOT SET MANUALLY (auto-extracted from markdown content)
|
|
120
|
+
* @description:en-US First image URL extracted from markdown content
|
|
121
|
+
* @description:zh-CN 从 Markdown 内容中自动提取的第一张图片 URL
|
|
122
|
+
*/
|
|
123
|
+
firstImage: string
|
|
112
124
|
/**
|
|
113
125
|
* display toc
|
|
114
126
|
* @description 是否显示目录
|