ck-editor5的研究 (3):初步使用 CKEditor5 的事件系统和API

article/2025/7/24 3:52:52

前言

在上一篇文章中—— ck-editor5的研究(2):对 CKEditor5 进行设计,并封装成一个可用的 vue 组件 ,我已经把 CKEditor5 封装成了一个通用vue组件,并且成功在nuxt中运行,并具备一定的通用性,已经可以正式使用了。

但是,它只能完全替换内容,或者手动输入,并不能通过使用 js 的方法,对 CKEditor5 进行更细粒度的控制,比如 在编辑器中插入一小部分内容

那么这篇文章,我将初步研究 CKEditor5 的 事件系统API。大概的效果如下:
在这里插入图片描述

大概分成了3步

1. 理解CKEditor5的核心概念

首先,来看看这张图:

在这里插入图片描述
也可以去 官方文档 看,但是 ckeditor 设计理念太过于庞大,太难读懂了。我这里就按我的理解,把CKEditor5比作人体的 脑(Model)眼睛(Input data)手和脚(Editing view 和 Data view) 三大部分了。

注意:这里我们换了个称呼,把 Output data 叫成 Data view。方便后面写代码更容易理解API。

于是我们就按照常理思考:眼睛用于接收信息,通过神经再次转化信息,并传入我们的大脑,大脑经过思考后下达命令给手和脚,让手和脚都握笔写字 (做同一件事情)。

相对应地,CKEditor5也一样:编辑区域接收用户输入的内容(Input data),通过捕捉并向上转化(Data upcast),传入Model中,Model再把数据进行向下转化(downcast),处理成一条条的命令(也就是给一个对象添加一个个属性),并且分配到Editing 对象和 Data 对象中去,等待我们进行调用。

我们先打印一下 editor实例对象,进行观察:
编辑器实例对象
我们发现有 model、editing、data、conversion,正好对应上面提到的 Model、Editing view、Data view 和转化器(upcast, downcast)。

尝试理解一下官方的架构图(MVC设计模式),找找对应的部位:
在这里插入图片描述

2. 开始搭建目录

仍然使用之前的代码,准备 一个 ts ,一个 vue组件,一个 demo3/index.vue 测试页面。我将会重点在 demo3/index.vue 里面写代码,先观察一下3个文件大致内容:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

3. 尝试做些事情

通过观察,可以看到,我已经在组件中用 @ready 事件把编辑器实例对象传送了出来,并且用 editorInstance 进行了接收,这样我们就可以在页面中调用实例对象的方法了。

1. 点击按钮插入文本到末尾

我们添加一个按钮和事件,点击按钮时,插入文本到末尾:

/*** 点击按钮插入文本到末尾*/
function insertTextToEnd() {if (!editorInstance) {return;}/*** 使用model.change方法,可以改写编辑内容, change方法是一个回调函数,参数是writer,* 在这里找它的属性 https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_writer-Writer.html* 这是我们第一篇文章提到过的 API 文档*/editorInstance.model.change((writer) => {/*** 找到根节点<root> (可以理解为dom树的根节点 document.documentElement 也就是 <html> 标签),* ckeditor5也有自己的一棵dom树,但与网页文档对象dom树不一样,而是一一对应的*/const root = editorInstance?.model.document.getRoot();if (!root) {return;}// 获取末尾位置const endPosition = writer.createPositionAt(root, 'end');console.log('endPosition :>> ', endPosition);// 移动光标到末尾writer.setSelection(endPosition);// 执行enter命令editorInstance?.execute('enter');// 插入文本editorInstance?.model.change((writer) => {const content = writer.createText('The End!');console.log('content :>> ', content);editorInstance?.model.insertContent(content);});});
}

看下效果,注意看我的光标:
在这里插入图片描述

2. 添加内容变更事件

// 内容变更事件
const initContentChangeEvent = () => {editorInstance?.model.document.on('change:data', () => {console.log('The data has changed!');});
};

3. 修改默认回车事件

默认回车换行,是创建一个 p 标签,我们使用 Editing view,把它换成 br 标签。
默认的 shitf + 回车 是创建一个 br 标签,反而变成 p 标签了。

/*** 把默认的回车事件,由 p 标签改为 br 标签*/
function changeEnterEvent() {editorInstance?.editing.view.document.on('enter',(evt, data) => {data.preventDefault();evt.stop();if (data.isSoft) {editorInstance?.execute('enter');editorInstance?.editing.view.scrollToTheSelection();return;}editorInstance?.execute('shiftEnter');editorInstance?.editing.view.scrollToTheSelection();},{ priority: 'high' },);
}

4. 插入一些较长的 HTML 代码

使用 Data view 方式来插入内容

/*** 插入一些较长的 HTML 代码*/
function insertLongHtmlCode() {editorInstance?.model.change((writer) => {const content = '<p>A paragraph with <a href="https://ckeditor.com">some link</a>.</p>';const viewFragment = editorInstance?.data.processor.toView(content);if (!viewFragment) {return;}const modelFragment = editorInstance?.data.toModel(viewFragment);if (!modelFragment) {return;}editorInstance?.model.insertContent(modelFragment);});
}

最终的demo代码和测试效果图

demo的代码

<template><div class="space-y-4"><h1 class="text-xl font-bold">demo3: 初步使用ckeditor5的 事件 和 API</h1><!-- TODO 测试控制面板 --><div class="flex gap-2"><button@click="insertTextToEnd"class="text-white cursor-pointer rounded border-none bg-blue-500 px-4 py-2 text-[#fff] outline-none">插入文本到末尾</button><button@click="changeEnterEvent"class="text-white cursor-pointer rounded border-none bg-blue-500 px-4 py-2 text-[#fff] outline-none">把默认的回车事件,由 p 标签改为 br 标签</button><button@click="insertLongHtmlCode"class="text-white cursor-pointer rounded border-none bg-blue-500 px-4 py-2 text-[#fff] outline-none">插入一些较长的 HTML 代码</button></div><!-- 编辑器组件 --><ClientOnly><ck-editor3 @ready="onEditorReady" /></ClientOnly></div>
</template><script setup lang="ts">
import type MyClassicEditor from '@/components/ck/editor3/ckeditor3';let editorInstance: MyClassicEditor | null = null;
const onEditorReady = (editor: MyClassicEditor) => {editorInstance = editor;console.log('editorInstance :>> ', editorInstance);initContentChangeEvent();
};/*** 点击按钮插入文本到末尾*/
function insertTextToEnd() {/*** 使用model.change方法,可以改写编辑内容, change方法是一个回调函数,参数是writer,* 在这里找它的属性 https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_model_writer-Writer.html* 这是我们第一篇文章提到过的 API 文档*/editorInstance?.model.change((writer) => {/*** 找到根节点<root> (可以理解为dom树的根节点 document.documentElement 也就是 <html> 标签),* ckeditor5也有自己的一棵dom树,但与网页文档对象dom树不一样,而是一一对应的*/const root = editorInstance?.model.document.getRoot();if (!root) {return;}// 获取末尾位置const endPosition = writer.createPositionAt(root, 'end');// console.log('endPosition :>> ', endPosition);// 移动光标到末尾writer.setSelection(endPosition);// 执行enter命令editorInstance?.execute('enter');// 插入文本editorInstance?.model.change((writer) => {const content = writer.createText('The End!');// console.log('content :>> ', content);editorInstance?.model.insertContent(content);});});
}// 内容变更事件
const initContentChangeEvent = () => {editorInstance?.model.document.on('change:data', () => {console.log('The data has changed!');});
};/*** 把默认的回车事件,由 p 标签改为 br 标签*/
function changeEnterEvent() {editorInstance?.editing.view.document.on('enter',(evt, data) => {data.preventDefault();evt.stop();if (data.isSoft) {editorInstance?.execute('enter');editorInstance?.editing.view.scrollToTheSelection();return;}editorInstance?.execute('shiftEnter');editorInstance?.editing.view.scrollToTheSelection();},{ priority: 'high' },);
}/*** 插入一些较长的 HTML 代码*/
function insertLongHtmlCode() {editorInstance?.model.change((writer) => {const content = '<p>A paragraph with <a href="https://ckeditor.com">some link</a>.</p>';const viewFragment = editorInstance?.data.processor.toView(content);if (!viewFragment) {return;}const modelFragment = editorInstance?.data.toModel(viewFragment);if (!modelFragment) {return;}editorInstance?.model.insertContent(modelFragment);});
}
</script><style lang="less" scoped>
// 样式
</style>

效果图

在这里插入图片描述


http://www.hkcw.cn/article/TeOCOaUtbS.shtml

相关文章

苏超联赛无锡观众离场不留一片垃圾 文明观赛获赞

南京奥体中心门口,几个年轻球迷正蹲在路边刷手机,手指都快把屏幕戳破了。苏超联赛的门票开售后十分钟内所有场次全部售罄,尤其是6月1日南京队主场对阵无锡队的比赛,黄牛票甚至炒到了原价的五倍。南京队更衣室里,25岁的孟振正在脚上缠绷带。这位身价75万的本土球星最近压力…

北京今天最高气温31℃,西部北部有分散性阵雨或雷阵雨 北风较大需防风

今天6月2日白天,北京天气由晴转多云,最高气温达到31℃。西部和北部地区可能出现分散性阵雨或雷阵雨。受冷空气影响,北京北风较强,阵风可达6至7级,市民需注意防风。责任编辑:zx0176

美官员:预计中美本周就关税问题会谈 双方表达谈判意愿

当地时间6月1日,白宫国家经济委员会主任凯文哈西特在美国广播公司《本周》节目中表示,预计中美将于本周就关税问题进行会谈。他提到双方都表达了谈判的意愿,并且每天都在沟通,试图推动此事取得进展。5月10日至11日,中美经贸高层在瑞士日内瓦举行会谈,双方同意在90天内降低…

SpringAI(GA):RAG下的ETL快速上手

原文链接&#xff1a;SpringAI(GA)&#xff1a;RAG下的ETL快速上手 教程说明 说明&#xff1a;本教程将采用2025年5月20日正式的GA版&#xff0c;给出如下内容 核心功能模块的快速上手教程核心功能模块的源码级解读Spring ai alibaba增强的快速上手教程 源码级解读 版本&a…

AI大模型赋能,aPaaS+iPaaS构建新一代数智化应用|爱分析报告

01 aPaaS和iPaaS成为企业用户关注重点 PaaS市场定义 根据Gartner的定义&#xff0c;PaaS&#xff08;Platform as a Service&#xff09;平台是应用基础架构&#xff08;中间件&#xff09;服务的广泛集合&#xff0c; 包含应用平台、集成、业务流程管理、数据服务和AI应用等…

性能优化 - 工具篇:基准测试 JMH

文章目录 Pre引言1. JMH 简介2. JMH 执行流程详解3. 关键注解详解3.1 Warmup3.2 Measurement3.3 BenchmarkMode3.4 OutputTimeUnit3.5 Fork3.6 Threads3.7 Group 与 GroupThreads3.8 State3.9 Setup 与 TearDown3.10 Param3.11 CompilerControl 4. 示例代码与分析4.1 关键点解读…

郑钦文淋雨一直走 从黑洞到彩虹的心情旅程

6月1日,郑钦文在法网女单1/8决赛中获胜。赛后她用张韶涵的《淋雨一直走》来形容自己的心情:“有时掉进黑洞,有时爬上彩虹。”谈及决胜盘前的调整和获胜关键,郑钦文表示第二盘第一局曾有40-0的领先优势,但未能把握住机会,反而让对手进入了状态。在丢掉第二盘后,她去卫生间…

印度拉拢蒙古能抄中国稀土的作业吗 绕不开的运输难题

印度在与巴基斯坦的冲突中失利后不久,便与蒙古国展开了联合军演。蒙古国空军成立100周年阅兵式上,仅有的两架米格-29战机飞过乌兰巴托上空。五天后,印度陆军特遣队跨越5000公里抵达这片草原,启动了一场被网友戏称为“蒙古出海军,印度出空军”的联合军演。5月31日,“游牧大…

斯瓦泰克2比1莱巴金娜 逆转晋级法网八强

6月1日,在法国网球公开赛女单第四轮比赛中,四届赛会冠军、5号种子斯瓦泰克以2-1(1-6、6-3、7-5)逆转战胜12号种子莱巴金娜,本赛季三次击败对手,并取得法网25连胜。斯瓦泰克在这场比赛中获得了430分和44万欧元的奖金。她连续六年闯入法网八强,这是她第11次跻身大满贯八强…

南京五台山体育场再现人浪 雨夜观赛创纪录

6月1日晚,江苏省城市足球联赛的一场焦点战在南京五台山体育场举行,对阵双方是南京队与无锡队。最终,南京队以1:0小胜无锡队,取得赛事两连胜,并将城市联赛排名提升至第三。比赛日下午5点不到,五台山体育场已经开始有序检票,球迷方阵先行进场布置助威横幅。由于比赛日一直…

Linux服务器 TensorFlow找不到GPU

记录一下这次长达两天的心累Debug&#xff1a;Could not find cuda drivers on your machine, GPU will not be used.&#xff0c;先说一下我的项目情况 使用VSCode ssh连接实验室服务器&#xff0c;无root权限&#xff0c;不能使用sudo指令&#xff0c;Linux系统&#xff0c;…

xdma 驱动测试与分析

目录 1. 简介 2. 基本测试 2.1 H2C 测试 2.1.1 MRRS 2.1.2 抓取 H2C 数据 2.1.3 数据位宽 2.1.4 数据对比 2.1.5 写入地址测试 2.1.6 带宽测试 2.1.6.1 x86_Gen2x4 2.1.6.2 x86_Gen3x4 2.1.6.3 x86_Gen3x8 2.1.6.4 ZCU102_Gen2x1 2.1.6.5 AGX_Gen3x4 2.1.7 带宽…

基因编辑首次临床救人,罕见病婴儿绝处逢生 医学奇迹搬进现实

得了绝症竟然能靠“改写基因”治好?2025年5月,患有罕见病CPS1的婴儿KJ通过基因编辑重获新生,这场医学奇迹直接把科幻片情节搬进现实。当科学家用“分子剪刀”剪断遗传病的枷锁,我们正站在改写生命规则的转折点——未来癌症、衰老甚至农作物都能被重新编码,但这把双刃剑也藏…

郎永淳儿子哥大双硕士家里蹲:360万换月薪8000,学历还值钱吗? 精英教育的困境

北京的五月,蝉鸣未起,但社交媒体上关于“前央视名嘴郎永淳儿子找不到工作”的话题已经沸沸扬扬。25岁的郎俣,顶着哥伦比亚大学经济统计学和哲学双硕士的光环回国一年,却以“家里蹲”的状态成了网友口中的“高配版躺平青年”。郎俣的故事像一部现实版《变形记》。14岁赴美留…

26岁女孩骨质疏松上热搜!竟然是因为这个习惯……很多人都有,快自查 过度防晒导致维生素D缺乏

随着天气逐渐变热,日晒也变得越来越强烈,很多人开始重视防晒。然而,最近有一则新闻引起了广泛关注:一名26岁的女孩因为长期全面防晒,竟然被确诊为骨质疏松。这让人不禁疑惑,防晒和骨质疏松之间到底有什么联系?过度防晒会导致人体缺乏维生素D。维生素D是一种脂溶性维生素…

“芒种不过午,三伏棉衣捂”,今年芒种在何时? 芒种三大特点解析

2025年6月5日17点57分将迎来芒种节气。这个节气在农事上具有重要意义,有“芒种芒种,连收带种”的说法,意味着北方忙着收麦子,南方忙着种稻子,全国进入“夏收、夏种、夏管”的忙碌模式。今年的芒种被认为不一般,有三个特点。首先是芒种在端午后。2025年的端午节是5月31日,…

SSRF 接收器

接收请求 IP.php <?php // 定义日志文件路径 $logFile hackip.txt;// 处理删除请求 if (isset($_POST[delete])) {$ipToDelete $_POST[ip];$lines file($logFile, FILE_IGNORE_NEW_LINES);$newLines array();foreach ($lines as $line) {$parts explode( | , $line);…

UCS(Universal Control System)能成为下一代通用控制系统吗?

UCS&#xff08;Universal Control System&#xff09;是下一代革命性通用控制系统。它以 “云 - 网 - 端” 极简架构&#xff0c;以及软件定义、全数字化、云原生等特性&#xff0c;号称颠覆了应用近 50 年的传统 DCS 技术架构。具体介绍如下&#xff1a; 架构组成&#xff1a…

卢伟冰称竞争从来不是小米未来的关键 挑战在于自身

近日,小米集团ceo卢伟冰发博回答投资者提问:小米未来困难或挑战是什么。他表示:“竞争从来不是,未来也不会是关键。我一直深信: 不可胜在己,可胜在敌”责任编辑:zx0176

南通成苏超榜一大哥 自称“南哥” 三连胜领跑积分榜

江苏省城市足球联赛第三轮比赛中,南通队以4:0客场战胜泰州队,赢得了“苏中德比”,并取得三连胜。目前,南通队在积分榜上排名第一,继续领跑“苏超”。比赛上半场双方互有攻守,但比分一直保持在0:0。下半场开始后,南通队在短短25分钟内连进3球,最终在比赛结束前再入一球,…