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,65 +1,65 @@
1
- <script setup lang="ts">
2
- import type { AbcVisualParams, SynthObjectController, SynthVisualOptions, TuneObject } from 'abcjs'
3
-
4
- const props = defineProps<{
5
- abc: string
6
- }>()
7
-
8
- const container = useTemplateRef('abcjs-container')
9
- const synthController = useTemplateRef('synth-controller')
10
-
11
- const tuneObj = ref<TuneObject>()
12
- const synthObjController = ref<SynthObjectController>()
13
-
14
- const abcVisualParams: AbcVisualParams = {
15
- responsive: 'resize',
16
- }
17
-
18
- const synthVisualOptions: SynthVisualOptions = {
19
- displayLoop: true,
20
- displayRestart: true,
21
- displayPlay: true,
22
- displayProgress: true,
23
- displayWarp: true,
24
- }
25
-
26
- async function checkSoundFonts() {
27
- try {
28
- const res = await fetch('https://paulrosen.github.io/midi-js-soundfonts/', { method: 'HEAD' })
29
- return res.ok
30
- }
31
- catch (e) {
32
- console.error('[music-abc] 无法连接 SoundFonts,不启用播放能力\n', e)
33
- return false
34
- }
35
- }
36
-
37
- onMounted(async () => {
38
- const { renderAbc, synth } = await import('abcjs')
39
-
40
- tuneObj.value = renderAbc(container.value!, props.abc, abcVisualParams)[0]
41
-
42
- if (!synth.supportsAudio() || !(await checkSoundFonts()))
43
- return
44
-
45
- synthObjController.value = new synth.SynthController()
46
- synthObjController.value.load(synthController.value!, null, synthVisualOptions)
47
- synthObjController.value.setTune(tuneObj.value, false)
48
- })
49
-
50
- onUnmounted(() => {
51
- synthObjController.value?.pause()
52
- })
53
- </script>
54
-
55
- <template>
56
- <div class="music-score">
57
- <div ref="abcjs-container" />
58
- <div ref="synth-controller" />
59
- </div>
60
- </template>
61
-
62
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { AbcVisualParams, SynthObjectController, SynthVisualOptions, TuneObject } from 'abcjs'
3
+
4
+ const props = defineProps<{
5
+ abc: string
6
+ }>()
7
+
8
+ const container = useTemplateRef('abcjs-container')
9
+ const synthController = useTemplateRef('synth-controller')
10
+
11
+ const tuneObj = ref<TuneObject>()
12
+ const synthObjController = ref<SynthObjectController>()
13
+
14
+ const abcVisualParams: AbcVisualParams = {
15
+ responsive: 'resize',
16
+ }
17
+
18
+ const synthVisualOptions: SynthVisualOptions = {
19
+ displayLoop: true,
20
+ displayRestart: true,
21
+ displayPlay: true,
22
+ displayProgress: true,
23
+ displayWarp: true,
24
+ }
25
+
26
+ async function checkSoundFonts() {
27
+ try {
28
+ const res = await fetch('https://paulrosen.github.io/midi-js-soundfonts/', { method: 'HEAD' })
29
+ return res.ok
30
+ }
31
+ catch (e) {
32
+ console.error('[music-abc] 无法连接 SoundFonts,不启用播放能力\n', e)
33
+ return false
34
+ }
35
+ }
36
+
37
+ onMounted(async () => {
38
+ const { renderAbc, synth } = await import('abcjs')
39
+
40
+ tuneObj.value = renderAbc(container.value!, props.abc, abcVisualParams)[0]
41
+
42
+ if (!synth.supportsAudio() || !(await checkSoundFonts()))
43
+ return
44
+
45
+ synthObjController.value = new synth.SynthController()
46
+ synthObjController.value.load(synthController.value!, null, synthVisualOptions)
47
+ synthObjController.value.setTune(tuneObj.value, false)
48
+ })
49
+
50
+ onUnmounted(() => {
51
+ synthObjController.value?.pause()
52
+ })
53
+ </script>
54
+
55
+ <template>
56
+ <div class="music-score">
57
+ <div ref="abcjs-container" />
58
+ <div ref="synth-controller" />
59
+ </div>
60
+ </template>
61
+
62
+ <style lang="scss" scoped>
63
63
  .music-score {
64
64
  line-height: 1.4;
65
65
 
@@ -175,4 +175,4 @@ onUnmounted(() => {
175
175
  }
176
176
  }
177
177
  }
178
- </style>
178
+ </style>
@@ -1,55 +1,55 @@
1
- <script setup lang="ts">
2
- import type { UtilImgProps } from '../util/Img.vue'
3
- import { LazyPopoverLightbox } from '#components'
4
-
5
- const props = withDefaults(defineProps<UtilImgProps & {
6
- caption?: string
7
- zoom?: boolean
8
- }>(), {
9
- caption: '',
10
- zoom: true,
11
- })
12
-
13
- const slots = defineSlots<{
14
- caption: () => any
15
- }>()
16
-
17
- const pic = useTemplateRef<HTMLImageElement>('pic')
18
- const modalStore = useModalStore()
19
-
20
- const { open } = modalStore.use(
21
- () => h(LazyPopoverLightbox, {
22
- el: unrefElement(pic)!,
23
- caption: props.alt || props.caption || slots.caption,
24
- }),
25
- { unique: true },
26
- )
27
- </script>
28
-
29
- <template>
30
- <!-- <ProseImg> 被 <p> 包裹,服务端渲染时若内含块级元素会自动关闭,导致水合不匹配 -->
31
- <figure class="image">
32
- <UtilImg
33
- ref="pic"
34
- class="image"
35
- :style="{ cursor: zoom && 'zoom-in' }"
36
- :alt="caption || alt"
37
- :src :width :height :mirror :filter :densities
38
- @click="zoom && open()"
39
- />
40
- <figcaption v-if="caption || $slots.caption" aria-hidden>
41
- <slot name="caption">
42
- {{ caption }}
43
- </slot>
44
- </figcaption>
45
- </figure>
46
- </template>
47
-
48
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ import type { UtilImgProps } from '../util/Img.vue'
3
+ import { LazyPopoverLightbox } from '#components'
4
+
5
+ const props = withDefaults(defineProps<UtilImgProps & {
6
+ caption?: string
7
+ zoom?: boolean
8
+ }>(), {
9
+ caption: '',
10
+ zoom: true,
11
+ })
12
+
13
+ const slots = defineSlots<{
14
+ caption: () => any
15
+ }>()
16
+
17
+ const pic = useTemplateRef<HTMLImageElement>('pic')
18
+ const modalStore = useModalStore()
19
+
20
+ const { open } = modalStore.use(
21
+ () => h(LazyPopoverLightbox, {
22
+ el: unrefElement(pic)!,
23
+ caption: props.alt || props.caption || slots.caption,
24
+ }),
25
+ { unique: true },
26
+ )
27
+ </script>
28
+
29
+ <template>
30
+ <!-- <ProseImg> 被 <p> 包裹,服务端渲染时若内含块级元素会自动关闭,导致水合不匹配 -->
31
+ <figure class="image">
32
+ <UtilImg
33
+ ref="pic"
34
+ class="image"
35
+ :style="{ cursor: zoom && 'zoom-in' }"
36
+ :alt="caption || alt"
37
+ :src :width :height :mirror :filter :densities
38
+ @click="zoom && open()"
39
+ />
40
+ <figcaption v-if="caption || $slots.caption" aria-hidden>
41
+ <slot name="caption">
42
+ {{ caption }}
43
+ </slot>
44
+ </figcaption>
45
+ </figure>
46
+ </template>
47
+
48
+ <style lang="scss" scoped>
49
49
  figcaption {
50
50
  margin-top: -0.5em;
51
51
  font-size: 0.8em;
52
52
  text-align: center;
53
53
  color: var(--c-text-2);
54
54
  }
55
- </style>
55
+ </style>
@@ -1,29 +1,29 @@
1
- <script setup lang="ts">
2
- defineProps<{
3
- title?: string
4
- author?: string
5
- footer?: string
6
- }>()
7
- </script>
8
-
9
- <template>
10
- <section class="poetry">
11
- <h2 class="poetry-title text-center">
12
- {{ title }}
13
- </h2>
14
- <div class="text-center">
15
- {{ author }}
16
- </div>
17
- <div class="poetry-content">
18
- <slot />
19
- </div>
20
- <div class="text-center">
21
- {{ footer }}
22
- </div>
23
- </section>
24
- </template>
25
-
26
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ title?: string
4
+ author?: string
5
+ footer?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <section class="poetry">
11
+ <h2 class="poetry-title text-center">
12
+ {{ title }}
13
+ </h2>
14
+ <div class="text-center">
15
+ {{ author }}
16
+ </div>
17
+ <div class="poetry-content">
18
+ <slot />
19
+ </div>
20
+ <div class="text-center">
21
+ {{ footer }}
22
+ </div>
23
+ </section>
24
+ </template>
25
+
26
+ <style lang="scss" scoped>
27
27
  .poetry {
28
28
  width: fit-content;
29
29
  margin: 2em auto;
@@ -38,4 +38,4 @@ defineProps<{
38
38
  margin: 0.5em auto;
39
39
  }
40
40
  }
41
- </style>
41
+ </style>
@@ -1,26 +1,26 @@
1
- <script setup lang="ts">
2
- const props = withDefaults(defineProps<{
3
- href: string
4
- icon?: string | false
5
- }>(), {
6
- icon: undefined,
7
- })
8
-
9
- const icon = computed(() => props.icon ?? getDomainIcon(props.href))
10
- const tip = computed(() => ({
11
- content: isExtLink(props.href) ? getDomain(props.href) : safelyDecodeUriComponent(props.href),
12
- inlinePositioning: true,
13
- }))
14
- </script>
15
-
16
- <template>
17
- <UtilLink v-tip="tip" class="z-link" :to="href">
18
- <Icon v-if="icon" class="domain-icon" :name="icon" />
19
- <slot />
20
- </UtilLink>
21
- </template>
22
-
23
- <style lang="scss" scoped>
1
+ <script setup lang="ts">
2
+ const props = withDefaults(defineProps<{
3
+ href: string
4
+ icon?: string | false
5
+ }>(), {
6
+ icon: undefined,
7
+ })
8
+
9
+ const icon = computed(() => props.icon ?? getDomainIcon(props.href))
10
+ const tip = computed(() => ({
11
+ content: isExtLink(props.href) ? getDomain(props.href) : safelyDecodeUriComponent(props.href),
12
+ inlinePositioning: true,
13
+ }))
14
+ </script>
15
+
16
+ <template>
17
+ <UtilLink v-tip="tip" class="z-link" :to="href">
18
+ <Icon v-if="icon" class="domain-icon" :name="icon" />
19
+ <slot />
20
+ </UtilLink>
21
+ </template>
22
+
23
+ <style lang="scss" scoped>
24
24
  .z-link[href] {
25
25
  margin: -0.1em -0.2em;
26
26
  padding: 0.1em 0.2em;
@@ -37,4 +37,4 @@ const tip = computed(() => ({
37
37
  margin-inline-end: 0.1em;
38
38
  }
39
39
  }
40
- </style>
40
+ </style>
@@ -35,31 +35,31 @@ const { copy: copyCode, copied } = useCopy(rawCode.value)
35
35
  const shiki = useShiki()
36
36
  const codeElement = useTemplateRef('code')
37
37
  const highlighted = ref(false)
38
-
39
- onMounted(async () => {
38
+
39
+ onMounted(async () => {
40
40
  if (!props.language)
41
41
  return
42
42
  await shiki.mountInline(codeElement.value!, rawCode.value, {
43
43
  language: props.language,
44
44
  transformerOptions: ['ignoreColorizedBrackets'],
45
- })
46
- highlighted.value = true
47
- })
48
- </script>
49
-
45
+ })
46
+ highlighted.value = true
47
+ })
48
+ </script>
49
+
50
50
  <template>
51
51
  <code ref="code" :class="{ copyable: copy }">
52
52
  <template v-if="!language || !highlighted">
53
53
  <slot>{{ rawCode }}</slot>
54
54
  </template>
55
- <Icon v-if="copy" v-show="false" name="tabler:check" />
56
- <button v-if="copy" type="button" class="copy-button" aria-label="复制" @click="copyCode()">
57
- <Icon :name="copied ? 'tabler:check' : 'tabler:copy'" />
58
- </button>
59
- </code>
60
- </template>
61
-
62
- <style lang="scss" scoped>
55
+ <Icon v-if="copy" v-show="false" name="tabler:check" />
56
+ <button v-if="copy" type="button" class="copy-button" aria-label="复制" @click="copyCode()">
57
+ <Icon :name="copied ? 'tabler:check' : 'tabler:copy'" />
58
+ </button>
59
+ </code>
60
+ </template>
61
+
62
+ <style lang="scss" scoped>
63
63
  code {
64
64
  margin: 0.1em;
65
65
  padding: 0.1rem 0.3em;
@@ -92,4 +92,4 @@ code {
92
92
  color: var(--c-primary);
93
93
  }
94
94
  }
95
- </style>
95
+ </style>
@@ -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>