Initial rearrangment of vuetom site source
This commit is contained in:
10
packages/vuetom/blog/components/Page.vue
Normal file
10
packages/vuetom/blog/components/Page.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<main
|
||||
class="page">
|
||||
<div class="container">
|
||||
<slot name="top" />
|
||||
<Content class="content" />
|
||||
<slot name="bottom" />
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
126
packages/vuetom/blog/components/VPAlgoliaSearchBox.vue
Normal file
126
packages/vuetom/blog/components/VPAlgoliaSearchBox.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script setup lang="ts">
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import docsearch from '@docsearch/js'
|
||||
import { onMounted } from 'vue'
|
||||
import { useRouter, useRoute, useData } from 'vitepress'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { theme } = useData()
|
||||
|
||||
onMounted(() => {
|
||||
initialize(theme.value.algolia)
|
||||
setTimeout(poll, 16)
|
||||
})
|
||||
|
||||
function poll() {
|
||||
// programmatically open the search box after initialize
|
||||
const e = new Event('keydown') as any
|
||||
|
||||
e.key = 'k'
|
||||
e.metaKey = true
|
||||
|
||||
window.dispatchEvent(e)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!document.querySelector('.DocSearch-Modal')) {
|
||||
poll()
|
||||
}
|
||||
}, 16)
|
||||
}
|
||||
|
||||
function initialize(userOptions: DefaultTheme.AlgoliaSearchOptions) {
|
||||
// note: multi-lang search support is removed since the theme
|
||||
// doesn't support multiple locales as of now.
|
||||
const options = Object.assign({}, userOptions, {
|
||||
container: '#docsearch',
|
||||
|
||||
navigator: {
|
||||
navigate({ itemUrl }: { itemUrl: string }) {
|
||||
const { pathname: hitPathname } = new URL(
|
||||
window.location.origin + itemUrl
|
||||
)
|
||||
|
||||
// router doesn't handle same-page navigation so we use the native
|
||||
// browser location API for anchor navigation
|
||||
if (route.path === hitPathname) {
|
||||
window.location.assign(window.location.origin + itemUrl)
|
||||
} else {
|
||||
router.go(itemUrl)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
transformItems(items: any[]) {
|
||||
return items.map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
url: getRelativePath(item.url)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
hitComponent({ hit, children }: { hit: any; children: any }) {
|
||||
const relativeHit = hit.url.startsWith('http')
|
||||
? getRelativePath(hit.url as string)
|
||||
: hit.url
|
||||
|
||||
return {
|
||||
__v: null,
|
||||
type: 'a',
|
||||
ref: undefined,
|
||||
constructor: undefined,
|
||||
key: undefined,
|
||||
|
||||
props: {
|
||||
href: hit.url,
|
||||
|
||||
onClick(event: MouseEvent) {
|
||||
if (isSpecialClick(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
// we rely on the native link scrolling when user is already on
|
||||
// the right anchor because Router doesn't support duplicated
|
||||
// history entries.
|
||||
if (route.path === relativeHit) {
|
||||
return
|
||||
}
|
||||
|
||||
// if the hits goes to another page, we prevent the native link
|
||||
// behavior to leverage the Router loading feature.
|
||||
if (route.path !== relativeHit) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
router.go(relativeHit)
|
||||
},
|
||||
|
||||
children
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
docsearch(options)
|
||||
}
|
||||
|
||||
function isSpecialClick(event: MouseEvent) {
|
||||
return (
|
||||
event.button === 1 ||
|
||||
event.altKey ||
|
||||
event.ctrlKey ||
|
||||
event.metaKey ||
|
||||
event.shiftKey
|
||||
)
|
||||
}
|
||||
|
||||
function getRelativePath(absoluteUrl: string) {
|
||||
const { pathname, hash } = new URL(absoluteUrl)
|
||||
|
||||
return pathname + hash
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="docsearch" />
|
||||
</template>
|
81
packages/vuetom/blog/components/VPContent.vue
Normal file
81
packages/vuetom/blog/components/VPContent.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- @format -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useData } from 'vitepress'
|
||||
import { useSidebar } from '../composables/sidebar.js'
|
||||
import NotFound from '../layouts/NotFound.vue'
|
||||
import VTHome from './VTHome.vue'
|
||||
import VTDoc from './article/VTDoc.vue'
|
||||
import VTDocList from './article/VTDocList.vue'
|
||||
import VTSidebar from './sidebar/VTSidebar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const { frontmatter } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPContent"
|
||||
id="VPContent"
|
||||
:class="{
|
||||
'has-sidebar': hasSidebar,
|
||||
'is-home': frontmatter.layout === 'home',
|
||||
}"
|
||||
>
|
||||
<NotFound v-if="route.component === NotFound" />
|
||||
<VTHome
|
||||
v-else-if="frontmatter.layout === 'home' || frontmatter.layout === 'doc'"
|
||||
>
|
||||
<template #sidebar><VTSidebar /></template>
|
||||
<template #doclist>
|
||||
<!-- 文章列表 -->
|
||||
<VTDocList v-if="frontmatter.layout === 'home'" />
|
||||
</template>
|
||||
<template #docone>
|
||||
<!-- 单个文章 -->
|
||||
<VTDoc v-if="frontmatter.layout === 'doc'">
|
||||
<Content />
|
||||
</VTDoc>
|
||||
</template>
|
||||
</VTHome>
|
||||
|
||||
<div v-else>
|
||||
<Content></Content>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPContent {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.VPContent.is-home {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.VPContent {
|
||||
padding-top: var(--vp-nav-height);
|
||||
}
|
||||
|
||||
.VPContent.has-sidebar {
|
||||
margin: 0;
|
||||
padding-left: var(--vp-sidebar-width);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.VPContent.has-sidebar {
|
||||
padding-right: calc((100vw - var(--vp-layout-max-width)) / 2);
|
||||
padding-left: calc(
|
||||
(100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width)
|
||||
);
|
||||
}
|
||||
}
|
||||
</style>
|
156
packages/vuetom/blog/components/VPFlyout.vue
Normal file
156
packages/vuetom/blog/components/VPFlyout.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { useFlyout } from '../composables/flyout.js'
|
||||
import VPIconChevronDown from './icons/VPIconChevronDown.vue'
|
||||
import VPIconMoreHorizontal from './icons/VPIconMoreHorizontal.vue'
|
||||
import VPMenu from './VPMenu.vue'
|
||||
|
||||
defineProps<{
|
||||
icon?: any
|
||||
button?: string
|
||||
label?: string
|
||||
items?: any[]
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
useFlyout({ el, onBlur })
|
||||
|
||||
function onBlur() {
|
||||
open.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VPFlyout"
|
||||
ref="el"
|
||||
@mouseenter="open = true"
|
||||
@mouseleave="open = false"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="button"
|
||||
aria-haspopup="true"
|
||||
:aria-expanded="open"
|
||||
:aria-label="label"
|
||||
@click="open = !open"
|
||||
>
|
||||
<span v-if="button || icon" class="text">
|
||||
<component v-if="icon" :is="icon" class="option-icon" />
|
||||
{{ button }}
|
||||
<VPIconChevronDown class="text-icon" />
|
||||
</span>
|
||||
|
||||
<VPIconMoreHorizontal v-else class="icon" />
|
||||
</button>
|
||||
|
||||
<div class="menu">
|
||||
<VPMenu :items="items">
|
||||
<slot />
|
||||
</VPMenu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPFlyout {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.VPFlyout:hover {
|
||||
color: var(--vp-c-bland);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.VPFlyout:hover .text {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VPFlyout:hover .icon {
|
||||
fill: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VPFlyout.active .text {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.VPFlyout.active:hover .text {
|
||||
color: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.VPFlyout:hover .menu,
|
||||
.button[aria-expanded="true"] + .menu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
height: var(--vp-nav-height-mobile);
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.5s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.button {
|
||||
height: var(--vp-nav-height-desktop);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: var(--vp-nav-height-mobile);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.text {
|
||||
line-height: var(--vp-nav-height-desktop);
|
||||
}
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
margin-right: 0px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.text-icon {
|
||||
margin-left: 4px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: currentColor;
|
||||
transition: fill 0.25s;
|
||||
}
|
||||
|
||||
.menu {
|
||||
position: absolute;
|
||||
top: calc(var(--vp-nav-height-mobile) / 2 + 20px);
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.25s, visibility 0.25s, transform 0.25s;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.menu {
|
||||
top: calc(var(--vp-nav-height-desktop) / 2 + 20px);
|
||||
}
|
||||
}
|
||||
</style>
|
84
packages/vuetom/blog/components/VPFooter.vue
Normal file
84
packages/vuetom/blog/components/VPFooter.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import { useSidebar } from '../composables/sidebar.js'
|
||||
import VTLeftButton from './VTLeftButton.vue'
|
||||
|
||||
const { theme } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const brandColor = ref('')
|
||||
const colorRef = ref(null)
|
||||
const changeBrandColor = (color: string) => {
|
||||
brandColor.value = color
|
||||
document.documentElement.style.setProperty('--vp-c-brand', color)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer
|
||||
v-if="theme.footer"
|
||||
class="VTFooter relative left-0 right-0 bottom-0 container mx-auto w-full pb-8"
|
||||
:class="{ 'has-sidebar': hasSidebar }"
|
||||
>
|
||||
<!-- <div class="grid grid-cols-4 gap-10"> -->
|
||||
<div class="flex">
|
||||
<div
|
||||
id="VTFooterLeft"
|
||||
class="rounded-vt hidden w-64 px-4 flex-none md:block">
|
||||
<VTLeftButton>
|
||||
<div ref="colorRef">
|
||||
<span @click="changeBrandColor('red')">red</span>
|
||||
<span @click="changeBrandColor('green')" >green</span>
|
||||
</div>
|
||||
</VTLeftButton>
|
||||
</div>
|
||||
<div
|
||||
id="VTFooterMiddle"
|
||||
class="rounded-vt w-full mt-8 px-4 md:w-1/2 flex-grow">
|
||||
<div class="rounded-vt bg-cbg w-4/5 mx-auto text-center py-8">
|
||||
<p class="message">{{ theme.footer.message }}</p>
|
||||
<p class="copyright">{{ theme.footer.copyright }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* .VPFooter {
|
||||
position: absolute;
|
||||
z-index: var(--vp-z-index-footer);
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding: 32px 24px;
|
||||
background-color: var(--vp-c-bg);
|
||||
width: 80%;
|
||||
left: 10%;
|
||||
border-radius: 10px;
|
||||
bottom: 20px;
|
||||
} */
|
||||
|
||||
.VPFooter.has-sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VPFooter {
|
||||
/* padding: 32px; */
|
||||
}
|
||||
}
|
||||
|
||||
.message,
|
||||
.copyright {
|
||||
line-height: 24px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.message {
|
||||
order: 2;
|
||||
}
|
||||
.copyright {
|
||||
order: 1;
|
||||
}
|
||||
</style>
|
38
packages/vuetom/blog/components/VPImage.vue
Normal file
38
packages/vuetom/blog/components/VPImage.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script setup lang="ts">
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import { withBase } from 'vitepress'
|
||||
|
||||
defineProps<{
|
||||
image: DefaultTheme.ThemeableImage
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
inheritAttrs: false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="image">
|
||||
<img
|
||||
v-if="typeof image === 'string' || 'src' in image"
|
||||
class="VPImage"
|
||||
v-bind="typeof image === 'string' ? $attrs : { ...image, ...$attrs }"
|
||||
:src="withBase(typeof image === 'string' ? image : image.src)"
|
||||
/>
|
||||
<template v-else>
|
||||
<VPImage class="dark" :image="image.dark" v-bind="$attrs" />
|
||||
<VPImage class="light" :image="image.light" v-bind="$attrs" />
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
html:not(.dark) .VPImage.dark {
|
||||
display: none;
|
||||
}
|
||||
.dark .VPImage.light {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
38
packages/vuetom/blog/components/VPLink.vue
Normal file
38
packages/vuetom/blog/components/VPLink.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { normalizeLink } from '../support/utils.js'
|
||||
import VPIconExternalLink from './icons/VPIconExternalLink.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
href?: string
|
||||
noIcon?: boolean
|
||||
}>()
|
||||
|
||||
const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="href ? 'a' : 'span'"
|
||||
class="VPLink"
|
||||
:class="{ link: href }"
|
||||
:href="href ? normalizeLink(href) : undefined"
|
||||
:target="isExternal ? '_blank' : undefined"
|
||||
:rel="isExternal ? 'noopener noreferrer' : undefined"
|
||||
>
|
||||
<slot />
|
||||
<VPIconExternalLink v-if="isExternal && !noIcon" class="icon" />
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.icon {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
margin-left: 4px;
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
fill: var(--vp-c-text-3);
|
||||
transition: fill 0.25s;
|
||||
}
|
||||
</style>
|
74
packages/vuetom/blog/components/VPMenu.vue
Normal file
74
packages/vuetom/blog/components/VPMenu.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script lang="ts" setup>
|
||||
import VPMenuLink from './VPMenuLink.vue'
|
||||
import VPMenuGroup from './VPMenuGroup.vue'
|
||||
|
||||
defineProps<{
|
||||
items?: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPMenu">
|
||||
<div v-if="items" class="items">
|
||||
<template v-for="item in items" :key="item.text">
|
||||
<VPMenuLink v-if="'link' in item" :item="item" />
|
||||
<VPMenuGroup v-else :text="item.text" :items="item.items" />
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPMenu {
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
min-width: 128px;
|
||||
border: 1px solid var(--vp-c-divider-light);
|
||||
background-color: var(--vp-c-bg);
|
||||
box-shadow: var(--vp-shadow-3);
|
||||
transition: background-color 0.5s;
|
||||
}
|
||||
|
||||
.dark .VPMenu {
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
}
|
||||
|
||||
.VPMenu :deep(.group) {
|
||||
margin: 0 -12px;
|
||||
padding: 0 12px 12px;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.group + .group) {
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding: 11px 12px 12px;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.group:last-child) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.group + .item) {
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding: 11px 16px 0;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.item) {
|
||||
padding: 0 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.label) {
|
||||
flex-grow: 1;
|
||||
line-height: 28px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color .5s;
|
||||
}
|
||||
|
||||
.VPMenu :deep(.action) {
|
||||
padding-left: 24px;
|
||||
}
|
||||
</style>
|
46
packages/vuetom/blog/components/VPMenuGroup.vue
Normal file
46
packages/vuetom/blog/components/VPMenuGroup.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import VPMenuLink from './VPMenuLink.vue'
|
||||
|
||||
defineProps<{
|
||||
text?: string
|
||||
items: any[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPMenuGroup">
|
||||
<p v-if="text" class="title">{{ text }}</p>
|
||||
|
||||
<template v-for="item in items">
|
||||
<VPMenuLink v-if="'link' in item" :item="item" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPMenuGroup {
|
||||
margin: 12px -12px 0;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding: 12px 12px 0;
|
||||
}
|
||||
|
||||
.VPMenuGroup:first-child {
|
||||
margin-top: 0;
|
||||
border-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.VPMenuGroup + .VPMenuGroup {
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 0 12px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
</style>
|
55
packages/vuetom/blog/components/VPMenuLink.vue
Normal file
55
packages/vuetom/blog/components/VPMenuLink.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts" setup>
|
||||
import { useData } from 'vitepress'
|
||||
import { isActive } from '../support/utils.js'
|
||||
import VPLink from './VPLink.vue'
|
||||
|
||||
defineProps<{
|
||||
item: any
|
||||
}>()
|
||||
|
||||
const { page } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPMenuLink">
|
||||
<VPLink
|
||||
:class="{ active: isActive(page.relativePath, item.activeMatch || item.link) }"
|
||||
:href="item.link"
|
||||
>
|
||||
{{ item.text }}
|
||||
</VPLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPMenuGroup + .VPMenuLink {
|
||||
margin: 12px -12px 0;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding: 12px 12px 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: block;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
line-height: 32px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--vp-c-text-1);
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.25s, color 0.25s;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
color: var(--vp-c-brand);
|
||||
background-color: var(--vp-c-bg-mute);
|
||||
}
|
||||
|
||||
.dark .link:hover {
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.link.active {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
</style>
|
63
packages/vuetom/blog/components/VPSocialLink.vue
Normal file
63
packages/vuetom/blog/components/VPSocialLink.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import VPIconDiscord from './icons/VPIconDiscord.vue'
|
||||
import VPIconFacebook from './icons/VPIconFacebook.vue'
|
||||
import VPIconGitHub from './icons/VPIconGitHub.vue'
|
||||
import VPIconLinkedIn from './icons/VPIconLinkedIn.vue'
|
||||
import VPIconInstagram from './icons/VPIconInstagram.vue'
|
||||
import VPIconSlack from './icons/VPIconSlack.vue'
|
||||
import VPIconTwitter from './icons/VPIconTwitter.vue'
|
||||
import VPIconYouTube from './icons/VPIconYouTube.vue'
|
||||
|
||||
defineProps<{
|
||||
icon: DefaultTheme.SocialLinkIcon
|
||||
link: string
|
||||
}>()
|
||||
|
||||
const icons = {
|
||||
discord: VPIconDiscord,
|
||||
facebook: VPIconFacebook,
|
||||
github: VPIconGitHub,
|
||||
instagram: VPIconInstagram,
|
||||
linkedin: VPIconLinkedIn,
|
||||
slack: VPIconSlack,
|
||||
twitter: VPIconTwitter,
|
||||
youtube: VPIconYouTube
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a
|
||||
class="VPSocialLink"
|
||||
:href="link"
|
||||
:title="icon"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<component :is="icons[icon]" class="icon" />
|
||||
<span class="visually-hidden">{{ icon }}</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPSocialLink {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color .5s;
|
||||
}
|
||||
|
||||
.VPSocialLink:hover {
|
||||
color: var(--vp-c-text-1);
|
||||
transition: color .25s;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
27
packages/vuetom/blog/components/VPSocialLinks.vue
Normal file
27
packages/vuetom/blog/components/VPSocialLinks.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script lang="ts" setup>
|
||||
import type { DefaultTheme } from 'vitepress/theme'
|
||||
import VPSocialLink from './VPSocialLink.vue'
|
||||
|
||||
defineProps<{
|
||||
links: DefaultTheme.SocialLink[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VPSocialLinks">
|
||||
<VPSocialLink
|
||||
v-for="{ link, icon } in links"
|
||||
:key="link"
|
||||
:icon="icon"
|
||||
:link="link"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPSocialLinks {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
87
packages/vuetom/blog/components/VTBackground.vue
Normal file
87
packages/vuetom/blog/components/VTBackground.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed, reactive } from 'vue'
|
||||
import { useMediaQuery, useParallax } from '@vueuse/core'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { useHomeParallax } from '../composables/homebg.js'
|
||||
|
||||
const { parallaxEnable } = useHomeParallax()
|
||||
|
||||
onMounted(() => {})
|
||||
|
||||
const parallaxRef = ref(null)
|
||||
const isMobile = useMediaQuery('(max-width: 700px)')
|
||||
const parallax = reactive(useParallax(parallaxRef))
|
||||
const cardWindowStyle: CSSProperties = {
|
||||
overflow: 'hidden',
|
||||
fontSize: '1rem',
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
}
|
||||
const layerBase: CSSProperties = {
|
||||
position: 'absolute',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
transition: '.3s ease-out all',
|
||||
}
|
||||
const layerBg = computed(() => ({
|
||||
...layerBase,
|
||||
transform: `translateX(${parallax.tilt * 10}px) translateY(${
|
||||
parallax.roll * 10
|
||||
}px) scale(1.1)`,
|
||||
}))
|
||||
const layerCloud = computed(() => ({
|
||||
...layerBase,
|
||||
transform: `translateX(${parallax.tilt * 200}px) translateY(${
|
||||
parallax.roll * 30 - 100
|
||||
}px) scale(1)`,
|
||||
}))
|
||||
const layerHuman = computed(() => ({
|
||||
...layerBase,
|
||||
transform: `translateX(${parallax.tilt * -50}px) translateY(${
|
||||
parallax.roll * 30
|
||||
}px) scale(1.2)`,
|
||||
}))
|
||||
const layerGrass = computed(() => ({
|
||||
...layerBase,
|
||||
transform: `translateX(${parallax.tilt * -200}px) translateY(${
|
||||
parallax.roll * 100 - 120
|
||||
}px) scale(1.5)`,
|
||||
}))
|
||||
const cardStyle = computed(() => ({
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
background: '#00000000',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
transition: '.3s ease-out all',
|
||||
boxShadow: '0 0 20px 0 rgba(255, 255, 255, 0.25)',
|
||||
|
||||
// transform: `rotateX(${parallax.roll * 20}deg) rotateY(${
|
||||
// parallax.tilt * 20
|
||||
// }deg)`
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTBackgroud" ref="parallaxRef">
|
||||
<div v-if="parallaxEnable" :style="cardStyle">
|
||||
<div :style="cardWindowStyle">
|
||||
<img :style="layerBg" src="/imgs/blog-bg-cloud.png" alt="" />
|
||||
<img :style="layerCloud" src="/imgs/cloud2.png" alt="" />
|
||||
<img :style="layerHuman" src="/imgs/blog-bg-human.png" alt="" />
|
||||
<img :style="layerGrass" src="/imgs/blog-bg-grass.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VTBackground {
|
||||
}
|
||||
</style>
|
93
packages/vuetom/blog/components/VTFloat.vue
Normal file
93
packages/vuetom/blog/components/VTFloat.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import VPSwitchAppearance from './switch/VPSwitchAppearance.vue'
|
||||
|
||||
defineProps<{
|
||||
icon?: any
|
||||
button?: string
|
||||
label?: string
|
||||
items?: any[]
|
||||
}>()
|
||||
|
||||
const open = ref(false)
|
||||
const el = ref<HTMLElement>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VTFloat"
|
||||
ref="el"
|
||||
@mouseenter="open = true"
|
||||
@mouseleave="open = false"
|
||||
>
|
||||
<div class="box hover:animate-wiggle">
|
||||
<VPSwitchAppearance />
|
||||
</div>
|
||||
|
||||
<div class="box hover:animate-wiggle">
|
||||
Set
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VTFloat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 100px;
|
||||
width: 60px;
|
||||
height: max-content;
|
||||
height: -moz-max-content;
|
||||
z-index: 1000;
|
||||
transition: all .3s ease;
|
||||
}
|
||||
|
||||
.VTFloat:hover {
|
||||
color: var(--vp-c-bland);
|
||||
transition: color 0.25s;
|
||||
}
|
||||
|
||||
.VTFloat:hover .text {
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VTFloat:hover .icon {
|
||||
fill: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VTFloat.active .text {
|
||||
color: var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.VTFloat.active:hover .text {
|
||||
color: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 5px;
|
||||
transition: opacity 0.25s, border-color 0.5s, transform 0.25s;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--vp-c-divider-light);
|
||||
background-color: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.box:hover {
|
||||
border-color: var(--vp-c-gray);
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.menu {
|
||||
top: calc(var(--vp-nav-height-desktop) / 2 + 20px);
|
||||
}
|
||||
}
|
||||
</style>
|
60
packages/vuetom/blog/components/VTHome.vue
Normal file
60
packages/vuetom/blog/components/VTHome.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import VPFooter from './VPFooter.vue'
|
||||
|
||||
onMounted(() => {
|
||||
const homeDown = document.getElementById('home-down')
|
||||
const top = homeDown.clientHeight - 110
|
||||
window.scroll({
|
||||
top,
|
||||
left: 0,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTHome">
|
||||
<div id="home-down" class="min-h-eight relative">
|
||||
<div :class="[
|
||||
'absolute bottom-5 left-0 right-0 animate-bounce ',
|
||||
'mx-auto w-10 text-3xl text-white'
|
||||
]">
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="VTContainer" class="container mx-auto w-11/12 pb-8">
|
||||
<div class="flex">
|
||||
<!-- <div class="grid grid-cols-4 gap-10"> -->
|
||||
<div id="VTLeft" :class="[
|
||||
'rounded-vt hidden w-64 px-4 flex-none',
|
||||
'md:block'
|
||||
]">
|
||||
<slot name="sidebar"></slot>
|
||||
</div>
|
||||
<div id="VTContent" class="rounded-vt w-full px-4 md:w-1/2 flex-grow">
|
||||
<slot name="doclist"></slot>
|
||||
<slot name="docone"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<VPFooter />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VTHome {
|
||||
}
|
||||
|
||||
.VTHome :deep(.VTHomeSponsors) {
|
||||
margin-top: 112px;
|
||||
margin-bottom: -128px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.VTHome {
|
||||
}
|
||||
}
|
||||
.home-vtp-btn-up {
|
||||
}
|
||||
</style>
|
383
packages/vuetom/blog/components/VTLeftButton.vue
Normal file
383
packages/vuetom/blog/components/VTLeftButton.vue
Normal file
@@ -0,0 +1,383 @@
|
||||
<template>
|
||||
<div class="vt-btn-up-wrapper">
|
||||
<input type="checkbox" />
|
||||
<div class="btn"></div>
|
||||
<div class="tooltip ">
|
||||
<slot>
|
||||
</slot>
|
||||
</div>
|
||||
<svg>
|
||||
<use xlink:href="#shape-01" class="shape shape-01" />
|
||||
<use xlink:href="#shape-02" class="shape shape-02" />
|
||||
<use xlink:href="#shape-03" class="shape shape-03" />
|
||||
<use xlink:href="#shape-04" class="shape shape-04" />
|
||||
<use xlink:href="#shape-05" class="shape shape-05" />
|
||||
<use xlink:href="#shape-06" class="shape shape-06" />
|
||||
<use xlink:href="#shape-07" class="shape shape-07" />
|
||||
<use xlink:href="#shape-08" class="shape shape-08" />
|
||||
<use xlink:href="#shape-09" class="shape shape-09" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vt-btn-up-wrapper {
|
||||
--background: #62abff;
|
||||
--icon-color: #414856;
|
||||
--shape-color-01: #b8cbee;
|
||||
--shape-color-02: #7691e8;
|
||||
--shape-color-03: #fdd053;
|
||||
--width: 40px;
|
||||
--height: 40px;
|
||||
--border-radius: var(--height);
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
position: relative;
|
||||
border-radius: var(--border-radius);
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
.vt-btn-up-wrapper .btn {
|
||||
background: var(--background);
|
||||
width: var(--width);
|
||||
height: var(--height);
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 10px 30px rgba(65, 72, 86, 0.05);
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
-webkit-box-pack: center;
|
||||
justify-content: center;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-animation: plus-animation-reverse 0.5s ease-out forwards;
|
||||
animation: plus-animation-reverse 0.5s ease-out forwards;
|
||||
}
|
||||
.vt-btn-up-wrapper .btn::before,
|
||||
.vt-btn-up-wrapper .btn::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
}
|
||||
.vt-btn-up-wrapper .btn::before {
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
}
|
||||
.vt-btn-up-wrapper .btn::after {
|
||||
width: 14px;
|
||||
height: 4px;
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip {
|
||||
/* width: 90px;
|
||||
height: 75px; */
|
||||
border-radius: 50px;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
padding: 0 15px;
|
||||
box-shadow: 0 10px 30px rgba(65, 72, 86, 0.1);
|
||||
opacity: 0;
|
||||
top: 0;
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
-webkit-transition: opacity 0.15s ease-in, top 0.15s ease-in,
|
||||
width 0.15s ease-in;
|
||||
transition: opacity 0.15s ease-in, top 0.15s ease-in, width 0.15s ease-in;
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip:hover {
|
||||
box-shadow: 0 10px 30px rgba(65, 72, 86, 0.2);
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip > svg {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
display: -webkit-box;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
-webkit-box-align: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip > svg .icon {
|
||||
fill: none;
|
||||
stroke: var(--icon-color);
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
opacity: 0.4;
|
||||
-webkit-transition: opacity 0.3s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip > svg:hover .icon {
|
||||
opacity: 1;
|
||||
}
|
||||
.vt-btn-up-wrapper .tooltip::after {
|
||||
content: '';
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: #fff;
|
||||
border-radius: 3px;
|
||||
position: absolute;
|
||||
left: 2.6rem;
|
||||
bottom: -0.5rem;
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
z-index: 0;
|
||||
}
|
||||
.vt-btn-up-wrapper > svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
}
|
||||
.vt-btn-up-wrapper > svg .shape {
|
||||
fill: none;
|
||||
stroke: none;
|
||||
stroke-width: 3px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
-webkit-transform-origin: 50% 20%;
|
||||
transform-origin: 50% 20%;
|
||||
}
|
||||
.vt-btn-up-wrapper input {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--border-radius);
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg {
|
||||
-webkit-animation: pang-animation 1.2s ease-out forwards;
|
||||
animation: pang-animation 1.2s ease-out forwards;
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(1) {
|
||||
-webkit-transform: translate(-17px, 30%) rotate(40deg);
|
||||
transform: translate(-17px, 30%) rotate(40deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(2) {
|
||||
-webkit-transform: translate(15px, 30%) rotate(80deg);
|
||||
transform: translate(15px, 30%) rotate(80deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(3) {
|
||||
-webkit-transform: translate(11px, 30%) rotate(120deg);
|
||||
transform: translate(11px, 30%) rotate(120deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(4) {
|
||||
-webkit-transform: translate(20px, 30%) rotate(160deg);
|
||||
transform: translate(20px, 30%) rotate(160deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(5) {
|
||||
-webkit-transform: translate(-20px, 30%) rotate(200deg);
|
||||
transform: translate(-20px, 30%) rotate(200deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(6) {
|
||||
-webkit-transform: translate(10px, 30%) rotate(240deg);
|
||||
transform: translate(10px, 30%) rotate(240deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(7) {
|
||||
-webkit-transform: translate(3px, 30%) rotate(280deg);
|
||||
transform: translate(3px, 30%) rotate(280deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(8) {
|
||||
-webkit-transform: translate(0px, 30%) rotate(320deg);
|
||||
transform: translate(0px, 30%) rotate(320deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ svg .shape:nth-of-type(9) {
|
||||
-webkit-transform: translate(25px, 30%) rotate(360deg);
|
||||
transform: translate(25px, 30%) rotate(360deg);
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ .btn {
|
||||
-webkit-animation: plus-animation 0.5s ease-out forwards;
|
||||
animation: plus-animation 0.5s ease-out forwards;
|
||||
}
|
||||
.vt-btn-up-wrapper input:checked ~ .tooltip {
|
||||
width: 20rem;
|
||||
height: 20rem;
|
||||
-webkit-animation: stretch-animation 1s ease-out forwards 0.15s;
|
||||
animation: stretch-animation 1s ease-out forwards 0.15s;
|
||||
top: -22rem;
|
||||
left: -2rem;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@-webkit-keyframes pang-animation {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pang-animation {
|
||||
0% {
|
||||
-webkit-transform: scale(0);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
-webkit-transform: scale(1);
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes plus-animation {
|
||||
0% {
|
||||
-webkit-transform: rotate(0) scale(1);
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
20% {
|
||||
-webkit-transform: rotate(60deg) scale(0.93);
|
||||
transform: rotate(60deg) scale(0.93);
|
||||
}
|
||||
55% {
|
||||
-webkit-transform: rotate(35deg) scale(0.97);
|
||||
transform: rotate(35deg) scale(0.97);
|
||||
}
|
||||
80% {
|
||||
-webkit-transform: rotate(48deg) scale(0.94);
|
||||
transform: rotate(48deg) scale(0.94);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(45deg) scale(0.95);
|
||||
transform: rotate(45deg) scale(0.95);
|
||||
}
|
||||
}
|
||||
@keyframes plus-animation {
|
||||
0% {
|
||||
-webkit-transform: rotate(0) scale(1);
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
20% {
|
||||
-webkit-transform: rotate(60deg) scale(0.93);
|
||||
transform: rotate(60deg) scale(0.93);
|
||||
}
|
||||
55% {
|
||||
-webkit-transform: rotate(35deg) scale(0.97);
|
||||
transform: rotate(35deg) scale(0.97);
|
||||
}
|
||||
80% {
|
||||
-webkit-transform: rotate(48deg) scale(0.94);
|
||||
transform: rotate(48deg) scale(0.94);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(45deg) scale(0.95);
|
||||
transform: rotate(45deg) scale(0.95);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes plus-animation-reverse {
|
||||
0% {
|
||||
-webkit-transform: rotate(45deg) scale(0.95);
|
||||
transform: rotate(45deg) scale(0.95);
|
||||
}
|
||||
20% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
55% {
|
||||
-webkit-transform: rotate(10deg);
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
80% {
|
||||
-webkit-transform: rotate(-3deg);
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(0) scale(1);
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes plus-animation-reverse {
|
||||
0% {
|
||||
-webkit-transform: rotate(45deg) scale(0.95);
|
||||
transform: rotate(45deg) scale(0.95);
|
||||
}
|
||||
20% {
|
||||
-webkit-transform: rotate(-15deg);
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
55% {
|
||||
-webkit-transform: rotate(10deg);
|
||||
transform: rotate(10deg);
|
||||
}
|
||||
80% {
|
||||
-webkit-transform: rotate(-3deg);
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(0) scale(1);
|
||||
transform: rotate(0) scale(1);
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes stretch-animation {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
10% {
|
||||
-webkit-transform: scale(1.1, 0.9);
|
||||
transform: scale(1.1, 0.9);
|
||||
}
|
||||
30% {
|
||||
-webkit-transform: scale(0.9, 1.1);
|
||||
transform: scale(0.9, 1.1);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1.05, 0.95);
|
||||
transform: scale(1.05, 0.95);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
@keyframes stretch-animation {
|
||||
0% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
10% {
|
||||
-webkit-transform: scale(1.1, 0.9);
|
||||
transform: scale(1.1, 0.9);
|
||||
}
|
||||
30% {
|
||||
-webkit-transform: scale(0.9, 1.1);
|
||||
transform: scale(0.9, 1.1);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: scale(1.05, 0.95);
|
||||
transform: scale(1.05, 0.95);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: scale(1, 1);
|
||||
transform: scale(1, 1);
|
||||
}
|
||||
}
|
||||
</style>
|
11
packages/vuetom/blog/components/article/VTDoc.vue
Normal file
11
packages/vuetom/blog/components/article/VTDoc.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="bg-cbg rounded-vt shadow-vt p-10">
|
||||
<div class="vp-doc">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
56
packages/vuetom/blog/components/article/VTDocCard.vue
Normal file
56
packages/vuetom/blog/components/article/VTDocCard.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { normalizeLink, fmt } from '../../support/utils.js'
|
||||
|
||||
const props = defineProps<{
|
||||
data: any
|
||||
}>()
|
||||
|
||||
const f = props.data.data.frontmatter
|
||||
|
||||
const tags = f.tags.split(',')
|
||||
|
||||
const timeStr = fmt(f.time)
|
||||
|
||||
const togo = (url: string) => {
|
||||
if (!url) url = ''
|
||||
window.location.href = normalizeLink(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTDocCard">
|
||||
<div class="rounded-vt h-80 w-full mb-6 bg-gray-700" @click="togo(data.path)">
|
||||
<div class="pic h-1/2 text-white relative text-center">
|
||||
<div class="title absolute w-4/5 bottom-2 left-0 right-0 mx-auto">
|
||||
<h1 class="font-black text-xl antialiased tracking-wide">{{f.title}}</h1>
|
||||
<p class="m-1">{{timeStr}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text h-1/2 bg-cbg rounded-vt px-10 py-6">
|
||||
<div class="desc h-3/4">
|
||||
<!-- {{data}} -->
|
||||
</div>
|
||||
<div class="tag h-1/4">
|
||||
<span class="inline-block w-24 font-bold
|
||||
hover:text-brand transition-colors duration-300">
|
||||
<i class="fa fa-star-half-o" aria-hidden="true"></i>
|
||||
|
||||
{{f.categories}}
|
||||
</span>
|
||||
<span>
|
||||
<i class="fa fa-tags" aria-hidden="true"></i>
|
||||
<span
|
||||
class="px-1.5 m-1 inline-block text-center
|
||||
bg-cbgs border-2 border-cbgs rounded-vt
|
||||
hover:border-brand transition-colors duration-300
|
||||
"
|
||||
v-for="t in tags" :key="t">{{t}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
18
packages/vuetom/blog/components/article/VTDocList.vue
Normal file
18
packages/vuetom/blog/components/article/VTDocList.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import { getRoutes } from '../../../support/pages.js'
|
||||
import VTDocCard from './VTDocCard.vue'
|
||||
|
||||
const { routes } = getRoutes()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTDoc">
|
||||
<div v-for="(r,i) in routes" :key="r.__hmrId">
|
||||
<VTDocCard :data="r" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M21,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,11,21,11z" />
|
||||
<path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z" />
|
||||
<path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z" />
|
||||
<path d="M21,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,19,21,19z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z" />
|
||||
<path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z" />
|
||||
<path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z" />
|
||||
<path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M21,11H7c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S21.6,11,21,11z" />
|
||||
<path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z" />
|
||||
<path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z" />
|
||||
<path d="M21,19H7c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S21.6,19,21,19z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19,11H7.4l5.3-5.3c0.4-0.4,0.4-1,0-1.4s-1-0.4-1.4,0l-7,7c-0.1,0.1-0.2,0.2-0.2,0.3c-0.1,0.2-0.1,0.5,0,0.8c0.1,0.1,0.1,0.2,0.2,0.3l7,7c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3c0.4-0.4,0.4-1,0-1.4L7.4,13H19c0.6,0,1-0.4,1-1S19.6,11,19,11z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M15,19c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4l6-6c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4L10.4,12l5.3,5.3c0.4,0.4,0.4,1,0,1.4C15.5,18.9,15.3,19,15,19z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M9,19c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l5.3-5.3L8.3,6.7c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4l-6,6C9.5,18.9,9.3,19,9,19z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M18,16c-0.3,0-0.5-0.1-0.7-0.3L12,10.4l-5.3,5.3c-0.4,0.4-1,0.4-1.4,0s-0.4-1,0-1.4l6-6c0.4-0.4,1-0.4,1.4,0l6,6c0.4,0.4,0.4,1,0,1.4C18.5,15.9,18.3,16,18,16z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconDiscord.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconDiscord.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M20.222 0c1.406 0 2.54 1.137 2.607 2.475V24l-2.677-2.273-1.47-1.338-1.604-1.398.67 2.205H3.71c-1.402 0-2.54-1.065-2.54-2.476V2.48C1.17 1.142 2.31.003 3.715.003h16.5L20.222 0zm-6.118 5.683h-.03l-.202.2c2.073.6 3.076 1.537 3.076 1.537-1.336-.668-2.54-1.002-3.744-1.137-.87-.135-1.74-.064-2.475 0h-.2c-.47 0-1.47.2-2.81.735-.467.203-.735.336-.735.336s1.002-1.002 3.21-1.537l-.135-.135s-1.672-.064-3.477 1.27c0 0-1.805 3.144-1.805 7.02 0 0 1 1.74 3.743 1.806 0 0 .4-.533.805-1.002-1.54-.468-2.14-1.404-2.14-1.404s.134.066.335.2h.06c.03 0 .044.015.06.03v.006c.016.016.03.03.06.03.33.136.66.27.93.4.466.202 1.065.403 1.8.536.93.135 1.996.2 3.21 0 .6-.135 1.2-.267 1.8-.535.39-.2.87-.4 1.397-.737 0 0-.6.936-2.205 1.404.33.466.795 1 .795 1 2.744-.06 3.81-1.8 3.87-1.726 0-3.87-1.815-7.02-1.815-7.02-1.635-1.214-3.165-1.26-3.435-1.26l.056-.02zm.168 4.413c.703 0 1.27.6 1.27 1.335 0 .74-.57 1.34-1.27 1.34-.7 0-1.27-.6-1.27-1.334.002-.74.573-1.338 1.27-1.338zm-4.543 0c.7 0 1.266.6 1.266 1.335 0 .74-.57 1.34-1.27 1.34-.7 0-1.27-.6-1.27-1.334 0-.74.57-1.338 1.27-1.338z" />
|
||||
</svg>
|
||||
</template>
|
6
packages/vuetom/blog/components/icons/VPIconEdit.vue
Normal file
6
packages/vuetom/blog/components/icons/VPIconEdit.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z" />
|
||||
<path d="M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z" />
|
||||
</svg>
|
||||
</template>
|
13
packages/vuetom/blog/components/icons/VPIconExternalLink.vue
Normal file
13
packages/vuetom/blog/components/icons/VPIconExternalLink.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
focusable="false"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
width="24px"
|
||||
>
|
||||
<path d="M0 0h24v24H0V0z" fill="none" />
|
||||
<path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconFacebook.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconFacebook.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconGitHub.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconGitHub.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconHeart.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconHeart.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M12,22.2c-0.3,0-0.5-0.1-0.7-0.3l-8.8-8.8c-2.5-2.5-2.5-6.7,0-9.2c2.5-2.5,6.7-2.5,9.2,0L12,4.3l0.4-0.4c0,0,0,0,0,0C13.6,2.7,15.2,2,16.9,2c0,0,0,0,0,0c1.7,0,3.4,0.7,4.6,1.9l0,0c1.2,1.2,1.9,2.9,1.9,4.6c0,1.7-0.7,3.4-1.9,4.6l-8.8,8.8C12.5,22.1,12.3,22.2,12,22.2zM7,4C5.9,4,4.7,4.4,3.9,5.3c-1.8,1.8-1.8,4.6,0,6.4l8.1,8.1l8.1-8.1c0.9-0.9,1.3-2,1.3-3.2c0-1.2-0.5-2.3-1.3-3.2l0,0C19.3,4.5,18.2,4,17,4c0,0,0,0,0,0c-1.2,0-2.3,0.5-3.2,1.3c0,0,0,0,0,0l-1.1,1.1c-0.4,0.4-1,0.4-1.4,0l-1.1-1.1C9.4,4.4,8.2,4,7,4z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678c-3.405 0-6.162 2.76-6.162 6.162 0 3.405 2.76 6.162 6.162 6.162 3.405 0 6.162-2.76 6.162-6.162 0-3.405-2.76-6.162-6.162-6.162zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405c0 .795-.646 1.44-1.44 1.44-.795 0-1.44-.646-1.44-1.44 0-.794.646-1.439 1.44-1.439.793-.001 1.44.645 1.44 1.439z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path
|
||||
d=" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z "
|
||||
class="css-c4d79v"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconLinkedIn.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconLinkedIn.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconMinus.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconMinus.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M22,13H2a1,1,0,0,1,0-2H22a1,1,0,0,1,0,2Z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
|
||||
<path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z" />
|
||||
<path d="M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconMoon.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconMoon.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<circle cx="12" cy="12" r="2" />
|
||||
<circle cx="19" cy="12" r="2" />
|
||||
<circle cx="5" cy="12" r="2" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconPlus.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconPlus.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z" />
|
||||
</svg>
|
||||
</template>
|
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z" />
|
||||
<path d="M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconSlack.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconSlack.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z" />
|
||||
</svg>
|
||||
</template>
|
13
packages/vuetom/blog/components/icons/VPIconSun.vue
Normal file
13
packages/vuetom/blog/components/icons/VPIconSun.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z" />
|
||||
<path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z" />
|
||||
<path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z" />
|
||||
<path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z" />
|
||||
<path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z" />
|
||||
<path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z" />
|
||||
<path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z" />
|
||||
<path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z" />
|
||||
<path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconTwitter.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconTwitter.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" />
|
||||
</svg>
|
||||
</template>
|
5
packages/vuetom/blog/components/icons/VPIconYouTube.vue
Normal file
5
packages/vuetom/blog/components/icons/VPIconYouTube.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
||||
</svg>
|
||||
</template>
|
37
packages/vuetom/blog/components/sidebar/VTSidebar.vue
Normal file
37
packages/vuetom/blog/components/sidebar/VTSidebar.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { useData } from 'vitepress'
|
||||
import { useSidebar } from '../../composables/sidebar.js'
|
||||
import VTSidebarTop from './VTSidebarTop.vue'
|
||||
import VTSidebarBottom from './VTSidebarBottom.vue'
|
||||
|
||||
const { site, theme } = useData()
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { base } = site.value
|
||||
let { avatar, author } = theme.value
|
||||
let baseUrl = base
|
||||
|
||||
if (base === '/' || base.endsWith('/')) {
|
||||
baseUrl = base.substring(0, base.length - 1)
|
||||
}
|
||||
|
||||
avatar = `${baseUrl}${avatar}`
|
||||
author += ' '
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="VTSidebar"
|
||||
:class="{ 'has-sidebar': hasSidebar }"
|
||||
>
|
||||
<VTSidebarTop />
|
||||
<div class="h-6"></div>
|
||||
<VTSidebarBottom
|
||||
:avatar="avatar"
|
||||
:author="author"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
76
packages/vuetom/blog/components/sidebar/VTSidebarBottom.vue
Normal file
76
packages/vuetom/blog/components/sidebar/VTSidebarBottom.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
const d = defineProps<{
|
||||
avatar?: string,
|
||||
author?: string
|
||||
}>()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTSidebarBottom">
|
||||
<div class="rounded-vt shadow-vt bg-cbg p-6">
|
||||
<div class="avatar w-full pt-6">
|
||||
<div
|
||||
class="bg-cover bg-center rounded-full h-24 w-24 bg-green-100 mx-auto"
|
||||
:style="{'background-image': `url(${d.avatar})`}"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="author">
|
||||
<p class="text-center py-6 text-xl font-bold">{{d.author}}</p>
|
||||
</div>
|
||||
<div class="tag">
|
||||
<div class="grid grid-cols-3 w-4/5 mx-auto text-center">
|
||||
<div>
|
||||
<p class="font-bold">1</p>
|
||||
<p class="text-sm hover:text-brand-d hover:font-bold">文章</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold">0</p>
|
||||
<p class="text-sm hover:text-brand-d hover:font-bold">分类</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="font-bold">10</p>
|
||||
<p class="text-sm hover:text-brand-d hover:font-bold">标签</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="self-links">
|
||||
<div class="py-6 flex flex-wrap text-left text-sm">
|
||||
<div class="w-1/2 py-1.5 pl-2 mt-1 hover:bg-cbgs rounded-vt">
|
||||
<i class="w-3 h-3 text-sm mr-1 fa fa-ambulance"></i>
|
||||
<span>Gitee</span>
|
||||
</div>
|
||||
<div class="w-1/2 py-1.5 pl-2 mt-1 hover:bg-cbgs rounded-vt">
|
||||
<i class="w-3 h-3 text-sm mr-1 fa fa-github-alt"></i>
|
||||
<span>Github</span>
|
||||
</div>
|
||||
<div class="w-1/2 py-1.5 pl-2 mt-1 hover:bg-cbgs rounded-vt">
|
||||
<i class="w-3 h-3 text-sm mr-1 fa fa-envelope-o"></i>
|
||||
<span>Email</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="friend-links">
|
||||
<div class="py-6 border-t-2 border-gray-100 border-opacity-50 text-center">
|
||||
<div>
|
||||
<a
|
||||
class="border-b-2 border-gray-200 inline-block m-1 px-1 hover:border-gray-500 transition duration-500 border-opacity-50"
|
||||
href="https://github.com/lauset"
|
||||
>LauSET</a
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<a
|
||||
class="border-b-2 border-gray-200 inline-block m-1 px-1 hover:border-gray-500 transition duration-500 border-opacity-50"
|
||||
href="https://github.com/lauset"
|
||||
>Vuetom</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
24
packages/vuetom/blog/components/sidebar/VTSidebarLink.vue
Normal file
24
packages/vuetom/blog/components/sidebar/VTSidebarLink.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { normalizeLink } from '../../support/utils.js'
|
||||
|
||||
const props = defineProps<{
|
||||
text: string,
|
||||
link: string,
|
||||
items: any
|
||||
}>()
|
||||
|
||||
const togo = (url: string) => {
|
||||
if (!url) url = ''
|
||||
window.location.href = normalizeLink(url)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="rounded-md w-full py-1 px-4 border-2 border-transparent
|
||||
hover:border-brand transition-colors"
|
||||
@click="togo(link)">
|
||||
{{text}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
28
packages/vuetom/blog/components/sidebar/VTSidebarTop.vue
Normal file
28
packages/vuetom/blog/components/sidebar/VTSidebarTop.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import VTSidebarLink from './VTSidebarLink.vue'
|
||||
import { useSidebar } from '../../composables/sidebar.js'
|
||||
|
||||
const { sidebar } = useSidebar()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="VTSidebarTop">
|
||||
<div class="rounded-vt shadow-vt overflow-hidden bg-cbg">
|
||||
<div class="h-20 px-6 py-3 bg-brand">
|
||||
<p class="font-bold py-1">公告</p>
|
||||
<p class="text-sm">暂无</p>
|
||||
</div>
|
||||
<div class="h-60 px-6 py-4">
|
||||
<div v-for="s in sidebar" :key="s.text">
|
||||
<VTSidebarLink
|
||||
:text="s.text"
|
||||
:items="s.items"
|
||||
:link="s.link" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
66
packages/vuetom/blog/components/switch/VPSwitch.vue
Normal file
66
packages/vuetom/blog/components/switch/VPSwitch.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<button class="VPSwitch" type="button" role="switch">
|
||||
<span class="check">
|
||||
<span class="icon" v-if="$slots.default">
|
||||
<slot />
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.VPSwitch {
|
||||
position: relative;
|
||||
border-radius: 11px;
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
/* border: 1px solid var(--vp-c-divider); */
|
||||
/* background-color: var(--vp-c-bg-mute); */
|
||||
transition: border-color 0.25s, background-color 0.25s;
|
||||
}
|
||||
|
||||
.VPSwitch:hover {
|
||||
/* border-color: var(--vp-c-gray); */
|
||||
}
|
||||
|
||||
.check {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
/* background-color: var(--vp-c-white);
|
||||
box-shadow: var(--vp-shadow-1); */
|
||||
transition: background-color 0.25s, transform 0.25s;
|
||||
}
|
||||
|
||||
.dark .check {
|
||||
/* background-color: var(--vp-c-black); */
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon :deep(svg) {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.dark .icon :deep(svg) {
|
||||
fill: var(--vp-c-text-1);
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
</style>
|
@@ -0,0 +1,74 @@
|
||||
<script lang="ts" setup>
|
||||
import { APPEARANCE_KEY } from '../../shared.js'
|
||||
import VPSwitch from './VPSwitch.vue'
|
||||
import VPIconSun from '../icons/VPIconSun.vue'
|
||||
import VPIconMoon from '../icons/VPIconMoon.vue'
|
||||
|
||||
const toggle = typeof localStorage !== 'undefined' ? useAppearance() : () => {}
|
||||
|
||||
function useAppearance() {
|
||||
const query = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
const { classList } = document.documentElement
|
||||
|
||||
let userPreference = localStorage.getItem(APPEARANCE_KEY) || 'auto'
|
||||
|
||||
let isDark = userPreference === 'auto'
|
||||
? query.matches
|
||||
: userPreference === 'dark'
|
||||
|
||||
query.onchange = (e) => {
|
||||
if (userPreference === 'auto') {
|
||||
setClass((isDark = e.matches))
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
setClass((isDark = !isDark))
|
||||
|
||||
userPreference = isDark
|
||||
? query.matches ? 'auto' : 'dark'
|
||||
: query.matches ? 'light' : 'auto'
|
||||
|
||||
localStorage.setItem(APPEARANCE_KEY, userPreference)
|
||||
}
|
||||
|
||||
function setClass(dark: boolean): void {
|
||||
classList[dark ? 'add' : 'remove']('dark')
|
||||
}
|
||||
|
||||
return toggle
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPSwitch
|
||||
class="VPSwitchAppearance"
|
||||
aria-label="toggle dark mode"
|
||||
@click="toggle"
|
||||
>
|
||||
<VPIconSun class="sun" />
|
||||
<VPIconMoon class="moon" />
|
||||
</VPSwitch>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sun {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.moon {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dark .sun {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.dark .moon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dark .VPSwitchAppearance :deep(.check) {
|
||||
/* transform: translateX(18px); */
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user