【element-tiptap】如何把分隔线改造成下拉框的形式?

当前的分隔线只有细横线这一种形式
在这里插入图片描述
但是咱们可以看一下wps中的分隔线,花里胡哨的
这些在wps里都需要使用快捷键打出来,真没找到菜单在哪里
在这里插入图片描述
那么这篇文章咱们就来看一下如何改造分隔线组件,改造成下拉框的形式,并且把咱们想要的分隔线都放进去
分隔线扩展是这个 HorizontalRule
src/extensions/horizontal-rule.ts

1、创建下拉框组件

项目中有好几个下拉框组件,首先,咱们需要仿照它们,创建分隔线的下拉框组件
仿照上一篇文章研究的 FontFamilyDropdown.vue,先大致写一下,后面再详细补充

<template>
    <el-dropdown placement="bottom" trigger="click" @command="insertHorizontalRule">
        <command-button :enable-tooltip="enableTooltip" tooltip="插入分隔线" icon="horizontal-rule" />
        <template #dropdown>
            <el-dropdown-menu>
                <el-dropdown-item v-for="rule in horizontalRules" :key="rule.value" :command="rule.value">
                    {{ rule.label }}
                </el-dropdown-item>
            </el-dropdown-menu>
        </template>
    </el-dropdown>
</template>
<script lang="ts">
import {defineComponent, inject} from 'vue';
import {Editor, getMarkAttributes} from '@tiptap/vue-3';
import {ElDropdown, ElDropdownMenu, ElDropdownItem} from 'element-plus';
import CommandButton from './CommandButton.vue';

export default defineComponent({
  name: 'FontFamilyDropdown',

  components: {
    ElDropdown,
    ElDropdownMenu,
    ElDropdownItem,
    CommandButton,
  },
  props: {
    editor: {
      type: Editor,
      required: true,
    },
  },

  setup() {
    const t = inject('t');
    const enableTooltip = inject('enableTooltip', true);
    const isCodeViewMode = inject('isCodeViewMode', false);
    return {t, enableTooltip, isCodeViewMode};
  },

  computed: {
    horizontalRules() {
      return [
            { label: '细线', value: '---' },
            { label: '粗线', value: '___' },
            { label: '星号线', value: '***' },
        ];
    },
  },
  methods: {
    insertHorizontalRule(rule: string) {
      this.editor.commands.setHorizontalRule();
    },
  },
});
</script>

2、在扩展中应用分隔线下拉框组件

src/extensions/horizontal-rule.ts

import type { Editor } from '@tiptap/core';
import TiptapHorizontalRule from '@tiptap/extension-horizontal-rule';
import HorizontalRuleDropdown from '@/components/MenuCommands/HorizontalRuleDropdown.vue';

const HorizontalRule = TiptapHorizontalRule.extend({
  addOptions() {
    return {
      // 保留父扩展的所有选项
      ...this.parent?.(),
      button({ editor, t }: { editor: Editor; t: (...args: any[]) => string }) {
        return {
          component: HorizontalRuleDropdown,
          componentProps: {
            editor,
          },
        };
      },
    };
  },
});

export default HorizontalRule;

此时模样已经出来了
在这里插入图片描述
点击菜单选项都能够插入
看一下此时插入的分隔线,其实是一个 <hr/> 标签
在这里插入图片描述
显然这个方法满足不了我们的需求,因为 editor.commands.setHorizontalRule() 这个方法不允许传递参数,给元素增加类名或者其他属性。那我们只能重新写一个函数

3、探索插入的实现

向文档中插入节点,有一个方法是 editor.commands.insertContentAt
可以看一下这个函数的定义
node_modules/@tiptap/core/dist/commands/insertContentAt.d.ts

insertContentAt: (
	 /**
	  * 插入内容的位置。
	  */
	 position: number | Range, 
	 /**
	  * 要插入的 ProseMirror 内容。
	  */
	 value: Content, 
	 /**
	  * 可选的选项
	  */
	 options?: {
	     /**
	      * 解析内容的选项。
	      */
	     parseOptions?: ParseOptions;
	     /**
	      * 插入内容后是否更新选区。
	      */
	     updateSelection?: boolean;
	     /**
	      * 插入内容后是否应用输入规则。
	      */
	     applyInputRules?: boolean;
	     /**
	      * 插入内容后是否应用粘贴规则。
	      */
	     applyPasteRules?: boolean;
	     /**
	      * 内容无效时是否抛出错误。
	      */
	     errorOnInvalidContent?: boolean;
	 }) => ReturnType;

可选的选项咱们可以先不管,先看下前两个参数

  • position
    可以传数字或者 Range,传数字表示索引,传 Range 就是在固定位置插入
  • value: Content
    具体内容
export type Content = HTMLContent | JSONContent | JSONContent[] | null;

HTMLContentstring 类型,直接这样写就行

'<h1>Example</h1>'

JSONContent 的定义如下

export type JSONContent = {
  type?: string;
  attrs?: Record<string, any>;
  content?: JSONContent[];
  marks?: {
    type: string;
    attrs?: Record<string, any>;
    [key: string]: any;
  }[];
  text?: string;
  [key: string]: any;
};

JSONContenttype 属性,我原以为会有一个常量的列表,但是我太天真了,找了半天没找到。但是经过我的实验,可以确定的是,如果你想往文档里插入一个 div,那么你注定会失败,例如我想用下面代码插入一个 div 标签:

editor.commands.insertContentAt(selection.from,
{
    type: 'div',
    text: 'dsadsa'
})

结果执行完了之后,长这样:
在这里插入图片描述
合理猜测,这个 type 属性,只允许在编辑器中定义好的节点类型。哭唧唧
但是我们还有有路可以走,比如,探索一下其他的节点是怎么插入的,然后模仿并且超越。

不如就来看一下图片是怎么插入的!
一个图片插入功能,其实需要好几个文件来支撑

  • src/utils/image.ts 文件,这个文件定义加载和缓存图片的方法、以及提供了图像显示方式的枚举,可以认为一些基础方法、枚举值都在这个文件夹的 ts 文件中定义

  • src/extensions/image.ts,这个文件是用来扩展 tiptap 图像节点的,并且集成了插入图片的组件,提供了自定义图像属性的方法和渲染HTML的方法

  • src/components/ExtensionViews/ImageView.vue,是图像展示的组件,用于渲染和交互式调整图像
    在这个文件中,我们可以看到,实际的插入的图像内容是被一个标签 node-view-wrapper 包裹起来的,所以咱们待会构建分割符组件的时候也要用这个标签把我们实际要插入的内容包裹起来
    在这里插入图片描述

  • src/components/MenuCommands/Image/ImageDisplayCommandButton.vue,是一个弹出菜单修改图像显示方式的组件
    在这里插入图片描述

  • src/components/MenuCommands/Image/InsertImageCommandButton.vue,插入图像的按钮,下拉框有两个选项
    在这里插入图片描述

  • src/components/MenuCommands/Image/EditImageCommandButton.vue,编辑图像的组件
    在这里插入图片描述

  • src/components/MenuCommands/Image/RemoveImageCommandButton.vue 删除图像的组件,其实就一个小按钮
    在这里插入图片描述

好吧,万幸,插入分割线功能没有这么的复杂,在插入之后就不需要修改了。那我们来梳理一下我们需要创建几个文件来插入分割线。

1、src/utils/horizontal-rule.ts 定义分割线类型和html之间的对应关系
2、src/extensions/horizontal-rule.ts 调用 tiptap 的API增加扩展项
3、src/components/MenuCommands/HorizontalRuleDropdown.vue 定义下拉菜单,用来选择分割线的类型
4、src/components/ExtensionViews/HorizontalRuleView.vue 定义插入分割线渲染出来的组件

接下来,咱们就挨个文件看,我这里主要仿照两个组件,一个是图片相关的,一个是Iframe相关的

4、src/utils/horizontal-rule.ts

这里定义为数组,在下拉框中,我们需要直接展示出来分割线,但是点击分割线的时候,需要把分割线的类型取出来,给 setHorizontalRule 方法;但是当插入的时候,又要根据分割线的类型去找对应的分割线的html。所以说,分割线的类型,其实也可以叫做唯一标识,与分割线的html之间是需要双向转换的。使用对象的话,反向查找就会有一些不方便,所以直接定义成数组。下面的html是经过我测试的,大家在开发的时候可以先写一些简单的测试数据,我的数据效果是这样子的:
在这里插入图片描述

export const horizontalRules = [
  {
    borderStyle: 'solid',
    html: `<hr style="border: none; border-top: 1px solid black;">`
  },
  {
    borderStyle: 'dotted',
    html: `<hr style="border: none; border-top: 1px dotted black;">`
  },
  {
    borderStyle: 'dashed',
    html: `<hr style="border: none; border-top: 1px dashed black;">`
  },
  {
    borderStyle: 'double',
    html: `<hr style="border: none; height: 6px; border-top: 1px solid black; border-bottom: 3px solid black;">`
  },
  {
    borderStyle: 'triple',
    html: `<div style="display: flex; flex-direction: column; gap: 2px;"><hr style="border: none; border-top: 1px solid black; margin: 0;"><hr style="border: none; border-top: 2px solid black; margin: 0;"><hr style="border: none; border-top: 1px solid black; margin: 0;"></div>`
  },
];

export default horizontalRules;


5、src/extensions/horizontal-rule.ts

还是来看 src/extensions/horizontal-rule.ts 文件,参考 src/extensions/image.ts 文件
① 分割线需要一个属性表示分割线的类型,那么就需要 addAttributes 方法,直接仿照图片扩展里面的代码写就行

addAttributes() {
  return {
    ...this.parent?.(),
    'border-style': {
      parseHTML: (element) => {
        const borderStyle = element.getAttribute('borderStyle');
        return borderStyle;
      },
      renderHTML: (attributes) => {
        return {
          'border-style': attributes['border-style'],
        };
      },
    },
  };
},

② 需要 addNodeView 为扩展添加节点视图

addNodeView() {
  return VueNodeViewRenderer(HorizontalRuleView);
},

③ 需要 parseHTMLrenderHTML 用来解析和渲染HTML

// 为扩展添加解析HTML
parseHTML() {
  return [
    {
      tag: 'div',
    },
  ];
},
// 为扩展添加渲染HTML
renderHTML({ HTMLAttributes }) {
  return [
    'div',
    HTMLAttributes
  ];
},

④ 添加命令。由于tiptap提供的 setHorizontalRule 方法满足不了需求,所以我们需要重写一下这个方法

addCommands() {
  return {
    setHorizontalRule:
      (options) =>
      ({ commands }) => {
        return commands.insertContent({
          type: this.name,
          attrs: {
            'border-style': options.borderStyle,
          },
        });
      },
  };
},

完整代码

import type { Editor } from '@tiptap/core';
import TiptapHorizontalRule from '@tiptap/extension-horizontal-rule';
import HorizontalRuleDropdown from '@/components/MenuCommands/HorizontalRuleDropdown.vue';
import { mergeAttributes, VueNodeViewRenderer } from '@tiptap/vue-3';
import HorizontalRuleView from '@/components/ExtensionViews/HorizontalRuleView.vue';

const HorizontalRule = TiptapHorizontalRule.extend({
  // 返回的数据,第一个是继承的父级的属性
  // 后面的是自己的属性
  addAttributes() {
    return {
      ...this.parent?.(),
      'border-style': {
        parseHTML: (element) => {
          const borderStyle = element.getAttribute('borderStyle');
          return borderStyle;
        },
        renderHTML: (attributes) => {
          return {
            'border-style': attributes['border-style'],
          };
        },
      },
    };
  },
  // 为扩展添加选项
  addOptions() {
    return {
      // 保留父扩展的所有选项
      ...this.parent?.(),
      button({ editor, t }: { editor: Editor; t: (...args: any[]) => string }) {
        return {
          component: HorizontalRuleDropdown,
          componentProps: {
            editor,
          },
        };
      },
    };
  },
  // 为扩展添加节点视图
  addNodeView() {
    return VueNodeViewRenderer(HorizontalRuleView);
  },
  // 为扩展添加解析HTML
  parseHTML() {
    return [
      {
        tag: 'div',
      },
    ];
  },
  // 为扩展添加渲染HTML
  renderHTML({ HTMLAttributes }) {
    return [
      'div',
      HTMLAttributes
    ];
  },
  // 为扩展添加命令
  addCommands() {
    return {
      setHorizontalRule:
        (options) =>
        ({ commands }) => {
          return commands.insertContent({
            type: this.name,
            attrs: {
              'border-style': options.borderStyle,
            },
          });
        },
    };
  },
});

export default HorizontalRule;

6、src/components/MenuCommands/HorizontalRuleDropdown.vue

这个方法咱们已经实现的很成熟了,首先数据在这里我们不需要再定义一遍了,需要从咱们刚定义的文件中引入

import horizontalRules from '@/utils/horizontal-rule';

在 setup 函数中返回 horizontalRules:

return { t, enableTooltip, isCodeViewMode, horizontalRules };

循环的模版代码要修改一下,因为我们现在使用的是数组了

<el-dropdown-item v-for="rule in horizontalRules" :key="rule.borderStyle" :command="rule.borderStyle">
   	<div contenteditable="false" class="horizontal-rule-item" v-html="rule.html"></div>
</el-dropdown-item>

要把 borderStyle 传到 setHorizontalRule 命令里面

insertHorizontalRule(borderStyle: string) {
    this.editor.commands.setHorizontalRule({ borderStyle });
},    

7、src/components/ExtensionViews/HorizontalRuleView.vue

这个文件就是插入分割线的时候实际插入的内容,模版需要使用 node-view-wrapper 标签包裹,数据也从我们定义的常量文件中获取

<template>
  <node-view-wrapper as="div" class="horizontal-rule">
    <div
     class="horizontal-rule__line" 
     v-html="getHorizontalRuleHtml(borderType)"
     >
    </div>
  </node-view-wrapper>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { NodeViewWrapper, nodeViewProps } from '@tiptap/vue-3';
import horizontalRules from '@/utils/horizontal-rule';

export default defineComponent({
  name: 'HorizontalRuleView',

  components: {
    NodeViewWrapper,
  },

  props: nodeViewProps,

  computed: {
    borderType(): string {
      return this.node!.attrs['border-style'];
    },
  },

  methods: {
    getHorizontalRuleHtml(borderStyle: string): string {
      const rule = horizontalRules.find(rule => rule.borderStyle === borderStyle);
      return rule ? rule.html : '';
    },
  },
});
</script>

<style scoped>
.horizontal-rule__line {
  width: 100%;
}
</style>

8、看看效果
首先,点击下拉框按钮,弹出菜单
在这里插入图片描述
然后,点击菜单项,就会插入分割线
在这里插入图片描述
边距之类的样式可以自己在调整调整 耶耶耶耶耶
通过这篇文章,也掌握了tiptap大致的扩展节点的方法就是需要那么几个目录文件
1、src/utils/xx.ts 定义常量
2、src/extensions/xx.ts 定义扩展,可以创建新的节点,也可以继承、重写已有的节点
3、src/compoents/MenuCommands/xx.vue 定义菜单项
4、src/components/ExtensionViews/xx.vue 定义实际插入的内容,需要使用node-view-wrapper 标签包裹

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/898669.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构-八大排序之归并排序

归并排序 一、概念 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分治法(Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使…

友思特技术 | 视觉阶梯发展:传感器材料对短波红外成像技术的影响

导读 短波红外成像技术的发展受到了传感器材料种类的限制与推动&#xff0c;从硅基到铟镓砷&#xff0c;从量子点到锗基&#xff0c;丰富的材料影响着短波红外相机的分辨率、质量、成本等性能特征。 短波红外成像与传感器 短波红外光通常定义在 900 - 1700nm&#xff0c;相比…

使用 Python 处理 CSV 文件

文章目录 常见问题及解决方案使用 Python 处理 CSV 文件&#xff1a;全面指南CSV 文件的基本概念使用内置 csv 模块使用 pandas 库处理缺失值使用 DictReader 和 DictWriter案例分析最佳实践参考资源性能比较结论 常见问题及解决方案 问题&#xff1a;文件编码错误 解决方案&am…

大厂为什么要禁止使用数据库自增主键

大表为何不能用自增主键&#xff1f; 数据库自增主键&#xff0c;以mysql为例&#xff0c;设置表的ID列为自动递增&#xff0c;便可以在插入数据时&#xff0c;ID字段值自动从1开始自动增长&#xff0c;不需要人为干预。 在小公司&#xff0c;或者自己做项目时&#xff0c;设置…

Ollama 离线安装

1. 查看服务器CPU的型号 ## 查看Linux系统CPU型号命令&#xff0c;我的服务器cpu型号是x86_64 lscpu 2. 根据CPU型号下载Ollama安装包&#xff0c;并保存到/home/Ollama目录 我下载的是Ollama的v0.1.31版本&#xff0c;后面均以此版本为例说明 下载地址 https://github.…

拴柱说Mac之Mac的高效使用技巧第三期

Mac的设计有着非常多的使用技巧&#xff0c;这些技巧能够极大的提高你的使用效率&#xff0c;但是还是有许多人并不知道&#xff0c;那么今天Mac高效使用技巧分享第三期来了 Mac有一个独特的设置&#xff0c;那就触发角&#xff0c;触发角有着非常多的妙用 在 “系统偏好设置…

为什么计算机科学存在图灵机和Lambda演算两种世界观,而量子力学却存在三种世界图景?

计算机科学存在两种基本的世界观&#xff1a;图灵机和Lambda演算&#xff0c;它们指出了到达图灵完备的两条技术路线。但是量子力学中却存在着三种世界图景&#xff1a;薛定谔图景&#xff0c;海森堡图景和狄拉克图景。为什么计算机科学有两种基本世界观&#xff0c;但是量子力…

【Python数据可视化】利用Matplotlib绘制美丽图表!

【Python数据可视化】利用Matplotlib绘制美丽图表&#xff01; 数据可视化是数据分析过程中的重要步骤&#xff0c;它能直观地展示数据的趋势、分布和相关性&#xff0c;帮助我们做出明智的决策。在 Python 中&#xff0c;Matplotlib 是最常用的可视化库之一&#xff0c;它功能…

Netty-TCP服务端粘包、拆包问题(两种格式)

前言 最近公司搞了个小业务&#xff0c;需要使用TCP协议&#xff0c;我这边负责服务端。客户端是某个设备&#xff0c;客户端传参格式、包头包尾等都是固定的&#xff0c;不可改变&#xff0c;而且还有个蓝牙传感器&#xff0c;透传数据到这个设备&#xff0c;然后通过这个设备…

使用ORDER BY排序

在一个不明确的查询结果中排序返回的行。ORDER BY子句用于排序。如果使用了ORDER BY子句&#xff0c;它必须位于SQL语句的最后。 SELECT语句的执行顺序如下&#xff1a; 1.FROM子句 2.WHERE子句 3.SELECT子句 4.ORDER BY子句 示例一&#xff1a;查询employees表中的所有雇…

通俗易懂的入门 Axure RP文章 ,速学

目录 1. Axure RP简介&#xff1f; 2. Axure RP基本操作 &#xff08;1&#xff09;入门理解 &#xff08;2&#xff09;插入形状 &#xff08;3&#xff09;位置对齐、 &#xff08;4&#xff09;资源库 3. Axure RP基本交互 &#xff08;1&#xff09;切换不同的页面 …

进程间通信大总结Linux

目录 进程间通信介绍 进程间通信目的 进程间通信发展 进程间通信分类 管道 System V IPC POSIX IPC 管道 什么是管道 匿名管道 用fork来共享管道原理 站在文件描述符角度-深度理解管道 管道读写规则 管道特点 命名管道 创建一个命名管道 匿名管道与命名管道的区…

《云原生安全攻防》-- K8s攻击案例:权限维持的攻击手法

在本节课程中&#xff0c;我们将一起深入了解K8s权限维持的攻击手法&#xff0c;通过研究这些攻击手法的技术细节&#xff0c;来更好地认识K8s权限维持所带来的安全风险。 在这个课程中&#xff0c;我们将学习以下内容&#xff1a; K8s权限维持&#xff1a;简单介绍K8s权限维持…

UG2312软件安装教程+Siemens NX三维建模中文安装包下载

一、软件下载 【软件名称】&#xff1a;UG 2312 【支持系统】&#xff1a;win10/win11 【百度网盘】&#xff1a; https://pan.baidu.com/s/1oF-X29m1f5pDhElwi0rK8A?pwd70zi 二、UG NX软件 UG&#xff08;Unigraphics NX&#xff09;是一款集 CAD、CAM、CAE 于一体的高效…

大范围实景三维智能调色 | 模方自动化匀色解决方案

《实景三维中国建设总体实施方案&#xff08;2023—2025年&#xff09;》、《实景三维中国建设技术大纲》等相关文件中指出&#xff0c;倾斜Mesh三维模型修饰要求模型整体色彩真实&#xff0c;无明显色差。9月&#xff0c;自然资源部在国务院新闻发布会上表示&#xff0c;实景三…

Linux:线程及其控制

我们已经学了线程的创建&#xff0c;现在要学习线程的控制 线程等待 我们来先写一个没有线程等待的代码&#xff1a; pthcon.c: #include<stdio.h> #include<pthread.h> void* gopthread(void* arg){while(1){printf("pthread is running\n");sleep(1…

银行客户贷款行为数据挖掘与分析

#1024程序员节 | 征文# 在新时代下&#xff0c;消费者的需求结构、内容与方式发生巨大改变&#xff0c;企业要想获取更多竞争优势&#xff0c;需要借助大数据技术持续创新。本文分析了传统商业银行面临的挑战&#xff0c;并基于knn、逻辑回归、人工神经网络三种算法&#xff0…

SpringBoot实现微信支付接口调用及回调函数(商户参数获取)

#1024程序员节 | 征文 # 一、具体业务流程 1. 用户下单 - 前端操作&#xff1a; - 用户在应用中选择商品、填写订单信息&#xff08;如地址、联系方式等&#xff09;&#xff0c;并点击“下单”按钮。 - 前端将订单信息&#xff08;商品ID、数量、价格等&#xff09;发送…

Pytorch 实现图片分类

CNN 网络适用于图片识别&#xff0c;卷积神经网络主要用于图片的处理识别。卷积神经网络&#xff0c;包括一下几部分&#xff0c;输入层、卷积层、池化层、全链接层和输出层。 使用 CIFAR-10 进行训练&#xff0c; CIFAR-10 中图片尺寸为 32 * 32。卷积层通过卷积核移动进行计…

C++ —— map系列的使用

目录 1. map和multimap参考文档 2. map类的介绍 3. pair 4. map的增删查 4.1 插入 4.2 删除 4.3 查找 5. map的数据修改 6. map的operator[] 7. multimap和map的差异 1. map和multimap参考文档 - C Referencehttps://legacy.cplusplus.com/reference/map/ 2. map类的…