☰
CKEditor 4 Autocomplete 组合使用 outputTemplate 与 followingSpace 的完整指南(含回归测试与源码解析)
2026/10/15 3:27:26 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载

导读

本文以 CKEditor 4 仓库中的手动测试用例 outputtemplatespace.md 为核心,深入讲解 Autocomplete 插件中outputTemplate(自定义插入内容的 HTML 模板)与followingSpace(确认匹配后自动补一个空格)两个配置项的组合使用方式,并还原该组合下的一个经典回归 Bug(#2008):在连续两次自动补全之间插入的文字被错误加粗或丢失斜体。读完本文,你将掌握这两个配置项的完整语义、底层commit()插入流程、如何运行 Bender 手动测试复现该场景,以及配套的自动化测试证据。

一、场景还原:连续两次自动补全之间的样式丢失

该测试用例位于仓库的 Bender 手动测试目录:

  • 测试说明:outputtemplatespace.md
  • 测试页面:outputtemplatespace.html

测试页面通过CKEDITOR.replace初始化一个 600px 宽的经典编辑器,并在instanceReady事件中创建一个 Autocomplete 实例,配置如下:

<div id="editor1" ></div> <script> // Ignore test due to IE issue (#2077). if ( CKEDITOR.env.ie && CKEDITOR.env.version < 11 ) { bender.ignore(); } CKEDITOR.replace( 'editor1', { width: 600, on: { instanceReady: function( evt ) { new CKEDITOR.plugins.autocomplete( evt.editor, { textTestCallback: autocompleteUtils.getTextTestCallback(), dataCallback: autocompleteUtils.getDataCallback(), outputTemplate: '<strong>{name}</strong>', followingSpace: true } ); } } } ); </script>

页面顶部还带有 Bender 元信息标签,声明了该用例的身份、所需插件与依赖:

@bender-tags: 4.20.0, bug, 2008 @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, basicstyles, autocomplete, textmatch @bender-include: _helpers/utils.js

解读这些标签:该测试针对 4.20.0 版本引入的followingSpace功能(bug 编号 #2008),需要wysiwygarea、toolbar、basicstyles、autocomplete、textmatch这些插件,并包含 tests/plugins/autocomplete/manual/_helpers/utils.js 提供的测试辅助工具(内置@john、@thomas、@anna、@cris、@julia五条候选数据)。

手动复现步骤

按测试说明依次操作:

  1. 聚焦编辑器(Focus the editor)。
  2. 点击斜体(italic)样式按钮,让后续输入的文字带上斜体样式。
  3. 输入@触发第一次自动补全。
  4. 接受(回车确认)第一条候选项。
  5. 输入and(注意两端各有一个空格)。
  6. 再次输入@触发第二次自动补全。
  7. 再次接受第一条候选项。

期望结果与异常现象

对比项内容
Expected result(期望结果)两次自动补全结果之间的文字(and)不应被加粗,但应保持斜体
Actual result(异常现象,Bug 修复前)中间的文字被错误加粗,或者丢失了斜体样式

也就是说,outputTemplate: '<strong>{name}</strong>'本应只让被补全的候选值(如@john)加粗,但在此 Bug 影响下,样式"越界"污染到了补全值之后的普通文本。

二、两个核心配置项:outputTemplate 与 followingSpace

outputTemplate:决定补全结果插入什么 HTML

在 plugins/autocomplete/plugin.js 中,outputTemplate被描述为"作为自动补全项被确认(commit)时插入到编辑器中的标记模板",支持通过候选数据项的属性做模板占位符插值,例如:

var outputTemplate = '<a href="/tracker/{ticket}">#{ticket} ({name})</a>';

其构造逻辑(plugin.js):

/** * Template of markup to be inserted as the autocomplete item gets committed. * You can use {@link CKEDITOR.plugins.autocomplete.model.item item} properties to customize the template. * @property {CKEDITOR.template} [outputTemplate=null] */ this.outputTemplate = config.outputTemplate !== undefined ? new CKEDITOR.template( config.outputTemplate ) : null;

关键点:

  • 未配置时默认值为null,此时插入的就是候选数据项的name纯文本。
  • 配置后会被包装成CKEDITOR.template实例,占位符{name}、{id}等会被替换为对应数据项属性值。
  • 模板输出前,数据项会经过encodeItem()做 HTML 编码(plugin.js),避免注入风险。

outputTemplate与候选列表里的itemTemplate是不同的:itemTemplate决定下拉面板中每条候选如何渲染,outputTemplate决定用户回车确认后,候选值以何种 HTML 形式写入编辑器内容。

followingSpace:确认匹配后自动补一个空格

followingSpace是 4.20.0 新增的配置项(Bug #2008),语义为"在已接受匹配项的后面追加一个空格",适合需要在补全后立即继续输入的场景。相关实现(plugin.js):

/** * Indicates if a following space should be added after inserted match into an editor. * @since 4.20.0 * @readonly * @property {Boolean} [followingSpace] */ this.followingSpace = config.followingSpace;

组合使用:commit() 的完整插入流程

两个配置项共同作用的代码路径在commit()方法中(plugin.js):

commit: function( itemId ) { if ( !this.model.isActive ) { return; } this.close(); if ( itemId == null ) { itemId = this.model.selectedItemId; // If non item is selected abort commit. if ( itemId == null ) { return; } } var item = this.model.getItemById( itemId ), editor = this.editor, html = this.getHtmlToInsert( item ); // Insert space after accepting match (#2008). html += this.followingSpace ? '&nbsp;' : ''; editor.fire( 'saveSnapshot' ); editor.getSelection().selectRanges( [ this.model.range ] ); editor.insertHtml( html, 'text' ); if ( this.followingSpace ) { removeLeadingSpace( editor ); } editor.fire( 'saveSnapshot' ); }

流程拆解:

  1. getHtmlToInsert( item )(plugin.js)决定基础 HTML:

    getHtmlToInsert: function( item ) { var encodedItem = encodeItem( item ); return this.outputTemplate ? this.outputTemplate.output( encodedItem ) : encodedItem.name; }

    即:有outputTemplate用模板渲染结果,没有则直接使用编码后的name。

  2. 若followingSpace为真,在 HTML 末尾追加&nbsp;(非断行空格,而不是普通空格,避免被浏览器折叠或触发意外的空白符处理)。

  3. editor.insertHtml( html, 'text' )将这段 HTML 插入到匹配文本所在的 range 中,替换掉原来的查询文本(例如@john被替换成<strong>@john</strong>&nbsp;)。

  4. 若followingSpace为真,再调用removeLeadingSpace( editor )(plugin.js)清理插入点之后紧邻的已有空格,防止"空格加倍":

    function removeLeadingSpace( editor ) { var selection = editor.getSelection(), nextNode = selection.getRanges()[ 0 ].getNextNode( function( node ) { return Boolean( node.type == CKEDITOR.NODE_TEXT && node.getText() ); } ); if ( nextNode && nextNode.getText().match( /^\s+/ ) ) { var range = editor.createRange(); range.setStart( nextNode, 0 ); range.setEnd( nextNode, 1 ); range.deleteContents(); } }
  5. 前后各触发一次saveSnapshot,保证撤销栈正确。

Bug 的根源与修复方向

从 commit 流程可以看出:&nbsp;被直接拼在outputTemplate渲染出的 HTML 字符串末尾,而editor.insertHtml在插入带内联样式(如斜体)的文本环境时,会把插入点处的现有样式传播到新插入的内容上。在连续两次补全、中间夹着and这类普通文本的场景里,第二个<strong>{name}</strong>的样式边界一旦处理不当,就会把中间的文本一起"加粗";反过来,样式传播机制也可能把中间文本应有的斜体样式"吃掉"。这正是该手动测试用"斜体按钮 + 两次补全"复现的回归点——它验证的是修复后outputTemplate与followingSpace组合时,&nbsp;及周边文本的样式边界必须精确、可控。

三、同一主题下的其他手动测试用例

该目录下还有一组与followingSpace直接相关的手动测试,相互印证:

1. 基础行为:followingspace

  • 测试说明:followingspace.md
  • 测试页面:followingspace.html

操作:光标置于编辑器内容末尾 → 输入@→ 回车。

期望结果:插入后自动追加一个空格,形如@john ^(^表示光标)。 异常结果:没有追加空格,形如@john^。

2. 已有空格时不重复:existingspace

  • 测试说明:existingspace.md

操作:光标置于两个单词之间(已有空格,如hello ^ world)→ 输入@→ 回车。

期望结果:不会重复添加空格,光标落在已有空格之后,形如hello @john ^world。 异常结果:空格被加倍(hello @john ^ world),或光标紧贴插入文本(hello @john^ world)。

该用例验证的正是removeLeadingSpace的去重逻辑:followingSpace追加的&nbsp;与紧随其后的已有空格会合并,只保留一个。

3. 仅 outputTemplate:outputtemplate

  • 测试说明:outputtemplate.md
  • 测试页面:outputtemplate.html(只配置了outputTemplate: '<strong>{name}</strong>',没有followingSpace)

操作:输入@→ 等待下拉出现 → 回车。

期望结果:插入的名字被加粗。 异常结果:插入的是纯文本。

这个用例单独验证了outputTemplate的基础渲染能力,是组合用例的"单变量对照版"。

四、自动化测试:从手动到断言

除了手动测试,仓库还提供了同名的自动化断言测试,位于 tests/plugins/autocomplete/autocomplete.js,与手动用例一一对应:

  • 'test following space is inserted after accepting match':配置followingSpace: true后回车,断言编辑器数据为<p>item1&nbsp;</p>(空格确实被插入)。
  • 'test following space is not doubled':在已有空格前补全,断言结果为<p>item1&nbsp;foo</p>,证明不会产生双空格。
  • 'test following space with output template':同时配置outputTemplate: '<strong>{name}</strong>'与followingSpace: true,断言结果为<p><strong>item1</strong>&nbsp;foo</p>。
  • 'test following space with output template is not doubled':在<p>^&nbsp;foo</p>上补全,同样断言<p><strong>item1</strong>&nbsp;foo</p>。

这四个测试组合在一起,用自动化断言锁死了本文核心场景的行为契约:

  1. 有outputTemplate时,加粗只作用于补全值本身(<strong>item1</strong>),&nbsp;在 strong 之外;
  2. 有followingSpace时,追加的空格不被重复。

注意测试中还有// Ignore test due to IE issue (#2077)的跳过逻辑:CKEDITOR.env.ie && CKEDITOR.env.version < 11(IE 10 及以下)时忽略,因为老 IE 在插入带样式 HTML 时的行为与标准浏览器不一致——这与手动测试页面顶部的bender.ignore()完全对应。

五、底层联动:textmatch 与 commit 键

理解该场景还需要知道补全动作如何被触发与确认:

  • 触发:textTestCallback借助 plugins/textmatch/plugin.js 的CKEDITOR.plugins.textMatch.match( range, callback )在 DOM 中定位光标左侧的匹配文本(本测试用例中为@\w*$正则,见 tests/plugins/autocomplete/manual/_helpers/utils.js 的getTextTestCallback)。
  • 确认:回车/制表键触发commit()。默认提交按键由全局配置autocomplete_commitKeystrokes控制,默认值为[ 9, 13 ](Tab 与 Enter,见 plugin.js),也可设为空数组来禁用。

commit()内部selectRanges( [ this.model.range ] )+insertHtml( html, 'text' )的组合,决定了插入内容会与周边文本的样式产生交互——这正是 outputtemplatespace 场景中样式边界问题的直接来源。

六、生态联动:mentions 与 emoji 中的 followingSpace

followingSpace并非 autocomplete 独有,它被集成进了基于 autocomplete 构建的两个官方插件:

  • Mentions 插件:plugins/mentions/plugin.js 在configDefinition中透传followingSpace,并在创建内部CKEDITOR.plugins.autocomplete实例时原样传给outputTemplate一起使用。

  • Emoji 插件:plugins/emoji/plugin.js 创建 autocomplete 实例时传入followingSpace: editor.config.emoji_followingSpace,对应全局配置CKEDITOR.config.emoji_followingSpace = false(默认关闭,见 plugins/emoji/plugin.js)。启用方式:

    config.emoji_followingSpace = true;

在 CHANGES.md 的 CKEditor 4.20 版本记录中,该特性被官方描述为:

  • 扩展了 Mentions 与 Emoji 插件,新增"在接受自动补全匹配后添加一个空格"的功能选项;
  • 为 Autocomplete 新增followingSpace选项,让已接受的匹配以空格收尾。

这解释了该配置的设计动机:@提及或:表情补全后,用户通常需要立刻继续输入文字,自动补空格能显著改善连续输入体验;而outputTemplate则负责让这些补全内容在视觉上更丰富(加粗、链接、图标等)。两者组合时的样式边界问题,正是 outputtemplatespace 用例存在的意义。

七、如何运行这些手动测试

这些manual/目录下的用例属于 Bender 手动测试体系,仓库根目录的 bender.js 是其运行配置(tests分组中Plugins组覆盖plugins/**路径)。手动测试与自动化测试不同,不会断言,而是由测试人员在浏览器中按步骤操作、人工对照 Expected/Actual 结果。

典型运行方式:

# 安装依赖后,启动 bender 服务器并运行手动测试 bender server run # 在浏览器中访问 bender 提供的 manual tests 列表, # 找到 Plugins → Autocomplete → outputtemplatespace 用例按步骤验证

自动化断言版则可直接随Plugins分组跑:tests/plugins/autocomplete/autocomplete.js中的四个followingSpace用例会按上文第四节的断言自动验证行为是否符合契约。

结语

outputTemplate与followingSpace是 CKEditor 4 Autocomplete 中两个紧密配合的配置项:前者决定补全结果以何种 HTML 落进正文,后者决定是否自动追加一个空格。而 outputtemplatespace.md 这一手动用例,精确捕捉了二者组合时的样式边界回归——中间的普通文本不该被&nbsp;的样式传播波及而加粗,也不该丢失已有的斜体。借助配套的 outputtemplate.md、followingspace.md、existingspace.md 手动用例以及 tests/plugins/autocomplete/autocomplete.js 的自动化断言,开发者可以在自己的集成中完整复现、验证并规避这一类样式边界问题。

  • 前端
  • UI组件

【免费下载链接】ckeditor4

The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载
上一篇:Flagsmith CLI 实战指南:从安装配置到 CI/CD 管道中的功能开关与特性评估
下一篇:从下载到畅玩:4步上手Whisky,让Mac流畅运行Windows软件

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询