ConvertKit: DOCX 感知编辑器扩展

Available in Start planBetav0.1.0

Beta 功能

ConvertKit 的 API、属性名称和捆绑扩展正在逐步稳定,但在正式可用之前仍可能会有 一些调整。如果你依赖 ConvertKit,请锁定准确的包版本,并在采用它之前先阅读 无法工作的内容 章节。

这会做什么

DOCX 文件包含了基础 Tiptap 扩展并不了解的格式元数据:段落间距(以 twips 为单位)、表格行高规则、图片裁剪百分比、单元格垂直对齐、各边框宽度。如果你用原生 Tiptap 渲染 DOCX 内容,这些信息会丢失或渲染错误。

ConvertKit 是一个单一扩展,用于配置你的编辑器以导入 DOCX 内容。 引入它后,你将获得:

  • 28 个捆绑扩展已连接好,用于处理 Convert API 生成的一切内容。
  • 自定义的 Paragraph、Heading、Image、Table、TableRow、TableCell 和 TableHeader 扩展,可接受 DOCX 特定属性。
  • 在编辑器创建时注入的一小段 CSS 重置,用于规范浏览器默认样式,使 DOCX 间距值能够准确渲染。
  • 每个捆绑扩展都可以单独配置或禁用。

安装

npm i @tiptap-pro/extension-convert-kit@^0.1.0

工作原理

三个部分:

  1. 一组预配置扩展。 ConvertKit 注册了一组精选的 Tiptap 扩展(用于 marks 和 lists 的基础扩展,以及针对段落、标题、图片和表格的支持 DOCX 的自定义覆盖)。你可以在 ConvertKit.configure({…}) 中为任意槽位传入 false 来排除对应扩展。
  2. schema 节点上的 DOCX 属性。 支持 DOCX 的覆盖扩展会继承其基础对应项,并声明额外属性(段落间距、图片裁剪、表格单元格属性)。转换服务会生成这些属性;ConvertKit 的节点会将其渲染出来。
  3. 作用域化 CSS 重置。 ConvertKit 注入了一小段 CSS 重置,用于规范段落外边距、表格行高、单元格垂直对齐以及精确高度行的浏览器默认值,从而使 DOCX 间距值能够准确渲染。编辑器销毁时,这个重置会被移除。
import { Editor } from '@tiptap/core'
import { ConvertKit } from '@tiptap-pro/extension-convert-kit'

const editor = new Editor({
  extensions: [ConvertKit],
})

自定义或禁用特定子扩展:

new Editor({
  extensions: [
    ConvertKit.configure({
      heading: { levels: [1, 2, 3] }, // 仅允许 H1–H3
      codeBlock: false, // 完全移除 code-block 节点
      table: false, // 替换为另一套表格栈
    }),
  ],
})

能工作

内置扩展

ConvertKit 在单个 @tiptap-pro/extension-convert-kit 包中捆绑了 28 个扩展。下面的列表是参考资料,方便你了解编辑器 schema 中会出现什么;这些扩展不需要单独安装或注册。

CategoryExtensions
CoreDocument, Text, Paragraph, Heading
MarksBold, Italic, Underline, Strike, Code, Link
BlocksBlockquote, HorizontalRule, HardBreak, CodeBlock, PageBreak
ListsBulletList, OrderedList, ListItem, ListKeymap
MediaImage*
StylingTextStyleKit (TextStyle, Color, BackgroundColor, FontFamily, FontSize, LineHeight), TextAlign, Highlight (multicolor), Superscript, Subscript
UI helpersDropcursor, Gapcursor
TablesConvertTableKit (Table*, TableRow*, TableCell*, TableHeader*)

标有 * 的扩展是 Tiptap 基础扩展的自定义、支持 DOCX 的覆盖版本。请查看下一节了解它们新增了什么。

在 Tiptap 基础扩展之上新增的 DOCX 属性

ExtensionAdded attributesSource
ParagraphspacingBefore, spacingAfter, lineHeight, fontSize, indent, firstLineIndent, contextualSpacingparagraph mark 上的 DOCX w:pPr + w:rPr
Headingsame as Paragraphparagraph mark 上的 DOCX w:pPr + w:rPr
ImagecropTop, cropBottom, cropLeft, cropRightdrawings 上的 DOCX a:srcRect
Tablewidth, indent (+ cellMinWidth: 1 default, vs Tiptap's 25)DOCX w:tblW, w:tblInd, w:tblGrid
TableRowheight, heightRule (exact / atLeast)DOCX w:trHeight + w:hRule
TableCell / TableHeaderbackground, verticalAlign, per-side border (width / style / color on top/bottom/left/right)DOCX w:tcPr

CSS 重置会规范什么

该重置作用于 .tiptap,并调整了几个区域;否则浏览器默认样式会与 DOCX 间距不对齐:

  • 段落间距。 段落以及 * 后代元素的上、下外边距会被清零,因此 DOCX 作者设置的间距值(其自身包含前后值)能够准确渲染,而不会叠加在浏览器默认外边距之上。
  • 表格行高和单元格内边距。 默认值会被收紧,因此声明了特定行高的表格能够按那些高度实际渲染。
  • 精确高度行会裁剪溢出内容。 具有 heightRule: "exact" 的行会通过内部单元格包装器裁剪溢出内容;比声明行高更高的内容会被隐藏,这与 Word 的行为一致。
  • 尾随换行伪影。 ProseMirror 的尾随换行会设置为零外边距和内边距,因此不会在段落末尾引入虚假的空白。

如果你需要覆盖其中任何一项,请在 .tiptap 内针对你自己的选择器进行设置——你的规则会覆盖重置。

一些小但重要的默认值

  • 表格的 cellMinWidth: 1。Tiptap 默认是 25,这会把 DOCX 的间距列(1–15 px 的窄列,用于布局)放大成可见的间隙。ConvertKit 将最小值上限限制为 1 px。
  • 使用 types: ['paragraph', 'heading'] 配置 textAlign,使 DOCX 段落两种对齐块都能传递过去。
  • 使用 multicolor: true 配置 highlight,从而让 DOCX w:highlight 的颜色原样传递。

将无法工作

这些是有意的限制

这些都不是 bug。它们是该功能明确记录的边界。如果你的工作流依赖下面任何一项, 那么这个功能并不适合你。

  • 浮动表格。 DOCX w:tblpPr(相对于锚点定位并带文字环绕的表格)会渲染为普通的纵向堆叠内联表格。定位元数据会被转换器丢弃。这是刻意为之;浮动布局的保真度不在范围内。
  • 负表格缩进。 小于零的 w:tblInd 值会保留在 attrs.indent 中,但在渲染时会被限制为 0。Word 会把负缩进渲染到页面边距沟槽中;浏览器没有这个沟槽,因此渲染它们会把表格推到页面边缘之外。
  • 精确行高溢出。 具有 heightRule: "exact" 的行会通过 .cell-content 包装器的 overflow: hidden 裁剪溢出内容。比声明高度更高的内容不可见。
  • 过宽表格宽度。 声明宽度超过容器的表格会以 max-width: 100% 渲染。声明的宽度会保留在 attrs.width 中,但视觉上会被限制。
  • 多页布局。 ConvertKit 不进行分页。如果你需要跨页表格、真实的页面容器、页眉/页脚,或者希望页面分隔符在视觉上生效,请将 ConvertKit 与 Pages extension 配合使用,并为表格扩展使用 PagesTableKit
  • 脚注、尾注、评论。 不会渲染。转换器会把它们作为段落输出或直接丢弃;ConvertKit 不会添加任何脚注/评论 UI。

你应该预期到

  • 编辑器挂载期间会应用一个小型、作用域化的 CSS 重置,并在编辑器销毁时移除。同一页面上的多个由 ConvertKit 驱动的编辑器会共享这份重置(它只插入一次)。
  • 在你的应用中,DOCX 导入内容会获得一致的渲染。带有非零 DOCX 裁剪的图片会真正裁剪显示;当列变窄时,具有 heightRule: "atLeast" 的行会垂直增长;verticalAlign: "center" 的单元格会将内容居中。
  • 与其余 Tiptap Pro 扩展生态系统完全兼容。@tiptap-pro/extension-import-docx@tiptap-pro/extension-export-docx 是天然的配套项;如果你需要分页,@tiptap-pro/extension-pages-tablekit 可以叠加使用。
  • 紧凑的默认排版。 CSS 重置会清零段落外边距并应用紧凑的行高。如果你用 ConvertKit 处理的内容不是从 DOCX 导入的,请在其上叠加你自己的排版规则——这份重置是有意设计的,而不是我们期望直接呈现为美观的默认样式。

不要预期到

  • 超出节点属性级别的 DOCX 保真度。 ConvertKit 会接入 Convert API 生成的属性。如果转换器没有解析某个 DOCX 特性,ConvertKit 就无法渲染它。
  • 表格的自动缩放手柄。 ConvertKit 的 Table 扩展继承自 @tiptap/extension-table,并支持标准的缩放行为,但默认的 cellMinWidth: 1 意味着手柄可以把列拖到 1 px 宽,这通常不是你在手动编辑时想要的。将 DOCX 导入与手动创作混合使用时,请覆盖 table.cellMinWidth

ConvertKit 不覆盖的内容

ConvertKit 会将 Convert API 产出的所有特性作为 Tiptap 节点和标记来处理。某些导入的 DOCX 内容超出了这一范围,仍然需要在应用层处理:

  • 脚注和尾注引用。 导入 REST API 会返回脚注和尾注数据,但 编辑器扩展 不会展示这些内容,也没有任何 Tiptap 扩展可以渲染脚注标记或注释。参见 脚注和尾注
  • 多栏布局。 导入会为多栏章节生成 columnscolumn 节点,但没有可用于渲染它们的编辑器扩展。参见 页面结构
  • 目录。 会被导入为 tableOfContents 节点。除非你添加自定义扩展,否则会以普通内容形式渲染。参见 支持的功能
  • Word 样式。 样式名在导入时用于结构识别(标题、引用块、代码块)。文档样式目录中定义的视觉样式不会被应用。可将 ConvertKit 与 CSS 注入 配合,以保留样式目录;或者参见 Word 样式
  • 数学公式。 Convert API 不会解析 OMML。参见 数学公式
  • 字间距。 Convert API 会将字符间距按逐段标记提取为 textStyle.letterSpacing,但捆绑的 TextStyleKit 没有声明 letterSpacing 属性,因此该值在渲染时会被丢弃。CSS 注入 也不会提取它(只有导出侧编译才接受它)。若要渲染导入的字间距,请为 TextStyle 扩展一个 letterSpacing 属性,并在 renderHTML 中输出 style="letter-spacing: …"。参见 字体族和字号
  • 页面布局。 页面大小、页边距、页眉和页脚属于文档级别的事项。ConvertKit 负责渲染内容;可将其与 Pages 扩展 配合使用,以实现分页布局。

如需了解如何扩展 ConvertKit 的内置扩展,以及如何使用 CSS 为转换后的内容设置样式,请参阅 转换后内容样式 指南。

配置参考

每个键都是可选的。若要排除某个扩展,请传入 false。若要把部分选项对象传递给底层扩展的 configure(),请传入对应对象。每个键的默认值都是 {}

ConvertKit.configure({
  document: Record<string, never> | false,
  text: Record<string, never> | false,
  paragraph: Partial<ParagraphOptions> | false,
  heading: Partial<HeadingOptions> | false,
  blockquote: Partial<BlockquoteOptions> | false,
  bold: Partial<BoldOptions> | false,
  italic: Partial<ItalicOptions> | false,
  underline: Partial<UnderlineOptions> | false,
  strike: Partial<StrikeOptions> | false,
  code: Partial<CodeOptions> | false,
  link: Partial<LinkOptions> | false,
  bulletList: Partial<BulletListOptions> | false,
  orderedList: Partial<OrderedListOptions> | false,
  listItem: Partial<ListItemOptions> | false,
  listKeymap: Partial<ListKeymapOptions> | false,
  hardBreak: Partial<HardBreakOptions> | false,
  horizontalRule: Partial<HorizontalRuleOptions> | false,
  dropcursor: Partial<DropcursorOptions> | false,
  gapcursor: Record<string, never> | false,
  codeBlock: Partial<CodeBlockOptions> | false,
  image: Partial<ImageOptions> | false,
  textStyleKit: Partial<TextStyleKitOptions> | false,
  textAlign: Partial<TextAlignOptions> | false, // 默认:{ types: ['paragraph', 'heading'] }
  highlight: Partial<HighlightOptions> | false, // 默认:{ multicolor: true }
  superscript: Partial<SuperscriptExtensionOptions> | false,
  subscript: Partial<SubscriptExtensionOptions> | false,
  pageBreak: Partial<PageBreakOptions> | false,
  table: Partial<ConvertTableKitOptions> | false,
})

相关