自定义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"],
  }),
  /*......*/
];
相关推荐
NoneCoder2 分钟前
工程化与框架系列(35)--前端微服务架构实践
前端·微服务·架构
Cirrod2 分钟前
react加antd封装表格单、多选组件,支持跨页选择缓存
javascript·react.js·缓存
洛祁枫3 分钟前
前端发布缓存导致白屏解决方案
前端·缓存
二川bro13 分钟前
前端高级CSS用法
前端·css
KL's pig/猪头/爱心/猪头13 分钟前
使用libwebsocket写一个server
linux·前端
丁总学Java22 分钟前
解锁 vue-property-decorator 的秘密:Vue 2 到 Vue 3 的 TypeScript 之旅!✨
前端·vue.js·typescript
MandiGao23 分钟前
ECharts 3D地球(铁路线、飞线、标点、图标、文字标注等)
前端·vue.js·3d·echarts
一个处女座的程序猿O(∩_∩)O23 分钟前
Vue 过滤器深度解析与应用实践
前端·javascript·vue.js
招风的黑耳42 分钟前
Web元件库 ElementUI元件库+后台模板页面(支持Axure9、10、11)
前端·elementui·axure