@sugarat/theme 0.1.30 → 0.1.31

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 CHANGED
@@ -59,10 +59,12 @@ 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;
@@ -158,8 +160,9 @@ declare namespace Theme {
158
160
  end?: string;
159
161
  lastupdate?: string;
160
162
  };
161
- status?: 'active' | 'negative' | 'off' | {
163
+ status?: {
162
164
  text: string;
165
+ type?: 'tip' | 'warning' | 'danger';
163
166
  };
164
167
  url?: string;
165
168
  github?: string | {
@@ -170,7 +173,7 @@ declare namespace Theme {
170
173
  };
171
174
  cover?: string | string[] | {
172
175
  urls: string[];
173
- layout?: 'swiper' | 'list' | 'card';
176
+ layout?: 'swiper' | 'list';
174
177
  };
175
178
  links?: {
176
179
  title: string;
@@ -193,6 +196,7 @@ declare namespace Theme {
193
196
  interface UserWorks {
194
197
  title: string;
195
198
  description?: string;
199
+ topTitle?: string;
196
200
  list: UserWork[];
197
201
  }
198
202
  interface BlogConfig {
@@ -214,7 +218,10 @@ declare namespace Theme {
214
218
  * power by https://giscus.app/zh-CN
215
219
  */
216
220
  comment?: GiscusConfig | false;
217
- recommend?: RecommendArticle;
221
+ /**
222
+ * 阅读文章左侧的推荐文章(替代默认的sidebar)
223
+ */
224
+ recommend?: RecommendArticle | false;
218
225
  article?: ArticleConfig;
219
226
  /**
220
227
  * 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-alpha.75_vue@3.2.45/node_modules/vitepress-plugin-tabs/dist/index.js
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) => {
@@ -352,7 +352,7 @@ function getThemeConfig(cfg) {
352
352
  pagesData: data,
353
353
  ...cfg
354
354
  },
355
- ...cfg?.blog !== false ? {
355
+ ...cfg?.blog !== false && cfg?.recommend !== false ? {
356
356
  sidebar: [
357
357
  {
358
358
  text: "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sugarat/theme",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
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-alpha.75",
50
- "vue": "^3.2.45",
51
- "vitepress-plugin-tabs": "^0.2.0"
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",
@@ -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
- const title = computed(() => recommend?.title || '🔍 相关文章')
57
- const pageSize = computed(() => recommend?.pageSize || 9)
58
- const nextText = computed(() => recommend?.nextText || '换一组')
59
- const emptyText = computed(() => recommend?.empty ?? '暂无推荐文章')
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
- <h1>{{ works.title }}</h1>
4
- <p v-if="works.description" class="description">{{ works.description }}</p>
5
- <!-- TODO:侧导筛选时间 -->
6
- <!-- 过滤,可吸顶 -->
7
- <div class="filter">
8
- <!-- 时间: -->
9
- <div></div>
10
- <!-- TODO: tags -->
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
- <!-- TODO: 支持锚点 -->
19
- <h2 v-if="work.year">{{ work.year }}</h2>
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 { reactive, ref, watch, watchEffect } from 'vue'
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 { useUserWorks } from '../composables/config/blog'
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
- sortDate.sort((a, b) => +new Date(b.startTime) - +new Date(a.startTime))
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 = sortDate.reduce((prev, cur) => {
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 covers = [
242
- 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
243
- 'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
244
- 'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
245
- 'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
246
- 'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
247
- 'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
248
- 'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
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
- max-width: 900px;
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: 10px;
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
- padding-top: 24px;
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: 360px;
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
- ref,
11
- Ref
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,12 +60,13 @@ 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
@@ -169,13 +170,10 @@ export namespace Theme {
169
170
  end?: string
170
171
  lastupdate?: string
171
172
  }
172
- status?:
173
- | 'active'
174
- | 'negative'
175
- | 'off'
176
- | {
177
- text: string
178
- }
173
+ status?: {
174
+ text: string
175
+ type?: 'tip' | 'warning' | 'danger'
176
+ }
179
177
  url?: string
180
178
  github?:
181
179
  | string
@@ -190,7 +188,7 @@ export namespace Theme {
190
188
  | string[]
191
189
  | {
192
190
  urls: string[]
193
- layout?: 'swiper' | 'list' | 'card'
191
+ layout?: 'swiper' | 'list'
194
192
  }
195
193
  links?: {
196
194
  title: string
@@ -217,6 +215,7 @@ export namespace Theme {
217
215
  export interface UserWorks {
218
216
  title: string
219
217
  description?: string
218
+ topTitle?: string
220
219
  list: UserWork[]
221
220
  }
222
221
  export interface BlogConfig {
@@ -238,7 +237,10 @@ export namespace Theme {
238
237
  * power by https://giscus.app/zh-CN
239
238
  */
240
239
  comment?: GiscusConfig | false
241
- recommend?: RecommendArticle
240
+ /**
241
+ * 阅读文章左侧的推荐文章(替代默认的sidebar)
242
+ */
243
+ recommend?: RecommendArticle | false
242
244
  article?: ArticleConfig
243
245
  /**
244
246
  * 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
@@ -175,7 +175,7 @@ export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
175
175
  pagesData: data as Theme.PageData[],
176
176
  ...cfg
177
177
  },
178
- ...(cfg?.blog !== false
178
+ ...(cfg?.blog !== false && cfg?.recommend !== false
179
179
  ? {
180
180
  sidebar: [
181
181
  {