Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name sscsort
- // @namespace slatestarcodex
- // @include http://slatestarcodex.com/20*
- // @version 1
- // @grant none
- // ==/UserScript==
- // What is this? Install as Greasemonkey-script to get a sort-comments bar:
- // https://imgur.com/WlXqvLp
- // Fast enough with ~100 comments, with 300+ it is quite slow.
- // No warranty for usefulness, merchantability etc etc. I did not test
- // this very much, might be that comments disappear or are scrambled.
- // Feel free to modify, distribute, use inside your own project,
- // do whatever you want with it. I place this code in the public domain :-)
- var lastby = "recency"; // property that was last sorted by
- // sort directions
- var recencydirection = 1;
- var bestdirection = 1;
- var topdirection = 1;
- var allComments = []; // all the list elements.
- var isMenuScrolling = 0; // toggle 0/1 when scrolling below the menu & fixing it to the top
- // create the menu
- d = document.createElement('div');
- d.innerHTML = '<div id="sortbar"><div id="sortbardiv" \
- style="background-color:#FAFAFA;color:#333;padding:4px;margin-top:4px;border:1px solid;\
- z-index:1000;width:548px;border-radius:5px;"> \
- <span id="recencysorter" style="background-color:#DDD;padding:0px 3px;">Old ^</span> \
- <span id="mostsorter" style="background-color:#DDD;padding:0px 3px;">Most </span> \
- <span id="topsorter" style="background-color:#DDD;padding:0px 3px;">Top </span> \
- <span id="lastlevelstable" style="background-color:#DDD;padding:0px 3px;">\
- <input id="llstablecheck" type="checkbox" checked="checked" style="vertical-align:middle;">\
- 5th level always oldest first</input></span> \
- </div></div>';
- //insert the menu
- coms = document.getElementById('comments');
- coms.insertBefore(d, coms.childNodes[0]);
- // the events when clicking the menu buttons:
- document.getElementById('recencysorter').onclick = function(event) {
- sortComments("recency");
- document.getElementById('recencysorter').childNodes[0].data = "Old " +
- ((recencydirection<0)?"v":"^");
- document.getElementById('mostsorter').childNodes[0].data = "Most \u00a0\u00a0";
- document.getElementById('topsorter').childNodes[0].data = "top \u00a0\u00a0";
- };
- document.getElementById('mostsorter').onclick = function(event) {
- sortComments("best");
- document.getElementById('mostsorter').childNodes[0].data = "Most " +
- ((bestdirection<0)?"v":"^");
- document.getElementById('recencysorter').childNodes[0].data = "Old \u00a0\u00a0";
- document.getElementById('topsorter').childNodes[0].data = "top \u00a0\u00a0";
- };
- document.getElementById('topsorter').onclick = function(event) {
- sortComments("top");
- document.getElementById('topsorter').childNodes[0].data = "Top " +
- ((topdirection<0)?"v":"^");
- document.getElementById('mostsorter').childNodes[0].data = "Most \u00a0\u00a0";
- document.getElementById('recencysorter').childNodes[0].data = "Old \u00a0\u00a0";
- };
- document.getElementById('llstablecheck').onchange = function(event) {
- // sort again when checkbox state changes.
- if (lastby == "recency" || lastby == "best") {
- if (recencydirection == 1) {
- // if we sorted oldest-first before, we don't need to resort.
- // this is also the case if we sorted best-first, since oldest-first is
- // the tie-breaker.
- return;
- }
- } else if (topdirection == -1) {
- // if we sorted top first, it puts more recent comments to the top; only when we
- // had top-last we don't need to resort.
- return;
- }
- //
- var lastsort = lastby;
- lastby = ""; // if we dont reset this, the sort direction will reverse.
- sortComments(lastsort);
- };
- // make sure the menu gets fixed to the top when scrolling below it
- scrollevent = function(event) {
- var posn = document.getElementById('sortbar').getBoundingClientRect();
- if (posn.top < 0) {
- if (!isMenuScrolling) {
- document.getElementById('sortbar').style.height=(posn.bottom -
- posn.top) + "px";
- document.getElementById('sortbardiv').style.position=
- "fixed";
- document.getElementById('sortbardiv').style.top="0px";
- isMenuScrolling = 1;
- }
- } else {
- if (isMenuScrolling) {
- document.getElementById('sortbardiv').style.position="relative";
- isMenuScrolling = 0;
- }
- }
- };
- window.onscroll = scrollevent;
- scrollevent(0); // trigger event if we start below the menu.
- // save score values of comments
- function tagComments() {
- commentol = document.querySelectorAll('#comments > .commentlist')[0];
- tagScores(commentol);
- allComments.sort(function(a, b) {
- return (parseInt(a.id.match(/\d+/)[0], 10) -
- parseInt(b.id.match(/\d+/)[0], 10));
- });
- j = 0;
- for(var i = allComments.length; i--;) {
- allComments[i].setAttribute("data-comments-after", j++);
- }
- tagPi(commentol);
- }
- function tagScores(list) {
- // recursively tag each li with score values
- // data-score: total number of comments that are anchestors of a li
- // data-escore: number of comments that are anchestors, weighted by 2^(-l), where
- // l is the distance to this comment. Direct children: l=0. Grandchild: l=1 etc.
- var totalscore = 0;
- var totalexpscore = 0;
- for (var i = list.childNodes.length; i--;) {
- currentchild = list.childNodes[i];
- if (currentchild.className=="post pingback") { // skip pingbacks
- continue;
- }
- if (currentchild.nodeName == "LI") {
- tscores = [0, 0];
- // find ul child
- for (var j = currentchild.childNodes.length; j--;) {
- if (currentchild.childNodes[j].nodeName == "UL") {
- tscores = tagScores(currentchild.childNodes[j]);
- break;
- }
- }
- currentchild = list.childNodes[i];
- currentchild.setAttribute("data-score", tscores[0]);
- currentchild.setAttribute("data-escore", tscores[1]);
- // save the id as a number string, so we can quickly parse it when sorting.
- currentchild.setAttribute("data-id", currentchild.id.match(/\d+/)[0]);
- totalscore += tscores[0] + 1;
- totalexpscore += tscores[1] / 2 + 1;
- allComments.push(list.childNodes[i]);
- }
- }
- return [totalscore, totalexpscore];
- }
- function tagPi(list) {
- // recursively tag each li with pi values
- // data-pi: average proportion of comments that are sub-comments.
- var numerator = 1;
- var denominator = 2;
- for (var i = list.childNodes.length; i--;) {
- currentchild = list.childNodes[i];
- if (currentchild.className=="post pingback") { // skip pingbacks
- continue;
- }
- if (currentchild.nodeName == "LI") {
- // find ul child
- for (var j = currentchild.childNodes.length; j--;) {
- if (currentchild.childNodes[j].nodeName == "UL") {
- tagPi(currentchild.childNodes[j]);
- break;
- }
- }
- currentchild = list.childNodes[i];
- numerator += parseInt(currentchild.getAttribute("data-score"), 10);
- denominator += parseInt(currentchild.getAttribute("data-comments-after"), 10);
- }
- }
- for (var i2 = list.childNodes.length; i2--;) {
- currentchild = list.childNodes[i2];
- if (currentchild.className=="post pingback") { // skip pingbacks
- continue;
- }
- if (currentchild.nodeName == "LI") {
- currentchild.setAttribute("data-pi", 1.0 * numerator / denominator);
- }
- }
- }
- function sortComments(by) {
- commentol = document.querySelectorAll('#comments > .commentlist')[0];
- commentol.style.display = "none"; // faster DOM manipulation if hidden.
- if (by == lastby) {
- if (lastby == "recency") {
- recencydirection *= -1;
- } else if (lastby == "best") {
- bestdirection *= -1;
- } else if (lastby == "top") {
- topdirection *= -1;
- }
- }
- sortLIs(commentol, by);
- lastby = by;
- //need to get the OL anew, since we replaced it in sortLIs.
- document.querySelectorAll('#comments > .commentlist')[0].style.display = "block";
- }
- // the sorting functions. Return +ve if a belongs after b,
- // -ve if b belongs after a, and 0 in a tie.
- recencysorter = function(a, b) {
- // sort by comment ID number; we assume it is monotonically ascending.
- return (parseInt(a.getAttribute("data-id"), 10) -
- parseInt(b.getAttribute("data-id"), 10));
- };
- bestsorter = function(a, b) {
- // sort by number of sub-comments; break ties with most number of direct children.
- diff = parseInt(b.getAttribute("data-score"), 10) -
- parseInt(a.getAttribute("data-score"), 10);
- if (diff === 0) {
- // break ties with 'escore':
- // more weight on lower level comments.
- diff = parseInt(b.getAttribute("data-escore"), 10) -
- parseInt(a.getAttribute("data-escore"), 10);
- }
- return diff;
- };
- topsorter = function(a, b) {
- // sort by the ratio:
- // (number of children + 1) / (number of comments that were made after this one + 2)
- // the '+1' prevents the most recent comment from always winning,
- // and childless comments from always losing (and has some handwavy justification from bayesian stats.)
- // If there are lots of comments, this gives an advantage to very recent comments when
- // the number of comments is high. Is this sensible?
- // One could say that the very recent comments are 'probably good', since someone did not find
- // another thread to post it (although there are already lots of threads). Also, if the comment
- // does not gather replies, it very rapidly disappears into averageness. I am not 100% convinced, however.
- // Maybe a good thing would be to fit a Beta-distribution to the number of comments.
- alpha = 2 * parseFloat(b.getAttribute("data-pi")); // the '2' is relatively arbitrary here...
- bca = parseInt(b.getAttribute("data-comments-after"), 10) + 2;
- bscore = parseInt(b.getAttribute("data-score"), 10) + alpha;
- aca = parseInt(a.getAttribute("data-comments-after"), 10) + 2;
- ascore = parseInt(a.getAttribute("data-score"), 10) + alpha;
- // compare bscore / bca with ascore / aca.
- diff = bscore * aca - ascore * bca;
- return diff;
- };
- function sortLIs(list, by, iii) {
- // recursively sort comment list
- // iii is for debugging purposes: array of index of list element currently looked at.
- // however. we also need iii to remember which level we are (and sort the 5th level
- // differently).
- if(typeof(iii)==='undefined') {iii = [];}
- iii.push(0);
- var new_list = list.cloneNode(false);
- var larr = [];
- var pingbacks = [];
- // Add all li-elements to an array
- for(var i = list.childNodes.length; i--;){
- currentchild = list.childNodes[i];
- if(currentchild.nodeName === 'LI') { //check for 'li', we want to skip empty textNodes.
- if (currentchild.className=="post pingback") { // skip pingbacks
- pingbacks.push(currentchild);
- continue;
- }
- for (var j = currentchild.childNodes.length; j--;) {
- // look for child comments: they would be in a UL-element.
- if (currentchild.childNodes[j].nodeName == "UL") {
- iii[iii.length-1]++;
- sortLIs(currentchild.childNodes[j], by, iii);
- break; // only one UL per comment.
- }
- }
- larr.push(list.childNodes[i]); // at this point currentchild has been burned.
- }
- }
- iii.pop();
- if (document.getElementById('llstablecheck').checked && iii.length >= 4) {
- // if we are in the last level and want it always
- larr.sort(recencysorter);
- } else if (by == "recency") {
- // sort oldest first / last
- larr.sort(function(a, b) {
- return recencysorter(a, b) * recencydirection;
- });
- } else if (by == "best") {
- // sort by number of descendant comments
- larr.sort(function(a, b) {
- diff = bestsorter(a, b) * bestdirection;
- if (diff === 0) {
- // tie breaker is recency, with default recencydirection
- diff = recencysorter(a, b) * recencydirection;
- }
- return diff;
- });
- } else if (by == "top") {
- larr.sort(function(a, b) {
- diff = topsorter(a, b);
- if (diff === 0) {
- // first tie breaker is total score, with sort direction topdirection.
- diff = bestsorter(a, b);
- } if (diff === 0) {
- // second tie breaker is recency, with default recencydirection
- diff = recencysorter(a, b) * recencydirection;
- } else {
- // if we don't use recency, sort direction is applied.
- diff *= topdirection;
- }
- return diff;
- });
- }
- // Add the pingbacks at the end.
- larr = larr.concat(pingbacks);
- for(var i2 = 0; i2 < larr.length; i2++) {
- new_list.appendChild(larr[i2]);
- }
- list.parentNode.replaceChild(new_list, list);
- }
- tagComments();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement