Skip to content

Markdown扩展

更新: 11/17/2024 字数: 0 字 时长: 0 分钟

功能扩展

MarkdownIt 插件集合

增强Markdown语法插件,可以挑选使用,Github

图片查看插件

vitepress-plugin-lightbox

代码组图标

vitepress-plugin-group-icons

自定义图标配置

TIP

此处文件已经过拆包,可查看文件结构一章

config/vite.ts
ts
import type { UserConfig } from 'vitepress';
import { groupIconVitePlugin } from 'vitepress-plugin-group-icons';

export const vite: UserConfig['vite'] = {
	plugins: [
		groupIconVitePlugin({
			customIcon: {
				'.mts': 'vscode-icons:file-type-typescript',
				'.cts': 'vscode-icons:file-type-typescript',
				'.go': 'vscode-icons:file-type-go-lightblue',
				go: 'vscode-icons:file-type-go-lightblue',
				golang: 'vscode-icons:file-type-go-lightblue',
				bash: 'logos:bash-icon',
				'.sh': 'logos:bash-icon',
				profile: 'vscode-icons:file-type-dotenv',
				'.xml': 'vscode-icons:file-type-xml',
				worker: 'vscode-icons:file-type-dotenv',
				'.java': 'vscode-icons:file-type-java',
				java: 'vscode-icons:file-type-java',
				'pom.xml': 'vscode-icons:file-type-maven',
				dockerfile: 'vscode-icons:file-type-docker2',
				nginx: 'logos:nginx',
				mysql: 'vscode-icons:file-type-mysql',
				'.sql': 'vscode-icons:file-type-sql',
			},
		}),
	]
};

自定义功能

通过 MarkdownIt 的扩展,我们可以自自定义功能, 参考 实现了一个可以在H1标签下插入字数和阅读时间的功能

TIP

此处文件已经过拆包,可查看文件结构一章

ts
import type {MarkdownOptions} from 'vitepress';

export const markdown: MarkdownOptions = {
    config: md => {
        md.use(md => {
            const defaultRender = md.render;
            md.render = (...args) => {
                const [, env] = args;
                const isHomePage = env.path === '/' || env.relativePath === 'index.md'; // 判断是否是首页
                if (isHomePage) {
                    return defaultRender.apply(md, args); // 如果是首页,直接渲染内容
                }
                // 在每个 md 文件内容的开头插入组件,如果有H1标签添加在第一个H1后面
                const defaultContent = defaultRender.apply(md, args);
                const docMetaData = '<DocMetaData />\n';
                const newContent = defaultContent.replace(
                    /<\/h1>/,
                    `</h1> ${docMetaData}\n`,
                );
                return defaultContent === newContent
                    ? docMetaData + defaultContent
                    : newContent;
            };
        });
    },
};
ts
const pattern =
    /[a-zA-Z0-9_\u0392-\u03C9\u00C0-\u00FF\u0600-\u06FF\u0400-\u04FF]+|[\u4E00-\u9FFF\u3400-\u4DBF\uF900-\uFAFF\u3040-\u309F\uAC00-\uD7AF]+/g;

export function countWord(data: string) {
    const m = data.match(pattern);
    let count = 0;
    if (!m) {
        return 0;
    }
    for (let i = 0; i < m.length; i += 1) {
        if (m[i].charCodeAt(0) >= 0x4e00) {
            count += m[i].length;
        } else {
            count += 1;
        }
    }
    return count;
}
vue

<script setup lang="ts">
  import {useData} from 'vitepress';
  import {computed, onMounted, ref} from 'vue';
  import { countWord } from '../src/docMeta';

  const {page} = useData();

  const date = computed(() => new Date(page.value.lastUpdated));
  const wordCount = ref(0);
  const imageCount = ref(0);

  const wordTime = computed(() => {
    return (wordCount.value / 275) * 60;
  });

  const imageTime = computed(() => {
    const n = imageCount.value;
    if (imageCount.value <= 10) {
      // 等差数列求和
      return n * 13 + (n * (n - 1)) / 2;
    }
    return 175 + (n - 10) * 3;
  });

  // 阅读时间
  const readTime = computed(() => {
    return Math.ceil((wordTime.value + imageTime.value) / 60);
  });

  function analyze() {
    for (const v of document.querySelectorAll('.meta-des')) {
      v.remove();
    }
    const docDomContainer = window.document.querySelector('#VPContent');
    const imgs = docDomContainer?.querySelectorAll<HTMLImageElement>(
        '.content-container .main img',
    );
    imageCount.value = imgs?.length || 0;
    const words =
        docDomContainer?.querySelector('.content-container .main')?.textContent ||
        '';
    wordCount.value = countWord(words);
  }

  onMounted(() => {
    // 初始化时执行一次
    analyze();
  });
</script>

<template>
  <div class="word">
    <p>
      <svg t="1724572866572" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
           p-id="18131" width="16" height="16">
        <path
            d="M168.021333 504.192A343.253333 343.253333 0 0 1 268.629333 268.8a342.229333 342.229333 0 0 1 243.285334-100.778667A341.504 341.504 0 0 1 755.029333 268.8c9.856 9.898667 19.2 20.394667 27.733334 31.402667l-60.16 46.976a8.021333 8.021333 0 0 0 2.986666 14.122666l175.701334 43.008a8.021333 8.021333 0 0 0 9.898666-7.68l0.810667-180.906666a7.936 7.936 0 0 0-12.885333-6.314667L842.666667 253.44a418.858667 418.858667 0 0 0-330.922667-161.493333c-229.12 0-415.488 183.594667-419.797333 411.818666a8.021333 8.021333 0 0 0 8.021333 8.192H160a7.978667 7.978667 0 0 0 8.021333-7.808zM923.946667 512H864a7.978667 7.978667 0 0 0-8.021333 7.808 341.632 341.632 0 0 1-26.88 125.994667 342.186667 342.186667 0 0 1-73.685334 109.397333 342.442667 342.442667 0 0 1-243.328 100.821333 342.229333 342.229333 0 0 1-270.976-132.224l60.16-46.976a8.021333 8.021333 0 0 0-2.986666-14.122666l-175.701334-43.008a8.021333 8.021333 0 0 0-9.898666 7.68l-0.682667 181.034666c0 6.698667 7.68 10.496 12.885333 6.314667L181.333333 770.56a419.072 419.072 0 0 0 330.922667 161.408c229.205333 0 415.488-183.722667 419.797333-411.818667a8.021333 8.021333 0 0 0-8.021333-8.192z"
            fill="#8a8a8a" p-id="18132"></path>
      </svg>
      更新: {{ date.toLocaleDateString() }}
      <svg t="1724571760788" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
           p-id="6125" width="16" height="16">
        <path
            d="M204.8 0h477.866667l273.066666 273.066667v614.4c0 75.093333-61.44 136.533333-136.533333 136.533333H204.8c-75.093333 0-136.533333-61.44-136.533333-136.533333V136.533333C68.266667 61.44 129.706667 0 204.8 0z m307.2 607.573333l68.266667 191.146667c13.653333 27.306667 54.613333 27.306667 61.44 0l102.4-273.066667c6.826667-20.48 0-34.133333-20.48-40.96s-34.133333 0-40.96 13.653334l-68.266667 191.146666-68.266667-191.146666c-13.653333-27.306667-54.613333-27.306667-68.266666 0l-68.266667 191.146666-68.266667-191.146666c-6.826667-13.653333-27.306667-27.306667-47.786666-20.48s-27.306667 27.306667-20.48 47.786666l102.4 273.066667c13.653333 27.306667 54.613333 27.306667 61.44 0l75.093333-191.146667z"
            fill="#777777" p-id="6126"></path>
        <path d="M682.666667 0l273.066666 273.066667h-204.8c-40.96 0-68.266667-27.306667-68.266666-68.266667V0z"
              fill="#E0E0E0" opacity=".619" p-id="6127"></path>
      </svg>
      字数: {{ wordCount }} 字
      <svg t="1724572797268" class="icon" viewBox="0 0 1060 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
           p-id="15031" width="16" height="16">
        <path
            d="M556.726857 0.256A493.933714 493.933714 0 0 0 121.929143 258.998857L0 135.021714v350.390857h344.649143L196.205714 334.482286a406.820571 406.820571 0 1 1-15.908571 312.649143H68.937143A505.819429 505.819429 0 1 0 556.726857 0.256z m-79.542857 269.531429v274.907428l249.197714 150.966857 42.422857-70.070857-212.114285-129.389714V269.787429h-79.542857z"
            fill="#8a8a8a" p-id="15032"></path>
      </svg>
      时长: {{ readTime }} 分钟
    </p>
  </div>
</template>

<style scoped>
  .word {
    color: var(--vp-c-text-2);
    font-size: 15px;
  }

  .icon {
    display: inline-block;
    transform: translate(0px, 2px);
  }
</style>

语法扩展

参考文档

标题锚

代码: 标题会自动应用锚链接

说明

[] 中括号内文字随便输,() 括号里的填一个 # 号加标题

无论是几级标题,都是一个 #

输入:

markdown
[点我跳转:容器](#容器)

输出:

点我跳转:容器


自定义锚点,以应对中文无法正确跳转的问题

先再标题后,添加英文锚点

markdown
 # 标题锚 {#title-anchor}

输入:

markdown
[点我跳转:标题锚](#title-anchor)

输出:

点我跳转:标题锚

折叠语法

输入:

html

<details>
    <summary>点我展开</summary>
    Markdown默认折叠语法,Vitepress可以使用容器折叠语法,更加美观
</details>

输出:

点我展开 Markdown默认折叠语法,Vitepress可以使用容器折叠语法,更加美观

目录

markdown
[[toc]]
点我查看

容器

基础使用

容器可以通过其类型、标题和内容来定义

输入:

markdown
::: info
这是一条info,自定义格式:info+空格+自定义文字
:::

::: tip 提示
这是一个提示,自定义格式:tip+空格+自定义文字
:::

::: warning 警告
这是一条警告,自定义格式:warning+空格+自定义文字
:::

::: danger 危险
这是一个危险警告,自定义格式:danger+空格+自定义文字
:::

::: details 点我查看
这是一条详情,自定义格式:details+空格+自定义文字
:::

输出:

INFO

这是一条info,自定义格式:info+空格+自定义文字

提示

这是一个提示,自定义格式:tip+空格+自定义文字

警告

这是一条警告,自定义格式:warning+空格+自定义文字

危险

这是一个危险警告,自定义格式:danger+空格+自定义文字

点我查看

这是一条详情,自定义格式:details+空格+自定义文字

还可以加入代码块

md
Hello, VitePress!

GitHub风格警报

你也可以使用 GitHub 风格的警报 ,只是书写方式不同,使用上是一样的

输入:

markdown
> [!NOTE] 重要
> 强调用户在快速浏览文档时也不应忽略的重要信息。

> [!TIP]
> 有助于用户更顺利达成目标的建议性信息。

> [!IMPORTANT]
> 对用户达成目标至关重要的信息。

> [!WARNING]
> 因为可能存在风险,所以需要用户立即关注的关键内容。

> [!CAUTION]
> 行为可能带来的负面影响。

输出:

重要

强调用户在快速浏览文档时也不应忽略的重要信息。

TIP

有助于用户更顺利达成目标的建议性信息。

IMPORTANT

对用户达成目标至关重要的信息。

WARNING

因为可能存在风险,所以需要用户立即关注的关键内容。

CAUTION

行为可能带来的负面影响。

如果你是 Typora 的用户,本地不生效

使用下面代码进行配置,本代码由 Aurorxa 提供

typescript
// .vitepress/config.mts
export default defineConfig({

    markdown: {
        config: (md) => {
            // 创建 markdown-it 插件
            md.use((md) => {
                const defaultRender = md.render
                md.render = function (...args) {

                    // 调用原始渲染
                    let defaultContent = defaultRender.apply(md, args)
                    // 替换内容
                    defaultContent = defaultContent
                        .replace(/NOTE/g, '提醒')
                        .replace(/TIP/g, '建议')
                        .replace(/IMPORTANT/g, '重要')
                        .replace(/WARNING/g, '警告')
                        .replace(/CAUTION/g, '注意')
                    // 返回渲染的内容
                    return defaultContent
                }
            })
        }
    },

})

Badge组件

徽章可让您向标题添加状态

输入:

markdown
* VitePress <Badge type="info" text="default" />
* VitePress <Badge type="tip" text="^1.9.0" />
* VitePress <Badge type="warning" text="beta" />
* VitePress <Badge type="danger" text="caution" />

输出:

  • VitePress default
  • VitePress ^1.9.0
  • VitePress beta
  • VitePress caution

你也可以自定义 children

输入:

markdown
* VitePress <Badge type="info">custom element</Badge>

输出:

  • VitePress custom element

代码块

行高亮

比如我要第2-3行和第5行显示,连续行用 - ,不连续行用 ,

输入:

markdown
```typescript {2-3,5}
export default defineConfig({
  lang: 'zh-CN',
  title: "VitePress",
  description: "我的vitpress文档教程",
  titleTemplate: '另起标题覆盖title' ,
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN',
    title: "VitePress",
    description: "我的vitpress文档教程",
    titleTemplate: '另起标题覆盖title',
})

也可以使用 // [!code highlight]

输入:

markdown
```typescript
export default defineConfig({
  lang: 'zh-CN',
  title: "VitePress",
  description: "我的vitpress文档教程", // [!!code highlight]
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN',
    title: "VitePress",
    description: "我的vitpress文档教程", 
})

也可以通过 // [!code highlight:<lines>] 连续行号

输入:

markdown
```typescript
export default defineConfig({
  lang: 'zh-CN', // [!!code highlight:3]
  title: "VitePress",
  description: "我的vitpress文档教程",
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN', 
    title: "VitePress",
    description: "我的vitpress文档教程",
})

聚焦代码

在某一行后添加 // [!code focus] 注释会聚焦该行,并模糊代码的其他部分

输入:

markdown
```typescript {4}
export default defineConfig({
  lang: 'zh-CN',
  title: "VitePress",
  description: "我的vitpress文档教程", 
  titleTemplate: '另起标题覆盖title' ,
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN',
    title: "VitePress",
    description: "我的vitpress文档教程", 
    titleTemplate: '另起标题覆盖title',
})

如果你要聚焦连续多行,可以使用 // [!code focus:<lines>]

说明

从添加行的位置开始,输入最终聚焦的行号即可

分散的行,请单独添加使用

输入:

```typescript {2-5}
export default defineConfig({
  lang: 'zh-CN', // [!!code focus:4]
  title: "VitePress",
  description: "我的vitpress文档教程",
  titleTemplate: '另起标题覆盖title' ,
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN', 
    title: "VitePress",
    description: "我的vitpress文档教程",
    titleTemplate: '另起标题覆盖title',
})

增减差异

在某一行上添加 // [!code --]// [!code ++] 注释将创建该行的差异,同时保留代码块的颜色

输入:

markdown
```typescript {4-5}
export default defineConfig({
  lang: 'zh-CN', 
  title: "VitePress", 
  description: "我的vitpress文档教程", 
  description: "更详细的vitpress中文文档教程", 
  titleTemplate: '另起标题覆盖title' ,
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN',
    title: "VitePress",
    description: "我的vitpress文档教程", 
    description: "更详细的vitpress中文文档教程", 
    titleTemplate: '另起标题覆盖title',
})

错误和警告

在某一行上添加 // [!code warning]// [!code error] 注释会相应地为其着色

输入:

```typescript {4-5}
export default defineConfig({
  lang: 'zh-CN', 
  title: "VitePress", 
  description: "我的vitpress文档教程", // [!!code error]
  description: "更详细的vitpress中文文档教程", // [!!code warning]
  titleTemplate: '另起标题覆盖title' ,
})
```

输出:

typescript
export default defineConfig({
    lang: 'zh-CN',
    title: "VitePress",
    description: "我的vitpress文档教程", 
    description: "更详细的vitpress中文文档教程", 
    titleTemplate: '另起标题覆盖title',
})

代码组

和Vuepress不同,我们用 code-group 包裹

::: code-group
:::

输入:

::: code-group

```shell [pnpm]
#查询pnpm版本
pnpm -v
```

```shell [yarn]
#查询yarn版本
yarn -v
```

:::

输出:

shell
#查询pnpm版本
pnpm -v
shell
#查询yarn版本
yarn -v

导入代码

要输出准确的文件路径,可以指定代码的片段和高亮部分

导入片段,我们需要对原文件进行注释 // #region// #endregion

注意

开始和结束都要有,后面的字必须是字母,不能汉字!

可以自定义,比如示例中的 fav

原文件修改示例:

text
// #region code
  你的代码...
// #endregion code

输入:

说明

{} 大括号中是高亮的行号

markdown
<!-- 绝对路径 二选一-->
<<< @/.vitepress/config.mts#code{2}

<!-- 相对路径 二选一-->
<<< ./.vitepress/config.mts#code{2}

输出:

config.mts
mts
export default defineConfig({
  lang: metaData.lang,
  title: metaData.title,
  description: metaData.description,
  cleanUrls: true, //开启纯净链接
  lastUpdated: true,
  sitemap: {
    hostname: metaData.site,
  },
  metaChunk: true, //将页面元数据提取到单独的 JavaScript 块中,而不是内联在初始 HTML 中
  markdown: markdown,
  head: head,
  vite: vite,
  themeConfig: themeConfig,
});