{"id":125239,"date":"2014-02-13T07:30:00","date_gmt":"2014-02-13T12:30:00","guid":{"rendered":"http:\/\/premium.wpmudev.org\/blog\/?p=125239"},"modified":"2014-04-02T18:25:48","modified_gmt":"2014-04-02T22:25:48","slug":"how-to-have-paragraph-commenting-just-like-medium","status":"publish","type":"post","link":"https:\/\/wpmudev.com\/blog\/how-to-have-paragraph-commenting-just-like-medium\/","title":{"rendered":"How To Have Paragraph Commenting Just Like Medium"},"content":{"rendered":"<p>Paragraph commenting, or annotations is not exactly new. Readers have been scribbling in the margins of books, magazines and uni assignments for years.<\/p>\n<p>The online world has been slow to adopt this approach which is perhaps why Medium caused a stir and no shortage of admiring looks when it went the annotation route.<\/p>\n<p>Well, admire forlornly no more because I&#8217;m going to show you how to add paragraph commenting to your WordPress site.<\/p>\n<figure id=\"attachment_126089\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-ratio-large wp-image-126089\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/02\/800px-Mona_Lisa_margin_scribble-700x210.jpg\" alt=\"Photo of a manuscript with a note in the margin\" width=\"700\" height=\"210\" \/><figcaption class=\"wp-caption-text\">Leonardo&#8217;s model for Mona Lisa was a margin-scribbler way back in 1503<\/figcaption><\/figure>\n<p>There are existing annotation solutions for WordPress but they are generally theme dependent, or in the case of CommentPress actually provide a theme.<\/p>\n<p>I wanted to make this solution work on as many different themes as possible so I actually ditched the requirement to have margins to scribble in. That said, if your template does have margins then you&#8217;ll just need to do tweak the CSS to achieve Medium-esque commenting.<\/p>\n<h2>Solution Overview<\/h2>\n<figure id=\"attachment_126092\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-126092\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/02\/paracom-count.png\" alt=\"Screenshot showing the paragraphs with the comments count \" width=\"700\" height=\"318\" \/><figcaption class=\"wp-caption-text\">Each paragraph has an and a comment count<\/figcaption><\/figure>\n<p>Annotations, by their very nature, are ajax-based as page reloads would kill the user-experience.<\/p>\n<p>Obviously, for paragraph commenting to work, each comment has to be tied to the paragraph which means that the paragraph has to have a unique ID. Determining what is a paragraph is quite difficult within WordPress as post content is generally stored unfiltered.<\/p>\n<p>With no HTML it&#8217;s also difficult to assign ids to a paragraph, so I decided that the simpler solution was to do that processing on the client-side, when WordPress has converted the content to HTML.<\/p>\n<p>What this means is that WordPress has no notion of paragraph ids &#8211; the browser handles everything. It also means that the ids are calculated each time the post is loaded and because I&#8217;ve used hashing to ensure a unique ID, if the text changes, the hashcode changes and comments can become orphaned.<\/p>\n<p>At first, I thought this would be a problem but on reflection, this is actually a logical outcome. If you are allowing comments on individual paragraphs then it seems that changing the paragraph text is &#8220;cheating&#8221;.<\/p>\n<figure id=\"attachment_126090\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-126090\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/02\/paracom-orphans.png\" alt=\"Screenshot showing listing of orphan comments\" width=\"700\" height=\"227\" \/><figcaption class=\"wp-caption-text\">Changing the text of a paragraph will orphan its comments<\/figcaption><\/figure>\n<p>The upside, though, is that paragraphs can change position in a post (either moved, or content is added or removed) without impacting on associated comments. Paragraphs with no comments can also be safely edited.<\/p>\n<h3>When the Post Loads&#8230;<\/h3>\n<ol>\n<li>When the post is displayed, client-side code generates a hashcode for each paragraph (<em>p<\/em> tag) by hashing the paragraph text<\/li>\n<li>The comments are searched for matches on the hashcode (the template for the comment display is altered to grab the hashcode from comment meta) and a total count is displayed at the end of the paragraph. An on-click event is attached to the count to toggle the display of the relevant comments.<\/li>\n<li>As the comments are matched, they are hidden.<\/li>\n<li>Any unmatched comments (orphans) are moved to a new div for permanent display at the bottom of the post.<\/li>\n<\/ol>\n<h3>When the Reader Clicks On A Paragraph&#8217;s Comment Count&#8230;<\/h3>\n<ol>\n<li>The div containing all the comments is moved to sit under the relevant paragraph and its <em>z-index<\/em> changed so that it sits &#8220;on top&#8221; of the post<\/li>\n<li>All comments are hidden by default, so those comments attached to the paragraph are switched to display<\/li>\n<li>The add new comment form is also displayed<\/li>\n<\/ol>\n<p>The net effect here is that only the comments for the paragraph (if there are any) along with the new comment form are displayed under the appropriate paragraph.<\/p>\n<figure id=\"attachment_126091\" class=\"wp-caption aligncenter\" data-caption=\"true\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-126091\" src=\"https:\/\/wpmudev.com\/blog\/wp-content\/uploads\/2014\/02\/paracom-clicked.png\" alt=\"Screenshot showing the paragraph comments\" width=\"700\" height=\"376\" \/><figcaption class=\"wp-caption-text\">All comments for this paragraph only<\/figcaption><\/figure>\n<h3>When the Reader Adds A New Comment&#8230;<\/h3>\n<ol>\n<li>When the Reader hits return and there&#8217;s text in the comment field, the hashcode for the paragraph is determined and added to the call to <em>admin-ajax.php<\/em> to create a new comment<\/li>\n<li>The server-side function that creates the new comment keeps the ID of the new comment and uses this and the paragraph hashcode to create a new entry in <em>comment_meta<\/em><\/li>\n<li>When the server-side function completes, the comment count for the paragraph is updated and the new comment displayed<\/li>\n<\/ol>\n<p>Clicking on the comment count closes the comments list.<\/p>\n<p>As you can see it&#8217;s a combination of client-side functions and server-side functions but predominantly client-side. The hashcodes are all generated on the client-side and WordPress is really oblivious to them apart from when creating the comment meta for a new comment.<\/p>\n<h2>Standing On Shoulders<\/h2>\n<p>As is often the way in WordPress hacking, I didn&#8217;t create this solution from scratch but took an existing plugin, the excellent <a title=\"Read more about this plugin in the WordPress Plugin Repository\" href=\"http:\/\/wordpress.org\/plugins\/inline-ajax-comments\/\" rel=\"noopener\" target=\"_blank\">Inline Ajax Comments<\/a> which although only has compatibility to 3.6.1, happily works with 3.8.1.<\/p>\n<p>This plugin already handled the listing and creation of new comments using ajax, so it was a matter of building on <a title=\"Visit Zane's personal website\" href=\"http:\/\/zanematthew.com\/\" rel=\"noopener\" target=\"_blank\">Zane Matthew&#8217;s<\/a> easy-to-follow and well-written code to get the behavior outlined above.<\/p>\n<p>Whilst I&#8217;m not going to step through the whole plugin, I&#8217;ll just be highlighting my updates, it&#8217;s worth looking at Zane&#8217;s code, especially if you are not familiar with using WordPress&#8217; admin-ajax functionality.<\/p>\n<h3>Creating Comment Meta, Adding Paragraph Hashcode To Comments Template<\/h3>\n<p>Despite<em> admin-ajax.php<\/em> being located in the <em>wp-admin<\/em> folder, it&#8217;s a technique that can be used from the front-end and is preferred by many developers over APIs such as JSON due to its tighter integration with WordPress.<\/p>\n<p>The basic approach is that the browser makes an ajax request to <em>admin-ajax.php<\/em> requesting a particular action. WordPress checks a list of pre-registered actions and if a match is found calls the associated function. It is the functions job to build the response which is passed back to the browser.<\/p>\n<p>Zane keeps all his action functions in the <code>template-tags.php<\/code> file and two actions, <code>inline_comments_load_template<\/code> and <code>inline_comments_add_comment<\/code> need small updates.<\/p>\n<p>At the bottom of the <em>inline_comments_add_comment<\/em>, I amended the call to <em>wp_insert_comment<\/em> to capture the new comment&#8217;s ID, and then add comment meta using the captured ID and the passed paragraph hashcode.<\/p>\n<p><code><br \/>\n\/\/ ck - catch the new comment id for updating comment meta<br \/>\n$comment_id = wp_insert_comment( $data );<\/code><\/p>\n<p>\/\/ ck &#8211; now add the para-id to the comment meta<br \/>\nadd_comment_meta( $comment_id, &#8216;para_id&#8217; , $_POST[&#8216;para_id&#8217;] );<\/p>\n<p>Further down the script, in the inline_comments_load_template function, I made two small changes. The first is to get the related paragraph hashcode:<\/p>\n<p><code><br \/>\n$para_id = get_comment_meta( $comment-&gt;comment_ID, 'para_id', true );<br \/>\n<\/code><\/p>\n<p>The second is simply to add <em>orphan-comment<\/em> and <em>comment-para-id<\/em> classes to the comment&#8217;s container <em>div<\/em>.<\/p>\n<p>The <em>comment-para-id<\/em> has the paragraph hashcode added to it. This makes it possible to do jQuery searching using CSS selectors which is more efficient than using a non-standard attribute.<\/p>\n<p>The <em>orphan-comment<\/em> class is added to every comment because there are no hashcodes assigned anywhere in WordPress. Client-side processing removes the class if a match is found with an existing paragraph after it has calculated the paragraph hashcodes.<\/p>\n<h3>Kicking Off Paragraph Hashcode Calculations<\/h3>\n<p>I only needed to make four small changes to the Zane&#8217;s existing client-side script (<em>script.js<\/em>). Two were to ensure that the paragraph hashcode calculation got kicked-off, achieved by adding a call to <em>set_up_para_comments<\/em> in the <em>inline_comments_ajax_load_template<\/em> and <em>load<\/em> functions.<\/p>\n<p>I tried for quite some time to avoid this but I couldn&#8217;t seem to hook into the <em>done()<\/em> action of the right event to be able to leave Zane&#8217;s script untouched. I&#8217;d be grateful if anyone has any suggestions as to how this could done.<\/p>\n<h3>Add New Comment &#8211; Including The Hashcode, Updating the Comment Count<\/h3>\n<p>The other two changes are concerned with adding a new comment and takes place in the function that is kicked off by the onsubmit event for the comment form.<\/p>\n<p>The first change is to simply to add the paragraph hashcode as a parameter on the ajax call to the<em> inline_comments_add_comment<\/em> action. The hashcode is actually stored in a global variable that is assigned when the comment count link is clicked and the comments and comment form are displayed.<\/p>\n<p>The second change is new code to update the comment count if the add is successful:<\/p>\n<p><code><br \/>\nvar comment_count_holder = $('p[data-para-id=\"' + current_para_id + '\"] &gt; span &gt; a');<br \/>\nvar comment_count = parseInt( comment_count_holder.text() );<br \/>\ncomment_count_holder.text( comment_count + 1 );<br \/>\n<\/code><\/p>\n<p>The comment count is contained in an <em>a<\/em> tag. This is retrieved, the number is incremented and then reassigned.<\/p>\n<p>The rest of the solution is new client-side scripting.<\/p>\n<h3>Calculating a Paragraph&#8217;s Hashcode<\/h3>\n<p>I wanted to be able to generate a unique code for each paragraph, so I decided that hashing the text would be a good way to do this.<\/p>\n<p>The function to do this, borrowed from werxltd.com is actually added as a method to the javascript <em>string<\/em> object:<\/p>\n<div class=\"gist\" data-gist=\"&lt;\/p&gt;\n&lt;h3&gt;Setting Up Paragraphs and Comments&lt;\/h3&gt;\n&lt;p&gt;When the page is loaded in the browser, or when a new comment is made, the &lt;em&gt;set_up_para_comments&lt;\/em&gt; function is called.&lt;\/p&gt;\n&lt;p&gt;[gist id=&quot;8969845&quot; file=&quot;set_up_para_comments.js&quot;]&lt;\/p&gt;\n&lt;p&gt;This function grabs all the paragraphs on the page for each:&lt;\/p&gt;\n&lt;ol&gt;\n&lt;li&gt;Calculates a hashcode which is used to generate a class and the custom &lt;em&gt;data-para-id&lt;\/em&gt; attribute.&lt;\/li&gt;\n&lt;li&gt;Finds all the comments which have the same hashcode, counts them, removes the orphan-comment class and hides them&lt;\/li&gt;\n&lt;li&gt;Adds a comment count to the end of the paragraph contained in an a tag&lt;\/li&gt;\n&lt;\/ol&gt;\n&lt;p&gt;After processing each paragraph, any remaining comments (they will still have the orphan-comment class) are moved to their own container &lt;em&gt;div&lt;\/em&gt;.&lt;\/p&gt;\n&lt;p&gt;If this function is called after a new comment has been added then it simply shows the related comments and the add new comment form.&lt;\/p&gt;\n&lt;h3&gt;Displaying and Hiding A Paragraphs Comments&lt;\/h3&gt;\n&lt;p&gt;This functionality is activated by the &lt;em&gt;onclick&lt;\/em&gt; action on the comment count link (class of &lt;em&gt;.toggle-comments&lt;\/em&gt;).&lt;\/p&gt;\n&lt;p&gt;It first checks to see if the comments are visible and if they are then it just hides them.&lt;\/p&gt;\n&lt;p&gt;If the comments are not visible then we need to filter the list for the paragraph concerned &#8211; remember, all the post&#8217;s comments are in the one container &#8211; and then display them.&lt;\/p&gt;\n&lt;p&gt;[gist file=&quot;toggle_comments.js&quot;]8969845\" data-gist-file=\"hashcode.js\"><a class=\"loading\" href=\"https:\/\/gist.github.com\/\/ph3Setting%20Up%20Paragraphs%20and%20Comments\/h3pWhen%20the%20page%20is%20loaded%20in%20the%20browser,%20or%20when%20a%20new%20comment%20is%20made,%20the%20emset_up_para_comments\/em%20function%20is%20called.\/pp%5Bgist%20id=8969845%20file=set_up_para_comments.js%5D\/ppThis%20function%20grabs%20all%20the%20paragraphs%20on%20the%20page%20for%20each:\/polliCalculates%20a%20hashcode%20which%20is%20used%20to%20generate%20a%20class%20and%20the%20custom%20emdata-para-id\/em%20attribute.\/liliFinds%20all%20the%20comments%20which%20have%20the%20same%20hashcode,%20counts%20them,%20removes%20the%20orphan-comment%20class%20and%20hides%20them\/liliAdds%20a%20comment%20count%20to%20the%20end%20of%20the%20paragraph%20contained%20in%20an%20a%20tag\/li\/olpAfter%20processing%20each%20paragraph,%20any%20remaining%20comments%20(they%20will%20still%20have%20the%20orphan-comment%20class)%20are%20moved%20to%20their%20own%20container%20emdiv\/em.\/ppIf%20this%20function%20is%20called%20after%20a%20new%20comment%20has%20been%20added%20then%20it%20simply%20shows%20the%20related%20comments%20and%20the%20add%20new%20comment%20form.\/ph3Displaying%20and%20Hiding%20A%20Paragraphs%20Comments\/h3pThis%20functionality%20is%20activated%20by%20the%20emonclick\/em%20action%20on%20the%20comment%20count%20link%20(class%20of%20em.toggle-comments\/em).\/ppIt%20first%20checks%20to%20see%20if%20the%20comments%20are%20visible%20and%20if%20they%20are%20then%20it%20just%20hides%20them.\/ppIf%20the%20comments%20are%20not%20visible%20then%20we%20need%20to%20filter%20the%20list%20for%20the%20paragraph%20concerned%20&#038;?file=hashcode.js#8211;%20remember,%20all%20the%20post&#8217;s%20comments%20are%20in%20the%20one%20container%20&#8211;%20and%20then%20display%20them.\/pp%5Bgist%20file=toggle_comments.js%5D8969845.js\">Loading gist <\/p>\n<h3>Setting Up Paragraphs and Comments<\/h3>\n<p>When the page is loaded in the browser, or when a new comment is made, the <em>set_up_para_comments<\/em> function is called.<\/p>\n<p>[gist id=\"8969845\" file=\"set_up_para_comments.js\"]<\/p>\n<p>This function grabs all the paragraphs on the page for each:<\/p>\n<ol>\n<li>Calculates a hashcode which is used to generate a class and the custom <em>data-para-id<\/em> attribute.<\/li>\n<li>Finds all the comments which have the same hashcode, counts them, removes the orphan-comment class and hides them<\/li>\n<li>Adds a comment count to the end of the paragraph contained in an a tag<\/li>\n<\/ol>\n<p>After processing each paragraph, any remaining comments (they will still have the orphan-comment class) are moved to their own container <em>div<\/em>.<\/p>\n<p>If this function is called after a new comment has been added then it simply shows the related comments and the add new comment form.<\/p>\n<h3>Displaying and Hiding A Paragraphs Comments<\/h3>\n<p>This functionality is activated by the <em>onclick<\/em> action on the comment count link (class of <em>.toggle-comments<\/em>).<\/p>\n<p>It first checks to see if the comments are visible and if they are then it just hides them.<\/p>\n<p>If the comments are not visible then we need to filter the list for the paragraph concerned &#8211; remember, all the post&#8217;s comments are in the one container &#8211; and then display them.<\/p>\n<p>[gist file=\"toggle_comments.js\"]8969845<\/a><div class=\"gist-consent-notice\" style=\"display:none\"><p>Please <a href=\"javascript:Cookiebot.renew()\">update your cookie preferences<\/a> to enable preference cookies to view this gist.<\/p><\/div><\/div>\n\n<p><!-- insert here --><\/p>\n<h2>Go Scribble in the Margins<\/h2>\n<p>Okay, so it&#8217;s not exactly scribbling in the margins but it is paragraph commenting (or annotations) and it can be applied to almost any theme.<\/p>\n<p>Of course, whether you want to apply this style of commenting to your site will depend on your content and primarily it&#8217;s length. Certainly, if you find your commenters are frequently trying to pull quotes from your posts then annotations may well be the way to go.<\/p>\n<p>Photo Credit: <a title=\"Visit this photo's page on Wikimedia\" rel=\"noopener\" class=\"blog-thumbnail\" href=\"http:\/\/commons.wikimedia.org\/wiki\/File:Mona_Lisa_margin_scribble.jpg\" target=\"_blank\">Wikimedia<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Are you a forlorn admirer of Medium&#8217;s paragraph commenting? Well, forlorn no more because here&#8217;s how to add paragraph commenting to your WordPress site.<\/p>\n","protected":false},"author":262394,"featured_media":126089,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"blog_reading_time":"","wds_primary_category":0,"wds_primary_tutorials_categories":0,"footnotes":""},"categories":[263],"tags":[1256,264,9812],"tutorials_categories":[],"class_list":["post-125239","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-ajax","tag-comments","tag-medium"],"_links":{"self":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/125239","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/users\/262394"}],"replies":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/comments?post=125239"}],"version-history":[{"count":3,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/125239\/revisions"}],"predecessor-version":[{"id":193728,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/posts\/125239\/revisions\/193728"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media\/126089"}],"wp:attachment":[{"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/media?parent=125239"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/categories?post=125239"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tags?post=125239"},{"taxonomy":"tutorials_categories","embeddable":true,"href":"https:\/\/wpmudev.com\/blog\/wp-json\/wp\/v2\/tutorials_categories?post=125239"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}