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 @@
|
|
1
|
+
declare module "*-markdown-it"
|
@@ -0,0 +1 @@
|
|
1
|
+
declare module "markdown-toc"
|
@@ -0,0 +1 @@
|
|
1
|
+
declare const __ALGOLIA__: boolean
|
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2022 云游君
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,118 @@
|
|
1
|
+
<script setup lang="ts">
|
2
|
+
import docsearch from '@docsearch/js'
|
3
|
+
import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
|
4
|
+
import { onMounted } from 'vue'
|
5
|
+
import type { AlgoliaSearchOptions } from 'valaxy'
|
6
|
+
import { useConfig } from 'valaxy'
|
7
|
+
import { useRoute, useRouter } from 'vue-router'
|
8
|
+
|
9
|
+
const router = useRouter()
|
10
|
+
const route = useRoute()
|
11
|
+
const config = useConfig()
|
12
|
+
|
13
|
+
onMounted(() => {
|
14
|
+
initialize(config.value.search.algolia)
|
15
|
+
setTimeout(poll, 16)
|
16
|
+
})
|
17
|
+
|
18
|
+
/**
|
19
|
+
* poll until open
|
20
|
+
*/
|
21
|
+
function poll() {
|
22
|
+
// programmatically open the search box after initialize
|
23
|
+
const e = new Event('keydown') as any
|
24
|
+
|
25
|
+
e.key = 'k'
|
26
|
+
e.metaKey = true
|
27
|
+
|
28
|
+
window.dispatchEvent(e)
|
29
|
+
|
30
|
+
setTimeout(() => {
|
31
|
+
if (!document.querySelector('.DocSearch-Modal'))
|
32
|
+
poll()
|
33
|
+
}, 16)
|
34
|
+
}
|
35
|
+
|
36
|
+
function initialize(userOptions: AlgoliaSearchOptions) {
|
37
|
+
// note: multi-lang search support is removed since the theme
|
38
|
+
// doesn't support multiple locales as of now.
|
39
|
+
const options = Object.assign({}, userOptions, {
|
40
|
+
container: '#docsearch',
|
41
|
+
navigator: {
|
42
|
+
navigate({ itemUrl }: { itemUrl: string }) {
|
43
|
+
const { pathname: hitPathname } = new URL(
|
44
|
+
window.location.origin + itemUrl,
|
45
|
+
)
|
46
|
+
// router doesn't handle same-page navigation so we use the native
|
47
|
+
// browser location API for anchor navigation
|
48
|
+
if (route.path === hitPathname)
|
49
|
+
window.location.assign(window.location.origin + itemUrl)
|
50
|
+
|
51
|
+
else
|
52
|
+
router.push(itemUrl)
|
53
|
+
},
|
54
|
+
},
|
55
|
+
transformItems(items: DocSearchHit[]) {
|
56
|
+
return items.map((item) => {
|
57
|
+
return Object.assign({}, item, {
|
58
|
+
url: getRelativePath(item.url),
|
59
|
+
})
|
60
|
+
})
|
61
|
+
},
|
62
|
+
hitComponent({ hit, children }: { hit: DocSearchHit; children: any }) {
|
63
|
+
const relativeHit = hit.url.startsWith('http')
|
64
|
+
? getRelativePath(hit.url as string)
|
65
|
+
: hit.url
|
66
|
+
return {
|
67
|
+
__v: null,
|
68
|
+
type: 'a',
|
69
|
+
ref: undefined,
|
70
|
+
constructor: undefined,
|
71
|
+
key: undefined,
|
72
|
+
props: {
|
73
|
+
href: hit.url,
|
74
|
+
onClick(event: MouseEvent) {
|
75
|
+
if (isSpecialClick(event))
|
76
|
+
return
|
77
|
+
|
78
|
+
// we rely on the native link scrolling when user is already on
|
79
|
+
// the right anchor because Router doesn't support duplicated
|
80
|
+
// history entries.
|
81
|
+
if (route.path === relativeHit)
|
82
|
+
return
|
83
|
+
|
84
|
+
// if the hits goes to another page, we prevent the native link
|
85
|
+
// behavior to leverage the Router loading feature.
|
86
|
+
if (route.path !== relativeHit)
|
87
|
+
event.preventDefault()
|
88
|
+
|
89
|
+
router.push(relativeHit)
|
90
|
+
},
|
91
|
+
children,
|
92
|
+
},
|
93
|
+
}
|
94
|
+
},
|
95
|
+
})
|
96
|
+
docsearch(options as any)
|
97
|
+
}
|
98
|
+
|
99
|
+
function isSpecialClick(event: MouseEvent) {
|
100
|
+
return (
|
101
|
+
event.button === 1
|
102
|
+
|| event.altKey
|
103
|
+
|| event.ctrlKey
|
104
|
+
|| event.metaKey
|
105
|
+
|| event.shiftKey
|
106
|
+
)
|
107
|
+
}
|
108
|
+
|
109
|
+
function getRelativePath(absoluteUrl: string) {
|
110
|
+
const { pathname, hash } = new URL(absoluteUrl)
|
111
|
+
return pathname + hash
|
112
|
+
}
|
113
|
+
</script>
|
114
|
+
|
115
|
+
<template>
|
116
|
+
<div id="docsearch" />
|
117
|
+
</template>
|
118
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import type { Post } from 'valaxy'
|
3
|
+
|
4
|
+
defineProps<{
|
5
|
+
post: Post
|
6
|
+
reverse?: boolean
|
7
|
+
}>()
|
8
|
+
</script>
|
9
|
+
|
10
|
+
<template>
|
11
|
+
<HairyArticleImageDefault :post="post" :reverse="reverse" class="hidden sm:block" />
|
12
|
+
<HairyArticleImageSmall :post="post" :reverse="reverse" class="hidden lt-sm:block" />
|
13
|
+
</template>
|
14
|
+
|
15
|
+
<style lang="scss">
|
16
|
+
|
17
|
+
</style>
|
@@ -0,0 +1,127 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import type { Post } from 'valaxy'
|
3
|
+
import { computed, defineProps } from 'vue'
|
4
|
+
import dayjs from 'dayjs'
|
5
|
+
import { useRouter } from 'vue-router'
|
6
|
+
import { useI18n } from 'vue-i18n'
|
7
|
+
import last from 'lodash/last'
|
8
|
+
import { usePostLayout } from '../hooks/usePostLayout'
|
9
|
+
import { toArr } from '../utils'
|
10
|
+
|
11
|
+
const props = defineProps<{
|
12
|
+
post: Post
|
13
|
+
reverse?: boolean
|
14
|
+
}>()
|
15
|
+
const router = useRouter()
|
16
|
+
const layout = usePostLayout()
|
17
|
+
const slice = computed(() => layout.value.includes('slice'))
|
18
|
+
const image = computed(() => props.post.image)
|
19
|
+
|
20
|
+
const i18n = useI18n()
|
21
|
+
|
22
|
+
const onReadMore = () => {
|
23
|
+
if (props.post.path)
|
24
|
+
router.push(props.post.path)
|
25
|
+
}
|
26
|
+
|
27
|
+
const displayCategory = (keys: string | string[] = []) => {
|
28
|
+
router.push({ path: `/categories/${toArr(keys).join('/')}` })
|
29
|
+
}
|
30
|
+
</script>
|
31
|
+
|
32
|
+
<template>
|
33
|
+
<li class="HairyArticleImage my-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
|
34
|
+
<article>
|
35
|
+
<div class="flex justify-between items-center">
|
36
|
+
<a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-first' : 'order-last']" @click="onReadMore">{{ post.title }}</a>
|
37
|
+
<div class="flex justify-end gap-2 text-size-sm">
|
38
|
+
<span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
39
|
+
<span>{{ (post.length / 1000).toFixed(1) }}k字</span>
|
40
|
+
<span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
<div class="h-200px flex">
|
44
|
+
<div class="flex-1 post-image-content" :class="[reverse ? 'order-last' : 'order-first']">
|
45
|
+
<img
|
46
|
+
class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image"
|
47
|
+
@click="onReadMore"
|
48
|
+
/>
|
49
|
+
</div>
|
50
|
+
<div class="flex flex-col justify-between flex-1">
|
51
|
+
<div class="flex-1 text-size-sm">
|
52
|
+
<div class="line-clamp-text">
|
53
|
+
{{ post.text }}
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
<div class="flex justify-between items-center">
|
57
|
+
<a class="cursor-pointer" :class="[reverse && 'order-1']">
|
58
|
+
<span v-if="post.categories?.length" @click="displayCategory(post.categories)">
|
59
|
+
{{ i18n.t(last(toArr(post.categories)) || '') }}
|
60
|
+
</span>
|
61
|
+
</a>
|
62
|
+
<div class="text-base leading-6 font-medium">
|
63
|
+
<a class="link cursor-pointer" aria-label="read more" @click="onReadMore">
|
64
|
+
<span v-if="reverse">←</span>
|
65
|
+
Read more
|
66
|
+
<span v-if="!reverse">→</span>
|
67
|
+
</a>
|
68
|
+
</div>
|
69
|
+
</div>
|
70
|
+
</div>
|
71
|
+
</div>
|
72
|
+
</article>
|
73
|
+
</li>
|
74
|
+
</template>
|
75
|
+
|
76
|
+
<style lang="scss" scoped>
|
77
|
+
.line-clamp-text {
|
78
|
+
word-break: break-all;
|
79
|
+
overflow: hidden;
|
80
|
+
text-overflow: ellipsis;
|
81
|
+
display: -webkit-box;
|
82
|
+
text-overflow: ellipsis;
|
83
|
+
-webkit-box-orient: vertical;
|
84
|
+
-webkit-line-clamp: 5;
|
85
|
+
}
|
86
|
+
|
87
|
+
.dark {
|
88
|
+
.post-image {
|
89
|
+
@apply opacity-75 hover: opacity-90 duration-200;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
.post-image-content {
|
94
|
+
margin-right: 1rem;
|
95
|
+
}
|
96
|
+
|
97
|
+
.slice {
|
98
|
+
.post-image-content {
|
99
|
+
webkit-clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
|
100
|
+
clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
|
101
|
+
border-radius: 0.625rem 0 0 0.625rem;
|
102
|
+
overflow: hidden;
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
.reverse {
|
107
|
+
.post-image-content {
|
108
|
+
margin-right: 0;
|
109
|
+
margin-left: 1rem;
|
110
|
+
clip-path: polygon(0 0,100% 0,100% 100%,8% 100%);
|
111
|
+
border-radius: 0 0.625rem 0.625rem 0;
|
112
|
+
overflow: hidden;
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
.HairyArticleImage.slice {
|
117
|
+
.post-image {
|
118
|
+
@apply transition-all;
|
119
|
+
}
|
120
|
+
|
121
|
+
&:hover {
|
122
|
+
.post-image {
|
123
|
+
transform: scale(1.05) rotate(1deg);
|
124
|
+
}
|
125
|
+
}
|
126
|
+
}
|
127
|
+
</style>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import type { Post } from 'valaxy'
|
3
|
+
import { computed, defineProps } from 'vue'
|
4
|
+
import dayjs from 'dayjs'
|
5
|
+
import { useRouter } from 'vue-router'
|
6
|
+
import { useI18n } from 'vue-i18n'
|
7
|
+
import last from 'lodash/last'
|
8
|
+
import { usePostLayout } from '../hooks/usePostLayout'
|
9
|
+
import { toArr } from '../utils'
|
10
|
+
|
11
|
+
const props = defineProps<{
|
12
|
+
post: Post
|
13
|
+
reverse?: boolean
|
14
|
+
}>()
|
15
|
+
const router = useRouter()
|
16
|
+
const layout = usePostLayout()
|
17
|
+
const slice = computed(() => layout.value.includes('slice'))
|
18
|
+
const image = computed(() => props.post.image)
|
19
|
+
|
20
|
+
const i18n = useI18n()
|
21
|
+
|
22
|
+
const onReadMore = () => {
|
23
|
+
if (props.post.path)
|
24
|
+
router.push(props.post.path)
|
25
|
+
}
|
26
|
+
|
27
|
+
const displayCategory = (keys: string | string[] = []) => {
|
28
|
+
router.push({ path: `/categories/${toArr(keys).join('/')}` })
|
29
|
+
}
|
30
|
+
</script>
|
31
|
+
|
32
|
+
<template>
|
33
|
+
<li class="HairyArticleImage my-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
|
34
|
+
<article>
|
35
|
+
<div class="flex justify-between items-center">
|
36
|
+
<a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-first' : 'order-last']" @click="onReadMore">{{ post.title }}</a>
|
37
|
+
<div class="flex justify-end gap-2 text-size-sm">
|
38
|
+
<span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
39
|
+
<span>{{ (post.length / 1000).toFixed(1) }}k字</span>
|
40
|
+
<span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
</article>
|
44
|
+
</li>
|
45
|
+
</template>
|
46
|
+
|
47
|
+
<style lang="scss">
|
48
|
+
|
49
|
+
</style>
|
@@ -0,0 +1,73 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useFrontmatter } from 'valaxy'
|
3
|
+
import { computed } from 'vue'
|
4
|
+
import { toArr } from '../utils'
|
5
|
+
import { useCurrentCategory } from '../hooks/useCategory'
|
6
|
+
|
7
|
+
const frontmatter = useFrontmatter()
|
8
|
+
const paths = computed(() => toArr(frontmatter.value.categories).filter(Boolean) as string[])
|
9
|
+
const category = useCurrentCategory(paths)
|
10
|
+
const posts = computed(() => category.value.posts || [])
|
11
|
+
|
12
|
+
function isCurrent(title = '') {
|
13
|
+
return frontmatter.value.title === title
|
14
|
+
}
|
15
|
+
</script>
|
16
|
+
|
17
|
+
<template>
|
18
|
+
<div class="pl-16px text-14px relative overflow-hidden">
|
19
|
+
<div class="outline-title">
|
20
|
+
On this Series
|
21
|
+
</div>
|
22
|
+
<ul class="va-toc relative z-1">
|
23
|
+
<a v-for="(item, index) of posts" :key="index" class="va-toc-item" @click="$router.push(item.path || '')">
|
24
|
+
<a class="outline-link" :class="[isCurrent(item.title) && 'active']">{{ index + 1 }}.{{ item.title }}</a>
|
25
|
+
</a>
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
</template>
|
29
|
+
|
30
|
+
<style lang="scss" scoped>
|
31
|
+
.outline-title {
|
32
|
+
letter-spacing: 0.4px;
|
33
|
+
line-height: 28px;
|
34
|
+
font-size: 14px;
|
35
|
+
font-weight: 600;
|
36
|
+
}
|
37
|
+
|
38
|
+
.outline-link {
|
39
|
+
display: block;
|
40
|
+
position: relative;
|
41
|
+
line-height: 28px;
|
42
|
+
color: var(--va-c-text-light);
|
43
|
+
white-space: nowrap;
|
44
|
+
text-overflow: ellipsis;
|
45
|
+
transition: color 0.5s;
|
46
|
+
cursor: pointer;
|
47
|
+
|
48
|
+
&:hover {
|
49
|
+
color: var(--va-c-brand);
|
50
|
+
transition: color 0.25s;
|
51
|
+
}
|
52
|
+
|
53
|
+
&.active {
|
54
|
+
color: var(--va-c-brand);
|
55
|
+
transition: color .25s;
|
56
|
+
|
57
|
+
&::after {
|
58
|
+
position: absolute;
|
59
|
+
content: '';
|
60
|
+
left: -1.12rem;
|
61
|
+
top: 0;
|
62
|
+
bottom: 0;
|
63
|
+
margin: auto;
|
64
|
+
width: 4px;
|
65
|
+
height: 18px;
|
66
|
+
background-color: var(--va-c-brand);
|
67
|
+
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
|
68
|
+
border-top-right-radius: 2px;
|
69
|
+
border-bottom-right-radius: 2px;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
</style>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useThemeConfig } from 'valaxy'
|
3
|
+
import type { Post } from 'valaxy'
|
4
|
+
import { computed, defineProps } from 'vue'
|
5
|
+
import type { HairyTheme } from '..'
|
6
|
+
const props = defineProps<{
|
7
|
+
post: Post
|
8
|
+
}>()
|
9
|
+
const themeConfig = useThemeConfig<HairyTheme>()
|
10
|
+
const layout = computed(() => themeConfig.value.post?.layout || 'text')
|
11
|
+
const text = computed(() => {
|
12
|
+
if (layout.value === 'text')
|
13
|
+
return props.post.text
|
14
|
+
return props.post.excerpt
|
15
|
+
})
|
16
|
+
</script>
|
17
|
+
|
18
|
+
<template>
|
19
|
+
<li class="py-12">
|
20
|
+
<article class="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline">
|
21
|
+
<div class="space-y-5 xl:col-span-4">
|
22
|
+
<div class="space-y-6">
|
23
|
+
<h2 class="text-2xl leading-8 font-bold tracking-tight">
|
24
|
+
<a class="st-text" :href="post.path">{{ post.title }}</a>
|
25
|
+
</h2>
|
26
|
+
<div
|
27
|
+
v-if="text"
|
28
|
+
class="prose max-w-none text-gray-500"
|
29
|
+
v-html="text"
|
30
|
+
/>
|
31
|
+
</div>
|
32
|
+
<div class="text-base leading-6 font-medium">
|
33
|
+
<a class="link" aria-label="read more" :href="post.path">Read more →</a>
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
</article>
|
37
|
+
</li>
|
38
|
+
</template>
|
File without changes
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { computed } from 'vue'
|
3
|
+
import { useBackToTop } from 'valaxy'
|
4
|
+
|
5
|
+
const { show, percentage } = useBackToTop({ offset: 100 })
|
6
|
+
|
7
|
+
const radius = 48
|
8
|
+
const circumference = 2 * radius * Math.PI
|
9
|
+
|
10
|
+
const strokeOffset = computed(() => {
|
11
|
+
// 周长
|
12
|
+
const val = (1 - percentage.value) * circumference
|
13
|
+
return val < 0 ? 0 : val
|
14
|
+
})
|
15
|
+
|
16
|
+
function toTop() {
|
17
|
+
window.scrollTo({ top: 0, behavior: 'smooth' })
|
18
|
+
}
|
19
|
+
</script>
|
20
|
+
|
21
|
+
<template>
|
22
|
+
<a class="back-to-top cursor-pointer" :class="show && 'show'" @click="toTop">
|
23
|
+
<div w="8" h="8" i-ri-arrow-up-s-line />
|
24
|
+
<svg class="progress-circle-container" viewBox="0 0 100 100">
|
25
|
+
<circle :stroke-dasharray="`${circumference} ${circumference}`" :stroke-dashoffset="strokeOffset" class="progress-circle" cx="50" cy="50" :r="radius" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
26
|
+
</svg>
|
27
|
+
</a>
|
28
|
+
</template>
|
29
|
+
|
30
|
+
<style lang="scss">
|
31
|
+
@use "sass:map";
|
32
|
+
|
33
|
+
.back-to-top {
|
34
|
+
position: fixed;
|
35
|
+
width: 32px;
|
36
|
+
height: 32px;
|
37
|
+
right: 0rem;
|
38
|
+
bottom: 1.5rem;
|
39
|
+
z-index: 10;
|
40
|
+
opacity: 0;
|
41
|
+
pointer-events: none;
|
42
|
+
|
43
|
+
color: var(--hy-c-primary);
|
44
|
+
transform: translateX(0) rotate(270deg);
|
45
|
+
// override yun-icon-btn transition
|
46
|
+
transition: transform var(--va-transition-duration), opacity var(--va-transition-duration-fast) !important;
|
47
|
+
|
48
|
+
&.show {
|
49
|
+
transform: translateX(-32px) rotate(360deg);
|
50
|
+
opacity: 1;
|
51
|
+
pointer-events: fill;
|
52
|
+
}
|
53
|
+
|
54
|
+
.icon {
|
55
|
+
width: 2.5rem;
|
56
|
+
height: 2.5rem;
|
57
|
+
}
|
58
|
+
> div {
|
59
|
+
margin-bottom: -2em;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
.progress-circle {
|
64
|
+
transition: 0.3s stroke-dashoffset;
|
65
|
+
transform: rotate(-90deg);
|
66
|
+
transform-origin: 50% 50%;
|
67
|
+
|
68
|
+
&-container {
|
69
|
+
position: absolute;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
</style>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useFrontmatter } from 'valaxy'
|
3
|
+
import { computed } from 'vue'
|
4
|
+
import { useRoute } from 'vue-router'
|
5
|
+
const fr = useFrontmatter()
|
6
|
+
const route = useRoute()
|
7
|
+
|
8
|
+
const showWaline = computed(() => route.path.includes('/posts/') || fr.value.waline)
|
9
|
+
</script>
|
10
|
+
|
11
|
+
<template>
|
12
|
+
<div class="HairyBody min-h-50vh relative">
|
13
|
+
<div class="mx-auto breakpoint flex z-1 relative">
|
14
|
+
<div class="relative flex-1 pt-2">
|
15
|
+
<slot />
|
16
|
+
<HairyWaline v-if="showWaline" />
|
17
|
+
</div>
|
18
|
+
<div class="ml-4 w-60 lg:block hidden">
|
19
|
+
<div class="sticky top-3.125rem z-1">
|
20
|
+
<slot v-if="$slots.slide" name="slide" />
|
21
|
+
<HairyUserCard v-else />
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
<div class="HairyBodyBackground" />
|
26
|
+
</div>
|
27
|
+
</template>
|
28
|
+
|
29
|
+
<style lang="scss">
|
30
|
+
.a {
|
31
|
+
overflow: hidden;
|
32
|
+
}
|
33
|
+
|
34
|
+
.HairyBodyBackground {
|
35
|
+
@apply transition-all duration-200;
|
36
|
+
@apply absolute top-0 max-h-150vh top-5 bottom-0 w-full transition-opacity;
|
37
|
+
opacity: 0;
|
38
|
+
}
|
39
|
+
|
40
|
+
.dark {
|
41
|
+
.HairyBodyBackground {
|
42
|
+
transition-delay: 200ms;
|
43
|
+
transition-delay: 0;
|
44
|
+
opacity: 1;
|
45
|
+
background-image:
|
46
|
+
linear-gradient(to bottom, var(--hy-c-waves-dimm) 0%, transparent 60%, var(--hy-c-waves-dimm) 100%),
|
47
|
+
url(https://tva2.sinaimg.cn/large/008ugSUaly8h4mt4lbuc0j31hc0u040c.jpg);
|
48
|
+
background-position: center;
|
49
|
+
opacity: 0.4;
|
50
|
+
background-repeat: no-repeat;
|
51
|
+
filter: blur(0px);
|
52
|
+
background-size: cover;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
</style>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { ElBreadcrumb, breadcrumbProps } from 'element-plus/es/components/breadcrumb/index'
|
3
|
+
import 'element-plus/es/components/breadcrumb/style/index'
|
4
|
+
import type { PropType } from 'vue'
|
5
|
+
import { computed } from 'vue'
|
6
|
+
|
7
|
+
const props = defineProps({
|
8
|
+
...breadcrumbProps,
|
9
|
+
size: String as PropType<'default' | 'large'>,
|
10
|
+
after: String,
|
11
|
+
})
|
12
|
+
const text = computed(() => `"${props.after || ''}"`)
|
13
|
+
</script>
|
14
|
+
|
15
|
+
<template>
|
16
|
+
<ElBreadcrumb :class="[size]" :style="{ '--after-text': text }" v-bind="$props">
|
17
|
+
<slot />
|
18
|
+
</ElBreadcrumb>
|
19
|
+
</template>
|
20
|
+
|
21
|
+
<style lang="scss">
|
22
|
+
.el-breadcrumb {
|
23
|
+
overflow: hidden;
|
24
|
+
display: inline-block;
|
25
|
+
position: relative;
|
26
|
+
}
|
27
|
+
.el-breadcrumb .el-breadcrumb__inner,
|
28
|
+
.el-breadcrumb .el-breadcrumb__item:last-child .el-breadcrumb__inner {
|
29
|
+
color: initial;
|
30
|
+
}
|
31
|
+
.el-breadcrumb .el-breadcrumb__inner.is-link {
|
32
|
+
@apply border-b border-dashed hover:border-primary hover:text-primary transition-all cursor-pointer;
|
33
|
+
font-weight: normal;
|
34
|
+
}
|
35
|
+
|
36
|
+
.el-breadcrumb::after {
|
37
|
+
@apply text-gray-5 text-size-5;
|
38
|
+
content: var(--after-text);
|
39
|
+
clear: none;
|
40
|
+
transform: translateX(2px);
|
41
|
+
}
|
42
|
+
.el-breadcrumb.large {
|
43
|
+
margin-top: 3rem;
|
44
|
+
font-size: 2.5em;
|
45
|
+
@apply lt-md:text-3xl lt-sm:text-xl;
|
46
|
+
.el-breadcrumb__separator {
|
47
|
+
@apply text-size-5 text-gray-5;
|
48
|
+
font-weight: normal;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
</style>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { ElBreadcrumbItem, breadcrumbItemProps } from 'element-plus/es/components/breadcrumb/index'
|
3
|
+
defineProps(breadcrumbItemProps)
|
4
|
+
</script>
|
5
|
+
|
6
|
+
<template>
|
7
|
+
<ElBreadcrumbItem v-bind="$props">
|
8
|
+
<slot />
|
9
|
+
</ElBreadcrumbItem>
|
10
|
+
</template>
|
11
|
+
|
12
|
+
<style lang="scss" scoped>
|
13
|
+
|
14
|
+
</style>
|