Contribute to this guide

guide剪贴板

剪贴板功能(由 Clipboard 插件实现)负责与本地剪贴板的集成。本地剪贴板是操作系统和浏览器的功能。它在用户在编辑器内或从/到“外部”复制、剪切、粘贴或拖放内容时使用。

CKEditor 5 拦截所有本地事件,如 copycutdrop,并在其自身侧处理它们。目标是不允许浏览器触碰富文本编辑器中的内容。这会导致浏览器将其弄乱。

有两种内容处理方向

  • 当内容被粘贴或拖放到编辑器中时,它会通过 输入管道
  • 当内容从编辑器中复制、剪切或拖动时,它会通过 输出管道

这两个管道都允许功能处理要插入或设置到剪贴板的内容。它们还允许在这些过程的不同阶段覆盖默认机制。

# 输入管道

当用户将内容粘贴或拖放到编辑器中时,浏览器会触发事件。剪贴板功能会拦截此事件并启动以下机制

  1. ClipboardObserver 将此事件转换为合成 view.Document#pasteview.Document#drop
  2. 两种操作(粘贴和拖放)要插入的内容通常应该以相同的方式处理,并且这两种操作都有类似的效果。因此,这两个事件都被转换为单个 view.Document#clipboardInput 事件,以便于处理。
  3. 接下来,剪贴板功能监听 view.Document#clipboardInput 事件,并检索和预处理它在 事件的 dataTransfer 中找到的 text/htmltext/plain 内容。然后,它会使用事件数据 content 属性中的检索到的内容作为 view.DocumentFragment 触发 ClipboardPipeline#inputTransformation 事件。
  4. 然后,剪贴板功能监听 ClipboardPipeline#inputTransformation 事件。它会获取处理后的内容并将其转换为 model.DocumentFragment。然后,它会使用事件数据 content 属性中的转换后的内容作为 model.DocumentFragment 触发 ClipboardPipeline#contentInsertion 事件。
  5. 最后,剪贴板功能监听 ClipboardPipeline#contentInsertion 事件。它获取模型片段并 插入 到编辑器中。然后,它将包含所有执行的更改的范围存储在事件数据的 resultRange 属性中。

剪贴板功能使用 低优先级监听器 监听 view.Document#clipboardInputClipboardPipeline#inputTransformationClipboardPipeline#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/htmltext/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. 获取选定内容 从编辑器中。
  2. 阻止本地 copycut 事件的默认操作。
  3. 使用由 模型文档片段 表示的选定内容触发 clipboard.ClipboardPipeline#outputTransformation`。

默认操作是

  1. 处理由 模型文档片段 表示的 data.content
  2. 使用已处理的 data.content(已转换为 视图文档片段)触发 view.Document#clipboardOutput

默认操作是将内容 (data.content,表示为一个 DocumentFragment) 作为 HTML 放入剪贴板。在剪切操作的情况下,选定的内容也会从编辑器中删除。

此操作由一个低优先级监听器执行,因此它可以被一个正常的监听器覆盖。

在此阶段,其他功能可以处理复制或剪切的内容。