guideIntegration

The software described herein is deprecated and is no longer updated. We recommend using CKEditor 5 with collaboration features instead.

Visit the collaboration samples repository for practical solutions. Also, visit the Online builder if you would rather prepare a custom CKEditor 5 build with collaboration enabled.

The basic instructions for integrating Letters with your application are available in Quick start guide. This article goes through some additional useful topics.

Complementary to this guide, we provide ready-to-use samples available for download. You may use the samples as an example or as a starting point for your own integration.

# Finish editing button

Letters comes with a finish editing button that can be used for exiting the application, for example when showing Letters in a modal window.

The finish editing button.

In order to show the finish editing button, provide the finishEditing configuration option. The action property defines a callback that will be executed.

Letters.create( document.body, {
    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: 'dogs'
    },
    finishEditing: {
        show: true,
        action: () => { alert( 'finished'); }
    }
} );

You can also use the event fired when the finish editing button is clicked:

Letters.create( document.body, {
    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: 'dogs'
    },
    finishEditing: {
        show: true
    }
} ).then( ( letters ) => {
    letters.on( 'finish', () => { alert( 'finished' ); } );
} );

Note that if the button is enabled, the user can also trigger the finish action with the keyboard (using the Esc key).
There is also no default action attached to the finish event.

# Sharing the document

It is possible to enable the share button in the top bar using the share option:

Letters.create( document.body, {
    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: '21'
    },
    share: {
        url: 'https://example.com/share/21',
        message: '<h4>Invite users to collaborate with them.</h4><p>Everybody with this link will be able to edit the document with you.</p>'
    }
} );

The share button lets you invite users to collaborate on the same document in a convenient manner. When the user clicks the share button, a panel is shown with the provided share URL.

The share button and balloon panel.

As an integrator, you need to take care of creating Letters with a proper channelId under the provided URL. Letters only offers a simple way to display the URL to the user, but it will not add any special logic to that given URL.

In the example above, a balloon with “https://example.com/share/21” as the share link will be displayed to the user. To make it work well when another user gets and opens this URL, your application should handle the request to that URL and launch Letters there with the same channel ID (21).

# Bootstrapping and loading

The default way to load Letters is to call the Letters.create() method. It bootstraps Letters UI first, with the top bar, finish editing and share buttons and the loading animation. Then it loads the editor, connects it to CKEditor Cloud Services and replaces the loading animation with the editor content.

However, you may want to perform some additional time-consuming action, like loading some data from the server. In such case, you may want to display Letters UI with the loading animation, perform your action and then, when it is finished, load the editor. This is possible to achieve using the letters.bootstrap() and letters.load() methods, instead of Letters.create():

const letters = new Letters( {
    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: 'dogs'
    }
} );

// Bootstrap the UI and the loading screen.
letters.bootstrap( document.body );

// You can execute asynchronous actions here, like loading data.
const loadData = new Promise( resolve => {
    // You can overwrite the configuration at this stage.
    letters.config.title = 'foo';
    letters.config.body = '<p>bar</p>';

    setTimeout( resolve, 3000 );
} );

// Finally load the Letters editor.
loadData.then( () => letters.load() );

# Saving and document versions

The document is temporarily stored in the cloud by CKEditor Cloud Services only when there are connected users. It means that when the last user disconnects, the content should be saved in the database on your server. However, there might be a conflict when two collaborating users are saving the same document and the older document overwrites the new one.

To prevent race conditions, Letters provides an additional variable: cloudDocumentVersion, available via letters.editor.plugins.get( 'RealTimeCollaborationClient' ).cloudDocumentVersion. It is a number that defines which client has a newer version of the document. This number should be stored in the database together with the content. When any client wants to save the content, document versions should be compared. The document should be saved only when the version is higher. Otherwise it means that this is an older version of the document and there is already a newer version saved.

# Autosave

Autosave feature is built into the Letters build. It provides an easy-to-use API to save the editor content. All you need to implement is the save callback. Autosave makes sure that the callback will be executed when the user changes the content. It also ensures that the callback will not be executed too often, but at the same time takes care of cases when the editor is destroyed or the user tries to close the browser window when some actions are pending.

To enable autosave you should proceed as follows.

On the client side:

Letters.create( document.body, {
    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: '21'
    },
    autosave: {
        save: ( editor ) => {
            // Note: Pseudocode - sendToServer does not exist.
            sendToServer( {
                // Autosave plugin ensures that it will not be called
                // before the initialization finishes.
                title: editor.getTitle(),
                body: editor.getBody(),
                cloudDocumentVersion: editor.plugins.get( 'RealTimeCollaborationClient' ).cloudDocumentVersion
            } );
        }
    }
} );

On the server side:

// Is this client sending a newer version of the document
// than the one already saved?
// Note: Pseudocode again.
if ( data.cloudDocumentVersion > database.get( 'cloudDocumentVersion' ) ) {
    // Save the document.
    database.set( 'title', data.title );
    database.set( 'body', data.body );

    // And bump the document version.
    database.set( 'cloudDocumentVersion', data.cloudDocumentVersion );
}

To learn more about autosave refer to the CKEditor 5 Autosave feature guide.

# Display mode

Letters switches between the inline and sidebar display modes for comment threads and suggestion annotations depending on the Letters container width. By default the thresholds are set to 1000px for narrow sidebar and 1200px for wide sidebar, but it can be changed using the narrowSidebarThreshold and wideSidebarThreshold configuration:

Letters.create( document.body, {
    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: '21'
    },
    narrowSidebarThreshold: 1100,
    wideSidebarThreshold: 1300
} );

When the container width is smaller than the threshold value, comment threads and suggestion annotations will be displayed inline and the sidebar will be hidden. Otherwise they will be shown in the sidebar.

# Collaborative comments

Collaborative comments are already enabled in the default Letters build. You can read more about the feature and its configuration in the Comments guide and Real-time collaborative features integration guide in the CKEditor 5 documentation.

Letters can be run in the “comments-only” mode, where the users are only allowed to add, edit and remove comments but not directly edit the content. To do so, please refer to the Comments-only mode guide.

# letters.editor

Letters uses a custom editor based on the CKEditor 5 Editor. It is available as the letters.editor property and is very useful whenever you want to integrate Letters with your application.

For instance, if you want to bold the currently selected content, use:

letters.editor.execute( 'bold' );

See the Introduction to CKEditor 5 Framework architecture to learn more about the editor API.

What is important, Letters has a special mechanism to handle errors and crashes. It restarts the editor, creating a new instance of it. It means that after the restart letters.editor will not be the same instance as it was before. Because of it, you should not store this variable nor modify the editor directly. Use custom plugins if you want to change the editor behavior.

# UMD package

Letters is distributed as a Universal Module Definition (UMD) package. It means that it can be loaded as an ES6 module:

import Letters from './letters';

Letters.create( element, config );

It can also be loaded as a CommonJS or AMD module, for instance:

requirejs( [ 'letters' ], ( Letters ) => {
    Letters.create( element, config );
} );

Or as a global JavaScript object, if there is no module format provided:

<script src="https://cdn.ckeditor.com/letters/36.0.1/letters.js">
Letters.create( element, config );