首页
/ Blockquotes

Blockquotes

2026-09-04 10:51:16作者:滕妙奇

Use it if you're quoting a person, a song or whatever.

foo

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.

  • foo
  • bar

  • baz
  1. Lorem Ipsum is simply dummy text 1
  2. Lorem Ipsum is simply dummy text 2
  3. Lorem Ipsum is simply dummy text 3

Use it if you're quoting a person, a song or whatever.

You can use italic or lists inside them also.


逐段拆解这些样例各自“钉住”的语法点:

- **单行引用块**:`> Use it if you're quoting a person, a song or whatever.`,最基础的 `> ` 前缀形式,前后夹普通段落验证块边界。
- **多行引用块**:连续多段 Lorem Ipsum 文本,每行都带 `> ` 前缀,验证引用块内部跨行的内容保持。
- **列表项内的行内引用**:`- foo / - > bar / - baz`,引用块作为列表项的子块嵌套,是“引用块包裹内容”之外的反向嵌套(列表包裹引用块)。
- **引用块内嵌套有序列表**:`> 1. ...` 形式,验证引用块内部的有序列表子块,其缩进序列化是往返一致性中最容易出错的形态之一。
- **引用块内的行内样式**:`*italic*` 强调与行内元素共存,验证块级解析不影响行内语法。

### 1.1 “Failing Tests”:已知的边界场景

夹具末尾还保留了两个被显式标注为 Failing Tests 的代码块,对应 CommonMark 规范中更刁钻的两种情况:

```markdown
> You can use *italic* or lists inside them also.
And just like with other paragraphs,
all of these lines are still
part of the blockquote, even without the > character in front.

To end the blockquote, just put a blank line before the following
paragraph.

这是引用块的**懒延续(lazy continuation)**语义:不带 > 前缀的后续行仍属于引用块,只有空行才结束它。

* foo

  > This is a blockquote
  > inside a list item.

* bar

这是列表项内的独立引用块(松散列表 + 嵌套引用块),涉及缩进对齐与块归属判定。

这两个样例被单独放在围栏代码块中,意味着它们不会进入上方参与往返断言的“活”内容——从文件结构看,它们是以文本形式记录的历史已知失败用例,用于跟踪这些边界场景的回归状态,而非当前断言的一部分。

二、测试驱动层:roundTrip.spec.ts 如何消费这份夹具

夹具本身只是数据,其价值由 roundTrip.spec.ts 赋予。该测试文件头部注释说明了它的来源:回移自 marktext 旧的 test/unit/specs/markdown-basic.spec.js,每个夹具会被解析为新的 Muyu 状态树再重新序列化,期望往返在身份映射意义下稳定。

2.1 夹具注册与读取

测试在 fixtures 数组中显式注册了 11 个夹具文件,引用块夹具注册为 { label: 'common / Blockquotes', file: 'common/Blockquotes.md' },夹具根目录通过 fileURLToPath(import.meta.url) 相对解析到 fixtures/marktext-round-trip/(注释中特意说明改用 fileURLToPath 而非 new URL(...).pathname,是为了避免 Windows 或工作区路径含特殊字符时的解析问题)。

2.2 往返管线:两个状态转换器

核心往返函数 roundTrip 展示了 Muyu 的状态机双向转换链路:

function roundTrip(markdown: string): string {
    const states = new MarkdownToState({
        footnote: false,
        math: true,
        isGitlabCompatibilityEnabled: true,
        trimUnnecessaryCodeBlockEmptyLines: false,
        frontMatter: true,
    }).generate(markdown);
    return new StateToMarkdown({ listIndentation: 1 }).generate(states);
}
  • 正向MarkdownToState 把原始 Markdown 文本解析为块级状态树(引用块对应的状态名为 block-quote,且被列入可容器化块类型集合);
  • 反向StateToMarkdown 把状态树序列化回 Markdown 文本。

{ listIndentation: 1 } 这一选项对 Blockquotes.md 尤为关键:夹具中 - > bar> 1. ... 这类嵌套缩进形态,序列化时列表缩进宽度的选择直接决定输出字节。

2.3 断言策略:收敛优先,恒等次之

一个值得注意的设计是断言的“松紧两层结构”:

function isStableUnderRoundTrip(markdown: string): boolean {
    const once = roundTrip(markdown);
    const twice = roundTrip(once);
    return normalise(once) === normalise(twice);
}

对包括 Blockquotes 在内的全部 11 个夹具,严格断言是收敛性:第二趟往返的输出必须与第一趟相同。测试注释给出了理由——对非平凡 Markdown,逐字节恒等几乎不可能,且 marktext 旧版在这些夹具上本就是非确定的(列表缩进与 ExportMarkdown 的规范化选择不同)。而 normalise 只做两件事:CRLF 归一为 LF、去掉结尾换行;注释里特别强调刻意不按行去除尾随空格,因为 CommonMark 中“行尾两个空格”是硬换行标记,若折叠掉会掩盖真实的往返不稳定。

另外,测试单独列出 4 个可通过更严格“逐字恒等”断言的夹具(Images、Escapes、GFM 基础格式、GFM 表格),Blockquotes.md 不在其中——恰好说明引用块 + 列表嵌套的序列化会规范化首趟输出(例如缩进形态被重写为规范形式),但仍满足收敛性要求。

在编辑器场景下的实际意义是:用户在 MarkText 中打开文件 → 状态树 → 保存,若序列化不收敛,连续“打开—保存”会使文件内容漂移;该断言正是对这种漂移的回归防线。

三、源码纵深:引用块在 Muyu 中的解析、表示与序列化

3.1 解析:markdownToState.ts 对 blockquote token 的处理

MarkdownToState 基于 marked 的 token 流构建状态树。blockquote 被列入可容器化的块类型(与 list、list_item、footnote 一起进入父栈弹出逻辑);针对 case 'blockquote' 的处理中,源码还包含一处针对 MarkText issue #1735 的显式修复——空引用块(token 为空)的兜底构造,避免空 > 行导致解析中断。这些细节保证了夹具中“引用块紧贴普通段落”“引用块内含多段子块”等边界能稳定产出 block-quote 状态节点。

3.2 表示:BlockQuote 块类

渲染层的块实现位于 block/commonMark/blockQuote/index.ts

class BlockQuote extends Parent {
    static override blockName = 'block-quote';

    static create(muya: Muya, state: IBlockQuoteState) {
        const blockQuote = new BlockQuote(muya);
        for (const child of state.children)
            blockQuote.append(ScrollPage.loadBlock(child.name).create(muya, child));
        return blockQuote;
    }
    ...
}

BlockQuote 继承 Parent 块基类(容器块),静态工厂 create 递归加载子块(段落、列表等),tagNameblockquoteclassListmu-block-quotegetState() 把子块状态回读为 { name: 'block-quote', children: [...] }。状态树中的引用块因此是一个纯粹的结构节点:所有语法差异都被收敛到“容器 + 子块数组”这一形态,序列化器只需处理容器前缀。

3.3 序列化:stateToMarkdown.ts 中的引用块输出

反向序列化器 StateToMarkdown 在分派表中对 case 'block-quote' 先调用 _insertLineBreak 保证块间距,再调用 _serializeBlockquote 输出带 > 前缀的各行。其中 _insertLineBreak 有一段与引用块直接相关的注释逻辑:

// Blank lines inside a list item should be empty, not carry the
// item's indent as trailing whitespace. For blockquote-style indents
// like `> ` we keep the `>` so the quote stays continuous — only
// strip the trailing run of plain spaces.
result.push(`${indent.replace(/ +$/, '')}\n`);

即:嵌套列表内的空行不应携带完整缩进空白,但 > 这类引用前缀必须保留以维持引用块连续性,只剥掉纯空格尾巴。这正是夹具中 > 1. Lorem... 这类“引用内嵌列表”能收敛序列化的底层保障之一。同族行为还有更细的回归测试 listSerialization.spec.ts(“引用块内嵌套有序/无序列表的往返”)与 blockSerialization.spec.ts(单行/多行/嵌套引用块往返)。

四、如何运行与扩展这套引用块往返测试

在 Muyu 包内可直接运行该规格文件(依赖安装以仓库 pnpm-workspace.yaml 描述的 pnpm 工作区为准):

cd packages/muya && npx vitest run test/spec/roundTrip.spec.ts
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384