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

Available in Start planBetav0.20.0

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

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

寻找 REST 兼容版本?

函数无法通过 HTTP 序列化,因此本页上的函数 API 仅在编辑器扩展内部有效。对于一个基于 JSON、可通过 REST 传输并在两端运行的等价方案,请参见 自定义节点 DSL

将自定义节点导出为 .docx

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

自定义节点约定

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

定义自定义节点扩展

举例来说,假设你的编辑器有一个自定义节点类型 hintbox(一个带有提示样式的框)。你可以定义它在导出文档中应如何显示。

下面是 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 自定义节点应如何渲染:

// 导入 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 节点,
            // 它可以是 Paragraph、Paragraph 数组、Table、TextRun、ExternalHyperlink 或 null。
            return new Docx.Paragraph({
              children: node.content.map((content) => convertTextNode(content)),
              style: 'Hintbox', // 这里我们将自定义样式应用到 Paragraph 节点。
            })
          },
        },
      ], // 自定义节点
      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 导入提供的。