@sugarat/theme 0.3.4 → 0.3.6
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/node.d.ts +12 -1
- package/node.js +1 -1
- package/package.json +6 -6
- package/src/components/BlogApp.vue +2 -3
- package/src/components/BlogList.vue +20 -20
- package/src/components/BlogOml2d.vue +10 -0
- package/src/components/BlogPopover.vue +35 -10
- package/src/components/CommentGiscus.vue +6 -6
- package/src/composables/config/index.ts +12 -1
package/node.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DefaultTheme, UserConfig } from 'vitepress';
|
|
1
|
+
import { Route, DefaultTheme, UserConfig } from 'vitepress';
|
|
2
2
|
import { ElButton } from 'element-plus';
|
|
3
3
|
import { RSSOptions } from 'vitepress-plugin-rss';
|
|
4
4
|
import { Repo, Mapping } from '@giscus/vue';
|
|
5
5
|
import { Options } from 'oh-my-live2d';
|
|
6
|
+
import { Ref } from 'vue';
|
|
6
7
|
export { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
|
|
7
8
|
|
|
8
9
|
type RSSPluginOptions = RSSOptions;
|
|
@@ -231,6 +232,11 @@ declare namespace Theme {
|
|
|
231
232
|
* @default true
|
|
232
233
|
*/
|
|
233
234
|
reopen?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* 是否打开闪烁提示,通常需要和 reopen 搭配使用
|
|
237
|
+
* @default true
|
|
238
|
+
*/
|
|
239
|
+
twinkle?: boolean;
|
|
234
240
|
/**
|
|
235
241
|
* 设置展示图标,svg
|
|
236
242
|
* @recommend https://iconbuddy.app/search?q=fire
|
|
@@ -241,6 +247,11 @@ declare namespace Theme {
|
|
|
241
247
|
* @recommend https://iconbuddy.app/search?q=fire
|
|
242
248
|
*/
|
|
243
249
|
closeIcon?: string;
|
|
250
|
+
/**
|
|
251
|
+
* 自定义展示策略
|
|
252
|
+
* @param to 切换到的目标路由
|
|
253
|
+
*/
|
|
254
|
+
onRouteChanged?: (to: Route, show: Ref<boolean>) => void;
|
|
244
255
|
}
|
|
245
256
|
interface FriendLink {
|
|
246
257
|
nickname: string;
|
package/node.js
CHANGED
|
@@ -40,7 +40,7 @@ module.exports = __toCommonJS(node_exports);
|
|
|
40
40
|
// src/utils/node/mdPlugins.ts
|
|
41
41
|
var import_module = require("module");
|
|
42
42
|
|
|
43
|
-
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.
|
|
43
|
+
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.2.2_@algolia+client-search@4.19.1_@types+node@20.6.3__yypdqxhxkwwat3gkog3hzzeasy/node_modules/vitepress-plugin-tabs/dist/index.js
|
|
44
44
|
var tabsMarker = "=tabs";
|
|
45
45
|
var tabsMarkerLen = tabsMarker.length;
|
|
46
46
|
var ruleBlockTabs = (state, startLine, endLine, silent) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
|
|
5
5
|
"author": "sugar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,23 +46,23 @@
|
|
|
46
46
|
"gray-matter": "^4.0.3",
|
|
47
47
|
"markdown-it-task-checkbox": "^1.0.6",
|
|
48
48
|
"mermaid": "^10.9.0",
|
|
49
|
-
"oh-my-live2d": "^0.
|
|
49
|
+
"oh-my-live2d": "^0.19.3",
|
|
50
50
|
"swiper": "^11.1.1",
|
|
51
51
|
"vitepress-markdown-timeline": "^1.2.1",
|
|
52
52
|
"vitepress-plugin-mermaid": "2.0.13",
|
|
53
53
|
"vitepress-plugin-tabs": "0.2.0",
|
|
54
|
-
"vitepress-plugin-pagefind": "0.
|
|
55
|
-
"vitepress-plugin-rss": "0.2.
|
|
54
|
+
"vitepress-plugin-pagefind": "0.3.3",
|
|
55
|
+
"vitepress-plugin-rss": "0.2.6"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@element-plus/icons-vue": "^2.3.1",
|
|
59
59
|
"artalk": "^2.8.5",
|
|
60
60
|
"element-plus": "^2.7.2",
|
|
61
|
-
"pagefind": "1.1.0",
|
|
61
|
+
"pagefind": "^1.1.0",
|
|
62
62
|
"sass": "^1.76.0",
|
|
63
63
|
"typescript": "^5.4.5",
|
|
64
64
|
"vite": "^5.2.11",
|
|
65
|
-
"vitepress": "1.
|
|
65
|
+
"vitepress": "1.2.2",
|
|
66
66
|
"vue": "^3.4.26"
|
|
67
67
|
},
|
|
68
68
|
"scripts": {
|
|
@@ -3,7 +3,6 @@ import Theme from 'vitepress/theme'
|
|
|
3
3
|
import { useData } from 'vitepress'
|
|
4
4
|
import { computed } from 'vue'
|
|
5
5
|
import { useDarkTransition } from '../hooks/useDarkTransition'
|
|
6
|
-
import { useOml2d } from '../hooks/useOml2d'
|
|
7
6
|
import { useBlogThemeMode, useDarkTransitionConfig } from '../composables/config/blog'
|
|
8
7
|
import BlogHomeInfo from './BlogHomeInfo.vue'
|
|
9
8
|
import BlogHomeBanner from './BlogHomeBanner.vue'
|
|
@@ -17,6 +16,7 @@ import BlogFooter from './BlogFooter.vue'
|
|
|
17
16
|
import BlogHomeHeaderAvatar from './BlogHomeHeaderAvatar.vue'
|
|
18
17
|
import BlogBackToTop from './BlogBackToTop.vue'
|
|
19
18
|
import CommentGiscus from './CommentGiscus.vue'
|
|
19
|
+
import BlogOml2d from './BlogOml2d.vue'
|
|
20
20
|
|
|
21
21
|
import CommentArtalk from './CommentArtalk.vue'
|
|
22
22
|
import BlogButtonAfterArticle from './BlogButtonAfterArticle.vue'
|
|
@@ -27,8 +27,6 @@ const layout = computed(() => frontmatter.value.layout)
|
|
|
27
27
|
const isBlogTheme = useBlogThemeMode()
|
|
28
28
|
const { Layout } = Theme
|
|
29
29
|
|
|
30
|
-
// oh-my-live2d 扩展
|
|
31
|
-
useOml2d()
|
|
32
30
|
// 切换深色模式过渡
|
|
33
31
|
// https://vitepress.dev/zh/guide/extending-default-theme#on-appearance-toggle
|
|
34
32
|
useDarkTransition()
|
|
@@ -40,6 +38,7 @@ const openTransition = useDarkTransitionConfig()
|
|
|
40
38
|
<template #layout-top>
|
|
41
39
|
<slot name="layout-top" />
|
|
42
40
|
<ClientOnly>
|
|
41
|
+
<BlogOml2d />
|
|
43
42
|
<BlogAlert />
|
|
44
43
|
<BlogPopover />
|
|
45
44
|
</ClientOnly>
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed,
|
|
2
|
+
import { computed, watch } from 'vue'
|
|
3
3
|
import { ElPagination } from 'element-plus'
|
|
4
|
-
import { useData, useRouter } from 'vitepress'
|
|
5
|
-
import { useBrowserLocation } from '@vueuse/core'
|
|
4
|
+
import { useData, useRoute, useRouter } from 'vitepress'
|
|
6
5
|
import {
|
|
7
6
|
useActiveTag,
|
|
8
7
|
useArticles,
|
|
@@ -54,12 +53,12 @@ const currentWikiData = computed(() => {
|
|
|
54
53
|
})
|
|
55
54
|
|
|
56
55
|
const router = useRouter()
|
|
57
|
-
const location = useBrowserLocation()
|
|
58
56
|
const queryPageNumKey = 'pageNum'
|
|
59
57
|
function handleUpdatePageNum(current: number) {
|
|
60
58
|
if (currentPage.value === current) {
|
|
61
59
|
return
|
|
62
60
|
}
|
|
61
|
+
currentPage.value = current
|
|
63
62
|
const { searchParams } = new URL(window.location.href!)
|
|
64
63
|
searchParams.delete(queryPageNumKey)
|
|
65
64
|
searchParams.append(queryPageNumKey, String(current))
|
|
@@ -69,25 +68,26 @@ function handleUpdatePageNum(current: number) {
|
|
|
69
68
|
)
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
const route = useRoute()
|
|
72
|
+
|
|
73
|
+
function refreshCurrentPage() {
|
|
74
|
+
if (typeof window === 'undefined')
|
|
75
|
+
return
|
|
76
|
+
const search = window.location.search.slice(1)
|
|
77
|
+
const searchParams = new URLSearchParams(search)
|
|
78
|
+
const pageNum = Number(searchParams.get(queryPageNumKey)) || 1
|
|
79
|
+
if (pageNum !== currentPage.value) {
|
|
80
|
+
currentPage.value = pageNum
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
|
-
|
|
82
|
-
refreshCurrentPage(to.slice(to.indexOf('?') + 1))
|
|
83
|
-
}
|
|
84
|
-
// 未覆盖的场景处理
|
|
85
|
-
router.onAfterRouteChanged = (to) => {
|
|
86
|
-
refreshCurrentPage(to.slice(to.indexOf('?') + 1))
|
|
87
|
-
}
|
|
88
|
-
onMounted(() => {
|
|
83
|
+
watch(route, () => {
|
|
89
84
|
refreshCurrentPage()
|
|
90
|
-
})
|
|
85
|
+
}, { immediate: true })
|
|
86
|
+
|
|
87
|
+
// 未覆盖的场景处理 左上回到首页
|
|
88
|
+
router.onAfterRouteChanged = () => {
|
|
89
|
+
refreshCurrentPage()
|
|
90
|
+
}
|
|
91
91
|
</script>
|
|
92
92
|
|
|
93
93
|
<template>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { ElButton, ElIcon } from 'element-plus'
|
|
3
3
|
import { CircleCloseFilled } from '@element-plus/icons-vue'
|
|
4
|
-
import { computed, h, onMounted, ref } from 'vue'
|
|
4
|
+
import { computed, h, onMounted, ref, watch } from 'vue'
|
|
5
5
|
import type { BlogPopover } from '@sugarat/theme'
|
|
6
6
|
import { parseStringStyle } from '@vue/shared'
|
|
7
|
-
import { useWindowSize } from '@vueuse/core'
|
|
7
|
+
import { useDebounceFn, useWindowSize } from '@vueuse/core'
|
|
8
|
+
import { useRoute, useRouter } from 'vitepress'
|
|
8
9
|
import { useBlogConfig } from '../composables/config/blog'
|
|
9
10
|
import { vOuterHtml } from '../directives'
|
|
10
11
|
|
|
@@ -24,7 +25,8 @@ const closeFlag = `${storageKey}-close`
|
|
|
24
25
|
|
|
25
26
|
// 移动端最小化
|
|
26
27
|
const { width } = useWindowSize()
|
|
27
|
-
|
|
28
|
+
const router = useRouter()
|
|
29
|
+
const route = useRoute()
|
|
28
30
|
onMounted(() => {
|
|
29
31
|
if (!popoverProps?.title) {
|
|
30
32
|
return
|
|
@@ -65,6 +67,12 @@ onMounted(() => {
|
|
|
65
67
|
}
|
|
66
68
|
})
|
|
67
69
|
|
|
70
|
+
const onAfterRouteChanged = useDebounceFn(() => {
|
|
71
|
+
popoverProps?.onRouteChanged?.(route, show)
|
|
72
|
+
}, 10)
|
|
73
|
+
|
|
74
|
+
watch(route, onAfterRouteChanged, { immediate: true })
|
|
75
|
+
|
|
68
76
|
function handleClose() {
|
|
69
77
|
show.value = false
|
|
70
78
|
if (popoverProps?.duration === -1) {
|
|
@@ -105,7 +113,12 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
|
105
113
|
{
|
|
106
114
|
type: 'primary',
|
|
107
115
|
onClick: () => {
|
|
108
|
-
|
|
116
|
+
if (/^\s*http(s)?:\/\//.test(item.link)) {
|
|
117
|
+
window.open(item.link)
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
router.go(item.link)
|
|
121
|
+
}
|
|
109
122
|
},
|
|
110
123
|
style: parseStringStyle(item.style || ''),
|
|
111
124
|
...item.props
|
|
@@ -129,9 +142,7 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
|
129
142
|
<div class="title-wrapper">
|
|
130
143
|
<ElIcon size="20px">
|
|
131
144
|
<i v-if="popoverProps?.icon" v-outer-html="popoverProps.icon" />
|
|
132
|
-
<svg v-else
|
|
133
|
-
<path fill="currentColor" d="M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.6c-3.7 11.6-5.6 23.9-5.6 36.4c0 65.9 53.8 119.5 120 119.5c55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3c16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112M344 762.3c-26.5 0-48-21.4-48-47.8c0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1" />
|
|
134
|
-
</svg>
|
|
145
|
+
<svg v-else t="1716085184855" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4274" width="200" height="200"><path d="M660.48 872.448q6.144 0-3.584 15.36t-29.696 33.792-47.104 33.792-57.856 15.36q-27.648 0-53.248-15.36t-45.056-33.792-29.696-33.792-6.144-15.36l272.384 0zM914.432 785.408q7.168 9.216 6.656 17.92t-4.608 14.848-10.24 9.728-12.288 3.584l-747.52 0q-14.336 0-20.992-11.776t4.608-29.184q17.408-30.72 40.96-68.608t44.544-81.408 36.352-92.16 15.36-101.888q0-51.2 14.336-92.16t37.376-71.68 53.248-52.224 62.976-32.768q-16.384-26.624-16.384-55.296 0-41.984 28.672-70.656t70.656-28.672 70.656 28.672 28.672 70.656q0 14.336-4.096 28.16t-11.264 25.088q34.816 11.264 66.048 32.768t54.272 53.248 36.864 72.704 13.824 91.136q0 51.2 15.36 100.864t36.864 94.208 45.568 81.408 43.52 63.488zM478.208 142.336q0 16.384 11.264 28.16t27.648 11.776l2.048 0q16.384-1.024 27.648-12.288t11.264-27.648q0-17.408-11.264-28.672t-28.672-11.264-28.672 11.264-11.264 28.672z" p-id="4275" /></svg>
|
|
135
146
|
</ElIcon>
|
|
136
147
|
<span class="title">{{ popoverProps?.title }}</span>
|
|
137
148
|
</div>
|
|
@@ -154,13 +165,12 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
|
154
165
|
</div>
|
|
155
166
|
<div
|
|
156
167
|
v-show="!show && (popoverProps?.reopen ?? true) && popoverProps?.title" class="theme-blog-popover-close"
|
|
168
|
+
:class="{ twinkle: !show && (popoverProps?.twinkle ?? true) }"
|
|
157
169
|
@click="show = true"
|
|
158
170
|
>
|
|
159
171
|
<ElIcon>
|
|
160
172
|
<i v-if="popoverProps?.icon" v-outer-html="popoverProps.icon" />
|
|
161
|
-
<svg v-else
|
|
162
|
-
<path fill="currentColor" d="M880 112c-3.8 0-7.7.7-11.6 2.3L292 345.9H128c-8.8 0-16 7.4-16 16.6v299c0 9.2 7.2 16.6 16 16.6h101.6c-3.7 11.6-5.6 23.9-5.6 36.4c0 65.9 53.8 119.5 120 119.5c55.4 0 102.1-37.6 115.9-88.4l408.6 164.2c3.9 1.5 7.8 2.3 11.6 2.3c16.9 0 32-14.2 32-33.2V145.2C912 126.2 897 112 880 112M344 762.3c-26.5 0-48-21.4-48-47.8c0-11.2 3.9-21.9 11-30.4l84.9 34.1c-2 24.6-22.7 44.1-47.9 44.1" />
|
|
163
|
-
</svg>
|
|
173
|
+
<svg v-else t="1716085184855" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4274" width="200" height="200"><path d="M660.48 872.448q6.144 0-3.584 15.36t-29.696 33.792-47.104 33.792-57.856 15.36q-27.648 0-53.248-15.36t-45.056-33.792-29.696-33.792-6.144-15.36l272.384 0zM914.432 785.408q7.168 9.216 6.656 17.92t-4.608 14.848-10.24 9.728-12.288 3.584l-747.52 0q-14.336 0-20.992-11.776t4.608-29.184q17.408-30.72 40.96-68.608t44.544-81.408 36.352-92.16 15.36-101.888q0-51.2 14.336-92.16t37.376-71.68 53.248-52.224 62.976-32.768q-16.384-26.624-16.384-55.296 0-41.984 28.672-70.656t70.656-28.672 70.656 28.672 28.672 70.656q0 14.336-4.096 28.16t-11.264 25.088q34.816 11.264 66.048 32.768t54.272 53.248 36.864 72.704 13.824 91.136q0 51.2 15.36 100.864t36.864 94.208 45.568 81.408 43.52 63.488zM478.208 142.336q0 16.384 11.264 28.16t27.648 11.776l2.048 0q16.384-1.024 27.648-12.288t11.264-27.648q0-17.408-11.264-28.672t-28.672-11.264-28.672 11.264-11.264 28.672z" p-id="4275" /></svg>
|
|
164
174
|
</ElIcon>
|
|
165
175
|
</div>
|
|
166
176
|
</template>
|
|
@@ -262,4 +272,19 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
|
|
|
262
272
|
display: flex;
|
|
263
273
|
flex-direction: column;
|
|
264
274
|
}
|
|
275
|
+
.theme-blog-popover-close.twinkle {
|
|
276
|
+
animation: twinkle 1s ease-in-out infinite;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@keyframes twinkle {
|
|
280
|
+
0% {
|
|
281
|
+
opacity: 0.5;
|
|
282
|
+
}
|
|
283
|
+
50% {
|
|
284
|
+
opacity: 0;
|
|
285
|
+
}
|
|
286
|
+
100% {
|
|
287
|
+
opacity: 0.5;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
265
290
|
</style>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useData, useRoute } from 'vitepress'
|
|
3
|
-
import { computed, ref, watch } from 'vue'
|
|
3
|
+
import { computed, nextTick, ref, watch } from 'vue'
|
|
4
4
|
import Giscus from '@giscus/vue'
|
|
5
5
|
import { useBlogConfig } from '../composables/config/blog'
|
|
6
6
|
|
|
@@ -23,14 +23,14 @@ const commentConfig = computed(() => {
|
|
|
23
23
|
const { isDark } = useData()
|
|
24
24
|
|
|
25
25
|
const route = useRoute()
|
|
26
|
-
const showComment = ref(
|
|
26
|
+
const showComment = ref(false)
|
|
27
27
|
watch(
|
|
28
|
-
|
|
28
|
+
route,
|
|
29
29
|
() => {
|
|
30
30
|
showComment.value = false
|
|
31
|
-
|
|
31
|
+
nextTick(() => {
|
|
32
32
|
showComment.value = true
|
|
33
|
-
}
|
|
33
|
+
})
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
immediate: true
|
|
@@ -40,7 +40,7 @@ watch(
|
|
|
40
40
|
|
|
41
41
|
<template>
|
|
42
42
|
<Giscus
|
|
43
|
-
v-if="commentConfig" :repo="commentConfig.repo" :repo-id="commentConfig.repoId"
|
|
43
|
+
v-if="commentConfig && showComment" :repo="commentConfig.repo" :repo-id="commentConfig.repoId"
|
|
44
44
|
:category="commentConfig.category" :category-id="commentConfig.categoryId"
|
|
45
45
|
:mapping="commentConfig.mapping || 'pathname'" reactions-enabled="1" emit-metadata="0"
|
|
46
46
|
:input-position="commentConfig.inputPosition || 'top'" :theme="isDark ? 'dark' : 'light'"
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable ts/no-namespace */
|
|
2
2
|
import type { ElButton } from 'element-plus'
|
|
3
|
-
import type { DefaultTheme } from 'vitepress'
|
|
3
|
+
import type { DefaultTheme, Route } from 'vitepress'
|
|
4
4
|
import type { RSSOptions } from 'vitepress-plugin-rss'
|
|
5
5
|
import type { Mapping, Repo } from '@giscus/vue'
|
|
6
6
|
import type { Options as Oml2dOptions } from 'oh-my-live2d'
|
|
7
|
+
import type { Ref } from 'vue'
|
|
7
8
|
|
|
8
9
|
type RSSPluginOptions = RSSOptions
|
|
9
10
|
|
|
@@ -243,6 +244,11 @@ export namespace Theme {
|
|
|
243
244
|
* @default true
|
|
244
245
|
*/
|
|
245
246
|
reopen?: boolean
|
|
247
|
+
/**
|
|
248
|
+
* 是否打开闪烁提示,通常需要和 reopen 搭配使用
|
|
249
|
+
* @default true
|
|
250
|
+
*/
|
|
251
|
+
twinkle?: boolean
|
|
246
252
|
/**
|
|
247
253
|
* 设置展示图标,svg
|
|
248
254
|
* @recommend https://iconbuddy.app/search?q=fire
|
|
@@ -253,6 +259,11 @@ export namespace Theme {
|
|
|
253
259
|
* @recommend https://iconbuddy.app/search?q=fire
|
|
254
260
|
*/
|
|
255
261
|
closeIcon?: string
|
|
262
|
+
/**
|
|
263
|
+
* 自定义展示策略
|
|
264
|
+
* @param to 切换到的目标路由
|
|
265
|
+
*/
|
|
266
|
+
onRouteChanged?: (to: Route, show: Ref<boolean>) => void
|
|
256
267
|
}
|
|
257
268
|
export interface FriendLink {
|
|
258
269
|
nickname: string
|