Report an issue

guideTrack changes API

# TrackChanges

Below is the public track changes API provided by the TrackChanges plugin.

/**
 * An adapter object that should communicate with the data source
 * to fetch or save the suggestion data.
 */
adapter

/**
 * Adds suggestion data.
 *
 * Use this method to load the suggestion data during the editor initialization
 * if you do not use the adapter integration.
 *
 * @param {Object} suggestionData The data of a suggestion to add.
 * @returns {Suggestion} Suggestion instance.
 */
addSuggestion( suggestionData ) {}

/**
 * Returns the suggestion instance for a given ID.
 *
 * @param {String} id The suggestion ID.
 * @returns {Suggestion}
 */
getSuggestion( id ) {}

/**
 * Returns a list of suggestions.
 *
 * You can provide filtering options to narrow down the results set.
 *
 * @param {Object} [options]
 * @param {Boolean} [options.skipNotAttached=false] Skip removed comment threads.
 * @param {Boolean} [options.toJSON=false] Return the data in the JSON format.
 * @returns {Array.<Suggestion|Object>}
 */
getSuggestions( options ) {}

To use this API, you need to get the track changes plugin:

// Get the track changes plugin:
const trackChangesPlugin = editor.plugins.get( 'TrackChanges' );

// Add a suggestion:
trackChangesPlugin.addSuggestion( { ... } );

// Get all suggestions:
trackChangesPlugin.getSuggestions();

// Set the adapter:
trackChangesPlugin.adapter = {
    ...
};

# TrackChangesData

TrackChangesData is a plugin that allows getting the editor data with all the suggestions accepted or discarded. Learn how to use this plugin in the dedicated guide.

/**
 * Returns the editor data with all the suggestions accepted.
 *
 * @param {Object} [options] Options for `Editor#getData()`.
 * @returns {Promise.<String>} Promise which resolves with the output data.
 */
getDataWithAcceptedSuggestions( options ) {}

/**
 * Returns the editor data with all the suggestions discarded.
 *
 * @param {Object} [options] Options for `Editor#getData()`.
 * @returns {Promise.<String>} Promise which resolves with the output data.
 */
getDataWithDiscardedSuggestions( options ) {}

TrackChangesData plugin is configurable:

/**
 * A callback that creates an editor instance.
 *
 * The callback receives two parameters:
 *
 * * `config` - editor configuration to be used,
 * * `elementCreator` - a function that creates a DOM element
 * that can be used for the editor initialization.
 *
 * The callback should return a promise that resolves with the editor instance.
 */
editorCreator

# Suggestion

Represents a suggestion created when the track changes feature is turned on.

/**
 * Suggestion ID.
 *
 * @readonly
 * @type {String}
 */
id

/**
 * Suggestion type.
 *
 * @readonly
 * @type {String}
 */
type

/**
 * Suggestion sub-type.
 *
 * This is an additional identifier for suggestions.
 * Two suggestions with the same type may have different
 * sub-types to differentiate suggestions behavior and
 * handle interactions between suggestions.
 *
 * @readonly
 * @type {String|null}
 */
subType

/**
 * Suggestion author.
 *
 * @readonly
 * @type {User}
 */
author

/**
 * Additional suggestion data.
 *
 * @readonly
 * @member {Object|null} #data
 */
data

/**
 * Custom suggestion attributes.
 * See also `setAttribute()` and `removeAttribute()`.
 *
 * @readonly
 * @observable
 * @type {Object}
 */
attributes

/**
 * Date when the suggestion was created.
 *
 * @readonly
 * @observable
 * @member {Date|null}
 */
createdAt

/**
 * Previous suggestion in the suggestions chain.
 *
 * Chained suggestions should be handled as one entity.
 *
 * @readonly
 * @observable
 * @member {Suggestion|null}
 */
previous

/**
 * Next suggestion in the suggestions chain.
 *
 * Chained suggestions should be handled as one entity.
 *
 * @readonly
 * @observable
 * @member {Suggestion|null}
 */
next

/**
 * Comment thread model for comments added to this suggestion.
 *
 * @readonly
 * @member {CommentThread}
 */
commentThread

/**
 * The first suggestion in this suggestion chain.
 *
 * @readonly
 * @type {Suggestion}
 */
head

/**
 * Informs whether a suggestion has at least one comment.
 *
 * @readonly
 * @type {Boolean}
 */
hasComments

/**
 * Informs whether the suggestion is a multi-range
 * suggestion or a single-range suggestion.
 *
 * This is evaluated based on the marker name
 * belonging to this suggestion. Even if only one
 * marker belongs to the suggestion at a given time,
 * it can still be a multi-range suggestion.
 *
 * @readonly
 * @type {Boolean}
 */
isMultiRange

/**
 * Informs whether the suggestion is still in the editor content.
 *
 * Returns `true` if there is at least one marker suggestion
 * in the editor content.
 *
 * @readonly
 * @type {Boolean}
 */
isInContent

/**
 * Binds the given marker name to this suggestion.
 *
 * @param {String} markerName
 */
addMarkerName( markerName ) {}

/**
 * Returns all names of markers belonging to this suggestion.
 *
 * @returns {Array.<String>}
 */
getMarkerNames() {}

/**
 * Returns all markers belonging to this suggestion.
 *
 * @returns {Array.<module:engine/model/markercollection~Marker>}
 */
getMarkers() {}

/**
 * Returns the first marker belonging to this suggestion,
 * i.e the first marker that was added to this suggestion
 * that has not been removed yet.
 *
 * @returns {module:engine/model/markercollection~Marker|null}
 */
getFirstMarker() {}

/**
 * Removes all markers from the suggestion and the editor content.
 */
removeMarkers() {}

/**
 * Removes a marker with the given name from the suggestion and the editor content.
 *
 * @param {String} markerName The name of the marker to remove.
 */
removeMarker( markerName ) {}

/**
 * Adds a new range to this suggestion.
 * It is assumed that the suggestion is a multi-range suggestion.
 *
 * A marker name is generated from this suggestion's
 * properties and a marker with that name is created
 * and set to a given `range`.
 *
 * @param {module:engine/model/range~Range} range
 */
addRange( range ) {}

/**
 * Checks if the given `range` is intersecting with any of
 * the ranges of markers belonging to this suggestion.
 *
 * @param {module:engine/model/range~Range} range
 * @returns {Boolean}
 */
isIntersectingWithRange( range ) {}

/**
 * Returns all ranges of all markers belonging to this suggestion.
 *
 * @returns {Array.<module:engine/model/range~Range>}
 */
getRanges() {}

/**
 * Returns all model items that are in this suggestion.
 *
 * @returns {Array.<module:engine/model/item~Item>}
 */
getItems() {}

/**
 * Returns the first range belonging to this suggestion,
 * i.e. the range of the first marker that was added to
 * this suggestion and has not been removed yet.
 *
 * This method is useful if you know that the suggestion has
 * only one marker and want to process its range.
 *
 * @returns {module:engine/model/range~Range}
 */
getFirstRange() {}

/**
 * Returns the model element contained in the suggestion.
 *
 * A model element is considered as contained if there is exactly
 * one range in the suggestion, and that range contains exactly one element.
 *
 * Returns `null` if there is no contained element.
 *
 * @returns {module:engine/model/element~Element|null}
 */
getContainedElement() {}

/**
 * Accepts the suggestion.
 */
accept() {}

/**
 * Discards the suggestion.
 */
discard() {}

/**
 * Returns all suggestions that are in this suggestion chain.
 *
 * @returns {Array.<Suggestion>}
 */
getAllAdjacentSuggestions() {}

/**
 * Adds a suggestion attribute.
 *
 * Suggestion attributes are custom data that can be set and used by features
 * built around suggestions. Use it to store your feature data with other suggestion data.
 *
 *      suggestion.setAttribute( 'isImportant', true );
 *
 * You can group multiple values in an object using dot notation:
 *
 *      suggestion.setAttribute( 'customData.type', 'image' );
 *      suggestion.setAttribute( 'customData.src', 'foo.jpg' );
 *
 * Attributes set on the suggestion can be accessed through the `attribute` property:
 *
 *      const isImportant = suggestion.attributes.isImportant;
 *      const type = suggestion.attributes.customData.type;
 *
 * You can also observe the `attributes` property or bind other properties to it:
 *
 *      myObj.bind( 'customData' ).to( suggestion, 'attributes', attributes => attributes.customData );
 *
 * Whenever `setAttribute()` or `removeAttribute()` is called, the `attributes` property
 * is re-set and observables are refreshed.
 *
 * @param {String} name Attribute name.
 * @param {*} value Attribute value.
 */
setAttribute( name, value ) {}

/**
 * Removes a suggestion attribute.
 *
 * See also `setAttribute()`.
 *
 * @param {String} name Attribute name.
 */
removeAttribute( name ) {}

# Track changes adapter

The track changes adapter is an object that communicates asynchronously with the data source to fetch or save the suggestion data. It is used internally by the track changes feature whenever a suggestion is loaded, created or deleted.

The adapter is optional. You might need to provide it if you are using the track changes feature without real-time collaboration.

To set the adapter, overwrite the TrackChanges#adapter property.

/**
 * Called each time the suggestion data is needed.
 *
 * The method should return a promise that resolves with the suggestion data object.
 *
 * @param {String} id The ID of the suggestion to get.
 * @returns {Promise}
 */
getSuggestion( id ) {}

/**
 * Called each time a new suggestion is created.
 *
 * The method should save the suggestion data in the database
 * and return a promise that should be resolved when the save is
 * completed.
 *
 * If the promise resolves with an object with the `createdAt` property,
 * this suggestion property will be updated in the suggestion in the editor.
 * This lets you update the suggestion data with server-side information.
 *
 * The `suggestionData` object does not expect the `authorId` property.
 * For security reasons, the author of the suggestion should be set
 * on the server side.
 *
 * If `suggestionData.originalSuggestionId` is set, the new suggestion should
 * have the `authorId` property set to the same as the suggestion with
 * `originalSuggestionId`. This happens when one user breaks
 * another user's suggestion, creating a new suggestion as a result.
 *
 * In any other case, use the current (local) user to set `authorId`.
 *
 * The `suggestionData` object does not expect the `createdAt` property either.
 * You should use the server-side time generator to ensure that all users
 * see the same date.
 *
 * It is recommended to stringify the `suggestionData.attributes` value to JSON
 * and to save it as a string in your database and then to parse the
 * value from JSON when loading suggestions.
 *
 * @param {Object} suggestionData
 * @param {String} suggestionData.id The suggestion ID.
 * @param {String} suggestionData.type The suggestion type. It may include a subtype - the
 * format is then <type>:<subtype>.
 * @param {Boolean} suggestionData.hasComments Always `false`. A new suggestion does not have comments.
 * @param {Object|null} suggestionData.data Additional suggestion data. Used by format suggestions.
 * @param {Object} suggestionData.attributes Custom suggestion attributes.
 * @param {String|null} suggestionData.originalSuggestionId The ID of the suggestion from which
 * the `authorId` property should be taken.
 * @returns {Promise}
 */
addSuggestion( suggestionData ) {}

/**
 * Called each time the suggestion properties change.
 *
 * The method should update the suggestion properties in the database
 * and return a promise that should be resolved when the save is
 * completed.
 *
 * Keep in mind that the `data` parameter only contains the
 * properties of a suggestion that changed.
 *
 * @param {String} id The suggestion ID.
 * @param {Object} suggestionData The suggestion data to update.
 * @param {Boolean} [suggestionData.hasComments] Defines if the suggestion has comments or not.
 * @param {'open'|'accepted'|'rejected'} [suggestionData.state] The suggestion state.
 * @param {Object} [suggestionData.attributes] Custom suggestion attributes.
 * @returns {Promise}
 */
updateSuggestion( id, suggestionData ) {}

# Commands

The track changes plugin adds a set of commands to the editor. These commands are executed when you turn on the track changes mode, accept a suggestion, etc. You do not need to care about the commands if you are integrating the track changes plugin together with the UI part. However, knowing about them might be useful if you are working on a custom UI.

The following commands are available:

  • trackChanges – Toggles the track changes mode in the editor.
  • acceptSuggestion – Accepts a suggestion with the specified ID.
  • discardSuggestion – Discards a suggestion with the specified ID.
  • acceptAllSuggestions – Accepts all suggestions.
  • discardAllSuggestions – Discards all suggestions.
  • acceptSelectedSuggestions – Accepts all suggestions in the current selection.
  • discardSelectedSuggestions – Discards all suggestions in the current selection.

Examples:

editor.execute( 'trackChanges' );
editor.execute( 'acceptSuggestion', 'suggestion-1' );
editor.execute( 'discardSuggestion', 'suggestion-1' );
editor.execute( 'acceptAllSuggestions' );
editor.execute( 'discardAllSuggestions' );
editor.execute( 'acceptSelectedSuggestions' );
editor.execute( 'discardSelectedSuggestions' );

Note that there is no command to add a suggestion. This is because suggestions are added automatically when editing commands are executed while the editor is in track changes mode. For instance:

// Turn on the track changes mode:
editor.execute( 'trackChanges' );

// Insert some text. It will be automatically inserted as a suggestion:
editor.execute( 'input', { text: 'foo' } );

# Suggestion views

# BaseSuggestionThreadView

Available in @ckeditor/ckeditor5-track-changes/src/ui/view/basesuggestionthreadview.js.

An abstract suggestion thread view class that should be used as a base for suggestion thread view implementations.
It provides some behaviors, flags and building blocks to use when creating a custom suggestion thread view class.

Extends module:ui/view~View.

/**
 * @param {module:utils/locale~Locale} locale The localization services instance.
 * @param {Suggestion} model The model on which the view will base.
 * @param {User} localUser The current local user.
 * @param {Object} config Additional view configuration.
 * @param {Function} config.CommentView The view class to be used to create comment views.
 * @param {Boolean} config.disableComments When set to `true`, comments for suggestions are disabled (not displayed).
 * @param {Number} config.maxCommentsWhenCollapsed The number of comments shown when the thread is collapsed.
 * @param {Number} config.maxThreadTotalWeight The maximal weight of the comments for which the view will not collapse when not active.
 * @param {Number} config.maxCommentCharsWhenCollapsed The number of characters to which the comment content will be trimmed when
 * the suggestion thread view is collapsed.
 * @param {Function} config.formatDateTime A function that takes a `Date` object, formats it to a desired string and returns it.
 * It should be used when displaying the comments creation date.
 * @param {module:core/editor/editorconfig~EditorConfig} [config.editorConfig={}] Optional custom configuration for the comment editor.
 * It is used for the editor created for adding a new comment and editing an existing comment.
 */
constructor( locale, model, localUser, config ) {}

/**
 * Informs whether the comment thread view is in an active state ("highlighted").
 * Comment thread view is in this state when it is focused or was activated by the user in any different way.
 *
 * @observable
 * @type {Boolean}
 */
isActive

/**
 * Suggestion creation date.
 *
 * @observable
 * @type {Date}
 */
createdAt

/**
 * Stores description entries generated for this suggestion that describe the suggested change.
 * They are used to create the final description presented in the suggestion view to the user.
 *
 * Note that one suggestion may include multiple changes.
 *
 * Most cases are simple and include just one description item:
 *
 *  [
 *      { type: 'insertion', content: '*Insert:* "Foo"' }
 *  ]
 *
 * This description item represents a suggestion for inserting the "Foo" text.
 * The `type` property describes the performed action while the `content` property
 * contains additional information about the action and is optional.
 *
 * A more complex example is presented below:
 *
 *  [
 *      { type: 'insertion', content: '*Insert:* 2 paragraphs' },
 *      { type: 'insertion', content: '*Insert:* image },
 *      { type: 'replace', content: '*Replace:* "Foo" *with* "Bar"' }
 *  ]
 *
 * In this example, there are three description items (or lines). Two new (empty) paragraphs were added,
 * an image was added and then the "Foo" text was replaced by "Bar". The above structure could be rendered as:
 *
 *  <p><strong>Insert:</strong> 2 paragraphs</p>
 *  <p><strong>Insert:</strong> image</p>
 *  <p><strong>Replace:</strong> "Foo" <strong>with</strong> "Bar"</p>
 *
 * @observable
 * @type {Array.<Object>}
 */
descriptionParts

/**
 * Informs whether a suggestion thread has any changes that have not been saved.
 *
 * @observable
 * @type {Boolean}
 */
isDirty

/**
 * Informs whether a suggestion can be accepted or discarded.
 *
 * @observable
 * @type {Boolean}
 */
isEnabled

/**
 * Suggestion model.
 *
 * @protected
 * @readonly
 * @type {Suggestion}
 */
_model

/**
 * Local user.
 *
 * @protected
 * @readonly
 * @type {User}
 */
_localUser

/**
 * The configuration for the whole suggestion thread view.
 *
 * @protected
 * @readonly
 * @type {Object}
 */
_config

/**
 * The list of comment views. It should be used as a part of the view template if present.
 *
 * @readonly
 * @type {CommentsListView|null}
 */
commentsListView

/**
 * The number of items in the view, where the suggestion itself counts as one.
 *
 * In other words, it is equal to the number of comments in the suggestion thread view plus one.
 *
 * @observable
 * @readonly
 * @type {Number}
 */
length

/**
 * Comment input area view. It should be used as a part of the view template when present.
 *
 * @readonly
 * @type {CommentThreadInputView|null}
 */
commentThreadInputView

Events fired by BaseSuggestionThreadView to communicate with CKEditor 5 collaboration features:

/**
 * Fired when a user performed an action that should lead to accepting the suggestion.
 *
 * This event is not fired by default by any component created by `BaseSuggestionThreadView`.
 * If you create a view class extending `BaseSuggestionCommentView`, you should provide
 * a UI element that will fire this event.
 */
acceptSuggestion

/**
 * Fired when a user performed an action that should lead to discarding the suggestion.
 *
 * This event is not fired by default by any component created by `BaseSuggestionThreadView`.
 * If you create a view class extending `BaseSuggestionCommentView`, you should provide
 * a UI element that will fire this event.
 */
discardSuggestion

All events fired by BaseCommentView are delegated to BaseSuggestionThreadView. This means that BaseSuggestionThreadView can also fire these events to communicate with CKEditor 5 collaboration features.

# SuggestionThreadView

Available in @ckeditor/ckeditor5-track-changes/src/ui/view/suggestionthreadview.js.

The default view for a suggestion thread.

Extends BaseSuggestionThreadView.

/**
 * The suggestion thread view type. Used for styling.
 *
 * This value is evaluated based on the type of the first item in `#descriptionParts`.
 *
 * @observable
 * @type {'format'|'replace'|'deletion'|'insertion'}
 */
type

/**
 * Suggestion description explaining what has been changed.
 * To be presented to the user. Based on `#descriptionParts`.
 *
 * @observable
 * @readonly
 * @type {String}
 */
description

/**
 * Button view for the button that accepts the suggestion.
 *
 * @type {module:ui/button/buttonview~ButtonView}
 */
acceptButton

/**
 * Button view for the button that discards the suggestion.
 *
 * @type {module:ui/button/buttonview~ButtonView}
 */
discardButton

/**
 * User view for the suggestion author.
 *
 * @type {module:ui/view~View}
 */
userView

/**
 * An element that contains the suggestion description.
 * It is cached in the `view#render()` call for performance reasons.
 *
 * @protected
 * @member {HTMLElement|null} #_descriptionElement
 */
_descriptionElement

/**
 * Returns a template definition that will be passed to `View#setTemplate()`.
 *
 * Overwrite this method if you want to set a custom template for the suggestion thread view.
 *
 * @protected
 * @returns {module:ui/template~TemplateDefinition} The definition of a suggestion thread view's template.
 */
_getTemplate() {}

SuggestionThreadView by default uses the following template. It is returned by SuggestionThreadView#_getTemplate()).

{
    tag: 'div',

    attributes: {
        class: [
            'ck-suggestion-wrapper',
            bind.if( 'isActive', 'ck-suggestion-wrapper--active' ),
            bind.to( 'type', value => `ck-suggestion-${ value }` ),
            this._config.disableComments && 'ck-suggestion--disabled-comments'
        ],
        'data-suggestion-id': this._model.id,
        'data-thread-id': this._model.commentThread.id,
        'data-author-id': this._model.author.id,
        // Needed for managing focus after adding a new comment.
        tabindex: -1
    },

    children: [
        {
            tag: 'div',

            attributes: {
                class: [
                    'ck-suggestion',
                    'ck-annotation'
                ]
            },

            children: [
                this.userView,
                {
                    tag: 'div',

                    attributes: {
                        class: [ 'ck-suggestion__main', 'ck-annotation__main' ]
                    },

                    children: [
                        {
                            tag: 'div',

                            attributes: {
                                class: [ 'ck-suggestion__info', 'ck-annotation__info' ]
                            },

                            children: [
                                {
                                    tag: 'span',

                                    children: [
                                        {
                                            text: this.userView.name
                                        }
                                    ],

                                    attributes: {
                                        class: [ 'ck-suggestion__info-name', 'ck-annotation__info-name' ]
                                    }
                                },
                                {
                                    tag: 'time',

                                    attributes: {
                                        datetime: bind.to( 'createdAt' ),
                                        class: [ 'ck-comment__info-time', 'ck-annotation__info-time' ]
                                    },

                                    children: [
                                        {
                                            text: bind.to( 'createdAt', value => this._config.formatDateTime( value ) )
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            tag: 'div',

                            attributes: {
                                class: [
                                    'ck-suggestion__actions',
                                    'ck-annotation__actions'
                                ]
                            },

                            children: [
                                this.acceptButton,
                                this.discardButton
                            ]
                        },
                        {
                            tag: 'div',

                            attributes: {
                                class: [ 'ck-annotation__content-wrapper' ]
                            }
                        }
                    ]
                }
            ]
        },
        this.commentsListView,
        this.commentThreadInputView
    ]
}

# Configuration

/**
 * View class to be used to create suggestion thread views
 * (used as annotations - in sidebar balloons or in inline annotations).
 *
 * @type {module:comments/comments/ui/view/basesuggestionthreadview~BaseSuggestionThreadView}.
 */
editorConfig.trackChanges.SuggestionThreadView

/**
 * A property that specifies if the comments for suggestions are enabled or disabled.
 * When the option is set to `true`, the comment thread UI for suggestions will be hidden
 * and commenting suggestions will be disabled.
 *
 * Suggestion thread views will be marked with the additional `ck-suggestion--disabled-comments` class when the suggestion comments
 * are disabled.
 *
 * @type {Boolean}
 */
editorConfig.trackChanges.disableComments

Using the SuggestionThreadView property is described in the Annotation customization section.