prosemirror 学习记录(二)创建 apple 节点

news2025/1/13 10:01:54

apple type

向 schema 中添加 apple type

const nodes = {
  apple: {
    inline: true,
    attrs: {
      name: { default: "unknown" },
    },
    group: "inline",
    draggable: true,
    parseDOM: [
      {
        tag: "span[custom-node-type=apple]",
        getAttrs(dom) {
          return {
            name: dom.getAttribute("name"),
          };
        },
      },
    ],
    toDOM(node) {
      let { name } = node.attrs;
      return ["span", { "custom-node-type": "apple", name }];
    },
  },
};

加上样式:

span[custom-node-type="apple"]::before {
  content: attr(name);
  background: pink;
  outline: 1px dashed red;
}

效果:
在这里插入图片描述

在这里插入图片描述

insertApple

<template>
  <section>
    <input type="button" value="红富士" @click="handleClick" />
    <input type="button" value="国光" @click="handleClick" />
  </section>
</template>

<script setup>
import { inject } from "vue";
const editorView = inject("editorView");

function handleClick(e) {
  const name = e.target.value;
  insertApple(name);
}

function insertApple(name) {
  const view = editorView.value;
  const appleType = view.state.schema.nodes.apple;
  const newAppleNode = appleType.create({ name });
  view.dispatch(view.state.tr.replaceSelectionWith(newAppleNode));
}
</script>

点击按钮就可以在文档中插入一个apple节点
在这里插入图片描述

实时更新按钮状态

增加功能:插入前需要判断,仅在文档中没有此类苹果时才能添加

function insertApple(name) {
  const view = editorView.value;
  const appleType = view.state.schema.nodes.apple;
  const find = findNodeIndex(view.state.doc, (node) => {
    return node.type.name === appleType.name && node.attrs.name === name;
  });
  if (find !== -1) {
    return;
  }
  const newAppleNode = appleType.create({ name });
  view.dispatch(view.state.tr.replaceSelectionWith(newAppleNode));
}

function findNodeIndex(doc, isMyNode) {
  let found = -1;
  doc.nodesBetween(0, doc.content.size, (node, pos) => {
    if (found !== -1) return false;
    if (isMyNode(node)) found = pos;
  });
  return found;
}

在这里插入图片描述

增加功能:按钮不可用时,将按钮禁用。

改写 insertApple 方法:添加 just_check 参数
在这里插入图片描述

  • insertApple(name, true) 只想看看命令是否可用,并不想真的插入一个苹果
  • insertApple(name) 确实是想插入一个苹果

根据 insertApple(name, true) 的返回值更新 button 的 disabled 状态:

const button1 = ref();
const button2 = ref();
function updateButtonState(el) {
  const name = el.value;
  const flag = insertApple(name, true);
  if (flag) {
    el.removeAttribute("disabled");
  } else {
    el.setAttribute("disabled", true);
  }
}
setInterval(() => updateButtonState(button1.value), 1000 / 60);
setInterval(() => updateButtonState(button2.value), 1000 / 60);

上面的代码用定时器调用 updateButtonState,很垃圾。

如果能在 view 变化时才调用 updateButtonState 就好了 —— prosemirror 的 Plugin 提供了这个能力!!!

用 Plugin 实现实时更新

import {Plugin} from "prosemirror-state"

new Plugin({
  view(view) {
    // 初始化时执行,只执行一次

    return {
      update(view, prevState) {
        // view 每次变化时都会执行 update
      },
      destroy() {},
    };
  },
})

使用 Plugin 重写插入苹果的功能:(伪代码)

new Plugin({
  view() {
    appleMenus= [
      { name: "红苹果", active: true },
      { name: "绿苹果", active: true },
    ];

    return {
      update(view, prevState) {
        appleMenus.forEach((appleMenu) => {
          appleMenu.active = insertApple(appleMenu.name, true);
        });
      },
      destroy() {},
    };
  },
})

将 insertApple 改写成 command 形式

prosemirror 的 command 格式为:

function command_a(state, dispatch, view){
	// When a command isn't applicable, it should return false and do nothing. 
	// When applicable, it should dispatch a transaction and return true.
}

举例:toggleMark 是 prosemirror 的内置方法,返回一个 切换指定 mark 和 attrs 的 command

function toggleMark(markType, attrs){
	return function(state, dispatch){
		if(无法切换) return false
		if(dispatch){
			dispatch(tr....)
		}
		return true
	}
}

依样画葫芦改造 insertApple:(改造后 insertApple 本身不是 command,它返回一个 command)

function insertApple(name) {
  return function (state, dispatch) {
    const appleType = state.schema.nodes.apple;
    const find = findNodeIndex(state.doc, (node) => {
      return node.type.name === appleType.name && node.attrs.name === name;
    });
    if (find !== -1) {
      return false;
    }
    if (dispatch) {
      const newAppleNode = appleType.create({ name });
      dispatch(state.tr.replaceSelectionWith(newAppleNode));
    }
    return true;
  };
}

这样调用内置方法(toggleMark)和自定义方法(insertApple)就可以用统一的方式调用了

自定义菜单

MyCustomMenuPlugin.js

import { setBlockType, toggleMark } from "prosemirror-commands";
import { Plugin } from "prosemirror-state";
import { ref } from "vue";
import { mySchema } from "./schema";
import { findNodeIndex } from "./utils/utils";

export const MyCustomMenuPlugin = new Plugin({
  view(view) {
    function update(view) {
      // 按钮的 active 和 enable 状态需要即时更新
      menus.value.forEach((menu) => {
        if (menu.updateActive) {
          menu.active = menu.updateActive(view.state);
        }
        if (menu.updateEnable) {
          menu.enable = menu.updateEnable(view.state);	// 不传dispatch参数
        }
      });
    }
    update(view);
    return { update };
  },
});
export const menus = ref([
  {
    label: "加粗",
    run: toggleMark(mySchema.marks.strong),
    active: true,
    updateActive: (state) => markActive(state, mySchema.marks.strong),
    enable: true,
  },
  {
    label: "段落",
    run: setBlockType(mySchema.nodes.paragraph),
    active: true,
    updateActive: (state) => blockTypeActive(state, mySchema.nodes.paragraph),
    enable: true,
  },
  {
    label: "标题1",
    run: setBlockType(mySchema.nodes.heading, { attrs: { level: 1 } }),
    active: true,
    updateActive: (state) => blockTypeActive(state, mySchema.nodes.heading, { level: 1 }),
    enable: true,
  },
  {
    label: "插入大苹果",
    run: insertApple("大苹果"),
    enable: true,
    updateEnable: (state) => insertApple("大苹果")(state),
  },
  {
    label: "插入小苹果",
    run: insertApple("小苹果"),
    enable: true,
    updateEnable: (state) => insertApple("小苹果")(state),
  },
]);
// 自定义命令
function insertApple(name) {
  return function (state, dispatch) {
    const appleType = state.schema.nodes.apple;
    const find = findNodeIndex(state.doc, (node) => {
      return node.type.name === appleType.name && node.attrs.name === name;
    });
    if (find !== -1) {
      return false;
    }
    if (dispatch) {
      const newAppleNode = appleType.create({ name });
      dispatch(state.tr.replaceSelectionWith(newAppleNode));
    }
    return true;
  };
}

// mark 级别的按钮用来判断 active(从 prosemirror-example-setup 包中抄的)
function markActive(state, type) {
  let { from, $from, to, empty } = state.selection;
  if (empty) return !!type.isInSet(state.storedMarks || $from.marks());
  else return state.doc.rangeHasMark(from, to, type);
}
// block 级别的按钮用来判断 active(从 prosemirror-example-setup 包中抄的)
function blockTypeActive(state, nodeType, attrs) {
  let { $from, to, node } = state.selection;
  if (node) return node.hasMarkup(nodeType, attrs);
  return to <= $from.end() && $from.parent.hasMarkup(nodeType, attrs);
}

TestEditor.vue:

<script setup>
import { exampleSetup } from "prosemirror-example-setup";
import { EditorState } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { onMounted, shallowRef } from "vue";
import "./editor.css";
import { MyCustomMenuPlugin, menus } from "./MyCustomMenuPlugin";
import { mySchema } from "./schema";


const editorView = shallowRef(); // 不能用ref

onMounted(() => {
  editorView.value = new EditorView(document.querySelector("#editor"), {
    state: EditorState.create({
      schema: mySchema,
      plugins: exampleSetup({
        schema: mySchema,
        menuBar: false,	// 不使用 exampleSetup 提供的 menu
      }).concat(MyCustomMenuPlugin),	// 用 concat 加上我们自定义的 menu 插件
    }),
  });
});

function handleClick(e, o) {
  e.preventDefault();
  o.run(editorView.value.state, editorView.value.dispatch);
}
</script>

<template>
  <section class="custom-menu">
    <input
      v-for="o in menus"
      :key="o.label"
      type="button"
      :value="o.label"
      @click="(e) => handleClick(e, o)"
      :class="{ active: o.active }"
      :disabled="!o.enable"
    />
  </section>
  <section id="editor"></section>
</template>

<style>
span[custom-node-type="apple"]::before {
  content: attr(name);
  background: pink;
  outline: 1px dashed red;
}
input[type="button"].active {
  font-weight: bold;
  background: gray;
  color: white;
}
</style>

效果:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【高效开发工具系列】Apifox

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

Vue单页面应用(SPA)怎么做SEO

在Vue单页面应用(SPA)中,由于内容的动态加载和路由切换,搜索引擎可能无法直接获取和索引页面的内容。不过,你可以采取一些策略来优化SEO,使你的Vue单页面应用在搜索引擎中更好地被索引和展示: 1:使用预渲染(Prerendering)或服务器端渲染(Server-Side Rendering,SS…

爬虫采集如何解决ip被限制的问题呢?

在进行爬虫采集的过程中&#xff0c;很多开发者会遇到IP被限制的问题&#xff0c;这给采集工作带来了很大的不便。那么&#xff0c;如何解决这个问题呢&#xff1f;下面我们将从以下几个方面进行探讨。 一、了解网站的反爬机制 首先&#xff0c;我们需要了解目标网站的反爬机制…

设计模式(13)适配器模式

一、介绍&#xff1a; 1、定义&#xff1a;是一种结构型设计模式&#xff0c;它可以将一个类的接口转换成客户端所期望的另一种接口。适配器模式常用于系统的不兼容性问题。 2、组成&#xff1a; &#xff08;1&#xff09;目标接口&#xff08;Target&#xff09;&#xff…

商品价格区间筛选

列表应用&#xff0c;商品价格区间筛选。 (本笔记适合熟悉python列表及列表的条件筛选的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么…

允许mysql远程访问

一、修改表 进入mysql&#xff0c;mysql -u root &#xff0c;如果没有root用户&#xff0c;那么是可以直接进入的 使用 mysql数据库 use mysql 执行 update user set host% where userroot; 查询 用户表 select User,Host from user 把root用户的host改为 % 允许任何主…

elment-ui 日期选择器 月份区间选择的问题解决(含代码、截图)

elment-ui 日期选择器 月份区间选择的问题解决&#xff08;含代码、截图&#xff09; 参考文章&#xff1a; elment-ui 日期选择器 月份区间选择的问题解决官方文档参考&#xff1a;https://element.eleme.cn/#/zh-CN/component/date-picker 效果图&#xff1a; 代码案例&…

赢球票(蓝桥杯)

赢球票 题目描述 某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。 主持人拿出 N 张卡片&#xff08;上面写着 1⋯N 的数字&#xff09;&#xff0c;打乱顺序&#xff0c;排成一个圆圈。 你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯ 如果数到的数字刚好和卡片上的…

东莞理工网安学院举办第三届“火焰杯”软件测试高校就业选拔赛颁奖典礼

3月7日下午&#xff0c;由软件测试就业联盟主办的第三届“火焰杯”软件测试高校就业选拔赛颁奖典礼在9A206报告厅举行。本届比赛我院有25位同学报名参加&#xff0c;预选赛阶段&#xff0c;有5位同学获奖。其中&#xff0c;一等奖1项&#xff0c;奖金价值10000元&#xff1b;二…

element-plus 自动按需引入icon unplugin-icons相关配置(有效)

1.安装的组件有这四个2.vite.config.js配置文件修改页面使用附完整vite.config.js配置 相关配置&#xff08;自行根据配置文件中的安装哈&#xff0c;我就不一 一列举了&#xff09; 1.安装的组件有这四个 2.vite.config.js配置文件修改 页面使用 <i-ep-edit />效果 附…

Linux操作系统使用及C高级编程

一、Linux介绍及环境配置 Linux介绍及环境配置 一、UNIX、LINUX和GNU简介 1、UNIX简介 2、Linux 1)操作系统内核软件操作系统 2&#xff09;操作系统内核-能统一的管理硬件了 3&#xff09;基于UNIX&#xff0c;UNIX不开源&#xff0c;对硬件要求专一&#xff0c;因此出现…

2023年【安全生产监管人员】考试题及安全生产监管人员考试内容

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【安全生产监管人员】考试题及安全生产监管人员考试内容&#xff0c;包含安全生产监管人员考试题答案和解析及安全生产监管人员考试内容练习。安全生产模拟考试一点通结合国家安全生产监管人员考试最新大纲及安…

【OpenCV实现图像的算数运算,性能测试和优化,改变颜色空间】

文章目录 OpenCV功能概要图像的算数运算性能测试和优化改变颜色空间对象追踪 OpenCV功能概要 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习库&#xff0c;提供了丰富的图像处理和计算机视觉算法。它支持多种编程语言&…

【C++入门:C++世界的奇幻之旅】

1. 什么是C 2. C发展史 3. C的重要性 4. C关键字 5. 命名空间 6. C输入&输出 7. 缺省参数 8. 函数重载 9. 引用 10. 内联函数 11. auto关键字(C11) 12. 基于范围的for循环(C11) 13. 指针空值---nullptr(C11)05. 1. 什么是C C语言是结构化和模块化的语言&…

HDR图像处理软件 Photomatix Pro mac中文版新增功能

Photomatix Pro mac是一款专业的HDR合成软件&#xff0c;可以将不同曝光的多张照片合成为一张照片&#xff0c;而保留更多的细节。并且合成时可以帮助去除照片中的鬼影。Photomatix Pro提供两种类型的过程来增加动态范围&#xff0c;一个过程称为HDR色调映射&#xff0c;另一个…

【MySQL】数据库常见错误及解决

目录 2003错误&#xff1a;连接错误1251错误&#xff1a;身份验证错误1045错误&#xff1a;拒绝访问错误服务没有报告任何错误net start mysql 发生系统错误 5。 1064错误&#xff1a;语法错误1054错误&#xff1a;列名不存在1442错误&#xff1a;触发器中不能对本表增删改1303…

SAFe敏捷发布火车ART案例分析-汽车公司里面的百人级团队

“Nothing beats an Agile Team&#xff0c; except a team of Agile Teams” ---SAFe 本案例来自于某汽车公司里一个百人级团队&#xff0c;该团队所开发功能是完全面向车主的&#xff0c;追求最佳用户体验。 01—转型前面临困难 在该团队启用SAFe ART之前&#xff0c;多个1…

欧拉筛(线性筛)算法的理解

欧拉筛&#xff08;Eulers Sieve&#xff09;(又叫线性筛)是一种用于生成素数的高效算法。与传统的试除法不同&#xff0c;欧拉筛通过线性时间复杂度来筛选出一定范围内的素数。这个算法以瑞士数学家莱昂哈德欧拉&#xff08;Leonhard Euler&#xff09;的名字命名&#xff0c;…

vue3使用Element ui plus中MessageBox消息框+radio框配合使用

想要达到的效果 首先安装element ui plus 省略~~ 官网地址&#xff1a; https://element-plus.gitee.io/zh-CN/component/message-box.htmlhttps://element-plus.gitee.io/zh-CN/component/message-box.html 需要用到的 引入 import { h } from "vue"; import {E…