页面页眉和页脚

你可以调整页面布局的一些方面,以更好地满足你的需求。主要选项有 headerfooterheaderHeightfooterHeight

Imported from DOCX

当你导入 .docx 文件并使用 DOCX 导入 扩展 时,文档中的页眉和页脚会自动应用到 Pages 中——包括首页不同以及奇偶页不同的变体。无需额外配置。

页眉内容

  • 将在页面页眉中渲染的内容
  • 默认值:'' – 空字符串

初始配置(字符串)

Pages.configure({
  header: 'Awesome Tiptap header', // 字符串!
})

初始配置(HTML)

Pages.configure({
  header: () => '<bold>Awesome Tiptap header</bold>', // 返回 HTML 或字符串的函数!
})

页眉内容编辑命令

editor.commands.setHeader('My awesome header') // 字符串!
// 或者
editor.commands.Header((page, total) => `${page} of ${total}`) // 返回 HTML 或字符串的函数!

页眉替换模板

header 属性使用字符串时,提供了两个便捷的模板替换:

  • {page}:被替换为当前页码。
  • {total}:被替换为文档的总页数。

header 属性也可以接受一个函数,传递相同的信息,这次通过函数参数提供:

Pages.configure({
  header: {
    type: 'doc',
    content: [
      {
        type: 'paragraph',
        content: [{ type: 'text', text: 'Document Title', marks: [{ type: 'bold' }] }],
      },
    ],
  },
})

页眉高度

  • 控制每页顶部页眉区域的高度。
  • 默认值:50(像素)

两种方式都可以设置内容:

使用 configure 的初始设置:

Pages.configure({
  header: 'My Document Title',
  footer: 'Page {page} of {total}',
})

使用 commands 的运行时更新:

// 编辑器初始化后更新页眉
editor.commands.setHeader('Updated Header')

页眉配置

默认页眉

header 选项设置每页页眉区域显示的内容。

初始配置:

Pages.configure({
  headerHeight: 80, // 让页眉区域更高
})

页眉高度编辑命令

editor.commands.setHeaderHeight(30) // 更小的页眉!

页脚内容

  • 将在页面页脚中渲染的内容
  • 默认值:{page} – 带有页码模板替换的字符串

初始配置(字符串)

Pages.configure({
  footer: 'Awesome Tiptap footer', // 字符串!
})

编辑器命令:

// 覆盖页眉上边距
editor.commands.setHeaderTopMargin(40)

// 清除覆盖并回退到当前格式的默认值
//(该格式顶部边距的 50%)
editor.commands.resetHeaderTopMargin()

Input validation

setHeaderTopMargin 会拒绝负值和 NaN——在这些情况下,该命令会返回 false 并记录警告。

页脚配置

默认页脚

footer 选项设置每页页脚区域显示的内容。

初始配置:

Pages.configure({
  footer: () => '<bold>Awesome Tiptap footer</bold>', // 返回 HTML 或字符串的函数!
})

页脚内容编辑命令

editor.commands.setFooter('My awesome footer') // 字符串!
// 或者
editor.commands.setFooter((page, total) => `${page} of ${total}`) // 返回 HTML 或字符串的函数!

页脚替换模板

footer 属性使用字符串时,提供了两个便捷的模板替换:

  • {page}:被替换为当前页码。
  • {total}:被替换为文档的总页数。

footer 属性也可以接受一个函数,传递相同的信息,这次通过函数参数提供:

Pages.configure({
  footer: (page, total) => `${page} of ${total}` // 示例,显示:"1 of 10"
})

编辑器命令:

// 覆盖页脚下边距
editor.commands.setFooterBottomMargin(40)

// 清除覆盖并回退到当前格式的默认值
//(该格式底部边距的 50%)
editor.commands.resetFooterBottomMargin()

Input validation

setFooterBottomMargin 会拒绝负值和 NaN——在这些情况下,该命令会返回 false 并记录警告。

首页不同

editor.commands.setFooterHeight(30) // 更小的页脚!

注意

setDifferentFirstPage() 命令可同时启用首页不同的页眉和页脚,行为类似 Microsoft Word。

页面边距可以通过使用自定义页面格式进行自定义。内置格式具有固定边距,但你可以创建自己的带有自定义边距的格式:

Pages.configure({
  header: 'Default Header',
  footer: 'Page {page}',
  differentFirstPage: true,
  headerFirstPage: 'Title Page',
  footerFirstPage: '', // 首页无页脚
})

命令

// 启用首页不同(同时影响页眉和页脚)
editor.commands.setDifferentFirstPage(true)

// 设置首页页眉和页脚内容
editor.commands.setHeaderFirstPage('Welcome to My Document')
editor.commands.setFooterFirstPage('')

奇偶页不同

启用奇数页(1、3、5……)和偶数页(2、4、6……)不同的页眉和/或页脚,类似于微软 Word 的“奇偶页不同”选项。

注意

setDifferentOddEven() 命令可同时启用奇偶页不同的页眉和页脚,行为类似 Microsoft Word。

配置

Pages.configure({
  differentOddEven: true,
  headerOdd: '章节标题 – 奇数页',
  headerEven: '书名 – 偶数页',
  footerOdd: '第 {page} 页',
  footerEven: '第 {page} 页',
})

命令

// 启用奇偶页不同(同时影响页眉和页脚)
editor.commands.setDifferentOddEven(true)

// 设置奇数页和偶数页页眉
editor.commands.setHeaderOdd('奇数页页眉')
editor.commands.setHeaderEven('偶数页页眉')

// 设置奇数页和偶数页页脚
editor.commands.setFooterOdd('第 {page} 页')
editor.commands.setFooterEven('第 {page} 页')

首页与奇偶页同时启用

当同时启用 differentFirstPagedifferentOddEven 时,首页页眉/页脚优先于奇偶页设置,同时页 1 使用首页设置。

优先顺序:

  1. 首页(如果启用 differentFirstPage)- 适用于第 1 页
  2. 奇偶页(如果启用 differentOddEven)- 适用于第 2 页及之后
  3. 默认页眉/页脚 - 后备
Pages.configure({
  header: 'Default Header', // 后备
  differentFirstPage: true,
  headerFirstPage: 'Title Page', // 第 1 页
  differentOddEven: true,
  headerOdd: 'Odd Page Header', // 第 3, 5, 7 页……
  headerEven: 'Even Page Header', // 第 2, 4, 6 页……
})

可编辑的页眉和页脚

用户可以通过双击页眉或页脚直接编辑它们。这将打开一个功能完备的 Tiptap 编辑器,允许丰富文本编辑。

自定义扩展

页眉/页脚编辑器使用与你在主编辑器中配置的相同扩展。为了匹配 schema(这样相同的 mark、节点和表格行为都能在页眉和页脚中工作),请通过 headerFooterExtensions 传入 ConvertKitTableKit

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

Pages.configure({
  header: 'My header',
  headerFooterExtensions: [ConvertKit.configure({ table: false }), TableKit],
})

任何 Tiptap 扩展都可以通过 headerFooterExtensions 选项添加到页眉/页脚编辑器中。与主编辑器的扩展栈保持一致,可以让文档及其页眉页脚之间的 schema、键盘快捷键和渲染效果保持统一。

激活的编辑器状态

当用户双击页眉或页脚进行编辑时,扩展会通过存储暴露当前激活的编辑器。这使你能够构建与页眉/页脚编辑器配合使用的自定义工具栏。

存储属性:

  • activeEditor – 当前打开的页眉或页脚编辑器对应的 Tiptap Editor 实例(或 null
  • activeEditorType'header''footer'null
  • activePageNumber – 正在编辑的页码(或 null
  • headerEditorOn / headerEditorOff – 订阅/取消订阅页眉覆盖层内部 Tiptap 编辑器上的事件
  • footerEditorOn / footerEditorOff – 订阅/取消订阅页脚覆盖层内部 Tiptap 编辑器上的事件

该扩展还会监听页眉和页脚内部编辑器发出的 focus 事件。当用户聚焦到已打开的页眉或页脚编辑器时,activeEditoractiveEditorTypeactivePageNumber 会从当前激活的覆盖层中刷新。

监听页眉/页脚编辑器事件

页眉和页脚各自拥有独立的编辑器。你可以通过 editor.storage.pages 订阅这些编辑器的事件。

import { useEffect, useState } from 'react'

function HeaderFooterStatus({ editor }) {
  const [activeEditorType, setActiveEditorType] = useState(null)
  const [activePageNumber, setActivePageNumber] = useState(null)

  useEffect(() => {
    if (!editor) return

    const syncActiveEditorState = () => {
      const { activeEditorType, activePageNumber } = editor.storage.pages
      setActiveEditorType(activeEditorType)
      setActivePageNumber(activePageNumber)
    }

    editor.storage.pages.headerEditorOn?.('focus', syncActiveEditorState)
    editor.storage.pages.footerEditorOn?.('focus', syncActiveEditorState)

    return () => {
      editor.storage.pages.headerEditorOff?.('focus', syncActiveEditorState)
      editor.storage.pages.footerEditorOff?.('focus', syncActiveEditorState)
    }
  }, [editor])

  if (!activeEditorType || !activePageNumber) {
    return <span>正在编辑文档</span>
  }

  return (
    <span>
      正在编辑第 {activePageNumber} 页的 {activeEditorType}
    </span>
  )
}

你也可以将相同的辅助方法用于其他 Tiptap 编辑器事件,例如 updateselectionUpdateblurtransaction

构建自定义工具栏

下面是一个 React 组件示例,它为主编辑器和页眉/页脚编辑器提供一个统一的工具栏:

import { useEditor, useEditorState, EditorContent } from '@tiptap/react'
import { useEffect, useState } from 'react'

function DocumentEditor() {
  const [headerFooterEditor, setHeaderFooterEditor] = useState(null)
  const [activeEditorType, setActiveEditorType] = useState(null)
  const [activePageNumber, setActivePageNumber] = useState(null)

  const editor = useEditor({
    extensions: [
      ConvertKit.configure({ table: false }),
      TableKit,
      Pages.configure({
        header: '文档页眉',
        footer: '第 {page} 页',
      }),
    ],
  })

  // 监听页眉/页脚编辑器的激活变化
  useEffect(() => {
    if (!editor) return

    const syncActiveHeaderFooterEditor = () => {
      const { activeEditor, activeEditorType, activePageNumber } = editor.storage.pages
      setHeaderFooterEditor(activeEditor)
      setActiveEditorType(activeEditorType)
      setActivePageNumber(activePageNumber)
    }

    editor.on('update', syncActiveHeaderFooterEditor)
    editor.storage.pages.headerEditorOn?.('focus', syncActiveHeaderFooterEditor)
    editor.storage.pages.footerEditorOn?.('focus', syncActiveHeaderFooterEditor)

    return () => {
      editor.off('update', syncActiveHeaderFooterEditor)
      editor.storage.pages.headerEditorOff?.('focus', syncActiveHeaderFooterEditor)
      editor.storage.pages.footerEditorOff?.('focus', syncActiveHeaderFooterEditor)
    }
  }, [editor])

  // 使用激活的编辑器(页眉/页脚或主编辑器)
  const activeEditor = activeEditorType ? headerFooterEditor : editor

  const { isBoldActive } = useEditorState({
    editor: activeEditor,
    selector: ({ editor: e }) => ({
      isBoldActive: e?.isActive('bold') ?? false,
    }),
  })

  return (
    <div>
      <div className="toolbar">
        <span>
          Editing:{' '}
          {activeEditorType && activePageNumber
            ? `${activeEditorType} on page ${activePageNumber}`
            : 'Document'}
        </span>
        <button
          className={isBoldActive ? 'is-active' : ''}
          onClick={() => activeEditor?.chain().focus().toggleBold().run()}
        >
          加粗
        </button>
      </div>
      <EditorContent editor={editor} />
    </div>
  )
}

访问页眉和页脚内容

用户通过页眉或页脚编辑器修改内容后,内容会存储在扩展的存储区。这对于保存文档或导出为 DOCX 等格式非常有用。

HTML 内容

访问编辑后的 HTML 内容:

// 默认页眉/页脚
const headerHTML = editor.storage.pages.headerHTML
const footerHTML = editor.storage.pages.footerHTML

// 首页
const headerFirstPageHTML = editor.storage.pages.headerFirstPageHTML
const footerFirstPageHTML = editor.storage.pages.footerFirstPageHTML

// 奇偶页
const headerOddHTML = editor.storage.pages.headerOddHTML
const headerEvenHTML = editor.storage.pages.headerEvenHTML
const footerOddHTML = editor.storage.pages.footerOddHTML
const footerEvenHTML = editor.storage.pages.footerEvenHTML

注意

这些存储值在用户通过相应的页眉/页脚编辑器编辑内容之前均为 null。编辑之前,使用配置中的原始模板。

用于 DOCX 导出的 JSON 内容

对于 DOCX 导出,使用 JSON 版本,它们提供结构化的 Tiptap 文档格式:

// 默认页眉/页脚
const headerJSON = editor.storage.pages.headerJSON
const footerJSON = editor.storage.pages.footerJSON

// 首页
const headerFirstPageJSON = editor.storage.pages.headerFirstPageJSON
const footerFirstPageJSON = editor.storage.pages.footerFirstPageJSON

// 奇偶页
const headerOddJSON = editor.storage.pages.headerOddJSON
const headerEvenJSON = editor.storage.pages.headerEvenJSON
const footerOddJSON = editor.storage.pages.footerOddJSON
const footerEvenJSON = editor.storage.pages.footerEvenJSON

注意

这些存储值在用户通过相应的页眉/页脚编辑器编辑内容之前均为 null。编辑之前,使用配置中的原始模板。

保存页眉和页脚内容

保存页眉和页脚内容是你的责任。保存文档时从存储区获取内容,加载时再恢复。

保存所有页眉和页脚内容:

function getHeaderFooterContent(editor) {
  const { pages } = editor.storage

  return {
    // 默认页眉/页脚
    headerHTML: pages.headerHTML,
    headerJSON: pages.headerJSON,
    footerHTML: pages.footerHTML,
    footerJSON: pages.footerJSON,

    // 首页
    headerFirstPageHTML: pages.headerFirstPageHTML,
    headerFirstPageJSON: pages.headerFirstPageJSON,
    footerFirstPageHTML: pages.footerFirstPageHTML,
    footerFirstPageJSON: pages.footerFirstPageJSON,

    // 奇偶页
    headerOddHTML: pages.headerOddHTML,
    headerOddJSON: pages.headerOddJSON,
    headerEvenHTML: pages.headerEvenHTML,
    headerEvenJSON: pages.headerEvenJSON,
    footerOddHTML: pages.footerOddHTML,
    footerOddJSON: pages.footerOddJSON,
    footerEvenHTML: pages.footerEvenHTML,
    footerEvenJSON: pages.footerEvenJSON,
  }
}

// 保存到后端
const headerFooterData = getHeaderFooterContent(editor)
await saveDocument({ content: editor.getJSON(), headerFooter: headerFooterData })

编程式打开和关闭编辑器

你可以通过编程方式打开和关闭页眉和页脚编辑器。这对于集成自定义 UI 元素、构建导航界面或响应用户操作非常有用。

打开编辑器

使用 openHeaderEditoropenFooterEditor 命令为指定页面打开编辑器:

// 打开第 1 页的页眉编辑器
editor.commands.openHeaderEditor({ pageNumber: 1 })

// 打开第 3 页的页脚编辑器
editor.commands.openFooterEditor({ pageNumber: 3 })

这些命令在成功打开编辑器时返回 true,页面不存在时返回 false

注意

打开一个编辑器时会自动关闭之前打开的任何页眉或页脚编辑器。主文档编辑器在页眉或页脚编辑器打开时不可编辑。

使用场景:

  • 构建页码导航 UI,让用户直接跳转编辑指定页的页眉或页脚
  • 在工具栏中创建“跳转到页眉/页脚”按钮
  • 实现快捷键打开页眉或页脚
  • 响应外部事件时编程打开编辑器

示例:页面导航组件

function PageNavigation({ editor }) {
  const pageCount = editor.storage.pages.getPageCount?.() ?? 1

  return (
    <div>
      {Array.from({ length: pageCount }, (_, i) => (
        <div key={i + 1}>
          <span>第 {i + 1} 页</span>
          <button onClick={() => editor.commands.openHeaderEditor({ pageNumber: i + 1 })}>
            编辑页眉
          </button>
          <button onClick={() => editor.commands.openFooterEditor({ pageNumber: i + 1 })}>
            编辑页脚
          </button>
        </div>
      ))}
    </div>
  )
}

关闭编辑器

使用关闭命令关闭页眉或页脚编辑器:

// 关闭当前打开的任何编辑器
editor.commands.closeHeaderFooterEditors()

// 关闭特定编辑器
editor.commands.closeHeaderEditor()
editor.commands.closeFooterEditor()

防止双击关闭编辑器

默认情况下,在打开的页眉或页脚编辑器外双击会关闭编辑器。你可以使用回调选项防止此行为,这在有工具栏按钮或其他 UI 元素时很有用,避免用户双击时编辑器意外关闭。

配置

提供三种回调选项:

  • onDblClickHeaderFooterPreventClose - 适用于页眉和页脚(回退)
  • onDblClickHeaderPreventClose - 仅适用于页眉(优先于回退)
  • onDblClickFooterPreventClose - 仅适用于页脚(优先于回退)

每个回调函数接收 MouseEvent,返回 true 表示阻止关闭,false 允许关闭。

示例:点击工具栏时防止关闭

Pages.configure({
  header: '我的页眉',
  footer: '第 {page} 页',
  onDblClickHeaderFooterPreventClose: (event) => {
    // 如果点击在工具栏内,保持编辑器打开
    const toolbar = document.querySelector('.my-toolbar')
    return toolbar?.contains(event.target)
  },
})

示例:对页眉和页脚采用不同行为

Pages.configure({
  header: '我的页眉',
  footer: '第 {page} 页',
  // 双击页眉外部永不关闭
  onDblClickHeaderPreventClose: () => true,
  // 仍允许双击页脚外部关闭
  onDblClickFooterPreventClose: () => false,
})

重点强调颜色

可以自定义页眉和页脚编辑器覆盖层的视觉样式,调整强调色包括:

  • 光标颜色 — 编辑器中的文本光标颜色
  • 工具栏边框 — 编辑工具栏的顶部边框(页眉)或底部边框(页脚)
  • 标签背景 — “页眉” 或 “页脚” 标签背景色

配置

在扩展配置时设置强调色:

Pages.configure({
  header: '我的页眉',
  footer: '第 {page} 页',
  // 同时设置页眉和页脚的颜色
  accentColor: '#3b82f6',
})

也可以分别设置页眉和页脚的强调色:

Pages.configure({
  header: '我的页眉',
  footer: '第 {page} 页',
  headerAccentColor: '#3b82f6', // 页眉的蓝色
  footerAccentColor: '#10b981', // 页脚的绿色
})

运行时更新

使用命令动态修改强调色:

// 同时更新页眉和页脚颜色
editor.commands.setAccentColor('#8b5cf6')

// 仅更新页眉颜色
editor.commands.setHeaderAccentColor('#3b82f6')

// 仅更新页脚颜色
editor.commands.setFooterAccentColor('#ef4444')

CSS 颜色值

强调色选项支持任何有效的 CSS 颜色值,包括十六进制(#3b82f6)、rgb(rgb(59, 130, 246))、hsl(hsl(217, 91%, 60%))、oklch 或 CSS 变量(var(--primary-color))。

限制

编辑功能不能禁用

双击编辑行为是内置于扩展中的,无法禁用。所有有权访问编辑器的用户都可以编辑页眉和页脚。

页眉和页脚编辑器样式

页眉/页脚编辑器使用内置样式。但你可以通过 accentColorheaderAccentColorfooterAccentColor 选项自定义强调色。

协作功能

页眉和页脚编辑器会与主文档一起参与协作。请像为任何 Pages 编辑器一样设置协作提供器——无需额外连接即可让页眉和页脚进入同一会话。完整设置流程请参见 向 Pages 添加协作

页眉/页脚编辑器使用内置样式,但你可通过 accentColorheaderAccentColorfooterAccentColor 选项自定义强调色。

选项类型默认值描述
headerstring | JSONContent''默认页眉内容
footerstring | JSONContent''默认页脚内容
headerTopMarginnumber顶部边距的 50%页眉内容距离页面顶部的距离
footerBottomMarginnumber底部边距的 50%页脚内容距离页面底部的距离
differentFirstPagebooleanfalse是否启用首页不同的页眉和页脚
headerFirstPagePagesHeaderFooter''首页页眉内容
footerFirstPagePagesHeaderFooter''首页页脚内容
differentOddEvenbooleanfalse是否启用奇偶页不同的页眉和页脚
headerOddPagesHeaderFooter''奇数页页眉内容
headerEvenPagesHeaderFooter''偶数页页眉内容
footerOddPagesHeaderFooter''奇数页页脚内容
footerEvenPagesHeaderFooter''偶数页页脚内容
headerFooterExtensionsExtension[]StarterKit页眉/页脚编辑器使用的扩展
onDblClickHeaderFooterPreventClose(event: MouseEvent) => booleanundefined双击外部时阻止关闭页眉/页脚编辑器的回调
onDblClickHeaderPreventClose(event: MouseEvent) => booleanundefined双击外部时阻止关闭页眉编辑器的回调
onDblClickFooterPreventClose(event: MouseEvent) => booleanundefined双击外部时阻止关闭页脚编辑器的回调
accentColorstring'#6366f1'页眉/页脚编辑器覆盖层的强调色
headerAccentColorstringaccentColor页眉编辑器覆盖层的强调色
footerAccentColorstringaccentColor页脚编辑器覆盖层的强调色

页眉和页脚编辑不支持协作同步。它们分别是独立的 Tiptap 实例,与主文档的协作会话无关。

CommandParametersDescription
setHeaderheader: PagesHeaderFooter设置默认页眉内容
setFooterfooter: PagesHeaderFooter设置默认页脚内容
setHeaderTopMarginmargin: number设置页眉顶部边距(像素)。拒绝负值和 NaN
resetHeaderTopMarginnone清除页眉顶部边距覆盖;回退到当前格式的默认值(格式顶部边距的 50%)。
setFooterBottomMarginmargin: number设置页脚底部边距(像素)。拒绝负值和 NaN
resetFooterBottomMarginnone清除页脚底部边距覆盖;回退到当前格式的默认值(格式底部边距的 50%)。
setDifferentFirstPageenabled: boolean切换首页不同(页眉 + 页脚)
setHeaderFirstPageheader: PagesHeaderFooter设置首页页眉内容
setFooterFirstPagefooter: PagesHeaderFooter设置首页页脚内容
setDifferentOddEvenenabled: boolean切换奇偶页不同(页眉 + 页脚)
setHeaderOddheader: PagesHeaderFooter设置奇数页页眉内容
setHeaderEvenheader: PagesHeaderFooter设置偶数页页眉内容
setFooterOddfooter: PagesHeaderFooter设置奇数页页脚内容
setFooterEvenfooter: PagesHeaderFooter设置偶数页页脚内容
openHeaderEditor{ pageNumber: number }为特定页面打开页眉编辑器(从 1 开始编号)
openFooterEditor{ pageNumber: number }为特定页面打开页脚编辑器(从 1 开始编号)
closeHeaderFooterEditorsnone关闭当前打开的页眉/页脚编辑器
closeHeaderEditornone如果页眉编辑器已打开,则将其关闭
closeFooterEditornone如果页脚编辑器已打开,则将其关闭
setAccentColorcolor: string为两个编辑器覆盖层设置强调色
setHeaderAccentColorcolor: string为页眉编辑器覆盖层设置强调色
setFooterAccentColorcolor: string为页脚编辑器覆盖层设置强调色