clarity-theme 0.1.3 → 0.1.4
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/README.md +12 -2
- package/README.zh-CN.md +12 -2
- package/nuxt.config.ts +17 -1
- package/package.json +6 -2
- package/src/app.config.ts +8 -104
- package/src/assets/icons/zhilu.svg +8 -0
- package/src/blog.config.ts +71 -0
- package/src/components/blog/BlogFooter.vue +3 -3
- package/src/components/blog/BlogHeader.global.vue +9 -9
- package/src/components/blog/BlogSidebar.vue +3 -3
- package/src/components/blog/ThemeToggle.vue +2 -2
- package/src/components/content/Alert.vue +2 -2
- package/src/components/content/FeedCard.vue +2 -2
- package/src/components/content/Mermaid.vue +1 -1
- package/src/components/content/ProseCode.vue +4 -32
- package/src/components/content/ProsePre.vue +2 -2
- package/src/components/content/ProseTable.vue +1 -1
- package/src/components/popover/Search.vue +3 -3
- package/src/components/post/Comment.vue +3 -15
- package/src/components/post/Excerpt.vue +3 -3
- package/src/components/post/OrderToggle.vue +3 -3
- package/src/components/post/PostFooter.vue +3 -4
- package/src/components/post/PostHeader.vue +4 -4
- package/src/components/post/PostSurround.vue +2 -2
- package/src/components/post/Slide.vue +2 -2
- package/src/components/util/Img.vue +1 -1
- package/src/components/widget/BlogLog.vue +9 -19
- package/src/components/widget/BlogStats.vue +4 -4
- package/src/components/widget/BlogTech.vue +16 -17
- package/src/components/widget/CommGroup.vue +43 -0
- package/src/composables/useArticle.ts +9 -9
- package/src/composables/usePagination.ts +2 -2
- package/src/composables/useWidgets.ts +2 -0
- package/src/config/app.ts +50 -4
- package/src/config/content.ts +6 -0
- package/src/config/feed.ts +2 -42
- package/src/config/pnpm-workspace.ts +19 -0
- package/src/config/ui.ts +111 -0
- package/src/generated/blog.config.mjs +3 -0
- package/src/generated/package-json.mjs +4 -0
- package/src/generated/pnpm-workspace.mjs +1 -0
- package/src/modules/anti-mirror/index.ts +33 -0
- package/src/modules/{clarity-config/anti-mirror-client.ts → anti-mirror/runtime/client.ts} +3 -3
- package/src/modules/clarity-config/index.ts +335 -77
- package/src/modules/clarity-source-layout/index.ts +13 -0
- package/src/pages/archive.vue +4 -4
- package/src/pages/index.vue +4 -3
- package/src/pages/link.vue +5 -5
- package/src/pages/preview.vue +2 -2
- package/src/server/api/stats.get.ts +9 -19
- package/src/server/routes/atom.xml.get.ts +48 -50
- package/src/server/routes/subscriptions.opml.get.ts +14 -31
- package/src/shared/utils/time.ts +13 -18
- package/src/types/article.ts +2 -2
- package/src/types/feed.ts +35 -1
- package/src/types/upstream-compat.d.ts +17 -0
- package/src/types/yaml-modules.d.ts +26 -0
- package/src/shared/types/content.ts +0 -30
- package/src/types/modal.ts +0 -16
- /package/src/assets/icons/{solar-rewind-back-bold-duotone.svg → SolarRewindBackBoldDuotone.svg} +0 -0
- /package/src/assets/icons/{solar-rewind-forward-bold-duotone.svg → SolarRewindForwardBoldDuotone.svg} +0 -0
|
@@ -5,15 +5,15 @@ const props = defineProps<{
|
|
|
5
5
|
excerpt: string
|
|
6
6
|
}>()
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const appConfig = useAppConfig()
|
|
9
9
|
|
|
10
10
|
const excerpt = ref(props.excerpt)
|
|
11
11
|
const caret = ref('')
|
|
12
12
|
|
|
13
|
-
if (
|
|
13
|
+
if (appConfig.component.excerpt?.animation !== false) {
|
|
14
14
|
excerpt.value = ''
|
|
15
15
|
onMounted(async () => {
|
|
16
|
-
caret.value =
|
|
16
|
+
caret.value = appConfig.component.excerpt?.caret ?? '_'
|
|
17
17
|
for (const char of props.excerpt) {
|
|
18
18
|
excerpt.value += char
|
|
19
19
|
await delay(50)
|
|
@@ -9,10 +9,10 @@ const props = defineProps<{
|
|
|
9
9
|
secretDelay?: string
|
|
10
10
|
}>()
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const orderMap = computed(() =>
|
|
12
|
+
const appConfig = useAppConfig()
|
|
13
|
+
const orderMap = computed(() => appConfig.article.order)
|
|
14
14
|
// 配置文件中允许升序时,且未明确禁用升序时,允许升序
|
|
15
|
-
const allowAscending = computed(() =>
|
|
15
|
+
const allowAscending = computed(() => appConfig.pagination.allowAscending ? !props.disableAscending : props.enableAscending)
|
|
16
16
|
|
|
17
17
|
const category = defineModel<string>('category')
|
|
18
18
|
const sortOrder = defineModel<ArticleOrderType>('sortOrder', { default: 'date' })
|
|
@@ -8,7 +8,7 @@ const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{
|
|
|
8
8
|
title: string
|
|
9
9
|
}>({ inheritAttrs: false })
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const appConfig = useAppConfig()
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
14
|
<template>
|
|
@@ -38,10 +38,9 @@ const clarity = useClarityConfig()
|
|
|
38
38
|
<ReuseTemplate :title="meta?.slots?.copyright?.props?.title as string || '许可协议'">
|
|
39
39
|
<ContentRenderer v-if="meta?.slots?.copyright" :value="meta?.slots?.copyright" />
|
|
40
40
|
<p v-else>
|
|
41
|
-
本文采用 <ProseA
|
|
42
|
-
{{
|
|
41
|
+
本文采用 <ProseA :href="appConfig.copyright.url">
|
|
42
|
+
{{ appConfig.copyright.name }}
|
|
43
43
|
</ProseA>
|
|
44
|
-
<span v-else-if="clarity.site.copyright?.name">{{ clarity.site.copyright.name }}</span>
|
|
45
44
|
许可协议,转载请注明出处。
|
|
46
45
|
</p>
|
|
47
46
|
</ReuseTemplate>
|
|
@@ -4,13 +4,13 @@ import type { ArticleProps } from '../../types/article'
|
|
|
4
4
|
defineOptions({ inheritAttrs: false })
|
|
5
5
|
const props = defineProps<ArticleProps>()
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const appConfig = useAppConfig()
|
|
8
8
|
|
|
9
9
|
const coverFilter = computed(() => props.meta?.coverFilter || (props.meta?.coverDim && 'brightness(0.75)') || undefined)
|
|
10
10
|
|
|
11
|
-
const shareText = `【${
|
|
11
|
+
const shareText = `【${appConfig.title}】${props.title}\n\n${
|
|
12
12
|
props.description ? `${props.description}\n\n` : ''}${
|
|
13
|
-
new URL(props.path!,
|
|
13
|
+
new URL(props.path!, appConfig.url).href}`
|
|
14
14
|
|
|
15
15
|
const { copy, copied } = useCopy(shareText)
|
|
16
16
|
</script>
|
|
@@ -38,7 +38,7 @@ const { copy, copied } = useCopy(shareText)
|
|
|
38
38
|
/>
|
|
39
39
|
|
|
40
40
|
<UtilDate
|
|
41
|
-
v-if="updated && isTimeDiffSignificant(date, updated,
|
|
41
|
+
v-if="updated && isTimeDiffSignificant(date, updated, 1)"
|
|
42
42
|
v-tip
|
|
43
43
|
:tip-transform="d => `修改于${d}`"
|
|
44
44
|
:date="updated"
|
|
@@ -36,11 +36,11 @@ const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{
|
|
|
36
36
|
|
|
37
37
|
<div v-if="prev || next" class="surround-post" dir="ltr">
|
|
38
38
|
<ReuseTemplate
|
|
39
|
-
:post="next" icon="
|
|
39
|
+
:post="next" icon="zi:solar-rewind-back-bold-duotone"
|
|
40
40
|
fallback-icon="line-md:coffee-twotone-loop" fallback-text="新故事即将发生"
|
|
41
41
|
/>
|
|
42
42
|
<ReuseTemplate
|
|
43
|
-
:post="prev" icon="
|
|
43
|
+
:post="prev" icon="zi:solar-rewind-forward-bold-duotone"
|
|
44
44
|
fallback-icon="line-md:construction-twotone" fallback-text="已抵达博客尽头"
|
|
45
45
|
align-end
|
|
46
46
|
/>
|
|
@@ -6,8 +6,8 @@ import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures'
|
|
|
6
6
|
|
|
7
7
|
defineProps<{ list: ArticleProps[] }>()
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
const compConf = computed(() =>
|
|
9
|
+
const appConfig = useAppConfig()
|
|
10
|
+
const compConf = computed(() => appConfig.component.slide)
|
|
11
11
|
|
|
12
12
|
// @keep-sorted
|
|
13
13
|
const [carouselEl, carouselApi] = emblaCarouselVue({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { joinURL, withLeadingSlash, withTrailingSlash } from 'ufo'
|
|
3
2
|
import ImageComponent from '#build/mdc-image-component.mjs'
|
|
3
|
+
import { joinURL, withLeadingSlash, withTrailingSlash } from 'ufo'
|
|
4
4
|
|
|
5
5
|
export interface UtilImgProps {
|
|
6
6
|
src: string
|
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
const
|
|
2
|
+
const { timeEstablished } = useAppConfig()
|
|
3
3
|
|
|
4
|
-
const blogLog =
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
const blogLog = [
|
|
5
|
+
{ label: '2025-07-26', value: '重构到 Nuxt 4 + Nuxt Content v3,启用 zhilu.site 域名' },
|
|
6
|
+
{ label: '2024-08-11', value: '重构到 Nuxt 3 + Nuxt Content v2' },
|
|
7
|
+
{ label: '2023-05-24', value: '更换框架为 Hexo,升级博客' },
|
|
8
|
+
{ label: '2020-08-24', value: '使用 zhilu.cyou 域名' },
|
|
9
|
+
{ label: timeEstablished, value: '发布第一篇文章' },
|
|
10
|
+
]
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
13
|
<template>
|
|
14
14
|
<BlogWidget card title="更新日志">
|
|
15
|
-
<ZDlGroup
|
|
16
|
-
<p v-else class="no-log">
|
|
17
|
-
暂无更新日志,可在 clarity.config.ts 的 changelog 中配置。
|
|
18
|
-
</p>
|
|
15
|
+
<ZDlGroup size="large" :items="blogLog" />
|
|
19
16
|
</BlogWidget>
|
|
20
17
|
</template>
|
|
21
|
-
|
|
22
|
-
<style lang="scss" scoped>
|
|
23
|
-
.no-log {
|
|
24
|
-
padding: 1em;
|
|
25
|
-
color: var(--c-text-3);
|
|
26
|
-
}
|
|
27
|
-
</style>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { UtilDate } from '#components'
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const appConfig = useAppConfig()
|
|
5
5
|
const runtimeConfig = useRuntimeConfig()
|
|
6
6
|
|
|
7
7
|
// 响应头不正确时,stats.value 可能会是字符串,首次属性访问可能为 undefined
|
|
@@ -16,8 +16,8 @@ const yearlyTip = computed(() => Object
|
|
|
16
16
|
|
|
17
17
|
const blogStats = [{
|
|
18
18
|
label: '运营时长',
|
|
19
|
-
value:
|
|
20
|
-
tip:
|
|
19
|
+
value: timeElapse(appConfig.timeEstablished),
|
|
20
|
+
tip: `博客于${appConfig.timeEstablished}上线`,
|
|
21
21
|
}, {
|
|
22
22
|
label: '上次更新',
|
|
23
23
|
value: () => h(UtilDate, {
|
|
@@ -26,7 +26,7 @@ const blogStats = [{
|
|
|
26
26
|
tipPrefix: '构建于',
|
|
27
27
|
}),
|
|
28
28
|
}, {
|
|
29
|
-
label:
|
|
29
|
+
label: appConfig.stats.includePaths.length ? '文章字数' : '总字数',
|
|
30
30
|
value: computed(() => formatNumber(stats.value?.total?.words) || '--'),
|
|
31
31
|
tip: yearlyTip,
|
|
32
32
|
}]
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { Icon } from '#components'
|
|
3
|
+
import { merge } from 'es-toolkit/object'
|
|
4
|
+
import { packageManager, version } from '~~/package.json'
|
|
5
|
+
import pnpmWorkspace from '~~/pnpm-workspace.yaml'
|
|
3
6
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const buildInfo = runtimeConfig.public.clarity as {
|
|
7
|
-
theme: string
|
|
8
|
-
themeVersion: string
|
|
9
|
-
siteVersion: string
|
|
10
|
-
sitePackageManager: string
|
|
11
|
-
nuxtVersion: string
|
|
12
|
-
vueVersion: string
|
|
13
|
-
}
|
|
14
|
-
const { public: { arch, ci, nodeVersion, platform } } = runtimeConfig
|
|
7
|
+
const appConfig = useAppConfig()
|
|
8
|
+
const { public: { arch, ci, nodeVersion, platform } } = useRuntimeConfig()
|
|
15
9
|
|
|
16
10
|
const ciPlatform = computed(() => {
|
|
17
11
|
const iconName = ciIcons[ci]
|
|
@@ -25,20 +19,25 @@ const ciPlatform = computed(() => {
|
|
|
25
19
|
return h('span', {}, [iconNode, ` ${ci.split(' ')[0]}`])
|
|
26
20
|
})
|
|
27
21
|
|
|
22
|
+
// @ts-expect-error pnpm-workspace.yaml 无类型定义
|
|
23
|
+
const packages = merge(...Object.values(pnpmWorkspace.catalogs))
|
|
24
|
+
const [pm, pmVersion] = packageManager.split('@') as [string, string]
|
|
25
|
+
|
|
28
26
|
const service = computed(() => ([
|
|
29
27
|
...ci ? [{ label: '构建平台', value: ciPlatform }] : [],
|
|
30
28
|
{ label: '图片存储', value: () => [h(Icon, { name: 'devicon:cloudflare' }), ' R2'] },
|
|
31
29
|
{ label: '软件协议', value: 'MIT' },
|
|
32
|
-
{ label: '文章许可', value:
|
|
33
|
-
{ label: '规范域名', value: getDomain(
|
|
30
|
+
{ label: '文章许可', value: appConfig.copyright.abbr },
|
|
31
|
+
{ label: '规范域名', value: getDomain(appConfig.url) },
|
|
34
32
|
]))
|
|
35
33
|
|
|
36
34
|
const techstack = computed(() => ([
|
|
37
|
-
{ label: 'Blog', value:
|
|
38
|
-
{ label: '
|
|
39
|
-
{ label: '
|
|
40
|
-
{ label: '
|
|
35
|
+
{ label: 'Blog', value: version },
|
|
36
|
+
{ label: 'Vue', value: packages.vue },
|
|
37
|
+
{ label: 'Nuxt', value: packages.nuxt },
|
|
38
|
+
{ label: 'Content', value: packages['@nuxt/content'] },
|
|
41
39
|
{ label: 'Node', value: nodeVersion },
|
|
40
|
+
{ label: pm, value: pmVersion },
|
|
42
41
|
{ label: 'OS', value: platform },
|
|
43
42
|
{ label: 'Arch', value: arch },
|
|
44
43
|
]))
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<BlogWidget
|
|
3
|
+
card
|
|
4
|
+
dim
|
|
5
|
+
title="博客/技术社区"
|
|
6
|
+
bg-img="https://p.qlogo.cn/gh/169994096/169994096/100/"
|
|
7
|
+
bg-aside
|
|
8
|
+
>
|
|
9
|
+
<div class="title text-creative">
|
|
10
|
+
纸网接入点
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<Tip copy icon :tip-options="{ placement: 'left' }">
|
|
14
|
+
<Icon name="ri:qq-fill" />
|
|
15
|
+
<span>169994096</span>
|
|
16
|
+
</Tip>
|
|
17
|
+
</BlogWidget>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<style lang="scss" scoped>
|
|
21
|
+
.title {
|
|
22
|
+
background-clip: text;
|
|
23
|
+
background-image: linear-gradient(60deg, var(--c-accent) -30%, var(--c-primary), var(--c-text-1));
|
|
24
|
+
background-position: 100% 0;
|
|
25
|
+
background-size: 200%;
|
|
26
|
+
font-size: 1.8em;
|
|
27
|
+
color: transparent;
|
|
28
|
+
transition: background-position 0.2s;
|
|
29
|
+
|
|
30
|
+
.blog-widget:hover & {
|
|
31
|
+
background-position: 0 0;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tip {
|
|
36
|
+
font-size: 0.9em;
|
|
37
|
+
line-height: 2;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:deep(.bg-img).bg-img.bg-img {
|
|
41
|
+
opacity: 0.5;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { ContentCollectionItem } from '@nuxt/content'
|
|
1
2
|
import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
|
|
2
|
-
import type { ClarityContentRow } from '../shared/types/content'
|
|
3
3
|
import type { ArticleOrderType, ArticleProps } from '../types/article'
|
|
4
4
|
import { orderBy } from 'es-toolkit/array'
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ import { orderBy } from 'es-toolkit/array'
|
|
|
7
7
|
export function useArticle(path?: MaybeRefOrGetter<string | undefined>) {
|
|
8
8
|
const route = useRoute()
|
|
9
9
|
const dataKey = computed(() => `content:${toValue(path) ?? route.path}`)
|
|
10
|
-
const post = computed(() => useNuxtData<
|
|
10
|
+
const post = computed(() => useNuxtData<ContentCollectionItem | null | undefined>(dataKey.value).data.value)
|
|
11
11
|
|
|
12
12
|
return {
|
|
13
13
|
dataKey,
|
|
@@ -64,12 +64,12 @@ interface UseArticleSortOptions {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export function useArticleSort(list: MaybeRefOrGetter<ArticleProps[]>, options?: UseArticleSortOptions) {
|
|
67
|
-
const
|
|
67
|
+
const appConfig = useAppConfig()
|
|
68
68
|
const {
|
|
69
69
|
bindDirectionQuery,
|
|
70
70
|
bindOrderQuery,
|
|
71
71
|
initialAscend = false,
|
|
72
|
-
initialOrder =
|
|
72
|
+
initialOrder = appConfig.pagination.sortOrder || 'date',
|
|
73
73
|
} = options || {}
|
|
74
74
|
|
|
75
75
|
const sortOrder = bindOrderQuery
|
|
@@ -87,7 +87,7 @@ export function useArticleSort(list: MaybeRefOrGetter<ArticleProps[]>, options?:
|
|
|
87
87
|
|
|
88
88
|
const listSorted = computed(() => orderBy(
|
|
89
89
|
toValue(list),
|
|
90
|
-
[sortOrder.value, 'date']
|
|
90
|
+
[sortOrder.value, 'date'],
|
|
91
91
|
[isAscending.value ? 'asc' : 'desc'],
|
|
92
92
|
))
|
|
93
93
|
|
|
@@ -99,13 +99,13 @@ export function useArticleSort(list: MaybeRefOrGetter<ArticleProps[]>, options?:
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
export function getCategoryIcon(category?: string) {
|
|
102
|
-
const
|
|
103
|
-
return
|
|
102
|
+
const appConfig = useAppConfig()
|
|
103
|
+
return appConfig.article.categories[category!]?.icon ?? 'tabler:folder'
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
export function getCategoryColor(category?: string) {
|
|
107
|
-
const
|
|
108
|
-
return
|
|
107
|
+
const appConfig = useAppConfig()
|
|
108
|
+
return appConfig.article.categories[category!]?.color
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
interface GetPostTypeClassNameOptions {
|
|
@@ -5,10 +5,10 @@ interface UsePaginationOptions {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export default function usePagination<T>(list: MaybeRefOrGetter<T[]>, options?: UsePaginationOptions) {
|
|
8
|
-
const
|
|
8
|
+
const appConfig = useAppConfig()
|
|
9
9
|
const {
|
|
10
10
|
initialPage = 1,
|
|
11
|
-
perPage =
|
|
11
|
+
perPage = appConfig.pagination.perPage || 10,
|
|
12
12
|
bindQuery,
|
|
13
13
|
} = options || {}
|
|
14
14
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
LazyWidgetBlogLog,
|
|
6
6
|
LazyWidgetBlogStats,
|
|
7
7
|
LazyWidgetBlogTech,
|
|
8
|
+
LazyWidgetCommGroup,
|
|
8
9
|
LazyWidgetEmpty,
|
|
9
10
|
LazyWidgetToc,
|
|
10
11
|
} from '#components'
|
|
@@ -14,6 +15,7 @@ const rawWidgets = {
|
|
|
14
15
|
LazyWidgetBlogLog,
|
|
15
16
|
LazyWidgetBlogStats,
|
|
16
17
|
LazyWidgetBlogTech,
|
|
18
|
+
LazyWidgetCommGroup,
|
|
17
19
|
LazyWidgetEmpty,
|
|
18
20
|
LazyWidgetToc,
|
|
19
21
|
}
|
package/src/config/app.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NavItem } from '../types/nav'
|
|
2
|
-
import type { ClarityPublicConfig } from './public'
|
|
2
|
+
import type { ClarityPublicConfig, ClarityPublicIntegrationsConfig } from './public'
|
|
3
|
+
import type { ClarityArticleConfig, ClarityAuthor, ClarityFeedConfig, ClarityHeadScript, ClarityStatsConfig } from './schema'
|
|
3
4
|
|
|
4
5
|
export interface NavGroup {
|
|
5
6
|
title: string
|
|
@@ -27,8 +28,10 @@ export interface ClarityUiConfig {
|
|
|
27
28
|
showTitle: boolean
|
|
28
29
|
}
|
|
29
30
|
stats: {
|
|
30
|
-
/**
|
|
31
|
-
birthYear
|
|
31
|
+
/** 归档页面每年标题对应的年龄,0 表示不展示 */
|
|
32
|
+
birthYear: number
|
|
33
|
+
/** blog-stats widget 的预置文本 */
|
|
34
|
+
wordCount: string
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
footer: {
|
|
@@ -70,10 +73,53 @@ export interface ClarityUiConfig {
|
|
|
70
73
|
}>
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
/**
|
|
77
|
+
* CustomAppConfig 中 article 的声明形状。
|
|
78
|
+
*
|
|
79
|
+
* 必须是 interface(而非 type 别名):MergedAppConfig 对 extends Record 的
|
|
80
|
+
* 类型会进入递归合并,Record<string, string> 的索引签名会把 order 键类型
|
|
81
|
+
* 碾碎成 keyof any;interface 不带隐式索引签名,直接整体采用本类型。
|
|
82
|
+
*/
|
|
83
|
+
export interface ClarityAppConfigArticle extends ClarityArticleConfig {
|
|
84
|
+
categories: Record<string, { icon?: string, color?: string }>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 上游 blog-v3 的扁平 AppConfig 形状(blog.config.ts 展开进 app.config.ts)。
|
|
89
|
+
* Clarity 由 clarity-config 模块注入同一形状,上游同步组件保持 byte-identical。
|
|
90
|
+
*/
|
|
91
|
+
export interface ClarityFlatAppConfig extends ClarityUiConfig {
|
|
92
|
+
title: string
|
|
93
|
+
subtitle: string
|
|
94
|
+
description: string
|
|
95
|
+
author: ClarityAuthor
|
|
96
|
+
copyright: { abbr: string, name: string, url: string }
|
|
97
|
+
favicon: string
|
|
98
|
+
language: string
|
|
99
|
+
timeEstablished: string
|
|
100
|
+
timeZone: string
|
|
101
|
+
url: string
|
|
102
|
+
defaultCategory: string
|
|
103
|
+
article: ClarityArticleConfig
|
|
104
|
+
feed: ClarityFeedConfig
|
|
105
|
+
scripts: ClarityHeadScript[]
|
|
106
|
+
stats: ClarityStatsConfig
|
|
107
|
+
twikoo?: ClarityPublicIntegrationsConfig['twikoo']
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** 消费项目 app/app.config.ts 顶层扁平键的覆盖输入类型(深层可选,数组整体替换) */
|
|
111
|
+
export type ClarityFlatAppConfigInput<T = ClarityFlatAppConfig> = {
|
|
112
|
+
[K in keyof T]?: T[K] extends readonly unknown[]
|
|
113
|
+
? T[K]
|
|
114
|
+
: T[K] extends object
|
|
115
|
+
? ClarityFlatAppConfigInput<T[K]>
|
|
116
|
+
: T[K]
|
|
117
|
+
}
|
|
118
|
+
|
|
73
119
|
export type ClarityAppConfig = ClarityPublicConfig & ClarityUiConfig
|
|
74
120
|
|
|
75
121
|
/**
|
|
76
|
-
* app/app.config.ts 中 `clarity`
|
|
122
|
+
* app/app.config.ts 中 `clarity` 键的覆盖输入类型(0.1.x 兼容):
|
|
77
123
|
* UI 配置深度可选(数组整体替换,与 defu 行为一致)。
|
|
78
124
|
*
|
|
79
125
|
* 站点级字段(site / article / feed / stats / integrations / features / changelog)
|
package/src/config/content.ts
CHANGED
|
@@ -22,6 +22,12 @@ export interface ArticleSchema {
|
|
|
22
22
|
permalink?: string
|
|
23
23
|
|
|
24
24
|
readingTime?: ReadTimeResults
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 开放索引签名:clarity.config.ts 的 article.order 允许自定义排序键,
|
|
28
|
+
* 上游归档页通过 article[sortOrder] 索引(上游依赖其配置字面量类型)。
|
|
29
|
+
*/
|
|
30
|
+
[key: string]: unknown
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
function createArticleSchema(config: ClarityConfig) {
|
package/src/config/feed.ts
CHANGED
|
@@ -1,42 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 友链数据类型:数据由消费项目提供,结构由 Theme 定义。
|
|
5
|
-
*/
|
|
6
|
-
export type { Arch }
|
|
7
|
-
|
|
8
|
-
export interface FeedEntry {
|
|
9
|
-
/** 博客作者,使用作者明确的自称 */
|
|
10
|
-
author: string
|
|
11
|
-
/** 友链卡片的作者名后缀;作者自称明确且较短时,可填简短站名,避免重复作者名 */
|
|
12
|
-
sitenick?: string
|
|
13
|
-
/** 悬浮卡片与订阅源使用的完整站点标题;与作者名相同时省略,默认使用 sitenick 或 author */
|
|
14
|
-
title?: string
|
|
15
|
-
/** 个人简介/博客描述 */
|
|
16
|
-
desc?: string
|
|
17
|
-
/** 博客地址 */
|
|
18
|
-
link: string
|
|
19
|
-
/** 订阅源 */
|
|
20
|
-
feed?: string
|
|
21
|
-
/** 站点小图标 */
|
|
22
|
-
icon: string
|
|
23
|
-
/** 个人头像 */
|
|
24
|
-
avatar: string
|
|
25
|
-
/** 博客技术架构 */
|
|
26
|
-
archs?: Arch[]
|
|
27
|
-
/** 申请/评论日期;主动收录时使用收录日期 */
|
|
28
|
-
date: string
|
|
29
|
-
/** 博主备注 */
|
|
30
|
-
comment?: string
|
|
31
|
-
/** 错误信息 */
|
|
32
|
-
error?: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface FeedGroup {
|
|
36
|
-
/** 分组名 */
|
|
37
|
-
name: string
|
|
38
|
-
/** 描述 */
|
|
39
|
-
desc?: string
|
|
40
|
-
/** 友链列表 */
|
|
41
|
-
entries: FeedEntry[]
|
|
42
|
-
}
|
|
1
|
+
export type { Arch } from '../shared/utils/icon'
|
|
2
|
+
export type { FeedEntry, FeedGroup } from '../types/feed'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme 内置的技术栈版本数据(上游 BlogTech 通过 ~~/pnpm-workspace.yaml
|
|
3
|
+
* 的 catalogs 展示 Vue / Nuxt / Nuxt Content 版本)。
|
|
4
|
+
*
|
|
5
|
+
* 分组结构与上游 blog-v3 的 pnpm-workspace.yaml 保持一致:上游 BlogTech
|
|
6
|
+
* 使用 es-toolkit 的 merge(...Object.values(catalogs))(二元签名),
|
|
7
|
+
* 至少需要两个分组才能正常展开。
|
|
8
|
+
*/
|
|
9
|
+
export const fallbackPnpmWorkspace = {
|
|
10
|
+
catalogs: {
|
|
11
|
+
content: {
|
|
12
|
+
'@nuxt/content': '^3.16.0',
|
|
13
|
+
},
|
|
14
|
+
framework: {
|
|
15
|
+
nuxt: '^4.5.2',
|
|
16
|
+
vue: '^3.5.42',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
} as const
|
package/src/config/ui.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clarity Theme UI 默认值(上游 blog-v3 `app/app.config.ts` 的可分发子集)。
|
|
3
|
+
*
|
|
4
|
+
* 上游的 app/app.config.ts 属于消费项目文件(其 init-project 会重置),
|
|
5
|
+
* Layer 无法引用消费项目的 blog.config 来派生 logo / subtitle / copyright,
|
|
6
|
+
* 因此这些站点派生值由 src/modules/clarity-config 注入;
|
|
7
|
+
* 本文件只保留不携带个人数据的结构性默认值(footer.iconNav / footer.nav
|
|
8
|
+
* 的个人链接不能随 npm 包分发,默认置空,由消费项目自行填写)。
|
|
9
|
+
*/
|
|
10
|
+
import type { Nav, NavItem } from '../types/nav'
|
|
11
|
+
|
|
12
|
+
// @keep-sorted
|
|
13
|
+
export default {
|
|
14
|
+
component: {
|
|
15
|
+
alert: {
|
|
16
|
+
/** 默认使用卡片风格还是扁平风格 */
|
|
17
|
+
defaultStyle: 'card' as 'card' | 'flat',
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
codeblock: {
|
|
21
|
+
/** 代码块触发折叠的行数 */
|
|
22
|
+
triggerRows: 32,
|
|
23
|
+
/** 代码块折叠后的行数 */
|
|
24
|
+
collapsedRows: 16,
|
|
25
|
+
/** 启用代码块缩进导航会关闭空格渲染 */
|
|
26
|
+
enableIndentGuide: true,
|
|
27
|
+
/** 代码块缩进导航(Indent Guide)竖线匹配空格数 */
|
|
28
|
+
indent: 4,
|
|
29
|
+
/** tab渲染宽度 */
|
|
30
|
+
tabSize: 3,
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
/** 文章开头摘要 */
|
|
34
|
+
excerpt: {
|
|
35
|
+
animation: true,
|
|
36
|
+
caret: '_',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
/** 精选文章 Slide */
|
|
40
|
+
slide: {
|
|
41
|
+
/** 适合封面图无字时启用 */
|
|
42
|
+
showTitle: true,
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
stats: {
|
|
46
|
+
/** 归档页面每年标题对应的年龄,无需展示时可设为 0 */
|
|
47
|
+
birthYear: 0,
|
|
48
|
+
/** blog-stats widget 的预置文本 */
|
|
49
|
+
wordCount: '',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// @keep-sorted
|
|
54
|
+
footer: {
|
|
55
|
+
/** 侧边栏底部图标导航(个人链接不随 Theme 分发,由消费项目配置) */
|
|
56
|
+
iconNav: [] as NavItem[],
|
|
57
|
+
/** 页脚站点地图(同上,默认为空) */
|
|
58
|
+
nav: [] as Nav,
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/** 左侧栏顶部 Logo */
|
|
62
|
+
header: {
|
|
63
|
+
/** 展示标题文本,否则展示纯 Logo */
|
|
64
|
+
showTitle: true,
|
|
65
|
+
/** 标题后的随机表情 */
|
|
66
|
+
emojiTail: ['📝', '✨', '🌱'] as string[],
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/** 友链页面 */
|
|
70
|
+
link: {
|
|
71
|
+
/** 无订阅源展示静音图标 */
|
|
72
|
+
remindNoFeed: true,
|
|
73
|
+
/** 友链分组内随机排序 */
|
|
74
|
+
randomInGroup: true,
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/** 左侧栏导航 */
|
|
78
|
+
nav: [
|
|
79
|
+
{
|
|
80
|
+
title: '',
|
|
81
|
+
items: [
|
|
82
|
+
{ icon: 'tabler:files', text: '文章', url: '/' },
|
|
83
|
+
{ icon: 'tabler:link', text: '友链', url: '/link' },
|
|
84
|
+
{ icon: 'tabler:archive', text: '归档', url: '/archive' },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
] as Nav,
|
|
88
|
+
|
|
89
|
+
pagination: {
|
|
90
|
+
perPage: 10,
|
|
91
|
+
/** 默认排序方式,需要是 article.order 中的键名 */
|
|
92
|
+
sortOrder: 'date',
|
|
93
|
+
/** 允许(普通/预览/归档)文章列表正序,开启后排序方式左侧图标可切换顺序 */
|
|
94
|
+
allowAscending: false,
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
themes: {
|
|
98
|
+
light: {
|
|
99
|
+
icon: 'tabler:sun',
|
|
100
|
+
tip: '浅色模式',
|
|
101
|
+
},
|
|
102
|
+
system: {
|
|
103
|
+
icon: 'tabler:device-desktop',
|
|
104
|
+
tip: '跟随系统',
|
|
105
|
+
},
|
|
106
|
+
dark: {
|
|
107
|
+
icon: 'tabler:moon',
|
|
108
|
+
tip: '深色模式',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const blogConfig = {"title":"Clarity Playground","subtitle":"Clarity Theme 示例站点","description":"Clarity Theme 的最小可运行示例,用于验证 Layer 独立构建与站点配置注入。","author":{"name":"Clarity 用户","avatar":"","email":"user@example.com","homepage":"https://clarity-theme.example.com/"},"copyright":{"abbr":"CC BY 4.0","name":"署名 4.0 国际","url":"https://creativecommons.org/licenses/by/4.0/deed.zh-hans"},"favicon":"/favicon.svg","language":"zh-CN","timeEstablished":"2026-01-01","timeZone":"Asia/Shanghai","url":"http://localhost:22098/","defaultCategory":"未分类","article":{"categories":{"未分类":{"icon":"tabler:circle-dashed"},"技术":{"icon":"tabler:code","color":"#7777ff"},"生活":{"icon":"tabler:leaf","color":"#ff7777"}},"types":{"tech":{},"story":{}},"order":{"date":"创建日期","updated":"更新日期"},"useRandomPremalat":false,"hidePostPrefix":true,"robotsNotIndex":[]},"feed":{"limit":20,"enableStyle":true},"scripts":[],"stats":{"includePaths":["posts/%"]},"twikoo":{"envId":"","preload":""}}
|
|
2
|
+
export default blogConfig
|
|
3
|
+
export const myFeed = {"author":"Clarity 用户","sitenick":"Clarity Playground","title":"Clarity Playground","desc":"Clarity Theme 示例站点","link":"http://localhost:22098/","feed":"http://localhost:22098/atom.xml","icon":"/favicon.svg","avatar":"","archs":["Nuxt"],"date":"2026-01-01","comment":"这是我自己"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default {"catalogs":{"content":{"@nuxt/content":"^3.16.0"},"framework":{"nuxt":"^4.5.2","vue":"^3.5.42"}}}
|