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.
Files changed (156) hide show
  1. package/client/index.ts +1 -0
  2. package/components/HairyBody.vue +15 -17
  3. package/components/HairyComment.vue +33 -0
  4. package/components/HairyContainer.vue +17 -0
  5. package/components/{HairyUserPopup.vue → HairyDrawer.vue} +16 -16
  6. package/components/HairyFooter.vue +12 -9
  7. package/components/HairyHeader.vue +10 -9
  8. package/components/HairyImage.vue +3 -2
  9. package/components/HairyImageGroup.vue +12 -16
  10. package/components/HairyNavbar.vue +56 -0
  11. package/components/HairyPageArchives.vue +59 -0
  12. package/components/HairyPageTags.vue +48 -0
  13. package/components/{HairyPostList.vue → HairyPosts.vue} +3 -3
  14. package/components/{HairyNavSearch.vue → HairySearch.vue} +23 -87
  15. package/components/{HairyUserCard.vue → HairySidebar.vue} +4 -4
  16. package/components/HairyTabbar.vue +56 -0
  17. package/components/PageTags.vue +48 -0
  18. package/components/ValaxyMain.vue +3 -3
  19. package/components/navbar/HairyNav.vue +16 -0
  20. package/components/navbar/HairyNavExpand.vue +12 -0
  21. package/components/navbar/HairyNavItem.vue +35 -0
  22. package/components/navbar/HairyNavbarBackground.vue +7 -0
  23. package/components/navbar/HairyNavbarSearch.vue +8 -0
  24. package/components/{HairyNavTitle.vue → navbar/HairyNavbarTitle.vue} +5 -3
  25. package/components/navbar/HairyNavbarToggleDark.vue +22 -0
  26. package/components/{HairyBreadcrumb.vue → parts/HairyBreadcrumb.vue} +2 -2
  27. package/components/{HairyBreadcrumbItem.vue → parts/HairyBreadcrumbItem.vue} +1 -4
  28. package/components/{lib/fish.js → parts/HairyFootFish.js} +5 -7
  29. package/components/parts/HairyFootFish.vue +38 -0
  30. package/components/{HairyPostTitle.vue → parts/HairyHeadHero.vue} +6 -5
  31. package/components/{HairyWaves.vue → parts/HairyHeadWaves.vue} +5 -5
  32. package/components/parts/HairyImageGlobal.vue +51 -0
  33. package/components/{HairyImageViewer.vue → parts/HairyImageViewer.vue} +5 -4
  34. package/components/parts/HairyLink.vue +21 -0
  35. package/components/{HairyMenu.vue → parts/HairyMenu.vue} +2 -1
  36. package/components/{HairyMenuItem.vue → parts/HairyMenuItem.vue} +11 -4
  37. package/components/parts/HairyOutline.vue +99 -0
  38. package/components/parts/HairyOutlineItem.vue +48 -0
  39. package/components/{HairySocialLinks.vue → parts/HairySocialLinks.vue} +2 -2
  40. package/components/{HairyTimelinePostItem.vue → parts/HairyTimelineContent.vue} +7 -8
  41. package/components/parts/HairyUserNav.vue +95 -0
  42. package/components/{article-layout → posts}/HairyArticleImage.vue +18 -19
  43. package/components/{article-layout → posts}/HairyArticleSeries.vue +8 -5
  44. package/components/{article-layout → posts}/HairyArticleText.vue +11 -4
  45. package/components/posts/HairyPostFooter.vue +15 -0
  46. package/components/{article-layout → posts}/HairyPostImageList.vue +4 -5
  47. package/components/{article-layout → posts}/HairyPostTextsList.vue +0 -1
  48. package/components/posts/HairyPostToggleLayout.vue +36 -0
  49. package/components/third/HairyAlgoliaSearch.vue +17 -0
  50. package/components/third/HairyFuseSearch.vue +10 -0
  51. package/components/third/HairyFuseSearchDialog.vue +32 -0
  52. package/components/third/HairyFuseSearchDropdown.vue +77 -0
  53. package/components/third/HairyFuseSearchFooter.vue +28 -0
  54. package/components/third/HairyFuseSearchHeader.vue +30 -0
  55. package/components/third/HairyFuseSearchHit.vue +52 -0
  56. package/components/third/HairySearchBtnDisplay.vue +29 -0
  57. package/components/third/HairySearchBtnInput.vue +20 -0
  58. package/components/third/HairySearchBtnKeys.vue +19 -0
  59. package/components/{HairyCarousel.vue → third/HairySwiperCarousel.vue} +6 -6
  60. package/{hooks/useYearArchives.ts → composables/archives.ts} +4 -3
  61. package/composables/category.ts +43 -0
  62. package/composables/config.ts +11 -0
  63. package/composables/dark.ts +13 -0
  64. package/composables/fuse.ts +60 -0
  65. package/composables/index.ts +8 -0
  66. package/composables/layout.ts +16 -0
  67. package/composables/outline.ts +49 -0
  68. package/composables/tags.ts +36 -0
  69. package/layouts/archive-month.vue +13 -0
  70. package/layouts/archive-year.vue +13 -0
  71. package/layouts/archives.vue +9 -9
  72. package/layouts/categories.vue +11 -4
  73. package/layouts/default.vue +9 -3
  74. package/layouts/home.vue +28 -18
  75. package/layouts/post.vue +42 -36
  76. package/layouts/tag.vue +8 -4
  77. package/layouts/tags.vue +12 -4
  78. package/{modules → library}/loading.ts +18 -6
  79. package/{modules → library}/scroll.ts +3 -2
  80. package/locales/zh-CN.yml +0 -2
  81. package/node/images/default.json +139 -0
  82. package/node/images/index.ts +46 -0
  83. package/node/index.ts +2 -0
  84. package/node/theme/index.ts +78 -0
  85. package/package.json +22 -28
  86. package/pages/archives/[year]/[month]/index.vue +15 -18
  87. package/pages/archives/[year]/index.vue +20 -20
  88. package/pages/archives/index.vue +1 -54
  89. package/pages/categories/{[...categories].vue → [...its].vue} +29 -36
  90. package/pages/index.vue +1 -1
  91. package/pages/page/[page].vue +2 -2
  92. package/pages/tags/[tag]/index.vue +38 -0
  93. package/pages/tags/index.vue +1 -36
  94. package/setup/main.ts +1 -1
  95. package/store/index.ts +1 -0
  96. package/store/modules/global.ts +12 -0
  97. package/styles/components/index.scss +4 -0
  98. package/styles/{markdown.scss → components/markdown.scss} +2 -1
  99. package/styles/components/nprogress.scss +16 -0
  100. package/styles/css-vars.scss +11 -0
  101. package/styles/element-plus/tabs.scss +1 -1
  102. package/styles/element-plus/timeline.scss +1 -1
  103. package/styles/global.scss +39 -0
  104. package/styles/index.scss +4 -73
  105. package/tsconfig.json +27 -0
  106. package/types/index.d.ts +163 -0
  107. package/unocss.config.ts +5 -1
  108. package/utils/index.ts +21 -39
  109. package/valaxy.config.ts +21 -24
  110. package/@types/markdown-it.d.ts +0 -1
  111. package/@types/markdown-toc.d.ts +0 -1
  112. package/@types/types.d.ts +0 -1
  113. package/@types/valaxy.d.ts +0 -10
  114. package/components/HairyAlgoliaSearchBox.vue +0 -118
  115. package/components/HairyBackToTop.vue +0 -72
  116. package/components/HairyDivider.vue +0 -0
  117. package/components/HairyFooterFish.vue +0 -29
  118. package/components/HairyLayout.vue +0 -28
  119. package/components/HairyLink.vue +0 -10
  120. package/components/HairyLinks.vue +0 -69
  121. package/components/HairyMeting.vue +0 -19
  122. package/components/HairyNav.vue +0 -42
  123. package/components/HairyNavBackground.vue +0 -7
  124. package/components/HairyNavMenu.vue +0 -11
  125. package/components/HairyNavToggleDark.vue +0 -16
  126. package/components/HairyPostToggleLayout.vue +0 -33
  127. package/components/HairyToc.vue +0 -135
  128. package/components/HairyUserNav.vue +0 -64
  129. package/components/HairyUserTab.vue +0 -40
  130. package/components/HairyWaline.vue +0 -44
  131. package/hooks/setupDefaultDark.ts +0 -11
  132. package/hooks/useCategory.ts +0 -18
  133. package/hooks/useCategoryPost.ts +0 -21
  134. package/hooks/useContext.ts +0 -13
  135. package/hooks/useHeaderHeight.ts +0 -9
  136. package/hooks/usePostLayout.ts +0 -16
  137. package/images.json +0 -140
  138. package/index.d.ts +0 -100
  139. package/layouts/hairy.vue +0 -36
  140. package/layouts/month.vue +0 -6
  141. package/layouts/year.vue +0 -6
  142. package/node/addon-hairy.ts +0 -36
  143. package/node/addon-images.ts +0 -61
  144. package/node/addon-meting.ts +0 -13
  145. package/node/addon-statistics.ts +0 -19
  146. package/node/addon-toc.ts +0 -20
  147. package/node/utils.ts +0 -20
  148. package/pages/tags/[tag].vue +0 -40
  149. package/utils/createContext.ts +0 -40
  150. package/utils/fonts.ts +0 -15
  151. /package/components/{HairyUserStats.vue → parts/HairyUserStats.vue} +0 -0
  152. /package/components/{article-layout → posts}/HairyArticleTop.vue +0 -0
  153. /package/{modules → library}/loading.scss +0 -0
  154. /package/{shims.d.ts → node/images/shims.d.ts} +0 -0
  155. /package/styles/{aplayer.scss → components/aplayer.scss} +0 -0
  156. /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
- const themeConfig = useThemeConfig<HairyTheme>()
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 { ejectWindow } from '../utils'
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
- const toLink = () => {
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
- <div class="px-2.5 HairyMenuItem" :class="[isPointer ? 'cursor-pointer' : 'select-none', isActive && 'text-primary active']">
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
- </div>
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>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
- import { useConfig } from 'valaxy'
2
+ import { useSiteConfig } from 'valaxy'
3
3
 
4
- const config = useConfig()
4
+ const config = useSiteConfig()
5
5
  </script>
6
6
 
7
7
  <template>
@@ -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 { toArr } from '../utils'
8
+ import { toArray } from '../../utils'
9
+
9
10
  withDefaults(
10
11
  defineProps<{
11
12
  post: Post
12
- format: string
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
- const displayCategory = (keys: string | string[] = []) => {
24
- router.push({ path: `/categories/${toArr(keys).join('/')}` })
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(toArr(post.categories)) || '') }}
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 { last } from 'lodash-es'
8
- import { usePostLayout } from '../../hooks/usePostLayout'
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 = usePostLayout()
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
- const displayCategory = (keys: string | string[] = []) => {
28
- router.push({ path: `/categories/${toArr(keys).join('/')}` })
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>{{ (post.length / 1000).toFixed(1) }}k字</span>
40
- <span class="lt-sm:hidden">{{ post.durations.minutes.toFixed(2) }}分钟</span>
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 duration-200" :class="[reverse ? 'pl-4' : 'pr-4']">
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
- {{ post.text }}
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(last(toArr(post.categories)) || '') }}
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: opacity-90 duration-200;
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 { toArr } from '../../utils'
6
- import { useCurrentCategory } from '../../hooks/useCategory'
6
+ import { toArray } from '../../utils'
7
+ import { useCategory } from '../../composables'
7
8
 
8
9
  const frontmatter = useFrontmatter()
9
- const paths = computed(() => toArr(frontmatter.value.categories).filter(Boolean) as string[])
10
- const category = useCurrentCategory(paths)
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.posts || []
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 { usePostLayout } from '../../hooks/usePostLayout'
4
+ import { removeTags } from '../../utils'
5
+ import { useLayoutPost } from '../../composables'
6
+
5
7
  const props = defineProps<{
6
8
  post: Post
7
9
  }>()
8
- const layout = usePostLayout()
10
+
11
+ const layout = useLayoutPost()
12
+
9
13
  const text = computed(() => {
10
14
  if (layout.value === 'text')
11
- return props.post.text
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">Read more →</a>
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
-
@@ -20,4 +20,3 @@ const posts = computed(() => props.posts || routes.value)
20
20
  </Transition>
21
21
  </ul>
22
22
  </template>
23
-
@@ -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>
@@ -0,0 +1,10 @@
1
+ <script lang="ts" setup>
2
+ import { ref } from 'vue'
3
+
4
+ const visible = ref(false)
5
+ </script>
6
+
7
+ <template>
8
+ <HairySearchBtnDisplay @click="visible = true" />
9
+ <HairyFuseSearchDialog v-model:visible="visible" />
10
+ </template>