使用tiptap开发一个富文本编辑器 —— 起始篇

tiptap介绍

tiptap是一个"无头"的富文本编辑器。"无头",即,非开箱即用,不提供功能面板,需要用户自己开发功能面板的UI。

官方文档地址:Introduction -- Tiptap

tiptap安装(以vue为例)

新建一个vue工程

shell 复制代码
pnpm create vue@latest

# 输入项目名称,配置工程内容等

pnpm i

安装tiptap依赖

shell 复制代码
pnpm install @tiptap/vue-3 @tiptap/pm @tiptap/starter-kit

初始化tiptap

html 复制代码
<template>
  <EditorContent :editor="editor"></EditorContent>
</template>

<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import { StarterKit } from '@tiptap/starter-kit'

const editor = useEditor({
  content: 'hello tiptap',
  extensions: [StarterKit]
})
</script>

预览效果如下

好的,相信你已经学会了tiptap编辑器的基本内容,接下来我们就去开发一个飞书文档吧(大雾)。

首先对这3个依赖进行作用分析:

@tiptap/vue3提供vue3里的组件引用与editor暴露,同时,所有在@tiptap/core中导出的api,也会在该库中导出

@tiptap/pm提供tiptapProseMirror的api,tiptap是基于ProseMirror开发,pm库属于必要库。 @tiptap/start-kit为插件合集库,包含了如下内容:

需要配置上述插件能力的地方,可以直接从start-kit中进行处理,也可以不使用start-kit包,自身配置一个最简易的文本输入可通过配置DocumentTextParagraph3个插件实现。

tiptap功能面板实现

首先准备一份富文本图标(本文从iconfont上白嫖一份公开库:富文本编辑器图标),并实现一下基本UI和交互逻辑

tiptap的outline可以通过注入属性的方式去除,根据当前的UI,需要实现如下功能:

根据API实现代码:

html 复制代码
<template>
  <div style="border: 2px solid #dcdfe6; width: 600px; height: 300px">
    <div class="tool-panel">
      <div
        :class="['btn', editor.isActive('bold') && 'active']"
        @click="editor.chain().focus().toggleBold().run()"
      >
        <i class="icon iconfont icon-bold"></i>
      </div>
      <div
        :class="['btn', editor.isActive('italic') && 'active']"
        @click="editor.chain().focus().toggleItalic().run()"
      >
        <i class="icon iconfont icon-italic"></i>
      </div>
      <div
        :class="['btn', editor.isActive('orderedList') && 'active']"
        @click="editor.chain().focus().toggleOrderedList().run()"
      >
        <i class="icon iconfont icon-list-order"></i>
      </div>
      <div
        :class="['btn', editor.isActive('bulletList') && 'active']"
        @click="editor.chain().focus().toggleBulletList().run()"
      >
        <i class="icon iconfont icon-list-disorder"></i>
      </div>
      <div
        :class="['btn', editor.isActive('blockquote') && 'active']"
        @click="editor.chain().focus().toggleBlockquote().run()"
      >
        <i class="icon iconfont icon-double-quotes-left"></i>
      </div>
      <div class="btn" @click="editor.chain().focus().setHorizontalRule().run()">
        <i class="icon iconfont icon-move-horizontal"></i>
      </div>
      <div class="btn" @click="editor.chain().focus().toggleCodeBlock().run()">
        <i class="icon iconfont icon-code-block"></i>
      </div>
    </div>
    <div style="height: calc(100% - 30px)">
      <EditorContent :editor="editor" class="tiptap-container"></EditorContent>
    </div>
  </div>
</template>

<script setup>
import { useEditor, EditorContent } from '@tiptap/vue-3'
import { StarterKit } from '@tiptap/starter-kit'
const editor = useEditor({
  content: 'hello tiptap',
  extensions: [StarterKit],
  editorProps: {
    attributes: {
      class: 'tiptap-editor'
    }
  }
})
</script>

<style>
.tiptap-editor {
  width: 100%;
  height: 100%;
  outline: none;
}

.tiptap-container {
  width: 100%;
  height: 100%;
}

.tool-panel {
  border-bottom: 2px solid #dcdfe6;
  height: 30px;
  display: flex;
  align-items: center;
}

.icon {
  font-size: 20px;
}
.btn {
  width: 30px;
  height: 30px;
  line-height: 30px;
  text-align: center;
  cursor: pointer;
}
.active {
  background-color: #909399;
}
blockquote {
  padding-left: 1rem;
  border-left: 3px solid rgb(144, 143, 153);
}

pre {
  background: #0d0d0d;
  color: #fff;
  font-family: 'JetBrainsMono', monospace;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
}
pre code {
  color: inherit;
  padding: 0;
  background: none;
  font-size: 0.8rem;
}
</style>

效果图如下:

editor.isActive可以检测当前正在激活的nodemark,可用于确认当前文本为哪种格式(示例中已激活的粗体)

各图标绑定了点击事件,使用editor.chain().focus().xxx().run()。这事使用tiptap中的command能力来使富文本进行指定交互,除了上述方法外,也可以通过editor.commands.xxx(),但是还是推荐使用chain来实现。chain的使用实际上可以进行复合命令输入,即可以实现一次注入多个命令,例如editor.chain().focus().toggleBold().toggleItalic().run()可以同时实现文本的粗体和斜体切换。

重要 :如果未使用run(),命令不会生效,开发时注意不要丢了run的调用。

简易的起始篇到此结束

相关推荐
源猿人21 小时前
企业级文件浏览系统的Vue实现:架构设计与最佳实践
前端·javascript·数据可视化
RoyLin21 小时前
TypeScript设计模式:迭代器模式
javascript·后端·node.js
小桥风满袖1 天前
极简三分钟ES6 - ES9中for await of
前端·javascript
编程贝多芬1 天前
Promise 的场景和最佳实践
前端·javascript
Asort1 天前
JavaScript 从零开始(四):基础语法详解——从变量声明到数据类型的完全指南
前端·javascript
木木jio1 天前
前端大文件分片上传 —— 基于 React 的工程化实现
前端·javascript
Lotzinfly1 天前
12个TypeScript奇淫技巧你需要掌握😏😏😏
前端·javascript·面试
一个大苹果1 天前
setTimeout延迟超过2^31立即执行?揭秘JavaScript定时器的隐藏边界
javascript
普郎特1 天前
"不再迷惑!用'血缘关系'彻底搞懂JavaScript原型链机制"
前端·javascript
一枚前端小能手1 天前
「周更第3期」实用JS库推荐:Lodash
前端·javascript