valaxy-theme-hairy 0.0.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/@types/markdown-it.d.ts +1 -0
- package/@types/markdown-toc.d.ts +1 -0
- package/@types/types.d.ts +1 -0
- package/@types/valaxy.d.ts +10 -0
- package/LICENSE +21 -0
- package/components/HairyAlgoliaSearchBox.vue +118 -0
- package/components/HairyArticleImage.vue +17 -0
- package/components/HairyArticleImageDefault.vue +127 -0
- package/components/HairyArticleImageSmall.vue +49 -0
- package/components/HairyArticleSeries.vue +73 -0
- package/components/HairyArticleText.vue +38 -0
- package/components/HairyArticleTop.vue +0 -0
- package/components/HairyBackToTop.vue +72 -0
- package/components/HairyBody.vue +55 -0
- package/components/HairyBreadcrumb.vue +51 -0
- package/components/HairyBreadcrumbItem.vue +14 -0
- package/components/HairyCarousel.vue +45 -0
- package/components/HairyDivider.vue +0 -0
- package/components/HairyHeader.vue +31 -0
- package/components/HairyImage.vue +14 -0
- package/components/HairyImageGroup.vue +69 -0
- package/components/HairyImageViewer.vue +22 -0
- package/components/HairyLayout.vue +29 -0
- package/components/HairyLink.vue +10 -0
- package/components/HairyMenu.vue +15 -0
- package/components/HairyMenuItem.vue +40 -0
- package/components/HairyMeting.vue +19 -0
- package/components/HairyNav.vue +39 -0
- package/components/HairyNavBackground.vue +7 -0
- package/components/HairyNavSearch.vue +265 -0
- package/components/HairyNavTitle.vue +13 -0
- package/components/HairyNavToggleDark.vue +16 -0
- package/components/HairyPostImageList.vue +28 -0
- package/components/HairyPostList.vue +24 -0
- package/components/HairyPostTitle.vue +33 -0
- package/components/HairySocialLinks.vue +27 -0
- package/components/HairyTimelinePostItem.vue +40 -0
- package/components/HairyToc.vue +135 -0
- package/components/HairyUserCard.vue +30 -0
- package/components/HairyUserNav.vue +68 -0
- package/components/HairyUserTab.vue +40 -0
- package/components/HairyWaline.vue +25 -0
- package/components/HairyWaves.vue +67 -0
- package/components/ValaxyMain.vue +45 -0
- package/hooks/useCategory.ts +18 -0
- package/hooks/useCategoryPost.ts +21 -0
- package/hooks/useContext.ts +16 -0
- package/hooks/useHeaderHeight.ts +9 -0
- package/hooks/usePostLayout.ts +9 -0
- package/hooks/useYearArchives.ts +43 -0
- package/images.json +101 -0
- package/index.d.ts +54 -0
- package/layouts/archives.vue +11 -0
- package/layouts/categories.vue +6 -0
- package/layouts/default.vue +9 -0
- package/layouts/home.vue +23 -0
- package/layouts/month.vue +6 -0
- package/layouts/post.vue +26 -0
- package/layouts/tag.vue +6 -0
- package/layouts/tags.vue +6 -0
- package/layouts/year.vue +6 -0
- package/locales/en.yml +1 -0
- package/locales/zh-CN.yml +3 -0
- package/modules/context.ts +5 -0
- package/modules/loading.scss +531 -0
- package/modules/loading.ts +42 -0
- package/modules/scroll.ts +21 -0
- package/node/addon-hairy.ts +18 -0
- package/node/addon-images.ts +61 -0
- package/node/addon-meting.ts +13 -0
- package/node/addon-statistics.ts +19 -0
- package/node/addon-toc.ts +20 -0
- package/node/utils.ts +20 -0
- package/package.json +47 -0
- package/pages/archives/[year]/[month]/index.vue +52 -0
- package/pages/archives/[year]/index.vue +73 -0
- package/pages/archives/index.vue +53 -0
- package/pages/categories/[...categories].vue +115 -0
- package/pages/index.vue +14 -0
- package/pages/tags/[tag].vue +40 -0
- package/pages/tags/index.vue +31 -0
- package/setup/main.ts +11 -0
- package/shims.d.ts +8 -0
- package/styles/css-vars.scss +161 -0
- package/styles/element-plus/index.scss +2 -0
- package/styles/element-plus/tabs.scss +26 -0
- package/styles/element-plus/timeline.scss +19 -0
- package/styles/font-face.scss +20 -0
- package/styles/fonts/FrederickatheGreat.ttf +0 -0
- package/styles/fonts/Modesty.ttf +0 -0
- package/styles/fonts/MountainsofChristmas-Bold.ttf +0 -0
- package/styles/fonts/MountainsofChristmas-Regular.ttf +0 -0
- package/styles/fonts/Seto.ttf +0 -0
- package/styles/index.scss +65 -0
- package/styles/markdown.scss +60 -0
- package/styles/scrollbar.scss +26 -0
- package/unocss.config.ts +29 -0
- package/utils/createContext.ts +40 -0
- package/utils/index.ts +28 -0
- package/utils/loading.scss +531 -0
- package/utils/loading.ts +30 -0
- 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
|
+
}
|