Report an issue

guideIntegrating revision history with your application

This guide describes integrating the revision history feature as a standalone plugin.

The real-time collaboration integration is currently under development.

The revision history plugin provides an API for creating and managing named revisions of the document. To save and load the revisions from your database you need to provide a proper integration.

This guide will discuss two ways to integrate CKEditor 5 with revision history plugin:

The adapter integration is the recommended one for two reasons:

  • it gives you a better control over the data,
  • it is more efficient and provides a better user experience, as the revisions’ data is loaded on demand rather than upfront before the editor is initialized.

# Before you start

This is a premium feature and a license key is needed to run it. Please contact us if you would like to purchase a license. Let us know if you have any feedback or questions!

Before you start creating an integration, there are a few concepts you should be familiar with. This guide will explain how to create a custom build with the revision history plugin, what data structure do the revisions use and what the plugin API looks like.

Make sure that you understand all of these concepts before you proceed with the integration.

# Prepare a custom build

The revision history plugin is not included by default in any CKEditor 5 build. To enable it, you need to create a custom CKEditor 5 build that includes the revision history plugin.

You can also easily create your own CKEditor 5 build with the revision history feature included using the CKEditor5 online builder.

git clone -b stable https://github.com/ckeditor/ckeditor5
cd ckeditor5/packages/ckeditor5-build-classic

npm install

You need to install the @ckeditor5/ckeditor5-revision-history package using npm:

npm install --save-dev @ckeditor/ckeditor5-revision-history

To make the revision history work, you need to import revision history plugin and add it to the list of plugins.

An updated src/ckeditor.js should look like this:

import ClassicEditorBase from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';

import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
import Image from '@ckeditor/ckeditor5-image/src/image';
import Table from '@ckeditor/ckeditor5-table/src/table';

import RevisionHistory from '@ckeditor/ckeditor5-revision-history/src/revisionhistory';

export default class ClassicEditor extends ClassicEditorBase {}

// Plugins to include in the build.
ClassicEditor.builtinPlugins = [ Essentials, Paragraph, Bold, Italic, Image, Table, RevisionHistory ];

// The editor configuration.
ClassicEditor.defaultConfig = {
    language: 'en'
};

Note that your custom build needs to be bundled using a webpack.

npm run build

Read more about this topic in the installing plugins guide.

# Core setup

The examples below implement the wide sidebar display mode.

When you have the revision history package included in your custom build, prepare an HTML structure for the sidebar. After that you can enable the revision history plugin.

To browse through the saved revisions, the feature uses an additional editor instance on top of the original editor instance. To enable that, you will need to provide an HTML structure for both the original editor and the revision history editor. The HTML elements for the revision history editor structure should be hidden by default.

  • Add the layout for the document editor and the revision history editor.
  • Add the sidebar and revisionHistory configurations.
  • Add the revisionHistory dropdown to the toolbar.
  • Add your licenseKey. If you do not have a key yet, please contact us.

Edit the sample/index.html file as follows:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    .ck.ck-editor {
        max-width: 800px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>

<div class="editors-holder">
    <!-- The original editor !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    ClassicEditor.create( document.querySelector( '#editor' ), {
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [ 'bold', 'italic', '|', 'revisionHistory' ]
        }
    } )
    .catch( error => console.error( error ) );
</script>

</body>
</html>

When you open the sample in the browser, you should see the WYSIWYG editor with the revision history plugin. However, it still does not load or save any revision data and the user is not defined. You will learn how to add those later in this guide.

# A simple “load and save” integration

In this solution, users and revisions data are loaded during the editor initialization and revisions data is saved after you finish working with the editor (for example when you submit the form containing the WYSIWYG editor).

The integration below uses the revision history API. Making yourself familiar with the API may help you understand the code snippets. In case of any problems, refer to the revision history API documentation.

# Loading the data

When the revision history plugin is already included in the editor, you need to create a plugin which will initialize users and existing revisions.

First, dump the users and the revisions data to a variable that will be available for your plugin.

If your application needs to request the revisions data from the server asynchronously, you can create a plugin that will fetch the data from the database instead of putting the data in the HTML source. In such a case, your plugin should return a Promise from the Plugin.init method to make sure that the editor initialization waits for your data.

// Application data will be available under a global variable `appData`.
const appData = {
    // Initial editor data
    data: `<figure class="image">
        <img src="../../assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

    // User data.
    user: {
        id: 'u1',
        name: 'Joe Doe',
        // Note that the avatar is optional.
        avatar: 'https://randomuser.me/api/portraits/thumb/men/26.jpg'
    },

    // Initial revisions data
    revisions: [
        {
            "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
            "name": "Initial revision - template",
            "creatorId": "u1",
            "data": {
                "main": {
                    "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                    "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                }
            },
            "createdAt": "2021-05-27T13:22:59.077Z",
            "attributes": {}
        },
        {
            "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
            "name": "Updated with the actual data",
            "creatorId": "u1",
            "data": {
                "main": {
                    "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                    "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                }
            },
            "createdAt": "2021-05-27T13:23:52.553Z",
            "attributes": {}
        },
        {
            "id": "e6590c50ccbc86acacb7d27231ad32064",
            "name": "Inserted logo",
            "creatorId": "u1",
            "data": {
                "main": {
                    "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","../../assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                    "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                }
            },
            "createdAt": "2021-05-27T13:26:39.252Z",
            "attributes": {}
        }
    ]
};

Then, prepare a plugin that will read the data from appData and use the Users and RevisionsRepository API.

class RevisionHistoryIntegration {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'RevisionHistoryIntegration';
    }

    static get requires() {
        return [ 'RevisionHistory' ];
    }

    init() {
        const revisionsRepositoryPlugin = this.editor.plugins.get( 'RevisionsRepository' );
        const revisions = appData.revisions || [];

        // Load revisions data
        for ( const revision of revisions ) {
            const newRevision = revisionsRepositoryPlugin.createRevision( revision );
            revisionsRepositoryPlugin.addRevision( newRevision );
        }
    }
}

// Initialize users here
class UsersInit {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'UsersInit';
    }

    static get requires() {
        return [ 'Users' ];
    }

    init() {
        const users = this.editor.plugins.get( 'Users' );

        users.addUser( appData.user );
        users.defineMe( appData.user.id );
    }
}

// In order to load comments and track changes,
// you should also create the comments and track changes integrations.

# Saving the data

To save the revisions data you need to get it from the RevisionsRepository plugin first. To do this, use the getRevisions() method.

Then, use the data to save it in your database in a selected way. See the example below.

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        initialData: appData.data,
        extraPlugins: [ UsersInit, RevisionHistoryIntegration ],
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [ 'bold', 'italic', '|', 'revisionHistory' ]
        }
    } )
    .then( editor => {
        // After the editor is initialized, add an action to be performed after a button is clicked.
        document.querySelector( '#get-data' ).addEventListener( 'click', () => {
            const revisionsRepositoryPlugin = editor.plugins.get( 'RevisionsRepository' );

            // Get the data on demand.
            const editorData = editor.data.get();
            const revisionsData = revisionsRepositoryPlugin.getRevisions();

            // Now, use `editorData` and `revisionsData` to save the data in your application.
            console.log( editorData );
            console.log( revisionsData );
        } );
    } )
    .catch( error => console.error( error ) );

It is recommended to stringify the revisions’ attributes property value to JSON and to save it as a string in your database. Then parse the value from JSON when loading revisions.

# Full implementation

Below you can find the final implementation.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>
<button id="get-data">Get editor data</button>
<div class="editors-holder">
    <!-- The original editor !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    // Application data will be available under a global variable `appData`.
    const appData = {
        // Initial editor data
        data: `<figure class="image">
            <img src="../../assets/img/revision-history-demo.png">
        </figure>
        <h1>PUBLISHING AGREEMENT</h1>
        <h3>Introduction</h3>
        <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
        <h3>Grant of Rights</h3>
        <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
        <ul>
            <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
            <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
            <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
        </ul>
        <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
        <p>Any rights not granted to the Publisher above remain with the Author.</p>
        <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

        // User data.
        user: {
            id: 'u1',
            name: 'Joe Doe',
            // Note that the avatar is optional.
            avatar: 'https://randomuser.me/api/portraits/thumb/men/26.jpg'
        },

        // Initial revisions data
        revisions: [
            {
                "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
                "name": "Initial revision - template",
                "creatorId": "u1",
                "data": {
                    "main": {
                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                    }
                },
                "createdAt": "2021-05-27T13:22:59.077Z",
                "attributes": {}
            },
            {
                "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                "name": "Updated with the actual data",
                "creatorId": "u1",
                "data": {
                    "main": {
                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                    }
                },
                "createdAt": "2021-05-27T13:23:52.553Z",
                "attributes": {}
            },
            {
                "id": "e6590c50ccbc86acacb7d27231ad32064",
                "name": "Inserted logo",
                "creatorId": "u1",
                "data": {
                    "main": {
                        "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","../../assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                        "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                    }
                },
                "createdAt": "2021-05-27T13:26:39.252Z",
                "attributes": {}
            }
        ]
    };

    class RevisionHistoryIntegration {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'RevisionHistoryIntegration';
        }

        static get requires() {
            return [ 'RevisionHistory' ];
        }

        init() {
            const revisionsRepositoryPlugin = this.editor.plugins.get( 'RevisionsRepository' );
            const revisions = appData.revisions || [];

            // Load revisions data
            for ( const revision of revisions ) {
                const newRevision = revisionsRepositoryPlugin.createRevision( revision );
                revisionsRepositoryPlugin.addRevision( newRevision );
            }
        }
    }

    class UsersInit {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'UsersInit';
        }

        static get requires() {
            return [ 'Users' ];
        }

        init() {
            const users = this.editor.plugins.get( 'Users' );

            users.addUser( appData.user );
            users.defineMe( appData.user.id );
        }
    }

    // In order to load comments and track changes,
    // you should also create the comments and track changes integrations.

    ClassicEditor
        .create( document.querySelector( '#editor' ), {
            initialData: appData.data,
            extraPlugins: [ UsersInit, RevisionHistoryIntegration ],
            licenseKey: 'your-license-key',
            sidebar: {
                container: document.querySelector( '#sidebar-container' )
            },
            revisionHistory: {
                editorContainer: document.querySelector( '#editor-container' ),
                viewerContainer: document.querySelector( '#revision-viewer-container' ),
                viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
                viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
            },
            toolbar: {
                items: [ 'bold', 'italic', '|', 'revisionHistory' ]
            }
        } )
        .then( editor => {
            // After the editor is initialized, add an action to be performed after a button is clicked.
            document.querySelector( '#get-data' ).addEventListener( 'click', () => {

                const revisionsRepositoryPlugin = editor.plugins.get( 'RevisionsRepository' );

                // Get the data on demand.
                const editorData = editor.data.get();
                const revisionsData = revisionsRepositoryPlugin.getRevisions();

                // Now, use `editorData` and `revisionsData` to save the data in your application.
                console.log( editorData );
                console.log( revisionsData );
            } );
        } )
        .catch( error => console.error( error ) );
</script>

</body>
</html>

# Demo

Console:

// Use the `Save revisions` button to see the result...

# Adapter integration

An adapter integration uses an adapter object — provided by you — to immediately save revisions data in your data store.

This is a recommended way of integrating revision history with your application as it lets you handle the client-server communication in a more secure way. For example, you can check user permissions, validate sent data or update the data with information obtained on the server side.

Additionally, revisions may include significant amount of data. Loading multiple revisions of a big document may adversely impact the loading time of your application. When using an adapter, the revision data is loaded on demand, when needed, which improves the overall user experience.

Note that this sample does not contain the comments and track changes adapters. Check the comments integration guide and track changes integration guide to learn how to build a complete solution. Also note that those snippets define the same list of users. Make sure to deduplicate this code and define the list of users only once to avoid errors.

# Implementation

First, define the adapter using the RevisionHistory#adapter property. The adapter methods allow you to load and save changes in your database.

Each change in revisions is performed immediately on the UI side. However, all adapter actions are asynchronous and are performed in the background. Because of this, all adapter methods need to return a Promise. When the promise is resolved, it means that everything went fine and a local change was successfully saved in the data store. When the promise is rejected, the editor throws a CKEditorError error, which works nicely together with the watchdog feature. When you handle the server response, you can decide if the promise should be resolved or rejected.

While the adapter is saving the revision’s data, a pending action is automatically added to the editor PendingActions plugin, so you do not have to worry that the editor will be destroyed before the adapter action has finished.

Now you are ready to implement the adapter.

// Application data will be available under a global variable `appData`
const appData = {
    // Initial editor data
    data: `<figure class="image">
        <img src="../../assets/img/revision-history-demo.png">
    </figure>
    <h1>PUBLISHING AGREEMENT</h1>
    <h3>Introduction</h3>
    <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
    <h3>Grant of Rights</h3>
    <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
    <ul>
        <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
        <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
        <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
    </ul>
    <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
    <p>Any rights not granted to the Publisher above remain with the Author.</p>
    <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

    // User data.
    user: {
        id: 'u1',
        name: 'Joe Doe',
        // Note that the avatar is optional.
        avatar: 'https://randomuser.me/api/portraits/thumb/men/26.jpg'
    }
}

// Plugin that introduces the adapter.
class RevisionHistoryAdapter {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'RevisionHistoryAdapter';
    }

    static get requires() {
        return [ 'RevisionHistory' ];
    }

    init() {
        const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

        revisionHistory.adapter = {
            getRevisions: ( { channelId } ) => {
                // Get all revisions for the document with id `channelId`.
                // This should be an asynchronous request to your database.
                return Promise.resolve( [
                    {
                        "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
                        "name": "Initial revision - template",
                        "creatorId": "u1",
                        "createdAt": "2021-05-27T13:22:59.077Z",
                        "attributes": {}
                    },
                    {
                        "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                        "name": "Updated with the actual data",
                        "creatorId": "u1",
                        "createdAt": "2021-05-27T13:23:52.553Z",
                        "attributes": {}
                    },
                    {
                        "id": "e6590c50ccbc86acacb7d27231ad32064",
                        "name": "Inserted logo",
                        "creatorId": "u1",
                        "createdAt": "2021-05-27T13:26:39.252Z",
                        "attributes": {}
                    }
                ] );
            },
            getRevision: ( { revisionId } ) => {
                // Get revision data, based on its id.
                // This should be an asynchronous request to your database.
                switch ( revisionId ) {
                    case 'e232bd41cc1d6ff75d3b8f100fc9a070b':
                        return Promise.resolve(
                            {
                                "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
                                "name": "Initial revision - template",
                                "creatorId": "u1",
                                "data": {
                                    "main": {
                                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:22:59.077Z",
                                "attributes": {}
                            }
                        );
                    case 'e6f80e6be6ee6057fd5a449ab13fba25d':
                        return Promise.resolve(
                            {
                                "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                                "name": "Updated with the actual data",
                                "creatorId": "u1",
                                "data": {
                                    "main": {
                                        "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                        "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:23:52.553Z",
                                "attributes": {}
                            }
                        );
                    case 'e6590c50ccbc86acacb7d27231ad32064':
                    	return Promise.resolve(
                            {
                                "id": "e6590c50ccbc86acacb7d27231ad32064",
                                "name": "Inserted logo",
                                "creatorId": "u1",
                                "data": {
                                    "main": {
                                        "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","../../assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                        "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                    }
                                },
                                "createdAt": "2021-05-27T13:26:39.252Z",
                                "attributes": {}
                            }
                        );
                }
            },
            updateRevision: revisionData => {
                // This should be an asynchronous request to your database.
                return Promise.resolve();
            },
            addRevision: revisionData => {
                // This should be an asynchronous request to your database.
                return Promise.resolve();
            }
        };
    }
}

// Initialize the users here
class UsersInit {
    constructor( editor ) {
        this.editor = editor;
    }

    static get pluginName() {
        return 'UsersInit';
    }

    static get requires() {
        return [ 'Users' ];
    }

    init() {
        const users = this.editor.plugins.get( 'Users' );

        users.addUser( appData.user );
        users.defineMe( appData.user.id );
    }
}

// In order to load comments and track changes,
// you should also integrate the comments and track changes adapters.

ClassicEditor
    .create( document.querySelector( '#editor' ), {
        initialData: appData.data,
        extraPlugins: [ UsersInit, RevisionHistoryAdapter ],
        licenseKey: 'your-license-key',
        sidebar: {
            container: document.querySelector( '#sidebar-container' )
        },
        revisionHistory: {
            editorContainer: document.querySelector( '#editor-container' ),
            viewerContainer: document.querySelector( '#revision-viewer-container' ),
            viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
            viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
        },
        toolbar: {
            items: [ 'bold', 'italic', '|', 'revisionHistory' ]
        }
    } )
    .catch( error => console.error( error ) );

It is recommended to stringify the revisions’ attributes property value to JSON and to save it as a string in your database. Then parse the value from JSON when loading revisions.

The adapter is now ready to use with your rich text editor.

# Full implementation

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>CKEditor 5 with revision history</title>
    <style type="text/css">
    .editor-container {
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        position: relative;
        width: 1260px;
    }

    #revision-viewer-container {
        display: none;
    }

    .editor-container > .ck-editor {
        position: relative;
        width: 950px;
    }

    .editor-container .ck-editor__top .ck-toolbar {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .editor-container .ck-editor__editable_inline {
        border-top-right-radius: 0;
        border-bottom-right-radius: 0;
    }

    .sidebar-container {
        position: relative;
        width: 600px;
        overflow: hidden;
        background: var(--ck-color-toolbar-background);
        border: 1px solid var(--ck-color-toolbar-border);
        margin-left: -1px;
    }
    </style>
</head>
<body>

<div class="editors-holder">
    <!-- The original editor !-->
    <div id="editor-container">
        <div class="editor-container">
            <div id="editor"></div>
            <div class="sidebar-container" id="sidebar-container"></div>
        </div>
    </div>

    <!-- Structure for the revision viewer editor !-->
    <div id="revision-viewer-container">
        <div class="editor-container">
            <div id="revision-viewer-editor"></div>
            <div class="sidebar-container" id="revision-viewer-sidebar"></div>
        </div>
    </div>
</div>
<script src="../build/ckeditor.js"></script>
<script>
    // Application data will be available under a global variable `appData`.
    const appData = {
        // Initial editor data
        data: `<figure class="image">
            <img src="../../assets/img/revision-history-demo.png">
        </figure>
        <h1>PUBLISHING AGREEMENT</h1>
        <h3>Introduction</h3>
        <p>This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”.</p>
        <h3>Grant of Rights</h3>
        <p>The Author grants the Publisher full right and title to the following, in perpetuity:</p>
        <ul>
            <li>To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future.</li>
            <li>To create or devise modified, abridged, or derivative works based on the works listed.</li>
            <li>To allow others to use the listed works at their discretion, without providing additional compensation to the Author.</li>
        </ul>
        <p>These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future.</p>
        <p>Any rights not granted to the Publisher above remain with the Author.</p>
        <p>The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.</p>`,

        // User data.
        user: {
            id: 'u1',
            name: 'Joe Doe',
            // Note that the avatar is optional.
            avatar: 'https://randomuser.me/api/portraits/thumb/men/26.jpg'
        }
    }

    // Plugin that introduces the adapter.
    class RevisionHistoryAdapter {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'RevisionHistoryAdapter';
        }

        static get requires() {
            return [ 'RevisionHistory' ];
        }

        init() {
            const revisionHistory = this.editor.plugins.get( 'RevisionHistory' );

            revisionHistory.adapter = {
                getRevisions: ( { channelId } ) => {
                    // Get all revisions for the document with id `channelId`.
                    // This should be an asynchronous request to your database.
                    return Promise.resolve( [
                        {
                            "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
                            "name": "Initial revision - template",
                            "creatorId": "u1",
                            "createdAt": "2021-05-27T13:22:59.077Z",
                            "attributes": {}
                        },
                        {
                            "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                            "name": "Updated with the actual data",
                            "creatorId": "u1",
                            "createdAt": "2021-05-27T13:23:52.553Z",
                            "attributes": {}
                        },
                        {
                            "id": "e6590c50ccbc86acacb7d27231ad32064",
                            "name": "Inserted logo",
                            "creatorId": "u1",
                            "createdAt": "2021-05-27T13:26:39.252Z",
                            "attributes": {}
                        }
                    ] );
                },
                getRevision: ( { revisionId } ) => {
                    // Get revision data, based on its id.
                    // This should be an asynchronous request to your database.
                    switch ( revisionId ) {
                        case 'e232bd41cc1d6ff75d3b8f100fc9a070b':
                            return Promise.resolve(
                                    {
                                        "id": "e232bd41cc1d6ff75d3b8f100fc9a070b",
                                        "name": "Initial revision - template",
                                        "creatorId": "u1",
                                        "data": {
                                            "main": {
                                                "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]',
                                                "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ………… by and between The Lower Shelf, the “Publisher”, and …………, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him/herself and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A."]}]'
                                            }
                                        },
                                        "createdAt": "2021-05-27T13:22:59.077Z",
                                        "attributes": {}
                                    }
                            );
                        case 'e6f80e6be6ee6057fd5a449ab13fba25d':
                            return Promise.resolve(
                                    {
                                        "id": "e6f80e6be6ee6057fd5a449ab13fba25d",
                                        "name": "Updated with the actual data",
                                        "creatorId": "u1",
                                        "data": {
                                            "main": {
                                                "insertions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","insertion:u1:0"]],"children":[]},"1st",{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]}," ",{"name":"revision-start","attributes":[["name","insertion:u1:1"]],"children":[]},"June 2020 ",{"name":"revision-end","attributes":[["name","insertion:u1:1"]],"children":[]},"by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","insertion:u1:2"]],"children":[]},"John Smith",{"name":"revision-end","attributes":[["name","insertion:u1:2"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                                "deletions": '[{"name":"h1","attributes":[],"children":["PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of ",{"name":"revision-start","attributes":[["name","deletion:u1:0"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]}," by and between The Lower Shelf, the “Publisher”, and ",{"name":"revision-start","attributes":[["name","deletion:u1:1"]],"children":[]},"…………",{"name":"revision-end","attributes":[["name","deletion:u1:1"]],"children":[]},", the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him",{"name":"revision-start","attributes":[["name","deletion:u1:2"]],"children":[]},"/herself",{"name":"revision-end","attributes":[["name","deletion:u1:2"]],"children":[]}," and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature.",{"name":"revision-start","attributes":[["name","deletion:u1:3"]],"children":[]}]},{"name":"p","attributes":[],"children":["Publishing formats are enumerated in Appendix A.",{"name":"revision-end","attributes":[["name","deletion:u1:3"]],"children":[]}]}]'
                                            }
                                        },
                                        "createdAt": "2021-05-27T13:23:52.553Z",
                                        "attributes": {}
                                    }
                            );
                        case 'e6590c50ccbc86acacb7d27231ad32064':
                            return Promise.resolve(
                                    {
                                        "id": "e6590c50ccbc86acacb7d27231ad32064",
                                        "name": "Inserted logo",
                                        "creatorId": "u1",
                                        "data": {
                                            "main": {
                                                "insertions": '[{"name":"figure","attributes":[["data-revision-start-before","insertion:u1:0"],["class","image"]],"children":[{"name":"img","attributes":[["src","../../assets/img/revision-history-demo.png"]],"children":[]}]},{"name":"h1","attributes":[],"children":[{"name":"revision-end","attributes":[["name","insertion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]',
                                                "deletions": '[{"name":"h1","attributes":[["data-revision-start-before","deletion:u1:0"]],"children":[{"name":"revision-end","attributes":[["name","deletion:u1:0"]],"children":[]},"PUBLISHING AGREEMENT"]},{"name":"h3","attributes":[],"children":["Introduction"]},{"name":"p","attributes":[],"children":["This publishing contract, the “contract”, is entered into as of 1st June 2020 by and between The Lower Shelf, the “Publisher”, and John Smith, the “Author”."]},{"name":"h3","attributes":[],"children":["Grant of Rights"]},{"name":"p","attributes":[],"children":["The Author grants the Publisher full right and title to the following, in perpetuity:"]},{"name":"ul","attributes":[],"children":[{"name":"li","attributes":[],"children":["To publish, sell, and profit from the listed works in all languages and formats in existence today and at any point in the future."]},{"name":"li","attributes":[],"children":["To create or devise modified, abridged, or derivative works based on the works listed."]},{"name":"li","attributes":[],"children":["To allow others to use the listed works at their discretion, without providing additional compensation to the Author."]}]},{"name":"p","attributes":[],"children":["These rights are granted by the Author on behalf of him and their successors, heirs, executors, and any other party who may attempt to lay claim to these rights at any point now or in the future."]},{"name":"p","attributes":[],"children":["Any rights not granted to the Publisher above remain with the Author."]},{"name":"p","attributes":[],"children":["The rights granted to the Publisher by the Author shall not be constrained by geographic territories and are considered global in nature."]}]'
                                            }
                                        },
                                        "createdAt": "2021-05-27T13:26:39.252Z",
                                        "attributes": {}
                                    }
                            );
                    }
                },
                updateRevision: revisionData => {
                    // This should be an asynchronous request to your database.
                    return Promise.resolve();
                },
                addRevision: revisionData => {
                    // This should be an asynchronous request to your database.
                    return Promise.resolve();
                }
            };
        }
    }

    // Initialize the users here
    class UsersInit {
        constructor( editor ) {
            this.editor = editor;
        }

        static get pluginName() {
            return 'UsersInit';
        }

        static get requires() {
            return [ 'Users' ];
        }

        init() {
            const users = this.editor.plugins.get( 'Users' );

            users.addUser( appData.user );
            users.defineMe( appData.user.id );
        }
    }

    // In order to load comments and track changes,
    // you should also integrate the comments and track changes adapters.

    ClassicEditor
        .create( document.querySelector( '#editor' ), {
            initialData: appData.data,
            extraPlugins: [ UsersInit, RevisionHistoryAdapter ],
            licenseKey: 'your-license-key',
            sidebar: {
                container: document.querySelector( '#sidebar-container' )
            },
            revisionHistory: {
                editorContainer: document.querySelector( '#editor-container' ),
                viewerContainer: document.querySelector( '#revision-viewer-container' ),
                viewerEditorElement: document.querySelector( '#revision-viewer-editor' ),
                viewerSidebarContainer: document.querySelector( '#revision-viewer-sidebar' )
            },
            toolbar: {
                items: [ 'bold', 'italic', '|', 'revisionHistory' ]
            }
        } )
        .catch( error => console.error( error ) );
</script>

</body>
</html>

# Demo

Revision history adapter actions console:

// Create new version to see the result...

# Saving revisions and autosave

# Saving revisions through API

It is possible to save a revision at any point using the feature’s API, so you can save a revision when the document data is saved:

const revisionTrackerPlugin = this.editor.plugins.get( 'RevisionTracker' );

revisionTrackerPlugin.saveRevision();

You can create a plugin that saves a revision based on your own conditions, e.g. once every 30 minutes or after creating a chosen number of operations. These conditions may also be concurrent.

# Revisions and data autosave

Improvements are planned to allow for the integration between the revision history feature and the autosave feature.

It is required to keep the saved editor data in sync with the saved revisions. Whenever the editor data is saved, a revision should be saved as well. This is easy to achieve for applications that do not use the autosave feature and use “load & save” integration type. In such applications, the document state is saved only at the end of an editing session (e.g. when the form with the editor is submitted) and the revisions can be saved at the same time.

The problem may be more challenging for integrations which use the autosave feature to simplify the document creation process. In such a scenario, saving is triggered frequently whenever the editor content changes. However, saving a revision at every autosave trigger would lead to creating far too many revisions and cluttering the UI.

Instead of using the autosave plugin, it is recommended to create a plugin that would save the data and a revision less frequently and would also add a callback to the beforeunload event that would save the data and a revision when a user tries to close the browser window.

Below is a proposal of such plugin:

import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import PendingActions from '@ckeditor/ckeditor5-core/src/pendingactions';

class RevisionsAutosave extends Plugin {
    static get requires() {
        return [ 'RevisionHistory', PendingActions ];
    }

    static get pluginName() {
        return 'RevisionsAutosave';
    }

    init() {
        this._saveInterval = null;
        this._resetSaveInterval();

        const pendingActions = this.editor.plugins.get( 'PendingActions' );

        window.addEventListener( 'beforeunload', e => {
            // Save data and revisions whenever user tries to leave the website.
            // This will add a pending action if there is anything to save.
            this._save();

            // Prevent from leaving website if there are pending actions.
            if ( pendingActions.hasAny ) {
                e.returnValue = pendingActions.first.message;
            }
        } );
    }

    destroy() {
        clearInterval( this._saveInterval );
    }

    _save() {
        const t = this.editor.t;
        // `RevisionTracker` plugin is required by `RevisionHistory` plugin.
        const revisionTracker = this.editor.plugins.get( 'RevisionTracker' );
        const pendingActions = this.editor.plugins.get( 'PendingActions' );

        // Check if we need to save anything.
        if ( revisionTracker.isDirty ) {
            this._resetSaveInterval();

            // `saveRevision()` will remove `isDirty` flag
            // and add a pending action for revision saving.
            revisionTracker.saveRevision();

            // Add pending action for data saving.
            const pendingAction = pendingActions.add( t( 'Saving changes' ) );

            // Below should be an asynchronous call that saves the data.
            new Promise( res => {
                const data = this.editor.getData();

                // Simulates asynchronous saving delay.
                setTimeout( res, 3000 );
            } ).then( () => {
                // Remove pending action for data saving.
                pendingActions.remove( pendingAction );
            } );
        }
    }

    _resetSaveInterval() {
        clearInterval( this._saveInterval );

        // Save data and revisions every 2 hours
        // in addition to saving when leaving the website.
        const timeoutInMinutes = 120;

        this._saveInterval = setInterval( () => {
            this._save();
        }, timeoutInMinutes * 1000 * 60 );
    }
}