剪贴板
剪贴板功能(由 Clipboard 插件实现)负责与本地剪贴板的集成。本地剪贴板是操作系统和浏览器的功能。它在用户在编辑器内或从/到“外部”复制、剪切、粘贴或拖放内容时使用。
CKEditor 5 拦截所有本地事件,如 copy、cut 或 drop,并在其自身侧处理它们。目标是不允许浏览器触碰富文本编辑器中的内容。这会导致浏览器将其弄乱。
有两种内容处理方向
这两个管道都允许功能处理要插入或设置到剪贴板的内容。它们还允许在这些过程的不同阶段覆盖默认机制。
# 输入管道
当用户将内容粘贴或拖放到编辑器中时,浏览器会触发事件。剪贴板功能会拦截此事件并启动以下机制
ClipboardObserver将此事件转换为合成view.Document#paste或view.Document#drop。- 两种操作(粘贴和拖放)要插入的内容通常应该以相同的方式处理,并且这两种操作都有类似的效果。因此,这两个事件都被转换为单个
view.Document#clipboardInput事件,以便于处理。 - 接下来,剪贴板功能监听
view.Document#clipboardInput事件,并检索和预处理它在 事件的dataTransfer中找到的text/html或text/plain内容。然后,它会使用事件数据content属性中的检索到的内容作为view.DocumentFragment触发ClipboardPipeline#inputTransformation事件。 - 然后,剪贴板功能监听
ClipboardPipeline#inputTransformation事件。它会获取处理后的内容并将其转换为model.DocumentFragment。然后,它会使用事件数据content属性中的转换后的内容作为model.DocumentFragment触发ClipboardPipeline#contentInsertion事件。 - 最后,剪贴板功能监听
ClipboardPipeline#contentInsertion事件。它获取模型片段并 插入 到编辑器中。然后,它将包含所有执行的更改的范围存储在事件数据的resultRange属性中。
剪贴板功能使用 低优先级监听器 监听 view.Document#clipboardInput、ClipboardPipeline#inputTransformation 和 ClipboardPipeline#contentInsertion 事件。这意味着添加一个普通监听器并调用 evt.stop() 允许覆盖剪贴板功能实现的行为。这与 DOM 的 evt.preventDefault() 机制类似,它允许你覆盖默认的浏览器行为。
# 输入管道事件概述
┌──────────────────────┐ ┌──────────────────────┐
│ view.Document │ │ view.Document │
│ paste │ │ drop │
└───────────┬──────────┘ └───────────┬──────────┘
│ │
└────────────────┌────────────────┘
│
┌─────────V────────┐
│ view.Document │ Retrieves text/html from data.dataTransfer
│ clipboardInput │ and processes it to view.DocumentFragment.
└─────────┬────────┘
│
┌───────────V───────────┐
│ ClipboardPipeline │ Converts view.DocumentFragment
│ inputTransformation │ to model.DocumentFragment.
└───────────┬───────────┘
│
┌──────────V──────────┐
│ ClipboardPipeline │ Calls model.insertContent().
│ contentInsertion │
└─────────────────────┘
# 以不同方式处理剪贴板输入
默认情况下,剪贴板功能从剪贴板中检索 text/html 或 text/plain。它会对数据进行一些规范化(例如,清理 空格方面的混乱)。它会将其转换为 视图 DocumentFragment 并在进一步处理时触发 ClipboardPipeline#inputTransformation 事件,其中包含该文档片段。
你可以使用 view.Document#clipboardInput 事件来覆盖此行为。例如,你可以使用它来
-
处理粘贴或拖放的文件。你可以从
dataTransfer中检索这些文件。但是,处理文件上传需要比读取
dataTransfer.files多得多。有关完整代码示例,请查看像ImageUploadEditing这样的插件的源代码。 -
更改剪贴板功能从剪贴板中读取的数据类型。例如,如果
dataTransfer中存在application/rtf,你可能希望使用它(在这种情况下忽略text/html)。editor.editing.view.document.on( 'clipboardInput', ( evt, data ) => { const dataTransfer = data.dataTransfer; const rtfContent = dataTransfer.getData( 'application/rtf' ); // If no RTF was pasted, abort and let the clipboard feature handle the input. if ( !rtfContent ) { return; } // Convert an RTF raw string to a view document fragment. const viewContent = convertRtfStringToView( rtfContent ); // Pass the view fragment to the default clipboard input handler // to allow further processing of the content. data.content = viewContent; } );
# 处理输入内容
ClipboardPipeline#inputTransformation 事件允许你处理将要插入到编辑器中的内容。
默认操作是触发 ClipboardPipeline#contentInsertion 事件。如果数据不为空,此事件将 插入 内容(data.content,用 DocumentFragment 表示)到编辑器中。
在此阶段,功能可以处理粘贴的内容。例如,你可以实现一个功能,它希望以以下方式将粘贴的文本转换为链接
const writer = new UpcastWriter( editor.editing.view.document );
editor.plugins.get( 'ClipboardPipeline' ).on( 'inputTransformation', ( evt, data ) => {
if ( data.content.childCount == 1 && isUrlText( data.content.getChild( 0 ) ) ) {
const linkUrl = data.content.getChild( 0 ).data;
data.content = writer.createDocumentFragment( [
writer.createElement(
'a',
{ href: linkUrl },
[ writer.createText( linkUrl ) ]
)
] );
}
} );
默认操作(将内容插入到编辑器中)由低优先级监听器执行,因此它可以被普通监听器覆盖。使用 lowest 优先级,你也可以在内容已插入后执行操作。
editor.plugins.get( 'ClipboardPipeline' ).on( 'contentInsertion', ( evt, data ) => {
console.log( 'Content was inserted.' );
}, { priority: 'lowest' } );
查看 事件系统深入指南,了解有关事件监听器优先级的更多信息。
# 粘贴为纯文本插件示例
你可以使用前面部分的知识来创建一个完整的插件。一个完美的示例是我们的 [PastePlainText](https://github.com/ckeditor/ckeditor5/blob/master/packages/ckeditor5-clipboard/src/pasteplaintext.ts),它在按下 Shift 时粘贴纯文本。如果你不熟悉在 CKEditor 5 中创建插件,请先阅读 创建简单插件 指南。
# 输出管道
输出管道等同于输入管道,但用于复制和剪切操作。它允许处理将要放入剪贴板的内容,或覆盖整个过程。
# 输出管道事件概述
┌──────────────────────┐ ┌──────────────────────┐ Retrieves the selected
│ view.Document │ │ view.Document │ model.DocumentFragment
│ copy │ │ cut │ and fires the `outputTransformation`
└───────────┬──────────┘ └───────────┬──────────┘ event.
│ │
└────────────────┌────────────────┘
│
┌─────────────V────────────┐ Processes model.DocumentFragment
│ ClipboardPipeline │ and converts it to
│ outputTransformation │ view.DocumentFragment.
└──────────────────────────┘
│
┌─────────────V────────────┐ Processes view.DocumentFragment
│ view.Document │ to text/html and text/plain
│ clipboardOutput │ and stores results in data.dataTransfer.
└──────────────────────────┘
# 1. 在 view.Document#copy 和 view.Document#cut 上
默认操作是
- 获取选定内容 从编辑器中。
- 阻止本地
copy或cut事件的默认操作。 - 使用由 模型文档片段 表示的选定内容触发
clipboard.ClipboardPipeline#outputTransformation`。
# 2. 在 clipboard.ClipboardPipeline#outputTransformation 上
默认操作是
- 处理由 模型文档片段 表示的
data.content。 - 使用已处理的
data.content(已转换为 视图文档片段)触发view.Document#clipboardOutput。
# 3. 在 view.Document#clipboardOutput 上
默认操作是将内容 (data.content,表示为一个 DocumentFragment) 作为 HTML 放入剪贴板。在剪切操作的情况下,选定的内容也会从编辑器中删除。
此操作由一个低优先级监听器执行,因此它可以被一个正常的监听器覆盖。
在此阶段,其他功能可以处理复制或剪切的内容。
我们每天都在努力保持文档的完整性。您是否发现了过时的信息?是否缺少某些内容?请通过我们的 问题跟踪器 报告。
随着 42.0.0 版本的发布,我们重新编写了许多文档,以反映新的导入路径和功能。我们感谢您的反馈,以帮助我们确保文档的准确性和完整性。