refactor(theme): improve theme structure

This commit is contained in:
pengzhanbo 2024-06-15 09:18:10 +08:00
parent 93adb30012
commit 862c886ff6
90 changed files with 1897 additions and 1408 deletions

View File

@ -12,14 +12,14 @@
"vuepress": "2.0.0-rc.13"
},
"dependencies": {
"@iconify/json": "^2.2.217",
"@iconify/json": "^2.2.219",
"@vuepress/bundler-vite": "2.0.0-rc.13",
"anywhere": "^1.6.0",
"chart.js": "^4.4.3",
"echarts": "^5.5.0",
"flowchart.ts": "^3.0.0",
"mermaid": "^10.9.1",
"vue": "^3.4.27",
"vue": "^3.4.29",
"vuepress-theme-plume": "workspace:~"
},
"devDependencies": {

View File

@ -3,7 +3,7 @@
"type": "module",
"version": "1.0.0-rc.64",
"private": true,
"packageManager": "pnpm@9.1.4",
"packageManager": "pnpm@9.3.0",
"author": "pengzhanbo <q942450674@outlook.com> (https://github.com/pengzhanbo/)",
"license": "MIT",
"keywords": [
@ -54,7 +54,7 @@
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.4.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.5",
"lint-staged": "^15.2.7",
"rimraf": "^5.0.7",
"stylelint": "^16.6.1",
"tsconfig-vuepress": "^4.5.0",

View File

@ -43,7 +43,7 @@
"@vue/devtools-api": "6.6.1",
"chokidar": "^3.6.0",
"create-filter": "^1.0.1",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -40,7 +40,7 @@
"vuepress": "2.0.0-rc.13"
},
"dependencies": {
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -42,7 +42,7 @@
},
"dependencies": {
"@vuepress-plume/plugin-content-update": "workspace:~",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -41,7 +41,7 @@
},
"dependencies": {
"@iconify/vue": "^4.1.2",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -48,17 +48,17 @@
"dependencies": {
"@iconify/utils": "^2.1.24",
"@vuepress/helper": "2.0.0-rc.34",
"@vueuse/core": "^10.10.0",
"@vueuse/core": "^10.11.0",
"local-pkg": "^0.5.0",
"markdown-it-container": "^4.0.0",
"nanoid": "^5.0.7",
"shiki": "^1.6.3",
"tm-grammars": "^1.12.8",
"shiki": "^1.6.4",
"tm-grammars": "^1.12.10",
"tm-themes": "^1.4.3",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"devDependencies": {
"@iconify/json": "^2.2.217",
"@iconify/json": "^2.2.219",
"@types/markdown-it": "^14.1.1"
},
"publishConfig": {

View File

@ -50,9 +50,9 @@
"chokidar": "^3.6.0",
"cpx2": "^7.0.1",
"dotenv": "^16.4.5",
"esbuild": "^0.21.4",
"esbuild": "^0.21.5",
"execa": "^9.2.0",
"netlify-cli": "^17.26.0",
"netlify-cli": "^17.27.0",
"portfinder": "^1.0.32"
},
"devDependencies": {

View File

@ -43,7 +43,7 @@
"@vue/devtools-api": "6.6.1",
"chokidar": "^3.6.0",
"create-filter": "^1.0.1",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -36,7 +36,7 @@
"dependencies": {
"@netlify/functions": "^2.7.0",
"leancloud-storage": "^4.15.2",
"vue": "^3.4.27",
"vue": "^3.4.29",
"vue-router": "4.3.2",
"vuepress-plugin-netlify-functions": "workspace:~"
},

View File

@ -41,14 +41,14 @@
},
"dependencies": {
"@vuepress/helper": "2.0.0-rc.34",
"@vueuse/core": "^10.10.0",
"@vueuse/integrations": "^10.10.0",
"@vueuse/core": "^10.11.0",
"@vueuse/integrations": "^10.11.0",
"chokidar": "^3.6.0",
"focus-trap": "^7.5.4",
"mark.js": "^8.11.1",
"minisearch": "^6.3.0",
"p-map": "^7.0.2",
"vue": "^3.4.27"
"vue": "^3.4.29"
},
"publishConfig": {
"access": "public"

View File

@ -36,18 +36,18 @@
"vuepress": "2.0.0-rc.13"
},
"dependencies": {
"@shikijs/transformers": "^1.6.3",
"@shikijs/twoslash": "^1.6.3",
"@shikijs/transformers": "^1.6.4",
"@shikijs/twoslash": "^1.6.4",
"@types/hast": "^3.0.4",
"@vuepress/helper": "2.0.0-rc.34",
"floating-vue": "^5.2.2",
"mdast-util-from-markdown": "^2.0.1",
"mdast-util-gfm": "^3.0.0",
"mdast-util-to-hast": "^13.1.0",
"mdast-util-to-hast": "^13.2.0",
"nanoid": "^5.0.7",
"shiki": "^1.6.3",
"twoslash": "^0.2.6",
"twoslash-vue": "^0.2.6"
"shiki": "^1.6.4",
"twoslash": "^0.2.8",
"twoslash-vue": "^0.2.8"
},
"publishConfig": {
"access": "public"

1347
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -89,15 +89,15 @@
"@vuepress/plugin-sitemap": "2.0.0-rc.34",
"@vuepress/plugin-theme-data": "2.0.0-rc.34",
"@vuepress/plugin-watermark": "2.0.0-rc.34",
"@vueuse/core": "^10.10.0",
"@vueuse/core": "^10.11.0",
"bcrypt-ts": "^5.0.2",
"chokidar": "^3.6.0",
"date-fns": "^3.6.0",
"katex": "^0.16.10",
"lodash.merge": "^4.6.2",
"nanoid": "^5.0.7",
"vue": "^3.4.27",
"vue-router": "^4.3.2",
"vue": "^3.4.29",
"vue-router": "^4.3.3",
"vuepress-plugin-md-enhance": "2.0.0-rc.48",
"vuepress-plugin-md-power": "workspace:*"
}

View File

@ -6,7 +6,7 @@ import { isLinkHttp } from 'vuepress/shared'
import { useBlogExtract } from '../../composables/blog.js'
import { useData } from '../../composables/data.js'
import { inBrowser } from '../../utils/index.js'
import AutoLink from '../AutoLink.vue'
import VPLink from '../VPLink.vue'
const { theme } = useData()
const route = useRoute()
@ -86,14 +86,14 @@ const showBlogExtract = computed(() => {
</div>
</div>
<div v-if="hasBlogExtract" class="blog-nav" :class="{ 'no-avatar': !avatar }">
<AutoLink class="nav-link" :href="tags.link" no-icon>
<VPLink class="nav-link" :href="tags.link" no-icon>
<span class="vpi-tag icon" />
<span>{{ tags.text }}</span>
</AutoLink>
<AutoLink class="nav-link" :href="archives.link" no-icon>
</VPLink>
<VPLink class="nav-link" :href="archives.link" no-icon>
<span class="vpi-archive icon" />
<span>{{ archives.text }}</span>
</AutoLink>
</VPLink>
</div>
</div>
</div>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { useRoute } from 'vuepress/client'
import { useBlogExtract } from '../../composables/blog.js'
import AutoLink from '../AutoLink.vue'
import VPLink from '../VPLink.vue'
const props = defineProps<{
isLocal?: boolean
@ -14,7 +14,7 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
<template>
<div v-if="hasBlogExtract" class="blog-nav" :class="{ local: props.isLocal }">
<AutoLink
<VPLink
class="nav-link"
:class="{ active: route.path === tags.link }"
:href="tags.link"
@ -23,8 +23,8 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
<span class="text">{{ tags.text }}</span>
<span class="total">{{ tags.total }}</span>
<span class="icon vpi-chevron-right" />
</AutoLink>
<AutoLink
</VPLink>
<VPLink
class="nav-link"
:class="{ active: route.path === archives.link }"
:href="archives.link"
@ -33,7 +33,7 @@ const { hasBlogExtract, tags, archives } = useBlogExtract()
<span class="text">{{ archives.text }}</span>
<span class="total">{{ archives.total }}</span>
<span class="icon vpi-chevron-right" />
</AutoLink>
</VPLink>
</div>
</template>

View File

@ -3,7 +3,7 @@ import { computed } from 'vue'
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { useData } from '../../composables/data.js'
import SocialLinks from '../SocialLinks.vue'
import VPSocialLinks from '../VPSocialLinks.vue'
const { theme } = useData()
const avatar = computed(() => theme.value.avatar)
@ -35,7 +35,7 @@ const imageUrl = computed(() => {
</div>
</div>
<div v-if="theme.social" class="avatar-social">
<SocialLinks :links="theme.social" />
<VPSocialLinks :links="theme.social" />
</div>
</div>
</template>
@ -106,12 +106,12 @@ const imageUrl = computed(() => {
transition: border var(--t-color);
}
.avatar-social :deep(.social-link) {
.avatar-social :deep(.vp-social-link) {
width: 32px;
height: 32px;
}
.avatar-social :deep(.social-link:hover) {
.avatar-social :deep(.vp-social-link:hover) {
color: var(--vp-c-brand-1);
}
</style>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'
import type { PlumeThemeBlogPostItem } from '../../../shared/index.js'
import { useTagColors } from '../../composables/tag-colors.js'
import AutoLink from '../AutoLink.vue'
import VPLink from '../VPLink.vue'
const props = defineProps<{
post: PlumeThemeBlogPostItem
@ -38,9 +38,9 @@ const createTime = computed(() =>
TOP
</div>
<span v-if="post.encrypt" class="icon-lock vpi-lock" />
<AutoLink :href="post.path">
<VPLink :href="post.path">
{{ post.title }}
</AutoLink>
</VPLink>
</h3>
<div class="post-meta">
<div v-if="categoryList.length" class="category-list">

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import AutoLink from '../AutoLink.vue'
import VPLink from '../VPLink.vue'
defineProps<{
postList: {
@ -14,9 +14,9 @@ defineProps<{
<ul class="post-list">
<li v-for="post in postList" :key="post.path">
<p class="post-title">
<AutoLink class="post-link" :href="post.path">
<VPLink class="post-link" :href="post.path">
{{ post.title }}
</AutoLink>
</VPLink>
</p>
<span class="post-time">{{ post.createTime }}</span>
</li>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup>
import VIcon from '../VIcon.vue'
import VPIcon from '../VPIcon.vue'
import MenuLink from './MenuLink.vue'
defineProps<{
@ -12,7 +12,7 @@ defineProps<{
<template>
<div class="menu-group">
<p v-if="text" class="title">
<VIcon v-if="icon" :name="icon" />
<VPIcon v-if="icon" :name="icon" />
<span v-text="text" />
</p>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import { isActive } from '../../utils/index.js'
import AutoLink from '../AutoLink.vue'
import VIcon from '../VIcon.vue'
import VPLink from '../VPLink.vue'
import VPIcon from '../VPIcon.vue'
defineProps<{
item: any
@ -13,7 +13,7 @@ const { page } = useData()
<template>
<div class="menu-link">
<AutoLink
<VPLink
:class="{
active: isActive(
page.path,
@ -23,9 +23,9 @@ const { page } = useData()
}"
:href="item.link"
>
<VIcon v-if="item.icon" :name="item.icon" />
<VPIcon v-if="item.icon" :name="item.icon" />
<i v-text="item.text" />
</AutoLink>
</VPLink>
</div>
</template>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { ref } from 'vue'
import { useFlyout } from '../../composables/flyout.js'
import VIcon from '../VIcon.vue'
import VPIcon from '../VPIcon.vue'
import VMenu from './VMenu.vue'
defineProps<{
@ -45,7 +45,7 @@ export default {
@click="open = !open"
>
<span v-if="button || icon" class="text">
<VIcon v-if="prefixIcon" :name="prefixIcon" />
<VPIcon v-if="prefixIcon" :name="prefixIcon" />
<span v-if="icon" class="option-icon" :class="[icon]" />
<span v-if="button" v-html="button" />
<span class="vpi-chevron-down text-icon" />

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'
import { useData } from '../composables/data.js'
import { useEditNavLink } from '../composables/page.js'
import AutoLink from './AutoLink.vue'
import VPLink from './VPLink.vue'
import FriendsItem from './FriendsItem.vue'
import FriendsGroup from './FriendsGroup.vue'
@ -32,14 +32,14 @@ const groups = computed(() => matter.value.groups || [])
<FriendsGroup v-for="(group, index) in groups" :key="index" :group="group" />
<div v-if="editNavLink" class="edit-link">
<AutoLink
<VPLink
class="edit-link-button"
:href="editNavLink.link"
:no-icon="true"
>
<span class="vpi-square-pen edit-link-icon" aria-label="edit icon" />
{{ editNavLink.text }}
</AutoLink>
</VPLink>
</div>
</div>
</template>

View File

@ -3,7 +3,7 @@ import { isPlainObject } from '@vuepress/helper/client'
import { computed } from 'vue'
import type { FriendsItem } from '../../shared/index'
import { useDarkMode } from '../composables/dark-mode.js'
import AutoLink from './AutoLink.vue'
import VPLink from './VPLink.vue'
const props = defineProps<{
friend: FriendsItem
@ -30,7 +30,7 @@ const friendStyle = computed(() => {
<template>
<div class="friend" :style="friendStyle">
<AutoLink
<VPLink
class="avatar-link"
:href="friend.link"
no-icon
@ -39,16 +39,16 @@ const friendStyle = computed(() => {
class="avatar"
:style="{ backgroundImage: `url(${friend.avatar})` }"
/>
</AutoLink>
</VPLink>
<div class="content">
<AutoLink
<VPLink
class="title"
:href="friend.link"
no-icon
>
{{ friend.name }}
</AutoLink>
</VPLink>
<p v-if="friend.desc">
{{ friend.desc }}
</p>

View File

@ -60,7 +60,7 @@ let el: HTMLDivElement | null = null
watch(() => onlyOnce.value, value => nextTick(() => {
if (typeof document !== 'undefined') {
el ??= document.querySelector('#LayoutContent')
el ??= document.querySelector('#VPContent')
el?.classList.toggle('footer-no-border', value)
}
}), { immediate: true })

View File

@ -4,7 +4,7 @@ import { isLinkHttp } from 'vuepress/shared'
import { computed } from 'vue'
import type { PlumeThemeHomeBanner } from '../../../shared/index.js'
import { useData } from '../../composables/data.js'
import VButton from '../VButton.vue'
import VPButton from '../VPButton.vue'
const props = defineProps<PlumeThemeHomeBanner>()
@ -51,7 +51,7 @@ const actions = computed(() => props.hero?.actions ?? matter.value.hero?.actions
<div v-if="actions.length" class="actions">
<div v-for="action in actions" :key="action.link" class="action">
<VButton
<VPButton
tag="a"
size="medium"
:theme="action.theme"

View File

@ -1,13 +1,13 @@
<script setup lang="ts">
import type { PlumeThemeHomeFeature } from '../../../shared/index.js'
import AutoLink from '../AutoLink.vue'
import VImage from '../VImage.vue'
import VPLink from '../VPLink.vue'
import VPImage from '../VPImage.vue'
defineProps<PlumeThemeHomeFeature>()
</script>
<template>
<AutoLink
<VPLink
class="home-feature"
:href="link"
:rel="rel"
@ -17,14 +17,14 @@ defineProps<PlumeThemeHomeFeature>()
>
<article class="box">
<div v-if="typeof icon === 'object' && icon.wrap" class="icon">
<VImage
<VPImage
:image="icon"
:alt="icon.alt"
:height="icon.height || 48"
:width="icon.width || 48"
/>
</div>
<VImage
<VPImage
v-else-if="typeof icon === 'object'"
:image="icon"
:alt="icon.alt"
@ -41,7 +41,7 @@ defineProps<PlumeThemeHomeFeature>()
</p>
</div>
</article>
</AutoLink>
</VPLink>
</template>
<style scoped>

View File

@ -2,7 +2,7 @@
import { withBase } from 'vuepress/client'
import { isLinkHttp } from 'vuepress/shared'
import { computed, ref } from 'vue'
import VButton from '../VButton.vue'
import VPButton from '../VPButton.vue'
import { useData } from '../../composables/data.js'
import { useHomeHeroTintPlate } from '../../composables/home.js'
import type { PlumeThemeHomeHero } from '../../../shared/index.js'
@ -59,9 +59,14 @@ useHomeHeroTintPlate(
<div v-if="actions.length" class="actions">
<div class="action">
<VButton
v-for="action in actions" :key="action.link" tag="a" size="medium" :theme="action.theme"
:text="action.text" :href="action.link"
<VPButton
v-for="action in actions"
:key="action.link"
tag="a"
size="medium"
:theme="action.theme"
:text="action.text"
:href="action.link"
/>
</div>
</div>
@ -162,11 +167,11 @@ useHomeHeroTintPlate(
margin: 30px 0 0;
}
.action :deep(.VPButton) {
.action :deep(.vp-button) {
margin-right: 24px;
}
.action :deep(.VPButton:last-of-type) {
.action :deep(.vp-button:last-of-type) {
margin-right: 0;
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { PlumeThemeHomeProfile } from '../../../shared/index.js'
import VImage from '../VImage.vue'
import VPImage from '../VPImage.vue'
import { useData } from '../../composables/data.js'
import HomeBox from './HomeBox.vue'
@ -29,7 +29,7 @@ const profile = computed(() => {
:background-attachment="backgroundAttachment"
:full="full"
>
<VImage v-if="profile.avatar" :image="profile.avatar" :class="{ circle: profile.circle }" />
<VPImage v-if="profile.avatar" :image="profile.avatar" :class="{ circle: profile.circle }" />
<h3 v-if="profile.name" v-html="profile.name" />

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed } from 'vue'
import type { PlumeThemeHomeTextImage } from '../../../shared/index.js'
import VImage from '../VImage.vue'
import VPImage from '../VPImage.vue'
import HomeBox from './HomeBox.vue'
const props = defineProps<PlumeThemeHomeTextImage>()
@ -26,7 +26,7 @@ const maxWidth = computed(() => {
:container-class="{ reverse: type === 'text-image' }"
>
<div class="content-image">
<VImage :image="image" :style="{ maxWidth }" />
<VPImage :image="image" :style="{ maxWidth }" />
</div>
<div class="content-text plume-content">

View File

@ -1,67 +0,0 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useData, useSidebar } from '../composables/index.js'
const props = defineProps<{
isNotFound?: boolean
}>()
const { hasSidebar } = useSidebar()
const { theme, frontmatter } = useData()
const enabledExternalIcon = computed(() => {
return frontmatter.value.externalLink ?? theme.value.externalLinkIcon ?? true
})
</script>
<template>
<div
id="LayoutContent" class="layout-content" :class="{
'has-sidebar': hasSidebar && !props.isNotFound,
'external-link-icon-enabled': enabledExternalIcon,
}"
>
<slot />
</div>
</template>
<style scoped>
.layout-content {
flex-grow: 1;
flex-shrink: 0;
width: 100%;
padding-left: 0;
margin: var(--vp-layout-top-height, 0) auto 0;
transition: padding-left 0.2s ease;
}
.layout-content.is-home {
width: 100%;
max-width: 100%;
}
.layout-content.has-sidebar {
margin: 0;
}
@media (min-width: 960px) {
.layout-content {
padding-top: var(--vp-nav-height);
}
.layout-content.has-sidebar {
padding-left: var(--vp-sidebar-width);
margin: var(--vp-layout-top-height, 0) 0 0;
}
}
@media (min-width: 1440px) {
.layout-content.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>

View File

@ -1,56 +0,0 @@
<script setup lang="ts">
import type { MenuItem } from '../../composables/outline.js'
defineProps<{
headers: MenuItem[]
root?: boolean
}>()
function onClick({ target: el }: Event) {
const id = (el as HTMLAnchorElement).href!.split('#')[1]
const heading = document.getElementById(decodeURIComponent(id))
heading?.focus({ preventScroll: true })
}
</script>
<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers" :key="link">
<a class="outline-link" :href="link" :title="title" @click="onClick">{{ title }}</a>
<template v-if="children?.length">
<DocOutlineItem :headers="children" />
</template>
</li>
</ul>
</template>
<style scoped>
.root {
position: relative;
z-index: 1;
}
.nested {
padding-left: 16px;
}
.outline-link {
display: block;
overflow: hidden;
font-weight: 400;
line-height: 28px;
color: var(--vp-c-text-2);
text-overflow: ellipsis;
white-space: nowrap;
transition: color var(--t-color);
}
.outline-link:hover,
.outline-link.active {
color: var(--vp-c-text-1);
}
.outline-link.nested {
padding-left: 13px;
}
</style>

View File

@ -26,7 +26,7 @@ const classes = ref<Record<string, boolean>>({})
watchPostEffect(() => {
classes.value = {
'has-sidebar': hasSidebar.value,
'top': !!frontmatter.value.home && y.value === 0,
'top': frontmatter.value.pageLayout === 'home' && y.value === 0,
}
})
</script>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import SwitchAppearance from '../SwitchAppearance.vue'
import VPSwitchAppearance from '../VPSwitchAppearance.vue'
const { theme } = useData()
</script>
@ -10,7 +10,7 @@ const { theme } = useData()
v-if="theme.appearance && theme.appearance !== 'force-dark'"
class="navbar-appearance"
>
<SwitchAppearance />
<VPSwitchAppearance />
</div>
</template>

View File

@ -4,8 +4,8 @@ import { useData } from '../../composables/data.js'
import { useLangs } from '../../composables/langs.js'
import Flyout from '../Flyout/index.vue'
import MenuLink from '../Flyout/MenuLink.vue'
import SocialLinks from '../SocialLinks.vue'
import SwitchAppearance from '../SwitchAppearance.vue'
import VPSocialLinks from '../VPSocialLinks.vue'
import VPSwitchAppearance from '../VPSwitchAppearance.vue'
const { theme } = useData()
const { localeLinks, currentLang } = useLangs()
@ -46,14 +46,14 @@ const social = computed(() => {
{{ theme.appearanceText || 'Appearance' }}
</p>
<div class="appearance-action">
<SwitchAppearance />
<VPSwitchAppearance />
</div>
</div>
</div>
<div v-if="social" class="group">
<div class="item social-links">
<SocialLinks class="social-links-list" :links="social" />
<VPSocialLinks class="social-links-list" :links="social" />
</div>
</div>
</Flyout>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup>
import type { NavItemWithLink } from '../../../shared/index.js'
import { isActive } from '../../utils/index.js'
import AutoLink from '../AutoLink.vue'
import VIcon from '../VIcon.vue'
import VPLink from '../VPLink.vue'
import VPIcon from '../VPIcon.vue'
import { useData } from '../../composables/data.js'
defineProps<{
@ -13,7 +13,7 @@ const { page } = useData()
</script>
<template>
<AutoLink
<VPLink
class="navbar-menu-link" :class="{
active: isActive(
page.path,
@ -24,9 +24,9 @@ const { page } = useData()
:href="item.link"
:no-icon="true"
>
<VIcon v-if="item.icon" :name="item.icon" />
<VPIcon v-if="item.icon" :name="item.icon" />
<i v-text="item.text" />
</AutoLink>
</VPLink>
</template>
<style scoped>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { useData } from '../../composables/data.js'
import SocialLinks from '../SocialLinks.vue'
import VPSocialLinks from '../VPSocialLinks.vue'
const { theme } = useData()
@ -17,7 +17,7 @@ const social = computed(() => {
</script>
<template>
<SocialLinks
<VPSocialLinks
v-if="social"
class="navbar-social-links"
:links="social"

View File

@ -2,8 +2,8 @@
import { useRouteLocale } from 'vuepress/client'
import { useSidebar } from '../../composables/sidebar.js'
import { useData } from '../../composables/data.js'
import AutoLink from '../AutoLink.vue'
import VImage from '../VImage.vue'
import VPLink from '../VPLink.vue'
import VPImage from '../VPImage.vue'
const { theme, site } = useData()
const { hasSidebar } = useSidebar()
@ -12,14 +12,14 @@ const routeLocale = useRouteLocale()
<template>
<div class="navbar-title" :class="{ 'has-sidebar': hasSidebar }">
<AutoLink class="title" :href="theme.home ?? routeLocale">
<VImage
<VPLink class="title" :href="theme.home ?? routeLocale">
<VPImage
v-if="theme.logo"
class="logo"
:image="{ light: theme.logo, dark: theme.logoDark || theme.logo }"
/>
{{ site.title }}
</AutoLink>
</VPLink>
</div>
</template>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import SwitchAppearance from '../SwitchAppearance.vue'
import VPSwitchAppearance from '../VPSwitchAppearance.vue'
const { theme } = useData()
</script>
@ -13,7 +13,7 @@ const { theme } = useData()
<p class="text">
{{ theme.appearanceText ?? 'Appearance' }}
</p>
<SwitchAppearance />
<VPSwitchAppearance />
</div>
</template>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, ref } from 'vue'
import VIcon from '../VIcon.vue'
import VPIcon from '../VPIcon.vue'
import NavScreenMenuGroupLink from './NavScreenMenuGroupLink.vue'
import NavScreenMenuGroupSection from './NavScreenMenuGroupSection.vue'
@ -30,7 +30,7 @@ function toggle() {
@click="toggle"
>
<span class="button-text">
<VIcon v-if="icon" :name="icon" />
<VPIcon v-if="icon" :name="icon" />
<i v-text="text" />
</span>
<span class="vpi-plus button-icon" />

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { inject } from 'vue'
import AutoLink from '../AutoLink.vue'
import VIcon from '../VIcon.vue'
import VPLink from '../VPLink.vue'
import VPIcon from '../VPIcon.vue'
defineProps<{
icon?: string | { svg: string }
@ -13,14 +13,14 @@ const closeScreen = inject('close-screen') as () => void
</script>
<template>
<AutoLink
<VPLink
class="nav-screen-menu-group-link"
:href="link"
@click="closeScreen"
>
<VIcon v-if="icon" :name="icon" />
<VPIcon v-if="icon" :name="icon" />
<i v-text="text" />
</AutoLink>
</VPLink>
</template>
<style scoped>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { NavItemWithLink } from '../../../shared/index.js'
import VIcon from '../VIcon.vue'
import VPIcon from '../VPIcon.vue'
import NavScreenMenuGroupLink from './NavScreenMenuGroupLink.vue'
defineProps<{
@ -13,7 +13,7 @@ defineProps<{
<template>
<div class="nav-screen-menu-group-section">
<p v-if="text" class="title">
<VIcon v-if="icon" :name="icon" />
<VPIcon v-if="icon" :name="icon" />
{{ text }}
</p>
<NavScreenMenuGroupLink

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { inject } from 'vue'
import AutoLink from '../AutoLink.vue'
import VIcon from '../VIcon.vue'
import VPLink from '../VPLink.vue'
import VPIcon from '../VPIcon.vue'
defineProps<{
text: string
@ -13,10 +13,10 @@ const closeScreen = inject('close-screen') as () => void
</script>
<template>
<AutoLink class="nav-screen-menu-link" :href="link" @click="closeScreen">
<VIcon v-if="icon" :name="icon" />
<VPLink class="nav-screen-menu-link" :href="link" @click="closeScreen">
<VPIcon v-if="icon" :name="icon" />
<i v-text="text" />
</AutoLink>
</VPLink>
</template>
<style scoped>

View File

@ -1,12 +1,12 @@
<script lang="ts" setup>
import { useData } from '../../composables/data.js'
import SocialLinks from '../SocialLinks.vue'
import VPSocialLinks from '../VPSocialLinks.vue'
const { theme } = useData()
</script>
<template>
<SocialLinks
<VPSocialLinks
v-if="theme.social"
class="VPNavScreenSocialLinks"
:links="theme.social"

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useLangs } from '../../composables/langs.js'
import AutoLink from '../AutoLink.vue'
import VPLink from '../VPLink.vue'
const { localeLinks, currentLang } = useLangs()
const isOpen = ref(false)
@ -25,9 +25,9 @@ function toggle() {
<ul class="list">
<li v-for="locale in localeLinks" :key="locale.link" class="item">
<AutoLink class="link" :href="locale.link">
<VPLink class="link" :href="locale.link">
{{ locale.text }}
</AutoLink>
</VPLink>
</li>
</ul>
</div>

View File

@ -12,7 +12,7 @@ const { isScreenOpen, closeScreen, toggleScreen } = useNav()
const fixedInclude = ['blog', 'friends', 'blog-archives', 'blog-tags']
const fixed = computed(() => {
return fixedInclude.includes(page.value.frontmatter.type as string)
return fixedInclude.includes(page.value.type as string)
})
provide('close-screen', closeScreen)

View File

@ -1,237 +0,0 @@
<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue'
import { useRoute } from 'vuepress/client'
import { useData } from '../composables/data.js'
import { useSidebar } from '../composables/sidebar.js'
import { usePageEncrypt } from '../composables/encrypt.js'
import PageAside from './PageAside.vue'
import PageFooter from './PageFooter.vue'
import PageMeta from './PageMeta.vue'
import EncryptPage from './EncryptPage.vue'
import TransitionFadeSlideY from './TransitionFadeSlideY.vue'
const { hasSidebar, hasAside } = useSidebar()
const { page, isDark } = useData()
const route = useRoute()
const { isPageDecrypted } = usePageEncrypt()
const hasComments = computed(() => {
return page.value.frontmatter.comments !== false
})
const enableAside = computed(() => {
if (page.value.isBlogPost)
return hasAside.value && isPageDecrypted.value && page.value.headers.length
return hasAside.value && isPageDecrypted.value
})
const asideEl = ref<HTMLElement>()
watch(
() => route.hash,
hash =>
nextTick(() => {
if (!asideEl.value)
return
const activeItem = asideEl.value.querySelector(
`.outline-link[href="${hash}"]`,
)
if (!activeItem || !hash) {
asideEl.value.scrollTop = 0
return
}
const { top: navTop, height: navHeight }
= asideEl.value.getBoundingClientRect()
const { top: activeTop, height: activeHeight }
= activeItem.getBoundingClientRect()
if (activeTop < navTop || activeTop + activeHeight > navTop + navHeight)
activeItem.scrollIntoView({ block: 'center' })
}),
{ immediate: true },
)
</script>
<template>
<TransitionFadeSlideY>
<div
:key="page.path" class="plume-page" :class="{
'has-sidebar': hasSidebar,
'has-aside': hasAside,
'is-blog': page.isBlogPost,
'with-encrypt': !isPageDecrypted,
}"
>
<div class="container">
<div v-if="enableAside" class="aside">
<div ref="asideEl" class="aside-container">
<div class="aside-content">
<PageAside />
</div>
</div>
</div>
<div class="content">
<div class="content-container">
<PageMeta />
<EncryptPage v-if="!isPageDecrypted" />
<template v-else>
<Content class="plume-content" />
<PageFooter />
<PageComment v-if="hasComments" :darkmode="isDark" />
</template>
</div>
</div>
</div>
</div>
</TransitionFadeSlideY>
</template>
<style scoped>
.plume-page {
position: relative;
display: flex;
}
.plume-page {
width: 100%;
min-height: calc(100vh - var(--vp-nav-height) - var(--vp-footer-height, 0px) - 49px);
padding: 32px 24px 96px;
}
.plume-page.with-encrypt {
padding: 32px 24px;
}
.container {
width: 100%;
margin: 0 auto;
}
.aside {
position: relative;
display: none;
flex-grow: 1;
order: 2;
width: 100%;
max-width: 256px;
padding-left: 32px;
}
.aside-container {
position: sticky;
top: 0;
min-height: calc(100vh - var(--vp-footer-height, 0px));
max-height: 100vh;
padding-top:
calc(
var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px
);
margin-top:
calc(
(var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1 - 32px
);
overflow: hidden auto;
scrollbar-width: none;
}
.aside-container::-webkit-scrollbar {
display: none;
}
.aside-content {
display: flex;
flex-direction: column;
min-height:
calc(
100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px + var(--vp-footer-height, 0px))
);
padding-bottom: 32px;
}
.content {
position: relative;
width: 100%;
margin: 0 auto;
}
.content-container {
margin: 0 auto;
}
.plume-page.has-aside .content-container {
max-width: 828px;
}
.giscus-wrapper {
padding: 5rem 0 0;
}
@media (min-width: 768px) {
.plume-page {
padding: 48px 32px 128px;
}
}
@media (min-width: 960px) {
.plume-page {
min-height: calc(100vh - var(--vp-nav-height) - var(--vp-footer-height, 0px));
}
.plume-page,
.plume-page.is-blog {
padding: 32px 32px 0;
}
.plume-page .container {
display: flex;
justify-content: center;
}
.plume-page.is-blog .aside {
display: block;
}
.plume-page:not(.has-sidebar) .container {
display: flex;
justify-content: center;
max-width: 992px;
}
.plume-page:not(.has-sidebar) .content {
max-width: 752px;
}
}
@media (min-width: 1280px) {
.plume-page .aside {
display: block;
}
}
@media (min-width: 1440px) {
.plume-page:not(.has-sidebar) .content {
max-width: 884px;
}
.plume-page:not(.has-sidebar) .container {
max-width: 1404px;
}
}
@media (min-width: 960px) {
.content {
padding: 0 32px 128px;
}
}
@media (min-width: 1280px) {
.content {
order: 1;
min-width: 640px;
margin: 0;
}
}
</style>

View File

@ -27,7 +27,7 @@ const stroke = computed(() =>
)
const mustHidden = computed(() => {
return page.value.frontmatter.backToTop === false || (page.value.frontmatter.home && page.value.frontmatter.config && (page.value.frontmatter.config as any).length <= 1)
return page.value.frontmatter.backToTop === false || (page.value.frontmatter.pageLayout === 'home' && page.value.frontmatter.config && (page.value.frontmatter.config as any).length <= 1)
})
const show = computed(() => {
@ -60,7 +60,7 @@ function handleClick() {
<button
v-show="!mustHidden && (show || isScrolling)"
type="button"
class="back-to-top-button"
class="vp-back-to-top"
aria-label="back to top"
@click="handleClick"
>
@ -74,7 +74,7 @@ function handleClick() {
</template>
<style scoped>
.back-to-top-button {
.vp-back-to-top {
position: fixed;
inset-inline-end: 1rem;
right: 24px;
@ -90,8 +90,8 @@ function handleClick() {
box-shadow var(--t-color);
}
.back-to-top-button .percent,
.back-to-top-button .icon {
.vp-back-to-top .percent,
.vp-back-to-top .icon {
position: absolute;
top: 0;
left: 0;
@ -99,12 +99,12 @@ function handleClick() {
transition: opacity 0.5s ease, color var(--t-color);
}
.back-to-top-button .percent.show,
.back-to-top-button .icon.show {
.vp-back-to-top .percent.show,
.vp-back-to-top .icon.show {
opacity: 1;
}
.back-to-top-button .percent {
.vp-back-to-top .percent {
width: 100%;
height: 100%;
font-size: 10px;
@ -113,7 +113,7 @@ function handleClick() {
user-select: none;
}
.back-to-top-button .icon {
.vp-back-to-top .icon {
top: 50%;
left: 50%;
width: 18px;
@ -122,12 +122,12 @@ function handleClick() {
transform: translate(-50%, -50%);
}
.back-to-top-button svg {
.vp-back-to-top svg {
width: 100%;
height: 100%;
}
.back-to-top-button svg circle {
.vp-back-to-top svg circle {
fill: none;
stroke: var(--vp-c-brand-2);
stroke-dasharray: 0% 314.1593%;
@ -139,23 +139,23 @@ function handleClick() {
}
@media (min-width: 768px) {
.back-to-top-button {
.vp-back-to-top {
bottom: calc(var(--vp-footer-height, 88px) - 24px);
width: 48px;
height: 48px;
}
.back-to-top-button .percent {
.vp-back-to-top .percent {
font-size: 14px;
line-height: 48px;
}
.back-to-top-button .icon {
.vp-back-to-top .icon {
width: 24px;
height: 24px;
}
.back-to-top-button svg circle {
.vp-back-to-top svg circle {
r: 22;
}
}
@ -171,7 +171,7 @@ function handleClick() {
}
@media print {
.back-to-top-button {
.vp-back-to-top {
display: none;
}
}

View File

@ -6,12 +6,12 @@ defineProps<{
<template>
<Transition name="fade">
<div v-if="show" class="backdrop" />
<div v-if="show" class="vp-backdrop" />
</Transition>
</template>
<style scoped>
.backdrop {
.vp-backdrop {
position: fixed;
top: 0;
@ -26,17 +26,17 @@ defineProps<{
transition: opacity var(--t-color);
}
.backdrop.fade-enter-from,
.backdrop.fade-leave-to {
.vp-backdrop.fade-enter-from,
.vp-backdrop.fade-leave-to {
opacity: 0;
}
.backdrop.fade-leave-active {
.vp-backdrop.fade-leave-active {
transition-duration: 0.25s;
}
@media (min-width: 1280px) {
.backdrop {
.vp-backdrop {
display: none;
}
}

View File

@ -38,8 +38,7 @@ function linkTo(e: Event) {
<template>
<Component
:is="component"
class="VPButton"
:class="[size, theme]"
class="vp-button" :class="[size, theme]"
:href="href"
:target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noreferrer' : undefined"
@ -50,7 +49,7 @@ function linkTo(e: Event) {
</template>
<style scoped>
.VPButton {
.vp-button {
display: inline-block;
font-weight: 600;
text-align: center;
@ -60,76 +59,76 @@ function linkTo(e: Event) {
transition-property: border, color, background-color;
}
.VPButton:active {
.vp-button:active {
transition:
color 0.1s,
border-color 0.1s,
background-color 0.1s;
}
.VPButton.medium {
.vp-button.medium {
padding: 0 20px;
font-size: 14px;
line-height: 38px;
border-radius: 20px;
}
.VPButton.big {
.vp-button.big {
padding: 0 24px;
font-size: 16px;
line-height: 46px;
border-radius: 24px;
}
.VPButton.brand {
.vp-button.brand {
color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg);
border-color: var(--vp-button-brand-border);
}
.VPButton.brand:hover {
.vp-button.brand:hover {
color: var(--vp-button-brand-hover-text);
background-color: var(--vp-button-brand-hover-bg);
border-color: var(--vp-button-brand-hover-border);
}
.VPButton.brand:active {
.vp-button.brand:active {
color: var(--vp-button-brand-active-text);
background-color: var(--vp-button-brand-active-bg);
border-color: var(--vp-button-brand-active-border);
}
.VPButton.alt {
.vp-button.alt {
color: var(--vp-button-alt-text);
background-color: var(--vp-button-alt-bg);
border-color: var(--vp-button-alt-border);
}
.VPButton.alt:hover {
.vp-button.alt:hover {
color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg);
border-color: var(--vp-button-alt-hover-border);
}
.VPButton.alt:active {
.vp-button.alt:active {
color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg);
border-color: var(--vp-button-alt-active-border);
}
.VPButton.sponsor {
.vp-button.sponsor {
color: var(--vp-button-sponsor-text);
background-color: var(--vp-button-sponsor-bg);
border-color: var(--vp-button-sponsor-border);
}
.VPButton.sponsor:hover {
.vp-button.sponsor:hover {
color: var(--vp-button-sponsor-hover-text);
background-color: var(--vp-button-sponsor-hover-bg);
border-color: var(--vp-button-sponsor-hover-border);
}
.VPButton.sponsor:active {
.vp-button.sponsor:active {
color: var(--vp-button-sponsor-active-text);
background-color: var(--vp-button-sponsor-active-bg);
border-color: var(--vp-button-sponsor-active-border);

View File

@ -0,0 +1,126 @@
<script setup lang="ts">
import { computed } from 'vue'
import { useData, useSidebar } from '../composables/index.js'
import Blog from './Blog/Blog.vue'
import VPDoc from './VPDoc.vue'
import VPPage from './VPPage.vue'
import Home from './Home/Home.vue'
import Friends from './Friends.vue'
const props = defineProps<{
isNotFound?: boolean
}>()
const { hasSidebar } = useSidebar()
const { frontmatter, page } = useData()
const isBlogLayout = computed(() => {
const { type } = page.value
return type === 'blog' || type === 'blog-archives' || type === 'blog-tags'
})
</script>
<template>
<div
id="VPContent"
vp-content
class="vp-content" :class="{
'has-sidebar': hasSidebar && !props.isNotFound,
'is-home': frontmatter.pageLayout === 'home',
}"
>
<Blog v-if="isBlogLayout" />
<VPPage v-else-if="frontmatter.pageLayout === 'page'">
<template #page-top>
<slot name="page-top" />
</template>
<template #page-bottom>
<slot name="page-bottom" />
</template>
</VPPage>
<Friends v-else-if="frontmatter.pageLayout === 'friends'" />
<Home v-else-if="frontmatter.pageLayout === 'home'" />
<component
:is="frontmatter.pageLayout"
v-else-if="frontmatter.pageLayout && frontmatter.pageLayout !== 'doc'"
/>
<VPDoc v-else>
<template #doc-top>
<slot name="doc-top" />
</template>
<template #doc-bottom>
<slot name="doc-bottom" />
</template>
<template #doc-footer-before>
<slot name="doc-footer-before" />
</template>
<template #doc-before>
<slot name="doc-before" />
</template>
<template #doc-after>
<slot name="doc-after" />
</template>
<template #aside-top>
<slot name="aside-top" />
</template>
<template #aside-outline-before>
<slot name="aside-outline-before" />
</template>
<template #aside-outline-after>
<slot name="aside-outline-after" />
</template>
<template #aside-ads-before>
<slot name="aside-ads-before" />
</template>
<template #aside-ads-after>
<slot name="aside-ads-after" />
</template>
<template #aside-bottom>
<slot name="aside-bottom" />
</template>
</VPDoc>
</div>
</template>
<style scoped>
.vp-content {
flex-grow: 1;
flex-shrink: 0;
width: 100%;
margin: var(--vp-layout-top-height, 0) auto 0;
}
.vp-content.is-home {
width: 100%;
max-width: 100%;
}
.vp-content.has-sidebar {
margin: 0;
}
@media (min-width: 960px) {
.vp-content {
padding-top: var(--vp-nav-height);
}
.vp-content.has-sidebar {
padding-left: var(--vp-sidebar-width);
margin: var(--vp-layout-top-height, 0) 0 0;
}
}
@media (min-width: 1440px) {
.vp-content.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>

View File

@ -0,0 +1,275 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import { useRoute } from 'vuepress/client'
import { useData } from '../composables/data.js'
import { useSidebar } from '../composables/sidebar.js'
import { usePageEncrypt } from '../composables/encrypt.js'
import TransitionFadeSlideY from './TransitionFadeSlideY.vue'
import VPDocAside from './VPDocAside.vue'
import VPDocFooter from './VPDocFooter.vue'
import VPEncryptPage from './VPEncryptPage.vue'
import VPDocMeta from './VPDocMeta.vue'
const { page, theme, frontmatter, isDark } = useData()
const route = useRoute()
const { hasSidebar, hasAside, leftAside } = useSidebar()
const { isPageDecrypted } = usePageEncrypt()
const hasComments = computed(() => {
return page.value.frontmatter.comments !== false
})
const enableAside = computed(() => {
if (page.value.isBlogPost)
return hasAside.value && isPageDecrypted.value && page.value.headers.length
return hasAside.value && isPageDecrypted.value
})
const pageName = computed(() =>
route.path.replace(/[./]+/g, '_').replace(/_html$/, ''),
)
const enabledExternalLinkIcon = computed(
() =>
theme.value.externalLinkIcon
&& frontmatter.value.externalLinkIcon !== false,
)
const asideEl = ref<HTMLElement>()
watch(
() => route.hash,
hash =>
nextTick(() => {
if (!asideEl.value)
return
const activeItem = asideEl.value.querySelector(
`.outline-link[href="${hash}"]`,
)
if (!activeItem || !hash) {
asideEl.value.scrollTop = 0
return
}
const { top: navTop, height: navHeight }
= asideEl.value.getBoundingClientRect()
const { top: activeTop, height: activeHeight }
= activeItem.getBoundingClientRect()
if (activeTop < navTop || activeTop + activeHeight > navTop + navHeight)
activeItem.scrollIntoView({ block: 'center' })
}),
{ immediate: true },
)
</script>
<template>
<TransitionFadeSlideY>
<div
:key="page.path" class="vp-doc-container" :class="{
'has-sidebar': hasSidebar,
'has-aside': hasAside,
'is-blog': page.isBlogPost,
'with-encrypt': !isPageDecrypted,
}"
>
<slot name="doc-top" />
<div class="container">
<div v-if="enableAside" class="aside" :class="{ 'left-aside': leftAside }">
<div class="aside-curtain" />
<div ref="asideEl" class="aside-container">
<div class="aside-content">
<VPDocAside>
<template #aside-top>
<slot name="aside-top" />
</template>
<template #aside-bottom>
<slot name="aside-bottom" />
</template>
<template #aside-outline-before>
<slot name="aside-outline-before" />
</template>
<template #aside-outline-after>
<slot name="aside-outline-after" />
</template>
<template #aside-ads-before>
<slot name="aside-ads-before" />
</template>
<template #aside-ads-after>
<slot name="aside-ads-after" />
</template>
</VPDocAside>
</div>
</div>
</div>
<div class="content">
<div class="content-container">
<slot name="doc-before" />
<main class="main">
<VPDocMeta />
<VPEncryptPage v-if="!isPageDecrypted" />
<Content
v-else class="vp-doc plume-content"
:class="[pageName, enabledExternalLinkIcon && 'external-link-icon-enabled']"
/>
</main>
<VPDocFooter>
<template #doc-footer-before>
<slot name="doc-footer-before" />
</template>
</VPDocFooter>
<PageComment v-if="hasComments" :darkmode="isDark" />
<slot name="doc-after" />
</div>
</div>
</div>
</div>
</TransitionFadeSlideY>
</template>
<style scoped>
.vp-doc-container {
width: 100%;
padding: 32px 24px 96px;
}
.vp-doc-container.with-encrypt {
padding: 32px 24px;
}
@media (min-width: 768px) {
.vp-doc-container {
padding: 48px 32px 128px;
}
}
@media (min-width: 960px) {
.vp-doc-container {
padding: 48px 32px 0;
}
.vp-doc-container:not(.has-sidebar) .container {
display: flex;
justify-content: center;
max-width: 992px;
}
.vp-doc-container:not(.has-sidebar) .content {
max-width: 752px;
}
}
@media (min-width: 1280px) {
.vp-doc-container .container {
display: flex;
justify-content: center;
}
.vp-doc-container .aside {
display: block;
}
}
@media (min-width: 1440px) {
.vp-doc-container:not(.has-sidebar) .content {
max-width: 784px;
}
.vp-doc-container:not(.has-sidebar) .container {
max-width: 1104px;
}
}
.container {
width: 100%;
margin: 0 auto;
}
.aside {
position: relative;
display: none;
flex-grow: 1;
order: 2;
width: 100%;
max-width: 256px;
padding-left: 32px;
}
.left-aside {
order: 1;
padding-right: 32px;
padding-left: unset;
}
.aside-container {
position: sticky;
top: 0;
min-height: calc(100vh - var(--vp-footer-height, 0px));
max-height: 100vh;
padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px);
margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1 - 32px);
overflow: hidden auto;
scrollbar-width: none;
}
.aside-container::-webkit-scrollbar {
display: none;
}
@property --vp-aside-curtain-bg {
inherits: false;
initial-value: #fff;
syntax: "<color>";
}
.aside-curtain {
--vp-aside-curtain-bg: var(--vp-c-bg);
position: fixed;
bottom: 0;
z-index: 10;
width: 224px;
height: 32px;
background: linear-gradient(transparent, var(--vp-aside-curtain-bg) 70%);
transition: --vp-aside-curtain-bg var(--t-color);
}
.aside-content {
display: flex;
flex-direction: column;
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));
padding-bottom: 32px;
}
.content {
position: relative;
width: 100%;
margin: 0 auto;
}
@media (min-width: 960px) {
.content {
padding: 0 32px 128px;
}
}
@media (min-width: 1280px) {
.content {
order: 1;
min-width: 640px;
margin: 0;
}
}
.content-container {
margin: 0 auto;
}
.vp-doc-container.has-aside .content-container {
max-width: 688px;
}
</style>

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import VPDocAsideOutline from './VPDocAsideOutline.vue'
</script>
<template>
<div class="vp-doc-aside">
<slot name="aside-top" />
<slot name="aside-outline-before" />
<VPDocAsideOutline />
<slot name="aside-outline-after" />
<div class="spacer" />
<slot name="aside-bottom" />
</div>
</template>
<style scoped>
.vp-doc-aside {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.spacer {
flex-grow: 1;
}
</style>

View File

@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import { type MenuItem, getHeaders, useActiveAnchor } from '../composables/outline.js'
import { useData } from '../composables/data.js'
import PageAsideItem from './PageAsideItem.vue'
import VPDocOutlineItem from './VPDocOutlineItem.vue'
const { theme, frontmatter } = useData()
@ -25,46 +25,37 @@ function handlePrint() {
</script>
<template>
<div class="page-aside">
<div
ref="container"
class="page-aside-outline"
:class="{ 'has-outline': hasOutline }"
>
<div class="content">
<div ref="marker" class="outline-marker" />
<nav
ref="container"
aria-labelledby="doc-outline-aria-label"
class="vp-doc-aside-outline"
:class="{ 'has-outline': hasOutline }"
role="navigation"
>
<div class="content">
<div ref="marker" class="outline-marker" />
<div class="outline-title">
<span>{{ theme.outlineLabel || 'On this page' }}</span>
<span class="vpi-print icon" @click="handlePrint" />
</div>
<nav aria-labelledby="doc-outline-aria-label">
<span id="doc-outline-aria-label" class="visually-hidden">
Table of Contents for current page
</span>
<PageAsideItem
:headers="headers"
:root="true"
/>
</nav>
<div
id="doc-outline-aria-label"
aria-level="2"
class="outline-title"
role="heading"
>
<span>{{ theme.outlineLabel || 'On this page' }}</span>
<span class="vpi-print icon" @click="handlePrint" />
</div>
<VPDocOutlineItem :headers="headers" :root="true" />
</div>
</div>
</nav>
</template>
<style scoped>
.page-aside {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.page-aside-outline {
.vp-doc-aside-outline {
display: none;
}
.page-aside-outline.has-outline {
.vp-doc-aside-outline.has-outline {
display: block;
}

View File

@ -7,7 +7,7 @@ import {
usePageNav,
} from '../composables/page.js'
import { useData } from '../composables/data.js'
import AutoLink from './AutoLink.vue'
import VPLink from './VPLink.vue'
const { theme } = useData()
const editNavLink = useEditNavLink()
@ -21,13 +21,15 @@ const showFooter = computed(() => {
</script>
<template>
<footer v-if="showFooter" class="page-footer">
<footer v-if="showFooter" class="vp-doc-footer">
<slot name="doc-footer-before" />
<div v-if="editNavLink || lastUpdated" class="edit-info">
<div v-if="editNavLink" class="edit-link">
<AutoLink class="edit-link-button" :href="editNavLink.link" :no-icon="true">
<VPLink class="edit-link-button" :href="editNavLink.link" :no-icon="true">
<span class="vpi-square-pen edit-link-icon" aria-label="edit icon" />
{{ editNavLink.text }}
</AutoLink>
</VPLink>
</div>
<div v-if="lastUpdated" class="last-updated">
@ -56,23 +58,23 @@ const showFooter = computed(() => {
<nav v-if="prev?.link || next?.link" class="prev-next">
<div class="pager">
<AutoLink v-if="prev?.link" class="pager-link prev" :href="prev.link">
<VPLink v-if="prev?.link" class="pager-link prev" :href="prev.link">
<span class="desc" v-html="theme.prevPageLabel || 'Previous page'" />
<span class="title" v-html="prev.text" />
</AutoLink>
</VPLink>
</div>
<div class="pager">
<AutoLink v-if="next?.link" class="pager-link next" :href="next.link">
<VPLink v-if="next?.link" class="pager-link next" :href="next.link">
<span class="desc" v-html="theme.nextPageLabel || 'Next page'" />
<span class="title" v-html="next.text" />
</AutoLink>
</VPLink>
</div>
</nav>
</footer>
</template>
<style scoped>
.page-footer {
.vp-doc-footer {
margin-top: 96px;
}

View File

@ -37,7 +37,7 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
<template>
<div
v-if="page.isBlogPost && categoryList.length"
class="page-category-wrapper"
class="vp-doc-category"
>
<template
v-for="({ type, name }, index) in categoryList"
@ -47,10 +47,10 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
<span v-if="index !== categoryList.length - 1" class="dot">&rsaquo;</span>
</template>
</div>
<h1 class="page-title" :class="{ padding: !hasMeta }">
<h1 class="vp-doc-title" :class="{ padding: !hasMeta }">
{{ page.title }}
</h1>
<div v-if="hasMeta" class="page-meta-wrapper">
<div v-if="hasMeta" class="vp-doc-meta">
<p v-if="matter.author" class="author">
<span class="icon vpi-user" />
<span>{{ matter.author }}</span>
@ -78,7 +78,7 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
</template>
<style scoped>
.page-category-wrapper {
.vp-doc-category {
padding-left: 1rem;
margin-bottom: 2rem;
font-size: 16px;
@ -87,21 +87,21 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
transition: border-left var(--t-color);
}
.page-category-wrapper .category {
.vp-doc-category .category {
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.page-category-wrapper .category:hover {
.vp-doc-category .category:hover {
color: var(--vp-c-brand-1);
}
.page-category-wrapper .dot {
.vp-doc-category .dot {
margin: 0 0.2rem;
color: var(--vp-c-text-3);
}
.page-title {
.vp-doc-title {
margin-bottom: 0.7rem;
font-size: 28px;
font-weight: 600;
@ -110,11 +110,11 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
transition: color var(--t-color);
}
.page-title.padding {
.vp-doc-title.padding {
padding-bottom: 4rem;
}
.page-meta-wrapper {
.vp-doc-meta {
display: flex;
flex-wrap: wrap;
align-items: center;
@ -127,25 +127,25 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
transition: color var(--t-color), border-bottom var(--t-color);
}
.page-meta-wrapper p {
.vp-doc-meta p {
display: flex;
align-items: center;
margin-right: 1rem;
}
.page-meta-wrapper .icon {
.vp-doc-meta .icon {
width: 14px;
height: 14px;
margin-right: 0.3rem;
}
.page-meta-wrapper .author .icon,
.page-meta-wrapper .author span {
.vp-doc-meta .author .icon,
.vp-doc-meta .author span {
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.page-meta-wrapper .tag {
.vp-doc-meta .tag {
display: inline-block;
padding: 3px 5px;
margin-right: 6px;
@ -156,26 +156,26 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
border-radius: 3px;
}
.page-meta-wrapper .tag:last-of-type {
.vp-doc-meta .tag:last-of-type {
margin-right: 0;
}
.page-meta-wrapper .reading-time span {
.vp-doc-meta .reading-time span {
margin-right: 8px;
}
.page-meta-wrapper .reading-time span:last-of-type {
.vp-doc-meta .reading-time span:last-of-type {
margin-right: 0;
}
.page-meta-wrapper .create-time {
.vp-doc-meta .create-time {
min-width: 110px;
margin-right: 0;
text-align: right;
}
@media (min-width: 768px) {
.page-meta-wrapper .create-time {
.vp-doc-meta .create-time {
flex: 1;
justify-content: right;
}

View File

@ -18,9 +18,11 @@ function handleClick({ target: el }: Event) {
<template>
<ul :class="root ? 'root' : 'nested'">
<li v-for="{ children, link, title } in headers" :key="link">
<a class="outline-link" :href="link" @click="handleClick">{{ title }}</a>
<a
class="outline-link" :href="link" @click="handleClick"
>{{ title }}</a>
<template v-if="children?.length">
<PageAsideItem :headers="children" />
<VPDocOutlineItem :headers="children" />
</template>
</li>
</ul>

View File

@ -2,8 +2,8 @@
import { computed } from 'vue'
import { useData } from '../composables/data.js'
import { useGlobalEncrypt } from '../composables/encrypt.js'
import VFooter from './VFooter.vue'
import EncryptForm from './EncryptForm.vue'
import VPFooter from './VPFooter.vue'
import VPEncryptForm from './VPEncryptForm.vue'
const { theme, site } = useData()
const { compareGlobal } = useGlobalEncrypt()
@ -23,10 +23,10 @@ const title = computed(() => avatar.value?.name || site.value.title)
{{ title }}
</h3>
</div>
<EncryptForm :compare="compareGlobal" :info="theme.encryptGlobalText" />
<VPEncryptForm :compare="compareGlobal" :info="theme.encryptGlobalText" />
</div>
</div>
<VFooter />
<VPFooter />
</template>
<style scoped>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import { usePageEncrypt } from '../composables/encrypt.js'
import { useData } from '../composables/data.js'
import EncryptForm from './EncryptForm.vue'
import VPEncryptForm from './VPEncryptForm.vue'
const { theme } = useData()
const { comparePage } = usePageEncrypt()
@ -12,7 +12,7 @@ const { comparePage } = usePageEncrypt()
<div class="logo">
<span class="vpi-lock icon-lock-head" />
</div>
<EncryptForm :compare="comparePage" :info="theme.encryptPageText" />
<VPEncryptForm :compare="comparePage" :info="theme.encryptPageText" />
</div>
</template>

View File

@ -21,7 +21,7 @@ onMounted(() => {
<footer
v-if="theme.footer"
ref="footer"
class="plume-footer"
class="vp-footer"
:class="{ 'has-sidebar': hasSidebar }"
>
<div class="container">
@ -40,7 +40,7 @@ onMounted(() => {
</template>
<style scoped>
.plume-footer {
.vp-footer {
position: relative;
z-index: var(--vp-z-index-footer);
padding: 24px;
@ -48,16 +48,16 @@ onMounted(() => {
transition: all var(--t-color);
}
.footer-no-border .plume-footer {
.footer-no-border .vp-footer {
border-top: none;
}
.plume-footer p {
.vp-footer p {
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.plume-footer :deep(a) {
.vp-footer :deep(a) {
color: var(--vp-c-text-2);
text-decoration-line: underline;
text-underline-offset: 2px;
@ -66,18 +66,24 @@ onMounted(() => {
text-underline-offset var(--t-color);
}
.plume-footer :deep(a:hover) {
.vp-footer :deep(a:hover) {
color: var(--vp-c-text-1);
text-underline-offset: 4px;
}
@media (min-width: 1440px) {
.plume-footer {
padding: 24px;
@media (min-width: 960px) {
.vp-footer.has-sidebar {
margin-left: var(--vp-sidebar-width);
}
.plume-footer.has-sidebar {
margin-right: calc(0px - ((100vw - var(--vp-layout-max-width)) / 2));
.vp-footer.vp-footer.has-sidebar .container {
margin-left: calc(0px - var(--vp-sidebar-width));
}
}
@media (min-width: 1440px) {
.vp-footer {
padding: 24px;
}
}

View File

@ -24,13 +24,13 @@ export default {
:alt="alt ?? (typeof image === 'string' ? '' : image.alt || '')"
>
<template v-else>
<VImage
<VPImage
class="dark"
:image="image.dark"
:alt="image.alt"
v-bind="$attrs"
/>
<VImage
<VPImage
class="light"
:image="image.light"
:alt="image.alt"

View File

@ -39,7 +39,7 @@ function linkTo(e: Event) {
<template>
<Component
:is="tag" class="auto-link" :class="{ link }"
:is="tag" class="vp-link" :class="{ link }"
:href="link"
:target="target ?? (isExternal ? '_blank' : undefined)"
:rel="rel ?? (isExternal ? 'noreferrer' : undefined)"
@ -61,7 +61,7 @@ function linkTo(e: Event) {
transition: fill 0.25s;
}
.auto-link :deep(i) {
.vp-link :deep(i) {
font-style: normal;
font-weight: inherit;
line-height: normal;

View File

@ -2,10 +2,10 @@
import { useWindowScroll } from '@vueuse/core'
import { computed, onMounted, ref } from 'vue'
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import { useSidebar } from '../../composables/sidebar.js'
import { type MenuItem, getHeaders } from '../../composables/outline.js'
import { useData } from '../../composables/data.js'
import LocalNavOutlineDropdown from './LocalNavOutlineDropdown.vue'
import { useSidebar } from '../composables/sidebar.js'
import { type MenuItem, getHeaders } from '../composables/outline.js'
import { useData } from '../composables/data.js'
import VPLocalNavOutlineDropdown from './VPLocalNavOutlineDropdown.vue'
const props = defineProps<{
open: boolean
@ -41,7 +41,7 @@ onMounted(() => {
const classes = computed(() => {
return {
'local-nav': true,
'vp-local-nav': true,
'fixed': empty.value,
'reached-top': y.value >= navHeight.value,
'is-blog': page.value.isBlogPost,
@ -67,12 +67,12 @@ const showLocalNav = computed(() => {
<span class="menu-text"> {{ theme.sidebarMenuLabel || 'Menu' }} </span>
</button>
<LocalNavOutlineDropdown v-if="showOutline" :headers="headers" :nav-height="navHeight" />
<VPLocalNavOutlineDropdown v-if="showOutline" :headers="headers" :nav-height="navHeight" />
</div>
</template>
<style scoped>
.local-nav {
.vp-local-nav {
position: sticky;
top: 0;
@ -93,43 +93,43 @@ const showLocalNav = computed(() => {
border var(--t-color);
}
.local-nav.fixed {
.vp-local-nav.fixed {
position: fixed;
}
.local-nav.reached-top {
.vp-local-nav.reached-top {
border-top-color: transparent;
}
@media (min-width: 960px) {
.local-nav.is-blog {
.vp-local-nav.is-blog {
display: none;
}
.local-nav {
.vp-local-nav {
top: var(--vp-nav-height);
width: calc(100% - var(--vp-sidebar-width));
margin-left: var(--vp-sidebar-width);
border-top: none;
}
.local-nav .menu {
.vp-local-nav .menu {
visibility: hidden;
}
.local-nav.with-outline {
.vp-local-nav.with-outline {
display: none;
}
}
@media (min-width: 1280px) {
.local-nav {
.vp-local-nav {
display: none;
}
}
@media print {
.local-nav {
.vp-local-nav {
display: none;
}
}

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core'
import { nextTick, ref, watch } from 'vue'
import type { MenuItem } from '../../composables/outline.js'
import { useData } from '../../composables/data.js'
import DocOutlineItem from './DocOutlineItem.vue'
import type { MenuItem } from '../composables/outline.js'
import { useData } from '../composables/data.js'
import VPDocOutlineItem from './VPDocOutlineItem.vue'
const props = defineProps<{
headers: MenuItem[]
@ -48,7 +48,7 @@ function scrollToTop() {
</script>
<template>
<div class="local-nav-outline-dropdown" :style="{ '--vp-vh': `${vh}px` }">
<div class="vp-local-nav-outline-dropdown" :style="{ '--vp-vh': `${vh}px` }">
<button v-if="headers.length > 0" ref="btn" :class="{ open }" @click="toggle">
{{ theme.outlineLabel || 'On this page' }}
<span class="vpi-chevron-right icon" />
@ -64,7 +64,7 @@ function scrollToTop() {
</a>
</div>
<div class="outline">
<DocOutlineItem :headers="headers" />
<VPDocOutlineItem :headers="headers" />
</div>
</div>
</Transition>
@ -72,11 +72,11 @@ function scrollToTop() {
</template>
<style scoped>
.local-nav-outline-dropdown {
.vp-local-nav-outline-dropdown {
padding: 12px 20px 11px;
}
.local-nav-outline-dropdown button {
.vp-local-nav-outline-dropdown button {
position: relative;
display: block;
font-size: 12px;
@ -86,11 +86,11 @@ function scrollToTop() {
transition: color var(--t-color);
}
.local-nav-outline-dropdown button:hover {
.vp-local-nav-outline-dropdown button:hover {
color: var(--vp-c-text-1);
}
.local-nav-outline-dropdown button.open {
.vp-local-nav-outline-dropdown button.open {
color: var(--vp-c-text-1);
}

View File

@ -0,0 +1,7 @@
<template>
<div class="vp-page">
<slot name="page-top" />
<Content class="plume-content" />
<slot name="page-bottom" />
</div>
</template>

View File

@ -4,7 +4,7 @@ import { onMounted, ref, watch } from 'vue'
import { useRoutePath } from 'vuepress/client'
import { useSidebar } from '../composables/sidebar.js'
import { inBrowser } from '../utils/index.js'
import SidebarItem from './SidebarItem.vue'
import VPSidebarItem from './VPSidebarItem.vue'
import TransitionFadeSlideY from './TransitionFadeSlideY.vue'
const props = defineProps<{
@ -32,7 +32,7 @@ watch(
onMounted(() => {
const activeItem = document.querySelector(
`.sidebar-wrapper .auto-link[href*="${routePath.value}"]`,
`.vp-sidebar .vp-link[href*="${routePath.value}"]`,
)
if (!activeItem || !navEl.value)
return
@ -51,7 +51,7 @@ onMounted(() => {
<aside
v-if="hasSidebar"
ref="navEl"
class="sidebar-wrapper"
class="vp-sidebar"
:class="{ open }"
@click.stop
>
@ -74,7 +74,7 @@ onMounted(() => {
:key="item.text"
class="group"
>
<SidebarItem :item="item" :depth="0" />
<VPSidebarItem :item="item" :depth="0" />
</div>
</nav>
</TransitionFadeSlideY>
@ -83,7 +83,7 @@ onMounted(() => {
</template>
<style scoped>
.sidebar-wrapper {
.vp-sidebar {
position: fixed;
top: var(--vp-layout-top-height, 0);
bottom: 0;
@ -106,7 +106,7 @@ onMounted(() => {
scrollbar-width: thin;
}
.sidebar-wrapper.open {
.vp-sidebar.open {
visibility: visible;
opacity: 1;
transition:
@ -115,12 +115,12 @@ onMounted(() => {
transform: translateX(0);
}
.dark .sidebar-wrapper {
.dark .vp-sidebar {
box-shadow: var(--vp-shadow-1);
}
@media (min-width: 960px) {
.sidebar-wrapper {
.vp-sidebar {
z-index: 1;
width: var(--vp-sidebar-width);
max-width: 100%;
@ -134,7 +134,7 @@ onMounted(() => {
}
@media (min-width: 1440px) {
.sidebar-wrapper {
.vp-sidebar {
width:
calc(
(100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) -

View File

@ -2,8 +2,8 @@
import type { NotesSidebarItem } from '@vuepress-plume/plugin-notes-data'
import { computed } from 'vue'
import { useSidebarControl } from '../composables/sidebar.js'
import AutoLink from './AutoLink.vue'
import VIcon from './VIcon.vue'
import VPLink from './VPLink.vue'
import VPIcon from './VPIcon.vue'
const props = defineProps<{
item: NotesSidebarItem
@ -56,7 +56,7 @@ function onCaretClick() {
</script>
<template>
<Component :is="sectionTag" class="sidebar-item" :class="classes">
<Component :is="sectionTag" class="vp-sidebar-item" :class="classes">
<div
v-if="item.text"
class="item"
@ -69,16 +69,16 @@ function onCaretClick() {
>
<div class="indicator" />
<VIcon v-if="item.icon" :name="item.icon" />
<VPIcon v-if="item.icon" :name="item.icon" />
<AutoLink
<VPLink
v-if="item.link"
:tag="linkTag"
class="link"
:href="item.link"
>
<Component :is="textTag" class="text" v-html="item.text" />
</AutoLink>
</VPLink>
<Component :is="textTag" v-else class="text" v-html="item.text" />
@ -97,7 +97,7 @@ function onCaretClick() {
<div v-if="item.items && item.items.length" class="items">
<template v-if="depth < 5">
<SidebarItem
<VPSidebarItem
v-for="i in (item.items as NotesSidebarItem[])"
:key="i.text"
:item="i"
@ -109,11 +109,11 @@ function onCaretClick() {
</template>
<style scoped>
.sidebar-item.level-0 {
.vp-sidebar-item.level-0 {
padding-bottom: 24px;
}
.sidebar-item.collapsed.level-0 {
.vp-sidebar-item.collapsed.level-0 {
padding-bottom: 10px;
}
@ -124,7 +124,7 @@ function onCaretClick() {
width: 100%;
}
.sidebar-item.collapsible > .item {
.vp-sidebar-item.collapsible > .item {
cursor: pointer;
}
@ -137,10 +137,10 @@ function onCaretClick() {
transition: background-color var(--t-color);
}
.sidebar-item.level-2.is-active > .item > .indicator,
.sidebar-item.level-3.is-active > .item > .indicator,
.sidebar-item.level-4.is-active > .item > .indicator,
.sidebar-item.level-5.is-active > .item > .indicator {
.vp-sidebar-item.level-2.is-active > .item > .indicator,
.vp-sidebar-item.level-3.is-active > .item > .indicator,
.vp-sidebar-item.level-4.is-active > .item > .indicator,
.vp-sidebar-item.level-5.is-active > .item > .indicator {
background-color: var(--vp-c-brand-1);
}
@ -157,50 +157,50 @@ function onCaretClick() {
transition: color var(--t-color);
}
.sidebar-item.level-0 .text {
.vp-sidebar-item.level-0 .text {
font-weight: 700;
color: var(--vp-c-text-1);
}
.sidebar-item.level-1 .text,
.sidebar-item.level-2 .text,
.sidebar-item.level-3 .text,
.sidebar-item.level-4 .text,
.sidebar-item.level-5 .text {
.vp-sidebar-item.level-1 .text,
.vp-sidebar-item.level-2 .text,
.vp-sidebar-item.level-3 .text,
.vp-sidebar-item.level-4 .text,
.vp-sidebar-item.level-5 .text {
font-weight: 500;
color: var(--vp-c-text-2);
}
.sidebar-item.level-0.has-active > .item > .text,
.sidebar-item.level-1.has-active > .item > .text,
.sidebar-item.level-2.has-active > .item > .text,
.sidebar-item.level-3.has-active > .item > .text,
.sidebar-item.level-4.has-active > .item > .text,
.sidebar-item.level-5.has-active > .item > .text,
.sidebar-item.level-0.has-active > .item > .link > .text,
.sidebar-item.level-1.has-active > .item > .link > .text,
.sidebar-item.level-2.has-active > .item > .link > .text,
.sidebar-item.level-3.has-active > .item > .link > .text,
.sidebar-item.level-4.has-active > .item > .link > .text,
.sidebar-item.level-5.has-active > .item > .link > .text {
.vp-sidebar-item.level-0.has-active > .item > .text,
.vp-sidebar-item.level-1.has-active > .item > .text,
.vp-sidebar-item.level-2.has-active > .item > .text,
.vp-sidebar-item.level-3.has-active > .item > .text,
.vp-sidebar-item.level-4.has-active > .item > .text,
.vp-sidebar-item.level-5.has-active > .item > .text,
.vp-sidebar-item.level-0.has-active > .item > .link > .text,
.vp-sidebar-item.level-1.has-active > .item > .link > .text,
.vp-sidebar-item.level-2.has-active > .item > .link > .text,
.vp-sidebar-item.level-3.has-active > .item > .link > .text,
.vp-sidebar-item.level-4.has-active > .item > .link > .text,
.vp-sidebar-item.level-5.has-active > .item > .link > .text {
color: var(--vp-c-text-1);
}
.sidebar-item.level-0.is-active > .item .link > .text,
.sidebar-item.level-1.is-active > .item .link > .text,
.sidebar-item.level-2.is-active > .item .link > .text,
.sidebar-item.level-3.is-active > .item .link > .text,
.sidebar-item.level-4.is-active > .item .link > .text,
.sidebar-item.level-5.is-active > .item .link > .text {
.vp-sidebar-item.level-0.is-active > .item .link > .text,
.vp-sidebar-item.level-1.is-active > .item .link > .text,
.vp-sidebar-item.level-2.is-active > .item .link > .text,
.vp-sidebar-item.level-3.is-active > .item .link > .text,
.vp-sidebar-item.level-4.is-active > .item .link > .text,
.vp-sidebar-item.level-5.is-active > .item .link > .text {
color: var(--vp-c-brand-1);
}
.sidebar-item.level-0.is-link > .item > .link:hover .text,
.sidebar-item.level-1.is-link > .item > .link:hover .text,
.sidebar-item.level-2.is-link > .item > .link:hover .text,
.sidebar-item.level-3.is-link > .item > .link:hover .text,
.sidebar-item.level-4.is-link > .item > .link:hover .text,
.sidebar-item.level-5.is-link > .item > .link:hover .text {
.vp-sidebar-item.level-0.is-link > .item > .link:hover .text,
.vp-sidebar-item.level-1.is-link > .item > .link:hover .text,
.vp-sidebar-item.level-2.is-link > .item > .link:hover .text,
.vp-sidebar-item.level-3.is-link > .item > .link:hover .text,
.vp-sidebar-item.level-4.is-link > .item > .link:hover .text,
.vp-sidebar-item.level-5.is-link > .item > .link:hover .text {
color: var(--vp-c-brand-1);
}
@ -236,21 +236,21 @@ function onCaretClick() {
color: var(--vp-c-text-1);
}
.sidebar-item.level-0.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-1.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-2.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-3.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-4.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-5.is-active > .item > :deep(.vp-iconify) {
.vp-sidebar-item.level-0.is-active > .item > :deep(.vp-iconify),
.vp-sidebar-item.level-1.is-active > .item > :deep(.vp-iconify),
.vp-sidebar-item.level-2.is-active > .item > :deep(.vp-iconify),
.vp-sidebar-item.level-3.is-active > .item > :deep(.vp-iconify),
.vp-sidebar-item.level-4.is-active > .item > :deep(.vp-iconify),
.vp-sidebar-item.level-5.is-active > .item > :deep(.vp-iconify) {
color: var(--vp-c-brand-1);
}
.sidebar-item.level-0.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-1.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-2.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-3.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-4.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-5.is-link > .item:hover :deep(.vp-iconify) {
.vp-sidebar-item.level-0.is-link > .item:hover :deep(.vp-iconify),
.vp-sidebar-item.level-1.is-link > .item:hover :deep(.vp-iconify),
.vp-sidebar-item.level-2.is-link > .item:hover :deep(.vp-iconify),
.vp-sidebar-item.level-3.is-link > .item:hover :deep(.vp-iconify),
.vp-sidebar-item.level-4.is-link > .item:hover :deep(.vp-iconify),
.vp-sidebar-item.level-5.is-link > .item:hover :deep(.vp-iconify) {
color: var(--vp-c-brand-1);
}
@ -262,21 +262,21 @@ function onCaretClick() {
transform: rotate(90deg);
}
.sidebar-item.collapsed .caret-icon {
.vp-sidebar-item.collapsed .caret-icon {
transform: rotate(0);
}
.sidebar-item.level-1 .items,
.sidebar-item.level-2 .items,
.sidebar-item.level-3 .items,
.sidebar-item.level-4 .items,
.sidebar-item.level-5 .items {
.vp-sidebar-item.level-1 .items,
.vp-sidebar-item.level-2 .items,
.vp-sidebar-item.level-3 .items,
.vp-sidebar-item.level-4 .items,
.vp-sidebar-item.level-5 .items {
padding-left: 16px;
border-left: 1px solid var(--vp-c-divider);
transition: border-left var(--t-color);
}
.sidebar-item.collapsed .items {
.vp-sidebar-item.collapsed .items {
display: none;
}
</style>

View File

@ -32,7 +32,7 @@ function focusOnTargetAnchor({ target }: Event) {
<template>
<span ref="backToTop" tabindex="-1" />
<a
href="#LayoutContent"
href="#VPContent"
class="skip-link visually-hidden"
@click="focusOnTargetAnchor"
>

View File

@ -17,17 +17,15 @@ const svg = computed(() => {
<template>
<a
class="social-link"
class="vp-social-link"
:href="link"
:aria-label="ariaLabel ?? (typeof icon === 'string' ? icon : '')"
target="_blank"
rel="noopener"
v-html="svg"
target="_blank" rel="noopener" v-html="svg"
/>
</template>
<style scoped>
.social-link {
.vp-social-link {
display: flex;
align-items: center;
justify-content: center;
@ -37,12 +35,12 @@ const svg = computed(() => {
transition: color var(--t-color);
}
.social-link:hover {
.vp-social-link:hover {
color: var(--vp-c-text-1);
}
.social-link > :deep(svg),
.social-link > :deep([class^="vpi-social-"]) {
.vp-social-link > :deep(svg),
.vp-social-link > :deep([class^="vpi-social-"]) {
width: 20px;
height: 20px;
fill: currentcolor;

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { SocialLink as SocialLinkType } from '../../shared/index.js'
import SocialLink from './SocialLink.vue'
import VPSocialLink from './VPSocialLink.vue'
defineProps<{
links: SocialLinkType[]
@ -8,8 +8,8 @@ defineProps<{
</script>
<template>
<div class="social-links">
<SocialLink
<div class="vp-social-links">
<VPSocialLink
v-for="{ link, icon } in links"
:key="link"
:icon="icon"
@ -19,7 +19,7 @@ defineProps<{
</template>
<style scoped>
.social-links {
.vp-social-links {
display: flex;
flex-wrap: wrap;
justify-content: center;

View File

@ -1,5 +1,5 @@
<template>
<button class="switch-wrapper" type="button" role="switch">
<button class="vp-switch" type="button" role="switch">
<span class="check">
<span v-if="$slots.default" class="icon">
<slot />
@ -9,7 +9,7 @@
</template>
<style scoped>
.switch-wrapper {
.vp-switch {
position: relative;
display: block;
flex-shrink: 0;
@ -23,7 +23,7 @@
background-color 0.25s ease;
}
.switch-wrapper:hover {
.vp-switch:hover {
border-color: var(--vp-c-brand-1);
}

View File

@ -1,7 +1,7 @@
<script lang="ts" setup>
import { computed, inject, ref } from 'vue'
import { useData } from '../composables/data.js'
import Switch from './Switch.vue'
import VPSwitch from './VPSwitch.vue'
const checked = ref(false)
const { theme, isDark } = useData()
@ -18,15 +18,15 @@ const switchTitle = computed(() => {
</script>
<template>
<Switch
class="switch-appearance"
<VPSwitch
class="vp-switch-appearance"
:title="switchTitle"
:aria-checked="checked"
@click="toggleAppearance"
>
<span class="vpi-sun sun" />
<span class="vpi-moon moon" />
</Switch>
</VPSwitch>
</template>
<style scoped>
@ -46,7 +46,7 @@ const switchTitle = computed(() => {
opacity: 1;
}
.dark .switch-appearance :deep(.check) {
.dark .vp-switch-appearance :deep(.check) {
/* rtl:ignore */
transform: translateX(18px);
}

View File

@ -10,13 +10,13 @@ withDefaults(defineProps<Props>(), {
</script>
<template>
<span class="badge-view" :class="type">
<span class="vp-badge" :class="type">
<slot>{{ text }}</slot>
</span>
</template>
<style scoped>
.badge-view {
.vp-badge {
display: inline-block;
padding: 0 10px;
margin-left: 2px;
@ -30,47 +30,47 @@ withDefaults(defineProps<Props>(), {
transform: translateY(-2px);
}
h1 .badge-view {
h1 .vp-badge {
margin-top: 4px;
vertical-align: top;
}
h2 .badge-view {
h2 .vp-badge {
padding: 0 8px;
margin-top: 3px;
vertical-align: top;
}
h3 .badge-view {
h3 .vp-badge {
vertical-align: middle;
}
h4 .badge-view,
h5 .badge-view,
h6 .badge-view {
h4 .vp-badge,
h5 .vp-badge,
h6 .vp-badge {
line-height: 18px;
vertical-align: middle;
}
.badge-view.info {
.vp-badge.info {
color: var(--vp-badge-info-text);
background-color: var(--vp-badge-info-bg);
border-color: var(--vp-badge-info-border);
}
.badge-view.tip {
.vp-badge.tip {
color: var(--vp-badge-tip-text);
background-color: var(--vp-badge-tip-bg);
border-color: var(--vp-badge-tip-border);
}
.badge-view.warning {
.vp-badge.warning {
color: var(--vp-badge-warning-text);
background-color: var(--vp-badge-warning-bg);
border-color: var(--vp-badge-warning-border);
}
.badge-view.danger {
.vp-badge.danger {
color: var(--vp-badge-danger-text);
background-color: var(--vp-badge-danger-bg);
border-color: var(--vp-badge-danger-border);

View File

@ -66,7 +66,7 @@ function serializeHeader(h: Element): string {
for (const node of Array.from(el?.childNodes ?? [])) {
if (node.nodeType === 1) {
if (
(node as Element).classList.contains('badge-view')
(node as Element).classList.contains('vp-badge')
|| (node as Element).classList.contains('ignore-header')
) {
continue

View File

@ -11,16 +11,15 @@ import type {
PlumeThemePageFrontmatter,
} from '../../shared/index.js'
import { useSidebar, useThemeLocaleData } from '../composables/index.js'
import { useData } from '../composables/data.js'
import { resolveEditLink, resolveNavLink } from '../utils/index.js'
export function useEditNavLink(): ComputedRef<null | NavItemWithLink> {
const themeLocale = useThemeLocaleData()
const page = usePageData<PlumeThemePageData>()
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
const { theme, page, frontmatter } = useData()
return computed(() => {
const showEditLink
= frontmatter.value.editLink ?? themeLocale.value.editLink ?? true
= frontmatter.value.editLink ?? theme.value.editLink ?? true
if (!showEditLink)
return null
@ -29,7 +28,7 @@ export function useEditNavLink(): ComputedRef<null | NavItemWithLink> {
docsBranch = 'main',
docsDir = '',
editLinkText,
} = themeLocale.value
} = theme.value
if (!docsRepo)
return null
@ -40,7 +39,7 @@ export function useEditNavLink(): ComputedRef<null | NavItemWithLink> {
docsDir,
filePathRelative: page.value.filePathRelative,
editLinkPattern:
frontmatter.value.editLinkPattern ?? themeLocale.value.editLinkPattern,
frontmatter.value.editLinkPattern ?? theme.value.editLinkPattern,
})
if (!editLink)
@ -54,9 +53,7 @@ export function useEditNavLink(): ComputedRef<null | NavItemWithLink> {
}
export function useLastUpdated() {
const theme = useThemeLocaleData()
const page = usePageData<PlumeThemePageData>()
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
const { theme, page, frontmatter } = useData()
const lang = usePageLang()
const date = computed(() => page.value.git?.updatedTime ? new Date(page.value.git.updatedTime) : null)
@ -97,13 +94,11 @@ export function useLastUpdated() {
export function useContributors(): ComputedRef<
null | Required<PlumeThemePageData['git']>['contributors']
> {
const themeLocale = useThemeLocaleData()
const page = usePageData<PlumeThemePageData>()
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
const { theme, page, frontmatter } = useData()
return computed(() => {
const showContributors
= frontmatter.value.contributors ?? themeLocale.value.contributors ?? true
= frontmatter.value.contributors ?? theme.value.contributors ?? true
if (!showContributors)
return null

View File

@ -57,7 +57,7 @@ export function getSidebarFirstLink(sidebar: NotesSidebarItem[]) {
export function useSidebar() {
const route = useRoute()
const notesData = useNotesData()
const { frontmatter } = useData()
const { frontmatter, theme } = useData()
const is960 = useMediaQuery('(min-width: 960px)')
@ -79,14 +79,28 @@ export function useSidebar() {
const hasSidebar = computed(() => {
return (
!frontmatter.value.home
frontmatter.value.pageLayout !== 'home'
&& sidebar.value.length > 0
&& frontmatter.value.sidebar !== false
&& frontmatter.value.layout !== 'NotFound'
)
})
const hasAside = computed(() => {
return !frontmatter.value.home && frontmatter.value.aside !== false
if (frontmatter.value.pageLayout === 'home')
return false
if (frontmatter.value.aside != null)
return !!frontmatter.value.aside
return theme.value.aside !== false
})
const leftAside = computed(() => {
if (hasAside.value) {
return frontmatter.value.aside == null
? theme.value.aside === 'left'
: frontmatter.value.aside === 'left'
}
return false
})
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
@ -112,6 +126,7 @@ export function useSidebar() {
sidebar,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
sidebarGroups,
sidebarKey,

View File

@ -3,7 +3,7 @@ import './styles/index.css'
import { defineClientConfig } from 'vuepress/client'
import type { ClientConfig } from 'vuepress/client'
import { h } from 'vue'
import Badge from './components/global/Badge.vue'
import VPBadge from './components/global/VPBadge.vue'
import { setupDarkMode, setupWatermark, useScrollPromise } from './composables/index.js'
import Layout from './layouts/Layout.vue'
import NotFound from './layouts/NotFound.vue'
@ -13,7 +13,8 @@ export default defineClientConfig({
enhance({ app, router }) {
setupDarkMode(app)
// global component
app.component('Badge', Badge)
app.component('Badge', VPBadge)
app.component('VPBadge', VPBadge) // alias
app.component('DocSearch', () => {
const SearchComponent

View File

@ -1,25 +1,18 @@
<script setup lang="ts">
import { usePageData, useRoute } from 'vuepress/client'
import { computed, provide, watch } from 'vue'
import type { PlumeThemePageData } from '../../shared/index.js'
import Backdrop from '../components/Backdrop.vue'
import Blog from '../components/Blog/Blog.vue'
import Friends from '../components/Friends.vue'
import Home from '../components/Home/Home.vue'
import LayoutContent from '../components/LayoutContent.vue'
import LocalNav from '../components/Nav/LocalNav.vue'
import { useRoute } from 'vuepress/client'
import { watch } from 'vue'
import VPBackdrop from '../components/VPBackdrop.vue'
import VPContent from '../components/VPContent.vue'
import VPLocalNav from '../components/VPLocalNav.vue'
import Nav from '../components/Nav/index.vue'
import Page from '../components/Page.vue'
import Sidebar from '../components/Sidebar.vue'
import SkipLink from '../components/SkipLink.vue'
import VFooter from '../components/VFooter.vue'
import BackToTop from '../components/BackToTop.vue'
import EncryptGlobal from '../components/EncryptGlobal.vue'
import TransitionFadeSlideY from '../components/TransitionFadeSlideY.vue'
import { useCloseSidebarOnEscape, useSidebar } from '../composables/index.js'
import VPSidebar from '../components/VPSidebar.vue'
import VPSkipLink from '../components/VPSkipLink.vue'
import VPFooter from '../components/VPFooter.vue'
import VPBackToTop from '../components/VPBackToTop.vue'
import VPEncryptGlobal from '../components/VPEncryptGlobal.vue'
import { useCloseSidebarOnEscape, useSidebar } from '../composables/sidebar.js'
import { useGlobalEncrypt, usePageEncrypt } from '../composables/encrypt.js'
const page = usePageData<PlumeThemePageData>()
import { useData } from '../composables/data.js'
const {
isOpen: isSidebarOpen,
@ -27,53 +20,76 @@ const {
close: closeSidebar,
} = useSidebar()
const { frontmatter } = useData()
const { isGlobalDecrypted } = useGlobalEncrypt()
const { isPageDecrypted } = usePageEncrypt()
const route = useRoute()
watch(() => route.path, closeSidebar)
const isBlogLayout = computed(() => {
return (
page.value.type === 'blog'
|| page.value.type === 'blog-archives'
|| page.value.type === 'blog-tags'
)
})
useCloseSidebarOnEscape(isSidebarOpen, closeSidebar)
provide('close-sidebar', closeSidebar)
provide('is-sidebar-open', isSidebarOpen)
</script>
<template>
<div class="theme-plume">
<EncryptGlobal v-if="!isGlobalDecrypted" />
<div
v-if="frontmatter.pageLayout !== false && frontmatter.pageLayout !== 'custom'" class="theme-plume vp-layout"
:class="frontmatter.pageClass"
>
<VPEncryptGlobal v-if="!isGlobalDecrypted" />
<template v-else>
<SkipLink />
<Backdrop :show="isSidebarOpen" @click="closeSidebar" />
<VPSkipLink />
<VPBackdrop :show="isSidebarOpen" @click="closeSidebar" />
<Nav />
<LocalNav :open="isSidebarOpen" :show-outline="isPageDecrypted" @open-menu="openSidebar" />
<Sidebar :open="isSidebarOpen" />
<LayoutContent>
<Home v-if="page.frontmatter.home" />
<template v-else>
<TransitionFadeSlideY>
<Friends v-if="page.frontmatter.friends" />
<Blog v-else-if="isBlogLayout" />
<Page v-else />
</TransitionFadeSlideY>
</template>
<BackToTop />
<VFooter />
</LayoutContent>
<VPLocalNav :open="isSidebarOpen" :show-outline="isPageDecrypted" @open-menu="openSidebar" />
<VPSidebar :open="isSidebarOpen" />
<slot name="custom-content">
<VPContent>
<template #page-top>
<slot name="page-top" />
</template>
<template #page-bottom>
<slot name="page-bottom" />
</template>
<template #doc-footer-before>
<slot name="doc-footer-before" />
</template>
<template #doc-before>
<slot name="doc-before" />
</template>
<template #doc-after>
<slot name="doc-after" />
</template>
<template #doc-top>
<slot name="doc-top" />
</template>
<template #doc-bottom>
<slot name="doc-bottom" />
</template>
<template #aside-top>
<slot name="aside-top" />
</template>
<template #aside-bottom>
<slot name="aside-bottom" />
</template>
<template #aside-outline-before>
<slot name="aside-outline-before" />
</template>
<template #aside-outline-after>
<slot name="aside-outline-after" />
</template>
</VPContent>
</slot>
<VPBackToTop />
<VPFooter />
</template>
</div>
<Content v-else />
</template>
<style scoped>
.theme-plume {
.vp-layout {
display: flex;
flex-direction: column;
min-height: 100vh;

View File

@ -1,7 +1,8 @@
<script setup lang="ts">
import { useRouteLocale, withBase } from 'vuepress/client'
import LayoutContent from '../components/LayoutContent.vue'
import Nav from '../components/Nav/index.vue'
import VPSkipLink from '../components/VPSkipLink.vue'
import VPFooter from '../components/VPFooter.vue'
import { useData } from '../composables/data.js'
const root = useRouteLocale()
@ -9,45 +10,68 @@ const { theme } = useData()
</script>
<template>
<div class="theme-plume">
<Nav />
<LayoutContent is-not-found>
<div class="not-found">
<p class="code">
{{ theme.notFound?.code ?? '404' }}
</p>
<h1 class="title">
{{ theme.notFound?.title ?? 'PAGE NOT FOUND' }}
</h1>
<div class="divider" />
<blockquote class="quote">
{{ theme.notFound?.quote ?? `But if you don't change your direction, and if you keep looking, you may end up where you are heading.` }}
</blockquote>
<div vp-not-found class="theme-plume vp-layout">
<slot name="layout-top" />
<VPSkipLink />
<div class="action">
<a class="link" :href="withBase(root)" :aria-label="theme.notFound?.linkLabel ?? 'go to home'">
{{ theme.notFound?.linkText ?? 'Take me home' }}
</a>
<Nav />
<div id="VPContent" class="vp-content">
<slot name="not-found">
<div class="vp-not-found">
<p class="code">
{{ theme.notFound?.code ?? '404' }}
</p>
<h1 class="title">
{{ theme.notFound?.title ?? 'PAGE NOT FOUND' }}
</h1>
<div class="divider" />
<blockquote class="quote">
{{
theme.notFound?.quote
?? "But if you don't change your direction, and if you keep looking, you may end up where you are heading."
}}
</blockquote>
<div class="action">
<a class="link" :href="withBase(root)" :aria-label="theme.notFound?.linkLabel ?? 'go to home'">
{{ theme.notFound?.linkText ?? 'Take me home' }}
</a>
</div>
</div>
</div>
</LayoutContent>
</slot>
</div>
<VPFooter />
<slot name="layout-bottom" />
</div>
</template>
<style scoped>
.theme-plume {
.vp-layout {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.not-found {
.vp-content {
flex-grow: 1;
flex-shrink: 0;
width: 100%;
margin: var(--vp-layout-top-height, 0) auto 0;
}
@media (min-width: 960px) {
.vp-content {
padding-top: var(--vp-nav-height);
}
}
.vp-not-found {
padding: 64px 24px 96px;
text-align: center;
}
@media (min-width: 768px) {
.not-found {
.vp-not-found {
padding: 96px 32px 168px;
}
}
@ -71,6 +95,7 @@ const { theme } = useData()
height: 1px;
margin: 24px auto 18px;
background-color: var(--vp-c-divider);
transition: background-color var(--t-color);
}
.quote {
@ -79,6 +104,7 @@ const { theme } = useData()
font-size: 14px;
font-weight: 500;
color: var(--vp-c-text-2);
transition: color var(--t-color);
}
.action {
@ -90,16 +116,14 @@ const { theme } = useData()
padding: 3px 16px;
font-size: 14px;
font-weight: 500;
color: var(--vp-c-brand);
border: 1px solid var(--vp-c-brand);
color: var(--vp-c-brand-1);
border: 1px solid var(--vp-c-brand-1);
border-radius: 16px;
transition:
border-color 0.25s,
color 0.25s;
transition: color var(--t-color), border-color var(--t-color);
}
.link:hover {
color: var(--vp-c-brand-dark);
border-color: var(--vp-c-brand-dark);
color: var(--vp-c-brand-2);
border-color: var(--vp-c-brand-2);
}
</style>

View File

@ -1,3 +1,4 @@
/* ------------------ Plugin Nprogress ------------------ */
#nprogress .bar {
background: var(--vp-c-brand-1);
}
@ -10,3 +11,8 @@
#nprogress .peg {
box-shadow: 0 0 10px var(--vp-c-brand-1), 0 0 5px var(--vp-c-brand-1);
}
/* ------------------ Plugin Comment ------------------ */
#vp-comment {
margin-top: 80px;
}

View File

@ -4,7 +4,7 @@
@import url("./normalize.css");
@import url("./icons.css");
@import url("./social-icons.css");
@import url("./nprogress.css");
@import url("./compat.css");
@import url("./utils.css");
@import url("./content.css");
@import url("./code.css");

View File

@ -11,6 +11,7 @@ const FALLBACK_OPTIONS: PlumeThemeLocaleData = {
article: '/article/',
notes: { link: '/', dir: '/notes/', notes: [] },
navbarSocialInclude: ['github', 'twitter', 'discord', 'facebook'],
aside: true,
outline: [2, 3],
// page meta

View File

@ -38,19 +38,19 @@ export async function setupPage(
// 添加 博客页面
pageList.push(createPage(app, {
path: withBase(link, localePath),
frontmatter: { lang, type: 'blog', title: getTitle(locale, 'blog') },
frontmatter: { lang, _pageLayout: 'blog', title: getTitle(locale, 'blog') },
}))
// 添加 标签页
blog.tags !== false && pageList.push(createPage(app, {
path: withBase(blog.tagsLink || `${link}/tags/`, localePath),
frontmatter: { lang, type: 'blog-tags', title: getTitle(locale, 'tag') },
frontmatter: { lang, _pageLayout: 'blog-tags', title: getTitle(locale, 'tag') },
}))
// 添加归档页
blog.archives !== false && pageList.push(createPage(app, {
path: withBase(blog.archivesLink || `${link}/archives/`, localePath),
frontmatter: { lang, type: 'blog-archives', title: getTitle(locale, 'archive') },
frontmatter: { lang, _pageLayout: 'blog-archives', title: getTitle(locale, 'archive') },
}))
}
app.pages.push(...await Promise.all(pageList))
@ -61,22 +61,31 @@ export function extendsPageData(
localeOptions: PlumeThemeLocaleOptions,
) {
page.data.filePathRelative = page.filePathRelative
page.routeMeta.title = page.title
page.routeMeta.title = page.frontmatter.title || page.title
if (page.frontmatter.icon)
page.routeMeta.icon = page.frontmatter.icon
if (page.frontmatter.home) {
page.frontmatter.pageLayout = 'home'
delete page.frontmatter.home
}
if (page.frontmatter.friends) {
page.frontmatter.article = false
page.frontmatter.type = 'friends'
page.data.isBlogPost = false
page.data.type = 'friends'
page.permalink = page.permalink ?? '/friends/'
page.frontmatter.pageLayout = 'friends'
delete page.frontmatter.friends
}
if ((page.frontmatter.type as string)?.startsWith('blog')) {
page.data.isBlogPost = false
const pageType = page.frontmatter._pageLayout as string
if (pageType) {
page.frontmatter.article = false
page.data.type = page.frontmatter.type as any
page.data.type = pageType as any
delete page.frontmatter._pageLayout
}
if ('externalLink' in page.frontmatter) {
page.frontmatter.externalLinkIcon = page.frontmatter.externalLink
delete page.frontmatter.externalLink
}
autoCategory(page, localeOptions)
@ -86,6 +95,7 @@ export function extendsPageData(
let uuid = 10000
const cache: Record<string, number> = {}
const RE_CATEGORY = /^(\d+)?(?:\.?)([^]+)$/
let LOCALE_RE: RegExp
export function autoCategory(
page: Page<PlumeThemePageData>,
@ -93,18 +103,18 @@ export function autoCategory(
) {
const pagePath = page.filePathRelative
if (page.frontmatter.type || !pagePath)
if (page.data.type || !pagePath)
return
const notesLinks = resolveNotesLinkList(options)
if (notesLinks.some(link => page.path.startsWith(link)))
return
const RE_LOCALE = new RegExp(
LOCALE_RE ??= new RegExp(
`^(${Object.keys(options.locales || {}).filter(l => l !== '/').join('|')})`,
)
const categoryList: PageCategoryData[] = ensureLeadingSlash(pagePath)
.replace(RE_LOCALE, '')
.replace(LOCALE_RE, '')
.replace(/^\//, '')
.split('/')
.slice(0, -1)

View File

@ -1,7 +1,9 @@
import type { ThemeImage } from '../base.js'
import type { PlumeNormalFrontmatter } from './normal.js'
export interface PlumeThemeHomeFrontmatter extends Omit<PlumeThemeHomeBanner, 'type'> {
export interface PlumeThemeHomeFrontmatter extends PlumeNormalFrontmatter, Omit<PlumeThemeHomeBanner, 'type'> {
home?: true
friends?: never
config?: PlumeThemeHomeConfig[]
}

View File

@ -0,0 +1,43 @@
import type { PageFrontmatter } from 'vuepress'
export interface PlumeNormalFrontmatter extends PageFrontmatter {
/**
* @deprecated
*
* 使 pageLayout = 'home'
*/
home?: boolean
/**
* @deprecated
*
* 使 pageLayout = 'friends'
*/
friends?: boolean
/**
* page layout
*/
pageLayout?: false | 'home' | 'doc' | 'custom' | 'page' | 'friends'
/**
* class
*/
pageClass?: string
/**
*
*/
backToTop?: boolean
/**
*
*/
externalLinkIcon?: boolean
/**
* @deprecated 使 `externalLinkIcon`
*/
externalLink?: boolean
}

View File

@ -1,21 +1,65 @@
import type { WatermarkPluginFrontmatter } from '@vuepress/plugin-watermark'
import type { ThemeOutline } from '../base.js'
import type { NavItemWithLink } from '../navbar.js'
import type { PlumeNormalFrontmatter } from './normal.js'
export interface PlumeThemePageFrontmatter {
home?: false
export interface PlumeThemePageFrontmatter extends PlumeNormalFrontmatter {
home?: never
friends?: never
/**
*
*/
comments?: boolean
/**
*
*/
editLink?: boolean
/**
*
*/
editLinkPattern?: string
/**
*
*/
lastUpdated?: boolean
/**
*
*/
contributors?: boolean
/**
*
*/
prev?: string | NavItemWithLink
/**
*
*/
next?: string | NavItemWithLink
/**
*
*/
sidebar?: string | false
aside?: boolean
/**
*
*/
aside?: boolean | 'left'
/**
*
*/
outline?: ThemeOutline
backToTop?: boolean
externalLink?: boolean
/**
*
*/
readingTime?: boolean
/**
*
*/
watermark?: WatermarkPluginFrontmatter['watermark'] & { fullPage?: boolean }
/**
* navbar sidebar
* iconify 使 iconify name
* svg 使 svg url
* svg
*/
icon?: string | { svg: string }
}

View File

@ -76,6 +76,13 @@ export interface PlumeThemeLocaleData extends LocaleData {
outline?: ThemeOutline
/**
*
*
* @default true
*/
aside?: boolean | 'left'
/**
* language text
*/

View File

@ -41,7 +41,7 @@ export interface PlumeThemePluginOptions {
/**
* git
*/
git?: false
git?: boolean
nprogress?: false