@sugarat/theme 0.2.8 → 0.2.10
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/README.md +3 -2
- package/node.d.ts +18 -1
- package/node.js +1 -1
- package/package.json +2 -2
- package/src/components/BlogAlert.vue +18 -2
- package/src/components/BlogFriendLink.vue +95 -14
- package/src/components/BlogHomeTags.vue +14 -2
- package/src/components/BlogPopover.vue +18 -2
- package/src/composables/config/index.ts +19 -1
- package/src/utils/client/index.ts +8 -0
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ npm create @sugarat/theme@latest
|
|
|
18
18
|
yarn create @sugarat/theme
|
|
19
19
|
|
|
20
20
|
# With Bun
|
|
21
|
-
|
|
21
|
+
bun create @sugarat/theme
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|

|
|
@@ -54,4 +54,5 @@ pnpm serve
|
|
|
54
54
|
* [charles7c.github.io](https://github.com/Charles7c/charles7c.github.io)
|
|
55
55
|
* [vitepress-blog-zaun](https://github.com/clark-cui/vitepress-blog-zaun)
|
|
56
56
|
* [surmon](https://surmon.me/)
|
|
57
|
-
* [vuejs/blog](https://github.com/vuejs/blog/tree/main)
|
|
57
|
+
* [vuejs/blog](https://github.com/vuejs/blog/tree/main)
|
|
58
|
+
* [列表无限滚动轮播效果](https://code.juejin.cn/pen/7145007064350195748)
|
package/node.d.ts
CHANGED
|
@@ -181,6 +181,23 @@ declare namespace Theme {
|
|
|
181
181
|
url: string;
|
|
182
182
|
avatar: ThemeableImage;
|
|
183
183
|
}
|
|
184
|
+
interface FriendConfig {
|
|
185
|
+
list: FriendLink[];
|
|
186
|
+
/**
|
|
187
|
+
* 是否随机展示
|
|
188
|
+
* @default false
|
|
189
|
+
*/
|
|
190
|
+
random?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* 是否限制展示数量(超出自动切换)
|
|
193
|
+
*/
|
|
194
|
+
limit?: number;
|
|
195
|
+
/**
|
|
196
|
+
* 滚动速度(ms),设置为 0 不滚动直接截取
|
|
197
|
+
* @default "动态计算"
|
|
198
|
+
*/
|
|
199
|
+
scrollSpeed?: number;
|
|
200
|
+
}
|
|
184
201
|
interface UserWork {
|
|
185
202
|
title: string;
|
|
186
203
|
description: string;
|
|
@@ -264,7 +281,7 @@ declare namespace Theme {
|
|
|
264
281
|
*/
|
|
265
282
|
alert?: Alert;
|
|
266
283
|
popover?: Popover;
|
|
267
|
-
friend?: FriendLink[];
|
|
284
|
+
friend?: FriendLink[] | FriendConfig;
|
|
268
285
|
authorList?: Omit<FriendLink, 'avatar'>[];
|
|
269
286
|
/**
|
|
270
287
|
* 启用 [vitepress-plugin-tabs](https://www.npmjs.com/package/vitepress-plugin-tabs)
|
package/node.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(node_exports, {
|
|
|
36
36
|
});
|
|
37
37
|
module.exports = __toCommonJS(node_exports);
|
|
38
38
|
|
|
39
|
-
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.0.0-rc.
|
|
39
|
+
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.0.0-rc.32_vue@3.3.4/node_modules/vitepress-plugin-tabs/dist/index.js
|
|
40
40
|
var tabsMarker = "=tabs";
|
|
41
41
|
var tabsMarkerLen = tabsMarker.length;
|
|
42
42
|
var ruleBlockTabs = (state, startLine, endLine, silent) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
4
4
|
"description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
|
|
5
5
|
"author": "sugar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"pagefind": "1.0.3",
|
|
55
55
|
"sass": "^1.56.1",
|
|
56
56
|
"typescript": "^4.8.2",
|
|
57
|
-
"vitepress": "1.0.0-rc.
|
|
57
|
+
"vitepress": "1.0.0-rc.32",
|
|
58
58
|
"vue": "^3.3.4"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
@@ -5,9 +5,9 @@ import { useBlogConfig } from '../composables/config/blog'
|
|
|
5
5
|
|
|
6
6
|
const { alert: alertProps } = useBlogConfig()
|
|
7
7
|
const show = ref(false)
|
|
8
|
-
|
|
8
|
+
const storageKey = 'theme-blog-alert'
|
|
9
|
+
const closeFlag = `${storageKey}-close`
|
|
9
10
|
onMounted(() => {
|
|
10
|
-
const storageKey = 'theme-blog-alert'
|
|
11
11
|
// 取旧值
|
|
12
12
|
const oldValue = localStorage.getItem(storageKey)
|
|
13
13
|
const newValue = JSON.stringify(alertProps)
|
|
@@ -21,13 +21,28 @@ onMounted(() => {
|
|
|
21
21
|
show.value = false
|
|
22
22
|
}, alertProps?.duration)
|
|
23
23
|
}
|
|
24
|
+
return
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
if (oldValue !== newValue && alertProps?.duration === -1) {
|
|
27
28
|
// 当做新值处理
|
|
28
29
|
show.value = true
|
|
30
|
+
localStorage.removeItem(closeFlag)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 新旧相等,判断是否点击过close,没点击关闭依然展示
|
|
35
|
+
if (oldValue === newValue && alertProps?.duration === -1 && !localStorage.getItem(closeFlag)) {
|
|
36
|
+
show.value = true
|
|
29
37
|
}
|
|
30
38
|
})
|
|
39
|
+
|
|
40
|
+
function handleClose() {
|
|
41
|
+
show.value = false
|
|
42
|
+
if (alertProps?.duration === -1) {
|
|
43
|
+
localStorage.setItem(closeFlag, `${+new Date()}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
31
46
|
</script>
|
|
32
47
|
|
|
33
48
|
<template>
|
|
@@ -40,6 +55,7 @@ onMounted(() => {
|
|
|
40
55
|
:closable="alertProps?.closable"
|
|
41
56
|
:close-text="alertProps?.closeText"
|
|
42
57
|
:description="alertProps?.description"
|
|
58
|
+
@close="handleClose"
|
|
43
59
|
>
|
|
44
60
|
<div v-if="alertProps?.html" v-html="alertProps?.html" />
|
|
45
61
|
</ElAlert>
|
|
@@ -3,15 +3,48 @@ import { ElAvatar } from 'element-plus'
|
|
|
3
3
|
import { useDark } from '@vueuse/core'
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
import { useBlogConfig } from '../composables/config/blog'
|
|
6
|
-
import { getImageUrl } from '../utils/client'
|
|
6
|
+
import { getImageUrl, shuffleArray } from '../utils/client'
|
|
7
|
+
import type { Theme } from '../'
|
|
7
8
|
|
|
8
9
|
const isDark = useDark({
|
|
9
10
|
storageKey: 'vitepress-theme-appearance'
|
|
10
11
|
})
|
|
11
12
|
|
|
12
13
|
const { friend } = useBlogConfig()
|
|
14
|
+
const friendConfig = computed<Theme.FriendConfig>(() => ({
|
|
15
|
+
list: [],
|
|
16
|
+
random: false,
|
|
17
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
18
|
+
...(Array.isArray(friend) ? { list: friend } : friend)
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
// TODO: 待优化
|
|
22
|
+
const limit = computed(() => {
|
|
23
|
+
const { limit } = friendConfig.value
|
|
24
|
+
return (!limit || limit <= 0) ? 0 : limit || Number.MAX_SAFE_INTEGER
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const scrollSpeed = computed(() => {
|
|
28
|
+
const { scrollSpeed } = friendConfig.value
|
|
29
|
+
return (!scrollSpeed || scrollSpeed <= 0) ? (friendConfig.value.list.length - 1) * (friendConfig.value.list.length / limit.value) * 1000 : scrollSpeed
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const openScroll = computed(() => {
|
|
33
|
+
return scrollSpeed.value > 0 && limit.value < friendConfig.value.list.length
|
|
34
|
+
})
|
|
35
|
+
|
|
13
36
|
const friendList = computed(() => {
|
|
14
|
-
|
|
37
|
+
const data = [...friendConfig.value.list]
|
|
38
|
+
// 简单的随机打乱
|
|
39
|
+
if (friendConfig.value.random) {
|
|
40
|
+
data.splice(0, data.length, ...shuffleArray(data))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 展示个数限制,删除多余的
|
|
44
|
+
if (scrollSpeed.value === 0 && limit.value) {
|
|
45
|
+
data.splice(limit.value)
|
|
46
|
+
}
|
|
47
|
+
const list = data.map((v) => {
|
|
15
48
|
const { avatar, nickname } = v
|
|
16
49
|
const avatarUrl = getImageUrl(avatar, isDark.value)
|
|
17
50
|
let alt = nickname
|
|
@@ -25,6 +58,18 @@ const friendList = computed(() => {
|
|
|
25
58
|
alt
|
|
26
59
|
}
|
|
27
60
|
})
|
|
61
|
+
return openScroll.value ? [...list, ...list] : list
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const cardHeight = 83
|
|
65
|
+
const scrollWrapperHeight = computed(() => {
|
|
66
|
+
return openScroll.value ? limit.value * cardHeight : 0
|
|
67
|
+
})
|
|
68
|
+
const containerHeight = computed(() => {
|
|
69
|
+
return scrollWrapperHeight.value ? `${scrollWrapperHeight.value}px` : 'auto'
|
|
70
|
+
})
|
|
71
|
+
const scrollTop = computed(() => {
|
|
72
|
+
return `-${scrollWrapperHeight.value * 2}px`
|
|
28
73
|
})
|
|
29
74
|
</script>
|
|
30
75
|
|
|
@@ -51,18 +96,30 @@ const friendList = computed(() => {
|
|
|
51
96
|
/>
|
|
52
97
|
</svg> 友情链接</span>
|
|
53
98
|
</div>
|
|
54
|
-
<!--
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
99
|
+
<!-- 友链列表 -->
|
|
100
|
+
<div
|
|
101
|
+
class="scroll-wrapper" :style="{
|
|
102
|
+
height: containerHeight,
|
|
103
|
+
}"
|
|
104
|
+
>
|
|
105
|
+
<ol
|
|
106
|
+
class="friend-list" :style="{
|
|
107
|
+
animationPlayState: openScroll ? 'running' : 'paused',
|
|
108
|
+
animationDuration: `${scrollSpeed / 1000}s`,
|
|
109
|
+
}
|
|
110
|
+
"
|
|
111
|
+
>
|
|
112
|
+
<li v-for="v in friendList" :key="v.nickname">
|
|
113
|
+
<a :href="v.url" target="_blank">
|
|
114
|
+
<ElAvatar :size="50" :src="v.avatar" :alt="v.alt" />
|
|
115
|
+
<div>
|
|
116
|
+
<span class="nickname">{{ v.nickname }}</span>
|
|
117
|
+
<p class="des">{{ v.des }}</p>
|
|
118
|
+
</div>
|
|
119
|
+
</a>
|
|
120
|
+
</li>
|
|
121
|
+
</ol>
|
|
122
|
+
</div>
|
|
66
123
|
</div>
|
|
67
124
|
</template>
|
|
68
125
|
|
|
@@ -102,6 +159,20 @@ const friendList = computed(() => {
|
|
|
102
159
|
flex-direction: column;
|
|
103
160
|
}
|
|
104
161
|
|
|
162
|
+
@keyframes scrollList {
|
|
163
|
+
0% {
|
|
164
|
+
top: 0;
|
|
165
|
+
}
|
|
166
|
+
100% {
|
|
167
|
+
top: v-bind(scrollTop);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.scroll-wrapper {
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
position: relative;
|
|
174
|
+
}
|
|
175
|
+
|
|
105
176
|
.friend-list {
|
|
106
177
|
display: flex;
|
|
107
178
|
flex-direction: column;
|
|
@@ -110,6 +181,16 @@ const friendList = computed(() => {
|
|
|
110
181
|
padding: 0 10px 0 0px;
|
|
111
182
|
width: 100%;
|
|
112
183
|
|
|
184
|
+
position: relative;
|
|
185
|
+
width: 100%;
|
|
186
|
+
animation-name: scrollList;
|
|
187
|
+
animation-timing-function:linear;
|
|
188
|
+
animation-iteration-count:infinite;
|
|
189
|
+
|
|
190
|
+
&:hover {
|
|
191
|
+
animation-play-state: paused !important;
|
|
192
|
+
}
|
|
193
|
+
|
|
113
194
|
li {
|
|
114
195
|
padding: 6px;
|
|
115
196
|
margin-top: 10px;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { computed, watch } from 'vue'
|
|
3
3
|
import { ElTag } from 'element-plus'
|
|
4
|
-
import { useBrowserLocation, useDark } from '@vueuse/core'
|
|
5
|
-
import { useRouter } from 'vitepress'
|
|
4
|
+
import { useBrowserLocation, useDark, useUrlSearchParams } from '@vueuse/core'
|
|
5
|
+
import { useRouter, useRoute } from 'vitepress'
|
|
6
6
|
import {
|
|
7
7
|
useActiveTag,
|
|
8
8
|
useArticles,
|
|
9
9
|
useCurrentPageNum
|
|
10
10
|
} from '../composables/config/blog'
|
|
11
11
|
|
|
12
|
+
const route = useRoute()
|
|
12
13
|
const docs = useArticles()
|
|
13
14
|
|
|
14
15
|
const tags = computed(() => {
|
|
@@ -62,6 +63,17 @@ watch(
|
|
|
62
63
|
immediate: true
|
|
63
64
|
}
|
|
64
65
|
)
|
|
66
|
+
|
|
67
|
+
watch(
|
|
68
|
+
route,
|
|
69
|
+
() => {
|
|
70
|
+
const params = useUrlSearchParams()
|
|
71
|
+
if (!params.tag) {
|
|
72
|
+
activeTag.value.type = ''
|
|
73
|
+
activeTag.value.label = ''
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
)
|
|
65
77
|
</script>
|
|
66
78
|
|
|
67
79
|
<template>
|
|
@@ -17,13 +17,14 @@ const bodyContent = computed(() => {
|
|
|
17
17
|
const footerContent = computed(() => {
|
|
18
18
|
return popoverProps?.footer || []
|
|
19
19
|
})
|
|
20
|
+
const storageKey = 'theme-blog-popover'
|
|
21
|
+
const closeFlag = `${storageKey}-close`
|
|
20
22
|
|
|
21
23
|
onMounted(() => {
|
|
22
24
|
if (!popoverProps?.title) {
|
|
23
25
|
return
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const storageKey = 'theme-blog-popover'
|
|
27
28
|
// 取旧值
|
|
28
29
|
const oldValue = localStorage.getItem(storageKey)
|
|
29
30
|
const newValue = JSON.stringify(popoverProps)
|
|
@@ -37,14 +38,29 @@ onMounted(() => {
|
|
|
37
38
|
show.value = false
|
|
38
39
|
}, popoverProps?.duration)
|
|
39
40
|
}
|
|
41
|
+
return
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
if (oldValue !== newValue && popoverProps?.duration === -1) {
|
|
43
45
|
// 当做新值处理
|
|
44
46
|
show.value = true
|
|
47
|
+
localStorage.removeItem(closeFlag)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 新旧相等,判断是否点击过close,没点击关闭依然展示
|
|
52
|
+
if (oldValue === newValue && popoverProps?.duration === -1 && !localStorage.getItem(closeFlag)) {
|
|
53
|
+
show.value = true
|
|
45
54
|
}
|
|
46
55
|
})
|
|
47
56
|
|
|
57
|
+
function handleClose() {
|
|
58
|
+
show.value = false
|
|
59
|
+
if (popoverProps?.duration === -1) {
|
|
60
|
+
localStorage.setItem(closeFlag, `${+new Date()}`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
48
64
|
function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
49
65
|
{ slots }: any) {
|
|
50
66
|
const { key, item } = props
|
|
@@ -105,7 +121,7 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
|
105
121
|
</ElIcon>
|
|
106
122
|
<span class="title">{{ popoverProps?.title }}</span>
|
|
107
123
|
</div>
|
|
108
|
-
<ElIcon class="close-icon" size="20px" @click="
|
|
124
|
+
<ElIcon class="close-icon" size="20px" @click="handleClose">
|
|
109
125
|
<CircleCloseFilled />
|
|
110
126
|
</ElIcon>
|
|
111
127
|
</div>
|
|
@@ -193,6 +193,24 @@ export namespace Theme {
|
|
|
193
193
|
avatar: ThemeableImage
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
export interface FriendConfig {
|
|
197
|
+
list: FriendLink[]
|
|
198
|
+
/**
|
|
199
|
+
* 是否随机展示
|
|
200
|
+
* @default false
|
|
201
|
+
*/
|
|
202
|
+
random?: boolean
|
|
203
|
+
/**
|
|
204
|
+
* 是否限制展示数量(超出自动切换)
|
|
205
|
+
*/
|
|
206
|
+
limit?: number
|
|
207
|
+
/**
|
|
208
|
+
* 滚动速度(ms),设置为 0 不滚动直接截取
|
|
209
|
+
* @default "动态计算"
|
|
210
|
+
*/
|
|
211
|
+
scrollSpeed?: number
|
|
212
|
+
}
|
|
213
|
+
|
|
196
214
|
export interface UserWork {
|
|
197
215
|
title: string
|
|
198
216
|
description: string
|
|
@@ -295,7 +313,7 @@ export namespace Theme {
|
|
|
295
313
|
*/
|
|
296
314
|
alert?: Alert
|
|
297
315
|
popover?: Popover
|
|
298
|
-
friend?: FriendLink[]
|
|
316
|
+
friend?: FriendLink[] | FriendConfig
|
|
299
317
|
authorList?: Omit<FriendLink, 'avatar'>[]
|
|
300
318
|
/**
|
|
301
319
|
* 启用 [vitepress-plugin-tabs](https://www.npmjs.com/package/vitepress-plugin-tabs)
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ThemeableImage } from '../../composables/config'
|
|
2
2
|
|
|
3
|
+
export function shuffleArray(arr: any[]) {
|
|
4
|
+
const array = [...arr]
|
|
5
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
6
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
7
|
+
[array[i], array[j]] = [array[j], array[i]]
|
|
8
|
+
}
|
|
9
|
+
return array
|
|
10
|
+
}
|
|
3
11
|
export function formatDate(d: any, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
|
4
12
|
if (!(d instanceof Date)) {
|
|
5
13
|
d = new Date(d)
|