valaxy-theme-hairy 0.2.2 → 1.0.1
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 +17 -0
- package/components/{HairyUserPopup.vue → HairyDrawer.vue} +16 -16
- package/components/HairyFooter.vue +12 -9
- package/components/HairyHeader.vue +10 -9
- package/components/HairyImage.vue +3 -2
- package/components/HairyImageGroup.vue +12 -16
- package/components/HairyNavbar.vue +56 -0
- package/components/HairyPageArchives.vue +59 -0
- package/components/HairyPageTags.vue +48 -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/HairyTabbar.vue +56 -0
- 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} +2 -2
- 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} +5 -4
- 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/archives.vue +9 -9
- package/layouts/categories.vue +11 -4
- package/layouts/default.vue +9 -3
- package/layouts/home.vue +28 -18
- package/layouts/post.vue +42 -36
- package/layouts/tag.vue +8 -4
- package/layouts/tags.vue +12 -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 -18
- package/pages/archives/[year]/index.vue +20 -20
- package/pages/archives/index.vue +1 -54
- package/pages/categories/{[...categories].vue → [...its].vue} +29 -36
- package/pages/index.vue +1 -1
- package/pages/page/[page].vue +2 -2
- package/pages/tags/[tag]/index.vue +38 -0
- package/pages/tags/index.vue +1 -36
- 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/HairyUserTab.vue +0 -40
- 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/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/pages/tags/[tag].vue +0 -40
- 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,21 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
defineProps<{
|
3
|
+
type?: 'white' | 'normal'
|
4
|
+
bordered?: boolean
|
5
|
+
}>()
|
6
|
+
</script>
|
7
|
+
|
8
|
+
<template>
|
9
|
+
<a
|
10
|
+
class="cursor-pointer" :class="[
|
11
|
+
type === 'white'
|
12
|
+
? 'text-black hover:text-primary dark:text-white'
|
13
|
+
: 'text:text-primary-light hover:text-primary-dark',
|
14
|
+
bordered && 'border-b border-dashed hover:border-primary',
|
15
|
+
]"
|
16
|
+
>
|
17
|
+
<slot />
|
18
|
+
</a>
|
19
|
+
</template>
|
20
|
+
|
21
|
+
<style lang="scss" scoped></style>
|
@@ -2,7 +2,8 @@
|
|
2
2
|
import type { HairyTheme } from 'valaxy-theme-hairy'
|
3
3
|
import { useThemeConfig } from 'valaxy'
|
4
4
|
import { computed } from 'vue'
|
5
|
-
|
5
|
+
|
6
|
+
const themeConfig = useThemeConfig<HairyTheme.Config>()
|
6
7
|
const nav = computed(() => themeConfig.value.nav || [])
|
7
8
|
</script>
|
8
9
|
|
@@ -2,21 +2,28 @@
|
|
2
2
|
import type { NavItem } from 'valaxy-theme-hairy'
|
3
3
|
import { computed } from 'vue'
|
4
4
|
import { useRoute, useRouter } from 'vue-router'
|
5
|
-
import {
|
5
|
+
import { storeToRefs } from 'pinia'
|
6
|
+
import { ejectWindow } from '../../utils'
|
7
|
+
import { useGlobalStore } from '../../store'
|
8
|
+
|
6
9
|
const props = defineProps<{
|
7
10
|
item: NavItem
|
8
11
|
}>()
|
12
|
+
const { showDrawer } = storeToRefs(useGlobalStore())
|
9
13
|
const urlReg = /^(((ht|f)tps?):\/\/)?([^!@#$%^&*?.\s-]([^!@#$%^&*?.\s]{0,63}[^!@#$%^&*?.\s])?\.)+[a-z]{2,6}\/?/
|
10
14
|
const isLink = computed(() => urlReg.test(props.item?.link || ''))
|
11
15
|
const isPointer = computed(() => Boolean(props.item.link) || isLink.value)
|
12
16
|
const router = useRouter()
|
13
17
|
const route = useRoute()
|
14
|
-
|
18
|
+
|
19
|
+
function toLink() {
|
15
20
|
if (isLink.value)
|
16
21
|
return ejectWindow(props.item.link!)
|
17
22
|
if (props.item.link)
|
18
23
|
router.push(props.item.link)
|
24
|
+
showDrawer.value = false
|
19
25
|
}
|
26
|
+
|
20
27
|
const isActive = computed(() => {
|
21
28
|
if (isLink.value)
|
22
29
|
return false
|
@@ -27,14 +34,14 @@ const isActive = computed(() => {
|
|
27
34
|
</script>
|
28
35
|
|
29
36
|
<template>
|
30
|
-
<
|
37
|
+
<button class="px-2.5 HairyMenuItem" :class="[isPointer ? 'cursor-pointer' : 'select-none', isActive && 'text-primary active']">
|
31
38
|
<div class="flex items-center hover:text-primary" @click="toLink">
|
32
39
|
<div v-if="item.icon" class="mr-1 icon" :class="item.icon" />
|
33
40
|
<div class="question">
|
34
41
|
{{ item.text }}
|
35
42
|
</div>
|
36
43
|
</div>
|
37
|
-
</
|
44
|
+
</button>
|
38
45
|
</template>
|
39
46
|
|
40
47
|
<style lang="scss" scoped></style>
|
@@ -0,0 +1,99 @@
|
|
1
|
+
<script setup lang="ts">
|
2
|
+
import { ref } from 'vue'
|
3
|
+
import {
|
4
|
+
useActiveAnchor,
|
5
|
+
} from 'valaxy'
|
6
|
+
import { useOutline } from '../../composables'
|
7
|
+
|
8
|
+
const container = ref()
|
9
|
+
const marker = ref()
|
10
|
+
|
11
|
+
useActiveAnchor(container, marker)
|
12
|
+
|
13
|
+
const { headers, handleClick } = useOutline()
|
14
|
+
</script>
|
15
|
+
|
16
|
+
<template>
|
17
|
+
<div v-show="headers.length" ref="container">
|
18
|
+
<div class="content">
|
19
|
+
<div class="outline-title">
|
20
|
+
{{ 'On this page' }}
|
21
|
+
</div>
|
22
|
+
|
23
|
+
<div ref="marker" class="outline-marker" />
|
24
|
+
|
25
|
+
<nav aria-labelledby="doc-outline-aria-label">
|
26
|
+
<span id="doc-outline-aria-label" class="visually-hidden">
|
27
|
+
Table of Contents for current page
|
28
|
+
</span>
|
29
|
+
|
30
|
+
<HairyOutlineItem
|
31
|
+
class="va-toc relative z-1 css-i18n-toc"
|
32
|
+
:headers="headers"
|
33
|
+
:on-click="handleClick"
|
34
|
+
root
|
35
|
+
/>
|
36
|
+
</nav>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</template>
|
40
|
+
|
41
|
+
<style lang="scss" scoped>
|
42
|
+
.va-toc {
|
43
|
+
text-align: left;
|
44
|
+
}
|
45
|
+
|
46
|
+
.content {
|
47
|
+
position: relative;
|
48
|
+
padding-left: 16px;
|
49
|
+
font-size: 14px;
|
50
|
+
text-align: left;
|
51
|
+
}
|
52
|
+
|
53
|
+
.outline-marker {
|
54
|
+
position: absolute;
|
55
|
+
top: 32px;
|
56
|
+
left: -2px;
|
57
|
+
z-index: 0;
|
58
|
+
opacity: 0;
|
59
|
+
width: 4px;
|
60
|
+
height: 18px;
|
61
|
+
background-color: var(--va-c-brand);
|
62
|
+
transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
|
63
|
+
border-top-right-radius: 2px;
|
64
|
+
border-bottom-right-radius: 2px;
|
65
|
+
}
|
66
|
+
|
67
|
+
.outline-title {
|
68
|
+
letter-spacing: 0.4px;
|
69
|
+
line-height: 28px;
|
70
|
+
font-size: 14px;
|
71
|
+
font-weight: 600;
|
72
|
+
}
|
73
|
+
|
74
|
+
.outline-link {
|
75
|
+
display: block;
|
76
|
+
line-height: 28px;
|
77
|
+
color: var(--va-c-text-light);
|
78
|
+
white-space: nowrap;
|
79
|
+
overflow: hidden;
|
80
|
+
text-overflow: ellipsis;
|
81
|
+
transition: color 0.5s;
|
82
|
+
}
|
83
|
+
|
84
|
+
.outline-link:hover,
|
85
|
+
.outline-link.active {
|
86
|
+
color: var(--va-c-brand);
|
87
|
+
transition: color 0.25s;
|
88
|
+
}
|
89
|
+
|
90
|
+
.visually-hidden {
|
91
|
+
position: absolute;
|
92
|
+
width: 1px;
|
93
|
+
height: 1px;
|
94
|
+
white-space: nowrap;
|
95
|
+
clip: rect(0 0 0 0);
|
96
|
+
clip-path: inset(50%);
|
97
|
+
overflow: hidden;
|
98
|
+
}
|
99
|
+
</style>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
<script setup lang="ts">
|
2
|
+
import type { MenuItem } from 'valaxy'
|
3
|
+
import { useI18n } from 'vue-i18n'
|
4
|
+
|
5
|
+
defineProps<{
|
6
|
+
headers: MenuItem[]
|
7
|
+
onClick: (e: MouseEvent) => void
|
8
|
+
root?: boolean
|
9
|
+
}>()
|
10
|
+
|
11
|
+
const { locale } = useI18n()
|
12
|
+
</script>
|
13
|
+
|
14
|
+
<template>
|
15
|
+
<ul :class="root ? 'root' : 'nested'">
|
16
|
+
<li v-for="{ children, link, title, lang } in headers" :key="link" class="va-toc-item" :lang="lang || locale">
|
17
|
+
<a class="outline-link" :href="link" @click="onClick">{{ title }}</a>
|
18
|
+
<template v-if="children?.length">
|
19
|
+
<HairyOutlineItem :headers="children" :on-click="onClick" />
|
20
|
+
</template>
|
21
|
+
</li>
|
22
|
+
</ul>
|
23
|
+
</template>
|
24
|
+
|
25
|
+
<style lang="scss" scoped>
|
26
|
+
.va-toc {
|
27
|
+
.va-toc-item {
|
28
|
+
.outline-link {
|
29
|
+
color: var(--va-c-text-light);
|
30
|
+
white-space: nowrap;
|
31
|
+
overflow: hidden;
|
32
|
+
text-overflow: ellipsis;
|
33
|
+
transition: color 0.5s;
|
34
|
+
|
35
|
+
&:hover,
|
36
|
+
&.active {
|
37
|
+
color: var(--va-c-primary-lighter);
|
38
|
+
transition: color 0.25s;
|
39
|
+
}
|
40
|
+
|
41
|
+
}
|
42
|
+
|
43
|
+
.nested {
|
44
|
+
padding-left: 0.8rem;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
}
|
48
|
+
</style>
|
@@ -5,11 +5,12 @@ import { withDefaults } from 'vue'
|
|
5
5
|
import { last } from 'lodash-es'
|
6
6
|
import { useRouter } from 'vue-router'
|
7
7
|
import { useI18n } from 'vue-i18n'
|
8
|
-
import {
|
8
|
+
import { toArray } from '../../utils'
|
9
|
+
|
9
10
|
withDefaults(
|
10
11
|
defineProps<{
|
11
12
|
post: Post
|
12
|
-
format
|
13
|
+
format?: string
|
13
14
|
}>(),
|
14
15
|
{
|
15
16
|
format: 'YYYY-MM-DD',
|
@@ -20,8 +21,8 @@ const i18n = useI18n()
|
|
20
21
|
|
21
22
|
const router = useRouter()
|
22
23
|
|
23
|
-
|
24
|
-
router.push({ path: `/categories/${
|
24
|
+
function displayCategory(keys: string | string[] = []) {
|
25
|
+
router.push({ path: `/categories/${toArray(keys).join('/')}` })
|
25
26
|
}
|
26
27
|
</script>
|
27
28
|
|
@@ -30,11 +31,9 @@ const displayCategory = (keys: string | string[] = []) => {
|
|
30
31
|
<div class="mr-2.2 dark:text-gray-500 text-gray-400">
|
31
32
|
{{ dayjs(post.date).format(format) }}
|
32
33
|
</div>
|
33
|
-
<HairyLink v-if="post.categories?.length" @click="displayCategory(post.categories)">
|
34
|
-
{{ i18n.t(last(
|
34
|
+
<HairyLink v-if="post.categories?.length" bordered type="white" @click="displayCategory(post.categories)">
|
35
|
+
{{ i18n.t(last(toArray(post.categories)) || '') }}
|
35
36
|
</HairyLink>
|
36
37
|
</div>
|
37
38
|
<a class="cursor-pointer text-size-4" @click="$router.push(post.path || '')">{{ post.title }}</a>
|
38
39
|
</template>
|
39
|
-
|
40
|
-
<style lang="scss" scoped></style>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { usePostList, useTags } from 'valaxy'
|
3
|
+
import { computed, nextTick } from 'vue'
|
4
|
+
import { useRouter } from 'vue-router'
|
5
|
+
import { storeToRefs } from 'pinia'
|
6
|
+
import { toArray } from '../../utils'
|
7
|
+
import { useGlobalStore } from '../../store'
|
8
|
+
|
9
|
+
const posts = usePostList()
|
10
|
+
const tags = useTags()
|
11
|
+
const router = useRouter()
|
12
|
+
const { showDrawer } = storeToRefs(useGlobalStore())
|
13
|
+
|
14
|
+
const total = computed(() => {
|
15
|
+
const categories = posts.value.map(v => toArray(v.categories || [])).filter(v => v.length)
|
16
|
+
const maps: string[] = []
|
17
|
+
for (const category of categories) {
|
18
|
+
let caches: string[] = []
|
19
|
+
for (const iterator of category) {
|
20
|
+
caches.push(iterator)
|
21
|
+
maps.push(caches.join('-'))
|
22
|
+
}
|
23
|
+
caches = []
|
24
|
+
}
|
25
|
+
return new Set(maps).size
|
26
|
+
})
|
27
|
+
|
28
|
+
async function navigation(path: string) {
|
29
|
+
await router.push(path)
|
30
|
+
await nextTick()
|
31
|
+
showDrawer.value = false
|
32
|
+
}
|
33
|
+
</script>
|
34
|
+
|
35
|
+
<template>
|
36
|
+
<div class="flex justify-center mt-2">
|
37
|
+
<HairyUserStats :count="posts.length" @click="navigation('/archives/')">
|
38
|
+
文章
|
39
|
+
</HairyUserStats>
|
40
|
+
<div class="w-1px bg-gray bg-opacity-50" />
|
41
|
+
<HairyUserStats :count="total" @click="navigation('/categories/')">
|
42
|
+
分类
|
43
|
+
</HairyUserStats>
|
44
|
+
<div class="w-1px bg-gray bg-opacity-50" />
|
45
|
+
<HairyUserStats :count="tags.size" @click="navigation('/tags/')">
|
46
|
+
标签
|
47
|
+
</HairyUserStats>
|
48
|
+
</div>
|
49
|
+
<HairySocialLinks class="mt-5" />
|
50
|
+
<HairyMenu class="HairyUserMenu mt-5 flex-col h-auto" />
|
51
|
+
</template>
|
52
|
+
|
53
|
+
<style lang="scss">
|
54
|
+
.HairyUserMenu {
|
55
|
+
display: flex;
|
56
|
+
flex-direction: column;
|
57
|
+
gap: 6px;
|
58
|
+
.HairyMenuItem {
|
59
|
+
padding: 2px;
|
60
|
+
width: 100%;
|
61
|
+
border: 1px solid transparent;
|
62
|
+
border-radius: 10px;
|
63
|
+
transition: all 0.2s;
|
64
|
+
background-color: transparent;
|
65
|
+
user-select: none;
|
66
|
+
> div {
|
67
|
+
justify-content: center;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
.HairyMenuItem:hover,
|
72
|
+
.HairyMenuItem.active {
|
73
|
+
background-color: #f4f4f5;
|
74
|
+
}
|
75
|
+
.HairyMenuItem:active {
|
76
|
+
background-color: #efefef;
|
77
|
+
}
|
78
|
+
|
79
|
+
.HairyMenuItem.active + .HairyMenuItem {
|
80
|
+
border-top-color: transparent;
|
81
|
+
}
|
82
|
+
.HairyMenuItem:hover + .HairyMenuItem {
|
83
|
+
border-top-color: transparent;
|
84
|
+
}
|
85
|
+
}
|
86
|
+
.dark .HairyUserMenu {
|
87
|
+
.HairyMenuItem:hover,
|
88
|
+
.HairyMenuItem.active {
|
89
|
+
background-color: #f4f4f50a;
|
90
|
+
}
|
91
|
+
.HairyMenuItem:active {
|
92
|
+
background-color: #f4f4f521;
|
93
|
+
}
|
94
|
+
}
|
95
|
+
</style>
|
@@ -4,28 +4,27 @@ import { computed, defineProps } from 'vue'
|
|
4
4
|
import dayjs from 'dayjs'
|
5
5
|
import { useRouter } from 'vue-router'
|
6
6
|
import { useI18n } from 'vue-i18n'
|
7
|
-
import {
|
8
|
-
import {
|
9
|
-
import { toArr } from '../../utils'
|
7
|
+
import { removeTags, toArray } from '../../utils'
|
8
|
+
import { useLayoutPost } from '../../composables'
|
10
9
|
|
11
10
|
const props = defineProps<{
|
12
11
|
post: Post
|
13
12
|
reverse?: boolean
|
14
13
|
}>()
|
14
|
+
|
15
15
|
const router = useRouter()
|
16
|
-
const layout =
|
16
|
+
const layout = useLayoutPost()
|
17
17
|
const slice = computed(() => layout.value.includes('slice'))
|
18
|
-
const image = computed(() => props.post.image)
|
19
|
-
|
18
|
+
const image = computed(() => props.post.image?.toString())
|
19
|
+
const text = computed(() => removeTags(props.post.excerpt))
|
20
20
|
const i18n = useI18n()
|
21
|
-
|
22
|
-
const onReadMore = () => {
|
21
|
+
function onReadMore() {
|
23
22
|
if (props.post.path)
|
24
23
|
router.push(props.post.path)
|
25
24
|
}
|
26
25
|
|
27
|
-
|
28
|
-
router.push({ path: `/categories/${
|
26
|
+
function displayCategory(keys: string | string[] = []) {
|
27
|
+
router.push({ path: `/categories/${toArray(keys).join('/')}` })
|
29
28
|
}
|
30
29
|
</script>
|
31
30
|
|
@@ -36,33 +35,33 @@ const displayCategory = (keys: string | string[] = []) => {
|
|
36
35
|
<a class="text-size-2xl font-bold truncate cursor-pointer lt-sm:text-size-lg" :class="[reverse ? 'order-last' : 'order-first']" @click="onReadMore">{{ post.title }}</a>
|
37
36
|
<div class="flex justify-end gap-2 text-size-sm lt-sm:text-size-xs">
|
38
37
|
<span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
39
|
-
<span>{{
|
40
|
-
<span class="lt-sm:hidden">{{ post.
|
38
|
+
<span>{{ post.wordCount }}字</span>
|
39
|
+
<span class="lt-sm:hidden">{{ post.readingTime }}分钟</span>
|
41
40
|
</div>
|
42
41
|
</div>
|
43
|
-
<div class="h-200px lt-sm:h-150px flex bg-light-2 dark:bg-transparent rounded-5
|
42
|
+
<div class="h-200px lt-sm:h-150px flex bg-light-2 dark:bg-transparent rounded-5" :class="[reverse ? 'pl-4' : 'pr-4']">
|
44
43
|
<div class="flex-1 post-image-content" :class="[reverse ? 'order-last' : 'order-first']">
|
45
44
|
<img
|
46
45
|
class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image"
|
47
46
|
@click="onReadMore"
|
48
|
-
|
47
|
+
>
|
49
48
|
</div>
|
50
49
|
<div class="flex-1 flex flex-col justify-between py-2 dark:py-0">
|
51
50
|
<div class="flex-1 text-size-sm">
|
52
51
|
<div class="line-clamp-text">
|
53
|
-
{{
|
52
|
+
{{ text }}
|
54
53
|
</div>
|
55
54
|
</div>
|
56
55
|
<div class="flex justify-between items-center">
|
57
56
|
<a class="cursor-pointer" :class="[reverse && 'order-1']">
|
58
57
|
<span v-if="post.categories?.length" @click="displayCategory(post.categories)">
|
59
|
-
{{ i18n.t(
|
58
|
+
{{ i18n.t(toArray(post.categories).at(-1) || '') }}
|
60
59
|
</span>
|
61
60
|
</a>
|
62
61
|
<div class="text-base leading-6 font-medium">
|
63
62
|
<a class="link cursor-pointer" aria-label="read more" @click="onReadMore">
|
64
63
|
<span v-if="reverse">←</span>
|
65
|
-
Read more
|
64
|
+
<span class="hidden md:block">Read more</span>
|
66
65
|
<span v-if="!reverse">→</span>
|
67
66
|
</a>
|
68
67
|
</div>
|
@@ -86,7 +85,7 @@ const displayCategory = (keys: string | string[] = []) => {
|
|
86
85
|
|
87
86
|
.dark {
|
88
87
|
.post-image {
|
89
|
-
@apply opacity-75 hover:
|
88
|
+
@apply opacity-75 hover:opacity-90 ;
|
90
89
|
}
|
91
90
|
}
|
92
91
|
|
@@ -115,7 +114,7 @@ const displayCategory = (keys: string | string[] = []) => {
|
|
115
114
|
|
116
115
|
.HairyArticleImage.slice {
|
117
116
|
.post-image {
|
118
|
-
@apply transition-all;
|
117
|
+
// @apply transition-all;
|
119
118
|
}
|
120
119
|
|
121
120
|
&:hover {
|
@@ -1,17 +1,20 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
|
+
import type { PostFrontMatter } from 'valaxy'
|
2
3
|
import { useFrontmatter } from 'valaxy'
|
3
4
|
import { computed, inject, nextTick, ref } from 'vue'
|
4
5
|
import { useRouter } from 'vue-router'
|
5
|
-
import {
|
6
|
-
import {
|
6
|
+
import { toArray } from '../../utils'
|
7
|
+
import { useCategory } from '../../composables'
|
7
8
|
|
8
9
|
const frontmatter = useFrontmatter()
|
9
|
-
const paths = computed(() =>
|
10
|
-
const category =
|
10
|
+
const paths = computed(() => toArray(frontmatter.value.categories).filter(Boolean) as string[])
|
11
|
+
const category = useCategory(paths)
|
12
|
+
|
11
13
|
const posts = computed(() => {
|
12
|
-
const result = category.value.
|
14
|
+
const result = [...(category.value.children?.values() || [])] as PostFrontMatter[]
|
13
15
|
return result.sort((a, b) => (a.date || 1) > (b.date || 1) ? 1 : -1)
|
14
16
|
})
|
17
|
+
|
15
18
|
const router = useRouter()
|
16
19
|
|
17
20
|
const active = inject('HairyUserTab:active', ref(''))
|
@@ -1,14 +1,18 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
2
|
import type { Post } from 'valaxy'
|
3
3
|
import { computed, defineProps } from 'vue'
|
4
|
-
import {
|
4
|
+
import { removeTags } from '../../utils'
|
5
|
+
import { useLayoutPost } from '../../composables'
|
6
|
+
|
5
7
|
const props = defineProps<{
|
6
8
|
post: Post
|
7
9
|
}>()
|
8
|
-
|
10
|
+
|
11
|
+
const layout = useLayoutPost()
|
12
|
+
|
9
13
|
const text = computed(() => {
|
10
14
|
if (layout.value === 'text')
|
11
|
-
return props.post.
|
15
|
+
return removeTags(props.post.excerpt)
|
12
16
|
return props.post.excerpt
|
13
17
|
})
|
14
18
|
</script>
|
@@ -28,7 +32,10 @@ const text = computed(() => {
|
|
28
32
|
/>
|
29
33
|
</div>
|
30
34
|
<div class="text-base leading-6 font-medium">
|
31
|
-
<a class="link" aria-label="read more" :href="post.path">
|
35
|
+
<a class="link" aria-label="read more" :href="post.path">
|
36
|
+
<span class="hidden md:block">Read more</span>
|
37
|
+
<span> →</span>
|
38
|
+
</a>
|
32
39
|
</div>
|
33
40
|
</div>
|
34
41
|
</article>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useRoute } from 'vue-router'
|
3
|
+
|
4
|
+
const route = useRoute()
|
5
|
+
</script>
|
6
|
+
|
7
|
+
<template>
|
8
|
+
<div class="mb-15">
|
9
|
+
<div class="border-t border-gray-200 dark:border-gray-600" />
|
10
|
+
<div class="flex items-center justify-end mt-2">
|
11
|
+
<div class="i-ri-eye-fill mr-2" />
|
12
|
+
阅读次数 <span class="waline-pageview-count mx-2" :data-path="route.path"> - </span> 次
|
13
|
+
</div>
|
14
|
+
</div>
|
15
|
+
</template>
|
@@ -3,8 +3,7 @@ import type { Ref } from 'vue'
|
|
3
3
|
import { computed } from 'vue'
|
4
4
|
import type { Post } from 'valaxy'
|
5
5
|
import { usePostList } from 'valaxy'
|
6
|
-
|
7
|
-
import { usePostLayout } from '../../hooks/usePostLayout'
|
6
|
+
import { useLayoutPost } from '../../composables'
|
8
7
|
|
9
8
|
const props = withDefaults(defineProps<{
|
10
9
|
type?: string
|
@@ -12,10 +11,11 @@ const props = withDefaults(defineProps<{
|
|
12
11
|
}>(), {
|
13
12
|
})
|
14
13
|
|
14
|
+
const layout = useLayoutPost()
|
15
|
+
const reverse = computed(() => layout.value.includes('reverse'))
|
16
|
+
|
15
17
|
const routes = usePostList() as any as Ref<Post[]>
|
16
18
|
const posts = computed(() => props.posts || routes.value)
|
17
|
-
const layout = usePostLayout()
|
18
|
-
const reverse = computed(() => layout.value.includes('reverse'))
|
19
19
|
</script>
|
20
20
|
|
21
21
|
<template>
|
@@ -25,4 +25,3 @@ const reverse = computed(() => layout.value.includes('reverse'))
|
|
25
25
|
</Transition>
|
26
26
|
</ul>
|
27
27
|
</template>
|
28
|
-
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useElementSize, useScroll } from '@vueuse/core'
|
3
|
+
import { computed } from 'vue'
|
4
|
+
import { storeToRefs } from 'pinia'
|
5
|
+
import { useGlobalStore } from '../../store'
|
6
|
+
import { useLayoutPost } from '../../composables'
|
7
|
+
|
8
|
+
const layout = useLayoutPost()
|
9
|
+
|
10
|
+
const layouts = [
|
11
|
+
{ layout: 'image:slice:reverse' as const, icon: 'i-fluent-text-align-distributed-24-filled' },
|
12
|
+
{ layout: 'image:slice' as const, icon: 'i-fluent-text-align-left-16-filled' },
|
13
|
+
{ layout: 'image' as const, icon: 'i-fluent-text-align-justify-20-filled' },
|
14
|
+
{ layout: 'markdown' as const, icon: 'i-fluent-markdown-20-filled' },
|
15
|
+
{ layout: 'text' as const, icon: 'i-fluent-code-text-16-filled' },
|
16
|
+
]
|
17
|
+
|
18
|
+
const globalStore = useGlobalStore()
|
19
|
+
const { headerRef } = storeToRefs(globalStore)
|
20
|
+
const { height: headerHeight } = useElementSize(headerRef)
|
21
|
+
const scroll = useScroll(document)
|
22
|
+
|
23
|
+
const show = computed(() => {
|
24
|
+
return scroll.y.value > headerHeight.value
|
25
|
+
})
|
26
|
+
</script>
|
27
|
+
|
28
|
+
<template>
|
29
|
+
<div class="inline-flex gap-2 sticky top-15 inset-0 rounded-2" :class="[show && 'bg-white bg-opacity-85 dark:bg-black dark:bg-opacity-80 z-100']">
|
30
|
+
<div v-for="(item) in layouts" :key="item.layout" class="p-2 rounded-full cursor-pointer" :class="[layout === item.layout && 'text-primary']" @click="layout = item.layout">
|
31
|
+
<div class="text-size-xl" :class="item.icon" />
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
</template>
|
35
|
+
|
36
|
+
<style lang="scss" scoped></style>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import * as addonAlgolia from 'valaxy-addon-algolia'
|
3
|
+
import { isEmptyAddon } from 'valaxy'
|
4
|
+
import { whenever } from '@vueuse/core'
|
5
|
+
|
6
|
+
if (isEmptyAddon(addonAlgolia))
|
7
|
+
throw new Error('Algolia addon is not installed')
|
8
|
+
|
9
|
+
const { loaded, load, dispatchEvent } = addonAlgolia.useAddonAlgolia()
|
10
|
+
|
11
|
+
whenever(loaded, dispatchEvent)
|
12
|
+
</script>
|
13
|
+
|
14
|
+
<template>
|
15
|
+
<AlgoliaSearchBox v-if="loaded" class="VPNavBarSearch" />
|
16
|
+
<HairySearchBtnDisplay v-else @click="load()" />
|
17
|
+
</template>
|