valaxy-theme-hairy 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/client/index.ts +1 -0
- package/components/HairyBody.vue +15 -17
- package/components/HairyComment.vue +33 -0
- package/components/HairyContainer.vue +13 -0
- package/components/{HairyUserPopup.vue → HairyDrawer.vue} +14 -13
- package/components/HairyFooter.vue +12 -9
- package/components/HairyHeader.vue +10 -9
- package/components/HairyImage.vue +2 -1
- package/components/HairyImageGroup.vue +12 -16
- package/components/HairyNavbar.vue +56 -0
- package/components/{HairyPostList.vue → HairyPosts.vue} +3 -3
- package/components/{HairyNavSearch.vue → HairySearch.vue} +23 -87
- package/components/{HairyUserCard.vue → HairySidebar.vue} +4 -4
- package/components/{HairyUserTab.vue → HairyTabbar.vue} +27 -11
- package/components/PageTags.vue +48 -0
- package/components/ValaxyMain.vue +3 -3
- package/components/navbar/HairyNav.vue +16 -0
- package/components/navbar/HairyNavExpand.vue +12 -0
- package/components/navbar/HairyNavItem.vue +35 -0
- package/components/navbar/HairyNavbarBackground.vue +7 -0
- package/components/navbar/HairyNavbarSearch.vue +8 -0
- package/components/{HairyNavTitle.vue → navbar/HairyNavbarTitle.vue} +5 -3
- package/components/navbar/HairyNavbarToggleDark.vue +22 -0
- package/components/{HairyBreadcrumb.vue → parts/HairyBreadcrumb.vue} +1 -1
- package/components/{HairyBreadcrumbItem.vue → parts/HairyBreadcrumbItem.vue} +1 -4
- package/components/{lib/fish.js → parts/HairyFootFish.js} +5 -7
- package/components/parts/HairyFootFish.vue +38 -0
- package/components/{HairyPostTitle.vue → parts/HairyHeadHero.vue} +6 -5
- package/components/{HairyWaves.vue → parts/HairyHeadWaves.vue} +5 -5
- package/components/parts/HairyImageGlobal.vue +51 -0
- package/components/{HairyImageViewer.vue → parts/HairyImageViewer.vue} +4 -3
- package/components/parts/HairyLink.vue +21 -0
- package/components/{HairyMenu.vue → parts/HairyMenu.vue} +2 -1
- package/components/{HairyMenuItem.vue → parts/HairyMenuItem.vue} +11 -4
- package/components/parts/HairyOutline.vue +99 -0
- package/components/parts/HairyOutlineItem.vue +48 -0
- package/components/{HairySocialLinks.vue → parts/HairySocialLinks.vue} +2 -2
- package/components/{HairyTimelinePostItem.vue → parts/HairyTimelineContent.vue} +7 -8
- package/components/parts/HairyUserNav.vue +95 -0
- package/components/{article-layout → posts}/HairyArticleImage.vue +18 -19
- package/components/{article-layout → posts}/HairyArticleSeries.vue +8 -5
- package/components/{article-layout → posts}/HairyArticleText.vue +11 -4
- package/components/posts/HairyPostFooter.vue +15 -0
- package/components/{article-layout → posts}/HairyPostImageList.vue +4 -5
- package/components/{article-layout → posts}/HairyPostTextsList.vue +0 -1
- package/components/posts/HairyPostToggleLayout.vue +36 -0
- package/components/third/HairyAlgoliaSearch.vue +17 -0
- package/components/third/HairyFuseSearch.vue +10 -0
- package/components/third/HairyFuseSearchDialog.vue +32 -0
- package/components/third/HairyFuseSearchDropdown.vue +77 -0
- package/components/third/HairyFuseSearchFooter.vue +28 -0
- package/components/third/HairyFuseSearchHeader.vue +30 -0
- package/components/third/HairyFuseSearchHit.vue +52 -0
- package/components/third/HairySearchBtnDisplay.vue +29 -0
- package/components/third/HairySearchBtnInput.vue +20 -0
- package/components/third/HairySearchBtnKeys.vue +19 -0
- package/components/{HairyCarousel.vue → third/HairySwiperCarousel.vue} +6 -6
- package/{hooks/useYearArchives.ts → composables/archives.ts} +4 -3
- package/composables/category.ts +43 -0
- package/composables/config.ts +11 -0
- package/composables/dark.ts +13 -0
- package/composables/fuse.ts +60 -0
- package/composables/index.ts +8 -0
- package/composables/layout.ts +16 -0
- package/composables/outline.ts +49 -0
- package/composables/tags.ts +36 -0
- package/layouts/archive-month.vue +13 -0
- package/layouts/archive-year.vue +13 -0
- package/layouts/archive.vue +11 -0
- package/layouts/categories.vue +11 -4
- package/layouts/default.vue +8 -7
- package/layouts/home.vue +28 -18
- package/layouts/post.vue +41 -35
- package/layouts/tag.vue +8 -4
- package/layouts/tags.vue +11 -4
- package/{modules → library}/loading.ts +18 -6
- package/{modules → library}/scroll.ts +3 -2
- package/locales/zh-CN.yml +0 -2
- package/node/images/default.json +139 -0
- package/node/images/index.ts +46 -0
- package/node/index.ts +2 -0
- package/node/theme/index.ts +78 -0
- package/package.json +22 -28
- package/pages/archives/[year]/[month]/index.vue +15 -16
- package/pages/archives/[year]/index.vue +20 -18
- package/pages/archives/index.vue +10 -8
- package/pages/categories/{[...categories].vue → [...its].vue} +29 -34
- package/pages/index.vue +1 -1
- package/pages/page/[page].vue +2 -2
- package/pages/tags/{[tag].vue → [tag]/index.vue} +12 -12
- package/pages/tags/index.vue +12 -5
- package/setup/main.ts +1 -1
- package/store/index.ts +1 -0
- package/store/modules/global.ts +12 -0
- package/styles/components/index.scss +4 -0
- package/styles/{markdown.scss → components/markdown.scss} +2 -1
- package/styles/components/nprogress.scss +16 -0
- package/styles/css-vars.scss +11 -0
- package/styles/element-plus/tabs.scss +1 -1
- package/styles/element-plus/timeline.scss +1 -1
- package/styles/global.scss +39 -0
- package/styles/index.scss +4 -73
- package/tsconfig.json +27 -0
- package/types/index.d.ts +163 -0
- package/unocss.config.ts +5 -1
- package/utils/index.ts +21 -39
- package/valaxy.config.ts +21 -24
- package/@types/markdown-it.d.ts +0 -1
- package/@types/markdown-toc.d.ts +0 -1
- package/@types/types.d.ts +0 -1
- package/@types/valaxy.d.ts +0 -10
- package/components/HairyAlgoliaSearchBox.vue +0 -118
- package/components/HairyBackToTop.vue +0 -72
- package/components/HairyDivider.vue +0 -0
- package/components/HairyFooterFish.vue +0 -29
- package/components/HairyLayout.vue +0 -28
- package/components/HairyLink.vue +0 -10
- package/components/HairyLinks.vue +0 -69
- package/components/HairyMeting.vue +0 -19
- package/components/HairyNav.vue +0 -42
- package/components/HairyNavBackground.vue +0 -7
- package/components/HairyNavMenu.vue +0 -11
- package/components/HairyNavToggleDark.vue +0 -16
- package/components/HairyPostToggleLayout.vue +0 -33
- package/components/HairyToc.vue +0 -135
- package/components/HairyUserNav.vue +0 -64
- package/components/HairyWaline.vue +0 -44
- package/hooks/setupDefaultDark.ts +0 -11
- package/hooks/useCategory.ts +0 -18
- package/hooks/useCategoryPost.ts +0 -21
- package/hooks/useContext.ts +0 -13
- package/hooks/useHeaderHeight.ts +0 -9
- package/hooks/usePostLayout.ts +0 -16
- package/images.json +0 -140
- package/index.d.ts +0 -100
- package/layouts/archives.vue +0 -11
- package/layouts/hairy.vue +0 -36
- package/layouts/month.vue +0 -6
- package/layouts/year.vue +0 -6
- package/node/addon-hairy.ts +0 -36
- package/node/addon-images.ts +0 -61
- package/node/addon-meting.ts +0 -13
- package/node/addon-statistics.ts +0 -19
- package/node/addon-toc.ts +0 -20
- package/node/utils.ts +0 -20
- package/utils/createContext.ts +0 -40
- package/utils/fonts.ts +0 -15
- /package/components/{HairyUserStats.vue → parts/HairyUserStats.vue} +0 -0
- /package/components/{article-layout → posts}/HairyArticleTop.vue +0 -0
- /package/{modules → library}/loading.scss +0 -0
- /package/{shims.d.ts → node/images/shims.d.ts} +0 -0
- /package/styles/{aplayer.scss → components/aplayer.scss} +0 -0
- /package/styles/{scrollbar.scss → components/scrollbar.scss} +0 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import '@docsearch/css'
|
3
|
+
</script>
|
4
|
+
|
5
|
+
<template>
|
6
|
+
<div class="VPNavBarSearch">
|
7
|
+
<button type="button" class="DocSearch DocSearch-Button" aria-label="Search">
|
8
|
+
<span class="DocSearch-Button-Container lt-sm:text-size-xl">
|
9
|
+
<svg
|
10
|
+
class="DocSearch-Search-Icon"
|
11
|
+
width="20"
|
12
|
+
height="20"
|
13
|
+
viewBox="0 0 20 20"
|
14
|
+
>
|
15
|
+
<path
|
16
|
+
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
|
17
|
+
stroke="currentColor"
|
18
|
+
fill="none"
|
19
|
+
fill-rule="evenodd"
|
20
|
+
stroke-linecap="round"
|
21
|
+
stroke-linejoin="round"
|
22
|
+
/>
|
23
|
+
</svg>
|
24
|
+
<span class="DocSearch-Button-Placeholder">Search</span>
|
25
|
+
</span>
|
26
|
+
<HairySearchBtnKeys />
|
27
|
+
</button>
|
28
|
+
</div>
|
29
|
+
</template>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<template>
|
2
|
+
<span class="DocSearch-Button-Container lt-sm:text-size-xl">
|
3
|
+
<svg
|
4
|
+
class="DocSearch-Search-Icon"
|
5
|
+
width="20"
|
6
|
+
height="20"
|
7
|
+
viewBox="0 0 20 20"
|
8
|
+
>
|
9
|
+
<path
|
10
|
+
d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z"
|
11
|
+
stroke="currentColor"
|
12
|
+
fill="none"
|
13
|
+
fill-rule="evenodd"
|
14
|
+
stroke-linecap="round"
|
15
|
+
stroke-linejoin="round"
|
16
|
+
/>
|
17
|
+
</svg>
|
18
|
+
<span class="DocSearch-Button-Placeholder">Search</span>
|
19
|
+
</span>
|
20
|
+
</template>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<template>
|
2
|
+
<span class="DocSearch-Button-Keys">
|
3
|
+
<kbd class="DocSearch-Button-Key">
|
4
|
+
<svg
|
5
|
+
width="15" height="15"
|
6
|
+
class="DocSearch-Control-Key-Icon"
|
7
|
+
>
|
8
|
+
<path
|
9
|
+
d="M4.505 4.496h2M5.505 5.496v5M8.216 4.496l.055 5.993M10 7.5c.333.333.5.667.5 1v2M12.326 4.5v5.996M8.384 4.496c1.674 0 2.116 0 2.116 1.5s-.442 1.5-2.116 1.5M3.205 9.303c-.09.448-.277 1.21-1.241 1.203C1 10.5.5 9.513.5 8V7c0-1.57.5-2.5 1.464-2.494.964.006 1.134.598 1.24 1.342M12.553 10.5h1.953"
|
10
|
+
stroke-width="1.2"
|
11
|
+
stroke="currentColor"
|
12
|
+
fill="none"
|
13
|
+
stroke-linecap="square"
|
14
|
+
/>
|
15
|
+
</svg>
|
16
|
+
</kbd>
|
17
|
+
<kbd class="DocSearch-Button-Key">K</kbd>
|
18
|
+
</span>
|
19
|
+
</template>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
2
|
import images from '@hairy:images:home'
|
3
3
|
import { Swiper, SwiperSlide } from 'swiper/vue'
|
4
|
-
import { Autoplay, EffectFade } from 'swiper'
|
4
|
+
import { Autoplay, EffectFade } from 'swiper/modules'
|
5
5
|
import 'swiper/css'
|
6
6
|
import 'swiper/css/autoplay'
|
7
7
|
import 'swiper/css/effect-fade'
|
@@ -10,16 +10,16 @@ images.sort(() => Math.random() - 0.5)
|
|
10
10
|
</script>
|
11
11
|
|
12
12
|
<template>
|
13
|
-
<
|
13
|
+
<Swiper
|
14
14
|
effect="fade" :speed="2000" :fade-effect="{ crossFade: true }" :modules="[Autoplay, EffectFade]"
|
15
15
|
:slides-per-view="1" :space-between="50" :autoplay="{
|
16
16
|
delay: 4000,
|
17
17
|
}" class="HairyCarousel swiper-no-swiping"
|
18
18
|
>
|
19
|
-
<
|
20
|
-
<img class="w-full h-full object-cover" :src="item"
|
21
|
-
</
|
22
|
-
</
|
19
|
+
<SwiperSlide v-for="(item, index) in images" :key="index" class="w-full h-full">
|
20
|
+
<img class="w-full h-full object-cover" :src="item">
|
21
|
+
</SwiperSlide>
|
22
|
+
</Swiper>
|
23
23
|
</template>
|
24
24
|
|
25
25
|
<style lang="scss">
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/* eslint-disable style/max-statements-per-line */
|
1
2
|
import type { Post } from 'valaxy'
|
2
3
|
import { usePostList } from 'valaxy'
|
3
4
|
import { computed } from 'vue'
|
@@ -5,10 +6,10 @@ import dayjs from 'dayjs'
|
|
5
6
|
|
6
7
|
interface ArchiveYearMaps {
|
7
8
|
[year: string]: {
|
8
|
-
[month: string]: { count: number
|
9
|
+
[month: string]: { count: number, posts: Post[] }
|
9
10
|
}
|
10
11
|
}
|
11
|
-
interface
|
12
|
+
interface ArchiveYear {
|
12
13
|
year: string
|
13
14
|
month: string
|
14
15
|
count: number
|
@@ -19,7 +20,7 @@ export function useYearArchives() {
|
|
19
20
|
const posts = usePostList()
|
20
21
|
const archives = computed(() => {
|
21
22
|
const maps: ArchiveYearMaps = {}
|
22
|
-
const items:
|
23
|
+
const items: ArchiveYear[] = []
|
23
24
|
for (const post of posts.value) {
|
24
25
|
if (!post.date)
|
25
26
|
continue
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import type { CategoryList, PostFrontMatter } from 'valaxy'
|
2
|
+
import { useCategories, usePostList } from 'valaxy'
|
3
|
+
import type { MaybeRef } from '@vueuse/core'
|
4
|
+
import { computed, unref } from 'vue'
|
5
|
+
|
6
|
+
import { isEqual } from 'lodash-es'
|
7
|
+
|
8
|
+
export function useCategory(categories: MaybeRef<string[]>) {
|
9
|
+
const all = useCategories()
|
10
|
+
|
11
|
+
unref(categories)
|
12
|
+
// [BBB]
|
13
|
+
return computed(() => {
|
14
|
+
let parent: PostFrontMatter & Partial<CategoryList> = all.value as any
|
15
|
+
for (const category of unref(categories)) {
|
16
|
+
const children = (parent.children || new Map([]))
|
17
|
+
for (const [key, value] of children) {
|
18
|
+
if (key === category && !key.startsWith('/posts')) {
|
19
|
+
parent = (value as PostFrontMatter)
|
20
|
+
continue
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
return parent
|
25
|
+
})
|
26
|
+
}
|
27
|
+
|
28
|
+
export function useCategoryPost(categories: MaybeRef<string[]>) {
|
29
|
+
const postList = usePostList()
|
30
|
+
return computed(() => {
|
31
|
+
if (!unref(categories).length)
|
32
|
+
return postList.value
|
33
|
+
return postList.value.filter((item) => {
|
34
|
+
let target = Array.isArray(item.categories)
|
35
|
+
? item.categories
|
36
|
+
: [item.categories || '']
|
37
|
+
target = target.filter(Boolean)
|
38
|
+
if (unref(categories)[0] === 'Uncategorized')
|
39
|
+
return !target.length
|
40
|
+
return isEqual(target.sort(), unref(categories).sort())
|
41
|
+
}) as any
|
42
|
+
})
|
43
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import { computed } from 'vue'
|
2
|
+
import { useConfig } from 'valaxy'
|
3
|
+
import type { ThemeConfig } from '../types'
|
4
|
+
|
5
|
+
/**
|
6
|
+
* getThemeConfig
|
7
|
+
*/
|
8
|
+
export function useThemeConfig<T = ThemeConfig>() {
|
9
|
+
const config = useConfig<T>()
|
10
|
+
return computed(() => config!.value.themeConfig)
|
11
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { useAppStore, useThemeConfig } from 'valaxy'
|
2
|
+
import { useLocalStorage } from '@vueuse/core'
|
3
|
+
import type { ThemeConfig } from 'valaxy-theme-hairy'
|
4
|
+
|
5
|
+
export function setupDefaultDark() {
|
6
|
+
const theme = useThemeConfig<ThemeConfig>()
|
7
|
+
const { toggleDark } = useAppStore()
|
8
|
+
const local = useLocalStorage('--hairy-mode', '')
|
9
|
+
if (theme.value.theme && !local.value) {
|
10
|
+
local.value = theme.value.theme
|
11
|
+
toggleDark()
|
12
|
+
}
|
13
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import { computed, onMounted, ref } from 'vue'
|
2
|
+
import type { UseFuseOptions } from '@vueuse/integrations/useFuse'
|
3
|
+
import { useFuse } from '@vueuse/integrations/useFuse'
|
4
|
+
import type { FuseListItem } from 'valaxy/types'
|
5
|
+
import { useSiteConfig } from 'valaxy'
|
6
|
+
import type { MaybeRefOrGetter } from '@vueuse/shared'
|
7
|
+
|
8
|
+
export function useFuseParsed(input: MaybeRefOrGetter<string>) {
|
9
|
+
const siteConfig = useSiteConfig()
|
10
|
+
const data = ref<FuseListItem[]>([])
|
11
|
+
|
12
|
+
const keys = computed(() => {
|
13
|
+
const defKs = ['title', 'tags', 'categories', 'excerpt']
|
14
|
+
const ks = siteConfig.value.fuse.options.keys || []
|
15
|
+
return ks.length === 0 ? defKs : ks
|
16
|
+
})
|
17
|
+
|
18
|
+
const fuseOptions = computed<UseFuseOptions<FuseListItem>>(() => ({
|
19
|
+
fuseOptions: {
|
20
|
+
...siteConfig.value.fuse.options,
|
21
|
+
keys: keys.value,
|
22
|
+
// threshold: 0.99,
|
23
|
+
// ignoreLocation: true,
|
24
|
+
},
|
25
|
+
includeMatches: true,
|
26
|
+
findAllMatches: true,
|
27
|
+
// resultLimit: resultLimit.value,
|
28
|
+
// matchAllWhenSearchEmpty: matchAllWhenSearchEmpty.value,
|
29
|
+
}))
|
30
|
+
|
31
|
+
onMounted(async () => {
|
32
|
+
const path = !siteConfig.value.fuse.dataPath.startsWith('http')
|
33
|
+
? `${import.meta.env.BASE_URL}${siteConfig.value.fuse.dataPath}`
|
34
|
+
: siteConfig.value.fuse.dataPath
|
35
|
+
data.value = await fetch(path).then(r => r.json() || [])
|
36
|
+
})
|
37
|
+
|
38
|
+
const { fuse, results: _results } = useFuse(input, data, fuseOptions)
|
39
|
+
const results = computed(() => _results.value.map((v) => {
|
40
|
+
return {
|
41
|
+
...v,
|
42
|
+
item: {
|
43
|
+
...v.item,
|
44
|
+
excerpt: removeTags(v.item.excerpt || ''),
|
45
|
+
},
|
46
|
+
}
|
47
|
+
}))
|
48
|
+
return { results, fuse, data }
|
49
|
+
}
|
50
|
+
|
51
|
+
function removeTags(content = '') {
|
52
|
+
content = content.replace(/<\/?[^>]*>/g, '')
|
53
|
+
content = content.replace(/[|]/g, '')
|
54
|
+
content = content.replace(/```.*?\n/sg, '')
|
55
|
+
content = content.replace(/:::.*?\n/sg, '')
|
56
|
+
content = content.replace(/\[\[toc\]\]/g, '')
|
57
|
+
content = content.replace(/\{lang=".*?\}/sg, '')
|
58
|
+
content = content.replace(/#.*? /sg, '')
|
59
|
+
return content
|
60
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { useThemeConfig } from 'valaxy'
|
2
|
+
import { createSharedComposable, useStorage } from '@vueuse/core'
|
3
|
+
import { computed } from 'vue'
|
4
|
+
import type { HairyTheme } from '../types'
|
5
|
+
|
6
|
+
const useSharedStorage = createSharedComposable(useStorage)
|
7
|
+
|
8
|
+
export function useLayoutPost() {
|
9
|
+
const themeConfig = useThemeConfig<HairyTheme.Config>()
|
10
|
+
const cache = useSharedStorage<HairyTheme.Layout['post']>('--hairy-theme:post-layout', null)
|
11
|
+
const layout = computed({
|
12
|
+
get: () => cache.value || themeConfig.value.layout?.post || 'image',
|
13
|
+
set: value => cache.value = value,
|
14
|
+
})
|
15
|
+
return layout
|
16
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import { defineStore } from 'pinia'
|
2
|
+
import type { MenuItem } from 'valaxy'
|
3
|
+
import { getHeaders, onContentUpdated, useFrontmatter, useThemeConfig } from 'valaxy'
|
4
|
+
import type { DefaultTheme } from 'valaxy/types'
|
5
|
+
import { computed } from 'vue'
|
6
|
+
import { useRoute } from 'vue-router'
|
7
|
+
|
8
|
+
const useOutlineStore = defineStore('OutlineStore', {
|
9
|
+
state: () => ({} as Record<string, MenuItem[]>),
|
10
|
+
})
|
11
|
+
|
12
|
+
/**
|
13
|
+
* export headers & handleClick to generate outline
|
14
|
+
*/
|
15
|
+
export function useOutline() {
|
16
|
+
const frontmatter = useFrontmatter()
|
17
|
+
const themeConfig = useThemeConfig()
|
18
|
+
const route = useRoute()
|
19
|
+
const store = useOutlineStore()
|
20
|
+
|
21
|
+
const pageOutline = computed<DefaultTheme.Config['outline']>(
|
22
|
+
() => frontmatter.value.outline ?? themeConfig.value.outline,
|
23
|
+
)
|
24
|
+
|
25
|
+
const headers = computed(() => store.$state[route.path] || [])
|
26
|
+
|
27
|
+
onContentUpdated(() => {
|
28
|
+
if (pageOutline.value === false)
|
29
|
+
return
|
30
|
+
store.$state[route.path] = getHeaders(pageOutline.value)
|
31
|
+
})
|
32
|
+
|
33
|
+
const handleClick = ({ target: el }: Event) => {
|
34
|
+
const id = (el as HTMLAnchorElement).href!.split('#')[1]
|
35
|
+
const heading = document.getElementById(decodeURIComponent(id)) as HTMLAnchorElement
|
36
|
+
heading?.focus({ preventScroll: true })
|
37
|
+
}
|
38
|
+
|
39
|
+
return {
|
40
|
+
/**
|
41
|
+
* headers for toc
|
42
|
+
*/
|
43
|
+
headers,
|
44
|
+
/**
|
45
|
+
* click hash heading
|
46
|
+
*/
|
47
|
+
handleClick,
|
48
|
+
}
|
49
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
import { TinyColor } from '@ctrl/tinycolor'
|
2
|
+
import { useTags } from 'valaxy'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* get utils about tags
|
6
|
+
*/
|
7
|
+
export function useHairyTags(options: {
|
8
|
+
/**
|
9
|
+
* Primary Color
|
10
|
+
*/
|
11
|
+
primary: string
|
12
|
+
} = {
|
13
|
+
primary: '#0078E7',
|
14
|
+
}) {
|
15
|
+
const tags = useTags()
|
16
|
+
|
17
|
+
const gray = new TinyColor('#999999')
|
18
|
+
const primaryColor = new TinyColor(options.primary)
|
19
|
+
|
20
|
+
const getTagStyle = (count: number) => {
|
21
|
+
const counts = Array.from(tags.value).map(([_, value]) => value.count)
|
22
|
+
const max = Math.max(...counts)
|
23
|
+
const min = Math.min(...counts)
|
24
|
+
const range = max - min
|
25
|
+
const percent = (count - min) / range
|
26
|
+
return {
|
27
|
+
'--yun-tag-color': gray.mix(primaryColor, percent * 100).toString(),
|
28
|
+
'fontSize': `${percent * 36 + 12}px`,
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return {
|
33
|
+
tags,
|
34
|
+
getTagStyle,
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
</script>
|
3
|
+
|
4
|
+
<template>
|
5
|
+
<HairyContainer>
|
6
|
+
<HairyNavbar />
|
7
|
+
<HairyHeader :title="`发表于'${$route.params.year}年${$route.params.month}月'的文章`" />
|
8
|
+
<HairyBody>
|
9
|
+
<router-view />
|
10
|
+
</HairyBody>
|
11
|
+
<HairyFooter />
|
12
|
+
</HairyContainer>
|
13
|
+
</template>
|
package/layouts/categories.vue
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
</script>
|
3
|
+
|
1
4
|
<template>
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
+
<HairyContainer>
|
6
|
+
<HairyNavbar />
|
7
|
+
<HairyHeader title="分类" />
|
8
|
+
<HairyBody>
|
9
|
+
<router-view />
|
10
|
+
</HairyBody>
|
11
|
+
<HairyFooter />
|
12
|
+
</HairyContainer>
|
5
13
|
</template>
|
6
|
-
|
package/layouts/default.vue
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
<script lang="ts" setup>
|
2
|
-
</script>
|
3
|
-
|
4
1
|
<template>
|
5
|
-
<
|
6
|
-
<
|
7
|
-
|
2
|
+
<HairyContainer>
|
3
|
+
<HairyNavbar />
|
4
|
+
<HairyHeader />
|
5
|
+
<HairyBody>
|
6
|
+
<router-view />
|
7
|
+
</HairyBody>
|
8
|
+
<HairyFooter />
|
9
|
+
</HairyContainer>
|
8
10
|
</template>
|
9
|
-
|
package/layouts/home.vue
CHANGED
@@ -1,23 +1,33 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
|
-
import {
|
3
|
-
|
2
|
+
import { useSiteConfig } from 'valaxy'
|
3
|
+
import dayjs from 'dayjs'
|
4
|
+
import { riposte } from '../utils'
|
5
|
+
|
6
|
+
const config = useSiteConfig()
|
7
|
+
|
8
|
+
const hour = dayjs().hour()
|
9
|
+
const hello = riposte(
|
10
|
+
[hour >= 5 && hour < 12, 'Good morning'],
|
11
|
+
[hour >= 12 && hour < 18, 'Good afternoon'],
|
12
|
+
[true, 'Good evening'],
|
13
|
+
)
|
4
14
|
</script>
|
5
15
|
|
6
16
|
<template>
|
7
|
-
<
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
+
<HairyContainer>
|
18
|
+
<HairyNavbar>
|
19
|
+
<template #nav>
|
20
|
+
<slot name="header-nav" />
|
21
|
+
</template>
|
22
|
+
</HairyNavbar>
|
23
|
+
<HairyHeader
|
24
|
+
:headline="config.title"
|
25
|
+
:title="config.author.name"
|
26
|
+
:description="`${hello}, how are you doing?`"
|
27
|
+
/>
|
28
|
+
<HairyBody>
|
29
|
+
<router-view />
|
30
|
+
</HairyBody>
|
31
|
+
<HairyFooter />
|
32
|
+
</HairyContainer>
|
17
33
|
</template>
|
18
|
-
|
19
|
-
<style>
|
20
|
-
.HairyHomeHeader .title {
|
21
|
-
@apply tracking-5;
|
22
|
-
}
|
23
|
-
</style>
|
package/layouts/post.vue
CHANGED
@@ -1,48 +1,54 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
|
+
import { dayjs } from 'element-plus'
|
2
3
|
import { useFrontmatter } from 'valaxy'
|
4
|
+
import { useRouter } from 'vue-router'
|
3
5
|
import { ElTag } from 'element-plus/es/components/tag/index'
|
4
6
|
import 'element-plus/es/components/tag/style/index'
|
5
|
-
import dayjs from 'dayjs'
|
6
|
-
import { useRouter } from 'vue-router'
|
7
|
-
const router = useRouter()
|
8
7
|
|
8
|
+
defineProps<{
|
9
|
+
header?: {
|
10
|
+
title?: string
|
11
|
+
headline?: string
|
12
|
+
description?: string
|
13
|
+
}
|
14
|
+
}>()
|
15
|
+
|
16
|
+
const router = useRouter()
|
9
17
|
const post = useFrontmatter()
|
10
18
|
|
11
|
-
|
19
|
+
function displayTag(tag: string) {
|
12
20
|
router.push(`/tags/${tag}`)
|
13
21
|
}
|
14
22
|
</script>
|
15
23
|
|
16
24
|
<template>
|
17
|
-
<
|
18
|
-
<
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
</
|
31
|
-
</
|
32
|
-
</
|
33
|
-
<
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
</
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
</
|
44
|
-
|
25
|
+
<HairyContainer>
|
26
|
+
<HairyNavbar />
|
27
|
+
<HairyHeader :title="post.title">
|
28
|
+
<template #description>
|
29
|
+
<div class="flex gap-2">
|
30
|
+
<span>发表于 {{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
31
|
+
<span>本文字数 {{ post.wordCount }} 字</span>
|
32
|
+
<span>阅读时长 {{ post.readingTime }} 分钟</span>
|
33
|
+
</div>
|
34
|
+
<div v-if="post.tags?.length" class="tags flex-center gap-2 mt-2">
|
35
|
+
<ElTag v-for="(tag) in post.tags" :key="tag" class="dark:bg-dark-50 cursor-pointer" @click="displayTag(tag)">
|
36
|
+
{{ tag }}
|
37
|
+
</ElTag>
|
38
|
+
</div>
|
39
|
+
</template>
|
40
|
+
</HairyHeader>
|
41
|
+
<HairyBody>
|
42
|
+
<template #default>
|
43
|
+
<HairyImageGlobal>
|
44
|
+
<router-view />
|
45
|
+
</HairyImageGlobal>
|
46
|
+
<HairyPostFooter />
|
47
|
+
</template>
|
48
|
+
<template #slide>
|
49
|
+
<HairyTabbar />
|
50
|
+
</template>
|
51
|
+
</HairyBody>
|
52
|
+
<HairyFooter />
|
53
|
+
</HairyContainer>
|
45
54
|
</template>
|
46
|
-
|
47
|
-
<style lang="scss">
|
48
|
-
</style>
|
package/layouts/tag.vue
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
<template>
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
2
|
+
<HairyContainer>
|
3
|
+
<HairyNavbar />
|
4
|
+
<HairyHeader :title="`包含标签'${$route.params.tag}'的标签`" />
|
5
|
+
<HairyBody>
|
6
|
+
<router-view />
|
7
|
+
</HairyBody>
|
8
|
+
<HairyFooter />
|
9
|
+
</HairyContainer>
|
5
10
|
</template>
|
6
|
-
|
package/layouts/tags.vue
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
</script>
|
3
|
+
|
1
4
|
<template>
|
2
|
-
<
|
3
|
-
<
|
4
|
-
|
5
|
+
<HairyContainer>
|
6
|
+
<HairyNavbar />
|
7
|
+
<HairyHeader title="所有标签" />
|
8
|
+
<HairyBody>
|
9
|
+
<router-view />
|
10
|
+
</HairyBody>
|
11
|
+
<HairyFooter />
|
12
|
+
</HairyContainer>
|
5
13
|
</template>
|
6
|
-
|