valaxy-theme-hairy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. package/@types/markdown-it.d.ts +1 -0
  2. package/@types/markdown-toc.d.ts +1 -0
  3. package/@types/types.d.ts +1 -0
  4. package/@types/valaxy.d.ts +10 -0
  5. package/LICENSE +21 -0
  6. package/components/HairyAlgoliaSearchBox.vue +118 -0
  7. package/components/HairyArticleImage.vue +17 -0
  8. package/components/HairyArticleImageDefault.vue +127 -0
  9. package/components/HairyArticleImageSmall.vue +49 -0
  10. package/components/HairyArticleSeries.vue +73 -0
  11. package/components/HairyArticleText.vue +38 -0
  12. package/components/HairyArticleTop.vue +0 -0
  13. package/components/HairyBackToTop.vue +72 -0
  14. package/components/HairyBody.vue +55 -0
  15. package/components/HairyBreadcrumb.vue +51 -0
  16. package/components/HairyBreadcrumbItem.vue +14 -0
  17. package/components/HairyCarousel.vue +45 -0
  18. package/components/HairyDivider.vue +0 -0
  19. package/components/HairyHeader.vue +31 -0
  20. package/components/HairyImage.vue +14 -0
  21. package/components/HairyImageGroup.vue +69 -0
  22. package/components/HairyImageViewer.vue +22 -0
  23. package/components/HairyLayout.vue +29 -0
  24. package/components/HairyLink.vue +10 -0
  25. package/components/HairyMenu.vue +15 -0
  26. package/components/HairyMenuItem.vue +40 -0
  27. package/components/HairyMeting.vue +19 -0
  28. package/components/HairyNav.vue +39 -0
  29. package/components/HairyNavBackground.vue +7 -0
  30. package/components/HairyNavSearch.vue +265 -0
  31. package/components/HairyNavTitle.vue +13 -0
  32. package/components/HairyNavToggleDark.vue +16 -0
  33. package/components/HairyPostImageList.vue +28 -0
  34. package/components/HairyPostList.vue +24 -0
  35. package/components/HairyPostTitle.vue +33 -0
  36. package/components/HairySocialLinks.vue +27 -0
  37. package/components/HairyTimelinePostItem.vue +40 -0
  38. package/components/HairyToc.vue +135 -0
  39. package/components/HairyUserCard.vue +30 -0
  40. package/components/HairyUserNav.vue +68 -0
  41. package/components/HairyUserTab.vue +40 -0
  42. package/components/HairyWaline.vue +25 -0
  43. package/components/HairyWaves.vue +67 -0
  44. package/components/ValaxyMain.vue +45 -0
  45. package/hooks/useCategory.ts +18 -0
  46. package/hooks/useCategoryPost.ts +21 -0
  47. package/hooks/useContext.ts +16 -0
  48. package/hooks/useHeaderHeight.ts +9 -0
  49. package/hooks/usePostLayout.ts +9 -0
  50. package/hooks/useYearArchives.ts +43 -0
  51. package/images.json +101 -0
  52. package/index.d.ts +54 -0
  53. package/layouts/archives.vue +11 -0
  54. package/layouts/categories.vue +6 -0
  55. package/layouts/default.vue +9 -0
  56. package/layouts/home.vue +23 -0
  57. package/layouts/month.vue +6 -0
  58. package/layouts/post.vue +26 -0
  59. package/layouts/tag.vue +6 -0
  60. package/layouts/tags.vue +6 -0
  61. package/layouts/year.vue +6 -0
  62. package/locales/en.yml +1 -0
  63. package/locales/zh-CN.yml +3 -0
  64. package/modules/context.ts +5 -0
  65. package/modules/loading.scss +531 -0
  66. package/modules/loading.ts +42 -0
  67. package/modules/scroll.ts +21 -0
  68. package/node/addon-hairy.ts +18 -0
  69. package/node/addon-images.ts +61 -0
  70. package/node/addon-meting.ts +13 -0
  71. package/node/addon-statistics.ts +19 -0
  72. package/node/addon-toc.ts +20 -0
  73. package/node/utils.ts +20 -0
  74. package/package.json +47 -0
  75. package/pages/archives/[year]/[month]/index.vue +52 -0
  76. package/pages/archives/[year]/index.vue +73 -0
  77. package/pages/archives/index.vue +53 -0
  78. package/pages/categories/[...categories].vue +115 -0
  79. package/pages/index.vue +14 -0
  80. package/pages/tags/[tag].vue +40 -0
  81. package/pages/tags/index.vue +31 -0
  82. package/setup/main.ts +11 -0
  83. package/shims.d.ts +8 -0
  84. package/styles/css-vars.scss +161 -0
  85. package/styles/element-plus/index.scss +2 -0
  86. package/styles/element-plus/tabs.scss +26 -0
  87. package/styles/element-plus/timeline.scss +19 -0
  88. package/styles/font-face.scss +20 -0
  89. package/styles/fonts/FrederickatheGreat.ttf +0 -0
  90. package/styles/fonts/Modesty.ttf +0 -0
  91. package/styles/fonts/MountainsofChristmas-Bold.ttf +0 -0
  92. package/styles/fonts/MountainsofChristmas-Regular.ttf +0 -0
  93. package/styles/fonts/Seto.ttf +0 -0
  94. package/styles/index.scss +65 -0
  95. package/styles/markdown.scss +60 -0
  96. package/styles/scrollbar.scss +26 -0
  97. package/unocss.config.ts +29 -0
  98. package/utils/createContext.ts +40 -0
  99. package/utils/index.ts +28 -0
  100. package/utils/loading.scss +531 -0
  101. package/utils/loading.ts +30 -0
  102. package/valaxy.config.ts +26 -0
@@ -0,0 +1,24 @@
1
+ <script lang="ts" setup>
2
+ import type { Ref } from 'vue'
3
+ import { computed } from 'vue'
4
+ import type { Post } from 'valaxy'
5
+ import { usePostList } from 'valaxy'
6
+
7
+ const props = withDefaults(defineProps<{
8
+ type?: string
9
+ posts?: Post[]
10
+ }>(), {
11
+ })
12
+
13
+ const routes = usePostList() as any as Ref<Post[]>
14
+ const posts = computed(() => props.posts || routes.value)
15
+ </script>
16
+
17
+ <template>
18
+ <ul class="divide-y divide-gray-200 dark:divide-gray-700">
19
+ <Transition v-for="post, i in posts" :key="i" name="fade">
20
+ <HairyArticleText :post="post" />
21
+ </Transition>
22
+ </ul>
23
+ </template>
24
+
@@ -0,0 +1,33 @@
1
+ <script lang="ts" setup>
2
+ import { useFrontmatter } from 'valaxy'
3
+ import { computed } from 'vue'
4
+ const props = defineProps<{
5
+ headline?: String
6
+ title: String
7
+ description?: string
8
+ }>()
9
+
10
+ const post = useFrontmatter()
11
+
12
+ const headline = computed(() => post.value.headline || props.headline)
13
+ const title = computed(() => post.value.title || props.title)
14
+ </script>
15
+
16
+ <template>
17
+ <div class="flex-center flex-col text-shadow-lg text-white">
18
+ <div v-if="headline" class="font-frederick text-size-3.35em lt-sm:text-size-3rem leading-snug">
19
+ {{ headline }}
20
+ </div>
21
+ <div class="text-size-2.5em lt-sm:text-size-2rem font-bold title tracking-1">
22
+ {{ title }}
23
+ </div>
24
+ <p v-if="description || $slots.description" class="text-size-sm">
25
+ <template v-if="description">
26
+ {{ description }}
27
+ </template>
28
+ <slot v-else name="description" />
29
+ </p>
30
+ </div>
31
+ </template>
32
+
33
+ <style lang="scss" scoped></style>
@@ -0,0 +1,27 @@
1
+ <script lang="ts" setup>
2
+ import { useConfig } from 'valaxy'
3
+
4
+ const config = useConfig()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="links-of-author grid grid-cols-8 justify-center gap-1">
9
+ <a v-for="item, i in config.social" :key="i" class="links-of-author-item" rel="noopener" :href="item.link" :title="item.name" target="_blank" :style="`color:${item.color}`">
10
+ <div class="icon" :class="item.icon" />
11
+ </a>
12
+ </div>
13
+ </template>
14
+
15
+ <style lang="scss">
16
+ .links-of-author {
17
+ .icon {
18
+ width: 1.5rem;
19
+ height: 1.5rem;
20
+ }
21
+
22
+ &-item {
23
+ line-height: 1;
24
+ font-size: 0.9rem;
25
+ }
26
+ }
27
+ </style>
@@ -0,0 +1,40 @@
1
+ <script lang="ts" setup>
2
+ import type { Post } from 'valaxy'
3
+ import dayjs from 'dayjs'
4
+ import { withDefaults } from 'vue'
5
+ import last from 'lodash/last'
6
+ import { useRouter } from 'vue-router'
7
+ import { useI18n } from 'vue-i18n'
8
+ import { toArr } from '../utils'
9
+ withDefaults(
10
+ defineProps<{
11
+ post: Post
12
+ format: string
13
+ }>(),
14
+ {
15
+ format: 'YYYY-MM-DD',
16
+ },
17
+ )
18
+
19
+ const i18n = useI18n()
20
+
21
+ const router = useRouter()
22
+
23
+ const displayCategory = (keys: string | string[] = []) => {
24
+ router.push({ path: `/categories/${toArr(keys).join('/')}` })
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <div class="mb-1 text-size-3.5 mt-1.5 flex items-center">
30
+ <div class="mr-2.2 dark:text-gray-500 text-gray-400">
31
+ {{ dayjs(post.date).format(format) }}
32
+ </div>
33
+ <HairyLink v-if="post.categories?.length" @click="displayCategory(post.categories)">
34
+ {{ i18n.t(last(toArr(post.categories)) || '') }}
35
+ </HairyLink>
36
+ </div>
37
+ <a class="cursor-pointer text-size-4" @click="$router.push(post.path || '')">{{ post.title }}</a>
38
+ </template>
39
+
40
+ <style lang="scss" scoped></style>
@@ -0,0 +1,135 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+ import type { Header } from 'valaxy'
5
+ import {
6
+ resolveHeaders,
7
+ useActiveAnchor,
8
+ useFrontmatter,
9
+ useThemeConfig,
10
+ } from 'valaxy'
11
+
12
+ const props = defineProps<{ headers?: Header[] }>()
13
+
14
+ const frontmatter = useFrontmatter()
15
+ const themeConfig = useThemeConfig()
16
+
17
+ const { locale } = useI18n()
18
+ const container = ref()
19
+ const marker = ref()
20
+
21
+ useActiveAnchor(container, marker)
22
+
23
+ const resolvedHeaders = computed(() => {
24
+ return resolveHeaders(props.headers || frontmatter.value['it-toc'])
25
+ })
26
+
27
+ function handleClick({ target: el }: Event) {
28
+ const id = `#${(el as HTMLAnchorElement).href!.split('#')[1]}`
29
+ const heading = document.querySelector(decodeURIComponent(id)) as HTMLAnchorElement
30
+ heading?.focus()
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <div v-show="resolvedHeaders.length" ref="container">
36
+ <div class="content">
37
+ <div class="outline-title">
38
+ {{ themeConfig.outlineTitle || 'On this page' }}
39
+ </div>
40
+
41
+ <div ref="marker" class="outline-marker" />
42
+
43
+ <nav aria-labelledby="doc-outline-aria-label">
44
+ <span id="doc-outline-aria-label" class="visually-hidden">
45
+ Table of Contents for current page
46
+ </span>
47
+
48
+ <ul class="va-toc relative z-1">
49
+ <li
50
+ v-for="{ text, link, children, hidden, lang } in resolvedHeaders"
51
+ v-show="!hidden"
52
+ :key="link"
53
+ class="va-toc-item"
54
+ :lang="lang || locale"
55
+ >
56
+ <a class="outline-link" :href="link" @click="handleClick">
57
+ {{ text }}
58
+ </a>
59
+ <ul v-if="children && frontmatter.outline === 'deep'">
60
+ <li v-for="item in children" v-show="!item.hidden" :key="item.link" :lang="lang || locale">
61
+ <a class="outline-link" p="l-3" :href="link" @click="handleClick">
62
+ {{ item.text }}
63
+ </a>
64
+ </li>
65
+ </ul>
66
+ </li>
67
+ </ul>
68
+ </nav>
69
+ </div>
70
+ </div>
71
+ </template>
72
+
73
+ <style lang="scss" scoped>
74
+ .va-toc {
75
+ text-align: left;
76
+
77
+ .va-toc-item {
78
+ color: var(--va-c-text-light);
79
+ }
80
+ }
81
+
82
+ .content {
83
+ position: relative;
84
+ padding-left: 16px;
85
+ font-size: 14px;
86
+ text-align: left;
87
+ }
88
+
89
+ .outline-marker {
90
+ position: absolute;
91
+ top: 32px;
92
+ left: -2px;
93
+ z-index: 0;
94
+ opacity: 0;
95
+ width: 4px;
96
+ height: 18px;
97
+ background-color: var(--va-c-brand);
98
+ transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
99
+ border-top-right-radius: 2px;
100
+ border-bottom-right-radius: 2px;
101
+ }
102
+
103
+ .outline-title {
104
+ letter-spacing: 0.4px;
105
+ line-height: 28px;
106
+ font-size: 14px;
107
+ font-weight: 600;
108
+ }
109
+
110
+ .outline-link {
111
+ display: block;
112
+ line-height: 28px;
113
+ color: var(--va-c-text-light);
114
+ white-space: nowrap;
115
+ overflow: hidden;
116
+ text-overflow: ellipsis;
117
+ transition: color 0.5s;
118
+ }
119
+
120
+ .outline-link:hover,
121
+ .outline-link.active {
122
+ color: var(--va-c-brand);
123
+ transition: color 0.25s;
124
+ }
125
+
126
+ .visually-hidden {
127
+ position: absolute;
128
+ width: 1px;
129
+ height: 1px;
130
+ white-space: nowrap;
131
+ clip: rect(0 0 0 0);
132
+ clip-path: inset(50%);
133
+ overflow: hidden;
134
+ }
135
+ </style>
@@ -0,0 +1,30 @@
1
+ <script lang="tsx" setup>
2
+ import { useConfig, useThemeConfig } from 'valaxy'
3
+ import { computed } from 'vue'
4
+ import type { HairyTheme } from 'valaxy-theme-hairy'
5
+
6
+ const config = useConfig()
7
+ const theme = useThemeConfig<HairyTheme>()
8
+
9
+ const name = computed(() => theme.value.user?.name || config.value.author.name)
10
+ const description = computed(() => theme.value.user?.description || config.value.description)
11
+ </script>
12
+
13
+ <template>
14
+ <div class="pt-5">
15
+ <div class="flex flex-col items-center">
16
+ <img class="mx-auto w-40 rounded-full -mx-1px" :src="config.author.avatar" />
17
+ <div class="leading-loose mt-2">
18
+ {{ name }}
19
+ </div>
20
+ <div class="text-coolgray">
21
+ {{ description }}
22
+ </div>
23
+ </div>
24
+ <HairyUserNav />
25
+ </div>
26
+ </template>
27
+
28
+ <style lang="scss">
29
+
30
+ </style>
@@ -0,0 +1,68 @@
1
+ <script lang="tsx" setup>
2
+ import { useCategory, usePostList, useTag } from 'valaxy'
3
+ import { defineComponent } from 'vue'
4
+ const category = useCategory()
5
+ const posts = usePostList()
6
+ const tags = useTag()
7
+ const State = defineComponent({
8
+ props: {
9
+ count: {
10
+ type: Number,
11
+ default: 0,
12
+ },
13
+ },
14
+ setup: (props, { slots }) => {
15
+ return () => <div class="flex-center flex-col cursor-pointer hover:text-primary px-3 py-1">
16
+ <div class="font-bold leading-none">
17
+ {props.count}
18
+ </div>
19
+ <div class="leading-none mt-1">
20
+ {slots.default?.()}
21
+ </div>
22
+ </div>
23
+ },
24
+ })
25
+ </script>
26
+
27
+ <template>
28
+ <div class="flex justify-center mt-2">
29
+ <State :count="posts.length" @click="$router.push('/archives/')">
30
+ 文章
31
+ </State>
32
+ <div class="w-1px bg-gray bg-opacity-50" />
33
+ <State :count="category.total" @click="$router.push('/categories/')">
34
+ 分类
35
+ </State>
36
+ <div class="w-1px bg-gray bg-opacity-50" />
37
+ <State :count="tags.size" @click="$router.push('/tags/')">
38
+ 标签
39
+ </State>
40
+ </div>
41
+ <HairySocialLinks class="mt-5" />
42
+ <HairyMenu class="HairyUserMenu mt-5 flex-col h-auto" />
43
+ </template>
44
+
45
+ <style lang="scss">
46
+ .HairyUserMenu {
47
+ .HairyMenuItem {
48
+ padding: 2px;
49
+ width: 100%;
50
+ border: 1px solid transparent;
51
+ > div {
52
+ justify-content: center;
53
+ }
54
+ }
55
+
56
+ .HairyMenuItem:hover,
57
+ .HairyMenuItem.active {
58
+ border-top: 1px solid var(--hy-c-primary);
59
+ border-bottom: 1px solid var(--hy-c-primary);
60
+ }
61
+ .HairyMenuItem.active + .HairyMenuItem {
62
+ border-top-color: transparent;
63
+ }
64
+ .HairyMenuItem:hover + .HairyMenuItem {
65
+ border-top-color: transparent;
66
+ }
67
+ }
68
+ </style>
@@ -0,0 +1,40 @@
1
+ <script lang="ts" setup>
2
+ import { ElTabPane, ElTabs } from 'element-plus/es/components/tabs/index'
3
+ import 'element-plus/es/components/tabs/style/index'
4
+ import 'element-plus/es/components/tab-pane/style/index'
5
+ import { ref } from 'vue'
6
+
7
+ const active = ref('aside')
8
+ </script>
9
+
10
+ <template>
11
+ <el-tabs v-model="active" class="pt-3">
12
+ <el-tab-pane name="aside">
13
+ <template #label>
14
+ <div class="flex items-center">
15
+ <div class="i-ri-list-check-2" />
16
+ <span class="ml-1">Aside</span>
17
+ </div>
18
+ </template>
19
+ <HairyToc />
20
+ </el-tab-pane>
21
+ <el-tab-pane label="Series" name="series">
22
+ <template #label>
23
+ <div class="flex items-center gap-1">
24
+ <div class="i-ri-flow-chart" />
25
+ <span class="ml-1">Series</span>
26
+ </div>
27
+ </template>
28
+ <HairyArticleSeries />
29
+ </el-tab-pane>
30
+ <el-tab-pane label="User" name="user">
31
+ <template #label>
32
+ <div class="flex items-center gap-1">
33
+ <div class="i-ri-user-line" />
34
+ <span class="ml-1">User</span>
35
+ </div>
36
+ </template>
37
+ <HairyUserCard />
38
+ </el-tab-pane>
39
+ </el-tabs>
40
+ </template>
@@ -0,0 +1,25 @@
1
+ <script lang="ts" setup>
2
+ import { useConfig } from 'valaxy'
3
+ import { useWaline } from 'valaxy/client/composables/comments/waline'
4
+ // 读取用户配置
5
+ const config = useConfig()
6
+ // 挂载 Waline
7
+ useWaline({
8
+ el: '#waline',
9
+ ...config.value.comment.waline,
10
+ })
11
+ </script>
12
+
13
+ <!-- Also, the documentation doesn't state that we need to install @waline/client, which I think is also an issue -->
14
+ <template>
15
+ <!-- waline html 挂载点,将其写入布局中 -->
16
+ <div id="waline" w="full" />
17
+ </template>
18
+
19
+ <style lang="scss">
20
+ // 可以在此处覆盖 waline 样式
21
+ #waline {
22
+ --waline-theme-color: var(--hy-c-primary);
23
+ --waline-active-color: var(--hy-c-primary-dark)
24
+ }
25
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <svg
3
+ class="waves w-full min-h-100px max-h-150px"
4
+ viewBox="0 24 150 28" preserveAspectRatio="none" shape-rendering="auto"
5
+ >
6
+ <defs>
7
+ <path id="gentle-wave" d="M-160 44c30 0 58-18 88-18s 58 18 88 18 58-18 88-18 58 18 88 18 v44h-352z" />
8
+ </defs>
9
+ <g class="parallax">
10
+ <use class="transition-all duration-200" xlink:href="#gentle-wave" x="48" y="0" fill="var(--hy-c-waves-dimm-1)" />
11
+ <use class="transition-all duration-200" xlink:href="#gentle-wave" x="48" y="3" fill="var(--hy-c-waves-dimm-2)" />
12
+ <use class="transition-all duration-200" xlink:href="#gentle-wave" x="48" y="5" fill="var(--hy-c-waves-dimm-3)" />
13
+ <use class="transition-all duration-200" xlink:href="#gentle-wave" x="48" y="7" fill="var(--hy-c-waves-dimm)" />
14
+ </g>
15
+ </svg>
16
+ </template>
17
+
18
+ <style lang="scss" scoped>
19
+ .waves {
20
+ height: max(100px, min(150px, 15vh));
21
+ margin-bottom: -7px;
22
+ }
23
+
24
+ /* Animation */
25
+ .parallax > use {
26
+ animation: move-forever 25s cubic-bezier(.55, .5, .45, .5) infinite;
27
+ }
28
+
29
+ .parallax > use:nth-child(1) {
30
+ animation-delay: -2s;
31
+ animation-duration: 7s;
32
+ }
33
+
34
+ .parallax > use:nth-child(2) {
35
+ animation-delay: -3s;
36
+ animation-duration: 10s;
37
+ }
38
+
39
+ .parallax > use:nth-child(3) {
40
+ animation-delay: -4s;
41
+ animation-duration: 13s;
42
+ }
43
+
44
+ .parallax > use:nth-child(4) {
45
+ animation-delay: -5s;
46
+ animation-duration: 20s;
47
+ }
48
+
49
+ @keyframes move-forever {
50
+ 0% {
51
+ transform: translate3d(-90px, 0, 0);
52
+ }
53
+ 100% {
54
+ transform: translate3d(85px, 0, 0);
55
+ }
56
+ }
57
+
58
+ /*Shrinking for mobile*/
59
+ @media (max-width: 768px) {
60
+ .waves {
61
+ margin-top: -40px;
62
+ height: 40px;
63
+ min-height: 40px;
64
+ }
65
+
66
+ }
67
+ </style>
@@ -0,0 +1,45 @@
1
+ <script lang="ts" setup>
2
+ import type { PageData, Post } from 'valaxy'
3
+ import { useConfig } from 'valaxy'
4
+
5
+ defineProps<{
6
+ frontmatter: Post
7
+ data?: PageData
8
+ }>()
9
+ const config = useConfig()
10
+ </script>
11
+
12
+ <template>
13
+ <main>
14
+ <div w="full" flex="~">
15
+ <slot name="main">
16
+ <div class="content" flex="~ col grow" w="full" p="l-4 lt-md:0">
17
+ <slot name="main-header" />
18
+ <slot name="main-header-after" />
19
+
20
+ <slot name="main-content">
21
+ <div class="markdown-body prose max-w-none pb-8">
22
+ <ValaxyMd :frontmatter="frontmatter">
23
+ <slot name="main-content-md" />
24
+ <slot />
25
+ </ValaxyMd>
26
+ </div>
27
+ <slot name="main-content-after" />
28
+ </slot>
29
+ </div>
30
+
31
+ <slot name="main-nav-before" />
32
+
33
+ <slot name="main-nav" />
34
+
35
+ <slot name="main-nav-after" />
36
+
37
+ <slot v-if="config.comment.enable && frontmatter.comment !== false" name="comment" />
38
+
39
+ <slot name="footer" />
40
+ </slot>
41
+ </div>
42
+
43
+ <slot name="aside" />
44
+ </main>
45
+ </template>
@@ -0,0 +1,18 @@
1
+ import type { ParentCategory, PostCategory } from 'valaxy'
2
+ import { useCategory } from 'valaxy'
3
+ import type { MaybeRef } from '@vueuse/core'
4
+ import { computed, unref } from 'vue'
5
+
6
+ export function useCurrentCategory(categories: MaybeRef<string[]>) {
7
+ const all = useCategory()
8
+ return computed(() => {
9
+ let parent: ParentCategory & Partial<PostCategory> = all
10
+ for (const category of unref(categories)) {
11
+ for (const [key, value] of parent.children) {
12
+ if (key === category)
13
+ parent = (value as ParentCategory)
14
+ }
15
+ }
16
+ return parent
17
+ })
18
+ }
@@ -0,0 +1,21 @@
1
+ import type { MaybeRef } from '@vueuse/shared'
2
+ import { usePostList } from 'valaxy'
3
+ import isEqual from 'lodash/isEqual'
4
+ import { computed, unref } from 'vue'
5
+
6
+ export function useCategoryPost(categories: MaybeRef<string[]>) {
7
+ const postList = usePostList()
8
+ return computed(() => {
9
+ if (!unref(categories).length)
10
+ return postList.value
11
+ return postList.value.filter((item) => {
12
+ let target = Array.isArray(item.categories)
13
+ ? item.categories
14
+ : [item.categories || '']
15
+ target = target.filter(Boolean)
16
+ if (unref(categories)[0] === 'Uncategorized')
17
+ return !target.length
18
+ return isEqual(target.sort(), unref(categories).sort())
19
+ }) as any
20
+ })
21
+ }
@@ -0,0 +1,16 @@
1
+ import { ref } from 'vue'
2
+ import { createContext } from '../utils/createContext'
3
+
4
+ const HairyContext = createContext('Hairy', () => {
5
+ const headerRef = ref<HTMLDivElement>()
6
+
7
+ return {
8
+ headerRef,
9
+ }
10
+ })
11
+
12
+ export const useContext = () => {
13
+ return HairyContext.inject()
14
+ }
15
+
16
+ export default HairyContext
@@ -0,0 +1,9 @@
1
+ import { useElementSize } from '@vueuse/core'
2
+ import { computed } from 'vue'
3
+ import { useContext } from './useContext'
4
+
5
+ export function useHeaderHeight() {
6
+ const { headerRef } = useContext()
7
+ const size = useElementSize(headerRef)
8
+ return computed(() => size.height.value)
9
+ }
@@ -0,0 +1,9 @@
1
+ import { useThemeConfig } from 'valaxy'
2
+ import { computed } from 'vue'
3
+ import type { HairyTheme } from '..'
4
+
5
+ export function usePostLayout() {
6
+ const themeConfig = useThemeConfig<HairyTheme>()
7
+ const layout = computed(() => themeConfig.value.post?.layout || 'text')
8
+ return layout
9
+ }
@@ -0,0 +1,43 @@
1
+ import type { Post } from 'valaxy'
2
+ import { usePostList } from 'valaxy'
3
+ import { computed } from 'vue'
4
+ import dayjs from 'dayjs'
5
+
6
+ interface ArchiveYearMaps {
7
+ [year: string]: {
8
+ [month: string]: { count: number; posts: Post[] }
9
+ }
10
+ }
11
+ interface ArchiveYearItem {
12
+ year: string
13
+ month: string
14
+ count: number
15
+ posts: Post[]
16
+ }
17
+
18
+ export function useYearArchives() {
19
+ const posts = usePostList()
20
+ const archives = computed(() => {
21
+ const maps: ArchiveYearMaps = {}
22
+ const items: ArchiveYearItem[] = []
23
+ for (const post of posts.value) {
24
+ if (!post.date)
25
+ continue
26
+ const days = dayjs(post.date)
27
+ const [year, month] = [days.format('YYYY'), days.format('MM')]
28
+ if (!maps[year])
29
+ maps[year] = {}
30
+ if (!maps[year][month]) { maps[year][month] = { count: 1, posts: [post] } }
31
+ else {
32
+ maps[year][month].count++
33
+ maps[year][month].posts.push(post)
34
+ }
35
+ }
36
+ for (const [year, months] of Object.entries(maps)) {
37
+ for (const [month, { count, posts }] of Object.entries(months))
38
+ items.push({ year, month, count, posts })
39
+ }
40
+ return items
41
+ })
42
+ return archives
43
+ }