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

@tdh-editor/mohai-vue2

v1.0.0

Published

🌸A modern WYSIWYG rich-text editor using tiptap and Element UI for Vue.js

Downloads

23

Readme

TDH Tiptap Editor

TDH Tiptap Editor 是一个基于 Tiptap 2ProseMirror 和 Element UI 的 Vue 2 所见即所得富文本编辑器。

编辑器以扩展(Extension)为基本功能单元。通过扩展配置文档节点、文本样式、表格、图片、自动保存、导出和自定义菜单等功能,并通过 Tiptap Editor 实例扩展编辑行为。

特性

  • Vue 2 和 TypeScript 支持,npm 包名为 @tdh-editor/mohai-vue2
  • HTML 和 ProseMirror JSON 内容格式。
  • 菜单栏、气泡菜单、只读、全屏、预览和打印。
  • 标题、列表、任务列表、引用、代码块、表格、链接、图片和 iframe。
  • 字体、字号、文字颜色、文字高亮、对齐、缩进、行高和字间距。
  • 搜索、格式清除、格式刷、范围高亮和流式内容更新。
  • 本地图片粘贴、拖拽、网络地址插入和异步图片上传。
  • 基于 localStorage 的自动保存,以及自定义保存回调。
  • Word(.docx)导出、文件名配置和页边距配置。
  • 中文(zh)和英文(en)界面,默认语言为中文。
  • 自定义 Extension、菜单按钮、菜单插槽、内容插槽和底部插槽。

兼容性

  • Vue ^2.6.10
  • Tiptap 2.x
  • 包管理器:pnpm 9.15.9
  • Element UI 不包含在编辑器构建产物中。宿主项目应安装并注册 Element UI、tdh-element-ui 或其他提供兼容组件和服务 API 的组件库。

示例与本地开发

项目使用 pnpm workspace 管理编辑器源码和 examples 示例应用。

pnpm install
pnpm dev

示例应用包含基础用法、扩展总览、气泡菜单、占位符、只读、标题、事件、内容输出、搜索、范围高亮、图片、自定义插槽和流式输出等页面。开发服务器启动后,访问终端输出的地址即可查看示例。

根目录脚本:

pnpm dev            # 启动 examples 开发服务器
pnpm build:lib      # 构建编辑器库到 lib/
pnpm build:example  # 构建 examples 应用
pnpm build          # 等同于 pnpm build:lib
pnpm lint           # 检查 src 和 examples
pnpm lint:source    # 只检查 src

examples 目录执行以下命令,可单独操作示例应用:

pnpm --dir examples dev
pnpm --dir examples build
pnpm --dir examples lint

安装

pnpm add @tdh-editor/mohai-vue2 element-ui

采用 tdh-element-ui 或其他兼容实现时,应将组件库依赖和样式路径替换为对应包的配置。

全局注册

import Vue from 'vue';
import ElementUI, { MessageBox } from 'element-ui';
import { ElementTiptapPlugin } from '@tdh-editor/mohai-vue2';

import 'element-ui/lib/theme-chalk/index.css';
import '@tdh-editor/mohai-vue2/lib/index.css';

Vue.use(ElementUI);

// 图片上传和 iframe 插入功能使用 $prompt 服务。
Vue.prototype.$prompt = MessageBox.prompt;

Vue.use(ElementTiptapPlugin, {
  lang: 'zh',
  spellcheck: true,
});

// 完成注册后,可在组件中使用 <el-tiptap />。

编辑器依赖以下 Element UI 全局组件。按需引入组件库时,应注册这些组件:

  • ButtonCheckboxCol
  • Dialog
  • DropdownDropdownMenuDropdownItem
  • FormFormItem
  • InputInputNumber
  • PopoverTooltipUpload

图片上传和 iframe 插入还依赖 Loading 服务以及 MessageBox.prompt。上述组件和服务必须使用与 ElementTiptapPlugin 相同的 Vue 实例注册。

局部注册

局部注册方式如下:

<template>
  <el-tiptap
    v-model="content"
    :extensions="extensions"
  />
</template>

<script>
import {
  Doc,
  ElementTiptap,
  Paragraph,
  Text,
} from '@tdh-editor/mohai-vue2';

export default {
  components: {
    'el-tiptap': ElementTiptap,
  },
  data() {
    return {
      content: '<p>示例内容</p>',
      extensions: [
        Doc.configure(),
        Text.configure(),
        Paragraph.configure(),
      ],
    };
  },
};
</script>

局部注册仅影响编辑器组件的注册方式,不改变 Element UI 组件的注册要求及语言、拼写检查的插件级默认配置。单实例配置可通过 langspellcheck 属性覆盖插件默认值。

基础用法

组件仅自动附加占位符和内容属性扩展,不会自动添加文档结构扩展。编辑器配置至少应包含 DocTextParagraph

<template>
  <el-tiptap
    v-model="content"
    :extensions="extensions"
    placeholder="请输入内容..."
    lang="zh"
  />
</template>

<script>
import {
  Bold,
  BulletList,
  Doc,
  Heading,
  History,
  Italic,
  ListItem,
  OrderedList,
  Paragraph,
  Text,
  Underline,
} from '@tdh-editor/mohai-vue2';

export default {
  data() {
    return {
      content: '<h2>TDH Tiptap Editor</h2><p>示例内容。</p>',
      extensions: [
        Doc.configure(),
        Text.configure(),
        Paragraph.configure(),
        Heading.configure({ levels: [1, 2, 3, 4, 5, 6] }),
        Bold.configure(),
        Underline.configure(),
        Italic.configure(),
        ListItem.configure(),
        BulletList.configure(),
        OrderedList.configure(),
        History.configure(),
      ],
    };
  },
};
</script>

菜单按钮的顺序由 extensions 中的扩展顺序决定。扩展默认显示在菜单栏中;配置 bubble: true 后,扩展菜单项可显示在气泡菜单中:

Bold.configure({ bubble: true });

// 仅显示在气泡菜单,不显示在菜单栏
Underline.configure({ bubble: true, menubar: false });

扩展

所有扩展均从包入口导出,并通过 .configure(options) 配置。

文档与文本

| 扩展 | 说明 | | --- | --- | | Doc | 文档根节点 | | Title | 文档标题节点;需配合 Doc.configure({ title: true }) | | Text | 文本节点 | | Paragraph | 段落 | | Heading | 标题,默认支持 1 到 6 级 | | Blockquote | 引用 | | CodeCodeBlock | 行内代码和代码块 | | HardBreakHorizontalRule | 换行和水平分隔线 | | TrailingNode | 在文档末尾保留可编辑段落 |

标题示例:

Doc.configure({ title: true }),
Title.configure({ placeholder: '文档标题' }),
Text.configure(),
Paragraph.configure(),

列表、表格和媒体

| 扩展 | 依赖或说明 | | --- | --- | | ListItemBulletListOrderedList | 无序列表和有序列表的依赖组合 | | TodoItemTodoList | 任务列表的依赖组合;TodoItem.configure({ nested: true }) 开启嵌套任务列表 | | TableTableHeaderTableCellTableRow | 表格的依赖组合;Table.configure({ resizable: true }) 开启列宽调整 | | Image | 网络图片、本地图片、粘贴和拖拽图片 | | Iframe | 插入 iframe |

图片异步上传:

Image.configure({
  uploadRequest: async (file) => {
    const response = await uploadImage(file);
    return response.url;
  },
});

图片首先以 base64 形式插入编辑器。uploadRequest 返回最终 URL 后,编辑器将使用该 URL 替换临时地址;返回空字符串时保留 base64 图片,抛出异常时删除临时图片。

文本样式与段落格式

| 扩展 | 说明 | | --- | --- | | BoldItalicUnderlineStrike | 常用文字样式 | | Link | 链接,默认支持粘贴链接和在新标签页打开 | | ColorTextColor | 文字颜色;示例中同时注册两个扩展 | | TextHighlight | 多颜色文字高亮 | | TextStyleFontTypeFontSize | 字体、字号和文字样式基础;字体或字号功能需要注册 TextStyle | | TextAlign | 左对齐、居中、右对齐和两端对齐 | | Indent | 增加或减少缩进 | | LineHeight | 行高 | | LetterSpacing | 字间距 |

字体和字号示例:

TextStyle.configure(),
FontType.configure({
  customFonts: [
    { name: '宋体', value: 'SimSun' },
    { name: '黑体', value: 'SimHei' },
  ],
  customFontMode: 'prepend', // prepend 或 replace
}),
FontSize.configure(),

工具扩展

| 扩展 | 说明 | | --- | --- | | History | 撤销和重做 | | FormatClear | 清除格式 | | FormatPainter | 复制和应用格式 | | Preview | 预览当前 HTML 内容 | | Print | 打印当前内容,支持 Mod-p 快捷键 | | Fullscreen | 全屏编辑模式 | | SelectAll | 全选内容 | | Search | 搜索文本、区分大小写、全词匹配和结果定位 | | PageMargin | 调整编辑区和 Word 导出的页边距 | | Export | 导出为 Word,格式为 .docx | | CodeView | 基于 CodeMirror 编辑 HTML | | HighlightRange | 按文本索引添加、移除、替换和定位范围高亮 |

CodeMirror 代码视图

CodeView 需要额外安装并传入 CodeMirror 实例:

pnpm add codemirror
import codemirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
import 'codemirror/mode/xml/xml.js';
import 'codemirror/addon/edit/closetag.js';

CodeView.configure({
  codemirror,
  codemirrorOptions: {
    styleActiveLine: true,
    autoCloseTags: true,
  },
});

搜索

注册 Search 后,通过 Tiptap Editor 实例调用搜索命令:

const matches = this.$refs.editor.editor.commands.search({
  searchText: '关键字',
  caseSensitive: false,
  wholeWord: false,
  index: 1,
});

命令返回匹配范围数组;index 从 1 开始,超出有效范围时使用第一个匹配结果。

范围高亮

HighlightRange 使用文档纯文本索引定位范围:

const { commands } = this.$refs.editor.editor;

commands.addHighlight([
  {
    id: 'review-1',
    startIndex: 0,
    endIndex: 8,
    highlightColor: '#ffff00',
  },
]);

commands.scrollToHighlight('review-1');
commands.replaceHighlight({
  id: 'review-1',
  replaceText: '替换后的内容',
});
commands.removeHighlight('review-1');

Props

以下属性由 el-tiptap 组件提供。除特别说明外,默认值以源码定义为准。

| 属性 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | extensions | Array | [] | Tiptap 扩展数组;应至少配置 DocTextParagraph | | content | String | '' | 初始内容,支持通过 v-model 双向绑定 | | contentType | 'html' \| 'json' \| 'markdown' | 'html' | 初始 content 的输入格式;设置为 markdown 时支持 Markdown 输入 | | output | 'html' \| 'json' \| 'markdown' | 'html' | onUpdate、保存回调和 getEditorOutput 的输出格式;不影响 content 的输入解析 | | placeholder | String | '' | 空内容时的占位文本 | | editorProperties | Object | {} | 传入 Tiptap Editor 的其他配置,例如 editorProps | | readonly | Boolean | false | 是否只读 | | spellcheck | Boolean | 插件配置值 | 是否启用浏览器拼写检查 | | tooltip | Boolean | true | 是否显示菜单按钮提示 | | lang | String | 插件配置值(默认 zh) | 支持 zhen | | widthheight | String \| Number | undefined | 编辑器尺寸;数字会按 px 处理 | | showMenubar | Boolean | true | 是否显示菜单栏 | | charCounterCount | Boolean | true | 是否显示字数统计 | | menuBubbleOptions | Object | {} | 传入 Tiptap BubbleMenu 的配置,例如 shouldShowpluginKeytippyOptions | | editorClass | String \| Array \| Object | undefined | 编辑器根节点 class | | editorContentClass | String \| Array \| Object | undefined | 编辑器内容区域 class | | editorMenubarClass | String \| Array \| Object | undefined | 菜单栏 class | | editorBubbleMenuClass | String \| Array \| Object | undefined | 气泡菜单 class | | editorFooterClass | String \| Array \| Object | undefined | 底部区域 class | | autoSave | Boolean | false | 是否启用定时自动保存 | | autoSaveInterval | Number | 300000 | 自动保存间隔,单位为毫秒 | | storageKey | String | 'el-tiptap-autosave' | 默认 localStorage 保存键 | | saveHandler | Function | undefined | 自定义保存回调,签名为 (content, isAutoSave) => void \| Promise<void> | | downloadFileName | String | 时间戳文件名 | Export 导出 Word 时使用的文件名,扩展名会自动补全为 .docx |

editorProperties

通过 editorProperties.editorProps 配置 ProseMirror 编辑器属性:

<el-tiptap
  :extensions="extensions"
  :editor-properties="{
    editorProps: {
      handleKeyDown(view, event) {
        console.log('keydown', event.key);
      },
    },
  }"
/>

组件会接管 contenteditableextensionsonFocusonBluronPasteonDroponTransactiononUpdate。这些字段不应通过 editorProperties 覆盖,请使用组件 Props 或事件监听。

内容输出

默认输出格式为 HTML:

<el-tiptap
  v-model="content"
  :extensions="extensions"
  output="html"
  @onUpdate="handleUpdate"
/>

配置为 JSON 输出:

<el-tiptap
  v-model="content"
  :extensions="extensions"
  output="json"
/>

output="json" 时,contentonUpdate 和保存相关 API 使用 JSON 字符串,而不是 JSON 对象。JSON 结构遵循 Tiptap/ProseMirror 文档结构。

配置 Markdown 输入时,Markdown 会先转换为 HTML,再交给当前 schema 解析;输入和输出格式可以分别配置:

<el-tiptap
  v-model="content"
  :extensions="extensions"
  content-type="markdown"
  output="html"
  placeholder="Markdown 输入,HTML 输出"
/>

也可以让输入和输出都使用 Markdown:

<el-tiptap
  v-model="content"
  :extensions="extensions"
  content-type="markdown"
  output="markdown"
  placeholder="请输入 Markdown 内容..."
/>

自定义节点会通过节点的 renderHTML 输出为 HTML,并在 Markdown 往返时保留。已有 CustomSlot 节点可直接在 Markdown 中使用原始 HTML,例如:

段落中的自定义节点:<span data-type="customSlot" data-slot-name="custom" data-xx="demo"></span>

也可以单独使用导出的 markdownToHTMLhtmlToMarkdown 方法。

自动保存与手动保存

启用 autoSave 后,编辑器按 autoSaveInterval 定时保存。未配置 saveHandler 时,内容保存到浏览器 localStorage

<el-tiptap
  ref="editor"
  v-model="content"
  :extensions="extensions"
  :auto-save="true"
  :auto-save-interval="60000"
  storage-key="article-1001"
  @onSave="handleSave"
/>

启用自动保存时,默认页脚显示保存状态,并提供“从缓存中恢复”和“保存”操作。自动保存会跳过空文档和未发生变化的内容;手动调用 save() 仍执行保存。

业务接口场景下,通过 saveHandler 指定保存逻辑。回调返回 Promise 时,Promise resolve 后更新保存状态并触发 onSave

<el-tiptap
  ref="editor"
  :content="content"
  :extensions="extensions"
  :save-handler="saveContent"
  @onSave="handleSave"
/>
export default {
  methods: {
    async saveContent(content, isAutoSave) {
      await api.saveArticle({
        id: this.articleId,
        content,
        autoSave: isAutoSave,
      });
    },

    handleSave(content) {
      console.log('保存成功', content);
    },
  },
};

手动保存与获取编辑器输出:

await this.$refs.editor.save();

const output = this.$refs.editor.getEditorOutput();
const persistableOutput = this.$refs.editor.getEditorOutput(true);

getEditorOutput(true) 和保存流程会移除搜索、范围高亮等临时标记,用于持久化或导出;不传参数时返回当前编辑器的完整输出。

Word 导出与页边距

注册 Export 扩展后,菜单栏提供 Word 导出功能。注册 PageMargin 后,可在编辑器中调整页边距,导出时使用当前页边距:

<template>
  <el-tiptap
    :extensions="extensions"
    download-file-name="业务文书"
  />
</template>

<script>
import {
  Doc,
  Export,
  PageMargin,
  Paragraph,
  Text,
} from '@tdh-editor/mohai-vue2';

export default {
  data() {
    return {
      extensions: [
        Doc.configure(),
        Text.configure(),
        Paragraph.configure(),
        Export.configure(),
        PageMargin.configure(),
      ],
    };
  },
};
</script>

导出格式为 .docx。未设置 downloadFileName 时,文件名默认为 document_<timestamp>.docx。导出在浏览器端完成,不依赖服务端接口。

事件

事件按 Vue 事件语法监听:

| 事件 | 参数 | 说明 | | --- | --- | --- | | onInit | { editor } | Tiptap Editor 创建完成 | | onTransaction | { editor, ...payload } | 文档事务更新 | | onFocus | { editor, ...payload } | 编辑器获得焦点 | | onBlur | { editor, ...payload } | 编辑器失去焦点 | | onPaste | { editor, ...payload } | 粘贴内容 | | onDrop | { editor, ...payload } | 拖放内容 | | onUpdate | output, { editor, transaction } | 文档内容更新;outputoutput 属性影响 | | onSave | output | 保存成功;output 为可持久化内容 |

示例:

<el-tiptap
  :extensions="extensions"
  @onInit="handleInit"
  @onUpdate="handleUpdate"
  @onSave="handleSave"
/>
methods: {
  handleInit({ editor }) {
    this.editor = editor;
  },

  handleUpdate(output, { editor, transaction }) {
    console.log(output, editor, transaction);
  },

  handleSave(output) {
    console.log('saved:', output);
  },
}

插槽

menubarmenububble

menubarmenububble 插槽均提供 { editor, t }

<el-tiptap :extensions="extensions">
  <template #menubar="{ editor }">
    <el-button
      :type="editor.isActive('bold') ? 'primary' : ''"
      @click="editor.chain().focus().toggleBold().run()"
    >
      加粗
    </el-button>
  </template>

  <template #menububble="{ editor }">
    <el-button @click="editor.chain().focus().toggleItalic().run()">
      斜体
    </el-button>
  </template>
</el-tiptap>

配置上述插槽后,对应区域渲染调用方提供的内容。气泡菜单仍可通过 menuBubbleOptions 配置行为。

footer

footer 插槽提供 { editor },用于替换默认的字数统计和保存状态区域:

<el-tiptap :extensions="extensions">
  <template #footer="{ editor }">
    <span>当前字数:{{ editor.state.doc.textContent.length }}</span>
  </template>
</el-tiptap>

MenuSlotCustomSlot

MenuSlot 用于在内置菜单流程中插入指定名称的菜单插槽:

MenuSlot.configure({ slotName: 'extra-menu' });
<el-tiptap :extensions="extensions">
  <template #extra-menu="{ editor }">
    <el-button @click="editor.commands.setParagraph()">
      正文
    </el-button>
  </template>
</el-tiptap>

CustomSlot 用于在文档中插入由 Vue 组件渲染的自定义内容节点。以下示例基于已有的基础扩展数组 extensions

<template>
  <el-tiptap :extensions="extensionsWithSlot">
    <template #custom="{ node }">
      <custom-component :data="node" />
    </template>
  </el-tiptap>
</template>

<script>
import { CustomSlot } from '@tdh-editor/mohai-vue2';

export default {
  computed: {
    extensionsWithSlot() {
      return [...this.extensions, CustomSlot.configure()];
    },
  },
};
</script>

自定义扩展和菜单

扩展可通过 menubarbubblemenu 选项控制菜单位置及渲染组件。自定义扩展示例:

import { Bold } from '@tdh-editor/mohai-vue2';
import CustomButton from './CustomButton.vue';

export default Bold.extend({
  addOptions() {
    return {
      ...this.parent?.(),
      menu: (editorContext) => ({
        component: CustomButton,
        componentProps: {
          editorContext,
        },
        componentEvents: {
          confirm: () => editorContext.editor.commands.toggleBold(),
        },
      }),
    };
  },
});

此外,可使用内置的 CustomMenu 创建菜单按钮:

CustomMenu.configure({
  menubar: true,
  bubble: true,
  menuItems: [
    {
      key: 'review',
      label: '审查',
      svgIcon: '<svg viewBox="0 0 24 24"><path fill="currentColor" d="M12 2l2.9 6.2 6.8.8-5 4.7 1.4 6.7L12 17l-6.1 3.4 1.4-6.7-5-4.7 6.8-.8L12 2z" /></svg>',
    },
  ],
  onMenuClick({ key, selectedText, from, to }) {
    console.log(key, selectedText, from, to);
  },
});

onMenuClick 接收菜单键、选中文本以及选区的 fromto 位置。完整示例参见 examples/views/CustomMenubar.vueexamples/views/BubbleMenu.vue

插件配置与国际化

插件配置:

Vue.use(ElementTiptapPlugin, {
  lang: 'en',
  spellcheck: false,
});

支持语言:

  • zh:默认语言
  • en

单个编辑器可通过 lang 属性设置语言:

<el-tiptap lang="en" :extensions="extensions" />

目录结构

src/        编辑器组件、扩展、菜单和工具函数
examples/   Vue 2 示例应用
lib/        构建后的 npm 包产物
types/      补充的 TypeScript 类型声明

相关文档