yggdrasil/docs/test-markdown.md
xfy d44b1565de
Some checks failed
CI / check (push) Has been cancelled
CI / build (push) Has been cancelled
docs(markdown): 新增 Markdown 全特性测试文章
覆盖 yggdrasil 渲染管线的全部特性:标题层级、强调、引用、嵌套列表、
任务列表、表格、自定义语法高亮(Kotlin/Swift/TS/TSX/Vue/Zig)、
KaTeX 行内与块级公式、Mermaid 各类图表、Runnable 代码块、
脚注、转义字符与多语言压力测试,便于回归验证整条渲染链路。
2026-07-21 11:26:38 +08:00

12 KiB
Raw Permalink Blame History

Markdown 全特性测试文章

这是一份用于测试 yggdrasil 博客系统 Markdown 渲染管线的综合性文章,覆盖:标题层级、强调、引用、列表、任务列表、表格、代码高亮(含 Kotlin/Swift/Vue 自定义语法、数学公式KaTeX 行内 + 块级、Mermaid 流程图、Runnable 代码块、脚注、链接、分割线等全部特性。

一、标题层级

以下从 H2 到 H6 依次展开H1 已被文章标题占用)。

三级标题 H3

四级标题 H4

五级标题 H5
六级标题 H6

二、强调与文本样式

这是粗体文本这是斜体文本这是粗斜体这是删除线

还可以使用强调,也可以混合删除与粗体线

行内代码:使用 cargo build --release 构建项目,或运行 make dev 启动开发服务器。

Note

如果不支持 GitHub 风格的告示框,上面这一行会显示为普通引用。

三、段落与换行

这是第一段。同一段落内的软换行(单个换行符)在渲染时会被合并为一个空格。

这是第二段。段落之间需要留一个空行。

如果要强制换行,可以在行尾加两个空格,
或者使用 <br> 标签
来强制换行。

四、引用块

这是单层引用块。引用块内可以包含 强调行内代码链接 等。

这是嵌套引用块,第二层。

引用块里也可以有列表:

  1. 第一项
  2. 第二项

以及代码:

let x = 42;

五、列表

无序列表

  • 第一项
  • 第二项
    • 嵌套项 2.1
    • 嵌套项 2.2
      • 更深一层 2.2.1
  • 第三项

有序列表

  1. 第一步:克隆仓库
  2. 第二步:安装依赖
    1. 子步骤 2.1
    2. 子步骤 2.2
  3. 第三步:启动服务

任务列表

  • 配置 Rust 工具链1.95+
  • 安装 dx CLI
  • 准备 PostgreSQL
  • 编写单元测试
  • 部署到生产环境

六、表格

简单表格

语言 扩展名 是否可运行 默认超时
Python py 5s
Node.js js 5s
Go go 10s
Rust rs 15s
TypeScript ts

对齐方式不同的表格

左对齐 居中对齐 右对齐
Left cell Center cell Right cell
第二行 第二行 第二行

七、代码块(语法高亮)

Rust

use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert("answer", 42);
    for (k, v) in &map {
        println!("{k} = {v}");
    }
}

Python

from dataclasses import dataclass
from typing import List

@dataclass
class Point:
    x: float
    y: float

def centroid(points: List[Point]) -> Point:
    n = len(points)
    return Point(
        x=sum(p.x for p in points) / n,
        y=sum(p.y for p in points) / n,
    )

Kotlin项目自定义语法定义

data class User(val name: String, val age: Int)

fun main() {
    val users = listOf(User("Alice", 30), User("Bob", 25))
    users.filter { it.age >= 28 }
         .sortedByDescending { it.age }
         .forEach { println("${it.name}: ${it.age}") }
}

Swift项目自定义语法定义

import Foundation

struct Greeting {
    let message: String
    func say(to name: String) -> String {
        return "\(message), \(name)!"
    }
}

let g = Greeting(message: "Hello")
print(g.say(to: "world"))

TypeScript项目自定义语法定义

interface Todo {
  id: number;
  title: string;
  done: boolean;
}

function toggle(todo: Todo): Todo {
  return { ...todo, done: !todo.done };
}

TSX项目自定义语法定义

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount((c) => c + 1)}>
      clicked {count} times
    </button>
  );
}

Vue SFC项目自定义语法定义

<template>
  <div class="hello" @click="onClick">{{ message }}</div>
</template>

<script setup lang="ts">
import { ref } from "vue";
const message = ref<string>("Hello Vue!");
function onClick() {
  message.value += "!";
}
</script>

<style scoped>
.hello {
  color: #42b883;
}
</style>

Zig项目自定义语法定义

const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, {s}!\n", .{"Zig"});
}

Shell

# 构建并部署
make build
docker compose up -d
tail -f /var/log/yggdrasil.log

SQL

SELECT
    u.id,
    u.name,
    COUNT(p.id) AS post_count
FROM users u
LEFT JOIN posts p ON p.author_id = u.id
WHERE u.created_at > NOW() - INTERVAL '30 days'
GROUP BY u.id, u.name
ORDER BY post_count DESC
LIMIT 10;

JSON

{
  "name": "yggdrasil",
  "version": "1.0.0",
  "features": ["fullstack", "ssr", "markdown"],
  "rust_version": "1.95"
}

不带语言标识的代码块

这是一段没有语言标识的代码块。
应当以纯文本形式渲染(无语法高亮)。

八、数学公式KaTeX 服务端渲染)

行内公式

  • 质能方程:E = mc^2
  • 勾股定理:a^2 + b^2 = c^2
  • 欧拉恒等式:e^{i\pi} + 1 = 0
  • 黄金比例:\varphi = \frac{1 + \sqrt{5}}{2} \approx 1.618
  • 求和:\displaystyle\sum_{i=1}^{n} i = \frac{n(n+1)}{2}

块级公式


\int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}

\frac{\partial}{\partial t} \rho + \nabla \cdot (\rho \vec{v}) = 0

\hat{H}\Psi = E\Psi \quad \text{(薛定谔方程)}

含矩阵与对齐的复杂公式


\begin{aligned}
\nabla \times \vec{B} &= \mu_0 \vec{J} + \mu_0 \varepsilon_0 \frac{\partial \vec{E}}{\partial t} \\
\nabla \times \vec{E} &= -\frac{\partial \vec{B}}{\partial t}
\end{aligned}

A = \begin{pmatrix}
a_{11} & a_{12} & a_{13} \\
a_{21} & a_{22} & a_{23} \\
a_{31} & a_{32} & a_{33}
\end{pmatrix}

标题中的公式

勾股定理 a^2 + b^2 = c^2

若 KaTeX 渲染正常,上方二级标题里的 a^2 + b^2 = c^2 应被渲染成数学符号。

九、Mermaid 流程图

流程图flowchart

graph TD
    A[用户访问文章页] --> B{命中 SSR 缓存?}
    B -->|是| C[直接返回缓存 HTML]
    B -->|否| D[渲染 Markdown]
    D --> E[应用 KaTeX 数学渲染]
    E --> F[语法高亮代码块]
    F --> G[写入 SSR 缓存]
    G --> H[返回响应]
    C --> H

横向流程图

graph LR
    Client[浏览器] --> CDN[反向代理]
    CDN --> App[Dioxus 服务端]
    App --> DB[(PostgreSQL)]
    App --> FS[静态资源]
    App --> Docker[Docker Runner]

时序图

sequenceDiagram
    participant U as 用户
    participant B as 浏览器
    participant S as 服务端
    participant D as Docker

    U->>B: 点击「运行」按钮
    B->>S: StartExec(source, lang)
    S->>S: 限流 & 白名单校验
    S->>D: 创建只读容器
    D-->>S: 注入源码并执行
    S-->>B: 返回 task_id
    B->>S: GetExecResult(task_id)
    S-->>B: 流式输出 stdout/stderr
    B->>U: 渲染到 xterm 终端

类图

classDiagram
    class Post {
        +i64 id
        +String slug
        +String title
        +String content_md
        +publish()
        +trash()
    }
    class User {
        +i64 id
        +String username
        +bool is_admin
    }
    class Tag {
        +i64 id
        +String name
    }
    User "1" --> "*" Post : authors
    Post "*" --> "*" Tag : tagged

饼图

pie title 文章内容构成
    "正文" : 45
    "代码块" : 25
    "数学公式" : 15
    "图表" : 10
    "其他" : 5

十、Runnable 代码块(可在浏览器执行)

Python最简形式

import sys
import platform

print(f"Python {sys.version.split()[0]} on {platform.system()}")
for i in range(5):
    print(f"fib({i}) =", (lambda n: round(((1+5**0.5)/2)**n/5**0.5))(i))

Node.js带资源覆盖

const fib = (n) => (n < 2 ? n : fib(n - 1) + fib(n - 2));
console.log(`Node ${process.version}`);
Array.from({ length: 15 }, (_, i) => console.log(`fib(${i}) = ${fib(i)}`));

Gorun 作为 runnable 的别名)

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Printf("Go %s on %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
    for i := 0; i < 5; i++ {
        fmt.Printf("square(%d) = %d\n", i, i*i)
    }
}

Rust带 timeout 覆盖)

fn main() {
    println!("Hello from Rust runner!");
    let nums: Vec<i32> = (1..=10).collect();
    let sum: i32 = nums.iter().sum();
    println!("sum(1..=10) = {sum}");
}

十一、链接与图片

链接

图片

占位图片

十二、脚注

正文中引用了 PostgreSQL1,渲染管线基于 pulldown-cmark2KaTeX 数学渲染来自 katex-rs3,流程图则依赖 mermaid.js4

同一脚注可以多次引用1,再次引用 PostgreSQL1

脚注 label 含空格也行5,服务端会把空格清洗为 -

十三、分割线

上方文字


中间分割线


再一条分割线


下方文字

十四、转义字符

下面这些字符在 Markdown 中有特殊含义,前面加 \ 可原样输出:

* 星号 _ 下划线 ` 反引号 # 井号 + 加号 - 减号 . 句点 ! 感叹号 [ ] 方括号 圆括号 { } 花括号 \ 反斜杠

十五、混合排版压力测试

  1. 引用 + 有序列表 + 强调
  2. 包含 行内代码链接
功能 渲染器 位置
代码高亮 syntect 服务端
数学公式 katex-rs 服务端
流程图 mermaid.js 客户端懒加载
可运行代码 bollard + Docker 服务端容器

下面这段同时出现数学公式 $f(x) = x^2$、inline code强调超链接和脚注6


\mathcal{F}\{f\}(\xi) = \int_{-\infty}^{\infty} f(x)\,e^{-2\pi i x \xi}\,dx
graph TD
    A[开始] --> B{渲染管线}
    B --> C[Markdown → HTML]
    B --> D[KaTeX 数学]
    B --> E[syntect 高亮]
    B --> F[mermaid 占位]
    C --> G[ammonia 消毒]
    D --> G
    E --> G
    F --> G
    G --> H[SSR 缓存写入]
    H --> I[客户端水合]
    I --> J[mermaid 懒加载渲染]

十六、Unicode 与多语言

  • 中文:世界,你好!
  • 日本語:こんにちは世界
  • 한국어: 안녕하세요 세계
  • العربية: مرحبا بالعالم
  • Русский: Привет, мир
  • Emoji🚀 🦀 🌳 📝 🔥

测试结束 —— 如果以上所有内容都正确渲染,说明 Markdown 管线工作正常。


  1. PostgreSQL 是一个强大的开源关系型数据库,本项目用 deadpool-postgres 管理连接池。 ↩︎

  2. pulldown-cmark 是 Rust 生态中最快的 CommonMark 解析库之一,支持 GFM 扩展。 ↩︎

  3. katex-rs 是 KaTeX 的 Rust 绑定,本项目以 OutputFormat::Html 模式渲染,不输出 MathMLXSS 面最小。 ↩︎

  4. mermaid.js 通过 IntersectionObserver 在视口可见时动态 import 独立 IIFE bundle避免首屏加载 ~3.4MB 的成本。 ↩︎

  5. 这条脚注的 label 是 my note,清洗后 id 中空格变为连字符。 ↩︎

  6. 这是一条用于混合排版测试的脚注。 ↩︎