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.
- package/LICENSE +20 -20
- package/README.md +70 -4
- package/README.zh-CN.md +67 -4
- package/nuxt.config.ts +217 -214
- package/package.json +35 -37
- package/{app → src}/app.config.ts +110 -110
- package/{app → src}/app.vue +5 -5
- package/{app → src}/components/blog/BlogAside.vue +25 -25
- package/{app → src}/components/blog/BlogFooter.vue +25 -25
- package/{app → src}/components/blog/BlogHeader.global.vue +48 -48
- package/{app → src}/components/blog/BlogPanel.vue +43 -43
- package/{app → src}/components/blog/BlogSidebar.vue +53 -53
- package/{app → src}/components/blog/BlogWidget.vue +42 -42
- package/{app → src}/components/blog/Mask.vue +25 -25
- package/{app → src}/components/blog/SkipToContent.vue +9 -9
- package/{app → src}/components/blog/ThemeToggle.vue +22 -22
- package/{app → src}/components/content/Badge.vue +45 -45
- package/{app → src}/components/content/Blur.vue +14 -14
- package/{app → src}/components/content/CardList.vue +8 -8
- package/{app → src}/components/content/Chat.vue +41 -41
- package/{app → src}/components/content/Copy.vue +71 -71
- package/{app → src}/components/content/EmojiClock.vue +49 -49
- package/{app → src}/components/content/FeedCard.vue +96 -96
- package/{app → src}/components/content/FeedGroup.vue +61 -61
- package/{app → src}/components/content/Folding.vue +19 -19
- package/{app → src}/components/content/Key.vue +143 -143
- package/{app → src}/components/content/LinkBanner.vue +27 -27
- package/{app → src}/components/content/LinkCard.vue +28 -28
- package/{app → src}/components/content/MdTitle.vue +8 -8
- package/{app → src}/components/content/Mermaid.vue +99 -99
- package/{app → src}/components/content/MusicScore.vue +63 -63
- package/{app → src}/components/content/Pic.vue +49 -49
- package/{app → src}/components/content/Poetry.vue +27 -27
- package/{app → src}/components/content/ProseA.vue +24 -24
- package/{app → src}/components/content/ProseCode.vue +16 -16
- package/{app → src}/components/content/ProsePre.vue +117 -117
- package/{app → src}/components/content/ProseTable.vue +23 -23
- package/{app → src}/components/content/Quote.vue +21 -21
- package/{app → src}/components/content/Tab.vue +32 -32
- package/{app → src}/components/content/Timeline.vue +33 -33
- package/{app → src}/components/content/Tip.vue +40 -40
- package/{app → src}/components/content/VideoEmbed.vue +112 -112
- package/{app → src}/components/partial/Button.vue +36 -36
- package/{app → src}/components/partial/DlGroup.vue +31 -31
- package/{app → src}/components/partial/Dropdown.vue +21 -21
- package/{app → src}/components/partial/Error.vue +39 -39
- package/{app → src}/components/partial/Expand.vue +26 -26
- package/{app → src}/components/partial/IconNavList.vue +24 -24
- package/{app → src}/components/partial/Pagination.vue +62 -62
- package/{app → src}/components/partial/RadioGroup.vue +30 -30
- package/{app → src}/components/partial/Secret.vue +11 -11
- package/{app → src}/components/partial/Slider.vue +48 -48
- package/{app → src}/components/partial/Toggle.vue +17 -17
- package/{app → src}/components/popover/Lightbox.vue +44 -44
- package/{app → src}/components/popover/Search.vue +143 -143
- package/{app → src}/components/popover/SearchItem.vue +28 -28
- package/{app → src}/components/post/Archive.vue +40 -40
- package/{app → src}/components/post/Article.vue +49 -49
- package/{app → src}/components/post/Comment.vue +119 -119
- package/{app → src}/components/post/Excerpt.vue +40 -40
- package/{app → src}/components/post/OrderToggle.vue +70 -70
- package/{app → src}/components/post/PostFooter.vue +52 -52
- package/{app → src}/components/post/PostHeader.vue +66 -66
- package/{app → src}/components/post/PostSurround.vue +51 -51
- package/{app → src}/components/post/Slide.vue +85 -85
- package/{app → src}/components/util/Date.vue +62 -62
- package/{app → src}/components/util/HydrateSafe.vue +10 -10
- package/{app → src}/components/util/Img.vue +38 -38
- package/{app → src}/components/util/Link.vue +11 -11
- package/{app → src}/components/widget/BlogLog.vue +23 -23
- package/{app → src}/components/widget/BlogStats.vue +38 -38
- package/{app → src}/components/widget/BlogTech.vue +57 -57
- package/{app → src}/components/widget/Empty.vue +3 -3
- package/{app → src}/components/widget/Toc.vue +67 -67
- package/{app → src}/composables/useArticle.ts +115 -115
- package/{app → src}/composables/useAvoid.ts +87 -87
- package/{app → src}/composables/useCopy.ts +21 -21
- package/{app → src}/composables/usePagination.ts +70 -70
- package/{app → src}/composables/useShiki.ts +129 -129
- package/{app → src}/composables/useToc.ts +57 -57
- package/{app → src}/composables/useWidgets.ts +56 -56
- package/{config → src/config}/app.ts +62 -62
- package/{config → src/config}/content.ts +75 -75
- package/{config → src/config}/define.ts +3 -3
- package/src/config/feeds.empty.ts +6 -0
- package/{config → src/config}/index.ts +1 -1
- package/{config → src/config}/schema.ts +89 -89
- package/{app → src}/error.vue +31 -31
- package/{img → src/img}/index.ts +2 -2
- package/{app → src}/layouts/default.vue +25 -25
- package/{modules → src/modules}/clarity-config/index.ts +168 -162
- package/src/modules/clarity-source-layout/index.ts +78 -0
- package/{app → src}/pages/[...slug].vue +59 -59
- package/{app → src}/pages/archive.vue +129 -129
- package/{app → src}/pages/index.vue +76 -76
- package/{app → src}/pages/link.vue +62 -62
- package/{app → src}/pages/preview.vue +46 -46
- package/{app → src}/plugins/easter-egg.ts +23 -23
- package/{app → src}/plugins/init.ts +6 -6
- package/{app → src}/plugins/tippy.ts +22 -22
- package/{public → src/public}/assets/atom.css +274 -274
- package/{public → src/public}/assets/atom.xsl +109 -109
- package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.ts +58 -58
- package/{remark-plugins → src/remark-plugins}/remark-code-component.ts +41 -41
- package/{server → src/server}/api/stats.get.ts +84 -83
- package/{server → src/server}/routes/atom.xml.get.ts +85 -85
- package/{server → src/server}/routes/subscriptions.opml.get.ts +64 -64
- package/{shared → src/shared}/utils/clarity.ts +24 -24
- package/{shared → src/shared}/utils/icon.ts +218 -218
- package/{shared → src/shared}/utils/link.ts +52 -52
- package/{shared → src/shared}/utils/str.ts +88 -88
- package/{shared → src/shared}/utils/time.ts +115 -115
- package/{app → src}/shiki.config.ts +8 -8
- package/{app → src}/stores/layout.ts +34 -34
- package/{app → src}/stores/search.ts +37 -37
- package/{app → src}/types/article.ts +16 -16
- package/src/types/feed.ts +1 -0
- package/{app → src}/types/index.ts +14 -14
- package/{app → src}/types/nav.ts +11 -11
- package/{app → src}/utils/anim.ts +37 -37
- package/{app → src}/utils/img.ts +83 -83
- package/app/types/feed.ts +0 -1
- package/config/feeds.empty.ts +0 -4
- package/{app → src}/assets/css/_variable.scss +0 -0
- package/{app → src}/assets/css/animation.scss +0 -0
- package/{app → src}/assets/css/article.scss +0 -0
- package/{app → src}/assets/css/color.scss +0 -0
- package/{app → src}/assets/css/font.scss +0 -0
- package/{app → src}/assets/css/main.scss +0 -0
- package/{app → src}/assets/css/reusable.scss +0 -0
- package/{app → src}/assets/icons/solar-rewind-back-bold-duotone.svg +0 -0
- package/{app → src}/assets/icons/solar-rewind-forward-bold-duotone.svg +0 -0
- package/{app → src}/components/content/Alert.vue +0 -0
- package/{config → src/config}/content.d.mts +0 -0
- package/{config → src/config}/content.mjs +0 -0
- package/{config → src/config}/define.mjs +0 -0
- package/{config → src/config}/feed.ts +0 -0
- package/{config → src/config}/index.d.mts +0 -0
- package/{config → src/config}/index.mjs +0 -0
- package/{config → src/config}/public.ts +1 -1
- package/{config → src/config}/schema.d.mts +0 -0
- package/{config → src/config}/schema.mjs +0 -0
- package/{config → src/config}/server.ts +1 -1
- /package/{img → src/img}/index.d.mts +0 -0
- /package/{img → src/img}/index.mjs +0 -0
- /package/{modules → src/modules}/clarity-config/anti-mirror-client.ts +0 -0
- /package/{public → src/public}/fonts/AlimamaFangYuanTi.woff2 +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/rehype-meta-slots.mjs +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.d.mts +0 -0
- /package/{remark-plugins → src/remark-plugins}/remark-code-component.mjs +0 -0
- /package/{server → src/server}/utils/clarity.ts +0 -0
- /package/{shared → src/shared}/types/content.ts +0 -0
- /package/{app → src}/types/modal.ts +0 -0
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
import type { MetaSlotsTree } from '
|
|
2
|
-
import type { ClarityContentRow } from '
|
|
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
|
+
}
|