CKEditor自定义占位符插件表格内span的data属性重载丢失问题
问题根因
表格单元格内的占位符属性被剥离,是两层默认规则未覆盖自定义元素导致的,和span标签本身无关:
- CKEditor5的Table插件会将每个
<td>、<th>初始化为独立的嵌套可编辑实例,这些实例默认不会继承根编辑器注册的自定义元素schema、内容转换规则,重载初始化时会自动清理不在自身白名单内的类名、自定义属性,仅保留标签结构与内部文本。你测试发现<i>标签能正常保留属性,只是因为默认白名单对这类行内元素预先开放了属性权限,不是i标签有特殊保护逻辑。 - Neos对表格这类嵌套内容块额外配置了服务端内容清洗规则,默认仅放行span标签本身,自定义data属性、非内置类名会在内容入库前被过滤。
解决方案
全程不需要替换span标签,按以下三步做最小范围配置即可:
1. 给嵌套表格编辑器同步注册占位符规则
你之前给根编辑器写的占位符schema、upcast/downcast转换逻辑,需要同步注册到所有td/th的嵌套编辑器实例上,在你的自定义插件init方法中加入以下逻辑:
editor.on('instanceReady', () => { const nestedEditableEls = editor.ui.getEditableElement()?.querySelectorAll('.ck-editor__nested-editable'); nestedEditableEls?.forEach(el => { const nestedEditor = el.ckeditorInstance; if (!nestedEditor) return; // 以下注册逻辑和你在根编辑器写的占位符规则完全一致 nestedEditor.model.schema.register('fooPlaceholder', { inheritAllFrom: '$inlineObject', allowAttributes: ['placeholderIdentifier', 'placeholderValue', 'nodeIdentifier'] }); // 配置upcast:将带对应属性的span转换为模型元素 nestedEditor.conversion.for('upcast').elementToElement({ view: { name: 'span', classes: 'foo-placeholder' }, model: (viewEl, { writer }) => { return writer.createElement('fooPlaceholder', { placeholderIdentifier: viewEl.getAttribute('data-placeholder-identifier'), placeholderValue: viewEl.getAttribute('data-placeholder-value'), nodeIdentifier: viewEl.getAttribute('data-node-identifier') }); } }); // 配置editingDowncast:将模型元素渲染为带widget属性的span nestedEditor.conversion.for('editingDowncast').elementToElement({ model: 'fooPlaceholder', view: (modelEl, { writer }) => { const placeholderSpan = writer.createContainerElement('span', { class: 'foo-placeholder ck-widget', contenteditable: 'false', 'data-placeholder-identifier': modelEl.getAttribute('placeholderIdentifier'), 'data-placeholder-value': modelEl.getAttribute('placeholderValue'), 'data-node-identifier': modelEl.getAttribute('nodeIdentifier') }); return toWidget(placeholderSpan, writer, { hasSelectionHandler: true }); } }); // 配置dataDowncast:保存时输出正确的带属性span结构 nestedEditor.conversion.for('dataDowncast').elementToElement({ model: 'fooPlaceholder', view: (modelEl, { writer }) => { return writer.createContainerElement('span', { class: 'foo-placeholder', 'data-placeholder-identifier': modelEl.getAttribute('placeholderIdentifier'), 'data-placeholder-value': modelEl.getAttribute('placeholderValue'), 'data-node-identifier': modelEl.getAttribute('nodeIdentifier') }); } }); }); });
2. 配置Neos表格白名单放行自定义属性
找到项目内的表格节点配置Yaml(一般在NodeTypes/Content/Table.yaml或全局CKEditor预设配置中),给内容处理规则加放行配置,避免服务端清洗时剥离属性:
'Neos.NodeTypes:Table': properties: content: ui: inline: editorOptions: allowedHtml: span: attributes: class: true contenteditable: true 'data-placeholder-identifier': true 'data-placeholder-value': true 'data-node-identifier': true
3. 关闭表格对widget元素的自动拆分逻辑
在插件初始化时加一行配置,禁止表格编辑逻辑对识别为widget的占位符元素做拆分、额外包裹处理:
editor.plugins.get('Table').editingDowncastDispatcher.on('insert:tableCell', (evt, data, conversionApi) => { conversionApi.writer.setCustomProperty('allowNestingWidgets', true, data.item); }, { priority: 'high' });
验证方式
改完后清除Neos应用缓存、浏览器硬刷新:
- 在任意表格单元格内插入占位符,保存发布
- 重载页面后检查单元格内元素,占位符span的类名、所有自定义data属性会完整保留,功能恢复正常
注意:不要全局关闭CKEditor的内容过滤、也不要修改Table插件核心源码,以上配置为最小范围放行规则,不会引入额外XSS安全风险。
内容的提问来源于stack exchange,提问作者Markus
相关产品推荐
相关产品推荐

