Tuesday 13 March 2012

Son of the better recent comments gadget

Blogger added comment authors' avatar (profile image) urls to comment feed in earlier December. This new version uses those urls to get the avatars, instead of using my google appengine application to get avatars from profile pages, as the previous version does.
As an added bonus in this new "Son" version, avatar size can be adjusted easier using a configuration variable, and I give you two additional CSS styles for the gadget.


Features of my recent comments gadget

You can configure:
  • number of recent comments displayed
  • max number of comments per post (so one post's comments don't "flood" recent comments)
  • length of comment excerpt
  • translation or own version of gadget's texts
  • customize what is displayed.
Also:
  • blog author's comments can be styled differently (CSS)
  • display comment author's site favicon, comment author's Blogger profile image, or mystery man icon (configurable) for non-registered commenters
  • does not have the bugs the recent comment gadget that Blogger offers has.
The newest version, "Son of the better recent comments gadget", adds the following new features:
  • loads Blogger avatars (profile images) using the new avatar urls in comments feed
  • avatar icon size can be more easily configured
  • three CSS examples for different size avatars.

The Script

Add a HTML/Javascript gadget to your blog, and paste the following code into it. If you had previous version installed, replace it with this, and make the same configuration changes you made to the previous version. I'm sorry that there is no automated upgrade, but this is no rocket science, I'm sure you can do it.

  1. <style type="text/css">
  2. .recent-comment        { margin-bottom:10px; padding-left: 24px; }
  3. .recent-comment-admin  { background-color: #F4F4F4; }
  4. .recent-comment-ico    { margin-left:-20px;margin-top:4px;float:left;margin-right:3px;}
  5. .recent-comment-header {}
  6. .recent-comment-body   { padding-right: 4px; font-size: 95%;}
  7. .recent-comment-footer { font-size: 85%; }
  8. </style>
  9. <script type="text/javascript">
  10. //
  11. // Recent Comments blogger gadget by MS-potilas 2011, using feed avatars
  12. // see http://yabtb.blogspot.com/2011/12/son-of-better-recent-comments-gadget.html
  13. //
  14. // CONFIG:
  15. var numRecentComments = 5;
  16. var numPerPost = 2; // max comments per post (to try) or 0
  17. var maxCommentChars = 90;
  18. var maxPostTitleChars = 0; // if 0, use full post title
  19.  
  20. var txtWrote = 'wrote:';
  21. var txtMore = 'Continue >>';
  22. var txtTooltip = '[user] on &quot;[title]&quot; - [date MM/dd/yyyy hh:mm]';
  23. var txtAnonymous = ''; // empty, or Anonymous user name localized
  24. // Variables [xxx] in texts:
  25. // supports [title], [user], [date], [time], [datetime], [date format]
  26. // format supports: yyyy=long year, yy=short year, MM=month(01-12), dd=monthday, hh=hour, mm=min, ss=sec
  27.  
  28. var getTitles = true;   // false faster
  29. var trueAvatars = true; // false faster
  30. var urlMyAvatar = '';   // can be empty (then it is fetched) or url to image
  31. var urlMyProfile = '';  // set if you have no profile gadget on page
  32. //
  33. var cropAvatar = true;
  34. var sizeAvatar = 16;
  35. var urlNoAvatar = "https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkupwEqu86Fci0otcGOzaqfXOiqSMhdCvuvzAUKMOX1yDjv55vq0yjEu0ahuW5Wce8kwsLbdBqu61e2PShyphenhyphenvoVSYIqmRfOnXQkCqJkFmTW3hsIfS8j6beGoZnLfMVHML6sl9Z-nojAyjQ/"+sizeAvatar+"/avatar_blue_m_96.png"; // http://www.blogger.com/img/avatar_blue_m_96.png resizeable
  36. //
  37. var urlAnoAvatar = 'http://www.gravatar.com/avatar/00000000000000000000000000000000?d=mm&s=' + sizeAvatar;
  38. var maxResultsPosts = "";       // or for example "&max-results=100"
  39. var maxResultsComments = "";    // or for example "&max-results=300"
  40. // CONFIG END
  41. var urlToTitle = {};
  42. function replaceVars(text, user, title, date) {
  43.   text = text.replace('[user]', user);
  44.   text = text.replace('[date]', date.toLocaleDateString());
  45.   text = text.replace('[datetime]', date.toLocaleString());
  46.   text = text.replace('[time]', date.toLocaleTimeString());
  47.   text = text.replace('[title]', title.replace(/\"/g,'&quot;'));
  48.   var i = text.indexOf("[date ");
  49.   if(> -1) {
  50.     var format = /\[date\s+(.+?)\]/.exec(text)[1];
  51.     if(format != '') {
  52.       var txtDate = format.replace(/yyyy/i, date.getFullYear());
  53.       txtDate = txtDate.replace(/yy/i, date.getFullYear().toString().slice(-2));
  54.       txtDate = txtDate.replace("MM", String("0"+(date.getMonth()+1)).slice(-2));
  55.       txtDate = txtDate.replace("mm", String("0"+date.getMinutes()).slice(-2));
  56.       txtDate = txtDate.replace("ss", String("0"+date.getSeconds()).slice(-2));
  57.       txtDate = txtDate.replace("dd", String("0"+date.getDate()).slice(-2));
  58. //or: txtDate = txtDate.replace("dd", date.getDate());
  59.       txtDate = txtDate.replace("hh", String("0"+date.getHours()).slice(-2));
  60. //or: txtDate = txtDate.replace("hh", date.getHours());
  61.       text = text.replace(/\[date\s+(.+?)\]/, txtDate)
  62.     }
  63.   }
  64.   return text;
  65. }
  66. if(urlMyProfile == "") {
  67.   var elements = document.getElementsByTagName("*");
  68.   var expr = /(^| )profile-link( |$)/;
  69.   for(var i=0 ; i<elements.length ; i++)
  70.     if(expr.test(elements[i].className)) {
  71.       urlMyProfile = elements[i].href;
  72.       break;
  73.     }
  74. }
  75. function getPostUrlsForComments(json) {
  76.   for(var i = 0 ; i < json.feed.entry.length ; i++ ) {
  77.     var entry = json.feed.entry[i];
  78.     for (var k = 0; k < entry.link.length; k++ ) {
  79.       if (entry.link[k].rel == 'alternate') {
  80.         href = entry.link[k].href;
  81.         break;
  82.       }
  83.     }
  84.     urlToTitle[href] = entry.title.$t;
  85.   }
  86. }
  87. function showRecentComments(json) {
  88.   var postHandled = {};
  89.   var j = 0;
  90.   if(numPerPost) {
  91.     while(numPerPost < numRecentComments) {
  92.       for(var i = 0 ; i < json.feed.entry.length ; i++ ) {
  93.         var entry = json.feed.entry[i];
  94.         if(entry["thr$in-reply-to"]) {
  95.           if(!postHandled[entry["thr$in-reply-to"].href])
  96.               postHandled[entry["thr$in-reply-to"].href] = 1;
  97.           else
  98.               postHandled[entry["thr$in-reply-to"].href]++;
  99.           if(postHandled[entry["thr$in-reply-to"].href] <= numPerPost)
  100.             j++;
  101.         }
  102.       }
  103.       if(>= numRecentComments)
  104.         break;
  105.       numPerPost++;
  106.       j = 0;
  107.       postHandled = {};
  108.     }
  109.     if(numRecentComments == numPerPost)
  110.        numPerPost = 0;
  111.   }
  112.   postHandled = {};
  113.   j = 0;
  114.   for(var i = 0 ; j < numRecentComments && i < json.feed.entry.length ; i++ ) {
  115.     var entry = json.feed.entry[i];
  116.     if(numPerPost && postHandled[entry["thr$in-reply-to"].href] && postHandled[entry["thr$in-reply-to"].href] >= numPerPost)
  117.       continue;
  118.     if(entry["thr$in-reply-to"]) {
  119.       if(!postHandled[entry["thr$in-reply-to"].href])
  120.           postHandled[entry["thr$in-reply-to"].href] = 1;
  121.       else
  122.           postHandled[entry["thr$in-reply-to"].href]++;
  123.       j++;
  124.       var href='';
  125.       for (var k = 0; k < entry.link.length; k++ ) {
  126.         if (entry.link[k].rel == 'alternate') {
  127.           href = entry.link[k].href;
  128.           break;
  129.         }
  130.       }
  131.       if(href=='') {j--; continue; }
  132.       var hrefPost = href.split("?")[0];
  133.       var comment = "";
  134.       if("content" in entry) comment = entry.content.$t;
  135.       else                   comment = entry.summary.$t;
  136.       comment = comment.replace(/<br[^>]*>/ig, " ");
  137.       comment = comment.replace(/<\S[^>]*>/g, "");
  138.       var postTitle="";
  139.       if(urlToTitle[hrefPost]) postTitle=urlToTitle[hrefPost];
  140.       else {
  141.         postTitle = hrefPost.split("/")[5].split(".html")[0].replace(/_\d{2}$/, "");
  142.         postTitle = postTitle.replace(/-/g," ");
  143.         postTitle = postTitle[0].toUpperCase() + postTitle.slice(1);
  144.       }
  145.       if(maxPostTitleChars && postTitle.length > maxPostTitleChars) {
  146.         postTitle = postTitle.substring(0, maxPostTitleChars);
  147.         var indexBreak = postTitle.lastIndexOf(" ");
  148.         postTitle = postTitle.substring(0, indexBreak) + "...";
  149.       }
  150.  
  151.       var authorName = entry.author[0].name.$t;
  152.       var authorUri = "";
  153.       if(entry.author[0].uri && entry.author[0].uri.$t != "")
  154.         authorUri = entry.author[0].uri.$t;
  155.    
  156.       var avaimg = urlAnoAvatar;
  157.       var bloggerprofile = "http://www.blogger.com/profile/";
  158.       if(trueAvatars && entry.author[0].gd$image && entry.author[0].gd$image.src && authorUri.substr(0,bloggerprofile.length) == bloggerprofile)
  159.         avaimg = entry.author[0].gd$image.src;
  160.       else {
  161.         var parseurl = document.createElement('a');
  162.         if(authorUri != "") {
  163.           parseurl.href = authorUri;
  164.           avaimg = 'http://www.google.com/s2/favicons?domain=' + parseurl.hostname;
  165.         }
  166.       }
  167.       if(urlMyProfile != "" && authorUri == urlMyProfile && urlMyAvatar != "")
  168.         avaimg = urlMyAvatar;
  169.       if(avaimg == "http://img2.blogblog.com/img/b16-rounded.gif" && urlNoAvatar != "")
  170.         avaimg = urlNoAvatar;
  171.       var newsize="s"+sizeAvatar;
  172.       avaimg = avaimg.replace(/\/s\d\d+-c\//, "/"+newsize+"-c/");
  173.       if(cropAvatar) newsize+="-c";
  174.       avaimg = avaimg.replace(/\/s\d\d+(-c){0,1}\//, "/"+newsize+"/");
  175.       if(authorName == 'Anonymous' && txtAnonymous != '' && avaimg == urlAnoAvatar)
  176.         authorName = txtAnonymous;
  177.       var imgcode = '<img height="'+sizeAvatar+'" width="'+sizeAvatar+'" title="'+authorName+'" src="'+avaimg+'" />';
  178.       if (authorUri!="") imgcode = '<a href="'+authorUri+'">'+imgcode+'</a>';
  179.       var clsAdmin = "";
  180.       if(urlMyProfile != "" && authorUri == urlMyProfile)
  181.           clsAdmin = " recent-comment-admin";
  182.       var datePart = entry.published.$t.match(/\d+/g); // assume ISO 8601
  183.       var cmtDate = new Date(datePart[0],datePart[1]-1,datePart[2],datePart[3],datePart[4],datePart[5]);
  184.  
  185.       var txtHeader = txtWrote;
  186.       if(txtWrote.indexOf('[')==-1)
  187.         txtHeader = authorName + ' ' + txtWrote;
  188.       else
  189.         txtHeader = replaceVars(txtHeader, authorName, postTitle, cmtDate);
  190.  
  191.       var tooltip = replaceVars(txtTooltip, authorName, postTitle, cmtDate);
  192.  
  193.       document.write('<div title="'+tooltip+'" class="recent-comment'+clsAdmin+'">');
  194.       document.write('<div title="'+tooltip+'" class="recent-comment-header'+clsAdmin+'"><div title="'+tooltip+'" class="recent-comment-ico'+clsAdmin+'">'+imgcode+'</div><a title="'+tooltip+'" href="' + href + '">' + txtHeader + ' </a></div>');
  195.       if(comment.length < maxCommentChars)
  196.         document.write('<div title="'+tooltip+'" class="recent-comment-body'+clsAdmin+'">' + comment + '</div>');
  197.       else {
  198.         comment = comment.substring(0, maxCommentChars);
  199.         var indexBreak = comment.lastIndexOf(" ");
  200.         comment = comment.substring(0, indexBreak);
  201.         document.write('<div title="'+tooltip+'" class="recent-comment-body'+clsAdmin+'">' + comment + '...</div>');
  202.         if(txtMore != "") {
  203.           var moretext = replaceVars(txtMore, authorName, postTitle, cmtDate);
  204.           document.write('<div title="'+tooltip+'" class="recent-comment-footer'+clsAdmin+'"><a title="'+tooltip+'" href="' + href + '">' + moretext + '</a></div>');
  205.         }
  206.       }
  207.       document.write('<div style="clear:both;"></div></div>');
  208.     }
  209.   }
  210. }
  211. if(getTitles)
  212.   document.write('<script type="text/javascript" src="http://'+window.location.hostname+'/feeds/posts/summary?redirect=false'+maxResultsPosts+'&alt=json-in-script&callback=getPostUrlsForComments"></'+'script>');
  213. document.write('<script type="text/javascript" src="http://'+window.location.hostname+'/feeds/comments/default?redirect=false'+maxResultsComments+'&alt=json-in-script&callback=showRecentComments"></'+'script>');
  214. </script>


Script configuration

There are some CSS definitions at first, which can be changed to your liking. After that there comes javascript code and configurable variables. Here is a list of the variables and a short explanation.

numRecentComments: how many comments you want displayed
numPerPost: how many comments per post max is the goal (will fallback when necessary)
maxCommentChars: how many chars max in excerpt of comment
maxPostTitleChars: how long post title can be, or 0 (=don't cut title)
txtWrote: header text, links to comment
txtMore: footer text, links to comment
txtToolTip: text to be shown when mouse hovers over comment
txtAnonymous: "Anonymous" username localized, or empty if English text "Anonymous" is to be used
getTitles: if true, fetch titles from post feed; if false, titles are generated from url
trueAvatars: if true, uses Blogger avatars (Blogger profile images) from comment feed, not just the blogger favico [B]
urlMyAvatar: especially if trueAvatars = false, set here the url to your avatar image
urlMyProfile: if you don't have a profile gadget on page, put your profile url here!
urlAnoAvatar: anonymous avatar, you can change from mystery man to a custom one, if you want, possibly this
maxResultsPosts: can be empty or &max-results=### where ### is number of posts to get from posts feed to get titles
maxResultsComments: empty or &max-results=### where ### is number of comments to get from comments feed

New configurable variables in "Son"

cropAvatar: crop avatar to square (true) or stretch (false) to square
sizeAvatar: size of avatar in x and y direction, pixels
urlNoAvatar: comment feed offers the  icon (Blogger logo) for those Bloggers who have not set their profile image. You can override the  icon with this setting (default: ).

txtWrote, txtMore and txtTooltip

These can be just normal text. If txtWrote is normal text, it is preceded by commenter name. So if txtWrote is "wrote..." the result would be Jason wrote..., if Jason was commenting.

All texts can also have simple variables, which are: [title], [user], [date], [time], [datetime] and [dateformat]. [title] is substituted by Post title, where comment belongs. [user] is substituted by commenter name. [date] displays date in current (user's computer's) locale, [time] displays time and [datetime] date and time in user's locale.

format in [date format]

In format some character combinations are substituted:

yyyy=long year, yy=short year, MM=month(01-12), dd=monthday,
hh=hour, mm=min, ss=sec (time/hours in 24-hour clock)

but format can contain also other text/characters (which are displayed as is).

"Written [date MM/dd/yyyy hh:mm]" could become "Written 11/03/2011 08:30".

Again, the script has good configuration defaults, so if all this was too complicated for you, you don't have to care about it. If you don't have a profile gadget on your page, put your profile url in the code I provided, and you're ready to go. If you have a profile gadget, then you don't have to change anything, just copy and paste.

Styles for different avatar sizes

Default size for avatar is 16 pixels. If you change, the CSS might need some adjusting. In the picture in the beginning of this post there are three examples, first the default (16 pixels), then 20 pixel avatars, and then 24 pixel avatars.

Styles for 20 pixel avatars:
?
1
2
3
4
5
6
7
8
9
10
11
12
<style type="text/css">
.recent-comment        { margin-bottom:10px; padding-left: 28px; }
.recent-comment-admin  { background-color: #F4F4F4; }
.recent-comment-ico    { margin-left:-24px;margin-top:4px;margin-right:3px;float:left;}
.recent-comment-header {}
.recent-comment-body   { padding-right: 4px; font-size: 95%;}
.recent-comment-footer { font-size: 85%; }
</style>
 
...
 
var sizeAvatar = 20;

Styles for 24 (and larger) avatars:
?
1
2
3
4
5
6
7
8
9
10
11
12
<style type="text/css">
.recent-comment        { margin-bottom:10px; }
.recent-comment-admin  { background-color: #F8F1DF; }
.recent-comment-ico    { margin-left:3px;margin-top:3px;margin-right:3px;float:left;}
.recent-comment-header {}
.recent-comment-body   { padding-right: 4px; font-size: 95%;}
.recent-comment-footer { font-size: 85%; }
</style>
 
...
 
var sizeAvatar = 24;
May be this doesn't works with IE7 ,one of my friend said.

No comments: