Advertisement
Guest User

SSC sort comments bar

a guest
Feb 11th, 2015
293
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        sscsort
  3. // @namespace   slatestarcodex
  4. // @include     http://slatestarcodex.com/20*
  5. // @version     1
  6. // @grant       none
  7. // ==/UserScript==
  8.  
  9. // What is this? Install as Greasemonkey-script to get a sort-comments bar:
  10. // https://imgur.com/WlXqvLp
  11. // Fast enough with ~100 comments, with 300+ it is quite slow.
  12.  
  13. // No warranty for usefulness, merchantability etc etc. I did not test
  14. // this very much, might be that comments disappear or are scrambled.
  15.  
  16. // Feel free to modify, distribute, use inside your own project,
  17. // do whatever you want with it. I place this code in the public domain :-)
  18.  
  19. var lastby = "recency"; // property that was last sorted by
  20. // sort directions
  21. var recencydirection = 1;
  22. var bestdirection = 1;
  23. var topdirection = 1;
  24.  
  25. var allComments = [];  // all the list elements.
  26. var isMenuScrolling = 0; // toggle 0/1 when scrolling below the menu & fixing it to the top
  27.  
  28. // create the menu
  29. d = document.createElement('div');
  30. d.innerHTML = '<div id="sortbar"><div id="sortbardiv" \
  31. style="background-color:#FAFAFA;color:#333;padding:4px;margin-top:4px;border:1px solid;\
  32. z-index:1000;width:548px;border-radius:5px;"> \
  33. <span id="recencysorter" style="background-color:#DDD;padding:0px 3px;">Old ^</span> \
  34. <span id="mostsorter" style="background-color:#DDD;padding:0px 3px;">Most &nbsp;&nbsp;</span> \
  35. <span id="topsorter" style="background-color:#DDD;padding:0px 3px;">Top &nbsp;&nbsp;</span> \
  36. <span id="lastlevelstable" style="background-color:#DDD;padding:0px 3px;">\
  37. <input id="llstablecheck" type="checkbox" checked="checked" style="vertical-align:middle;">\
  38. 5th level always oldest first</input></span> \
  39. </div></div>';
  40. //insert the menu
  41. coms = document.getElementById('comments');
  42. coms.insertBefore(d, coms.childNodes[0]);
  43.  
  44. // the events when clicking the menu buttons:
  45. document.getElementById('recencysorter').onclick = function(event) {
  46.     sortComments("recency");
  47.     document.getElementById('recencysorter').childNodes[0].data = "Old " +
  48.         ((recencydirection<0)?"v":"^");
  49.     document.getElementById('mostsorter').childNodes[0].data = "Most \u00a0\u00a0";
  50.     document.getElementById('topsorter').childNodes[0].data = "top \u00a0\u00a0";
  51. };
  52. document.getElementById('mostsorter').onclick = function(event) {
  53.     sortComments("best");
  54.     document.getElementById('mostsorter').childNodes[0].data = "Most " +
  55.         ((bestdirection<0)?"v":"^");
  56.     document.getElementById('recencysorter').childNodes[0].data = "Old \u00a0\u00a0";
  57.     document.getElementById('topsorter').childNodes[0].data = "top \u00a0\u00a0";
  58. };
  59. document.getElementById('topsorter').onclick = function(event) {
  60.     sortComments("top");
  61.     document.getElementById('topsorter').childNodes[0].data = "Top " +
  62.         ((topdirection<0)?"v":"^");
  63.     document.getElementById('mostsorter').childNodes[0].data = "Most \u00a0\u00a0";
  64.     document.getElementById('recencysorter').childNodes[0].data = "Old \u00a0\u00a0";
  65. };
  66. document.getElementById('llstablecheck').onchange = function(event) {
  67.     // sort again when checkbox state changes.
  68.     if (lastby == "recency" || lastby == "best") {
  69.         if  (recencydirection == 1) {
  70.             // if we sorted oldest-first before, we don't need to resort.
  71.             // this is also the case if we sorted best-first, since oldest-first is
  72.             // the tie-breaker.
  73.             return;
  74.         }
  75.     } else if (topdirection == -1) {
  76.         // if we sorted top first, it puts more recent comments to the top; only when we
  77.         // had top-last we don't need to resort.
  78.         return;
  79.     }
  80.     //
  81.     var lastsort = lastby;
  82.     lastby = "";  // if we dont reset this, the sort direction will reverse.
  83.     sortComments(lastsort);
  84. };
  85.  
  86. // make sure the menu gets fixed to the top when scrolling below it
  87. scrollevent = function(event) {
  88.     var posn = document.getElementById('sortbar').getBoundingClientRect();
  89.     if (posn.top < 0) {
  90.         if (!isMenuScrolling) {
  91.             document.getElementById('sortbar').style.height=(posn.bottom -
  92.                 posn.top) + "px";
  93.             document.getElementById('sortbardiv').style.position=
  94.                 "fixed";          
  95.             document.getElementById('sortbardiv').style.top="0px";
  96.             isMenuScrolling = 1;
  97.         }
  98.     } else {
  99.         if (isMenuScrolling) {
  100.             document.getElementById('sortbardiv').style.position="relative";
  101.             isMenuScrolling = 0;
  102.         }
  103.     }
  104. };
  105. window.onscroll = scrollevent;
  106. scrollevent(0); // trigger event if we start below the menu.
  107.  
  108. // save score values of comments
  109. function tagComments() {
  110.     commentol = document.querySelectorAll('#comments > .commentlist')[0];
  111.     tagScores(commentol);
  112.     allComments.sort(function(a, b) {
  113.         return (parseInt(a.id.match(/\d+/)[0], 10) -
  114.                 parseInt(b.id.match(/\d+/)[0], 10));
  115.     });
  116.     j = 0;
  117.     for(var i = allComments.length; i--;) {
  118.         allComments[i].setAttribute("data-comments-after", j++);
  119.     }
  120.     tagPi(commentol);
  121. }
  122. function tagScores(list) {
  123.     // recursively tag each li with score values
  124.     // data-score: total number of comments that are anchestors of a li
  125.     // data-escore: number of comments that are anchestors, weighted by 2^(-l), where
  126.     //              l is the distance to this comment. Direct children: l=0. Grandchild: l=1 etc.
  127.     var totalscore = 0;
  128.     var totalexpscore = 0;
  129.     for (var i = list.childNodes.length; i--;) {
  130.         currentchild = list.childNodes[i];
  131.         if (currentchild.className=="post pingback") { // skip pingbacks
  132.             continue;
  133.         }
  134.         if (currentchild.nodeName == "LI") {
  135.             tscores = [0, 0];
  136.             // find ul child
  137.             for (var j = currentchild.childNodes.length; j--;) {
  138.                 if (currentchild.childNodes[j].nodeName == "UL") {
  139.                     tscores = tagScores(currentchild.childNodes[j]);
  140.                     break;
  141.                 }
  142.             }
  143.             currentchild = list.childNodes[i];
  144.             currentchild.setAttribute("data-score", tscores[0]);
  145.             currentchild.setAttribute("data-escore", tscores[1]);
  146.             // save the id as a number string, so we can quickly parse it when sorting.
  147.             currentchild.setAttribute("data-id", currentchild.id.match(/\d+/)[0]);
  148.             totalscore += tscores[0] + 1;
  149.             totalexpscore += tscores[1] / 2 + 1;
  150.             allComments.push(list.childNodes[i]);
  151.         }
  152.     }
  153.     return [totalscore, totalexpscore];
  154. }
  155. function tagPi(list) {
  156.     // recursively tag each li with pi values
  157.     // data-pi: average proportion of comments that are sub-comments.
  158.     var numerator = 1;
  159.     var denominator = 2;
  160.     for (var i = list.childNodes.length; i--;) {
  161.         currentchild = list.childNodes[i];
  162.         if (currentchild.className=="post pingback") { // skip pingbacks
  163.             continue;
  164.         }
  165.         if (currentchild.nodeName == "LI") {
  166.             // find ul child
  167.             for (var j = currentchild.childNodes.length; j--;) {
  168.                 if (currentchild.childNodes[j].nodeName == "UL") {
  169.                     tagPi(currentchild.childNodes[j]);
  170.                     break;
  171.                 }
  172.             }
  173.             currentchild = list.childNodes[i];
  174.             numerator += parseInt(currentchild.getAttribute("data-score"), 10);
  175.             denominator += parseInt(currentchild.getAttribute("data-comments-after"), 10);
  176.         }
  177.     }
  178.     for (var i2 = list.childNodes.length; i2--;) {
  179.         currentchild = list.childNodes[i2];
  180.         if (currentchild.className=="post pingback") { // skip pingbacks
  181.             continue;
  182.         }
  183.         if (currentchild.nodeName == "LI") {
  184.             currentchild.setAttribute("data-pi", 1.0 * numerator / denominator);
  185.         }
  186.     }
  187. }
  188.  
  189.  
  190.  
  191. function sortComments(by) {
  192.     commentol = document.querySelectorAll('#comments > .commentlist')[0];
  193.  
  194.     commentol.style.display = "none"; // faster DOM manipulation if hidden.
  195.  
  196.     if (by == lastby) {
  197.         if (lastby == "recency") {
  198.             recencydirection *= -1;
  199.         } else if (lastby == "best") {
  200.             bestdirection *= -1;
  201.         } else if (lastby == "top") {
  202.             topdirection *= -1;
  203.         }
  204.     }
  205.     sortLIs(commentol, by);
  206.     lastby = by;
  207.     //need to get the OL anew, since we replaced it in sortLIs.
  208.     document.querySelectorAll('#comments > .commentlist')[0].style.display = "block";
  209. }
  210.  
  211.  
  212. // the sorting functions. Return +ve if a belongs after b,
  213. // -ve if b belongs after a, and 0 in a tie.
  214. recencysorter = function(a, b) {
  215.     // sort by comment ID number; we assume it is monotonically ascending.
  216.     return (parseInt(a.getAttribute("data-id"), 10) -
  217.             parseInt(b.getAttribute("data-id"), 10));
  218. };
  219. bestsorter = function(a, b) {
  220.     // sort by number of sub-comments; break ties with most number of direct children.
  221.     diff = parseInt(b.getAttribute("data-score"), 10) -
  222.            parseInt(a.getAttribute("data-score"), 10);
  223.     if (diff === 0) {
  224.         // break ties with 'escore':
  225.         // more weight on lower level comments.
  226.         diff = parseInt(b.getAttribute("data-escore"), 10) -
  227.                parseInt(a.getAttribute("data-escore"), 10);
  228.     }
  229.     return diff;
  230. };
  231. topsorter = function(a, b) {
  232.     // sort by the ratio:
  233.     // (number of children + 1) / (number of comments that were made after this one + 2)
  234.     // the '+1' prevents the most recent comment from always winning,
  235.     // and childless comments from always losing (and has some handwavy justification from bayesian stats.)
  236.     // If there are lots of comments, this gives an advantage to very recent comments when
  237.     // the number of comments is high. Is this sensible?
  238.     // One could say that the very recent comments are 'probably good', since someone did not find
  239.     // another thread to post it (although there are already lots of threads). Also, if the comment
  240.     // does not gather replies, it very rapidly disappears into averageness. I am not 100% convinced, however.
  241.     // Maybe a good thing would be to fit a Beta-distribution to the number of comments.
  242.     alpha = 2 * parseFloat(b.getAttribute("data-pi")); // the '2' is relatively arbitrary here...
  243.     bca = parseInt(b.getAttribute("data-comments-after"), 10) + 2;
  244.     bscore = parseInt(b.getAttribute("data-score"), 10) + alpha;
  245.     aca = parseInt(a.getAttribute("data-comments-after"), 10) + 2;
  246.     ascore = parseInt(a.getAttribute("data-score"), 10) + alpha;
  247.     // compare bscore / bca with ascore / aca.
  248.     diff = bscore * aca - ascore * bca;
  249.     return diff;
  250. };
  251.  
  252.  
  253.  
  254. function sortLIs(list, by, iii) {
  255.     // recursively sort comment list
  256.  
  257.     // iii is for debugging purposes: array of index of list element currently looked at.
  258.     // however. we also need iii to remember which level we are (and sort the 5th level
  259.     // differently).
  260.  
  261.     if(typeof(iii)==='undefined') {iii = [];}
  262.     iii.push(0);
  263.  
  264.     var new_list = list.cloneNode(false);
  265.     var larr = [];
  266.     var pingbacks = [];
  267.     // Add all li-elements to an array
  268.     for(var i = list.childNodes.length; i--;){
  269.         currentchild = list.childNodes[i];
  270.         if(currentchild.nodeName === 'LI') { //check for 'li', we want to skip empty textNodes.
  271.             if (currentchild.className=="post pingback") { // skip pingbacks
  272.               pingbacks.push(currentchild);
  273.               continue;
  274.             }
  275.             for (var j = currentchild.childNodes.length; j--;) {
  276.                 // look for child comments: they would be in a UL-element.
  277.                 if (currentchild.childNodes[j].nodeName == "UL") {
  278.                     iii[iii.length-1]++;
  279.                     sortLIs(currentchild.childNodes[j], by, iii);
  280.                     break; // only one UL per comment.
  281.                 }
  282.             }
  283.             larr.push(list.childNodes[i]); // at this point currentchild has been burned.
  284.         }
  285.     }
  286.     iii.pop();
  287.  
  288.     if (document.getElementById('llstablecheck').checked && iii.length >= 4) {
  289.         // if we are in the last level and want it always
  290.         larr.sort(recencysorter);
  291.     } else if (by == "recency") {
  292.        
  293.         // sort oldest first / last
  294.         larr.sort(function(a, b) {
  295.             return recencysorter(a, b) * recencydirection;
  296.         });
  297.     } else if (by == "best") {
  298.         // sort by number of descendant comments
  299.         larr.sort(function(a, b) {
  300.             diff = bestsorter(a, b) * bestdirection;
  301.             if (diff === 0) {
  302.                 // tie breaker is recency, with default recencydirection
  303.                 diff = recencysorter(a, b) * recencydirection;
  304.             }
  305.             return diff;
  306.         });
  307.     } else if (by == "top") {
  308.         larr.sort(function(a, b) {
  309.             diff = topsorter(a, b);
  310.             if (diff === 0) {
  311.                 // first tie breaker is total score, with sort direction topdirection.
  312.                 diff = bestsorter(a, b);
  313.             } if (diff === 0) {
  314.                 // second tie breaker is recency, with default recencydirection
  315.                 diff = recencysorter(a, b) * recencydirection;
  316.             } else {
  317.                 // if we don't use recency, sort direction is applied.
  318.                 diff *= topdirection;
  319.             }
  320.             return diff;
  321.         });
  322.     }
  323.  
  324.     // Add the pingbacks at the end.
  325.     larr = larr.concat(pingbacks);
  326.     for(var i2 = 0; i2 < larr.length; i2++) {
  327.         new_list.appendChild(larr[i2]);
  328.     }
  329.     list.parentNode.replaceChild(new_list, list);
  330. }
  331.  
  332.  
  333. tagComments();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement