- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
导读
本文围绕 CKEditor 4 官方仓库中的手动测试用例 tests/plugins/copyformatting/manual/tables.md 展开,系统讲解 Copy Formatting(格式刷)插件在表格(table)这一特殊上下文中的行为验证方法,并结合 plugins/copyformatting/plugin.js 源码、tests/plugins/copyformatting/下的自动化测试以及 styles/copyformatting.css 样式文件,深入剖析"单元格样式隔离、表头样式、混合选区、表格结构不被破坏"背后的实现原理。读完本文,你将能够独立运行这套手动测试,理解表格上下文样式复制/粘贴的底层机制,并学会通过copyFormatting_allowedContexts、copyFormatting_allowRules等配置精确控制插件行为。
一、测试用例定位:tables.md 在仓库中的角色
tables.md是 Bender 测试框架下的一个**手动测试(manual test)**用例,与同目录的tables.html配套使用。其文件头部的元数据声明了运行环境:
@bender-ui: collapsed @bender-tags: bug, copyformatting, 4.6.0 @bender-ckeditor-plugins: copyformatting, toolbar, wysiwygarea, floatingspace, elementspath, table, tabletools, undo@bender-ui: collapsed:手动测试页面以折叠的 UI 方式呈现,便于测试人员专注操作;@bender-tags: bug, copyformatting, 4.6.0:该用例自 Copy Formatting 功能引入的版本(4.6.0)起即被用于回归验证;@bender-ckeditor-plugins:声明了测试页面需要加载的插件集合,其中copyformatting、table、tabletools是表格场景的核心依赖,wysiwygarea、floatingspace、elementspath、toolbar负责编辑器框架与界面,undo用于验证撤销/重做集成。
整个tests/plugins/copyformatting/manual/目录还包含lists.md、keystroke.md、sticky.md、allowedcontexts.md、links.md、undointegration.md等二十余个用例,分别覆盖列表、快捷键、粘性模式、上下文限制、链接、撤销集成等维度;tables.md专门聚焦表格这一最容易出问题的结构。
二、测试准备:理解配套的 tables.html 页面
手动测试的"操作台"是 tests/plugins/copyformatting/manual/tables.html。页面创建了两个编辑器实例:
<script> CKEDITOR.replace( 'editor1', { allowedContent: true, extraPlugins: 'sourcearea' } ); CKEDITOR.inline( 'editor2', { allowedContent: true, extraPlugins: 'dialog,sourcedialog' } ); </script>editor1是经典(classic)编辑器(CKEDITOR.replace,基于wysiwygareaiframe);editor2是行内(inline)编辑器(CKEDITOR.inline)。
两个编辑器配置了allowedContent: true(关闭 ACF 内容过滤限制),确保测试时样式不会被过滤器拦截。页面内的表格内容经过精心构造,覆盖了大量样式组合,是验证插件行为的"富样式样本":
- 带表头的复杂表格:包含
<thead>与<th scope="col">,表头单元格内嵌多层<span>,组合了字体族(Comic Sans MS)、字号(20px)、斜体、加粗、下划线、文字颜色(#DAA520)与背景色(#00FF00),用于验证"表头样式是否被正确复制/隔离"; - 富样式数据单元格:包含水平对齐(center)、垂直对齐(top)、word wrap(nowrap)、背景色(orange)、边框色(plum)等,用于验证"单元格级 CSS 属性是否只作用到单元格";
- 文字级样式样本:背景色 + 文字颜色 + 字体(Times New Roman)+ 字号(26px)嵌套组合,用于验证"文本内联样式与单元格样式是否被区分对待";
- 表格级属性:
align="center"、border="5"、width="90%"、border-collapse: collapse、dir="ltr",以及另一张table-layout: fixed; width: 500px的固定宽度表格,用于验证"表格结构属性是否被误改"。
三、核心操作步骤(继承原文档 Procedure)
原文档给出的操作步骤非常简洁,但作为手动测试,其"玩转"(Play with tables)背后隐含着一套标准操作流。结合copyformatting插件的交互模型(工具栏按钮 + 鼠标点击 + 快捷键),完整步骤如下:
- 在
editor1(经典编辑器)中,将光标放入任意带样式的文本/单元格; - 点击工具栏中的Copy Formatting按钮(或按
Ctrl + Shift + C)复制格式,此时按钮进入"按下(On)"状态,光标变为插件专用光标; - 移动光标到目标位置(可以是另一个单元格、表头、表格外的段落),点击鼠标左键应用格式;也可以先框选一段文本再应用;
- 如需连续粘贴,双击Copy Formatting 按钮进入 sticky(粘性)模式,可反复点击多处;再次点击按钮或按
Esc退出; - 在
editor2(行内编辑器)中重复以上操作,验证两种编辑器形态下行为一致; - 结合
elementspath(元素路径)面板观察当前光标所处的 DOM 层级,便于判断样式实际作用到了哪个元素。
四、Things to check:四个检查点逐条解读
原文档列出的四个检查点,正是表格场景最容易出 Bug 的四个维度,每一条背后都有明确的源码逻辑支撑。
检查点 1:混合上下文(选区跨段落与表格)下样式正确应用
Styles are applied correctly even in the mixed context (e.g. selection begins in a paragraph and ends in a table).
当选区起点在普通段落、终点落在表格内部时,插件需要把样式同时应用到两处。插件先通过_determineContext( range )判断选区所属上下文(见 plugin.js):
_determineContext: function( range ) { function detect( query ) { /* 用 walker 遍历 range 内节点,检测祖先 */ } if ( detect( { ul: 1, ol: 1 } ) ) { return 'list'; } else if ( detect( 'table' ) ) { return 'table'; } else { return 'text'; } }随后在applyFormatting事件监听器中按上下文分发(plugin.js):
editor.copyFormatting.on( 'applyFormatting', function( evt ) { var context = plugin._determineContext( evt.data.range ); if ( context === 'list' && editor.copyFormatting._isContextAllowed( 'list' ) ) { plugin._applyStylesToListContext( evt.editor, evt.data.range, evt.data.styles ); } else if ( context === 'table' && editor.copyFormatting._isContextAllowed( 'table' ) ) { plugin._applyStylesToTableContext( evt.editor, evt.data.range, evt.data.styles ); } else if ( editor.copyFormatting._isContextAllowed( 'text' ) ) { plugin._applyStylesToTextContext( evt.editor, evt.data.range, evt.data.styles ); } }, null, null, 999 );注意该监听器的优先级为 999,晚于默认优先级(10)的"先清除旧样式"监听器执行。_applyStylesToTableContext内部对不属于表格专属元素的样式(如b、span)会回退调用_applyStylesToTextContext,从而保证混合选区中段落部分也能获得样式。
自动化测试 tests/plugins/copyformatting/applyformat.js 的test applyFormat on mixed context with table styles用例使用选区<p>Apply for{mat</p><table><tr><td>Maybe h}ere</td></tr></table>精确验证了这一行为:断言表格单元格(td)内应用了 4 个样式、段落(p)内应用了 1 个样式,且td上text-decoration: underline生效。
检查点 2:单元格与表头样式只作用于单元格/表头
Styles from table cells (background color) and headings (background color and color) are applied only to table cells or headings. Mind that bold font in headings is a part of default browser's styles and won't be copied to the table cells.
这一条揭示了两个层面的行为:
(a)样式提取的"元素定位"。复制格式时,copyFormatting命令调用_extractStylesFromElement(plugin.js)从光标所在元素向上逐级提取样式,但遇到breakOnElements中的元素就停止向上:
breakOnElements: [ 'ul', 'ol', 'table' ],也就是说,从单元格内提取样式时会一直走到<table>边界为止,不会把表格外的段落/文档级样式也一并抓进来。提取时通过_convertElementToStyleDef把元素转换为CKEDITOR.STYLE_INLINE类型的样式定义,并排除excludedAttributes中的无关属性:
excludedAttributes: [ 'id', 'style', 'href', 'data-cke-saved-href', 'dir' ],(b)样式应用的"目标映射"。应用时,_applyStylesToTableContext(plugin.js)按来源元素的类型决定目标:
if ( indexOf( [ 'table', 'tr' ], style.element ) !== -1 ) { getNodeAndApplyCmd( range, style.element, function( currentNode ) { style.applyToObject( currentNode ); } ); } else if ( indexOf( [ 'td', 'th' ], style.element ) !== -1 ) { getNodeAndApplyCmd( range, { td: 1, th: 1 }, function( currentNode ) { applyToTableCell( currentNode, style ); } ); } else if ( indexOf( [ 'thead', 'tbody' ], style.element ) !== -1 ) { // ... 同样落到单元格 } else { CKEDITOR.plugins.copyformatting._applyStylesToTextContext( editor, range, [ style ] ); }其中applyToTableCell有一个关键细节:如果目标单元格标签与样式来源标签不同(如td样式应用到th),会重建样式定义并把 element 改为目标单元格的标签再应用,这正是"表头背景色/文字色只作用于表头"的实现基础。自动化测试test applyFormat on table context with table styles (within thead)(applyformat.js)验证了 thead 内th上样式的正确应用。
关于"表头加粗"的提示:<th>的font-weight: bold属于浏览器默认样式,并非显式的内联样式,因此 Copy Formatting 复制的是内联样式属性,不会把浏览器默认加粗"复制"到普通单元格——这正是检查点要求测试人员留意的语义边界。
检查点 3:只有内联样式被应用到文本
Only inline styles are applied to the text.
Copy Formatting 提取的所有样式定义统一为type: CKEDITOR.STYLE_INLINE(见_convertElementToStyleDef的注释与实现)。应用时,属于块级边界的元素会被转成内联<span>再应用:
inlineBoundary: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div' ],_filterStyles: function( styles ) { // 把 h1..h6/p/div 等块级元素名改为 'span' if ( CKEDITOR.tools.indexOf( CKEDITOR.plugins.copyformatting.inlineBoundary, styleDef.element ) !== -1 ) { styleDef.element = styles[ i ].element = 'span'; } // 过滤掉空的 span if ( styleDef.element === 'span' && isEmpty( styleDef.attributes ) && isEmpty( styleDef.styles ) ) { continue; } }这也解释了 tables.html 样本中为何把"背景色 + 文字色 + 字体 + 字号"全部用<span>嵌套表达——它们是标准的内联样式载体,复制/应用后以span style="..."的形式落在目标文本上,而不会改变表格的块级结构。
检查点 4:应用样式后表格不被拆成两半
The table is not splitted into two after applying styles.
这是表格场景最经典的回归点。源码中有两处机制共同保障:
- 应用阶段使用书签(bookmark)保护选区:
_applyStylesToTableContext中对每个样式应用前后都执行bkm = range.createBookmark(); ... range.moveToBookmark( bkm );,并在注释中明确说明"防止表格的怪异行为(例如样式应用到所有单元格而不是仅选中的单元格)"。_applyStylesToListContext同样如此。 - 清除旧样式阶段保留表格元素本身:
preservedElements中包含了全部表格相关元素:
preservedElements: [ 'ul', 'ol', 'li', 'td', 'th', 'tr', 'thead', 'tbody', 'table' ],在"先清除旧样式"的监听器(plugin.js)中,如果旧样式来源元素在preservedElements之列,则调用_removeStylesFromElementInRange只剥离属性、不删除元素(如<td style="...">变成<td>),从而保证表格结构完整;对于表格等元素还启用了stopOnFirst只处理最外层,避免连锁破坏。
五、配套自动化测试:把检查点固化为断言
手动测试之外,仓库还为表格场景提供了多组自动化测试,可作为 CI 回归防线:
- tests/plugins/copyformatting/applyformat.js:覆盖"表格上下文应用表格样式""thead 内 th 样式应用""段落与表格混合选区"等场景,直接调用
CKEDITOR.plugins.copyformatting._applyFormat并断言最终 DOM 状态; - tests/plugins/copyformatting/applyformatcontext.js:验证
copyFormatting_allowedContexts配置对text/list/table三种上下文的开关行为。例如配置[ 'text' ]时,表格内应用样式的结果为空([]),而配置[ 'text', 'table' ]时表格上下文正常生效,并且测试还覆盖了传入非法值(如100)时的安全兜底; - tests/plugins/copyformatting/undointegration.js 与 tests/plugins/copyformatting/manual/undointegration.md:验证频繁移动选区后 Undo/Redo 按钮状态不被破坏,保证格式刷操作可被撤销;
- tests/plugins/copyformatting/_helpers/tools.js:提供
testApplyingFormat、fixHtml等公共断言辅助函数,其中fixHtml还处理了 Safari/IE8 选区符号差异。
测试通过 Bender 框架运行(配置见 bender.js 与 bender-runner.config.json,其中定义了 Bender 服务端口 9001、runner 端口 9002 及各平台浏览器列表)。手动测试用例则由manualBrowsers机制承载,测试人员启动 Bender 服务后即可在浏览器中逐条执行。
六、可配置项:按需裁剪表格场景行为
tables.md场景中表格上下文默认完全可用,是因为插件默认配置copyFormatting_allowedContexts = true(见 plugin.js)。如需定制,可在编辑器配置中覆盖以下选项:
| 配置项 | 默认值 | 作用 | 示例 |
|---|---|---|---|
copyFormatting_allowedContexts | true | 允许的上下文,可选'text'、'list'、'table',设为数组即只允许列出的上下文 | config.copyFormatting_allowedContexts = [ 'text', 'table' ]; |
copyFormatting_allowRules | 'b s u i em strong span p div td th ol ul li(*)[*]{*}' | 允许提取样式的元素规则(ACF 语法),设为true关闭过滤 | config.copyFormatting_allowRules = 'span(*)[*]{*}'; |
copyFormatting_disallowRules | '*data-cke-widget*,data-widget*,data-cke-realelement' | 明确禁止提取样式的元素(如 widget) | config.copyFormatting_disallowRules = 'span(important)'; |
copyFormatting_keystrokeCopy | Ctrl + Shift + C | 复制格式快捷键,设为false关闭 | config.copyFormatting_keystrokeCopy = CKEDITOR.CTRL + CKEDITOR.SHIFT + 66; |
copyFormatting_keystrokePaste | Ctrl + Shift + V | 应用格式快捷键 | config.copyFormatting_keystrokePaste = false; |
copyFormatting_outerCursor | true | 激活格式刷时是否在整个页面显示"禁用"光标(提示当前悬停位置不可应用格式) | config.copyFormatting_outerCursor = false; |
其中copyFormatting_allowRules/copyFormatting_disallowRules通过State构造器构建的CKEDITOR.filter实例生效(plugin.js),并在extractFormatting事件中逐一校验(editor.copyFormatting.filter.check( new CKEDITOR.style( style ), true, true ),见 plugin.js)——不符合规则的样式不会进入复制结果。
七、运行与体验建议
要亲身体验这套表格测试,可按以下方式在仓库内运行:
- 安装依赖:
npm install(仓库 package.json 的 devDependencies 中包含benderjs系列工具); - 启动 Bender 服务(端口配置见 bender-runner.config.json);
- 在浏览器中打开手动测试入口,定位到
tests/plugins/copyformatting/manual/tables.html页面; - 按照第三节的操作步骤在两个编辑器中反复复制/粘贴表格样式,逐条核对第四节四个检查点。
操作时注意观察两类细节:一是elementspath 面板,它实时显示光标所在元素链(如body > table > tbody > tr > td),帮助你确认样式落点;二是光标形态变化——激活格式刷后,styles/copyformatting.css 会为.cke_copyformatting_active应用专用光标cursor.svg,并用!important覆盖 Table Resize 插件的内联光标样式(cke_copyformatting_tableresize_cursor),这是插件在表格区域交互时特意处理过的细节。同时插件为无碍访问提供了屏幕阅读器通知(_putScreenReaderMessage),在复制/应用/取消时播报对应文案(中文文案见 plugins/copyformatting/lang/zh-cn.js,如"格式已复制""格式已应用")。
总结
tables.md看似只是一份简短的手动测试清单,但它浓缩了 Copy Formatting 插件在表格上下文中的全部关键行为契约:混合选区、单元格/表头样式隔离、纯内联样式、结构完整性。配合 plugins/copyformatting/plugin.js 中_determineContext、_applyStylesToTableContext、breakOnElements、preservedElements等实现细节与 applyformat.js、applyformatcontext.js 的自动化断言,开发者既能用手动流程复现问题,也能从源码层面理解每个检查点"为什么能通过",从而在集成 Copy Formatting 时准确预测其在表格场景下的行为,并借助copyFormatting_allowedContexts等配置按需裁剪功能范围。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
TIL 实战:在 Go 中用 database/sql 与 go-sqlite3 连接 SQLite 数据库
TIL 实战:在 Go 中用 database/sql 与 go sqlite3 连接 SQLite 数据库 本篇技术指南以 TIL 仓库中的 connect
前端UI组件旧 Mac 真的能装新系统吗?一篇讲完 OCLP 安装与回滚的免费避坑指南
旧 Mac 真的能装新系统吗?一篇讲完 OCLP 安装与回滚的免费避坑指南 系统更新弹窗告诉你"此 Mac 机型不再受支持"的那一刻,你多半会不甘心——机器明明
前端UI组件CKEditor 4 Copy Formatting 插件 sticky(粘性)模式:手动测试规程与源码实现解析
CKEditor 4 Copy Formatting 插件 sticky(粘性)模式:手动测试规程与源码实现解析 导读 本文围绕 CKEditor 4 仓库中的
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考