Report an issue

指南使用协作功能中的上下文

The Context class 将多个编辑器组织成一个环境。这样,您可以初始化某些功能,这些功能不是针对单个编辑器,而是针对其可供多个编辑器访问的上下文。

# 上下文简介

# 协作功能和多个编辑器

大多数应用程序每个网页或表单只使用一个编辑器实例。在这些集成中,编辑器是最顶层的实体,它初始化功能并协调其工作。协作功能(如边栏或状态列表)与这个单个编辑器实例相关联,并且只处理发生在这个编辑器实例中的更改。

然而,对于需要在一个网页上显示多个编辑器的应用程序来说,这并不能创造良好的用户体验。在这种情况下,每个编辑器实例都有自己的边栏和状态列表。此外,还需要处理多个独立的连接来进行数据交换。

The Context class 的引入是为了解决这些问题。它将多个编辑器组织成一个环境。一些功能,而不是为单个编辑器初始化,可以为整个上下文初始化。然后,每个编辑器都可以使用该功能的同一个实例,这意味着可以有一个通用的状态列表或边栏与多个编辑器链接。

请注意,只有选定的插件准备作为上下文插件使用。请参阅协作功能的 API 文档,了解哪些插件可以作为上下文插件使用。

此外,在使用 Context 和多个编辑器实例的集成中,每个编辑器实例只会在评论存档面板中显示“它自己”的评论线程。

# 协作功能和无编辑器

添加到上下文的上下文插件在上下文创建后即可使用。这允许在没有创建编辑器的情况下使用上下文插件!

因此,您可以提供诸如 非编辑器表单字段上的评论 之类的功能,这些功能不再仅仅是编辑器功能,而是与您的应用程序深度集成。

# 频道 ID

频道 ID 用于标识给定编辑器或上下文应连接到的协作功能的数据存储。如果您使用多个编辑器,则每个编辑器必须使用不同的频道 ID 来连接到特定文档。此外,上下文本身也需要指定自己的唯一频道 ID。

要设置频道 ID,请使用 config.collaboration.channelId 配置属性。请参见下面的代码片段。

频道 ID 通常在 评论 API 中用作参数或数据属性。如果您正在准备使用评论 API 的自定义集成,您可以使用频道 ID 来识别评论是添加到编辑器实例还是上下文。

如果您遇到由于 _CKEditorCloudServicesServerError: cloud-services-server-error: Validation failed. 错误导致编辑器初始化失败的问题,这可能与缺少 config.collaboration.channelId 配置有关。

# 开始之前

作为本指南的补充,我们提供了一个 现成的示例.

您可以将其用作示例或作为您自己的集成的起点。

# 使用上下文准备自定义编辑器设置

上下文可以与独立协作功能和实时协作功能一起使用。以下是一个使用实时协作的示例。

我们建议您阅读 实时协作功能集成 指南中有关准备自定义编辑器设置的内容。以下示例将基于本指南。

要使用 Context,请将其添加到您的编辑器设置中

import {
    /* ... */

    CloudServices,
    Context
} from 'ckeditor5';

import {
    /* ... */

    // Context plugins:
    CommentsRepository,
    NarrowSidebar,
    WideSidebar,
    CloudServicesCommentsAdapter,
    PresenceList
} from 'ckeditor5-premium-features';

// The context's configuration.
const contextConfig = {
    // Plugins specific for the context:
    plugins: [
        CloudServices,
        CloudServicesCommentsAdapter,
        CommentsRepository,
        NarrowSidebar,
        PresenceList,
        WideSidebar
    ],

    // Sidebar and presence list's shared locations:
    sidebar: {
        container: document.querySelector( '#editor-annotations' )
    },
    presenceList: {
        container: document.querySelector( '#editor-presence' )
    },

    // Default configuration of the comments feature needs to be specified on the
    // context as it is a context plugin:
    comments: {
        editorConfig: {
            extraPlugins: [ Autoformat, Bold, Italic, List, Mention ],
            mention: {
                feeds: [
                    {
                        marker: '@',
                        feed: [
                            /* See: https://ckeditor.npmjs.net.cn/docs/ckeditor5/latest/features/mentions.html#comments-with-mentions */
                        ]
                    }
                ]
            }
        }
    },

    // Real-time features configuration:
    // NOTE: PROVIDE CORRECT VALUES HERE.
    cloudServices: {
        tokenUrl: 'https://example.com/cs-token-endpoint',
        uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
        webSocketUrl: 'your-organization-id.cke-cs.com/ws/'
    },

    collaboration: {
        channelId: 'channel-id'
    }
};

// Template of a specific editor configuration.
const editorConfig = {
    /*
        Assuming that you based your setup on the configuration  produced by the Builder (https://ckeditor.npmjs.net.cn/ckeditor-5/builder),
        now you can comment out (remove) a couple of properties that were specified on the context level
        and do not need to be customized per editor instance:
    */

    // comments: ...
    // sidebar: ...
    // presenceList: ...
    // cloudServices: ...

    /* The channelId property will be specified later so you can remove it too: */

    // collaboration: { channelId: ... }
};

# 在集成中使用上下文

编辑器设置准备就绪后,您需要配置和初始化上下文和编辑器。

如果您使用 Builder 生成的 HTML,您只需要将 .editor-container__editor 容器的内容更新为以下内容

<div class="editor-container__editor">
    <div id="editor1" class="editor"></div>
    <div id="editor2" class="editor"></div>
    <div id="editor3" class="editor"></div>
</div>

现在,我们可以在这三个元素上设置三个编辑器实例,共享一个上下文

// An example of initialization of the context and multiple editors:
Context.create( contextConfig ).then( context => {
    // After the context is ready, initialize an editor on all elements in the DOM with the `.editor` class:
    for ( const editorElement of document.querySelectorAll( '.editor' ) ) {
        // Create current editor's config based on the earlier defined template.
        const currentEditorConfig = Object.assign( editorConfig, {
            // Pass the context to the editor.
            context,

            // Each editor should connect to its document.
            // Create a unique channel ID for an editor (document).
            collaboration: {
                channelId: 'channel-id-' + editorElement.id
            }
        } );

        DecoupledEditor.create( editorElement, currentEditorConfig ).then( editor => {
            // Insert the toolbar of this editor right above its editing area.
            document.querySelector( '.editor-container__editor' )
                .insertBefore( editor.ui.view.toolbar.element, editor.ui.view.editable.element );

            // You can do something with the editor instance after it was initialized.
            // ...
        } );
    }
} );

演示将依次渲染三个编辑器实例。由于它们共享一个上下文,您将能够观察到

  • 成员列表在它们之间共享(并且它们使用一个协作会话)。
  • 带有评论的侧边栏也在它们之间共享。

Builder 提出的 HTML 可能不适合这种修改后的设置。使用 Context 配置完整设置需要进一步的自定义。

# 上下文和看门狗

类似于编辑器实例,上下文可以与看门狗集成,以处理编辑器或上下文功能中可能发生的错误。请参考 看门狗文档 了解如何使用上下文看门狗。

# 最小实现

以下是展示上下文和看门狗与实时协作功能一起使用的代码片段。

main.js 文件

import {
    ClassicEditor,

    // Import the context, its watchdog and plugins:
    ContextWatchdog,
    Context,
    CloudServices,

    // Editor plugins:
    Bold, Italic, Essentials, Heading, Paragraph
} from 'ckeditor5';

// Real-time collaboration plugins are editor plugins:
import {
    RealTimeCollaborativeComments,
    RealTimeCollaborativeEditing,
    RealTimeCollaborativeTrackChanges,
    Comments,
    TrackChanges,

    // Context plugins:
    CommentsRepository,
    NarrowSidebar,
    WideSidebar,
    CloudServicesCommentsAdapter,
    PresenceList
} from 'ckeditor5-premium-features';

import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';

const channelId = 'channel-id';

// The context's configuration.
const contextConfig = {
    // Plugins to include in the context.
    plugins: [
        CloudServices,
        CloudServicesCommentsAdapter,
        CommentsRepository,
        NarrowSidebar,
        WideSidebar,
        PresenceList
    ],

    // Default configuration for the context plugins:
    sidebar: {
        container: document.querySelector( '#sidebar' )
    },
    presenceList: {
        container: document.querySelector( '#presence-list-container' )
    },
    // The configuration shared between the editors:
    toolbar: {
        items: [
            'bold', 'italic', '|', 'undo', 'redo', '|',
            'comment', 'commentsArchive', 'trackChanges'
        ]
    },
    comments: {
        editorConfig: {
            plugins: [ Essentials, Paragraph, Bold, Italic ]
        }
    },

    // The configuration for real-time collaboration features, shared between the editors:
    cloudServices: {
        // PROVIDE CORRECT VALUES HERE:
        tokenUrl: 'https://example.com/cs-token-endpoint',
        uploadUrl: 'https://your-organization-id.cke-cs.com/easyimage/upload/',
        webSocketUrl: 'your-organization-id.cke-cs.com/ws/'
    },
    // Collaboration configuration for the context:
    collaboration: {
        channelId
    }
};

// Template of a specific editor configuration.
const editorConfig = {
    // Plugins to include in the editor:
    plugins: [
        Essentials, Paragraph, Bold, Italic, Heading,

        // Real-time collaboration plugins are editor plugins:
        RealTimeCollaborativeEditing,
        RealTimeCollaborativeComments,
        RealTimeCollaborativeTrackChanges,
        Comments,
        TrackChanges
    ],
};

const watchdog = new ContextWatchdog( Context );

await watchdog.create( contextConfig );

for ( const editorElement of document.querySelectorAll( '.editor' ) ) {
    // Create current editor's configuration based on the template defined earlier.
    const currentEditorConfig = Object.assign( editorConfig, {
        collaboration: {
            // Create a unique channel ID for an editor (document).
            channelId: channelId + '-' + editorElement.id
        }
        // You do not need to pass the context to the configuration
        // if you are using the context watchdog.
    } );

    await watchdog.add( {
        id: editorElement.id,
        type: 'editor',
        sourceElementOrData: editorElement,
        config: currentEditorConfig,
        creator: ( element, config ) => ClassicEditor.create( element, config )
    } );
}

HTML 文件

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 Collaboration – Hello World!</title>

    <style type="text/css">
        #presence-list-container {
            width: 800px;
            margin: 0 auto;
        }

        #container {
            display: flex;
            position: relative;
            width: 800px;
            margin: 0 auto;
        }

        .form {
            width: 500px;
        }

        #container .ck.ck-editor {
            width: 100%;
        }

        #sidebar {
            width: 300px;
            padding: 0 10px;
        }
    </style>
</head>

<body>
    <div id="presence-list-container"></div>

    <div id="container">
        <div class="form">
            <div class="editor" id="editor-1">
                <h2>Editor 1</h2>
                <p>Foo bar baz</p>
            </div>
            <div class="editor" id="editor-2">
                <h2>Editor 2</h2>
                <p>Foo bar baz</p>
            </div>
        </div>
        <div id="sidebar"></div>
    </div>

    <script type="module" src="./main.js"></script>
</body>
</html>

# 演示

与您的同事分享此页面的完整 URL,以便实时协作!

双语人格障碍

这可能是您第一次听说这种虚构的疾病,但实际上它并不遥远。正如最近的研究表明,您使用的语言对您的影响比您意识到的更大。研究表明,一个人的语言会影响他们的认知、行为、情绪,进而影响他们的性格。

研究

这并不令人惊讶 因为我们已经知道 大脑的不同区域会根据活动变得更加活跃。语言的结构、信息,尤其是 **文化** 差异很大,一个人使用的语言是日常生活中的一个重要元素。