CallioText

CallioText 是一个用来编写结构化文档的编辑器库,基于 React、Slate 和 MUI 构建。 这一页介绍它是什么、为什么值得用,并给出安装方法和一个能直接跑起来的最小例子。

它是什么?

在 Word 里,想让论文中的每个定理都有统一的样式和连续的编号,只能手动维护: 样式是自己调的,编号是自己写的,中间插入一个新定理,后面的编号就得挨个改。 LaTeX 解决了这个问题:定理写作 \begin{theorem}...\end{theorem}, 作者只声明「这是一个定理」,样式和编号由系统统一处理。

但 LaTeX 毕竟为打印而生。它假定文章的归宿是一叠纸:编号排到最后一页就完成了使命, 引用一旦印上纸面就再也不能动。而文章如今大多活在网页上,编号本可以跨越页面自动接续, 引用本可以在鼠标停留时展开被引的原文,这些都不是 LaTeX 给得了的。 它的输入又是代码:文章一长,源文件里反斜杠与花括号丛生,臃肿到读也吃力、改也吃力。

CallioText 就是这样的一个编辑器:它像 LaTeX 一样把结构和样式分开, 又像 Word 一样容易编辑,而输出像网页一样自由。

它是怎么工作的

在 CallioText 里,写文章是这样分工的:

三个环节各管一段:概念定义决定文章能使用哪些语义,写作只操作语义,排版由渲染规则统一执行。 想换样式,改概念的定义就够了,文章本身一个字不用动;想加一种新的文档组件, 甚至可以在运行的程序里直接创建,不用写代码。这套模型是整个库的地基,教程第 1 章会把它展开讲透。

它能做什么

把这套分工落到实处,你得到的是这些能力:

这些能力在教程里都有对应的章节:编辑与印刷的分离在第 3、4 章亲手搭出来, 概念系统在第 5 章,自动编号和交叉引用在第 6 章,插件、保存加载和键盘操作在第 7 章。 读完教程,上面每一条你都不只是「知道有」,而是「知道怎么来的」。 教程之后还有一部分专门讲默认实现,把默认编辑器的界面、渲染器工厂和定制方式逐项讲开。

想在继续阅读之前先亲眼看看这一切,可以打开在线演示: 一个用 CallioText 搭出来的完整编辑器,预置了一篇简短的数学短文,整个跑在浏览器里。

安装

CallioText 以 npm 包的形式发布:

npm install @project-callio/calliotext

快速开始

下面是一个能跑起来的最小例子。这里出现的名字会在教程里逐一解释, 这个例子只用来展示跑起来一个编辑器需要哪些代码。

import React from "react"
import {
    Printer, EditorCore,
    DefaultEditorComponent,
    get_default_editors,
    get_default_paragraph_renderer,
    useless_renderer_block, useless_renderer_inline, useless_renderer_text,
} from "@project-callio/calliotext"
import "@project-callio/calliotext/calliotext.css"

// 我们还没有定义任何概念,所以这里都是空的。
const no_renderers = {group: {}, inline: {}, support: {}, structure: {}, abstract: {}}

// 印刷器:负责把文档渲染成排版后的成品。
const printer = new Printer([], [], no_renderers, {
    group     : useless_renderer_block,
    structure : useless_renderer_block,
    support   : useless_renderer_block,
    abstract  : useless_renderer_block,
    inline    : useless_renderer_inline,
    text      : useless_renderer_text,
    paragraph : get_default_paragraph_renderer({}),
})

// 编辑器核心:管理编辑器需要知道的所有信息。
const core = new EditorCore({
    renderers: no_renderers,
    default_renderers: get_default_editors(),
    printer,
})

export default function App(){
    return <div style={{height: "100vh", position: "relative"}}>
        <DefaultEditorComponent editorcore={core} />
    </div>
}

这段代码做了三件事:

  1. 创建了一个印刷器(Printer)。它负责把文档变成排版成品。因为还没有定义任何概念,我们传给它的都是空列表和最朴素的渲染方式。
  2. 创建了一个编辑器核心(EditorCore)。它管理编辑器的配置,并且和印刷器相连,以便查询概念的定义。
  3. 把默认的编辑器组件(DefaultEditorComponent)显示在页面上。

打开页面,你会看到一张可以直接打字的「纸」。我们没有指定文档内容,所以编辑器自动创建了一篇空文档。

不过,一个没有概念的编辑器和普通的文本框差别不大。CallioText 的能力要从定义概念开始展现。 请从教程第一章开始阅读:核心思想:概念与文档树

文档导航

整套文档分成四个部分。教程是主线,从零开始按章推进;默认实现那一部分专讲开箱即用的那套界面, 读完教程再看;API 文档按模块组织,供开发时查阅;TypeDoc 参考覆盖每一个导出项,前面几者没讲到的细节去那里找:

部分内容
教程手把手从零搭建一个带定理编号、交叉引用的结构化编辑器。
默认实现默认编辑器的界面、键盘操作、渲染器工厂与定制方式,逐项讲开。
API 文档按模块整理的公开 API:签名、用途与说明。
完整参考由 TypeDoc 从源码注释自动生成的逐项参考(注释为中文)。
在线演示用 CallioText 搭出来的完整编辑器,直接在浏览器里运行;源码在仓库的 demo/ 目录。