接手过这类涉密内网办公系统改造的人,大概率都经历过相似的"至暗时刻":文印室老师傅把Word红头文件直接Ctrl+C进浏览器,排版全散;科研人员从内部资料站复制一段带公式的内容,粘进去全是乱码;最麻烦的是年底总结时,从Excel拷出来的表格,在编辑器里变成一坨没有框线的纯文本。问题出在哪?大多数人第一反应是"前端组件不行",甚至有人直接提需求说"把富文本编辑器换成Word Online"。但实际上,CKEditor的底子是够用的,真正缺的是格式保留这一层的插件选型和配置。这篇文章就把我在军工系统这类高安全要求环境里,给CKEditor搭配格式保留插件的完整思路和落地配置讲清楚。
先说清楚这篇文章适合谁看:如果你正在做内网办公系统、涉密OA、军工科研单位知识库,或者只是想把Web端富文本编辑器做得更接近Word的排版体验,那么下面这些插件选型和坑点都可以直接抄作业。我不会只给一份插件清单,还会把每个插件的取舍逻辑、离线部署方式、安全过滤策略一起讲明白。毕竟在军工系统里,能装什么、不能装什么,比功能本身更先决定方案走向。
1. 先拆需求:格式保留到底在保什么
1.1 三个层面的格式保留
很多人一上来就问"要装哪些格式保留插件",但这个问题其实藏着一个前置问题:你要保留的是哪一层格式?我在实际项目里会把"格式"拆成三层看,每一层的插件选型完全不同。
第一层是粘贴来源格式,也就是从Word、WPS、Excel、网页里复制内容后,还能不能保留原文的字体、字号、颜色、表格框线、列表编号和图片。这一层靠的是CKEditor的粘贴预处理插件,典型代表是Paste from Office。很多"粘贴后全乱"的抱怨,其实都是这一层没有配好。
第二层是编辑状态格式,也就是用户在编辑器里手动设置的加粗、倾斜、下划线、对齐、缩进、列表样式、标题级别、代码块、公式、表格列宽等。这一层靠的是编辑器内置和扩展的样式插件,比如Font、Alignment、ListProperties、TableProperties等。这一层漏配的结果是:从外部拷贝进来看起来正常,但用户一改就丢样式,或者菜单栏上根本没那个按钮。
第三层是输出与回显格式,也就是内容存进数据库再加载回来时,HTML结构和内联样式是否被保留。这一层人们最容易忽略,但它往往不是缺插件,而是缺配置,比如内容过滤规则太严格,<span style="...">一进去就被过滤掉,导致编辑时看着没问题,保存再打开就变了样。
1.2 为什么CKEditor默认配置会"丢格式"
理解这个问题要回到CKEditor的数据模型。CKEditor不是简单地把HTML字符串塞进contenteditable就完事,它会先做一套HTML解析和内容过滤,把不合规的标签、样式、事件属性全部剔除,再转成自己的模型结构并重新渲染。这个机制叫ACF(Advanced Content Filter,高级内容过滤,CKEditor 4的叫法)或者htmlSupport配置(CKEditor 5里的叫法)。
这套机制的本意是好的,能阻止用户粘贴进来的危险脚本和脏样式,但副作用也很明显:默认白名单往往只包含基础标签,比如<p>、<strong>、<em>、<h1>-<h6>、<ul>、<ol>、<li>、<table>等,对于<span style="font-family:...">、自定义CSS类、<section>这类结构标签,默认是不保留的。所以在军工项目里,我看到最多的现象是:Word文档粘贴进来,正文文字保住了,但字体全变成默认体,表格框线没了,图片不显示,公式变成纯文本。这不是CKEditor不行,而是没有按需放开过滤规则、没有挂对应的处理插件。
2. 插件选型:按保留对象逐类拆解
2.1 粘贴来源抢救:Word、WPS、Excel的格式导入
第一个要配的插件就是PasteFromOffice,在CKEditor 5里官方包名是@ckeditor/ckeditor5-paste-from-office,CKEditor 4里对应的是pastefromword插件。这个插件做的是"粘贴净化"工作:用户从Word、Excel里复制内容时,剪贴板里不只是纯文本,还带一份HTML片段(Office自己生成的OOXML转HTML结构),PasteFromOffice会解析它,把Word特有的命名空间标签、无效样式、vml图形封装、域代码(域代码就是Word里那种自动编号、交叉引用的隐藏指令)清洗掉,同时把能映射到CKEditor模型的内容尽量保留,比如字体、字号、加粗、对齐、表格框线。
我实测下来,CKEditor 5的PasteFromOffice对Word的兼容性已经相当好,但对WPS的支持不如原生Word。毕竟WPS生成的HTML片段在命名空间和样式写法上有自己的变种。碰到这种情况,我的做法是再加一个兜底:配置clipboard的粘贴钩子,在数据进入编辑器之前先做一次预处理,把WPS特有的标签替换掉。下面这段伪代码思路可以放在项目里:
editor.plugins.get( 'ClipboardPipeline' ).on( 'inputTransformation', ( evt, data ) => { const content = data.content; // 对WPS生成的标签做一次清洗,去除类似<o:p>这类无效的XML占位 content.change( writer => { // 这里可以用writer遍历content根节点,做标签重映射 // 实际项目中我们会在这一层剥离无意义的font-family内联, // 并统一'Times New Roman'到系统默认中文字体。 } ); } );另一个被忽视的点是PasteFromPlainText。军工系统里有很多用户习惯从PDF、CAJ阅读器、甚至老旧的TXT格式文档里复制内容,这些来源的剪贴板数据往往是"伪纯文本"或者带大量不可见字符。我建议在工具栏上保留一个"粘贴为纯文本"的入口,让用户自己选择,而不是让编辑器把所有来源都按富文本处理。用户一定会在某个时候感谢你这个按钮。
提示:不要把
PasteFromOffice当成万能。它只能保证从Word/Excel粘贴的常见格式被保留,像Word里用文本框、形状工具做的复杂版式,粘贴进HTML后依然会崩。这是浏览器剪贴板机制决定的,任何Web富文本编辑器都没法完全还原那种版式。
2.2 基础文本和版式:字体字号颜色对齐缩进
这一层是用户在编辑器内主动排版的核心。CKEditor 5的官方包@ckeditor/ckeditor5-font提供了字体、字号、字体颜色、背景色四个子功能,对应插件分别叫FontFamily、FontSize、FontColor、FontBackgroundColor。它们生成的是<span style="font-family:...">和<span style="font-size:...">这类内联样式,所以必须配合htmlSupport白名单才能保证保存后回显不丢。
这里有个军工项目里很常见的需求:单位公文要求字体必须是"方正小标宋""仿宋_GB2312""黑体"这类中文字体,而员工的浏览器可能没装这些字体。如果你只把字体列表配成固定的中文字体名,也不做任何降级,那么另一个没装字体的电脑打开内容时,就会自动替换成默认字体,版式就"看着好像没变、实际全变了"。我的建议是把字体列配置为"字体名 + 安全兜底字体"的组合,类似CSS里的font-family: "仿宋_GB2312", "FangSong", "SimSun", serif;这样的逻辑。在FontFamily配置里可以指定选项时附带fallback,虽然CKEditor在输出HTML时还是会写成单个字体名,但至少你可以规定统一使用"仿宋_GB2312"这一名字,并让前端展示样式时做一次字体映射。
对齐和缩进也是格式保留的重灾区。CKEditor 5里对应的包是@ckeditor/ckeditor5-alignment,提供左对齐、居中、右对齐、两端对齐,生成text-align样式。缩进则需要@ckeditor/ckeditor5-indent配合IndentBlock一起用,否则只能缩进列表项不能缩进段落。在军工公文场景里,段落首行缩进2字符是硬需求,但我必须提醒一句:CKEditor的IndentBlock默认输出的是margin-left或padding-left的像素值,输出到打印端时,除非打印CSS做了特殊适配,否则缩进会跟Word的"首行缩进2字符"产生视觉误差。我建议在前端展示层用一个约定:正文段落统一不加内联缩进样式,而是用一种特殊类名,比如class="first-line-indent",再由CSS统一控制缩进量。这样既能保证编辑时所见接近Word,又能保证打印导出时的统一性。
2.3 表格与复杂结构
表格是格式保留里的重头戏。CKEditor 5的表格功能拆得比较细:Table是最基础的表格支持,TableToolbar提供插入行、列、删除表格这些操作入口,TableProperties和TableCellProperties则负责表格宽度、高度、边框颜色、背景色、单元格宽高这些属性级格式。从Excel粘贴表格后,框线、合并单元格能否保留,主要看PasteFromOffice和Table插件配合得怎么样。
实际项目里,Excel粘贴进CKEditor往往有两个问题。一是框线丢失,因为Excel的单元格边框在粘贴后的HTML里经常表现为border样式而不是<table border="1">属性,如果内容过滤白名单把border-style、border-color过滤掉了,表格就变成无框线。二是列宽失真,Excel的列宽转成HTML之后往往是像素或百分比,但CKEditor默认可能使用固定像素模式,粘贴后列宽比例看起来会跟Excel不完全一致。解决列宽的思路是:在粘贴预处理里把Excel生成的<colgroup>和<col width>转换成CKEditor能识别的<table style="width:100%">加每列<td style="width:??%">的百分比结构。这样至少能保住大致的比例关系。
对于合并单元格,CKEditor 5的Table插件原生支持rowspan和colspan的可视化操作,工具栏里能用鼠标合并拆分单元格。这一点在军工科研单位的"多级表头"场景里特别有用,因为涉密装备的测试记录表、指标对照表几乎都有复杂的多级表头结构。如果你手里的老系统还在用CKEditor 4,那建议把TableTools和tableresize一起装上,否则合并单元格和拖拽调整列宽会很痛苦。
2.4 代码块与数学公式
军工系统里搞计算机和通信技术的人多,科技文档经常要贴代码片段和数学公式。代码块用@ckeditor/ckeditor5-code-block,它会把代码包在<pre><code class="language-xxx">里,支持高亮。选它的时候要注意两点:第一,语言类别列表要按单位实际情况裁剪,不要默认给几十种语言,内网用户翻起来麻烦;第二,它输出的代码块用的是<pre>标签,而Word粘贴进来的"代码"往往是一堆乱七八糟的等宽字体文本加换行,粘贴后并不会自动识别成代码块,需要用户手动套用代码块样式。
数学公式是另一个大坑。CKEditor 5官方文档推荐的是商业插件MathType,它提供可视化的公式编辑面板,用户体验非常好,但问题是它需要网络通信做公式渲染和授权校验,在物理隔离的内网环境里落地很麻烦。我的替代方案是用开源思路集成KaTeX,把公式编辑组件做成一个弹窗性质的插件,用户从LaTeX文本粘贴或手写公式语法,预览渲染用KaTeX实时完成,最终在编辑器里插入一段用<span class="math-tex">\(...\)</span>包裹的HTML。这套方案不需要额外的云端服务,离线部署完全可控。对于老项目用CKEditor 4的场景,官方有个mathjax插件,可以把MathJax库放到内网静态资源目录下,通过config.mathJaxLib指定本地地址,一样能跑。
注意:公式和代码不要混用一套过滤规则。代码块里的
<、>字符很多,数学公式里又有大量#、{、},如果保存到后端后还经过一次HTML实体解码或转义,很容易出现显示异常。我在项目里会把这类内容按"原文块"存储,前端展示时再动态渲染,避免在传输链路上被二次转义。
2.5 图片、附件与多媒体
图片是格式保留里最容易被忽略的一环。CKEditor 5的图片功能由Image、ImageToolbar、ImageStyle、ImageResize、ImageUpload等插件组成。这里我特别想提醒的是图片粘贴上传问题:用户在Word里复制一张截图后粘贴到编辑器,剪贴板里其实是一张二进制图片,如果编辑器没有配置上传适配器,CKEditor 5会默认把图片转成base64字符串塞进HTML源码里。一张几MB的图片立刻让文档体积膨胀一半,保存到数据库还会撑爆字段长度。
军工内网环境里,我推荐的做法是自建一个简单的图片上传接口,用@ckeditor/ckeditor5-upload提供的SimpleUploadAdapter或者自定义UploadAdapter,把图片传到内网文件服务器,然后在编辑器里保存的是图片URL。这样有几个好处:文档库体积可控、图片可以走内网CDN缓存、后续归档导出时图片是独立文件,方便做密级标记和脱敏处理。需要注意,图片URL在内网环境里要写绝对路径还是相对路径,要跟系统部署架构对齐,否则就会出现"编辑时能看到图,别人打开却看不到"的问题。
多媒体方面还有@ckeditor/ckeditor5-html-embed可以做原始的HTML嵌入,比如科研人员需要插入一段WebGL演示或者嵌入式可视化图表时,直接用HtmlEmbed把HTML片段塞进文档。但这个插件要小心,它等于允许用户往编辑器里塞任意HTML,安全风险极高,在涉密系统里最好只对管理员开放,或者干脆禁用。
2.6 HTML原始标签的兜底
不管前面怎么做,总有一些奇怪场景需要保留原始HTML结构。CKEditor 5为此提供了GeneralHtmlSupport,它允许你配置一个白名单,声明哪些标签、哪些属性、哪些样式可以在编辑器里保留。比如某个系统需要保留<section>块和>import { ClassicEditor } from '@ckeditor/ckeditor5-editor-classic'; import { Essentials } from '@ckeditor/ckeditor5-essentials'; import { Undo } from '@ckeditor/ckeditor5-undo'; import { Bold, Italic, Underline, Strikethrough, Subscript, Superscript } from '@ckeditor/ckeditor5-basic-styles'; import { Font } from '@ckeditor/ckeditor5-font'; import { Alignment } from '@ckeditor/ckeditor5-alignment'; import { List, ListProperties } from '@ckeditor/ckeditor5-list'; import { Indent, IndentBlock } from '@ckeditor/ckeditor5-indent'; import { BlockQuote } from '@ckeditor/ckeditor5-block-quote'; import { Table, TableToolbar, TableProperties, TableCellProperties } from '@ckeditor/ckeditor5-table'; import { Image, ImageToolbar, ImageStyle, ImageResize, ImageUpload } from '@ckeditor/ckeditor5-image'; import { CodeBlock } from '@ckeditor/ckeditor5-code-block'; import { Link } from '@ckeditor/ckeditor5-link'; import { RemoveFormat } from '@ckeditor/ckeditor5-remove-format'; import { SourceEditing } from '@ckeditor/ckeditor5-source-editing'; import { GeneralHtmlSupport } from '@ckeditor/ckeditor5-html-support'; import { HtmlEmbed } from '@ckeditor/ckeditor5-html-embed'; import { PasteFromOffice } from '@ckeditor/ckeditor5-paste-from-office'; import { HorizontalLine } from '@ckeditor/ckeditor5-horizontal-line'; import { SpecialCharacters } from '@ckeditor/ckeditor5-special-characters'; ClassicEditor.create( document.querySelector( '#editor' ), { plugins: [ Essentials, Undo, Bold, Italic, Underline, Strikethrough, Subscript, Superscript, Font, Alignment, List, ListProperties, Indent, IndentBlock, BlockQuote, Table, TableToolbar, TableProperties, TableCellProperties, Image, ImageToolbar, ImageStyle, ImageResize, ImageUpload, CodeBlock, Link, RemoveFormat, SourceEditing, GeneralHtmlSupport, HtmlEmbed, PasteFromOffice, HorizontalLine, SpecialCharacters ], toolbar: { items: [ 'undo', 'redo', 'heading', '|', 'fontfamily', 'fontsize', 'fontColor', 'fontBackgroundColor', '|', 'bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'removeFormat', '|', 'alignment', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', 'blockQuote', 'horizontalLine', '|', 'link', 'uploadImage', 'insertTable', 'codeBlock', // 如果集成了公式插件,在这里加 'mathType' 或 'katexFormula' '|', 'htmlEmbed', 'sourceEditing' ] }, table: { contentToolbar: [ 'tableRow', 'tableColumn', 'mergeTableCells', 'tableProperties', 'tableCellProperties' ] }, fontSize: { options: [ '12px', '14px', '16px', '18px', '20px', '24px', '28px', '36px', { title: '小五', value: '9pt' }, { title: '五号', value: '10.5pt' }, { title: '小四', value: '12pt' }, { title: '四号', value: '14pt' }, { title: '小三', value: '15pt' }, { title: '三号', value: '16pt' }, { title: '小二', value: '18pt' }, { title: '二号', value: '22pt' } ] }, list: { properties: { styles: true, startIndex: true, reversed: true } }, image: { toolbar: [ 'imageStyle:block', 'imageStyle:side', 'imageUpload', 'toggleImageCaption' ], upload: { types: [ 'jpg', 'png', 'gif', 'bmp', 'webp' ] } }, htmlSupport: { allow: [ { name: 'span', attributes: true, styles: { 'font-weight': true, 'font-style': true, 'text-decoration': true, 'color': true, 'background-color': true, 'font-size': true, 'font-family': true, 'text-align': true, 'margin-left': true, 'margin-right': true, 'padding-left': true, 'padding-right': true } }, { name: 'section', attributes: true, classes: true }, { name: 'div', attributes: true, classes: true, styles: true } ] } } ).catch( error => { console.error( error ); } );
几个关键配置的解释:fontSize里我同时保留了像素值和以"号"为单位的公文字号,因为军工单位的文档规范里经常要求"三号仿宋"这类说法,给出预设字号能大大减少踩踏式调整。htmlSupport只放开span的部分样式,其他标签默认仍由ACF阻断。这里要特别提醒:我写的allow规则是"允许attributes: true"和"classes: true",在真实项目里要根据安全策略收紧,最好精确到属性名。
4.3 CKEditor4 老项目兼容方案
很多军工单位的老系统还在用CKEditor 4,升级到CKEditor 5是大工程,因为CKEditor 5的框架结构完全重写了,插件不兼容。如果你的老项目暂时不能升级,那至少在格式保留上把CKEditor 4配到最优状态。
在CKEditor 4里,我推荐配置如下插件:pastefromword(从Word粘贴)、font(字体字号)、colorbutton和colordialog(字体颜色)、justify(对齐)、liststyle(列表样式)、indentblock(段落缩进)、table和tableresize和tabletools(表格)、image(图片)、codesnippet(代码块)、mathjax(公式)、sourcearea(源码)。在config.js里可以这样写:
config.extraPlugins = 'pastefromword,font,colorbutton,colordialog,justify,liststyle,indentblock,tableresize,tabletools,codesnippet,mathjax,image,sourcearea'; // 保留尽可能多的标签和样式,但注意这条配置需要用白名单收紧 config.allowedContent = true; config.pasteFromWordPromptCleanup = true; config.mathJaxLib = '/internal-resources/mathjax/MathJax.js?config=TeX-AMS_HTML'; // 字体配置示例,按单位规范配置 config.font_names = '仿宋_FangSong;楷体_KaiTi;黑体_SimHei;宋体_SimSun;方正小标宋_FZXiaoBiaoSong;'; config.fontSize_defaultLabel = '14px';这里我要泼一盆冷水:config.allowedContent = true;虽然能保证最完整的格式保留,但同时也是安全审计里最刺眼的红灯。因为这个开关等于关闭了CKEditor 4的自动内容过滤,粘贴进来的<script>虽然还是会被浏览器的粘贴事件剥掉大部分,但一些通过DOM事件属性构造的恶意代码就可能留住了。我建议实在要保留复杂格式,可以用config.extraAllowedContent指定放行项,而不是无脑开allowedContent = true。这算是我踩过内网安全扫描之后总结的一条硬经验。
5. 常见问题与排查技巧
5.1 粘贴Word内容后样式丢失或错乱
我排查这类问题的顺序是:先看是"全丢"还是"部分丢"。全丢一般是PasteFromOffice插件没挂上,或者挂上了但被内容过滤规则压制了。此时打开浏览器控制台,把粘贴内容截取一段,看有没有Office特有的<o:p>、<?xml>残留标签。如果残留说明PasteFromOffice根本没有处理你的剪贴板数据,可能原因是剪贴板数据不是Word的标准格式,也可能是插件版本与核心不匹配。部分丢则通常是过滤规则把某种样式或标签干掉了,这时用开发者工具查看编辑器的模型数据,对照htmlSupport白名单逐项排查。
还有一个很隐蔽的坑:从Word粘贴过来后,字体颜色显示正常,但背景色全没了。这是Word里很多文字的高亮颜色其实存在"主题颜色"里,不是普通的background-color,PasteFromOffice并不会把主题颜色全部映射为CSS色值。这种情况我一般是反其道而行之,在粘贴预处理阶段将主题色值做一次映射表转换,把Word的标准主题色转成固定十六进制色值。
5.2 WPS粘贴后的兼容问题
WPS作为国内办公软件的常见选择,它的剪贴板HTML生成规则跟Word有差异。典型症状是粘贴后出现大量空的<span>标签、字体变成wps:name这种自定义命名空间、行距异常。我遇到最崩溃的一次,是用户从WPS粘了一个带着"仿宋_GB2312"字体的文档进来,编辑器显示完全正常,但导出PDF时那些空span导致了莫名其妙的换行。
处理思路是:在inputTransformation里对WPS特有标签做主动清理。我写过一个简单的正则和节点遍历逻辑,把所有<span>里没有任何有效样式且没有文本内容的空节点直接删除;对wps前缀的属性统一剥掉;对WPS生成的<p style="mso-pagination:...">这类无效样式也可以剥除。这个清理不会影响Word粘贴的内容,因为Word生成的HTML结构里这些噪声相对较少。
5.3 图片粘贴不显示或变成base64
如果粘贴截图后编辑器里有个图标占位但图片不显示,多半是上传适配器没有正确返回图片URL。CKEditor 5的图片上传流程是:粘贴图片后生成一个临时占位,调用自定义UploadAdapter的upload()方法,拿到Promise返回的{ url: 'xxx' }后才替换成真实图片。如果上传接口返回的不是标准JSON结构,编辑器就会一直停留在占位状态。
如果图片显示正常但保存到数据库后文档体积变得很大,那就是走了base64内嵌路线。在军工系统的数据库里,这种体积膨胀是不可接受的。我的建议是配置ImageUpload的同时,把现有的GeneralHtmlSupport白名单里对src="data:image/..."的限制一起加上,优先禁止在新文档里生成base64图片只允许上传后的URL地址。不过这也意味着离线环境必须要有一个稳定可靠的内网图片上传服务。
5.4 公式渲染异常
公式类问题分两种:编辑时渲染不了,和保存后展示异常。编辑时渲染不了,多半是MathJax或KaTeX的脚本资源没有加载,内网环境尤其容易漏配路径。检查网络请求看MathJax.js或katex.min.js是否返回200,没有就是路径问题。
保存后展示异常的情况,我排查重点在LaTeX源码的存储格式。有的方案会把\(...\)标记存进数据库,但展示页没有做同样的解析脚本,于是用户看到一串反斜杠和花括号;还有的方案直接把公式转成了base64图片,保存后能显示但清晰度极差,打印出来全是马赛克。我更推荐用LaTeX文本+渲染引擎方案,因为文本不会因为设备分辨率变化而模糊,而且能保持可检索性。
5.5 粘贴外部HTML带入危险脚本
这是安全审计必查的一项。我遇到过内网某个知识库被人贴了一段包含<img src=x onerror=alert(1)>的内容,前端过滤规则漏了大小写变体,结果所有打开该文档的用户浏览器都弹了警告。虽然只是一次内部演练,但这个问题的危害级别很高。
对付它不能只靠前端,我通常用后端清洗的时候会做几件事:一是解析HTML时把标签名统一转成小写再进行白名单匹配,防止<SCRIPT>这类大小写绕过;二是把所有事件属性(onclick、onerror、onload等)在正则层面直接剥除;三是对href、src、action这类URL属性做协议白名单,只允许http:、https:、mailto:和相对路径,其他如javascript:、data:全部拦截。做完这三步,配合前端的htmlSupport白名单,双端基本能堵住大多数XSS变体。
6. 经验与总结
6.1 插件不是越多越好
我在前面推荐了二十来个插件,但真实项目里你完全可以根据业务裁剪。插件越多,包体积越大,内网加载时间越长,更重要的是很多插件会引入额外的DOM结构和样式,反而干扰格式保真的最终效果。比如SpecialCharacters,如果单位不是经常要写特殊符号,完全可以去掉;HtmlEmbed这种高风险插件,默认就不该出现在普通用户的工具栏里。
我的原则是:先把业务真正需要的场景列出来,再按场景反向配插件。比如军工系统里的"公文编辑场景"和"技术文档场景"其实需要完全不同的插件组合。公文场景重在字体、字号、首行缩进、表格框线;技术文档场景重在代码块、公式、多级列表。日后再有变更,再按需求小批量增加插件并回归一次内容过滤配置。
6.2 版本升级前一定要做粘贴回归
我在项目里踩过的最痛的坑是:从CKEditor 4升CKEditor 5之后,存储的旧文档里大量内联样式失效。原因很简单,CKEditor 5对HTML结构的清洗策略更激进,旧的<font face="...">标签、废弃的align属性在读取时会被当成无效内容丢掉。当时文档库里几千篇历史文档一夜之间版式有点乱,解决起来非常麻烦。
所以如果你在一个有存量数据的内网系统里做升级,务必将"旧文档回显效果"列成验收标准。测试时不能只看新建一篇文档的效果,还要拿不同年代的存量文档逐篇看版式。如果存量数据里的格式大多依赖<font>标签和废弃属性,建议先在旧数据入库一刻做一次HTML规范化转换,把旧标签统一映射成CKEditor 5支持的CSS内联样式或类名,再进行升级。这个迁移过程虽然烦,但总比上线后天天被人骂要强。
最后再分享一个我常年保留的小技巧:无论用CKEditor 4还是CKEditor 5,都建议在编辑器下方预留一个"HTML源码"折叠区。内网用户里总有一些熟悉代码的老专家,他们贴复杂排版时,HTML源码视图比任何按钮都好用。SourceEditing插件在CKEditor 5里提供了这个入口,CKEditor 4里就是sourcearea。做好这个入口之后,至少格式保留的求助工单能少掉三分之一。别小看这个看似不起眼的开关,很多"编辑器丢了格式"的最终解法,就是让用户能亲手打开源码把那个该死的空标签删掉。