valaxy-theme-hairy 1.0.5 → 1.1.0

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/App.vue ADDED
@@ -0,0 +1,15 @@
1
+ <script lang="ts" setup>
2
+ import { onMounted } from 'vue'
3
+ import { setupDefaultDark } from './composables'
4
+
5
+ import 'element-plus/theme-chalk/base.css'
6
+ import 'element-plus/theme-chalk/el-timeline.css'
7
+ import 'element-plus/theme-chalk/el-timeline-item.css'
8
+ import 'element-plus/theme-chalk/el-tag.css'
9
+
10
+ onMounted(setupDefaultDark)
11
+ </script>
12
+
13
+ <template>
14
+ <!-- ! -->
15
+ </template>
@@ -1,12 +1,20 @@
1
1
  <script lang="ts" setup>
2
+ withDefaults(
3
+ defineProps<{
4
+ comment?: boolean
5
+ }>(),
6
+ {
7
+ comment: true,
8
+ },
9
+ )
2
10
  </script>
3
11
 
4
12
  <template>
5
13
  <div class="min-h-49vh relative z-5">
6
14
  <div class="mx-auto container flex z-1 relative">
7
15
  <div class="relative flex-1 pt-2 main">
8
- <slot />
9
- <HairyComment />
16
+ <slot v-if="$slots.default" />
17
+ <HairyComment v-if="comment" />
10
18
  </div>
11
19
  <div class="ml-4 w-60 lg:block hidden">
12
20
  <div class="sticky top-3.125rem z-1">
@@ -15,7 +23,6 @@
15
23
  </div>
16
24
  </div>
17
25
  </div>
18
-
19
26
  <div class="HairyBodyBackground" />
20
27
  </div>
21
28
  </template>
@@ -1,17 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { onMounted } from 'vue'
3
- import { setupDefaultDark } from '../composables'
4
-
5
- import 'element-plus/theme-chalk/base.css'
6
- import 'element-plus/theme-chalk/el-timeline.css'
7
- import 'element-plus/theme-chalk/el-timeline-item.css'
8
-
9
- onMounted(setupDefaultDark)
10
2
  </script>
11
3
 
12
4
  <template>
13
5
  <div class="min-h-80vh">
14
6
  <slot />
15
- <HairyDrawer />
16
7
  </div>
8
+ <HairyDrawer />
17
9
  </template>
@@ -1,5 +1,7 @@
1
1
  <script lang="ts" setup>
2
- import 'element-plus/theme-chalk/el-dialog.css'
2
+ import 'element-plus/theme-chalk/el-drawer.css'
3
+ import 'element-plus/theme-chalk/el-overlay.css'
4
+
3
5
  import { ElDrawer } from 'element-plus'
4
6
  import { useRoute } from 'vue-router'
5
7
  import { watch } from 'vue'
@@ -14,7 +16,7 @@ watch(() => route.fullPath, () => showDrawer.value = false)
14
16
  </script>
15
17
 
16
18
  <template>
17
- <ElDrawer v-model="showDrawer" direction="ltr" size="auto" @close="showDrawer = false">
19
+ <ElDrawer v-model="showDrawer" :z-index="10000" class="z-100" direction="ltr" size="auto" @close="showDrawer = false">
18
20
  <div class="h-24px" />
19
21
  <HairyTabbar v-if="route.fullPath.includes('/posts/')" />
20
22
  <HairySidebar v-else />
@@ -23,7 +25,7 @@ watch(() => route.fullPath, () => showDrawer.value = false)
23
25
  </template>
24
26
 
25
27
  <style lang="scss">
26
- .el-drawer {
28
+ .el-drawer {
27
29
  background-color: transparent;
28
30
  .el-drawer__header {
29
31
  display: none;
@@ -12,7 +12,7 @@ const { headerRef } = storeToRefs(useGlobalStore())
12
12
  </script>
13
13
 
14
14
  <template>
15
- <header ref="headerRef" class="relative animate__animated animate__fadeIn">
15
+ <header ref="headerRef" class="relative min-h-30vh animate__animated animate__fadeIn">
16
16
  <div class="h-30vh lt-md:h-60vh min-h-80 flex-center">
17
17
  <HairyHeadHero v-if="title || headline || description || $slots.description" class="relative z-2" :title="title" v-bind="$props">
18
18
  <template #description>
@@ -0,0 +1,70 @@
1
+ <script lang="ts" setup>
2
+ import { defineProps } from 'vue'
3
+
4
+ defineProps<{
5
+ links?: {
6
+ name: string
7
+ url: string
8
+ image: string
9
+ color: string
10
+ desc?: string
11
+ }[]
12
+ }>()
13
+ </script>
14
+
15
+ <template>
16
+ <div class="min-h-30vh">
17
+ <div class="links">
18
+ <div
19
+ v-for="(item, index) in links" :key="index" class="link-block flex items-center py-0.5rem px-1rem rounded-lg"
20
+ :style="{ '--block-color': item.color }"
21
+ >
22
+ <a :href="item.url" class="w-4rem h-4rem">
23
+ <HairyImage class="w-full h-full rounded-xl" :src="item.image" />
24
+ </a>
25
+ <div class="pl-1rem flex-1">
26
+ <a :href="item.url" class="font-bold text-lg title">
27
+ {{ item.name }}
28
+ </a>
29
+ <div class="max-w-180px text-sm my-0.5rem truncate desc">
30
+ {{ item.desc }}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </template>
37
+
38
+ <style lang="scss" scoped>
39
+ .link-block {
40
+ border: 0.0625rem solid #f7f7f7;
41
+ box-shadow: 0 0.625rem 1.875rem -0.9375rem rgba(0, 0, 0, 0.1);
42
+ --bg-color: var(--block-color, #666);
43
+ @apply transition-all duration-200;
44
+
45
+ .title {
46
+ color: var(--block-color);
47
+ }
48
+ }
49
+
50
+ .dark .link-block {
51
+ background: rgba($color: #989898, $alpha: 0.1);
52
+ }
53
+
54
+ .links .link-block:hover {
55
+ background-color: var(--bg-color);
56
+ box-shadow: 0 0.125rem 1.25rem var(--bg-color);
57
+ border-color: var(--bg-color);
58
+
59
+ .title,
60
+ .desc {
61
+ color: #fff;
62
+ }
63
+ }
64
+
65
+ .links {
66
+ display: grid;
67
+ grid-template-columns: repeat(auto-fill, 300px);
68
+ gap: 24px;
69
+ }
70
+ </style>
@@ -29,7 +29,7 @@ onMounted(() => documentRef.value = document)
29
29
 
30
30
  <template>
31
31
  <div
32
- class="fixed w-full h-3.125rem lt-sm:h-3.5rem top-0 opacity-0 z-10000"
32
+ class="fixed w-full h-3.125rem lt-sm:h-3.5rem top-0 opacity-0 z-1000 duration-200"
33
33
  :class="[show && 'opacity-100']"
34
34
  >
35
35
  <div class="px-12px md:px-0 mx-auto container flex relative z-1 h-full">
@@ -21,7 +21,7 @@ function displayTag(tag: string) {
21
21
  <div text="center" class="max-w-7xl flex flex-wrap justify-center items-center gap-2">
22
22
  <a
23
23
  v-for="[key, tag] in Array.from(tags).sort()"
24
- :key="key" class="post-tag cursor-pointer"
24
+ :key="key" class="post-tag cursor-pointer transition-all duration-200"
25
25
  :style="getTagStyle(tag.count)"
26
26
  p="1"
27
27
  @click="displayTag(key.toString())"
@@ -9,6 +9,7 @@ const props = withDefaults(defineProps<{
9
9
  posts?: Post[]
10
10
  curPage?: number
11
11
  pagination?: boolean
12
+ updated?: boolean
12
13
  }>(), {
13
14
  curPage: 1,
14
15
  pagination: false,
@@ -25,6 +26,7 @@ const displayedPosts = computed(() => props.pagination ? pagePosts.value : posts
25
26
  <template>
26
27
  <div class="mt-8">
27
28
  <HairyPostToggleLayout />
29
+ <HairyUpdatedPost v-if="updated" :posts="posts" />
28
30
  <HairyPostImageList v-if="layout.includes('image')" :posts="displayedPosts" />
29
31
  <HairyPostTextsList v-else :posts="displayedPosts" />
30
32
  <ValaxyPagination v-if="pagination" class="mb-6" :cur-page="curPage" :page-size="pageSize" :total="posts.length" />
@@ -1,6 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import { useRouter } from 'vue-router'
3
- import { useHairyTags } from '../../composables'
3
+ import { useHairyTags } from '../composables'
4
4
 
5
5
  const router = useRouter()
6
6
 
@@ -6,7 +6,9 @@ import { onMounted, onUnmounted } from 'vue'
6
6
 
7
7
  const props = defineProps(imageViewerProps)
8
8
 
9
- const { visible, resolve } = usePrograms()
9
+ const { visible, resolve } = usePrograms({
10
+ duration: 1000,
11
+ })
10
12
 
11
13
  onMounted(() => {
12
14
  document.body.style.overflow = 'hidden'
@@ -32,19 +32,22 @@ function displayCategory(keys: string | string[] = []) {
32
32
  <li class="HairyArticleImage mb-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
33
33
  <article>
34
34
  <div class="flex justify-between items-center">
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>
35
+ <a
36
+ class="text-size-2xl font-bold truncate cursor-pointer lt-sm:text-size-lg"
37
+ :class="[reverse ? 'order-last' : 'order-first']" @click="onReadMore"
38
+ >{{ post.title }}</a>
36
39
  <div class="flex justify-end gap-2 text-size-sm lt-sm:text-size-xs">
37
40
  <span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
38
41
  <span>{{ post.wordCount }}字</span>
39
42
  <span class="lt-sm:hidden">{{ post.readingTime }}分钟</span>
40
43
  </div>
41
44
  </div>
42
- <div class="h-200px lt-sm:h-150px flex bg-light-2 dark:bg-transparent rounded-5" :class="[reverse ? 'pl-4' : 'pr-4']">
45
+ <div
46
+ class="h-200px lt-sm:h-150px flex bg-light-2 dark:bg-transparent rounded-5"
47
+ :class="[reverse ? 'pl-4' : 'pr-4']"
48
+ >
43
49
  <div class="flex-1 post-image-content" :class="[reverse ? 'order-last' : 'order-first']">
44
- <img
45
- class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image"
46
- @click="onReadMore"
47
- >
50
+ <img class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image" @click="onReadMore">
48
51
  </div>
49
52
  <div class="flex-1 flex flex-col justify-between py-2 dark:py-0">
50
53
  <div class="flex-1 text-size-sm">
@@ -83,6 +86,10 @@ function displayCategory(keys: string | string[] = []) {
83
86
  -webkit-line-clamp: 5;
84
87
  }
85
88
 
89
+ .post-image {
90
+ @apply duration-200;
91
+ }
92
+
86
93
  .dark {
87
94
  .post-image {
88
95
  @apply opacity-75 hover:opacity-90 duration-200;
@@ -106,7 +113,7 @@ function displayCategory(keys: string | string[] = []) {
106
113
  .post-image-content {
107
114
  margin-right: 0;
108
115
  margin-left: 1rem;
109
- clip-path: polygon(0 0,100% 0,100% 100%,8% 100%);
116
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 8% 100%);
110
117
  border-radius: 0 0.625rem 0.625rem 0;
111
118
  overflow: hidden;
112
119
  }
@@ -18,7 +18,7 @@ const layouts = [
18
18
  const globalStore = useGlobalStore()
19
19
  const { headerRef } = storeToRefs(globalStore)
20
20
  const { height: headerHeight } = useElementSize(headerRef)
21
- const scroll = useScroll(document)
21
+ const scroll = useScroll(typeof document !== 'undefined' ? document : undefined)
22
22
 
23
23
  const show = computed(() => {
24
24
  return scroll.y.value > headerHeight.value
@@ -26,7 +26,10 @@ const show = computed(() => {
26
26
  </script>
27
27
 
28
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']">
29
+ <div
30
+ class="inline-flex gap-2 sticky top-15 inset-0 rounded-2 transition-colors duration-200"
31
+ :class="[show && 'bg-white bg-opacity-85 dark:bg-black dark:bg-opacity-80 z-100']"
32
+ >
30
33
  <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
34
  <div class="text-size-xl" :class="item.icon" />
32
35
  </div>
@@ -0,0 +1,75 @@
1
+ <!-- eslint-disable unused-imports/no-unused-vars -->
2
+ <script lang="ts" setup>
3
+ import { computed, defineProps } from 'vue'
4
+ import type { Post } from 'valaxy'
5
+ import { ElTag } from 'element-plus'
6
+ import { useRouter } from 'vue-router'
7
+ import { toArray } from '../../utils'
8
+
9
+ const props = defineProps<{
10
+ type?: string
11
+ posts: Post[]
12
+ }>()
13
+
14
+ const router = useRouter()
15
+
16
+ const post = computed(() => {
17
+ const clone = [...props.posts].map(v => ({
18
+ ...v,
19
+ date: v.updated instanceof Date
20
+ ? v.updated.valueOf()
21
+ : new Date(v.updated || Date.now()).valueOf(),
22
+ }))
23
+ clone.sort((b, a) => a.date - b.date)
24
+ return clone[0]
25
+ })
26
+
27
+ function displayCategory(keys: string | string[] = []) {
28
+ router.push({ path: `/categories/${toArray(keys).join('/')}` })
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <div class="border-b border-[var(--hy-c-divider)] flex-wrap items-center flex justify-between mb-4 gap-2">
34
+ <div class="flex-shrink-0 w-100px">
35
+ 最近更新:
36
+ </div>
37
+ <div class="flex-1 flex items-center justify-end gap-2 flex-wrap">
38
+ <HairyLink :href="post.path">
39
+ {{ post.title }}
40
+ </HairyLink>
41
+ <template v-if="post.tags?.length">
42
+ <div class="lt-md:hidden">
43
+ |
44
+ </div>
45
+ <div class="lt-md:hidden">
46
+ <div class="tags flex-center gap-2">
47
+ <div class="i-material-symbols-bookmarks" />
48
+ <ElTag
49
+ v-for="(tag) in post.tags"
50
+ :key="tag"
51
+ size="small"
52
+ class="dark:bg-dark-50 cursor-pointer border-none!"
53
+ @click="$router.push(`/tags/${tag}`)"
54
+ >
55
+ {{ tag }}
56
+ </ElTag>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ <template v-if="post.categories">
61
+ <div class="lt-md:hidden">
62
+ |
63
+ </div>
64
+ <div class="lt-md:hidden flex items-center gap-2">
65
+ <div class="i-material-symbols-folder-open-rounded text-14px" />
66
+ <ElTag size="small" class="dark:bg-dark-50 cursor-pointer border-none!" @click="displayCategory(post.categories)">
67
+ {{ post.categories }}
68
+ </ElTag>
69
+ </div>
70
+ </template>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <style lang="scss" scoped></style>
package/layouts/home.vue CHANGED
@@ -25,8 +25,8 @@ const hello = riposte(
25
25
  :title="config.author.name"
26
26
  :description="`${hello}, how are you doing?`"
27
27
  />
28
- <HairyBody>
29
- <router-view />
28
+ <HairyBody :comment="false">
29
+ <HairyPosts updated pagination :cur-page="parseInt(String($route.params.page || 1))" />
30
30
  </HairyBody>
31
31
  <HairyFooter />
32
32
  </HairyContainer>
package/layouts/post.vue CHANGED
@@ -4,8 +4,6 @@ import { useFrontmatter, useRuntimeConfig } from 'valaxy'
4
4
  import { useRouter } from 'vue-router'
5
5
  import { computed } from 'vue'
6
6
 
7
- import 'element-plus/theme-chalk/el-tag.css'
8
-
9
7
  defineProps<{
10
8
  header?: {
11
9
  title?: string
@@ -30,6 +30,8 @@ function createElement() {
30
30
  }
31
31
 
32
32
  function showFullLoading() {
33
+ if (typeof window === 'undefined')
34
+ return
33
35
  if (!el)
34
36
  el = createElement()
35
37
  el.style.opacity = '0'
@@ -41,6 +43,8 @@ function showFullLoading() {
41
43
  }
42
44
 
43
45
  function hideFullLoading() {
46
+ if (typeof window === 'undefined')
47
+ return
44
48
  if (!el)
45
49
  return
46
50
  el.style.opacity = '0'
@@ -52,6 +56,8 @@ function hideFullLoading() {
52
56
  }
53
57
 
54
58
  function loadFonts(fontFamily: string, url: string) {
59
+ if (typeof window === 'undefined')
60
+ return
55
61
  const font = new FontFace(fontFamily, `url(${url})`)
56
62
  font.load().then(() => {
57
63
  ;(document.fonts as any).add(font)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valaxy-theme-hairy",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "packageManager": "pnpm@8.10.5",
5
5
  "author": {
6
6
  "email": "wwu710632@gmail.com",
package/pages/index.vue CHANGED
@@ -1,6 +1,4 @@
1
- <template>
2
- <HairyPosts pagination />
3
- </template>
1
+ <template></template>
4
2
 
5
3
  <route lang="yaml">
6
4
  meta:
@@ -1,10 +1,4 @@
1
- <script setup lang="ts">
2
-
3
- </script>
4
-
5
- <template>
6
- <HairyPosts :pagination="true" :cur-page="parseInt(String($route.params.page))" />
7
- </template>
1
+ <template></template>
8
2
 
9
3
  <route lang="yaml">
10
4
  meta:
@@ -6,6 +6,7 @@ body {
6
6
  font-family: var(--hy-font-family-seto);
7
7
  color: var(--va-c-text);
8
8
  background-color: var(--va-c-bg);
9
+ @apply transition-colors duration-200;
9
10
  }
10
11
 
11
12
  html,
package/utils/size.ts CHANGED
@@ -19,7 +19,6 @@ export interface Size { width: string, height: string }
19
19
  /**
20
20
  * 将 size 转换为宽高,用于元素宽高
21
21
  * @param size AtWillSize
22
- * @returns
23
22
  */
24
23
  export function atWillToSize(size: AtWillSize, unit?: string): Size {
25
24
  const _atWillToUnit = (value: any) => atWillToUnit(value, unit)