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 +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 +70 -0
- package/components/HairyNavbar.vue +1 -1
- package/components/HairyPageTags.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>
|
@@ -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-
|
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" />
|
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)
|