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,88 +1,88 @@
1
- import { toArray } from '@vueuse/core'
2
- import { escape, escapeRegExp } from 'es-toolkit/string'
3
-
4
- // @keep-sorted
5
- const promptLanguageMap: Record<string, string> = {
6
- '#': 'sh',
7
- '$': 'sh',
8
- 'CMD': 'bat',
9
- 'PS': 'powershell',
10
- }
11
-
12
- export function formatNumber(num?: number) {
13
- if (typeof num !== 'number')
14
- return ''
15
- const intervals = [
16
- { label: '万亿', threshold: 1e12 },
17
- { label: '亿', threshold: 1e8 },
18
- { label: '万', threshold: 1e4 },
19
- ]
20
- for (const interval of intervals) {
21
- if (num >= interval.threshold)
22
- return `${(num / interval.threshold).toFixed(2)}${interval.label}`
23
- }
24
- return num.toString()
25
- }
26
-
27
- interface FormatBytesOptions {
28
- decimals?: number
29
- binary?: boolean
30
- unitSeparator?: string
31
- }
32
-
33
- export function formatBytes(bytes: number, options: FormatBytesOptions = {}) {
34
- const {
35
- decimals = 2,
36
- binary = true,
37
- unitSeparator = ' ',
38
- } = options
39
-
40
- if (bytes === 0)
41
- return `0${unitSeparator}Bytes`
42
-
43
- const base = binary ? 1024 : 1000
44
- const units = binary
45
- ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
46
- : ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
47
-
48
- const i = Math.floor(Math.log(bytes) / Math.log(base))
49
- const value = Number.parseFloat((bytes / base ** i).toFixed(decimals))
50
-
51
- return `${value}${unitSeparator}${units[i]}`
52
- }
53
-
54
- export function getPromptLanguage(prompt: string | boolean) {
55
- if (typeof prompt === 'boolean')
56
- return 'text'
57
- for (const promptPrefix in promptLanguageMap) {
58
- if (prompt.startsWith(promptPrefix))
59
- return promptLanguageMap[promptPrefix] ?? 'text'
60
- }
61
- return 'text'
62
- }
63
-
64
- export function joinWith(strings: (string | undefined)[], separator = '\n') {
65
- return strings.filter(Boolean).join(separator)
66
- }
67
-
68
- export function highlightHtml(text: string, words: string | string[] | undefined, className?: string) {
69
- const validTerms = toArray(words)
70
- .filter((t): t is string => !!t?.trim())
71
- .map(t => t.toLowerCase())
72
-
73
- const highlightRegex = new RegExp(`(${Array.from(validTerms, escapeRegExp).join('|')})`, 'gi')
74
-
75
- return text
76
- .split(highlightRegex)
77
- .map(part => part && validTerms.includes(part.toLowerCase())
78
- ? `<mark${className ? ` class="${className}"` : ''}>${escape(part)}</mark>`
79
- : escape(part))
80
- .join('')
81
- .replace(/\n+/g, '<br>')
82
- }
83
-
84
- export function removeHtmlTags(str?: string) {
85
- if (typeof str !== 'string')
86
- return ''
87
- return str.replace(/<[^>]+(>|$)/g, '')
88
- }
1
+ import { toArray } from '@vueuse/core'
2
+ import { escape, escapeRegExp } from 'es-toolkit/string'
3
+
4
+ // @keep-sorted
5
+ const promptLanguageMap: Record<string, string> = {
6
+ '#': 'sh',
7
+ '$': 'sh',
8
+ 'CMD': 'bat',
9
+ 'PS': 'powershell',
10
+ }
11
+
12
+ export function formatNumber(num?: number) {
13
+ if (typeof num !== 'number')
14
+ return ''
15
+ const intervals = [
16
+ { label: '万亿', threshold: 1e12 },
17
+ { label: '亿', threshold: 1e8 },
18
+ { label: '万', threshold: 1e4 },
19
+ ]
20
+ for (const interval of intervals) {
21
+ if (num >= interval.threshold)
22
+ return `${(num / interval.threshold).toFixed(2)}${interval.label}`
23
+ }
24
+ return num.toString()
25
+ }
26
+
27
+ interface FormatBytesOptions {
28
+ decimals?: number
29
+ binary?: boolean
30
+ unitSeparator?: string
31
+ }
32
+
33
+ export function formatBytes(bytes: number, options: FormatBytesOptions = {}) {
34
+ const {
35
+ decimals = 2,
36
+ binary = true,
37
+ unitSeparator = ' ',
38
+ } = options
39
+
40
+ if (bytes === 0)
41
+ return `0${unitSeparator}Bytes`
42
+
43
+ const base = binary ? 1024 : 1000
44
+ const units = binary
45
+ ? ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
46
+ : ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
47
+
48
+ const i = Math.floor(Math.log(bytes) / Math.log(base))
49
+ const value = Number.parseFloat((bytes / base ** i).toFixed(decimals))
50
+
51
+ return `${value}${unitSeparator}${units[i]}`
52
+ }
53
+
54
+ export function getPromptLanguage(prompt: string | boolean) {
55
+ if (typeof prompt === 'boolean')
56
+ return 'text'
57
+ for (const promptPrefix in promptLanguageMap) {
58
+ if (prompt.startsWith(promptPrefix))
59
+ return promptLanguageMap[promptPrefix] ?? 'text'
60
+ }
61
+ return 'text'
62
+ }
63
+
64
+ export function joinWith(strings: (string | undefined)[], separator = '\n') {
65
+ return strings.filter(Boolean).join(separator)
66
+ }
67
+
68
+ export function highlightHtml(text: string, words: string | string[] | undefined, className?: string) {
69
+ const validTerms = toArray(words)
70
+ .filter((t): t is string => !!t?.trim())
71
+ .map(t => t.toLowerCase())
72
+
73
+ const highlightRegex = new RegExp(`(${Array.from(validTerms, escapeRegExp).join('|')})`, 'gi')
74
+
75
+ return text
76
+ .split(highlightRegex)
77
+ .map(part => part && validTerms.includes(part.toLowerCase())
78
+ ? `<mark${className ? ` class="${className}"` : ''}>${escape(part)}</mark>`
79
+ : escape(part))
80
+ .join('')
81
+ .replace(/\n+/g, '<br>')
82
+ }
83
+
84
+ export function removeHtmlTags(str?: string) {
85
+ if (typeof str !== 'string')
86
+ return ''
87
+ return str.replace(/<[^>]+(>|$)/g, '')
88
+ }
@@ -1,116 +1,116 @@
1
- import { Temporal } from 'temporal-polyfill'
2
-
1
+ import { Temporal } from 'temporal-polyfill'
2
+
3
3
  export function isSameUnit(date1: string, date2: string, unit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second', timeZone = 'UTC') {
4
- try {
5
- const p1 = toZonedTemporal(date1, timeZone).toPlainDateTime()
6
- const p2 = toZonedTemporal(date2, timeZone).toPlainDateTime()
7
- return p1.until(p2, {
8
- largestUnit: unit,
9
- smallestUnit: unit,
10
- roundingMode: 'trunc',
11
- }).blank
12
- }
13
- catch {
14
- return false
15
- }
16
- }
17
-
18
- /** 检查两个时间相对现在是否相差显著 */
19
- export function isTimeDiffSignificant(
20
- date1?: string,
21
- date2?: string,
22
- timeZone = 'UTC',
23
- /** 对于时间差的敏感程度,0~1 之间,1:不同则认为显著,>1:始终认为显著 */
24
- threshold = 0.6,
25
- ) {
26
- if (!date1 || !date2 || threshold <= 0)
27
- return false
28
- if (threshold > 1)
29
- return true
30
- try {
31
- const now = Temporal.Now.instant().epochMilliseconds
32
- const diff1 = now - toZonedTemporal(date1, timeZone).epochMilliseconds
33
- const diff2 = now - toZonedTemporal(date2, timeZone).epochMilliseconds
34
- return diff1 / diff2 < threshold || diff2 / diff1 < threshold
35
- }
36
- catch {
37
- return true
38
- }
39
- }
40
-
41
- const timeIntervals = [
42
- { label: '世纪', threshold: 60 * 60 * 24 * 365.2422 * 100 },
43
- { label: '年', threshold: 60 * 60 * 24 * 365.2422 },
44
- { label: '个月', threshold: 60 * 60 * 24 * 30.44 },
45
- { label: '天', threshold: 60 * 60 * 24 },
46
- { label: '小时', threshold: 60 * 60 },
47
- { label: '分', threshold: 60 },
48
- { label: '秒', threshold: 1 },
49
- ]
50
-
51
- export function timeElapse(date: string | Temporal.PlainDateTime, maxDepth = 2) {
52
- let timeString = ''
53
- let secRemained = Temporal.Now.plainDateTimeISO().since(date, { largestUnit: 'second' }).seconds
54
- for (const interval of timeIntervals) {
55
- const count = Math.floor(secRemained / interval.threshold)
56
- if (count <= 0)
57
- continue
58
- timeString += `${count}${interval.label}`
59
- secRemained -= count * interval.threshold
60
- if (--maxDepth <= 0)
61
- break
62
- }
63
- return timeString || '刚刚'
64
- }
65
-
66
- export function toInstantString(date: string | Temporal.ZonedDateTime, timeZone = 'UTC') {
67
- return (typeof date === 'string' ? toZonedTemporal(date, timeZone) : date).toInstant().toString()
68
- }
69
-
70
- /**
71
- * 将日期字符串解析为带时区的 Temporal 对象
72
- * @param date 日期字符串
73
- * @param timeZone 无时区信息时使用的默认时区(来自站点配置)
74
- */
75
- export function toZonedTemporal(date: string, timeZone = 'UTC') {
76
- try {
77
- return Temporal.ZonedDateTime.from(date)
78
- }
79
- catch {
80
- try {
81
- return Temporal.Instant.from(date).toZonedDateTimeISO(timeZone)
82
- }
83
- catch {
84
- return Temporal.PlainDateTime.from(date).toZonedDateTime(timeZone)
85
- }
86
- }
87
- }
88
-
89
- export const dateTimeFormat = {
90
- date: {
91
- year: 'numeric',
92
- month: '2-digit',
93
- day: '2-digit',
94
- },
95
- monthDay: {
96
- month: '2-digit',
97
- day: '2-digit',
98
- },
99
- full: {
100
- year: 'numeric',
101
- month: '2-digit',
102
- day: '2-digit',
103
- weekday: 'long',
104
- hour: '2-digit',
105
- minute: '2-digit',
106
- second: '2-digit',
107
- timeZoneName: 'long',
108
- },
109
- } satisfies Record<string, Intl.DateTimeFormatOptions>
110
-
111
- export type dateTimeFormatOptions = keyof typeof dateTimeFormat | Intl.DateTimeFormatOptions
112
-
113
- export function toZdtLocaleString(date: string | Temporal.ZonedDateTime, format: dateTimeFormatOptions = 'full', timeZone = 'UTC') {
114
- return (typeof date === 'string' ? toZonedTemporal(date, timeZone) : date)
115
- .toLocaleString(undefined, typeof format === 'string' ? dateTimeFormat[format] : format)
116
- }
4
+ try {
5
+ const p1 = toZonedTemporal(date1, timeZone).toPlainDateTime()
6
+ const p2 = toZonedTemporal(date2, timeZone).toPlainDateTime()
7
+ return p1.until(p2, {
8
+ largestUnit: unit,
9
+ smallestUnit: unit,
10
+ roundingMode: 'trunc',
11
+ }).blank
12
+ }
13
+ catch {
14
+ return false
15
+ }
16
+ }
17
+
18
+ /** 检查两个时间相对现在是否相差显著 */
19
+ export function isTimeDiffSignificant(
20
+ date1?: string,
21
+ date2?: string,
22
+ timeZone = 'UTC',
23
+ /** 对于时间差的敏感程度,0~1 之间,1:不同则认为显著,>1:始终认为显著 */
24
+ threshold = 0.6,
25
+ ) {
26
+ if (!date1 || !date2 || threshold <= 0)
27
+ return false
28
+ if (threshold > 1)
29
+ return true
30
+ try {
31
+ const now = Temporal.Now.instant().epochMilliseconds
32
+ const diff1 = now - toZonedTemporal(date1, timeZone).epochMilliseconds
33
+ const diff2 = now - toZonedTemporal(date2, timeZone).epochMilliseconds
34
+ return diff1 / diff2 < threshold || diff2 / diff1 < threshold
35
+ }
36
+ catch {
37
+ return true
38
+ }
39
+ }
40
+
41
+ const timeIntervals = [
42
+ { label: '世纪', threshold: 60 * 60 * 24 * 365.2422 * 100 },
43
+ { label: '年', threshold: 60 * 60 * 24 * 365.2422 },
44
+ { label: '个月', threshold: 60 * 60 * 24 * 30.44 },
45
+ { label: '天', threshold: 60 * 60 * 24 },
46
+ { label: '小时', threshold: 60 * 60 },
47
+ { label: '分', threshold: 60 },
48
+ { label: '秒', threshold: 1 },
49
+ ]
50
+
51
+ export function timeElapse(date: string | Temporal.PlainDateTime, maxDepth = 2) {
52
+ let timeString = ''
53
+ let secRemained = Temporal.Now.plainDateTimeISO().since(date, { largestUnit: 'second' }).seconds
54
+ for (const interval of timeIntervals) {
55
+ const count = Math.floor(secRemained / interval.threshold)
56
+ if (count <= 0)
57
+ continue
58
+ timeString += `${count}${interval.label}`
59
+ secRemained -= count * interval.threshold
60
+ if (--maxDepth <= 0)
61
+ break
62
+ }
63
+ return timeString || '刚刚'
64
+ }
65
+
66
+ export function toInstantString(date: string | Temporal.ZonedDateTime, timeZone = 'UTC') {
67
+ return (typeof date === 'string' ? toZonedTemporal(date, timeZone) : date).toInstant().toString()
68
+ }
69
+
70
+ /**
71
+ * 将日期字符串解析为带时区的 Temporal 对象
72
+ * @param date 日期字符串
73
+ * @param timeZone 无时区信息时使用的默认时区(来自站点配置)
74
+ */
75
+ export function toZonedTemporal(date: string, timeZone = 'UTC') {
76
+ try {
77
+ return Temporal.ZonedDateTime.from(date)
78
+ }
79
+ catch {
80
+ try {
81
+ return Temporal.Instant.from(date).toZonedDateTimeISO(timeZone)
82
+ }
83
+ catch {
84
+ return Temporal.PlainDateTime.from(date).toZonedDateTime(timeZone)
85
+ }
86
+ }
87
+ }
88
+
89
+ export const dateTimeFormat = {
90
+ date: {
91
+ year: 'numeric',
92
+ month: '2-digit',
93
+ day: '2-digit',
94
+ },
95
+ monthDay: {
96
+ month: '2-digit',
97
+ day: '2-digit',
98
+ },
99
+ full: {
100
+ year: 'numeric',
101
+ month: '2-digit',
102
+ day: '2-digit',
103
+ weekday: 'long',
104
+ hour: '2-digit',
105
+ minute: '2-digit',
106
+ second: '2-digit',
107
+ timeZoneName: 'long',
108
+ },
109
+ } satisfies Record<string, Intl.DateTimeFormatOptions>
110
+
111
+ export type dateTimeFormatOptions = keyof typeof dateTimeFormat | Intl.DateTimeFormatOptions
112
+
113
+ export function toZdtLocaleString(date: string | Temporal.ZonedDateTime, format: dateTimeFormatOptions = 'full', timeZone = 'UTC') {
114
+ return (typeof date === 'string' ? toZonedTemporal(date, timeZone) : date)
115
+ .toLocaleString(undefined, typeof format === 'string' ? dateTimeFormat[format] : format)
116
+ }
@@ -1,8 +1,8 @@
1
- import { defineConfig } from '#shiki/config'
2
-
3
- export default defineConfig({
4
- themes: {
5
- light: () => import('shiki/themes/catppuccin-latte.mjs'),
6
- dark: () => import('shiki/themes/one-dark-pro.mjs'),
7
- },
8
- })
1
+ import { defineConfig } from '#shiki/config'
2
+
3
+ export default defineConfig({
4
+ themes: {
5
+ light: () => import('shiki/themes/catppuccin-latte.mjs'),
6
+ dark: () => import('shiki/themes/one-dark-pro.mjs'),
7
+ },
8
+ })
@@ -1,34 +1,34 @@
1
- export type LayoutState = 'none' | 'sidebar' | 'aside' | 'search' | 'lightbox'
2
-
3
- export const useLayoutStore = defineStore('layout', () => {
4
- const router = useRouter()
5
-
6
- const state = ref<LayoutState>('none')
7
- const avoidTargets = ref<AvoidTarget[]>([])
8
-
9
- const close = () => state.value = 'none'
10
-
11
- const toggle = (key: LayoutState) => {
12
- if (state.value === key)
13
- return close()
14
- state.value = key
15
- }
16
-
17
- useEventListener('keydown', (e) => {
18
- if (state.value !== 'none' && e.key === 'Escape') {
19
- e.preventDefault()
20
- close()
21
- }
22
- })
23
-
24
- router.beforeEach(() => {
25
- close()
26
- })
27
-
28
- return {
29
- state,
30
- avoidTargets,
31
- close,
32
- toggle,
33
- }
34
- })
1
+ export type LayoutState = 'none' | 'sidebar' | 'aside' | 'search' | 'lightbox'
2
+
3
+ export const useLayoutStore = defineStore('layout', () => {
4
+ const router = useRouter()
5
+
6
+ const state = ref<LayoutState>('none')
7
+ const avoidTargets = ref<AvoidTarget[]>([])
8
+
9
+ const close = () => state.value = 'none'
10
+
11
+ const toggle = (key: LayoutState) => {
12
+ if (state.value === key)
13
+ return close()
14
+ state.value = key
15
+ }
16
+
17
+ useEventListener('keydown', (e) => {
18
+ if (state.value !== 'none' && e.key === 'Escape') {
19
+ e.preventDefault()
20
+ close()
21
+ }
22
+ })
23
+
24
+ router.beforeEach(() => {
25
+ close()
26
+ })
27
+
28
+ return {
29
+ state,
30
+ avoidTargets,
31
+ close,
32
+ toggle,
33
+ }
34
+ })
@@ -1,37 +1,37 @@
1
- import { LazyPopoverSearch } from '#components'
2
-
3
- export const useSearchStore = defineStore('search', () => {
4
- // 搜索框应和侧边栏状态联动
5
- const layoutStore = useLayoutStore()
6
- const modalStore = useModalStore()
7
-
8
- const word = ref('')
9
- const debouncedWord = refDebounced(word)
10
-
11
- const {
12
- open: _open,
13
- close: _close,
14
- } = modalStore.use(() => h(LazyPopoverSearch, {
15
- onClose: () => {
16
- _close()
17
- layoutStore.close()
18
- },
19
- }), {
20
- unique: true,
21
- duration: 200,
22
- })
23
-
24
- // 从外部调用时应该操作 layoutStore
25
- watch(() => layoutStore.state, (state) => {
26
- if (state !== 'search')
27
- return _close()
28
-
29
- word.value = window.getSelection()?.toString().trim() || word.value
30
- _open()
31
- })
32
-
33
- return {
34
- word,
35
- debouncedWord,
36
- }
37
- })
1
+ import { LazyPopoverSearch } from '#components'
2
+
3
+ export const useSearchStore = defineStore('search', () => {
4
+ // 搜索框应和侧边栏状态联动
5
+ const layoutStore = useLayoutStore()
6
+ const modalStore = useModalStore()
7
+
8
+ const word = ref('')
9
+ const debouncedWord = refDebounced(word)
10
+
11
+ const {
12
+ open: _open,
13
+ close: _close,
14
+ } = modalStore.use(() => h(LazyPopoverSearch, {
15
+ onClose: () => {
16
+ _close()
17
+ layoutStore.close()
18
+ },
19
+ }), {
20
+ unique: true,
21
+ duration: 200,
22
+ })
23
+
24
+ // 从外部调用时应该操作 layoutStore
25
+ watch(() => layoutStore.state, (state) => {
26
+ if (state !== 'search')
27
+ return _close()
28
+
29
+ word.value = window.getSelection()?.toString().trim() || word.value
30
+ _open()
31
+ })
32
+
33
+ return {
34
+ word,
35
+ debouncedWord,
36
+ }
37
+ })
@@ -1,16 +1,16 @@
1
- import type { ClarityAppConfig } from '../../config/app'
2
- import type { ArticleSchema } from '../../config/content'
3
- import type { MetaSlotsTree } from '../../remark-plugins/rehype-meta-slots'
4
-
5
- export type ArticleOrderType = keyof ClarityAppConfig['article']['order']
6
-
7
- export interface ArticleProps extends ArticleSchema {
8
- path: string
9
-
10
- meta?: {
11
- coverDim?: boolean
12
- coverFilter?: string
13
- hideInfo?: boolean
14
- slots?: Record<string, MetaSlotsTree>
15
- }
16
- }
1
+ import type { ClarityAppConfig } from '../config/app'
2
+ import type { ArticleSchema } from '../config/content'
3
+ import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
4
+
5
+ export type ArticleOrderType = keyof ClarityAppConfig['article']['order']
6
+
7
+ export interface ArticleProps extends ArticleSchema {
8
+ path: string
9
+
10
+ meta?: {
11
+ coverDim?: boolean
12
+ coverFilter?: string
13
+ hideInfo?: boolean
14
+ slots?: Record<string, MetaSlotsTree>
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export type { FeedEntry, FeedGroup } from '../config/feed'
@@ -1,14 +1,14 @@
1
- declare global {
2
- interface Window {
3
- twikoo?: {
4
- init: (options: {
5
- envId: string
6
- el: string
7
- region?: string
8
- path?: string
9
- lang?: string
10
- }) => void
11
- version: string
12
- }
13
- }
14
- }
1
+ declare global {
2
+ interface Window {
3
+ twikoo?: {
4
+ init: (options: {
5
+ envId: string
6
+ el: string
7
+ region?: string
8
+ path?: string
9
+ lang?: string
10
+ }) => void
11
+ version: string
12
+ }
13
+ }
14
+ }
@@ -1,11 +1,11 @@
1
- export interface NavItem {
2
- icon: string
3
- text: string
4
- url: string
5
- external?: boolean
6
- }
7
-
8
- export type Nav = {
9
- title: string
10
- items: NavItem[]
11
- }[]
1
+ export interface NavItem {
2
+ icon: string
3
+ text: string
4
+ url: string
5
+ external?: boolean
6
+ }
7
+
8
+ export type Nav = {
9
+ title: string
10
+ items: NavItem[]
11
+ }[]