clarity-theme 0.1.0 → 0.1.2

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.
Files changed (154) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +70 -4
  3. package/README.zh-CN.md +67 -4
  4. package/nuxt.config.ts +217 -214
  5. package/package.json +35 -37
  6. package/{app → src}/app.config.ts +110 -110
  7. package/{app → src}/app.vue +5 -5
  8. package/{app → src}/components/blog/BlogAside.vue +25 -25
  9. package/{app → src}/components/blog/BlogFooter.vue +25 -25
  10. package/{app → src}/components/blog/BlogHeader.global.vue +48 -48
  11. package/{app → src}/components/blog/BlogPanel.vue +43 -43
  12. package/{app → src}/components/blog/BlogSidebar.vue +53 -53
  13. package/{app → src}/components/blog/BlogWidget.vue +42 -42
  14. package/{app → src}/components/blog/Mask.vue +25 -25
  15. package/{app → src}/components/blog/SkipToContent.vue +9 -9
  16. package/{app → src}/components/blog/ThemeToggle.vue +22 -22
  17. package/{app → src}/components/content/Badge.vue +45 -45
  18. package/{app → src}/components/content/Blur.vue +14 -14
  19. package/{app → src}/components/content/CardList.vue +8 -8
  20. package/{app → src}/components/content/Chat.vue +41 -41
  21. package/{app → src}/components/content/Copy.vue +71 -71
  22. package/{app → src}/components/content/EmojiClock.vue +49 -49
  23. package/{app → src}/components/content/FeedCard.vue +96 -96
  24. package/{app → src}/components/content/FeedGroup.vue +61 -61
  25. package/{app → src}/components/content/Folding.vue +19 -19
  26. package/{app → src}/components/content/Key.vue +143 -143
  27. package/{app → src}/components/content/LinkBanner.vue +27 -27
  28. package/{app → src}/components/content/LinkCard.vue +28 -28
  29. package/{app → src}/components/content/MdTitle.vue +8 -8
  30. package/{app → src}/components/content/Mermaid.vue +99 -99
  31. package/{app → src}/components/content/MusicScore.vue +63 -63
  32. package/{app → src}/components/content/Pic.vue +49 -49
  33. package/{app → src}/components/content/Poetry.vue +27 -27
  34. package/{app → src}/components/content/ProseA.vue +24 -24
  35. package/{app → src}/components/content/ProseCode.vue +16 -16
  36. package/{app → src}/components/content/ProsePre.vue +117 -117
  37. package/{app → src}/components/content/ProseTable.vue +23 -23
  38. package/{app → src}/components/content/Quote.vue +21 -21
  39. package/{app → src}/components/content/Tab.vue +32 -32
  40. package/{app → src}/components/content/Timeline.vue +33 -33
  41. package/{app → src}/components/content/Tip.vue +40 -40
  42. package/{app → src}/components/content/VideoEmbed.vue +112 -112
  43. package/{app → src}/components/partial/Button.vue +36 -36
  44. package/{app → src}/components/partial/DlGroup.vue +31 -31
  45. package/{app → src}/components/partial/Dropdown.vue +21 -21
  46. package/{app → src}/components/partial/Error.vue +39 -39
  47. package/{app → src}/components/partial/Expand.vue +26 -26
  48. package/{app → src}/components/partial/IconNavList.vue +24 -24
  49. package/{app → src}/components/partial/Pagination.vue +62 -62
  50. package/{app → src}/components/partial/RadioGroup.vue +30 -30
  51. package/{app → src}/components/partial/Secret.vue +11 -11
  52. package/{app → src}/components/partial/Slider.vue +48 -48
  53. package/{app → src}/components/partial/Toggle.vue +17 -17
  54. package/{app → src}/components/popover/Lightbox.vue +44 -44
  55. package/{app → src}/components/popover/Search.vue +143 -143
  56. package/{app → src}/components/popover/SearchItem.vue +28 -28
  57. package/{app → src}/components/post/Archive.vue +40 -40
  58. package/{app → src}/components/post/Article.vue +49 -49
  59. package/{app → src}/components/post/Comment.vue +119 -119
  60. package/{app → src}/components/post/Excerpt.vue +40 -40
  61. package/{app → src}/components/post/OrderToggle.vue +70 -70
  62. package/{app → src}/components/post/PostFooter.vue +52 -52
  63. package/{app → src}/components/post/PostHeader.vue +66 -66
  64. package/{app → src}/components/post/PostSurround.vue +51 -51
  65. package/{app → src}/components/post/Slide.vue +85 -85
  66. package/{app → src}/components/util/Date.vue +62 -62
  67. package/{app → src}/components/util/HydrateSafe.vue +10 -10
  68. package/{app → src}/components/util/Img.vue +38 -38
  69. package/{app → src}/components/util/Link.vue +11 -11
  70. package/{app → src}/components/widget/BlogLog.vue +23 -23
  71. package/{app → src}/components/widget/BlogStats.vue +38 -38
  72. package/{app → src}/components/widget/BlogTech.vue +57 -57
  73. package/{app → src}/components/widget/Empty.vue +3 -3
  74. package/{app → src}/components/widget/Toc.vue +67 -67
  75. package/{app → src}/composables/useArticle.ts +115 -115
  76. package/{app → src}/composables/useAvoid.ts +87 -87
  77. package/{app → src}/composables/useCopy.ts +21 -21
  78. package/{app → src}/composables/usePagination.ts +70 -70
  79. package/{app → src}/composables/useShiki.ts +129 -129
  80. package/{app → src}/composables/useToc.ts +57 -57
  81. package/{app → src}/composables/useWidgets.ts +56 -56
  82. package/{config → src/config}/app.ts +62 -62
  83. package/{config → src/config}/content.ts +75 -75
  84. package/{config → src/config}/define.ts +3 -3
  85. package/src/config/feeds.empty.ts +6 -0
  86. package/{config → src/config}/index.ts +1 -1
  87. package/{config → src/config}/schema.ts +89 -89
  88. package/{app → src}/error.vue +31 -31
  89. package/{img → src/img}/index.ts +2 -2
  90. package/{app → src}/layouts/default.vue +25 -25
  91. package/{modules → src/modules}/clarity-config/index.ts +168 -162
  92. package/src/modules/clarity-source-layout/index.ts +78 -0
  93. package/{app → src}/pages/[...slug].vue +59 -59
  94. package/{app → src}/pages/archive.vue +129 -129
  95. package/{app → src}/pages/index.vue +76 -76
  96. package/{app → src}/pages/link.vue +62 -62
  97. package/{app → src}/pages/preview.vue +46 -46
  98. package/{app → src}/plugins/easter-egg.ts +23 -23
  99. package/{app → src}/plugins/init.ts +6 -6
  100. package/{app → src}/plugins/tippy.ts +22 -22
  101. package/{public → src/public}/assets/atom.css +274 -274
  102. package/{public → src/public}/assets/atom.xsl +109 -109
  103. package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +58 -58
  104. package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +41 -41
  105. package/{server → src/server}/api/stats.get.ts +84 -83
  106. package/{server → src/server}/routes/atom.xml.get.ts +85 -85
  107. package/{server → src/server}/routes/subscriptions.opml.get.ts +64 -64
  108. package/{shared → src/shared}/utils/clarity.ts +24 -24
  109. package/{shared → src/shared}/utils/icon.ts +218 -218
  110. package/{shared → src/shared}/utils/link.ts +52 -52
  111. package/{shared → src/shared}/utils/str.ts +88 -88
  112. package/{shared → src/shared}/utils/time.ts +115 -115
  113. package/{app → src}/shiki.config.ts +8 -8
  114. package/{app → src}/stores/layout.ts +34 -34
  115. package/{app → src}/stores/search.ts +37 -37
  116. package/{app → src}/types/article.ts +16 -16
  117. package/src/types/feed.ts +1 -0
  118. package/{app → src}/types/index.ts +14 -14
  119. package/{app → src}/types/nav.ts +11 -11
  120. package/{app → src}/utils/anim.ts +37 -37
  121. package/{app → src}/utils/img.ts +83 -83
  122. package/app/types/feed.ts +0 -1
  123. package/config/feeds.empty.ts +0 -4
  124. package/{app → src}/assets/css/_variable.scss +0 -0
  125. package/{app → src}/assets/css/animation.scss +0 -0
  126. package/{app → src}/assets/css/article.scss +0 -0
  127. package/{app → src}/assets/css/color.scss +0 -0
  128. package/{app → src}/assets/css/font.scss +0 -0
  129. package/{app → src}/assets/css/main.scss +0 -0
  130. package/{app → src}/assets/css/reusable.scss +0 -0
  131. package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
  132. package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
  133. package/{app → src}/components/content/Alert.vue +0 -0
  134. package/{config → src/config}/content.d.mts +0 -0
  135. package/{config → src/config}/content.mjs +0 -0
  136. package/{config → src/config}/define.mjs +0 -0
  137. package/{config → src/config}/feed.ts +0 -0
  138. package/{config → src/config}/index.d.mts +0 -0
  139. package/{config → src/config}/index.mjs +0 -0
  140. package/{config → src/config}/public.ts +1 -1
  141. package/{config → src/config}/schema.d.mts +0 -0
  142. package/{config → src/config}/schema.mjs +0 -0
  143. package/{config → src/config}/server.ts +1 -1
  144. /package/{img → src/img}/index.d.mts +0 -0
  145. /package/{img → src/img}/index.mjs +0 -0
  146. /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
  147. /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
  148. /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
  149. /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
  150. /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
  151. /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
  152. /package/{server → src/server}/utils/clarity.ts +0 -0
  153. /package/{shared → src/shared}/types/content.ts +0 -0
  154. /package/{app → src}/types/modal.ts +0 -0
@@ -1,145 +1,145 @@
1
- <script setup lang="ts">
2
- import { useEventListener } from '@vueuse/core'
3
- import { computed, ref } from 'vue'
4
-
5
- const props = withDefaults(defineProps<{
6
- text?: string
7
- /** https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent/key */
8
- code?: string
9
- /** 仅 macOS 默认显示图标 */
10
- icon?: boolean
11
- ctrl?: boolean
12
- shift?: boolean
13
- alt?: boolean
14
- meta?: boolean
15
- win?: boolean
16
- /** 智能适配:Windows用Ctrl,macOS用Cmd */
17
- cmd?: boolean
18
- prevent?: boolean
19
- }>(), {
20
- icon: undefined,
21
- })
22
-
23
- const emit = defineEmits<{
24
- press: []
25
- }>()
26
-
27
- const mounted = useMounted()
28
- const isMac = computed(() => mounted.value && /mac ?os/i.test(navigator?.userAgent))
29
- const useSymbol = computed(() => isMac.value ? props.icon !== false : props.icon)
30
- const keyJoiner = computed(() => useSymbol.value ? '' : '+')
31
-
32
- // @keep-sorted
33
- const displayMap = {
34
- ' ': 'Space',
35
- 'ArrowDown': '↓',
36
- 'ArrowLeft': '←',
37
- 'ArrowRight': '→',
38
- 'ArrowUp': '↑',
39
- 'Control': 'Ctrl',
40
- 'Delete': 'Del',
41
- 'Escape': 'Esc',
42
- get 'Meta'() { return isMac.value ? 'Cmd' : 'Win' },
43
- }
44
-
45
- // @keep-sorted
46
- const symbolMap = {
47
- ' ': '␣',
48
- 'Alt': '⌥',
49
- 'Backspace': '⌫',
50
- 'Control': '⌃',
51
- 'Delete': '⌦',
52
- 'Enter': '↵',
53
- 'Escape': '⎋',
54
- get 'Meta'() { return isMac.value ? '⌘' : '⊞' },
55
- 'Shift': '⇧',
56
- 'Tab': '⇥',
57
- 'Win': '⊞',
58
- }
59
-
60
- function normalizeCodeDisplay(code?: string) {
61
- if (!code)
62
- return ''
63
- if (useSymbol.value && code in symbolMap)
64
- return symbolMap[code as keyof typeof symbolMap]
65
- if (code in displayMap)
66
- return displayMap[code as keyof typeof displayMap]
67
- return code
68
- }
69
-
70
- const codeDisplay = computed(() => {
71
- if (props.text)
72
- return props.text
73
-
74
- const keyConfigs = [
75
- { condition: props.cmd, code: isMac.value ? 'Meta' : 'Control' },
76
- { condition: props.ctrl && !props.cmd, code: 'Control' },
77
- { condition: props.shift, code: 'Shift' },
78
- { condition: props.alt, code: 'Alt' },
79
- { condition: props.meta && !props.cmd, code: 'Meta' },
80
- { condition: props.win && !props.meta, code: 'Win' },
81
- { condition: props.code, code: props.code },
82
- ]
83
-
84
- return keyConfigs
85
- .filter(config => config.condition)
86
- .map(config => normalizeCodeDisplay(config.code))
87
- .join(keyJoiner.value)
88
- })
89
-
90
- const active = ref(false)
91
-
92
- const ctrlState = useKeyModifier('Control')
93
- const shiftState = useKeyModifier('Shift')
94
- const altState = useKeyModifier('Alt')
95
- const metaState = useKeyModifier('Meta')
96
-
97
- /** 检查当前修饰键状态是否匹配 props */
98
- function modifiersMatch() {
99
- const cmdMatch = props.cmd
100
- ? (isMac.value ? metaState.value : ctrlState.value)
101
- : true
102
-
103
- return cmdMatch
104
- && (!props.ctrl || (!props.cmd && ctrlState.value))
105
- && (!props.shift || shiftState.value)
106
- && (!props.alt || altState.value)
107
- && (!props.meta || (!props.cmd && metaState.value))
108
- }
109
-
110
- function matchKeyEvent(e: KeyboardEvent, expectedCode?: string) {
111
- if (expectedCode && e.key?.toLowerCase() !== expectedCode.toLowerCase())
112
- return false
113
- return modifiersMatch()
114
- }
115
-
116
- useEventListener('keydown', (e) => {
117
- if (matchKeyEvent(e, props.code)) {
118
- emit('press')
119
- active.value = true
120
- props.prevent && e.preventDefault()
121
- }
122
- })
123
-
124
- useEventListener('keyup', (e) => {
125
- if (matchKeyEvent(e, props.code) || !modifiersMatch())
126
- active.value = false
127
- })
128
-
129
- useEventListener('blur', () => {
130
- active.value = false
131
- })
132
- </script>
133
-
134
- <template>
135
- <UtilHydrateSafe>
136
- <kbd :class="{ active }" @click.stop="emit('press')">
137
- <slot>{{ codeDisplay }}</slot>
138
- </kbd>
139
- </UtilHydrateSafe>
140
- </template>
141
-
142
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import { useEventListener } from '@vueuse/core'
3
+ import { computed, ref } from 'vue'
4
+
5
+ const props = withDefaults(defineProps<{
6
+ text?: string
7
+ /** https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent/key */
8
+ code?: string
9
+ /** 仅 macOS 默认显示图标 */
10
+ icon?: boolean
11
+ ctrl?: boolean
12
+ shift?: boolean
13
+ alt?: boolean
14
+ meta?: boolean
15
+ win?: boolean
16
+ /** 智能适配:Windows用Ctrl,macOS用Cmd */
17
+ cmd?: boolean
18
+ prevent?: boolean
19
+ }>(), {
20
+ icon: undefined,
21
+ })
22
+
23
+ const emit = defineEmits<{
24
+ press: []
25
+ }>()
26
+
27
+ const mounted = useMounted()
28
+ const isMac = computed(() => mounted.value && /mac ?os/i.test(navigator?.userAgent))
29
+ const useSymbol = computed(() => isMac.value ? props.icon !== false : props.icon)
30
+ const keyJoiner = computed(() => useSymbol.value ? '' : '+')
31
+
32
+ // @keep-sorted
33
+ const displayMap = {
34
+ ' ': 'Space',
35
+ 'ArrowDown': '↓',
36
+ 'ArrowLeft': '←',
37
+ 'ArrowRight': '→',
38
+ 'ArrowUp': '↑',
39
+ 'Control': 'Ctrl',
40
+ 'Delete': 'Del',
41
+ 'Escape': 'Esc',
42
+ get 'Meta'() { return isMac.value ? 'Cmd' : 'Win' },
43
+ }
44
+
45
+ // @keep-sorted
46
+ const symbolMap = {
47
+ ' ': '␣',
48
+ 'Alt': '⌥',
49
+ 'Backspace': '⌫',
50
+ 'Control': '⌃',
51
+ 'Delete': '⌦',
52
+ 'Enter': '↵',
53
+ 'Escape': '⎋',
54
+ get 'Meta'() { return isMac.value ? '⌘' : '⊞' },
55
+ 'Shift': '⇧',
56
+ 'Tab': '⇥',
57
+ 'Win': '⊞',
58
+ }
59
+
60
+ function normalizeCodeDisplay(code?: string) {
61
+ if (!code)
62
+ return ''
63
+ if (useSymbol.value && code in symbolMap)
64
+ return symbolMap[code as keyof typeof symbolMap]
65
+ if (code in displayMap)
66
+ return displayMap[code as keyof typeof displayMap]
67
+ return code
68
+ }
69
+
70
+ const codeDisplay = computed(() => {
71
+ if (props.text)
72
+ return props.text
73
+
74
+ const keyConfigs = [
75
+ { condition: props.cmd, code: isMac.value ? 'Meta' : 'Control' },
76
+ { condition: props.ctrl && !props.cmd, code: 'Control' },
77
+ { condition: props.shift, code: 'Shift' },
78
+ { condition: props.alt, code: 'Alt' },
79
+ { condition: props.meta && !props.cmd, code: 'Meta' },
80
+ { condition: props.win && !props.meta, code: 'Win' },
81
+ { condition: props.code, code: props.code },
82
+ ]
83
+
84
+ return keyConfigs
85
+ .filter(config => config.condition)
86
+ .map(config => normalizeCodeDisplay(config.code))
87
+ .join(keyJoiner.value)
88
+ })
89
+
90
+ const active = ref(false)
91
+
92
+ const ctrlState = useKeyModifier('Control')
93
+ const shiftState = useKeyModifier('Shift')
94
+ const altState = useKeyModifier('Alt')
95
+ const metaState = useKeyModifier('Meta')
96
+
97
+ /** 检查当前修饰键状态是否匹配 props */
98
+ function modifiersMatch() {
99
+ const cmdMatch = props.cmd
100
+ ? (isMac.value ? metaState.value : ctrlState.value)
101
+ : true
102
+
103
+ return cmdMatch
104
+ && (!props.ctrl || (!props.cmd && ctrlState.value))
105
+ && (!props.shift || shiftState.value)
106
+ && (!props.alt || altState.value)
107
+ && (!props.meta || (!props.cmd && metaState.value))
108
+ }
109
+
110
+ function matchKeyEvent(e: KeyboardEvent, expectedCode?: string) {
111
+ if (expectedCode && e.key?.toLowerCase() !== expectedCode.toLowerCase())
112
+ return false
113
+ return modifiersMatch()
114
+ }
115
+
116
+ useEventListener('keydown', (e) => {
117
+ if (matchKeyEvent(e, props.code)) {
118
+ emit('press')
119
+ active.value = true
120
+ props.prevent && e.preventDefault()
121
+ }
122
+ })
123
+
124
+ useEventListener('keyup', (e) => {
125
+ if (matchKeyEvent(e, props.code) || !modifiersMatch())
126
+ active.value = false
127
+ })
128
+
129
+ useEventListener('blur', () => {
130
+ active.value = false
131
+ })
132
+ </script>
133
+
134
+ <template>
135
+ <UtilHydrateSafe>
136
+ <kbd :class="{ active }" @click.stop="emit('press')">
137
+ <slot>{{ codeDisplay }}</slot>
138
+ </kbd>
139
+ </UtilHydrateSafe>
140
+ </template>
141
+
142
+ <style lang="scss" scoped>
143
143
  kbd {
144
144
  display: inline-block;
145
145
  margin: 0.1em;
@@ -161,4 +161,4 @@ kbd {
161
161
  transform: translateY(0.05em);
162
162
  }
163
163
  }
164
- </style>
164
+ </style>
@@ -1,29 +1,29 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- banner?: string
4
- mirror?: ImgService
5
- title: string
6
- description?: string
7
- link: string
8
- }>()
9
- </script>
10
-
11
- <template>
12
- <UtilLink :to="link" class="link-banner card" :title="joinWith([title, description, link])">
13
- <UtilImg v-if="banner" class="link-banner-bg" :src="banner" :mirror />
14
- <div class="link-banner-header" />
15
- <div class="link-banner-info">
16
- <div class="link-banner-title">
17
- {{ title }}
18
- </div>
19
- <div class="link-banner-desc">
20
- {{ description ?? getDomain(link) }}
21
- </div>
22
- </div>
23
- </UtilLink>
24
- </template>
25
-
26
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ banner?: string
4
+ mirror?: ImgService
5
+ title: string
6
+ description?: string
7
+ link: string
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <UtilLink :to="link" class="link-banner card" :title="joinWith([title, description, link])">
13
+ <UtilImg v-if="banner" class="link-banner-bg" :src="banner" :mirror />
14
+ <div class="link-banner-header" />
15
+ <div class="link-banner-info">
16
+ <div class="link-banner-title">
17
+ {{ title }}
18
+ </div>
19
+ <div class="link-banner-desc">
20
+ {{ description ?? getDomain(link) }}
21
+ </div>
22
+ </div>
23
+ </UtilLink>
24
+ </template>
25
+
26
+ <style lang="scss" scoped>
27
27
  .link-banner {
28
28
  line-height: 1.4;
29
29
 
@@ -56,4 +56,4 @@ defineProps<{
56
56
  color: var(--c-text-2);
57
57
  }
58
58
  }
59
- </style>
59
+ </style>
@@ -1,30 +1,30 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- link: string
4
- title: string
5
- description?: string
6
- icon?: string
7
- mirror?: ImgService
8
- }>()
9
- </script>
10
-
11
- <template>
12
- <UtilLink :to="link" class="link-card card" :title="joinWith([title, description, link])">
13
- <div class="link-card-info">
14
- <div class="link-card-title">
15
- {{ title }}
16
- </div>
17
- <div class="link-card-description">
18
- {{ description ?? getDomain(link) }}
19
- </div>
20
- </div>
21
- <slot name="icon" class="link-card-icon-slot">
22
- <UtilImg v-if="icon" class="link-card-icon" :src="icon" :mirror />
23
- </slot>
24
- </UtilLink>
25
- </template>
26
-
27
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ link: string
4
+ title: string
5
+ description?: string
6
+ icon?: string
7
+ mirror?: ImgService
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <UtilLink :to="link" class="link-card card" :title="joinWith([title, description, link])">
13
+ <div class="link-card-info">
14
+ <div class="link-card-title">
15
+ {{ title }}
16
+ </div>
17
+ <div class="link-card-description">
18
+ {{ description ?? getDomain(link) }}
19
+ </div>
20
+ </div>
21
+ <slot name="icon" class="link-card-icon-slot">
22
+ <UtilImg v-if="icon" class="link-card-icon" :src="icon" :mirror />
23
+ </slot>
24
+ </UtilLink>
25
+ </template>
26
+
27
+ <style lang="scss" scoped>
28
28
  .link-card {
29
29
  display: flex;
30
30
  align-items: center;
@@ -71,4 +71,4 @@ defineProps<{
71
71
  object-fit: cover;
72
72
  }
73
73
  }
74
- </style>
74
+ </style>
@@ -1,10 +1,10 @@
1
- <template>
2
- <div class="md-title">
3
- <slot />
4
- </div>
5
- </template>
6
-
7
- <style lang="scss" scoped>
1
+ <template>
2
+ <div class="md-title">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style lang="scss" scoped>
8
8
  .md-title {
9
9
  margin: 0.5em 0;
10
10
  font-size: 1.1em;
@@ -23,4 +23,4 @@
23
23
  opacity: 0.5;
24
24
  }
25
25
  }
26
- </style>
26
+ </style>
@@ -1,101 +1,101 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- code: string
4
- }>()
5
-
6
- const colorMode = useColorMode()
7
- const container = useTemplateRef('mermaid')
8
- const [scroll, toggleScroll] = useToggle(true)
9
-
10
- const id = useId()
11
- // mermaid 会移除 DOM 中同 id 的旧图,故每次渲染另起 id,避免切换主题时高度塌陷
12
- let renderCount = 0
13
-
14
- // mermaid 体积较大,接近视口时才动态引入;隐藏容器(如 Tab)内不渲染,避免量取到错误的尺寸
15
- const isVisible = useElementVisibility(container, { rootMargin: '50%' })
16
- // 可见后即锁定,避免滚出视口时图表被清空
17
- const shouldRender = ref(false)
18
- whenever(isVisible, () => shouldRender.value = true, { once: true })
19
-
20
- const diagram = computedAsync<{ svg?: string, width?: number, error?: string }>(async () => {
21
- // 异步依赖需在 await 之前读取
22
- const { code } = props
23
- const darkMode = colorMode.value === 'dark'
24
- if (!shouldRender.value)
25
- return {}
26
-
27
- try {
28
- const { default: mermaid } = await import('mermaid')
29
- // 等待 color-mode 换好根元素类名,以及字体就绪——否则取到旧配色、量出偏窄的文本
30
- await Promise.all([nextTick(), document.fonts.ready])
31
-
32
- const style = getComputedStyle(document.documentElement)
33
- const cssVar = (name: string) => style.getPropertyValue(name)
34
-
35
- mermaid.initialize({
36
- fontFamily: 'inherit',
37
- // 须在 load 事件前关闭,否则 mermaid 会自行扫描并接管页面元素
38
- startOnLoad: false,
39
- suppressErrorRendering: true,
40
- // themeVariables 仅对 base 主题生效,其余主题会重算这些颜色
41
- theme: 'base',
42
- themeVariables: {
43
- darkMode,
44
- // 不指定则深色下连线被推导为近黑色、边标签被推导为绿色
45
- background: cssVar('--c-bg'),
46
- edgeLabelBackground: cssVar('--c-bg-2'),
47
- // 仅把自带主题的紫色换成博客主题色,其余配色仍由 mermaid 推导
48
- primaryBorderColor: cssVar('--c-primary'),
49
- primaryColor: cssVar('--c-primary-soft'),
50
- primaryTextColor: cssVar('--c-text-1'),
51
- textColor: cssVar('--c-text-1'),
52
- },
53
- })
54
- const { svg } = await mermaid.render(`${id}-${renderCount++}`, code)
55
- // 用原始画布宽度保留字号,超宽图表在容器内滚动
56
- const width = new DOMParser().parseFromString(svg, 'image/svg+xml').querySelector('svg')?.viewBox.baseVal.width
57
- return { svg, width }
58
- }
59
- catch (error) {
60
- return { error: error instanceof Error ? error.message : String(error) }
61
- }
62
- }, {})
63
- </script>
64
-
65
- <template>
66
- <div ref="mermaid" class="mermaid-diagram">
67
- <Tooltip
68
- v-if="diagram.svg"
69
- tag="div"
70
- interactive
71
- trigger="mouseenter focusin"
72
- :hide-on-click="false"
73
- :delay="500"
74
- >
75
- <template #content>
76
- <Icon v-show="false" :name="scroll ? 'tabler:arrows-horizontal' : 'tabler:arrows-minimize'" />
77
- <ZButton
78
- variant="text"
79
- :icon="scroll ? 'tabler:arrows-minimize' : 'tabler:arrows-horizontal'"
80
- :text="scroll ? '适应宽度' : '横向滚动'"
81
- @click="toggleScroll()"
82
- />
83
- </template>
84
- <div class="scrollcheck-x" tabindex="0" role="region" aria-label="Mermaid 图表">
85
- <div :style="{ minWidth: scroll && diagram.width ? `${diagram.width}px` : undefined }" v-html="diagram.svg" />
86
- </div>
87
- </Tooltip>
88
- <template v-else-if="diagram.error">
89
- <details class="mermaid-error">
90
- <summary>图表渲染失败,查看错误详情</summary>
91
- <pre>{{ diagram.error }}</pre>
92
- </details>
93
- <ProsePre :code language="mermaid" meta="wrap" />
94
- </template>
95
- </div>
96
- </template>
97
-
98
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ code: string
4
+ }>()
5
+
6
+ const colorMode = useColorMode()
7
+ const container = useTemplateRef('mermaid')
8
+ const [scroll, toggleScroll] = useToggle(true)
9
+
10
+ const id = useId()
11
+ // mermaid 会移除 DOM 中同 id 的旧图,故每次渲染另起 id,避免切换主题时高度塌陷
12
+ let renderCount = 0
13
+
14
+ // mermaid 体积较大,接近视口时才动态引入;隐藏容器(如 Tab)内不渲染,避免量取到错误的尺寸
15
+ const isVisible = useElementVisibility(container, { rootMargin: '50%' })
16
+ // 可见后即锁定,避免滚出视口时图表被清空
17
+ const shouldRender = ref(false)
18
+ whenever(isVisible, () => shouldRender.value = true, { once: true })
19
+
20
+ const diagram = computedAsync<{ svg?: string, width?: number, error?: string }>(async () => {
21
+ // 异步依赖需在 await 之前读取
22
+ const { code } = props
23
+ const darkMode = colorMode.value === 'dark'
24
+ if (!shouldRender.value)
25
+ return {}
26
+
27
+ try {
28
+ const { default: mermaid } = await import('mermaid')
29
+ // 等待 color-mode 换好根元素类名,以及字体就绪——否则取到旧配色、量出偏窄的文本
30
+ await Promise.all([nextTick(), document.fonts.ready])
31
+
32
+ const style = getComputedStyle(document.documentElement)
33
+ const cssVar = (name: string) => style.getPropertyValue(name)
34
+
35
+ mermaid.initialize({
36
+ fontFamily: 'inherit',
37
+ // 须在 load 事件前关闭,否则 mermaid 会自行扫描并接管页面元素
38
+ startOnLoad: false,
39
+ suppressErrorRendering: true,
40
+ // themeVariables 仅对 base 主题生效,其余主题会重算这些颜色
41
+ theme: 'base',
42
+ themeVariables: {
43
+ darkMode,
44
+ // 不指定则深色下连线被推导为近黑色、边标签被推导为绿色
45
+ background: cssVar('--c-bg'),
46
+ edgeLabelBackground: cssVar('--c-bg-2'),
47
+ // 仅把自带主题的紫色换成博客主题色,其余配色仍由 mermaid 推导
48
+ primaryBorderColor: cssVar('--c-primary'),
49
+ primaryColor: cssVar('--c-primary-soft'),
50
+ primaryTextColor: cssVar('--c-text-1'),
51
+ textColor: cssVar('--c-text-1'),
52
+ },
53
+ })
54
+ const { svg } = await mermaid.render(`${id}-${renderCount++}`, code)
55
+ // 用原始画布宽度保留字号,超宽图表在容器内滚动
56
+ const width = new DOMParser().parseFromString(svg, 'image/svg+xml').querySelector('svg')?.viewBox.baseVal.width
57
+ return { svg, width }
58
+ }
59
+ catch (error) {
60
+ return { error: error instanceof Error ? error.message : String(error) }
61
+ }
62
+ }, {})
63
+ </script>
64
+
65
+ <template>
66
+ <div ref="mermaid" class="mermaid-diagram">
67
+ <Tooltip
68
+ v-if="diagram.svg"
69
+ tag="div"
70
+ interactive
71
+ trigger="mouseenter focusin"
72
+ :hide-on-click="false"
73
+ :delay="500"
74
+ >
75
+ <template #content>
76
+ <Icon v-show="false" :name="scroll ? 'tabler:arrows-horizontal' : 'tabler:arrows-minimize'" />
77
+ <ZButton
78
+ variant="text"
79
+ :icon="scroll ? 'tabler:arrows-minimize' : 'tabler:arrows-horizontal'"
80
+ :text="scroll ? '适应宽度' : '横向滚动'"
81
+ @click="toggleScroll()"
82
+ />
83
+ </template>
84
+ <div class="scrollcheck-x" tabindex="0" role="region" aria-label="Mermaid 图表">
85
+ <div :style="{ minWidth: scroll && diagram.width ? `${diagram.width}px` : undefined }" v-html="diagram.svg" />
86
+ </div>
87
+ </Tooltip>
88
+ <template v-else-if="diagram.error">
89
+ <details class="mermaid-error">
90
+ <summary>图表渲染失败,查看错误详情</summary>
91
+ <pre>{{ diagram.error }}</pre>
92
+ </details>
93
+ <ProsePre :code language="mermaid" meta="wrap" />
94
+ </template>
95
+ </div>
96
+ </template>
97
+
98
+ <style lang="scss" scoped>
99
99
  // 不可命名为 .mermaid:mermaid 会按此类名自动扫描并接管元素
100
100
  .mermaid-diagram {
101
101
  margin: 0.5em 0;
@@ -130,4 +130,4 @@ const diagram = computedAsync<{ svg?: string, width?: number, error?: string }>(
130
130
  white-space: pre-wrap;
131
131
  }
132
132
  }
133
- </style>
133
+ </style>