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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,18 +36,18 @@
"vuepress": "2.0.0-rc.13" "vuepress": "2.0.0-rc.13"
}, },
"dependencies": { "dependencies": {
"@shikijs/transformers": "^1.6.3", "@shikijs/transformers": "^1.6.4",
"@shikijs/twoslash": "^1.6.3", "@shikijs/twoslash": "^1.6.4",
"@types/hast": "^3.0.4", "@types/hast": "^3.0.4",
"@vuepress/helper": "2.0.0-rc.34", "@vuepress/helper": "2.0.0-rc.34",
"floating-vue": "^5.2.2", "floating-vue": "^5.2.2",
"mdast-util-from-markdown": "^2.0.1", "mdast-util-from-markdown": "^2.0.1",
"mdast-util-gfm": "^3.0.0", "mdast-util-gfm": "^3.0.0",
"mdast-util-to-hast": "^13.1.0", "mdast-util-to-hast": "^13.2.0",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"shiki": "^1.6.3", "shiki": "^1.6.4",
"twoslash": "^0.2.6", "twoslash": "^0.2.8",
"twoslash-vue": "^0.2.6" "twoslash-vue": "^0.2.8"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "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-sitemap": "2.0.0-rc.34",
"@vuepress/plugin-theme-data": "2.0.0-rc.34", "@vuepress/plugin-theme-data": "2.0.0-rc.34",
"@vuepress/plugin-watermark": "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", "bcrypt-ts": "^5.0.2",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"katex": "^0.16.10", "katex": "^0.16.10",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"vue": "^3.4.27", "vue": "^3.4.29",
"vue-router": "^4.3.2", "vue-router": "^4.3.3",
"vuepress-plugin-md-enhance": "2.0.0-rc.48", "vuepress-plugin-md-enhance": "2.0.0-rc.48",
"vuepress-plugin-md-power": "workspace:*" "vuepress-plugin-md-power": "workspace:*"
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import type { PlumeThemeHomeTextImage } from '../../../shared/index.js' import type { PlumeThemeHomeTextImage } from '../../../shared/index.js'
import VImage from '../VImage.vue' import VPImage from '../VPImage.vue'
import HomeBox from './HomeBox.vue' import HomeBox from './HomeBox.vue'
const props = defineProps<PlumeThemeHomeTextImage>() const props = defineProps<PlumeThemeHomeTextImage>()
@ -26,7 +26,7 @@ const maxWidth = computed(() => {
:container-class="{ reverse: type === 'text-image' }" :container-class="{ reverse: type === 'text-image' }"
> >
<div class="content-image"> <div class="content-image">
<VImage :image="image" :style="{ maxWidth }" /> <VPImage :image="image" :style="{ maxWidth }" />
</div> </div>
<div class="content-text plume-content"> <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(() => { watchPostEffect(() => {
classes.value = { classes.value = {
'has-sidebar': hasSidebar.value, 'has-sidebar': hasSidebar.value,
'top': !!frontmatter.value.home && y.value === 0, 'top': frontmatter.value.pageLayout === 'home' && y.value === 0,
} }
}) })
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -38,8 +38,7 @@ function linkTo(e: Event) {
<template> <template>
<Component <Component
:is="component" :is="component"
class="VPButton" class="vp-button" :class="[size, theme]"
:class="[size, theme]"
:href="href" :href="href"
:target="isExternal ? '_blank' : undefined" :target="isExternal ? '_blank' : undefined"
:rel="isExternal ? 'noreferrer' : undefined" :rel="isExternal ? 'noreferrer' : undefined"
@ -50,7 +49,7 @@ function linkTo(e: Event) {
</template> </template>
<style scoped> <style scoped>
.VPButton { .vp-button {
display: inline-block; display: inline-block;
font-weight: 600; font-weight: 600;
text-align: center; text-align: center;
@ -60,76 +59,76 @@ function linkTo(e: Event) {
transition-property: border, color, background-color; transition-property: border, color, background-color;
} }
.VPButton:active { .vp-button:active {
transition: transition:
color 0.1s, color 0.1s,
border-color 0.1s, border-color 0.1s,
background-color 0.1s; background-color 0.1s;
} }
.VPButton.medium { .vp-button.medium {
padding: 0 20px; padding: 0 20px;
font-size: 14px; font-size: 14px;
line-height: 38px; line-height: 38px;
border-radius: 20px; border-radius: 20px;
} }
.VPButton.big { .vp-button.big {
padding: 0 24px; padding: 0 24px;
font-size: 16px; font-size: 16px;
line-height: 46px; line-height: 46px;
border-radius: 24px; border-radius: 24px;
} }
.VPButton.brand { .vp-button.brand {
color: var(--vp-button-brand-text); color: var(--vp-button-brand-text);
background-color: var(--vp-button-brand-bg); background-color: var(--vp-button-brand-bg);
border-color: var(--vp-button-brand-border); border-color: var(--vp-button-brand-border);
} }
.VPButton.brand:hover { .vp-button.brand:hover {
color: var(--vp-button-brand-hover-text); color: var(--vp-button-brand-hover-text);
background-color: var(--vp-button-brand-hover-bg); background-color: var(--vp-button-brand-hover-bg);
border-color: var(--vp-button-brand-hover-border); border-color: var(--vp-button-brand-hover-border);
} }
.VPButton.brand:active { .vp-button.brand:active {
color: var(--vp-button-brand-active-text); color: var(--vp-button-brand-active-text);
background-color: var(--vp-button-brand-active-bg); background-color: var(--vp-button-brand-active-bg);
border-color: var(--vp-button-brand-active-border); border-color: var(--vp-button-brand-active-border);
} }
.VPButton.alt { .vp-button.alt {
color: var(--vp-button-alt-text); color: var(--vp-button-alt-text);
background-color: var(--vp-button-alt-bg); background-color: var(--vp-button-alt-bg);
border-color: var(--vp-button-alt-border); border-color: var(--vp-button-alt-border);
} }
.VPButton.alt:hover { .vp-button.alt:hover {
color: var(--vp-button-alt-hover-text); color: var(--vp-button-alt-hover-text);
background-color: var(--vp-button-alt-hover-bg); background-color: var(--vp-button-alt-hover-bg);
border-color: var(--vp-button-alt-hover-border); border-color: var(--vp-button-alt-hover-border);
} }
.VPButton.alt:active { .vp-button.alt:active {
color: var(--vp-button-alt-active-text); color: var(--vp-button-alt-active-text);
background-color: var(--vp-button-alt-active-bg); background-color: var(--vp-button-alt-active-bg);
border-color: var(--vp-button-alt-active-border); border-color: var(--vp-button-alt-active-border);
} }
.VPButton.sponsor { .vp-button.sponsor {
color: var(--vp-button-sponsor-text); color: var(--vp-button-sponsor-text);
background-color: var(--vp-button-sponsor-bg); background-color: var(--vp-button-sponsor-bg);
border-color: var(--vp-button-sponsor-border); border-color: var(--vp-button-sponsor-border);
} }
.VPButton.sponsor:hover { .vp-button.sponsor:hover {
color: var(--vp-button-sponsor-hover-text); color: var(--vp-button-sponsor-hover-text);
background-color: var(--vp-button-sponsor-hover-bg); background-color: var(--vp-button-sponsor-hover-bg);
border-color: var(--vp-button-sponsor-hover-border); border-color: var(--vp-button-sponsor-hover-border);
} }
.VPButton.sponsor:active { .vp-button.sponsor:active {
color: var(--vp-button-sponsor-active-text); color: var(--vp-button-sponsor-active-text);
background-color: var(--vp-button-sponsor-active-bg); background-color: var(--vp-button-sponsor-active-bg);
border-color: var(--vp-button-sponsor-active-border); 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 { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
import { type MenuItem, getHeaders, useActiveAnchor } from '../composables/outline.js' import { type MenuItem, getHeaders, useActiveAnchor } from '../composables/outline.js'
import { useData } from '../composables/data.js' import { useData } from '../composables/data.js'
import PageAsideItem from './PageAsideItem.vue' import VPDocOutlineItem from './VPDocOutlineItem.vue'
const { theme, frontmatter } = useData() const { theme, frontmatter } = useData()
@ -25,46 +25,37 @@ function handlePrint() {
</script> </script>
<template> <template>
<div class="page-aside"> <nav
<div ref="container"
ref="container" aria-labelledby="doc-outline-aria-label"
class="page-aside-outline" class="vp-doc-aside-outline"
:class="{ 'has-outline': hasOutline }" :class="{ 'has-outline': hasOutline }"
> role="navigation"
<div class="content"> >
<div ref="marker" class="outline-marker" /> <div class="content">
<div ref="marker" class="outline-marker" />
<div class="outline-title"> <div
<span>{{ theme.outlineLabel || 'On this page' }}</span> id="doc-outline-aria-label"
<span class="vpi-print icon" @click="handlePrint" /> aria-level="2"
</div> class="outline-title"
role="heading"
<nav aria-labelledby="doc-outline-aria-label"> >
<span id="doc-outline-aria-label" class="visually-hidden"> <span>{{ theme.outlineLabel || 'On this page' }}</span>
Table of Contents for current page <span class="vpi-print icon" @click="handlePrint" />
</span>
<PageAsideItem
:headers="headers"
:root="true"
/>
</nav>
</div> </div>
<VPDocOutlineItem :headers="headers" :root="true" />
</div> </div>
</div> </nav>
</template> </template>
<style scoped> <style scoped>
.page-aside { .vp-doc-aside-outline {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.page-aside-outline {
display: none; display: none;
} }
.page-aside-outline.has-outline { .vp-doc-aside-outline.has-outline {
display: block; display: block;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { onClickOutside } from '@vueuse/core' import { onClickOutside } from '@vueuse/core'
import { nextTick, ref, watch } from 'vue' import { nextTick, ref, watch } from 'vue'
import type { MenuItem } from '../../composables/outline.js' import type { MenuItem } from '../composables/outline.js'
import { useData } from '../../composables/data.js' import { useData } from '../composables/data.js'
import DocOutlineItem from './DocOutlineItem.vue' import VPDocOutlineItem from './VPDocOutlineItem.vue'
const props = defineProps<{ const props = defineProps<{
headers: MenuItem[] headers: MenuItem[]
@ -48,7 +48,7 @@ function scrollToTop() {
</script> </script>
<template> <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"> <button v-if="headers.length > 0" ref="btn" :class="{ open }" @click="toggle">
{{ theme.outlineLabel || 'On this page' }} {{ theme.outlineLabel || 'On this page' }}
<span class="vpi-chevron-right icon" /> <span class="vpi-chevron-right icon" />
@ -64,7 +64,7 @@ function scrollToTop() {
</a> </a>
</div> </div>
<div class="outline"> <div class="outline">
<DocOutlineItem :headers="headers" /> <VPDocOutlineItem :headers="headers" />
</div> </div>
</div> </div>
</Transition> </Transition>
@ -72,11 +72,11 @@ function scrollToTop() {
</template> </template>
<style scoped> <style scoped>
.local-nav-outline-dropdown { .vp-local-nav-outline-dropdown {
padding: 12px 20px 11px; padding: 12px 20px 11px;
} }
.local-nav-outline-dropdown button { .vp-local-nav-outline-dropdown button {
position: relative; position: relative;
display: block; display: block;
font-size: 12px; font-size: 12px;
@ -86,11 +86,11 @@ function scrollToTop() {
transition: color var(--t-color); transition: color var(--t-color);
} }
.local-nav-outline-dropdown button:hover { .vp-local-nav-outline-dropdown button:hover {
color: var(--vp-c-text-1); 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); 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 { useRoutePath } from 'vuepress/client'
import { useSidebar } from '../composables/sidebar.js' import { useSidebar } from '../composables/sidebar.js'
import { inBrowser } from '../utils/index.js' import { inBrowser } from '../utils/index.js'
import SidebarItem from './SidebarItem.vue' import VPSidebarItem from './VPSidebarItem.vue'
import TransitionFadeSlideY from './TransitionFadeSlideY.vue' import TransitionFadeSlideY from './TransitionFadeSlideY.vue'
const props = defineProps<{ const props = defineProps<{
@ -32,7 +32,7 @@ watch(
onMounted(() => { onMounted(() => {
const activeItem = document.querySelector( const activeItem = document.querySelector(
`.sidebar-wrapper .auto-link[href*="${routePath.value}"]`, `.vp-sidebar .vp-link[href*="${routePath.value}"]`,
) )
if (!activeItem || !navEl.value) if (!activeItem || !navEl.value)
return return
@ -51,7 +51,7 @@ onMounted(() => {
<aside <aside
v-if="hasSidebar" v-if="hasSidebar"
ref="navEl" ref="navEl"
class="sidebar-wrapper" class="vp-sidebar"
:class="{ open }" :class="{ open }"
@click.stop @click.stop
> >
@ -74,7 +74,7 @@ onMounted(() => {
:key="item.text" :key="item.text"
class="group" class="group"
> >
<SidebarItem :item="item" :depth="0" /> <VPSidebarItem :item="item" :depth="0" />
</div> </div>
</nav> </nav>
</TransitionFadeSlideY> </TransitionFadeSlideY>
@ -83,7 +83,7 @@ onMounted(() => {
</template> </template>
<style scoped> <style scoped>
.sidebar-wrapper { .vp-sidebar {
position: fixed; position: fixed;
top: var(--vp-layout-top-height, 0); top: var(--vp-layout-top-height, 0);
bottom: 0; bottom: 0;
@ -106,7 +106,7 @@ onMounted(() => {
scrollbar-width: thin; scrollbar-width: thin;
} }
.sidebar-wrapper.open { .vp-sidebar.open {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transition: transition:
@ -115,12 +115,12 @@ onMounted(() => {
transform: translateX(0); transform: translateX(0);
} }
.dark .sidebar-wrapper { .dark .vp-sidebar {
box-shadow: var(--vp-shadow-1); box-shadow: var(--vp-shadow-1);
} }
@media (min-width: 960px) { @media (min-width: 960px) {
.sidebar-wrapper { .vp-sidebar {
z-index: 1; z-index: 1;
width: var(--vp-sidebar-width); width: var(--vp-sidebar-width);
max-width: 100%; max-width: 100%;
@ -134,7 +134,7 @@ onMounted(() => {
} }
@media (min-width: 1440px) { @media (min-width: 1440px) {
.sidebar-wrapper { .vp-sidebar {
width: width:
calc( calc(
(100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - (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 type { NotesSidebarItem } from '@vuepress-plume/plugin-notes-data'
import { computed } from 'vue' import { computed } from 'vue'
import { useSidebarControl } from '../composables/sidebar.js' import { useSidebarControl } from '../composables/sidebar.js'
import AutoLink from './AutoLink.vue' import VPLink from './VPLink.vue'
import VIcon from './VIcon.vue' import VPIcon from './VPIcon.vue'
const props = defineProps<{ const props = defineProps<{
item: NotesSidebarItem item: NotesSidebarItem
@ -56,7 +56,7 @@ function onCaretClick() {
</script> </script>
<template> <template>
<Component :is="sectionTag" class="sidebar-item" :class="classes"> <Component :is="sectionTag" class="vp-sidebar-item" :class="classes">
<div <div
v-if="item.text" v-if="item.text"
class="item" class="item"
@ -69,16 +69,16 @@ function onCaretClick() {
> >
<div class="indicator" /> <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" v-if="item.link"
:tag="linkTag" :tag="linkTag"
class="link" class="link"
:href="item.link" :href="item.link"
> >
<Component :is="textTag" class="text" v-html="item.text" /> <Component :is="textTag" class="text" v-html="item.text" />
</AutoLink> </VPLink>
<Component :is="textTag" v-else class="text" v-html="item.text" /> <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"> <div v-if="item.items && item.items.length" class="items">
<template v-if="depth < 5"> <template v-if="depth < 5">
<SidebarItem <VPSidebarItem
v-for="i in (item.items as NotesSidebarItem[])" v-for="i in (item.items as NotesSidebarItem[])"
:key="i.text" :key="i.text"
:item="i" :item="i"
@ -109,11 +109,11 @@ function onCaretClick() {
</template> </template>
<style scoped> <style scoped>
.sidebar-item.level-0 { .vp-sidebar-item.level-0 {
padding-bottom: 24px; padding-bottom: 24px;
} }
.sidebar-item.collapsed.level-0 { .vp-sidebar-item.collapsed.level-0 {
padding-bottom: 10px; padding-bottom: 10px;
} }
@ -124,7 +124,7 @@ function onCaretClick() {
width: 100%; width: 100%;
} }
.sidebar-item.collapsible > .item { .vp-sidebar-item.collapsible > .item {
cursor: pointer; cursor: pointer;
} }
@ -137,10 +137,10 @@ function onCaretClick() {
transition: background-color var(--t-color); transition: background-color var(--t-color);
} }
.sidebar-item.level-2.is-active > .item > .indicator, .vp-sidebar-item.level-2.is-active > .item > .indicator,
.sidebar-item.level-3.is-active > .item > .indicator, .vp-sidebar-item.level-3.is-active > .item > .indicator,
.sidebar-item.level-4.is-active > .item > .indicator, .vp-sidebar-item.level-4.is-active > .item > .indicator,
.sidebar-item.level-5.is-active > .item > .indicator { .vp-sidebar-item.level-5.is-active > .item > .indicator {
background-color: var(--vp-c-brand-1); background-color: var(--vp-c-brand-1);
} }
@ -157,50 +157,50 @@ function onCaretClick() {
transition: color var(--t-color); transition: color var(--t-color);
} }
.sidebar-item.level-0 .text { .vp-sidebar-item.level-0 .text {
font-weight: 700; font-weight: 700;
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.sidebar-item.level-1 .text, .vp-sidebar-item.level-1 .text,
.sidebar-item.level-2 .text, .vp-sidebar-item.level-2 .text,
.sidebar-item.level-3 .text, .vp-sidebar-item.level-3 .text,
.sidebar-item.level-4 .text, .vp-sidebar-item.level-4 .text,
.sidebar-item.level-5 .text { .vp-sidebar-item.level-5 .text {
font-weight: 500; font-weight: 500;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
} }
.sidebar-item.level-0.has-active > .item > .text, .vp-sidebar-item.level-0.has-active > .item > .text,
.sidebar-item.level-1.has-active > .item > .text, .vp-sidebar-item.level-1.has-active > .item > .text,
.sidebar-item.level-2.has-active > .item > .text, .vp-sidebar-item.level-2.has-active > .item > .text,
.sidebar-item.level-3.has-active > .item > .text, .vp-sidebar-item.level-3.has-active > .item > .text,
.sidebar-item.level-4.has-active > .item > .text, .vp-sidebar-item.level-4.has-active > .item > .text,
.sidebar-item.level-5.has-active > .item > .text, .vp-sidebar-item.level-5.has-active > .item > .text,
.sidebar-item.level-0.has-active > .item > .link > .text, .vp-sidebar-item.level-0.has-active > .item > .link > .text,
.sidebar-item.level-1.has-active > .item > .link > .text, .vp-sidebar-item.level-1.has-active > .item > .link > .text,
.sidebar-item.level-2.has-active > .item > .link > .text, .vp-sidebar-item.level-2.has-active > .item > .link > .text,
.sidebar-item.level-3.has-active > .item > .link > .text, .vp-sidebar-item.level-3.has-active > .item > .link > .text,
.sidebar-item.level-4.has-active > .item > .link > .text, .vp-sidebar-item.level-4.has-active > .item > .link > .text,
.sidebar-item.level-5.has-active > .item > .link > .text { .vp-sidebar-item.level-5.has-active > .item > .link > .text {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.sidebar-item.level-0.is-active > .item .link > .text, .vp-sidebar-item.level-0.is-active > .item .link > .text,
.sidebar-item.level-1.is-active > .item .link > .text, .vp-sidebar-item.level-1.is-active > .item .link > .text,
.sidebar-item.level-2.is-active > .item .link > .text, .vp-sidebar-item.level-2.is-active > .item .link > .text,
.sidebar-item.level-3.is-active > .item .link > .text, .vp-sidebar-item.level-3.is-active > .item .link > .text,
.sidebar-item.level-4.is-active > .item .link > .text, .vp-sidebar-item.level-4.is-active > .item .link > .text,
.sidebar-item.level-5.is-active > .item .link > .text { .vp-sidebar-item.level-5.is-active > .item .link > .text {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
.sidebar-item.level-0.is-link > .item > .link:hover .text, .vp-sidebar-item.level-0.is-link > .item > .link:hover .text,
.sidebar-item.level-1.is-link > .item > .link:hover .text, .vp-sidebar-item.level-1.is-link > .item > .link:hover .text,
.sidebar-item.level-2.is-link > .item > .link:hover .text, .vp-sidebar-item.level-2.is-link > .item > .link:hover .text,
.sidebar-item.level-3.is-link > .item > .link:hover .text, .vp-sidebar-item.level-3.is-link > .item > .link:hover .text,
.sidebar-item.level-4.is-link > .item > .link:hover .text, .vp-sidebar-item.level-4.is-link > .item > .link:hover .text,
.sidebar-item.level-5.is-link > .item > .link:hover .text { .vp-sidebar-item.level-5.is-link > .item > .link:hover .text {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
@ -236,21 +236,21 @@ function onCaretClick() {
color: var(--vp-c-text-1); color: var(--vp-c-text-1);
} }
.sidebar-item.level-0.is-active > .item > :deep(.vp-iconify), .vp-sidebar-item.level-0.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-1.is-active > .item > :deep(.vp-iconify), .vp-sidebar-item.level-1.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-2.is-active > .item > :deep(.vp-iconify), .vp-sidebar-item.level-2.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-3.is-active > .item > :deep(.vp-iconify), .vp-sidebar-item.level-3.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-4.is-active > .item > :deep(.vp-iconify), .vp-sidebar-item.level-4.is-active > .item > :deep(.vp-iconify),
.sidebar-item.level-5.is-active > .item > :deep(.vp-iconify) { .vp-sidebar-item.level-5.is-active > .item > :deep(.vp-iconify) {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
.sidebar-item.level-0.is-link > .item:hover :deep(.vp-iconify), .vp-sidebar-item.level-0.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-1.is-link > .item:hover :deep(.vp-iconify), .vp-sidebar-item.level-1.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-2.is-link > .item:hover :deep(.vp-iconify), .vp-sidebar-item.level-2.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-3.is-link > .item:hover :deep(.vp-iconify), .vp-sidebar-item.level-3.is-link > .item:hover :deep(.vp-iconify),
.sidebar-item.level-4.is-link > .item:hover :deep(.vp-iconify), .vp-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-5.is-link > .item:hover :deep(.vp-iconify) {
color: var(--vp-c-brand-1); color: var(--vp-c-brand-1);
} }
@ -262,21 +262,21 @@ function onCaretClick() {
transform: rotate(90deg); transform: rotate(90deg);
} }
.sidebar-item.collapsed .caret-icon { .vp-sidebar-item.collapsed .caret-icon {
transform: rotate(0); transform: rotate(0);
} }
.sidebar-item.level-1 .items, .vp-sidebar-item.level-1 .items,
.sidebar-item.level-2 .items, .vp-sidebar-item.level-2 .items,
.sidebar-item.level-3 .items, .vp-sidebar-item.level-3 .items,
.sidebar-item.level-4 .items, .vp-sidebar-item.level-4 .items,
.sidebar-item.level-5 .items { .vp-sidebar-item.level-5 .items {
padding-left: 16px; padding-left: 16px;
border-left: 1px solid var(--vp-c-divider); border-left: 1px solid var(--vp-c-divider);
transition: border-left var(--t-color); transition: border-left var(--t-color);
} }
.sidebar-item.collapsed .items { .vp-sidebar-item.collapsed .items {
display: none; display: none;
} }
</style> </style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,13 +10,13 @@ withDefaults(defineProps<Props>(), {
</script> </script>
<template> <template>
<span class="badge-view" :class="type"> <span class="vp-badge" :class="type">
<slot>{{ text }}</slot> <slot>{{ text }}</slot>
</span> </span>
</template> </template>
<style scoped> <style scoped>
.badge-view { .vp-badge {
display: inline-block; display: inline-block;
padding: 0 10px; padding: 0 10px;
margin-left: 2px; margin-left: 2px;
@ -30,47 +30,47 @@ withDefaults(defineProps<Props>(), {
transform: translateY(-2px); transform: translateY(-2px);
} }
h1 .badge-view { h1 .vp-badge {
margin-top: 4px; margin-top: 4px;
vertical-align: top; vertical-align: top;
} }
h2 .badge-view { h2 .vp-badge {
padding: 0 8px; padding: 0 8px;
margin-top: 3px; margin-top: 3px;
vertical-align: top; vertical-align: top;
} }
h3 .badge-view { h3 .vp-badge {
vertical-align: middle; vertical-align: middle;
} }
h4 .badge-view, h4 .vp-badge,
h5 .badge-view, h5 .vp-badge,
h6 .badge-view { h6 .vp-badge {
line-height: 18px; line-height: 18px;
vertical-align: middle; vertical-align: middle;
} }
.badge-view.info { .vp-badge.info {
color: var(--vp-badge-info-text); color: var(--vp-badge-info-text);
background-color: var(--vp-badge-info-bg); background-color: var(--vp-badge-info-bg);
border-color: var(--vp-badge-info-border); border-color: var(--vp-badge-info-border);
} }
.badge-view.tip { .vp-badge.tip {
color: var(--vp-badge-tip-text); color: var(--vp-badge-tip-text);
background-color: var(--vp-badge-tip-bg); background-color: var(--vp-badge-tip-bg);
border-color: var(--vp-badge-tip-border); border-color: var(--vp-badge-tip-border);
} }
.badge-view.warning { .vp-badge.warning {
color: var(--vp-badge-warning-text); color: var(--vp-badge-warning-text);
background-color: var(--vp-badge-warning-bg); background-color: var(--vp-badge-warning-bg);
border-color: var(--vp-badge-warning-border); border-color: var(--vp-badge-warning-border);
} }
.badge-view.danger { .vp-badge.danger {
color: var(--vp-badge-danger-text); color: var(--vp-badge-danger-text);
background-color: var(--vp-badge-danger-bg); background-color: var(--vp-badge-danger-bg);
border-color: var(--vp-badge-danger-border); 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 ?? [])) { for (const node of Array.from(el?.childNodes ?? [])) {
if (node.nodeType === 1) { if (node.nodeType === 1) {
if ( if (
(node as Element).classList.contains('badge-view') (node as Element).classList.contains('vp-badge')
|| (node as Element).classList.contains('ignore-header') || (node as Element).classList.contains('ignore-header')
) { ) {
continue continue

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouteLocale, withBase } from 'vuepress/client' import { useRouteLocale, withBase } from 'vuepress/client'
import LayoutContent from '../components/LayoutContent.vue'
import Nav from '../components/Nav/index.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' import { useData } from '../composables/data.js'
const root = useRouteLocale() const root = useRouteLocale()
@ -9,45 +10,68 @@ const { theme } = useData()
</script> </script>
<template> <template>
<div class="theme-plume"> <div vp-not-found class="theme-plume vp-layout">
<Nav /> <slot name="layout-top" />
<LayoutContent is-not-found> <VPSkipLink />
<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 class="action"> <Nav />
<a class="link" :href="withBase(root)" :aria-label="theme.notFound?.linkLabel ?? 'go to home'"> <div id="VPContent" class="vp-content">
{{ theme.notFound?.linkText ?? 'Take me home' }} <slot name="not-found">
</a> <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>
</div> </slot>
</LayoutContent> </div>
<VPFooter />
<slot name="layout-bottom" />
</div> </div>
</template> </template>
<style scoped> <style scoped>
.theme-plume { .vp-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100vh; 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; padding: 64px 24px 96px;
text-align: center; text-align: center;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.not-found { .vp-not-found {
padding: 96px 32px 168px; padding: 96px 32px 168px;
} }
} }
@ -71,6 +95,7 @@ const { theme } = useData()
height: 1px; height: 1px;
margin: 24px auto 18px; margin: 24px auto 18px;
background-color: var(--vp-c-divider); background-color: var(--vp-c-divider);
transition: background-color var(--t-color);
} }
.quote { .quote {
@ -79,6 +104,7 @@ const { theme } = useData()
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: var(--vp-c-text-2); color: var(--vp-c-text-2);
transition: color var(--t-color);
} }
.action { .action {
@ -90,16 +116,14 @@ const { theme } = useData()
padding: 3px 16px; padding: 3px 16px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: var(--vp-c-brand); color: var(--vp-c-brand-1);
border: 1px solid var(--vp-c-brand); border: 1px solid var(--vp-c-brand-1);
border-radius: 16px; border-radius: 16px;
transition: transition: color var(--t-color), border-color var(--t-color);
border-color 0.25s,
color 0.25s;
} }
.link:hover { .link:hover {
color: var(--vp-c-brand-dark); color: var(--vp-c-brand-2);
border-color: var(--vp-c-brand-dark); border-color: var(--vp-c-brand-2);
} }
</style> </style>

View File

@ -1,3 +1,4 @@
/* ------------------ Plugin Nprogress ------------------ */
#nprogress .bar { #nprogress .bar {
background: var(--vp-c-brand-1); background: var(--vp-c-brand-1);
} }
@ -10,3 +11,8 @@
#nprogress .peg { #nprogress .peg {
box-shadow: 0 0 10px var(--vp-c-brand-1), 0 0 5px var(--vp-c-brand-1); 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("./normalize.css");
@import url("./icons.css"); @import url("./icons.css");
@import url("./social-icons.css"); @import url("./social-icons.css");
@import url("./nprogress.css"); @import url("./compat.css");
@import url("./utils.css"); @import url("./utils.css");
@import url("./content.css"); @import url("./content.css");
@import url("./code.css"); @import url("./code.css");

View File

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

View File

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

View File

@ -1,7 +1,9 @@
import type { ThemeImage } from '../base.js' 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 home?: true
friends?: never
config?: PlumeThemeHomeConfig[] 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 { WatermarkPluginFrontmatter } from '@vuepress/plugin-watermark'
import type { ThemeOutline } from '../base.js' import type { ThemeOutline } from '../base.js'
import type { NavItemWithLink } from '../navbar.js' import type { NavItemWithLink } from '../navbar.js'
import type { PlumeNormalFrontmatter } from './normal.js'
export interface PlumeThemePageFrontmatter { export interface PlumeThemePageFrontmatter extends PlumeNormalFrontmatter {
home?: false home?: never
friends?: never
/**
*
*/
comments?: boolean comments?: boolean
/**
*
*/
editLink?: boolean editLink?: boolean
/**
*
*/
editLinkPattern?: string editLinkPattern?: string
/**
*
*/
lastUpdated?: boolean lastUpdated?: boolean
/**
*
*/
contributors?: boolean contributors?: boolean
/**
*
*/
prev?: string | NavItemWithLink prev?: string | NavItemWithLink
/**
*
*/
next?: string | NavItemWithLink next?: string | NavItemWithLink
/**
*
*/
sidebar?: string | false sidebar?: string | false
aside?: boolean /**
*
*/
aside?: boolean | 'left'
/**
*
*/
outline?: ThemeOutline outline?: ThemeOutline
backToTop?: boolean /**
externalLink?: boolean *
*/
readingTime?: boolean readingTime?: boolean
/**
*
*/
watermark?: WatermarkPluginFrontmatter['watermark'] & { fullPage?: 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 outline?: ThemeOutline
/**
*
*
* @default true
*/
aside?: boolean | 'left'
/** /**
* language text * language text
*/ */

View File

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