valaxy-theme-hairy 1.0.6 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- package/App.vue +15 -0
- package/components/HairyBody.vue +10 -3
- package/components/HairyContainer.vue +1 -9
- package/components/HairyDrawer.vue +5 -3
- package/components/HairyHeader.vue +1 -1
- package/components/HairyLinks.vue +1 -0
- package/components/HairyNavbar.vue +1 -1
- package/components/HairyPosts.vue +2 -0
- package/components/PageTags.vue +1 -1
- package/components/parts/HairyImageViewer.vue +3 -1
- package/components/posts/HairyArticleImage.vue +14 -7
- package/components/posts/HairyPostToggleLayout.vue +5 -2
- package/components/posts/HairyUpdatedPost.vue +75 -0
- package/layouts/home.vue +2 -2
- package/layouts/post.vue +0 -2
- package/library/loading.ts +6 -0
- package/package.json +1 -1
- package/pages/index.vue +1 -3
- package/pages/page/[page].vue +1 -7
- package/styles/global.scss +1 -0
- package/utils/size.ts +0 -1
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>
|
package/components/HairyBody.vue
CHANGED
@@ -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-
|
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
|
-
|
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>
|
@@ -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-
|
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">
|
@@ -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" />
|
package/components/PageTags.vue
CHANGED
@@ -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
|
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
|
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
|
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
|
-
<
|
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
package/library/loading.ts
CHANGED
@@ -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
package/pages/index.vue
CHANGED
package/pages/page/[page].vue
CHANGED
package/styles/global.scss
CHANGED
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)
|