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,118 +1,118 @@
1
- import type { MetaSlotsTree } from '../../remark-plugins/rehype-meta-slots'
2
- import type { ClarityContentRow } from '../../shared/types/content'
1
+ import type { MetaSlotsTree } from '../remark-plugins/rehype-meta-slots'
2
+ import type { ClarityContentRow } from '../shared/types/content'
3
3
  import type { ArticleOrderType, ArticleProps } from '../types/article'
4
- import { orderBy } from 'es-toolkit/array'
5
-
6
- /** 获取已加载的文章内容/元信息 */
7
- export function useArticle(path?: MaybeRefOrGetter<string | undefined>) {
8
- const route = useRoute()
9
- const dataKey = computed(() => `content:${toValue(path) ?? route.path}`)
4
+ import { orderBy } from 'es-toolkit/array'
5
+
6
+ /** 获取已加载的文章内容/元信息 */
7
+ export function useArticle(path?: MaybeRefOrGetter<string | undefined>) {
8
+ const route = useRoute()
9
+ const dataKey = computed(() => `content:${toValue(path) ?? route.path}`)
10
10
  const post = computed(() => useNuxtData<ClarityContentRow | null | undefined>(dataKey.value).data.value)
11
-
12
- return {
13
- dataKey,
14
- post,
15
- toc: computed(() => post.value?.body.toc),
16
- metaSlots: computed(() => post.value?.meta.slots as Record<string, MetaSlotsTree>),
17
- }
18
- }
19
-
20
- /**
21
- * 生成文章查询参数,完全包装 useAsyncData 会使 SSR 行为异常,缓存 key 需要暴露
22
- * @see https://nuxt.com/docs/4.x/api/composables/use-async-data#usage
23
- * @see https://github.com/nuxt/nuxt/issues/14736
24
- * @todo 支持分页/分类筛选
25
- */
26
- export function getArticleIndexOptions(path = 'posts/%') {
27
- return queryCollection('content')
28
- .where('stem', 'LIKE', path)
29
- .select('categories', 'date', 'description', 'image', 'path', 'readingTime', 'recommend', 'tags', 'title', 'type', 'updated')
30
- .all()
31
- }
32
-
33
- interface UseCategoryOptions {
34
- bindQuery?: string
35
- }
36
-
37
- export function useCategory(list: MaybeRefOrGetter<ArticleProps[]>, options?: UseCategoryOptions) {
38
- const { bindQuery } = options || {}
39
-
40
- const category = bindQuery
41
- ? useRouteQuery(bindQuery, undefined)
42
- : ref<string | undefined>()
43
-
44
- const categories = computed(() => [...new Set(toValue(list).map(item => item.categories?.[0]))])
45
-
46
- const listCategorized = computed(
47
- () => toValue(list).filter(
48
- item => !category.value || item.categories?.[0] === category.value,
49
- ),
50
- )
51
-
52
- return {
53
- category,
54
- categories,
55
- listCategorized,
56
- }
57
- }
58
-
59
- interface UseArticleSortOptions {
60
- bindDirectionQuery?: string
61
- bindOrderQuery?: string
62
- initialAscend?: boolean
63
- initialOrder?: ArticleOrderType
64
- }
65
-
66
- export function useArticleSort(list: MaybeRefOrGetter<ArticleProps[]>, options?: UseArticleSortOptions) {
67
- const clarity = useClarityConfig()
68
- const {
69
- bindDirectionQuery,
70
- bindOrderQuery,
71
- initialAscend = false,
72
- initialOrder = (clarity.pagination.sortOrder as ArticleOrderType) || 'date',
73
- } = options || {}
74
-
75
- const sortOrder = bindOrderQuery
76
- ? useRouteQuery(bindOrderQuery, initialOrder)
77
- : ref<ArticleOrderType>(initialOrder)
78
-
79
- const booleanQueryTransformer = {
80
- get: (val: string) => val === 'true',
81
- set: (val: boolean) => val.toString(),
82
- }
83
-
84
- const isAscending = bindDirectionQuery
85
- ? useRouteQuery(bindDirectionQuery, initialAscend.toString(), { transform: booleanQueryTransformer })
86
- : ref<boolean>(initialAscend)
87
-
88
- const listSorted = computed(() => orderBy(
89
- toValue(list),
11
+
12
+ return {
13
+ dataKey,
14
+ post,
15
+ toc: computed(() => post.value?.body.toc),
16
+ metaSlots: computed(() => post.value?.meta.slots as Record<string, MetaSlotsTree>),
17
+ }
18
+ }
19
+
20
+ /**
21
+ * 生成文章查询参数,完全包装 useAsyncData 会使 SSR 行为异常,缓存 key 需要暴露
22
+ * @see https://nuxt.com/docs/4.x/api/composables/use-async-data#usage
23
+ * @see https://github.com/nuxt/nuxt/issues/14736
24
+ * @todo 支持分页/分类筛选
25
+ */
26
+ export function getArticleIndexOptions(path = 'posts/%') {
27
+ return queryCollection('content')
28
+ .where('stem', 'LIKE', path)
29
+ .select('categories', 'date', 'description', 'image', 'path', 'readingTime', 'recommend', 'tags', 'title', 'type', 'updated')
30
+ .all()
31
+ }
32
+
33
+ interface UseCategoryOptions {
34
+ bindQuery?: string
35
+ }
36
+
37
+ export function useCategory(list: MaybeRefOrGetter<ArticleProps[]>, options?: UseCategoryOptions) {
38
+ const { bindQuery } = options || {}
39
+
40
+ const category = bindQuery
41
+ ? useRouteQuery(bindQuery, undefined)
42
+ : ref<string | undefined>()
43
+
44
+ const categories = computed(() => [...new Set(toValue(list).map(item => item.categories?.[0]))])
45
+
46
+ const listCategorized = computed(
47
+ () => toValue(list).filter(
48
+ item => !category.value || item.categories?.[0] === category.value,
49
+ ),
50
+ )
51
+
52
+ return {
53
+ category,
54
+ categories,
55
+ listCategorized,
56
+ }
57
+ }
58
+
59
+ interface UseArticleSortOptions {
60
+ bindDirectionQuery?: string
61
+ bindOrderQuery?: string
62
+ initialAscend?: boolean
63
+ initialOrder?: ArticleOrderType
64
+ }
65
+
66
+ export function useArticleSort(list: MaybeRefOrGetter<ArticleProps[]>, options?: UseArticleSortOptions) {
67
+ const clarity = useClarityConfig()
68
+ const {
69
+ bindDirectionQuery,
70
+ bindOrderQuery,
71
+ initialAscend = false,
72
+ initialOrder = (clarity.pagination.sortOrder as ArticleOrderType) || 'date',
73
+ } = options || {}
74
+
75
+ const sortOrder = bindOrderQuery
76
+ ? useRouteQuery(bindOrderQuery, initialOrder)
77
+ : ref<ArticleOrderType>(initialOrder)
78
+
79
+ const booleanQueryTransformer = {
80
+ get: (val: string) => val === 'true',
81
+ set: (val: boolean) => val.toString(),
82
+ }
83
+
84
+ const isAscending = bindDirectionQuery
85
+ ? useRouteQuery(bindDirectionQuery, initialAscend.toString(), { transform: booleanQueryTransformer })
86
+ : ref<boolean>(initialAscend)
87
+
88
+ const listSorted = computed(() => orderBy(
89
+ toValue(list),
90
90
  [sortOrder.value, 'date'] as any,
91
- [isAscending.value ? 'asc' : 'desc'],
92
- ))
93
-
94
- return {
95
- sortOrder,
96
- isAscending,
97
- listSorted,
98
- }
99
- }
100
-
101
- export function getCategoryIcon(category?: string) {
102
- const clarity = useClarityConfig()
103
- return clarity.article.categories[category!]?.icon ?? 'tabler:folder'
104
- }
105
-
106
- export function getCategoryColor(category?: string) {
107
- const clarity = useClarityConfig()
108
- return clarity.article.categories[category!]?.color
109
- }
110
-
111
- interface GetPostTypeClassNameOptions {
112
- prefix?: string
113
- }
114
-
115
- export function getPostTypeClassName(type = 'tech', options?: GetPostTypeClassNameOptions) {
116
- const { prefix = 'text' } = options || {}
117
- return `${prefix}-${type}`
118
- }
91
+ [isAscending.value ? 'asc' : 'desc'],
92
+ ))
93
+
94
+ return {
95
+ sortOrder,
96
+ isAscending,
97
+ listSorted,
98
+ }
99
+ }
100
+
101
+ export function getCategoryIcon(category?: string) {
102
+ const clarity = useClarityConfig()
103
+ return clarity.article.categories[category!]?.icon ?? 'tabler:folder'
104
+ }
105
+
106
+ export function getCategoryColor(category?: string) {
107
+ const clarity = useClarityConfig()
108
+ return clarity.article.categories[category!]?.color
109
+ }
110
+
111
+ interface GetPostTypeClassNameOptions {
112
+ prefix?: string
113
+ }
114
+
115
+ export function getPostTypeClassName(type = 'tech', options?: GetPostTypeClassNameOptions) {
116
+ const { prefix = 'text' } = options || {}
117
+ return `${prefix}-${type}`
118
+ }
@@ -1,87 +1,87 @@
1
- import type { UseElementBoundingReturn } from '@vueuse/core'
2
-
3
- export type AvoidTarget = Ref<HTMLElement | null | undefined>
4
-
5
- export function useAvoidTransform(
6
- originRef: AvoidTarget,
7
- targets: Ref<AvoidTarget[]>,
8
- ) {
9
- const { height: windowHeight, width: windowWidth } = useWindowSize()
10
- const targetBounds = shallowRef(new Map<AvoidTarget, UseElementBoundingReturn>())
11
- const originRect = shallowRef({ top: 0, bottom: 0, left: 0, right: 0 })
12
-
13
- function updateOriginPosition() {
14
- const origin = originRef.value
15
- if (!origin)
16
- return
17
- const style = getComputedStyle(origin)
18
- const bottom = Number.parseFloat(style.bottom) || 0
19
- const right = Number.parseFloat(style.insetInlineEnd || style.right) || 0
20
- originRect.value = {
21
- top: windowHeight.value - bottom - origin.offsetHeight,
22
- bottom: windowHeight.value - bottom,
23
- left: windowWidth.value - right - origin.offsetWidth,
24
- right: windowWidth.value - right,
25
- }
26
- }
27
-
28
- watch([windowHeight, windowWidth], updateOriginPosition)
29
- onMounted(updateOriginPosition)
30
-
31
- watchImmediate(targets, (list) => {
32
- const newMap = new Map<AvoidTarget, UseElementBoundingReturn>()
33
- for (const target of list) {
34
- if (target.value)
35
- newMap.set(target, targetBounds.value.get(target) ?? useElementBounding(target))
36
- }
37
- targetBounds.value = newMap
38
- }, { deep: true })
39
-
40
- const transform = computed(() => {
41
- if (!originRef.value)
42
- return ''
43
- const { bottom: originBottom, left: originLeft, right: originRight, top: originTop } = originRect.value
44
-
45
- const shifts = Array.from(targetBounds.value.values())
46
- .filter(({ top, bottom, left, right }) => {
47
- const hasHOverlap = originLeft < right.value && originRight > left.value
48
- const hasVOverlap = top.value < originBottom && bottom.value > originTop
49
- return hasHOverlap && hasVOverlap
50
- })
51
- .map(({ top }) => originBottom - top.value)
52
-
53
- const maxShift = Math.max(...shifts)
54
- return maxShift > 0 ? `translateY(-${maxShift + 16}px)` : ''
55
- })
56
-
57
- return {
58
- transform,
59
- }
60
- }
61
-
62
- export function useAvoidTarget(
63
- targetRef: AvoidTarget,
64
- active?: MaybeRefOrGetter<boolean>,
65
- ) {
66
- const layoutStore = useLayoutStore()
67
-
68
- function add() {
69
- if (!layoutStore.avoidTargets.includes(targetRef))
70
- layoutStore.avoidTargets.push(targetRef)
71
- }
72
-
73
- function remove() {
74
- const idx = layoutStore.avoidTargets.indexOf(targetRef)
75
- if (idx > -1)
76
- layoutStore.avoidTargets.splice(idx, 1)
77
- }
78
-
79
- watchImmediate([toRef(active), targetRef], ([isActive]) => {
80
- if (isActive ?? true)
81
- add()
82
- else
83
- remove()
84
- })
85
-
86
- onUnmounted(remove)
87
- }
1
+ import type { UseElementBoundingReturn } from '@vueuse/core'
2
+
3
+ export type AvoidTarget = Ref<HTMLElement | null | undefined>
4
+
5
+ export function useAvoidTransform(
6
+ originRef: AvoidTarget,
7
+ targets: Ref<AvoidTarget[]>,
8
+ ) {
9
+ const { height: windowHeight, width: windowWidth } = useWindowSize()
10
+ const targetBounds = shallowRef(new Map<AvoidTarget, UseElementBoundingReturn>())
11
+ const originRect = shallowRef({ top: 0, bottom: 0, left: 0, right: 0 })
12
+
13
+ function updateOriginPosition() {
14
+ const origin = originRef.value
15
+ if (!origin)
16
+ return
17
+ const style = getComputedStyle(origin)
18
+ const bottom = Number.parseFloat(style.bottom) || 0
19
+ const right = Number.parseFloat(style.insetInlineEnd || style.right) || 0
20
+ originRect.value = {
21
+ top: windowHeight.value - bottom - origin.offsetHeight,
22
+ bottom: windowHeight.value - bottom,
23
+ left: windowWidth.value - right - origin.offsetWidth,
24
+ right: windowWidth.value - right,
25
+ }
26
+ }
27
+
28
+ watch([windowHeight, windowWidth], updateOriginPosition)
29
+ onMounted(updateOriginPosition)
30
+
31
+ watchImmediate(targets, (list) => {
32
+ const newMap = new Map<AvoidTarget, UseElementBoundingReturn>()
33
+ for (const target of list) {
34
+ if (target.value)
35
+ newMap.set(target, targetBounds.value.get(target) ?? useElementBounding(target))
36
+ }
37
+ targetBounds.value = newMap
38
+ }, { deep: true })
39
+
40
+ const transform = computed(() => {
41
+ if (!originRef.value)
42
+ return ''
43
+ const { bottom: originBottom, left: originLeft, right: originRight, top: originTop } = originRect.value
44
+
45
+ const shifts = Array.from(targetBounds.value.values())
46
+ .filter(({ top, bottom, left, right }) => {
47
+ const hasHOverlap = originLeft < right.value && originRight > left.value
48
+ const hasVOverlap = top.value < originBottom && bottom.value > originTop
49
+ return hasHOverlap && hasVOverlap
50
+ })
51
+ .map(({ top }) => originBottom - top.value)
52
+
53
+ const maxShift = Math.max(...shifts)
54
+ return maxShift > 0 ? `translateY(-${maxShift + 16}px)` : ''
55
+ })
56
+
57
+ return {
58
+ transform,
59
+ }
60
+ }
61
+
62
+ export function useAvoidTarget(
63
+ targetRef: AvoidTarget,
64
+ active?: MaybeRefOrGetter<boolean>,
65
+ ) {
66
+ const layoutStore = useLayoutStore()
67
+
68
+ function add() {
69
+ if (!layoutStore.avoidTargets.includes(targetRef))
70
+ layoutStore.avoidTargets.push(targetRef)
71
+ }
72
+
73
+ function remove() {
74
+ const idx = layoutStore.avoidTargets.indexOf(targetRef)
75
+ if (idx > -1)
76
+ layoutStore.avoidTargets.splice(idx, 1)
77
+ }
78
+
79
+ watchImmediate([toRef(active), targetRef], ([isActive]) => {
80
+ if (isActive ?? true)
81
+ add()
82
+ else
83
+ remove()
84
+ })
85
+
86
+ onUnmounted(remove)
87
+ }
@@ -1,21 +1,21 @@
1
- import type { MaybeComputedElementRef } from '@vueuse/core'
2
-
3
- /**
4
- * 点击触发元素时,将文本复制到剪贴板,并在触发元素上显示提示信息。
5
- * @param target - 提供复制文本、目标元素或组件实例。可为字符串、输入框(复制其 `value`)或其他 HTMLElement(复制其文本内容)。
6
- */
7
- export default function useCopy(
8
- target: MaybeComputedElementRef | string,
9
- ) {
10
- function getText() {
11
- if (typeof target === 'string')
12
- return target
13
-
14
- const el = unrefElement(target)
15
- if (el instanceof HTMLInputElement)
16
- return el.value
17
- return el?.textContent || ''
18
- }
19
-
20
- return useClipboard({ source: getText, legacy: true })
21
- }
1
+ import type { MaybeComputedElementRef } from '@vueuse/core'
2
+
3
+ /**
4
+ * 点击触发元素时,将文本复制到剪贴板,并在触发元素上显示提示信息。
5
+ * @param target - 提供复制文本、目标元素或组件实例。可为字符串、输入框(复制其 `value`)或其他 HTMLElement(复制其文本内容)。
6
+ */
7
+ export default function useCopy(
8
+ target: MaybeComputedElementRef | string,
9
+ ) {
10
+ function getText() {
11
+ if (typeof target === 'string')
12
+ return target
13
+
14
+ const el = unrefElement(target)
15
+ if (el instanceof HTMLInputElement)
16
+ return el.value
17
+ return el?.textContent || ''
18
+ }
19
+
20
+ return useClipboard({ source: getText, legacy: true })
21
+ }
@@ -1,70 +1,70 @@
1
- interface UsePaginationOptions {
2
- initialPage?: number
3
- perPage?: number
4
- bindQuery?: string
5
- }
6
-
7
- export default function usePagination<T>(list: MaybeRefOrGetter<T[]>, options?: UsePaginationOptions) {
8
- const clarity = useClarityConfig()
9
- const {
10
- initialPage = 1,
11
- perPage = clarity.pagination.perPage || 10,
12
- bindQuery,
13
- } = options || {}
14
-
15
- const totalPages = computed(() => Math.ceil(toValue(list).length / perPage) || initialPage)
16
-
17
- function transformPage(val: string) {
18
- const page = Number(val)
19
- return page >= 1 && page <= totalPages.value ? page : initialPage
20
- }
21
-
22
- // 仅从无查询参数增加 query 时 push 一次
23
- const mode = computed({
24
- get: () => bindQuery && useRoute().query[bindQuery] ? 'replace' : 'push',
25
- set() { },
26
- })
27
-
28
- const page = bindQuery
29
- ? useRouteQuery(bindQuery, initialPage.toString(), { transform: transformPage, mode })
30
- : ref(initialPage)
31
-
32
- const listPaged = computed(() => {
33
- const start = (page.value - 1) * perPage
34
- return toValue(list).slice(start, start + perPage)
35
- })
36
-
37
- // 不应在此处 watch list
38
-
39
- return {
40
- totalPages,
41
- page,
42
- listPaged,
43
- }
44
- }
45
-
46
- /**
47
- * 生成分页数组
48
- *
49
- * 根据当前页码、扩展范围和总页数,生成一个用于显示的分页数组,包含起始页、结束页和省略符号位置。
50
- *
51
- * @param current 当前页码
52
- * @param total 总页数
53
- * @param expand 当前页码的扩展范围,默认值为1
54
- * @returns 返回一个包含可显示页码的数组。
55
- * 数组中的 `Number.NEGATIVE_INFINITY` 表示向前省略页码符号(...)的位置;
56
- * 数组中的 `Number.POSITIVE_INFINITY` 表示向后省略页码符号(...)的位置。
57
- *
58
- */
59
- export function getPaginationIndicator(current: number, total: number, expand: number = 1) {
60
- const start = Math.max(2, Math.min(current - expand, total - 2 * expand))
61
- const end = Math.min(total, start + 2 * expand)
62
- const pageArr = Array.from({ length: end - start + 1 }, (_, i) => start + i)
63
- start > 3 && pageArr.unshift(Number.NEGATIVE_INFINITY)
64
- start === 3 && pageArr.unshift(2)
65
- start > 1 && pageArr.unshift(1)
66
- end < total - 2 && pageArr.push(Number.POSITIVE_INFINITY)
67
- end === total - 2 && pageArr.push(total - 1)
68
- end < total && pageArr.push(total)
69
- return pageArr
70
- }
1
+ interface UsePaginationOptions {
2
+ initialPage?: number
3
+ perPage?: number
4
+ bindQuery?: string
5
+ }
6
+
7
+ export default function usePagination<T>(list: MaybeRefOrGetter<T[]>, options?: UsePaginationOptions) {
8
+ const clarity = useClarityConfig()
9
+ const {
10
+ initialPage = 1,
11
+ perPage = clarity.pagination.perPage || 10,
12
+ bindQuery,
13
+ } = options || {}
14
+
15
+ const totalPages = computed(() => Math.ceil(toValue(list).length / perPage) || initialPage)
16
+
17
+ function transformPage(val: string) {
18
+ const page = Number(val)
19
+ return page >= 1 && page <= totalPages.value ? page : initialPage
20
+ }
21
+
22
+ // 仅从无查询参数增加 query 时 push 一次
23
+ const mode = computed({
24
+ get: () => bindQuery && useRoute().query[bindQuery] ? 'replace' : 'push',
25
+ set() { },
26
+ })
27
+
28
+ const page = bindQuery
29
+ ? useRouteQuery(bindQuery, initialPage.toString(), { transform: transformPage, mode })
30
+ : ref(initialPage)
31
+
32
+ const listPaged = computed(() => {
33
+ const start = (page.value - 1) * perPage
34
+ return toValue(list).slice(start, start + perPage)
35
+ })
36
+
37
+ // 不应在此处 watch list
38
+
39
+ return {
40
+ totalPages,
41
+ page,
42
+ listPaged,
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 生成分页数组
48
+ *
49
+ * 根据当前页码、扩展范围和总页数,生成一个用于显示的分页数组,包含起始页、结束页和省略符号位置。
50
+ *
51
+ * @param current 当前页码
52
+ * @param total 总页数
53
+ * @param expand 当前页码的扩展范围,默认值为1
54
+ * @returns 返回一个包含可显示页码的数组。
55
+ * 数组中的 `Number.NEGATIVE_INFINITY` 表示向前省略页码符号(...)的位置;
56
+ * 数组中的 `Number.POSITIVE_INFINITY` 表示向后省略页码符号(...)的位置。
57
+ *
58
+ */
59
+ export function getPaginationIndicator(current: number, total: number, expand: number = 1) {
60
+ const start = Math.max(2, Math.min(current - expand, total - 2 * expand))
61
+ const end = Math.min(total, start + 2 * expand)
62
+ const pageArr = Array.from({ length: end - start + 1 }, (_, i) => start + i)
63
+ start > 3 && pageArr.unshift(Number.NEGATIVE_INFINITY)
64
+ start === 3 && pageArr.unshift(2)
65
+ start > 1 && pageArr.unshift(1)
66
+ end < total - 2 && pageArr.push(Number.POSITIVE_INFINITY)
67
+ end === total - 2 && pageArr.push(total - 1)
68
+ end < total && pageArr.push(total)
69
+ return pageArr
70
+ }