- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
本篇技术指南以仓库内手动测试用例 tests/plugins/mentions/manual/diacritic.md 为核心脉络,剖析 CKEditor 4 Mentions 插件在匹配带变音符字符(如
@trąba_)时的工作原理、历史缺陷(Issue #2491)与修复方式。读完你将掌握:如何复现与验证该缺陷、默认查询正则的字符集边界、mentions全部配置项的语义,以及对应的自动化测试用例,从而能在自己的集成场景中正确配置 Mentions 并规避同类字符匹配问题。
一、从一份手动测试用例说起
仓库中tests/plugins/mentions/manual/diacritic.md是一份典型的 Bender 手动测试(manual test)用例,用于人工验证一个真实缺陷:Mentions 插件无法匹配带变音符(diacritic)的字符。其完整的用例元数据与操作步骤如下:
@bender-tags: 4.11.0, 2491, bug, mentions @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, basicstyles, mentions 1. Set Polish keyboard on your OS. 2. Focus the editor. 3. Type `@trąba_`. ## Expected Dropdown with `@trąba_bomba` entry shows up. ## Unexpected Nothing happens. 4. Play with the rest of the names available after typing `@` to check if diacritic characters matching.Bender 标签(@bender-tags)揭示了这份用例的定位:
4.11.0:缺陷在 CKEditor 4.11.0 版本中被修复;2491:对应 GitHub Issue #2491(详见下文 CHANGES.md 的变更记录);bug, mentions:这是 Mentions 插件的一个缺陷回归测试;@bender-ckeditor-plugins: wysiwygarea, toolbar, basicstyles, mentions:手动测试页面需要加载的编辑器插件集合,其中mentions依赖autocomplete、textmatch、ajax三个插件(见 plugins/mentions/plugin.js 中requires声明)。
复现要点在于:将操作系统键盘切换为波兰语(Polish keyboard)后,输入@触发 Mentions 提及,随后键入trąba_—— 其中ą(U+0105,a 下方带 ogonek 的变音符字母)是关键。预期行为是下拉框立即出现@trąba_bomba候选项;而缺陷未修复时,什么都不发生(Nothing happens)。
二、手动测试对应的页面配置
该手动测试配套的 HTML 页面 tests/plugins/mentions/manual/diacritic.html 给出了完整的编辑器初始化代码,它是理解测试数据源的直接入口:
<textarea id="editor"></textarea> <script> var editor = CKEDITOR.replace( 'editor', { mentions: [ { feed: [ 'bączek', 'ćwikła', 'trąba_bomba' ], minChars: 0 } ] } ); bender.tools.ignoreUnsupportedEnvironment( 'mentions', editor ); </script>这里展示了 Mentions 最简单的数据源形态——数组 feed(同步数据源):feed是一个字符串数组,包含三个带变音符的候选项bączek、ćwikła、trąba_bomba;minChars: 0表示触发提及的最小字符数为 0(即输入@后立即开始匹配)。
三、缺陷根因:#2491 与默认查询正则的字符集缺陷
3.1 修复前的默认正则
Mentions 插件在构造时,若用户未显式提供pattern配置,会调用内部函数createPattern( marker, minChars )生成默认查询正则(见 plugins/mentions/plugin.js):
function createPattern( marker, minChars ) { // Match also diacritic characters (#2491). var pattern = '\\' + marker + '[_a-zA-Z0-9À-ž]'; if ( minChars ) { pattern += '{' + minChars + ',}'; } else { pattern += '*'; } pattern += '$'; return new RegExp( pattern ); }修复后的默认正则等价于:
/@[_a-zA-Z0-9À-ž]{2,}$/ // minChars 为 2(默认值)时 /@[_a-zA-Z0-9À-ž]*$/ // minChars 为 0 时正则语义解读:
\\+marker:转义后的触发标记符,默认是@(marker默认为'@',见 plugins/mentions/plugin.js);[_a-zA-Z0-9À-ž]:匹配一个"提及字符",字符类由四部分构成:_:下划线,保证trąba_bomba这类含下划线的用户名可被整体匹配;a-zA-Z0-9:ASCII 字母与数字;À-ž:拉丁扩展字符区间(U+00C0 至 U+017E),覆盖 Latin-1 Supplement 与 Latin Extended-A 中的全部带变音符字母,如ą、ć、ę、ł、ń、ó、ś、ź、ż(波兰语)以及à、é、ü、ñ、ç、ş、ţ等欧洲语言字符;
{minChars,}/*:最小字符数量词。注意minChars默认值为 2(见 plugins/mentions/plugin.js),即触发匹配至少需要@后跟 2 个字符;设为0时退化为*;$:锚定到光标前文本的末尾,确保匹配的是"正在输入中的最后一个提及片段"。
3.2 修复前的问题
修复前,默认正则字符类只覆盖 ASCII 字母与数字(大致形如[_\w]或[a-zA-Z0-9_])。当用户键入@trąba_时,正则无法把ą识别为合法的提及字符,导致整个@trąba_片段无法被 textmatch 匹配,Mentions 下拉框自然不出现——这正是手动测试用例中标记为Unexpected的 "Nothing happens"。
该修复在 CKEditor 4.11.0 的变更记录中有明确记载(见 CHANGES.md):
#2491: Fixed: The Mentions plugin is not matching diacritic characters.
四、匹配链路:textmatch 与 caseSensitive 过滤
理解变音符匹配,还需要看清从"键入字符"到"下拉框出现"的完整调用链。Mentions 实例在构造时把三个回调交给 autocomplete 插件(见 plugins/mentions/plugin.js):
this._autocomplete = new CKEDITOR.plugins.autocomplete( editor, { textTestCallback: getTextTestCallback( this.marker, this.minChars, this.pattern ), dataCallback: getDataCallback( feed, this ), itemTemplate: config.itemTemplate, outputTemplate: config.outputTemplate, throttle: this.throttle, itemsLimit: config.itemsLimit, followingSpace: this.followingSpace } );链路分两段:
第一段——文本探测(textTestCallback)。getTextTestCallback返回的回调基于 plugins/textmatch/plugin.js 提供的CKEDITOR.plugins.textMatch.match服务,它把光标位置左侧的整段文本取出(并剔除浏览器填充字符序列FILLING_CHAR_SEQUENCE),再交给内部matchCallback用默认正则去匹配光标前紧邻的片段(见 plugins/mentions/plugin.js)。同时还有一个重要守卫:匹配片段的前一个字符若存在且不是空白符,则放弃匹配(避免匹配到单词中间的子串)。正则字符类是否包含ą,直接决定这一段的成败——这正是 #2491 的病灶所在。
第二段——数据过滤(dataCallback)。命中文本后,getDataCallback把查询串交给 feed 处理(见 plugins/mentions/plugin.js)。对于数组 feed,过滤逻辑是:
function createArrayFeed() { var data = indexArrayFeed( feed ).filter( function( item ) { var itemName = item.name; if ( !mentions.caseSensitive ) { itemName = itemName.toLowerCase(); query = query.toLowerCase(); } return itemName.indexOf( query ) === 0; } ); resolveCallbackData( data ); }即:数组 feed 默认不区分大小写(caseSensitive默认false),先将候选名与查询串都toLowerCase(),再判断候选名是否以查询串开头(indexOf( query ) === 0)。这就是为什么键入@trąba_时trąba_bomba能被命中——查询串trąba_是候选名trąba_bomba的前缀。若将caseSensitive设为true,则跳过小写归一化,直接做前缀比较(见 plugins/mentions/plugin.js)。
需要注意的是:大小写不敏感(toLowerCase)只对数组 feed 生效。若使用 URL 或函数 feed,结果预期已在服务端/回调中完成过滤,caseSensitive不参与处理(源码注释明确说明这一点)。
五、Mentions 配置项全解析
围绕上述匹配机制,Mentions 的全部配置项语义如下(定义见 plugins/mentions/plugin.js 中的configDefinition):
| 配置项 | 默认值 | 说明 |
|---|---|---|
feed | 无(必填) | 数据源,支持三种形式:字符串数组、后端 URL 字符串、函数 |
marker | '@' | 触发自动补全的标记字符 |
minChars | 2 | 标记符后触发 Mentions 所需的最小字符数 |
caseSensitive | false | 数组 feed 是否区分大小写(URL/函数 feed 无效) |
cache | true | 是否缓存 URL feed 的 HTTP 响应(全局共享缓存) |
throttle | 200 | 自动补全请求的节流毫秒数(autocomplete 插件默认20,Mentions 覆写为200) |
pattern | 内置正则 | 自定义查询匹配正则,可完全接管默认字符集 |
itemTemplate | 无 | 下拉列表中单个候选项的渲染模板 |
outputTemplate | 无 | 选中候选项后插入编辑器的 HTML 模板 |
itemsLimit | 0(不限) | 下拉框最多显示的候选项数量 |
followingSpace | 无 | 选中后是否在插入内容后追加一个不换行空格( ),4.20.0 引入 |
5.1 feed 的三种数据源形态
- 数组 feed(同步):最简单,插件自动为每一项生成
id(从 1 递增),并做大小写不敏感的前缀匹配:
var definition = { feed: [ 'Anna', 'Thomas', 'John' ], minChars: 0 };- URL feed(异步):每次键入匹配文本时向服务端发起 Ajax 请求,URL 中可用
{encodedQuery}占位符承载查询串。例如查询@anna、URL 为/users?name={encodedQuery}时,实际请求/users?name=anna。响应需为 JSON 数组。响应默认被全局缓存以避免重复请求:
var definition = { feed: '/users?query={encodedQuery}' };- 函数 feed(异步,最高自由度):函数接收
options(含query与marker)和callback两个参数,自行决定数据来源:
var definition = { feed: function( opts, callback ) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if ( xhr.readyState == 4 ) { if ( xhr.status == 200 ) { callback( JSON.parse( this.responseText ) ); } else { callback( [] ); } } } xhr.open( 'GET', '/users?name=' + opts.query ); xhr.send(); } };使用异步 feed 时,返回的候选项应为{ id, name, ... }结构(name用于展示与匹配,最终插入时会被自动加上marker前缀)。完整的多实例配置示例(来自 plugins/mentions/plugin.js):
config.mentions = [ { feed: [ 'Anna', 'Thomas', 'Jack' ], minChars: 0 }, { feed: backendApiFunction, marker: '#' }, { feed: '/users?query={encodedQuery}', marker: '$' } ];5.2 自定义 pattern:变音符的进阶控制
若默认的À-ž区间仍不满足需求(例如需要匹配拉丁扩展-B、希腊文、西里尔字母等更宽泛的字符),可通过pattern完全自定义匹配规则,例如只匹配以a开头的单词(源码注释中的示例):
var pattern = /^a+\w*$/;自定义pattern会取代createPattern的默认结果,因此必须自行包含marker、字符类与锚定逻辑(建议以$结尾并限定在光标前文本内匹配)。
六、自动化测试:#2491 的回归验证
除手动测试外,仓库还提供了与 #2491 一一对应的单元测试,位于 tests/plugins/mentions/mentions.js,共三个用例:
// (#2491) 'test matching diacritic characters': function() { var diacritics = 'áàâäãéèëêíìïîóòöôõúùüûñçăşţ'; this.editorBot.setHtmlWithSelection( '<p>@' + diacritics + '^</p>' ); testView( this.createMentionsInstance( { feed: [ diacritics ] } ), [ diacritics ] ); }, // (#2491) 'test matching underscore character': function() { var expected = 'john_doe'; this.editorBot.setHtmlWithSelection( '<p>@' + expected + '^</p>' ); testView( this.createMentionsInstance( { feed: [ expected ] } ), [ expected ] ); }, // (#2491) 'test not matching special characters': function() { ... var specialCharacters = '~!@#$%^&*()-+=`/.,\'"<>?|\\/[]{}'.split( '' ), mentions = this.createMentionsInstance( { feed: specialCharacters, throttle: 0, minChars: 0 } ); ... }三个用例从正反两面锁死字符集边界:
- 变音符字符必须匹配:
áàâäãéèëêíìïîóòöôõúùüûñçăşţ这一串拉丁变音符字母全部应被默认正则识别并触发下拉框; - 下划线必须匹配:
john_doe这类含_的用户名是 Mentions 的常见场景(对应手动用例中的@trąba_bomba); - 特殊字符不得匹配:
~!@#$%^&*()-+=/.,'"<>?|[]{}` 等标点与符号不应被当作提及字符,避免下拉框在无意义的输入下误触发。
测试通过this.editorBot.setHtmlWithSelection( '<p>@' + diacritics + '^</p>' )把光标(^)定位在@之后,再断言testView中下拉框应展示的候选项。测试文件头部声明了/* bender-ckeditor-plugins: mentions */,运行前会通过bender.tools.ignoreUnsupportedEnvironment跳过不支持的环境(见 tests/plugins/mentions/mentions.js)。
七、总结:变音符匹配的正确姿势
结合手动测试、源码与自动化测试,可以归纳出在 CKEditor 4 中正确使用带变音符的 Mentions 提及功能的三条要点:
- 默认即支持:从 4.11.0 起,Mentions 默认正则的字符类
[_a-zA-Z0-9À-ž]已覆盖绝大多数欧洲语言的变音符字母(ą ć ę ł ń ó ś ź ż、à é ü ñ ç ş ţ等),波兰键盘场景下的@trąba_可直接命中trąba_bomba; - 边界牢记:下划线被视为提及字符的一部分;而标点、空格与特殊符号会中断或阻止匹配;
minChars决定@后至少需要多少个字符才触发; - 数据源决定过滤语义:数组 feed 默认大小写不敏感的前缀匹配,
caseSensitive仅对数组 feed 生效;URL/函数 feed 的过滤由服务端或回调自行负责。
若需验证或回归该行为,可运行手动用例 tests/plugins/mentions/manual/diacritic.md(配合其页面 diacritic.html)或对应的自动化用例 tests/plugins/mentions/mentions.js。如需深入阅读实现,核心逻辑集中在 plugins/mentions/plugin.js,文本匹配机制见 plugins/textmatch/plugin.js,自动补全宿主框架见 plugins/autocomplete/plugin.js。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
CKEditor 4 Mentions 插件 `caseSensitive` 配置详解:从源码到手动测试用例的完整指南
CKEditor 4 Mentions 插件 caseSensitive 配置详解:从源码到手动测试用例的完整指南 CKEditor 4 的 Mentions
前端UI组件CKEditor 4 Auto Grow「失控高度」缺陷复盘:从 14413 手动回归测试到插件源码解析
CKEditor 4 Auto Grow「失控高度」缺陷复盘:从 14413 手动回归测试到插件源码解析 本文以 CKEditor 4 仓库中 tests/pl
前端UI组件CKEditor 4 Auto Link 插件源码模式兼容性修复解析:2756 缺陷、根因与 Bender 回归测试
CKEditor 4 Auto Link 插件源码模式兼容性修复解析: 2756 缺陷、根因与 Bender 回归测试 Auto Link 是 CKEditor
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考