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.
- package/LICENSE +20 -20
- package/README.md +70 -4
- package/README.zh-CN.md +67 -4
- package/nuxt.config.ts +217 -214
- package/package.json +35 -37
- package/{app → src}/app.config.ts +110 -110
- package/{app → src}/app.vue +5 -5
- package/{app → src}/components/blog/BlogAside.vue +25 -25
- package/{app → src}/components/blog/BlogFooter.vue +25 -25
- package/{app → src}/components/blog/BlogHeader.global.vue +48 -48
- package/{app → src}/components/blog/BlogPanel.vue +43 -43
- package/{app → src}/components/blog/BlogSidebar.vue +53 -53
- package/{app → src}/components/blog/BlogWidget.vue +42 -42
- package/{app → src}/components/blog/Mask.vue +25 -25
- package/{app → src}/components/blog/SkipToContent.vue +9 -9
- package/{app → src}/components/blog/ThemeToggle.vue +22 -22
- package/{app → src}/components/content/Badge.vue +45 -45
- package/{app → src}/components/content/Blur.vue +14 -14
- package/{app → src}/components/content/CardList.vue +8 -8
- package/{app → src}/components/content/Chat.vue +41 -41
- package/{app → src}/components/content/Copy.vue +71 -71
- package/{app → src}/components/content/EmojiClock.vue +49 -49
- package/{app → src}/components/content/FeedCard.vue +96 -96
- package/{app → src}/components/content/FeedGroup.vue +61 -61
- package/{app → src}/components/content/Folding.vue +19 -19
- package/{app → src}/components/content/Key.vue +143 -143
- package/{app → src}/components/content/LinkBanner.vue +27 -27
- package/{app → src}/components/content/LinkCard.vue +28 -28
- package/{app → src}/components/content/MdTitle.vue +8 -8
- package/{app → src}/components/content/Mermaid.vue +99 -99
- package/{app → src}/components/content/MusicScore.vue +63 -63
- package/{app → src}/components/content/Pic.vue +49 -49
- package/{app → src}/components/content/Poetry.vue +27 -27
- package/{app → src}/components/content/ProseA.vue +24 -24
- package/{app → src}/components/content/ProseCode.vue +16 -16
- package/{app → src}/components/content/ProsePre.vue +117 -117
- package/{app → src}/components/content/ProseTable.vue +23 -23
- package/{app → src}/components/content/Quote.vue +21 -21
- package/{app → src}/components/content/Tab.vue +32 -32
- package/{app → src}/components/content/Timeline.vue +33 -33
- package/{app → src}/components/content/Tip.vue +40 -40
- package/{app → src}/components/content/VideoEmbed.vue +112 -112
- package/{app → src}/components/partial/Button.vue +36 -36
- package/{app → src}/components/partial/DlGroup.vue +31 -31
- package/{app → src}/components/partial/Dropdown.vue +21 -21
- package/{app → src}/components/partial/Error.vue +39 -39
- package/{app → src}/components/partial/Expand.vue +26 -26
- package/{app → src}/components/partial/IconNavList.vue +24 -24
- package/{app → src}/components/partial/Pagination.vue +62 -62
- package/{app → src}/components/partial/RadioGroup.vue +30 -30
- package/{app → src}/components/partial/Secret.vue +11 -11
- package/{app → src}/components/partial/Slider.vue +48 -48
- package/{app → src}/components/partial/Toggle.vue +17 -17
- package/{app → src}/components/popover/Lightbox.vue +44 -44
- package/{app → src}/components/popover/Search.vue +143 -143
- package/{app → src}/components/popover/SearchItem.vue +28 -28
- package/{app → src}/components/post/Archive.vue +40 -40
- package/{app → src}/components/post/Article.vue +49 -49
- package/{app → src}/components/post/Comment.vue +119 -119
- package/{app → src}/components/post/Excerpt.vue +40 -40
- package/{app → src}/components/post/OrderToggle.vue +70 -70
- package/{app → src}/components/post/PostFooter.vue +52 -52
- package/{app → src}/components/post/PostHeader.vue +66 -66
- package/{app → src}/components/post/PostSurround.vue +51 -51
- package/{app → src}/components/post/Slide.vue +85 -85
- package/{app → src}/components/util/Date.vue +62 -62
- package/{app → src}/components/util/HydrateSafe.vue +10 -10
- package/{app → src}/components/util/Img.vue +38 -38
- package/{app → src}/components/util/Link.vue +11 -11
- package/{app → src}/components/widget/BlogLog.vue +23 -23
- package/{app → src}/components/widget/BlogStats.vue +38 -38
- package/{app → src}/components/widget/BlogTech.vue +57 -57
- package/{app → src}/components/widget/Empty.vue +3 -3
- package/{app → src}/components/widget/Toc.vue +67 -67
- package/{app → src}/composables/useArticle.ts +115 -115
- package/{app → src}/composables/useAvoid.ts +87 -87
- package/{app → src}/composables/useCopy.ts +21 -21
- package/{app → src}/composables/usePagination.ts +70 -70
- package/{app → src}/composables/useShiki.ts +129 -129
- package/{app → src}/composables/useToc.ts +57 -57
- package/{app → src}/composables/useWidgets.ts +56 -56
- package/{config → src/config}/app.ts +62 -62
- package/{config → src/config}/content.ts +75 -75
- package/{config → src/config}/define.ts +3 -3
- package/src/config/feeds.empty.ts +6 -0
- package/{config → src/config}/index.ts +1 -1
- package/{config → src/config}/schema.ts +89 -89
- package/{app → src}/error.vue +31 -31
- package/{img → src/img}/index.ts +2 -2
- package/{app → src}/layouts/default.vue +25 -25
- package/{modules → src/modules}/clarity-config/index.ts +168 -162
- package/src/modules/clarity-source-layout/index.ts +78 -0
- package/{app → src}/pages/[...slug].vue +59 -59
- package/{app → src}/pages/archive.vue +129 -129
- package/{app → src}/pages/index.vue +76 -76
- package/{app → src}/pages/link.vue +62 -62
- package/{app → src}/pages/preview.vue +46 -46
- package/{app → src}/plugins/easter-egg.ts +23 -23
- package/{app → src}/plugins/init.ts +6 -6
- package/{app → src}/plugins/tippy.ts +22 -22
- package/{public → src/public}/assets/atom.css +274 -274
- package/{public → src/public}/assets/atom.xsl +109 -109
- package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +58 -58
- package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +41 -41
- package/{server → src/server}/api/stats.get.ts +84 -83
- package/{server → src/server}/routes/atom.xml.get.ts +85 -85
- package/{server → src/server}/routes/subscriptions.opml.get.ts +64 -64
- package/{shared → src/shared}/utils/clarity.ts +24 -24
- package/{shared → src/shared}/utils/icon.ts +218 -218
- package/{shared → src/shared}/utils/link.ts +52 -52
- package/{shared → src/shared}/utils/str.ts +88 -88
- package/{shared → src/shared}/utils/time.ts +115 -115
- package/{app → src}/shiki.config.ts +8 -8
- package/{app → src}/stores/layout.ts +34 -34
- package/{app → src}/stores/search.ts +37 -37
- package/{app → src}/types/article.ts +16 -16
- package/src/types/feed.ts +1 -0
- package/{app → src}/types/index.ts +14 -14
- package/{app → src}/types/nav.ts +11 -11
- package/{app → src}/utils/anim.ts +37 -37
- package/{app → src}/utils/img.ts +83 -83
- package/app/types/feed.ts +0 -1
- package/config/feeds.empty.ts +0 -4
- package/{app → src}/assets/css/_variable.scss +0 -0
- package/{app → src}/assets/css/animation.scss +0 -0
- package/{app → src}/assets/css/article.scss +0 -0
- package/{app → src}/assets/css/color.scss +0 -0
- package/{app → src}/assets/css/font.scss +0 -0
- package/{app → src}/assets/css/main.scss +0 -0
- package/{app → src}/assets/css/reusable.scss +0 -0
- package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
- package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
- package/{app → src}/components/content/Alert.vue +0 -0
- package/{config → src/config}/content.d.mts +0 -0
- package/{config → src/config}/content.mjs +0 -0
- package/{config → src/config}/define.mjs +0 -0
- package/{config → src/config}/feed.ts +0 -0
- package/{config → src/config}/index.d.mts +0 -0
- package/{config → src/config}/index.mjs +0 -0
- package/{config → src/config}/public.ts +1 -1
- package/{config → src/config}/schema.d.mts +0 -0
- package/{config → src/config}/schema.mjs +0 -0
- package/{config → src/config}/server.ts +1 -1
- /package/{img → src/img}/index.d.mts +0 -0
- /package/{img → src/img}/index.mjs +0 -0
- /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
- /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
- /package/{server → src/server}/utils/clarity.ts +0 -0
- /package/{shared → src/shared}/types/content.ts +0 -0
- /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>
|