valaxy-theme-hairy 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)