clarity-theme 0.1.0 → 0.1.1

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 (119) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +18 -4
  3. package/README.zh-CN.md +18 -4
  4. package/app/app.config.ts +110 -110
  5. package/app/app.vue +5 -5
  6. package/app/components/blog/BlogAside.vue +25 -25
  7. package/app/components/blog/BlogFooter.vue +25 -25
  8. package/app/components/blog/BlogHeader.global.vue +48 -48
  9. package/app/components/blog/BlogPanel.vue +43 -43
  10. package/app/components/blog/BlogSidebar.vue +53 -53
  11. package/app/components/blog/BlogWidget.vue +42 -42
  12. package/app/components/blog/Mask.vue +25 -25
  13. package/app/components/blog/SkipToContent.vue +9 -9
  14. package/app/components/blog/ThemeToggle.vue +22 -22
  15. package/app/components/content/Badge.vue +45 -45
  16. package/app/components/content/Blur.vue +14 -14
  17. package/app/components/content/CardList.vue +8 -8
  18. package/app/components/content/Chat.vue +41 -41
  19. package/app/components/content/Copy.vue +71 -71
  20. package/app/components/content/EmojiClock.vue +49 -49
  21. package/app/components/content/FeedCard.vue +96 -96
  22. package/app/components/content/FeedGroup.vue +61 -61
  23. package/app/components/content/Folding.vue +19 -19
  24. package/app/components/content/Key.vue +143 -143
  25. package/app/components/content/LinkBanner.vue +27 -27
  26. package/app/components/content/LinkCard.vue +28 -28
  27. package/app/components/content/MdTitle.vue +8 -8
  28. package/app/components/content/Mermaid.vue +99 -99
  29. package/app/components/content/MusicScore.vue +63 -63
  30. package/app/components/content/Pic.vue +49 -49
  31. package/app/components/content/Poetry.vue +27 -27
  32. package/app/components/content/ProseA.vue +24 -24
  33. package/app/components/content/ProseCode.vue +16 -16
  34. package/app/components/content/ProsePre.vue +117 -117
  35. package/app/components/content/ProseTable.vue +23 -23
  36. package/app/components/content/Quote.vue +21 -21
  37. package/app/components/content/Tab.vue +32 -32
  38. package/app/components/content/Timeline.vue +33 -33
  39. package/app/components/content/Tip.vue +40 -40
  40. package/app/components/content/VideoEmbed.vue +112 -112
  41. package/app/components/partial/Button.vue +36 -36
  42. package/app/components/partial/DlGroup.vue +31 -31
  43. package/app/components/partial/Dropdown.vue +21 -21
  44. package/app/components/partial/Error.vue +39 -39
  45. package/app/components/partial/Expand.vue +26 -26
  46. package/app/components/partial/IconNavList.vue +24 -24
  47. package/app/components/partial/Pagination.vue +62 -62
  48. package/app/components/partial/RadioGroup.vue +30 -30
  49. package/app/components/partial/Secret.vue +11 -11
  50. package/app/components/partial/Slider.vue +48 -48
  51. package/app/components/partial/Toggle.vue +17 -17
  52. package/app/components/popover/Lightbox.vue +44 -44
  53. package/app/components/popover/Search.vue +143 -143
  54. package/app/components/popover/SearchItem.vue +28 -28
  55. package/app/components/post/Archive.vue +40 -40
  56. package/app/components/post/Article.vue +49 -49
  57. package/app/components/post/Comment.vue +119 -119
  58. package/app/components/post/Excerpt.vue +40 -40
  59. package/app/components/post/OrderToggle.vue +70 -70
  60. package/app/components/post/PostFooter.vue +52 -52
  61. package/app/components/post/PostHeader.vue +66 -66
  62. package/app/components/post/PostSurround.vue +51 -51
  63. package/app/components/post/Slide.vue +85 -85
  64. package/app/components/util/Date.vue +62 -62
  65. package/app/components/util/HydrateSafe.vue +10 -10
  66. package/app/components/util/Img.vue +38 -38
  67. package/app/components/util/Link.vue +11 -11
  68. package/app/components/widget/BlogLog.vue +23 -23
  69. package/app/components/widget/BlogStats.vue +38 -38
  70. package/app/components/widget/BlogTech.vue +57 -57
  71. package/app/components/widget/Empty.vue +3 -3
  72. package/app/components/widget/Toc.vue +67 -67
  73. package/app/composables/useArticle.ts +113 -113
  74. package/app/composables/useAvoid.ts +87 -87
  75. package/app/composables/useCopy.ts +21 -21
  76. package/app/composables/usePagination.ts +70 -70
  77. package/app/composables/useShiki.ts +129 -129
  78. package/app/composables/useToc.ts +57 -57
  79. package/app/composables/useWidgets.ts +56 -56
  80. package/app/error.vue +31 -31
  81. package/app/layouts/default.vue +25 -25
  82. package/app/pages/[...slug].vue +59 -59
  83. package/app/pages/archive.vue +129 -129
  84. package/app/pages/index.vue +76 -76
  85. package/app/pages/link.vue +62 -62
  86. package/app/pages/preview.vue +46 -46
  87. package/app/plugins/easter-egg.ts +23 -23
  88. package/app/plugins/init.ts +6 -6
  89. package/app/plugins/tippy.ts +22 -22
  90. package/app/shiki.config.ts +8 -8
  91. package/app/stores/layout.ts +34 -34
  92. package/app/stores/search.ts +37 -37
  93. package/app/types/article.ts +16 -16
  94. package/app/types/index.ts +14 -14
  95. package/app/types/nav.ts +11 -11
  96. package/app/utils/anim.ts +37 -37
  97. package/app/utils/img.ts +83 -83
  98. package/config/app.ts +61 -61
  99. package/config/content.ts +75 -75
  100. package/config/define.ts +3 -3
  101. package/config/feeds.empty.ts +6 -4
  102. package/config/public.ts +1 -1
  103. package/config/schema.ts +89 -89
  104. package/config/server.ts +1 -1
  105. package/modules/clarity-config/index.ts +162 -159
  106. package/nuxt.config.ts +212 -212
  107. package/package.json +25 -21
  108. package/public/assets/atom.css +274 -274
  109. package/public/assets/atom.xsl +109 -109
  110. package/remark-plugins/rehype-meta-slots.ts +58 -58
  111. package/remark-plugins/remark-code-component.ts +41 -41
  112. package/server/api/stats.get.ts +84 -83
  113. package/server/routes/atom.xml.get.ts +85 -85
  114. package/server/routes/subscriptions.opml.get.ts +64 -64
  115. package/shared/utils/clarity.ts +24 -24
  116. package/shared/utils/icon.ts +218 -218
  117. package/shared/utils/link.ts +52 -52
  118. package/shared/utils/str.ts +88 -88
  119. package/shared/utils/time.ts +115 -115
@@ -1,119 +1,119 @@
1
- <script setup lang="ts">
2
- import { escape } from 'es-toolkit/string'
3
-
4
- const props = withDefaults(defineProps<{
5
- code?: string
6
- language?: string
7
- filename?: string
8
- /** @deprecated Use `transformerNotationHighlight` instead */
9
- highlights?: number[]
10
- meta?: string
11
- class?: string
12
- }>(), {
13
- code: '',
14
- meta: '',
15
- language: 'text', // Nuxt Content 已经做了此处理
16
- })
17
-
18
- interface CodeblockMeta {
19
- icon?: string
20
- wrap?: boolean
21
- [meta: string]: string | boolean | undefined
22
- }
23
-
24
- const meta = computed(() => props.meta.split(' ').reduce((acc: CodeblockMeta, item) => {
25
- const [key, value] = item.split('=')
26
- acc[key!] = value ?? true
27
- return acc
28
- }, {}))
29
-
30
- const clarity = useClarityConfig()
31
- const compConf = computed(() => clarity.component.codeblock)
32
-
33
- const rows = computed(() => props.code.split('\n').length - 1)
34
- const collapsible = computed(() => !meta.value.expand && rows.value > compConf.value.triggerRows)
35
- const [isCollapsed, toggleCollapsed] = useToggle(collapsible.value)
36
-
37
- const icon = computed(() => meta.value.icon || getFileIcon(props.filename) || getLangIcon(props.language))
38
- const isWrap = ref(meta.value.wrap)
39
- const byteSize = computed(() => formatBytes(new TextEncoder().encode(props.code).length))
40
-
41
- const codeblock = useTemplateRef('codeblock')
42
- const { copy, copied } = useCopy(codeblock)
43
- const shiki = useShiki()
44
- const rawHtml = ref(escape(props.code))
45
-
46
- function getIndent() {
47
- if (meta.value.indent)
48
- return meta.value.indent
49
-
50
- if (['md', 'mdc', 'json', 'jsonc', 'yaml', 'yml'].includes(props.language))
51
- return 2
52
-
53
- return compConf.value.indent
54
- }
55
-
56
- onMounted(async () => {
57
- rawHtml.value = await shiki.codeToHtml(props.code.trimEnd(), {
58
- language: props.language,
59
- transformerOptions: [compConf.value.enableIndentGuide ? 'ignoreRenderWhitespace' : 'ignoreRenderIndentGuides'],
60
- shikiOptions: { meta: { indent: getIndent() } },
61
- embeddedLanguages: true,
62
- })
63
- })
64
- </script>
65
-
66
- <template>
67
- <figure
68
- class="z-codeblock"
69
- :class="{ collapsed: collapsible && isCollapsed, collapsible }"
70
- :style="{
71
- '--collapsed-rows': compConf.collapsedRows,
72
- '--tab-size': meta.indent as string || compConf.tabSize,
73
- }"
74
- >
75
- <figcaption>
76
- <span v-if="filename" class="filename">
77
- <Icon :name="icon" /> {{ filename }}
78
- </span>
79
- <span v-else />
80
- <!-- 语言不采用绝对定位,因为和文件名占据互斥空间 -->
81
- <span v-if="language" class="language">{{ language }}</span>
82
- <div class="operations">
83
- <button @click="isWrap = !isWrap">
84
- {{ isWrap ? '横向滚动' : '自动换行' }}
85
- </button>
86
- <button @click="copy()">
87
- {{ copied ? '已复制' : '复制' }}
88
- </button>
89
- </div>
90
- </figcaption>
91
-
92
- <!-- 嘿嘿,不要换行 -->
93
- <pre
94
- ref="codeblock"
95
- class="shiki scrollcheck-x"
96
- :class="[props.class, { wrap: isWrap }]"
97
- v-html="rawHtml"
98
- />
99
-
100
- <button
101
- v-if="collapsible"
102
- class="toggle-btn"
103
- :aria-label="isCollapsed ? '展开代码块' : '折叠代码块'"
104
- @click="toggleCollapsed()"
105
- >
106
- <Icon
107
- class="toggle-icon"
108
- :class="{ 'is-collapsed': isCollapsed }"
109
- name="tabler:chevrons-up"
110
- />
111
- <span>{{ rows }} lines, {{ props.code.length }} chars, {{ byteSize }}</span>
112
- </button>
113
- </figure>
114
- </template>
115
-
116
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import { escape } from 'es-toolkit/string'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ code?: string
6
+ language?: string
7
+ filename?: string
8
+ /** @deprecated Use `transformerNotationHighlight` instead */
9
+ highlights?: number[]
10
+ meta?: string
11
+ class?: string
12
+ }>(), {
13
+ code: '',
14
+ meta: '',
15
+ language: 'text', // Nuxt Content 已经做了此处理
16
+ })
17
+
18
+ interface CodeblockMeta {
19
+ icon?: string
20
+ wrap?: boolean
21
+ [meta: string]: string | boolean | undefined
22
+ }
23
+
24
+ const meta = computed(() => props.meta.split(' ').reduce((acc: CodeblockMeta, item) => {
25
+ const [key, value] = item.split('=')
26
+ acc[key!] = value ?? true
27
+ return acc
28
+ }, {}))
29
+
30
+ const clarity = useClarityConfig()
31
+ const compConf = computed(() => clarity.component.codeblock)
32
+
33
+ const rows = computed(() => props.code.split('\n').length - 1)
34
+ const collapsible = computed(() => !meta.value.expand && rows.value > compConf.value.triggerRows)
35
+ const [isCollapsed, toggleCollapsed] = useToggle(collapsible.value)
36
+
37
+ const icon = computed(() => meta.value.icon || getFileIcon(props.filename) || getLangIcon(props.language))
38
+ const isWrap = ref(meta.value.wrap)
39
+ const byteSize = computed(() => formatBytes(new TextEncoder().encode(props.code).length))
40
+
41
+ const codeblock = useTemplateRef('codeblock')
42
+ const { copy, copied } = useCopy(codeblock)
43
+ const shiki = useShiki()
44
+ const rawHtml = ref(escape(props.code))
45
+
46
+ function getIndent() {
47
+ if (meta.value.indent)
48
+ return meta.value.indent
49
+
50
+ if (['md', 'mdc', 'json', 'jsonc', 'yaml', 'yml'].includes(props.language))
51
+ return 2
52
+
53
+ return compConf.value.indent
54
+ }
55
+
56
+ onMounted(async () => {
57
+ rawHtml.value = await shiki.codeToHtml(props.code.trimEnd(), {
58
+ language: props.language,
59
+ transformerOptions: [compConf.value.enableIndentGuide ? 'ignoreRenderWhitespace' : 'ignoreRenderIndentGuides'],
60
+ shikiOptions: { meta: { indent: getIndent() } },
61
+ embeddedLanguages: true,
62
+ })
63
+ })
64
+ </script>
65
+
66
+ <template>
67
+ <figure
68
+ class="z-codeblock"
69
+ :class="{ collapsed: collapsible && isCollapsed, collapsible }"
70
+ :style="{
71
+ '--collapsed-rows': compConf.collapsedRows,
72
+ '--tab-size': meta.indent as string || compConf.tabSize,
73
+ }"
74
+ >
75
+ <figcaption>
76
+ <span v-if="filename" class="filename">
77
+ <Icon :name="icon" /> {{ filename }}
78
+ </span>
79
+ <span v-else />
80
+ <!-- 语言不采用绝对定位,因为和文件名占据互斥空间 -->
81
+ <span v-if="language" class="language">{{ language }}</span>
82
+ <div class="operations">
83
+ <button @click="isWrap = !isWrap">
84
+ {{ isWrap ? '横向滚动' : '自动换行' }}
85
+ </button>
86
+ <button @click="copy()">
87
+ {{ copied ? '已复制' : '复制' }}
88
+ </button>
89
+ </div>
90
+ </figcaption>
91
+
92
+ <!-- 嘿嘿,不要换行 -->
93
+ <pre
94
+ ref="codeblock"
95
+ class="shiki scrollcheck-x"
96
+ :class="[props.class, { wrap: isWrap }]"
97
+ v-html="rawHtml"
98
+ />
99
+
100
+ <button
101
+ v-if="collapsible"
102
+ class="toggle-btn"
103
+ :aria-label="isCollapsed ? '展开代码块' : '折叠代码块'"
104
+ @click="toggleCollapsed()"
105
+ >
106
+ <Icon
107
+ class="toggle-icon"
108
+ :class="{ 'is-collapsed': isCollapsed }"
109
+ name="tabler:chevrons-up"
110
+ />
111
+ <span>{{ rows }} lines, {{ props.code.length }} chars, {{ byteSize }}</span>
112
+ </button>
113
+ </figure>
114
+ </template>
115
+
116
+ <style lang="scss" scoped>
117
117
  .z-codeblock {
118
118
  --line-height: 1.4;
119
119
 
@@ -288,4 +288,4 @@ pre {
288
288
  transform: rotate(180deg);
289
289
  }
290
290
  }
291
- </style>
291
+ </style>
@@ -1,25 +1,25 @@
1
- <script setup lang="ts">
2
- const [scroll, toggleScroll] = useToggle(true)
3
- </script>
4
-
5
- <template>
6
- <Tooltip class="md-table" tag="figure" interactive :delay="500">
7
- <template #content>
8
- <Icon v-show="false" :name="scroll ? 'tabler:text-wrap-disabled' : 'tabler:text-wrap'" />
9
- <ZButton
10
- variant="text"
11
- :icon="scroll ? 'tabler:text-wrap' : 'tabler:text-wrap-disabled'"
12
- :text="scroll ? '自动换行' : '横向滚动'"
13
- @click="toggleScroll()"
14
- />
15
- </template>
16
- <table class="scrollcheck-x" :class="{ scroll }">
17
- <slot />
18
- </table>
19
- </Tooltip>
20
- </template>
21
-
22
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const [scroll, toggleScroll] = useToggle(true)
3
+ </script>
4
+
5
+ <template>
6
+ <Tooltip class="md-table" tag="figure" interactive :delay="500">
7
+ <template #content>
8
+ <Icon v-show="false" :name="scroll ? 'tabler:text-wrap-disabled' : 'tabler:text-wrap'" />
9
+ <ZButton
10
+ variant="text"
11
+ :icon="scroll ? 'tabler:text-wrap' : 'tabler:text-wrap-disabled'"
12
+ :text="scroll ? '自动换行' : '横向滚动'"
13
+ @click="toggleScroll()"
14
+ />
15
+ </template>
16
+ <table class="scrollcheck-x" :class="{ scroll }">
17
+ <slot />
18
+ </table>
19
+ </Tooltip>
20
+ </template>
21
+
22
+ <style lang="scss" scoped>
23
23
  .md-table {
24
24
  position: relative;
25
25
  margin: 1rem 0;
@@ -67,4 +67,4 @@ const [scroll, toggleScroll] = useToggle(true)
67
67
  border: 1px solid var(--c-border);
68
68
  }
69
69
  }
70
- </style>
70
+ </style>
@@ -1,23 +1,23 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- icon?: string
4
- }>()
5
-
6
- const icon = computed(() => props.icon || 'tabler:message-2')
7
- </script>
8
-
9
- <template>
10
- <div class="quote title-like">
11
- <div class="icon-line">
12
- <slot name="icon">
13
- <Icon :name="icon" />
14
- </slot>
15
- </div>
16
- <slot />
17
- </div>
18
- </template>
19
-
20
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ icon?: string
4
+ }>()
5
+
6
+ const icon = computed(() => props.icon || 'tabler:message-2')
7
+ </script>
8
+
9
+ <template>
10
+ <div class="quote title-like">
11
+ <div class="icon-line">
12
+ <slot name="icon">
13
+ <Icon :name="icon" />
14
+ </slot>
15
+ </div>
16
+ <slot />
17
+ </div>
18
+ </template>
19
+
20
+ <style lang="scss" scoped>
21
21
  .quote {
22
22
  font-size: 1.2rem;
23
23
  line-height: 1.5;
@@ -48,4 +48,4 @@ const icon = computed(() => props.icon || 'tabler:message-2')
48
48
  transform: translateY(-0.5rem);
49
49
  }
50
50
  }
51
- </style>
51
+ </style>
@@ -1,34 +1,34 @@
1
- <script setup lang="ts">
2
- const props = defineProps<{
3
- /** tab 下标从 1 开始 */
4
- tabs: string[]
5
- center?: boolean
6
- active?: string | number
7
- }>()
8
-
9
- const activeTab = ref(Number(props.active) || 1)
10
- </script>
11
-
12
- <template>
13
- <!-- BUG: MDC Tab插槽块内会吞代码缩进 -->
14
- <div :class="{ center }">
15
- <div class="tabs">
16
- <button
17
- v-for="(tab, tabIndex) in tabs"
18
- :key="tabIndex"
19
- :class="{ active: activeTab === tabIndex + 1 }"
20
- @click="activeTab = tabIndex + 1"
21
- >
22
- {{ tab }}
23
- </button>
24
- </div>
25
- <div v-for="tabIndex in tabs.length" v-show="activeTab === tabIndex" :key="tabIndex" class="tab-content">
26
- <slot :name="`tab${tabIndex}`" />
27
- </div>
28
- </div>
29
- </template>
30
-
31
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ /** tab 下标从 1 开始 */
4
+ tabs: string[]
5
+ center?: boolean
6
+ active?: string | number
7
+ }>()
8
+
9
+ const activeTab = ref(Number(props.active) || 1)
10
+ </script>
11
+
12
+ <template>
13
+ <!-- BUG: MDC Tab插槽块内会吞代码缩进 -->
14
+ <div :class="{ center }">
15
+ <div class="tabs">
16
+ <button
17
+ v-for="(tab, tabIndex) in tabs"
18
+ :key="tabIndex"
19
+ :class="{ active: activeTab === tabIndex + 1 }"
20
+ @click="activeTab = tabIndex + 1"
21
+ >
22
+ {{ tab }}
23
+ </button>
24
+ </div>
25
+ <div v-for="tabIndex in tabs.length" v-show="activeTab === tabIndex" :key="tabIndex" class="tab-content">
26
+ <slot :name="`tab${tabIndex}`" />
27
+ </div>
28
+ </div>
29
+ </template>
30
+
31
+ <style lang="scss" scoped>
32
32
  .float-in-leave-active {
33
33
  /* stylelint-disable-next-line declaration-no-important */
34
34
  position: revert !important;
@@ -97,4 +97,4 @@ button {
97
97
  .tab-content {
98
98
  margin: 1em 0;
99
99
  }
100
- </style>
100
+ </style>
@@ -1,35 +1,35 @@
1
- <script lang="tsx" setup>
2
- const slots = defineSlots<{
3
- default: () => VNode[]
4
- }>()
5
-
6
- const timelineRegex = /^\{(?<caption>.*)\}$/
7
-
8
- function render() {
9
- const slotContent = slots.default()
10
- if (!slotContent)
11
- return <span>时间线为空</span>
12
-
13
- return slotContent.map((node: VNode) => {
14
- // WARN: 此处使用了非标准的 v-slot:default
15
- const textContent = (node.children as any)?.default?.()[0].children
16
- const matchGroups = typeof textContent === 'string'
17
- ? textContent.match(timelineRegex)?.groups
18
- : undefined
19
- return matchGroups
20
- ? <dt class="timeline-caption">{matchGroups.caption}</dt>
21
- : <dd class="timeline-body card">{node}</dd>
22
- })
23
- }
24
- </script>
25
-
26
- <template>
27
- <dl class="timeline">
28
- <render />
29
- </dl>
30
- </template>
31
-
32
- <style lang="scss" scoped>
1
+ <script lang="tsx" setup>
2
+ const slots = defineSlots<{
3
+ default: () => VNode[]
4
+ }>()
5
+
6
+ const timelineRegex = /^\{(?<caption>.*)\}$/
7
+
8
+ function render() {
9
+ const slotContent = slots.default()
10
+ if (!slotContent)
11
+ return <span>时间线为空</span>
12
+
13
+ return slotContent.map((node: VNode) => {
14
+ // WARN: 此处使用了非标准的 v-slot:default
15
+ const textContent = (node.children as any)?.default?.()[0].children
16
+ const matchGroups = typeof textContent === 'string'
17
+ ? textContent.match(timelineRegex)?.groups
18
+ : undefined
19
+ return matchGroups
20
+ ? <dt class="timeline-caption">{matchGroups.caption}</dt>
21
+ : <dd class="timeline-body card">{node}</dd>
22
+ })
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+ <dl class="timeline">
28
+ <render />
29
+ </dl>
30
+ </template>
31
+
32
+ <style lang="scss" scoped>
33
33
  // TODO: 优化时间线样式
34
34
  .timeline {
35
35
  position: relative;
@@ -70,4 +70,4 @@ function render() {
70
70
  padding: 0 1em;
71
71
  }
72
72
  }
73
- </style>
73
+ </style>
@@ -1,42 +1,42 @@
1
- <script setup lang="ts">
2
- import type { TippyOptions } from 'vue-tippy'
3
-
4
- const props = withDefaults(defineProps<{
5
- text?: string
6
- tip?: string
7
- icon?: string | boolean
8
- copy?: boolean
9
- tipOptions?: TippyOptions
10
- }>(), {
11
- icon: undefined,
12
- })
13
-
14
- const tip = computed(() => ({
15
- content: props.tip || (props.copy ? '点击复制' : ''),
16
- inlinePositioning: true,
17
- ...props.tipOptions,
18
- }))
19
- const tipSource = useTemplateRef('tip-text')
20
-
21
- const { copy, copied } = useCopy(tipSource)
22
- const icon = computed(() => props.icon ?? (copied.value ? 'tabler:check' : props.copy && 'tabler:copy'))
23
- </script>
24
-
25
- <template>
26
- <span
27
- ref="tip-text"
28
- v-tip="tip"
29
- class="tip"
30
- tabindex="0"
31
- @keypress.enter="props.copy && copy()"
32
- @click="props.copy && copy()"
33
- >
34
- <slot>{{ text }}</slot>
35
- <Icon v-if="typeof icon === 'string'" :name="icon" class="tip-icon" />
36
- </span>
37
- </template>
38
-
39
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { TippyOptions } from 'vue-tippy'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ text?: string
6
+ tip?: string
7
+ icon?: string | boolean
8
+ copy?: boolean
9
+ tipOptions?: TippyOptions
10
+ }>(), {
11
+ icon: undefined,
12
+ })
13
+
14
+ const tip = computed(() => ({
15
+ content: props.tip || (props.copy ? '点击复制' : ''),
16
+ inlinePositioning: true,
17
+ ...props.tipOptions,
18
+ }))
19
+ const tipSource = useTemplateRef('tip-text')
20
+
21
+ const { copy, copied } = useCopy(tipSource)
22
+ const icon = computed(() => props.icon ?? (copied.value ? 'tabler:check' : props.copy && 'tabler:copy'))
23
+ </script>
24
+
25
+ <template>
26
+ <span
27
+ ref="tip-text"
28
+ v-tip="tip"
29
+ class="tip"
30
+ tabindex="0"
31
+ @keypress.enter="props.copy && copy()"
32
+ @click="props.copy && copy()"
33
+ >
34
+ <slot>{{ text }}</slot>
35
+ <Icon v-if="typeof icon === 'string'" :name="icon" class="tip-icon" />
36
+ </span>
37
+ </template>
38
+
39
+ <style lang="scss" scoped>
40
40
  .tip {
41
41
  position: relative;
42
42
  text-decoration: underline dashed var(--c-text-3);
@@ -49,4 +49,4 @@ const icon = computed(() => props.icon ?? (copied.value ? 'tabler:check' : props
49
49
  font-size: 1em;
50
50
  vertical-align: top;
51
51
  }
52
- </style>
52
+ </style>