1.安装
npm install @tiptap/extension-table
使用
import { TableKit } from '@tiptap/extension-table'
const editor = useEditor({
extensions: [
TableKit.configure({
table: { resizable: true },
}),
],
})
1.1 table 和 TableKit 关系
import { Editor } from '@tiptap/core'
import { Table, TableRow, TableCell, TableHeader,TableKit } from '@tiptap/extension-table'
new Editor({
extensions: [
Table,
TableRow,
TableCell,
TableHeader,
// 需要手动添加所有相关组件
],
})
new Editor({
extensions: [
TableKit, // 一个扩展包含所有功能
],
})
//Table 只提供基本的表格功能 需要手动配置其他组件
// Tabkit 包含所有表格相关功能 开箱即用,无需额外配置
| 组件 | 作用 | 使用场景 |
|---|---|---|
| Table | 基础的表格节点 | 定义表格的基本结构 |
| TableKit | 完整的表格工具包 | 包含所有表格相关功能 |
2. table/tableCell/tableHeader/tableRow
2.1 table
import { Table } from '@tiptap/extension-table'
table 一些方法
//修复表()
editor.commands.fixTables()
//设置单元格属性
editor.commands.setCellAttribute('customAttribute', 'value')
editor.commands.setCellAttribute('backgroundColor', '#000')
2.2. TableCell
import { TableCell } from '@tiptap/extension-table/cell'
2.3 TableHeader
- 管理列的装饰器(decorations)
import { TableHeader } from '@tiptap/extension-table/header'
2.3.1 tableHeader 和 tableRow的关系
// 它们就像"特殊士兵"和"普通士兵"的关系
TableRow: "普通士兵" - 可以包含普通单元格
TableHeader: "特种士兵" - 特殊类型的单元格,用于表头
默认配置(包含表头)
// ✅ 默认:表行可以包含普通单元格 或 表头单元格
TableRow.extend({
content: '(tableCell | tableHeader)*', // 可以混合使用
})
// 这样配置后,表格可以这样结构:
<table>
<tr>
<th>姓名</th> {/* 表头单元格 - 特殊样式 */}
<th>年龄</th>
<th>城市</th>
</tr>
<tr>
<td>张三</td> {/* 普通单元格 */}
<td>25</td>
<td>北京</td>
</tr>
</table>
无表头配置
// ❌ 无表头:表行只能包含普通单元格
TableRow.extend({
content: 'tableCell*', // 只能使用普通单元格
})
// 这样配置后,表格结构:
<table>
<tr>
<td>姓名</td> {/* 全是普通单元格 */}
<td>年龄</td>
<td>城市</td>
</tr>
<tr>
<td>张三</td>
<td>25</td>
<td>北京</td>
</tr>
</table>
2.4 TableRow
import { TableRow } from '@tiptap/extension-table/row'
| 组件 | 比喻 | 作用 | 负责什么 | |
|---|---|---|---|---|
| Table | 整个学校 | 包含所有班级的容器 | 整个表格的容器和布局 | 整个表格的"外壳” |
| TableRow | 一个班级 | 包含所有学生的行 | 每一行的样式和行为 | 表格的"行” |
| TableCell | 一个学生座位 | 具体的内容位置 | 每个单元格的内容和样式 | 表格的单元格 |
| TableHeader | 班级表头 | 显示"姓名"、"学号"等标题 | 表格头部的特殊样式 | 表格的头部 |
Table (最外层)
└── TableRow (行)
└── TableCell (单元格)
└── TableHeader (头部,可选)
3. TableMap 是什么?
TableMap 是 ProseMirror 提供的表格数据结构,它把表格抽象成一个二维矩阵:
3.1 参数
TableMap {
width: 4, // 4列
height: 3, // 3行
map: [1, 5, 9, 13, 19, 23, 27, 31, 37, 41, 45, 49]
}
注意:每次加4 相当于 tiptap 固定的
关键理解点
1. 单元格索引 → 行列
const cellIndex = 9;
const row = Math.floor(9 / 4); // 2
const col = 9 % 4; // 1
// 结果:第2行第1列
2. 行列 → 单元格索引
const row = 2, col = 1;
const cellIndex = 2 * 4 + 1; // 9
// 结果:单元格索引9
3. 获取单元格位置
const cellPos = tableStart + map[cellIndex];
表格: 4列 × 3行
列: 0 1 2 3
行: ┌───┬───┬───┬───┐ 0
│ 0 │ 1 │ 2 │ 3 │ map[0,1,2,3] = [1,5,9,13]
├───┼───┼───┼───┤ 1
│ 4 │ 5 │ 6 │ 7 │ map[4,5,6,7] = [19,23,27,31]
├───┼───┼───┼───┤ 2
│ 8 │ 9 │10 │11 │ map[8,9,10,11] = [37,41,45,49]
└───┴───┴───┴───┘
示例表格(3列 x 2行):
┌─────┬─────┬─────┐
│ A1 │ A2 │ A3 │ ← 第0行
├─────┼─────┼─────┤
│ B1 │ B2 │ B3 │ ← 第1行
└─────┴─────┴─────┘
↑ ↑ ↑
列0 列1 列2
map.width = 3 // 列数
map.height = 2 // 行数
const { selection } = editor.state;
// 获取表格的 TableMap 和选择的矩形区域
const map = TableMap.get(selection.$anchorCell.node(-1));
const start = selection.$anchorCell.start(-1);
const rect = map.rectBetween(
selection.$anchorCell.pos - start,
selection.$headCell.pos - start,
);
- selection.headCell
- $anchorCell:选择的起点单元格(鼠标按下的位置)
- $headCell:选择的终点单元格(鼠标释放的位置)
- .node(-1) 获取表格节点(-1 表示向上查找父节点)
- .start(-1) 获取表格节点的起始位置
- rectBetween
它计算从起点到终点之间的最小矩形区域,返回一个 Rect 对象:typescript
type Rect = {
left: number; // 左边界(列索引)
right: number; // 右边界(列索引 + 1)
top: number; // 上边界(行索引)
bottom: number; // 下边界(行索引 + 1)
}
eg
列0 列1 列2
┌─────┬─────┬─────┐
行0 │ A │ B │ C │
├─────┼─────┼─────┤
行1 │ D │ E │ F │
├─────┼─────┼─────┤
行2 │ G │ H │ I │
└─────┴─────┴─────┘
选择:B → E → F
rect = { left: 1, right: 3, top: 0, bottom: 2 }
列1 列2
┌─────┬─────┐
行0 │ B │ C │ ✓
├─────┼─────┤
行1 │ E │ F │ ✓
└─────┴─────┘
4.Tiptap 扩展开发
Tiptap 扩展开发的两种主要方式:
- 使用
Node.create<Options>(...): 重新从头创建整个 Tiptap 节点。 - 使用
Extension.extend(...): 继承 Tiptap 官方的扩展,并在此基础上进行修改。
| 方面 | TableCell.extend() (推荐) | Node.create() (不推荐) |
|---|---|---|
| 稳定性 & 兼容性 | 继承了官方扩展的所有内部逻辑、默认行为、依赖和修复。版本升级时更安全。 | 需要手动复制所有核心配置(如 tableRole、content、默认 parseHTML 等)。如果官方扩展内部有更新,您的代码可能会损坏。 |
| 代码量 | 只需要编写你想要添加或覆盖的部分(如 addProseMirrorPlugins)。代码更简洁。 | 必须重新编写所有内容,包括 Node 的基本结构定义、content、tableRole 等。代码冗余。 |
| 表格功能 | 确保与 ProseMirror prosemirror-tables 库的内部交互(如合并单元格、列操作)是完全同步和正确的。 | 容易遗漏 ProseMirror 表格所需的底层配置,可能导致合并/拆分等复杂操作出现不可预测的错误。 |
| 可维护性 | 清晰地表明您只是在扩展一个标准功能。 | 看起来像在重新实现一个标准功能。 |
4.1 (Node.create / Extension.create)
从零开始创造新积木 Tiptap 没有“警告框”节点,我们需要从零开始定义它:
import { Node, mergeAttributes } from "@tiptap/core";
// 1. 使用 Node.create 从零开始定义一个新节点
export const AlertNode = Node.create({
name: "alert", // 节点名称,必须唯一
// 2. 定义节点包含的内容(必须)
content: "block+",
// 3. 定义如何从 HTML 解析(必须)
parseHTML() {
return [{ tag: "div[data-type='alert']" }];
},
// 4. 定义如何渲染成 HTML(必须)
renderHTML({ HTMLAttributes }) {
// 渲染成一个带有特定属性的 div
return [
"div",
mergeAttributes(
{ "data-type": "alert", class: "bg-red-100 p-4 border-l-4 border-red-500" },
HTMLAttributes
),
0, // 0 表示内容应该插入到这里
];
},
// 5. 定义属性(可选)
addAttributes() {
return {
type: {
default: 'warning',
}
}
}
// ... 还需要定义 Commands, InputRules, Plugins 等等
});
- 乐高方式二:修改现有积木 (
Extension.extend)
4.2. Extension.extend
修改现有积木
import { TableCell as TiptapTableCell } from "@tiptap/extension-table";
import { Plugin } from "@tiptap/pm/state";
import { Decoration, DecorationSet } from "@tiptap/pm/view";
/**
* 继承 Tiptap 官方的 TableCell 扩展
* 目标:在保留所有原生表格功能的基础上,添加一个自定义的 ProseMirror 插件
* 来绘制行选择器(Row Grip)。
*/
export const EnhancedTableCell = TiptapTableCell.extend({
// 1. 覆盖 addProseMirrorPlugins 方法
addProseMirrorPlugins() {
return [
// 2. 确保调用父级方法,以保留所有原生表格插件
...(this.parent?.() || []),
// 3. 添加我们自定义的插件(例如,绘制 Row Grip 的装饰器)
new Plugin({
props: {
decorations: (state) => {
// 仅在此处编写绘制 Row Grip 的逻辑
const decorations: Decoration[] = [];
// ... 复杂的 Row Grip 绘制代码 ...
return DecorationSet.create(state.doc, decorations);
},
},
}),
];
},
// 4. 只覆盖我们想修改的属性(例如,给 td 标签添加默认 class)
addOptions() {
return {
...this.parent?.(), // 继承父级所有选项
HTMLAttributes: {
class: 'custom-table-cell-style' // 增加自定义 class
}
}
}
});
5. Decorations
-
是 ProseMirror 的“视图层标注”,用来在不修改文档内容的前提下,给某些位置或节点附加样式/DOM 节点。
-
作用场景:
- 高亮、下划线、占位符、错误提示等“样式变化但不入库”的东西。
- 浮动/绝对定位的交互控件(如表格列头 grip、行头 grip、添加列/行按钮)。
- 选区辅助可视化(例如整列/整行被选中的描边)。
三种类型
5.1 三种类型
-
Decoration.inline(from, to, attrs)
- 针对文本区间的内联标注,常用于语法高亮、拼写错误下划线。
-
Decoration.node(from, to, attrs)
- 针对某个节点(块)的标注,改变该节点的 attrs(如 class、style、draggable)。
-
Decoration.widget(pos, domBuilder, options?)
- 在某个文档位置插入“零宽”小部件(DOM 节点),不改变文档模型,最常用来放按钮、光标标记等。