Comments 1.0
Tiny Comments 1.0 provides the ability to add comments to the content and collaborate with other users for content editing.
Contribute to this pageIntroduction
The Comments 1.0 plugin provides the user an ability to start or join a conversation by adding comments to the content within the TinyMCE editor. The Comments 1.0 plugin is built upon the Annotations API and uses annotations to create comment threads (conversations).
This section describes the various configuration options for the Comments 1.0 plugin.
Storage
Like TinyMCE, the Comments 1.0 plugin does not directly provide the user an ability to save the comments. User needs to configure the storage to be able to save comments on their server. Storage settings can be configured to either persist them immediately or save them at the same time as the content.
How these comments are stored affects when other users see new comments. The Comments 1.0 functions (create, reply, delete, and lookup) are configured differently depending upon the server-side storage configuration.
In this chapter, we have provided examples of both ways of configuring Comments 1.0 storage.
Storage - persist in real-time
The following demo showcases the Comments 1.0 functionality using storage configured to persist in real-time:
See the Pen pOzxJw by TinyMCE (@tinymce) on CodePen.
Storage - persist on content-save
The following demo showcases the Comments 1.0 functionality using storage configured to persist on content-save.
See the Pen 4d07e4da27b1e7245b5333ed7413083b by TinyMCE (@tinymce) on CodePen.
Helper Functions
We have used the following helper functions in our demo above:
setConversation(uid, conversation)
setConversation
is a function written to synchronously write a conversation to a form field for submission to the server later.randomString()
randomString()
is a function used in thecreate
function to return a 62-bits random strings to provision a large number of UIDs.getConversation(uid)
getConversation
is a function written to synchronously retrieve an existing conversation from a form field populated by the server.deleteConversation(uid)
deleteConversation(uid)
is a function to allow only the first commenter to delete a comment.getAuthorDisplayName(uid)
getAuthorDisplayName(authorID)
is a function to retrieve an existing conversation via a conversation UID (authorID
in our example).
Comments 1.0 Implementation Functions
Comments 1.0 requires four functions to be defined:
tinymce.init({
...
tinycomments_create: create,
tinycomments_reply: reply,
tinycomments_delete: del,
tinycomments_lookup: lookup
});
All functions incorporate done
and fail
callbacks as parameters. The function return type is not important, but all functions must call one of these two callbacks.
If comments are being persisted to a form field to be persisted on document save, an appropriate callback is likely called prior to the function returning.
However, if comments are being persisted directly back to a server as they are made, they are called asynchronously after the network call to do so had completed.
Considerations
Display Names
Comments 1.0 expects each comment to contain the author's display name, not a user ID, as Comments 1.0 does not know the user identities. The implementation of lookup
will most likely need to consider this and resolve user identifiers to an appropriate display name.
Current Author
Comments 1.0 does not know the name of the current user. After a user comments (triggering create
for the first comment, or reply
for subsequent comments) Comments 1.0 requests the updated conversation via lookup
, which should now contain the additional comment with the proper author. Determining the current user, and storing the comment related to that user, has to be done by the user.
Create
Comments 1.0 uses the Conversation create
function to create a comment.
The create
function saves the comment as a new conversation and returns a unique conversation ID via the done
callback. If an unrecoverable error occurs, it should indicate this with the fail
callback.
The following are examples of how create
can be implemented if storage settings are configured to be either persistent in real time or on content-save.
Example - Storage - persist in real-time
Here is an example of how create
can be implemented using storage configured to persist in real-time:
function create(content, done, fail) {
fetch(
'https://api.example/conversations/',
{ method: 'POST', body: content }
).then(function(response) {
return response.json();
}).then(function(json) {
done(json.uid);
}).catch(function() {
fail(new Error('Something has gone wrong...'));
});
}
Example - Storage - persist on content-save
Here is an example of how create
can be implemented using storage configured to persist on content-save:
var currentAuthorId = ...
function create(content, done, fail) {
// `randomString` should be written to produce random strings with a very low
// chance of collisions.
var uid = 'annotation-' + randomString();
try {
// `setConversation` here is a function written to synchronously persist
// the new conversation to a form field for later submission to the server
setConversation(
uid,
[ { user: currentAuthorId, comment: content } ]
);
done(uid);
} catch {
fail(new Error('Error creating conversation...'));
}
}
Reply
Comments 1.0 uses the Conversation reply
function to reply to a comment.
The reply
function saves the comment as a reply to an existing conversation and returns via the done
callback once successful. Unrecoverable errors are communicated to TinyMCE by calling the fail
callback instead.
The following are examples of how reply
can be implemented if storage settings are configured to be either persistent in real time or on content-save.
Example - Storage - persist in real-time
Here is an example of how reply
can be implemented using storage configured to persist in real-time:
function reply(uid, content, done, fail) {
fetch(
'https://api.example/conversations/'+uid,
{ method: 'PATCH', body: content }
).then(function(response) {
if (response.ok) {
done();
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
Example - Storage - persist on content-save
Here is an example of how reply
can be implemented using storage configured to persist on content-save:
var currentAuthorId = ...
function reply(uid, content, done, fail) {
try {
// "getConversation" here is a function written to synchronously retrieve an
// existing conversation from a form field populated by the server.
var comments = getConversation(uid);
// Add comment to the conversation
comments.push({
user: currentAuthorId,
comment: content
});
// Synchronously write the comment back to the form field, awaiting persist
// on document save.
setConversation(uid, comments);
done();
} catch {
fail(new Error('Error replying to conversation...'));
}
}
Delete
Comments 1.0 uses the Conversation delete
function to delete an entire conversation.
The delete
function should asynchronously return a flag indicating whether the comment/comment thread was removed using the done
callback. Unrecoverable errors are communicated to TinyMCE by calling the fail
callback instead.
The following are examples of how delete
can be implemented if storage settings are configured to be either persistent in real time or on content-save.
Example - Storage - persist in real-time
Here is an example of how delete
can be implemented using storage configured to persist in real-time:
function del(uid, done, fail) {
fetch(
'https://api.example/conversations/'+uid,
{ method: 'DELETE' }
).then(function(response) {
if (response.ok) {
done(true);
} else if (response.status == 403) {
done(false)
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
Example - Storage - persist on content-save
Here is an example of how delete
can be implemented using storage configured to persist on content-save:
function del(uid, done, fail) {
fetch(
'https://api.example/conversations/'+uid,
{ method: 'DELETE' }
).then(function(response) {
if (response.ok) {
done(true);
} else if (response.status == 403) {
done(false)
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
Note: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
Lookup
Comments 1.0 uses the Conversation lookup
function to retrieve an existing conversation via a conversation unique ID.
The conventional conversation object structure that should be returned via the done
callback is as follows:
Conversation object
{
"comments": [
<comment1>,
<comment2>,
...
]
}
Comment object
{
"author": "Author Display Name",
"content": "This is the text of the comment"
}
The following are examples of how lookup
can be implemented if storage settings are configured to be either persistent in real time or on content-save.
Example - Storage - persist in real-time
Here is an example of how lookup
can be implemented using storage configured to persist in real-time:
function lookup(uid, done, fail) {
fetch('https://api.example/conversations/'+uid)
.then(function(response) { return response.json(); })
.then(function(json) {
var conversation = json.comments;
return fetch('https://api.example/users/')
.then(function(response) { return response.json(); })
.then(function(json) {
var users = json.users;
var unknown = { displayName: 'Unknown' };
return conversation.map(function(item) {
var user = users.find(function(v) { return v.id == item.user; });
return {
author: (user || unknown).displayName,
content: item.comment
};
});
});
})
.then(function(comments) {
done({ comments: comments });
})
.catch(function() {
fail(new Error('Something has gone wrong...'));
})
}
Example - Storage - persist on content-save
Here is an example of how lookup
can be implemented using storage configured to persist on content-save, utilizing an in-memory lookup function to resolve author display names:
function lookup(uid, done, fail) {
try {
var comments = getConversation(uid).map(function(item) {
return {
author: getAuthorDisplayName(item.user),
content: item.comment
};
});
done({ comments: comments });
} catch {
fail(new Error('Error looking up conversation...'));
}
}
For more information on Comments 1.0 commercial feature, visit our Premium Features page.
Was this article helpful? Yes - No
Well, that's awkward . Would you mind opening an issue or helping us out?
Thanks for the feedback!
Can't find what you're looking for? Let us know.
Except as otherwise noted, the content of this page is licensed under the Creative Commons BY-NC-SA 3.0 License, and code samples are licensed under the Apache 2.0 License.