npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

zt-reactjs-tiptap

v2.0.17

Published

A powerful Markdown editor based on TipTap with Notion-like features

Readme

zt-reactjs-tiptap

基于 TipTap 的 React 富文本编辑器组件,支持 Notion 风格(斜杠命令、块状编辑)与 Headless(顶部工具栏、可嵌入自定义 UI)两种模式。

npm version license

GitHub: https://github.com/lzt-T/md-tiptap

演示地址:https://tiptap.xjoker.top/

特性

  • 🧩 双模式编辑体验:Notion 风格与 Headless 顶部工具栏模式
  • ✨ 丰富的文本格式化选项(粗体、斜体、下划线、删除线、行内代码)
  • 🔗 链接编辑面板(Toolbar 与 BubbleMenu 共用,支持更新与删除链接)
  • 📝 支持标题(H1、H2、H3)、列表(有序/无序)、引用、可高亮代码块
  • 🖼️ 图片插入和管理(文件上传、URL 插入、缩放、对齐、描述)
  • 🎬 视频插入(文件上传、URL 插入)
  • 📎 附件上传与文件链接(支持文件选择、拖拽上传、类型限制)
  • 📊 完整表格支持(插入/删除行列、切换表头、单元格对齐、合并/拆分、删除表格)
  • ✅ 任务列表(带复选框的可交互待办事项)
  • 🎨 文本颜色和背景高亮
  • 🔢 数学公式支持(行内和块级 LaTeX 公式,基于 KaTeX)
  • ⚡ 斜杠命令(输入 / 快速插入各种内容块)
  • 🎯 气泡菜单(选中文本时显示格式化工具栏)
  • 💻 代码块操作(语言选择、复制、格式化、删除)
  • 🔄 撤销/重做支持
  • 📐 文本对齐(左对齐、居中、右对齐、两端对齐)
  • ↔️ 块级缩进(支持增加/减少段落与标题缩进)
  • ⬆️⬇️ 上标/下标支持
  • 🌓 浅色/暗色主题与只读禁用状态
  • 🌐 国际化(i18n):支持 zh-CN / en-US,工具栏、斜杠命令、弹窗与默认占位文案可本地化
  • 🧹 HTML 粘贴清洗(移除高风险标签、事件属性与常见 Office 污染)
  • 📄 HTML/DOM 转纯文本工具(与编辑器 getText() 一致)

安装

npm install zt-reactjs-tiptap
# 或
pnpm add zt-reactjs-tiptap
# 或
yarn add zt-reactjs-tiptap

使用

基础使用

import { ReactTiptapEditor } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'  // 必须导入样式

function App() {
  const [content, setContent] = useState('<p>Hello World!</p>')

  return (
    <ReactTiptapEditor
      value={content}
      onChange={setContent}
    />
  )
}

带图片/视频/附件预上传(Confirm 后回调)

import { ReactTiptapEditor } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

function App() {
  const [content, setContent] = useState('<p>Hello World!</p>')

  // 选择/拖拽图片时触发(预上传)
  const handleImagePreUpload = async (file: File) => {
    const formData = new FormData()
    formData.append('file', file)
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    })
    const data = await response.json()
    return data.url
  }

  // 仅在点击 Confirm 时触发
  const handleImageUpload = async (payload: { file: File; url: string; alt?: string }) => {
    console.log('Image confirmed:', payload)
  }

  // 选择/拖拽视频时触发(预上传)
  const handleVideoPreUpload = async (file: File) => {
    const formData = new FormData()
    formData.append('file', file)
    const response = await fetch('/api/video-upload', {
      method: 'POST',
      body: formData,
    })
    const data = await response.json()
    return data.url
  }

  // 仅在点击 Confirm 时触发
  const handleVideoUpload = async (payload: { file: File; url: string; title?: string }) => {
    console.log('Video confirmed:', payload)
  }

  // 选择/拖拽附件时触发(预上传)
  const handleFilePreUpload = async (file: File) => {
    const formData = new FormData()
    formData.append('file', file)
    const response = await fetch('/api/file-upload', {
      method: 'POST',
      body: formData,
    })
    const data = await response.json()
    return { url: data.url, name: data.name ?? file.name }
  }

  // 仅在点击 Confirm/Insert Link 时触发
  const handleFileUpload = async (payload: { file: File; url: string; name: string }) => {
    console.log('File confirmed:', payload)
  }

  return (
    <ReactTiptapEditor
      value={content}
      onChange={setContent}
      onImagePreUpload={handleImagePreUpload}
      onImageUpload={handleImageUpload}
      onVideoPreUpload={handleVideoPreUpload}
      onVideoUpload={handleVideoUpload}
      onFilePreUpload={handleFilePreUpload}
      onFileUpload={handleFileUpload}
      fileUploadTypes={['pdf', 'docx']}
    />
  )
}

国际化使用示例

import { ReactTiptapEditor } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

function App() {
  return (
    <>
      {/* 强制中文 */}
      <ReactTiptapEditor language="zh-CN" />

      {/* 强制英文 */}
      <ReactTiptapEditor language="en-US" />

      {/* 不传 language:自动跟随浏览器语言(zh* -> zh-CN,其余 -> en-US) */}
      <ReactTiptapEditor />
    </>
  )
}

受控组件完整示例

import { useState } from 'react'
import { ReactTiptapEditor } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

function EditorExample() {
  const [content, setContent] = useState('<p>开始编辑...</p>')

  const handleImagePreUpload = async (file: File): Promise<string> => {
    // 示例:上传到服务器
    const formData = new FormData()
    formData.append('image', file)
    
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    })
    
    const data = await response.json()
    return data.url
  }

  const handleImageUpload = (payload: { file: File; url: string; alt?: string }) => {
    // 仅在点击 Confirm 后触发
    console.log('图片确认插入:', payload)
  }

  return (
    <div style={{ maxWidth: '800px', margin: '0 auto' }}>
      <h1>我的编辑器</h1>
      <ReactTiptapEditor
        value={content}
        onChange={(html) => {
          setContent(html)
          console.log('内容已更新:', html)
        }}
        onImagePreUpload={handleImagePreUpload}
        onImageUpload={handleImageUpload}
      />
      <div style={{ marginTop: '20px' }}>
        <h3>当前 HTML 内容:</h3>
        <pre style={{ background: '#f5f5f5', padding: '10px', overflow: 'auto' }}>
          {content}
        </pre>
      </div>
    </div>
  )
}

主题切换(light / dark)

import { useState } from 'react'
import { ReactTiptapEditor, EditorTheme } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

function App() {
  const [theme, setTheme] = useState(EditorTheme.Light)

  return (
    <>
      <button
        type="button"
        onClick={() =>
          setTheme((prev) =>
            prev === EditorTheme.Dark ? EditorTheme.Light : EditorTheme.Dark
          )
        }
      >
        切换主题({theme})
      </button>

      <ReactTiptapEditor theme={theme} />
    </>
  )
}

未传 theme 时,编辑器会自动读取宿主 html.dark:存在 dark 则为暗色,不存在则固定为 light

Props

| 属性 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|--------|------| | value | string | 否 | - | 编辑器的 HTML 内容 | | onChange | (html: string) => void | 否 | - | 内容变化时的回调函数,参数为 HTML 字符串 | | onError | (event: { source: 'image-upload' \| 'video-upload' \| 'file-upload' \| 'paste'; stage: 'pre-upload' \| 'confirm' \| 'transform'; message: string; error?: unknown }) => void | 否 | - | 统一错误上报:上传预处理失败、Confirm 回调失败、HTML 粘贴清洗失败等 | | onImagePreUpload | (file: File) => Promise<string> | 否 | - | 图片预上传函数(选择/拖拽文件时触发),返回图片 URL。不提供时图片将以 Base64 插入 | | onImageUpload | (payload: { file: File; url: string; alt?: string }) => void \| Promise<void> | 否 | - | 图片 Confirm 回调(仅在点击 Confirm 后触发) | | onVideoPreUpload | (file: File) => Promise<string> | 否 | - | 视频预上传函数(选择/拖拽文件时触发),返回视频 URL。不提供时视频将以 Base64 插入 | | onVideoUpload | (payload: { file: File; url: string; title?: string }) => void \| Promise<void> | 否 | - | 视频 Confirm 回调(仅在点击 Confirm 后触发) | | onFilePreUpload | (file: File) => Promise<{ url: string; name: string }> | 否 | - | 附件预上传函数(选择/拖拽文件时触发),返回文件 URL 与展示名 | | onFileUpload | (payload: { file: File; url: string; name: string }) => void \| Promise<void> | 否 | - | 附件 Confirm 回调(仅在点击 Confirm/Insert Link 后触发) | | editorMode | 'notion-like' \| 'headless' | 否 | 'notion-like' | 编辑器模式:Notion 风格(斜杠命令、块状编辑等)或无头模式 | | theme | 'light' \| 'dark' | 否 | 自动跟随 html.dark(无 dark 时为 light) | 编辑器主题。传入时强制使用传入值;不传时仅根据 html.dark 自动解析 | | headlessToolbarMode | 'always' \| 'on-focus' | 否 | 'always' | 仅在 editorMode='headless' 时生效always:工具栏始终显示;on-focus:编辑器聚焦或点击工具栏时显示,失焦到编辑器区域外时隐藏 | | commandMenuMaxHeight | number | 否 | 240 | 斜杠命令菜单最大高度(px) | | commandMenuMinHeight | number | 否 | 160 | 斜杠命令菜单最小高度(px) | | language | 'zh-CN' \| 'en-US' | 否 | 自动解析浏览器语言(zh* -> zh-CN,其余 -> en-US) | 控制工具栏、斜杠命令、弹窗与默认 placeholder 文案 | | placeholder | string | 否 | NotionLike: "Type '/' for commands...";Headless: "Start typing..." | 编辑器为空时的占位文本。不传时按模式使用上述默认值;传入后两种模式均使用该值 | | disabled | boolean | 否 | false | 是否禁用编辑器(只读) | | onChangeDebounceMs | number | 否 | 300 | onChange 防抖延迟(毫秒) | | border | boolean | 否 | true | 是否显示编辑器容器边框 | | imageMaxSizeBytes | number | 否 | 5242880(5MB) | 图片上传最大体积(字节),超过则拒绝并提示 | | videoMaxSizeBytes | number | 否 | 52428800(50MB) | 视频上传最大体积(字节),超过则拒绝并提示 | | fileMaxSizeBytes | number | 否 | 10485760(10MB) | 附件上传最大体积(字节),超过则拒绝并提示 | | fileUploadTypes | string[] | 否 | ['pdf'] | 附件可上传扩展名列表(不区分大小写),如 ['pdf', 'docx']。会自动去空格、去重、去掉前导 .;为空时回落到默认 ['pdf'] | | codeBlockLanguages | Array<{ value: string; label: string }> | 否 | 内置 20 种常用语言 | 代码块语言列表。传入后覆盖内置列表(会自动去重并回落无效语言) | | textColorOptions | Array<{ name: string; value: string }> | 否 | 内置默认文字颜色列表 | 顶部工具栏与气泡菜单共用的文字颜色预设列表。传入后覆盖默认文字颜色面板选项 | | highlightColorOptions | Array<{ name: string; value: string }> | 否 | 内置默认高亮颜色列表 | 顶部工具栏与气泡菜单共用的高亮颜色预设列表。传入后覆盖默认高亮颜色面板选项 | | defaultCodeBlockLanguage | string | 否 | 'plaintext' | 新增代码块默认语言。传入无效值时自动回落 plaintext | | onCodeBlockFormat | (payload: { code: string; language: string }) => string \| Promise<string> | 否 | - | 代码块格式化回调。点击代码块右上角“格式化代码”按钮时触发;返回值会覆盖当前代码块文本内容(保留语言) | | formulaCategories | FormulaPickerCategory[] | 否 | 内置默认分类 | 公式选择器的分类列表。不传则使用内置分类;传入时可完全自定义或在默认基础上扩展(见下方「扩展公式分类」) | | maxHeight | number \| string | 否 | - | 编辑器容器的最大高度。不配置时容器为 height: 100%;配置后高度限制为该值,内容超出时在编辑区内滚动。数字为像素(如 400),字符串为任意合法 CSS 长度(如 "50vh""20rem") | | toolbarItems | ToolbarItemConfig[] | 否 | 内置默认工具栏 | 工具栏配置:支持重排/裁剪内置按钮与追加自定义按钮 | | slashCommands | SlashCommandConfig[] | 否 | 内置默认斜杠命令 | 斜杠配置:支持重排/裁剪内置命令与追加自定义命令 | | hideDefaultToolbarItems | boolean | 否 | false | 为 true 时不注入默认工具栏项,仅渲染 toolbarItems | | hideDefaultSlashCommands | boolean | 否 | false | 为 true 时不注入默认斜杠命令,仅使用 slashCommands | | extensions | AnyExtension[] | 否 | [] | 外部 TipTap 扩展,按顺序追加在内置扩展后 | | editorConfigVersion | string \| number | 否 | - | 高级开关:值变化时强制重建 editor(用于创建期配置刷新) |

Headless 模式下的工具栏显示

editorMode="headless" 时,可以通过 headlessToolbarMode 控制顶部工具栏的显示策略:

  • headlessToolbarMode="always":工具栏始终显示。
  • headlessToolbarMode="on-focus":当编辑器获得焦点或用户点击工具栏区域时显示;当焦点离开整个编辑器容器(包括工具栏)后隐藏。点击工具栏按钮本身不会导致工具栏立即消失。

示例:

<ReactTiptapEditor editorMode="headless" headlessToolbarMode="on-focus" />

功能说明

国际化(i18n)

  • 当前支持语言:zh-CNen-US
  • 可通过 language 属性显式指定语言,例如:language="zh-CN"language="en-US"
  • 不传 language 时,默认跟随浏览器语言:zh* -> zh-CN,其余 -> en-US
  • placeholder 优先级高于语言默认文案:传入 placeholder 后,会覆盖不同语言下的默认占位文本
  • 如需新增语言,可扩展 src/locales 下的语言文件,并在 src/locales/index.tslocaleMap 中注册映射

斜杠命令

在编辑器中输入 / 可以打开命令菜单,快速插入以下内容:

  • 标题:H1、H2、H3
  • 列表:有序列表、无序列表、任务列表
  • 引用块:带左边框的引用样式
  • 代码块:语法高亮的代码块
  • 表格:插入 3x3 表格
  • 图片:打开图片上传对话框
  • 视频:打开视频上传对话框
  • 数学公式:行内公式、块级公式
  • 分割线:水平分隔线

代码块语言选择与操作

  • 光标位于代码块中时,原位置(代码块下方右侧)会显示“代码语言(Code language)”按钮。
  • 点击后可选择语言,选中项会高亮,代码块会即时按语言高亮渲染。
  • 默认内置常用 20 种语言(含 plaintext 兜底)。
  • 鼠标悬浮代码块时,右上角会显示代码块操作栏(复制 + 格式化 + 删除)。
  • 未传入 onCodeBlockFormat 时,“格式化代码”按钮为禁用态。
import {
  ReactTiptapEditor,
  DEFAULT_CODE_BLOCK_LANGUAGES,
  DEFAULT_CODE_BLOCK_LANGUAGE,
  type CodeBlockLanguageOption,
} from 'zt-reactjs-tiptap'

const customLanguages: CodeBlockLanguageOption[] = [
  { value: 'plaintext', label: '纯文本' },
  { value: 'javascript', label: 'JavaScript' },
  { value: 'typescript', label: 'TypeScript' },
  { value: 'python', label: 'Python' },
]

<ReactTiptapEditor
  defaultCodeBlockLanguage={DEFAULT_CODE_BLOCK_LANGUAGE}
  codeBlockLanguages={customLanguages}
/>

如果你希望接入业务侧格式化(例如调用后端、Monaco、Prettier Worker 等),可传入 onCodeBlockFormat

<ReactTiptapEditor
  onCodeBlockFormat={async ({ code, language }) => {
    const response = await fetch('/api/format-code', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ code, language }),
    })
    const data = await response.json()
    return data.formattedCode ?? code
  }}
/>

操作方式

  • 输入 / 打开命令菜单
  • 使用 ↑ ↓ 方向键选择
  • 按 Enter 确认插入
  • 按 Escape 关闭菜单

HTML 粘贴清洗(默认开启)

  • 仅对剪贴板中的 text/html 内容进行清洗,纯文本粘贴行为保持不变
  • 会移除高风险/无效标签(如 scriptstyleiframe 等)、内联事件属性(on*)、内联 style
  • 会清理 Office 常见污染 class(如 Mso*)并解包无意义 span/font 包裹
  • 清洗异常时会回退到原生粘贴流程;若传入 onError,会收到 source: 'paste'stage: 'transform' 的错误事件

工具栏自定义(重排内置项 + 新增自定义按钮)

import { Clock3 } from 'lucide-react'
import {
  ReactTiptapEditor,
  BuiltinToolbarItemKey,
  type ToolbarItemConfig,
} from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

const toolbarItems: ToolbarItemConfig[] = [
  { type: 'builtin', key: BuiltinToolbarItemKey.Heading, group: 'block' },
  { type: 'builtin', key: BuiltinToolbarItemKey.Blockquote, group: 'block' },
  { type: 'builtin', key: BuiltinToolbarItemKey.Bold, group: 'format' },
  { type: 'builtin', key: BuiltinToolbarItemKey.Italic, group: 'format' },
  {
    type: 'custom',
    key: 'insert-timestamp',
    title: '插入时间',
    group: 'custom',
    icon: <Clock3 size={16} />,
    onClick: ({ editor }) => {
      editor.chain().focus().insertContent(`[${new Date().toLocaleString()}]`).run()
    },
  },
]

<ReactTiptapEditor toolbarItems={toolbarItems} hideDefaultToolbarItems />

可选接入:导出 PDF(独立 npm 包)

zt-reactjs-tiptap 默认不内置 PDF 导出能力。你可以按需安装独立包 zt-tiptap-export-pdf,并通过 toolbarItems 自定义按钮接入。

import { Download } from 'lucide-react'
import {
  ReactTiptapEditor,
  type ToolbarItemConfig,
} from 'zt-reactjs-tiptap'
import { exportEditorToPdf } from 'zt-tiptap-export-pdf'
import 'zt-reactjs-tiptap/style.css'

const toolbarItems: ToolbarItemConfig[] = [
  {
    type: 'custom',
    key: 'export-pdf',
    title: '导出 PDF',
    group: 'custom',
    icon: <Download size={16} />,
    onClick: async ({ editor }) => {
      // 使用编辑器根 DOM 作为导出源。
      const root = editor.view.dom as HTMLElement
      await exportEditorToPdf(root, { filename: 'editor.pdf' })
    },
  },
]

<ReactTiptapEditor toolbarItems={toolbarItems} />

Slash 命令自定义(保留默认 + 追加自定义命令)

import { Lightbulb } from 'lucide-react'
import {
  ReactTiptapEditor,
  type SlashCommandConfig,
} from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

const slashCommands: SlashCommandConfig[] = [
  {
    type: 'custom',
    key: 'callout',
    title: 'Callout',
    description: '插入提示块',
    icon: Lightbulb,
    command: ({ editor }) => {
      editor
        .chain()
        .focus()
        .insertContent('<blockquote><p>💡 提示内容</p></blockquote>')
        .run()
    },
    disabled: ({ editor }) => editor.isActive('table'),
  },
]

<ReactTiptapEditor slashCommands={slashCommands} />

注入外部扩展(追加到内置扩展后)

import Typography from '@tiptap/extension-typography'
import { ReactTiptapEditor } from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

<ReactTiptapEditor extensions={[Typography]} />

扩展顺序为:内置扩展 -> 你传入的 extensions。若存在同名行为冲突,请在业务侧自行确认兼容性。 库内部已做浅稳定处理,toolbarItems / slashCommands / extensions 即使每次传新数组引用,也不会因此无意义重建 editor。

气泡菜单

选中文本时会显示浮动工具栏,包含以下功能:

  • 文本样式:粗体、斜体、下划线、删除线、行内代码
  • 颜色:文本颜色(10种预设 + 自定义)、背景高亮
  • 对齐:左对齐、居中、右对齐、两端对齐
  • 标题:快速切换 H1/H2/H3/正文
  • 上标/下标:数学公式常用
  • 清除格式:一键移除所有格式

表格操作

插入表格后,点击表格任意位置会显示表格工具栏,支持:

  • 在上方/下方插入行
  • 在左侧/右侧插入列
  • 删除当前行/列
  • 设置行/列单元格水平与垂直对齐
  • 切换表头行
  • 删除整个表格

提示:鼠标悬停在表格上会显示操作按钮。

数学公式

基于 KaTeX 支持 LaTeX 格式的数学公式:

  • 行内公式:使用斜杠命令选择"行内公式",例如:E = mc^2
  • 块级公式:使用斜杠命令选择"块级公式",例如:
    \int_{-\infty}^{\infty} e^{-x^2} dx = \sqrt{\pi}
  • 编辑公式:点击已插入的公式可以打开编辑对话框修改
  • 实时预览:编辑时实时渲染公式效果

扩展公式分类:可通过 formulaCategories 自定义或扩展公式选择器中的分类与片段。库会导出 FORMULA_CATEGORIESFormulaPickerCategoryFormulaSnippetItem,便于在默认基础上追加分类:

import {
  ReactTiptapEditor,
  FORMULA_CATEGORIES,
  type FormulaPickerCategory,
  type FormulaSnippetItem,
} from 'zt-reactjs-tiptap'
import 'zt-reactjs-tiptap/style.css'

const customCategory: FormulaPickerCategory = {
  id: 'custom',
  title: '自定义',
  items: [
    { id: 'my-1', label: '我的公式', latex: '\\mycommand{x}' },
  ] as FormulaSnippetItem[],
}

<ReactTiptapEditor
  formulaCategories={[...FORMULA_CATEGORIES, customCategory]}
/>

HTML 转纯文本

库导出 htmlToPlainTextdomToPlainText,使用与编辑器相同的 schema 将 HTML 或 DOM 转为纯文本,结果与编辑器实例的 getText() 一致(如图片→[image]、公式→[latex]、表格→[table] 等)。

import { htmlToPlainText, domToPlainText } from 'zt-reactjs-tiptap'

// 从 HTML 字符串
const text = htmlToPlainText('<p>Hello</p><p>World</p>')
// => 'Hello\nWorld'

// 从 DOM 元素(如编辑器内容区)
const el = document.querySelector('.editor-content')
const text2 = domToPlainText(el)

// 单行模式(块之间用空格、换行替换为空格)
const oneLine = htmlToPlainText(html, { singleLine: true })

// 自定义块分隔符
const custom = htmlToPlainText(html, { blockSeparator: '\n\n' })

选项 HtmlToPlainTextOptions

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | blockSeparator | string | '\n' | 块级节点之间的分隔符;singleLine: true 时视为 ' ' | | singleLine | boolean | false | 为 true 时块之间用空格连接,结果中的换行会替换为空格 |

图片上传

支持两种方式插入图片:

  1. 文件上传:拖拽或选择本地图片文件
  2. URL 插入:通过图片链接地址插入

图片处理

  • 如果提供 onImagePreUpload 函数,将执行预上传并使用返回 URL 预览/插入
  • 如果没有提供 onImagePreUpload,图片将以 Base64 格式直接嵌入(适合小图片)
  • onImageUpload 仅在点击 Confirm 后触发,不负责执行上传
  • 支持 JPG、PNG、GIF 等常见格式
  • 建议图片大小不超过 5MB
  • 图片悬浮后可拖动左右手柄按百分比调整宽度,选中后可通过图片操作栏设置左对齐、居中、右对齐
  • 图片下方支持可开关的多行描述;开启且填写内容后,导出 HTML 时会使用 figure + figcaption

附件上传

  • 附件上传通过 onFilePreUpload + onFileUpload 配置
  • 默认仅允许 pdf
  • 可通过 fileUploadTypes 自定义可上传扩展名(如 ['pdf', 'docx']
  • 拖拽上传与文件选择共用同一套类型校验和大小校验

视频上传

  • 视频上传通过 onVideoPreUpload + onVideoUpload 配置
  • 支持两种方式:文件上传与 URL 插入
  • 如果提供 onVideoPreUpload 函数,将执行预上传并使用返回 URL 预览/插入
  • 如果没有提供 onVideoPreUpload,视频将以 Base64 格式直接插入
  • onVideoUpload 仅在点击 Confirm 后触发,不负责执行上传

任务列表

支持创建可交互的待办事项列表:

  • 使用斜杠命令插入"任务列表"
  • 点击复选框可切换完成状态
  • 支持嵌套任务(任务列表内再插入任务列表)

快捷键

| 快捷键 | 功能 | |--------|------| | Ctrl/Cmd + B | 粗体 | | Ctrl/Cmd + I | 斜体 | | Ctrl/Cmd + U | 下划线 | | Ctrl/Cmd + Z | 撤销 | | Ctrl/Cmd + Shift + Z | 重做 | | / | 打开斜杠命令菜单 | | Tab | 增加列表缩进 | | Shift + Tab | 减少列表缩进 |

依赖要求

  • React >= 18.0.0
  • React DOM >= 18.0.0

样式系统

样式导入

import 'zt-reactjs-tiptap/style.css'

样式文件包含:

  • ✅ Tailwind CSS v4 基础样式
  • ✅ 作用域化的 shadcn/ui 主题变量(仅作用于编辑器容器)
  • ✅ 编辑器组件样式
  • ✅ KaTeX 数学公式样式

文件大小:CSS 约 1.5MB(gzip 后约 950KB)

在其它项目中使用时样式不对?

  1. 务必导入样式:在使用编辑器的组件或入口文件顶部导入 import 'zt-reactjs-tiptap/style.css'

  2. 导入顺序:若希望用你项目的样式覆盖编辑器默认样式,建议先导入 zt-reactjs-tiptap/style.css,再导入你的全局样式

  3. 作用域主题变量:本库的 shadcn 变量只在 .zt-tiptap-theme 作用域内生效,不会覆盖宿主项目的全局 :root 变量

  4. 局部主题定制:可在宿主项目中按编辑器容器局部覆盖变量,例如:

.zt-tiptap-theme {
  --primary: oklch(0.58 0.19 260);
  --background: oklch(0.99 0 0);
}
  1. UI 主题 Token 化:编辑器 UI(工具栏、菜单、表格操作、弹窗、输入框、滚动条等)已统一使用主题 token。默认 light 视觉与旧版保持一致,切换 theme="dark" 时同一套 token 会自动映射为暗色值。

  2. 弹层/对话框:图片、视频、公式、附件弹层通过 Portal 挂载到编辑器容器内,继承同一套作用域变量;若样式仍异常,优先检查宿主全局 reset 或高优先级选择器

开发

本项目使用 pnpm、React 19、Vite 7、TypeScript 5.9、Tailwind CSS v4、TipTap 3.x。

# 安装依赖
pnpm install

# 开发模式(启动示例应用)
pnpm dev

# 构建示例应用
pnpm build

# 构建库(用于发布到 npm)
pnpm build:lib

# 代码检查
pnpm lint

# 预览构建结果
pnpm preview

License

MIT