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,44 +1,44 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- title?: string
4
- card?: boolean
5
- shrink?: boolean
6
- grayscale?: boolean
7
- dim?: boolean
8
- bgImg?: string
9
- bgAside?: boolean
10
- }>()
11
-
12
- const body = useTemplateRef('widget-body')
13
-
14
- defineExpose({ body })
15
- </script>
16
-
17
- <template>
18
- <section
19
- class="blog-widget"
20
- :class="{ shrink, grayscale, dim }"
21
- >
22
- <hgroup class="widget-header text-creative">
23
- <slot name="title">
24
- {{ title }}
25
- </slot>
26
- <span v-if="$slots.action" class="seperator" />
27
- <slot name="action" />
28
- </hgroup>
29
-
30
- <div
31
- ref="widget-body"
32
- class="widget-body"
33
- :class="{ 'widget-card': card, 'with-bg': bgImg, 'scrollcheck-y scrollbar-hidden': shrink }"
34
- >
35
- <NuxtImg v-if="bgImg" class="bg-img" :class="{ 'bg-right': bgAside }" :src="bgImg" alt="" />
36
- <slot />
37
- </div>
38
- </section>
39
- </template>
40
-
41
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ title?: string
4
+ card?: boolean
5
+ shrink?: boolean
6
+ grayscale?: boolean
7
+ dim?: boolean
8
+ bgImg?: string
9
+ bgAside?: boolean
10
+ }>()
11
+
12
+ const body = useTemplateRef('widget-body')
13
+
14
+ defineExpose({ body })
15
+ </script>
16
+
17
+ <template>
18
+ <section
19
+ class="blog-widget"
20
+ :class="{ shrink, grayscale, dim }"
21
+ >
22
+ <hgroup class="widget-header text-creative">
23
+ <slot name="title">
24
+ {{ title }}
25
+ </slot>
26
+ <span v-if="$slots.action" class="seperator" />
27
+ <slot name="action" />
28
+ </hgroup>
29
+
30
+ <div
31
+ ref="widget-body"
32
+ class="widget-body"
33
+ :class="{ 'widget-card': card, 'with-bg': bgImg, 'scrollcheck-y scrollbar-hidden': shrink }"
34
+ >
35
+ <NuxtImg v-if="bgImg" class="bg-img" :class="{ 'bg-right': bgAside }" :src="bgImg" alt="" />
36
+ <slot />
37
+ </div>
38
+ </section>
39
+ </template>
40
+
41
+ <style lang="scss" scoped>
42
42
  .blog-widget {
43
43
  flex-shrink: 1;
44
44
  font-size: 0.9em;
@@ -131,4 +131,4 @@ defineExpose({ body })
131
131
  }
132
132
  }
133
133
  }
134
- </style>
134
+ </style>
@@ -1,27 +1,27 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- blur?: boolean | string
4
- zIndex?: number
5
- }>()
6
-
7
- const show = defineModel<boolean>('show')
8
- </script>
9
-
10
- <template>
11
- <Transition>
12
- <div
13
- v-if="show"
14
- class="bg-mask"
15
- :style="{
16
- '--z-index-popover': zIndex,
17
- '--bdfilter': blur ? `blur(${blur === true ? '4px' : blur})` : 'none',
18
- }"
19
- @click="show = false"
20
- />
21
- </Transition>
22
- </template>
23
-
24
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ blur?: boolean | string
4
+ zIndex?: number
5
+ }>()
6
+
7
+ const show = defineModel<boolean>('show')
8
+ </script>
9
+
10
+ <template>
11
+ <Transition>
12
+ <div
13
+ v-if="show"
14
+ class="bg-mask"
15
+ :style="{
16
+ '--z-index-popover': zIndex,
17
+ '--bdfilter': blur ? `blur(${blur === true ? '4px' : blur})` : 'none',
18
+ }"
19
+ @click="show = false"
20
+ />
21
+ </Transition>
22
+ </template>
23
+
24
+ <style lang="scss" scoped>
25
25
  .bg-mask {
26
26
  position: fixed;
27
27
  inset: 0;
@@ -39,4 +39,4 @@ const show = defineModel<boolean>('show')
39
39
  .v-leave-active {
40
40
  transition: all var(--delay);
41
41
  }
42
- </style>
42
+ </style>
@@ -1,11 +1,11 @@
1
- <template>
2
- <!-- 若使用 NuxtLink 则键盘焦点不会切换 -->
3
- <a href="#main-content" class="skip-link gradient-card active">
4
- 跳转到主要内容 / Skip to content
5
- </a>
6
- </template>
7
-
8
- <style lang="scss" scoped>
1
+ <template>
2
+ <!-- 若使用 NuxtLink 则键盘焦点不会切换 -->
3
+ <a href="#main-content" class="skip-link gradient-card active">
4
+ 跳转到主要内容 / Skip to content
5
+ </a>
6
+ </template>
7
+
8
+ <style lang="scss" scoped>
9
9
  .skip-link {
10
10
  position: fixed;
11
11
  top: -100%;
@@ -19,4 +19,4 @@
19
19
  .skip-link:focus {
20
20
  top: auto;
21
21
  }
22
- </style>
22
+ </style>
@@ -1,24 +1,24 @@
1
- <script setup lang="ts">
2
- const clarity = useClarityConfig()
3
- const colorMode = useColorMode()
4
- </script>
5
-
6
- <template>
7
- <div class="theme-toggle">
8
- <button
9
- v-for="(themeData, themeName) in clarity.themes"
10
- :key="themeName"
11
- v-tip="themeData.tip"
12
- :aria-label="themeData.tip"
13
- :class="{ active: colorMode.preference === themeName }"
14
- @click="colorMode.preference = themeName"
15
- >
16
- <Icon :name="themeData.icon" />
17
- </button>
18
- </div>
19
- </template>
20
-
21
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const clarity = useClarityConfig()
3
+ const colorMode = useColorMode()
4
+ </script>
5
+
6
+ <template>
7
+ <div class="theme-toggle">
8
+ <button
9
+ v-for="(themeData, themeName) in clarity.themes"
10
+ :key="themeName"
11
+ v-tip="themeData.tip"
12
+ :aria-label="themeData.tip"
13
+ :class="{ active: colorMode.preference === themeName }"
14
+ @click="colorMode.preference = themeName"
15
+ >
16
+ <Icon :name="themeData.icon" />
17
+ </button>
18
+ </div>
19
+ </template>
20
+
21
+ <style lang="scss" scoped>
22
22
  .theme-toggle {
23
23
  display: flex;
24
24
  gap: 3px;
@@ -47,4 +47,4 @@ const colorMode = useColorMode()
47
47
  }
48
48
  }
49
49
  }
50
- </style>
50
+ </style>
@@ -1,47 +1,47 @@
1
- <script setup lang="ts">
2
- // 即使 boolean 可选,其值也不会是 undefined
3
- const props = defineProps<{
4
- img?: string
5
- text?: string
6
- link?: string
7
- round?: boolean
8
- square?: boolean
9
- }>()
10
-
11
- const img = computed(() => {
12
- if (props.img)
13
- return props.img
14
- const ghUsername = getGithubUsername(props.link)
15
- if (ghUsername)
16
- return getGithubAvatar(ghUsername)
17
- if (props.link && isExtLink(props.link))
18
- return getFavicon(getDomain(props.link))
19
- return ''
20
- })
21
-
22
- // 有图时默认为圆形样式,除非指定为方形
23
- // 无图时默认为方形样式,除非指定为圆形
24
- const round = computed(() => img.value ? !props.square : props.round)
25
-
26
- const tip = computed(() => {
27
- if (!props.link)
28
- return ''
29
- if (isExtLink(props.link))
30
- return getDomain(props.link)
31
- return safelyDecodeUriComponent(props.link)
32
- })
33
- </script>
34
-
35
- <template>
36
- <UtilLink v-tip="tip" class="badge" :class="{ round }" :to="link">
37
- <NuxtImg v-if="img" class="badge-icon" :src="img" alt="" densities="1x" />
38
- <span class="badge-text">
39
- <slot>{{ text }}</slot>
40
- </span>
41
- </UtilLink>
42
- </template>
43
-
44
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ // 即使 boolean 可选,其值也不会是 undefined
3
+ const props = defineProps<{
4
+ img?: string
5
+ text?: string
6
+ link?: string
7
+ round?: boolean
8
+ square?: boolean
9
+ }>()
10
+
11
+ const img = computed(() => {
12
+ if (props.img)
13
+ return props.img
14
+ const ghUsername = getGithubUsername(props.link)
15
+ if (ghUsername)
16
+ return getGithubAvatar(ghUsername)
17
+ if (props.link && isExtLink(props.link))
18
+ return getFavicon(getDomain(props.link))
19
+ return ''
20
+ })
21
+
22
+ // 有图时默认为圆形样式,除非指定为方形
23
+ // 无图时默认为方形样式,除非指定为圆形
24
+ const round = computed(() => img.value ? !props.square : props.round)
25
+
26
+ const tip = computed(() => {
27
+ if (!props.link)
28
+ return ''
29
+ if (isExtLink(props.link))
30
+ return getDomain(props.link)
31
+ return safelyDecodeUriComponent(props.link)
32
+ })
33
+ </script>
34
+
35
+ <template>
36
+ <UtilLink v-tip="tip" class="badge" :class="{ round }" :to="link">
37
+ <NuxtImg v-if="img" class="badge-icon" :src="img" alt="" densities="1x" />
38
+ <span class="badge-text">
39
+ <slot>{{ text }}</slot>
40
+ </span>
41
+ </UtilLink>
42
+ </template>
43
+
44
+ <style lang="scss" scoped>
45
45
  // 对齐到基线,同时保持图片垂直居中
46
46
  .badge {
47
47
  display: inline-flex;
@@ -87,4 +87,4 @@ const tip = computed(() => {
87
87
  display: none;
88
88
  }
89
89
  }
90
- </style>
90
+ </style>
@@ -1,16 +1,16 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- text?: string
4
- }>()
5
- </script>
6
-
7
- <template>
8
- <span class="blur">
9
- <slot>{{ text }}</slot>
10
- </span>
11
- </template>
12
-
13
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ text?: string
4
+ }>()
5
+ </script>
6
+
7
+ <template>
8
+ <span class="blur">
9
+ <slot>{{ text }}</slot>
10
+ </span>
11
+ </template>
12
+
13
+ <style lang="scss" scoped>
14
14
  .blur {
15
15
  transition: filter 0.2s;
16
16
  filter: blur(4px);
@@ -19,4 +19,4 @@ defineProps<{
19
19
  filter: blur(0);
20
20
  }
21
21
  }
22
- </style>
22
+ </style>
@@ -1,10 +1,10 @@
1
- <template>
2
- <div class="card-list">
3
- <slot />
4
- </div>
5
- </template>
6
-
7
- <style lang="scss" scoped>
1
+ <template>
2
+ <div class="card-list">
3
+ <slot />
4
+ </div>
5
+ </template>
6
+
7
+ <style lang="scss" scoped>
8
8
  :deep() {
9
9
  :where(ol, ul):not([class]) {
10
10
  margin: 0;
@@ -29,4 +29,4 @@
29
29
  }
30
30
  }
31
31
  }
32
- </style>
32
+ </style>
@@ -1,43 +1,43 @@
1
- <script lang="tsx" setup>
2
- const slots = defineSlots<{
3
- default: () => VNode[]
4
- }>()
5
-
6
- const chatRegex = /^\{(?<control>\.|:)?(?<caption>.*)\}$/
7
-
8
- function getControlClass(control?: string) {
9
- if (control === '.')
10
- return 'chat-myself'
11
- if (control === ':')
12
- return 'chat-system'
13
- return ''
14
- }
15
-
16
- function render() {
17
- const slotContent = slots.default()
18
- if (!slotContent)
19
- return <span>无会话内容</span>
20
-
21
- return slotContent.map((node: VNode) => {
22
- // WARN: 此处使用了非标准的 v-slot:default
23
- const textContent = (node.children as any)?.default?.()[0].children
24
- const matchGroups = typeof textContent === 'string'
25
- ? textContent.match(chatRegex)?.groups
26
- : undefined
27
- return matchGroups
28
- ? <dt class={`chat-caption ${getControlClass(matchGroups.control)}`}>{matchGroups.caption}</dt>
29
- : <dd class="chat-body">{node}</dd>
30
- })
31
- }
32
- </script>
33
-
34
- <template>
35
- <dl class="chat">
36
- <render />
37
- </dl>
38
- </template>
39
-
40
- <style lang="scss" scoped>
1
+ <script lang="tsx" setup>
2
+ const slots = defineSlots<{
3
+ default: () => VNode[]
4
+ }>()
5
+
6
+ const chatRegex = /^\{(?<control>\.|:)?(?<caption>.*)\}$/
7
+
8
+ function getControlClass(control?: string) {
9
+ if (control === '.')
10
+ return 'chat-myself'
11
+ if (control === ':')
12
+ return 'chat-system'
13
+ return ''
14
+ }
15
+
16
+ function render() {
17
+ const slotContent = slots.default()
18
+ if (!slotContent)
19
+ return <span>无会话内容</span>
20
+
21
+ return slotContent.map((node: VNode) => {
22
+ // WARN: 此处使用了非标准的 v-slot:default
23
+ const textContent = (node.children as any)?.default?.()[0].children
24
+ const matchGroups = typeof textContent === 'string'
25
+ ? textContent.match(chatRegex)?.groups
26
+ : undefined
27
+ return matchGroups
28
+ ? <dt class={`chat-caption ${getControlClass(matchGroups.control)}`}>{matchGroups.caption}</dt>
29
+ : <dd class="chat-body">{node}</dd>
30
+ })
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <dl class="chat">
36
+ <render />
37
+ </dl>
38
+ </template>
39
+
40
+ <style lang="scss" scoped>
41
41
  .chat {
42
42
  margin-inline: 2vw;
43
43
  font-size: 0.9em;
@@ -76,4 +76,4 @@ function render() {
76
76
  }
77
77
  }
78
78
  }
79
- </style>
79
+ </style>
@@ -1,73 +1,73 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(defineProps<{
3
- prompt?: string | boolean
4
- code?: string
5
- lang?: string
6
- }>(), {
7
- prompt: '$',
8
- })
9
-
10
- // prompt 传入空字符串会变成 true
11
- const showPrompt = computed(() => props.prompt !== true)
12
- const language = computed(() => props.lang ?? getPromptLanguage(props.prompt))
13
-
14
- const showUndo = ref(false)
15
- const codeInput = useTemplateRef('code-input')
16
- const shiki = useShiki()
17
-
18
- const { copy, copied } = useCopy(codeInput)
19
-
20
- function undo() {
21
- if (!codeInput.value)
22
- return
23
- codeInput.value.textContent = props.code ?? ''
24
- // 触发 shiki 高亮
25
- codeInput.value.dispatchEvent(new Event('input'))
26
- showUndo.value = false
27
- }
28
-
29
- function preventLineBreak(event: InputEvent) {
30
- const { data, inputType } = event
31
- if (data?.includes('\n') || inputType === 'insertLineBreak') {
32
- event.preventDefault()
33
- }
34
- }
35
-
36
- function checkUndoable(event: InputEvent) {
37
- showUndo.value = props.code !== (event.target as Element).textContent
38
- }
39
-
40
- onMounted(async () => {
41
- await shiki.mountPlain(codeInput.value!, language.value)
42
- })
43
- </script>
44
-
45
- <template>
46
- <code class="copy">
47
- <span v-if="showPrompt" class="prompt">{{ prompt }}</span>
48
-
49
- <div
50
- ref="code-input"
51
- contenteditable="plaintext-only"
52
- class="code scrollcheck-x"
53
- spellcheck="false"
54
- @beforeinput="preventLineBreak"
55
- @input="checkUndoable"
56
- v-text="code"
57
- />
58
-
59
- <button v-if="showUndo" class="operation" aria-label="恢复原始内容" @click="undo">
60
- <Icon name="tabler:arrow-back-up" />
61
- </button>
62
-
63
- <Icon v-show="false" name="tabler:check" />
64
- <button class="operation" aria-label="复制" @click="copy()">
65
- <Icon :name="copied ? 'tabler:check' : 'tabler:copy'" />
66
- </button>
67
- </code>
68
- </template>
69
-
70
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ prompt?: string | boolean
4
+ code?: string
5
+ lang?: string
6
+ }>(), {
7
+ prompt: '$',
8
+ })
9
+
10
+ // prompt 传入空字符串会变成 true
11
+ const showPrompt = computed(() => props.prompt !== true)
12
+ const language = computed(() => props.lang ?? getPromptLanguage(props.prompt))
13
+
14
+ const showUndo = ref(false)
15
+ const codeInput = useTemplateRef('code-input')
16
+ const shiki = useShiki()
17
+
18
+ const { copy, copied } = useCopy(codeInput)
19
+
20
+ function undo() {
21
+ if (!codeInput.value)
22
+ return
23
+ codeInput.value.textContent = props.code ?? ''
24
+ // 触发 shiki 高亮
25
+ codeInput.value.dispatchEvent(new Event('input'))
26
+ showUndo.value = false
27
+ }
28
+
29
+ function preventLineBreak(event: InputEvent) {
30
+ const { data, inputType } = event
31
+ if (data?.includes('\n') || inputType === 'insertLineBreak') {
32
+ event.preventDefault()
33
+ }
34
+ }
35
+
36
+ function checkUndoable(event: InputEvent) {
37
+ showUndo.value = props.code !== (event.target as Element).textContent
38
+ }
39
+
40
+ onMounted(async () => {
41
+ await shiki.mountPlain(codeInput.value!, language.value)
42
+ })
43
+ </script>
44
+
45
+ <template>
46
+ <code class="copy">
47
+ <span v-if="showPrompt" class="prompt">{{ prompt }}</span>
48
+
49
+ <div
50
+ ref="code-input"
51
+ contenteditable="plaintext-only"
52
+ class="code scrollcheck-x"
53
+ spellcheck="false"
54
+ @beforeinput="preventLineBreak"
55
+ @input="checkUndoable"
56
+ v-text="code"
57
+ />
58
+
59
+ <button v-if="showUndo" class="operation" aria-label="恢复原始内容" @click="undo">
60
+ <Icon name="tabler:arrow-back-up" />
61
+ </button>
62
+
63
+ <Icon v-show="false" name="tabler:check" />
64
+ <button class="operation" aria-label="复制" @click="copy()">
65
+ <Icon :name="copied ? 'tabler:check' : 'tabler:copy'" />
66
+ </button>
67
+ </code>
68
+ </template>
69
+
70
+ <style lang="scss" scoped>
71
71
  .copy {
72
72
  contain: paint;
73
73
  display: flex;
@@ -131,4 +131,4 @@ onMounted(async () => {
131
131
  }
132
132
  }
133
133
  }
134
- </style>
134
+ </style>