@sugarat/theme 0.2.15 → 0.2.17

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
@@ -92,6 +92,22 @@ declare namespace Theme {
92
92
  label: string;
93
93
  type: string;
94
94
  }
95
+ interface CommentConfig extends GiscusConfig {
96
+ /**
97
+ * @default '评论'
98
+ */
99
+ label?: string;
100
+ /**
101
+ * 自定义图标,SVG 格式
102
+ * @recommend https://iconbuddy.app/search?q=fire
103
+ */
104
+ icon?: string;
105
+ /**
106
+ * 移动端最小化按钮
107
+ * @default true
108
+ */
109
+ mobileMinify?: boolean;
110
+ }
95
111
  interface GiscusConfig {
96
112
  repo: Repo;
97
113
  repoId: string;
@@ -170,6 +186,9 @@ declare namespace Theme {
170
186
  showIcon?: boolean;
171
187
  html?: string;
172
188
  }
189
+ /**
190
+ * 公告
191
+ */
173
192
  interface Popover {
174
193
  title: string;
175
194
  /**
@@ -178,12 +197,28 @@ declare namespace Theme {
178
197
  * 配置改变时,会重新触发展示
179
198
  */
180
199
  duration: number;
200
+ /**
201
+ * 移动端自动最小化
202
+ * @default false
203
+ */
204
+ mobileMinify?: boolean;
181
205
  body?: BlogPopover.Value[];
182
206
  footer?: BlogPopover.Value[];
183
207
  /**
184
208
  * 手动重新打开
209
+ * @default true
185
210
  */
186
211
  reopen?: boolean;
212
+ /**
213
+ * 设置展示图标,svg
214
+ * @recommend https://iconbuddy.app/search?q=fire
215
+ */
216
+ icon?: string;
217
+ /**
218
+ * 设置关闭图标,svg
219
+ * @recommend https://iconbuddy.app/search?q=fire
220
+ */
221
+ closeIcon?: string;
187
222
  }
188
223
  interface FriendLink {
189
224
  nickname: string;
@@ -280,7 +315,7 @@ declare namespace Theme {
280
315
  * 配置评论
281
316
  * power by https://giscus.app/zh-CN
282
317
  */
283
- comment?: GiscusConfig | false;
318
+ comment?: CommentConfig | false;
284
319
  /**
285
320
  * 阅读文章左侧的推荐文章(替代默认的sidebar)
286
321
  */
@@ -332,6 +367,19 @@ declare namespace Theme {
332
367
  * 详见 https://github.com/linsir/markdown-it-task-checkbox
333
368
  */
334
369
  taskCheckbox?: TaskCheckbox | boolean;
370
+ backToTop?: boolean | BackToTop;
371
+ }
372
+ interface BackToTop {
373
+ /**
374
+ * 距离顶部多少距离出现
375
+ * @default 450
376
+ */
377
+ top?: number;
378
+ /**
379
+ * 设置展示图标,svg
380
+ * @recommend https://iconbuddy.app/search?q=fire
381
+ */
382
+ icon?: string;
335
383
  }
336
384
  interface TaskCheckbox {
337
385
  disabled?: boolean;
package/node.js CHANGED
@@ -36,7 +36,10 @@ __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.35_vue@3.3.4/node_modules/vitepress-plugin-tabs/dist/index.js
39
+ // src/utils/node/mdPlugins.ts
40
+ var import_module = require("module");
41
+
42
+ // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.0.0-rc.40_vue@3.3.4/node_modules/vitepress-plugin-tabs/dist/index.js
40
43
  var tabsMarker = "=tabs";
41
44
  var tabsMarkerLen = tabsMarker.length;
42
45
  var ruleBlockTabs = (state, startLine, endLine, silent) => {
@@ -295,8 +298,9 @@ function isBase64ImageURL(url) {
295
298
  const regex = /^data:image\/[a-z]+;base64,/;
296
299
  return regex.test(url);
297
300
  }
301
+ var imageRegex = /!\[.*?\]\((.*?)\s*(".*?")?\)/;
298
302
  function getFirstImagURLFromMD(content, route) {
299
- const url = content.match(/!\[.*\]\((.*)\)/)?.[1]?.replace(/['"]/g, "");
303
+ const url = content.match(imageRegex)?.[1];
300
304
  const isHTTPSource = url && url.startsWith("http");
301
305
  if (!url) {
302
306
  return "";
@@ -311,6 +315,10 @@ function getFirstImagURLFromMD(content, route) {
311
315
  }
312
316
 
313
317
  // src/utils/node/mdPlugins.ts
318
+ var import_meta = {};
319
+ function _require(module2) {
320
+ return (typeof import_meta?.url !== "undefined" ? (0, import_module.createRequire)(import_meta.url) : require)(module2);
321
+ }
314
322
  function getMarkdownPlugins(cfg) {
315
323
  const markdownPlugin = [];
316
324
  if (cfg?.tabs !== false) {
@@ -319,7 +327,7 @@ function getMarkdownPlugins(cfg) {
319
327
  if (cfg) {
320
328
  cfg.mermaid = cfg?.mermaid ?? true;
321
329
  if (cfg?.mermaid !== false) {
322
- const { MermaidMarkdown } = require("vitepress-plugin-mermaid");
330
+ const { MermaidMarkdown } = _require("vitepress-plugin-mermaid");
323
331
  markdownPlugin.push(MermaidMarkdown);
324
332
  }
325
333
  }
@@ -333,7 +341,7 @@ function getMarkdownPlugins(cfg) {
333
341
  }
334
342
  function taskCheckboxPlugin(ops) {
335
343
  return (md) => {
336
- md.use(require("markdown-it-task-checkbox"), ops);
344
+ md.use(_require("markdown-it-task-checkbox"), ops);
337
345
  };
338
346
  }
339
347
  function registerMdPlugins(vpCfg, plugins) {
@@ -469,7 +477,7 @@ function getVitePlugins(cfg) {
469
477
  );
470
478
  }
471
479
  if (cfg?.mermaid !== false) {
472
- const { MermaidPlugin } = require("vitepress-plugin-mermaid");
480
+ const { MermaidPlugin } = _require("vitepress-plugin-mermaid");
473
481
  plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : cfg?.mermaid ?? {}));
474
482
  }
475
483
  if (cfg?.RSS) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sugarat/theme",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
5
5
  "author": "sugar",
6
6
  "license": "MIT",
@@ -44,7 +44,7 @@
44
44
  "mermaid": "^10.2.4",
45
45
  "vitepress-plugin-mermaid": "2.0.13",
46
46
  "vitepress-plugin-pagefind": "0.2.10",
47
- "vitepress-plugin-rss": "0.2.0",
47
+ "vitepress-plugin-rss": "0.2.1",
48
48
  "vitepress-plugin-tabs": "0.2.0"
49
49
  },
50
50
  "devDependencies": {
@@ -56,7 +56,7 @@
56
56
  "pagefind": "1.0.3",
57
57
  "sass": "^1.56.1",
58
58
  "typescript": "^4.8.2",
59
- "vitepress": "1.0.0-rc.35",
59
+ "vitepress": "1.0.0-rc.40",
60
60
  "vue": "^3.3.4"
61
61
  },
62
62
  "scripts": {
@@ -16,6 +16,7 @@ import BlogAlert from './BlogAlert.vue'
16
16
  import BlogPopover from './BlogPopover.vue'
17
17
  import BlogFooter from './BlogFooter.vue'
18
18
  import BlogHomeHeaderAvatar from './BlogHomeHeaderAvatar.vue'
19
+ import BlogBackToTop from './BlogBackToTop.vue'
19
20
 
20
21
  const { frontmatter } = useData()
21
22
  const layout = computed(() => frontmatter.value.layout)
@@ -66,10 +67,13 @@ const { Layout } = Theme
66
67
  <slot name="sidebar-nav-after" />
67
68
  <BlogSidebar />
68
69
  </template>
69
- <!-- 评论 -->
70
70
  <template #doc-after>
71
71
  <slot name="doc-after" />
72
- <BlogComment />
72
+ <!-- 评论 -->
73
+ <ClientOnly>
74
+ <BlogBackToTop />
75
+ <BlogComment />
76
+ </ClientOnly>
73
77
  </template>
74
78
  <template #layout-bottom>
75
79
  <BlogFooter v-if="layout === 'home'" />
@@ -0,0 +1,88 @@
1
+ <script lang="ts" setup>
2
+ import { useElementSize, useScroll } from '@vueuse/core'
3
+ import { ElIcon } from 'element-plus'
4
+ import { computed, ref } from 'vue'
5
+ import { vOuterHtml } from '../directives'
6
+ import { useBackToTopConfig } from '../composables/config/blog'
7
+
8
+ function handleBackRoTop() {
9
+ window.scrollTo({ top: 0, behavior: 'smooth' })
10
+ }
11
+
12
+ const $vpDoc = document.querySelector('.vp-doc')
13
+ const el = ref<any>($vpDoc)
14
+ const { width } = useElementSize(el)
15
+ const docWidth = computed(() => `${width.value}px`)
16
+
17
+ const backToTopConfig = useBackToTopConfig()
18
+ const open = computed(() => !!(backToTopConfig ?? true))
19
+
20
+ const { y } = useScroll(window)
21
+ const defaultTriggerHeight = 450
22
+ const triggerTop = computed(() => (typeof backToTopConfig === 'boolean' ? undefined : backToTopConfig?.top) ?? defaultTriggerHeight)
23
+
24
+ const show = computed(() => width && y.value > triggerTop.value)
25
+
26
+ const iconSVGStr = computed(() => typeof backToTopConfig === 'boolean' ? '' : backToTopConfig?.icon)
27
+ </script>
28
+
29
+ <template>
30
+ <div v-if="open" v-show="show" class="back-to-top">
31
+ <span class="icon-wrapper">
32
+ <ElIcon @click="handleBackRoTop">
33
+ <i v-if="iconSVGStr" v-outer-html="iconSVGStr" />
34
+ <svg v-else width="512" height="512" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
35
+ <path
36
+ fill="currentColor"
37
+ d="m20 22l-3.86-1.55c.7-1.53 1.2-3.11 1.51-4.72zM7.86 20.45L4 22l2.35-6.27c.31 1.61.81 3.19 1.51 4.72M12 2s5 2 5 10c0 3.1-.75 5.75-1.67 7.83A2 2 0 0 1 13.5 21h-3a2 2 0 0 1-1.83-1.17C7.76 17.75 7 15.1 7 12c0-8 5-10 5-10m0 10c1.1 0 2-.9 2-2s-.9-2-2-2s-2 .9-2 2s.9 2 2 2"
38
+ />
39
+ </svg>
40
+ </ElIcon>
41
+ </span>
42
+ </div>
43
+ </template>
44
+
45
+ <style lang="scss" scoped>
46
+ .back-to-top {
47
+ position: fixed;
48
+ width: v-bind(docWidth);
49
+ text-align: right;
50
+ bottom: 80px;
51
+ font-size: 16px;
52
+ transition: all 0.3s ease-in-out;
53
+ opacity: 0.6;
54
+ display: flex;
55
+ justify-content: right;
56
+ z-index: 200;
57
+
58
+ &:hover {
59
+ opacity: 1;
60
+ }
61
+
62
+ .icon-wrapper {
63
+ cursor: pointer;
64
+ border-radius: 50%;
65
+ position: relative;
66
+ right: -80px;
67
+ background-color: var(--vp-c-bg);
68
+ box-shadow: var(--box-shadow);
69
+ padding: 4px;
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: center;
73
+ background-color: var(--vp-c-brand-soft);
74
+ color: var(--vp-c-brand-1);
75
+
76
+ &:hover {
77
+ box-shadow: var(--box-shadow-hover);
78
+ }
79
+ }
80
+ }
81
+
82
+ @media screen and (max-width: 1200px) {
83
+ .back-to-top .icon-wrapper {
84
+ border-radius: 50%;
85
+ position: static;
86
+ }
87
+ }
88
+ </style>
@@ -1,8 +1,8 @@
1
1
  <script setup lang="ts">
2
- import { useElementVisibility } from '@vueuse/core'
2
+ import { useElementSize, useElementVisibility, useWindowSize } from '@vueuse/core'
3
3
  import { useData, useRoute } from 'vitepress'
4
- import { computed, ref, watch } from 'vue'
5
- import { ElAffix, ElButton } from 'element-plus'
4
+ import { computed, h, ref, watch } from 'vue'
5
+ import { ElIcon } from 'element-plus'
6
6
  import { Comment } from '@element-plus/icons-vue'
7
7
  import Giscus from '@giscus/vue'
8
8
  import { useGiscusConfig } from '../composables/config/blog'
@@ -20,7 +20,7 @@ function handleScrollToComment() {
20
20
  }
21
21
  const giscusConfig = useGiscusConfig()
22
22
 
23
- const commentConfig = computed<Theme.GiscusConfig>(() => {
23
+ const commentConfig = computed<Theme.CommentConfig>(() => {
24
24
  if (!giscusConfig) {
25
25
  return {} as any
26
26
  }
@@ -58,87 +58,112 @@ watch(
58
58
  immediate: true
59
59
  }
60
60
  )
61
+
62
+ const { width } = useWindowSize()
63
+ const mobileMinify = computed(() => width.value < 768 && (commentConfig.value.mobileMinify ?? true))
64
+
65
+ const CommentIcon = commentConfig.value?.icon
66
+ ? h('i', {
67
+ onVnodeMounted(vnode) {
68
+ if (vnode.el) {
69
+ vnode.el.outerHTML = commentConfig.value?.icon
70
+ }
71
+ },
72
+ })
73
+ : h(Comment)
74
+
75
+ const $vpDoc = document.querySelector('.vp-doc')
76
+ const el = ref<any>($vpDoc)
77
+ const { width: _docWidth } = useElementSize(el)
78
+ const docWidth = computed(() => `${_docWidth.value}px`)
79
+
80
+ const labelText = computed(() => {
81
+ return commentConfig.value.label ?? '评论'
82
+ })
61
83
  </script>
62
84
 
63
85
  <template>
64
- <div
65
- v-if="show"
66
- id="giscus-comment"
67
- ref="commentEl"
68
- class="comment"
69
- data-pagefind-ignore="all"
70
- >
71
- <ElAffix
72
- :class="{ hidden: commentIsVisible }"
73
- class="comment-btn"
74
- target="main"
75
- position="bottom"
76
- :offset="40"
77
- >
78
- <ElButton
79
- plain
80
- :icon="Comment"
81
- type="primary"
82
- @click="handleScrollToComment"
83
- >
84
- 评论
85
- </ElButton>
86
- </ElAffix>
86
+ <div v-if="show && _docWidth" id="giscus-comment" ref="commentEl" class="comment" data-pagefind-ignore="all">
87
87
  <Giscus
88
- v-if="showComment"
89
- :repo="commentConfig.repo"
90
- :repo-id="commentConfig.repoId"
91
- :category="commentConfig.category"
92
- :category-id="commentConfig.categoryId"
93
- :mapping="commentConfig.mapping || 'pathname'"
94
- reactions-enabled="1"
95
- emit-metadata="0"
96
- :input-position="commentConfig.inputPosition || 'top'"
97
- :theme="isDark ? 'dark' : 'light'"
98
- :lang="commentConfig.lang || 'zh-CN'"
99
- :loading="commentConfig.loading || 'eager'"
88
+ v-if="showComment" :repo="commentConfig.repo" :repo-id="commentConfig.repoId"
89
+ :category="commentConfig.category" :category-id="commentConfig.categoryId"
90
+ :mapping="commentConfig.mapping || 'pathname'" reactions-enabled="1" emit-metadata="0"
91
+ :input-position="commentConfig.inputPosition || 'top'" :theme="isDark ? 'dark' : 'light'"
92
+ :lang="commentConfig.lang || 'zh-CN'" :loading="commentConfig.loading || 'eager'"
100
93
  />
94
+
95
+ <div v-show="!commentIsVisible" class="comment-btn-wrapper">
96
+ <span v-if="!mobileMinify && labelText" class="icon-wrapper-text" @click="handleScrollToComment">
97
+ <ElIcon>
98
+ <CommentIcon />
99
+ </ElIcon>
100
+ <span class="text">
101
+ {{ labelText }}
102
+ </span>
103
+ </span>
104
+ <span v-else class="icon-wrapper">
105
+ <ElIcon @click="handleScrollToComment">
106
+ <CommentIcon />
107
+ </ElIcon>
108
+ </span>
109
+ </div>
101
110
  </div>
102
111
  </template>
103
112
 
104
113
  <style scoped lang="scss">
105
- .comment {
106
- width: 100%;
107
- text-align: center;
108
- padding: 40px 0;
109
- :deep(.el-button.el-button--primary:hover) {
110
- background-color: var(--vp-c-brand-2);
111
- border-color: var(--vp-c-brand-2);
112
- color: #fff;
114
+ .comment-btn-wrapper {
115
+ position: fixed;
116
+ width: v-bind(docWidth);
117
+ text-align: right;
118
+ bottom: 40px;
119
+ font-size: 16px;
120
+ transition: all 0.3s ease-in-out;
121
+ opacity: 0.6;
122
+ display: flex;
123
+ justify-content: right;
124
+ z-index: 200;
125
+
126
+ &:hover {
127
+ opacity: 1;
113
128
  }
114
- :deep(.el-button.el-button--primary) {
129
+
130
+ .icon-wrapper,
131
+ .icon-wrapper-text {
132
+ cursor: pointer;
133
+ border-radius: 50%;
134
+ position: relative;
135
+ right: -80px;
136
+ background-color: var(--vp-c-bg);
137
+ box-shadow: var(--box-shadow);
138
+ padding: 4px;
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: center;
115
142
  background-color: var(--vp-c-brand-soft);
116
- border-color: var(--vp-c-brand-2);
117
- color: var(--vp-c-brand-2);
143
+ color: var(--vp-c-brand-1);
144
+
145
+ &:hover {
146
+ box-shadow: var(--box-shadow-hover);
147
+ }
118
148
  }
119
- }
120
149
 
121
- .hidden {
122
- opacity: 0;
123
- pointer-events: none;
124
- }
125
- .comment-btn {
126
- :deep(.el-affix--fixed) {
127
- text-align: right;
128
- .el-button {
129
- position: relative;
130
- right: -100px;
150
+ .icon-wrapper-text {
151
+ border-radius: 2px;
152
+ padding: 2px 6px;
153
+
154
+ span.text{
155
+ font-size: 12px;
156
+ margin-left: 4px;
131
157
  }
132
158
  }
133
159
  }
134
160
 
135
161
  @media screen and (max-width: 1200px) {
136
- .comment-btn {
137
- :deep(.el-affix--fixed) {
138
- opacity: 0.7;
139
- .el-button {
140
- position: static;
141
- }
162
+ .comment-btn-wrapper {
163
+
164
+ .icon-wrapper,
165
+ .icon-wrapper-text {
166
+ position: static;
142
167
  }
143
168
  }
144
169
  }
@@ -1,10 +1,12 @@
1
1
  <script lang="ts" setup>
2
2
  import { ElButton, ElIcon } from 'element-plus'
3
- import { CircleCloseFilled, Flag } from '@element-plus/icons-vue'
3
+ import { CircleCloseFilled } from '@element-plus/icons-vue'
4
4
  import { computed, h, onMounted, ref } from 'vue'
5
5
  import type { BlogPopover } from '@sugarat/theme'
6
6
  import { parseStringStyle } from '@vue/shared'
7
+ import { useWindowSize } from '@vueuse/core'
7
8
  import { useBlogConfig } from '../composables/config/blog'
9
+ import { vOuterHtml } from '../directives'
8
10
 
9
11
  const { popover: popoverProps } = useBlogConfig()
10
12
 
@@ -20,6 +22,9 @@ const footerContent = computed(() => {
20
22
  const storageKey = 'theme-blog-popover'
21
23
  const closeFlag = `${storageKey}-close`
22
24
 
25
+ // 移动端最小化
26
+ const { width } = useWindowSize()
27
+
23
28
  onMounted(() => {
24
29
  if (!popoverProps?.title) {
25
30
  return
@@ -30,6 +35,12 @@ onMounted(() => {
30
35
  const newValue = JSON.stringify(popoverProps)
31
36
  localStorage.setItem(storageKey, newValue)
32
37
 
38
+ // 移动端最小化
39
+ if (width.value < 768 && popoverProps?.mobileMinify) {
40
+ show.value = false
41
+ return
42
+ }
43
+
33
44
  // >= 0 每次都展示,区别是否自动消失
34
45
  if (Number(popoverProps?.duration ?? '') >= 0) {
35
46
  show.value = true
@@ -117,12 +128,16 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
117
128
  <div class="header">
118
129
  <div class="title-wrapper">
119
130
  <ElIcon size="20px">
120
- <Flag />
131
+ <i v-if="popoverProps?.icon" v-outer-html="popoverProps.icon" />
132
+ <svg v-else width="512" height="512" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
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>
121
135
  </ElIcon>
122
136
  <span class="title">{{ popoverProps?.title }}</span>
123
137
  </div>
124
138
  <ElIcon class="close-icon" size="20px" @click="handleClose">
125
- <CircleCloseFilled />
139
+ <i v-if="popoverProps?.closeIcon" v-outer-html="popoverProps.closeIcon" />
140
+ <CircleCloseFilled v-else />
126
141
  </ElIcon>
127
142
  </div>
128
143
  <div v-if="bodyContent.length" class="body content">
@@ -138,12 +153,14 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
138
153
  </div>
139
154
  </div>
140
155
  <div
141
- v-show="!show && (popoverProps?.reopen ?? true) && popoverProps?.title"
142
- class="theme-blog-popover-close"
156
+ v-show="!show && (popoverProps?.reopen ?? true) && popoverProps?.title" class="theme-blog-popover-close"
143
157
  @click="show = true"
144
158
  >
145
- <ElIcon size="20px">
146
- <Flag />
159
+ <ElIcon>
160
+ <i v-if="popoverProps?.icon" v-outer-html="popoverProps.icon" />
161
+ <svg v-else width="512" height="512" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
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>
147
164
  </ElIcon>
148
165
  </div>
149
166
  </template>
@@ -160,16 +177,13 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
160
177
  border-radius: 6px;
161
178
  background-color: rgba(var(--bg-gradient-home));
162
179
  box-shadow: var(--box-shadow);
180
+
163
181
  :deep(.el-button.el-button--primary) {
164
182
  background-color: var(--vp-c-brand-2);
165
183
  border-color: var(--vp-c-brand-2);
166
184
  }
167
185
  }
168
- @media screen and (min-width: 760px) and (max-width: 1140px) {
169
- .theme-blog-popover {
170
- top: 200px;
171
- }
172
- }
186
+
173
187
  .header {
174
188
  background-color: var(--vp-c-brand-3);
175
189
  color: #fff;
@@ -177,6 +191,7 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
177
191
  display: flex;
178
192
  justify-content: space-between;
179
193
  align-items: center;
194
+
180
195
  .close-icon {
181
196
  cursor: pointer;
182
197
  }
@@ -185,6 +200,7 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
185
200
  .title-wrapper {
186
201
  display: flex;
187
202
  align-items: center;
203
+
188
204
  .title {
189
205
  font-size: 14px;
190
206
  padding-left: 6px;
@@ -194,11 +210,13 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
194
210
  .body {
195
211
  box-sizing: border-box;
196
212
  padding: 10px 10px 0;
213
+
197
214
  hr {
198
215
  border: none;
199
216
  border-bottom: 1px solid #eaecef;
200
217
  }
201
218
  }
219
+
202
220
  .footer {
203
221
  box-sizing: border-box;
204
222
  padding: 10px;
@@ -207,15 +225,18 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
207
225
  .body.content,
208
226
  .footer.content {
209
227
  text-align: center;
228
+
210
229
  h4 {
211
230
  text-align: center;
212
231
  font-size: 12px;
213
232
  }
233
+
214
234
  p {
215
235
  text-align: center;
216
236
  padding: 10px 0;
217
237
  font-size: 14px;
218
238
  }
239
+
219
240
  img {
220
241
  width: 100%;
221
242
  }
@@ -216,3 +216,7 @@ export function useAutoUpdateAnchor() {
216
216
  export function useHomeFooterConfig() {
217
217
  return inject(homeFooter)
218
218
  }
219
+
220
+ export function useBackToTopConfig() {
221
+ return useBlogConfig().backToTop
222
+ }
@@ -99,6 +99,22 @@ export namespace Theme {
99
99
  type: string
100
100
  }
101
101
 
102
+ export interface CommentConfig extends GiscusConfig {
103
+ /**
104
+ * @default '评论'
105
+ */
106
+ label?: string
107
+ /**
108
+ * 自定义图标,SVG 格式
109
+ * @recommend https://iconbuddy.app/search?q=fire
110
+ */
111
+ icon?: string
112
+ /**
113
+ * 移动端最小化按钮
114
+ * @default true
115
+ */
116
+ mobileMinify?: boolean
117
+ }
102
118
  export interface GiscusConfig {
103
119
  repo: Repo
104
120
  repoId: string
@@ -181,6 +197,9 @@ export namespace Theme {
181
197
  html?: string
182
198
  }
183
199
 
200
+ /**
201
+ * 公告
202
+ */
184
203
  export interface Popover {
185
204
  title: string
186
205
  /**
@@ -189,12 +208,28 @@ export namespace Theme {
189
208
  * 配置改变时,会重新触发展示
190
209
  */
191
210
  duration: number
211
+ /**
212
+ * 移动端自动最小化
213
+ * @default false
214
+ */
215
+ mobileMinify?: boolean
192
216
  body?: BlogPopover.Value[]
193
217
  footer?: BlogPopover.Value[]
194
218
  /**
195
219
  * 手动重新打开
220
+ * @default true
196
221
  */
197
222
  reopen?: boolean
223
+ /**
224
+ * 设置展示图标,svg
225
+ * @recommend https://iconbuddy.app/search?q=fire
226
+ */
227
+ icon?: string
228
+ /**
229
+ * 设置关闭图标,svg
230
+ * @recommend https://iconbuddy.app/search?q=fire
231
+ */
232
+ closeIcon?: string
198
233
  }
199
234
  export interface FriendLink {
200
235
  nickname: string
@@ -312,7 +347,7 @@ export namespace Theme {
312
347
  * 配置评论
313
348
  * power by https://giscus.app/zh-CN
314
349
  */
315
- comment?: GiscusConfig | false
350
+ comment?: CommentConfig | false
316
351
  /**
317
352
  * 阅读文章左侧的推荐文章(替代默认的sidebar)
318
353
  */
@@ -364,6 +399,22 @@ export namespace Theme {
364
399
  * 详见 https://github.com/linsir/markdown-it-task-checkbox
365
400
  */
366
401
  taskCheckbox?: TaskCheckbox | boolean
402
+
403
+ backToTop?: boolean | BackToTop
404
+ }
405
+
406
+ export interface BackToTop {
407
+ /**
408
+ * 距离顶部多少距离出现
409
+ * @default 450
410
+ */
411
+ top?: number
412
+
413
+ /**
414
+ * 设置展示图标,svg
415
+ * @recommend https://iconbuddy.app/search?q=fire
416
+ */
417
+ icon?: string
367
418
  }
368
419
 
369
420
  export interface TaskCheckbox {
@@ -0,0 +1,5 @@
1
+ export const vOuterHtml = {
2
+ mounted: (el: HTMLElement, binding: any) => {
3
+ el.outerHTML = binding.value
4
+ }
5
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import 'element-plus/theme-chalk/dark/css-vars.css'
8
8
  import type { Theme } from 'vitepress'
9
9
  import DefaultTheme from 'vitepress/theme'
10
10
  import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client'
11
+ import Mermaid from 'vitepress-plugin-mermaid/Mermaid.vue'
11
12
  import BlogApp from './components/BlogApp.vue'
12
13
  import { withConfigProvider } from './composables/config/blog'
13
14
 
@@ -22,10 +23,11 @@ export const BlogTheme: Theme = {
22
23
  ...DefaultTheme,
23
24
  Layout: withConfigProvider(BlogApp),
24
25
  enhanceApp(ctx) {
25
- enhanceAppWithTabs(ctx.app)
26
+ enhanceAppWithTabs(ctx.app as any)
26
27
  DefaultTheme.enhanceApp(ctx)
27
28
  ctx.app.component('TimelinePage', TimelinePage)
28
29
  ctx.app.component('UserWorksPage', UserWorksPage)
30
+ ctx.app.component('Mermaid', Mermaid)
29
31
  }
30
32
  }
31
33
 
@@ -126,12 +126,14 @@ function isBase64ImageURL(url: string) {
126
126
  return regex.test(url)
127
127
  }
128
128
 
129
+ const imageRegex = /!\[.*?\]\((.*?)\s*(".*?")?\)/
130
+
129
131
  /**
130
132
  * 从文档内容中提取封面
131
133
  * @param content 文档内容
132
134
  */
133
135
  export function getFirstImagURLFromMD(content: string, route: string) {
134
- const url = content.match(/!\[.*\]\((.*)\)/)?.[1]?.replace(/['"]/g, '')
136
+ const url = content.match(imageRegex)?.[1]
135
137
  const isHTTPSource = url && url.startsWith('http')
136
138
  if (!url) {
137
139
  return ''
@@ -1,9 +1,14 @@
1
1
  /* eslint-disable global-require */
2
+ import { createRequire } from 'module'
2
3
  import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs'
3
4
  import type { UserConfig } from 'vitepress'
4
5
  import type { Theme } from '../../composables/config/index'
5
6
  import { aliasObjectToArray } from './index'
6
7
 
8
+ export function _require(module: any) {
9
+ return (typeof import.meta?.url !== 'undefined' ? createRequire(import.meta.url) : require)(module)
10
+ }
11
+
7
12
  export function getMarkdownPlugins(cfg?: Partial<Theme.BlogConfig>) {
8
13
  const markdownPlugin: any[] = []
9
14
  // tabs支持,默认开启
@@ -15,7 +20,7 @@ export function getMarkdownPlugins(cfg?: Partial<Theme.BlogConfig>) {
15
20
  if (cfg) {
16
21
  cfg.mermaid = cfg?.mermaid ?? true
17
22
  if (cfg?.mermaid !== false) {
18
- const { MermaidMarkdown } = require('vitepress-plugin-mermaid')
23
+ const { MermaidMarkdown } = _require('vitepress-plugin-mermaid')
19
24
  markdownPlugin.push(MermaidMarkdown)
20
25
  }
21
26
  }
@@ -32,7 +37,7 @@ export function getMarkdownPlugins(cfg?: Partial<Theme.BlogConfig>) {
32
37
 
33
38
  export function taskCheckboxPlugin(ops: Theme.TaskCheckbox | boolean) {
34
39
  return (md: any) => {
35
- md.use(require('markdown-it-task-checkbox'), ops)
40
+ md.use(_require('markdown-it-task-checkbox'), ops)
36
41
  }
37
42
  }
38
43
 
@@ -48,34 +53,6 @@ export function registerMdPlugins(vpCfg: any, plugins: any[]) {
48
53
  }
49
54
  }
50
55
 
51
- /**
52
- * 流程图支持,配置mermaid
53
- */
54
- export function assignMermaid(config: any) {
55
- if (!config?.mermaid)
56
- return
57
-
58
- if (!config.vite)
59
- config.vite = {}
60
- if (!config.vite.plugins)
61
- config.vite.plugins = []
62
- const { MermaidPlugin } = require('vitepress-plugin-mermaid')
63
- config.vite.plugins.push(MermaidPlugin(config.mermaid))
64
- if (!config.vite.resolve)
65
- config.vite.resolve = {}
66
- if (!config.vite.resolve.alias)
67
- config.vite.resolve.alias = {}
68
-
69
- config.vite.resolve.alias = [
70
- ...aliasObjectToArray({
71
- ...config.vite.resolve.alias,
72
- 'cytoscape/dist/cytoscape.umd.js': 'cytoscape/dist/cytoscape.esm.js',
73
- 'mermaid': 'mermaid/dist/mermaid.esm.mjs'
74
- }),
75
- { find: /^dayjs\/(.*).js/, replacement: 'dayjs/esm/$1' }
76
- ]
77
- }
78
-
79
56
  export function patchMermaidPluginCfg(config: any) {
80
57
  if (!config.vite.resolve)
81
58
  config.vite.resolve = {}
@@ -100,24 +77,6 @@ export function patchOptimizeDeps(config: any) {
100
77
  config.vite.optimizeDeps.include = ['element-plus']
101
78
  }
102
79
 
103
- export function wrapperCfgWithMermaid(config: UserConfig<Theme.Config>): any {
104
- // @ts-expect-error
105
- const extendThemeConfig = (config.extends?.themeConfig?.blog
106
- || {}) as Theme.BlogConfig
107
-
108
- // 开关支持Mermaid
109
- const resultConfig
110
- = extendThemeConfig.mermaid === false
111
- ? config
112
- : {
113
- ...config,
114
- mermaid:
115
- extendThemeConfig.mermaid === true ? {} : extendThemeConfig.mermaid
116
- }
117
- assignMermaid(resultConfig)
118
- return resultConfig
119
- }
120
-
121
80
  export function supportRunExtendsPlugin(config: UserConfig<Theme.Config>) {
122
81
  // 处理markdown插件
123
82
  if (!config.markdown)
@@ -11,6 +11,7 @@ import {
11
11
  } from 'vitepress-plugin-pagefind'
12
12
  import { RssPlugin } from 'vitepress-plugin-rss'
13
13
  import type { Theme } from '../../composables/config/index'
14
+ import { _require } from './mdPlugins'
14
15
  import { joinPath } from './index'
15
16
 
16
17
  export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
@@ -32,7 +33,7 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
32
33
 
33
34
  // 内置支持Mermaid
34
35
  if (cfg?.mermaid !== false) {
35
- const { MermaidPlugin } = require('vitepress-plugin-mermaid')
36
+ const { MermaidPlugin } = _require('vitepress-plugin-mermaid')
36
37
  plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : (cfg?.mermaid ?? {})))
37
38
  }
38
39