自定义tiptap插件

本文为开发开源项目的真实开发经历,感兴趣的可以来给我的项目点个star,谢谢啦~

具体博文介绍: 开源|Documind协同文档(接入deepseek-r1、支持实时聊天)Documind 🚀 一个支持实时聊天和接入 - 掘金

我干了什么

我定义了两个插件:

  • font-size:支持通过setFontSize设置tiptap编辑器字体大小,通过unsetFontSize重置为默认大小。
  • line-height:支持通过设置setLineHeight设置tiptap编辑器行高,通过unsetLineHeight重置为默认行高。

源码参考

这里就不一点一点讲解了,注释看不懂的话可以叫AI帮你解析。

font-size插件:

typescript 复制代码
import { Extension } from "@tiptap/core";
import "@tiptap/extension-text-style";

// 声明类型
declare module "@tiptap/core" {
  interface Commands<ReturnType> {
    fontSize: {
      /** 设置字体大小(支持CSS单位如12px/1.2rem等) */
      setFontSize: (fontSize: string) => ReturnType;
      /** 清除字体大小设置 */
      unsetFontSize: () => ReturnType;
    };
  }
}

// 创建扩展
export const FontSizeExtension = Extension.create({
  name: "fontSize",

  // 扩展配置项
  addOptions() {
    return {
      types: ["textStyle"], // 作用对象为文本样式标记
    };
  },

  // 注册全局属性
  addGlobalAttributes() {
    return [
      {
        types: this.options.types, // 应用范围(textStyle类型)
        attributes: {
          fontSize: {
            default: null, // 默认无字体大小
            // 从DOM解析字体大小(读取style属性)
            parseHTML: (element) => element.style.fontSize,
            // 渲染到DOM时生成样式
            renderHTML: (attributes) => {
              if (!attributes.fontSize) {
                return {}; // 无设置时返回空对象
              }
              return {
                style: `font-size: ${attributes.fontSize};`, // 生成内联样式
              };
            },
          },
        },
      },
    ];
  },

  // 注册编辑器命令
  addCommands() {
    return {
      /** 设置字体大小命令 */
      setFontSize:
        (fontSize: string) =>
        ({ chain }) => {
          return chain()
            .setMark("textStyle", { fontSize }) // 更新文本样式标记
            .run();
        },
      /** 清除字体大小命令 */
      unsetFontSize:
        () =>
        ({ chain }) => {
          return chain()
            .setMark("textStyle", { fontSize: null }) // 清除字体大小属性
            .removeEmptyTextStyle() // 移除空文本样式标记
            .run();
        },
    };
  },
});

line-height插件:

typescript 复制代码
import { Extension } from "@tiptap/core";

// 类型声明:扩展Tiptap的命令接口
declare module "@tiptap/core" {
  interface Commands<ReturnType> {
    lineHeight: {
      /** 设置行高(支持CSS单位如1.5/2/24px等) */
      setLineHeight: (lineHeight: string) => ReturnType;
      /** 重置为默认行高 */
      unsetLineHeight: () => ReturnType;
    };
  }
}

export const LineHeightExtension = Extension.create({
  name: "lineHeight",

  // 扩展配置项
  addOptions() {
    return {
      types: ["paragraph", "heading"], // 应用行高样式的节点类型
      defaultLineHeight: null, // 默认行高(null表示不设置)
    };
  },

  // 添加全局属性处理
  addGlobalAttributes() {
    return [
      {
        types: this.options.types, // 应用到的节点类型
        attributes: {
          lineHeight: {
            // 默认值(从配置项获取)
            default: this.options.defaultLineHeight,

            // 渲染到HTML时的处理
            renderHTML: (attributes) => {
              if (!attributes.lineHeight) {
                return {};
              }
              // 将行高转换为行内样式
              return {
                style: `line-height: ${attributes.lineHeight};`,
              };
            },

            // 从HTML解析时的处理
            parseHTML: (element) => {
              return {
                // 获取行高样式或使用默认值
                lineHeight: element.style.lineHeight || this.options.defaultLineHeight,
              };
            },
          },
        },
      },
    ];
  },

  // 添加自定义命令
  addCommands() {
    return {
      setLineHeight:
        (lineHeight) =>
        ({ tr, state, dispatch }) => {
          // 创建事务副本以保持不可变性
          tr = tr.setSelection(state.selection);
          // 遍历选区内的所有节点
          state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
            // 只处理配置的类型节点
            if (this.options.types.includes(node.type.name)) {
              tr = tr.setNodeMarkup(pos, undefined, {
                ...node.attrs,
                lineHeight, // 更新行高属性
              });
            }
          });

          // 提交事务更新
          if (dispatch) {
            dispatch(tr);
          }
          return true;
        },

      unsetLineHeight:
        () =>
        ({ tr, state, dispatch }) => {
          tr = tr.setSelection(state.selection);

          // 遍历选区节点重置行高
          state.doc.nodesBetween(state.selection.from, state.selection.to, (node, pos) => {
            if (this.options.types.includes(node.type.name)) {
              tr = tr.setNodeMarkup(pos, undefined, {
                ...node.attrs,
                lineHeight: this.options.defaultLineHeight, // 重置为默认值
              });
            }
          });

          if (dispatch) {
            dispatch(tr);
          }
          return true;
        },
    };
  },
});

使用案例

首先我们在extensions中添加扩展以激活

javascript 复制代码
extensions: [
  /*......*/
  FontSizeExtension,
  LineHeightExtension.configure({
    types: ["paragraph", "heading"],
  }),
  /*......*/
];
相关推荐
wangruofeng2 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng2 小时前
给前端开发者的 100 个设计参考网站
前端·设计
M--Y4 小时前
LangGraph 工作流模式
开发语言·javascript·python·langchain·大模型·llm·langgraph
星栈5 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技5 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569155 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马8 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng8 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding9 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js
johnjincaaaa9 小时前
猿人学第 18 题「jsvmp 洞察先机」逆向通关实录
javascript·后端