Created
May 3, 2022 15:13
-
-
Save mjpieters/84bbd7f9850b2ed063453b3c3463fe7c to your computer and use it in GitHub Desktop.
Threaded comments - recovered copy of Balpha's user script (https://stackapps.com/questions/2050/threaded-comments)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name threading-comments | |
// @namespace stackoverflow | |
// @description Show threaded comments | |
// @version 1.1 | |
// @match *://stackoverflow.com/* | |
// @match *://serverfault.com/* | |
// @match *://superuser.com/* | |
// @match *://meta.stackoverflow.com/* | |
// @match *://meta.serverfault.com/* | |
// @match *://meta.superuser.com/* | |
// @match *://stackapps.com/* | |
// @match *://*.stackexchange.com/* | |
// @match *://askubuntu.com/* | |
// @match *://meta.askubuntu.com/* | |
// @match *://answers.onstartups.com/* | |
// @match *://meta.answers.onstartups.com/* | |
// @match *://mathoverflow.net/* | |
// @match *://area51.stackexchange.com/proposals/* | |
// @author Benjamin Dumke-von der Ehe | |
// ==/UserScript== | |
// Thanks to Shog9 for this idea for making the script work in both | |
// Chrome and Firefox: | |
// http://meta.stackoverflow.com/46562 | |
function with_jquery(f) { | |
var script = document.createElement("script"); | |
script.type = "text/javascript"; | |
script.textContent = "(" + f.toString() + ")(jQuery)"; | |
document.body.appendChild(script); | |
}; | |
with_jquery(function($) { | |
if (!window.StackExchange) | |
return; | |
console.log("threading-comments: initialising"); | |
// https://bitbucket.org/balpha/lyfe; MIT license | |
(function(l){var q;q=Array.prototype.indexOf?function(a,b){return a.indexOf(b)}:function(a,b){for(var c=a.length,d=0;d<c;d++)if(d in a&&a[d]===b)return d;return-1};var m={},e=function(a){if(!(this instanceof e))return new e(a);this.forEach="function"===typeof a?n(a):a.constructor===Array?u(a):v(a)},r=function(){throw m;},p=function(a){this.message=a;this.name="IterationError"};p.prototype=Error.prototype;var n=function(a){return function(b,c){var d=!1,f=0,g=function(a){if(d)throw new p("yield after end of iteration"); | |
a=b.call(c,a,f,r);f++;return a},x=function(a){a=a instanceof e?a:new e(a);a.forEach(function(a){g(a)})};try{a(g,x,r)}catch(k){if(k!==m)throw k;}finally{d=!0}}},u=function(a){return n(function(b){for(var c=a.length,d=0;d<c;d++)d in a&&b(a[d])})},v=function(a){return n(function(b){for(var c in a)a.hasOwnProperty(c)&&b([c,a[c]])})},h=function(a){return"string"===typeof a?function(b){return b[a]}:a};e.prototype={toArray:function(){var a=[];this.forEach(function(b){a.push(b)});return a},filter:function(a, | |
b){var c=this;a=h(a);return new e(function(d){c.forEach(function(c){a.call(b,c)&&d(c)})})},take:function(a){var b=this;return new e(function(c){b.forEach(function(b,f,e){f>=a&&e();c(b)})})},skip:function(a){var b=this;return new e(function(c){b.forEach(function(b,f){f>=a&&c(b)})})},map:function(a,b){var c=this;a=h(a);return new e(function(d){c.forEach(function(c){d(a.call(b,c))})})},zipWithArray:function(a,b){"undefined"===typeof b&&(b=function(a,b){return[a,b]});var c=this;return new e(function(d){var e= | |
a.length,g=0;c.forEach(function(c,k,w){for(;!(k+g in a)&&k+g<e;)g++;k+g>=e&&w();d(b(c,a[k+g]))})})},reduce:function(a,b){var c,d;2>arguments.length?c=!0:(c=!1,d=b);this.forEach(function(b){c?(d=b,c=!1):d=a(d,b)});return d},and:function(a){var b=this;return new e(function(c,d){d(b);d(a)})},takeWhile:function(a){var b=this;a=h(a);return new e(function(c){b.forEach(function(b,e,g){a(b)?c(b):g()})})},skipWhile:function(a){var b=this;a=h(a);return new e(function(c){var d=!0;b.forEach(function(b){(d=d&& | |
a(b))||c(b)})})},all:function(a){var b=!0;a=h(a);this.forEach(function(c,d,e){(a?a(c):c)||(b=!1,e())});return b},any:function(a){var b=!1;a=h(a);this.forEach(function(c,d,e){if(a?a(c):c)b=!0,e()});return b},first:function(){var a;this.forEach(function(b,c,d){a=b;d()});return a},groupBy:function(a){var b=this;a=h(a);return new e(function(c,d){var f=[],g=[];b.forEach(function(b){var c=a(b),d=q(f,c);-1===d?(f.push(c),g.push([b])):g[d].push(b)});d((new e(f)).zipWithArray(g,function(a,b){var c=new e(b); | |
c.key=a;return c}))})},evaluated:function(){return new e(this.toArray())},except:function(a){return this.filter(function(b){return b!==a})},sortBy:function(a){var b=this;a=h(a);return new e(function(c){var d=b.toArray(),f=s(0,d.length).toArray();f.sort(function(b,c){var e=a(d[b]),f=a(d[c]);if(typeof e===typeof f){if(e===f)return b<c?-1:1;if(e<f)return-1;if(e>f)return 1}throw new TypeError("cannot compare "+e+" and "+f);});(new e(f)).forEach(function(a){c(d[a])})})},count:function(){var a=0;this.forEach(function(){a++}); | |
return a}};var t=function(a,b){var c=a;"undefined"===typeof b&&(b=1);return new e(function(a){for(;;)a(c),c+=b})},s=function(a,b){return t(a,1).take(b)},y=l.Generator;l.Generator=e;e.BreakIteration=m;e.Count=t;e.Range=s;e.IterationError=p;e.noConflict=function(){l.Generator=y;return e}})(this); | |
var Generator = window.Generator.noConflict(); | |
Generator.prototype.last = function () { | |
var result; | |
this.forEach(function (val) { result = val; }) | |
return result; | |
}; | |
// taken from kip's http://userscripts.org/scripts/review/62163 | |
var goodletters = Array('\u00c0','\u00c1','\u00c2','\u00c3','\u00c4','\u00c5','\u00c6','\u00c7' | |
,'\u00c8','\u00c9','\u00ca','\u00cb','\u00cc','\u00cd','\u00ce','\u00cf' | |
,'\u00d1','\u00d2','\u00d3','\u00d4','\u00d5','\u00d6' | |
,'\u00d8','\u00d9','\u00da','\u00db','\u00dc','\u00dd' | |
,'\u00e0','\u00e1','\u00e2','\u00e3','\u00e4','\u00e5','\u00e6','\u00e7' | |
,'\u00e8','\u00e9','\u00ea','\u00eb','\u00ec','\u00ed','\u00ee','\u00ef' | |
,'\u00f1','\u00f2','\u00f3','\u00f4','\u00f5','\u00f6' | |
,'\u00f8','\u00f9','\u00fa','\u00fb','\u00fc','\u00fd' ,'\u00ff').join(''); | |
var good = new RegExp("^[" + goodletters + "\\w]{3}"); | |
var bad = new RegExp("[^" + goodletters + "\\w]", "g"); | |
// from my http://userscripts.org/scripts/review/68252 | |
function goodify(s) | |
{ | |
var original = s; | |
while (s.length >3 && !s.match(good)) { | |
s = s.replace(bad, ""); | |
} | |
if (!s.match(good)) | |
{ | |
// failed, so we might as well use the original | |
s = original; | |
} | |
return s; | |
} | |
function extractMention(commentText, useAlg2) { | |
var match; | |
if (useAlg2) | |
// this is closer to the real @-reply heuristics | |
match = /@(\S+)/.exec(commentText); | |
else | |
match = /@([^ .;:!?,()[\]{}\/\s]+)/.exec(commentText); | |
if (!match) | |
return null; | |
if (useAlg2) | |
return goodify(match[1]).toLowerCase(); | |
else | |
return match[1].toLowerCase(); | |
} | |
function matcher(username, useAlg2) { | |
function fits(s) { | |
return s.substring(0, username.length).toLowerCase() == username; | |
} | |
if (useAlg2) | |
return function (candidateName) { | |
return fits(goodify(candidateName)); | |
}; | |
else | |
return function (candidateName) { | |
return fits(candidateName.replace(/\s/g, "")); | |
} | |
} | |
function userIdFromLink(link) { | |
var match = /\/users\/(\d+)\//.exec(link); | |
if (match) | |
return parseInt(match[1]); | |
else | |
return null; | |
} | |
function commenterId(jComment) { | |
var userlink = $("a.comment-user", jComment).attr("href"); | |
return userIdFromLink(userlink); | |
} | |
// How far may comments be indented? | |
// Note that MAX_NESTING = 3 means there are | |
// up to *four* levels (including top-level) | |
var MAX_NESTING = 12; | |
// How many pixels of indentation for the first level? | |
var INDENT = 30; | |
// By how much does the additional indentation decrease per level? | |
// Setting this to 1 means constant indentation (i.e. the original behaviour). | |
var GAMMA = .95; | |
var indent_widths = [0]; | |
var inc = INDENT; | |
var w = 0; | |
for (var i = 1; i <= MAX_NESTING; i++) { | |
w += inc; | |
inc *= GAMMA; | |
indent_widths.push(w); | |
} | |
function indenter(parent) { | |
for (var i = MAX_NESTING; i > 0; i--) | |
{ | |
if (parent.hasClass("threading-" + (i-1)) || (i == MAX_NESTING && parent.hasClass("threading-" + i))) | |
{ | |
return function(jComment) { | |
jComment.addClass("threading-" + i).find(".comment-text").css({"padding-left": indent_widths[i]}); | |
} | |
} | |
} | |
return function(jComment) { | |
jComment.addClass("threading-1").find(".comment-text").css({"padding-left": INDENT}); | |
}; | |
} | |
function thread(jCommentDiv) { | |
var opLink = jCommentDiv.closest("#question, .answer").find(".user-details:last a").attr("href"), | |
opId = userIdFromLink(opLink), | |
comments = [], | |
commentsGen = Generator(comments), | |
commenterCountExceptOp = 0, | |
commenters = {}; | |
jCommentDiv.find(".comment").each(function () { | |
var jComment = $(this), | |
newComment = { | |
id: jComment.attr("id"), | |
userId: commenterId(jComment), | |
userName: jComment.find(".comment-user").text() | |
}, | |
isFirstByThisUser = !commenters["u" + newComment.userId], | |
commentText = jComment.find(".comment-text").text(), | |
useAlg2 = false, | |
mention = extractMention(commentText), | |
candidates; | |
commenters["u" + newComment.userId] = true; | |
if (newComment.userId !== opId && isFirstByThisUser) | |
commenterCountExceptOp++; | |
if (!mention) { | |
mention = extractMention(commentText, true); | |
useAlg2 = true; | |
} | |
if (mention) { | |
var filter = matcher(mention, useAlg2); | |
candidates = commentsGen.filter(function (c) { return filter(c.userName); }); | |
} else { | |
if (commenterCountExceptOp === 1) { | |
candidates = commentsGen.filter(function (c) { return c.userId !== newComment.userId; }); | |
} else if (newComment.userId !== opId) { | |
candidates = commentsGen.filter(function (c) { return c.userId === opId && c.replyUserId === newComment.userId; }); | |
} | |
} | |
if (candidates) { | |
var conversation = candidates.filter(function (c) { return c.replyUserId === newComment.userId }), | |
replyComment = conversation.last() || candidates.last(); | |
if (replyComment) { | |
newComment.replyUserId = replyComment.userId; | |
newComment.replyCommentId = replyComment.id; | |
newComment.replyIsExplicit = !!mention; | |
} | |
} | |
comments.push(newComment); | |
}); | |
commentsGen | |
.filter(function (c) { return c.replyCommentId; }) | |
.groupBy(function (c) { return c.replyCommentId; }) | |
.forEach(function (sameParentGroup) { | |
var jParent = $("#" + sameParentGroup.key); | |
var ind = indenter(jParent); | |
var after = jParent; | |
sameParentGroup.forEach(function (comment) { | |
var jComment = $("#" + comment.id); | |
ind(jComment); | |
jComment.insertAfter(after); | |
after = jComment; | |
}); | |
}); | |
} | |
function go() { | |
$("div.comments:not(.nothread)").not(":has(.threaded)").each(function () { thread($(this)); }).find(".comment:first").addClass("threaded"); | |
} | |
function undo() { | |
$("#threading-menu").fadeOut("fast", function() { $(this).remove(); }); | |
var jComments = $(this).closest(".comments").addClass("nothread").find(".comment"); | |
if (jComments.length == 0) | |
return; | |
var orig_padding = jComments.eq(0).find(".comment-text").css("padding-left"); | |
var gComments = Generator(jComments.toArray()).sortBy(function (c) { return parseInt(c.id.replace("comment-", "")); }); | |
jComments.eq(0).parent().append(gComments.toArray()); | |
jComments.each(function() { | |
$(this).removeClass("threaded").find(".comment-text").css("padding-left", orig_padding); | |
}); | |
} | |
function redo() { | |
$(this).closest(".comments").removeClass("nothread"); | |
go(); | |
} | |
var menuTimeout; | |
$("body").delegate("div.comments", "mouseenter", function () { | |
var that = this; | |
clearTimeout(menuTimeout); | |
menuTimeout = setTimeout(function () { showThreadingMenu($(that)); }, 500); | |
}); | |
$("body").delegate("div.comments", "mouseleave", function () { | |
$("#threading-menu").fadeOut("fast", function() { $(this).remove(); }); | |
clearTimeout(menuTimeout); | |
}); | |
function showThreadingMenu(jCommentDiv) { | |
$("#threading-menu").remove(); | |
var shouldUndo = !jCommentDiv.hasClass("nothread"); | |
$("<div>" + (shouldUndo ? "un" : "") + "thread</div>").hide().css({ | |
position: "absolute", | |
marginTop: -20, | |
color: "black", | |
backgroundColor: "white", | |
padding: 5, | |
opacity: .6, | |
boxShadow: "0 0 4px rgba(0, 0, 0, .6)", | |
webkitBoxShadow: "0 0 4px rgba(0, 0, 0, .6)", | |
"-moz-box-shadow": "0 0 4px rgba(0, 0, 0, .6)", | |
borderRadius: 5, | |
"-moz-border-radius": 5, | |
cursor: "pointer" | |
}).prependTo(jCommentDiv).attr("id", "threading-menu").fadeIn("fast").click(shouldUndo ? undo : redo); | |
} | |
if (window.MathJax) { | |
var orig_go = go; | |
go = function () { | |
MathJax.Hub.Queue(orig_go); | |
} | |
} | |
$(document).ajaxComplete(go); | |
go(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment