☰
CKEditor 4 Mentions 插件变音符(Diacritic)字符匹配:从 2491 缺陷修复到源码与测试验证
2026/10/16 2:43:28 网站建设 项目流程
  • 前端
  • UI组件

【免费下载链接】ckeditor4

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

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

本篇技术指南以仓库内手动测试用例 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'@'触发自动补全的标记字符
minChars2标记符后触发 Mentions 所需的最小字符数
caseSensitivefalse数组 feed 是否区分大小写(URL/函数 feed 无效)
cachetrue是否缓存 URL feed 的 HTTP 响应(全局共享缓存)
throttle200自动补全请求的节流毫秒数(autocomplete 插件默认20,Mentions 覆写为200)
pattern内置正则自定义查询匹配正则,可完全接管默认字符集
itemTemplate无下拉列表中单个候选项的渲染模板
outputTemplate无选中候选项后插入编辑器的 HTML 模板
itemsLimit0(不限)下拉框最多显示的候选项数量
followingSpace无选中后是否在插入内容后追加一个不换行空格(&nbsp;),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 } ); ... }

三个用例从正反两面锁死字符集边界:

  1. 变音符字符必须匹配:áàâäãéèëêíìïîóòöôõúùüûñçăşţ这一串拉丁变音符字母全部应被默认正则识别并触发下拉框;
  2. 下划线必须匹配:john_doe这类含_的用户名是 Mentions 的常见场景(对应手动用例中的@trąba_bomba);
  3. 特殊字符不得匹配:~!@#$%^&*()-+=/.,'"<>?|[]{}` 等标点与符号不应被当作提及字符,避免下拉框在无意义的输入下误触发。

测试通过this.editorBot.setHtmlWithSelection( '<p>@' + diacritics + '^</p>' )把光标(^)定位在@之后,再断言testView中下拉框应展示的候选项。测试文件头部声明了/* bender-ckeditor-plugins: mentions */,运行前会通过bender.tools.ignoreUnsupportedEnvironment跳过不支持的环境(见 tests/plugins/mentions/mentions.js)。

七、总结:变音符匹配的正确姿势

结合手动测试、源码与自动化测试,可以归纳出在 CKEditor 4 中正确使用带变音符的 Mentions 提及功能的三条要点:

  1. 默认即支持:从 4.11.0 起,Mentions 默认正则的字符类[_a-zA-Z0-9À-ž]已覆盖绝大多数欧洲语言的变音符字母(ą ć ę ł ń ó ś ź ż、à é ü ñ ç ş ţ等),波兰键盘场景下的@trąba_可直接命中trąba_bomba;
  2. 边界牢记:下划线被视为提及字符的一部分;而标点、空格与特殊符号会中断或阻止匹配;minChars决定@后至少需要多少个字符才触发;
  3. 数据源决定过滤语义:数组 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.

项目地址:https://gitcode.com/gh_mirrors/ck/ckeditor4
点击查看免费下载
上一篇:cangjie_stdx log 包实战:从 getGlobalLogger 到自定义 Logger 与敏感字段过滤
下一篇:ImageJ完整指南:免费开源的科学图像处理与分析神器

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

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

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

立即咨询