sarken / Sign-up Summary Sorter

// ==UserScript==
// @name        Sign-up Summary Sorter
// @namespace   sarken
// @description Allows you to sort the sign-up summaries on the Archive of Our Own.
// @include     http://archiveofourown.org/collections/*/signups/summary
// @include     https://archiveofourown.org/collections/*/signups/summary
// @version     1.0
// ==/UserScript==

// Add jQuery
var GM_JQ = document.createElement('script');
  GM_JQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js';
  GM_JQ.type = 'text/javascript';
  document.getElementsByTagName('head')[0].appendChild(GM_JQ);

// Check if jQuery's loaded
function GM_wait() {
  if(typeof unsafeWindow.jQuery == 'undefined') { window.setTimeout(GM_wait,100); }
  else { unsafeWindow.$gm = unsafeWindow.jQuery.noConflict(); letsJQuery(); }
}
GM_wait();

// All your GM code must be inside this function
function letsJQuery() {
  // add tinysort to the body with the other Archive jQuery
  unsafeWindow.$gm("body").append('<!--* TinySort 1.5.6 * Copyright (c) 2008-2013 Ron Valstar http://tinysort.sjeiti.com/ * * Dual licensed under the MIT and GPL licenses: *   http://www.opensource.org/licenses/mit-license.php *   http://www.gnu.org/licenses/gpl.html *--><script type="text/javascript">!function(a,b){"use strict";function c(a){return a&&a.toLowerCase?a.toLowerCase():a}function d(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]==b)return!e;return e}var e=!1,f=null,g=parseFloat,h=Math.min,i=/(-?\d+\.?\d*)$/g,j=/(\d+\.?\d*)$/g,k=[],l=[],m=function(a){return"string"==typeof a},n=function(a,b){for(var c,d=a.length,e=d;e--;)c=d-e-1,b(a[c],c)},o=Array.prototype.indexOf||function(a){var b=this.length,c=Number(arguments[1])||0;for(c=0>c?Math.ceil(c):Math.floor(c),0>c&&(c+=b);b>c;c++)if(c in this&&this[c]===a)return c;return-1};a.tinysort={id:"TinySort",version:"1.5.6",copyright:"Copyright (c) 2008-2013 Ron Valstar",uri:"http://tinysort.sjeiti.com/",licensed:{MIT:"http://www.opensource.org/licenses/mit-license.php",GPL:"http://www.gnu.org/licenses/gpl.html"},plugin:function(){var a=function(a,b){k.push(a),l.push(b)};return a.indexOf=o,a}(),defaults:{order:"asc",attr:f,data:f,useVal:e,place:"start",returns:e,cases:e,forceStrings:e,ignoreDashes:e,sortFunction:f}},a.fn.extend({tinysort:function(){var p,q,r,s,t=this,u=[],v=[],w=[],x=[],y=0,z=[],A=[],B=function(a){n(k,function(b){b.call(b,a)})},C=function(a,b){return"string"==typeof b&&(a.cases||(b=c(b)),b=b.replace(/^\s*(.*?)\s*$/i,"$1")),b},D=function(a,b){var c=0;for(0!==y&&(y=0);0===c&&s>y;){var d=x[y],f=d.oSettings,h=f.ignoreDashes?j:i;if(B(f),f.sortFunction)c=f.sortFunction(a,b);else if("rand"==f.order)c=Math.random()<.5?1:-1;else{var k=e,o=C(f,a.s[y]),p=C(f,b.s[y]);if(!f.forceStrings){var q=m(o)?o&&o.match(h):e,r=m(p)?p&&p.match(h):e;if(q&&r){var t=o.substr(0,o.length-q[0].length),u=p.substr(0,p.length-r[0].length);t==u&&(k=!e,o=g(q[0]),p=g(r[0]))}}c=d.iAsc*(p>o?-1:o>p?1:0)}n(l,function(a){c=a.call(a,k,o,p,c)}),0===c&&y++}return c};for(p=0,r=arguments.length;r>p;p++){var E=arguments[p];m(E)?z.push(E)-1>A.length&&(A.length=z.length-1):A.push(E)>z.length&&(z.length=A.length)}for(z.length>A.length&&(A.length=z.length),s=z.length,0===s&&(s=z.length=1,A.push({})),p=0,r=s;r>p;p++){var F=z[p],G=a.extend({},a.tinysort.defaults,A[p]),H=!(!F||""===F),I=H&&":"===F[0];x.push({sFind:F,oSettings:G,bFind:H,bAttr:!(G.attr===f||""===G.attr),bData:G.data!==f,bFilter:I,$Filter:I?t.filter(F):t,fnSort:G.sortFunction,iAsc:"asc"==G.order?1:-1})}return t.each(function(c,d){var e,f=a(d),g=f.parent().get(0),h=[];for(q=0;s>q;q++){var i=x[q],j=i.bFind?i.bFilter?i.$Filter.filter(d):f.find(i.sFind):f;h.push(i.bData?j.data(i.oSettings.data):i.bAttr?j.attr(i.oSettings.attr):i.oSettings.useVal?j.val():j.text()),e===b&&(e=j)}var k=o.call(w,g);0>k&&(k=w.push(g)-1,v[k]={s:[],n:[]}),e.length>0?v[k].s.push({s:h,e:f,n:c}):v[k].n.push({e:f,n:c})}),n(v,function(a){a.s.sort(D)}),n(v,function(a){var b=a.s,c=a.n,f=b.length,g=c.length,i=f+g,j=[],k=i,l=[0,0];switch(G.place){case"first":n(b,function(a){k=h(k,a.n)});break;case"org":n(b,function(a){j.push(a.n)});break;case"end":k=g;break;default:k=0}for(p=0;i>p;p++){var m=d(j,p)?!e:p>=k&&k+f>p,o=m?0:1,q=(m?b:c)[l[o]].e;q.parent().append(q),(m||!G.returns)&&u.push(q.get(0)),l[o]++}}),t.length=0,Array.prototype.push.apply(t,u),t}}),a.fn.TinySort=a.fn.Tinysort=a.fn.tsort=a.fn.tinysort}(jQuery);</script>');
  
  // make a data attribute out of the count so we can sort properly
  unsafeWindow.$gm("td").find("span").each(function(i, el) {
    var el = unsafeWindow.$gm(this);
    el.attr('data-sortable', parseInt(el.text()));
  });

  // add buttons and update text
  unsafeWindow.$gm(".challenge_signups-summary").find("h3.heading").after('<ul class="sorting actions"><li><h4>Sort By: </h4></li><li><a href="#" class="sort-by-name">Name <span class="ascending">\u2191</span><span class="descending hidden">\u2193</span></a></li><li><a href="#" class="sort-by-requests">Requests <span class="ascending">\u2191</span><span class="descending hidden">\u2193</span></a></li><li><a href="#" class="sort-by-offers current">Offers <span class="ascending">\u2191</span><span class="descending hidden">\u2193</span></a></li><li><a href="#" class="sort-by-random">Random</a></li></ul>');
  unsafeWindow.$gm(".challenge_signups-summary").find(".note").replaceWith('<p class="notes">Listed by fewest offers and most requests. Use the buttons added by Sign-up Summary Sorter to rearrange.</p>');
  
  // sort
  unsafeWindow.$gm(".sort-by-name").one("click", sortByName);
  unsafeWindow.$gm(".sort-by-requests").one("click", sortByRequests);
  unsafeWindow.$gm(".sort-by-offers").one("click", sortByOffers);

  // randomize
  unsafeWindow.$gm(".sort-by-random").click(function() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort({order:"rand"});
    unsafeWindow.$gm(".sort-by-name, .sort-by-requests, .sort-by-offers").removeClass("current");
  })
  
  // remove current from the links that weren't clicked and add it to the link that was clicked
  unsafeWindow.$gm(".sorting").find("a").click(function(e) {
    e.preventDefault();
    unsafeWindow.$gm(this).parent().siblings("li").children("a").removeClass("current");
    unsafeWindow.$gm(this).addClass("current");
  })
  
  // change the arrow direction for name and requests
  unsafeWindow.$gm(".sort-by-name, .sort-by-requests").one("click", arrowToDesc);
  
  // sorting functions  
  function sortByName() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("th");    
    unsafeWindow.$gm(this).one("click", sortByNameDesc);
  }
  function sortByNameDesc() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("th", {order:"desc"});
    unsafeWindow.$gm(this).one("click", sortByName);
  }
  
  function sortByRequests() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("td .requested", {data:"sortable"}, "td .offered", {data:"sortable"}, "th");
    unsafeWindow.$gm(this).one("click", sortByRequestsDesc);
  }
  function sortByRequestsDesc() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("td .requested", {data:"sortable", order:"desc"}, "td .offered", {data:"sortable"}, "th");
    unsafeWindow.$gm(this).one("click", sortByRequests);
  }

  // ascending by offers is the default, so the first click should change it to descending
  function sortByOffers() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("td .offered", {data:"sortable", order:"desc"}, "td .requested", {data:"sortable", order:"desc"}, "th");
    unsafeWindow.$gm(this).find(".descending").removeClass("hidden");
    unsafeWindow.$gm(this).find(".ascending").addClass("hidden"); 
    unsafeWindow.$gm(this).one("click", sortByOffersAsc);
  }
  function sortByOffersAsc() {
    unsafeWindow.$gm("#challenge_summary").find("tbody").find("tr").tsort("td .offered", {data:"sortable"}, "td .requested", {data:"sortable", order:"desc"}, "th");
    unsafeWindow.$gm(this).find(".ascending").removeClass("hidden");
    unsafeWindow.$gm(this).find(".descending").addClass("hidden");
    unsafeWindow.$gm(this).one("click", sortByOffers);
  }
  
  // arrow direction for name and requests
  function arrowToDesc() {
    unsafeWindow.$gm(this).find(".ascending").removeClass("hidden");
    unsafeWindow.$gm(this).find(".descending").addClass("hidden");  
    unsafeWindow.$gm(this).one("click", arrowToAsc);
  }
  function arrowToAsc() {
    unsafeWindow.$gm(this).find(".descending").removeClass("hidden");
    unsafeWindow.$gm(this).find(".ascending").addClass("hidden");   
    unsafeWindow.$gm(this).one("click", arrowToDesc); 
  }
}