探索 Tiptap V3 的最新功能

使用 .docx 导入和导出自定义节点

Available in Start planBeta

@tiptap-pro/extension-export-docx@tiptap-pro/extension-import-docx 扩展的最大优点之一是能够定义您 Tiptap 模式中的自定义节点应如何在 DOCX 中呈现。

这使您能够在导出的 Word 文件中保留特定于应用程序的内容。

自定义节点规范

自定义节点转换器必须遵循底层 DOCX 生成库的要求。在实践中,自定义转换函数返回的 DOCX 应该是该节点的允许元素之一:一个 Paragraph 类(或一个 Paragraph 类的数组)、一个 Table 类,或如果节点应该在输出中被跳过则返回 null

导出自定义节点至 .docx

调用 editor.exportDocx() 时,可以在 ExportDocxOptions 参数中传递一个自定义节点定义数组。每个定义指定节点类型和渲染函数。

为了举例,假设您的编辑器有一个自定义节点类型 hintbox(一个提示框样式的盒子)。您可以定义它在 DOCX 中的外观。

以下是 Hintbox 扩展的自定义节点可能的样子:

import { mergeAttributes, Node } from '@tiptap/core'

export interface ParagraphOptions {
  /**
   * 段落节点的 HTML 属性。
   * @default {}
   * @example { class: 'foo' }
   */
  HTMLAttributes: Record<string, any>
}

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    hintbox: {
      /**
       * 设置一个提示框
       * @example editor.commands.setHintbox()
       */
      setHintbox: () => ReturnType
      /**
       * 切换一个提示框
       * @example editor.commands.toggleHintbox()
       */
      toggleHintbox: () => ReturnType
    }
  }
}

/**
 * 此扩展允许您创建提示框。
 * @see https://www.tiptap.dev/api/nodes/paragraph
 */
export const Hintbox = Node.create<ParagraphOptions>({
  name: 'hintbox',

  priority: 1000,

  addOptions() {
    return {
      HTMLAttributes: {
        style: 'padding: 20px; border: 1px solid #b8d8ff; border-radius: 5px; background-color: #e6f3ff;',
      },
    }
  },

  group: 'block',

  content: 'inline*',

  parseHTML() {
    return [{ tag: 'p' }]
  },

  renderHTML({ HTMLAttributes }) {
    return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
  },

  addCommands() {
    return {
      setHintbox:
        () =>
        ({ commands }) => {
          return commands.setNode(this.name)
        },
      toggleHintbox:
        () =>
        ({ commands }) => {
          return commands.toggleNode(this.name, 'paragraph')
        },
    }
  },

  addKeyboardShortcuts() {
    return {
      'Mod-Alt-h': () => this.editor.commands.toggleHintbox(),
    }
  },
})

然后,我们会定义 Hintbox 自定义节点在 DOCX 中的渲染方式:

// 导入 ExportDocx 扩展
import {
  convertTextNode,
  Docx,
  ExportDocx,
  lineHeightToDocx,
  pixelsToHalfPoints,
  pointsToTwips,
} from '@tiptap-pro/extension-export-docx'

const editor = new Editor({
  extensions: [
    // 其他扩展 ...
    ExportDocx.configure({
      onCompleteExport: result => {
        setIsLoading(false)
        const blob = new Blob([result], {
          type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = 'export.docx'
        a.click()
        URL.revokeObjectURL(url)
      },
      exportType: 'blob',
      customNodes: [
        {
          type: 'hintbox',
          render: node => {
            // 在这里我们定义自定义 Hintbox 节点在 DOCX 中的渲染方式。
            // 根据文档,我们应该返回一个 DOCX 节点
            // 这要么是一个段落,要么是一个段落的数组,或者是一个表格。
            return new Docx.Paragraph({
              children: node.content.map(content => convertTextNode(content)),
              style: 'Hintbox', // 在这里我们将自定义样式应用于段落节点。
            })
            },
        },
      ], // 自定义节点
      styleOverrides: {
        paragraphStyles: [
          // 在这里我们定义自定义 Hintbox 节点的样式。
          {
            id: 'Hintbox',
            name: 'Hintbox',
            basedOn: 'Normal',
            next: 'Normal',
            quickFormat: false,
            run: {
              font: 'Aptos Light',
              size: pixelsToHalfPoints(16),
            },
            paragraph: {
              spacing: {
                before: pointsToTwips(12),
                after: pointsToTwips(12),
                line: lineHeightToDocx(1),
              },
              border: {
                // DOCX 颜色为十六进制,不带前导 #
                top: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
                bottom: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
                right: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
                left: { style: Docx.BorderStyle.SINGLE, size: 1, color: 'b8d8ff', space: 5 },
              },
              shading: {
                type: Docx.ShadingType.SOLID,
                color: 'e6f3ff',
              },
            },
          },
        ],
      }, // 样式覆盖
    }),
    // 其他扩展 ...
  ],
  // 其他编辑器设置 ...
})

然后,在你的应用程序的某个后期,可以将编辑器内容导出为 DOCX 文件:

editor
  .chain()
  .exportDocx()
  .run()

您可以在 render 函数中使用 Docx 库类(ParagraphTextRunTable 等)构建任何支持的 DOCX 元素,这些类是通过从 @tiptap-pro/extension-export-docx 包的 Docx 导入提供的。

从 .docx 导入自定义节点

在导入 DOCX 文件时,您还可以定义自定义节点应如何转换回 Tiptap 节点。这是通过将自定义节点定义数组传递给 import 命令来完成的。

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

// ... 在您的编辑器或 useEditor 设置中:
Import.configure({
  appId: 'your-app-id',
  token: 'your-jwt',
  // 注意:这仅用于演示目的
  endpoint: 'https://your-endpoint.com',
  imageUploadCallbackUrl: 'https://your-endpoint.com/image-upload',
  // Promisemirror 自定义节点映射
  promisemirrorNodes: {
    Hintbox: 'hintbox',
  },
}),

@tiptap-pro/extension-import-docx 的最新版本提供了 promisemirrorNodes 配置选项。此选项允许您将 DOCX 中的自定义节点映射到您的 Tiptap 模式。在上例中,我们将 DOCX 的 Hintbox 自定义节点映射到 Tiptap 模式中的 hintbox 自定义节点。这样,当导入包含 Hintbox 自定义节点的 DOCX 文件时,它会转换为 Tiptap 中的 hintbox 节点。

DOCX, "prosemirrorNodes" 和 "prosemirrorMarks"

请注意,promisemirrorNodesprosemirrorMarks 选项仅在您导入 .docx 文件时有效。如果您导入另一种类型的文件,例如 .odt 文件,则会使用 /import 端点而不是 /import-docx 端点,promisemirrorNodesprosemirrorMarks 选项将不可用,因此您需要依赖于 自定义节点和标记映射 API 处理这些端点。