Browse Source

DOC-840: fixing the comments docs (#2007)

Co-authored-by: Lee Newson <lee.newson@tiny.cloud>
pull/2018/head
Tyler Kelly 4 years ago
committed by tylerkelly13
parent
commit
f57635dcb9
  1. 8
      _data/nav.yml
  2. 2
      _includes/configuration/contextmenu.md
  3. 214
      _includes/live-demos/comments-callback/example.js
  4. 32
      _includes/live-demos/comments-callback/index.html
  5. 686
      _includes/live-demos/comments-callback/index.js
  6. 0
      _includes/live-demos/comments-embedded/index.html
  7. 10
      _includes/live-demos/comments-embedded/index.js
  8. 2
      _includes/live-demos/full-featured/example.js
  9. 2
      _includes/live-demos/full-featured/index.js
  10. 2
      _includes/live-demos/readonly-demo/index.html
  11. 28
      _includes/live-demos/readonly-demo/style.css
  12. 9
      _includes/plugins/comments_embed_fullpage_issues.md
  13. 9
      _includes/plugins/comments_highlighting_css.md
  14. 45
      _includes/plugins/comments_open_sidebar.md
  15. 483
      advanced/configuring-comments-callbacks.md
  16. 2
      demo/comments-2.md
  17. 4
      demo/pageembed.md
  18. 2
      enterprise/tiny-comments.md
  19. 344
      plugins/premium/comments.md
  20. 595
      plugins/premium/comments/comments_callback_mode.md
  21. 16
      plugins/premium/comments/comments_commands_events_apis.md
  22. 211
      plugins/premium/comments/comments_embedded_mode.md
  23. 15
      plugins/premium/comments/comments_toolbars_menus.md
  24. 101
      plugins/premium/comments/comments_using_comments.md
  25. 25
      plugins/premium/comments/index.md
  26. 58
      plugins/premium/comments/introduction_to_tiny_comments.md

8
_data/nav.yml

@ -242,6 +242,13 @@
- url: "casechange"
- url: "checklist"
- url: "comments"
pages:
- url: "introduction_to_tiny_comments"
- url: "comments_using_comments"
- url: "comments_callback_mode"
- url: "comments_embedded_mode"
- url: "comments_toolbars_menus"
- url: "comments_commands_events_apis"
- url: "mediaembed"
- url: "export"
pages:
@ -723,7 +730,6 @@
- url: "#Deploying an icon pack"
- url: "creating-a-plugin"
- url: "annotations"
- url: "configuring-comments-callbacks"
- url: "yeoman-generator"
- url: "creating-custom-notifications"
- url: "php-upload-handler"

2
_includes/configuration/contextmenu.md

@ -29,6 +29,6 @@ tinymce.init({
});
```
{%if page.title != "Context menu" %}
{% if page.title != "Context menu" %}
For information on configuring the `contextmenu` option and creating custom context menu items [context menu examples]({{site.baseurl}}/ui-components/contextmenu/).
{% endif %}

214
_includes/live-demos/comments-callback/example.js

@ -0,0 +1,214 @@
/********************************
* Tiny Comments functions *
* (must call "done" or "fail") *
********************************/
function tinycomments_create(req, done, fail) {
let content = req.content;
let createdAt = req.createdAt;
fetch('https://api.example/conversations/', {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to create comment');
}
return response.json();
})
.then((req2) => {
let conversationUid = req2.conversationUid;
done({ conversationUid: conversationUid });
})
.catch((e) => {
fail(e);
});
}
function tinycomments_reply(req, done, fail) {
let conversationUid = req.conversationUid;
let content = req.content;
let createdAt = req.createdAt;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to reply to comment');
}
return response.json();
})
.then((req2) => {
let commentUid = req2.commentUid;
done({ commentUid: commentUid });
})
.catch((e) => {
fail(e);
});
}
function tinycomments_edit_comment(req, done, fail) {
let conversationUid = req.conversationUid;
let commentUid = req.commentUid;
let content = req.content;
let modifiedAt = req.modifiedAt;
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'PUT',
body: JSON.stringify({ content: content, modifiedAt: modifiedAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}
)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to edit comment');
}
return response.json();
})
.then((req2) => {
let canEdit = req2.canEdit;
done({ canEdit: canEdit });
})
.catch((e) => {
fail(e);
});
}
function tinycomments_delete(req, done, fail) {
let conversationUid = req.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
function tinycomments_delete_all(_req, done, fail) {
fetch('https://api.example/conversations', {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
function tinycomments_delete_comment(req, done, fail) {
let conversationUid = req.conversationUid;
let commentUid = req.commentUid;
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'DELETE',
}
).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
function tinycomments_lookup({ conversationUid }, done, fail) {
let lookup = async function () {
let convResp = await fetch(
'https://api.example/conversations/' + conversationUid
);
if (!convResp.ok) {
throw new Error('Failed to get conversation');
}
let comments = await convResp.json();
let usersResp = await fetch('https://api.example/users/');
if (!usersResp.ok) {
throw new Error('Failed to get users');
}
let { users } = await usersResp.json();
let getUser = function (userId) {
return users.find((u) => {
return u.id === userId;
});
};
return {
conversation: {
uid: conversationUid,
comments: comments.map((comment) => {
return {
...comment,
content: comment.content,
authorName: getUser(comment.author)?.displayName,
};
}),
},
};
};
lookup()
.then((data) => {
console.log('Lookup success ' + conversationUid, data);
done(data);
})
.catch((err) => {
console.error('Lookup failure ' + conversationUid, err);
fail(err);
});
}
tinymce.init({
selector: 'textarea#callback-mode',
height: 800,
plugins: 'paste code tinycomments help lists',
toolbar:
'undo redo | formatselect | ' +
'bold italic backcolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | addcomment showcomments | help',
menubar: 'file edit view insert format tc',
menu: {
tc: {
title: 'Comments',
items: 'addcomment showcomments deleteallconversations',
},
},
tinycomments_create,
tinycomments_reply,
tinycomments_edit_comment,
tinycomments_delete,
tinycomments_delete_all,
tinycomments_delete_comment,
tinycomments_lookup,
/* The following setup callback opens the comments sidebar when the editor loads */
setup: function (editor) {
editor.on('SkinLoaded', () => {
editor.execCommand('ToggleSidebar', false, 'showcomments');
});
},
});

32
_includes/live-demos/comments-callback/index.html

@ -0,0 +1,32 @@
<textarea id="callback-mode">
<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>Save</strong>.</li>
</ol>
<p>Your comment is then attached to the text, exactly like this!</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 30 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>

686
_includes/live-demos/comments-callback/index.js

@ -0,0 +1,686 @@
tinymce.ScriptLoader.loadScripts(
[
'//unpkg.com/@pollyjs/core@5.1.1',
'//unpkg.com/@pollyjs/adapter-fetch@5.1.1',
'//unpkg.com/@pollyjs/persister-local-storage@5.1.1',
],
() => {
/******************************
* Mock server implementation *
******************************/
let { Polly } = window['@pollyjs/core'];
let FetchAdapter = window['@pollyjs/adapter-fetch'];
let LocalStoragePersister = window['@pollyjs/persister-local-storage'];
Polly.register(FetchAdapter);
Polly.register(LocalStoragePersister);
let polly = new Polly('test', {
adapters: ['fetch'],
persister: 'local-storage',
});
let server = polly.server;
server.any().on('request', (req) => {
console.log('Server request:', req);
});
server.any().on('beforeResponse', (req, res) => {
console.log('Server response:', res);
});
/* this would be an admin for the file, they're allowed to do all operations */
function getOwner() {
return localStorage.getItem('owner') ?? users[0].id;
}
/* Server knows the author, probably by cookie or JWT token */
function getAuthor() {
return localStorage.getItem('author') ?? users[0].id;
}
/* this would be an admin for the file, they're allowed to do all operations */
function setOwner(user) {
localStorage.setItem('owner', user) ?? users[0].id;
}
/* Server knows the author, probably by cookie or JWT token */
function setAuthor(user) {
localStorage.setItem('author', user) ?? users[0].id;
}
function randomString() {
/* ~62 bits of randomness, so very unlikely to collide for <100K uses */
return Math.random().toString(36).substring(2, 14);
}
/* Our server "database" */
function getDB() {
return JSON.parse(localStorage.getItem('fakedb') ?? '{}');
}
function setDB(data) {
localStorage.setItem('fakedb', JSON.stringify(data));
}
function getConversation(uid) {
let store = getDB();
console.log('DB get:', uid, store[uid]);
return store[uid];
}
function setConversation(uid, conversation) {
let store = getDB();
console.log('DB set:', uid, store[uid], conversation);
store[uid] = conversation;
setDB(store);
}
function deleteConversation(uid) {
let store = getDB();
console.log('DB delete:', uid);
delete store[uid];
setDB(store);
}
function deleteAllConversations() {
console.log('DB delete all');
let store = {};
setDB(store);
}
server.host('https://api.example', () => {
/* create new conversation */
server.post('/conversations/').intercept((req, res) => {
let author = getAuthor();
let { content, createdAt } = JSON.parse(req.body);
console.log(req.body);
try {
let conversationUid = randomString();
setConversation(conversationUid, [
{
author,
createdAt,
modifiedAt: createdAt,
content,
uid: conversationUid /* first comment has same uid as conversation */,
},
]);
res.status(201).json({ conversationUid });
} catch (e) {
console.log('Server error:', e);
res.status(500);
}
});
/* add new comment to conversation */
server.post('/conversations/:conversationUid').intercept((req, res) => {
let author = getAuthor();
let { content, createdAt } = JSON.parse(req.body);
let conversationUid = req.params.conversationUid;
try {
let conversation = getConversation(conversationUid);
let commentUid = randomString();
setConversation(
conversationUid,
conversation.concat([
{
author,
createdAt,
modifiedAt: createdAt,
content,
uid: commentUid,
},
])
);
res.status(201).json({ commentUid });
} catch (e) {
console.log('Server error:', e);
res.status(500);
}
});
/* edit a comment */
server
.put('/conversations/:conversationUid/:commentUid')
.intercept((req, res) => {
let author = getAuthor();
let { content, modifiedAt } = JSON.parse(req.body);
let conversationUid = req.params.conversationUid;
let commentUid = req.params.commentUid;
try {
let conversation = getConversation(conversationUid);
let commentIndex = conversation.findIndex((comment) => {
return comment.uid === commentUid;
});
let comment = conversation[commentIndex];
let canEdit = comment.author === author;
if (canEdit) {
setConversation(conversationUid, [
...conversation.slice(0, commentIndex),
{
...comment,
content,
modifiedAt,
},
...conversation.slice(commentIndex + 1),
]);
}
res.status(201).json({ canEdit });
} catch (e) {
console.log('Server error:', e);
res.status(500);
}
});
/* delete a comment */
server
.delete('/conversations/:conversationUid/:commentUid')
.intercept((req, res) => {
let author = getAuthor();
let owner = getOwner();
let conversationUid = req.params.conversationUid;
let commentUid = req.params.commentUid;
let conversation = getConversation(conversationUid);
if (!conversation) {
res.status(404);
}
let commentIndex = conversation.findIndex((comment) => {
return comment.uid === commentUid;
});
if (commentIndex === -1) {
res.status(404);
}
if (
conversation[commentIndex].author === author ||
author === owner
) {
setConversation(conversationUid, [
...conversation.slice(0, commentIndex),
...conversation.slice(commentIndex + 1),
]);
res.status(204);
} else {
res.status(403);
}
});
/* delete a conversation */
server.delete('/conversations/:conversationUid').intercept((req, res) => {
let author = getAuthor();
let owner = getOwner();
let conversationUid = req.params.conversationUid;
let conversation = getConversation(conversationUid);
if (conversation) {
if (conversation[0].author === author || author === owner) {
deleteConversation(conversationUid);
res.status(204);
} else {
res.status(403);
}
} else {
res.status(404);
}
});
/* delete all conversations */
server.delete('/conversations').intercept((req, res) => {
let author = getAuthor();
let owner = getOwner();
if (author === owner) {
deleteAllConversations();
res.status(204);
} else {
res.status(403);
}
});
/* lookup a conversation */
server.get('/conversations/:conversationUid').intercept((req, res) => {
let conversation = getConversation(req.params.conversationUid);
if (conversation) {
res.status(200).json(conversation);
} else {
res.status(404);
}
});
/* lookup users */
server.get('/users/').intercept((req, res) => {
res.status(200).json({
users,
});
});
}); /* server.host */
/* Connect using the `connectTo` API */
polly.connectTo('fetch');
/************************************************
* Fake Users and associated pickers *
* Should be based on sessions and backend data *
***********************************************/
const users = [
{ id: 'alex', displayName: 'Alex' },
{ id: 'jessie', displayName: 'Jessie' },
{ id: 'sam', displayName: 'Sam' },
];
/* Set initial Owner */
setOwner(users[2].id);
/* Set initial Author */
setAuthor(users[0].id);
/********************************
* Tiny Comments functions *
* (must call "done" or "fail") *
********************************/
/**
* Callback for when the operation was successful.
* @template T
* @callback done
* @param {T} data - the data
* @returns {void}
*/
/**
* Callback for when the operation failed.
* @callback fail
* @param {string|Error} error - the reason for the failure
* @returns {void}
*/
/**
* The data supplied to create a comment.
* @typedef {Object} TinyCommentsCreateReq
* @property {string} content - comment content
* @property {string} createdAt - ISO creation date
*/
/**
* The response returned when a comment was created on the server.
* @typedef {Object} TinyCommentsCreateResp
* @property {string} conversationUid - ID of created comment
* @property {?fail} onError - error callback to call when the comment can't be put into the document
* @property {?done<string>} onSuccess - success callback to call when the comment is put into the document
*/
/**
* Conversation "create" function. Saves the comment as a new conversation,
* and asynchronously returns a conversation unique ID via the "done"
* callback.
*
* @param {TinyCommentsCreateReq} req - the comment to create
* @param {done<TinyCommentsCreateResp>} done - callback to call when the comment is created on the server
* @param {fail} fail - callback to call when something fails
*/
function tinycomments_create(req, done, fail) {
let content = req.content;
let createdAt = req.createdAt;
fetch('https://api.example/conversations/', {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to create comment');
}
return response.json();
})
.then((req2) => {
let conversationUid = req2.conversationUid;
done({ conversationUid: conversationUid });
})
.catch((e) => {
fail(e);
});
}
/**
*
* @typedef {Object} TinyCommentsReplyReq
* @property {string} conversationUid
* @property {string} content
* @property {string} createdAt
*/
/**
*
* @typedef {Object} TinyCommentsReplyResp
* @property {string} commentUid
*/
/**
* Conversation "reply" function. Saves the comment as a reply to the
* an existing conversation, and asynchronously returns via the "done"
* callback when finished.
*
* @param {TinyCommentsReplyReq} req - the comment to append
* @param {done<TinyCommentsReplyResp>} done - callback to call when the comment is created on the server
* @param {fail} fail - callback to call when something fails
*/
function tinycomments_reply(req, done, fail) {
let conversationUid = req.conversationUid;
let content = req.content;
let createdAt = req.createdAt;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to reply to comment');
}
return response.json();
})
.then((req2) => {
let commentUid = req2.commentUid;
done({ commentUid: commentUid });
})
.catch((e) => {
fail(e);
});
}
/**
*
* @typedef {Object} TinyCommentsEditReq
* @property {string} conversationUid
* @property {string} commentUid
* @property {string} content
* @property {string} modifiedAt
*/
/**
*
* @typedef {Object} TinyCommentsEditResp
* @property {boolean} canEdit
* @property {?string} reason
*/
/**
*
* @param {TinyCommentsEditReq} req
* @param {done<TinyCommentsEditResp>} done
* @param {fail} fail
*/
function tinycomments_edit_comment(req, done, fail) {
let conversationUid = req.conversationUid;
let commentUid = req.commentUid;
let content = req.content;
let modifiedAt = req.modifiedAt;
fetch(
'https://api.example/conversations/' +
conversationUid +
'/' +
commentUid,
{
method: 'PUT',
body: JSON.stringify({ content: content, modifiedAt: modifiedAt }),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}
)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to edit comment');
}
return response.json();
})
.then((req2) => {
let canEdit = req2.canEdit;
done({ canEdit: canEdit });
})
.catch((e) => {
fail(e);
});
}
/**
*
* @typedef TinyCommentsDeleteReq
* @property {string} conversationUid
*/
/**
*
* @typedef TinyCommentsDeleteResp
* @property {boolean} canDelete
* @property {?string} reason
*/
/**
* Conversation "delete" function. Deletes an entire conversation.
* Returns asynchronously whether the conversation was deleted.
* Failure to delete due to permissions or business rules is indicated
* by `{canDelete: false}`, while unexpected errors should be indicated using the
* "fail" callback.
* @param {TinyCommentsDeleteReq} req
* @param {done<TinyCommentsDeleteResp>} done
* @param {fail} fail
*/
function tinycomments_delete(req, done, fail) {
let conversationUid = req.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
/**
*
* @typedef TinyCommentsDeleteAllReq
* @type {object}
*/
/**
*
* @typedef TinyCommentsDeleteAllResp
* @property {boolean} canDelete
* @property {?string} reason
*/
/**
* All conversations "delete_all" function. Deletes all conversations.
* Returns asynchronously whether all conversations were deleted.
* Failure to delete due to permissions or business rules is indicated
* by `{canDelete: false}`, while unexpected errors should be indicated using the
* "fail" callback.
* @param {TinyCommentsDeleteAllReq} _req - no options
* @param {done<TinyCommentsDeleteAllResp>} done
* @param {fail} fail
*/
function tinycomments_delete_all(_req, done, fail) {
fetch('https://api.example/conversations', {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
/**
*
* @typedef TinyCommentsDeleteCommentReq
* @property {string} conversationUid
* @property {string} commentUid
*/
/**
*
* @typedef TinyCommentsDeleteCommentResp
* @property {boolean} canDelete
* @property {?string} reason
*/
/**
*
* @param {TinyCommentsDeleteCommentReq} req
* @param {done<TinyCommentsDeleteCommentResp>} done
* @param {fail} fail
*/
function tinycomments_delete_comment(req, done, fail) {
let conversationUid = req.conversationUid;
let commentUid = req.commentUid;
fetch(
'https://api.example/conversations/' +
conversationUid +
'/' +
commentUid,
{
method: 'DELETE',
}
).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
/**
* @typedef TinyCommentsLookupReq
* @property {string} conversationUid
*/
/**
*
* @typedef TinyCommentsLookupRespComment
* @property {string} author
* @property {?string} authorName
* @property {string} createdAt
* @property {string} modifiedAt
* @property {string} content
* @property {string} uid
*/
/**
*
* @typedef TinyCommentsLookupRespConversation
* @property {string} uid
* @property {TinyCommentsLookupRespComment[]} comments
*/
/**
*
* @typedef TinyCommentsLookupResp
* @property {TinyCommentsLookupRespConversation} conversation
*/
/**
* Conversation "lookup" function. Retreives an existing conversation
* via a conversation unique ID. Asynchronously returns the conversation
* via the "done" callback.
*
* @param {TinyCommentsLookupReq} req
* @param {done<TinyCommentsLookupResp>} done
* @param {fail} fail
*/
function tinycomments_lookup({ conversationUid }, done, fail) {
let lookup = async function () {
let convResp = await fetch(
'https://api.example/conversations/' + conversationUid
);
if (!convResp.ok) {
throw new Error('Failed to get conversation');
}
let comments = await convResp.json();
let usersResp = await fetch('https://api.example/users/');
if (!usersResp.ok) {
throw new Error('Failed to get users');
}
let { users } = await usersResp.json();
let getUser = function (userId) {
return users.find((u) => {
return u.id === userId;
});
};
return {
conversation: {
uid: conversationUid,
comments: comments.map((comment) => {
return {
...comment,
content: comment.content,
authorName: getUser(comment.author)?.displayName,
};
}),
},
};
};
lookup()
.then((data) => {
console.log('Lookup success ' + conversationUid, data);
done(data);
})
.catch((err) => {
console.error('Lookup failure ' + conversationUid, err);
fail(err);
});
}
tinymce.init({
selector: 'textarea#callback-mode',
height: 800,
plugins: 'paste code tinycomments help lists',
toolbar:
'undo redo | formatselect | ' +
'bold italic backcolor | alignleft aligncenter ' +
'alignright alignjustify | bullist numlist outdent indent | ' +
'removeformat | addcomment showcomments | help',
menubar: 'file edit view insert format tc',
menu: {
tc: {
title: 'Comments',
items: 'addcomment showcomments deleteallconversations',
},
},
tinycomments_create,
tinycomments_reply,
tinycomments_edit_comment,
tinycomments_delete,
tinycomments_delete_all,
tinycomments_delete_comment,
tinycomments_lookup,
/* The following setup callback opens the comments sidebar when the editor loads */
setup: function (editor) {
editor.on('SkinLoaded', () => {
editor.execCommand('ToggleSidebar', false, 'showcomments', {
skip_focus: true,
});
});
},
});
}
);

0
_includes/live-demos/comments-2/index.html → _includes/live-demos/comments-embedded/index.html

10
_includes/live-demos/comments-2/index.js → _includes/live-demos/comments-embedded/index.js

@ -7,7 +7,7 @@ tinymce.init({
menubar: 'file edit view insert format tools tc',
menu: {
tc: {
title: 'TinyComments',
title: 'Comments',
items: 'addcomment showcomments deleteallconversations'
}
},
@ -21,5 +21,11 @@ tinymce.init({
canResolve: allowed || currentAuthor === userAllowedToResolve
});
},
content_style: {{site.liveDemoIframeCSSStyles}}
content_style: {{site.liveDemoIframeCSSStyles}},
/* The following setup callback opens the comments sidebar when the editor loads */
setup: function (editor) {
editor.on('SkinLoaded', function () {
editor.execCommand("ToggleSidebar", false, "showcomments", { skip_focus: true });
})
}
});

2
_includes/live-demos/full-featured/example.js

@ -12,7 +12,7 @@ tinymce.init({
},
menu: {
tc: {
title: 'TinyComments',
title: 'Comments',
items: 'addcomment showcomments deleteallconversations'
}
},

2
_includes/live-demos/full-featured/index.js

@ -168,7 +168,7 @@ tinymce.ScriptLoader.loadScripts(['https://cdn.jsdelivr.net/npm/faker@5/dist/fak
},
menu: {
tc: {
title: 'TinyComments',
title: 'Comments',
items: 'addcomment showcomments deleteallconversations'
}
},

2
_includes/live-demos/readonly-demo/index.html

@ -23,4 +23,4 @@
<textarea id="readonly-demo">Hello, World!</textarea>
<button id="enableDisableButton" class="btn btn-candy link-sushi" onclick="enableDisable('readonly-demo','enableDisableButton')">Enable editor</button>
<button id="enableDisableButton" class="button button-color link-text" onclick="enableDisable('readonly-demo','enableDisableButton')">Enable editor</button>

28
_includes/live-demos/readonly-demo/style.css

@ -0,0 +1,28 @@
.button {
display: inline-block;
min-width: 158px;
height: 45px;
line-height: 45px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
padding: 0 25px;
text-align: center;
}
.button:active {
transform: translateY(1px);
}
.button-color {
background-color: {{site.primaryColor}};
}
.button-color:hover {
background-color: #1d6abe;
}
.link-text {
color: #fff;
font-weight: 600;
}

9
_includes/plugins/comments_embed_fullpage_issues.md

@ -0,0 +1,9 @@
## Using Comments embedded mode with the Full Page plugin
Developers have to be cautious when deciding the order in which the plugins are added in the plugins list.
Comments can cause an issue if the [Full Page]({{site.baseurl}}/plugins/opensource/fullpage/) plugin `fullpage` appears before Comments plugin `tinycomments` in the plugin list, and `tinycomments` is configured to use `embedded mode`.
The order that the plugins appear affects the order that the `getContent` hooks are processed in. This creates an issue with `tinycomments` working as expected since the `fullpage` plugin adds outer `<html>` elements before `tinycomments` adds its comment data. This leads to the comment data being in the wrong place. The consequence of this situation is that when a saved document is re-opened, the comment data is lost (but the highlights are still there).
For a workaround, please ensure that `tinycomments` is listed before `fullpage` in the plugins list. This should result in `tinycomments` working properly.

9
_includes/plugins/comments_highlighting_css.md

@ -0,0 +1,9 @@
## Configuring the commented text properties
The highlight styles are now a part of the overall content skin and are changed through customizing the skin.
{{site.productname}} open source project [oxide](https://github.com/tinymce/oxide/blob/master/src/less/theme/content/comments/comments.less) (default skin), defines the variables used for changing the annotation colours.
Refer to the [documentation]({{site.baseurl}}/advanced/creating-a-skin/#creatingaskin) for building a skin using this repo.
For more information on configuring {{site.productname}} formats, refer to the [formats]({{site.baseurl}}/configure/content-formatting/#formats) section.

45
_includes/plugins/comments_open_sidebar.md

@ -0,0 +1,45 @@
## Show the comments sidebar when TinyMCE loads
To show the comments sidebar when the editor is loaded or to display the sidebar by default, add a callback to open the sidebar once the editor 'skin' is loaded.
For example:
{% if page.name == "comments_callback_mode.md" %}
```js
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create,
tinycomments_reply,
tinycomments_edit_comment,
tinycomments_delete,
tinycomments_delete_all,
tinycomments_delete_comment,
tinycomments_lookup,
/* The following setup callback opens the comments sidebar when the editor loads */
setup: function (editor) {
editor.on('SkinLoaded', function () {
editor.execCommand("ToggleSidebar", false, "showcomments");
})
}
});
```
{% else %}
```js
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'embedded',
tinycomments_author: currentAuthor,
tinycomments_can_resolve: canResolveCommentsCallback,
/* The following setup callback opens the comments sidebar when the editor loads */
setup: function (editor) {
editor.on('SkinLoaded', function () {
editor.execCommand("ToggleSidebar", false, "showcomments");
})
}
});
```
{% endif %}

483
advanced/configuring-comments-callbacks.md

@ -1,40 +1,52 @@
---
layout: default
title: Configuring callbacks for the Comments plugin
title_nav: Configuring callbacks for the Comments plugin
description: Instructions for configuring callbacks for the Comments plugin
title: Configuring the Comments plugin in callback mode
title_nav: Callback mode
description: Information on configuring the Comments plugin in callback mode
keywords: comments commenting tinycomments callback
---
**Callback mode** is the default mode for [the Comments plugin]({{site.baseurl}}/plugins/premium/comments/). In the callback mode, the user needs to configure storage to be able to save comments on the server. The Comments functions (create, reply, edit, delete comment, delete all conversations, and lookup) are configured differently depending upon the server-side storage configuration.
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
### Required settings
**Callback mode** is the default mode for [the Comments plugin]({{site.baseurl}}/plugins/premium/comments/). In the callback mode, callback functions are required to save user comments on a server. The Comments functions (create, reply, edit, delete comment, delete all conversations, resolve, and lookup) are configured differently depending upon the server-side storage configuration.
The Comments plugin requires the following functions to be defined:
## Interactive example
```js
tinymce.init({
...
tinycomments_create: function (req, done, fail) { ... },
tinycomments_reply: function (req, done, fail) { ... },
tinycomments_delete: function (req, done, fail) { ... },
tinycomments_delete_all: function (req, done, fail) { ... },
tinycomments_delete_comment: function (req, done, fail) { ... },
tinycomments_lookup: function (req, done, fail) { ... },
tinycomments_edit_comment: function (req, done, fail) { ... }
});
The following example uses a simulated server (provided by [Polly.js](https://netflix.github.io/pollyjs/)) which has been hidden from the example javascript to keep the example code concise. The interactions between TinyMCE and Polly.js are visible in the browser console.
```
{% include live-demo.html id="comments-callback" %}
### How the comments plugin works in callback mode
All functions incorporate `done` and `fail` callbacks as parameters. The function return type is not important, but all functions must call exactly one of these two callbacks: `fail` or `done`.
All options accept functions incorporating `done` and `fail` callbacks as parameters. The function return type is not important, but all functions must call exactly one of these two callbacks: `fail` or `done`.
* The `fail` callback takes either a string or a JavaScript Error type.
* The `done` callback takes an argument specific to each function.
Most (create, reply, and edit) functions require configuring the **current author**:
Most (create, reply, and edit) functions require an `id` identifying the **current author**.
Current author
: The Comments plugin does not know the name of the current user. Determining the current user and storing the comment related to that user, has to be configured by the user.
After a user comments (triggering `tinycomments_create` for the first comment, or `tinycomments_reply` for subsequent comments), the Comments plugin requests the updated conversation using `tinycomments_lookup`, which should now contain the additional comment with the proper author.
## Configuration options - callback mode
### Required options
* **Current author** - the Comments plugin does not know the name of the current user. After a user comments (triggering `tinycomments_create` for the first comment, or `tinycomments_reply` for subsequent comments), Comments requests the updated conversation via `tinycomments_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 configured by the user.
When using callback mode, the Comments plugin requires callback functions for the following options:
* [`tinycomments_create`](#tinycomments_create)
* [`tinycomments_reply`](#tinycomments_reply)
* [`tinycomments_edit_comment`](#tinycomments_edit_comment)
* [`tinycomments_delete_comment`](#tinycomments_delete_comment)
* [`tinycomments_delete`](#tinycomments_delete)
* [`tinycomments_delete_all`](#tinycomments_delete_all)
* [`tinycomments_lookup`](#tinycomments_lookup)
The [`tinycomments_resolve`](#tinycomments_resolve) option is _optional_.
### `tinycomments_create`
@ -44,9 +56,11 @@ The `tinycomments_create` function saves the comment as a new conversation and r
The `tinycomments_create` function is given a request (req) object as the first parameter, which has these fields:
* **content**: The content of the comment to create.
`content`
: The content of the comment to create.
* **createdAt**: The date the comment was created.
`createdAt`
: The date the comment was created.
The `done` callback should accept the following object:
@ -60,6 +74,50 @@ The `done` callback should accept the following object:
}
```
For example:
```js
function create_comment(_ref, done, fail) {
var content = _ref.content;
var createdAt = _ref.createdAt;
fetch('https://api.example/conversations/', {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then(function (response) {
if (!response.ok) {
throw new Error('Failed to create comment');
}
return response.json();
})
.then(function (_ref2) {
var conversationUid = _ref2.conversationUid;
done({ conversationUid: conversationUid });
})
.catch(function (e) {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment, // Add the callback to TinyMCE
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_reply`
The Comments plugin uses the conversation `tinycomments_reply` function to reply to a comment.
@ -68,11 +126,14 @@ The `tinycomments_reply` function saves the comment as a reply to an existing co
The `tinycomments_reply` function is given a request (req) object as the first parameter, which has these fields:
* **conversationUid**: The uid of the conversation the reply is targeting.
`conversationUid`
: The uid of the conversation the reply is targeting.
* **content**: The content of the comment to create.
`content`
: The content of the comment to create.
* **createdAt**: The date the comment was created.
`createdAt`
: The date the comment was created.
The `done` callback should accept the following object:
@ -82,6 +143,51 @@ The `done` callback should accept the following object:
}
```
For example:
```js
function reply_comment(_ref, done, fail) {
var conversationUid = _ref.conversationUid;
var content = _ref.content;
var createdAt = _ref.createdAt;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then(function (response) {
if (!response.ok) {
throw new Error('Failed to reply to comment');
}
return response.json();
})
.then(function (_ref2) {
var commentUid = _ref2.commentUid;
done({ commentUid: commentUid });
})
.catch(function (e) {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment, // Add the callback to TinyMCE
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_edit_comment`
The Comments plugin uses the conversation `tinycomments_edit_comment` function to edit a comment.
@ -90,13 +196,17 @@ The `tinycomments_edit_comment` function allows updating or changing original co
The `tinycomments_edit_comment` function is given a request (req) object as the first parameter, which has these fields:
* **conversationUid**: The uid of the conversation the reply is targeting.
`conversationUid`
: The uid of the conversation the reply is targeting.
* **commentUid**: The uid of the comment to edit (it can be the same as `conversationUid` if editing the first comment in a conversation)
`commentUid`
: The uid of the comment to edit (it can be the same as `conversationUid` if editing the first comment in a conversation)
* **content**: The content of the comment to create.
`content`
: The content of the comment to create.
* **modifiedAt**: The date the comment was modified.
`modifiedAt`
: The date the comment was modified.
The `done` callback should accept the following object:
@ -107,24 +217,55 @@ The `done` callback should accept the following object:
}
```
### `tinycomments_delete`
The `tinycomments_delete` function should asynchronously return a flag indicating whether the comment or comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
* **conversationUid**: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
For example:
```js
{
canDelete: boolean // whether or not the conversation can be deleted
reason: string? // an optional string explaining why the delete was not allowed (if canDelete is false)
function edit_comment(_ref, done, fail) {
var conversationUid = _ref.conversationUid;
var commentUid = _ref.commentUid;
var content = _ref.content;
var modifiedAt = _ref.modifiedAt;
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'PUT',
body: JSON.stringify({ content: content, modifiedAt: modifiedAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}
)
.then(function (response) {
if (!response.ok) {
throw new Error('Failed to edit comment');
}
return response.json();
})
.then(function (_ref2) {
var canEdit = _ref2.canEdit;
done({ canEdit: canEdit });
})
.catch(function (e) {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment, // Add the callback to TinyMCE
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
> **Note**: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
### `tinycomments_resolve`
@ -136,7 +277,8 @@ The `tinycomments_resolve` function should asynchronously return a flag indicati
The `tinycomments_resolve` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
* **conversationUid**: The uid of the conversation the reply is targeting.
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
@ -149,56 +291,215 @@ The `done` callback should accept the following object:
> **Note**: Failure to resolve due to permissions or business rules should be indicated by `canResolve: false`, while unexpected errors should be indicated using the `fail` callback.
### `tinycomments_delete_all`
For example:
The `tinycomments_delete_all` function should asynchronously return a flag indicating whether all the comments in a conversation were removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
```js
function resolve_comment_thread(_ref, done, fail) {
var conversationUid = _ref.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'PUT',
}).then(function (response) {
if (response.ok) {
done({ canResolve: true });
} else if (response.status === 403) {
done({ canResolve: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
The `tinycomments_delete_all` function is given a request (req) object as the first parameter with no fields.
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_resolve: resolve_comment_thread, // Add the callback to TinyMCE
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete_comment`
The `tinycomments_delete_comment` function should asynchronously return a flag indicating whether the comment or comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete_comment` function is given a request (req) object as the first parameter, which has these fields:
`conversationUid`
: The uid of the conversation the reply is targeting.
`commentUid`
: The uid of the comment to delete (cannot be the same as `conversationUid`).
The `done` callback should accept the following object:
```js
{
canDelete: boolean, // whether or not all conversations can be deleted
reason: string? // an optional string explaining why the deleteAll was not allowed (if canDelete is false)
canDelete: boolean, // whether or not an individual comment can be deleted
reason: string? // an optional reason explaining why the delete was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules should be indicated by `canDelete: false`, while unexpected errors should be indicated using the `fail` callback.
> **Note**: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
### `tinycomments_delete_comment`
For example:
The `tinycomments_delete_comment` function should asynchronously return a flag indicating whether the comment or comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
```js
function delete_comment(_ref, done, fail) {
var conversationUid = _ref.conversationUid;
var commentUid = _ref.commentUid;
The `tinycomments_delete_comment` function is given a request (req) object as the first parameter, which has these fields:
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'DELETE',
}
).then(function (response) {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread, // Add the callback to TinyMCE
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete`
The `tinycomments_delete` function should asynchronously return a flag indicating whether the comment or comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
* **conversationUid**: The uid of the conversation the reply is targeting.
* **commentUid**: The uid of the comment to delete (cannot be the same as conversationUid)
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
```js
{
canDelete: boolean, // whether or not an individual comment can be deleted
reason: string? // an optional reason explaining why the delete was not allowed (if canDelete is false)
canDelete: boolean // whether or not the conversation can be deleted
reason: string? // an optional string explaining why the delete was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
For example:
```js
function delete_comment_thread(_ref, done, fail) {
var conversationUid = _ref.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'DELETE',
}).then(function (response) {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread, // Add the callback to TinyMCE
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete_all`
The `tinycomments_delete_all` function should asynchronously return a flag indicating whether all the comments in a conversation were removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete_all` function is given a request (req) object as the first parameter with no fields.
The `done` callback should accept the following object:
```js
{
canDelete: boolean, // whether or not all conversations can be deleted
reason: string? // an optional string explaining why the deleteAll was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules should be indicated by `canDelete: false`, while unexpected errors should be indicated using the `fail` callback.
For example:
```js
function delete_all_comment_threads(_req, done, fail) {
fetch('https://api.example/conversations', {
method: 'DELETE',
}).then(function (response) {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads, // Add the callback to TinyMCE
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_lookup`
The Comments plugin uses the Conversation `tinycomments_lookup` function to retrieve an existing conversation via a conversation unique ID.
The **Display names** configuration must be considered for the `tinycomments_lookup` function:
* **Display names** - The Comments plugin uses a simple string for the display name. For the `lookup` function, Comments expects each comment to contain the author's display name, not a user ID, as Comments does not know the user identities. The `lookup` function should be implemented considering this and resolve user identifiers to an appropriate display name.
Display names
: The Comments plugin uses a simple string for the display name. For the `lookup` function, Comments expects each comment to contain the author's display name, not a user ID, as Comments does not know the user identities. The `lookup` function should be implemented considering this and resolve user identifiers to an appropriate display name.
The conventional conversation object structure that should be returned via the `done` callback is as follows:
The `tinycomments_lookup` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
* **conversationUid**: The uid of the conversation the reply is targeting.
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
@ -230,4 +531,66 @@ The `done` callback should accept the following object:
> **Note**: The dates should use [ISO 8601 format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString). This can be generated in JavaScript with: `new Date().toISOString()`.
For more information on the Comments commercial feature, visit our [Premium Features]({{ site.baseurl }}/enterprise/tiny-comments/) page.
For example:
```js
function lookup_comment({ conversationUid }, done, fail) {
var lookup = async function () {
var convResp = await fetch(
'https://api.example/conversations/' + conversationUid
);
if (!convResp.ok) {
throw new Error('Failed to get conversation');
}
var comments = await convResp.json();
var usersResp = await fetch('https://api.example/users/');
if (!usersResp.ok) {
throw new Error('Failed to get users');
}
var { users } = await usersResp.json();
var getUser = function (userId) {
return users.find(function (u) {
return u.id === userId;
});
};
return {
conversation: {
uid: conversationUid,
comments: comments.map(function (comment) {
return {
...comment,
content: comment.content,
authorName: getUser(comment.author)?.displayName,
};
}),
},
};
};
lookup()
.then(function (data) {
console.log('Lookup success ' + conversationUid, data);
done(data);
})
.catch(function (err) {
console.error('Lookup failure ' + conversationUid, err);
fail(err);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment // Add the callback to TinyMCE
});
```
{% include plugins/comments_open_sidebar.md %}
{% include plugins/comments_highlighting_css.md %}

2
demo/comments-2.md

@ -15,4 +15,4 @@ In this example, the features in the Comments plugin are highlighted, including
For information on the other Comments plugin configuration options, see: [The Comments plugin]({{site.baseurl}}/plugins/premium/comments/).
{% include live-demo.html id="comments-2" %}
{% include live-demo.html id="comments-embedded" %}

4
demo/pageembed.md

@ -7,10 +7,8 @@ keywords: view Page Embed insert iframe
controls: toolbar button, menu item
---
## Interactive example
This example shows how to use the Page Embed plugin to embed a page in the content in a responsive or exactly sized iframe.For more information on the Page Embed plugin, see the [docs]({{site.baseurl}}/plugins/premium/pageembed/).
This example shows how to use the Page Embed plugin to embed a page in the content in a responsive or exactly sized iframe. For more information on the Page Embed plugin, see the [docs]({{site.baseurl}}/plugins/premium/pageembed/).
{% include live-demo.html id="page-embed" %}

2
enterprise/tiny-comments.md

@ -30,7 +30,7 @@ The Comments plugin allows the user to perform the following functions:
## Try our Comments plugin demo
{% include live-demo.html id="comments-2" %}
{% include live-demo.html id="comments-embedded" %}
{% assign pluginname = 'Comments' %}
{% assign pluginminimumplan = 'enterprise' %}

344
plugins/premium/comments.md

@ -1,344 +0,0 @@
---
layout: default
title: Comments
title_nav: Comments
description: Tiny Comments provides the ability to add comments to the content and collaborate with other users for content editing.
keywords: comments commenting tinycomments
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
{{site.premiumplugin}}
The Comments plugin provides the ability to start or join a conversation by adding comments to the content within the {{site.productname}} editor. The Comments plugin is built upon the [Annotations API]({{site.baseurl}}/advanced/annotations/) and uses annotations to create comment threads (conversations).
## Interactive example
{% include live-demo.html id="comments-2" %}
## Using Comments
### To add a comment
1. Select the text from the desired location in the editor body.
1. From the navigation menu, choose **Insert**-> **Add Comment** or click on the **Add comment** ![Add comment]({{site.baseurl}}/images/icons/comment-add.svg) toolbar button to add the comment.
1. The Comment dialog box appears in the sidebar of the editor instance.
1. Type the comment in the box displaying "_Say something…_" suggested text.
1. Press **Clear** to delete or **Save** to store the input comment.
**Result**: The selected text will be highlighted as per the configured options. The following screen with the option for editing, deleting, and replying to the comment, will appear.
![Delete Conversation]({{site.baseurl}}/images/comments-edit.png)
Note: The above procedure can be followed for adding multiple comments to the document.
### Editing a comment
Follow this procedure to edit a comment.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Edit** from the menu items.
1. The comment field becomes editable. Make the required changes.
1. Click **Cancel** to discard or **Save** to store the changes.
### Delete a comment
Follow this procedure to delete a comment. This option is not available for the first comment in a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Delete** from the menu items.
1. The following options appear in the comments sidebar:<br/>
![delete comment]({{site.baseurl}}/images/comments-delete-comment.png)
1. Click **Cancel** to cancel the action or **Delete** to remove the comment from the conversation.
### Delete conversation
This option is only available for the first comment in a conversation. Once the comment is saved, follow this procedure to delete a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Delete conversation** from the menu items.
1. The following decision dialog box will appear:<br/>
![delete conversation]({{site.baseurl}}/images/comments-delete-conversation.png)
1. Click **Cancel** to cancel the action or **Delete** to remove the conversation.
**Result**: The conversation and all its subsequent comments will be deleted.
### Resolve conversation
{{site.requires_5_8v}}
> **NOTE**: This feature requires the [`tinycomments_resolve`]({{site.baseurl}}/advanced/configuring-comments-callbacks/#tinycomments_resolve) or [`tinycomments_can_resolve`]({{site.baseurl}}/plugins/premium/comments/#tinycomments_can_resolve) setting to be configured.
This option is only available for the first comment in a conversation. Once a comment is saved, follow this procedure to resolve a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Resolve conversation** from the menu items.
1. The following decision dialog box will appear:<br/>
![resolve conversation]({{site.baseurl}}/images/comments-resolve-conversation.png)
1. Click **Cancel** to cancel the action or **Resolve** to resolve the conversation.
**Result**: The conversation will be resolved.
### Show comment
Follow this procedure to display the comments sidebar:
1. Place the cursor on the desired text in the editor body:
1. From the navigation menu, choose **View** -> **Show Comment** or click on the **Show Comments**![Comments]({{site.baseurl}}/images/comments-toolbar-button.png) toggle toolbar button to display the comment.
**Result**: The comments sidebar will appear and display the corresponding conversation for the highlighted text.
### Delete all conversations
Follow this procedure to delete all conversations in the document:
1. From the navigation menu, choose **File** -> **Delete all conversations** option to delete all the comments in a document.
1. The following decision dialog box will appear:<br />
![Delete all conversations]({{site.baseurl}}/images/comments-delete-conversations.png)
1. Click **Ok** to remove the all the comments or **Cancel** to dismiss the action.
**Result**: All the comments for the selected document will be deleted.
## Add the Comments plugin
To add the Comments plugin to the {{site.productname}} editor, use the following script:
```js
tinymce.init({
selector: '#tiny-ui .editor',
plugins: 'paste tinycomments',
tinycomments_mode: 'embedded',
tinycomments_author: 'Author'
});
```
## Modes
There are two modes available in Comments that provide the ability to save comments. These modes are configured in the Comments settings.
* **Callback Mode** - This is the default mode in Comments. This mode is used to configure storage and save comments on the user’s server. This option gives the user a choice of configuring the storage settings to either persist comments immediately or save them at the same time as the content. Additional callbacks must be configured to use Comments in callback mode.
* **Embedded Mode** - This mode allows the user to store the comments within the content. No additional callbacks are required to be configured to use this mode.
### Configuring Comments callback mode
Refer to the [configuring callbacks for comments]({{site.baseurl}}/advanced/configuring-comments-callbacks/) section for more information.
### Configuring Comments embedded mode
To configure Comments to use the embedded mode use the following script:
```js
tinymce.init({
selector: '#textarea',
tinycomments_author: 'embedded_journalist',
tinycomments_author_name: 'Embedded Journalist',
tinycomments_mode: 'embedded'
...
})
```
#### Embedded mode options
##### `tinycomments_author`
This option sets the author id to be used when creating or replying to comments.
**Type:** `String`
**Default Value:** `'Anon'`
###### Example: Using `tinycomments_author`
```js
tinymce.init({
selector: '#textarea',
tinycomments_author: 'embedded_journalist',
tinycomments_mode: 'embedded'
})
```
##### `tinycomments_author_name`
> Available in Tiny Comments version 2.1 onwards.
_Optional_ - This option sets the author's display name to be used when creating or replying to comments. If this option is omitted, then the author id is used instead.
**Type:** `String`
###### Example: Using `tinycomments_author_name`
```js
tinymce.init({
selector: '#textarea',
tinycomments_author: 'embedded_journalist',
tinycomments_author_name: 'Embedded Journalist',
tinycomments_mode: 'embedded'
})
```
##### `tinycomments_can_delete`
_Optional_ - This option sets the author permissions for _deleting comment conversations_. If the `tinycomments_can_delete` option is not included, the current author (`tinycomments_author`) cannot delete comment conversations created by other authors.
**Type:** `Function`
**Default Function:**
```js
function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === <Current_tinycomments_author>;
done({
canDelete: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to delete other author's comment conversations by adding `|| currentAuthor === '<Admin user>'`.
###### Example: Using `tinycomments_can_delete`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_delete: function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === currentAuthor;
done({
canDelete: allowed || currentAuthor === '<Admin user>'
});
}
});
```
##### `tinycomments_can_resolve`
{{site.requires_5_8v}}
_Optional_ - This option adds a _Resolve Conversation_ item to the dropdown menu of the first comment in a conversation. This callback sets the author permissions for _resolving comment conversations_.
**Type:** `Function`
###### Example: Using `tinycomments_can_resolve`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_resolve: function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === currentAuthor;
done({
canResolve: allowed || currentAuthor === '<Admin user>'
});
}
});
```
##### `tinycomments_can_delete_comment`
_Optional_ - This option sets the author permissions for _deleting comments_. If the `tinycomments_can_delete_comment` option is not included, the current author (`tinycomments_author`) cannot delete comments added by other authors.
**Type:** `Function`
**Default Function:**
```js
function (req, done, fail) {
var allowed = req.comment.author === <Current_tinycomments_author>;
done({
canDelete: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to delete other author's comments by adding `|| currentAuthor === '<Admin user>'`.
###### Example: Using `tinycomments_can_delete_comment`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_delete_comment: function (req, done, fail) {
var allowed = req.comment.author === currentAuthor;
done({
canDelete: allowed || currentAuthor === '<Admin user>'
});
}
});
```
##### `tinycomments_can_edit_comment`
_Optional_ - This option sets the author permissions for _editing comments_. If the `tinycomments_can_edit_comment` option is not included, the current author (`tinycomments_author`) cannot edit comments added by other authors.
**Type:** `Function`
**Default Function**
```js
function (req, done, fail) {
var allowed = req.comment.author === <Current_tinycomments_author>;
done({
canEdit: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to edit other author's comments by adding `|| currentAuthor === '<Admin user>'`.
###### Example: Using `tinycomments_can_edit_comment`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_edit_comment: function (req, done, fail) {
var allowed = req.comment.author === currentAuthor;
done({
canEdit: allowed || currentAuthor === '<Admin user>'
});
}
});
```
{% include misc/plugin-toolbar-button-id-boilerplate.md %}
{% include misc/plugin-menu-item-id-boilerplate.md %}
## Configuring the commented text properties
The highlight styles are now a part of the overall content skin and are changed through customizing the skin.
{{site.productname}} open source project [oxide](https://github.com/tinymce/oxide/blob/master/src/less/theme/content/comments/comments.less) (default skin), defines the variables used for changing the annotation colours.
Refer to the [documentation]({{site.baseurl}}/advanced/creating-a-skin/#creatingaskin) for building a skin using this repo.
For more information on configuring {{site.productname}} formats, refer to the [formats]({{site.baseurl}}/configure/content-formatting/#formats) section.
## Using Comments embedded mode with the Full Page plugin
Users have to be cautious when deciding the order in which the plugins are added in the plugins list.
Comments can cause an issue if the [Full Page]({{site.baseurl}}/plugins/opensource/fullpage/) plugin `fullpage` appears before Comments plugin `tinycomments` in the plugin list, and `tinycomments` is configured to use `embedded mode`.
The order that the plugins appear affects the order that the `getContent` hooks are processed in. This creates an issue with `tinycomments` working as expected since the `fullpage` plugin adds outer `<html>` elements before `tinycomments` adds its comment data. This leads to the comment data being in the wrong place. The consequence of this situation is that when a saved document is re-opened, the comment data is lost (but the highlights are still there).
For a workaround, please ensure that `tinycomments` is listed before `fullpage` in the plugins list. This should result in `tinycomments` working properly.
## Commands
The Comments plugin provides the following JavaScript commands.
{% include commands/comments-cmds.md %}

595
plugins/premium/comments/comments_callback_mode.md

@ -0,0 +1,595 @@
---
layout: default
title: Configuring the Comments plugin in callback mode
title_nav: Callback mode
description: Information on configuring the Comments plugin in callback mode
keywords: comments commenting tinycomments callback
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
**Callback mode** is the default mode for [the Comments plugin]({{site.baseurl}}/plugins/premium/comments/). In the callback mode, callback functions are required to save user comments on a server. The Comments functions (create, reply, edit, delete comment, delete all conversations, resolve, and lookup) are configured differently depending upon the server-side storage configuration.
## Interactive example
The following example uses a simulated server (provided by [Polly.js](https://netflix.github.io/pollyjs/)) which has been hidden from the example javascript to keep the example code concise. The interactions between TinyMCE and Polly.js are visible in the browser console.
{% include live-demo.html id="comments-callback" %}
### How the comments plugin works in callback mode
All options accept functions incorporating `done` and `fail` callbacks as parameters. The function return type is not important, but all functions must call exactly one of these two callbacks: `fail` or `done`.
* The `fail` callback takes either a string or a JavaScript Error type.
* The `done` callback takes an argument specific to each function.
Most (create, reply, and edit) functions require an `id` identifying the **current author**.
Current author
: The Comments plugin does not know the name of the current user. Determining the current user and storing the comment related to that user, has to be configured by the developer.
After a user comments (triggering `tinycomments_create` for the first comment, or `tinycomments_reply` for subsequent comments), the Comments plugin requests the updated conversation using `tinycomments_lookup`, which should now contain the additional comment with the proper author.
## Configuration options
### Required options
When using callback mode, the Comments plugin requires callback functions for the following options:
* [`tinycomments_create`](#tinycomments_create)
* [`tinycomments_reply`](#tinycomments_reply)
* [`tinycomments_edit_comment`](#tinycomments_edit_comment)
* [`tinycomments_delete_comment`](#tinycomments_delete_comment)
* [`tinycomments_delete`](#tinycomments_delete)
* [`tinycomments_delete_all`](#tinycomments_delete_all)
* [`tinycomments_lookup`](#tinycomments_lookup)
The [`tinycomments_resolve`](#tinycomments_resolve) option is _optional_.
### `tinycomments_create`
The Comments plugin uses the `tinycomments_create` function to create a comment.
The `tinycomments_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 `tinycomments_create` function is given a request (req) object as the first parameter, which has these fields:
`content`
: The content of the comment to create.
`createdAt`
: The date the comment was created.
The `done` callback should accept the following object:
```js
{
conversationUid: string, // the new conversation uid
// Optional error callback which will be run if the new conversation could not be created
onError: function (err) { ... },
// Optional success callback which will be run when the new conversation is successfully created
onSuccess: function (uid) { ... }
}
```
For example:
```js
function create_comment(ref, done, fail) {
let content = ref.content;
let createdAt = ref.createdAt;
fetch('https://api.example/conversations/', {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to create comment');
}
return response.json();
})
.then((ref2) => {
let conversationUid = ref2.conversationUid;
done({ conversationUid: conversationUid });
})
.catch((e) => {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment, // Add the callback to TinyMCE
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_reply`
The Comments plugin uses the `tinycomments_reply` function to reply to a comment.
The `tinycomments_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 {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_reply` function is given a request (req) object as the first parameter, which has these fields:
`conversationUid`
: The uid of the conversation the reply is targeting.
`content`
: The content of the comment to create.
`createdAt`
: The date the comment was created.
The `done` callback should accept the following object:
```js
{
commentUid: string // the value of the new comment uid
}
```
For example:
```js
function reply_comment(ref, done, fail) {
let conversationUid = ref.conversationUid;
let content = ref.content;
let createdAt = ref.createdAt;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'POST',
body: JSON.stringify({ content: content, createdAt: createdAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to reply to comment');
}
return response.json();
})
.then((ref2) => {
let commentUid = ref2.commentUid;
done({ commentUid: commentUid });
})
.catch((e) => {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment, // Add the callback to TinyMCE
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_edit_comment`
The Comments plugin uses the `tinycomments_edit_comment` function to edit a comment.
The `tinycomments_edit_comment` function allows updating or changing existing comments and returns via the `done` callback once successful. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_edit_comment` function is given a request (req) object as the first parameter, which has these fields:
`conversationUid`
: The uid of the conversation the reply is targeting.
`commentUid`
: The uid of the comment to edit (it can be the same as `conversationUid` if editing the first comment in a conversation).
`content`
: The content of the comment to create.
`modifiedAt`
: The date the comment was modified.
The `done` callback should accept the following object:
```js
{
canEdit: boolean, // whether or not the Edit succeeded
reason: string? // an optional string explaining why the edit was not allowed (if canEdit is false)
}
```
For example:
```js
function edit_comment(ref, done, fail) {
let conversationUid = ref.conversationUid;
let commentUid = ref.commentUid;
let content = ref.content;
let modifiedAt = ref.modifiedAt;
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'PUT',
body: JSON.stringify({ content: content, modifiedAt: modifiedAt }),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
}
)
.then((response) => {
if (!response.ok) {
throw new Error('Failed to edit comment');
}
return response.json();
})
.then((ref2) => {
let canEdit = ref2.canEdit;
done({ canEdit: canEdit });
})
.catch((e) => {
fail(e);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment, // Add the callback to TinyMCE
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_resolve`
{{site.requires_5_8v}}
This option adds a _Resolve Conversation_ item to the dropdown menu of the first comment in a conversation.
The `tinycomments_resolve` function should asynchronously return a flag indicating whether the comment thread was resolved using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_resolve` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
```js
{
canResolve: boolean // whether or not the conversation can be resolved
reason?: string // an optional string explaining why resolving was not allowed (if canResolve is false)
}
```
> **Note**: Failure to resolve due to permissions or business rules should be indicated by `canResolve: false`, while unexpected errors should be indicated using the `fail` callback.
For example:
```js
function resolve_comment_thread(ref, done, fail) {
let conversationUid = ref.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'PUT',
}).then((response) => {
if (response.ok) {
done({ canResolve: true });
} else if (response.status === 403) {
done({ canResolve: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_resolve: resolve_comment_thread, // Add the callback to TinyMCE
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete_comment`
The `tinycomments_delete_comment` function should asynchronously return a flag indicating whether the comment or comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete_comment` function is given a request (req) object as the first parameter, which has these fields:
`conversationUid`
: The uid of the conversation the reply is targeting.
`commentUid`
: The uid of the comment to delete (cannot be the same as `conversationUid`).
The `done` callback should accept the following object:
```js
{
canDelete: boolean, // whether or not an individual comment can be deleted
reason: string? // an optional reason explaining why the delete was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
For example:
```js
function delete_comment(ref, done, fail) {
let conversationUid = ref.conversationUid;
let commentUid = ref.commentUid;
fetch(
'https://api.example/conversations/' + conversationUid + '/' + commentUid,
{
method: 'DELETE',
}
).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread, // Add the callback to TinyMCE
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete`
The `tinycomments_delete` function should asynchronously return a flag indicating whether the comment thread was removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
```js
{
canDelete: boolean // whether or not the conversation can be deleted
reason: string? // an optional string explaining why the delete was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback.
For example:
```js
function delete_comment_thread(ref, done, fail) {
let conversationUid = ref.conversationUid;
fetch('https://api.example/conversations/' + conversationUid, {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread, // Add the callback to TinyMCE
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_delete_all`
The `tinycomments_delete_all` function should asynchronously return a flag indicating whether all the comment threads were removed using the `done` callback. Unrecoverable errors are communicated to {{site.productname}} by calling the `fail` callback instead.
The `tinycomments_delete_all` function is given a request (req) object as the first parameter with no fields.
The `done` callback should accept the following object:
```js
{
canDelete: boolean, // whether or not all conversations can be deleted
reason: string? // an optional string explaining why the deleteAll was not allowed (if canDelete is false)
}
```
> **Note**: Failure to delete due to permissions or business rules should be indicated by `canDelete: false`, while unexpected errors should be indicated using the `fail` callback.
For example:
```js
function delete_all_comment_threads(_req, done, fail) {
fetch('https://api.example/conversations', {
method: 'DELETE',
}).then((response) => {
if (response.ok) {
done({ canDelete: true });
} else if (response.status === 403) {
done({ canDelete: false });
} else {
fail(new Error('Something has gone wrong...'));
}
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads, // Add the callback to TinyMCE
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment
});
```
### `tinycomments_lookup`
The Comments plugin uses the `tinycomments_lookup` function to retrieve an existing conversation using a conversation's unique ID.
The **Display names** configuration must be considered for the `tinycomments_lookup` function:
Display names
: The Comments plugin uses a simple string for the display name. For the `lookup` function, Comments expects each comment to contain the author's display name, not a user ID, as Comments does not know the user identities. The `lookup` function should be implemented considering this and resolve user identifiers to an appropriate display name.
The conventional conversation object structure that should be returned via the `done` callback is as follows:
The `tinycomments_lookup` function is passed a (`req`) object as the first parameter, which contains the following key-value pair:
`conversationUid`
: The uid of the conversation the reply is targeting.
The `done` callback should accept the following object:
```js
{
conversation: {
uid: string, // the uid of the conversation,
comments: [
{
author: string, // author of first comment
authorName: string, // optional - Display name to use instead of author. Defaults to using `author` if not specified
createdAt: date, // when the first comment was created
content: string, // content of first comment
modifiedAt: date, // when the first comment was created/last updated
uid: string // the uid of the first comment in the conversation
},
{
author: string, // author of second comment
authorName: string, // optional - Display name to use instead of author. Defaults to using `author` if not specified
createdAt: date, // when the second comment was created
content: string, // content of second comment
modifiedAt: date, // when the second comment was created/last updated
uid: string // the uid of the second comment in the conversation
}
]
}
}
```
> **Note**: The dates should use [ISO 8601 format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString). This can be generated in JavaScript with: `new Date().toISOString()`.
For example:
```js
function lookup_comment({ conversationUid }, done, fail) {
let lookup = async function () {
let convResp = await fetch(
'https://api.example/conversations/' + conversationUid
);
if (!convResp.ok) {
throw new Error('Failed to get conversation');
}
let comments = await convResp.json();
let usersResp = await fetch('https://api.example/users/');
if (!usersResp.ok) {
throw new Error('Failed to get users');
}
let { users } = await usersResp.json();
let getUser = function (userId) {
return users.find((u) => {
return u.id === userId;
});
};
return {
conversation: {
uid: conversationUid,
comments: comments.map((comment) => {
return {
...comment,
content: comment.content,
authorName: getUser(comment.author)?.displayName,
};
}),
},
};
};
lookup()
.then((data) => {
console.log('Lookup success ' + conversationUid, data);
done(data);
})
.catch((err) => {
console.error('Lookup failure ' + conversationUid, err);
fail(err);
});
}
tinymce.init({
selector: '#editor',
plugins: 'tinycomments',
tinycomments_mode: 'callback',
tinycomments_create: create_comment,
tinycomments_reply: reply_comment,
tinycomments_edit_comment: edit_comment,
tinycomments_delete: delete_comment_thread,
tinycomments_delete_all: delete_all_comment_threads,
tinycomments_delete_comment: delete_comment,
tinycomments_lookup: lookup_comment // Add the callback to TinyMCE
});
```
{% include plugins/comments_open_sidebar.md %}
{% include plugins/comments_highlighting_css.md %}

16
plugins/premium/comments/comments_commands_events_apis.md

@ -0,0 +1,16 @@
---
layout: default
title: Commands for the comments plugin
title_nav: Commands
description: Information on the commands provided with the comments plugin.
keywords: comments commenting tinycomments
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
## Commands
The Comments plugin provides the following JavaScript commands.
{% include commands/comments-cmds.md %}

211
plugins/premium/comments/comments_embedded_mode.md

@ -0,0 +1,211 @@
---
layout: default
title: Configuring the Comments plugin in embedded mode
title_nav: Embedded mode
description: Information on configuring the Comments plugin in embedded mode
keywords: comments commenting tinycomments embedded mode
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
## Add the Comments plugin in embeddded mode
To add the Comments plugin in embedded mode to the {{site.productname}}, configure the following options:
```js
tinymce.init({
selector: '#textarea',
plugins: 'tinycomments',
tinycomments_author: 'author',
tinycomments_author_name: 'Name of the commenter',
tinycomments_mode: 'embedded'
})
```
This is the minimum recommended setup for the Comments plugin in embedded mode. If the `tinycomments_author` and `tinycomments_author_name` options are not configured, all users will be assigned the name "_ANON_".
## Interactive example
{% include live-demo.html id="comments-embedded" %}
## Configuration options
### `tinycomments_author`
This option sets the author id to be used when creating or replying to comments.
**Type:** `String`
**Default Value:** `'Anon'`
#### Example: Using `tinycomments_author`
```js
tinymce.init({
selector: '#textarea',
tinycomments_author: 'embedded_journalist',
tinycomments_mode: 'embedded'
})
```
### `tinycomments_author_name`
> Available in Tiny Comments version 2.1 onwards.
_Optional_ - This option sets the author's display name to be used when creating or replying to comments. If this option is omitted, then the author id is used instead.
**Type:** `String`
#### Example: Using `tinycomments_author_name`
```js
tinymce.init({
selector: '#textarea',
tinycomments_author: 'embedded_journalist',
tinycomments_author_name: 'Embedded Journalist',
tinycomments_mode: 'embedded'
})
```
### `tinycomments_can_delete`
_Optional_ - This option sets the author permissions for _deleting comment conversations_. If the `tinycomments_can_delete` option is not included, the current author (`tinycomments_author`) cannot delete comment conversations created by other authors.
**Type:** `Function`
**Default Function:**
```js
function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === <Current_tinycomments_author>;
done({
canDelete: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to delete other author's comment conversations by adding `|| currentAuthor === '<Admin user>'`.
#### Example: Using `tinycomments_can_delete`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_delete: function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === currentAuthor;
done({
canDelete: allowed || currentAuthor === '<Admin user>'
});
}
});
```
### `tinycomments_can_resolve`
{{site.requires_5_8v}}
_Optional_ - This option adds a _Resolve Conversation_ item to the dropdown menu of the first comment in a conversation. This callback sets the author permissions for _resolving comment conversations_.
**Type:** `Function`
#### Example: Using `tinycomments_can_resolve`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_resolve: function (req, done, fail) {
var allowed = req.comments.length > 0 &&
req.comments[0].author === currentAuthor;
done({
canResolve: allowed || currentAuthor === '<Admin user>'
});
}
});
```
### `tinycomments_can_delete_comment`
_Optional_ - This option sets the author permissions for _deleting comments_. If the `tinycomments_can_delete_comment` option is not included, the current author (`tinycomments_author`) cannot delete comments added by other authors.
**Type:** `Function`
**Default Function:**
```js
function (req, done, fail) {
var allowed = req.comment.author === <Current_tinycomments_author>;
done({
canDelete: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to delete other author's comments by adding `|| currentAuthor === '<Admin user>'`.
#### Example: Using `tinycomments_can_delete_comment`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_delete_comment: function (req, done, fail) {
var allowed = req.comment.author === currentAuthor;
done({
canDelete: allowed || currentAuthor === '<Admin user>'
});
}
});
```
### `tinycomments_can_edit_comment`
_Optional_ - This option sets the author permissions for _editing comments_. If the `tinycomments_can_edit_comment` option is not included, the current author (`tinycomments_author`) cannot edit comments added by other authors.
**Type:** `Function`
**Default Function**
```js
function (req, done, fail) {
var allowed = req.comment.author === <Current_tinycomments_author>;
done({
canEdit: allowed
});
}
```
The following example extends the default behavior to allow the author `<Admin user>` to edit other author's comments by adding `|| currentAuthor === '<Admin user>'`.
#### Example: Using `tinycomments_can_edit_comment`
```js
var currentAuthor = 'embedded_journalist';
tinymce.init({
selector: '#textarea',
tinycomments_author: currentAuthor,
tinycomments_can_edit_comment: function (req, done, fail) {
var allowed = req.comment.author === currentAuthor;
done({
canEdit: allowed || currentAuthor === '<Admin user>'
});
}
});
```
{% include plugins/comments_open_sidebar.md %}
{% include plugins/comments_embed_fullpage_issues.md %}
{% include plugins/comments_highlighting_css.md %}

15
plugins/premium/comments/comments_toolbars_menus.md

@ -0,0 +1,15 @@
---
layout: default
title: Toolbar buttons and menu items for the comments plugin
title_nav: Toolbar buttons and menu items
description: Details of the toolbar buttons and menu items provided for the comments plugin.
keywords: comments commenting tinycomments
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
{% include misc/plugin-toolbar-button-id-boilerplate.md %}
{% include misc/plugin-menu-item-id-boilerplate.md %}

101
plugins/premium/comments/comments_using_comments.md

@ -0,0 +1,101 @@
---
layout: default
title: Using TinyMCE Comments
title_nav: Using Comments
description: How to add, edit, resolve, and remove comments in TinyMCE
keywords: comments commenting tinycomments
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
I'm trying to:
- [Add a comment](#addacomment).
- [Edit a comment](#editacomment).
- [Delete a comment](#deleteacomment).
- [Delete a comment thread (conversation)](#deleteacommentthreadconversation).
- [Resolve a comment thread (conversation)](#resolveacommentthreadconversation).
- [Show or view a comment](#showorviewacomment).
- [Delete all comment threads](#deleteallcommentthreads).
## Add a comment
1. Select the text from the desired location in the editor body.
1. From the navigation menu, choose **Insert**-> **Add Comment** or click on the **Add comment** ![Add comment]({{site.baseurl}}/images/icons/comment-add.svg) toolbar button to add the comment.
1. The Comment box appears in the sidebar of the editor instance.
1. Type a comment in the comment box, the "_Say something…_" placeholder text will disappear.
1. Press **Clear** to discard or **Save** to store the input comment.
**Result**: The selected text will be highlighted as per the configured options. The following screen with the option for editing, deleting, and replying to the comment, will appear.
![Delete Conversation]({{site.baseurl}}/images/comments-edit.png)
Note: The above procedure can be followed for adding multiple comments to the document.
## Edit a comment
Follow this procedure to edit a comment.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Edit** from the menu items.
1. The comment field becomes editable. Make the required changes.
1. Click **Cancel** to discard or **Save** to store the changes.
## Delete a comment
Follow this procedure to delete a comment. This option is not available for the first comment in a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Delete** from the menu items.
1. The following options appear in the comments sidebar:<br/>
![delete comment]({{site.baseurl}}/images/comments-delete-comment.png)
1. Click **Cancel** to cancel the action or **Delete** to remove the comment from the conversation.
## Delete a comment thread (conversation)
This option is only available for the first comment in a conversation. Once the comment is saved, follow this procedure to delete a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Delete conversation** from the menu items.
1. The following decision dialog box will appear:<br/>
![delete conversation]({{site.baseurl}}/images/comments-delete-conversation.png)
1. Click **Cancel** to cancel the action or **Delete** to remove the conversation.
**Result**: The conversation and all its subsequent comments will be deleted.
## Resolve a comment thread (conversation)
{{site.requires_5_8v}}
> **NOTE**: This feature requires the [`tinycomments_resolve`]({{site.baseurl}}/advanced/configuring-comments-callbacks/#tinycomments_resolve) or [`tinycomments_can_resolve`]({{site.baseurl}}/plugins/premium/comments/#tinycomments_can_resolve) setting to be configured.
This option is only available for the first comment in a conversation. Once a comment is saved, follow this procedure to resolve a conversation.
1. Click on the ellipsis ![(ellipsis - 3 horizontal dots)]({{site.baseurl}}/images/icons/image-options.svg) icon above the comments box to expand the menu.
1. Select **Resolve conversation** from the menu items.
1. The following decision dialog box will appear:<br/>
![resolve conversation]({{site.baseurl}}/images/comments-resolve-conversation.png)
1. Click **Cancel** to cancel the action or **Resolve** to resolve the conversation.
**Result**: The conversation will be resolved.
## Show or view a comment
Follow this procedure to display the comments sidebar:
1. Place the cursor on the desired text in the editor body:
1. From the navigation menu, choose **View** -> **Show Comment** or click on the **Show Comments**![Comments]({{site.baseurl}}/images/comments-toolbar-button.png) toggle toolbar button to display the comment.
**Result**: The comments sidebar will appear and display the corresponding conversation for the highlighted text.
## Delete all comment threads
Follow this procedure to delete all conversations in the document:
1. From the navigation menu, choose **File** -> **Delete all conversations** option to delete all the comments in a document.
1. The following decision dialog box will appear:<br />
![Delete all conversations]({{site.baseurl}}/images/comments-delete-conversations.png)
1. Click **Ok** to remove the all the comments or **Cancel** to dismiss the action.
**Result**: All the comments for the selected document will be deleted.

25
plugins/premium/comments/index.md

@ -0,0 +1,25 @@
---
layout: default
title: Tiny Comments
title_nav: Comments
description_short: The TinyMCE Comments plugin
description: This section lists the premium plugins provided by Tiny.
type: folder
---
{% assign navigation = site.data.nav %}
{% for entry in navigation %}
{% if entry.url == "plugins" %}
{% for subentry in entry.pages %}
{% if subentry.url == "premium" %}
{% for subsubentry in subentry.pages %}
{% if subsubentry.url == "comments" %}
{% assign links = subsubentry.pages %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% include index.html links=links %}

58
plugins/premium/comments/introduction_to_tiny_comments.md

@ -0,0 +1,58 @@
---
layout: default
title: Introduction to Tiny Comments
title_nav: Introduction
description: Tiny Comments provides the ability to add comments to the content and collaborate with other users for content editing.
keywords: comments commenting tinycomments
---
{% assign pluginname = "Comments" %}
{% assign plugincode = "comments" %}
## Contents
* For help using comments in TinyMCE, see: [Using comments]({{site.baseurl}}/plugins/premium/comments/comments_using_comments/).
* For an overview of the TinyMCE Comments plugin, see: [Overview](#overview).
* For information on adding and configuring the comments plugin for TinyMCE, see: [Getting started with the Comments plugin - Selecting a mode](#gettingstartedwiththecommentsplugin-selectingamode).
## Overview
{{site.premiumplugin}}
The Comments plugin provides the ability to start or join a conversation by adding comments to the content within the {{site.productname}} editor.
### Collaborate on your projects within your content
The Comments plugin provides:
* A **user interface** to collaborate on content by creating and replying to comments.
* A way to control the delete and resolve operations on a comment or comment thread.
### Primary Comments functions
The Comments plugin allows the user to perform the following functions:
* Create a comment
* Edit a comment
* Reply to a comment
* Lookup a comment
* Resolve a comment thread
* Delete a comment or comment thread
### Interactive example
The following example shows how to configure the Comments plugin in **embedded** mode. For information on configuring the Comments plugin, see: [Comments plugin Modes](#gettingstartedwiththecommentsplugin-selectingamode).
{% include live-demo.html id="comments-embedded" %}
## Getting started with the Comments plugin - Selecting a mode
The Comments plugin is available in two _modes_: **Callback mode** and **Embedded mode**.
Callback Mode
: This is the default mode for the Comments plugin. This mode is used to store the comments outside the content on a server, such as a database. This mode requires a number of callback functions to handle comment data.
: For instructions on configuring the Comments plugin in callback mode, see: [Configuring the Comments plugin in callback mode]({{site.baseurl}}/plugins/premium/comments/comments_callback_mode/)
Embedded Mode
: This mode stores the comments within the content. No callbacks need to be configured for this mode.
: For instructions on configuring the Comments plugin in embedded mode, see: [Configuring the Comments plugin Comments in embedded mode]({{site.baseurl}}/plugins/premium/comments/comments_embedded_mode/)
Loading…
Cancel
Save