Merge pull request #163 from pengzhanbo/perf-blog
feat(theme): add support blog as home page
This commit is contained in:
commit
bc527fdd4d
@ -35,7 +35,7 @@
|
||||
"cac": "^6.7.14",
|
||||
"execa": "^9.3.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"picocolors": "^1.0.1"
|
||||
"picocolors": "^1.1.0"
|
||||
},
|
||||
"theme-plume": {
|
||||
"vuepress": "2.0.0-rc.15"
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import process from 'node:process'
|
||||
import path from 'node:path'
|
||||
import { createRequire } from 'node:module'
|
||||
import { cancel, confirm, group, select, text } from '@clack/prompts'
|
||||
import { setLang, t } from './translate.js'
|
||||
@ -132,34 +131,3 @@ export async function prompt(mode: Mode, root?: string): Promise<PromptResult> {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export async function getTargetDir(cwd: string, dir?: string) {
|
||||
if (dir === '.')
|
||||
return cwd
|
||||
|
||||
if (typeof dir === 'string' && dir) {
|
||||
return path.resolve(cwd, dir)
|
||||
}
|
||||
|
||||
const DEFAULT_DIR = 'my-project'
|
||||
|
||||
const dirPath = await text({
|
||||
message: t('question.root'),
|
||||
placeholder: DEFAULT_DIR,
|
||||
validate(value) {
|
||||
if (value && REG_DIR_CHAR.test(value))
|
||||
return t('hint.root.illegal')
|
||||
|
||||
return undefined
|
||||
},
|
||||
defaultValue: DEFAULT_DIR,
|
||||
})
|
||||
|
||||
if (typeof dirPath === 'string') {
|
||||
if (dirPath === '.')
|
||||
return cwd
|
||||
|
||||
return path.join(cwd, dirPath || DEFAULT_DIR)
|
||||
}
|
||||
return dirPath
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
---
|
||||
pageLayout: home
|
||||
externalLinkIcon: false
|
||||
config:
|
||||
-
|
||||
type: hero
|
||||
|
||||
@ -25,41 +25,47 @@ VuePress 是一个 [静态站点生成器](https://en.wikipedia.org/wiki/Static_
|
||||
|
||||
与 vuepress 默认主题相比:
|
||||
|
||||
- **更好的用户体验**
|
||||
### 更好的用户体验
|
||||
|
||||
大幅度优化了界面、交互,更为美观、简洁,易用。
|
||||
大幅度优化了界面、交互,更为美观、简洁,易用。
|
||||
|
||||
- **更多的功能**
|
||||
### 更多的功能
|
||||
|
||||
代码分组、提示容器、任务列表、数学公式、代码演示、内容搜索、文章评论、加密、文件树 等。
|
||||
- 灵活的,可完全自定义的 ==首页== ;
|
||||
|
||||
- **更好的开发体验**
|
||||
- 可选的 ==博客== 、==文档== 、==知识笔记== ;
|
||||
|
||||
增加编译缓存,缓存 markdown 文件编译、缓存 复杂 代码块 内容解析结果。
|
||||
- 内置支持 ==全文搜索== 、==文章评论== 、==内容加密== 、 ==文章水印== 等;
|
||||
|
||||
- **更好的配置**
|
||||
- 可对 ==代码块== 进行 分组、折叠、聚焦、行高亮、差异对比 等,还可以 嵌入 CodePen、JSFiddle、CodeSandbox 等作为 ==代码演示==;
|
||||
|
||||
支持使用单独的主题配置文件,避免修改配置导致频繁重启 VuePress 服务。
|
||||
- 内置支持 [iconify](https://icon-sets.iconify.design/) **200k+** ==图标==;
|
||||
|
||||
大幅度简化了配置,更易于使用,同时还保留了丰富灵活的配置项,满足个性化的需求。
|
||||
- 支持 ==嵌入 PDF==、==嵌入 Bilibili/Youtube 视频==;
|
||||
|
||||
- 支持 chart.js、Echarts、Mermaid、flowchart 等多种可选的 ==图表== ;
|
||||
|
||||
- 灵活的 markdown 容器语法,支持 ==提示容器==、==文件树==、==示例容器== 等;
|
||||
|
||||
- 支持 ==布局插槽==、==组件覆写== ==自定义样式== 等,你可以灵活地扩展组件,实现 个性化的 布局。
|
||||
|
||||
更多的功能等待你的发现 !
|
||||
|
||||
### 更好的开发体验
|
||||
|
||||
增加编译缓存,缓存 markdown 文件编译、缓存 复杂 代码块 内容解析结果。
|
||||
|
||||
### 更好的配置
|
||||
|
||||
支持使用单独的主题配置文件,避免修改配置导致频繁重启 VuePress 服务。
|
||||
|
||||
大幅度简化了配置,更易于使用,同时还保留了丰富灵活的配置项,满足个性化的需求。
|
||||
|
||||
### 更多
|
||||
|
||||
==plume 主题== 尽可能的内置你可能需要的功能,以及搭建站点所需要的一般性配置,您无需关注这些细节。
|
||||
目的是,让您更专注于 内容的创作,更好的表达你的想法,享受 Markdown 增强语法带来的便利。
|
||||
|
||||
## 功能
|
||||
|
||||
- 💻 响应式布局,适配不同的屏幕尺寸
|
||||
- 📖 博客 & 文档
|
||||
- 🔗 自动生成文章永久链接
|
||||
- ⚖ 支持多语言
|
||||
- 🔑 支持 全站加密、部分加密
|
||||
- 👀 支持 搜索、文章评论
|
||||
- 👨💻 支持 浅色/深色 主题 (包括代码高亮)
|
||||
- 📠 markdown 增强,支持 代码块分组、提示容器、任务列表、数学公式、代码演示、文件树 等
|
||||
- 📚 代码演示,支持 CodePen, Replit, JSFiddle, CodeSandbox
|
||||
- 📊 嵌入图标,支持 chart.js,Echarts,Mermaid,flowchart 等
|
||||
- 🎛 资源嵌入,支持 PDF, bilibili视频,youtube视频等
|
||||
|
||||
::: tip
|
||||
|
||||
本主题 基于 [vuepress-next](https://github.com/vuepress/vuepress-next), 目前处于 RC 阶段。
|
||||
|
||||
@ -17,10 +17,12 @@ import VPBlogProfile from 'vuepress-theme-plume/components/Blog/VPBlogProfile.vu
|
||||
|
||||
主题默认启用 博客功能,通常您无需进行额外的配置。
|
||||
|
||||
主题默认会将 `{sourceDir}` 目录下的,除了一些特定的目录(如 `notes` 目录将作为笔记所在目录),
|
||||
主题默认会将 `{sourceDir}` 目录下的,除了特定的目录(如 `notes` 目录将作为笔记所在目录),
|
||||
所有 md 文件作为博客文章。
|
||||
|
||||
同时,主题会生成一个 链接地址为 `/blog/` 的 博客文章列表页。
|
||||
主题还会根据 md 文件 所在的 文件目录结构,以 **目录名** 作为 博客文章所属的 **分类**。
|
||||
|
||||
主题默认会生成一个 链接地址为 `/blog/` 的 博客文章列表页。
|
||||
展示所有的博客文章,以及 博主的相关信息。
|
||||
|
||||
## 国际化支持
|
||||
@ -112,8 +114,92 @@ title: 标题
|
||||
|
||||
主题除了自动生成 **博客文章列表页** 以外,还会自动生成 **标签页**,**分类页** 和 **归档页**。
|
||||
|
||||
标签页 可以根据 标签 筛选并展示 博客文章。
|
||||
标签页 可以根据 标签 筛选并展示 博客文章, 默认地址为 `/blog/tags/`
|
||||
|
||||
分类页 可以根据 原始目录结构 分类展示 博客文章。
|
||||
分类页 可以根据 原始目录结构 分类展示 博客文章, 默认地址为 `/blog/categories/`
|
||||
|
||||
归档页根据文章的创建时间进行归档。
|
||||
归档页根据文章的创建时间进行归, 默认地址为 `/blog/archives/`
|
||||
|
||||
## 设置博客为主页
|
||||
|
||||
默认情况下,主题 将 **首页** 与 **博客页** 分离。
|
||||
|
||||
但对于 希望只建立一个 博客站点 的用户而言,可能直接将 博客页 作为 **首页** 是个更好的选择。
|
||||
|
||||
主题提供了两种方式来设置博客为主页,满足不同的需求场景:
|
||||
|
||||
- **方式一:配置 主页的 `pageLayout` 属性为 `blog`**
|
||||
|
||||
::: code-tabs
|
||||
@tab docs/README.md
|
||||
|
||||
```md
|
||||
---
|
||||
pageLayout: blog
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
此配置会直接将页面应用 博客布局,显示博客文章列表。
|
||||
|
||||
这是将主页修改为 博客页的 最简单的方式,但缺点是 缺少灵活性。
|
||||
|
||||
- **方式二:配置 主页的 `pageLayout` 属性为 `home`, 添加 `type: blog` 的首页区域类型**
|
||||
|
||||
::: code-tabs
|
||||
|
||||
@tab docs/README.md
|
||||
|
||||
```md
|
||||
---
|
||||
pageLayout: home
|
||||
config:
|
||||
- type: blog
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
使用这种方式,你不仅可以在首页中添加 博客文章列表,还可以灵活的在页面的其他区域添加不同的内容。
|
||||
|
||||
比如,配置首屏为 `banner`,然后紧跟着 博客文章列表:
|
||||
|
||||
::: code-tabs
|
||||
|
||||
@tab docs/README.md
|
||||
|
||||
```md
|
||||
---
|
||||
pageLayout: home
|
||||
config:
|
||||
- type: banner
|
||||
- type: blog
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
更多自定义配置,请参考 [自定义首页](./自定义首页.md)。
|
||||
|
||||
当使用以上两种方式 将首页配置为 博客页后,由于主题默认依然会生成 地址为`/blog/` 的博客文章列表页,
|
||||
这导致存在了重复功能的页面,为此,你需要 [主题配置 > 博客配置](../config/主题配置.md#blog) 中,
|
||||
**关闭自动生成博客文章列表页**:
|
||||
|
||||
(还可以在重新修改 分类页/标签页/归档页的链接地址)
|
||||
|
||||
```ts
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
blog: {
|
||||
postList: false, // 禁止生成博客文章列表页
|
||||
// tagsLink: '/blog/tags/',
|
||||
// categoriesLink: '/blog/categories/',
|
||||
// archiveLink: '/blog/archives/',
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
@ -270,23 +270,26 @@ cd open-source # 进入 D: 分区下的 open-source 目录
|
||||
- docs \# 文档源目录
|
||||
- .vuepress
|
||||
- public/ \# 静态资源目录
|
||||
- client.ts
|
||||
- config.ts \# vuepress 配值文件
|
||||
- client.ts \# 客户端配置
|
||||
- config.ts \# vuepress 配值
|
||||
- navbar.ts \# 导航栏配置
|
||||
- notes.ts \# notes 配置
|
||||
- plume.config.ts \# 主题配置文件
|
||||
- notes
|
||||
- plume.config.ts \# 主题配置
|
||||
- notes \# 系列文档、知识笔记
|
||||
- demo
|
||||
- foo.md
|
||||
- bar.md
|
||||
- preview
|
||||
- preview \# 博客分类
|
||||
- markdown.md
|
||||
- README.md \# 首页
|
||||
- package.json
|
||||
- package-lock.json
|
||||
- pnpm-lock.yaml
|
||||
- .gitignore
|
||||
- README.md
|
||||
:::
|
||||
|
||||
在 `docs` 目录中, 除 `.vuepress` 目录外,目录中的 所有 markdown 文件都会被识别为文档。
|
||||
其中,除 `notes` 目录外的 `markdown` 文件会被识别为 博客文章,而 `notes` 目录下 `markdown` 文件会被识别为 文档笔记。
|
||||
|
||||
- 除 `notes` 目录外的 `markdown` 文件会被识别为 博客文章,并根据其所在的目录结构,作为 文章分类。
|
||||
|
||||
- `notes` 目录下 `markdown` 文件会被识别为 文档笔记。
|
||||
|
||||
@ -11,52 +11,55 @@ tags:
|
||||
|
||||
## 概述
|
||||
|
||||
在本主题满足了 Blog 的基本功能后,期望能够 以 note 或者 book 的形式聚合文章,形式上类似于 vuepress 默认主题。
|
||||
同时也减少配置的复杂度。
|
||||
主题提供了 `笔记` 的功能,它用于聚合 同一个系列的文章、或者作为站点的 **子文档** 。
|
||||
|
||||
它能够让你以更加友好的方式,组织管理你的文档。
|
||||
`笔记` 以 文件结构 作为划分依据,默认以 `notes/` 作为根目录,
|
||||
存放在 `notes` 目录下的 文档不会作为 博客文章,不会出现在 博客文章列表页中。
|
||||
|
||||
## 目录
|
||||
## 文件结构与配置
|
||||
|
||||
文档/知识笔记 默认存放在 `{sourceDir}/notes` 目录下。
|
||||
|
||||
示例:
|
||||
我们有一个项目中,有以下文件结构:
|
||||
|
||||
::: file-tree
|
||||
|
||||
- \{sourceDir\}
|
||||
- docs
|
||||
- notes
|
||||
- typescript
|
||||
- foo.md
|
||||
- rust
|
||||
- foo.md
|
||||
- typescript \# typescript 笔记
|
||||
- basic.md
|
||||
- types.md
|
||||
- rust \# rust 笔记
|
||||
- tuple.md
|
||||
- struct.md
|
||||
- blog-post.md \# 博客文章
|
||||
- README.md \# 站点首页
|
||||
|
||||
:::
|
||||
|
||||
其中,`typescript` 和 `rust` 为目录名,各自独立保存与之相关的 markdown 文件。
|
||||
在 `docs/notes` 目录下,有两个子目录,分别用于存放 `typescript` 和 `rust` 的系列内容。
|
||||
|
||||
## 配置
|
||||
接下来,在配置文件中配置 `notes`:
|
||||
|
||||
```js
|
||||
```js :collapsed-lines=20
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { plumeTheme } from 'vuepress-theme-plume'
|
||||
|
||||
export default defineUserConfig({
|
||||
theme: plumeTheme({
|
||||
notes: {
|
||||
dir: '/notes/', // 声明所有笔记的目录
|
||||
link: '/', // 声明所有笔记默认的链接前缀, 默认为 '/'
|
||||
// 声明所有笔记的目录,(默认配置,通常您不需要声明它)
|
||||
dir: '/notes/',
|
||||
link: '/', // 声明所有笔记默认的链接前缀, 默认为 '/' (默认配置,通常您不需要声明它)
|
||||
notes: [
|
||||
// 每个笔记都是 `notes` 数组中的一个对象
|
||||
{
|
||||
dir: 'typescript', // 声明笔记的目录,相对于 `notes.dir`
|
||||
link: '/typescript/', // 声明笔记的链接前缀
|
||||
sidebar: [ // 配置侧边栏
|
||||
{
|
||||
text: '简介',
|
||||
icon: 'mdi:language-typescript', // 侧边栏图标
|
||||
items: ['foo'] // 简化写法,主题会自动补全为 `foo.md`
|
||||
}
|
||||
]
|
||||
// 声明笔记的目录,相对于 `notes.dir`,这里表示 `notes/typescript` 目录
|
||||
dir: 'typescript',
|
||||
// 声明笔记的链接前缀,与 `notes.link` 拼接,这里表示 `/typescript/`
|
||||
// 笔记内的所有文章会以 `/typescript/` 作为访问链接前缀。
|
||||
link: '/typescript/',
|
||||
// 配置 笔记侧边导航栏,用于导航向笔记内的所有文档
|
||||
// 声明为 `auto` 的,将根据目录结构自动生成侧边栏导航
|
||||
sidebar: 'auto'
|
||||
},
|
||||
{
|
||||
dir: 'rust',
|
||||
@ -71,13 +74,241 @@ export default defineUserConfig({
|
||||
})
|
||||
```
|
||||
|
||||
主题会根据配置,为对应目录中的 md 文件,生成 永久链接,以及侧边栏。
|
||||
|
||||
::: tip
|
||||
|
||||
你应该在创建文件之前,先把笔记的目录和链接前缀等配置好,主题需要根据配置,
|
||||
为目录中的 md 文件生成永久链接,以及侧边栏。
|
||||
你应该在创建文件之前,建议先把笔记的目录和链接前缀等配置好。
|
||||
主题默认启用了 [auto-frontmatter](../config/主题配置.md#autofrontmatter),
|
||||
需要根据配置,为目录中的 md 文件生成永久链接,以及侧边栏。
|
||||
|
||||
:::
|
||||
|
||||
完整配置查看 [notes配置](../config/notes配置.md)
|
||||
### 侧边栏配置
|
||||
|
||||
以 `typescript` 目录为例,它拥有如下的文件结构:
|
||||
|
||||
::: file-tree
|
||||
|
||||
- typescript
|
||||
- guide
|
||||
- intro.md
|
||||
- getting-start.md
|
||||
- config
|
||||
- config-file.md
|
||||
- configuration.md
|
||||
- reference
|
||||
- basic.md
|
||||
- syntax.md
|
||||
- modules.md
|
||||
- built-in
|
||||
- types
|
||||
- Required.md
|
||||
- Omit.md
|
||||
- README.md
|
||||
|
||||
:::
|
||||
|
||||
#### 自动生成侧边栏
|
||||
|
||||
一种最简单的配置方式是 `sidebar: 'auto'` , 主题会自动根据 文件结构生成侧边栏,并根据 首个字符的编码 来排序。
|
||||
|
||||
如果想要修改 自动生成的侧边栏的顺序,可以直接在 目录名 或 文件名之前,添加 `1.` 或 `2.` 等前缀。
|
||||
|
||||
::: file-tree
|
||||
|
||||
- typescript
|
||||
- 1.guide
|
||||
- 1.intro.md
|
||||
- 2.getting-start.md
|
||||
- 2.config
|
||||
- 1.config-file.md
|
||||
- 2.configuration.md
|
||||
- …
|
||||
:::
|
||||
|
||||
主题将根据 这部分的前缀的 数字 进行排序,前缀部分不会显示在侧边栏中。
|
||||
|
||||
#### 自定义侧边栏
|
||||
|
||||
有时候自动生成侧边栏 不能完全满足需求,你可以自定义侧边栏。
|
||||
|
||||
以下是 侧边栏的 类型定义:
|
||||
|
||||
```ts
|
||||
type Sidebar = (string | SidebarItem)[]
|
||||
|
||||
interface SidebarItem {
|
||||
/**
|
||||
* 侧边栏文本
|
||||
*/
|
||||
text?: string
|
||||
|
||||
/**
|
||||
* 侧边栏链接
|
||||
*/
|
||||
link?: string
|
||||
|
||||
/**
|
||||
* 侧边栏图标
|
||||
*/
|
||||
icon?: ThemeIcon
|
||||
|
||||
/**
|
||||
* 当前分组的链接前缀,链接前缀会拼接在 `items` 内的 `link` 之前
|
||||
* 当且仅当 `items` 内的 `link` 为 相对路径时,才会拼接
|
||||
*/
|
||||
prefix?: string
|
||||
/**
|
||||
* 次级侧边栏分组
|
||||
*/
|
||||
items?: 'auto' | (string | SidebarItem)[]
|
||||
|
||||
/**
|
||||
* 如果未指定,组不可折叠。
|
||||
* 如果为`true`,组可折叠,并默认折叠。
|
||||
* 如果为`false`,组可折叠,但默认展开。
|
||||
*/
|
||||
collapsed?: boolean
|
||||
}
|
||||
```
|
||||
|
||||
当 传入类型为 `string` 时,表示 markdown 文件的路径:
|
||||
|
||||
```ts
|
||||
const sidebar: Sidebar = [
|
||||
'/guide/intro.md',
|
||||
'/guide/getting-start.md',
|
||||
'/config/config-file.md',
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
你也可以省略 `.md` 文件后缀,简写为 `/guide/intro` 。主题会解析 对应的文件,获取 **标题** 和 **页面链接地址**
|
||||
并将其转换为 `{ text: string, link: string }` 的数据形式。
|
||||
|
||||
当传入类型为 `SidebarItem` 时:
|
||||
|
||||
```ts
|
||||
const sidebar: Sidebar = [
|
||||
{ text: '介绍', link: '/guide/intro' },
|
||||
{ text: '快速上手', link: '/guide/getting-start' },
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
也可以进行多层嵌套:
|
||||
|
||||
```ts
|
||||
const sidebar: Sidebar = [
|
||||
{
|
||||
text: '指南',
|
||||
prefix: '/guide', // 使用 prefix 拼接,可以简写 下面的 items 中的 link 为相对路径
|
||||
items: [
|
||||
// 可以混用 string 和 SidebarItem
|
||||
{ text: '介绍', link: 'intro' },
|
||||
'getting-start',
|
||||
],
|
||||
},
|
||||
{
|
||||
text: '配置',
|
||||
prefix: '/config',
|
||||
items: 'auto', // items 为 'auto',会根据 prefix 的文件结构自动生成侧边栏
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
:::warning
|
||||
不建议 侧边栏的层级过深,超过 3 层的侧边栏 可能会导致 糟糕的 UI 效果。
|
||||
:::
|
||||
|
||||
### 侧边栏图标
|
||||
|
||||
为侧边栏添加 图标 有助于 侧边栏更好的呈现。得益于 [iconify](https://iconify.design/) 这个强大的开源图标库,
|
||||
你可以使用超过 `200k` 的图标,仅需要添加 `icon` 配置即可。
|
||||
|
||||
```ts
|
||||
const sidebar: Sidebar = [
|
||||
{
|
||||
text: '指南',
|
||||
prefix: '/guide',
|
||||
icon: 'ep:guide', // iconify icon name // [!code hl]
|
||||
items: [
|
||||
{ text: '介绍', link: 'intro', icon: 'ph:info-light' }, // [!code hl]
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
也可以使用本地图标,或者本地图片:
|
||||
|
||||
```ts
|
||||
const sidebar: Sidebar = [
|
||||
{
|
||||
text: '指南',
|
||||
prefix: '/guide',
|
||||
icon: '/images/guide.png', // iconify icon name // [!code hl]
|
||||
items: [
|
||||
{ text: '介绍', link: 'intro', icon: '/images/info.png' }, // [!code hl]
|
||||
// 也可以是一个远程图片
|
||||
{ text: '快速上手', link: 'getting-start', icon: 'https://cn.vuejs.org/images/logo.png' },
|
||||
],
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**请注意,使用本地图片必须以 `/` 开头,表示为 静态资源路径,它将从 `.vuepress/public/` 目录中加载。**
|
||||
|
||||
::: file-tree
|
||||
|
||||
- docs
|
||||
- .vuepress
|
||||
- public \# 在这个位置保存静态资源
|
||||
- images
|
||||
- guide.png
|
||||
- info.png
|
||||
- …
|
||||
:::
|
||||
|
||||
你可能已经注意到,`sidebar: auto` 时,该如何配置 侧边栏图标,事实上很简单,直接在 文件的 `frontmatter` 部分,
|
||||
添加 一个 `icon` 字段即可。
|
||||
|
||||
::: code-tabs
|
||||
@tab typescript/guide/intro.md
|
||||
|
||||
```md
|
||||
---
|
||||
title: 介绍
|
||||
icon: ep:guide
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## 笔记首页
|
||||
|
||||
你可能注意到,在 `typescript` 目录下,有一个 `README.md` 文件,它会被作为笔记首页显示。
|
||||
|
||||
::: file-tree
|
||||
|
||||
- typescript
|
||||
- README.md
|
||||
- …
|
||||
- …
|
||||
:::
|
||||
|
||||
默认情况下,它与 普通的文档页面 没有区别,这是因为 主题 默认对 所有页面 设置了 `pageLayout: docs`。
|
||||
|
||||
但你可以直接配置 `pageLayout: 'home'`,就像配置 [站点首页](./自定义首页.md) 一样,为 笔记配置一个个性化的首页!
|
||||
|
||||
::: code-tabs
|
||||
@tab typescript/README.md
|
||||
|
||||
```md
|
||||
---
|
||||
pageLayout: home
|
||||
config:
|
||||
- type: hero
|
||||
- type: features
|
||||
---
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
@ -436,6 +436,10 @@ config:
|
||||
<img src="/images/custom-text-image.png" alt="text-image" />
|
||||
:::
|
||||
|
||||
### blog
|
||||
|
||||
将 博客文章列表页 作为一个单独区域,插入到 首页中。
|
||||
|
||||
### profile
|
||||
|
||||
- 类型: `PlumeThemeHomeProfile`
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
"echarts": "^5.5.1",
|
||||
"flowchart.ts": "^3.0.1",
|
||||
"http-server": "^14.1.1",
|
||||
"mermaid": "^11.0.2",
|
||||
"vue": "^3.4.38",
|
||||
"mermaid": "^11.1.0",
|
||||
"vue": "^3.5.0",
|
||||
"vuepress-theme-plume": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
"vuepress": "2.0.0-rc.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@ -44,10 +44,10 @@
|
||||
"@vueuse/core": "^11.0.3",
|
||||
"markdown-it-container": "^4.0.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"shiki": "^1.15.2",
|
||||
"tm-grammars": "^1.17.11",
|
||||
"shiki": "^1.16.1",
|
||||
"tm-grammars": "^1.17.14",
|
||||
"tm-themes": "^1.8.1",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2"
|
||||
|
||||
@ -67,20 +67,16 @@ function runCode() {
|
||||
</p>
|
||||
<div v-if="stderr.length" class="stderr">
|
||||
<h4>Stderr:</h4>
|
||||
<p
|
||||
<pre
|
||||
v-for="(item, index) in stderr" :key="index"
|
||||
:class="{ error: lang === 'rust' && item.startsWith('error') }"
|
||||
>
|
||||
<pre>{{ item }}</pre>
|
||||
</p>
|
||||
>{{ item }}</pre>
|
||||
</div>
|
||||
<div v-if="stdout.length" class="stdout">
|
||||
<h4 v-if="stderr.length">
|
||||
Stdout:
|
||||
</h4>
|
||||
<p v-for="(item, index) in stdout" :key="index">
|
||||
<pre>{{ item }}</pre>
|
||||
</p>
|
||||
<pre v-for="(item, index) in stdout" :key="index">{{ item }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -214,13 +210,13 @@ function runCode() {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.output-content p {
|
||||
.output-content pre {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.output-content p pre {
|
||||
.output-content pre {
|
||||
width: fit-content;
|
||||
padding: 0 20px 0 0;
|
||||
margin: 0;
|
||||
@ -228,13 +224,13 @@ function runCode() {
|
||||
}
|
||||
|
||||
.output-content .error,
|
||||
.output-content .stderr p,
|
||||
.output-content.rust .stderr p.error {
|
||||
.output-content .stderr pre,
|
||||
.output-content.rust .stderr pre.error {
|
||||
color: var(--vp-c-danger-1, #b8272c);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
|
||||
.output-content.rust .stderr p {
|
||||
.output-content.rust .stderr pre {
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
|
||||
@ -48,7 +48,7 @@
|
||||
"mark.js": "^8.11.1",
|
||||
"minisearch": "^7.1.0",
|
||||
"p-map": "^7.0.2",
|
||||
"vue": "^3.4.38"
|
||||
"vue": "^3.5.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@ -36,8 +36,8 @@
|
||||
"vuepress": "2.0.0-rc.15"
|
||||
},
|
||||
"dependencies": {
|
||||
"@shikijs/transformers": "^1.15.2",
|
||||
"@shikijs/twoslash": "^1.15.2",
|
||||
"@shikijs/transformers": "^1.16.1",
|
||||
"@shikijs/twoslash": "^1.16.1",
|
||||
"@types/hast": "^3.0.4",
|
||||
"@vuepress/helper": "2.0.0-rc.42",
|
||||
"@vueuse/core": "^11.0.3",
|
||||
@ -46,9 +46,9 @@
|
||||
"mdast-util-gfm": "^3.0.0",
|
||||
"mdast-util-to-hast": "^13.2.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"shiki": "^1.15.2",
|
||||
"twoslash": "^0.2.9",
|
||||
"twoslash-vue": "^0.2.9"
|
||||
"shiki": "^1.16.1",
|
||||
"twoslash": "^0.2.10",
|
||||
"twoslash-vue": "^0.2.10"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
728
pnpm-lock.yaml
generated
728
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -103,12 +103,12 @@
|
||||
"katex": "^0.16.11",
|
||||
"local-pkg": "^0.5.0",
|
||||
"nanoid": "^5.0.7",
|
||||
"vue": "^3.4.38",
|
||||
"vue-router": "^4.4.3",
|
||||
"vue": "^3.5.0",
|
||||
"vuepress-plugin-md-enhance": "2.0.0-rc.52",
|
||||
"vuepress-plugin-md-power": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.243"
|
||||
"@iconify/json": "^2.2.243",
|
||||
"vue-router": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,11 +9,17 @@ import VPBlogNav from '@theme/Blog/VPBlogNav.vue'
|
||||
import VPTransitionFadeSlideY from '@theme/VPTransitionFadeSlideY.vue'
|
||||
import { useData } from '../../composables/index.js'
|
||||
|
||||
defineProps<{
|
||||
homeBlog?: boolean
|
||||
type?: string
|
||||
onlyOnce?: boolean
|
||||
}>()
|
||||
|
||||
const { theme, page } = useData()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-blog">
|
||||
<div class="vp-blog" :class="{ 'home-blog': homeBlog }">
|
||||
<slot name="blog-top" />
|
||||
|
||||
<div class="blog-container" :class="{ 'no-profile': !theme.profile }">
|
||||
@ -53,7 +59,7 @@ const { theme, page } = useData()
|
||||
<slot name="blog-categories-content-before" />
|
||||
</template>
|
||||
</VPBlogCategories>
|
||||
<VPPostList v-else>
|
||||
<VPPostList v-else :home-blog="homeBlog">
|
||||
<template #blog-post-list-before>
|
||||
<slot name="blog-post-list-before" />
|
||||
</template>
|
||||
@ -96,6 +102,12 @@ const { theme, page } = useData()
|
||||
transition: background-color var(--t-color);
|
||||
}
|
||||
|
||||
@media(min-width: 419px) {
|
||||
.vp-blog.home-blog {
|
||||
background-color: var(--vp-c-bg-alt);
|
||||
}
|
||||
}
|
||||
|
||||
.blog-container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@ -1,9 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import VPTransitionDrop from '@theme/VPTransitionDrop.vue'
|
||||
import VPPostItem from '@theme/Blog/VPPostItem.vue'
|
||||
import VPPagination from '@theme/Blog/VPPagination.vue'
|
||||
import { usePostListControl } from '../../composables/index.js'
|
||||
|
||||
const props = defineProps<{
|
||||
homeBlog?: boolean
|
||||
}>()
|
||||
|
||||
const {
|
||||
postList,
|
||||
page,
|
||||
@ -13,7 +18,7 @@ const {
|
||||
isFirstPage,
|
||||
isPaginationEnabled,
|
||||
changePage,
|
||||
} = usePostListControl()
|
||||
} = usePostListControl(computed(() => !!props.homeBlog))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@ -1,13 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { type Component, computed, nextTick, onUnmounted, resolveComponent, watch } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { computed, h, nextTick, onUnmounted, resolveComponent, watch } from 'vue'
|
||||
import VPHomeBanner from '@theme/Home/VPHomeBanner.vue'
|
||||
import VPHomeHero from '@theme/Home/VPHomeHero.vue'
|
||||
import VPHomeFeatures from '@theme/Home/VPHomeFeatures.vue'
|
||||
import VPHomeTextImage from '@theme/Home/VPHomeTextImage.vue'
|
||||
import VPHomeProfile from '@theme/Home/VPHomeProfile.vue'
|
||||
import VPHomeCustom from '@theme/Home/VPHomeCustom.vue'
|
||||
import VPBlog from '@theme/Blog/VPBlog.vue'
|
||||
import { useData } from '../../composables/index.js'
|
||||
|
||||
const slots = defineSlots<{
|
||||
'blog-top': () => any
|
||||
'blog-bottom': () => any
|
||||
'blog-post-list-before': () => any
|
||||
'blog-post-list-after': () => any
|
||||
'blog-post-list-pagination-after': () => any
|
||||
}>()
|
||||
|
||||
function VPHomeBlog() {
|
||||
return h(VPBlog, { homeBlog: true }, {
|
||||
'blog-top': () => slots['blog-top']?.(),
|
||||
'blog-bottom': () => slots['blog-bottom']?.(),
|
||||
'blog-post-list-before': () => slots['blog-post-list-before']?.(),
|
||||
'blog-post-list-after': () => slots['blog-post-list-after']?.(),
|
||||
'blog-post-list-pagination-after': () => slots['blog-post-list-pagination-after']?.(),
|
||||
})
|
||||
}
|
||||
|
||||
const components: Record<string, Component<any, any, any>> = {
|
||||
'banner': VPHomeBanner,
|
||||
'hero': VPHomeHero,
|
||||
@ -15,6 +35,7 @@ const components: Record<string, Component<any, any, any>> = {
|
||||
'text-image': VPHomeTextImage,
|
||||
'image-text': VPHomeTextImage,
|
||||
'profile': VPHomeProfile,
|
||||
'blog': VPHomeBlog,
|
||||
'custom': VPHomeCustom,
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +99,23 @@ watch([isBlogLayout, () => frontmatter.value.pageLayout], () => nextTick(() =>
|
||||
|
||||
<VPFriends v-else-if="frontmatter.pageLayout === 'friends'" />
|
||||
|
||||
<VPHome v-else-if="frontmatter.pageLayout === 'home'" />
|
||||
<VPHome v-else-if="frontmatter.pageLayout === 'home'">
|
||||
<template #blog-top>
|
||||
<slot name="blog-top" />
|
||||
</template>
|
||||
<template #blog-bottom>
|
||||
<slot name="blog-bottom" />
|
||||
</template>
|
||||
<template #blog-post-list-before>
|
||||
<slot name="blog-post-list-before" />
|
||||
</template>
|
||||
<template #blog-post-list-after>
|
||||
<slot name="blog-post-list-after" />
|
||||
</template>
|
||||
<template #blog-post-list-pagination-after>
|
||||
<slot name="blog-post-list-pagination-after" />
|
||||
</template>
|
||||
</VPHome>
|
||||
|
||||
<component :is="frontmatter.pageLayout" v-else-if="frontmatter.pageLayout && frontmatter.pageLayout !== 'doc'" />
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { computed } from 'vue'
|
||||
import { type Ref, computed } from 'vue'
|
||||
import { useMediaQuery } from '@vueuse/core'
|
||||
import type { PlumeThemeBlogPostItem } from '../../shared/index.js'
|
||||
import { useLocalePostList } from './blog-data.js'
|
||||
@ -7,7 +7,7 @@ import { useRouteQuery } from './route-query.js'
|
||||
|
||||
const DEFAULT_PER_PAGE = 10
|
||||
|
||||
export function usePostListControl() {
|
||||
export function usePostListControl(homePage: Ref<boolean>) {
|
||||
const { theme } = useData()
|
||||
|
||||
const list = useLocalePostList()
|
||||
@ -111,7 +111,15 @@ export function usePostListControl() {
|
||||
if (page.value === current)
|
||||
return
|
||||
page.value = current
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'instant' })
|
||||
setTimeout(() => {
|
||||
let top = 0
|
||||
if (homePage.value) {
|
||||
top = document.querySelector('.vp-blog')?.getBoundingClientRect().top || 0
|
||||
top += window.scrollY - 64
|
||||
}
|
||||
|
||||
window.scrollTo({ top, behavior: 'instant' })
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -67,3 +67,15 @@ export function scrollTo(
|
||||
window.scrollTo({ top, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
export function getOffsetTop<T extends HTMLElement = HTMLElement>(target: T | null): number {
|
||||
if (!target)
|
||||
return 0
|
||||
let parent: HTMLElement | null = target
|
||||
let top = 0
|
||||
while (parent) {
|
||||
top += parent.offsetTop
|
||||
parent = parent.offsetParent as HTMLElement
|
||||
}
|
||||
return top
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
export * from './shared.js'
|
||||
export * from './dom.js'
|
||||
// export * from './dom.js'
|
||||
export * from './resolveEditLink.js'
|
||||
export * from './resolveRepoType.js'
|
||||
export * from './resolveNavLink.js'
|
||||
export * from './animate.js'
|
||||
// export * from './animate.js'
|
||||
|
||||
@ -8,11 +8,12 @@ const FALLBACK_OPTIONS: PlumeThemeLocaleData = {
|
||||
appearance: true,
|
||||
|
||||
blog: {
|
||||
link: '/blog/',
|
||||
pagination: { perPage: 15 },
|
||||
postList: true,
|
||||
tags: true,
|
||||
archives: true,
|
||||
categories: true,
|
||||
link: '/blog/',
|
||||
tagsLink: '/blog/tags/',
|
||||
archivesLink: '/blog/archives/',
|
||||
categoriesLink: '/blog/categories/',
|
||||
|
||||
@ -39,10 +39,12 @@ export async function setupPage(
|
||||
const locale = localePath === '/' ? rootPath : localePath
|
||||
|
||||
// 添加 博客页面
|
||||
pageList.push(createPage(app, {
|
||||
path: withBase(link, localePath),
|
||||
frontmatter: { lang, _pageLayout: 'blog', title: getTitle(locale, 'blog') },
|
||||
}))
|
||||
if (blog.postList !== false) {
|
||||
pageList.push(createPage(app, {
|
||||
path: withBase(link, localePath),
|
||||
frontmatter: { lang, _pageLayout: 'blog', title: getTitle(locale, 'blog') },
|
||||
}))
|
||||
}
|
||||
|
||||
// 添加 标签页
|
||||
if (blog.tags !== false) {
|
||||
@ -108,6 +110,11 @@ export function extendsPageData(
|
||||
delete page.frontmatter._pageLayout
|
||||
}
|
||||
|
||||
if (page.frontmatter.pageLayout === 'blog') {
|
||||
page.frontmatter.draft = true
|
||||
page.data.type = 'blog'
|
||||
}
|
||||
|
||||
if ('externalLink' in page.frontmatter) {
|
||||
page.frontmatter.externalLinkIcon = page.frontmatter.externalLink
|
||||
delete page.frontmatter.externalLink
|
||||
|
||||
@ -16,13 +16,6 @@ export type PlumeThemeBlogPostData = PlumeThemeBlogPostItem[]
|
||||
|
||||
export interface PlumeThemeBlog {
|
||||
|
||||
/**
|
||||
* 博客文章列表页链接
|
||||
*
|
||||
* @default '/blog/'
|
||||
*/
|
||||
link?: string
|
||||
|
||||
/**
|
||||
* 通过 glob string 配置包含文件,
|
||||
*
|
||||
@ -55,6 +48,19 @@ export interface PlumeThemeBlog {
|
||||
perPage?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 博客文章列表页链接
|
||||
*
|
||||
* @default '/blog/'
|
||||
*/
|
||||
link?: string
|
||||
|
||||
/**
|
||||
* 是否启用博客文章列表
|
||||
* @default true
|
||||
*/
|
||||
postList?: boolean
|
||||
|
||||
/**
|
||||
* 是否启用标签页
|
||||
* @default true
|
||||
|
||||
@ -19,7 +19,7 @@ export interface PlumeNormalFrontmatter extends PageFrontmatter {
|
||||
/**
|
||||
* page layout
|
||||
*/
|
||||
pageLayout?: false | 'home' | 'doc' | 'custom' | 'page' | 'friends'
|
||||
pageLayout?: false | 'home' | 'blog' | 'doc' | 'custom' | 'page' | 'friends'
|
||||
|
||||
/**
|
||||
* 自定义页面 class
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user