ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CKEditor 5 Mentions 功能完全指南:@ 触发自动补全、Feed 配置与转换定制的源码级解析

CKEditor 5 Mentions 功能完全指南:@ 触发自动补全、Feed 配置与转换定制的源码级解析 CKEditor 5 Mentions 功能完全指南 触发自动补全、Feed 配置与转换定制的源码级解析【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5Mention提及功能是 CKEditor 5 中基于用户输入的智能自动补全能力输入或#等预配置的标记字符后编辑器会弹出补全面板帮助用户快速标记协作者、标签或其他实体。本文以官方功能文档 mentions.md 为主体结合 ckeditor5-mention 包的源码实现完整覆盖安装、配置、Feed 提供方式、补全面板与输出 HTML 的定制方法并深入讲解自动补全的触发机制、模型层数据结构与底层转换链路帮助你在实际项目中聊天应用、协作评论、文档 人等场景落地并深度定制该功能。功能架构Mention 插件由什么组成从源码结构看Mention 功能由三个插件协作完成均位于 packages/ckeditor5-mention/src 目录插件文件职责Mentionmention.ts门面插件requires了MentionEditing与MentionUI并对外暴露toMentionAttribute()辅助方法MentionEditingmentionediting.ts注册模型mention文本属性、upcast/downcast 转换器、mention命令以及三个模型修正器post-fixerMentionUImentionui.ts基于TextWatcher监听输入、防抖请求 Feed、用ContextualBalloon展示补全面板理解这一分解很重要mention 在模型中并非独立元素而是一个文本属性text attribute其值是一个包含id、uid、_text等字段的对象见 mention.ts#L84-L105。默认情况下它被 downcast 渲染为span classmention>import { ClassicEditor, Mention } from ckeditor5; ClassicEditor .create( { licenseKey: YOUR_LICENSE_KEY, // Or GPL. plugins: [ Mention, /* ... */ ], mention: { // Configuration. } } ) .then( /* ... */ ) .catch( /* ... */ );Mention是ckeditor5包的内置导出见 packages/ckeditor5-mention/README.md 中的安装说明无需单独安装 npm 包。该功能还支持在聊天类场景中构建更高级的应用仓库中提供了 chat-with-mentions 示例其对应演示源码位于 docs/_snippets/examples/chat-with-mentions.js。基础配置marker、feed 与 minimumCharactersMention 的最小配置必须定义feed自动补全数据源和marker触发字符还可以定义minimumCharacters指定输入多少个字符后才显示补全面板。值得注意的是feed 条目的 ID 中允许包含空格。下面这段配置即官方 Demo 的配置定义了输入后编辑器自动补全的姓名列表ClassicEditor .create( { // ... Other configuration options ... mention: { feeds: [ { marker: , feed: [ Barney, Lily, Marry Ann, Marshall, Robin, Ted ], minimumCharacters: 1 } ] } } ) .then( /* ... */ ) .catch( /* ... */ );结合 mentionconfig.ts 中的 TypeScript 类型定义完整的配置项与默认值如下表配置项类型默认值说明mention.feedsArrayMentionFeed[]必须定义可包含多个 feed但各 feed 的marker必须互不相同例如给人、#给标签mention.commitKeysArraynumber[ 13, 9 ]Enter、Tab用于从下拉列表确认选中项的按键码可自定义任意多组mention.dropdownLimitnumber10下拉列表最多显示几条可设为Infinity显示全部feed.markerstring必填触发字符必须是单个字符feed.feedArrayMentionFeedItem \| function必填静态数组或回调函数函数以编辑器实例为this上下文执行feed.minimumCharactersnumber0标记字符后输入多少字符才弹出面板feed.itemRendererfunction无自定义面板中单条目的渲染见下文feed.dropdownLimitnumber继承mention.dropdownLimit按 feed 粒度覆盖列表长度上限其中commitKeys与dropdownLimit的默认值可以在源码中得到印证mentionui.ts#L59-L63 定义了defaultCommitKeyCodes [ keyCodes.enter, keyCodes.tab ]而 mentionui.ts#L245 中dropdownLimit的回退逻辑为「feed 级配置 →mention.dropdownLimit→10」注释说明保留 10 是为了向后兼容。此外Mention 还提供两项可选的深度定制能力详见下文对应章节补全面板中条目的渲染方式 —— 通过itemRenderer回调定制条目在转换conversion过程中生成的 HTML —— 通过覆盖默认 upcast/downcast 转换器定制。自动补全是怎么被触发的从源码看触发链路的核心在 mentionui.ts 的_setupTextWatcher()方法L357-L424正则匹配每个 feed 都会由createRegExp()L734-L756生成一个形如(?:^|[ 打开符号])(marker)(已输入文本*)$的正则。它要求 marker 必须出现在行首、空格或开括号类标点Unicode\p{Ps}\p{Pi}以及、之后且输入文本必须一直延续到光标处才视为有效——这解释了为什么在单词中间输入前面无空格或行首不会触发面板。minimumCharacters会转化为正则中的{n,}量词。模型标记匹配成功后插件会在模型中注册一个名为mention的 model marker精确覆盖「marker 已输入文本」的范围作为后续补全与插入的定位依据。防抖请求Feed 请求通过es-toolkit的debounce以 100ms 延迟发起mentionui.ts#L122避免每次击键都调用数据源。面板定位拿到结果后由ContextualBalloon在光标矩形caret周围按caret_se / caret_sw / caret_ne / caret_nw顺序寻找可放置位置L598-L671并保证面板水平方向不超出视口。面板打开后的键盘行为同样在源码中固定↑/↓ 移动选中项、Esc 关闭面板并移除 marker、commitKeys默认 Enter/Tab确认插入mentionui.ts#L139-L160点击面板外部也会关闭面板clickOutsideHandler。这些交互细节在手动测试用例 manual/mention.ts 与 manual/mention-custom-commitkeys.ts 中有对应的验证场景。提供 Feed静态数组与异步回调feed可以有两种提供方式静态数组—— 适合自动补全条目数量较少的场景回调函数—— 对返回列表拥有更细粒度的控制。使用回调时可以返回一个Promiseresolve 为匹配条目的数组。条目可以是简单字符串也可以至少包含name属性的普通对象对象的其余属性可以在后续「定制补全面板」和「定制输出」中使用。实践建议来自官方文档当 Feed 来自外部资源时建议加入缓存机制使相同建议的后续请求加载更快同时考虑设置minimumCharacters让编辑器在用户输入若干字符之后才调用 feed 回调而不是仅凭 marker 就发起请求。回调会接收到查询文本query text应使用它过滤候选项并返回一个Promiseresolve 为与查询文本匹配的条目数组。以下示例展示了异步回调的完整写法ClassicEditor .create( { // ... Other configuration options ... mention: { feeds: [ { marker: , feed: getFeedItems } ] } } ) .then( /* ... */ ) .catch( /* ... */ ); const items [ { id: swarley, userId: 1, name: Barney Stinson, link: https://www.imdb.com/title/tt0460649/characters/nm0000439 }, { id: lilypad, userId: 2, name: Lily Aldrin, link: https://www.imdb.com/title/tt0460649/characters/nm00004989 }, { id: marry, userId: 3, name: Marry Ann Lewis, link: https://www.imdb.com/title/tt0460649/characters/nm1130627 }, { id: marshmallow, userId: 4, name: Marshall Eriksen, link: https://www.imdb.com/title/tt0460649/characters/nm0781981 }, { id: rsparkles, userId: 5, name: Robin Scherbatsky, link: https://www.imdb.com/title/tt0460649/characters/nm1130627 }, { id: tdog, userId: 6, name: Ted Mosby, link: https://www.imdb.com/title/tt0460649/characters/nm1102140 } ]; function getFeedItems( queryText ) { // As an example of an asynchronous action, return a promise // that resolves after a 100ms timeout. // This can be a server request or any sort of delayed action. return new Promise( resolve { setTimeout( () { const itemsToDisplay items // Filter out the full list of all items to only those matching the query text. .filter( isItemMatching ) // Return 10 items max - needed for generic queries when the list may contain hundreds of elements. .slice( 0, 10 ); resolve( itemsToDisplay ); }, 100 ); } ); // Filtering function - it uses the name and username properties of an item to find a match. function isItemMatching( item ) { // Make the search case-insensitive. const searchString queryText.toLowerCase(); // Include an item in the search results if the name or username includes the current user input. return ( item.name.toLowerCase().includes( searchString ) || item.id.toLowerCase().includes( searchString ) ); } }这段示例与仓库内 docs/_snippets/features/mention-customization.js 的演示源码完全一致可直接作为服务端检索的参考模板。从源码实现看还有两个值得注意的行为静态数组的默认过滤是大小写不敏感的。当feed是数组时MentionUI内部用createFeedCallback()包装成回调按id的toLowerCase()做includes匹配mentionui.ts#L805-L818。乱序响应会被丢弃。异步 Feed 返回时插件会把该响应携带的feedText与最后一次请求_lastRequested比对不一致则触发requestFeed:discarded事件并丢弃结果mentionui.ts#L330-L340防止慢请求覆盖新查询的面板内容Feed 回调抛错时则触发mention-feed-callback-error警告并关闭面板。定制补全面板自定义条目渲染itemRenderer补全面板中的条目可以通过定义itemRenderer回调来定制。该回调接收一个 feed 条目对象至少包含name/id属性必须返回一个新的 DOM 元素ClassicEditor .create( { // ... Other configuration options ... mention: { feeds: [ { feed: [ /* ... */ ], // Define the custom item renderer. itemRenderer: customItemRenderer } ] } } ) .then( /* ... */ ) .catch( /* ... */ ); function customItemRenderer( item ) { const itemElement document.createElement( span ); itemElement.classList.add( custom-item ); itemElement.id mention-list-item-id-${ item.userId }; itemElement.textContent ${ item.name } ; const usernameElement document.createElement( span ); usernameElement.classList.add( custom-item-username ); usernameElement.textContent item.id; itemElement.appendChild( usernameElement ); return itemElement; }从 mentionui.ts#L518-L546 的_renderItem()可以看到其处理方式如果itemRenderer返回 DOM 元素会用MentionDomWrapperView包装该元素如果返回的是字符串则把字符串作为条目按钮的 label未配置itemRenderer时默认用item.id作为按钮文本。限制列表长度dropdownLimit通过dropdownLimit选项可以控制补全面板中展示的条目数量支持全局mention.dropdownLimit与按 feed 的feed.dropdownLimit两级设置ClassicEditor .create( { // ... Other configuration options ... mention: { // Define the custom number of visible mentions. dropdownLimit: 4, feeds: [ { /* ... */ } // More feeds. // ... ] } } ) .then( /* ... */ ) .catch( /* ... */ );对应的验证场景可参考手动测试 manual/mention-custom-dropdownlimit.ts 与自动测试 tests/mentionui.js。定制插入到编辑器的文本text 属性可以通过 mention 配置中条目对象的text属性控制创建 mention 时实际插入编辑器的文本ClassicEditor .create( { // ... Other configuration options ... mention: { feeds: [ // Feed items as objects. { marker: , feed: [ { id: Barney, fullName: Barney Stinson, // Custom text to be inserted into the editor text: Swarley }, // ... ] }, ] } } ) .then( /* ... */ ) .catch( /* ... */ );此时编辑器中显示的就是text指定的字符串而模型的 mention 属性仍保留id等元数据。从 mentioncommand.ts#L112-L183 的实现看text缺省为mention.id会作为带mention属性的文本写入模型并且命令自动处理了两个细节插入后如果后续文本不是空格会自动补一个空格保证提及与后续文字分隔如果提及被插入在一对匹配的空括号内支持()、[]、{}见 mentioncommand.ts#L16-L20 的BRACKET_PAIRS则不补空格得到(mention)而非(mention )。定制输出覆盖 upcast / downcast 转换器要更改编辑器为 mention 生成的 HTML可以覆盖 Mention 功能默认的转换器用ViewAttributeElement机制同时指定 upcast 与 downcast 转换器。例如把默认的span>a classmention>ClassicEditor .create( { // ... Other configuration options ... plugins: [ Mention, MentionCustomization, /* ... */ ], // Add the custom mention plugin function. mention: { // Configuration. // ... } } ) .then( /* ... */ ) .catch( /* ... */ ); function MentionCustomization( editor ) { // The upcast converter will convert view a classmention href>ClassicEditor .create( { attachTo: document.querySelector( #snippet-mention-customization ), // ... Other configuration options ... plugins: [ Mention, MentionCustomization, /* ... */ ], mention: { dropdownLimit: 4, feeds: [ { marker: , feed: getFeedItems, itemRenderer: customItemRenderer } ] } } ) .then( editor { window.editor editor; } ) .catch( err { console.error( err.stack ); } ); function MentionCustomization( editor ) { // The upcast converter will convert a classmention href>:root, :host { --ck-content-color-mention-background: hsla(341, 100%, 30%, 0.1); --ck-content-color-mention-text: hsl(341, 100%, 30%); } .ck-content .mention { background: var(--ck-content-color-mention-background); color: var(--ck-content-color-mention-text); }因此在你的全局样式中覆盖这两个变量即可改变提及的配色例如让背景变为蓝色、文字变为深灰色:root { /* Make the mention background blue. */ --ck-content-color-mention-background: hsla(220, 100%, 54%, 0.4); /* Make the mention text dark grey. */ --ck-content-color-mention-text: hsl(0, 0%, 15%); }补全面板自身的样式则由 theme/mentionui.css 提供随编辑器的主题体系一起定制。不支持的上下文代码块Mention 自动补全在代码块code block内会被自动禁用在代码块中输入等标记字符不会触发补全面板。同样构建在 mention 之上的功能如 slash commands、emoji 自动补全在代码块内也不生效。这一行为在源码中的实现很直接mentionediting.ts#L62-L69 通过model.schema.addAttributeCheck()声明「codeBlock $text上下文中不允许mention属性」从而使mention命令在代码块内处于禁用状态命令的refresh()依赖model.schema.checkAttributeInSelection()见 mentioncommand.ts#L72-L77补全面板在收到 Feed 响应时若发现命令被禁用也会主动关闭mentionui.ts#L440-L448。如果确实希望允许在代码块内使用 mention 自动补全可以通过监听Schema#checkAttribute事件覆盖该检查editor.model.schema.on( checkAttribute, ( evt, args ) { const context args[ 0 ]; const attributeName args[ 1 ]; if ( attributeName mention context.endsWith( codeBlock $text ) ) { evt.stop(); evt.return true; } }, { priority: high } );通用 API编程式插入 mentionMention插件注册了mention命令由 MentionCommand 实现。执行该命令即可在文档中插入一个 mention 元素editor.execute( mention, { marker: , mention: John } );命令的完整签名见 mentioncommand.ts#L92-L114支持更多参数mentionmention 对象传字符串时会被包装为{ id: 字符串 }marker标记字符例如。注意mention.id必须以 marker 开头否则抛出mentioncommand-incorrect-id错误text插入的文本缺省为marker mention字符串或对象形式的mention.idrange要替换的范围缺省为当前选区。若替换范围位于不可编辑区域命令直接跳过。结合源码中的用法示例可以精确替换选区前的一个字符即用户输入的 markerconst focus editor.model.document.selection.focus; // It will replace one character before the selection focus with the #1234 text // with the mention attribute filled with passed attributes. editor.execute( mention, { marker: #, mention: { id: #1234, name: Foo, title: Big Foo }, range: editor.model.createRange( focus.getShiftedBy( -1 ), focus ) } );开发调试时官方推荐使用 CKEditor 5 Inspector 查看编辑器内部数据、选区与命令状态。相关功能与延伸启用 Mention 之后以下功能可以与之形成互补自动文本转换Automatic text transformation—— 自动把(tm)转成™、foo转成“foo”等片段Autolink—— 把输入或粘贴的链接、邮箱自动转为可点击 URLAutoformatting—— 快速为正在书写的内容套用格式Emoji—— 快速插入 emojiEmoji 包位于 packages/ckeditor5-emoji与 Mention 一样基于自动补全机制Comments—— 可以配置 Mention 功能与 Comments 功能协同工作在评论编辑器中 相关成员具体配置方式可参考官方文档中 Annotations 自定义配置的章节。验证与参考路径汇总本文的关键结论均可在仓库中直接查证功能文档主体packages/ckeditor5-mention/docs/features/mentions.md配置类型定义packages/ckeditor5-mention/src/mentionconfig.tsMentionConfig/MentionFeed/MentionFeedItem及默认值注释模型属性、转换与命令mentionediting.ts、mentioncommand.ts、mention.ts自动补全触发与面板逻辑mentionui.ts自动测试tests/mention.jstoMentionAttribute、tests/mentionui.js含commitKeys自定义场景、tests/mentionediting.js转换与 post-fixer、tests/mentioncommand.js手动测试可对照的交互场景manual/mention.ts、manual/mention-asynchronous.ts、manual/mention-custom-renderer.ts、manual/mention-custom-view.ts、manual/mention-custom-commitkeys.ts主题样式theme/index-content.css、theme/mentionui.css。掌握以上内容后你就可以在 CKEditor 5 中从最简的静态列表起步逐步演进到「异步服务端 Feed 自定义面板渲染 a链接输出 CSS 变量主题」的完整企业级提及方案并清楚每一处行为背后的源码机制与可定制的扩展点。【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表