设置转换后的内容样式

Beta

When you import a DOCX file into the editor, the document structure is preserved, but its visual appearance changes. This guide explains why, what style information is available, and how to control it.

For a quick overview of which features are supported at each stage, see the Supported features matrix. For detailed information about each feature, including which properties are extracted and which extensions are required, see the Content reference page.

为什么 Word 的样式不会被传递

Tiptap 是无头的。它会转换文档结构(节点、标记、属性),但不会应用 Word 的视觉主题。Word 文档使用分层样式系统:文档主题、命名样式定义(例如 “Heading 2”),以及对单个文本片段的直接格式覆盖。转换服务会翻译结构含义(“这是一个 Heading 2”),但不会翻译视觉呈现(“Heading 2 是 Aptos Light 13pt 蓝色”)。

你的编辑器使用你的 CSS 来渲染内容。编辑器中的 <h2> 会按照你的样式表对 h2 的定义来显示,而不是按照 Word 对 “Heading 2” 的定义来显示。

保留了哪些样式

转换服务会从 DOCX 中提取两类样式信息:

行内格式(文本节点上的标记)

这些会作为单个文本节点上的标记存储,并由标准 Tiptap 扩展自动渲染<span> 元素上的行内 style 属性:

样式标记属性需要的扩展HTML 输出
文本颜色textStyle.colorColorstyle="color: #FF0000"
字体族textStyle.fontFamilyFontFamilystyle="font-family: Courier New"
字体大小textStyle.fontSizeFontSizestyle="font-size: 14px"
背景色textStyle.backgroundColorBackgroundColorstyle="background-color: #FFFF00"
高亮highlight.colorHighlight(multicolor: true<mark style="background-color: yellow">
字符间距textStyle.letterSpacing需要扩展 TextStylestyle="letter-spacing: 2px"
加粗、斜体、下划线、删除线独立标记ConvertKit(内置)<strong>, <em>, <u>, <s>

行高不在此表中,因为 Word 将行距存储在段落上,而不是存储在文本运行上。导入器始终会将其作为段落节点属性输出;请参见下方的块级格式部分。

如果你安装了这些扩展(推荐集合见 ConvertKit),行内格式将自动渲染,无需额外的 CSS。

块级格式(段落和标题节点上的属性)

这些会作为 paragraphheading 节点上的属性存储。是否渲染取决于你安装了哪些扩展。

样式节点属性格式使用标准扩展使用 ConvertKit
文本对齐textAlign"left", "center", "right", "justify"需要 TextAlign已渲染(已包含 TextAlign)
段前间距spacingBefore数值(像素)不渲染margin-top: Npx
段后间距spacingAfter数值(像素)不渲染margin-bottom: Npx
左缩进indent数值(像素)不渲染padding-left: Npx
首行缩进firstLineIndent数值(像素)不渲染text-indent: Npx
行高lineHeight字符串(例如 "1.5""24px"不渲染line-height: <value>
字体大小(段落标记)fontSize字符串(例如 "11pt"不渲染(由标记级 textStyle.fontSize 通过 FontSize 渲染)font-size: <value>(用于空白段落)
上下文间距contextualSpacing布尔值不渲染data-contextual-spacing="true" + 注入的 CSS 规则,用于抑制相邻上下文间距段落之间的外边距

Schema validation strips unrecognized attributes

当你调用 setEditorContent() 时,ProseMirror 会根据编辑器的 schema 校验内容。未在节点规范中定义的属性会被移除。context.content 中的原始 JSON 仍然包含所有属性,但一旦内容加载进编辑器后,它们就会丢失。要保留这些属性,请安装 ConvertKit(它会为上面所有属性扩展 schema),或者自行扩展节点 schema。

通过编辑器样式尽量还原 Word

即使不能保留每个段落属性,你仍然可以通过 CSS 让外观接近 Word。

基础标题样式

Word 的默认标题样式使用特定的字体、字号和颜色。你可以在编辑器中这样近似:

.tiptap h1 {
  font-family: 'Aptos Light', sans-serif;
  font-size: 16pt;
  font-weight: bold;
  color: #2E74B5;
  margin-top: 12pt;
  margin-bottom: 6pt;
  line-height: 1.15;
}

.tiptap h2 {
  font-family: 'Aptos Light', sans-serif;
  font-size: 14pt;
  font-weight: bold;
  color: #2E74B5;
  margin-top: 12pt;
  margin-bottom: 6pt;
  line-height: 1.15;
}

.tiptap h3 {
  font-family: 'Aptos', sans-serif;
  font-size: 13pt;
  font-weight: bold;
  color: #2E74B5;
  margin-top: 12pt;
  margin-bottom: 6pt;
  line-height: 1.15;
}

正文文本

Word 的默认 “Normal” 样式使用 Aptos 11pt,段后间距为 10pt,行高为 1.15:

.tiptap p {
  font-family: 'Aptos', sans-serif;
  font-size: 11pt;
  margin-top: 0;
  margin-bottom: 10pt;
  line-height: 1.15;
}

表格

Word 表格通常有细边框且没有单元格内边距。编辑器的默认表格样式可能不同:

.tiptap table {
  border-collapse: collapse;
  width: 100%;
}

.tiptap td,
.tiptap th {
  border: 1px solid #d0d0d0;
  padding: 4px 8px;
  vertical-align: top;
}

.tiptap th {
  font-weight: bold;
  background-color: #f5f5f5;
}

列表

Word 为嵌套列表使用特定的缩进。在 CSS 中可这样匹配:

.tiptap ul,
.tiptap ol {
  padding-left: 24px;
  margin-top: 0;
  margin-bottom: 2pt;
}

.tiptap li {
  line-height: 1.15;
}

.tiptap li > ul,
.tiptap li > ol {
  margin-top: 0;
  margin-bottom: 0;
}

使导出样式与编辑器样式保持一致

导出扩展在写入 DOCX 文件时会应用自己的默认样式。这些默认值(Normal 使用 Aptos 11pt,以及特定的标题字号和颜色)可能与用户在编辑器中看到的内容不一致。为了让导入、编辑和导出之间的外观保持一致,请将编辑器 CSS 与导出的 styleOverrides 对齐:

ExportDocx.configure({
  styleOverrides: {
    paragraphStyles: [
      {
        id: 'Normal',
        name: 'Normal',
        run: { font: 'Aptos', size: 22 }, // 11pt,单位为半磅
        paragraph: {
          spacing: { after: 200, line: 276 }, // 段后 10pt,行高 1.15
        },
      },
      {
        id: 'Heading1',
        name: 'Heading 1',
        basedOn: 'Normal',
        next: 'Normal',
        run: { font: 'Aptos Light', size: 32, bold: true, color: '2E74B5' },
        paragraph: {
          spacing: { before: 240, after: 120, line: 276 },
        },
      },
    ],
  },
})

如果你在编辑器 CSS 中使用了不同的字体或字号,请同步更新导出的 styleOverrides,以便导出的 DOCX 反映用户在编辑时所看到的样式。

为不支持的属性扩展扩展

ConvertKit 已经为 Convert API 生成的 DOCX 特定属性扩展了 ParagraphHeadingImage 以及表格栈。如果你使用 ConvertKit,上表中的段落和标题属性会自动渲染;你不需要自己编写扩展。

下面的模式适用于 ConvertKit 未覆盖的属性(典型情况是 textStyle.letterSpacing,Convert API 会提取它,但没有内置扩展来渲染),或者适用于选择不使用 ConvertKit、需要自己实现的编辑器。

Roadmap

将剩余的 text-style 属性(从 letterSpacing 开始)纳入 ConvertKit 捆绑的 TextStyleKit 已列入路线图,因此你将在未来的版本中开箱即用。在那之前,如下所示自己扩展 TextStyle,今天就能获得相同的渲染效果,而无需等待上游变更。

import { TextStyle } from '@tiptap/extension-text-style'

const TextStyleWithLetterSpacing = TextStyle.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      letterSpacing: {
        default: null,
        parseHTML: (element) => element.style.letterSpacing || null,
        renderHTML: (attributes) => {
          if (!attributes.letterSpacing) return {}
          return { style: `letter-spacing: ${attributes.letterSpacing}` }
        },
      },
    }
  },
})

然后禁用内置的 TextStyle,并将你扩展后的版本与 ConvertKit 一起添加:

ConvertKit.configure({
  textStyleKit: { textStyle: false },
}),
TextStyleWithLetterSpacing,

同样的模式也适用于转换器输出的任何其他属性,只要没有扩展来渲染它。关于扩展现有扩展的完整机制,请参阅 custom extensions guide

在加载前拦截内容

如果你想在内容进入编辑器之前转换或提取样式数据,请使用 onImport 回调,而不是自动的 setEditorContent()

editor.chain().importDocx({
  file,
  onImport(context) {
    if (context.error) {
      console.error(context.error)
      return
    }

    // context.content 包含带有所有属性的原始 JSON
    // 你可以在这里检查、转换或提取样式数据
    const doc = context.content

    // 示例:记录所有段落间距值
    function walkNodes(nodes) {
      for (const node of nodes) {
        if (node.type === 'paragraph' && node.attrs) {
          console.log('间距:', node.attrs.spacingBefore, node.attrs.spacingAfter)
        }
        if (node.content) walkNodes(node.content)
      }
    }
    walkNodes(doc.content || [])

    // 然后加载到编辑器中(这会触发 schema 校验)
    context.setEditorContent()
  },
}).run()

Automatic Style Import

If you don’t want to manually write CSS to approximate the appearance of Word, then the experimental CSS injection feature will extract the named style catalog from the imported document (Heading1, Normal, Quote, etc.) and return it as CSS objects, or inject it into the page as a scoped <style> tag. This makes the result closer to how the source document appears in Word, without having to write CSS manually for each document.

CSS injection covers 16 selectors (block-level: p, h1h6, blockquote, ul li, ol li; inline: strong, em, u, s, a, code) and 11 typography properties (fontSize, color, fontFamily, fontWeight, fontStyle, textDecoration, backgroundColor, textAlign, marginTop, marginBottom, lineHeight). Inline overrides on individual runs and arbitrary selectors are intentionally out of scope; please refer to that page for a complete list of notes.

These two features can be used together: ConvertKit will ensure that imported node attributes are preserved in the editor’s schema and rendered correctly, while CSS injection will bring the document’s typography catalog in as cascading CSS. The hand-written CSS approach earlier in this guide is still useful when neither of these features is sufficient.