时间线
采用了 @HanochMa/vitepress-markdown-timeline 的项目
Demo:https://hanochma.github.io/daily/2023-04
npm install vitepress-markdown-timeline
在 config.mts
中注册 markdown 解析插件
import timeline from "vitepress-markdown-timeline";
export default {
markdown: {
// 行号显示
lineNumbers: true,
// 时间线
config: (md) => {
md.use(timeline);
},
},
}
在 .vitepress/theme/index.ts
中引入时间线样式
说明
如果你没有这个文件,就自己新建
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
// 只需添加以下一行代码,引入时间线样式
import "vitepress-markdown-timeline/dist/theme/index.css";
export default {
extends: DefaultTheme,
}
最后我们在markdown文件中,按格式使用即可
输入:
::: timeline 2023-04-24
- 一个非常棒的开源项目 H5-Dooring 目前 star 3.1k
- 开源地址 https://github.com/MrXujiang/h5-Dooring
- 基本介绍 http://h5.dooring.cn/doc/zh/guide/
- 《深入浅出webpack》 http://webpack.wuhaolin.cn/
:::
::: timeline 2023-04-23
:::
谷歌分析
利用插件 google-analytics ,来查看网站访问量
这里我们用 @ZhongxuYang/vitepress-plugin-google-analytics 的插件
npm install vitepress-plugin-google-analytics
在 .vitepress/theme/index.ts
中引入
// .vitepress/theme/index.ts
import DefaultTheme from "vitepress/theme"
import googleAnalytics from 'vitepress-plugin-google-analytics'
export default {
extends: DefaultTheme,
enhanceApp({app}) {
googleAnalytics({
id: 'G-******', //跟踪ID,在analytics.google.com注册即可
}),
},
}
看板娘
第一次接触的人会比较懵,其实就是在右下角有个二次元的人物,类似电子宠物
这里使用 @xinlei3166/vitepress-theme-website 的 Live2D 插件
npm install vitepress-theme-website
在 .vitepress/theme/index.ts
粘贴下面代码并保存
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { useLive2d } from 'vitepress-theme-website'
export default {
extends: DefaultTheme,
setup() {
// 看板娘
useLive2d({
enable: true,
model: {
url: 'https://raw.githubusercontent.com/iCharlesZ/vscode-live2d-models/master/model-library/hibiki/hibiki.model.json'
},
display: {
position: 'right',
width: '135px',
height: '300px',
xOffset: '35px',
yOffset: '5px'
},
mobile: {
show: true
},
react: {
opacity: 0.8
}
})
}
}
想要更换模型在 @iCharlesZ 这里找,替换 model
中的 url
链接即可
useLive2d({
model: {
url: 'https://raw.githubusercontent.com/iCharlesZ/vscode-live2d-models/master/model-library/bilibili-22/index.json'
}
})
浏览量
基本上使用的是 不蒜子,免费的且足够好用
npm install busuanzi.pure.js
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { inBrowser } from 'vitepress'
import busuanzi from 'busuanzi.pure.js'
export default {
extends: DefaultTheme,
enhanceApp({ app , router }) {
if (inBrowser) {
router.onAfterRouteChanged = () => {
busuanzi.fetch()
}
}
},
}
使用就很简单了,复制到页面中使用即可
说明
本地开发出现数字即算成功,等你部署后会显示正确的数值
本站总访问量 <span id="busuanzi_value_site_pv" /> 次
本站访客数 <span id="busuanzi_value_site_uv" /> 人次
样式还可以根据自己需求选择封装
⭐自动侧边栏(插件vitepress-sidebar)
发现一款自动侧边栏,简单好用 @jooy2/vitepress-sidebar
安装文档:https://vitepress-sidebar.jooy2.com/guide/getting-started
npm i -D vitepress-sidebar
在 configs.mts
中引入配置,可以根据 作者api文档 按需修改
// .vitepress/configs.mts
import { generateSidebar } from 'vitepress-sidebar';
const vitepressSidebarOptions = {
/* Options... */
};
export default defineConfig({
themeConfig: {
sidebar: generateSidebar({
/*
* For detailed instructions, see the links below:
* https://vitepress-sidebar.jooy2.com/guide/api
*/
documentRootPath: '/docs', //文档根目录
// scanStartPath: null,
// resolvePath: null,
// useTitleFromFileHeading: true,
// useTitleFromFrontmatter: true,
// frontmatterTitleFieldName: 'title',
// useFolderTitleFromIndexFile: false, //是否使用层级首页文件名做分级标题
// useFolderLinkFromIndexFile: false, //是否链接至层级首页文件
// hyphenToSpace: true,
// underscoreToSpace: true,
// capitalizeFirst: false,
// capitalizeEachWords: false,
collapsed: false, //折叠组关闭
collapseDepth: 2, //折叠组2级菜单
// sortMenusByName: false,
// sortMenusByFrontmatterOrder: false,
// sortMenusByFrontmatterDate: false,
// sortMenusOrderByDescending: false,
// sortMenusOrderNumericallyFromTitle: false,
// sortMenusOrderNumericallyFromLink: false,
// frontmatterOrderDefaultValue: 0,
// manualSortFileNameByPriority: ['first.md', 'second', 'third.md'], //手动排序,文件夹不用带后缀
removePrefixAfterOrdering: false, //删除前缀,必须与prefixSeparator一起使用
prefixSeparator: '.', //删除前缀的符号
// excludeFiles: ['first.md', 'secret.md'],
// excludeFilesByFrontmatterFieldName: 'exclude',
// excludeFolders: ['secret-folder'],
// includeDotFiles: false,
// includeRootIndexFile: false,
// includeFolderIndexFile: false, //是否包含层级主页
// includeEmptyFolder: false,
// rootGroupText: 'Contents',
// rootGroupLink: 'https://github.com/jooy2',
// rootGroupCollapsed: false,
// convertSameNameSubFileToGroupIndexPage: false,
// folderLinkNotIncludesFileName: false,
// keepMarkdownSyntaxFromTitle: false,
// debugPrint: false,
}),
},
})
为了避免安装插件影响原项目,可以看下面的示例
stackblitz演示:https://stackblitz.com/edit/vite-y1rga7
等待生成后可查看,左侧是目录,右侧是页面
注意:插件在读取目录之后,你再修改文件名,需要重启才能生效
当位于一个页面时,隐藏另一个页面的菜单,可以参考多侧边栏操作方法 | VitePress Sidebar
自动侧边栏(插件vite-plugin-vitepress-auto-nav)
项目地址:https://github.com/Xaviw/vite-plugin-vitepress-auto-nav
安装
# 使用 ts 时推荐安装 vite,否则会有类型错误
npm i vite-plugin-vitepress-auto-nav vite -D
添加插件
// .vitepress/config.ts
import AutoNav from "vite-plugin-vitepress-auto-nav";
export default defineConfig({
vite: {
plugins: [
AutoNav({
// 自定义配置
pattern: ["**/!(README|TODO).md"], // 也可以在这里排除不展示的文件,例如不匹配 README 和 TODO 文件
settings: {
a: { hide: true }, // 不显示名称为 a 的文件夹或 md 文件
b: { title: 'bb' }, // 名称为 b 的文件夹或文件在菜单中显示为 bb
c/b: { sort : 3 }, // 通过路径精确匹配 c 文件夹下的 b 进行配置,排序时位于下标3的位置或最后
c/b2: { useArticleTitle: false }, // 关闭使用文章一级标题作为文章名称
d: { collapsed: true }, // 文件夹折叠配置
},
compareFn: (a, b) => {
// 按最新提交时间(没有提交记录时为本地文件修改时间)升序排列
return (b.options.lastCommitTime || b.options.modifyTime) - (a.options.lastCommitTime || a.options.modifyTime)
},
useArticleTitle: true // 全局开启使用文章一级标题作为文章名称
}),
],
},
});
自动导航和侧边栏(工具)
参考自Notes 的VitePress 自动生成导航和侧边栏
优点:配置根目录后能自动生成导航和侧边栏
弊端:无法自定义导航栏名称
/* .vitepress/navSidebarUtil.ts */
import { resolve, join, sep } from 'path'
import { readdirSync, statSync } from 'fs'
import { DefaultTheme } from 'vitepress'
interface SidebarGenerateConfig {
/**
* 需要遍历的目录. 默认:articles
*/
dirName?: string
/**
* 忽略的文件名. 默认: index.md
*/
ignoreFileName?: string
/**
* 忽略的文件夹名称. 默认: ['demo','asserts']
*/
ignoreDirNames?: string[]
}
interface SideBarItem {
text: string
collapsible?: boolean
collapsed?: boolean
items?: SideBarItem[]
link?: string
}
interface NavGenerateConfig {
/**
* 需要遍历的目录. 默认:articles
*/
dirName?: string
/**
* 最大遍历层级. 默认:1
*/
maxLevel?: number
}
/**
* 判断是否为markdown文件
* @param fileName 文件名
* @returns 有返回值则表示是markdown文件,否则不是
*/
function isMarkdownFile(fileName: string) {
return !!fileName.match(/.+\.md$/)
}
// 获取docs目录的完整名称(从根目录一直到docs目录)
const docsDirFullPath = join(__dirname, '../')
// 获取docs目录的完整长度
const docsDirFullPathLen = docsDirFullPath.length
/**
* 获取dirOrFileFullName中第一个/docs/后的所有内容
* 如:
* /a-root/docs/test 则 获取到 /test
* /a-root-docs/docs/test 则 获取到 /test
* /a-root-docs/docs/docs/test 则 获取到 /docs/test
* @param dirOrFileFullName 文件或者目录名
* @returns
*/
function getDocsDirNameAfterStr(dirOrFileFullName: string) {
// 使用docsDirFullPathLen采用字符串截取的方式,避免多层目录都叫docs的问题
return `${sep}${dirOrFileFullName.substring(docsDirFullPathLen)}`
}
export function getSidebarData(sidebarGenerateConfig: SidebarGenerateConfig = {}) {
const {
dirName = 'articles',
ignoreFileName = 'index.md',
ignoreDirNames = ['demo', 'asserts'],
} = sidebarGenerateConfig
// 获取目录的绝对路径
const dirFullPath = resolve(__dirname, `../${dirName}`)
const allDirAndFileNameArr = readdirSync(dirFullPath)
const obj = {}
allDirAndFileNameArr.map(dirName => {
let subDirFullName = join(dirFullPath, dirName)
const property = getDocsDirNameAfterStr(subDirFullName).replace(/\\/g, '/') + '/'
const arr = getSideBarItemTreeData(subDirFullName, 1, 3, ignoreFileName, ignoreDirNames)
obj[property] = arr
})
return obj
}
function getSideBarItemTreeData(
dirFullPath: string,
level: number,
maxLevel: number,
ignoreFileName: string,
ignoreDirNames: string[]
): SideBarItem[] {
// 获取所有文件名和目录名
const allDirAndFileNameArr = readdirSync(dirFullPath)
const result: SideBarItem[] = []
allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
const fileOrDirFullPath = join(dirFullPath, fileOrDirName)
const stats = statSync(fileOrDirFullPath)
if (stats.isDirectory()) {
if (!ignoreDirNames.includes(fileOrDirName)) {
const text = fileOrDirName.match(/^[0-9]{2}-.+/) ? fileOrDirName.substring(3) : fileOrDirName
// 当前为文件夹
const dirData: SideBarItem = {
text,
collapsed: false,
}
if (level !== maxLevel) {
dirData.items = getSideBarItemTreeData(fileOrDirFullPath, level + 1, maxLevel, ignoreFileName, ignoreDirNames)
}
if (dirData.items) {
dirData.collapsible = true
}
result.push(dirData)
}
} else if (isMarkdownFile(fileOrDirName) && ignoreFileName !== fileOrDirName) {
// 当前为文件
const matchResult = fileOrDirName.match(/(.+)\.md/)
let text = matchResult ? matchResult[1] : fileOrDirName
text = text.match(/^[0-9]{2}-.+/) ? text.substring(3) : text
const fileData: SideBarItem = {
text,
link: getDocsDirNameAfterStr(fileOrDirFullPath).replace('.md', '').replace(/\\/g, '/'),
}
result.push(fileData)
}
})
return result
}
export function getNavData(navGenerateConfig: NavGenerateConfig = {}) {
const { dirName = 'articles', maxLevel = 2 } = navGenerateConfig
const dirFullPath = resolve(__dirname, `../${dirName}`)
const result = getNavDataArr(dirFullPath, 1, maxLevel)
return result
}
/**
* 获取顶部导航数据
*
* @param {string} dirFullPath 当前需要遍历的目录绝对路径
* @param {number} level 当前层级
* @param {number[]} maxLevel 允许遍历的最大层级
* @return {NavItem[]} 导航数据数组
*/
function getNavDataArr(dirFullPath: string, level: number, maxLevel: number): DefaultTheme.NavItem[] {
// 获取所有文件名和目录名
const allDirAndFileNameArr = readdirSync(dirFullPath)
const result: DefaultTheme.NavItem[] = []
allDirAndFileNameArr.map((fileOrDirName: string, idx: number) => {
const fileOrDirFullPath = join(dirFullPath, fileOrDirName)
const stats = statSync(fileOrDirFullPath)
const link = getDocsDirNameAfterStr(fileOrDirFullPath).replace('.md', '').replace(/\\/g, '/')
const text = fileOrDirName.match(/^[0-9]{2}-.+/) ? fileOrDirName.substring(3) : fileOrDirName
if (stats.isDirectory()) {
// 当前为文件夹
const dirData: any = {
text,
link: `${link}/`,
}
if (level !== maxLevel) {
const arr = getNavDataArr(fileOrDirFullPath, level + 1, maxLevel).filter(v => v.text !== 'index.md')
if (arr.length > 0) {
// @ts-ignore
dirData.items = arr
delete dirData.link
}
}
dirData.activeMatch = link + '/'
result.push(dirData)
} else if (isMarkdownFile(fileOrDirName)) {
// 当前为文件
const fileData: DefaultTheme.NavItem = {
text,
link,
}
fileData.activeMatch = link + '/'
result.push(fileData)
}
})
return result
}
引入到配置中
/* .vitepress/config.ts */
import { defineConfig } from 'vitepress'
import { getSidebarData, getNavData } from './navSidebarUtil'
export default defineConfig({
// ...
themeConfig: {
nav: getNavData(),
sidebar: getSidebarData(),
},
})
其他侧边栏工具
https://blog.csdn.net/weixin_46463785/article/details/128592038
https://docs.zhengxinonly.com (VitePress 系列教程:自动生成侧边栏 #7)
https://juejin.cn/post/7227358177489961018
代码组图标
使用的是 @yuyinws/vitepress-plugin-group-icons
参照教程安装:https://vpgi.vercel.app/
npm install vitepress-plugin-group-icons
然后在 config.mts
中配置
groupIconMdPlugin
报错?
请备份配置及文件后,重新安装VitePress
// .vitepress/config.mts
import { defineConfig } from 'vitepress'
import { groupIconMdPlugin, groupIconVitePlugin } from 'vitepress-plugin-group-icons'
export default defineConfig({
markdown: {
config(md) {
md.use(groupIconMdPlugin) // 代码组图标
},
},
vite: {
plugins: [
groupIconVitePlugin() // 代码组图标
],
},
})
最后还需要再 index.ts
中引入样式
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import 'virtual:group-icons.css' // 代码组样式
export default {
extends: DefaultTheme,
}
使用时,请确保代码后有对应的文字触发
::: code-group
```sh [pnpm]
pnpm -v
```
```sh [yarn]
yarn -v
```
```sh [bun]
bun -v
```
:::
已经内置的常用图标有
export const builtInIcons: Record<string, string> = {
// package manager
pnpm: 'logos:pnpm',
npm: 'logos:npm-icon',
yarn: 'logos:yarn',
bun: 'logos:bun',
// framework
vue: 'logos:vue',
svelte: 'logos:svelte-icon',
angular: 'logos:angular-icon',
react: 'logos:react',
next: 'logos:nextjs-icon',
nuxt: 'logos:nuxt-icon',
solid: 'logos:solidjs-icon',
// bundler
rollup: 'logos:rollupjs',
webpack: 'logos:webpack',
vite: 'logos:vitejs',
esbuild: 'logos:esbuild',
}
那么如何自定义呢,我们先在 iconify 中找到中意的图标
说明
- 本地图标格式:只能使用相对路径
- 远程图标格式:必须是
logos:***
图标名复制后,可以在 config.mts
中配置
// .vitepress/config.mts
import { defineConfig } from 'vitepress'
import { groupIconMdPlugin, groupIconVitePlugin, localIconLoader } from 'vitepress-plugin-group-icons'
export default defineConfig({
markdown: {
config(md) {
md.use(groupIconMdPlugin) //代码组图标
},
},
vite: {
plugins: [
groupIconVitePlugin({
customIcon: {
ts: localIconLoader(import.meta.url, '../public/svg/typescript.svg'), //本地ts图标导入
js: 'logos:javascript', //js图标
md: 'logos:markdown', //markdown图标
css: 'logos:css-3', //css图标
},
})
],
},
})
禁用F12
使用的是 @cellinlab/vitepress-protect-plugin
npm install vitepress-protect-plugin
然后在 config.mts
中配置,不用的功能不配置即可
import { defineConfig } from "vitepress"
import vitepressProtectPlugin from "vitepress-protect-plugin"
export default defineConfig({
// other VitePress configs...
vite: {
plugins: [
vitepressProtectPlugin({
disableF12: true, // 禁用F12开发者模式
disableCopy: true, // 禁用文本复制
disableSelect: true, // 禁用文本选择
}),
],
},
})
切换路由进度条
当你切换页面,顶部会显示进度条,使用的是 @Skyleen77/nprogress-v2
先安装 nprogress-v2
npm install nprogress-v2
然后再 index.ts
中配置,即可生效
// .vitepress/theme/index.ts
import { NProgress } from 'nprogress-v2/dist/index.js' // 进度条组件
import 'nprogress-v2/dist/index.css' // 进度条样式
if (inBrowser) {
NProgress.configure({ showSpinner: false })
router.onBeforeRouteChange = () => {
NProgress.start() // 开始进度条
}
router.onAfterRouteChanged = () => {
busuanzi.fetch()
NProgress.done() // 停止进度条
}
}
评论
各种评论系统对比
评论系统 | 说明 |
---|---|
Valine | 不用登录账号即可评论,但容易产生垃圾评论,其次没有评论提醒通知 |
Waline | 是Valine的升级版,登录后方可评论,有通知,但是需要自己部署服务端 |
Twikoo | 不用登录账号即可评论,但容易产生垃圾评论,有通知,但是需要自己部署服务端 |
Artalk | 可设置是否启用登录账号后评论,有通知,但是需要自己部署服务端 |
utteranc | GitHub登录后方可评论,评论数据在 GitHub issues 中,评论后有邮件通知,无需部署服务端,但UI样式一般,且移动端不显示头像 |
gitalk | GitHub登录后方可评论,评论数据在 GitHub issues 中,评论后有邮件通知,无需部署服务端,UI样式一般,评论不支持点赞 |
⭐Giscus | GitHub登录后方可评论,评论数据在 GitHub Discussions 中,评论后有邮件通知,无需部署服务端,UI爱了 |
之前在 vuepress 中用过 Valine,效果并不是特别好
从上面对比来看,Giscus 最佳,因此决定使用 Giscus
关于 @xinlei3166 的 waline 插件
在使用看板娘发时候就已经装好了,直接引用就行了
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme'
import { useWaline } from 'vitepress-theme-website'
export default {
extends: DefaultTheme,
setup() {
useWaline({
serverURL: 'https://you_url.com'
}),
}
}
记得 serverURL
换成自己的即可,但是作者的插件有个bug,暗黑模式下看不清字
安装giscus
Giscus 是一个基于 GitHub Discussion 的评论系统,启用简便
进 Giscus App官网:https://github.com/apps/giscus
点击 Install
安装
选择 Only select repositories
,再指定一个你想开启讨论的仓库
注意
仓库必须是公开的,私有的不行
想单独放评论,新建一个也可
安装完成后可以在个人头像-设置-应用 Applications
中看到
开启讨论
因为giscus会把评论数据都放到讨论 discussions
中
我们进入要开启讨论的仓库,点设置 - 勾选讨论 Settings - discussions
生成数据
输入自己的仓库链接,满足条件会提示可用
下拉到 Discussion 分类推荐选 General
,懒加载评论也可以勾选下
关于讨论的类型分类如下,来自于 Github的讨论文档
类别 | 中文 | 说明 |
---|---|---|
Announcements | 公告 | 每次评论都会推送所有人 |
General | 常规 | 开放式讨论 |
Ideas | 想法 | 开放式讨论 |
Polls | 投票 | 可投票与讨论 |
Q&A | 问答 | 问答形式 |
Show and tell | 展示和说明 | 开放式讨论 |
下方就自动生成了你的关键数据
<script src="https://giscus.app/client.js"
data-repo="github repository"
data-repo-id="R_******"
data-category="General"
data-category-id="DIC_******"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="zh-CN"
data-loading="lazy"
crossorigin="anonymous"
async>
</script>
其中 data-repo
、 data-repo-id
、 data-category
和 data-category-id
这4个是我们的关键数据
安装使用
有能力的可以用官方给的js数据封装
我这里用 @T-miracle/vitepress-plugin-comment-with-giscus 的插件
npm install vitepress-plugin-comment-with-giscus
在 .vitepress/theme/index.ts
中填入下面代码
并将我们之前获取的4个关键数据填入,其他保持默认保存
// .vitepress/theme/index.ts
import DefaultTheme from 'vitepress/theme';
import giscusTalk from 'vitepress-plugin-comment-with-giscus';
import { useData, useRoute } from 'vitepress';
export default {
extends: DefaultTheme,
setup() {
// Get frontmatter and route
const { frontmatter } = useData();
const route = useRoute();
// giscus配置
giscusTalk({
repo: 'your github repository', //仓库
repoId: 'your repository id', //仓库ID
category: 'Announcements', // 讨论分类
categoryId: 'your category id', //讨论分类ID
mapping: 'pathname',
inputPosition: 'bottom',
lang: 'zh-CN',
},
{
frontmatter, route
},
//默认值为true,表示已启用,此参数可以忽略;
//如果为false,则表示未启用
//您可以使用“comment:true”序言在页面上单独启用它
true
);
}
安装完看下底部的效果吧
如果某一页不想启用,可以在当前页使用 Frontmatter
关闭
---
comment: false
---