whytrusttomhanks / FetishSort

// ==UserScript==
// @name         FetishSort
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  make FetLife's jaggiest feature a little bit less jaggy
// @author       WhyTrustTomHanks
// @match        *://fetlife.com/*
// @grant        none
// ==/UserScript==


// Helper function. Reports empty strings as TRUE.
function isEmpty(str) {
  return (!str || 0 === str.length);
}

/**
 * This function takes in a set of interests, and returns formatted HTML. Interests
 * are defined in the function beneath this one: each is an array of URL/text
 * pairings. All this does is run through the array, grab the URL and the text,
 * and writes a new bunch of links.
 */
function makeBlock(interests) {
  var block = [];

  for (i = 0; i < interests.length; i++) {
    block[block.length] = " <a href=" + interests[i][0] + ">" + interests[i][1] + "</a>"; // "writes" the link
  }

  // creates the opening and closing tags for the block
  var blockOpen = "<div style='margin-left: 20px; margin-top: 5px;'>";
  var blockClose = "</div>";

  // converts the block array to a string, and wraps it up
  var blockWrapped = blockOpen + block.toString() + blockClose;

  return blockWrapped;
}

/**
 * FetLife's HTML output is garbage. The way this function works is:
 *
 * — Scan all the links in a particular space.
 * — See which ones are followed by "descriptions" (giving, receiving, etc)
 * — Use those descriptions to create a bunch of categories.
 *
 * The key phrase up there? "Followed by". The descriptions have NOTHING to do with
 * the links they accompany, programmatically speaking. They're just... text. Floatin'
 * about. Like a buncha grey parenthetical assholes. It's disgusting, and it makes for
 * ugly code.
 *
 * Anyway, we make those categories, run makeBlock() on each of them, and spit out the
 * appropriate formatting. This function can be run on any arbitrary HTML element,
 * which is important, because, on FetLife, all HTML elements are arbitrary.
 */
$.fn.formatList = function(){

  // declare arrays, even the ones that'll be empty.
  var into = [];
  var giving = [];
  var receiving = [];
  var wearing = [];
  var watching = [];
  var everything = [];

  // find all links within this space, and break each one down.
  $(this).children('a').each(function(){

    // does this link belong to a category?
    if ($(this).next().is('span')) {
      var fetishType = $(this).next().text(); // literally: "what's the thing after this link say?"
      if (fetishType == "(giving)") {
        giving[giving.length] = [$(this).attr('href') ,$(this).text()];
      }
      if (fetishType == "(receiving)") {
        receiving[receiving.length] = [$(this).attr('href') ,$(this).text()];
      }
      if (fetishType == "(wearing)") {
        wearing[wearing.length] = [$(this).attr('href') ,$(this).text()];
      }
      if (fetishType == "(watching others wear)") {
        watching[watching.length] = [$(this).attr('href') ,$(this).text()];
      }
      if (fetishType == "(everything to do with it)") {
        everything[everything.length] = [$(this).attr('href') ,$(this).text()];
      }
    }
    else {
      into[into.length] = [$(this).attr('href') ,$(this).text()];
    }
    /**
     * [$(this).attr('href') ,$(this).text()] is simply a pairing of "anchor link" and
     * "anchor text".
     */
  });

  // write empty strings for each potential category
  listInto = "";
  listGiving = "";
  listReceiving = "";
  listWearing = "";
  listWatching = "";
  listEverything = "";

  /**
   * If these categories _do_ exist, then they'll need additional formatting. The titles
   * here are unique per-category, so they can't be stuck into makeBlock—especially since
   * "into" has to function differently from all the rest.
   */
  if (!isEmpty(into)) {
    into = makeBlock(into);
    listInto = "<div style='margin-bottom: 10px;'></div>"
                + into;
  }

  if (!isEmpty(giving)) {
    giving = makeBlock(giving);
    listGiving = "<br><span class='quiet'><em>Giving</em></span>"
                 + giving;
  }

  if (!isEmpty(receiving)) {
    receiving = makeBlock(receiving);
    listReceiving = "<br><span class='quiet'><em>Receiving</em></span>"
                    + receiving;
  }

  if (!isEmpty(wearing)) {
    wearing = makeBlock(wearing);
    listWearing = "<br><span class='quiet'><em>Wearing</em></span>"
                  + wearing;
  }

  if (!isEmpty(watching)) {
    watching = makeBlock(watching);
    listWatching = "<br><span class='quiet'><em>Watching others wear</em></span>"
                   + watching;
  }

  if (!isEmpty(everything)) {
    everything = makeBlock(everything);
    listEverything = "<br><span class='quiet'><em>Everything to do with</em></span>"
                     + everything;
  }

  // The final assembled list! Hooray!
  completedList = listInto + listGiving + listReceiving + listWearing + listWatching + listEverything;

  // rewrite all of Fet's code, lololol
  $(this).html(completedList);

};


/**
 * Here's where we actually format things.
 *
 * Note how I'm drilling down three levels here, then jumping right back out again
 * with "parent().parent()". Do you know _why_ I do that? It's because FetLife never
 * fucking declares a class name for these blocks. There's no such thing, in its HTML,
 * as your "fetish list". There's just an arbitrarily-placed title, and then an
 * arbitrary paragraph, and then INSIDE that paragraph is a styling span called "quiet",
 * and inside THAT is an arbitrary italic declaration, and THAT is where it finally says:
 * "Yes, I am into this shit."
 *
 * Not that this is hard to work around. It's just lazy, sloppy code.
 */
$('p > .quiet > em:contains("Into:")').each(function(){
  $(this).parent().parent().formatList();
});

$('p > .quiet > em:contains("Curious about:")').each(function(){
  $(this).parent().parent().before("<br><h3>Curious about</h3>");
  $(this).parent().parent().formatList();
});

/*$('p > .quiet > em:contains("Soft limits:")').each(function(){
  $(this).parent().parent().before("<br><h3>Soft limits</h3>");
  $(this).parent().parent().formatList();
});

$('p > .quiet > em:contains("Hard limits:")').each(function(){
  $(this).parent().parent().before("<br><h3>Hard limits</h3>");
  $(this).parent().parent().formatList();
});*/

/**
 * Hard and soft limits break formatList, because formatList assumes fetishes will be
 * presented as anchors. For whatever reason, the profile I used to test this _did_
 * that, so I initially thought there would be no problem. Turns out there were problems!
 * Now the code above is disabled.
 *
 * Formatting these will be tricky, because here there is _no fucking wrapper_ for
 * individual items. It's just a long block of text, with occasional, unreliable span
 * punctuations. You can't even use commas to delineate, because commas are of course
 * perfectly acceptable formatting within fetishes. Atrocious. Simply atrocious.
 *
 * Instead, I wrote the functions below, which find the initial formatting of these blocks,
 * and replace them with the pretty red headers I use everywhere else.
 */

$('p > .quiet > em:contains("Soft limits:")').each(function(){
  $(this).parent().parent().before("<br><h3>Soft limits</h3>");
  $(this).html('');
});

$('p > .quiet > em:contains("Hard limits:")').each(function(){
  $(this).parent().parent().before("<br><h3>Hard limits</h3>");
  $(this).html('');
});