feat(theme): add badge support for navbar and sidebar (#559)

This commit is contained in:
pengzhanbo 2025-04-19 11:40:35 +08:00 committed by GitHub
parent 0bceda590c
commit 73ed8dc9c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 264 additions and 67 deletions

View File

@ -39,11 +39,11 @@
"picocolors": "catalog:prod"
},
"plume-deps": {
"vuepress": "2.0.0-rc.20",
"vuepress": "2.0.0-rc.21",
"vue": "^3.5.13",
"sass-embedded": "^1.86.3",
"sass-loader": "^16.0.5",
"http-server": "^14.1.1",
"typescript": "^5.8.2"
"typescript": "^5.8.3"
}
}

View File

@ -46,6 +46,7 @@ export const zhNavbar = defineNavbarConfig([
{
text: `${version}`,
icon: 'codicon:versions',
badge: '新',
items: [
{ text: '更新日志', link: '/changelog/' },
{ text: '参与贡献', link: '/contributing/' },

View File

@ -80,7 +80,7 @@ export const themeGuide = defineNoteConfig({
'jsFiddle',
'codeSandbox',
'replit',
{ link: 'frontend-deprecated', text: '前端(弃用)' },
'frontend-deprecated',
],
},
{

View File

@ -2,7 +2,6 @@
title: 通用配置
createTime: 2024/03/02 20:01:09
permalink: /config/frontmatter/basic/
badge: 徽章 badge
---
## 概述

View File

@ -2,9 +2,6 @@
title: Markdown 配置
createTime: 2025/03/15 14:54:19
permalink: /config/markdown/
badge:
type: tip
text: 1.0.0-rc.136 +
---
## 概述

View File

@ -97,6 +97,19 @@ export default defineUserConfig({
类型: `NavItem[]`
```ts
interface ThemeBadge {
/* 徽章文本 */
text?: string
/* 徽章类型,内置: 'info' | 'tip' | 'danger' | 'warning' */
type?: string
/* 文本颜色 */
color?: string
/* 背景颜色 */
bgColor?: string
/* 边框颜色 */
borderColor?: string
}
type NavItem = string | {
/**
* 导航栏文本
@ -110,13 +123,18 @@ type NavItem = string | {
*/
link: string
/**
* - 支持 iconify 图标,直接使用 iconify name 即可自动加载
* 支持 iconify 图标,直接使用 iconify name 即可自动加载
* @see https://icon-sets.iconify.design/
*
* - 如果 iconify 图标不满足您的需求,也可以支持传入 svg 字符串。
* - 还支持使用 本地图片 或 远程图片,本地图片的路径需要以 `/` 开头。
*/
icon?: string | { svg: string }
/**
* 徽章,支持自定义徽章样式
*/
badge?: string | ThemeBadge
/**
* 控制元素何时被激活
*/
@ -156,12 +174,14 @@ export default defineUserConfig({
text: 'vuepress-theme-plume',
link: '/vuepress-theme-plume/',
icon: 'mdi:paper-airplane',
badge: '徽章'
},
],
},
{
text: 'Vuepress Plugin',
icon: 'mingcute:plugin-2-fill',
badge: { text: '徽章', type: 'warning' },
items: [
{
text: 'caniuse',
@ -184,8 +204,8 @@ export default defineUserConfig({
icon: 'material-symbols:note-alt-rounded',
},
{
text: 'shikiji',
link: '/vuepress-plugin/shikiji/',
text: 'shiki',
link: '/vuepress-plugin/shiki/',
icon: 'material-symbols-light:code-blocks-outline-rounded',
},
],

View File

@ -103,7 +103,20 @@ interface NoteItem {
sidebar?: 'auto' | (string | SidebarItem)[]
}
export interface SidebarItem {
interface ThemeBadge {
/* 徽章文本 */
text?: string
/* 徽章类型,内置: 'info' | 'tip' | 'danger' | 'warning' */
type?: string
/* 文本颜色 */
color?: string
/* 背景颜色 */
bgColor?: string
/* 边框颜色 */
borderColor?: string
}
interface SidebarItem {
/**
* 侧边栏文本
*/
@ -119,6 +132,11 @@ export interface SidebarItem {
*/
icon?: ThemeIcon
/**
* 徽章
*/
badge?: string | ThemeBadge
/**
* 次级侧边栏分组
* 不超过 3 层

View File

@ -3,8 +3,6 @@ title: 瀑布流容器
icon: ri:layout-masonry-line
createTime: 2024/12/14 17:17:06
permalink: /guide/components/card-masonry/
badge:
text: v1.0.0-rc.121 +
---
## 概述

View File

@ -3,7 +3,6 @@ title: 轮播图
icon: dashicons:images-alt2
createTime: 2024/09/12 22:00:22
permalink: /guide/components/swiper/
badge: v1.0.0-rc.98 +
---
## 概述

View File

@ -1,9 +1,6 @@
---
title: 公告板
icon: mingcute:announcement-line
badge:
type: tip
text: v1.0.0-rc.112 +
createTime: 2024/10/19 21:51:22
permalink: /guide/features/bulletin/
---

View File

@ -1,9 +1,6 @@
---
title: 文章变更历史
icon: radix-icons:activity-log
badge:
type: tip
text: v1.0.0-rc.115 +
createTime: 2024/11/07 18:16:25
permalink: /guide/features/changelog/
---

View File

@ -1,9 +1,6 @@
---
title: 文章贡献者
icon: simple-icons:contributorcovenant
badge:
type: tip
text: v1.0.0-rc.115 +
createTime: 2024/11/07 16:26:54
permalink: /guide/features/contributors/
---

View File

@ -1,9 +1,6 @@
---
title: 文章版权所有
icon: lucide:creative-commons
badge:
type: tip
text: v1.0.0-rc.118 +
createTime: 2024/11/20 10:52:49
permalink: /guide/features/copyright/
---

View File

@ -3,9 +3,7 @@ title: 资源链接替换
icon: lucide:replace
createTime: 2025/04/03 11:45:17
permalink: /guide/features/replace-assets/
badge:
type: tip
text: 1.0.0-rc.139 +
badge: 新
---
## 概述

View File

@ -3,9 +3,6 @@ title: 对话记录
icon: cil:chat-bubble
createTime: 2025/03/24 21:40:18
permalink: /guide/markdown/chat/
badge:
type: tip
text: 1.0.0-rc.138
---
## 前言

View File

@ -3,9 +3,6 @@ title: 折叠面板
icon: carbon:collapse-categories
createTime: 2025/03/22 22:27:22
permalink: /guide/markdown/collapse/
badge:
type: tip
text: 1.0.0-rc.137 +
---
## 概述

View File

@ -3,9 +3,6 @@ title: 时间线
icon: mdi:timeline-text-outline
createTime: 2025/03/20 18:05:29
permalink: /guide/markdown/timeline/
badge:
text: 1.0.0-rc.137 +
type: tip
---
## 概述

View File

@ -279,6 +279,21 @@ export default defineNoteConfig({
以下是 侧边栏的 类型定义:
```ts
interface ThemeBadge {
/* 徽章文本 */
text?: string
/* 徽章类型,内置: 'info' | 'tip' | 'danger' | 'warning' */
type?: string
/* 文本颜色 */
color?: string
/* 背景颜色 */
bgColor?: string
/* 边框颜色 */
borderColor?: string
}
type ThemeIcon = string | { svg: string }
type Sidebar = (string | SidebarItem)[]
interface SidebarItem {
@ -297,6 +312,11 @@ interface SidebarItem {
*/
icon?: ThemeIcon
/**
* 侧边栏徽章
*/
badge?: string | ThemeBadge
/**
* 当前分组的链接前缀,链接前缀会拼接在 `items` 内的 `link` 之前
* 当且仅当 `items` 内的 `link` 为 相对路径时,才会拼接
@ -461,7 +481,7 @@ const typescript = defineNoteConfig({
### 侧边栏图标
为侧边栏添加 图标 有助于 侧边栏更好的呈现。得益于 [iconify](https://iconify.design/) 这个强大的开源图标库,
为侧边栏添加 ==图标== 有助于 侧边栏更好的呈现。得益于 [iconify](https://iconify.design/) 这个强大的开源图标库,
你可以使用超过 `200k` 的图标,仅需要添加 `icon` 配置即可。
```ts title=".vuepress/notes.ts" twoslash
@ -519,16 +539,56 @@ const typescript = defineNoteConfig({
- …
:::
你可能已经注意到,`sidebar: auto` 时,该如何配置 侧边栏图标,事实上很简单,直接在 文件的 `frontmatter` 部分,
添加 一个 `icon` 字段即可。
`sidebar: auto` 时,可在 md 文件的 `frontmatter` 部分,添加 一个 `icon` 字段:
```md title="typescript/guide/intro.md"
```md title="intro.md"
---
title: 介绍
icon: ep:guide
---
```
### 侧边栏徽章 <Badge text="v1.0.0-rc.143 +" />
主题支持为侧边栏添加徽章,徽章可以用于辅助提供更多的信息。
```ts title=".vuepress/notes.ts" twoslash
import { defineNoteConfig } from 'vuepress-theme-plume'
const typescript = defineNoteConfig({
dir: 'typescript',
link: '/typescript/',
sidebar: [
{
text: '指南',
prefix: '/guide',
badge: { text: '徽章', type: 'danger' }, // [!code hl]
items: [
{ text: '介绍', link: 'intro', badge: '徽章' }, // [!code hl]
],
},
]
})
```
`sidebar: auto` 时,可在 md 文件的 `frontmatter` 部分,添加 一个 `badge` 字段:
```md title="intro.md"
---
title: 介绍
badge:
text: 徽章
type: danger
---
```
```md title="intro.md"
---
title: 介绍
badge: 徽章
---
```
### 侧边栏组内分隔
在组内对 项 进行分隔 是一个相对小众的需求,它在组的项比较多,但又不适合拆分为多个组,或者组内拆分多组的情况下,

View File

@ -3,8 +3,6 @@ title: 前端演示
icon: icon-park-outline:html-five
createTime: 2025/01/08 21:34:26
permalink: /guide/repl/frontend/
badge:
text: 1.0.0-rc.127 +
---
::: important [旧的前端代码演示](./frontend-deprecated.md) 已弃用,请迁移至此新的方案。

View File

@ -94,4 +94,14 @@ watchEffect(() => {
position: fixed;
}
}
.vp-nav :deep(.vp-menu-badge) {
padding: 3px 4px;
margin-left: 4px;
font-size: 10px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.2px;
border-radius: 6px;
}
</style>

View File

@ -41,5 +41,6 @@ const childrenActive = computed(() => isChildActive(props.item))
:button="item.text"
:items="item.items"
:prefix-icon="item.icon"
:badge="item.badge"
/>
</template>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { ResolvedNavItemWithLink } from '../../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { resolveRouteFullPath } from 'vuepress/client'
@ -30,6 +31,11 @@ const { page } = useData()
>
<VPIcon v-if="item.icon" :name="item.icon" />
<span v-html="item.text" />
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</VPLink>
</template>
@ -52,4 +58,8 @@ const { page } = useData()
.navbar-menu-link:hover {
color: var(--vp-c-brand-1);
}
.navbar-menu-link :deep(.vp-menu-badge) {
transform: translateY(0);
}
</style>

View File

@ -18,6 +18,7 @@ const navbar = useNavbarData()
:text="item.text || ''"
:items="item.items"
:icon="item.icon"
:badge="item.badge"
/>
</template>
</nav>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { ThemeIcon } from '../../../shared/index.js'
import type { ThemeBadge, ThemeIcon } from '../../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPNavScreenMenuGroupLink from '@theme/Nav/VPNavScreenMenuGroupLink.vue'
import VPNavScreenMenuGroupSection from '@theme/Nav/VPNavScreenMenuGroupSection.vue'
import VPIcon from '@theme/VPIcon.vue'
@ -11,6 +12,7 @@ import '@vuepress/helper/transition/fade-in-height-expand.css'
const props = defineProps<{
text: string
icon?: ThemeIcon
badge?: string | ThemeBadge
items: any[]
}>()
@ -36,6 +38,11 @@ function toggle() {
<span class="button-text">
<VPIcon v-if="icon" :name="icon" />
<span v-html="text" />
<VPBadge
v-if="badge"
class="vp-menu-badge"
v-bind="typeof badge === 'string' ? { text: badge } : badge"
/>
</span>
<span class="vpi-plus button-icon" />
</button>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { ResolvedNavItemWithLink } from '../../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { inject } from 'vue'
@ -22,6 +23,11 @@ const closeScreen = inject('close-screen') as () => void
>
<VPIcon v-if="item.icon" :name="item.icon" />
<span v-html="item.text" />
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</VPLink>
</template>

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import type { ResolvedNavItemWithLink } from '../../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { inject } from 'vue'
@ -22,6 +23,11 @@ const closeScreen = inject('close-screen') as () => void
>
<VPIcon v-if="item.icon" :name="item.icon" />
<span v-html="item.text" />
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</VPLink>
</template>

View File

@ -1,15 +1,18 @@
<script lang="ts" setup>
import type { ThemeBadge, ThemeIcon } from '../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPMenu from '@theme/VPMenu.vue'
import { ref } from 'vue'
import { useFlyout } from '../composables/index.js'
defineProps<{
prefixIcon?: string | { svg: string }
prefixIcon?: ThemeIcon
icon?: any
button?: string
label?: string
items?: any[]
badge?: string | ThemeBadge
}>()
const open = ref(false)
@ -43,6 +46,7 @@ function onBlur() {
<VPIcon v-if="prefixIcon" :name="prefixIcon" />
<span v-if="icon" class="option-icon" :class="[icon]" />
<span v-if="button" v-html="button" />
<VPBadge v-if="badge" class="vp-menu-badge" v-bind="typeof badge === 'string' ? { text: badge } : badge" />
<span class="vpi-chevron-down text-icon" />
</span>
@ -146,4 +150,8 @@ function onBlur() {
margin-left: 4px;
fill: currentcolor;
}
.vp-flyout :deep(.vp-menu-badge) {
transform: translateY(0);
}
</style>

View File

@ -1,4 +1,5 @@
<script lang="ts" setup>
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { resolveRouteFullPath } from 'vuepress/client'
@ -25,6 +26,11 @@ const { page } = useData()
>
<VPIcon v-if="item.icon" :name="item.icon" />
{{ item.text }}
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</VPLink>
</div>
</template>
@ -64,4 +70,8 @@ const { page } = useData()
.link :deep(.vp-icon-img) {
margin-left: 0;
}
.vp-menu-link .link :deep(.vp-menu-badge) {
transform: translateY(-2px);
}
</style>

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import type { ResolvedSidebarItem } from '../../shared/index.js'
import VPBadge from '@theme/global/VPBadge.vue'
import VPIcon from '@theme/VPIcon.vue'
import VPLink from '@theme/VPLink.vue'
import { FadeInExpandTransition } from '@vuepress/helper/client'
@ -86,10 +87,24 @@ function onCaretClick() {
class="link"
:href="item.link"
>
<Component :is="textTag" class="text" v-html="item.text" />
<Component :is="textTag" class="text">
<span v-html="item.text" />
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</Component>
</VPLink>
<Component :is="textTag" v-else class="text" :class="{ separator: isSeparator }" v-html="item.text" />
<Component :is="textTag" v-else class="text" :class="{ separator: isSeparator }">
<span v-html="item.text" />
<VPBadge
v-if="item.badge"
class="vp-menu-badge"
v-bind="typeof item.badge === 'string' ? { text: item.badge } : item.badge"
/>
</Component>
<div
v-if="item.collapsed != null"
@ -173,6 +188,7 @@ function onCaretClick() {
padding: 4px 0;
font-size: 14px;
line-height: 24px;
vertical-align: middle;
transition: color var(--vp-t-color);
}
@ -259,10 +275,12 @@ function onCaretClick() {
}
.item :deep(.vp-icon) {
align-self: baseline;
margin: 0 0.25rem 0 0;
font-size: 0.9em;
color: var(--vp-c-text-2);
transition: color var(--vp-t-color);
transform: translateY(9px);
}
.item :deep(.vp-icon-img) {
@ -317,4 +335,19 @@ function onCaretClick() {
border-left: 1px solid var(--vp-c-divider);
transition: border-left var(--vp-t-color);
}
.vp-sidebar-item .text :deep(.vp-menu-badge) {
padding: 3px 4px;
margin-top: 0;
margin-left: 4px;
font-size: 10px;
font-weight: 600;
line-height: 1;
letter-spacing: 0.2px;
border-radius: 6px;
}
.vp-sidebar-item.collapsible > .item .text :deep(.vp-menu-badge) {
transform: translateY(3px);
}
</style>

View File

@ -1,14 +1,8 @@
<script setup lang="ts">
import type { ThemeBadge } from '../../../shared/index.js'
import { computed } from 'vue'
interface Props {
text?: string
type?: string
color?: string
bgColor?: string
borderColor?: string
}
const props = withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<ThemeBadge>(), {
type: 'tip',
borderColor: 'transparent',
})

View File

@ -149,6 +149,7 @@ function resolveSidebarItems(
navLink.link = link.startsWith('---') ? link : normalizeLink(_prefix, link)
const nav = resolveNavLink(navLink.link)
navLink.icon = nav.icon || navLink.icon
navLink.badge = nav.badge || navLink.badge
}
const nextPrefix = normalizePrefix(_prefix, prefix || dir)
if (items === 'auto') {
@ -157,6 +158,7 @@ function resolveSidebarItems(
navLink.link = normalizeLink(autoHomeData.value[nextPrefix])
const nav = resolveNavLink(navLink.link)
navLink.icon = nav.icon || navLink.icon
navLink.badge = nav.badge || navLink.badge
}
}
else {

View File

@ -1,4 +1,4 @@
import type { ResolvedNavItemWithLink } from '../../shared/index.js'
import type { ResolvedNavItemWithLink, ThemeBadge } from '../../shared/index.js'
import {
ensureEndingSlash,
ensureLeadingSlash,
@ -18,6 +18,7 @@ export function resolveNavLink(link: string): ResolvedNavItemWithLink {
const { notFound, meta, path } = resolveRoute<{
title?: string
icon?: string
badge?: string | ThemeBadge
}>(link)
return notFound
@ -26,6 +27,7 @@ export function resolveNavLink(link: string): ResolvedNavItemWithLink {
text: meta.title || path,
link: path,
icon: meta.icon,
badge: meta.badge,
}
}

View File

@ -21,6 +21,10 @@ function cleanPageData(page: Page<ThemePageData>) {
page.routeMeta.icon = page.frontmatter.icon
}
if (page.frontmatter.badge) {
page.routeMeta.badge = page.frontmatter.badge
}
if (page.frontmatter.home) {
page.frontmatter.pageLayout = 'home'
delete page.frontmatter.home

View File

@ -20,3 +20,14 @@ export type ThemeColor = string | { light: string, dark: string }
* heading
*/
export type ThemeOutline = false | number | [number, number] | 'deep'
/**
*
*/
export interface ThemeBadge {
text?: string
type?: string
color?: string
bgColor?: string
borderColor?: string
}

View File

@ -1,4 +1,4 @@
import type { ThemeIcon } from '../common/index.js'
import type { ThemeBadge, ThemeIcon } from '../common/index.js'
/**
*
@ -19,6 +19,11 @@ export interface NavItemWithLink {
*/
icon?: ThemeIcon
/**
*
*/
badge?: string | ThemeBadge
/**
*
*/
@ -57,6 +62,11 @@ export interface NavItemChildren {
*/
icon?: ThemeIcon
/**
*
*/
badge?: string | ThemeBadge
/**
*
*/
@ -75,6 +85,11 @@ export interface NavItemWithChildren {
*/
icon?: ThemeIcon
/**
*
*/
badge?: string | ThemeBadge
/**
*
*/

View File

@ -1,4 +1,4 @@
import type { ThemeIcon } from '../common/index.js'
import type { ThemeBadge, ThemeIcon } from '../common/index.js'
export type ThemeSidebar = 'auto' | (string | ThemeSidebarItem)[] | ThemeSidebarMulti
@ -25,6 +25,11 @@ export interface ThemeSidebarItem {
*/
icon?: ThemeIcon
/**
*
*/
badge?: string | ThemeBadge
/**
*
*/

View File

@ -1,5 +1,5 @@
import type { WatermarkPluginFrontmatter } from '@vuepress/plugin-watermark'
import type { ThemeOutline } from '../common/index.js'
import type { ThemeBadge, ThemeIcon, ThemeOutline } from '../common/index.js'
import type { NavItemWithLink } from '../features/index.js'
import type { ThemeNormalFrontmatter } from './normal.js'
@ -66,13 +66,10 @@ export interface ThemePageFrontmatter extends ThemeNormalFrontmatter {
* svg 使 svg url
* svg
*/
icon?: string | { svg: string }
icon?: ThemeIcon
/**
*
*/
badge?: string | {
text: string
type?: 'info' | 'tip' | 'warning' | 'danger'
}
badge?: string | ThemeBadge
}

View File

@ -1,4 +1,4 @@
import type { ThemeIcon } from '../common/index.js'
import type { ThemeBadge, ThemeIcon } from '../common/index.js'
/**
* 使
@ -16,6 +16,7 @@ export interface ResolvedNavItemWithLink {
text: string
link: string
icon?: ThemeIcon
badge?: string | ThemeBadge
items?: never
activeMatch?: string
rel?: string
@ -30,6 +31,7 @@ export interface ResolvedNavItemWithLink {
export interface ResolvedNavItemChildren {
text?: string
icon?: ThemeIcon
badge?: string | ThemeBadge
items: ResolvedNavItemWithLink[]
}
@ -40,6 +42,7 @@ export interface ResolvedNavItemChildren {
export interface ResolvedNavItemWithChildren {
text?: string
icon?: ThemeIcon
badge?: string | ThemeBadge
items: (ResolvedNavItemChildren | ResolvedNavItemWithLink)[]
activeMatch?: string
}

View File

@ -1,4 +1,4 @@
import type { ThemeIcon } from '../common/index.js'
import type { ThemeBadge, ThemeIcon } from '../common/index.js'
/**
*
@ -35,6 +35,11 @@ export interface ResolvedSidebarItem {
*/
icon?: ThemeIcon
/**
*
*/
badge?: string | ThemeBadge
/**
*
*/