Introduction to Tiny Comments
Contents
-
For guidance on using comments in TinyMCE, refer to: Using comments.
-
For an overview of the TinyMCE Comments plugin, refer to: Overview.
-
For instructions on configuring the Comments plugin, refer to: Getting started with the Comments plugin - Selecting a mode.
Overview
| This plugin is only available for paid TinyMCE subscriptions. |
The Comments plugin allows users to initiate and engage in conversations by adding comments directly within the TinyMCE editor. It also supports viewing all comments in a document for efficient collaboration.
Interactive example
The following demo uses callback mode, the default mode for the Comments plugin. For a demo of embedded mode, see the Embedded mode interactive example.
-
TinyMCE
-
HTML
-
JS
-
Edit on CodePen
<textarea id="comments-callback">
<h2>Welcome to Tiny Comments!</h2>
<p>Please try out this demo of our Tiny Comments premium plugin.</p>
<ol>
<li>Highlight the content you want to comment on.</li>
<li>Click the <em>Add Comment</em> icon in the toolbar.</li>
<li>Type your comment into the text field at the bottom of the Comment sidebar.</li>
<li>Click <strong>Comment</strong>.</li>
</ol>
<p>Your comment is <span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_19679600221621399703915" data-mce-annotation="tinycomments">then</span> attached to the text, <span class="mce-annotation tox-comment" data-mce-annotation-uid="mce-conversation_420304606321716900864126" data-mce-annotation="tinycomments">exactly like this!</span></p>
<p>If you want to take Tiny Comments for a test drive in your own environment, Tiny Comments is one of the premium plugins you can try for free for 14 days by signing up for a Tiny account. Make sure to check out our documentation as well.</p>
<h2>A simple table to play with</h2>
<table style="border-collapse: collapse; width: 100%;" border="1">
<thead>
<tr>
<th>Product</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://www.tiny.cloud/">Tiny Cloud</a></td>
<td>The easiest and most reliable way to integrate powerful rich text editing into your application.</td>
</tr>
<tr>
<td><a href="https://www.tiny.cloud/drive/">Tiny Drive</a></td>
<td>Image and file management for TinyMCE in the cloud.</td>
</tr>
</tbody>
</table>
<p>Thanks for supporting TinyMCE! We hope it helps your users create great content.</p>
</textarea>
const API_URL = 'https://demouserdirectory.tiny.cloud/v1/users';
const user_id = 'james-wilson';
const collaborator_id = 'mia-andersson';
const now = new Date();
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
const anhourago = new Date(now.getTime() - 60 * 60 * 1000).toISOString();
const fakeDelay = 200;
const randomString = () => {
return crypto.getRandomValues(new Uint32Array(1))[0].toString(36).substring(2, 14);
};
const conversationDb = {
'mce-conversation_19679600221621399703915': {
uid: 'mce-conversation_19679600221621399703915',
comments: [{
uid: 'mce-conversation_19679600221621399703915',
author: user_id,
authorName: 'James Wilson',
authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
content: `What do you think about this?`,
createdAt: yesterday,
modifiedAt: yesterday
}, {
uid: 'mce-conversation_19679600221621399703917',
author: collaborator_id,
authorName: 'Mia Andersson',
authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_mia-andersson_128_e6f9424b.jpg',
content: `I think this is a great idea!`,
createdAt: anhourago,
modifiedAt: anhourago,
}]
},
'mce-conversation_420304606321716900864126': {
uid: 'mce-conversation_420304606321716900864126',
comments: [{
uid: 'mce-conversation_420304606321716900864126',
author: collaborator_id,
authorName: 'Mia Andersson',
authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_mia-andersson_128_e6f9424b.jpg',
content: `Please revise this sentence, exclamation points are unprofessional!`,
createdAt: yesterday,
modifiedAt: anhourago
}]
}
};
const tinycomments_create = (req, done, fail) => {
if (req.content === 'fail') {
fail(new Error('Something has gone wrong...'));
} else {
const uid = 'annotation-' + randomString();
conversationDb[uid] = {
uid,
comments: [{
uid,
author: user_id,
authorName: 'James Wilson',
authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
content: req.content,
createdAt: req.createdAt,
modifiedAt: req.createdAt
}]
};
setTimeout(() => done({ conversationUid: uid }), fakeDelay);
}
};
const tinycomments_reply = (req, done) => {
const commentUid = 'annotation-' + randomString();
conversationDb[req.conversationUid].comments.push({
uid: commentUid,
author: user_id,
authorName: 'James Wilson',
authorAvatar: 'https://sneak-preview.tiny.cloud/demouserdirectory/images/employee_james-wilson_128_52f19412.jpg',
content: req.content,
createdAt: req.createdAt,
modifiedAt: req.createdAt
});
setTimeout(() => done({ commentUid: commentUid }), fakeDelay);
};
const tinycomments_delete = (req, done) => {
if (user_id === collaborator_id) { // Replace wth your own logic, e.g. check if user created the conversation
delete conversationDb[req.conversationUid];
setTimeout(() => done({ canDelete: true }), fakeDelay);
} else {
setTimeout(() => done({ canDelete: false, reason: 'Must be admin user' }), fakeDelay);
}
};
const tinycomments_resolve = (req, done) => {
const conversation = conversationDb[req.conversationUid];
if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
delete conversationDb[req.conversationUid];
setTimeout(() => done({ canResolve: true }), fakeDelay);
} else {
setTimeout(() => done({ canResolve: false, reason: 'Must be conversation author' }), fakeDelay);
}
};
const tinycomments_delete_comment = (req, done) => {
const oldcomments = conversationDb[req.conversationUid].comments;
let reason = 'Comment not found';
const newcomments = oldcomments.filter((comment) => {
if (comment.uid === req.commentUid) { // Found the comment to delete
if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
return false; // Remove the comment
} else {
reason = 'Not authorised to delete this comment'; // Update reason
}
}
return true; // Keep the comment
});
if (newcomments.length === oldcomments.length) {
setTimeout(() => done({ canDelete: false, reason }), fakeDelay);
} else {
conversationDb[req.conversationUid].comments = newcomments;
setTimeout(() => done({ canDelete: true }), fakeDelay);
}
};
const tinycomments_edit_comment = (req, done) => {
const oldcomments = conversationDb[req.conversationUid].comments;
let reason = 'Comment not found';
let canEdit = false;
const newcomments = oldcomments.map((comment) => {
if (comment.uid === req.commentUid) { // Found the comment to delete
if (user_id === comment.author) { // Replace with your own logic, e.g. check if user has admin privileges
canEdit = true; // User can edit the comment
return { ...comment, content: req.content, modifiedAt: new Date().toISOString() }; // Update the comment
} else {
reason = 'Not authorised to edit this comment'; // Update reason
}
}
return comment; // Keep the comment
});
if (canEdit) {
conversationDb[req.conversationUid].comments = newcomments;
setTimeout(() => done({ canEdit }), fakeDelay);
} else {
setTimeout(() => done({ canEdit, reason }), fakeDelay);
}
};
const tinycomments_delete_all = (req, done) => {
const conversation = conversationDb[req.conversationUid];
if (user_id === conversation.comments[0].author) { // Replace wth your own logic, e.g. check if user has admin priveleges
delete conversationDb[req.conversationUid];
setTimeout(() => done({ canDelete: true }), fakeDelay);
} else {
setTimeout(() => done({ canDelete: false, reason: 'Must be conversation author' }), fakeDelay);
}
};
const tinycomments_lookup = (req, done) => {
setTimeout(() => {
done({
conversation: {
uid: conversationDb[req.conversationUid].uid,
comments: [...conversationDb[req.conversationUid].comments]
}
});
}, fakeDelay);
};
const tinycomments_fetch = (conversationUids, done) => {
const fetchedConversations = {};
conversationUids.forEach((uid) => {
const conversation = conversationDb[uid];
if (conversation) {
fetchedConversations[uid] = {...conversation};
}
});
setTimeout(() => done({ conversations: fetchedConversations }), fakeDelay);
};
tinymce.init({
selector: 'textarea#comments-callback',
toolbar: 'addcomment showcomments code | bold italic underline',
menubar: 'file edit view insert format tools tc help',
menu: {
tc: {
title: 'TinyComments',
items: 'addcomment showcomments deleteallconversations'
}
},
plugins: [ 'tinycomments', 'help', 'code', 'quickbars', 'link', 'lists', 'image' ],
quickbars_selection_toolbar: 'alignleft aligncenter alignright | addcomment showcomments',
quickbars_image_toolbar: 'alignleft aligncenter alignright | rotateleft rotateright | imageoptions',
tinycomments_mode: 'callback',
tinycomments_mentions_enabled: true,
sidebar_show: 'showcomments',
tinycomments_create,
tinycomments_reply,
tinycomments_delete,
tinycomments_resolve,
tinycomments_delete_all,
tinycomments_lookup,
tinycomments_delete_comment,
tinycomments_edit_comment,
tinycomments_fetch,
user_id,
fetch_users: (userIds) => Promise.all(userIds
.map((userId) =>
fetch(`${API_URL}/${userId}`)
.then((response) => response.json())
.catch(() => ({ id: userId }))))
});
Collaborate seamlessly within your content
With the Comments plugin, users can:
-
Collaborate on content through an intuitive interface for creating and replying to comments.
-
Control the ability to delete or resolve individual comments or entire comment threads.
Key features of the Comments plugin
The Comments plugin enables users to:
-
Create comments
-
Edit comments
-
Reply to comments
-
Search for comments
-
Resolve comment threads
-
Delete comments or entire threads
Supported Content
-
Text
-
Blocks
|
Comments supports the following features in TinyMCE 6.1 and later:
|
Getting started with the Comments plugin - Selecting a mode
The Comments plugin offers two modes:
Callback Mode
This default mode stores comments externally, such as in a database, and requires callback functions to manage comment data. For setup details, refer to: Configuring the Comments plugin in Callback mode.
Embedded Mode
In this mode, comments are stored directly within the content, without needing callback configuration. For setup details, refer to: Configuring the Comments plugin in Embedded mode.
| Comments are only supported when TinyMCE is run in classic mode. Comments require the Sidebar, and Sidebars are not supported in inline or distraction free mode. For more information on the differences between the editing modes, see Inline editing mode. |