组会分享:神奇的 Tiptap 编辑器

简介

最近在写 BuddyUp的前端,其中文书生成之后需要一个编辑器(进行一些修改),
简单的contentEditable怎么能行!于是就寻找一个功能丰富、配置简单的富文本编辑器,于是就接触到了大名鼎鼎的 Tiptap
Tiptap 是一个开源的富文本编辑器框架,基于 ProseMirror 构建,具有高度的可定制性和扩展性。它支持 Vue 和 React 的集成,并拥有丰富的插件生态。

下面粘贴一段简介,先水几行:

Tiptap 是一个开源的富文本编辑器框架,基于 ProseMirror 构建,具有高度的可定制性和扩展性。它允许开发者轻松构建功能强大的在线文本编辑工具,适用于博客、论坛、协作文档等多种场景。

Tiptap 提供了模块化设计,所有功能都通过扩展实现,开发者可以根据需求选择和配置扩展。此外,它支持 Vue 和 React 的集成,并拥有丰富的插件生态,如表格、代码块、任务列表等,满足复杂的文本编辑需求。

简而言之,Tiptap提供了无样式的、可拓展的无头富文本编辑器,十分适合开发中需要大段文字编辑的场景。我们开箱即用,拿来就用,十分符合鲁迅的风格!

使用

安装

Tiptap 的入门方法并不困难,我们直接 CV 命令过来,就像这样:

npm install @tiptap/react @tiptap/pm @tiptap/starter-kit

集成

通过下面的代码,我们可以轻松初始化一个 Tiptap 编辑器示例,这就像踩死一只蚂蚁那样简单。不过,注意第 10 - 11 行,如果正在使用 SSR,请务必这么设置。

"use client";

import { useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";

const Tiptap = () => {
  const editor = useEditor({
    extensions: [StarterKit],
    content: "<p>Hello World! 🌎️</p>",
    // Don't render immediately on the server to avoid SSR issues
    immediatelyRender: false,
  });

  return <EditorContent editor={editor} />;
};

export default Tiptap;

插件配置

Tiptap 具有丰富的插件配置,默认情况下,StarterKit就集成了丰富的内容:

Nodes(节点)
Marks(标记/格式)
Extensions(扩展功能)

如果不想要这么多插件,我们就妙妙禁用它们!

import { Editor } from "@tiptap/core";
import StarterKit from "@tiptap/starter-kit";

const editor = new Editor({
  content: "<p>Example Text</p>",
  extensions: [
    StarterKit.configure({
      // Disable an included extension
      undoRedo: false,

      // Configure an included extension
      heading: {
        levels: [1, 2],
      },
    }),
  ],
});

实践

实用插件

在这里列出一些使用性较强的插件,氵几行:

Placeholder

嘻嘻,一个美观的Placeholder插件

const editor = useEditor({
  extensions: [
    StarterKit,
    Placeholder.configure({
      placeholder: "Write something …",
      // Use different placeholders depending on the node type:
      placeholder: ({ node }) => {
        if (node.type.name === "heading") {
          return "What’s the title?";
        }

        return "Can you add some further context?";
      },
    }),
  ],
});

BubbleMenu

这个插件实现了一个悬浮菜单,选中部分文字的时候会显示一个浮动工具栏,省去了手动计算并且实现的麻烦(偷懒…

CharacterCount

顾名思义,算字符数和单词数的()

Drag Handle

想把节点随意拖来拖去?这个绝对适合你!

当然还有很多,可以自由地探索!不过,注意 Tiptap 是有付费机制的,所以说并不是所有插件都是可用的...

自定义插件

TipTap 丰富的可拓展性亦体现在其可以自定义插件,为编辑器添加丰富多彩的效果,比如下面这个用在 BuddyUp 编辑器里的自定义翻译块插件:

/* eslint-disable @typescript-eslint/no-explicit-any */
import Paragraph from "@tiptap/extension-paragraph";

export const ParagraphWithTranslation = Paragraph.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      translationId: {
        default: null,
        parseHTML: (element) => element.getAttribute("data-has-translation"),
        renderHTML: (attributes) => {
          if (!attributes.translationId) {
            return {};
          }
          return {
            "data-has-translation": attributes.translationId,
          };
        },
      },
    };
  },
});

我们通过自定义插件扩展了Paragraph节点,添加了一个translationId属性。

解析时(parseHTML):当编辑器从HTML加载内容时,如果遇到有data-has-translation属性的段落,就把这个属性值提取出来,存储为节点的translationId属性。

渲染时(renderHTML):当编辑器输出HTML时,如果节点有translationId属性,就在输出的HTML元素上添加data-has-translation属性。

这样,前端就可以通过CSS选择器p[data-has-translation]为这些段落添加特殊样式,实现翻译块的视觉区分。并且,我们也可以通过data-has-translation控制翻译行为,防止重复翻译。

自定义 UI

由于 Tiptap 是 headless 的,所以我们自然需要对其进行一些妙妙美化,Shadcn UI 就还不错,不过,似乎不是讨论的重点了。

总结

总而言之,Tiptap 非常适合处理一些需要富文本编辑的场景,既省去了手动写逻辑的麻烦,简化了实现方法,又规避了老旧富文本编辑器上手难,UI 难以控制的问题。
所以,针对来讲,是一个很不错的轻量化解决方案。所以,Check it out →