@sugarat/theme 0.1.30 → 0.1.32
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 +11 -3
- package/node.js +6 -2
- package/package.json +5 -4
- package/src/components/BlogList.vue +1 -1
- package/src/components/BlogRecommendArticle.vue +12 -8
- package/src/components/UserWorks.vue +248 -43
- package/src/composables/config/blog.ts +64 -2
- package/src/composables/config/index.ts +14 -10
- package/src/index.ts +1 -0
- package/src/node.ts +9 -1
- package/src/styles/index.scss +21 -0
package/node.d.ts
CHANGED
|
@@ -59,13 +59,16 @@ declare namespace Theme {
|
|
|
59
59
|
*/
|
|
60
60
|
recommend?: number | false;
|
|
61
61
|
/**
|
|
62
|
+
* TODO: 待开发
|
|
62
63
|
* 时间线
|
|
63
64
|
*/
|
|
64
65
|
timeline: string;
|
|
65
66
|
/**
|
|
67
|
+
* TODO: 待开发
|
|
66
68
|
* 专栏&合集
|
|
67
69
|
*/
|
|
68
70
|
album: string;
|
|
71
|
+
publish?: boolean;
|
|
69
72
|
}
|
|
70
73
|
interface PageData {
|
|
71
74
|
route: string;
|
|
@@ -158,8 +161,9 @@ declare namespace Theme {
|
|
|
158
161
|
end?: string;
|
|
159
162
|
lastupdate?: string;
|
|
160
163
|
};
|
|
161
|
-
status?:
|
|
164
|
+
status?: {
|
|
162
165
|
text: string;
|
|
166
|
+
type?: 'tip' | 'warning' | 'danger';
|
|
163
167
|
};
|
|
164
168
|
url?: string;
|
|
165
169
|
github?: string | {
|
|
@@ -170,7 +174,7 @@ declare namespace Theme {
|
|
|
170
174
|
};
|
|
171
175
|
cover?: string | string[] | {
|
|
172
176
|
urls: string[];
|
|
173
|
-
layout?: 'swiper' | 'list'
|
|
177
|
+
layout?: 'swiper' | 'list';
|
|
174
178
|
};
|
|
175
179
|
links?: {
|
|
176
180
|
title: string;
|
|
@@ -193,6 +197,7 @@ declare namespace Theme {
|
|
|
193
197
|
interface UserWorks {
|
|
194
198
|
title: string;
|
|
195
199
|
description?: string;
|
|
200
|
+
topTitle?: string;
|
|
196
201
|
list: UserWork[];
|
|
197
202
|
}
|
|
198
203
|
interface BlogConfig {
|
|
@@ -214,7 +219,10 @@ declare namespace Theme {
|
|
|
214
219
|
* power by https://giscus.app/zh-CN
|
|
215
220
|
*/
|
|
216
221
|
comment?: GiscusConfig | false;
|
|
217
|
-
|
|
222
|
+
/**
|
|
223
|
+
* 阅读文章左侧的推荐文章(替代默认的sidebar)
|
|
224
|
+
*/
|
|
225
|
+
recommend?: RecommendArticle | false;
|
|
218
226
|
article?: ArticleConfig;
|
|
219
227
|
/**
|
|
220
228
|
* el-alert
|
package/node.js
CHANGED
|
@@ -41,7 +41,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
41
41
|
var import_child_process = require("child_process");
|
|
42
42
|
var import_path = __toESM(require("path"));
|
|
43
43
|
|
|
44
|
-
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.0.0-
|
|
44
|
+
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.0.0-beta.2_vue@3.2.45/node_modules/vitepress-plugin-tabs/dist/index.js
|
|
45
45
|
var tabsMarker = "=tabs";
|
|
46
46
|
var tabsMarkerLen = tabsMarker.length;
|
|
47
47
|
var ruleBlockTabs = (state, startLine, endLine, silent) => {
|
|
@@ -279,6 +279,10 @@ function getThemeConfig(cfg) {
|
|
|
279
279
|
const wordCount = 100;
|
|
280
280
|
meta.description = meta.description || getTextSummary(fileContent, wordCount);
|
|
281
281
|
meta.cover = meta.cover || fileContent.match(/[!]\[.*?\]\((https:\/\/.+)\)/)?.[1] || "";
|
|
282
|
+
if (meta.publish === false) {
|
|
283
|
+
meta.hidden = true;
|
|
284
|
+
meta.recommend = false;
|
|
285
|
+
}
|
|
282
286
|
return {
|
|
283
287
|
route: `/${route}`,
|
|
284
288
|
meta
|
|
@@ -352,7 +356,7 @@ function getThemeConfig(cfg) {
|
|
|
352
356
|
pagesData: data,
|
|
353
357
|
...cfg
|
|
354
358
|
},
|
|
355
|
-
...cfg?.blog !== false ? {
|
|
359
|
+
...cfg?.blog !== false && cfg?.recommend !== false ? {
|
|
356
360
|
sidebar: [
|
|
357
361
|
{
|
|
358
362
|
text: "",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
4
4
|
"description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"exports": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"url": "https://github.com/ATQQ/sugar-blog/issues"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@mdit-vue/shared": "^0.12.0",
|
|
36
37
|
"@vue/shared": "^3.2.45",
|
|
37
38
|
"@vueuse/core": "^9.6.0",
|
|
38
39
|
"fast-glob": "^3.2.12",
|
|
@@ -46,9 +47,9 @@
|
|
|
46
47
|
"sass": "^1.56.1",
|
|
47
48
|
"tsup": " ^6.5.0",
|
|
48
49
|
"typescript": "^4.8.2",
|
|
49
|
-
"vitepress": "1.0.0-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
50
|
+
"vitepress": "1.0.0-beta.2",
|
|
51
|
+
"vitepress-plugin-tabs": "^0.2.0",
|
|
52
|
+
"vue": "^3.2.45"
|
|
52
53
|
},
|
|
53
54
|
"scripts": {
|
|
54
55
|
"dev": "npm run build:node && npm run dev:docs",
|
|
@@ -51,7 +51,7 @@ const activeTag = useActiveTag()
|
|
|
51
51
|
const activeTagLabel = computed(() => activeTag.value.label)
|
|
52
52
|
|
|
53
53
|
const wikiList = computed(() => {
|
|
54
|
-
const topList = docs.value.filter((v) => !!v.meta.top)
|
|
54
|
+
const topList = docs.value.filter((v) => !v.meta.hidden && !!v.meta.top)
|
|
55
55
|
topList.sort((a, b) => {
|
|
56
56
|
const aTop = a?.meta?.top
|
|
57
57
|
const bTop = b?.meta.top
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
3
|
class="card recommend"
|
|
4
|
-
v-if="recommendList.length || emptyText"
|
|
4
|
+
v-if="_recommend !== false && (recommendList.length || emptyText)"
|
|
5
5
|
data-pagefind-ignore="all"
|
|
6
6
|
>
|
|
7
7
|
<!-- 头部 -->
|
|
@@ -52,11 +52,15 @@ import { useRoute, withBase } from 'vitepress'
|
|
|
52
52
|
import { formatShowDate } from '../utils/index'
|
|
53
53
|
import { useArticles, useBlogConfig } from '../composables/config/blog'
|
|
54
54
|
|
|
55
|
-
const { recommend } = useBlogConfig()
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const { recommend: _recommend } = useBlogConfig()
|
|
56
|
+
|
|
57
|
+
const recommend = computed(() =>
|
|
58
|
+
_recommend === false ? undefined : _recommend
|
|
59
|
+
)
|
|
60
|
+
const title = computed(() => recommend.value?.title || '🔍 相关文章')
|
|
61
|
+
const pageSize = computed(() => recommend.value?.pageSize || 9)
|
|
62
|
+
const nextText = computed(() => recommend.value?.nextText || '换一组')
|
|
63
|
+
const emptyText = computed(() => recommend.value?.empty ?? '暂无推荐文章')
|
|
60
64
|
|
|
61
65
|
const docs = useArticles()
|
|
62
66
|
|
|
@@ -80,12 +84,12 @@ const recommendList = computed(() => {
|
|
|
80
84
|
// 过滤掉自己
|
|
81
85
|
.filter(
|
|
82
86
|
(v) =>
|
|
83
|
-
(recommend?.showSelf ?? true) ||
|
|
87
|
+
(recommend.value?.showSelf ?? true) ||
|
|
84
88
|
v.route !== decodeURIComponent(route.path).replace(/.html$/, '')
|
|
85
89
|
)
|
|
86
90
|
// 过滤掉不需要展示的
|
|
87
91
|
.filter((v) => v.meta.recommend !== false)
|
|
88
|
-
.filter((v) => recommend?.filter?.(v) ?? true)
|
|
92
|
+
.filter((v) => recommend.value?.filter?.(v) ?? true)
|
|
89
93
|
|
|
90
94
|
const topList = origin.filter((v) => v.meta?.recommend)
|
|
91
95
|
topList.sort((a, b) => Number(a.meta.recommend) - Number(b.meta.recommend))
|
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="user-works-page">
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<div></div>
|
|
2
|
+
<div class="user-works-page VPDoc">
|
|
3
|
+
<div class="aside-container">
|
|
4
|
+
<!-- TODO:过滤,可吸顶 -->
|
|
5
|
+
<div class="filter">
|
|
6
|
+
<!-- 时间: -->
|
|
7
|
+
<div></div>
|
|
8
|
+
<!-- TODO: tags -->
|
|
9
|
+
<div></div>
|
|
10
|
+
</div>
|
|
12
11
|
</div>
|
|
13
12
|
<!-- 作品列表 -->
|
|
14
13
|
<div class="works">
|
|
14
|
+
<h1>{{ works.title }}</h1>
|
|
15
|
+
<p v-if="works.description" class="description">
|
|
16
|
+
{{ works.description }}
|
|
17
|
+
</p>
|
|
15
18
|
<!-- 标题,描述信息,时间,线上链接,代码仓库,示例图片(几张,多种展示样式支持) -->
|
|
16
19
|
<div class="work" v-for="(work, idx) in workList" :key="idx">
|
|
17
20
|
<!-- 大日期标题 -->
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
<h2 :id="`work_${work.year}`" v-if="work.year">
|
|
22
|
+
<a :href="`#work_${work.year}`">{{ work.year }}</a>
|
|
23
|
+
</h2>
|
|
20
24
|
<!-- 作品标题 -->
|
|
21
|
-
<h3 class="title">
|
|
25
|
+
<h3 class="title" :id="slugify(work.title)">
|
|
26
|
+
<a class="pin" :href="'#' + slugify(work.title)"></a>
|
|
22
27
|
<a v-if="work.url" rel="noopener" target="_blank" :href="work.url">{{
|
|
23
28
|
work.title
|
|
24
29
|
}}</a>
|
|
25
30
|
<span v-else>{{ work.title }}</span>
|
|
31
|
+
<Badge v-if="work.status" :type="work.status?.type || 'tip'">{{
|
|
32
|
+
work.status.text
|
|
33
|
+
}}</Badge>
|
|
26
34
|
</h3>
|
|
27
35
|
<!-- 补充信息 -->
|
|
28
36
|
<div class="info">
|
|
@@ -70,7 +78,7 @@
|
|
|
70
78
|
>
|
|
71
79
|
</a>
|
|
72
80
|
</div>
|
|
73
|
-
<!--
|
|
81
|
+
<!-- 其它自定义链接 -->
|
|
74
82
|
<div class="links" v-if="work.links?.length">
|
|
75
83
|
<i class="icon" v-if="work.links?.length">
|
|
76
84
|
<svg
|
|
@@ -106,41 +114,118 @@
|
|
|
106
114
|
{{ link.title }}
|
|
107
115
|
</a>
|
|
108
116
|
</div>
|
|
117
|
+
<!-- tags -->
|
|
118
|
+
<div class="tags" v-if="work.tags?.length">
|
|
119
|
+
<i class="icon">
|
|
120
|
+
<svg
|
|
121
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
122
|
+
viewBox="0 0 1024 1024"
|
|
123
|
+
data-v-d328c40a=""
|
|
124
|
+
>
|
|
125
|
+
<path
|
|
126
|
+
fill="currentColor"
|
|
127
|
+
d="M256 128v698.88l196.032-156.864a96 96 0 0 1 119.936 0L768 826.816V128H256zm-32-64h576a32 32 0 0 1 32 32v797.44a32 32 0 0 1-51.968 24.96L531.968 720a32 32 0 0 0-39.936 0L243.968 918.4A32 32 0 0 1 192 893.44V96a32 32 0 0 1 32-32z"
|
|
128
|
+
></path>
|
|
129
|
+
</svg>
|
|
130
|
+
</i>
|
|
131
|
+
<span
|
|
132
|
+
@click="handleChooseTag(tag)"
|
|
133
|
+
class="tag"
|
|
134
|
+
v-for="tag in work.tags"
|
|
135
|
+
:key="tag"
|
|
136
|
+
>{{ tag }}
|
|
137
|
+
</span>
|
|
138
|
+
</div>
|
|
109
139
|
</div>
|
|
110
140
|
<!-- 封面图 -->
|
|
111
|
-
<div class="images">
|
|
141
|
+
<div class="images" v-if="work.covers?.length">
|
|
112
142
|
<!-- swiper -->
|
|
143
|
+
<div v-if="work.coverLayout === 'swiper'" class="swiper-mode">
|
|
144
|
+
<el-carousel
|
|
145
|
+
autoplay
|
|
146
|
+
height="260px"
|
|
147
|
+
:type="isCardMode && work.covers.length >= 3 ? 'card' : ''"
|
|
148
|
+
>
|
|
149
|
+
<el-carousel-item
|
|
150
|
+
style="text-align: center"
|
|
151
|
+
v-for="(url, idx) in work.covers"
|
|
152
|
+
:key="url"
|
|
153
|
+
>
|
|
154
|
+
<el-image
|
|
155
|
+
preview-teleported
|
|
156
|
+
:key="url"
|
|
157
|
+
:src="url"
|
|
158
|
+
loading="lazy"
|
|
159
|
+
:preview-src-list="work.covers"
|
|
160
|
+
:initial-index="idx"
|
|
161
|
+
hide-on-click-modal
|
|
162
|
+
:alt="work.title + '-' + idx"
|
|
163
|
+
/>
|
|
164
|
+
</el-carousel-item>
|
|
165
|
+
</el-carousel>
|
|
166
|
+
</div>
|
|
113
167
|
<!-- list -->
|
|
114
|
-
<div class="list-mode">
|
|
168
|
+
<div v-if="work.coverLayout === 'list'" class="list-mode">
|
|
115
169
|
<el-image
|
|
116
|
-
v-for="(url, idx) in covers"
|
|
170
|
+
v-for="(url, idx) in work.covers"
|
|
117
171
|
:key="url"
|
|
118
172
|
:src="url"
|
|
119
173
|
loading="lazy"
|
|
120
|
-
:preview-src-list="covers"
|
|
174
|
+
:preview-src-list="work.covers"
|
|
121
175
|
:initial-index="idx"
|
|
122
176
|
hide-on-click-modal
|
|
123
177
|
/>
|
|
124
178
|
</div>
|
|
125
|
-
<!-- card -->
|
|
126
179
|
</div>
|
|
127
180
|
<div class="description" v-html="work.description"></div>
|
|
128
181
|
</div>
|
|
129
182
|
</div>
|
|
183
|
+
<div class="aside-container">
|
|
184
|
+
<div class="aside-outline-container">
|
|
185
|
+
<VPDocAsideOutline />
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
130
188
|
</div>
|
|
131
189
|
</template>
|
|
132
190
|
|
|
133
191
|
<script lang="ts" setup>
|
|
134
|
-
import { ElImage } from 'element-plus'
|
|
135
|
-
import
|
|
192
|
+
import { ElImage, ElCarousel, ElCarouselItem, ElMessage } from 'element-plus'
|
|
193
|
+
import VPDocAsideOutline from 'vitepress/dist/client/theme-default/components/VPDocAsideOutline.vue'
|
|
194
|
+
import { computed, reactive, ref, watch, watchEffect } from 'vue'
|
|
195
|
+
import { slugify } from '@mdit-vue/shared'
|
|
196
|
+
import { useWindowSize } from '@vueuse/core'
|
|
136
197
|
import {
|
|
137
198
|
getGithubUpdateTime,
|
|
138
199
|
formatDate,
|
|
139
200
|
getGithubDirUpdateTime
|
|
140
201
|
} from '../utils'
|
|
141
|
-
import {
|
|
202
|
+
import {
|
|
203
|
+
useUserWorks,
|
|
204
|
+
useActiveAnchor,
|
|
205
|
+
useAutoUpdateAnchor
|
|
206
|
+
} from '../composables/config/blog'
|
|
142
207
|
import { Theme } from '../composables/config'
|
|
143
208
|
|
|
209
|
+
const currentAnchor = useAutoUpdateAnchor()
|
|
210
|
+
// 更新锚点的时候更新 url 中的 hash
|
|
211
|
+
watch(
|
|
212
|
+
() => currentAnchor.id,
|
|
213
|
+
(val) => {
|
|
214
|
+
if (val) {
|
|
215
|
+
window.history.replaceState(null, '', `#${val}`)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
const mountActiveAnchorEl = useActiveAnchor()
|
|
220
|
+
watch(mountActiveAnchorEl, () => {
|
|
221
|
+
const { value } = mountActiveAnchorEl
|
|
222
|
+
if (value) {
|
|
223
|
+
value.scroll({
|
|
224
|
+
behavior: 'smooth'
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
144
229
|
const works = useUserWorks()
|
|
145
230
|
const workList = reactive<
|
|
146
231
|
(Theme.UserWork & {
|
|
@@ -148,6 +233,8 @@ const workList = reactive<
|
|
|
148
233
|
startTime: string
|
|
149
234
|
lastUpdate?: string
|
|
150
235
|
endTime?: string
|
|
236
|
+
covers?: string[]
|
|
237
|
+
coverLayout?: string
|
|
151
238
|
})[]
|
|
152
239
|
>([])
|
|
153
240
|
|
|
@@ -157,6 +244,8 @@ watch(
|
|
|
157
244
|
(val) => {
|
|
158
245
|
const sortDate = [...val.list].map((v) => {
|
|
159
246
|
const { time } = v
|
|
247
|
+
|
|
248
|
+
// 格式化时间
|
|
160
249
|
const metaInfo =
|
|
161
250
|
typeof time === 'string'
|
|
162
251
|
? {
|
|
@@ -170,16 +259,37 @@ watch(
|
|
|
170
259
|
lastUpdate: time.lastupdate
|
|
171
260
|
}
|
|
172
261
|
|
|
262
|
+
// 格式化封面信息
|
|
263
|
+
const covers: string[] = []
|
|
264
|
+
let coverLayout = 'swiper'
|
|
265
|
+
|
|
266
|
+
if (typeof v.cover === 'string') {
|
|
267
|
+
covers.push(v.cover)
|
|
268
|
+
} else if (Array.isArray(v.cover)) {
|
|
269
|
+
covers.push(...v.cover)
|
|
270
|
+
} else if (typeof v.cover === 'object') {
|
|
271
|
+
covers.push(...v.cover.urls)
|
|
272
|
+
coverLayout = v.cover.layout ?? coverLayout
|
|
273
|
+
}
|
|
173
274
|
return {
|
|
174
275
|
...v,
|
|
175
|
-
...metaInfo
|
|
276
|
+
...metaInfo,
|
|
277
|
+
covers,
|
|
278
|
+
coverLayout
|
|
176
279
|
}
|
|
177
280
|
})
|
|
281
|
+
// 过滤出置顶数据
|
|
282
|
+
const topDate = sortDate.filter((v) => v.top !== undefined)
|
|
283
|
+
const normalDate = sortDate.filter((v) => v.top === undefined)
|
|
178
284
|
// 数据排序
|
|
179
|
-
|
|
180
|
-
|
|
285
|
+
topDate.sort((a, b) => a.top! - b.top!)
|
|
286
|
+
normalDate.sort((a, b) => +new Date(b.startTime) - +new Date(a.startTime))
|
|
287
|
+
if (topDate.length) {
|
|
288
|
+
// @ts-ignore
|
|
289
|
+
topDate[0].year = works.value.topTitle ?? '置顶'
|
|
290
|
+
}
|
|
181
291
|
// 数据分组
|
|
182
|
-
const groupDate =
|
|
292
|
+
const groupDate = normalDate.reduce((prev, cur) => {
|
|
183
293
|
const { startTime } = cur
|
|
184
294
|
const year = new Date(startTime).getFullYear()
|
|
185
295
|
const data = { ...cur }
|
|
@@ -192,7 +302,7 @@ watch(
|
|
|
192
302
|
prev[year].push(data)
|
|
193
303
|
return prev
|
|
194
304
|
}, {} as Record<string, (Theme.UserWork & { year?: string; startTime: string })[]>)
|
|
195
|
-
workList.push(...Object.values(groupDate).reverse().flat())
|
|
305
|
+
workList.push(...topDate, ...Object.values(groupDate).reverse().flat())
|
|
196
306
|
},
|
|
197
307
|
{ immediate: true }
|
|
198
308
|
)
|
|
@@ -221,7 +331,7 @@ watchEffect(() => {
|
|
|
221
331
|
let githubUrl = `https://github.com/${owner}/${repo}`
|
|
222
332
|
if (path) {
|
|
223
333
|
githubUrl += `/tree/${branch || 'master'}/${path}`
|
|
224
|
-
} else {
|
|
334
|
+
} else if (branch) {
|
|
225
335
|
githubUrl += `/tree/${branch}`
|
|
226
336
|
}
|
|
227
337
|
data.github = githubUrl
|
|
@@ -238,20 +348,21 @@ watchEffect(() => {
|
|
|
238
348
|
}
|
|
239
349
|
})
|
|
240
350
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
]
|
|
351
|
+
const { width } = useWindowSize()
|
|
352
|
+
const isCardMode = computed(() => width.value > 768)
|
|
353
|
+
const handleChooseTag = (tag: string) => {
|
|
354
|
+
ElMessage({
|
|
355
|
+
message: `点击了${tag}标签,标签过滤功能开发中ing...`,
|
|
356
|
+
type: 'warning'
|
|
357
|
+
})
|
|
358
|
+
}
|
|
250
359
|
</script>
|
|
251
360
|
|
|
252
361
|
<style lang="scss" scoped>
|
|
253
362
|
.user-works-page {
|
|
254
|
-
|
|
363
|
+
display: flex;
|
|
364
|
+
justify-content: center;
|
|
365
|
+
width: 100%;
|
|
255
366
|
margin: 20px auto;
|
|
256
367
|
padding: 16px;
|
|
257
368
|
h1 {
|
|
@@ -259,7 +370,7 @@ const covers = [
|
|
|
259
370
|
font-weight: bold;
|
|
260
371
|
}
|
|
261
372
|
.description {
|
|
262
|
-
margin-top:
|
|
373
|
+
margin-top: 16px;
|
|
263
374
|
color: #999;
|
|
264
375
|
font-size: 16px;
|
|
265
376
|
}
|
|
@@ -268,16 +379,58 @@ const covers = [
|
|
|
268
379
|
color: var(--vp-c-brand);
|
|
269
380
|
}
|
|
270
381
|
}
|
|
382
|
+
.works-container {
|
|
383
|
+
display: flex;
|
|
384
|
+
justify-content: center;
|
|
385
|
+
}
|
|
271
386
|
.work {
|
|
387
|
+
max-width: 900px;
|
|
388
|
+
|
|
272
389
|
h2 {
|
|
273
|
-
|
|
390
|
+
margin-top: 6px;
|
|
391
|
+
padding-top: 18px;
|
|
274
392
|
line-height: 32px;
|
|
275
393
|
font-size: 24px;
|
|
394
|
+
border-top: 1px solid var(--vp-c-divider);
|
|
395
|
+
a {
|
|
396
|
+
color: inherit;
|
|
397
|
+
}
|
|
398
|
+
&:hover {
|
|
399
|
+
a {
|
|
400
|
+
&::before {
|
|
401
|
+
opacity: 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
a {
|
|
406
|
+
position: relative;
|
|
407
|
+
&::before {
|
|
408
|
+
position: absolute;
|
|
409
|
+
left: -16px;
|
|
410
|
+
opacity: 0;
|
|
411
|
+
content: var(--vp-header-anchor-symbol);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
276
414
|
}
|
|
277
415
|
h3 {
|
|
278
416
|
margin: 32px 0 0;
|
|
279
417
|
line-height: 28px;
|
|
280
418
|
font-size: 20px;
|
|
419
|
+
position: relative;
|
|
420
|
+
&.title > a.pin {
|
|
421
|
+
position: absolute;
|
|
422
|
+
left: -16px;
|
|
423
|
+
&::before {
|
|
424
|
+
left: -16px;
|
|
425
|
+
opacity: 0;
|
|
426
|
+
content: var(--vp-header-anchor-symbol);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
&:hover > a.pin {
|
|
430
|
+
&::before {
|
|
431
|
+
opacity: 1;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
281
434
|
}
|
|
282
435
|
.info {
|
|
283
436
|
display: flex;
|
|
@@ -286,7 +439,8 @@ const covers = [
|
|
|
286
439
|
flex-wrap: wrap;
|
|
287
440
|
}
|
|
288
441
|
.links,
|
|
289
|
-
.times
|
|
442
|
+
.times,
|
|
443
|
+
.tags {
|
|
290
444
|
display: flex;
|
|
291
445
|
align-items: center;
|
|
292
446
|
.icon {
|
|
@@ -321,14 +475,65 @@ const covers = [
|
|
|
321
475
|
}
|
|
322
476
|
}
|
|
323
477
|
}
|
|
478
|
+
.tags {
|
|
479
|
+
span.tag {
|
|
480
|
+
cursor: pointer;
|
|
481
|
+
}
|
|
482
|
+
span.tag:not(:last-child) {
|
|
483
|
+
&::after {
|
|
484
|
+
content: '·';
|
|
485
|
+
display: inline-block;
|
|
486
|
+
padding: 0 4px;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
.aside-container {
|
|
492
|
+
display: none;
|
|
493
|
+
flex: 1;
|
|
494
|
+
padding-left: 32px;
|
|
495
|
+
width: 100%;
|
|
496
|
+
max-width: 256px;
|
|
497
|
+
}
|
|
498
|
+
@media screen and (min-width: 960px) {
|
|
499
|
+
.aside-container {
|
|
500
|
+
display: block;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
.aside-outline-container {
|
|
504
|
+
position: sticky;
|
|
505
|
+
top: calc(
|
|
506
|
+
var(--vp-nav-height) + var(--vp-layout-top-height, 0px) +
|
|
507
|
+
var(--vp-doc-top-height, 0px) + 32px
|
|
508
|
+
);
|
|
324
509
|
}
|
|
325
510
|
.lastupdate {
|
|
326
511
|
color: var(--vp-c-text-1);
|
|
327
512
|
}
|
|
513
|
+
|
|
328
514
|
.list-mode {
|
|
329
|
-
height:
|
|
330
|
-
margin: 10px auto;
|
|
515
|
+
max-height: 370px;
|
|
331
516
|
overflow-y: auto;
|
|
517
|
+
margin: 10px auto;
|
|
518
|
+
display: flex;
|
|
519
|
+
flex-wrap: wrap;
|
|
520
|
+
justify-content: center;
|
|
521
|
+
.el-image {
|
|
522
|
+
:deep(img) {
|
|
523
|
+
object-fit: contain;
|
|
524
|
+
// max-height: 360px;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.swiper-mode {
|
|
530
|
+
margin-top: 16px;
|
|
531
|
+
.el-image {
|
|
532
|
+
:deep(img) {
|
|
533
|
+
object-fit: contain;
|
|
534
|
+
max-height: 260px;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
332
537
|
}
|
|
333
538
|
.split {
|
|
334
539
|
display: inline-block;
|
|
@@ -7,9 +7,13 @@ import {
|
|
|
7
7
|
inject,
|
|
8
8
|
InjectionKey,
|
|
9
9
|
provide,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Ref,
|
|
11
|
+
onMounted,
|
|
12
|
+
onUnmounted,
|
|
13
|
+
reactive,
|
|
14
|
+
ref
|
|
12
15
|
} from 'vue'
|
|
16
|
+
|
|
13
17
|
import type { Theme } from './index'
|
|
14
18
|
|
|
15
19
|
const configSymbol: InjectionKey<Ref<Theme.Config>> = Symbol('theme-config')
|
|
@@ -128,3 +132,61 @@ function resolveConfig(config: Theme.Config): Theme.Config {
|
|
|
128
132
|
}
|
|
129
133
|
}
|
|
130
134
|
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 页面加载的时候定位到锚点内容
|
|
138
|
+
*/
|
|
139
|
+
export function useActiveAnchor() {
|
|
140
|
+
const el = ref<HTMLElement | null>(null)
|
|
141
|
+
onMounted(() => {
|
|
142
|
+
const { hash } = window.location
|
|
143
|
+
if (hash) {
|
|
144
|
+
el.value = document.querySelector(decodeURIComponent(hash))
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
return el
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 页面滚动的时候自动更新锚点
|
|
152
|
+
*/
|
|
153
|
+
export function useAutoUpdateAnchor() {
|
|
154
|
+
// 初始化当前锚点
|
|
155
|
+
const currentAnchor = reactive({
|
|
156
|
+
id: '',
|
|
157
|
+
top: -1
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// 定义计算当前锚点的方法
|
|
161
|
+
const calculateCurrentAnchor = () => {
|
|
162
|
+
// 获取页面中所有的锚点元素
|
|
163
|
+
const anchors = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
|
164
|
+
for (let i = 0; i < anchors.length; i += 1) {
|
|
165
|
+
const anchor = anchors[i]
|
|
166
|
+
const rect = anchor.getBoundingClientRect()
|
|
167
|
+
// 如果当前锚点距离顶部最近,且距离顶部小于等于100,则将其设置为当前锚点
|
|
168
|
+
if (rect.top <= 100 && anchor.id !== currentAnchor.id) {
|
|
169
|
+
currentAnchor.id = anchor.id
|
|
170
|
+
currentAnchor.top = rect.top
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 监听 window 对象的滚动事件
|
|
176
|
+
const onScroll = () => {
|
|
177
|
+
calculateCurrentAnchor()
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 在组件挂载时启动监听滚动事件
|
|
181
|
+
onMounted(() => {
|
|
182
|
+
window.addEventListener('scroll', onScroll)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
// 在组件卸载时移除监听滚动事件
|
|
186
|
+
onUnmounted(() => {
|
|
187
|
+
window.removeEventListener('scroll', onScroll)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// 返回当前锚点的响应式对象
|
|
191
|
+
return currentAnchor
|
|
192
|
+
}
|
|
@@ -60,15 +60,18 @@ export namespace Theme {
|
|
|
60
60
|
* 手动控制相关文章列表的顺序
|
|
61
61
|
*/
|
|
62
62
|
recommend?: number | false
|
|
63
|
-
// TODO: 待开发
|
|
64
63
|
/**
|
|
64
|
+
* TODO: 待开发
|
|
65
65
|
* 时间线
|
|
66
66
|
*/
|
|
67
67
|
timeline: string
|
|
68
68
|
/**
|
|
69
|
+
* TODO: 待开发
|
|
69
70
|
* 专栏&合集
|
|
70
71
|
*/
|
|
71
72
|
album: string
|
|
73
|
+
// 是否发布
|
|
74
|
+
publish?: boolean
|
|
72
75
|
}
|
|
73
76
|
export interface PageData {
|
|
74
77
|
route: string
|
|
@@ -169,13 +172,10 @@ export namespace Theme {
|
|
|
169
172
|
end?: string
|
|
170
173
|
lastupdate?: string
|
|
171
174
|
}
|
|
172
|
-
status?:
|
|
173
|
-
|
|
174
|
-
| '
|
|
175
|
-
|
|
176
|
-
| {
|
|
177
|
-
text: string
|
|
178
|
-
}
|
|
175
|
+
status?: {
|
|
176
|
+
text: string
|
|
177
|
+
type?: 'tip' | 'warning' | 'danger'
|
|
178
|
+
}
|
|
179
179
|
url?: string
|
|
180
180
|
github?:
|
|
181
181
|
| string
|
|
@@ -190,7 +190,7 @@ export namespace Theme {
|
|
|
190
190
|
| string[]
|
|
191
191
|
| {
|
|
192
192
|
urls: string[]
|
|
193
|
-
layout?: 'swiper' | 'list'
|
|
193
|
+
layout?: 'swiper' | 'list'
|
|
194
194
|
}
|
|
195
195
|
links?: {
|
|
196
196
|
title: string
|
|
@@ -217,6 +217,7 @@ export namespace Theme {
|
|
|
217
217
|
export interface UserWorks {
|
|
218
218
|
title: string
|
|
219
219
|
description?: string
|
|
220
|
+
topTitle?: string
|
|
220
221
|
list: UserWork[]
|
|
221
222
|
}
|
|
222
223
|
export interface BlogConfig {
|
|
@@ -238,7 +239,10 @@ export namespace Theme {
|
|
|
238
239
|
* power by https://giscus.app/zh-CN
|
|
239
240
|
*/
|
|
240
241
|
comment?: GiscusConfig | false
|
|
241
|
-
|
|
242
|
+
/**
|
|
243
|
+
* 阅读文章左侧的推荐文章(替代默认的sidebar)
|
|
244
|
+
*/
|
|
245
|
+
recommend?: RecommendArticle | false
|
|
242
246
|
article?: ArticleConfig
|
|
243
247
|
/**
|
|
244
248
|
* el-alert
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const BlogTheme: Theme = {
|
|
|
18
18
|
...DefaultTheme,
|
|
19
19
|
Layout: withConfigProvider(BlogApp),
|
|
20
20
|
enhanceApp(ctx) {
|
|
21
|
+
DefaultTheme.enhanceApp(ctx)
|
|
21
22
|
// @ts-ignore
|
|
22
23
|
ctx.app.component('TimelinePage', TimelinePage)
|
|
23
24
|
ctx.app.component('UserWorksPage', UserWorksPage)
|
package/src/node.ts
CHANGED
|
@@ -48,6 +48,7 @@ export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
48
48
|
const meta: Partial<Theme.PageMeta> = {
|
|
49
49
|
...matter(fileContent).data
|
|
50
50
|
}
|
|
51
|
+
|
|
51
52
|
if (!meta.title) {
|
|
52
53
|
meta.title = getDefaultTitle(fileContent)
|
|
53
54
|
}
|
|
@@ -85,6 +86,13 @@ export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
85
86
|
meta.cover ||
|
|
86
87
|
fileContent.match(/[!]\[.*?\]\((https:\/\/.+)\)/)?.[1] ||
|
|
87
88
|
''
|
|
89
|
+
|
|
90
|
+
// 是否发布 默认发布
|
|
91
|
+
if (meta.publish === false) {
|
|
92
|
+
meta.hidden = true
|
|
93
|
+
meta.recommend = false
|
|
94
|
+
}
|
|
95
|
+
|
|
88
96
|
return {
|
|
89
97
|
route: `/${route}`,
|
|
90
98
|
meta
|
|
@@ -175,7 +183,7 @@ export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
175
183
|
pagesData: data as Theme.PageData[],
|
|
176
184
|
...cfg
|
|
177
185
|
},
|
|
178
|
-
...(cfg?.blog !== false
|
|
186
|
+
...(cfg?.blog !== false && cfg?.recommend !== false
|
|
179
187
|
? {
|
|
180
188
|
sidebar: [
|
|
181
189
|
{
|
package/src/styles/index.scss
CHANGED
|
@@ -103,3 +103,24 @@ html[class='dark'] {
|
|
|
103
103
|
word-break: break-all;
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
::-webkit-scrollbar {
|
|
108
|
+
width: 5px;
|
|
109
|
+
height: 5px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
::-webkit-scrollbar-track-piece {
|
|
113
|
+
background-color: rgba(0, 0, 0, 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
::-webkit-scrollbar-thumb:vertical {
|
|
117
|
+
height: 5px;
|
|
118
|
+
border-radius: 4px;
|
|
119
|
+
background-color: var(--el-color-primary);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
::-webkit-scrollbar-thumb:horizontal {
|
|
123
|
+
width: 5px;
|
|
124
|
+
border-radius: 4px;
|
|
125
|
+
background-color: var(--el-color-primary);
|
|
126
|
+
}
|