ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

CKEditor 5 Word Count 字数统计插件:安装、配置与源码级原理解读

CKEditor 5 Word Count 字数统计插件:安装、配置与源码级原理解读 CKEditor 5 Word Count 字数统计插件安装、配置与源码级原理解读【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5导读本文聚焦 CKEditor 5 官方的 Word Count字数与字符数统计功能。该功能由packages/ckeditor5-word-count包实现可实时统计编辑器中的单词数与字符数适用于写作进度跟踪、内容长度校验、微博/评论类输入框的字符上限提示等场景。阅读本文后你将掌握该插件的安装方式、全部配置项container、displayWords、displayCharacters、onUpdate、公开 APIwordCountContainer、update事件、words/characters属性并能基于源码理解其统计口径与节流机制写出可直接上线的字数统计与字符上限校验代码。功能概览Word Count 是 CKEditor 5 官方出品的一个独立插件包用于在编辑过程中实时统计编辑器内容中的单词数量和字符数量。它的典型价值在于帮助写作者控制内容篇幅、跟踪写作进度在表单场景中校验内容长度例如社交平台发帖的 120 字符软上限无需人工数数即可随时拿到精确的统计数据用于业务逻辑如草稿字数奖励、按字数计费等。该插件属于开源聚合包ckeditor5的一部分同时被标记为官方插件isOfficialPlugin与高级插件isPremiumPlugin其许可校验特征码为WC见 packages/ckeditor5-word-count/src/wordcount.ts 与 packages/ckeditor5-word-count/tests/wordcount.js 中的测试断言。快速上手在页面中显示计数器官方演示的页面结构非常简单一个编辑器容器加上一个用于承接统计信息的div即可div ideditor pHello world./p /div div idword-count/div然后通过编辑器实例拿到WordCount插件并将其自更新的统计容器wordCountContainer挂载到页面上ClassicEditor .create( { // Configuration details. } ) .then( editor { const wordCountPlugin editor.plugins.get( WordCount ); const wordCountWrapper document.getElementById( word-count ); wordCountWrapper.appendChild( wordCountPlugin.wordCountContainer ); } );wordCountContainer是一个**自更新self-updating**的 HTML 元素只要编辑器内容发生变化它内部显示的Words与Characters数值就会自动刷新无需手动同步。你可以将这段逻辑与下方安装小节组合成一个可运行的页面。安装在基于 npm 的项目中本功能随开源聚合包一起分发直接安装即可npm install ckeditor5安装完成后将WordCount加入插件列表并通过wordCount键进行配置import { ClassicEditor, WordCount } from ckeditor5; ClassicEditor .create( { licenseKey: YOUR_LICENSE_KEY, // Or GPL. plugins: [ WordCount, /* ... */ ], wordCount: { // Configuration. } } ) .then( /* ... */ ) .catch( /* ... */ );关于授权WordCount是高级功能生产环境需要有效的许可证在本地开发与评估阶段可填写GPL使用 GPL 模式。类型层面插件通过 packages/ckeditor5-word-count/src/augmentation.ts 对EditorConfig进行了模块扩展因此wordCount配置项与editor.plugins.get( WordCount )的返回值都具备完整的 TypeScript 类型提示。配置详解插件提供了四个配置项全部以config.wordCount为前缀其类型定义见 packages/ckeditor5-word-count/src/wordcountconfig.ts。1. container指定统计容器的挂载位置将计数器注入页面有两种方式编程式挂载通过WordCount#wordCountContainer属性拿到元素后自行appendChild见上文快速上手声明式配置通过config.wordCount.container直接传入一个目标 DOM 元素插件初始化时会自动把计数器容器追加进去ClassicEditor .create( { plugins: [ WordCount, /* ... */ ], wordCount: { container: document.getElementById( container-for-word-count ) } } );对应的实现逻辑位于 packages/ckeditor5-word-count/src/wordcount.tsinit()阶段如果检测到container是一个真实元素isElement( this._config.container )就调用appendChild( this.wordCountContainer )完成注入。测试用例 packages/ckeditor5-word-count/tests/wordcount.js 验证了传入container后目标元素会获得一个子节点且该子节点正是wordCountContainer返回的元素。无论采用哪种方式插件渲染出的 DOM 结构固定如下div classck ck-word-count div classck-word-count__wordsWords: %%/div div classck-word-count__charactersCharacters: %%/div /div如果希望完全自定义渲染方式可以忽略容器改由update事件见下文驱动自己的 UI。2. displayWords / displayCharacters控制显示哪部分统计两个布尔配置项用于决定是否展示对应的统计行未配置时默认同时显示ClassicEditor .create( { plugins: [ WordCount, /* ... */ ], wordCount: { displayWords: false // 隐藏单词数 // displayCharacters: false // 隐藏字符数 } } );当displayWords: false时wordCountContainer只保留字符部分div classck ck-word-count div classck-word-count__charactersCharacters: 28/div /div当displayCharacters: false时只保留单词部分div classck ck-word-count div classck-word-count__wordsWords: 4/div /div从源码看packages/ckeditor5-word-count/src/wordcount.ts 在构建输出视图时逐项判断displayWords、displayCharactersundefined视为显示并按需创建ck-word-count__words/ck-word-count__characters子节点测试 packages/ckeditor5-word-count/tests/wordcount.js 分别断言了两种配置下的容器文本内容。3. onUpdate内容统计变化时执行回调如果要在每次统计值变化时执行自定义逻辑例如发送字数到后端、驱动进度条可通过onUpdate注册回调ClassicEditor .create( { // ... Other configuration options ... wordCount: { onUpdate: stats { // Prints the current content statistics. console.log( Characters: ${ stats.characters }\nWords: ${ stats.words } ); } } } ) .then( /* ... */ ) .catch( /* ... */ );回调收到一个形如{ words, characters }的对象。其实现位置在 packages/ckeditor5-word-count/src/wordcount.ts插件在init()中监听自身的update事件并转发给onUpdate配置。重要说明性能相关出于性能考虑统计刷新与onUpdate回调是**节流throttled**的因此回调拿到的数值可能不是最新时刻的值。如果你需要精确、即时的数字例如提交前的严格校验请直接读取插件的characters与words属性见下文Common API。实战案例带 120 字符上限的发帖编辑器官方提供的一个经典场景是编辑器下方附带环形进度图字符数接近上限时变橙色超过上限时编辑器背景变红并禁用发送按钮。完整 HTML/CSS 结构如下style .demo-update { border: 1px solid var(--ck-color-base-border); border-radius: var(--ck-border-radius); box-shadow: 2px 2px 0px hsla( 0, 0%, 0%, 0.1 ); margin: 1.5em 0; padding: 1em; } .demo-update h3 { font-size: 18px; font-weight: bold; margin: 0 0 .5em; padding: 0; } .demo-update .ck.ck-editor__editable_inline { border: 1px solid hsla( 0, 0%, 0%, 0.15 ); transition: background .5s ease-out; min-height: 6em; margin-bottom: 1em; } .demo-update__controls { display: flex; flex-direction: row; align-items: center; } .demo-update__chart { margin-right: 1em; } .demo-update__chart__circle { transform: rotate(-90deg); transform-origin: center; } .demo-update__chart__characters { font-size: 13px; font-weight: bold; } .demo-update__words { flex-grow: 1; opacity: .5; } .demo-update__limit-close .demo-update__chart__circle { stroke: hsl( 30, 100%, 52% ); } .demo-update__limit-exceeded .ck.ck-editor__editable_inline { background: hsl( 0, 100%, 97% ); } .demo-update__limit-exceeded .demo-update__chart__circle { stroke: hsl( 0, 100%, 52% ); } .demo-update__limit-exceeded .demo-update__chart__characters { fill: hsl( 0, 100%, 52% ); } /style div classdemo-update h3Post editor with word count/h3 div iddemo-update__editor pTourists frequently admit that a hrefhttps://en.wikipedia.org/wiki/Taj_MahalTaj Mahal/a “simply cannot be described with words”./p /div div classdemo-update__controls span classdemo-update__words/span svg classdemo-update__chart viewbox0 0 40 40 width40 height40 xmlnshttp://www.w3.org/2000/svg circle strokehsl(0, 0%, 93%) stroke-width3 fillnone cx20 cy20 r17 / circle classdemo-update__chart__circle strokehsl(202, 92%, 59%) stroke-width3 stroke-dasharray134,534 stroke-linecapround fillnone cx20 cy20 r17 / text classdemo-update__chart__characters x50% y50% dominant-baselinecentral text-anchormiddle/text /svg button typebutton classdemo-update__sendSend post/button /div /div配合的编辑器初始化代码以 BalloonEditor 为例利用onUpdate回调完成全部 UI 联动const maxCharacters 120; const container document.querySelector( .demo-update ); const progressCircle document.querySelector( .demo-update__chart__circle ); const charactersBox document.querySelector( .demo-update__chart__characters ); const wordsBox document.querySelector( .demo-update__words ); const circleCircumference Math.floor( 2 * Math.PI * progressCircle.getAttribute( r ) ); const sendButton document.querySelector( .demo-update__send ); BalloonEditor .create( { root: { element: document.querySelector( #demo-update__editor ) }, // Editor configuration. wordCount: { onUpdate: stats { const charactersProgress stats.characters / maxCharacters * circleCircumference; const isLimitExceeded stats.characters maxCharacters; const isCloseToLimit !isLimitExceeded stats.characters maxCharacters * .8; const circleDashArray Math.min( charactersProgress, circleCircumference ); // Set the stroke of the circle to show how many characters were typed. progressCircle.setAttribute( stroke-dasharray, ${ circleDashArray },${ circleCircumference } ); // Display the number of characters in the progress chart. When the limit is exceeded, // display how many characters should be removed. if ( isLimitExceeded ) { charactersBox.textContent -${ stats.characters - maxCharacters }; } else { charactersBox.textContent stats.characters; } wordsBox.textContent Words in the post: ${ stats.words }; // If the content length is close to the character limit, add a CSS class to warn the user. container.classList.toggle( demo-update__limit-close, isCloseToLimit ); // If the character limit is exceeded, add a CSS class that makes the contents background red. container.classList.toggle( demo-update__limit-exceeded, isLimitExceeded ); // If the character limit is exceeded, disable the send button. sendButton.toggleAttribute( disabled, isLimitExceeded ); } } } );这段代码演示了onUpdate最典型的用法输入法联动 UI、渐进式警告、超限硬校验三合一。需要再次强调的是onUpdate是节流的这里展示的 UI 反馈适合接近/超过上限这类对实时性要求不苛刻的场景若要在提交按钮点击时做最终校验请读取editor.plugins.get( WordCount ).characters获取精确值。Common API插件对外提供的三件套WordCount插件对外暴露了三个核心能力见 packages/ckeditor5-word-count/src/wordcount.ts 与官方文档wordCountContainer属性返回一个自更新 HTML 元素内容随编辑器统计值自动刷新。可通过displayWords/displayCharacters配置隐藏其中任意一行。该容器只在首次访问时创建重复调用返回同一个元素实例测试 packages/ckeditor5-word-count/tests/wordcount.js 验证了这一行为。编辑器销毁时容器元素会被自动从 DOM 中移除见destroy()实现 packages/ckeditor5-word-count/src/wordcount.ts。update事件每当插件更新统计值时触发携带{ words, characters }参数可用于注册自定义回调editor.plugins.get( WordCount ).on( update, ( evt, stats ) { // Prints the current content statistics. console.log( Characters: ${ stats.characters }\nWords: ${ stats.words } ); } );它等价于config.wordCount.onUpdate的底层机制——事实上onUpdate正是通过监听该事件实现的packages/ckeditor5-word-count/src/wordcount.ts。事件同样受节流影响统计值可能不是最新的。characters与words属性可直接读取的精确统计数字不受节流影响。源码中这两个属性被定义为 getter每次访问都会基于当前模型内容即时重算packages/ckeditor5-word-count/src/wordcount.ts因此非常适合用于保存前校验等需要精确值的逻辑。测试 packages/ckeditor5-word-count/tests/wordcount.js 验证了设置模型数据后立即读取words即可拿到正确结果。此外二者都是可观察observable属性可用change:words/change:characters监听变化。源码深潜统计口径与底层原理理解统计口径有助于预判为什么我的字数跟别的工具数出来不一样。整个计数流程为模型Model→ 纯文本 → 正则分词/计字符。第一步模型转纯文本packages/ckeditor5-word-count/src/utils.ts 中的modelElementToPlainText()递归遍历编辑器模型的所有子节点$text/$textProxy节点直接返回其文本数据不含任何样式标记对于块级元素如段落、表格单元格、列表项、图片说明等每遇到一个子元素就在文本前插入一个\n换行符作为分隔。也就是说每个块段落/单元格/说明文字之间会被换行符隔开而内联样式加粗、下划线、链接等不会产生任何额外字符。例如Foo加粗Bar两个段落会被转换为Foo\nBar。测试文件 packages/ckeditor5-word-count/tests/utils.js 用引用块、表格、软换行softBreak、混合结构等场景验证了这一转换逻辑。插件在_getText()packages/ckeditor5-word-count/src/wordcount.ts中遍历文档的所有根节点root将各根节点的纯文本用\n连接——这也是多根编辑器MultiRootEditor统计的实现基础。第二步分词与计数字符拿到纯文本后字符数txt.replace( /\n/g, ).length—— 即去掉换行符后的字符长度换行/回车不计入字符数packages/ckeditor5-word-count/src/wordcount.ts。单词数通过正则匹配。在支持 Unicode 属性转义的环境中现代浏览器使用([\p{L}\p{N}]\S?)gu标志其中\p{L}匹配任意语言的字母、\p{N}匹配任意文字系统中的数字在不支持的环境下降级为([a-zA-Z0-9À-ž]\S?)packages/ckeditor5-word-count/src/wordcount.ts。官方在类注释中给出了几个直观的例子packages/ckeditor5-word-count/src/wordcount.tsparagraphfoo/paragraph paragraphbar/paragraph // Words: 2, Characters: 7两个段落含 1 个换行字符数不含换行 paragraph$text boldtruefoo/$textbar/paragraph // Words: 1, Characters: 6内联样式不产生额外字符 paragraph*^%)/paragraph // Words: 0, Characters: 5纯符号不算词 paragraphfoo(bar)/paragraph // Words: 1, Characters: 8 paragraph12345/paragraph // Words: 1, Characters: 5数字串算一个词第三步节流刷新插件在init()中监听模型文档的change:data事件即内容数据变更选区变化不触发并用 250ms 的throttle包裹统计刷新逻辑packages/ckeditor5-word-count/src/wordcount.ts。这意味着连续输入时统计不会每击键都重算而是以 250ms 为周期合并计算从而保证大文档下的性能。测试 packages/ckeditor5-word-count/tests/wordcount.js 精确验证了首次内容变更立即触发update随后 250ms 内的连续多次变更只触发一次update且携带的是最终值仅改变选区不改内容不会触发update。边界行为汇总均有测试佐证以下行为全部来自 packages/ckeditor5-word-count/tests/wordcount.js输入场景结果1 12 3,5 3/4 1.2 06 个词数字均算词j.doecksource.com1 个词邮箱算一个词Foobar、Foo.bar各 1 个词撇号、点不拆分单词(#$%^*()) . ??? --- ...0 个词纯符号不算词列表项编号/项目符号不参与计数仅计列表文字图片说明caption参与计数表格全部单元格参与计数段落结束、软换行ShiftEnter分隔两个词希伯来/中文/日文/阿拉伯文等多语种每个词各计 1Unicode 属性模式下编辑器销毁后容器元素自动从 DOM 移除此外插件对多根编辑器MultiRootEditor会累加所有根的字符与单词数packages/ckeditor5-word-count/tests/wordcount.jsWords: %0 与 Characters: %0 标签支持翻译测试中验证了波兰语翻译生效packages/ckeditor5-word-count/tests/wordcount.js语言由编辑器的language配置决定。相关功能与延伸阅读CKEditor 5 中与字数统计场景常搭配的功能还包括拼写与语法检查spelling-and-grammar-checking、自动保存autosave、Markdown 风格的自动格式化autoformat与自动文本转换text-transformation。例如写作倒计时 定时自动保存 字数超限提醒的组合即可覆盖绝大多数内容创作工具的需求。若想进一步阅读与验证仓库内可直接参考的资源有功能官方指南packages/ckeditor5-word-count/docs/features/word-count.md插件源码packages/ckeditor5-word-count/src/wordcount.ts配置类型定义packages/ckeditor5-word-count/src/wordcountconfig.ts模型转纯文本工具packages/ckeditor5-word-count/src/utils.ts单元测试packages/ckeditor5-word-count/tests/wordcount.js、packages/ckeditor5-word-count/tests/utils.js官方演示片段packages/ckeditor5-word-count/docs/_snippets/features/word-count.js开发与调试阶段建议配合官方 CKEditor 5 Inspector 使用它可以直观展示编辑器的内部数据模型、选区与命令状态帮助你理解统计值与所见内容之间的对应关系。【免费下载链接】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),仅供参考
返回列表