TinyMCE 8.5.0 版本全解析:content_language 语言属性新选项与多项健壮性修复
【免费下载链接】tinymceThe world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular项目地址: https://gitcode.com/gh_mirrors/ti/tinymce
本文基于开源仓库 tinymce 的版本变更日志 .changes/tinymce/8.5.0.md(发布日期 2026-04-29)展开。8.5.0 是 TinyMCE 的一个功能与稳定性双修版本:新增了用于控制编辑区
lang属性的content_language选项,改善了 Suggested Edits 与 TinyMCE AI 插件的内联 diff 高亮视觉,并修复了 DomPurify 误删元素、列表切换异常、非 Latin-1 字符 URI 报错、屏幕阅读器播报缺失等 6 类问题。阅读本文后,你将掌握content_language的配置方法与底层实现原理,并了解各修复项的成因、代码位置与验证方式,为升级或排查 8.5.0 相关问题提供直接依据。
一、版本概览
8.5.0 的变更日志结构清晰,分为Added(新增)、Improved(改进)与Fixed(修复)三部分,共涉及 1 项新功能、1 项视觉改进和 6 项问题修复(对应 TinyMCE JIRA 工单号 TINY-11214、TINY-13958、TINY-9655、TINY-14070、TINY-14149、TINY-13938、TINY-13812):
| 类别 | 工单 | 内容摘要 |
|---|---|---|
| Added | TINY-11214 | 新增content_language选项,设置 iframe 编辑器html元素或内联编辑器目标元素的lang属性 |
| Improved | TINY-13958 | 改进 Suggested Edits 与 TinyMCE AI 插件中内联 diff 高亮的视觉样式 |
| Fixed | TINY-9655 | DomPurify 误删 schema 中合法且有效的script、style元素 |
| Fixed | TINY-9655 | DomPurify 误删带子元素的iframe元素 |
| Fixed | TINY-14070 | 列表中特定div组合导致关闭列表(turn off lists)异常 |
| Fixed | TINY-14149 | 特定选择操作误删编辑器 body |
| Fixed | TINY-13938 | 含非 Latin-1 字符的 URI 返回错误 |
| Fixed | TINY-13812 | 部分屏幕阅读器未正确播报 alert 与 confirm 对话框 |
下文按此结构逐项展开,并结合仓库源码给出实现证据。
二、新增功能:content_language 选项(TINY-11214)
2.1 功能定位
content_language是本版本最重要的新功能:它允许开发者显式指定编辑内容的语言,TinyMCE 会据此在编辑区根元素上设置标准的lang属性。这对于:
- 拼写检查与语法检查:浏览器和第三方服务可依据
lang属性选择正确的语言词典; - 屏幕阅读器(无障碍):读屏软件能按
lang属性选择合适的语音引擎与发音规则; - 多语言站点内容管理:区分正文语言与 UI 语言(后者由已有的
language/language_url选项控制,见 Options.ts)。
content_language与既有的language选项职责不同:language控制的是 TinyMCE 界面(UI)的本地化语言,而content_language声明的是编辑区内容的语言。仓库中还定义了content_langs选项(类型为ContentLanguage[],见 OptionTypes.ts),用于在 UI 上提供语言下拉列表;content_language则直接指定实际生效的语言代码。
2.2 配置方式
在初始化配置中加入字符串类型的语言标签即可,取值遵循 BCP 47 语言标签规范,例如'fr'、'en-US'、'zh-CN':
tinymce.init({ selector: 'textarea', // 将 <html lang="fr"> 设置在 iframe 编辑器文档上 content_language: 'fr' });对于内联编辑器(inline: true),该值会设置到目标元素本身而非 iframe:
tinymce.init({ selector: '#my-editable-div', inline: true, content_language: 'en-US' });2.3 底层实现原理
从源码结构看,content_language的实现分三层:
选项注册:在 Options.ts 中通过
registerOption('content_language', { processor: 'string' })注册,处理器限定为字符串类型;读取端使用getContentLanguage = option('content_language')(Options.ts),并在 Options.ts 导出,供初始化流程调用。类型定义content_language?: string位于 OptionTypes.ts。属性写入:核心逻辑在编辑器内容 body 加载完成的
contentBodyLoaded函数中(InitContentBody.ts):
const contentLanguage = Options.getContentLanguage(editor); if (contentLanguage) { const langTarget = editor.inline ? targetElm : doc.documentElement; DOM.setAttrib(langTarget, 'lang', contentLanguage); }代码明确区分两种模式:内联编辑器(editor.inline为真)将lang设置到targetElm(即editor.getElement()返回的目标元素,见同文件 L414);iframe 模式则设置到doc.documentElement,即 iframe 文档的<html>元素——这与变更日志的描述完全一致。只有当选项值存在(非空字符串)时才写入,因此默认不配置时不会对现有页面产生任何影响。
- 测试验证:仓库提供了专门的浏览器测试 InitEditorContentLanguageTest.ts。该测试对 iframe 与 inline 两种模式分别断言:以
content_language: 'fr'初始化编辑器后,lang属性值必须为'fr'(iframe 模式检查documentElement,inline 模式检查目标元素,见测试文件 L10-L27)。这为升级后的行为提供了可复现的验证基准。
三、改进:内联 diff 高亮视觉优化(TINY-13958)
3.1 变更内容
8.5.0 改进了Suggested Edits(建议编辑)与TinyMCE AI 插件中内联 diff 高亮的视觉样式,使新增(added)、修改(modified)、删除(removed)三类注释在行内呈现时更加清晰、可区分。
3.2 样式实现
相关样式定义在 oxide 主题的内容样式目录 diff.less 中,其核心是通过.diff-content(...)混入(mixin)为不同注释状态生成样式:
tox-{plugin}__annotation--added:底部线性渐变 + 成功色(@color-success)下划线,选中态显示整块背景色与上下轮廓阴影(见 diff.less);tox-{plugin}__annotation--modified:使用主题色(@color-tint)渐变,选中态同理(diff.less);tox-{plugin}__annotation--removed:line-through删除线 + 错误色(@color-error)渐变(diff.less)。
针对行内ins/del元素,样式使用calc(1lh + 3px)的背景尺寸实现更贴合行高的选中态外框,避免整行背景遮挡文字(见 diff.less)。三类注释还提供了--hidden隐藏态(diff.less)以及包含 iframe 的容器注释的内边距处理(diff.less),说明本次优化兼顾了多行块级注释与行内注释两类场景的选中态表现。
四、修复:DomPurify 误删合法元素(TINY-9655)
4.1 问题现象
TinyMCE 使用 DOMPurify(dompurify库)对粘贴/插入的 HTML 进行安全清洗。8.5.0 修复了两个相关缺陷:
- 当
script或style元素在 schema 中被判定为合法(例如开发者通过valid_elements/custom_elements显式放行)时,它们的内容仍会被 DomPurify 错误移除; - 带子元素的
iframe元素(如包含 fallback 内容的 iframe)会被 DomPurify 整体删除。
4.2 修复原理
修复位于核心 HTML 清洗模块 Sanitization.ts。处理逻辑为:在 DomPurify 的元素级钩子(uponSanitizeElement)回调中,对符合条件的目标元素先做内容预处理:
// TINY-9655: Preserve the content of script and style tags if they are valid elements in the schema const shouldKeepContent = (ElementType.isScript(element) && schema.isValid('script')) || (ElementType.isStyle(element) && schema.isValid('style')); if (shouldKeepContent) { Optional.from(TextContent.get(element)).each((content) => Attribute.set(element, 'data-mce-tmp', content)); } // TINY-9655: Clear innerHTML of script and iframe tags to prevent DOMPurify from removing them entirely const shouldClearContent = ElementType.isIframe(element) && schema.isValid('iframe'); if (shouldKeepContent || shouldClearContent) { Remove.empty(element); }即在清洗前:对 schema 合法的script/style,先把文本内容暂存到data-mce-tmp属性中;对 schema 合法的iframe,清空其 innerHTML(连同前一类情况)以阻止 DomPurify 因内容异常而删除整个元素;清洗完成后再恢复内容。这是典型的"先保护、后恢复"策略,保证安全清洗与 schema 许可并存。
4.3 相关测试
该修复在 DomParserTest.ts 与 SerializerTest.ts 中均有覆盖(文件内引用Sanitization相关行为),回归验证了"合法 script/style 内容保留"与"iframe 不再被整体删除"。
五、修复:列表关闭异常与 body 误删(TINY-14070 / TINY-14149)
5.1 列表中 div 组合导致关闭列表异常(TINY-14070)
在嵌套列表或包含块级div的复杂列表结构中,"关闭列表(turn off lists)"操作(即再次点击列表按钮取消列表格式)可能产生错误的 DOM 结果。该逻辑属于核心列表模块,入口为 ToggleList.ts 的toggleList:它先判断选区是否处于不可编辑列表内(isWithinNonEditableList),再检查是否存在多个选中的子列表(Selection.getSelectedSubLists),分别走toggleMultipleLists或toggleSingleList分支;在toggleSingleList中,若父列表与目标列表类型一致且无样式细节,则执行flattenListSelection将列表扁平化(ToggleList.ts)。本版本修复了该流程中div与列表项组合时的边界情况,确保取消列表后内容结构正确。
5.2 特定选择操作误删编辑器 body(TINY-14149)
某些选区操作(如全选后执行删除)在极端情况下会删除编辑器内容根元素(body),导致编辑区损坏。TinyMCE 的选择系统在设计上通过bodyElement、_editableRoot等状态(见 InitContentBody.ts)保护内容根节点,本次修复进一步加固了选区边界判断,避免 body 被当作普通内容删除。
六、修复:非 Latin-1 字符 URI 报错(TINY-13938)
6.1 问题现象
当 data URI(例如粘贴的data:image/svg+xml,...)中包含非 Latin-1 字符(如带 BOM 的 UTF-8 编码内容,字符码大于 255)时,TinyMCE 处理 blob 缓存的过程会抛出异常,导致粘贴/插入失败。
6.2 修复与验证
修复位于图片/文件处理模块 BlobCacheUtils.ts 的processDataUri(L18-L20),该函数解析 data URI 并生成BlobInfo。仓库提供了专门的原子测试 BlobCacheUtilsTest.ts:
it('TINY-13938: processDataUri should not throw error with URIs with Byte Order Mark (BOM)', () => { let result = ''; assert.doesNotThrow(() => processDataUri('data:image/svg+xml,%EF%BB%BF%3Csvg', false, (base64) => { result = base64; return Optional.none(); })); assert.equal(result, '77u/PHN2Zw=='); });测试使用带 BOM(%EF%BB%BF)的 SVG data URI,断言调用不再抛错且能正确转换为 Base64。从实现上看,BlobCacheUtils.ts 使用TextEncoder进行 UTF-8 编码,规避了原先基于 Latin-1 假设的字符处理路径。
七、修复:屏幕阅读器未播报 alert/confirm(TINY-13812)
7.1 问题现象
通过editor.windowManager.alert(...)与editor.windowManager.confirm(...)弹出的对话框,在部分屏幕阅读器(如 NVDA/JAWS 组合)下不会被自动播报,影响无障碍体验。
7.2 修复方式
alert 与 confirm 对话框由 Silver 主题实现,入口在 WindowManager.ts:alert委托给AlertDialog.open,confirm委托给confirmDialog.open。对话框渲染采用 Alloy 的ModalDialog,并将对话框角色声明为role: 'alertdialog'(见 AlertDialog.ts),同时通过AriaDescribe等机制为对话框关联可访问名称。本次修复完善了这些对话框在打开时的 ARIA 播报时机与属性设置,使屏幕阅读器能及时读出提示内容;对话框头部使用隐藏标题(Dialogs.hiddenHeader)保证视觉简洁的同时保留无障碍语义(AlertDialog.ts)。
八、升级与验证建议
- 若需要使用
content_language:按 2.2 节示例配置即可;注意它与 UI 语言选项language相互独立,如需在界面提供语言选择下拉可配合content_langs使用。可以运行 InitEditorContentLanguageTest.ts 作为行为基准。 - 若自定义了允许
script/style/iframe的 schema:升级后请回归粘贴与插入场景,确认内容不再被清洗删除;相关逻辑见 Sanitization.ts。 - 无障碍回归:使用屏幕阅读器检查
windowManager.alert/confirm的播报(实现见 WindowManager.ts)。 - URI 粘贴回归:包含 BOM 或非 Latin-1 字符的 data URI 粘贴场景可参考 BlobCacheUtilsTest.ts 的用例进行验证。
总结
TinyMCE 8.5.0 是一个小而实的版本:content_language为多语言内容编辑补上了标准化的语言声明能力(核心实现与测试分别位于 InitContentBody.ts 与 InitEditorContentLanguageTest.ts);6 项修复则覆盖了 HTML 清洗、列表操作、选区安全、URI 处理与无障碍播报等高频风险点,每一处都有对应的源码位置或测试用例可供核查。对于正在升级或排查相关问题的开发者,本文给出的文件路径与工单号可直接作为问题定位的起点。
【免费下载链接】tinymceThe world's #1 JavaScript library for rich text editing. Available for React, Vue and Angular项目地址: https://gitcode.com/gh_mirrors/ti/tinymce
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考