From 70fcb77fb57016405b310cf9c1ecdb9b9b0f1296 Mon Sep 17 00:00:00 2001 From: shikha Date: Mon, 7 Jan 2019 12:37:47 +1000 Subject: [PATCH] Update Comments --- _data/nav.yml | 1 + advanced/configuring-comments-callbacks.md | 225 ++++++++++++ enterprise/tiny-comments.md | 2 +- images/3dots.png | Bin 0 -> 278 bytes images/comment-disabled.png | Bin 0 -> 518 bytes images/commentedit.png | Bin 0 -> 12086 bytes images/decision.png | Bin 0 -> 8902 bytes images/decision2.png | Bin 0 -> 9665 bytes images/delete.png | Bin 0 -> 18624 bytes images/highlight.png | Bin 0 -> 6096 bytes plugins/comments.md | 389 ++++++--------------- 11 files changed, 341 insertions(+), 276 deletions(-) create mode 100644 advanced/configuring-comments-callbacks.md create mode 100644 images/3dots.png create mode 100644 images/comment-disabled.png create mode 100644 images/commentedit.png create mode 100644 images/decision.png create mode 100644 images/decision2.png create mode 100644 images/delete.png create mode 100644 images/highlight.png diff --git a/_data/nav.yml b/_data/nav.yml index 7ba074c..c7353d6 100644 --- a/_data/nav.yml +++ b/_data/nav.yml @@ -454,6 +454,7 @@ - url: "creating-a-skin" - url: "creating-a-plugin" - url: "annotations" + - url: "configuring-comments-callbacks" - url: "yeoman-generator" - url: "creating-custom-dialogs" - url: "creating-custom-notifications" diff --git a/advanced/configuring-comments-callbacks.md b/advanced/configuring-comments-callbacks.md new file mode 100644 index 0000000..30b5c8b --- /dev/null +++ b/advanced/configuring-comments-callbacks.md @@ -0,0 +1,225 @@ +--- +layout: default +title: Configuring callbacks for comments +title_nav: Configuring callbacks for comments +description: Instructions for configuring callbacks for Comments +keywords: comments commenting tinycomments callback +--- + +## Introduction + +Callback mode is the default mode for [Tiny Comments]({{site.baseurl}}/plugins/comments/). In the callback mode the user needs to configure storage to be able to save comments on the server. The storage settings can be configured to either persist the comments immediately or save them at the same time as the content. +How the comments are stored effects when other users see new comments. The Comments functions (create, reply, edit, delete comment, delete all conversations, and lookup) are configured differently depending upon the server-side storage configuration. + +### Helper Functions + +Comments uses the following helper functions: + +* **setConversation(uid, conversation)** `setConversation` is a function written to synchronously write a conversation to a form field for submission to the server later. + +* **randomString()** `randomString()` is a function used in the `create` function to return a 62-bits random strings to provision a large number of UIDs. + +* **getConversation(uid)** `getConversation` is a function written to synchronously retrieve an existing conversation from a form field populated by the server. + +* **deleteConversation(uid)** `deleteConversation(uid)` is a function to allow only the first commenter to delete a comment. + +* **deleteAllConversations()** `deleteAllConversations()` is a function to delete all the conversations in a document. + +* **getAuthorDisplayName(uid)** `getAuthorDisplayName(authorID)` is a function to retrieve an existing conversation via a conversation UID (`authorID` in our example). + + +### Comments Implementation Functions + +Comments requires the following functions to be defined: + +```js +tinymce.init({ + ... + tinycomments_create: create, + tinycomments_reply: reply, + tinycomments_delete: del, + tinycomments_delete_all: deleteAll, + tinycomments_delete_comment: deleteComment, + tinycomments_lookup: lookup, + tinycomments_edit_comment: editComment +}); +``` + +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`. + +* The `fail` callback takes either a string or a JavaScript Error type. + +* The `done` callback takes an argument specific to each function. + +### Considerations + +#### Display Names + +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. + +#### Current Author + +Comments does not know the name of the current user. After a user comments (triggering create for the first comment, or reply for subsequent comments) Comments requests the updated conversation via `lookup`, which should now contain the additional comment with the proper author. Determining the current user, and storing the comment related to that user, has to be done by the user. + +### Create + +Comments uses the Conversation create function to create a comment. + +The create function saves the comment as a new conversation and returns a unique conversation ID via the `done` callback. If an unrecoverable error occurs, it should indicate this with the fail callback. + +The create function is given a request 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 needs to take an object of the form: + +```js +{ + conversationUid: whatever the new conversation uid is +} +``` + +### Reply + +Comments uses the conversation `reply` function to reply to a comment. + +The `reply` function saves the comment as a reply to an existing conversation and returns via the `done` callback once successful. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. + +The `reply` function is given a request object as the first parameter, which has these fields: + +* **conversationUid**: the uid of the conversation the reply is a part of. + +* **content**: the content of the comment to create. + +* **createdAt**: the date the comment was created. + +The done callback needs to take an object of the form: + +```js +{ + commentUid: the value of the new comment uid +} +``` + +### Edit + +Comments uses the conversation `edit` function to edit a comment. + +The `edit` function allows updating or changing an original comments and returns via the `done` callback once successful. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. + +The `edit` function is given a request object as the first parameter, which has these fields: + +* **conversationUid**: the uid of the conversation the reply is a part of. + +* **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 needs to take an object of the form: + +```js +{ + canEdit: whether or not the Edit succeeded + reason: an optional string explaining why the edit was not allowed (if canEdit is false) +} +``` + +### Delete + +The `delete` function should asynchronously return a flag indicating whether the comment/comment thread was removed using the `done` callback. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. + +The `delete` function is given a request object as the first parameter, which has this field: + +* **conversationUid**: the uid of the conversation the reply is a part of. + +The done callback needs to take an object of the form: + +```js +{ + canDelete:boolean + reason: 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. + +### DeleteAll + +The `deleteAll` 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 TinyMCE by calling the `fail` callback instead. + +The `deleteAll` function is given a request object as the first parameter with no fields. + +The done callback needs to take an object of the form: + +```js +{ + canDelete:boolean + reason: an optional string explaining why the delete all 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. + +### DeleteComments + +The `deleteComment` function should asynchronously return a flag indicating whether the comment/comment thread was removed using the `done` callback. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. + +The `deleteComments` function is given a request object as the first parameter, which has these fields: + +* **conversationUid**: the uid of the conversation the reply is a part of. +* **commentUid**: the uid of the comment to delete (cannot be the same as conversationUid) + +The done callback needs to take an object of the form: + +```js +{ + canDelete:boolean + reason: an optional reason +} +``` + +> Note: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback. + + +### Lookup + +Comments uses the Conversation `lookup` function to retrieve an existing conversation via a conversation unique ID. + +The conventional conversation object structure that should be returned via the `done` callback is as follows: + +The `lookup` function is given a request object as the first parameter, which has this field: + +* **conversationUid**: the uid of the conversation the reply is a part of. + +The done callback needs to take an object of the form: + +```js +{ + comments: [ + { + author: 'Demouser1', + createdAt: 'date', + content: 'Starter', + modifiedAt: 'date', + uid: 'asfasdf87dfas08asd0fsadflsadf' + }, + { + author: 'Demouser2', + createdAt: 'date', + content: 'Reply', + modifiedAt: 'date', + uid: 'asfasdf87dfas08asd0fsadflsadg’' + }, + ] +} + + ] +} + +``` + +For more information on Comments commercial feature, visit our [Premium Features]({{ site.baseurl }}/enterprise/tiny-comments/) page. diff --git a/enterprise/tiny-comments.md b/enterprise/tiny-comments.md index fa7552a..a518651 100644 --- a/enterprise/tiny-comments.md +++ b/enterprise/tiny-comments.md @@ -27,7 +27,7 @@ The Tiny Comments plugin allows the user to perform the following functions: * Reply to a comment * Delete a comment/comment thread * Lookup a comment -* Store a comment +* Edit a comment ## Tiny Comments Integration diff --git a/images/3dots.png b/images/3dots.png new file mode 100644 index 0000000000000000000000000000000000000000..da8246e06c56fdd81904667815246495a35a6b23 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoW!3HGbD~^`}DVAa<&kznEsNqQI04XSOjVKAu zPb(=;EJ|fa&&$tE)h$jgN=?lx&d4u$&%ECbsAz?!i(`mK=i6x;1rHhUxb820xuN_c zLki>aa|}22eYy0QH!%3>l}9pbEClP zdoBOXpVD?0x(jEWG}Ya?Y-!=F&7VEq%)B#-=kb1^n;AS^{an^LB{Ts5C0S_j literal 0 HcmV?d00001 diff --git a/images/comment-disabled.png b/images/comment-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..dc834eaa2fe57920e341d9b8ad380984a151a060 GIT binary patch literal 518 zcmV+h0{Q)kP)X1^@s6z#LUx00004b3#c}2nYxW zdZgXgFbngSdJ^%m#m`OxIR7i>K zmQN^yVI0RlV@~$EvT=Nyc4D!tcG+R26bCM}0|$zWlnVz3+*V2)$PPGgP$)?$WvP`v z&A6C9JF+?3T-MwBI&oqo`W;7?01Q42@2ZN~MdENs@(ynQ5Av z8g+N3leKsE6T6Ay^LlXDPWGoS zNJ&qkxOC2lgLG`ml0hyUR~ee9~dE( ze$FdAKiXw(aha9%&4Lq1mP*!wv)JvjIEVqBQfZFQqQq|E0L(9~0#IwK;UVz|KyObM zRg&dHk)vM5?Q&8jSx{8%bD5EBf&FOgAH zO;t7FYVfpa8{2!I)`(U{QMLCJJjVM(^27g$ukh_VD+Z(N1*UcIiQ?PHH~;_u07*qo IM6N<$f(Z!eZ~y=R literal 0 HcmV?d00001 diff --git a/images/commentedit.png b/images/commentedit.png new file mode 100644 index 0000000000000000000000000000000000000000..0fd1a078b521fd3a68b2453e3df6527911a180c7 GIT binary patch literal 12086 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 hAvHA=i@hPBSw8ux-$57i1*pKc5J-3JeT6 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

1r{?knxAu7}BY5#ehny1GBMlYEI6ev7yYj+k#(N zlz?@h0UtiB+;$918WiYDNrV8w$PNO-%??@leW8=_edl9Ub)PIxmEE6&3jfa2I zl@l?!>Q)f{0xw6P-IJ4i`B7Enoa#&wTkh>FFhIWbrXaOUFiqiP4m)+^d$ zcDyavNn&JF4&AgaZ;hNkBo8ol01;GP-D4xOg|>&(&PTP380=SbIUZzDvx6{2os_ zK`mGHJ4a#My)ie(`Jb$=-lG5=r<2u>?jS(NE2jKwZd9IQ3VR+&5tR>8_U1P|ugJXr zOkEv;4o**Z&v!-@uAfP~D!g~p{~mRC0o8UwUvoHxB8x973Y5ai{q}cL>`Qk>Y6Xq_ zzIs&G&J-(Sw1Z;|&}MujUMbp%9W3iiXXs$;H&a=r-zmgOP8RFeD4d|P--dg#xe+-V z_Qz9uP<4_{n(IQkCy0bJrQ)S=d01GY6BFN>12e#0X646kW%|3ZsW6}A zDHE7&`kyotN0PytyVA+5Ve)c)FQld?m>N1BMWkZpKMJ{t4P||-3@CIjaB$+G;{`MG zeEr9qLgk@wTMDQ%U*IUnK-msX-NJy?3!ZnZP`I1%@eVIU=!H8>K%q`SsSxkxrC~6Dv~P`yVQIIJp^< zgqByPl)Ns78X^~I6Aq6M7r@^~r6^f5}DS8>Sz{!qcbUE6${f_y}-X z0J*A6O>RCh_dq{6ED<75G9n6sPxE6%IG#kbTCpu&7D^1+pWQ|sho?|iZYXh*p;vwH zFnhYiSG*XmL_!$)8p9FD{YRg~H-5{J(>kBkx4GxuKYRIA711voWWYdwvLBTstHeJA F{y(#KOCtaP literal 0 HcmV?d00001 diff --git a/images/delete.png b/images/delete.png new file mode 100644 index 0000000000000000000000000000000000000000..380b0b415982d541a82f358f922acead02556068 GIT binary patch literal 18624 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 literal 0 HcmV?d00001 diff --git a/plugins/comments.md b/plugins/comments.md index 83f0237..a71b72f 100644 --- a/plugins/comments.md +++ b/plugins/comments.md @@ -1,6 +1,6 @@ --- layout: default -title: Comments +title: Comments plugin 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 @@ -8,317 +8,156 @@ keywords: comments commenting tinycomments ## Introduction -The Tiny Comments plugin provides the user an ability to start or join a conversation by adding comments to the content within the TinyMCE editor. The Comments plugin is built upon the new [Annotations API]({{ site.baseurl }}/advanced/annotations/) and uses annotations to create comment threads (conversations). +The Tiny Comments plugin provides the ability to start or join a conversation by adding comments to the content within the TinyMCE editor. The Comments plugin is built upon the [Annotations API]({{site.baseurl}}/advanced/annotations/) and uses annotations to create comment threads (conversations). -This section describes the various configuration options for the Comments plugin. +Comments is a premium plugin from Tiny. Please see the [Premium features]({{site.baseurl}}/enterprise/tiny-comments/) section for all the buying options. -## Storage +Once you have obtained the Comments plugin, refer to the following instructions for using it. -Like TinyMCE, the Comments plugin does not directly provide the user an ability to save the comments. You need to configure storage at your end to be able to save comments on your server. You can choose to configure your storage settings to either persist them immediately or save them at the same time as the content. +### Modes -How you store those comments affects when other users see new comments. The Comments functions (create, reply, delete, and lookup) are configured differently depending upon the server-side storage configuration. +There are two modes available in Comments that provide the ability to save comments. These modes are configured in the Comments settings. -In this chapter, we have provided examples of both ways of configuring Comments storage. +* **Callback Mode** - This is the default mode in Comments. This mode is used to configure storage and save comments on user’s server. This option gives the user a choice to configure the storage settings to either persist comments immediately or save them at the same time as the content. Additional callbacks are required to be configured to use Comments in the callback mode. Refer to the [configuring callbacks for comments]({{site.baseurl}}/advanced/configuring-comments-callbacks/) section, for more information. -### Storage - persist in real-time +* **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. -The following demo showcases the Comments functionality using storage configured to persist in real-time: +### Configuring Comments embedded mode -{% include codepen.html id="pOzxJw" %} - -### Storage - persist on content-save - -The following demo showcases the Comments functionality using storage configured to persist on content-save. - -{% include codepen.html id="4d07e4da27b1e7245b5333ed7413083b" %} - -#### Helper Functions - -We have used the following helper functions in our demo above: - -* **setConversation(uid, conversation)** -`setConversation` is a function written to synchronously write a conversation to a form field for submission to the server later. - -* **randomString()** -`randomString()` is a function used in the `create` function to return a 62-bits random strings to provision a large number of UIDs. - -* **getConversation(uid)** -`getConversation` is a function written to synchronously retrieve an existing conversation from a form field populated by the server. - -* **deleteConversation(uid)** -`deleteConversation(uid)` is a function to allow only the first commenter to delete a comment. - -* **getAuthorDisplayName(uid)** -`getAuthorDisplayName(authorID)` is a function to retrieve an existing conversation via a conversation UID (`authorID` in our example). - -## Comments Implementation Functions - -Comments requires four functions to be defined: +To configure Comments to use the embedded mode use the following script: ```js tinymce.init({ - ... - tinycomments_create: create, - tinycomments_reply: reply, - tinycomments_delete: del, - tinycomments_lookup: lookup -}); -``` - -All functions incorporate `done` and `fail` callbacks as parameters. The function return type is not important, but all functions must call one of these two callbacks. - -If you are persisting the comments to a form field to be persisted on document save, you likely would call the appropriate callback prior to the function returning. - -However, if you are persisting comments directly back to a server as they are made, you would call them asynchronously after the network call to do so had completed. - -### Considerations - -#### Display Names - -Comments expects each comment to contain the author's _display name_, not a user ID, as Comments does not know the user identities. Your implementation of `lookup` will most likely need to consider this and resolve user identifiers to an appropriate display name. - -#### Current Author - -Comments does not know the name of the current user. After a user comments (triggering `create` for the first comment, or `reply` for subsequent comments) Comments requests the updated conversation via `lookup`, which should now contain the additional comment with the proper author. Determining the current user, and storing the comment related to that user, has to be done by the user. - -### Create - -Comments uses the Conversation `create` function to create a comment. - -The `create` function saves the comment as a new conversation and returns a unique conversation ID via the `done` callback. If an unrecoverable error occurs, it should indicate this with the `fail` callback. - -The following are examples of how `create` can be implemented if you choose to configure your storage settings to be either persistent in real time or on content-save. - -#### Example - Storage - persist in real-time - -Here is an example of how `create` can be implemented using storage configured to persist in real-time: - -```js -function create(content, done, fail) { - fetch( - 'https://api.example/conversations/', - { method: 'POST', body: content } - ).then(function(response) { - return response.json(); - }).then(function(json) { - done(json.uid); - }).catch(function() { - fail(new Error('Something has gone wrong...')); - }); - } -``` - -#### Example - Storage - persist on content-save - -Here is an example of how `create` can be implemented using storage configured to persist on content-save: - -```js -var currentAuthorId = ... -function create(content, done, fail) { - // `randomString` should be written to produce random strings with a very low - // chance of collisions. - var uid = 'annotation-' + randomString(); - try { - // `setConversation` here is a function written to synchronously persist - // the new conversation to a form field for later submission to the server - setConversation( - uid, - [ { user: currentAuthorId, comment: content } ] - ); - done(uid); - } catch { - fail(new Error('Error creating conversation...')); - } -} -``` - -### Reply - -Comments uses the Conversation `reply` function to reply to a comment. - -The `reply` function saves the comment as a reply to an existing conversation and returns via the `done` callback once successful. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. - -The following are examples of how `reply` can be implemented if you choose to configure your storage settings to be either persistent in real time or on content-save. - -#### Example - Storage - persist in real-time - -Here is an example of how `reply` can be implemented using storage configured to persist in real-time: - -```js -function reply(uid, content, done, fail) { - fetch( - 'https://api.example/conversations/'+uid, - { method: 'PATCH', body: content } - ).then(function(response) { - if (response.ok) { - done(); - } else { - fail(new Error('Something has gone wrong...')); - } - }); - } -``` - -#### Example - Storage - persist on content-save - -Here is an example of how `reply` can be implemented using storage configured to persist on content-save: - -```js -var currentAuthorId = ... -function reply(uid, content, done, fail) { - try { - // "getConversation" here is a function written to synchronously retrieve an - // existing conversation from a form field populated by the server. - var comments = getConversation(uid); - // Add comment to the conversation - comments.push({ - user: currentAuthorId, - comment: content - }); - // Synchronously write the comment back to the form field, awaiting persist - // on document save. - setConversation(uid, comments); - done(); - } catch { - fail(new Error('Error replying to conversation...')); + selector: "#textarea", + menubar: 'file edit view insert format tools tc', +menu: { + tc: { + title: 'TinyComments', + items: 'addcomment' } -} - + }, + tinycomments_author: 'Embedded Journalist', + tinycomments_mode: 'embedded' + ... +}) ``` -### Delete +### Configuring the Comments toolbar button -Comments uses the Conversation `delete` function to delete an entire conversation. - -The `delete` function should asynchronously return a flag indicating whether the comment/comment thread was removed using the `done` callback. Unrecoverable errors are communicated to TinyMCE by calling the `fail` callback instead. - -The following are examples of how `delete` can be implemented if you choose to configure your storage settings to be either persistent in real time or on content-save. - -#### Example - Storage - persist in real-time - -Here is an example of how `delete` can be implemented using storage configured to persist in real-time: +Use the following script to configure the Comments toolbar button: ```js -function del(uid, done, fail) { - fetch( - 'https://api.example/conversations/'+uid, - { method: 'DELETE' } - ).then(function(response) { - if (response.ok) { - done(true); - } else if (response.status == 403) { - done(false) - } else { - fail(new Error('Something has gone wrong...')); - } - }); - } +tinymce.init({ + selector: "#textarea", + toolbar: 'bold italic underline insertfile | addcomment', + ... +}) ``` -#### Example - Storage - persist on content-save +Optional values: addcomment, showcomments -Here is an example of how `delete` can be implemented using storage configured to persist on content-save: +**Result**: The **Comments** ![**Comments**]({{site.baseurl}}/images/comment-disabled.png) toolbar button appears in the toolbar menu. The function of this button is to add comments to selected text. + +> Note: Currently, there are two toolbar buttons available: + +* `addcomment` - Provides the ability to add comments. + +* `showcomments`- Provides the ability to display comments field for the selected text. It is a toggle button and is used to hide the comments sidebar as well. + + +### Configuring the Comments menu item + +By default, when Comments is added to the plugin list, the default menus will have entries for `addcomment` (Insert Menu), showcomments (View Menu), and deleteallconversations (File Menu). + +For more information on configuring menu items refer to the [toolbar]({{site.baseurl}}/configure/editor-appearance/#toolbar) and [menu]({{site.baseurl}}/configure/editor-appearance/#menu) sections. + +Currently, there are three menu items available: + +* `addcomment` - Provides the ability to add comments. By default, this option can be accessed through **Insert** -> **Add comment** menu bar item. + +* `showcomments`- Provides the ability to display comments field for the selected text. It is a toggle button and is used to hide the comments as well. By default, this option can be accessed through **View** -> **Show comment** menu bar item. + +* `deleteallconversations`- Provides the ability to delete all the comments in the content. By default, this option can be accessed through **File** -> **Delete all conversations** menu bar item. + +### Configuring the commented text properties + +The editor needs to be configured to highlight the commented text. The following configuration is an example of displaying the text that the comment has been added to in a desired style: ```js - function del(uid, done, fail) { - fetch( - 'https://api.example/conversations/'+uid, - { method: 'DELETE' } - ).then(function(response) { - if (response.ok) { - done(true); - } else if (response.status == 403) { - done(false) - } else { - fail(new Error('Something has gone wrong...')); - } - }); - } +tinymce.init({ +... + content_style: '.mce-annotation { background: #fff0b7; } .tc-active-annotation {background: #ffe168; color: black; }', + ... +}) ``` -> Note: Failure to delete due to permissions or business rules is indicated by "false", while unexpected errors should be indicated using the "fail" callback. +**Result**: The commented text will be highlighted yellow. +![**Highlighted text**]({{site.baseurl}}/images/highlight.png) -### Lookup +For more information on TinyMCE formats, refer to the [formats]({{site.baseurl}}/configure/content-formatting/#formats) section. -Comments uses the Conversation `lookup` function to retrieve an existing conversation via a conversation unique ID. -The conventional conversation object structure that should be returned via the `done` callback is as follows: +### Using Comments -#### Conversation object +#### To add a comment -```js -{ - "comments": [ - , - , - ... - ] -} +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 **Comments** ![**Comments**]({{site.baseurl}}/images/comment-disabled.png) 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. -``` -#### Comment object +**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/commentedit.png) -```js -{ - "author": "Author Display Name", - "content": "This is the text of the comment" -} +Note: The above procedure can be followed for adding multiple comments to the document. -``` +#### Editing a comment +Follow this procedure to edit a comment. -The following are examples of how `lookup` can be implemented if you choose to configure your storage settings to be either persistent in real time or on content-save. +1. Click on the ![**3dots**]({{site.baseurl}}/images/3dots.png) 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. -#### Example - Storage - persist in real-time +#### Delete a comment +Follow this procedure to delete a comment. This option is not available for the first comment in a conversation. -Here is an example of how `lookup` can be implemented using storage configured to persist in real-time: +1. Click on the ![**3dots**]({{site.baseurl}}/images/3dots.png) 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: +![**delete comment**]({{site.baseurl}}/images/delete.png) +1. Click **Cancel** to save or **Delete** to remove the comment from the conversation. -```js - function lookup(uid, done, fail) { - fetch('https://api.example/conversations/'+uid) - .then(function(response) { return response.json(); }) - .then(function(json) { - var conversation = json.comments; - return fetch('https://api.example/users/') - .then(function(response) { return response.json(); }) - .then(function(json) { - var users = json.users; - var unknown = { displayName: 'Unknown' }; - return conversation.map(function(item) { - var user = users.find(function(v) { return v.id == item.user; }); - return { - author: (user || unknown).displayName, - content: item.comment - }; - }); - }); - }) - .then(function(comments) { - done({ comments: comments }); - }) - .catch(function() { - fail(new Error('Something has gone wrong...')); - }) - } -``` -#### Example - Storage - persist on content-save +#### 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. -Here is an example of how `lookup` can be implemented using storage configured to persist on content-save, utilizing an in-memory lookup function to resolve author display names: +1. Click on the ![**3dots**]({{site.baseurl}}/images/3dots.png) 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: +![**delete conversation**]({{site.baseurl}}/images/decision.png) +1. Click **Cancel** to save or **Delete** to remove the conversation. -```js -function lookup(uid, done, fail) { - try { - var comments = getConversation(uid).map(function(item) { - return { - author: getAuthorDisplayName(item.user), - content: item.comment - }; - }); - done({ comments: comments }); - } catch { - fail(new Error('Error looking up conversation...')); - } -} -``` +**Result**: The conversation and all its subsequent comments will be deleted. -For more information on Comments commercial feature, visit our [Premium Features]({{ site.baseurl }}/enterprise/tiny-comments/) page. +#### 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/comment-disabled.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: +![**Delete all conversations**]({{site.baseurl}}/images/decision2.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. + +Check out the [Comments demo]({{site.baseurl}}/demo/comments/) to try out this new feature.