valaxy-theme-hairy 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- package/components/HairyBody.vue +2 -6
- package/components/HairyFooterFish.vue +14 -4
- package/components/HairyNavBackground.vue +1 -1
- package/components/HairyNavToggleDark.vue +1 -1
- package/components/HairyPostList.vue +6 -3
- package/components/HairyPostToggleLayout.vue +33 -0
- package/components/{HairyArticleImageDefault.vue → article-layout/HairyArticleImage.vue} +8 -8
- package/components/{HairyArticleSeries.vue → article-layout/HairyArticleSeries.vue} +2 -2
- package/components/{HairyArticleText.vue → article-layout/HairyArticleText.vue} +2 -11
- package/components/{HairyArticleTop.vue → article-layout/HairyArticleTop.vue} +0 -0
- package/components/{HairyPostImageList.vue → article-layout/HairyPostImageList.vue} +2 -2
- package/components/{HairyPostTextsList.vue → article-layout/HairyPostTextsList.vue} +0 -0
- package/components/images/bg.jpg +0 -0
- package/components/{fish.js → lib/fish.js} +20 -2
- package/hooks/usePostLayout.ts +9 -2
- package/index.d.ts +3 -1
- package/package.json +1 -1
- package/pages/categories/[...categories].vue +1 -4
- package/unocss.config.ts +10 -0
- package/components/HairyArticleImage.vue +0 -17
- package/components/HairyArticleImageSmall.vue +0 -49
package/components/HairyBody.vue
CHANGED
@@ -15,6 +15,7 @@ const showWaline = computed(() => route.path.includes('/posts/') || fr.value.wal
|
|
15
15
|
<slot />
|
16
16
|
<HairyWaline v-if="showWaline" />
|
17
17
|
</div>
|
18
|
+
|
18
19
|
<div class="ml-4 w-60 lg:block hidden">
|
19
20
|
<div class="sticky top-3.125rem z-1">
|
20
21
|
<slot v-if="$slots.slide" name="slide" />
|
@@ -27,10 +28,6 @@ const showWaline = computed(() => route.path.includes('/posts/') || fr.value.wal
|
|
27
28
|
</template>
|
28
29
|
|
29
30
|
<style lang="scss">
|
30
|
-
.a {
|
31
|
-
overflow: hidden;
|
32
|
-
}
|
33
|
-
|
34
31
|
.HairyBodyBackground {
|
35
32
|
@apply transition-all duration-200;
|
36
33
|
@apply absolute top-0 max-h-150vh top-5 bottom-0 w-full transition-opacity;
|
@@ -43,8 +40,7 @@ const showWaline = computed(() => route.path.includes('/posts/') || fr.value.wal
|
|
43
40
|
transition-delay: 0;
|
44
41
|
opacity: 1;
|
45
42
|
background-image:
|
46
|
-
linear-gradient(to bottom, var(--hy-c-waves-dimm) 0%, transparent 60%, var(--hy-c-waves-dimm) 100%),
|
47
|
-
url(https://tva2.sinaimg.cn/large/008ugSUaly8h4mt4lbuc0j31hc0u040c.jpg);
|
43
|
+
linear-gradient(to bottom, var(--hy-c-waves-dimm) 0%, transparent 60%, var(--hy-c-waves-dimm) 100%), url(./images/bg.jpg);
|
48
44
|
background-position: center;
|
49
45
|
opacity: 0.4;
|
50
46
|
background-repeat: no-repeat;
|
@@ -1,16 +1,26 @@
|
|
1
|
+
<!-- eslint-disable no-new -->
|
1
2
|
<script lang="ts" setup>
|
2
3
|
import { onMounted, ref } from 'vue'
|
3
4
|
import { useScriptTag } from '@vueuse/core'
|
4
|
-
import { RENDERER } from './fish'
|
5
|
+
import { RENDERER } from './lib/fish'
|
5
6
|
|
6
7
|
const fishContainer = ref()
|
7
8
|
|
8
9
|
const tag = useScriptTag('https://cdn.bootcdn.net/ajax/libs/zepto/1.2.0/zepto.min.js')
|
10
|
+
|
11
|
+
let renderer: RENDERER
|
12
|
+
|
13
|
+
function reset() {
|
14
|
+
const color = 'hsl(0, 0%, 95%)'
|
15
|
+
if (!renderer)
|
16
|
+
renderer = new RENDERER(color)
|
17
|
+
else
|
18
|
+
renderer.setColor(color)
|
19
|
+
}
|
20
|
+
|
9
21
|
onMounted(() => {
|
10
22
|
tag.load()
|
11
|
-
.then(
|
12
|
-
new RENDERER().init()
|
13
|
-
})
|
23
|
+
.then(reset)
|
14
24
|
})
|
15
25
|
</script>
|
16
26
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<template>
|
2
|
-
<div class="HairyNavBackground transition-colors absolute inset-0 bg-white bg-opacity-
|
2
|
+
<div class="HairyNavBackground transition-colors absolute inset-0 bg-white bg-opacity-85 dark:bg-black dark:bg-opacity-80 blur-5" />
|
3
3
|
</template>
|
4
4
|
|
5
5
|
<style>
|
@@ -10,7 +10,7 @@ const themeTitle = computed(() => {
|
|
10
10
|
</script>
|
11
11
|
|
12
12
|
<template>
|
13
|
-
<button class="yun-icon-btn" :title="themeTitle" :style="{ color: isDark ? '' : '#f1cb64' }" @click="toggleDark()">
|
13
|
+
<button class="yun-icon-btn bg-light-1 p-1 dark:bg-transparent rounded-5" :title="themeTitle" :style="{ color: isDark ? '' : '#f1cb64' }" @click="toggleDark()">
|
14
14
|
<div i="ri-sun-line dark:ri-moon-line" />
|
15
15
|
</button>
|
16
16
|
</template>
|
@@ -23,9 +23,12 @@ const displayedPosts = computed(() => props.pagination ? pagePosts.value : posts
|
|
23
23
|
</script>
|
24
24
|
|
25
25
|
<template>
|
26
|
-
<
|
27
|
-
|
28
|
-
|
26
|
+
<div class="mt-8">
|
27
|
+
<HairyPostToggleLayout />
|
28
|
+
<HairyPostImageList v-if="layout.includes('image')" :posts="displayedPosts" />
|
29
|
+
<HairyPostTextsList v-else :posts="displayedPosts" />
|
30
|
+
<ValaxyPagination v-if="pagination" :cur-page="curPage" :page-size="pageSize" :total="posts.length" />
|
31
|
+
</div>
|
29
32
|
</template>
|
30
33
|
|
31
34
|
<style lang="scss">
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<script lang="ts" setup>
|
2
|
+
import { useScroll } from '@vueuse/core'
|
3
|
+
import { computed } from 'vue'
|
4
|
+
import { usePostLayout } from '../hooks/usePostLayout'
|
5
|
+
import { useHeaderHeight } from '../hooks/useHeaderHeight'
|
6
|
+
import type { HairyPostLayout } from '..'
|
7
|
+
|
8
|
+
const layout = usePostLayout()
|
9
|
+
const layouts = [
|
10
|
+
{ layout: 'image:slice:reverse' as HairyPostLayout, icon: 'i-fluent-text-align-distributed-24-filled' },
|
11
|
+
{ layout: 'image:slice' as HairyPostLayout, icon: 'i-fluent-text-align-left-16-filled' },
|
12
|
+
{ layout: 'image' as HairyPostLayout, icon: 'i-fluent-text-align-justify-20-filled' },
|
13
|
+
{ layout: 'markdown' as HairyPostLayout, icon: 'i-fluent-markdown-20-filled' },
|
14
|
+
{ layout: 'text' as HairyPostLayout, icon: 'i-fluent-code-text-16-filled' },
|
15
|
+
]
|
16
|
+
|
17
|
+
const headerHeight = useHeaderHeight()
|
18
|
+
const scroll = useScroll(document)
|
19
|
+
|
20
|
+
const show = computed(() => {
|
21
|
+
return scroll.y.value > headerHeight.value
|
22
|
+
})
|
23
|
+
</script>
|
24
|
+
|
25
|
+
<template>
|
26
|
+
<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']">
|
27
|
+
<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">
|
28
|
+
<div class="text-size-xl" :class="item.icon" />
|
29
|
+
</div>
|
30
|
+
</div>
|
31
|
+
</template>
|
32
|
+
|
33
|
+
<style lang="scss" scoped></style>
|
@@ -5,8 +5,8 @@ import dayjs from 'dayjs'
|
|
5
5
|
import { useRouter } from 'vue-router'
|
6
6
|
import { useI18n } from 'vue-i18n'
|
7
7
|
import { last } from 'lodash-es'
|
8
|
-
import { usePostLayout } from '
|
9
|
-
import { toArr } from '
|
8
|
+
import { usePostLayout } from '../../hooks/usePostLayout'
|
9
|
+
import { toArr } from '../../utils'
|
10
10
|
|
11
11
|
const props = defineProps<{
|
12
12
|
post: Post
|
@@ -30,24 +30,24 @@ const displayCategory = (keys: string | string[] = []) => {
|
|
30
30
|
</script>
|
31
31
|
|
32
32
|
<template>
|
33
|
-
<li class="HairyArticleImage
|
33
|
+
<li class="HairyArticleImage mb-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
|
34
34
|
<article>
|
35
35
|
<div class="flex justify-between items-center">
|
36
|
-
<a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-
|
37
|
-
<div class="flex justify-end gap-2 text-size-sm">
|
36
|
+
<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
|
+
<div class="flex justify-end gap-2 text-size-sm lt-sm:text-size-xs">
|
38
38
|
<span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
39
39
|
<span>{{ (post.length / 1000).toFixed(1) }}k字</span>
|
40
|
-
<span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
|
40
|
+
<span class="lt-sm:hidden">{{ post.durations.minutes.toFixed(2) }}分钟</span>
|
41
41
|
</div>
|
42
42
|
</div>
|
43
|
-
<div class="h-200px flex">
|
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']">
|
44
44
|
<div class="flex-1 post-image-content" :class="[reverse ? 'order-last' : 'order-first']">
|
45
45
|
<img
|
46
46
|
class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image"
|
47
47
|
@click="onReadMore"
|
48
48
|
/>
|
49
49
|
</div>
|
50
|
-
<div class="flex flex-col justify-between
|
50
|
+
<div class="flex-1 flex flex-col justify-between py-2 dark:py-0">
|
51
51
|
<div class="flex-1 text-size-sm">
|
52
52
|
<div class="line-clamp-text">
|
53
53
|
{{ post.text }}
|
@@ -2,8 +2,8 @@
|
|
2
2
|
import { useFrontmatter } from 'valaxy'
|
3
3
|
import { computed, inject, nextTick, ref } from 'vue'
|
4
4
|
import { useRouter } from 'vue-router'
|
5
|
-
import { toArr } from '
|
6
|
-
import { useCurrentCategory } from '
|
5
|
+
import { toArr } from '../../utils'
|
6
|
+
import { useCurrentCategory } from '../../hooks/useCategory'
|
7
7
|
|
8
8
|
const frontmatter = useFrontmatter()
|
9
9
|
const paths = computed(() => toArr(frontmatter.value.categories).filter(Boolean) as string[])
|
@@ -1,25 +1,16 @@
|
|
1
1
|
<script lang="ts" setup>
|
2
|
-
import { useThemeConfig } from 'valaxy'
|
3
2
|
import type { Post } from 'valaxy'
|
4
3
|
import { computed, defineProps } from 'vue'
|
5
|
-
import
|
4
|
+
import { usePostLayout } from '../../hooks/usePostLayout'
|
6
5
|
const props = defineProps<{
|
7
6
|
post: Post
|
8
7
|
}>()
|
9
|
-
const
|
10
|
-
const layout = computed(() => themeConfig.value.post?.layout || 'text')
|
8
|
+
const layout = usePostLayout()
|
11
9
|
const text = computed(() => {
|
12
10
|
if (layout.value === 'text')
|
13
11
|
return props.post.text
|
14
12
|
return props.post.excerpt
|
15
13
|
})
|
16
|
-
|
17
|
-
const Blogs = {
|
18
|
-
name: 'Mao’s blog',
|
19
|
-
desc: '记录生活、持续学习。',
|
20
|
-
link: 'https://hairy.blog/',
|
21
|
-
thumbnail: 'https://user-images.githubusercontent.com/49724027/182444624-6228d153-94cb-461d-a5d8-be8535441fb6.png',
|
22
|
-
}
|
23
14
|
</script>
|
24
15
|
|
25
16
|
<template>
|
File without changes
|
@@ -4,7 +4,7 @@ import { computed } from 'vue'
|
|
4
4
|
import type { Post } from 'valaxy'
|
5
5
|
import { usePostList } from 'valaxy'
|
6
6
|
|
7
|
-
import { usePostLayout } from '
|
7
|
+
import { usePostLayout } from '../../hooks/usePostLayout'
|
8
8
|
|
9
9
|
const props = withDefaults(defineProps<{
|
10
10
|
type?: string
|
@@ -21,7 +21,7 @@ const reverse = computed(() => layout.value.includes('reverse'))
|
|
21
21
|
<template>
|
22
22
|
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
|
23
23
|
<Transition v-for="post, i in posts" :key="i" name="fade">
|
24
|
-
<HairyArticleImage :post="post" :reverse="reverse && (i % 2) === 0" />
|
24
|
+
<HairyArticleImage :post="post" :reverse="reverse && !((i % 2) === 0)" />
|
25
25
|
</Transition>
|
26
26
|
</ul>
|
27
27
|
</template>
|
File without changes
|
Binary file
|
@@ -11,9 +11,15 @@ class RENDERER {
|
|
11
11
|
MAX_INTERVAL_COUNT = 50
|
12
12
|
INIT_HEIGHT_RATE = 0.5
|
13
13
|
THRESHOLD = 50
|
14
|
-
|
14
|
+
|
15
|
+
COLOR = 'hsl(0, 0%, 95%)'
|
16
|
+
|
17
|
+
constructor(color) {
|
15
18
|
if (this.ENABLE)
|
16
19
|
return
|
20
|
+
|
21
|
+
this.setColor(color)
|
22
|
+
this.removeCanvas()
|
17
23
|
this.setParameters()
|
18
24
|
this.reconstructMethods()
|
19
25
|
this.setup()
|
@@ -22,6 +28,18 @@ class RENDERER {
|
|
22
28
|
this.ENABLE = true
|
23
29
|
}
|
24
30
|
|
31
|
+
removeCanvas() {
|
32
|
+
const container = document.querySelector('#jsi-flying-fish-container')
|
33
|
+
const canvas = container.querySelector('canvas')
|
34
|
+
if (canvas)
|
35
|
+
canvas.remove()
|
36
|
+
}
|
37
|
+
|
38
|
+
setColor(color) {
|
39
|
+
if (color)
|
40
|
+
this.COLOR = color
|
41
|
+
}
|
42
|
+
|
25
43
|
setParameters() {
|
26
44
|
this.$window = $(window)
|
27
45
|
this.$container = $('#jsi-flying-fish-container')
|
@@ -148,7 +166,7 @@ class RENDERER {
|
|
148
166
|
requestAnimationFrame(this.render)
|
149
167
|
this.controlStatus()
|
150
168
|
this.context.clearRect(0, 0, this.width, this.height)
|
151
|
-
this.context.fillStyle =
|
169
|
+
this.context.fillStyle = this.COLOR
|
152
170
|
for (let i = 0, count = this.fishes.length; i < count; i++)
|
153
171
|
this.fishes[i].render(this.context)
|
154
172
|
|
package/hooks/usePostLayout.ts
CHANGED
@@ -1,9 +1,16 @@
|
|
1
1
|
import { useThemeConfig } from 'valaxy'
|
2
|
+
import { createSharedComposable, useStorage } from '@vueuse/core'
|
2
3
|
import { computed } from 'vue'
|
3
|
-
import type { HairyTheme } from '..'
|
4
|
+
import type { HairyPostLayout, HairyTheme } from '..'
|
5
|
+
|
6
|
+
const useSharedStorage = createSharedComposable(useStorage)
|
4
7
|
|
5
8
|
export function usePostLayout() {
|
6
9
|
const themeConfig = useThemeConfig<HairyTheme>()
|
7
|
-
const
|
10
|
+
const cache = useSharedStorage<HairyPostLayout>('--hairy-theme:post-layout', null)
|
11
|
+
const layout = computed({
|
12
|
+
get: () => cache.value || themeConfig.value.post?.layout || 'image',
|
13
|
+
set: value => cache.value = value,
|
14
|
+
})
|
8
15
|
return layout
|
9
16
|
}
|
package/index.d.ts
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import type { ViteSSGContext } from 'vite-ssg'
|
2
2
|
|
3
|
+
export type HairyPostLayout = 'text' | 'markdown' | 'image' | 'image:slice' | 'image:slice:reverse'
|
4
|
+
|
3
5
|
export interface HairyTheme {
|
4
6
|
nav?: NavItem[];
|
5
7
|
mode?: 'dark'
|
@@ -14,7 +16,7 @@ export interface HairyTheme {
|
|
14
16
|
description?: string;
|
15
17
|
};
|
16
18
|
post?: {
|
17
|
-
layout?:
|
19
|
+
layout?: HairyPostLayout
|
18
20
|
images?: string[]
|
19
21
|
}
|
20
22
|
categories?: {
|
package/package.json
CHANGED
@@ -7,14 +7,12 @@ import { ElTimeline, ElTimelineItem } from 'element-plus/es/components/timeline/
|
|
7
7
|
import { useCurrentCategory } from '../../hooks/useCategory'
|
8
8
|
import { useCategoryPost } from '../../hooks/useCategoryPost'
|
9
9
|
|
10
|
-
import { usePostLayout } from '../../hooks/usePostLayout'
|
11
10
|
import type { HairyTheme } from '../..'
|
12
11
|
import 'element-plus/es/components/timeline/style/index'
|
13
12
|
import 'element-plus/es/components/timeline-item/style/index'
|
14
13
|
const props = defineProps<{
|
15
14
|
categories: string
|
16
15
|
}>()
|
17
|
-
const layout = usePostLayout()
|
18
16
|
const themeConfig = useThemeConfig<HairyTheme>()
|
19
17
|
const categoriesLayout = computed(() => themeConfig.value.categories?.layout || 'post')
|
20
18
|
|
@@ -76,8 +74,7 @@ const displayCategory = (key: string) => {
|
|
76
74
|
</el-timeline-item>
|
77
75
|
</el-timeline>
|
78
76
|
<template v-else>
|
79
|
-
<
|
80
|
-
<HairyPostList v-else :posts="posts" />
|
77
|
+
<HairyPostList :posts="posts" />
|
81
78
|
</template>
|
82
79
|
</template>
|
83
80
|
|
package/unocss.config.ts
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
import { defineConfig } from 'unocss'
|
2
2
|
const safelist = [
|
3
3
|
'i-ri:home-2-fill',
|
4
|
+
'i-ri-list-check-2',
|
5
|
+
'i-ri-flow-chart',
|
6
|
+
'i-ri-user-line',
|
7
|
+
|
8
|
+
'i-fluent-text-align-distributed-24-filled',
|
9
|
+
'i-fluent-text-align-left-16-filled',
|
10
|
+
'i-fluent-text-align-justify-20-filled',
|
11
|
+
'i-fluent-markdown-20-filled',
|
12
|
+
'i-fluent-code-text-16-filled',
|
4
13
|
]
|
5
14
|
export default defineConfig({
|
6
15
|
theme: {
|
@@ -20,6 +29,7 @@ export default defineConfig({
|
|
20
29
|
['font-frederick', { 'font-family': 'var(--hy-font-family-frederick)' }],
|
21
30
|
['text-primary', { color: 'var(--hy-c-primary)' }],
|
22
31
|
['border-primary', { 'border-color': 'var(--hy-c-primary)' }],
|
32
|
+
['bg-primary', { background: 'var(--hy-c-primary)' }],
|
23
33
|
],
|
24
34
|
shortcuts: [
|
25
35
|
['flex-center', 'flex justify-center items-center'],
|
@@ -1,17 +0,0 @@
|
|
1
|
-
<script lang="ts" setup>
|
2
|
-
import type { Post } from 'valaxy'
|
3
|
-
|
4
|
-
defineProps<{
|
5
|
-
post: Post
|
6
|
-
reverse?: boolean
|
7
|
-
}>()
|
8
|
-
</script>
|
9
|
-
|
10
|
-
<template>
|
11
|
-
<HairyArticleImageDefault :post="post" :reverse="reverse" class="hidden sm:block" />
|
12
|
-
<HairyArticleImageSmall :post="post" :reverse="reverse" class="hidden lt-sm:block" />
|
13
|
-
</template>
|
14
|
-
|
15
|
-
<style lang="scss">
|
16
|
-
|
17
|
-
</style>
|
@@ -1,49 +0,0 @@
|
|
1
|
-
<script lang="ts" setup>
|
2
|
-
import type { Post } from 'valaxy'
|
3
|
-
import { computed, defineProps } from 'vue'
|
4
|
-
import dayjs from 'dayjs'
|
5
|
-
import { useRouter } from 'vue-router'
|
6
|
-
import { useI18n } from 'vue-i18n'
|
7
|
-
import { last } from 'lodash-es'
|
8
|
-
import { usePostLayout } from '../hooks/usePostLayout'
|
9
|
-
import { toArr } from '../utils'
|
10
|
-
|
11
|
-
const props = defineProps<{
|
12
|
-
post: Post
|
13
|
-
reverse?: boolean
|
14
|
-
}>()
|
15
|
-
const router = useRouter()
|
16
|
-
const layout = usePostLayout()
|
17
|
-
const slice = computed(() => layout.value.includes('slice'))
|
18
|
-
const image = computed(() => props.post.image)
|
19
|
-
|
20
|
-
const i18n = useI18n()
|
21
|
-
|
22
|
-
const onReadMore = () => {
|
23
|
-
if (props.post.path)
|
24
|
-
router.push(props.post.path)
|
25
|
-
}
|
26
|
-
|
27
|
-
const displayCategory = (keys: string | string[] = []) => {
|
28
|
-
router.push({ path: `/categories/${toArr(keys).join('/')}` })
|
29
|
-
}
|
30
|
-
</script>
|
31
|
-
|
32
|
-
<template>
|
33
|
-
<li class="HairyArticleImage my-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
|
34
|
-
<article>
|
35
|
-
<div class="flex justify-between items-center">
|
36
|
-
<a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-first' : 'order-last']" @click="onReadMore">{{ post.title }}</a>
|
37
|
-
<div class="flex justify-end gap-2 text-size-sm">
|
38
|
-
<span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
|
39
|
-
<span>{{ (post.length / 1000).toFixed(1) }}k字</span>
|
40
|
-
<span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
|
41
|
-
</div>
|
42
|
-
</div>
|
43
|
-
</article>
|
44
|
-
</li>
|
45
|
-
</template>
|
46
|
-
|
47
|
-
<style lang="scss">
|
48
|
-
|
49
|
-
</style>
|