
Cherry Studio 富文本表格扩展 cherrystudio/extension-table-plus从 Tiptap Fork 到发布流程的完整解析【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio本篇技术指南围绕 Cherry Studio 仓库中的 packages/extension-table-plus 子包展开系统讲解它从tiptap/extension-tableFork 而来、经 Cherry Studio 定制增强后以独立包cherrystudio/extension-table-plus发布的完整脉络。读者将掌握该包在 Cherry Studio 富文本编辑器中的实际装配方式、Table/TableCell/TableHeader/TableRow/TableKit 五大入口与全部配置项、完整命令 API以及基于 changesets 的多包发布与构建修复实践。包定位为什么 Cherry Studio 需要一个自研表格扩展Cherry Studio 的富文本编辑器基于 TiptapProseMirror 之上 headless 的所见即所得编辑器框架构建其扩展装配集中在 src/renderer/components/RichEditor/createExtensions.ts其中表格能力并非直接引用上游包而是使用仓库内维护的cherrystudio/extension-table-plus。从 CHANGELOG.md 的 v3.0.11 条目可以确认该包的来源Forked from tiptap/extension-table v3.0.9 with Cherry Studio specific enhancements即该包以tiptap/extension-tablev3.0.9 为基线 Fork再叠加 Cherry Studio 特有的增强逻辑。之所以要自维护一份 Fork而不是直接依赖上游从 src/renderer/components/RichEditor/extensions/markdownTable.ts 的注释中可以找到原因cherrystudio/extension-table-plusships no markdown hooks, so without this the markedtable...该包不附带 markdown 钩子因此需要额外扩展来保证 GFM 表格在 Markdown 往返序列化中不丢失也就是说Cherry Studio 需要在上游 Tiptap 表格基础上补充 GFMGitHub Flavored Markdown表格的解析/序列化能力并定制行/列操作菜单回调等交互因此选择 Fork 一份并持续维护形成独立的 monorepo 子包。版本历史与发布节奏v3.0.9 → v3.0.12v3.0.11Fork 基线cherrystudio/extension-table-plus的第一个自有版本是 v3.0.11对应 Fork 自tiptap/extension-tablev3.0.9 的代码基线。上游 v3.0.9 的依赖锁定可见于 CHANGELOG-OLD.mdtiptap/core3.0.9与tiptap/pm3.0.9。v3.0.12引入 changesets 发布 构建修复v3.0.12 是一次 Patch 版本发布CHANGELOG 记录了三个关键变更变更PR / Commit作者内容changesets 基线发布#12783 /336176bEurFelux为此前未纳入管理的包变更建立基线同时引入基于 changesets 的发布流程移除无效 tsconfig 引用#13817 /7c2610bEurFelux移除对不存在的tsconfig.build.json的引用修复 CI 构建失败新增本地 tsconfig#13840 /ae13786EurFelux新增本地 tsconfig.json修复packages:build中 dts 构建失败第三个变更直接对应仓库中实际存在的 packages/extension-table-plus/tsconfig.json——该文件以declaration: true、outDir: ./dist、rootDir: ./src的配置输出类型声明。此前构建失败的原因在于dts 声明构建需要独立的本地 TypeScript 配置而包的根级 tsconfig 可能在 monorepo 中被聚合或覆盖新增本地 tsconfig 后packages:build阶段可以稳定地为该包产出dist/index.d.ts等类型声明文件。包结构与构建产物源码目录五个模块 类型扩展packages/extension-table-plus/src/ ├── index.ts # 总入口re-export 全部模块 ├── types.ts # 对 tiptap/core 的 tableRole 类型扩展 ├── table/ # Table 节点含 TableView 与 8 个工具函数 ├── cell/ # TableCell 节点 ├── header/ # TableHeader 节点 ├── row/ # TableRow 节点 └── kit/ # TableKit 聚合扩展入口文件 src/index.ts 将cell、header、kit、row、table五个子模块以及TableView全部导出使用 ESM 后缀.js导入与type: module的包配置保持一致。双格式构建tsdown 多入口tsdown.config.ts 定义了 6 个构建入口对应包提供 6 个可导入子路径入口源文件输出目录子路径导出src/index.tsdist/cherrystudio/extension-table-plussrc/table/index.tsdist/table/.../tablesrc/cell/index.tsdist/cell/.../cellsrc/header/index.tsdist/header/.../headersrc/kit/index.tsdist/kit/.../kitsrc/row/index.tsdist/row/.../row每个入口均生成 ESM.js与 CJS.cjs双格式、.d.ts与.d.cts类型声明及 sourcemap且通过external: [/^[^./]/]将tiptap/core、tiptap/pm等外部依赖排除在打包之外交由消费方peerDependencies提供。这些子路径映射在 package.json 的exports字段中有完整声明同时该文件还列出了peerDependenciestiptap/core与tiptap/pm均为^3.0.9与 Fork 基线版本匹配scripts.buildtsdownprepublishOnly为pnpm build保证发布前自动构建scripts.lintbiome formateslint --fixpackageManagerpnpm10.27.0。核心源码Table 节点与全部命令 APIsrc/table/table.ts 定义了Table节点content: tableRow、tableRole: table、isolating: true、group: block通过parseHTML识别table标签renderHTML借助 utilities/createColGroup.ts 生成colgroup并自动计算表格width或min-width。Table 扩展全部配置项配置项默认值说明HTMLAttributes{}渲染到table元素上的 HTML 属性如{ class: foo }resizablefalse是否启用列宽拖拽调整需编辑器处于可编辑状态handleWidth5列宽拖拽手柄的宽度pxcellMinWidth25单元格最小宽度pxViewTableView渲染表格的 NodeView 类可自定义替换lastColumnResizabletrue是否允许调整最后一列的宽度allowTableNodeSelectionfalse是否允许选中整个表格节点onRowActionClick—行操作菜单触发回调({ rowIndex, view, position? }) voidonColumnActionClick—列操作菜单触发回调({ colIndex, view, position? }) void其中onRowActionClick/onColumnActionClick是 Cherry Studio 在 Fork 基线上新增的回调用于在行/列首触发操作菜单position携带可选的悬浮坐标供 UI 层定位菜单弹层。完整命令 API19 个命令通过declare module tiptap/core的CommandsReturnType接口声明Table 节点注册了以下命令全部通过editor.commands.*或editor.chain().*调用命令签名 / 示例底层实现insertTable{ rows 3, cols 3, withHeaderRow true }如insertTable({ rows: 5, cols: 4, withHeaderRow: true })createTable见 utilities/createTable.tsaddColumnBeforeaddColumnBefore()addColumnBeforeaddColumnAfteraddColumnAfter()addColumnAfterdeleteColumndeleteColumn()deleteColumnaddRowBeforeaddRowBefore()addRowBeforeaddRowAfteraddRowAfter()addRowAfterdeleteRowdeleteRow()deleteRowdeleteTabledeleteTable()deleteTablemergeCellsmergeCells()mergeCellssplitCellsplitCell()splitCelltoggleHeaderColumntoggleHeaderColumn()toggleHeader(column)toggleHeaderRowtoggleHeaderRow()toggleHeader(row)toggleHeaderCelltoggleHeaderCell()toggleHeaderCellmergeOrSplitmergeOrSplit()先mergeCells失败则splitCellsetCellAttributesetCellAttribute(align, right)setCellAttrgoToNextCellgoToNextCell()goToNextCell(1)goToPreviousCellgoToPreviousCell()goToNextCell(-1)fixTablesfixTables()fixTablessetCellSelectionsetCellSelection({ anchorCell: 1, headCell: 2 })CellSelection.create这些命令大多是对 ProseMirror 官方tiptap/pm/tables模块的薄封装。其中insertTable是 Cherry Studio 特有的增强点它会检查tableCell扩展的allowNestedNodes选项当该选项为false时禁止在深度大于 1 的嵌套节点列表、引用块、表格等内部插入表格从源头避免嵌套表格带来的结构与序列化问题。键盘快捷键与 ProseMirror 插件addKeyboardShortcuts()注册了 6 组快捷键Tab跳到下一个单元格若已是最后一个单元格则自动追加一行并跳入新行addRowAfter().goToNextCell()Shift-Tab跳回上一个单元格Backspace/Mod-Backspace/Delete/Mod-Delete当全部单元格被选中时删除整个表格见 utilities/deleteTableWhenAllCellsSelected.ts。addProseMirrorPlugins()按条件注入两个插件当resizable为 true 且编辑器可编辑时注入columnResizing透传handleWidth、cellMinWidth、lastColumnResizable始终注入tableEditing透传allowTableNodeSelection。自定义 NodeViewTableViewTableView.ts 负责表格的 DOM 渲染。addNodeView()在默认TableView时会把onRowActionClick/onColumnActionClick一并传入构造器若开发者自定义了View则仅透传(node, cellMinWidth, view)三个构造参数。TableView 配合 utilities/ 目录下的createCell.ts、createColGroup.ts、getBorderWidth.ts、selectionBounds.ts、isCellSelection.ts、getTableNodeTypes.ts、colStyle.ts等工具完成列组生成、边框宽度推导、单元格创建与选区边界计算等底层工作。类型扩展tableRolesrc/types.ts 为tiptap/core的NodeConfig扩展了可选的tableRole字段允许字符串或函数默认table。Table节点通过extendNodeSchema将这个字段写入 ProseMirror schema供tiptap/pm/tables的表格相关插件识别节点角色。TableKit一键装配全套表格扩展src/kit/index.ts 提供TableKit聚合扩展一次注册即可装配 Table、TableCell、TableHeader、TableRow 四个节点推荐用于快速集成import { TableKit } from cherrystudio/extension-table-plus new Editor({ extensions: [ TableKit.configure({ table: { HTMLAttributes: { class: table }, resizable: true }, tableCell: { HTMLAttributes: { class: table-cell } }, tableHeader: { HTMLAttributes: { class: table-header } }, tableRow: { HTMLAttributes: { class: table-row } } }) ] })TableKitOptions中每个子项类型为Partial对应Options | false——传false表示不注册对应节点例如只需要表格主体而不要表头节点时可写tableHeader: false。其实现通过Extension.create的addExtensions()按配置逐个configure并收集返回。在 Cherry Studio 中的真实装配方式Cherry Studio 的富文本编辑器没有直接使用TableKit而是分别引入四个节点精确控制每个节点的配置见 createExtensions.tsimport { TableCell, TableHeader, TableRow } from cherrystudio/extension-table-plus // ... MarkdownTable.configure({ resizable: true, allowTableNodeSelection: true, onRowActionClick, onColumnActionClick }), TableRow, TableHeader, TableCell.configure({ allowNestedNodes: false }),这里的关键细节MarkdownTablesrc/renderer/components/RichEditor/extensions/markdownTable.ts基于Table扩展并补充 GFM 表格的 markdown parse/serialize 钩子开启resizable支持列宽拖拽、开启allowTableNodeSelection支持整表选中并注入行/列操作菜单回调TableCell显式配置allowNestedNodes: false与insertTable命令中的嵌套检查逻辑配合保证编辑器内不会出现嵌套表格onRowActionClick/onColumnActionClick由上层 hook 注入用于在行首/列首弹出增删行列等操作菜单。值得注意的是该扩展工厂同时被生产环境的useRichEditor与 Markdown 往返测试共用测试通过同一套扩展集构建编辑器 schema确保测试 schema 与生产永不漂移——这正是当初 GFM 表格序列化悄悄损坏而未被发现的原因也是markdownTable.ts需要单独存在的直接背景。历史版本要点CHANGELOG-OLDv3.0.11 之前的变更记录在上游 Tiptap 生命周期内的演进值得关注的关键点v2.8.0 / 3.0.1131c7d0将 table-header、table-cell、table-row 全部并入tiptap/extension-table单包其余包仅作 re-export并新增TableKit聚合扩展旧包tiptap/extension-table-header等可npm uninstall移除。v3.0.1991f43c新增TableView类导出本仓库 src/index.ts 同样 re-export 了它。v2.10.07619215即使列未被用户手动调整也强制应用cellMinWidth修复 #5435。v2.5.6c7f5550为表格设置正确的min-width修复 #5217。v3.0.11b4c82b / 89bd9c7使用 pnpm 包别名做 monorepo 版本锁定强制使用类型导入避免 bundler 将 TS 类型导入打包进 dist 的 index.js。v3.0.1a92f4a6构建迁移至 tsup/tsdown不再支持 UMD 产物。这些历史决策直接影响了当前仓库的实现如强制类型导入与 tsdown 构建配置一脉相承TableView导出与 NodeView 自定义能力至今保留。常见问题排查思路结合 CHANGELOG 中两次构建修复与源码实现可沉淀以下排查经验dts 构建失败检查包目录是否有独立的 tsconfig.json确认declaration: true、outDir、rootDir配置正确避免引用不存在的tsconfig.build.json。表格无法在嵌套节点内插入确认tableCell是否配置了allowNestedNodes: false此时insertTable在 depth 1 处会返回 false——这是设计行为而非 bug。列宽拖拽不生效确认resizable: true且编辑器editablecolumnResizing插件仅在两者同时成立时注册。GFM 表格丢失cherrystudio/extension-table-plus本身不含 markdown 钩子需按 Cherry Studio 的做法在消费侧补充 markdownTable.ts 之类的扩展。小结cherrystudio/extension-table-plus以tiptap/extension-tablev3.0.9 为基线通过 v3.0.12 的 changesets 基线发布纳入 Cherry Studio 的 monorepo 发布体系并借两次 tsconfig 修复解决了 CI 构建问题。源码层面它完整继承了 Tiptap 表格的 19 个命令、双插件体系与自定义 NodeView 能力同时新增onRowActionClick/onColumnActionClick回调与insertTable嵌套防护再配合消费侧的MarkdownTable扩展补足 GFM 往返序列化构成 Cherry Studio 富文本编辑器中稳定、可扩展的表格能力底座。【免费下载链接】cherry-studio Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考