Track 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.
*/
addSuggestion( suggestionData ) {}
/**
* Returns the suggestion data for a given ID.
*
* @param {String} id The suggestion ID.
* @returns {Object}
*/
getSuggestion( id ) {}
/**
* Iterates through the suggestion data for all suggestions that are
* present in the editor content.
*
* @returns {Array.<Object>}
*/
getSuggestions() {}
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 = {
...
};
# Suggestion data object
Below is the suggestion data structure expected by TrackChanges#addSuggestion()
and returned by TrackChanges#getSuggestion()
as well as TrackChanges#getSuggestions()
.
{
id: 'suggestion-1', // String
type: 'insertion', // String
authorId: 'user-1', // String
createdAt: new Date( ... ), // Date
hasComments: false // Boolean
data: { ... } // Object|null
}
# 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.
*
* @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 {String} [suggestionData.originalSuggestionId] The ID of the suggestion from which
* the `authorId` property should be taken.
* @param {Object|null} [suggestionData.data] Additional suggestion data. Used by format suggestions.
* @returns {Promise}
*/
addSuggestion( suggestionData ) {}
/**
* Called each time the suggestion data has changed. The only data that
* may change is information whether a suggestion has comments or not, and the suggestion state.
* When the first comment is added to the suggestion, the only
* comment is removed from the suggestion or the suggestion was accepted or rejected,
* the `updateSuggestion()` method is called with proper data.
*
* For suggestions with `hasComments` set to `false`, the editor
* will not try to fetch the comment thread through the comments adapter.
*
* The method should update the suggestion data in the database
* and return a promise that should be resolved when the save is
* completed.
*
* @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 Sets the suggestion state.
* @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 {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 rejected.
*
* @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.
*
* @readonly
* @type {CommentsListView}
*/
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.
*
* @readonly
* @type {CommentThreadInputView}
*/
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 }` )
],
'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}.
*/
SuggestionThreadView
Using the SuggestionThreadView
property is described in the Annotation customization section.