当前位置: 首页 > news >正文

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

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 falseif(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(); // 不能用refonMounted(() => {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"><inputv-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>

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

相关文章:

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

apple type 向 schema 中添加 apple type const nodes {apple: {inline: true,attrs: {name: { default: "unknown" },},group: "inline",draggable: true,parseDOM: [{tag: "span[custom-node-typeapple]",getAttrs(dom) {return {name: dom…...

自然语言处理---迁移学习

fasttext介绍 作为NLP工程领域常用的工具包&#xff0c;fasttext有两大作用&#xff1a;进行文本分类、训练词向量。在保持较高精度的情况下&#xff0c;快速的进行训练和预测是fasttext的最大优势。fasttext优势的原因: fasttext工具包中内含的fasttext模型具有十分简单的网络…...

node 第十天 原生node封装一个简易的服务器

原生node封装一个简易的服务器, 把前面几天的知识揉和起来做一个服务器基础实现, 首页访问, 静态资源服务器, 特定接口封装, 404app.js 服务器入口文件 app.js node app.js即可启动服务器 const { start } require(./modules/server); start();require_modules.js 整合模块导…...

php实战案例记录(25)intval函数的用法

在PHP中&#xff0c;intval()函数用于将一个字符串转换为整数。它的语法如下&#xff1a; intval(string $value, int $base 10): int参数说明&#xff1a; $value&#xff1a;要转换的字符串。$base&#xff08;可选&#xff09;&#xff1a;进制数&#xff0c;默认为10。如…...

laravel框架介绍(二) composer命令下载laravel报错

1.composer命令下载laravel报如下错 &#xff1a; curl error 18 while downloading https://repo.packagist.org/p2/symfony/uid.j son: transfer closed with 3808 bytes remaining to read&#xff0c;具体为 解决方案&#xff1a;执行以下命令切换镜像 >composer con…...

代码签名证书到期了怎么续费?

我们都知道代码签名证书最长期限可以申请3年&#xff0c;但有的首次申请也会申请1年&#xff0c;这种情况下证书到期了就意味着要重新办理&#xff0c;同样的实名验证步骤还需要再走一遍&#xff0c;尤其目前无论是哪种类型的代码签名证书都会有物理硬件&#xff0c;即使交钱实…...

JAVA 同城服务预约家政小程序开发的优势和运营

随着社会节奏的加快&#xff0c;人们对家庭清洁和维护的需求日益增长。为了满足这一需求&#xff0c;JAVA同城服务预约家政小程序应运而生。本文将详细介绍该小程序开发的优势及运营策略&#xff0c;帮助读者更好地了解其价值和潜力。 一、开发优势 方便快捷&#xff1a;用户…...

基于粒子群算法的无人机航迹规划-附代码

基于粒子群算法的无人机航迹规划 文章目录 基于粒子群算法的无人机航迹规划1.粒子群搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用粒子群算法来优化无人机航迹规划。 1.粒子群…...

前端使用qrcodejs2插件实现根据网址生成二维码

实现效果&#xff1a; 实现方法&#xff1a; 1.安装插件 npm install --save qrcodejs2 2.可以全局引入&#xff0c;也可以只在使用的vue文件中引入 import QRCode from qrcodejs2; 3.在vue文件的template中设置放置二维码的div <div id"qrcode"></di…...

A股风格因子看板 (2023.10 第11期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格暴露等。 今日为该因子跟踪第11期&#xff0c;指数组合数据截止日2023-09-30&#xff0c;要点如下 近1年A股风格因子检验统…...

anaconda安装python 3.11

最近需要测试gpt researcher项目&#xff0c;gpt researcher项目的环境是3.11&#xff0c;于是用anaconda创建一个虚拟环境&#xff0c;结果报错了&#xff1a; UnsatisfiableError: The following specifications were found to be incompatible with each other:Package xz c…...

问题:EventSource 收不到流数据及 EventSource 的 onmessage 方法为null

文章目录 问题分析问题 在开发时,有用到 EventSource,但是在 new EventSource 的时候,打印 new EventSource 如下: onmessage : null, onerror : null, onopen: f(event)前端...

P2 B+树索引

文章目录 Task1 B树页B树页B树内部结点B树叶子结点 Task2 B树操作Task2 B树插入和搜索的单一值插入单一值搜索单一值 Task2 B树删除 Task3 叶子扫描的迭代器Task4 并行索引 Task1 B树页 B树页 实际上是每个B树页面的标题部分&#xff0c;包含叶子页面和内部页面共享的信息。 …...

爬虫知识之BeautifulSoup库安装及简单介绍

一. 前言 在前面的几篇文章中我介绍了如何通过Python分析源代码来爬取博客、维基百科InfoBox和图片,其文章链接如下: 其中核心代码如下: # coding=utf-8 import urllib import re #下载静态HTML网页 url=http://www.csdn.net/ content = urllib.urlopen(url).read…...

如何有效取代FTP来帮助企业快速传输大文件

在互联网的发展历史上&#xff0c;FTP是一种具有里程碑意义的协议&#xff0c;它最早出现在1971年&#xff0c;是实现网络上文件传输的基础。FTP的优点是简单、稳定、兼容性强&#xff0c;可以在不同的操作系统和平台之间进行文件交换。然而&#xff0c;时代在进步&#xff0c;…...

免登陆积分商城原理

有客户需要免登陆积分商城&#xff0c;研究了一下发现免登陆用途广泛&#xff0c;实现原理也很简单。如果是浏览器无非就是使用fingerprintjs2之类的扩展来实现获取浏览器指纹ID&#xff0c;如果是APP就获取设备唯一标识&#xff0c;然后在使用cryptojs加密来传递到php&#xf…...

muduo源码学习base——Atomic(原子操作与原子整数)

Atomic(原子操作与原子整数&#xff09; 前置知识AtomicIntegerTget()getAndAdd()getAndSet() 关于原子操作实现无锁队列(lock-free-queue) 前置知识 happens-before&#xff1a; 用来描述两个操作的内存可见性 如果操作 X happens-before 操作 Y&#xff0c;那么 X 的结果对于…...

最短路相关笔记

Floyd Floyd 算法&#xff0c;是一种在图中求任意两点间最短路径的算法。 Floyd 算法适用于求解无负边权回路的图。 时间复杂度为 O ( n 3 ) O(n^3) O(n3)&#xff0c;空间复杂度 O ( n 2 ) O(n^2) O(n2)。 对于两点 ( i , j ) (i,j) (i,j) 之间的最短路径&#xff0c;有…...

Web前端-Vue2+Vue3基础入门到实战项目-Day5(自定义指令, 插槽, 案例商品列表, 路由入门)

自定义指令 基本使用 自定义指令: 自己定义的指令, 可以封装一些dom操作, 扩展额外功能全局注册// 1. 全局注册指令 Vue.directive(focus, {// inserted 会在 指令所在的元素, 被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el)el.focus()} …...

mysql json数据类型 相关函数

创建JSON文本的函数 1.JSON_ARRAY&#xff08;转换json数组&#xff09; 2.JSON_OBJECT&#xff08;转换json对象&#xff09; 3.JSON_QUOTE&#xff08;转义字符串&#xff09; 搜索JSON文本的函数 1.JSON_CONTAINS&#xff08;json当中是否包含指定value&#xff09; 2.J…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码&#xff0c;写上注释 当然可以&#xff01;这段代码是 Qt …...

YSYX学习记录(八)

C语言&#xff0c;练习0&#xff1a; 先创建一个文件夹&#xff0c;我用的是物理机&#xff1a; 安装build-essential 练习1&#xff1a; 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后…...

PL0语法,分析器实现!

简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...

django blank 与 null的区别

1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是&#xff0c;要注意以下几点&#xff1a; Django的表单验证与null无关&#xff1a;null参数控制的是数据库层面字段是否可以为NULL&#xff0c;而blank参数控制的是Django表单验证时字…...