clarity-theme 0.1.0 → 0.1.1
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 +18 -4
- package/README.zh-CN.md +18 -4
- package/app/app.config.ts +110 -110
- package/app/app.vue +5 -5
- package/app/components/blog/BlogAside.vue +25 -25
- package/app/components/blog/BlogFooter.vue +25 -25
- package/app/components/blog/BlogHeader.global.vue +48 -48
- package/app/components/blog/BlogPanel.vue +43 -43
- package/app/components/blog/BlogSidebar.vue +53 -53
- package/app/components/blog/BlogWidget.vue +42 -42
- package/app/components/blog/Mask.vue +25 -25
- package/app/components/blog/SkipToContent.vue +9 -9
- package/app/components/blog/ThemeToggle.vue +22 -22
- package/app/components/content/Badge.vue +45 -45
- package/app/components/content/Blur.vue +14 -14
- package/app/components/content/CardList.vue +8 -8
- package/app/components/content/Chat.vue +41 -41
- package/app/components/content/Copy.vue +71 -71
- package/app/components/content/EmojiClock.vue +49 -49
- package/app/components/content/FeedCard.vue +96 -96
- package/app/components/content/FeedGroup.vue +61 -61
- package/app/components/content/Folding.vue +19 -19
- package/app/components/content/Key.vue +143 -143
- package/app/components/content/LinkBanner.vue +27 -27
- package/app/components/content/LinkCard.vue +28 -28
- package/app/components/content/MdTitle.vue +8 -8
- package/app/components/content/Mermaid.vue +99 -99
- package/app/components/content/MusicScore.vue +63 -63
- package/app/components/content/Pic.vue +49 -49
- package/app/components/content/Poetry.vue +27 -27
- package/app/components/content/ProseA.vue +24 -24
- package/app/components/content/ProseCode.vue +16 -16
- package/app/components/content/ProsePre.vue +117 -117
- package/app/components/content/ProseTable.vue +23 -23
- package/app/components/content/Quote.vue +21 -21
- package/app/components/content/Tab.vue +32 -32
- package/app/components/content/Timeline.vue +33 -33
- package/app/components/content/Tip.vue +40 -40
- package/app/components/content/VideoEmbed.vue +112 -112
- package/app/components/partial/Button.vue +36 -36
- package/app/components/partial/DlGroup.vue +31 -31
- package/app/components/partial/Dropdown.vue +21 -21
- package/app/components/partial/Error.vue +39 -39
- package/app/components/partial/Expand.vue +26 -26
- package/app/components/partial/IconNavList.vue +24 -24
- package/app/components/partial/Pagination.vue +62 -62
- package/app/components/partial/RadioGroup.vue +30 -30
- package/app/components/partial/Secret.vue +11 -11
- package/app/components/partial/Slider.vue +48 -48
- package/app/components/partial/Toggle.vue +17 -17
- package/app/components/popover/Lightbox.vue +44 -44
- package/app/components/popover/Search.vue +143 -143
- package/app/components/popover/SearchItem.vue +28 -28
- package/app/components/post/Archive.vue +40 -40
- package/app/components/post/Article.vue +49 -49
- package/app/components/post/Comment.vue +119 -119
- package/app/components/post/Excerpt.vue +40 -40
- package/app/components/post/OrderToggle.vue +70 -70
- package/app/components/post/PostFooter.vue +52 -52
- package/app/components/post/PostHeader.vue +66 -66
- package/app/components/post/PostSurround.vue +51 -51
- package/app/components/post/Slide.vue +85 -85
- package/app/components/util/Date.vue +62 -62
- package/app/components/util/HydrateSafe.vue +10 -10
- package/app/components/util/Img.vue +38 -38
- package/app/components/util/Link.vue +11 -11
- package/app/components/widget/BlogLog.vue +23 -23
- package/app/components/widget/BlogStats.vue +38 -38
- package/app/components/widget/BlogTech.vue +57 -57
- package/app/components/widget/Empty.vue +3 -3
- package/app/components/widget/Toc.vue +67 -67
- package/app/composables/useArticle.ts +113 -113
- package/app/composables/useAvoid.ts +87 -87
- package/app/composables/useCopy.ts +21 -21
- package/app/composables/usePagination.ts +70 -70
- package/app/composables/useShiki.ts +129 -129
- package/app/composables/useToc.ts +57 -57
- package/app/composables/useWidgets.ts +56 -56
- package/app/error.vue +31 -31
- package/app/layouts/default.vue +25 -25
- package/app/pages/[...slug].vue +59 -59
- package/app/pages/archive.vue +129 -129
- package/app/pages/index.vue +76 -76
- package/app/pages/link.vue +62 -62
- package/app/pages/preview.vue +46 -46
- package/app/plugins/easter-egg.ts +23 -23
- package/app/plugins/init.ts +6 -6
- package/app/plugins/tippy.ts +22 -22
- package/app/shiki.config.ts +8 -8
- package/app/stores/layout.ts +34 -34
- package/app/stores/search.ts +37 -37
- package/app/types/article.ts +16 -16
- package/app/types/index.ts +14 -14
- package/app/types/nav.ts +11 -11
- package/app/utils/anim.ts +37 -37
- package/app/utils/img.ts +83 -83
- package/config/app.ts +61 -61
- package/config/content.ts +75 -75
- package/config/define.ts +3 -3
- package/config/feeds.empty.ts +6 -4
- package/config/public.ts +1 -1
- package/config/schema.ts +89 -89
- package/config/server.ts +1 -1
- package/modules/clarity-config/index.ts +162 -159
- package/nuxt.config.ts +212 -212
- package/package.json +25 -21
- package/public/assets/atom.css +274 -274
- package/public/assets/atom.xsl +109 -109
- package/remark-plugins/rehype-meta-slots.ts +58 -58
- package/remark-plugins/remark-code-component.ts +41 -41
- package/server/api/stats.get.ts +84 -83
- package/server/routes/atom.xml.get.ts +85 -85
- package/server/routes/subscriptions.opml.get.ts +64 -64
- package/shared/utils/clarity.ts +24 -24
- package/shared/utils/icon.ts +218 -218
- package/shared/utils/link.ts +52 -52
- package/shared/utils/str.ts +88 -88
- package/shared/utils/time.ts +115 -115
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineProps<{
|
|
3
|
-
hasAside?: boolean
|
|
4
|
-
}>()
|
|
5
|
-
|
|
6
|
-
const layoutStore = useLayoutStore()
|
|
7
|
-
const { avoidTargets } = storeToRefs(layoutStore)
|
|
8
|
-
|
|
9
|
-
const panelRef = useTemplateRef('blog-panel')
|
|
10
|
-
const { transform } = useAvoidTransform(panelRef, avoidTargets)
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<template>
|
|
14
|
-
<div
|
|
15
|
-
id="blog-panel"
|
|
16
|
-
ref="blog-panel"
|
|
17
|
-
:class="{ 'has-active': layoutStore.state !== 'none' }"
|
|
18
|
-
:style="{ transform }"
|
|
19
|
-
>
|
|
20
|
-
<button
|
|
21
|
-
v-if="hasAside"
|
|
22
|
-
class="toggle-aside widescreen-only"
|
|
23
|
-
:class="{ active: layoutStore.state === 'aside' }"
|
|
24
|
-
aria-label="切换侧边栏"
|
|
25
|
-
@click="layoutStore.toggle('aside')"
|
|
26
|
-
>
|
|
27
|
-
<Icon class="rtl-flip" name="tabler:align-right" />
|
|
28
|
-
</button>
|
|
29
|
-
|
|
30
|
-
<Icon v-show="false" name="tabler:layout-sidebar-filled" />
|
|
31
|
-
<button
|
|
32
|
-
class="toggle-sidebar mobile-only"
|
|
33
|
-
:class="{ active: layoutStore.state === 'sidebar' }"
|
|
34
|
-
aria-label="切换菜单"
|
|
35
|
-
@click="layoutStore.toggle('sidebar')"
|
|
36
|
-
>
|
|
37
|
-
<Icon class="rtl-flip" :name="layoutStore.state === 'sidebar' ? 'tabler:layout-sidebar-filled' : 'tabler:layout-sidebar'" />
|
|
38
|
-
</button>
|
|
39
|
-
</div>
|
|
40
|
-
</template>
|
|
41
|
-
|
|
42
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
hasAside?: boolean
|
|
4
|
+
}>()
|
|
5
|
+
|
|
6
|
+
const layoutStore = useLayoutStore()
|
|
7
|
+
const { avoidTargets } = storeToRefs(layoutStore)
|
|
8
|
+
|
|
9
|
+
const panelRef = useTemplateRef('blog-panel')
|
|
10
|
+
const { transform } = useAvoidTransform(panelRef, avoidTargets)
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<div
|
|
15
|
+
id="blog-panel"
|
|
16
|
+
ref="blog-panel"
|
|
17
|
+
:class="{ 'has-active': layoutStore.state !== 'none' }"
|
|
18
|
+
:style="{ transform }"
|
|
19
|
+
>
|
|
20
|
+
<button
|
|
21
|
+
v-if="hasAside"
|
|
22
|
+
class="toggle-aside widescreen-only"
|
|
23
|
+
:class="{ active: layoutStore.state === 'aside' }"
|
|
24
|
+
aria-label="切换侧边栏"
|
|
25
|
+
@click="layoutStore.toggle('aside')"
|
|
26
|
+
>
|
|
27
|
+
<Icon class="rtl-flip" name="tabler:align-right" />
|
|
28
|
+
</button>
|
|
29
|
+
|
|
30
|
+
<Icon v-show="false" name="tabler:layout-sidebar-filled" />
|
|
31
|
+
<button
|
|
32
|
+
class="toggle-sidebar mobile-only"
|
|
33
|
+
:class="{ active: layoutStore.state === 'sidebar' }"
|
|
34
|
+
aria-label="切换菜单"
|
|
35
|
+
@click="layoutStore.toggle('sidebar')"
|
|
36
|
+
>
|
|
37
|
+
<Icon class="rtl-flip" :name="layoutStore.state === 'sidebar' ? 'tabler:layout-sidebar-filled' : 'tabler:layout-sidebar'" />
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<style lang="scss" scoped>
|
|
43
43
|
#blog-panel {
|
|
44
44
|
contain: paint;
|
|
45
45
|
position: fixed;
|
|
@@ -76,4 +76,4 @@ button {
|
|
|
76
76
|
color: var(--c-primary);
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
</style>
|
|
79
|
+
</style>
|
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
const clarity = useClarityConfig()
|
|
3
|
-
const layoutStore = useLayoutStore()
|
|
4
|
-
const searchStore = useSearchStore()
|
|
5
|
-
|
|
6
|
-
const { text } = useTextSelection()
|
|
7
|
-
const debouncedSelection = refDebounced(text)
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<template>
|
|
11
|
-
<BlogMask
|
|
12
|
-
:show="layoutStore.state === 'sidebar'"
|
|
13
|
-
class="mobile-only"
|
|
14
|
-
@click="layoutStore.close()"
|
|
15
|
-
/>
|
|
16
|
-
|
|
17
|
-
<!-- 不能用 Transition 实现弹出收起动画,因为半宽屏状态始终显示 -->
|
|
18
|
-
<aside id="blog-sidebar" :class="{ show: layoutStore.state === 'sidebar' }">
|
|
19
|
-
<BlogHeader class="sidebar-header" to="/" />
|
|
20
|
-
|
|
21
|
-
<nav class="sidebar-nav scrollcheck-y">
|
|
22
|
-
<div class="search-btn sidebar-nav-item gradient-card" @click="layoutStore.toggle('search')">
|
|
23
|
-
<Icon name="tabler:search" />
|
|
24
|
-
<span class="nav-text">{{ debouncedSelection || searchStore.word || '搜索' }}</span>
|
|
25
|
-
<Key class="keycut" code="K" cmd prevent @press="layoutStore.toggle('search')" />
|
|
26
|
-
</div>
|
|
27
|
-
|
|
28
|
-
<template v-for="(group, groupIndex) in clarity.nav" :key="groupIndex">
|
|
29
|
-
<h3 v-if="group.title">
|
|
30
|
-
{{ group.title }}
|
|
31
|
-
</h3>
|
|
32
|
-
|
|
33
|
-
<menu>
|
|
34
|
-
<li v-for="(item, itemIndex) in group.items" :key="itemIndex">
|
|
35
|
-
<UtilLink :to="item.url" class="sidebar-nav-item">
|
|
36
|
-
<Icon :name="item.icon" />
|
|
37
|
-
<span class="nav-text">{{ item.text }}</span>
|
|
38
|
-
<Icon v-if="isExtLink(item.url)" class="external-tip" name="tabler:arrow-up-right" />
|
|
39
|
-
</UtilLink>
|
|
40
|
-
</li>
|
|
41
|
-
</menu>
|
|
42
|
-
</template>
|
|
43
|
-
</nav>
|
|
44
|
-
|
|
45
|
-
<footer class="sidebar-footer">
|
|
46
|
-
<BlogThemeToggle />
|
|
47
|
-
<ZIconNavList :list="clarity.footer.iconNav" />
|
|
48
|
-
</footer>
|
|
49
|
-
</aside>
|
|
50
|
-
</template>
|
|
51
|
-
|
|
52
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const clarity = useClarityConfig()
|
|
3
|
+
const layoutStore = useLayoutStore()
|
|
4
|
+
const searchStore = useSearchStore()
|
|
5
|
+
|
|
6
|
+
const { text } = useTextSelection()
|
|
7
|
+
const debouncedSelection = refDebounced(text)
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<BlogMask
|
|
12
|
+
:show="layoutStore.state === 'sidebar'"
|
|
13
|
+
class="mobile-only"
|
|
14
|
+
@click="layoutStore.close()"
|
|
15
|
+
/>
|
|
16
|
+
|
|
17
|
+
<!-- 不能用 Transition 实现弹出收起动画,因为半宽屏状态始终显示 -->
|
|
18
|
+
<aside id="blog-sidebar" :class="{ show: layoutStore.state === 'sidebar' }">
|
|
19
|
+
<BlogHeader class="sidebar-header" to="/" />
|
|
20
|
+
|
|
21
|
+
<nav class="sidebar-nav scrollcheck-y">
|
|
22
|
+
<div class="search-btn sidebar-nav-item gradient-card" @click="layoutStore.toggle('search')">
|
|
23
|
+
<Icon name="tabler:search" />
|
|
24
|
+
<span class="nav-text">{{ debouncedSelection || searchStore.word || '搜索' }}</span>
|
|
25
|
+
<Key class="keycut" code="K" cmd prevent @press="layoutStore.toggle('search')" />
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<template v-for="(group, groupIndex) in clarity.nav" :key="groupIndex">
|
|
29
|
+
<h3 v-if="group.title">
|
|
30
|
+
{{ group.title }}
|
|
31
|
+
</h3>
|
|
32
|
+
|
|
33
|
+
<menu>
|
|
34
|
+
<li v-for="(item, itemIndex) in group.items" :key="itemIndex">
|
|
35
|
+
<UtilLink :to="item.url" class="sidebar-nav-item">
|
|
36
|
+
<Icon :name="item.icon" />
|
|
37
|
+
<span class="nav-text">{{ item.text }}</span>
|
|
38
|
+
<Icon v-if="isExtLink(item.url)" class="external-tip" name="tabler:arrow-up-right" />
|
|
39
|
+
</UtilLink>
|
|
40
|
+
</li>
|
|
41
|
+
</menu>
|
|
42
|
+
</template>
|
|
43
|
+
</nav>
|
|
44
|
+
|
|
45
|
+
<footer class="sidebar-footer">
|
|
46
|
+
<BlogThemeToggle />
|
|
47
|
+
<ZIconNavList :list="clarity.footer.iconNav" />
|
|
48
|
+
</footer>
|
|
49
|
+
</aside>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style lang="scss" scoped>
|
|
53
53
|
#blog-sidebar {
|
|
54
54
|
display: flex;
|
|
55
55
|
flex-direction: column;
|
|
@@ -157,4 +157,4 @@ const debouncedSelection = refDebounced(text)
|
|
|
157
157
|
text-align: center;
|
|
158
158
|
color: var(--c-text-2);
|
|
159
159
|
}
|
|
160
|
-
</style>
|
|
160
|
+
</style>
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineProps<{
|
|
3
|
-
title?: string
|
|
4
|
-
card?: boolean
|
|
5
|
-
shrink?: boolean
|
|
6
|
-
grayscale?: boolean
|
|
7
|
-
dim?: boolean
|
|
8
|
-
bgImg?: string
|
|
9
|
-
bgAside?: boolean
|
|
10
|
-
}>()
|
|
11
|
-
|
|
12
|
-
const body = useTemplateRef('widget-body')
|
|
13
|
-
|
|
14
|
-
defineExpose({ body })
|
|
15
|
-
</script>
|
|
16
|
-
|
|
17
|
-
<template>
|
|
18
|
-
<section
|
|
19
|
-
class="blog-widget"
|
|
20
|
-
:class="{ shrink, grayscale, dim }"
|
|
21
|
-
>
|
|
22
|
-
<hgroup class="widget-header text-creative">
|
|
23
|
-
<slot name="title">
|
|
24
|
-
{{ title }}
|
|
25
|
-
</slot>
|
|
26
|
-
<span v-if="$slots.action" class="seperator" />
|
|
27
|
-
<slot name="action" />
|
|
28
|
-
</hgroup>
|
|
29
|
-
|
|
30
|
-
<div
|
|
31
|
-
ref="widget-body"
|
|
32
|
-
class="widget-body"
|
|
33
|
-
:class="{ 'widget-card': card, 'with-bg': bgImg, 'scrollcheck-y scrollbar-hidden': shrink }"
|
|
34
|
-
>
|
|
35
|
-
<NuxtImg v-if="bgImg" class="bg-img" :class="{ 'bg-right': bgAside }" :src="bgImg" alt="" />
|
|
36
|
-
<slot />
|
|
37
|
-
</div>
|
|
38
|
-
</section>
|
|
39
|
-
</template>
|
|
40
|
-
|
|
41
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
title?: string
|
|
4
|
+
card?: boolean
|
|
5
|
+
shrink?: boolean
|
|
6
|
+
grayscale?: boolean
|
|
7
|
+
dim?: boolean
|
|
8
|
+
bgImg?: string
|
|
9
|
+
bgAside?: boolean
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const body = useTemplateRef('widget-body')
|
|
13
|
+
|
|
14
|
+
defineExpose({ body })
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<section
|
|
19
|
+
class="blog-widget"
|
|
20
|
+
:class="{ shrink, grayscale, dim }"
|
|
21
|
+
>
|
|
22
|
+
<hgroup class="widget-header text-creative">
|
|
23
|
+
<slot name="title">
|
|
24
|
+
{{ title }}
|
|
25
|
+
</slot>
|
|
26
|
+
<span v-if="$slots.action" class="seperator" />
|
|
27
|
+
<slot name="action" />
|
|
28
|
+
</hgroup>
|
|
29
|
+
|
|
30
|
+
<div
|
|
31
|
+
ref="widget-body"
|
|
32
|
+
class="widget-body"
|
|
33
|
+
:class="{ 'widget-card': card, 'with-bg': bgImg, 'scrollcheck-y scrollbar-hidden': shrink }"
|
|
34
|
+
>
|
|
35
|
+
<NuxtImg v-if="bgImg" class="bg-img" :class="{ 'bg-right': bgAside }" :src="bgImg" alt="" />
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
38
|
+
</section>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<style lang="scss" scoped>
|
|
42
42
|
.blog-widget {
|
|
43
43
|
flex-shrink: 1;
|
|
44
44
|
font-size: 0.9em;
|
|
@@ -131,4 +131,4 @@ defineExpose({ body })
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
</style>
|
|
134
|
+
</style>
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineProps<{
|
|
3
|
-
blur?: boolean | string
|
|
4
|
-
zIndex?: number
|
|
5
|
-
}>()
|
|
6
|
-
|
|
7
|
-
const show = defineModel<boolean>('show')
|
|
8
|
-
</script>
|
|
9
|
-
|
|
10
|
-
<template>
|
|
11
|
-
<Transition>
|
|
12
|
-
<div
|
|
13
|
-
v-if="show"
|
|
14
|
-
class="bg-mask"
|
|
15
|
-
:style="{
|
|
16
|
-
'--z-index-popover': zIndex,
|
|
17
|
-
'--bdfilter': blur ? `blur(${blur === true ? '4px' : blur})` : 'none',
|
|
18
|
-
}"
|
|
19
|
-
@click="show = false"
|
|
20
|
-
/>
|
|
21
|
-
</Transition>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
blur?: boolean | string
|
|
4
|
+
zIndex?: number
|
|
5
|
+
}>()
|
|
6
|
+
|
|
7
|
+
const show = defineModel<boolean>('show')
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<Transition>
|
|
12
|
+
<div
|
|
13
|
+
v-if="show"
|
|
14
|
+
class="bg-mask"
|
|
15
|
+
:style="{
|
|
16
|
+
'--z-index-popover': zIndex,
|
|
17
|
+
'--bdfilter': blur ? `blur(${blur === true ? '4px' : blur})` : 'none',
|
|
18
|
+
}"
|
|
19
|
+
@click="show = false"
|
|
20
|
+
/>
|
|
21
|
+
</Transition>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style lang="scss" scoped>
|
|
25
25
|
.bg-mask {
|
|
26
26
|
position: fixed;
|
|
27
27
|
inset: 0;
|
|
@@ -39,4 +39,4 @@ const show = defineModel<boolean>('show')
|
|
|
39
39
|
.v-leave-active {
|
|
40
40
|
transition: all var(--delay);
|
|
41
41
|
}
|
|
42
|
-
</style>
|
|
42
|
+
</style>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<!-- 若使用 NuxtLink 则键盘焦点不会切换 -->
|
|
3
|
-
<a href="#main-content" class="skip-link gradient-card active">
|
|
4
|
-
跳转到主要内容 / Skip to content
|
|
5
|
-
</a>
|
|
6
|
-
</template>
|
|
7
|
-
|
|
8
|
-
<style lang="scss" scoped>
|
|
1
|
+
<template>
|
|
2
|
+
<!-- 若使用 NuxtLink 则键盘焦点不会切换 -->
|
|
3
|
+
<a href="#main-content" class="skip-link gradient-card active">
|
|
4
|
+
跳转到主要内容 / Skip to content
|
|
5
|
+
</a>
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<style lang="scss" scoped>
|
|
9
9
|
.skip-link {
|
|
10
10
|
position: fixed;
|
|
11
11
|
top: -100%;
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
.skip-link:focus {
|
|
20
20
|
top: auto;
|
|
21
21
|
}
|
|
22
|
-
</style>
|
|
22
|
+
</style>
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
const clarity = useClarityConfig()
|
|
3
|
-
const colorMode = useColorMode()
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<template>
|
|
7
|
-
<div class="theme-toggle">
|
|
8
|
-
<button
|
|
9
|
-
v-for="(themeData, themeName) in clarity.themes"
|
|
10
|
-
:key="themeName"
|
|
11
|
-
v-tip="themeData.tip"
|
|
12
|
-
:aria-label="themeData.tip"
|
|
13
|
-
:class="{ active: colorMode.preference === themeName }"
|
|
14
|
-
@click="colorMode.preference = themeName"
|
|
15
|
-
>
|
|
16
|
-
<Icon :name="themeData.icon" />
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
20
|
-
|
|
21
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const clarity = useClarityConfig()
|
|
3
|
+
const colorMode = useColorMode()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="theme-toggle">
|
|
8
|
+
<button
|
|
9
|
+
v-for="(themeData, themeName) in clarity.themes"
|
|
10
|
+
:key="themeName"
|
|
11
|
+
v-tip="themeData.tip"
|
|
12
|
+
:aria-label="themeData.tip"
|
|
13
|
+
:class="{ active: colorMode.preference === themeName }"
|
|
14
|
+
@click="colorMode.preference = themeName"
|
|
15
|
+
>
|
|
16
|
+
<Icon :name="themeData.icon" />
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<style lang="scss" scoped>
|
|
22
22
|
.theme-toggle {
|
|
23
23
|
display: flex;
|
|
24
24
|
gap: 3px;
|
|
@@ -47,4 +47,4 @@ const colorMode = useColorMode()
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
-
</style>
|
|
50
|
+
</style>
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// 即使 boolean 可选,其值也不会是 undefined
|
|
3
|
-
const props = defineProps<{
|
|
4
|
-
img?: string
|
|
5
|
-
text?: string
|
|
6
|
-
link?: string
|
|
7
|
-
round?: boolean
|
|
8
|
-
square?: boolean
|
|
9
|
-
}>()
|
|
10
|
-
|
|
11
|
-
const img = computed(() => {
|
|
12
|
-
if (props.img)
|
|
13
|
-
return props.img
|
|
14
|
-
const ghUsername = getGithubUsername(props.link)
|
|
15
|
-
if (ghUsername)
|
|
16
|
-
return getGithubAvatar(ghUsername)
|
|
17
|
-
if (props.link && isExtLink(props.link))
|
|
18
|
-
return getFavicon(getDomain(props.link))
|
|
19
|
-
return ''
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// 有图时默认为圆形样式,除非指定为方形
|
|
23
|
-
// 无图时默认为方形样式,除非指定为圆形
|
|
24
|
-
const round = computed(() => img.value ? !props.square : props.round)
|
|
25
|
-
|
|
26
|
-
const tip = computed(() => {
|
|
27
|
-
if (!props.link)
|
|
28
|
-
return ''
|
|
29
|
-
if (isExtLink(props.link))
|
|
30
|
-
return getDomain(props.link)
|
|
31
|
-
return safelyDecodeUriComponent(props.link)
|
|
32
|
-
})
|
|
33
|
-
</script>
|
|
34
|
-
|
|
35
|
-
<template>
|
|
36
|
-
<UtilLink v-tip="tip" class="badge" :class="{ round }" :to="link">
|
|
37
|
-
<NuxtImg v-if="img" class="badge-icon" :src="img" alt="" densities="1x" />
|
|
38
|
-
<span class="badge-text">
|
|
39
|
-
<slot>{{ text }}</slot>
|
|
40
|
-
</span>
|
|
41
|
-
</UtilLink>
|
|
42
|
-
</template>
|
|
43
|
-
|
|
44
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// 即使 boolean 可选,其值也不会是 undefined
|
|
3
|
+
const props = defineProps<{
|
|
4
|
+
img?: string
|
|
5
|
+
text?: string
|
|
6
|
+
link?: string
|
|
7
|
+
round?: boolean
|
|
8
|
+
square?: boolean
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const img = computed(() => {
|
|
12
|
+
if (props.img)
|
|
13
|
+
return props.img
|
|
14
|
+
const ghUsername = getGithubUsername(props.link)
|
|
15
|
+
if (ghUsername)
|
|
16
|
+
return getGithubAvatar(ghUsername)
|
|
17
|
+
if (props.link && isExtLink(props.link))
|
|
18
|
+
return getFavicon(getDomain(props.link))
|
|
19
|
+
return ''
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// 有图时默认为圆形样式,除非指定为方形
|
|
23
|
+
// 无图时默认为方形样式,除非指定为圆形
|
|
24
|
+
const round = computed(() => img.value ? !props.square : props.round)
|
|
25
|
+
|
|
26
|
+
const tip = computed(() => {
|
|
27
|
+
if (!props.link)
|
|
28
|
+
return ''
|
|
29
|
+
if (isExtLink(props.link))
|
|
30
|
+
return getDomain(props.link)
|
|
31
|
+
return safelyDecodeUriComponent(props.link)
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<UtilLink v-tip="tip" class="badge" :class="{ round }" :to="link">
|
|
37
|
+
<NuxtImg v-if="img" class="badge-icon" :src="img" alt="" densities="1x" />
|
|
38
|
+
<span class="badge-text">
|
|
39
|
+
<slot>{{ text }}</slot>
|
|
40
|
+
</span>
|
|
41
|
+
</UtilLink>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<style lang="scss" scoped>
|
|
45
45
|
// 对齐到基线,同时保持图片垂直居中
|
|
46
46
|
.badge {
|
|
47
47
|
display: inline-flex;
|
|
@@ -87,4 +87,4 @@ const tip = computed(() => {
|
|
|
87
87
|
display: none;
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
</style>
|
|
90
|
+
</style>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineProps<{
|
|
3
|
-
text?: string
|
|
4
|
-
}>()
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<span class="blur">
|
|
9
|
-
<slot>{{ text }}</slot>
|
|
10
|
-
</span>
|
|
11
|
-
</template>
|
|
12
|
-
|
|
13
|
-
<style lang="scss" scoped>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
text?: string
|
|
4
|
+
}>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<span class="blur">
|
|
9
|
+
<slot>{{ text }}</slot>
|
|
10
|
+
</span>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<style lang="scss" scoped>
|
|
14
14
|
.blur {
|
|
15
15
|
transition: filter 0.2s;
|
|
16
16
|
filter: blur(4px);
|
|
@@ -19,4 +19,4 @@ defineProps<{
|
|
|
19
19
|
filter: blur(0);
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
</style>
|
|
22
|
+
</style>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="card-list">
|
|
3
|
-
<slot />
|
|
4
|
-
</div>
|
|
5
|
-
</template>
|
|
6
|
-
|
|
7
|
-
<style lang="scss" scoped>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="card-list">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<style lang="scss" scoped>
|
|
8
8
|
:deep() {
|
|
9
9
|
:where(ol, ul):not([class]) {
|
|
10
10
|
margin: 0;
|
|
@@ -29,4 +29,4 @@
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
</style>
|
|
32
|
+
</style>
|