在编辑器中导入 .docx

Available in Start planBetav0.8.0

使用 @tiptap-pro/extension-import-docx 编辑器扩展将 .docx 文件转换为 Tiptap JSON 非常简单,该扩展可直接集成到您的 Tiptap 编辑器中。

您也可以通过 REST API 导入。这两种方式使用相同的转换服务,并生成完全相同的内容输出。主要区别如下:

编辑器扩展REST API
运行位置您的编辑器(客户端)Tiptap 云端(服务器端)
脚注和尾注不会在回调中暴露在响应中返回
页眉和页脚通过 Pages 扩展自动应用作为单独的数据字段返回
未知内容按您的编辑器 schema 重写原始 JSON,不进行 schema 过滤

如果您需要服务器端处理、访问脚注或尾注数据,或需要未过滤的原始 JSON,请选择 REST API。

安装 DOCX Import 扩展

Conversion 扩展发布在 Tiptap 的私有 npm 注册表中。请按照 私有注册表指南 集成这些扩展。

安装 Tiptap Import 扩展包:

npm i @tiptap-pro/extension-import-docx

请确保您的编辑器包含处理 DOCX 内容所需的所有 Tiptap 扩展。完整的扩展列表、配置以及各自处理的内容,请参见 ConvertKit 页面。

所需扩展

安装 ConvertKit — 它捆绑了导入器所需的所有扩展:标准 nodes 和 marks、用于颜色/字体/大小/行高/背景的 TextStyleKit、预配置为 ['paragraph', 'heading']TextAlign、带有 multicolor: trueHighlight、带有 DOCX 裁剪属性的 Image、用于 DOCX 感知表格的 ConvertTableKit,以及 PageBreak

import { ConvertKit } from '@tiptap-pro/extension-convert-kit'

new Editor({ extensions: [ConvertKit] })

如果您需要将捆绑扩展中的某一个替换为自己的实现,可以通过 ConvertKit.configure({ slot: false }) 禁用任意单个 slot(例如,与 Pages extension 配合时,设置 table: false,并改为注册来自 @tiptap-pro/extension-pages-tablekitTableKit)。完整的 slot 列表请参见 ConvertKit reference

配置

将 Import 扩展添加到您的编辑器设置中。

import { ImportDocx } from '@tiptap-pro/extension-import-docx'

const editor = new Editor({
  extensions: [
    // 其他扩展 ...
    ImportDocx.configure({
      appId: 'your-app-id', // 您的 Convert App ID(请参见 Tiptap Cloud 设置)
      token: 'your-jwt', // 用于身份验证的 JWT(请参见身份验证文档)
      imageUploadConfig: {
        url: 'https://your-image-upload-endpoint.com',
      },
    }),
    // 其他扩展 ...
  ],
  // 其他编辑器设置 ...
})
属性描述
appId您的 Tiptap Convert 应用的 ID(可在您 Tiptap 账户的 conversion settings 中找到)
token由您的服务器为 Convert 服务生成的 JWT 身份验证令牌。(有关获取和使用这些凭据的详细信息,请参见 Authentication guide。)
imageUploadConfig用于导入期间图片上传的结构化配置。详情请参见 图片上传配置
verbose一个 string | number 配置属性,可帮助您控制导入过程中的诊断输出级别。这对于调试或更深入了解转换过程中发生的情况尤其有用。更多信息请参见 详细输出

图片上传配置

imageUploadConfig 选项让您可以配置转换服务如何将 DOCX 文件中找到的图片上传到您的服务器。这取代了已弃用的 imageUploadCallbackUrl 字符串选项,并使您能够完全控制请求头、HTTP 方法和查询参数。

ImportDocx.configure({
  appId: 'your-app-id',
  token: 'your-jwt',
  imageUploadConfig: {
    url: 'https://your-server.com/upload-image',
    headers: {
      Authorization: 'Bearer your-upload-token',
      'X-Custom-Header': 'custom-value',
    },
    method: 'PUT',
    queryParams: {
      bucket: 'images',
      folder: 'docx-imports',
    },
  },
})
属性类型必需描述
urlstring接收上传图片的端点 URL。必须是有效的 HTTP 或 HTTPS URL。
headersHeadersInit转换服务在上传图片时转发的额外 HTTP 请求头。适用于身份验证(例如 Bearer token、API key)。接受普通对象、Headers 实例或键值对数组。
methodstring用于上传图片的 HTTP 方法。若省略,则默认为转换服务自身的默认值。
queryParamsRecord<string, string>由转换服务附加到上传 URL 的查询参数。

从 imageUploadCallbackUrl 迁移

imageUploadCallbackUrl 选项已弃用。请改用 imageUploadConfig

// 之前(已弃用)
ImportDocx.configure({
  imageUploadCallbackUrl: 'https://your-server.com/upload-image',
})

// 之后
ImportDocx.configure({
  imageUploadConfig: {
    url: 'https://your-server.com/upload-image',
  },
})

如果同时提供了 imageUploadConfigimageUploadCallbackUrl,则以 imageUploadConfig 为准,并会输出一条控制台警告。

导入 DOCX 文件

配置好扩展后,您就可以导入用户选择的 DOCX 文件。

基础导入

最简单的方法是直接将文件传递给 importDocx 命令。在这里,它会用转换后的内容替换当前编辑器内容,并聚焦编辑器:

editor.chain().focus().importDocx({ file }).run()

在大多数情况下,这一行代码就足以让用户导入 .docx 文件。该扩展会负责将文件发送到转换端点、获取转换后的 Tiptap JSON,并将其插入编辑器。

导入处理

为了在导入过程结束后拥有更多控制权,您可以使用 onImport 回调来处理转换结果。此回调会提供转换后的内容、发生的任何错误,以及一个名为 setEditorContent 的函数,用于将 context.content 中的内容插入编辑器。如果您不提供 onImport 回调,扩展会自动将内容插入编辑器,但您将无法处理错误或加载状态等其他事项。

editor
  .chain()
  .importDocx({
    file,
    onImport(context) {
      // 判别联合:先处理失败分支。
      if (context.error) {
        showErrorToast({ message: context.error.message })
        return
      }

      const { setEditorContent, content } = context

      // 您可以在插入之前修改内容。
      content.content?.push({
        type: 'paragraph',
        content: [{ type: 'text', text: 'Hello!' }],
      })

      // 例如,您可以更改应用程序的加载状态
      isLoading = false

      // 插入(可能已修改的)内容。将其传递给回调中提供的
      // `setEditorContent` 函数,它会正确处理相关联动
      //(包括在注册了 Pages 时自动应用页眉/页脚)。
      setEditorContent(content)
    },
  })
  .focus()
  .run()

在上面的示例中,我们控制的操作包括:

操作描述
错误处理如果转换失败,您可以显示提示消息或记录错误。
内容修改您可以根据需要插入额外节点、移除某些节点,或以其他方式调整转换后的 Tiptap JSON。
编辑器插入如果您想依赖扩展的默认插入行为(替换编辑器内容),可以调用回调中提供的 setEditorContent() 函数。如果您自行修改了内容,则必须手动使用 editor.commands.setContent(content) 进行设置。

页眉与页脚

当导入包含页眉和页脚的 .docx 文件时,如果安装了 Pages 扩展,导入扩展会自动检测并应用它们。没有 Pages,页眉/页脚数据仍会返回到你的 onImport 回调(见下面的手动处理)中,但没有地方渲染——请安装 Pages 以便它们在编辑器中可见。

自动处理

如果你的编辑器中注册了 Pages 扩展,在调用 setEditorContent() 时,页眉和页脚会自动应用:

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

      // 页眉和页脚会与正文内容一起自动应用
      context.setEditorContent()
    },
  })
  .focus()
  .run()

如果没有安装 Pages 扩展,页眉和页脚数据仍可在 onImport 回调中获取,但不会自动应用到编辑器中。

手动处理

onImport 回调提供所有页眉和页脚字段,供手动处理:

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

      // 直接访问页眉/页脚数据
      const {
        header, // 默认页眉(Tiptap JSON 或 null)
        footer, // 默认页脚(Tiptap JSON 或 null)
        headerFirstPage, // 首页页眉(Tiptap JSON 或 null)
        footerFirstPage, // 首页页脚(Tiptap JSON 或 null)
        headerOdd, // 奇数页页眉(Tiptap JSON 或 null)
        footerOdd, // 奇数页页脚(Tiptap JSON 或 null)
        headerEven, // 偶数页页眉(Tiptap JSON 或 null)
        footerEven, // 偶数页页脚(Tiptap JSON 或 null)
      } = context

      // 设置正文内容,不自动应用页眉/页脚
      editor.commands.setContent(context.content)

      // 通过 Pages 扩展命令手动应用页眉和页脚
      if (header) editor.commands.setHeader(header)
      if (footer) editor.commands.setFooter(footer)

      if (headerFirstPage || footerFirstPage) {
        editor.commands.setDifferentFirstPage(true)
        if (headerFirstPage) editor.commands.setHeaderFirstPage(headerFirstPage)
        if (footerFirstPage) editor.commands.setFooterFirstPage(footerFirstPage)
      }

      if (headerOdd || headerEven || footerOdd || footerEven) {
        editor.commands.setDifferentOddEven(true)
        if (headerOdd) editor.commands.setHeaderOdd(headerOdd)
        if (headerEven) editor.commands.setHeaderEven(headerEven)
        if (footerOdd) editor.commands.setFooterOdd(footerOdd)
        if (footerEven) editor.commands.setFooterEven(footerEven)
      }
    },
  })
  .focus()
  .run()

可用字段

字段描述
header默认页眉内容,格式为 Tiptap JSON,或 null
footer默认页脚内容,格式为 Tiptap JSON,或 null
headerFirstPage首页页眉(当 Word 中启用“首页不同”时),或 null
footerFirstPage首页页脚(当 Word 中启用“首页不同”时),或 null
headerOdd奇数页页眉(当 Word 中启用“奇偶页不同”时),或 null
footerOdd奇数页页脚(当 Word 中启用“奇偶页不同”时),或 null
headerEven偶数页页眉(当 Word 中启用“奇偶页不同”时),或 null
footerEven偶数页页脚(当 Word 中启用“奇偶页不同”时),或 null

脚注与尾注

脚注和尾注在扩展的 onImport 回调中不可用

REST API 会在其响应中返回脚注和尾注数据(作为 footnotesendnotes 字段),但编辑器扩展目前不会在 onImport 回调的 ImportContext 中提供这些内容。如果你需要脚注/尾注数据,请直接使用 REST API,并从响应中提取 footnotesendnotes 字段。

导入 API 可以从 DOCX 文件中提取脚注和尾注内容。文档正文中的内联引用表示为 footnoteReferenceendnoteReference 节点,每个节点都有一个 noteId 属性。不过,这些节点需要一个自定义扩展才能在编辑器中渲染。

详细输出

DOCX 导入扩展提供了一个 verbose 配置属性,用于帮助你控制导入过程中诊断输出的级别。这对于调试或更深入地了解转换过程中发生了什么特别有用。

verbose 属性是一个位掩码数字,用于确定输出哪些类型的日志消息。该扩展使用以下级别:

级别描述
1log一般信息日志
2warn警告
4error错误

Verbose 位掩码

你可以将各级别的值相加来组合它们。例如,verbose: 3 将同时启用 log(1)和 warn(2)消息。

详细输出除了 data 属性之外,还会再提供一个名为 logs 的属性,其中包含 infowarnerror 属性;它们各自都是数组,包含与对应详细级别相关的全部信息。

{
  "data": {
    "content": {
        // Tiptap JSON
    }
  },
  "logs": {
    "info": [],
    "warn": [
      {
        "message": "在媒体文件中未找到图像文件",
        "fileName": "image1.gif",
        "availableMediaFiles": []
      }
    ],
    "error": [
      {
        "message": "图像上传失败:一般错误",
        "fileName": "image1.gif",
        "url": "https://your-image-upload-endpoint.com",
        "error": "无法连接。计算机是否可以访问该 url?",
        "context": "uploadImage general error"
      }
    ]
  }
}

支持与限制

有关管道各阶段支持哪些文档特性的详细说明,请参见支持的特性矩阵。