Browse Source
DOC-840: fixing the comments docs (#2007)
DOC-840: fixing the comments docs (#2007)
Co-authored-by: Lee Newson <lee.newson@tiny.cloud>pull/2018/head

committed by
tylerkelly13

26 changed files with 2489 additions and 416 deletions
-
8_data/nav.yml
-
2_includes/configuration/contextmenu.md
-
214_includes/live-demos/comments-callback/example.js
-
32_includes/live-demos/comments-callback/index.html
-
686_includes/live-demos/comments-callback/index.js
-
0_includes/live-demos/comments-embedded/index.html
-
10_includes/live-demos/comments-embedded/index.js
-
2_includes/live-demos/full-featured/example.js
-
2_includes/live-demos/full-featured/index.js
-
2_includes/live-demos/readonly-demo/index.html
-
28_includes/live-demos/readonly-demo/style.css
-
9_includes/plugins/comments_embed_fullpage_issues.md
-
9_includes/plugins/comments_highlighting_css.md
-
45_includes/plugins/comments_open_sidebar.md
-
483advanced/configuring-comments-callbacks.md
-
2demo/comments-2.md
-
4demo/pageembed.md
-
2enterprise/tiny-comments.md
-
344plugins/premium/comments.md
-
595plugins/premium/comments/comments_callback_mode.md
-
16plugins/premium/comments/comments_commands_events_apis.md
-
211plugins/premium/comments/comments_embedded_mode.md
-
15plugins/premium/comments/comments_toolbars_menus.md
-
101plugins/premium/comments/comments_using_comments.md
-
25plugins/premium/comments/index.md
-
58plugins/premium/comments/introduction_to_tiny_comments.md
@ -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'); |
|||
}); |
|||
}, |
|||
}); |
@ -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> |
@ -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,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; |
|||
} |
|||
|
@ -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. |
@ -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. |
@ -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 %} |
@ -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**  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. |
|||
|
|||
 |
|||
|
|||
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  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  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/> |
|||
 |
|||
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  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/> |
|||
 |
|||
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  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/> |
|||
 |
|||
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** 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 /> |
|||
 |
|||
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 %} |
@ -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 %} |
@ -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 %} |
@ -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 %} |
@ -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 %} |
@ -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**  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. |
|||
|
|||
 |
|||
|
|||
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  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  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/> |
|||
 |
|||
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  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/> |
|||
 |
|||
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  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/> |
|||
 |
|||
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** 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 /> |
|||
 |
|||
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. |
@ -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 %} |
@ -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/) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue