脚注

脚注让读者可以为文本添加编号注释,这些注释会显示在页面底部、页脚正上方,效果与 Microsoft Word 中完全相同。每个脚注都通过正文中的上标引用标记进行锚定。脚注区域会占用页面可用空间的一部分,因此页面中的脚注越多,正文内容可用的空间就越少。

脚注始终与其引用保持在一起:如果编辑导致引用文本移到另一页,它的脚注会自动移动到那一页的脚注区域。

想找尾注?

汇总到文档末尾单独列表中的注释是 尾注。脚注和尾注彼此独立,可以在同一文档中一起使用。

从 DOCX 导入

当你使用 DOCX 导入扩展 导入 .docx 文件时,文档中的脚注会自动应用到 Pages:引用、内容和编号都会保留。无需额外设置。

启用脚注

脚注默认是禁用的。通过 footnotes 选项组将其开启:

import { Pages } from '@tiptap-pro/extension-pages'

Pages.configure({
  pageFormat: 'A4',
  footer: '第 {page} 页,共 {total} 页',
  footnotes: {
    enabled: true,
  },
})

你只需要这些:脚注引用节点会自动注册,只要文档中包含脚注,按页脚注区域就会立即渲染。

脚注的行为方式

其思维模型与 Microsoft Word 一致:

  • 一个标记,一个注释。 每个脚注都是正文中的一个上标数字,并与包含该标记的页面脚注区域中的一个编号条目相对应。
  • 脚注位于页脚之上。 每一页都会渲染自己的脚注区域,并在正文内容与页面页脚之间显示一条短分隔线。
  • 脚注会占用页面空间。 该区域会随着内容增长而变大,相应地正文内容区域会变小,因此在已满的页面中添加脚注会把正文推到下一页。
  • 脚注会跟随其引用。 该区域只显示那些标记位于当前页面上的脚注。文本跨页重排时,脚注也会随之移动;你无需手动管理脚注位置。
  • 编号是自动且连续的。 脚注会按文档顺序编号为 1, 2, 3…。在两个已有脚注之间插入一个脚注会重新编号其后的所有脚注;删除一个脚注会填补空缺。编号是计算得出的,而不是存储的,因此始终正确。

插入脚注

调用 insertFootnote() 在当前选区添加脚注:

editor.commands.insertFootnote()

这会在选区之后插入引用标记(被选中的文本会保留,和 Word 一样),创建一个空脚注,并打开脚注编辑器,将光标置于新脚注中,用户可以立即输入内容。

<button onClick={() => editor.commands.insertFootnote()}>插入脚注</button>

编辑脚注

用户可以通过双击页面的脚注区域直接编辑脚注。这会打开一个功能完整的、仅作用于该页的 Tiptap 编辑器:它会显示属于该页的脚注,并按编号顺序排列,每条都可像普通富文本一样编辑。双击某个具体脚注会将光标置于该脚注中。

按下 Escape、点击关闭按钮,或在编辑器外部双击即可关闭编辑器。当脚注编辑器打开时,主文档编辑器会暂时变为不可编辑,这与页眉和页脚编辑器的行为一致。

自定义扩展

脚注编辑器默认使用 ConvertKit。你可以通过 footnotes.extensions 传入自己的扩展栈,以保持与主编辑器的 schema 一致:

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

Pages.configure({
  footnotes: {
    enabled: true,
    extensions: [ConvertKit.configure({ table: false })],
  },
})

协作使用回调形式

footnotes.extensions 也接受 (ctx) => Extensions 形式的回调,该回调会接收脚注编辑器应绑定到的 Y 字段名称和 Y.Doc。在配置协作功能时请使用这种形式。另请参见下文的 脚注与协作

活动编辑器状态

当脚注编辑器打开时,该扩展会通过 storage 暴露它,这与页眉和页脚的模式一致,因此统一工具栏可以在这三者之间通用:

  • activeEditor – 当前打开的脚注编辑器的 Tiptap Editor 实例(或 null
  • activeEditorType – 在编辑脚注时为 'footnotes'(与已有的 'header' / 'footer' / null 值并列)
  • activePageNumber – 当前正在编辑脚注的页码
  • footnotesEditorOn / footnotesEditorOff – 订阅/取消订阅脚注编辑器事件
useEffect(() => {
  if (!editor) return

  const syncActiveEditorState = () => {
    const { activeEditor, activeEditorType } = editor.storage.pages
    // 当脚注编辑器打开时,activeEditorType === 'footnotes'
  }

  editor.on('update', syncActiveEditorState)
  return () => {
    editor.off('update', syncActiveEditorState)
  }
}, [editor])

关于遵循当前聚焦编辑器的完整自定义工具栏示例,请参见 页面页眉和页脚 → 构建自定义工具栏。为其添加脚注支持只需处理 activeEditorType 的额外 'footnotes' 值。

锁定脚注

footnotes.editable 设为 false 可在不提供双击编辑能力的情况下渲染脚注,或在运行时切换:

Pages.configure({
  footnotes: { enabled: true, editable: false },
})

// 运行时
editor.commands.setFootnotesEditable(false) // 锁定
editor.commands.setFootnotesEditable(true) // 解锁

锁定时,脚注仍会正常渲染;只有编辑功能会被禁用。openFootnoteEditor 会返回 false,双击不会产生任何作用,且已经打开的脚注编辑器会被关闭。

防止双击时关闭

与页眉和页脚一样,当用户在编辑器外部双击时,你可以保持脚注编辑器打开。这在你自己的工具栏位于编辑器外部时很有用:

Pages.configure({
  footnotes: { enabled: true },
  onDblClickFootnotesPreventClose: (event) => {
    const toolbar = document.querySelector('.my-toolbar')
    return toolbar?.contains(event.target)
  },
})

当未提供脚注专用回调时,通用的 onDblClickHeaderFooterPreventClose 回调会作为回退方案。

通过程序打开和关闭

// 为第 2 页打开脚注编辑器
editor.commands.openFootnoteEditor({ pageNumber: 2 })

// 打开并将光标置于特定脚注中
editor.commands.openFootnoteEditor({ pageNumber: 2, focusNoteId: '3' })

// 关闭
editor.commands.closeFootnoteEditor()

当脚注被禁用、被锁定,或该页没有脚注时,openFootnoteEditor 会返回 false

删除脚注与清理

从正文中删除引用标记后,会立即将其脚注从页面中移除,并对其余脚注重新编号。脚注的内容会在后台保留,因此普通的撤销操作可以同时恢复标记及其文本。这一点有意与 Word 不同(Word 会立即删除内容),以优先保证撤销安全。

当你想永久丢弃那些引用已消失的内容时,请调用:

editor.commands.cleanupOrphanFootnotes()

当至少移除了一个孤立脚注时,它会返回 true

复制与粘贴

复制包含脚注标记的文本并将其粘贴到其他位置,会像 Word 一样复制脚注:粘贴后的标记会拥有自己的一条脚注,并带有原始内容的副本,同时文档中的编号会更新。从那一刻起,这两条脚注彼此独立。

没有内容的引用

引用标记始终会生成一个可见的脚注,即使当前还没有任何对应内容(例如,在对一个包含标记但尚未提供任何脚注内容的文档调用 setContent() 之后)。这类脚注会渲染为空的编号条目,打开脚注编辑器后即可立即对其进行编辑。

从标记导航

点击正文中的脚注标记会将页面滚动到对应脚注可见的位置,这在长文档中很方便。

配置

所有脚注设置都位于 footnotes 选项组中:

Pages.configure({
  footnotes: {
    enabled: true, // 总开关(默认:false)
    extensions: [ConvertKit], // 编辑器扩展(或支持协作的回调)
    initialContent: undefined, // 初始内容,以注释 id 为键
    separator: true, // 区域上方的短横线(默认:true)
    maxHeightRatio: 0.5, // 页面内容高度可占用的最大比例(默认:0.5)
    editable: true, // 双击编辑(默认:true)
    accentColor: '#6366f1', // 脚注编辑器强调色(默认取 accentColor)
  },
})

设置初始内容

initialContent 接受以注释 id 为键的脚注内容,每个 id 都应与文档内容中 footnoteReference 节点的 noteId 属性匹配。其值为 Tiptap 的 JSONContent 文档:

Pages.configure({
  footnotes: {
    enabled: true,
    initialContent: {
      1: {
        type: 'doc',
        content: [
          {
            type: 'paragraph',
            content: [{ type: 'text', text: '第一条脚注。' }],
          },
        ],
      },
    },
  },
})

这与 DOCX 导入 REST API 在其 footnotes 字段中返回的结构完全一致,因此服务器导入的文档可以直接用它来设置初始内容。

协作

initialContent 仅在未启用协作时生效。在协作文档中,共享文档拥有脚注内容。若要作为明确的用户操作将脚注加载到协作文档中(例如在 DOCX 导入之后),请改用 setFootnotes 命令。

分隔线

Word 会在正文和脚注之间绘制一条短水平线。该线默认开启;可通过 separator: false 关闭。

最大区域高度

maxHeightRatio 限制脚注区域最多可占页面内容高度的多少(默认 0.5,即页面的一半)。当脚注超过上限时,该区域会被裁剪。与 Word 的差异请参见 不应期待什么

强调色

脚注编辑器默认使用共享的 accentColor;你也可以通过 footnotes.accentColor 覆盖它,或在运行时修改:

editor.commands.setFootnotesAccentColor('#10b981')

强调色会影响正文中的标记颜色、光标、编辑器工具栏边框,以及“脚注 – 第 N 页”标签。

访问脚注内容

脚注内容可在 editor.storage.pages 中获取,并以注释 id 为键:

// 每个脚注对应的 Tiptap JSON,用于持久化和 DOCX 导出
const footnotesJSON = editor.storage.pages.footnotesJSON
// { '1': { type: 'doc', content: [...] }, 'fn-abc123': { ... } }

// 每个脚注对应的渲染 HTML,与页面预览一致
const footnotesHTML = editor.storage.pages.footnotesHTML

// 当前编号(注释 id → 从 1 开始的编号)
const numbers = editor.storage.pages.footnoteNumbers

保存与恢复

与页眉和页脚一样,脚注内容的持久化由你负责。将 footnotesJSON 与文档一起保存,并在加载内容后使用 setFootnotes 命令将其恢复:

// 保存
await saveDocument({
  content: editor.getJSON(),
  footnotes: editor.storage.pages.footnotesJSON,
})

// 恢复
editor.commands.setContent(savedDocument.content)
editor.commands.setFootnotes(savedDocument.footnotes)

setFootnotes 会将所有脚注内容替换为一个 id → 文档 映射。正文中的引用仍然有效,因为它们是通过 id 匹配的。

DOCX 导入与导出

脚注可与 Word 文档无缝往返:

  • 导入: 使用 DOCX 导入编辑器扩展 时,导入文件中的脚注会自动应用:标记会出现在正文中,内容会落在页面区域中,编号与源文档一致。导入上下文还会公开原始的 footnotes / endnotes 数据,方便你自行处理。
  • 导出: 使用 DOCX 导出编辑器扩展 时,脚注内容会从 Pages 中自动提取,并写入为真正的 Word 脚注:标记会变成可点击的脚注引用,Word 会原生渲染并重新编号。

任一侧都无需配置。只需在 Pages 旁安装导入/导出扩展,脚注就会被包含在内。

脚注与协作

脚注会与主文档一起参与协作:并发用户可以实时看到彼此对脚注所做的编辑,插入或删除脚注也会在各个客户端之间收敛一致,包括编号。

要启用此功能,请将 footnotes.extensions 作为一个 回调 传入,并在其中为脚注编辑器附加一个 Collaboration 扩展,页眉和页脚使用的是同样的模式:

Pages.configure({
  footnotes: {
    enabled: true,
    extensions: (ctx) => {
      const base = [ConvertKit.configure({ undoRedo: false })]
      if (!ctx.isCollaborative || !ctx.ydoc) {
        return base
      }
      return [...base, Collaboration.configure({ document: ctx.ydoc, field: ctx.field })]
    },
  },
})

该回调会接收预先计算好的 Y 字段名(ctx.field)以及父编辑器的 Y.Doc(ctx.ydoc)。有关完整的协作设置,包括页眉和页脚的等价连接方式,请参见为 Pages 添加协作

预期行为

  • 类似 Word 的放置方式:脚注位于包含其标记的页面底部,页脚上方,并带有分隔线。
  • 自动、连续编号,在每次插入、删除、粘贴和重新排版时都会更新。
  • 随着脚注增多,页面主体会缩小,因此分页会考虑脚注空间。
  • 可使用你配置的扩展来编辑富文本脚注内容。
  • 渲染后的脚注与编辑视图外观完全一致:字体排版、编号和间距均一致,包括用作垂直间距的空段落。
  • 无需额外配置即可进行 DOCX 往返转换。

不应期待的行为

  • 脚注不会跨页续接。 当某页的脚注超过 maxHeightRatio 时,该区域会被裁切,而不会像 Word 那样在下一页继续显示溢出的内容。
  • 仅支持十进制编号。 脚注在整个文档中连续编号为 1, 2, 3…。目前还不支持按页重置以及其他格式(罗马数字、字母、符号等)。
  • 脚注中的表格不会导出。 它们会在编辑器中正常渲染,但 Word 的脚注格式只接受段落,因此在导出为 DOCX 时会丢弃表格。

Help us prioritise

如果这些缺口中的某一个阻碍了你的使用场景,请告诉我们。你的反馈会推动我们的路线图。

向 Tiptap 分享你的使用场景

完整选项参考

除非另有说明,所有选项都位于 Pages.configure()footnotes 键下。

选项类型默认值描述
enabledbooleanfalse脚注功能总开关
extensionsExtensions | (ctx) => ExtensionsConvertKit脚注编辑器使用的扩展。协作场景请使用回调形式。
initialContentRecord<string, JSONContent>undefined以注释 id 为键的初始脚注内容(仅适用于非协作文档)
separatorbooleantrue是否在区域上方渲染 Word 风格的分隔线
maxHeightRationumber0.5区域可占据的页面内容高度最大比例
editablebooleantrue双击该区域时是否打开脚注编辑器
accentColorstringaccentColor标记和脚注编辑器的强调色
onDblClickFootnotesPreventClose(event: MouseEvent) => boolean(顶层选项)undefined阻止在区域外双击时关闭脚注编辑器

命令参考

命令参数描述
insertFootnote在选区处插入脚注并打开其编辑器
setFootnotesfootnotes: Record<string, JSONContent>将所有脚注内容替换为一个 id → 文档 映射
openFootnoteEditor{ pageNumber: number, focusNoteId?: string }打开某一页的脚注编辑器,可选择聚焦到特定脚注
closeFootnoteEditor关闭脚注编辑器(如果已打开)
setFootnotesEditableenabled: boolean锁定或解锁脚注编辑
cleanupOrphanFootnotes永久移除其引用已消失的脚注内容
setFootnotesAccentColorcolor: string设置脚注强调色

Storage Reference

Read these values from editor.storage.pages:

PropertyTypeDescription
footnotesJSONRecord<string, JSONContent>每个注释 ID 对应的脚注内容;用于持久化和 DOCX 导出
footnotesHTMLRecord<string, string>每个注释 ID 对应的渲染后 HTML,与页面预览保持一致
footnoteNumbersRecord<string, number>当前编号(注释 ID → 从 1 开始的数字)
footnotesEnabledboolean是否启用脚注
editableFootnotesboolean是否解锁编辑(只读;使用 setFootnotesEditable
activeEditorType'header' | 'footer' | 'footnotes' | null脚注编辑器打开时为 'footnotes'
footnotesEditorOnEditor['on'] | null预绑定的脚注编辑器事件订阅器
footnotesEditorOffEditor['off'] | null预绑定的脚注编辑器取消订阅器