mirror of https://github.com/YunYouJun/valaxy
feat(theme-yun): add more animations for post/links/girls & abdjust layout
This commit is contained in:
parent
f3ff676182
commit
4478573c41
|
@ -5,7 +5,6 @@
|
|||
],
|
||||
"rules": {
|
||||
"no-descending-specificity": null,
|
||||
"alpha-value-notation": "number",
|
||||
"color-function-notation": "legacy"
|
||||
"alpha-value-notation": "number"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ const siteConfig = useSiteConfig()
|
|||
<main>
|
||||
<div w="full" flex="~">
|
||||
<slot name="main">
|
||||
<div class="content" flex="~ col grow" w="full" p="l-4 lt-md:0">
|
||||
<div class="content" flex="~ col grow" w="full" p="x-4 lt-md:0">
|
||||
<slot name="main-header" />
|
||||
<slot name="main-header-after" />
|
||||
|
||||
|
|
|
@ -4,4 +4,6 @@ categories:
|
|||
- 中文
|
||||
- 分类
|
||||
- 测试
|
||||
date: 2021-07-01 00:00:00
|
||||
updated: 2024-07-01 00:00:00
|
||||
---
|
||||
|
|
|
@ -6,7 +6,7 @@ export default defineSiteConfig({
|
|||
},
|
||||
|
||||
lang: 'zh-CN',
|
||||
title: '自定义博客标题',
|
||||
title: '自定义博客名称',
|
||||
timezone: 'Asia/Shanghai',
|
||||
url: 'https://yun.valaxy.site/',
|
||||
author: {
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
<script lang="ts" setup>
|
||||
import { useAppStore, useFrontmatter } from 'valaxy'
|
||||
import { useFrontmatter } from 'valaxy'
|
||||
import { usePressAppStore } from '../stores/app'
|
||||
import PressOutline from './PressOutline.vue'
|
||||
|
||||
const frontmatter = useFrontmatter()
|
||||
const app = useAppStore()
|
||||
const press = usePressAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="toc-btn shadow-lg fixed press-icon-btn z-99 lt-md:hidden! xl:hidden!"
|
||||
right="5" bottom="24"
|
||||
@click="app.toggleRightSidebar()"
|
||||
@click="press.rightSidebar.toggle()"
|
||||
>
|
||||
<div i-ri-file-list-line />
|
||||
</button>
|
||||
|
||||
<ValaxyOverlay :show="app.isRightSidebarOpen" @click="app.toggleRightSidebar()" />
|
||||
<ValaxyOverlay :show="press.rightSidebar.isOpen" @click="press.rightSidebar.toggle()" />
|
||||
|
||||
<aside
|
||||
class="press-aside lt-xl:fixed shadow xl:(shadow-none hover:shadow-none) hover:shadow-lg"
|
||||
flex="~ col grow"
|
||||
p="l-0 xl:l-8" text="center"
|
||||
z="$"
|
||||
:class="app.isRightSidebarOpen && 'open'"
|
||||
:class="press.rightSidebar.isOpen && 'open'"
|
||||
>
|
||||
<div class="aside-curtain" />
|
||||
<div class="aside-container lt-xl:fixed" flex="~ col">
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useToggle } from '@vueuse/core'
|
||||
|
||||
export const usePressAppStore = defineStore('press-app', () => {
|
||||
// 右侧边栏
|
||||
const [isRightSidebarOpen, toggleRightSidebar] = useToggle()
|
||||
|
||||
return {
|
||||
rightSidebar: {
|
||||
isOpen: isRightSidebarOpen,
|
||||
toggle: toggleRightSidebar,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
if (import.meta.hot)
|
||||
import.meta.hot.accept(acceptHMRUpdate(usePressAppStore, import.meta.hot))
|
|
@ -0,0 +1 @@
|
|||
export * from './app'
|
|
@ -3,6 +3,7 @@ import { useHead } from '@unhead/vue'
|
|||
import { useAppStore } from 'valaxy'
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useCssVar, useScroll } from '@vueuse/core'
|
||||
import { useThemeConfig } from './composables'
|
||||
import { useYunAppStore } from './stores'
|
||||
|
||||
|
@ -48,9 +49,21 @@ watch(
|
|||
onMounted(() => {
|
||||
app.showLoading = false
|
||||
})
|
||||
|
||||
// for scroll animation
|
||||
const scrollY = useCssVar('--scroll-y')
|
||||
const { y } = useScroll(window, {
|
||||
onScroll: () => {
|
||||
scrollY.value = y.value.toString()
|
||||
},
|
||||
})
|
||||
|
||||
const isDev = import.meta.env.DEV
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunDebug v-if="isDev" />
|
||||
|
||||
<YunPageHeaderGradient />
|
||||
<YunNavMenu />
|
||||
<YunFullscreenMenu />
|
||||
|
|
|
@ -37,64 +37,60 @@ onContentUpdated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main class="yun-main md:pl-$va-sidebar-width lt-md:ml-0" flex="~">
|
||||
<div w="full" flex="~">
|
||||
<slot name="main">
|
||||
<div class="content" :class="!aside && 'no-aside'" flex="~ col grow" w="full" p="l-4 lt-md:0">
|
||||
<YunCard :cover="frontmatter.cover" m="0" class="relative" :style="styles as StyleValue">
|
||||
<slot name="main-header">
|
||||
<YunPageHeader
|
||||
class="mb-2"
|
||||
:title="title"
|
||||
:icon="frontmatter.icon || icon"
|
||||
:color="frontmatter.color || color"
|
||||
:cover="frontmatter.cover"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
<main class="yun-main lt-md:w-full" flex="~ center">
|
||||
<slot name="main">
|
||||
<div
|
||||
class="content w-full md:w-3xl lg:w-2xl xl:w-4xl" :class="{
|
||||
'no-aside': !aside,
|
||||
}" flex="~ col grow"
|
||||
p="lt-md:0"
|
||||
>
|
||||
<YunCard :cover="frontmatter.cover" m="0" class="relative" :style="styles as StyleValue">
|
||||
<slot name="main-header">
|
||||
<YunPageHeader
|
||||
class="mb-2 mt-8"
|
||||
:title="title"
|
||||
:icon="frontmatter.icon || icon"
|
||||
:color="frontmatter.color || color"
|
||||
:cover="frontmatter.cover"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</slot>
|
||||
<slot name="main-header-after" />
|
||||
|
||||
<div p="x-4 b-8" class="sm:px-6 lg:px-12 xl:px-16" w="full">
|
||||
<slot name="main-content">
|
||||
<!-- <Transition appear> -->
|
||||
<ValaxyMd :frontmatter="frontmatter">
|
||||
<YunAiExcerpt v-if="frontmatter.excerpt_type === 'ai' && frontmatter.excerpt" />
|
||||
<YunMdTimeWarning />
|
||||
|
||||
<slot name="main-content-md" />
|
||||
<slot />
|
||||
</ValaxyMd>
|
||||
<!-- </Transition> -->
|
||||
</slot>
|
||||
<slot name="main-header-after" />
|
||||
|
||||
<div p="x-4 b-8" class="sm:px-6 lg:px-12 xl:px-16" w="full">
|
||||
<slot name="main-content">
|
||||
<!-- <Transition appear> -->
|
||||
<ValaxyMd :frontmatter="frontmatter">
|
||||
<YunAiExcerpt v-if="frontmatter.excerpt_type === 'ai' && frontmatter.excerpt" />
|
||||
<YunMdTimeWarning />
|
||||
<slot name="main-content-after" />
|
||||
</div>
|
||||
</YunCard>
|
||||
|
||||
<slot name="main-content-md" />
|
||||
<slot />
|
||||
</ValaxyMd>
|
||||
<!-- </Transition> -->
|
||||
</slot>
|
||||
<slot name="main-nav-before" />
|
||||
|
||||
<slot name="main-content-after" />
|
||||
</div>
|
||||
</YunCard>
|
||||
<slot name="main-nav">
|
||||
<YunPostNav v-if="frontmatter.nav !== false" />
|
||||
</slot>
|
||||
|
||||
<slot name="main-nav-before" />
|
||||
<slot name="main-nav-after" />
|
||||
|
||||
<slot name="main-nav">
|
||||
<YunPostNav v-if="frontmatter.nav !== false" />
|
||||
</slot>
|
||||
<slot v-if="siteConfig.comment.enable && frontmatter.comment !== false" name="comment">
|
||||
<YunComment :class="frontmatter.nav === false ? 'mt-4' : 0" />
|
||||
</slot>
|
||||
|
||||
<slot name="main-nav-after" />
|
||||
|
||||
<slot v-if="siteConfig.comment.enable && frontmatter.comment !== false" name="comment">
|
||||
<YunComment :class="frontmatter.nav === false ? 'mt-4' : 0" />
|
||||
</slot>
|
||||
|
||||
<slot name="main-footer-before" />
|
||||
<YunFooter />
|
||||
<slot name="main-footer-after" />
|
||||
</div>
|
||||
</slot>
|
||||
|
||||
<slot name="aside">
|
||||
<YunAside v-if="aside">
|
||||
<slot name="aside-custom" />
|
||||
</YunAside>
|
||||
</slot>
|
||||
</div>
|
||||
<slot name="main-footer-before" />
|
||||
<slot name="main-footer-after" />
|
||||
</div>
|
||||
</slot>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
@ -112,7 +108,7 @@ onContentUpdated(() => {
|
|||
@include screen('xl') {
|
||||
.content{
|
||||
// 8px scrollbar width
|
||||
max-width: calc(100vw - 2 * var(--va-sidebar-width) - 1rem - 8px);
|
||||
// max-width: calc(100vw - 2 * var(--va-sidebar-width) - 1rem - 8px);
|
||||
|
||||
&.no-aside {
|
||||
max-width: calc(100vw - var(--va-sidebar-width));
|
||||
|
|
|
@ -1,56 +1,57 @@
|
|||
<script lang="ts" setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore, useFrontmatter } from 'valaxy'
|
||||
import { useFrontmatter } from 'valaxy'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const frontmatter = useFrontmatter()
|
||||
const { t } = useI18n()
|
||||
const app = useAppStore()
|
||||
const yun = useYunAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
class="xl:hidden toc-btn shadow fixed yun-icon-btn z-350"
|
||||
opacity="75" right="2" bottom="19"
|
||||
@click="app.toggleRightSidebar()"
|
||||
@click="yun.rightSidebar.toggle()"
|
||||
>
|
||||
<div i-ri-file-list-line />
|
||||
</button>
|
||||
|
||||
<ValaxyOverlay :show="app.isRightSidebarOpen" @click="app.toggleRightSidebar()" />
|
||||
<ValaxyOverlay :show="yun.rightSidebar.isOpen" @click="yun.rightSidebar.toggle()" />
|
||||
|
||||
<!-- -->
|
||||
<aside
|
||||
class="va-card yun-aside"
|
||||
:class="app.isRightSidebarOpen && 'open'" m="l-4" text="center"
|
||||
overflow="auto"
|
||||
>
|
||||
<div class="aside-container" flex="~ col">
|
||||
<h2 v-if="frontmatter.toc !== false" m="t-6 b-2" font="serif black">
|
||||
{{ t('sidebar.toc') }}
|
||||
</h2>
|
||||
<Transition>
|
||||
<aside
|
||||
v-if="yun.rightSidebar.isOpen"
|
||||
flex="~ col"
|
||||
class="va-card yun-aside sticky top-68px min-h-sm w-80"
|
||||
:class="yun.rightSidebar.isOpen && 'open'"
|
||||
text="center"
|
||||
overflow="auto"
|
||||
>
|
||||
<div class="w-full" flex="~ col">
|
||||
<h2 v-if="frontmatter.toc !== false" m="t-6 b-2" font="serif black">
|
||||
{{ t('sidebar.toc') }}
|
||||
</h2>
|
||||
|
||||
<YunOutline v-if="frontmatter.toc !== false" />
|
||||
<YunOutline v-if="frontmatter.toc !== false" />
|
||||
|
||||
<div class="flex-grow" />
|
||||
<div class="flex-grow" />
|
||||
|
||||
<div v-if="$slots.default" class="custom-container">
|
||||
<slot />
|
||||
<div v-if="$slots.default" class="custom-container">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</aside>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'valaxy/client/styles/mixins/index.scss' as *;
|
||||
|
||||
.yun-aside {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
|
||||
// need fixed width
|
||||
width: var(--va-sidebar-width, 300px);
|
||||
// width: var(--va-sidebar-width, 300px);
|
||||
transform: translateX(100%);
|
||||
transition: box-shadow var(--va-transition-duration),
|
||||
background-color var(--va-transition-duration), opacity 0.25s,
|
||||
|
@ -62,12 +63,6 @@ const app = useAppStore()
|
|||
z-index: 10;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
&-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
@include screen('xl') {
|
||||
|
|
|
@ -38,6 +38,8 @@ if (typeof themeConfig.value.bg_image.url !== 'undefined') {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-image: var(--yun-bg-img);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
|
|
@ -24,14 +24,20 @@ const categoryList = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<ul v-for="category in categories.values()" :key="category.name" class="category-list" m="l-4">
|
||||
<YunCategory
|
||||
:parent-key="category.name"
|
||||
:category="category"
|
||||
:level="level + 1"
|
||||
:collapsable="!categoryList.includes(category.name)"
|
||||
/>
|
||||
</ul>
|
||||
<div flex="~ col">
|
||||
<ul
|
||||
v-for="category in categories.values()"
|
||||
:key="category.name"
|
||||
class="category-list"
|
||||
>
|
||||
<YunCategory
|
||||
:parent-key="category.name"
|
||||
:category="category"
|
||||
:level="level + 1"
|
||||
:collapsable="!categoryList.includes(category.name)"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -51,11 +57,5 @@ const categoryList = computed(() => {
|
|||
color: var(--va-c-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.category-name {
|
||||
&:hover {
|
||||
color: var(--va-c-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -45,6 +45,8 @@ const { show } = useInvisibleElement(postCollapseElRef)
|
|||
* @param category
|
||||
*/
|
||||
function jumpToDisplayCategory(category: string) {
|
||||
collapse.value = false
|
||||
|
||||
router.push({
|
||||
query: {
|
||||
category,
|
||||
|
@ -62,19 +64,39 @@ onMounted(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<li class="category-list-item inline-flex items-center cursor-pointer">
|
||||
<li
|
||||
class="category-list-item inline-flex items-center cursor-pointer w-full gap-2 transition px-3 py-2 rounded"
|
||||
hover="bg-black/5"
|
||||
>
|
||||
<span class="folder-action inline-flex" @click="collapse = !collapse">
|
||||
<div v-if="collapse" i-ri-folder-add-line />
|
||||
<div v-else style="color:var(--va-c-primary)" i-ri-folder-reduce-line />
|
||||
</span>
|
||||
<span class="category-name" m="l-1" @click="jumpToDisplayCategory(parentKey)">
|
||||
{{ category.name === 'Uncategorized' ? t('category.uncategorized') : category.name }} [{{ category.total }}]
|
||||
<span
|
||||
class="category-name inline-flex items-center gap-2 w-full"
|
||||
@click="jumpToDisplayCategory(parentKey)"
|
||||
>
|
||||
<span>
|
||||
{{ category.name === 'Uncategorized' ? t('category.uncategorized') : category.name }}
|
||||
</span>
|
||||
<span class="rounded-full px-1.5 bg-black/5 shadow-sm" text="xs black/55">
|
||||
{{ category.total }}
|
||||
</span>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<template v-if="!collapse">
|
||||
<ul>
|
||||
<li v-for="categoryItem, i in category.children.values()" :key="i" class="post-list-item" m="l-4">
|
||||
<Transition
|
||||
enter-active-class="v-enter-active"
|
||||
enter-from-class="v-enter-from"
|
||||
leave-active-class="v-leave-active"
|
||||
leave-to-class="v-leave-to"
|
||||
:duration="{ enter: 200, leave: 0 }"
|
||||
>
|
||||
<ul v-if="!collapse">
|
||||
<li
|
||||
v-for="categoryItem, i in category.children.values()" :key="i"
|
||||
class="post-list-item" m="l-4"
|
||||
>
|
||||
<template v-if="isCategoryList(categoryItem)">
|
||||
<YunCategory
|
||||
:parent-key="parentKey ? `${parentKey}/${categoryItem.name}` : categoryItem.name"
|
||||
|
@ -84,12 +106,16 @@ onMounted(() => {
|
|||
</template>
|
||||
|
||||
<template v-else>
|
||||
<RouterLink v-if="categoryItem.title" :to="categoryItem.path || ''" class="inline-flex items-center">
|
||||
<RouterLink
|
||||
v-if="categoryItem.title" :to="categoryItem.path || ''"
|
||||
class="inline-flex items-center gap-2 px-3 py-2 w-full rounded transition"
|
||||
hover="bg-black/5"
|
||||
>
|
||||
<div i-ri-file-text-line />
|
||||
<span m="l-1" font="serif black">{{ getTitle(categoryItem) }}</span>
|
||||
<span font="serif black">{{ getTitle(categoryItem) }}</span>
|
||||
</RouterLink>
|
||||
</template>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</Transition>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<script setup lang="ts">
|
||||
import { useMotion } from '@vueuse/motion'
|
||||
import Popover from 'primevue/popover'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const op = ref<typeof Popover>()
|
||||
|
||||
const pContentRef = ref<HTMLElement>()
|
||||
const motion = useMotion(pContentRef, {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
translateY: 30,
|
||||
},
|
||||
enter: {
|
||||
opacity: 1,
|
||||
translateY: 0,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
duration: 200,
|
||||
damping: 9,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
motion.variant.value = 'initial'
|
||||
|
||||
// 滚动时隐藏
|
||||
window.addEventListener('scroll', () => {
|
||||
op.value?.hide()
|
||||
motion.variant.value = 'initial'
|
||||
})
|
||||
})
|
||||
|
||||
function toggle(event: Event) {
|
||||
op.value?.toggle(event)
|
||||
motion.variant.value = op.value?.visible ? 'enter' : 'initial'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunNavMenuItem icon="i-ri-mind-map" @click="toggle" />
|
||||
<Popover
|
||||
ref="op"
|
||||
:auto-z-index="false"
|
||||
:base-z-index="1"
|
||||
>
|
||||
<div
|
||||
ref="pContentRef"
|
||||
class="p-4 shadow-xl z--1"
|
||||
bg="$va-c-bg-light"
|
||||
>
|
||||
<YunSiteInfo class="text-center" />
|
||||
<YunPostsInfo />
|
||||
</div>
|
||||
</Popover>
|
||||
</template>
|
|
@ -0,0 +1,47 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const yun = useYunAppStore()
|
||||
|
||||
const infoList = computed(() => {
|
||||
return [
|
||||
{
|
||||
label: 'xs',
|
||||
value: yun.size.isXs,
|
||||
},
|
||||
{
|
||||
label: 'sm',
|
||||
value: yun.size.isSm,
|
||||
},
|
||||
{
|
||||
label: 'md',
|
||||
value: yun.size.isMd,
|
||||
},
|
||||
{
|
||||
label: 'lg',
|
||||
value: yun.size.isLg,
|
||||
},
|
||||
{
|
||||
label: 'xl',
|
||||
value: yun.size.isXl,
|
||||
},
|
||||
{
|
||||
label: '2xl',
|
||||
value: yun.size.is2xl,
|
||||
},
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-black/50 fixed bottom-2 left-2 p-2 gap-1 rounded z-9999"
|
||||
text="xs white" flex="~ col"
|
||||
>
|
||||
<div v-for="item in infoList" :key="item.label" class="gap-2 inline-flex">
|
||||
<span class="w-6" font="bold">{{ item.label }}: </span>
|
||||
<span>{{ item.value ? '✅' : '❌' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -38,12 +38,15 @@ const fullscreenMenuRef = ref<HTMLElement>()
|
|||
|
||||
.slide-down-enter-active,
|
||||
.slide-down-leave-active {
|
||||
transition: transform 0.4s map.get($cubic-bezier, 'ease-in-out');
|
||||
opacity: 1;
|
||||
transition: transform 0.4s map.get($cubic-bezier, 'ease-in-out'),
|
||||
opacity 0.2s map.get($cubic-bezier, 'ease-in-out');
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.slide-down-enter-from,
|
||||
.slide-down-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useMotion } from '@vueuse/motion'
|
||||
import { onImgError } from '../utils'
|
||||
import type { GirlType } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
i: number
|
||||
girl: GirlType
|
||||
}>()
|
||||
|
||||
const itemRef = ref()
|
||||
useMotion(itemRef, {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
translateY: 40,
|
||||
},
|
||||
enter: {
|
||||
opacity: 1,
|
||||
translateY: 0,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
duration: 400,
|
||||
damping: 8,
|
||||
delay: props.i * 50,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li ref="itemRef" class="girl-item">
|
||||
<a
|
||||
class="girl-item-link"
|
||||
:href="girl.url || `https://zh.moegirl.org/${girl.name}`"
|
||||
:title="girl.reason" alt="portrait" target="_blank" rel="noopener"
|
||||
>
|
||||
<figure class="girl-info">
|
||||
<img class="girl-avatar" loading="lazy" :src="girl.avatar" :alt="girl.name" :onError="onImgError">
|
||||
<figcaption class="girl-name" :title="(i + 1).toString()">{{ girl.name }}</figcaption>
|
||||
<figcaption class="girl-from">{{ girl.from }}</figcaption>
|
||||
</figure>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.girl-item {
|
||||
display: inline-flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
width: 8rem;
|
||||
margin: 1rem;
|
||||
|
||||
.girl {
|
||||
&-info {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 50%;
|
||||
padding: 0.2rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 1rem rgb(0 0 0 / 0.12);
|
||||
transition: 0.5s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 2rem rgb(0 0 0 / 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
&-from {
|
||||
font-size: 12px;
|
||||
font-family: var(--va-font-serif);
|
||||
font-weight: bold;
|
||||
color: var(--va-c-text-light);
|
||||
|
||||
&::before {
|
||||
content: '「';
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '」';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,14 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { onImgError } from '../utils'
|
||||
import { useRandomData } from '../composables'
|
||||
|
||||
interface GirlType {
|
||||
name: string
|
||||
url: string
|
||||
avatar: string
|
||||
from?: string
|
||||
reason?: string
|
||||
}
|
||||
import type { GirlType } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
girls: GirlType[] | string
|
||||
|
@ -21,19 +13,7 @@ const { data } = useRandomData(props.girls, props.random)
|
|||
<template>
|
||||
<div class="girls">
|
||||
<ul class="girl-items">
|
||||
<li v-for="girl, i in data" :key="girl.name" class="girl-item">
|
||||
<a
|
||||
class="girl-item-link"
|
||||
:href="girl.url || `https://zh.moegirl.org/${girl.name}`"
|
||||
:title="girl.reason" alt="portrait" target="_blank" rel="noopener"
|
||||
>
|
||||
<figure class="girl-info">
|
||||
<img class="girl-avatar" loading="lazy" :src="girl.avatar" :alt="girl.name" :onError="onImgError">
|
||||
<figcaption class="girl-name" :title="(i + 1).toString()">{{ girl.name }}</figcaption>
|
||||
<figcaption class="girl-from">{{ girl.from }}</figcaption>
|
||||
</figure>
|
||||
</a>
|
||||
</li>
|
||||
<YunGirlItem v-for="girl, i in data" :key="i" :i="i" :girl="girl" />
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -53,55 +33,4 @@ const { data } = useRandomData(props.girls, props.random)
|
|||
.girls-number {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.girl-item {
|
||||
display: inline-flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
width: 8rem;
|
||||
margin: 1rem;
|
||||
|
||||
.girl {
|
||||
&-info {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
border-radius: 50%;
|
||||
padding: 0.2rem;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.12);
|
||||
transition: 0.5s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 2rem rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
&-from {
|
||||
font-size: 12px;
|
||||
font-family: var(--va-font-serif);
|
||||
font-weight: bold;
|
||||
color: var(--va-c-text-light);
|
||||
|
||||
&::before {
|
||||
content: '「';
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '」';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,24 +1,5 @@
|
|||
<script lang="ts" setup>
|
||||
import { gsap } from 'gsap'
|
||||
import ScrollToPlugin from 'gsap/ScrollToPlugin'
|
||||
|
||||
gsap.registerPlugin(ScrollToPlugin)
|
||||
|
||||
function goDown() {
|
||||
const banner = document.getElementById('yun-banner')
|
||||
if (banner) {
|
||||
// nav menu height
|
||||
const offset = 50
|
||||
|
||||
gsap.to(window, {
|
||||
duration: 1,
|
||||
scrollTo: {
|
||||
y: banner.clientHeight - offset,
|
||||
},
|
||||
ease: 'power3.inOut',
|
||||
})
|
||||
}
|
||||
}
|
||||
import { goDown } from '../utils'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<script setup lang="ts">
|
||||
import { useMotion } from '@vueuse/motion'
|
||||
import { ref } from 'vue'
|
||||
import { onImgError } from '../utils'
|
||||
import type { LinkType } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
i: number
|
||||
errorImg?: string
|
||||
link: LinkType
|
||||
}>()
|
||||
|
||||
function onError(e: Event) {
|
||||
onImgError(e, props.errorImg)
|
||||
}
|
||||
|
||||
const itemRef = ref()
|
||||
useMotion(itemRef, {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
translateY: 40,
|
||||
},
|
||||
enter: {
|
||||
opacity: 1,
|
||||
translateY: 0,
|
||||
transition: {
|
||||
type: 'spring',
|
||||
duration: 400,
|
||||
damping: 8,
|
||||
delay: props.i * 50,
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
ref="itemRef"
|
||||
class="yun-link-item inline-flex"
|
||||
:style="{
|
||||
'--primary-color': link.color,
|
||||
}"
|
||||
>
|
||||
<a
|
||||
class="yun-link-url" p="x-4 y-2" :href="link.url" :title="link.name"
|
||||
alt="portrait" rel="friend" target="_blank"
|
||||
>
|
||||
<div class="yun-link-left">
|
||||
<img
|
||||
class="yun-link-avatar" width="64" height="64" w="16" h="16"
|
||||
loading="lazy"
|
||||
:src="link.avatar" :alt="link.name"
|
||||
@error="onError"
|
||||
>
|
||||
</div>
|
||||
<div class="yun-link-info" m="l-2">
|
||||
<div class="yun-link-blog" font="serif black">{{ link.blog }}</div>
|
||||
<div class="yun-link-desc">{{ link.desc }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
|
@ -1,15 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import { useRandomData } from '../composables'
|
||||
import { onImgError } from '../utils'
|
||||
|
||||
interface LinkType {
|
||||
avatar: string
|
||||
name: string
|
||||
url: string
|
||||
color: string
|
||||
blog: string
|
||||
desc: string
|
||||
}
|
||||
import type { LinkType } from '../types'
|
||||
|
||||
const props = defineProps<{
|
||||
links: string | LinkType[]
|
||||
|
@ -21,50 +12,27 @@ const props = defineProps<{
|
|||
}>()
|
||||
|
||||
const { data } = useRandomData(props.links, props.random)
|
||||
|
||||
function onError(e: Event) {
|
||||
onImgError(e, props.errorImg)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="links">
|
||||
<ul class="link-items">
|
||||
<li v-for="link, i in data" :key="i" class="link-item" :style="`--primary-color: ${link.color}`">
|
||||
<a class="link-url" p="x-4 y-2" :href="link.url" :title="link.name" alt="portrait" rel="friend" target="_blank">
|
||||
<div class="link-left">
|
||||
<img
|
||||
class="link-avatar" width="64" height="64" w="16" h="16"
|
||||
loading="lazy" :src="link.avatar" :alt="link.name"
|
||||
@error="onError"
|
||||
>
|
||||
</div>
|
||||
<div class="link-info" m="l-2">
|
||||
<div class="link-blog" font="serif black">{{ link.blog }}</div>
|
||||
<div class="link-desc">{{ link.desc }}</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<div class="yun-links">
|
||||
<ul class="yun-link-items" flex="center wrap">
|
||||
<YunLinkItem
|
||||
v-for="link, i in data"
|
||||
:key="i"
|
||||
:i="i" :link="link" :error-img="errorImg"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.link-item {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.links {
|
||||
.link-items {
|
||||
display: flex;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
<style lang="scss">
|
||||
.yun-links {
|
||||
.yun-link-items {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.link-url {
|
||||
.yun-link-url {
|
||||
--smc-link-color: var(--primary-color);
|
||||
|
||||
display: inline-flex;
|
||||
|
@ -82,7 +50,7 @@ function onError(e: Event) {
|
|||
box-shadow: 0 2px 20px var(--primary-color, gray);
|
||||
}
|
||||
|
||||
.link {
|
||||
.yun-link {
|
||||
&-left {
|
||||
line-height: 0;
|
||||
}
|
||||
|
@ -97,7 +65,7 @@ function onError(e: Event) {
|
|||
transition: 0.5s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 20px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,7 +79,7 @@ function onError(e: Event) {
|
|||
}
|
||||
}
|
||||
|
||||
.link-info {
|
||||
.yun-link-info {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
import { useSiteConfig } from 'valaxy'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { useAppStore, useSiteConfig } from 'valaxy'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const app = useAppStore()
|
||||
const yunApp = useYunAppStore()
|
||||
const siteConfig = useSiteConfig()
|
||||
|
||||
|
@ -19,38 +20,86 @@ onMounted(() => {
|
|||
showMenu.value = true
|
||||
}
|
||||
})
|
||||
|
||||
const playAnimation = ref(false)
|
||||
watch(() => yunApp.scrollY, () => {
|
||||
if (yunApp.scrollY > 10)
|
||||
playAnimation.value = true
|
||||
else
|
||||
playAnimation.value = false
|
||||
})
|
||||
|
||||
const navMenu = ref([
|
||||
{
|
||||
title: '项目',
|
||||
url: 'https://sponsors.yunyoujun.cn/projects',
|
||||
},
|
||||
{
|
||||
title: '赞助者',
|
||||
url: 'https://sponsors.yunyoujun.cn',
|
||||
},
|
||||
])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="showMenu"
|
||||
class="yun-nav-menu shadow-xl"
|
||||
class="yun-nav-menu z-99999"
|
||||
border="~ solid $va-c-text"
|
||||
:class="{
|
||||
play: playAnimation,
|
||||
}"
|
||||
>
|
||||
<!-- -->
|
||||
<ValaxyHamburger
|
||||
:active="yunApp.fullscreenMenu.isOpen"
|
||||
class="menu-btn sidebar-toggle leading-4 size-12 bg-$va-c-bg"
|
||||
class="menu-btn sidebar-toggle leading-4 size-12"
|
||||
inline-flex cursor="pointer"
|
||||
hover="bg-$va-c-bg-soft"
|
||||
hover="bg-white/80 dark:bg-black/80"
|
||||
z="$yun-z-menu-btn"
|
||||
@click="yunApp.fullscreenMenu.toggle()"
|
||||
/>
|
||||
<NavMenuTitle />
|
||||
<YunSearchTrigger v-if="siteConfig.search.enable" />
|
||||
|
||||
<div class="flex flex-1 px-2">
|
||||
<YunNavMenuTitle />
|
||||
</div>
|
||||
|
||||
<div class="inline-flex-center">
|
||||
<template v-if="!app.isMobile">
|
||||
<AppLink
|
||||
v-for="item in navMenu"
|
||||
:key="item.url"
|
||||
:to="item.url"
|
||||
class="menu-btn inline-flex-center p-2 transition text-$va-c-text"
|
||||
inline-flex cursor="pointer"
|
||||
hover="bg-white/80 dark:bg-black/80"
|
||||
z="$yun-z-menu-btn"
|
||||
>
|
||||
{{ item.title }}
|
||||
</AppLink>
|
||||
</template>
|
||||
|
||||
<YunClassifyPopover v-if="!yunApp.size.isLg" />
|
||||
<YunSearchTrigger v-if="siteConfig.search.enable" />
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use 'sass:map';
|
||||
@use 'valaxy-theme-yun/styles/vars.scss' as *;
|
||||
@use "valaxy/client/styles/mixins/index.scss" as *;
|
||||
|
||||
.yun-nav-menu {
|
||||
position: fixed;
|
||||
animation: nav-menu-appear 0.3s linear forwards;
|
||||
animation-timeline: scroll();
|
||||
animation-range: 0 calc(30vh);
|
||||
|
||||
// safari not support
|
||||
// animation-timeline: scroll();
|
||||
// animation-range: 0 calc(30vh), exit;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
z-index: var(--yun-z-menu-btn);
|
||||
display: flex;
|
||||
top: var(--rect-margin);
|
||||
|
@ -59,27 +108,23 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 50px;
|
||||
transition: all 0.5s map.get($cubic-bezier, 'ease-in');
|
||||
|
||||
&.play {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--va-c-bg);
|
||||
border-color: rgb(0 0 0 / 0.1);
|
||||
|
||||
--un-shadow: var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color, rgb(0 0 0 / 0.1)), var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color, rgb(0 0 0 / 0.1));
|
||||
|
||||
box-shadow: var(--un-ring-offset-shadow), var(--un-ring-shadow),
|
||||
var(--un-shadow);
|
||||
}
|
||||
|
||||
.vt-hamburger-top, .vt-hamburger-middle, .vt-hamburger-bottom {
|
||||
background-color: var(--va-c-text);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nav-menu-appear {
|
||||
from {
|
||||
top: var(--rect-margin);
|
||||
left: var(--rect-margin);
|
||||
right: var(--rect-margin);
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
to {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--va-c-bg);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -20,27 +20,6 @@
|
|||
<style lang="scss">
|
||||
@use "valaxy/client/styles/mixins/index.scss" as *;
|
||||
|
||||
.site-author-avatar {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 96px;
|
||||
width: 96px;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 10px rgba(black, 0.2);
|
||||
transition: 0.4s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 30px rgba(var(--va-c-primary-rgb), 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.site-author-status {
|
||||
position: absolute;
|
||||
height: 1.8rem;
|
||||
|
@ -49,9 +28,9 @@
|
|||
right: 0;
|
||||
line-height: 1.8rem;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 0.2);
|
||||
background-color: var(--va-c-bg-light);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgb(255 255 255 / 0.1);
|
||||
}
|
||||
|
||||
.site-name {
|
||||
|
|
|
@ -11,7 +11,7 @@ defineProps<{
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<header class="post-header" m="t-20">
|
||||
<header class="post-header">
|
||||
<h1
|
||||
class="post-title flex-center" p="2" text="2xl center"
|
||||
font="serif black"
|
||||
|
|
|
@ -14,7 +14,13 @@ const { icon, styles } = usePostProperty(props.post.type)
|
|||
|
||||
<template>
|
||||
<YunCard
|
||||
m="y-4 auto"
|
||||
v-animateonscroll="{
|
||||
// translate
|
||||
enterClass: 'animate-fade-in animate-duration-200ms',
|
||||
leaveClass: 'animate-fade-out animate-duration-200ms',
|
||||
}"
|
||||
class="w-full"
|
||||
m="auto"
|
||||
:class="post.cover ? 'post-card-image' : 'post-card'"
|
||||
overflow="hidden" :style="styles"
|
||||
>
|
||||
|
@ -83,9 +89,10 @@ const { icon, styles } = usePostProperty(props.post.type)
|
|||
|
||||
<style lang="scss">
|
||||
.post-card {
|
||||
animation: card-appear 0.6s ease-in-out forwards, card-appear 0.6s ease-in-out forwards reverse;
|
||||
animation-timeline: view();
|
||||
animation-range: entry, exit;
|
||||
// safari not support
|
||||
// animation: card-appear 0.6s ease-in-out forwards, card-appear 0.6s ease-in-out forwards reverse;
|
||||
// animation-timeline: view();
|
||||
// animation-range: entry, exit;
|
||||
}
|
||||
|
||||
@keyframes card-appear {
|
||||
|
@ -101,6 +108,6 @@ const { icon, styles } = usePostProperty(props.post.type)
|
|||
}
|
||||
|
||||
.yun-card-actions {
|
||||
border-top: 1px solid rgba(122, 122, 122, 0.05);
|
||||
border-top: 1px solid rgb(122 122 122 / 0.05);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,7 +19,7 @@ defineProps<{
|
|||
>
|
||||
<div m="x-1" inline-flex i-ri-folder-2-line />
|
||||
<span>
|
||||
{{ Array.isArray(categories) ? categories.join(' > ') : categories }}
|
||||
{{ Array.isArray(categories) ? categories.join(' / ') : categories }}
|
||||
</span>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
|
|
@ -44,7 +44,7 @@ const sortedYears = computed(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="post-collapse px-10 lt-sm:px-5" relative>
|
||||
<div class="post-collapse px-10 lt-sm:px-5 max-w-3xl" relative>
|
||||
<div w="full" text="center" class="yun-text-light" p="2">
|
||||
{{ t('counter.archives', posts.length) }}
|
||||
</div>
|
||||
|
@ -65,15 +65,18 @@ const sortedYears = computed(() => {
|
|||
|
||||
<article
|
||||
v-for="post, j in sortByDate(postListByYear[year], isDesc)" :key="j"
|
||||
class="post-item" relative
|
||||
class="post-item relative"
|
||||
>
|
||||
<header class="post-header" flex items-center relative>
|
||||
<header
|
||||
class="post-header cursor-pointer" flex items-center relative
|
||||
hover="bg-black/1"
|
||||
>
|
||||
<div class="post-meta">
|
||||
<time v-if="post.date" class="post-time" font="mono" opacity="80">{{
|
||||
formatDate(post.date, 'MM-dd') }}
|
||||
</time>
|
||||
</div>
|
||||
<h2 class="post-title" inline-flex items-center font="serif black">
|
||||
<h2 class="post-title w-full" inline-flex items-center font="serif black">
|
||||
<RouterLink :to="post.path || ''" class="post-title-link">
|
||||
{{ post.title }}
|
||||
</RouterLink>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Post } from 'valaxy'
|
||||
import { formatDate } from 'valaxy'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { formatTimestamp } from '../utils'
|
||||
|
||||
defineProps<{
|
||||
// FrontMatter
|
||||
frontmatter: Post
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="frontmatter.date" class="post-time flex items-center gap-4">
|
||||
<span class="posted-time inline-flex-center gap-1" :title="t('post.posted') + formatTimestamp(frontmatter.date)">
|
||||
<div class="inline-block" i-ri-calendar-line />
|
||||
<time class="op-80">{{ formatDate(frontmatter.date) }}</time>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="frontmatter.updated && frontmatter.updated !== frontmatter.date"
|
||||
class="edited-time inline-flex-center gap-1" :title="t('post.edited') + formatTimestamp(frontmatter.updated)"
|
||||
>
|
||||
<div i-ri-calendar-2-line />
|
||||
<time class="op-80">{{ formatDate(frontmatter.updated) }}</time>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
|
@ -28,17 +28,18 @@ const displayedPosts = computed(() =>
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="yun-post-list" w="full" p="x-4 lt-sm:0">
|
||||
<div flex="~ col" class="yun-post-list gap-4" w="full" p="x-4 lt-sm:0">
|
||||
<template v-if="!displayedPosts.length">
|
||||
<div py2 op50 text-center>
|
||||
博主还什么都没写哦~
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<TransitionGroup name="fade">
|
||||
<YunPostCard v-for="route, i in displayedPosts" :key="i" :post="route" />
|
||||
</TransitionGroup>
|
||||
<YunPostCard v-for="route, i in displayedPosts" :key="i" :post="route" />
|
||||
</div>
|
||||
|
||||
<YunPagination :total="posts.length" :page-size="pageSize" />
|
||||
<YunPagination
|
||||
class="mt-5"
|
||||
:total="posts.length" :page-size="pageSize"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import type { Post } from 'valaxy'
|
||||
import { formatDate, useSiteConfig } from 'valaxy'
|
||||
import { useAppStore, useSiteConfig } from 'valaxy'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { formatTimestamp } from '../utils'
|
||||
|
||||
defineProps<{
|
||||
// FrontMatter
|
||||
frontmatter: Post
|
||||
}>()
|
||||
|
||||
const app = useAppStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const siteConfig = useSiteConfig()
|
||||
|
@ -27,50 +27,42 @@ const siteConfig = useSiteConfig()
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="frontmatter" class="post-meta"
|
||||
flex="~ col" justify="center" items="center" text="sm" py="1"
|
||||
v-if="frontmatter"
|
||||
class="post-meta gap-4"
|
||||
flex="~ center"
|
||||
text="sm"
|
||||
:class="{
|
||||
'flex-col gap-2!': app.isMobile || frontmatter.updated,
|
||||
}"
|
||||
>
|
||||
<div v-if="frontmatter.date" class="post-time flex items-center">
|
||||
<span class="posted-time inline-flex-center" :title="t('post.posted') + formatTimestamp(frontmatter.date)">
|
||||
<div class="inline-block" i-ri-calendar-line />
|
||||
<time m="l-1">{{ formatDate(frontmatter.date) }}</time>
|
||||
</span>
|
||||
<YunPostDateMeta :frontmatter="frontmatter" />
|
||||
|
||||
<span
|
||||
v-if="frontmatter.updated && frontmatter.updated !== frontmatter.date"
|
||||
class="edited-time inline-flex-center" :title="t('post.edited') + formatTimestamp(frontmatter.updated)"
|
||||
<div class="inline-flex-center gap-4">
|
||||
<div
|
||||
v-if="siteConfig.statistics.enable"
|
||||
class="inline-flex-center post-counter gap-4"
|
||||
>
|
||||
<span m="x-2">-</span>
|
||||
<div i-ri-calendar-2-line />
|
||||
<time m="l-1">{{ formatDate(frontmatter.updated) }}</time>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="frontmatter.wordCount"
|
||||
class="word-count inline-flex-center gap-1" :title="t('statistics.word')"
|
||||
>
|
||||
<div class="inline-block" i-ri-file-word-line />
|
||||
<span class="op-80">{{ frontmatter.wordCount }}</span>
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-if="siteConfig.statistics.enable"
|
||||
class="post-counter flex items-center" mt="2"
|
||||
>
|
||||
<span
|
||||
v-if="frontmatter.wordCount"
|
||||
class="word-count inline-flex-center" :title="t('statistics.word')"
|
||||
>
|
||||
<div class="inline-block" i-ri-file-word-line />
|
||||
<span m="l-1">{{ frontmatter.wordCount }}</span>
|
||||
</span>
|
||||
<span
|
||||
v-if="frontmatter.readingTime"
|
||||
class="reading-time inline-flex-center gap-1"
|
||||
:title="t('statistics.time')"
|
||||
>
|
||||
<div i-ri-timer-line />
|
||||
<time class="op-80">{{ frontmatter.readingTime }}m</time>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="frontmatter.readingTime"
|
||||
class="reading-time inline-flex-center"
|
||||
:title="t('statistics.time')"
|
||||
>
|
||||
<span m="x-2">-</span>
|
||||
<div i-ri-timer-line />
|
||||
<time m="l-1">{{ frontmatter.readingTime }}m</time>
|
||||
</span>
|
||||
<YunWalineMeta />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts" setup>
|
||||
import { useCategories, useSiteStore, useTags } from 'valaxy'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const site = useSiteStore()
|
||||
|
||||
const categories = useCategories()
|
||||
const tags = useTags()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="site-nav" text-xl mt-6>
|
||||
<YunPostClassifyNavItem
|
||||
to="/archives/" icon="i-ri-archive-line" :title="t('menu.archives')"
|
||||
:total="site.postList.length"
|
||||
/>
|
||||
<YunPostClassifyNavItem
|
||||
to="/categories/" icon="i-ri-folder-2-line" :title="t('menu.categories')"
|
||||
:total="Array.from(categories.children).length"
|
||||
/>
|
||||
<YunPostClassifyNavItem
|
||||
to="/tags/" icon="i-ri-price-tag-3-line" :title="t('menu.tags')"
|
||||
:total="Array.from(tags).length"
|
||||
/>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use "valaxy/client/styles/mixins/index.scss" as *;
|
||||
|
||||
.site-nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -21,9 +21,9 @@ function onClick() {
|
|||
|
||||
<template>
|
||||
<button
|
||||
class="yun-search-btn popup-trigger h-full inline-flex justify-center items-center aspect-1 bg-$va-c-bg"
|
||||
class="yun-search-btn popup-trigger size-12 inline-flex justify-center items-center"
|
||||
text="xl $va-c-text"
|
||||
hover="bg-$va-c-bg-soft"
|
||||
hover="bg-white/80 dark:bg-black/80"
|
||||
:title="t('menu.search')"
|
||||
@click="onClick"
|
||||
>
|
||||
|
|
|
@ -55,14 +55,4 @@ function toggleOptionVisible(e: MouseEvent) {
|
|||
.select-options {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity .2s ease;
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
// new sidebar card
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunCard class="sticky top-68px p-4 min-h-sm w-80">
|
||||
<YunSiteInfo class="text-center" />
|
||||
<YunPostsInfo />
|
||||
</YunCard>
|
||||
</template>
|
|
@ -22,6 +22,27 @@ const siteConfig = useSiteConfig()
|
|||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.site-author-avatar {
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
height: 96px;
|
||||
width: 96px;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 10px rgba(black, 0.2);
|
||||
transition: 0.4s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 30px rgba(var(--va-c-primary-rgb), 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.site-info {
|
||||
&.fix-top {
|
||||
margin-top: -1.5rem;
|
||||
|
|
|
@ -49,7 +49,7 @@ const sponsorBtnTitle = computed(() => {
|
|||
@use "valaxy/client/styles/mixins/index.scss" as *;
|
||||
|
||||
.sponsor-button {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
background-color: rgb(255 255 255 / 0.1);
|
||||
|
||||
div {
|
||||
transform: scale(1.1);
|
||||
|
@ -57,7 +57,7 @@ const sponsorBtnTitle = computed(() => {
|
|||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
background-color: rgb(255 255 255 / 0.9);
|
||||
|
||||
div {
|
||||
transform: scale(1.2);
|
||||
|
@ -74,10 +74,12 @@ const sponsorBtnTitle = computed(() => {
|
|||
.qrcode-container {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height var(--va-transition-duration) map.get($cubic-bezier, 'ease-in-out');
|
||||
opacity: 0;
|
||||
transition: all var(--va-transition-duration) map.get($cubic-bezier, 'ease-in-out');
|
||||
|
||||
&.show {
|
||||
height: 260px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
icon: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="size-12 inline-flex-center cursor-pointer"
|
||||
text="$va-c-text"
|
||||
hover="bg-white/80 dark:bg-black/80 op-100"
|
||||
op-80
|
||||
>
|
||||
<div class="size-6" :class="icon" />
|
||||
</div>
|
||||
</template>
|
|
@ -1,14 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
import { useFrontmatter, useSiteConfig } from 'valaxy'
|
||||
import { ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useYunAppStore } from '../../stores'
|
||||
|
||||
const yunApp = useYunAppStore()
|
||||
const fm = useFrontmatter()
|
||||
const siteConfig = useSiteConfig()
|
||||
|
||||
const showPostTitle = ref(false)
|
||||
watch(() => yunApp.scrollY, () => {
|
||||
showPostTitle.value = yunApp.scrollY > 200
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
function goToLink() {
|
||||
if (!showPostTitle.value)
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-motion
|
||||
flex="~ col h-full"
|
||||
:class="{
|
||||
'cursor-pointer': !showPostTitle,
|
||||
}"
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
y: 10,
|
||||
|
@ -21,12 +39,16 @@ const siteConfig = useSiteConfig()
|
|||
delay: 1200,
|
||||
},
|
||||
}"
|
||||
@click="goToLink"
|
||||
>
|
||||
<span
|
||||
v-if="fm.title"
|
||||
v-if="fm.title && showPostTitle"
|
||||
class="nav-menu-post-title text-sm font-bold flex items-center gap-1 truncate"
|
||||
>
|
||||
<div class="size-4" i-ri-article-line />
|
||||
<div
|
||||
class="size-4"
|
||||
:class="fm.icon || 'i-ri-article-line'"
|
||||
/>
|
||||
<span> {{ fm.title }}</span>
|
||||
</span>
|
||||
<span v-else class="font-light truncate">
|
||||
|
@ -37,9 +59,11 @@ const siteConfig = useSiteConfig()
|
|||
|
||||
<style lang="scss">
|
||||
.nav-menu-post-title {
|
||||
// safari not support
|
||||
animation: nav-menu-title-appear 0.3s linear forwards;
|
||||
animation-timeline: scroll();
|
||||
animation-range: 0 calc(30vh);
|
||||
|
||||
// animation-timeline: scroll();
|
||||
// animation-range: 0 calc(30vh);
|
||||
}
|
||||
|
||||
@keyframes nav-menu-title-appear {
|
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
icon: string
|
||||
title: string
|
||||
/**
|
||||
* Total number
|
||||
*/
|
||||
total: number
|
||||
to: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink
|
||||
flex="~ col center"
|
||||
class="gap-1 w-20"
|
||||
:to="to" :title="title"
|
||||
>
|
||||
<div flex="~ col" class="text-$va-c-text inline-flex-center gap-1">
|
||||
<div class="text-xl" :class="icon" />
|
||||
<span class="text-sm">
|
||||
{{ title }}
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="count text-base"
|
||||
>{{ total }}</span>
|
||||
</RouterLink>
|
||||
</template>
|
|
@ -11,15 +11,15 @@ const yunApp = useYunAppStore()
|
|||
|
||||
<template>
|
||||
<AppLink
|
||||
class="link-item w-full h-12 items-center justify-center gap-2 transition rounded"
|
||||
class="link-item w-full items-center justify-center gap-2 transition rounded text-xl p-3"
|
||||
inline-flex
|
||||
:to="page.url" :title="page.name"
|
||||
:style="`color:${page.color}`"
|
||||
hover="bg-gray-100/50 dark:bg-gray-800/50"
|
||||
@click="yunApp.fullscreenMenu.isOpen = false"
|
||||
>
|
||||
<div :class="page.icon" class="icon size-6" />
|
||||
<span class="text-lg">
|
||||
<div :class="page.icon" class="icon" />
|
||||
<span>
|
||||
{{ page.name }}
|
||||
</span>
|
||||
</AppLink>
|
||||
|
|
|
@ -14,14 +14,26 @@ const { t } = useI18n()
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="addonWaline && addonWaline.options" flex="~" text="sm" my="1" h="5">
|
||||
<div v-if="addonWaline.options.pageview" inline-flex justify="center" items="center" mx="2" :title="t('post.pageview_count')">
|
||||
<div v-if="addonWaline && addonWaline.options" class="inline-flex gap-4" text="sm" h="5">
|
||||
<div
|
||||
v-if="addonWaline.options.pageview" inline-flex justify="center" items="center"
|
||||
:title="t('post.pageview_count')"
|
||||
>
|
||||
<div inline-flex i-ri-eye-line />
|
||||
<span ml-1 inline-flex class="waline-pageview-count" :data-path="route.path" />
|
||||
<span
|
||||
ml-1 inline-flex class="waline-pageview-count op-80"
|
||||
:data-path="route.path"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="addonWaline.options.comment" inline-flex justify="center" items="center" mx="2" :title="t('post.comment_count')">
|
||||
<div
|
||||
v-if="addonWaline.options.comment" inline-flex justify="center" items="center"
|
||||
:title="t('post.comment_count')"
|
||||
>
|
||||
<div inline-flex i-ri-chat-4-line />
|
||||
<span ml-1 inline-flex class="waline-comment-count" :data-path="route.path" />
|
||||
<span
|
||||
ml-1 inline-flex class="waline-comment-count op-80"
|
||||
:data-path="route.path"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
<script lang="ts" setup>
|
||||
import { defineWebPage, useSchemaOrg } from '@unhead/schema-org'
|
||||
import { useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { useAppStore, useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const app = useAppStore()
|
||||
const yun = useYunAppStore()
|
||||
|
||||
const frontmatter = useFrontmatter()
|
||||
|
||||
const title = usePostTitle(frontmatter)
|
||||
|
@ -15,28 +20,43 @@ useSchemaOrg([
|
|||
'@type': 'CollectionPage',
|
||||
}),
|
||||
])
|
||||
|
||||
const pageIcon = computed(() => {
|
||||
if (!frontmatter.value.icon)
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
frontmatter.value.icon = 'i-ri-archive-line'
|
||||
return frontmatter.value.icon
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunSidebar v-if="$slots['sidebar-child']">
|
||||
<slot name="sidebar-child" />
|
||||
</YunSidebar>
|
||||
<YunSidebar v-else />
|
||||
<div
|
||||
flex="~"
|
||||
class="mt-24 md:mt-36 w-full max-w-screen-2xl m-auto justify-center items-start gap-4"
|
||||
:class="{
|
||||
'flex-col': app.isMobile,
|
||||
}"
|
||||
>
|
||||
<YunSidebarCard v-if="yun.size.isLg" />
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
:title="title || t('menu.archives')"
|
||||
:icon="frontmatter.icon || 'i-ri-archive-line'"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<RouterView />
|
||||
<YunPostCollapse :posts="site.postList" />
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
class="mt-8"
|
||||
:title="title || t('menu.archives')"
|
||||
:icon="pageIcon"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<RouterView />
|
||||
<YunPostCollapse :posts="site.postList" />
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
</div>
|
||||
|
||||
<YunFooter />
|
||||
</template>
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useCategories, useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { useAppStore, useCategories, useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { defineWebPage, useSchemaOrg } from '@unhead/schema-org'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const app = useAppStore()
|
||||
const yun = useYunAppStore()
|
||||
const site = useSiteStore()
|
||||
const frontmatter = useFrontmatter()
|
||||
|
||||
|
@ -14,6 +17,13 @@ const route = useRoute()
|
|||
const curCategory = computed(() => (route.query.category as string || ''))
|
||||
const categories = useCategories()
|
||||
|
||||
const pageIcon = computed(() => {
|
||||
if (!frontmatter.value.icon)
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
frontmatter.value.icon = 'i-ri-folder-2-line'
|
||||
return frontmatter.value.icon
|
||||
})
|
||||
|
||||
const posts = computed(() => {
|
||||
const list = site.postList.filter((post) => {
|
||||
if (post.categories && curCategory.value !== 'Uncategorized') {
|
||||
|
@ -39,38 +49,47 @@ useSchemaOrg([
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<YunSidebar v-if="$slots['sidebar-child']">
|
||||
<slot name="sidebar-child" />
|
||||
</YunSidebar>
|
||||
<YunSidebar v-else />
|
||||
<div
|
||||
flex="~"
|
||||
class="mt-24 md:mt-36 w-full max-w-screen-2xl m-auto justify-center items-start gap-4"
|
||||
:class="{
|
||||
'flex-col': app.isMobile,
|
||||
}"
|
||||
>
|
||||
<YunSidebarCard v-if="yun.size.isLg" />
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
:title="title || t('menu.categories')"
|
||||
:icon="frontmatter.icon || 'i-ri-folder-2-line'"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<div text="center" class="yun-text-light" p="2">
|
||||
{{ t('counter.categories', Array.from(categories.children).length) }}
|
||||
</div>
|
||||
<YunCategories :categories="categories.children" />
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<template #main-nav-before>
|
||||
<YunCard v-if="curCategory" class="post-collapse-container" m="t-4" w="full">
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
:title="curCategory === 'Uncategorized' ? t('category.uncategorized') : curCategory.split('/').join(' / ')"
|
||||
icon="i-ri-folder-open-line"
|
||||
class="mt-8"
|
||||
:title="title || t('menu.categories')"
|
||||
:icon="pageIcon"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
<YunPostCollapse w="full" m="b-4" p="x-20 lt-sm:x-5" :posts="posts" />
|
||||
</YunCard>
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<div text="center" class="yun-text-light" p="2">
|
||||
{{ t('counter.categories', Array.from(categories.children).length) }}
|
||||
</div>
|
||||
<YunCategories :categories="categories.children" />
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<template #main-nav-before>
|
||||
<YunCard v-if="curCategory" class="post-collapse-container" m="t-4" w="full">
|
||||
<YunPageHeader
|
||||
m="t-10"
|
||||
:title="curCategory === 'Uncategorized' ? t('category.uncategorized') : curCategory.split('/').join(' / ')"
|
||||
icon="i-ri-folder-open-line"
|
||||
/>
|
||||
<YunPostCollapse w="full" m="b-4" p="x-20 lt-sm:x-5" :posts="posts" />
|
||||
</YunCard>
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
</div>
|
||||
|
||||
<YunFooter />
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const yun = useYunAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<YunSidebar v-if="$slots['sidebar-child']">
|
||||
<slot name="sidebar-child" />
|
||||
</YunSidebar>
|
||||
<YunSidebar v-else />
|
||||
|
||||
<RouterView />
|
||||
|
||||
<div
|
||||
flex="~"
|
||||
class="mt-24 md:mt-36 w-full max-w-screen-2xl m-auto justify-center items-start gap-4"
|
||||
:class="{
|
||||
'flex-col': yun.size.isXs,
|
||||
}"
|
||||
>
|
||||
<YunSidebarCard v-if="yun.size.isLg" />
|
||||
<RouterView />
|
||||
</div>
|
||||
<YunFooter />
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<RouterView />
|
||||
</template>
|
|
@ -19,7 +19,11 @@ const showNotice = computed(() => {
|
|||
<template>
|
||||
<main
|
||||
class="yun-main flex-center"
|
||||
:class="!yunStore.leftSidebar.isOpen ? 'pl-0' : 'md:pl-$va-sidebar-width'" flex="~ col" w="full"
|
||||
:class="{
|
||||
'pl-0': !yunStore.leftSidebar.isOpen,
|
||||
'md:pl-$va-sidebar-width': yunStore.leftSidebar.isOpen,
|
||||
'pt-36': isPage,
|
||||
}" flex="~ col" w="full"
|
||||
>
|
||||
<YunSidebar :show-hamburger="true" />
|
||||
|
||||
|
@ -31,7 +35,11 @@ const showNotice = computed(() => {
|
|||
|
||||
<YunNotice
|
||||
v-if="showNotice"
|
||||
:content="themeConfig.notice.content" mt="4"
|
||||
class="mb-10"
|
||||
:class="{
|
||||
'mt-4': !isPage,
|
||||
}"
|
||||
:content="themeConfig.notice.content"
|
||||
/>
|
||||
|
||||
<slot name="board" />
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useFrontmatter, useFullUrl, useSiteConfig } from 'valaxy'
|
||||
import { useAppStore, useFrontmatter, useFullUrl, useSiteConfig } from 'valaxy'
|
||||
|
||||
import type { Article } from '@unhead/schema-org'
|
||||
import { defineArticle, useSchemaOrg } from '@unhead/schema-org'
|
||||
import { toDate } from 'date-fns'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
const app = useAppStore()
|
||||
const yun = useYunAppStore()
|
||||
const siteConfig = useSiteConfig()
|
||||
const frontmatter = useFrontmatter()
|
||||
const url = useFullUrl()
|
||||
|
@ -41,18 +44,26 @@ useSchemaOrg(
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-40">
|
||||
<YunSidebar v-if="$slots['sidebar-child']">
|
||||
<div
|
||||
flex="~"
|
||||
class="mt-24 md:mt-36 w-full max-w-screen-2xl m-auto justify-center items-start gap-4"
|
||||
:class="{
|
||||
'flex-col': app.isMobile,
|
||||
}"
|
||||
>
|
||||
<!-- <YunSidebar v-if="$slots['sidebar-child']">
|
||||
<slot name="sidebar-child" />
|
||||
</YunSidebar>
|
||||
<YunSidebar v-else />
|
||||
<YunSidebar v-else /> -->
|
||||
|
||||
<RouterView v-slot="{ Component }" class="mt-30">
|
||||
<YunSidebarCard v-if="yun.size.isLg" />
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header-after>
|
||||
<YunPostMeta :frontmatter="frontmatter" />
|
||||
<YunWalineMeta />
|
||||
<YunPostCategoriesAndTags :frontmatter="frontmatter" />
|
||||
|
||||
<YunPostCategoriesAndTags class="mt-2" :frontmatter="frontmatter" />
|
||||
</template>
|
||||
|
||||
<template #main-content-after>
|
||||
|
@ -65,5 +76,9 @@ useSchemaOrg(
|
|||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
|
||||
<YunAside v-if="!app.isMobile" />
|
||||
</div>
|
||||
|
||||
<YunFooter />
|
||||
</template>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import { useFrontmatter, useInvisibleElement, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { useAppStore, useFrontmatter, useInvisibleElement, usePostTitle, useSiteStore } from 'valaxy'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { defineWebPage, useSchemaOrg } from '@unhead/schema-org'
|
||||
import { useThemeConfig, useYunTags } from '../composables'
|
||||
import { useYunAppStore } from '../stores'
|
||||
|
||||
useSchemaOrg([
|
||||
defineWebPage({
|
||||
|
@ -12,6 +13,9 @@ useSchemaOrg([
|
|||
}),
|
||||
])
|
||||
|
||||
const app = useAppStore()
|
||||
const yun = useYunAppStore()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -58,46 +62,54 @@ const title = usePostTitle(frontmatter)
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<YunSidebar v-if="$slots['sidebar-child']">
|
||||
<slot name="sidebar-child" />
|
||||
</YunSidebar>
|
||||
<YunSidebar v-else />
|
||||
<div
|
||||
flex="~"
|
||||
class="mt-24 md:mt-36 w-full max-w-screen-2xl m-auto justify-center items-start gap-4"
|
||||
:class="{
|
||||
'flex-col': app.isMobile,
|
||||
}"
|
||||
>
|
||||
<YunSidebarCard v-if="yun.size.isLg" />
|
||||
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
:title="title || t('menu.tags')"
|
||||
:icon="frontmatter.icon || 'i-ri-tag-line'"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<div class="yun-text-light" text="center" p="2">
|
||||
{{ t('counter.tags', Array.from(tags).length) }}
|
||||
</div>
|
||||
|
||||
<div class="justify-center items-end" flex="~ wrap" gap="1">
|
||||
<YunLayoutPostTag
|
||||
v-for="[key, tag] in Array.from(tags).sort()"
|
||||
:key="key"
|
||||
:title="key"
|
||||
:count="tag.count"
|
||||
:style="getTagStyle(tag.count)"
|
||||
@click="displayTag(key.toString())"
|
||||
<RouterView v-slot="{ Component }">
|
||||
<component :is="Component">
|
||||
<template #main-header>
|
||||
<YunPageHeader
|
||||
class="mt-8"
|
||||
:title="title || t('menu.tags')"
|
||||
:icon="frontmatter.icon || 'i-ri-tag-line'"
|
||||
:color="frontmatter.color"
|
||||
:page-title-class="frontmatter.pageTitleClass"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #main-content>
|
||||
<div class="yun-text-light" text="center" p="2">
|
||||
{{ t('counter.tags', Array.from(tags).length) }}
|
||||
</div>
|
||||
|
||||
<RouterView />
|
||||
</template>
|
||||
<div class="justify-center items-end" flex="~ wrap" gap="1">
|
||||
<YunLayoutPostTag
|
||||
v-for="[key, tag] in Array.from(tags).sort()"
|
||||
:key="key"
|
||||
:title="key"
|
||||
:count="tag.count"
|
||||
:style="getTagStyle(tag.count)"
|
||||
@click="displayTag(key.toString())"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #main-nav-before>
|
||||
<YunCard v-if="curTag" ref="collapse" m="t-4" w="full">
|
||||
<YunPageHeader :title="curTag" icon="i-ri-hashtag" />
|
||||
<YunPostCollapse w="full" m="b-4" p="x-20 lt-sm:x-5" :posts="posts" />
|
||||
</YunCard>
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<template #main-nav-before>
|
||||
<YunCard v-if="curTag" ref="collapse" m="t-4" w="full">
|
||||
<YunPageHeader :title="curTag" icon="i-ri-hashtag" />
|
||||
<YunPostCollapse w="full" m="b-4" p="x-20 lt-sm:x-5" :posts="posts" />
|
||||
</YunCard>
|
||||
</template>
|
||||
</component>
|
||||
</RouterView>
|
||||
</div>
|
||||
|
||||
<YunFooter />
|
||||
</template>
|
||||
|
|
|
@ -25,7 +25,9 @@
|
|||
"@iconify-json/simple-icons": "^1.2.5",
|
||||
"@vueuse/motion": "^2.2.5",
|
||||
"animejs": "^3.2.2",
|
||||
"gsap": "^3.12.5"
|
||||
"gsap": "^3.12.5",
|
||||
"primevue": "^4.0.7",
|
||||
"radix-vue": "^1.9.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/animejs": "^3.1.12",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
// dynamic page
|
||||
// layout is home
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -2,12 +2,23 @@ import { defineAppSetup } from 'valaxy'
|
|||
import '../styles/global.scss'
|
||||
import { MotionPlugin } from '@vueuse/motion'
|
||||
|
||||
import PrimeVue from 'primevue/config'
|
||||
import AnimateOnScroll from 'primevue/animateonscroll'
|
||||
import StyleClass from 'primevue/styleclass'
|
||||
|
||||
export default defineAppSetup((ctx) => {
|
||||
// can do for setup
|
||||
|
||||
const { router, app } = ctx
|
||||
// https://motion.vueuse.org/
|
||||
app.use(MotionPlugin)
|
||||
// primevue
|
||||
app.use(PrimeVue, {
|
||||
unstyled: true,
|
||||
})
|
||||
app.directive('animateonscroll', AnimateOnScroll)
|
||||
app.directive('styleclass', StyleClass)
|
||||
|
||||
router.afterEach(() => {
|
||||
/**
|
||||
* router import order
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useToggle } from '@vueuse/core'
|
||||
import { useScroll, useToggle } from '@vueuse/core'
|
||||
import { useScreenSize } from 'valaxy'
|
||||
|
||||
export const useYunAppStore = defineStore('yun-app', () => {
|
||||
const { y } = useScroll(window)
|
||||
// 左侧边栏
|
||||
const [isLeftSidebarOpen, toggleLeftSidebar] = useToggle()
|
||||
// 右侧边栏
|
||||
const [isRightSidebarOpen, toggleRightSidebar] = useToggle()
|
||||
// 全屏菜单
|
||||
const [isFullscreenMenuOpen, toggleFullscreenMenu] = useToggle()
|
||||
|
||||
// init once
|
||||
const size = useScreenSize()
|
||||
|
||||
return {
|
||||
size,
|
||||
leftSidebar: {
|
||||
isOpen: isLeftSidebarOpen,
|
||||
toggle: toggleLeftSidebar,
|
||||
},
|
||||
rightSidebar: {
|
||||
isOpen: isRightSidebarOpen,
|
||||
toggle: toggleRightSidebar,
|
||||
},
|
||||
fullscreenMenu: {
|
||||
isOpen: isFullscreenMenuOpen,
|
||||
toggle: toggleFullscreenMenu,
|
||||
},
|
||||
scrollY: y,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
// cubic-bezier(0.18, 0.66, 0.05, 0.96)
|
||||
// cubic-bezier(0.77, 0, 0.17, 1.02)
|
||||
|
||||
@use 'sass:map';
|
||||
@use '../vars.scss' as *;
|
||||
|
||||
.v-enter-active,
|
||||
.v-leave-active {
|
||||
transition: opacity .2s map.get($cubic-bezier, 'ease-in');
|
||||
}
|
||||
|
||||
.v-enter-from,
|
||||
.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
opacity: 1;
|
||||
|
|
|
@ -224,3 +224,26 @@ export interface ThemeConfig extends DefaultTheme.Config {
|
|||
}
|
||||
|
||||
export type UserThemeConfig = Partial<ThemeConfig>
|
||||
|
||||
/**
|
||||
* For user links
|
||||
*/
|
||||
export interface LinkType {
|
||||
avatar: string
|
||||
name: string
|
||||
url: string
|
||||
color: string
|
||||
blog: string
|
||||
desc: string
|
||||
}
|
||||
|
||||
/**
|
||||
* girl
|
||||
*/
|
||||
export interface GirlType {
|
||||
name: string
|
||||
url: string
|
||||
avatar: string
|
||||
from?: string
|
||||
reason?: string
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { defineConfig } from 'unocss'
|
|||
export default defineConfig({
|
||||
shortcuts: [
|
||||
['yun-main', 'lt-md:pl-0'],
|
||||
['yun-card', 'm-auto transition yun-transition shadow hover:shadow-lg'],
|
||||
['yun-card', 'transition yun-transition shadow hover:shadow-lg'],
|
||||
],
|
||||
rules: [
|
||||
[
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './useInvisibleElement'
|
||||
export * from './useScreenSize'
|
|
@ -0,0 +1,22 @@
|
|||
import { useMediaQuery } from '@vueuse/core'
|
||||
|
||||
/**
|
||||
* breakpoints ref https://tailwindcss.com/docs/screens
|
||||
*/
|
||||
export function useScreenSize() {
|
||||
const isXs = useMediaQuery('(max-width: 639px)')
|
||||
const isSm = useMediaQuery('(min-width: 640px)')
|
||||
const isMd = useMediaQuery('(min-width: 768px)')
|
||||
const isLg = useMediaQuery('(min-width: 1024px)')
|
||||
const isXl = useMediaQuery('(min-width: 1280px)')
|
||||
const is2xl = useMediaQuery('(min-width: 1536px)')
|
||||
|
||||
return {
|
||||
isXs,
|
||||
isSm,
|
||||
isMd,
|
||||
isLg,
|
||||
isXl,
|
||||
is2xl,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import { acceptHMRUpdate, defineStore } from 'pinia'
|
||||
import { useToggle } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { useMobile, useThemeConfig, useValaxyDark } from 'valaxy'
|
||||
|
||||
|
@ -18,9 +17,6 @@ export const useAppStore = defineStore('app', () => {
|
|||
const isMobile = useMobile()
|
||||
const showLoading = ref(true)
|
||||
|
||||
// right sidebar with toc
|
||||
const [isRightSidebarOpen, toggleRightSidebar] = useToggle(false)
|
||||
|
||||
return {
|
||||
isMobile,
|
||||
// for dark
|
||||
|
@ -30,9 +26,6 @@ export const useAppStore = defineStore('app', () => {
|
|||
toggleDarkWithTransition,
|
||||
|
||||
showLoading,
|
||||
|
||||
isRightSidebarOpen,
|
||||
toggleRightSidebar,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ const EXCLUDE = [
|
|||
]
|
||||
|
||||
export function createConfigPlugin(options: ResolvedValaxyOptions): Plugin {
|
||||
const themeDeps = Object.keys((options.config.themeConfig.pkg.dependencies || {}))
|
||||
// const themeDeps = Object.keys((options.config.themeConfig.pkg.dependencies || {}))
|
||||
const addonDeps = options.addons.map(i => Object.keys(i.pkg.dependencies || {})).flat()
|
||||
|
||||
return {
|
||||
|
@ -94,8 +94,8 @@ export function createConfigPlugin(options: ResolvedValaxyOptions): Plugin {
|
|||
// must need it
|
||||
include: uniq([
|
||||
...clientDeps,
|
||||
// theme deps
|
||||
...themeDeps,
|
||||
// remove theme deps, for primevue parse entry
|
||||
// ...themeDeps,
|
||||
// addon deps
|
||||
...addonDeps,
|
||||
]).filter(i => !EXCLUDE.includes(i)),
|
||||
|
|
|
@ -705,6 +705,12 @@ importers:
|
|||
gsap:
|
||||
specifier: ^3.12.5
|
||||
version: 3.12.5
|
||||
primevue:
|
||||
specifier: ^4.0.7
|
||||
version: 4.0.7(vue@3.5.10(typescript@5.6.2))
|
||||
radix-vue:
|
||||
specifier: ^1.9.6
|
||||
version: 1.9.6(vue@3.5.10(typescript@5.6.2))
|
||||
devDependencies:
|
||||
'@types/animejs':
|
||||
specifier: ^3.1.12
|
||||
|
@ -1877,6 +1883,24 @@ packages:
|
|||
'@polka/url@1.0.0-next.28':
|
||||
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
|
||||
|
||||
'@primeuix/styled@0.0.5':
|
||||
resolution: {integrity: sha512-pVoGn/uPkVm/DyF3TR3EmH/pL/dP4nR42FcYbVduFq9VfO3KVeOEqvcCULHXos66RZO9MCbCFUoLy6ctf9GUGQ==}
|
||||
engines: {node: '>=12.11.0'}
|
||||
|
||||
'@primeuix/utils@0.0.5':
|
||||
resolution: {integrity: sha512-ntUiUgtRtkF8KuaxHffzhYxQxoXk6LAPHm7CVlFjdqS8Rx8xRkLkZVyo84E+pO2hcNFkOGVP/GxHhQ2s94O8zA==}
|
||||
engines: {node: '>=12.11.0'}
|
||||
|
||||
'@primevue/core@4.0.7':
|
||||
resolution: {integrity: sha512-SvWiNBEeR6hm4wjnze+rITUjHMFLwIzpRFlq+GqmJyZmjJy4h8UUksi0EoyqAWCAwKgmwlxY6XNqGJmMVyOguQ==}
|
||||
engines: {node: '>=12.11.0'}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
'@primevue/icons@4.0.7':
|
||||
resolution: {integrity: sha512-tj4dfRdV5iN6O0mbkpjhMsGlT3wZTqOPL779ndY5gKuCwN5zcFmKmABWVQmr/ClRivnMkw6Yr1x6gRTV/N0ydg==}
|
||||
engines: {node: '>=12.11.0'}
|
||||
|
||||
'@radix-ui/colors@3.0.0':
|
||||
resolution: {integrity: sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==}
|
||||
|
||||
|
@ -6602,6 +6626,10 @@ packages:
|
|||
resolution: {integrity: sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
primevue@4.0.7:
|
||||
resolution: {integrity: sha512-88qazHqldkqsCxvhjnjO65XMBfJyHQoFW3BQvrJYO6RqPheHB4f7cY61eqtBpJAjnM5x+YKTZiWx/gBuUzqT7Q==}
|
||||
engines: {node: '>=12.11.0'}
|
||||
|
||||
process-nextick-args@2.0.1:
|
||||
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
|
||||
|
||||
|
@ -9786,6 +9814,25 @@ snapshots:
|
|||
|
||||
'@polka/url@1.0.0-next.28': {}
|
||||
|
||||
'@primeuix/styled@0.0.5':
|
||||
dependencies:
|
||||
'@primeuix/utils': 0.0.5
|
||||
|
||||
'@primeuix/utils@0.0.5': {}
|
||||
|
||||
'@primevue/core@4.0.7(vue@3.5.10(typescript@5.6.2))':
|
||||
dependencies:
|
||||
'@primeuix/styled': 0.0.5
|
||||
'@primeuix/utils': 0.0.5
|
||||
vue: 3.5.10(typescript@5.6.2)
|
||||
|
||||
'@primevue/icons@4.0.7(vue@3.5.10(typescript@5.6.2))':
|
||||
dependencies:
|
||||
'@primeuix/utils': 0.0.5
|
||||
'@primevue/core': 4.0.7(vue@3.5.10(typescript@5.6.2))
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
'@radix-ui/colors@3.0.0': {}
|
||||
|
||||
'@react-dnd/asap@4.0.1': {}
|
||||
|
@ -15265,6 +15312,15 @@ snapshots:
|
|||
dependencies:
|
||||
parse-ms: 4.0.0
|
||||
|
||||
primevue@4.0.7(vue@3.5.10(typescript@5.6.2)):
|
||||
dependencies:
|
||||
'@primeuix/styled': 0.0.5
|
||||
'@primeuix/utils': 0.0.5
|
||||
'@primevue/core': 4.0.7(vue@3.5.10(typescript@5.6.2))
|
||||
'@primevue/icons': 4.0.7(vue@3.5.10(typescript@5.6.2))
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
|
||||
prompts@2.4.2:
|
||||
|
|
Loading…
Reference in New Issue