How To Have Paragraph Commenting Just Like Medium
Paragraph commenting, or annotations is not exactly new. Readers have been scribbling in the margins of books, magazines and uni assignments for years.
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.
Well, admire forlornly no more because I’m going to show you how to add paragraph commenting to your WordPress site.
There are existing annotation solutions for WordPress but they are generally theme dependent, or in the case of CommentPress actually provide a theme.
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’ll just need to do tweak the CSS to achieve Medium-esque commenting.
Annotations, by their very nature, are ajax-based as page reloads would kill the user-experience.
Obviously, for paragraph commenting to work, each comment has to be tied to the paragraph which means that the paragraph has to have an unique ID. Determining what is a paragraph is quite difficult within WordPress as post content is generally stored unfiltered.
With no HTML it’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.
What this means is that WordPress has no notion of paragraph ids – the browser handles everything. It also means that the ids are calculated each time the post is loaded and because I’ve used hashing to ensure a unique ID, if the text changes, the hashcode changes and comments can become orphaned.
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 “cheating”.
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.
When the Post Loads…
- When the post is displayed, client-side code generates a hashcode for each paragraph (p tag) by hashing the paragraph text
- 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.
- As the comments are matched, they are hidden.
- Any unmatched comments (orphans) are moved to a new div for permanent display at the bottom of the post.
When the Reader Clicks On A Paragraph’s Comment Count…
- The div containing all the comments is moved to sit under the relevant paragraph and its z-index changed so that it sits “on top” of the post
- All comments are hidden by default, so those comments attached to the paragraph are switched to display
- The add new comment form is also displayed
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.
When the Reader Adds A New Comment…
- When the Reader hits return and there’s text in the comment field, the hashcode for the paragraph is determined and added to the call to admin-ajax.php to create a new comment
- 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 comment_meta
- When the server-side function completes, the comment count for the paragraph is updated and the new comment displayed
Clicking on the comment count closes the comments list.
As you can see it’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.
Standing On Shoulders
As is often the way in WordPress hacking, I didn’t create this solution from scratch but took an existing plugin, the excellent Inline Ajax Comments which although only has compatibility to 3.6.1, happily works with 3.8.1.
This plugin already handled the listing and creation of new comments using ajax, so it was a matter of building on Zane Matthew’s easy-to-follow and well-written code to get the behavior outlined above.
Whilst I’m not going to step through the whole plugin, I’ll just be highlighting my updates, it’s worth looking at Zane’s code, especially if you are not familiar with using WordPress’ admin-ajax functionality.
Creating Comment Meta, Adding Paragraph Hashcode To Comments Template
Despite admin-ajax.php being located in the wp-admin folder, it’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.
The basic approach is that the browser makes an ajax request to admin-ajax.php 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.
Zane keeps all his action functions in the template-tags.php file and two actions, inline_comments_load_template and inline_comments_add_comment need small updates.
At the bottom of the inline_comments_add_comment, I amended the call to wp_insert_comment to capture the new comment’s ID, and then add comment meta using the captured ID and the passed paragraph hashcode.
// ck - catch the new comment id for updating comment meta
$comment_id = wp_insert_comment( $data );
// ck - now add the para-id to the comment meta
add_comment_meta( $comment_id, 'para_id' , $_POST['para_id'] );
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:
$para_id = get_comment_meta( $comment->comment_ID, 'para_id', true );
The second is simply to add orphan-comment and comment-para-id classes to the comment’s container div.
The comment-para-id 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.
The orphan-comment 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.
Kicking Off Paragraph Hashcode Calculations
I only needed to make four small changes to the Zane’s existing client-side script (script.js). Two were to ensure that the paragraph hashcode calculation got kicked-off, achieved by adding a call to set_up_para_comments in the inline_comments_ajax_load_template and load functions.
I tried for quite some time to avoid this but I couldn’t seem to hook into the done() action of the right event to be able to leave Zane’s script untouched. I’d be greatful if anyone has any suggestions as to how this could done.
Add New Comment – Including The Hashcode, Updating the Comment Count
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.
The first change is to simply to add the paragraph hashcode as a parameter on the ajax call to the inline_comments_add_comment 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.
The second change is new code to update the comment count if the add is successful:
var comment_count_holder = $('p[data-para-id="' + current_para_id + '"] > span > a');
var comment_count = parseInt( comment_count_holder.text() );
comment_count_holder.text( comment_count + 1 );
The comment count is contained in an a tag. This is retrieved, the number is incremented and then reassigned.
The rest of the solution is new client-side scripting.
Calculating a Paragraph’s Hashcode
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.
Setting Up Paragraphs and Comments
When the page is loaded in the browser, or when a new comment is made, the set_up_para_comments function is called.
[gist id="8969845" file="set_up_para_comments.js"]
This function grabs all the paragraphs on the page for each:
- Calculates a hashcode which is used to generate a class and the custom data-para-id attribute.
- Finds all the comments which have the same hashcode, counts them, removes the orphan-comment class and hides them
- Adds a comment count to the end of the paragraph contained in an a tag
After processing each paragraph, any remaining comments (they will still have the orphan-comment class) are moved to their own container div.
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.
Displaying and Hiding A Paragraphs Comments
This functionality is activated by the onclick action on the comment count link (class of .toggle-comments).
It first checks to see if the comments are visible and if they are then it just hides them.
If the comments are not visible then we need to filter the list for the paragraph concerned – remember, all the post’s comments are in the one container – and then display them.
Go Scribble in the Margins
Okay, so its not exactly scribbling in the margins but it is paragraph commenting (or annotations) and it can be applied to almost any theme.
Of course, whether you want to apply this style of commenting to your site will depend on your content and primarily it’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.
Photo Credit: Wikimedia
Would paragraph commenting work on your site? If you implement this on your site, let me know how it goes and perhaps include a link in the comments.