Report an issue

Using context with collaboration features

The Context class organizes multiple editors into one environment. This way you can initialize some features not for a single editor but for its context accessible to many editors.

# Introduction to the context

# Collaboration features and multiple editors

Most applications use just one editor instance per a web page or a form. In these integrations, the editor is the topmost entity that initializes features and coordinates their work. Collaboration features such as a sidebar or presence list are linked with this single editor instance and only handle the changes happening within this editor instance.

It does not create a good user experience for applications that need to present multiple editors on one web page, though. In that case, each editor instance has its own sidebar and presence list. Also, multiple, separate connections need to be handled for data exchange.

The Context class was introduced to solve such issues. It organizes multiple editors into one environment. Some of the features, instead of being initialized for a singular editor, can be initialized for the whole context. Then, each editor can use the same instance of the feature, which means that there can be one common presence list or sidebar linked with multiple editors.

Note that only selected plugins are prepared to work as context plugins. See the API documentation for collaboration features to learn which plugins can be used as context plugins.

Additionally, in integrations using Context and multiple editor instances, each editor instance will display only “its own” comment threads in the comments archive panel.

# Collaboration features and no editor

A context plugin that is added to a context is ready as soon as the context is created. This allows for using the context plugins without creating an editor at all!

Thanks to that you can provide features like comments on non-editor form fields that are no longer just editor features but are deeply integrated with your application.

# Channel ID

The channel ID is used to identify a data storage for collaboration features that a given editor or context should connect to. If you are using multiple editors, each of them must use a different channel ID to connect to a specific document. Additionally, the context itself, needs to specify its own, unique channel ID.

To set the channel ID, use the config.collaboration.channelId configuration property. See the code snippets below.

The channel ID is frequently used as a parameter or data property in the comments API. If you are preparing a custom integration using the comments API, you can use the channel ID to recognize whether the comment was added to an editor instance or to a context.

If you have encountered issues with editor initializing due to _CKEditorCloudServicesServerError: cloud-services-server-error: Validation failed. error, it might be related to missing config.collaboration.channelId configuration.

# Before you start

Complementary to this guide, we provide a ready-to-use sample.

You may use it as an example or as a starting point for your own integration.

# Preparing a custom editor setup with the context

The context can be used with both: standalone collaboration features and real-time collaboration features. Below is an example featuring real-time collaboration.

We recommend reading about preparing a custom editor setup in the real-time collaboration features integration guide. The samples below will be based on this guide.

To use Context, add it to your editor setup:

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.com/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.com/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: ... }
};

# Using the context in an integration

After your editor setup is ready, you need to configure and initialize the context and the editor.

If you use the HTML produced by the Builder, it is enough to update the content of the .editor-container__editor container to this:

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

We can now setup three editor instances, sharing one context, on these elements:

// 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.
            // ...
        } );
    }
} );

The demo will render three editor instances, one after another. Since they share one context, you will be able to observe that:

  • The presence list is shared between them (and they use one collaboration session).
  • The sidebar with comments is also shared between them.

The HTML proposed by the Builder may not work best with this altered setup. Configuring a full setup with the use of Context requires further customizations.

# Context and watchdog

Similarly to an editor instance, context can be integrated with a watchdog to handle the errors that may happen in the editor or context features. Please refer to the watchdog documentation to learn how to use the context watchdog.

# Minimal implementation

Below are code snippets showcasing the use of a context and a watchdog together with real-time collaboration features.

The main.js file:

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 )
    } );
}

The HTML file:

<!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>

# Demo

Share the complete URL of this page with your colleagues to collaborate in real-time!

Bilingual Personality Disorder

This may be the first time you hear about this made-up disorder but it actually isn’t so far from the truth. As recent studies show, the language you speak has more effects on you than you realize. According to the studies, the language a person speaks affects their cognition, behavior, emotions and hence their personality.

Research

This shouldn’t come as a surprise since we already know that different regions of the brain become more active depending on the activity. The structure, information and especially the culture of languages varies substantially and the language a person speaks is an essential element of daily life.