WEB 编程:使用富文本编辑器 Quill 配合 WebBroker 后端
使用 Delphi 的 WebBroker 框架写 Web Server,需要一个前端的富文本编辑器。
评估了好几个,最后选择 Quill 这个开源的。
官方地址:Quill - Your powerful rich text editor
把前端代码,存储为一个单独的文本文件,方便随便哪个页面需要的时候可以使用。相当于封装为一个独立的对象,方便代码重用。
Quill 编辑器的代码如下:
<!-- quill 编辑器的封装 -->
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet"><style>.edit_container {font-family: 'Avenir', Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;text-align: center;color: #2c3e50;margin-top: 60px;}.ql-editor{height:400px;}</style><form id="myForm" action="ContentUpdate" method="post"><div id="editor"></div><input type="hidden" name="delta" id="deltaInput"><input type="hidden" name="html" id="htmlInput"><input type="submit" value="Submit"></form><script>const quill = new Quill('#editor', {modules: {toolbar: [[{ header: [1, 2, false] }],['bold', 'italic', 'underline'],['image', 'code-block'],],},theme: 'snow'});document.getElementById('myForm').addEventListener('submit', function() {// 获取 Quill 编辑器的内容const delta = JSON.stringify(quill.getContents());const html = quill.root.innerHTML;// 将内容放入隐藏输入框中document.getElementById('deltaInput').value = delta;document.getElementById('htmlInput').value = html;});</script>
把上述代码,保存为文本文件:QuillEditor.txt;
然后,我们来做一个测试用的 html 文件,代码如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Quill Form Submission</title>
</head>
<body><#Editor>
</body>
</html>
这个文件,保存为 index.html;
这个文件里面的 <#Editor> 是给 WebBroker 的 PageProducer 自动替换的标记。Delphi 的后台程序会将这个标记替换为前面的 QuillEditor.txt 的内容。
Delphi 的代码
首先是如何输出页面
在 Delphi 里面创建一个 WebBroker 工程,
选择 StandAlone 模式,这样方便调试。
在这个工程里面,Delphi 自动帮我们创建了一个 Web 服务器端的核心框架:TWebModule1;
拖一个 PageProducer1 到 TWebModule1 上面。设置它的属性:HTMLFile 为 index.html;
这里的 index.html 就是前面的那个简单的页面框架。这个文件只要放在程序的相同目录下,运行时 PageProducer1 就会自动加载它。当然,也可以指定绝对路径比如:D:\MyHTML\index.html;
然后,选中 PageProducer1 然后把属性面板的页标签切换到 Events,可以看到 PageProducer1 只有一个事件:OnHTMLTag,双击这个事件,Delphi IDE 自动帮我们创建事件的代码框架。在这里我们写代码如下:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;const TagString: string; TagParams: TStrings; var ReplaceText: string);
varSL: TStringList;
beginSL := TStringList.Create;trySL.LoadFromFile('QuillEditor.txt');if TagString = 'Editor' thenReplaceText := SL.Text;finallySL.Free;end;
end;
上述代码的解释:
PageProducer1 在加载 index.html 的时候,会触发上述事件。我们在上述事件里面写代码,首先判断标记:Editor,这个标记就是前面我们写在 index.html 里面的那个 <#Editor>
ReplaceText 则是替换那个标记的内容。这里,我们用前面保存的 Quill Editor 的那个文件的内容来替换掉 Editor 这个标记。这里会将包括尖括号在内的整个 <#Editor> 都替换掉。最终输出的页面内容就是包含 QuillEditor.txt 的内容的一个完整 HTML 页面。
再然后,双击 TWebModule1 这个界面窗口,弹出 Editing WebModule1.Actions 对话框,里面已经有一个默认的 Action,选中它,然后在属性面板里面,把它的 Producer 属性下拉选择为 PageProducer1;这样操作的目的是浏览器访问服务器时,触发这个 Action,这个 Action 最终会把它绑定的 PageProducer1 的内容输出给浏览器。
到此,输出一个包含 Quill Editor 的页面制作完毕。
服务器端接收浏览器提交的内容
在前面提到的 Editing WebModule1.Actions 对话框里面,鼠标右键,下拉菜单:Add,添加一个 Action。
选择这个 Action,设置它的属性 Path 为:ContentUpdate
切换属性面板到事件,它只有一个 OnAction 事件,双击,Delphi IDE 自动产生事件代码框架,在里面写代码如下:
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); varS: string;RequestBody: string;Delta, Html: string;JSONValue: TJSONObject;SL: TStringList; beginS := Request.ContentFields.Values['html'];SL := TStringList.Create;trySL.Text := S;SL.SaveToFile('abcd.txt');finallySL.Free;end;Response.SendRedirect('Show');Handled := True; end;
上述代码解释:
这个 Action 的 Path 之所以设置为 ContentUpdate,是因为页面上的 Quill Editor 里面的代码:
<form id="myForm" action="ContentUpdate" method="post">
页面代码指明了提交给服务器的路径是 ContentUpdate,因此 Delphi WebBroker 服务器里面的这个 Action 的 Path 为 ContentUpdate 的 Action 事件就会被触发。
上述代码中:
S := Request.ContentFields.Values['html'];
这里的 'html' 是对应的 Quill Editor 的 HTML 代码里面的 name 部分的名称:
<input type="hidden" name="html" id="htmlInput">
假设把上述页面代码的 name="html" 改为 "html2",那么,对应的服务器端 Delphi 的代码就应该改为:
S := Request.ContentFields.Values['html2'];
最终,Delphi 把来自浏览器提交的编辑器的内容,保存为一个文本文件 abcd.txt
保存完编辑器提交的内容后,让浏览器跳转到 show 这个路径去:
Response.SendRedirect('Show');
假设浏览器访问的是 http://127.0.0.1:8080/ 就会跳转到 http://127.0.0.1:8080/show
为服务器端增加 Show 这个路径的处理方法
在前面提到的 Editing WebModule1.Actions 对话框里面,鼠标右键,下拉菜单:Add,添加一个 Action。
选择这个 Action,设置它的属性 Path 为:Show
选中这个 Action,切换属性面板到 Events,双击它的 OnAction 事件,IDE 创建事件代码框架,写代码如下:
procedure TWebModule1.WebModule1WebActionItem2Action(Sender: TObject;Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
beginvar SL := TStringList.Create;trySL.LoadFromFile('abcd.txt');Response.Content := SL.Text;finallySL.Free;end;
end;
上述代码解释:
这里我们加载前面保存的浏览器提交过来的 Quill Editor 的内容,也就是保存的 abcd.txt,然后把这个内容作为 Response 返回给浏览器。
如果在浏览器的地址栏里面输入:http://127.0.0.1:8080/show 就会直接触发服务器端的这个事件方法,浏览器页面上就能看到之前我们在 Quill Editor 里面输入的内容。
比较有意思的是:图片
在浏览器端的富文本编辑器里面插入图片,通常是要上传图片,然后把 URL 插入到编辑器里面。但是这样又会增加不少代码工作。比如服务器端需要有个专门的 Action 来接收上传的图片文件,把图片文件保存到 Web 服务器的某个目录里面,再返回 URL;在前端需要写不少 JavaScript 代码,用来上传图片,获得 URL,然后把 URL 插入到编辑器的对应位置。等等,很麻烦。
对于 Quill Editor 来说,前面我给出的代码,用户点击它的工具栏的图片按钮,选择本地硬盘的图片文件,然后,它会自动把图片贴进编辑框的正确位置,完成图文混排。点击提交按钮后,整个文本编辑框的内容包括图片数据,一起提交给服务器。这里的图片数据是编码为 Base64 的,并且格式复合 HTML 的标准的页面嵌入BASE64图片的格式,不完整但能体现这一点的代码片段数据如下:
src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAYEBQYF
因此,服务器端获得的完整的字符串,就是编辑器的完整内容,包含图片。把这个字符串保存后,作为文本输出给浏览器,图片也能完整地显示出来。
这样一来,图片和文字的混合编辑最终输出的就是一段字符串。在前端不需要写额外的 JavaScript 代码,后端也不需要额外的代码来处理图片文件的保存。
在实际的项目里面,后端可以把前端提交的字符串(包含图片和格式化的文字),直接原本保存到数据库的字段里面。需要显示时,直接从数据库的字段里面读取内容,输出给页面。
One more thing
前面提到的服务器端加载 Quill Editor 的代码,使用了 TStringList,重复一下代码片段:
procedure TWebModule1.PageProducer1HTMLTag(Sender: TObject; Tag: TTag;const TagString: string; TagParams: TStrings; var ReplaceText: string);
varSL: TStringList;
beginSL := TStringList.Create;trySL.LoadFromFile('QuillEditor.txt');if TagString = 'Editor' thenReplaceText := SL.Text;finallySL.Free;end;
end;
应该把上述代码,封装为一个对象,需要的地方,直接调用对象就好了。避免每个不同的 WEB 项目里面都要重复写上述代码。封装为对象,就可以实现代码重复使用。
代码如下:
unit UQuillEditor;interfaceusesSystem.SysUtils, System.Classes, System.IOUtils;typeTQuillEditor = classprivatepublicclass function GetHtml(const QuillEditorFileName: string): string;end;implementation{ TQuillEditor }class function TQuillEditor.GetHtml(const QuillEditorFileName: string): string;
varSL: TStringList;
beginif (not FileExists(QuillEditorFileName)) then raise Exception.Create('文件不存在!' + QuillEditorFileName);SL := TStringList.Create;trySL.LoadFromFile(QuillEditorFileName);Result := SL.Text;finallySL.Free;end;
end;end.
结论
Quill Editor 作为一个前端的开源的富文本编辑器,用起来还是很方便的。后端使用 Delphi WebBroker 框架来开发,可以简单地封装为一个文本文件和一个简单的 Delphi 对象,在需要的地方就可以直接使用了。
相关文章:
WEB 编程:使用富文本编辑器 Quill 配合 WebBroker 后端
使用 Delphi 的 WebBroker 框架写 Web Server,需要一个前端的富文本编辑器。 评估了好几个,最后选择 Quill 这个开源的。 官方地址:Quill - Your powerful rich text editor 把前端代码,存储为一个单独的文本文件,方…...
新书出版,大陆首本NestJS图书《NestJS全栈开发解析:快速上手与实践》
新书全栈实战项目:数字门店管理平台开源啦🎉🎉🎉 GitHub地址(持续更新NestJS企业级实践):欢迎star⭐️⭐️⭐️ 前端ReactTypeScriptVite 后端NestMySQLRedisDocker 前言 对,你没看…...
面试题:react、vue中的key有什么作用?(key的内部原理)
1.虚拟DOM中key的作用: key是虚拟DOM对象的标识,当数据发生变化时,vue会根据【新数据】生成【新的虚拟DOM】随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下: 2.对比规则: (1).旧虚拟DOM中找到了与新虚拟DOM相同的key: …...
基于python+django+vue的外卖管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于pythondjangovueMySQL的外…...
初始分布式系统和Redis特点(
(一)认识redis Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperlog…...
计算机毕业设计 家电销售展示平台的设计与实现 Java实战项目 附源码+文档+视频讲解
博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…...
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 引言 RecyclerView 是 Android 开发中用于展示列表和网格的强大组件。它通过高效的缓存机制,优化了滑动性能和内存使用。本文将深入…...
管道缺陷检测系统源码分享
管道缺陷检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…...
python定时发送邮件的功能如何实现自动化?
Python定时发送邮件教程?如何用Python发送电子邮件? Python定时发送邮件不仅能够帮助我们自动处理日常的邮件发送任务,还能在特定时间点触发邮件发送,确保信息的及时传达。AokSend将详细探讨如何利用Python实现定时发送邮件的自动…...
工业机器人9公里远距离图传模块,无人机低延迟高清视界,跨过距离限制
在科技日新月异的今天,无线通信技术正以未有的速度发展,其中,图传模块作为连接现实与数字世界的桥梁,正逐步展现出其巨大的潜力和应用价值。今天,我们将聚焦一款引人注目的产品——飞睿智能9公里远距离图传模块&#x…...
IEEE-754 32位十六进制数 转换为十进制浮点数
要将 IEEE-754 32位十六进制数 转换为 十进制浮点数,可以使用LabVIEW中的 Type Cast 函数。以下是一些具体步骤,以及相关实例的整理: 实现步骤: 输入十六进制数:在LabVIEW中,首先需要创建一个输入控制器&am…...
XSS跨站脚本攻击及防护
什么是XSS攻击? XSS(Cross-Site Scripting,跨站脚本攻击)是一种代码注入攻击。攻击者在目标网站上注入恶意代码,当用户(被攻击者)登录网站时就会执行这些恶意代码,通过这些脚本可以读取cookie,session tokens,或者网站其他敏感的网…...
利用ClasserLoader来实现jar包加载并调用里面的方法
1.ClasserLoader介绍? classloader顾名思义,即是类加载。虚拟机把描述类的数据从class字节码文件加载到内存,并对数据进行检验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。…...
【VUE】快速上手
一、快速上手 创建HTML文件引入vue.js <script src"https://unpkg.com/vue3/dist/vue.global.js"></script> <script src"https://cdn.bootcdn.net/ajax/libs/vue/3.3.4/vue.global.prod.js"></script>按照vue.js的语法编写代码…...
在 Docker 中部署无头 Chrome:在 Browserless 中运行
什么是 Browserless? Browserless 是一款基于云的浏览器解决方案,旨在实现高效的浏览器自动化、网页抓取和测试。 它利用 Nstbrowser 的指纹库,实现随机指纹切换,确保流畅的数据收集和自动化。得益于其强大的云基础设施…...
Meta-Learning数学原理
文章目录 什么是元学习元学习的目标元学习的类型数学推导1. 传统机器学习的数学表述2. 元学习的基本思想3. MAML 算法推导3.1 元任务设置3.2 内层优化:任务级别学习3.3 外层优化:元级别学习3.4 元梯度计算3.5 最终更新规则 4. 算法合并5. 理解 MAML 的优…...
【图像匹配】基于SURF算法的图像匹配,matlab实现
博主简介:matlab图像代码项目合作(扣扣:3249726188) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 本次案例是基于基于SURF算法的图像匹配,用matlab实现。 一、案例背景和算法介绍 前…...
RocketMQ实战与集群架构详解
目录 一、MQ简介 MQ的作用主要有以下三个方面 二、RocketMQ产品特点 1、RocketMQ介绍 2、RocketMQ特点 三、RocketMQ实战 1、快速搭建RocketMQ服务 2、快速实现消息收发 1. 命令行快速实现消息收发 2. 搭建Maven客户端项目 3、搭建RocketMQ可视化管理服务 4、升级分…...
docker容器中的内存占用高的问题分析
文章目录 问题描述原因分析分析1分析2验证猜想 结论和经验 问题描述 运维新增对某服务的监控后发现:内存不断上涨的现象。进一步确认,是因为有多个导出日志操作导致的内存上涨问题。 进一步的测试得出的结果是:容器刚启动是占用内存约为50M…...
纯血鸿蒙NEXT常用的几个官方网站
一、官方文档 https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/Readme-CN.md刚入门查看最多的就是UI开发模块,首先要熟悉组件使用 二、官方API参考 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/development-i…...
嘎嘎降AI使用教程:手把手教你用嘎嘎降AI降论文ai率,从97%降到7%实操
嘎嘎降AI使用教程:手把手教你用嘎嘎降AI降论文ai率,从97%降到7%实操 说实话,我当时论文被检测出AI率97%的时候,整个人是懵的。导师直接把报告甩给我说"你这论文是不是全让AI写的",我那叫一个尴尬。后来折腾了…...
python-flask-djangol框架的旅游导游管理系统的功能全bja0vffx
目录功能模块设计技术实现方案前端交互实现部署与测试方案项目进度规划项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作功能模块设计 旅游导游管理系统基于Python Flask/Django框架开发,需包含以下核心功能模块&…...
CasRel关系抽取实战:对接Airflow构建SPO抽取ETL调度流水线
CasRel关系抽取实战:对接Airflow构建SPO抽取ETL调度流水线 1. 项目背景与价值 在日常业务中,我们经常需要从大量文本数据中提取结构化信息。比如从新闻文章中提取人物关系,从产品描述中提取规格参数,从客服对话中提取用户诉求等…...
窗口调整工具:突破限制的窗口大小修改与窗口管理解决方案
窗口调整工具:突破限制的窗口大小修改与窗口管理解决方案 【免费下载链接】WindowResizer 一个可以强制调整应用程序窗口大小的工具 项目地址: https://gitcode.com/gh_mirrors/wi/WindowResizer 在日常电脑使用中,窗口调整工具是提升工作效率的重…...
ETS5保姆级教程:从零配置KNX智能开关,实现灯光、窗帘、场景联动
ETS5保姆级教程:从零配置KNX智能开关,实现灯光、窗帘、场景联动 KNX作为智能家居领域的国际标准协议,以其稳定性和灵活性备受推崇。而ETS5则是配置KNX系统的核心工具,掌握它意味着你能够自由定制属于自己的智能家居方案。本教程将…...
智能车PID调参实战:从电机到舵机的完整调试指南(附C代码)
智能车PID调参实战:从电机到舵机的完整调试指南(附C代码) 在智能车竞赛和自动化项目中,PID控制算法的调参往往是决定车辆性能的关键环节。无论是电机的转速控制还是舵机的转向精度,都需要通过精细的参数调整来实现快速…...
保姆级教程:Langchain框架详解 - 大模型开发者的必备技能
什么是Langchain Langchain是一款提供给用户与大模型之间快捷沟通的代理框架,其核心设计思想就是整合各大模型厂商的接口,给用户提供一个快捷入口能快速实现自己的agent。 核心组件 •agent:Langchain的核心部分,所有的操作都围…...
OpenClaw+Qwen3.5-9B:自动化竞品监测与分析报告生成
OpenClawQwen3.5-9B:自动化竞品监测与分析报告生成 1. 为什么需要自动化竞品监测 作为一位长期关注行业动态的技术从业者,我每周都要花费大量时间手动收集竞品信息。传统方式需要反复访问多个网站,复制粘贴内容到Excel,再人工分…...
硬件设计避坑指南:为什么你的AD原理图转PCB总会丢失元器件位号?
硬件工程师必看:AD原理图转PCB丢失元器件位号的深度解析与根治方案 每次打开Altium Designer准备将精心设计的原理图导入PCB时,却发现所有元器件位号神秘消失——这种场景对硬件工程师来说简直是一场噩梦。位号不仅是元器件在PCB上的身份标识,…...
AI上色有多强?cv_unet_image-colorization修复老照片效果对比展示
AI上色有多强?cv_unet_image-colorization修复老照片效果对比展示 1. 引言:老照片焕发新生的魔法 翻开泛黄的相册,那些黑白照片承载着无数珍贵记忆,却因年代久远失去了原本的色彩。传统的手工上色不仅耗时耗力,还需要…...
