前端UI组件【免费下载链接】ckeditor4The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.项目地址https://gitcode.com/gh_mirrors/ck/ckeditor4点击查看免费下载本文围绕 CKEditor 4 在 4.14.0 版本引入的font与fontSize命令issue #3728展开结合 字体插件源码 与其对应的 手动测试用例、自动化测试深入剖析通过富文本下拉框移除字体/字号样式的底层调用链与边界行为。读完本文你将掌握字体命令的注册与执行流程、折叠选区下的元素拆分策略、以及如何借助测试模板验证命令是否被正确、恰当地执行。背景字体操作从直接改内容走向命令化在 CKEditor 4 4.14.0 之前Font字体族与 Font Size字号两个下拉框richcombo在选择样式时直接操作编辑器内容。从 4.14.0 起二者改为通过统一的命令机制执行——这一点在 CHANGES.md 的 API Changes 中明确记录Added newfontandfontSizecommands that apply the selected font style chosen by the Font plugin.命令化带来的直接收益包括操作可以被beforeCommandExec事件拦截、被撤销系统undo快照管理、与编辑器只读readOnly状态联动、以及通过activeFilter感知内容过滤器的启用/禁用状态。仓库中配套的三份手动测试文档 removestyle.md移除样式、applystyle.md应用样式、activefilter.md过滤器与只读联动正是对这一改造的逐项验证。命令化架构richcombo 与命令如何绑定在 plugins/font/plugin.js 的插件入口中font插件依赖richcombo并为编辑器注册了两个命令与两个富文本下拉框addCombo( editor, { comboName: Font, commandName: font, styleVariable: family, entries: config.font_names, defaultLabel: config.font_defaultLabel, styleDefinition: config.font_style, order: 30 } ); addCombo( editor, { comboName: FontSize, commandName: fontSize, styleVariable: size, entries: config.fontSize_sizes, defaultLabel: config.fontSize_defaultLabel, styleDefinition: config.fontSize_style, order: 40 } );每个下拉框通过editor.addCommand( definition.commandName, {...} )注册同名命令命令定义包含exec与refresh两个核心函数plugin.js#L107-L144exec真正执行样式应用/移除逻辑refresh根据defaultContentStyle.checkApplicable( path, editor, editor.activeFilter )判断命令是否可用不可用时将命令状态置为CKEDITOR.TRISTATE_DISABLED——这就是 activefilter.md 中表格单元格只允许段落时按钮禁用readOnly 模式下按钮禁用的依据。下拉框侧onClick: onClickHandler是用户点击选项的入口plugin.js#L245-L258function onClickHandler( newValue ) { var oldValue this.getValue(); editor.focus(); editor.fire( saveSnapshot ); // 撤销快照操作前 editor.execCommand( definition.commandName, { newStyle: stylesData.getStyle( newValue ), // 目标样式undefined 表示移除 oldStyle: stylesData.getStyle( oldValue ) // 当前生效样式 } ); editor.fire( saveSnapshot ); // 撤销快照操作后 }其中oldValue是下拉框当前显示值newValue是用户新选的项当用户选择列表首位的(Default)项时newValue cke-default此时stylesData.getStyle( newValue )返回undefined命令便进入移除样式分支。下拉框初始化时会把该项作为默认值加入plugin.js#L204-L213而测试辅助文件 tests/plugins/font/_helpers/tools.js 中的defaultValue: cke-default正是该默认项的常量。移除样式的核心执行逻辑命令的exec函数根据传入的newStyle与oldStyle决定行为plugin.js#L107-L137exec: function( editor, data ) { var newStyle data.newStyle, oldStyle data.oldStyle, range editor.getSelection().getRanges()[ 0 ], isRemove newStyle undefined; if ( !oldStyle !newStyle ) { return; // 无事可做既无旧样式也无新样式 } // 折叠选区且存在旧样式时直接 removeStyle 会删掉整个元素 // 这里改为先拆分元素只移除光标所在的部分。 if ( oldStyle range.collapsed ) { splitElementOnCollapsedRange( { editor, range, style: oldStyle } ); } if ( isRemove ) { editor.removeStyle( oldStyle ); // 纯移除 } else { if ( oldStyle !isEqualStyle( oldStyle, newStyle ) ) { editor.removeStyle( oldStyle ); // 先移除旧样式 } editor.applyStyle( newStyle ); // 再应用新样式 } }几个值得注意的设计要点空操作短路当oldStyle与newStyle均为空时直接返回不产生任何内容变更同值点击不做额外操作isEqualStyleplugin.js#L261-L281通过CKEDITOR.tools.objectCompare比较属性、通过CKEDITOR.style.getStyleText比较样式文本。若新旧样式相等则跳过先移除再应用避免无谓的 DOM 抖动折叠选区特殊处理range.collapsed为真且存在旧样式时先调用splitElementOnCollapsedRange把样式元素切开光标停在新位置随后再由removeStyle或applyStyle处理——这正是 collapsedandnestedspans.md 所验证的反复应用同一字体不产生嵌套span修复 #1306的前提。折叠选区下的元素拆分splitElementOnCollapsedRangesplitElementOnCollapsedRangeplugin.js#L287-L338是移除逻辑中最精细的部分它沿元素路径向上查找可被该样式移除的元素然后分三种情况处理光标同时位于元素起点与终点元素为空直接把元素替换为其子节点保留内部的空行内元素。源码注释中的例子pxspan stylefont-size:48pxem[]/em/spanx/p变为pxem[]/emx/p光标位于元素某一边界把光标移出元素边界并通过cloneSubtreeIntoRangeplugin.js#L347-L362把路径上的嵌套样式克隆到光标处保证外层样式不丢失光标位于元素内部调用range.splitElement( matching )拆分元素再移动光标并克隆子树。splitElement是 core/dom/range.js 提供的原生能力提取选区点到元素末尾的内容、复制元素、把内容放入副本并插入原元素之后。checkBoundaryOfElementcore/dom/range.js#L2374用于判断光标相对元素的位置配合CKEDITOR.START/CKEDITOR.END常量完成上述分支。手动测试用例详解移除样式场景回到本文的关联文档 tests/plugins/font/manual/command/removestyle.md。它属于 Bender 手动测试manual test文件头部的元信息定义了运行环境bender-tags: 4.14.0, feature, 3728 bender-ui: collapsed bender-ckeditor-plugins: wysiwygarea, toolbar, font, enterkey, elementspath, notification, sourcearea即该用例针对 4.14.0 的 #3728 功能加载wysiwygarea、toolbar、font、enterkey、elementspath、notification、sourcearea这些插件。用例要求对font 与 fontSize 两个功能分别重复执行。测试步骤选中已应用样式的文本从下拉框中移除其 font / fontSize 样式即选择(Default)项对未应用样式的文本尝试移除样式。预期行为Expected每次点击 richcombo 中的选项都应触发对应的命令执行font或fontSize对不存在的样式执行移除操作不应产生命令日志。非预期行为Unexpected命令未执行一次 richcombo 点击导致命令被执行多次命令携带了错误的样式例如移除的是 Arial 却执行成了 Courier New对不存在的样式执行移除时命令仍被执行。已知缺陷#1116文档末尾特别提醒Please notice there is a bug #1116, which doesnt remove font style if selection starts outside of styled element.即当选区起点位于样式元素之外时移除样式可能失败。这是该场景下测试者需要留意的既有缺陷属于文档明确声明的事实。如何看见命令执行模板中的验证手段手动测试的配套页面 tests/plugins/font/manual/command/template.html 给出了验证命令执行的完整方法——监听编辑器的beforeCommandExec事件var editor CKEDITOR.replace( editor, { height: 400, on: { beforeCommandExec: function( evt ) { if ( evt.data.name font || evt.data.name fontSize ) { updateStatus( evt.data.name, evt.data.commandData ); } } } } );updateStatus根据commandData.newStyle/commandData.oldStyle的有无在页面下方红色边框的消息列表中追加不同文案同时输出到浏览器控制台仅oldStylefont command is executed which removes style: font-family with value: ...仅newStyle...which applies style: ...两者都有同时描述应用新样式 移除旧样式两者皆空直接返回不产生日志——这正是移除不存在的样式不应生成命令日志这一预期背后的实现依据对应exec中的if ( !oldStyle !newStyle ) return;。消息列表只保留最近 5 条命令记录方便观察一次点击是否产生多次执行。测试文档 applystyle.md 与 removestyle.md 中的 General Note 所提到的命令执行时编辑器下方红色框与浏览器控制台会出现通知即源于此模板。自动化测试佐证命令级行为断言除手动测试外仓库还提供完整的 Bender 自动化测试 tests/plugins/font/fontcommand.js其中与移除样式直接相关的用例包括test should remove font when newStyle is empty对span stylefont-family:Comic Sans MS,cursive{foo}/span执行移除期望结果为pfoo/ptest should remove selected part when newStyle is empty光标位于fo{o}时移除期望保留fo、只移除选中的o的样式test should split nodes when newStyle is empty折叠光标位于f{}oo中时移除样式期望拆分为fZWSoo三段test should run remove font command only once over the same content对已应用样式的文本执行一次移除断言exec恰好被调用一次sinon.assert.calledOnce且最终内容为pHello world!/ptest should run remove font command for not styled content对未应用样式的文本点击默认项命令依然会执行一次因为oldStyle为空时由exec内部短路UI 层仍发起调用但不会改变内容。这些用例与 fontcommand.js 中通过sinon.spy( editor.getCommand( font ), exec )观察命令调用次数的方式直接对应手动测试中命令执行次数与命令携带正确样式的检查项。配套配置参数定制下拉项与样式定义font插件允许通过配置定制下拉框选项与底层样式定义均定义在 plugins/font/plugin.js 的文档注释与默认值中配置项默认值示例说明config.font_namesArial/Arial, Helvetica, sans-serif;...字体下拉项;分隔显示名/实际值格式可让显示与输出不同config.fontSize_sizes8/8px;9/9px;...;72/72px字号下拉项支持12px、2.3em、130%、larger等 CSS 尺寸config.font_style{ element: span, styles: { font-family: #(family) }, overrides: [...] }字体应用到的样式定义#(family)是变量占位符config.fontSize_style{ element: span, styles: { font-size: #(size) }, overrides: [...] }字号样式定义#(size)同理config.font_defaultLabel/config.fontSize_defaultLabel当前内容无匹配样式时下拉框显示的提示文本注意两个overrides都声明了对旧式font标签的清理{ element: font, attributes: { face: null } }与{ element: font, attributes: { size: null } }同时 plugin.js#L156-L197 中的contentTransformations负责把历史内容中的font size...、font face...转换为等价的内联样式如font-size: 20px对应size3确保新旧内容在命令化体系下表现一致。总结与排查建议当你在实际项目中遇到字体样式无法移除的问题时可以按如下思路排查确认版本font/fontSize命令自 4.14.0 起提供旧版本是直接操作内容的实现监听beforeCommandExec参考template.html 的做法打印evt.data.commandData确认命令是否执行、携带的newStyle/oldStyle是否正确检查选区状态range.collapsed会走splitElementOnCollapsedRange的拆分路径若选区起点在样式元素之外需留意已知缺陷 #1116 的影响核对配置确认font_names/fontSize_sizes中使用的样式值与font_style/fontSize_style的#(family)/#(size)占位符匹配避免isEqualStyle误判导致样式残留检查过滤器与只读状态refresh会依据activeFilter禁用命令readOnly 模式下按钮同样不可用见 activefilter.md。命令化改造让字体操作第一次拥有了可拦截、可撤销、可感知过滤器状态的统一入口而本文介绍的测试用例与模板正是验证这套机制在移除样式这一关键场景下行为正确性的完整工具集。赞分享前端UI组件【免费下载链接】ckeditor4The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.项目地址https://gitcode.com/gh_mirrors/ck/ckeditor4点击查看免费下载相关推荐XPipe 16.4 更新详解Docker Compose 集成、批量模式与终端多路复用器全面升级XPipe 16.4 更新详解Docker Compose 集成、批量模式与终端多路复用器全面升级 本文基于 XPipe从本地桌面访问整个服务器基础设施的开前端UI组件CKEditor 4 Font 插件手动测试解析如何在字体样式下保留 font 标签CKEditor 4 Font 插件手动测试解析如何在字体样式下保留 font 标签 本文以 CKEditor 4 仓库中的手动测试文档 tests/plu前端UI组件CKEditor 4 font 插件的字号/字体样式保持机制preservestyle 手动测试场景与源码级实现剖析CKEditor 4 font 插件的字号/字体样式保持机制preservestyle 手动测试场景与源码级实现剖析 本文围绕 CKEditor 4 仓库中前端UI组件上一篇Quasar 框架 QNoSsr 组件实战指南SSR 下精准控制服务端与客户端渲染内容下一篇Readest 全量 API 路由认证审计29 个 src/app/api 端点的调用方鉴权与上游凭据透传分析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考