Unclutter VB5

Cleans up forum, mainly by removing additional space and merging elements

As of 2017-06-04. See the latest version.

// ==UserScript==
// @name        Unclutter VB5
// @namespace   RattleSN4K3/EpicGames
// @description Cleans up forum, mainly by removing additional space and merging elements
// @include     *//www.epicgames.com/unrealtournament/forums
// @include     *//www.epicgames.com/unrealtournament/forums/*
// @require     https://code.jquery.com/jquery-1.11.0.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment.js/1.7.2/moment.min.js
// @version     1
// @author      RattleSN4K3
// @grant       none
// @run-at      document-start
// ==/UserScript==

var IsProcessed = false;
var IsStickyHidden = false;

// add events for DOM and page loaded
document.addEventListener ("DOMContentLoaded", DOM_ContentReady);
window.addEventListener ("load", pageFullyLoaded);

function DOM_ContentReady () {
  mainProc();

  // check for mutations due to last/first-page links updated via Javascript
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      UpdatePaginationLinks(mutation.target);
    });    

    // Stop observing if needed:
    //this.disconnect();
  });
  $('.pagenav-container .js-pagenav-last-button, .pagenav-container .js-pagenav-first-button').each(function() {
    observer.observe(this, {childList: true, subtree: true, attributes: true, attributeFilter: ['href']});
  });
}

function pageFullyLoaded () {
  momentProc();
  
  window.setTimeout(checkAjaxInNewWindow, 500);

  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      dynamicProc(mutation.target, true);
      momentProc(mutation.target);
    });

    // Stop observing if needed:
    //this.disconnect();
  });
  
  $('#topic-tab').each(function() {
    observer.observe(this, {childList: true});
  });
  $('#thread-view-tab').each(function() {
    observer.observe(this, {childList: true, subtree: true});
  });
  
  // observer for dialog
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      mutation.addedNodes.forEach(function(addnode) {
        if ($(addnode).hasClass('ui-dialog-content') == true) {
          momentProc(addnode);
        }
      });
    });

    // Stop observing if needed:
    //this.disconnect();
  });

  // start observing body for dynamically added dialogs
  $('body').each(function() {
    observer.observe(this, {childList: true});
  });
}

function checkAjaxInNewWindow() {
  var hasajaxcommand = false;
  var urlparms = window.location.search.substr(1).split("&");
  $.each(urlparms, function(index, item) {
    if (item.startsWith("quote-") == true) {
      hasajaxcommand = true;
      var selector = '#'+item; //+' .b-post-control__label';
      $(selector).trigger('click');
    }
    else if (item.startsWith("edit-") == true) {
      hasajaxcommand = true;
      var selector = '#'+item;
      $(selector).trigger('click');
      document.getElementById(selector).scrollIntoView();
    }
    
    if (index == urlparms.length - 1) {
      if (hasajaxcommand == true) {
        history.replaceState( {} , '', GetThreadURL() );
      }
    }
  });
}

// hide logo and sub-forums manually with pure-Javascript and CSS before pages loads
(document.head || document.documentElement).insertAdjacentHTML('beforeend',
    `<style>
        #header { display: none!important; }
        .subforum-list { display: none!important; }
        div.votes-count { display: none!important; }

		.b-module.default-widget.page-title-widget { min-height: 0px; margin-bottom: 0px; }
		.b-module.default-widget.page-title-widget .widget-header .module-title { display: none!important; }

		.button-cloak { color: inherit!important; }

		.scrolltofixed-floating .floating-control { display: none!important; }
		.scrolltofixed-floating.scrolltofixed-top.scrolltofixed .floating-control { display: inline-block!important; }
		.scrolltofixed-floating.scrolltofixed-top .floating-control-sticky { display: inline-block!important; }

		.videocontainer .restrain { width: 640px!important; }
		.videocontainer .restrain { margin-right: 10px!important; }

    </style>`);

  
function dynamicProc(root, ajaxloaded) {
  ajaxloaded = ajaxloaded || false;
  
  if (root === null) {
    root = $(document);
  }

  if (ajaxloaded) {
    if (!IsStickyHidden) {
      IsStickyHidden = true;
      $('head').prepend('<style id="stickyhidden">.sticky-list { display: none!important; }');
    }

    // hide sticky topics and add notice to re-show them
    if (!$('.sticky-show').length && $('.sticky-list', root).length) {
      var showdiv = $('<div class="conversation-status-message notice stick"><span></span></div>');
      showdiv.addClass('sticky-show');
      var showlink = $('<a href="#" />');
      showlink.text('Sticky topics hidden (Click to show)');
      showlink.click(function(event) {
        event.preventDefault();
        IsStickyHidden = false;
        $('#stickyhidden').remove();
        $('.sticky-show').remove();
      });
      $('span', showdiv).html(showlink);
      $('.conversation-toolbar-wrapper').append(showdiv);
    }
  }

  // sub-forum list into main forum entry
  $('.subforum-list', root).each(function(index_sub, subforum){
    var mainforum = $(subforum).prev('tr');
    var subforumul = $('<ul style="margin-top: 10px;" />');
    $('tr td div', subforum).each(function(index_itm, subitm){
      $(subitm).css('width', '-moz-fit-content');
      subforumul.append($('<li style="display: inline; float: left; margin-left: 10px;" />').append(subitm));
    });
    $('.forum-info', mainforum).append(subforumul);
    $(subforum).remove();
  });
  
  $('li.b-post-control.js-post-control__edit', root).each(function(index_edit, editli) {
    // prevent adding additional controls link twice
    if ($(editli).data('unclutter') == true) return;
    $(editli).data('unclutter', true);

    // add link to edit 'buttons'
    var edita = $('<a class="editlink" nohref style="color: inherit!important;" href="'+GetThreadURL()+'?'+editli.id+'" onclick="return false;" />');
    $(editli).wrapInner(edita);
  });
  
  $('li.b-post-control__quote', root).each(function(index_quote, quoteli) {
    // prevent adding additional controls link twice
    if ($(quoteli).data('unclutter') == true) return;
    $(quoteli).data('unclutter', true);
    
    // adding multi-quote link
    var multiquoteli = $(quoteli).clone();
    $(multiquoteli).off();
    $('span', $(multiquoteli)).off();
    $(multiquoteli).removeClass('js-post-control__quote');
    $(multiquoteli).removeClass('b-post-control__quote');
    
    multiquoteli.attr('id', "multi" + multiquoteli.attr('id'));
    $('.b-post-control__label', multiquoteli).html("Multi Quote");
    $(multiquoteli).click(function() {
      var editordivs = [];
      $('.js-editor').each(function(index, elem) {
        var editordiv = $(elem).parents('.b-content-entry');
        editordivs.push(editordiv);

        editordiv.removeClass('b-content-entry');
        editordiv.removeClass('js-content-entry');
      });

      $(quoteli).trigger('click');
      
      $.each(editordivs, function( index, div ) {
        $(div).addClass('b-content-entry');
        $(div).addClass('js-content-entry');
      });
    });
    
    $(quoteli).after(multiquoteli);
    
    
    // add link to quote 'buttons'
    var quotea = $('<a class="quotelink" nohref style="color: inherit!important;" href="'+GetThreadURL()+'?'+quoteli.id+'" onclick="return false;" />');
    $(quoteli).wrapInner(quotea);
  });

  // thread likes remove and add to previous element
  $('.votes-count', root).each((i, elem) => {
    $(elem).prev('div').attr('title', $(elem).text());
    elem.remove();
  });
}

function mainProc()
{
  if (typeof jQuery == 'undefined') {
    return;
  }

  // mutex
  if (IsProcessed) return;
  IsProcessed = true;

  console.log("Unclutter VB5 loaded...");

  var topbar = $('#channel-tabbar');
  if (topbar === undefined || topbar === null || !topbar.length) {
    console.error("Unclutter VB5: no top bar");
    return;
  }

  var subbar = $('#channel-subtabbar');
  if (subbar === undefined || subbar === null || !subbar.length) {
    console.error("Unclutter VB5: No sub bar");
    return;
  }
  
  var mainbar = $('#main-navbar');
  var bread = $('#breadcrumbs');

  // Move main-bar search into channel bar
  $('#header > .toolbar > ul > li.search-container').css('margin', '0px');
  $('#header > .toolbar > ul').detach().appendTo(topbar);


  $('ul.secondary-nav', mainbar).addClass('channel-subtabbar-list');
  $('ul.secondary-nav', mainbar).addClass('js-channel-subtabbar-list');
  $('ul.secondary-nav', mainbar).detach().appendTo(subbar);


  // add profile link
  $('.notifications-container').before('<li class="welcomelink">Welcome, <a href="'+window.profileUrl+'">'+window.userName+'</a></li>');

  // add profile options
  $('.notifications-container').after('<li class="logoutlink"><a href="'+window.logoutUrl+'" onclick="return log_out(\'Are you sure you want to log out?\')">Log Out</a></li>');
  $('.notifications-container').after('<li><a href="'+window.settingUrl+'">Settings</a></li>');
  //$('#notifications').before('<li><a href="'+window.PROFILEURL+'">My Profile</a></li>');

  // add register/login for guests
  if (window.pageData === undefined || window.pageData.userid === undefined || window.pageData.userid == '0') {
    var nouserlist = $('<ul></ul>')
    .addClass('nouser')
    .addClass('h-right')
    .addClass('channel-subtabbar-list')
    .addClass('js-channel-subtabbar-list');

    if (window.signInLinks !== undefined && window.signInLinks.length > 0) {
      var registerurl = window.signInLinks[0].href;
      nouserlist.append('<li><a href="'+registerurl+'" rel="nofollow" class="notreg">Register</a></li>');
      nouserlist.append('<li>or</li>');
    }
    nouserlist.append('<li><a href="'+window.loginUrl+'" rel="nofollow" class="notreg">Sign in</a></li>');
    nouserlist.appendTo(subbar);
  }

  dynamicProc();

  // move post options ("see more", "goto post") into post controls bar
  $('li.b-post').each(function(index_post, post) {
    var postlinks = $('.post-links', post);
    var postli = $('<li class="h-margin-top-m" />');
        //$('ul.h-left', post)
    //postlinks.detach().appendTo($('<li class="h-margin-top-m" />').append($('ul.h-left', post))) ;
    postlinks.detach().appendTo(postli);
    $('ul.h-left', post).append(postli);

    //postlinks.detach().appendTo(.remove();
  });


  var threadbar = $('#thread-view-tab .conversation-toolbar');
  if (threadbar !== null && threadbar.length) {

    // check or create toolset
    var toolset = null;
    var toolsetcheck = $('ul.toolset-left', threadbar);
    if (toolsetcheck.length) {
      toolset = toolsetcheck.first();
    } else if ($('ul.toolset-right', threadbar).length) {
      var toolsetleft = $('ul.toolset-right', threadbar).clone().empty();
      toolsetleft.removeClass('toolset-right');
      toolsetleft.addClass('toolset-left');
      toolsetleft.appendTo(threadbar);
      toolset = toolsetleft;
    } else {
      console.info("Unclutter VB5: What to do with toolset?");
    }

    if (toolset !== null) {

      // move subscribe button in thread toolset section
      $('.conversation-controls button').each(function(){
        var libutton = $('<li></li>').append($(this));
        toolset.append(libutton);
      });


      // add thread into thread bar
      var threadli = $('<li></li>');
      //threadli.addClass('ui-widget-header');
      var threaddiv = GetTitleDiv();// $('div.module-title').last();
      threaddiv.removeClass('module-title');
      var threada = $('<a href="'+GetThreadURL()+'"/>');
      threada.css('color', 'inherit');
      threada[0].style.setProperty('background-color', 'inherit', 'important');
      $('h1', threaddiv).wrapInner(threada);
      threadli.append(threaddiv);

      toolset.append(threadli);
      /*var threadli = $('<li></li>')
      var threadtitle = $('.module-title h1.main-title').last().text();
      //var threadlink = $('<a />')
      //threadlink.text(threadtitle);
      //threadlink.attr('href', GetThreadURL());
      //threadlink.addClass('linkcontainer');
      var threadlink = $('<a href="'+GetThreadURL()+'"/>');
      threadlink.text(threadtitle);
      threadlink.css('color', 'inherit');
      threadlink.css('background-color', 'inherit !important');
      $('.module-title h1.main-title').last().wrapInner(threadlink);

      threadli.append(threadlink);
      toolset.append(threadli);
      $('.module-title h1.main-title').last().remove();
      */

      // add 'goto to top/bottom'
      var gotobuttons = CreateGotoButtons();
      $('.toolset-right', threadbar).prepend(gotobuttons);
    }
  }

  var topicbar = $('#topic-tab .conversation-toolbar');
  if (topicbar !== null && topicbar.length) {

    // move subscribe button in topic toolset section
    $('.channel-controls button').each(function(){
      var libutton = $('<li></li>').append($(this));
      $('ul', topicbar).first().append(libutton);
    });

    // move rss icon into breadcrumbs
    var rssa = $('div.module-title a');
    //$('<li class="vb-icon separator"></li>').appendTo(bread);
    //$('<li class="crumb ellipsis"></li>').append(bread);
    $('<li class="separator" />').appendTo(bread);
    rssa.appendTo(bread);


    // move title into topic bar
    var topiccontrols = $('ul', topicbar).first();
    if (topiccontrols.length) {
      var titleli = $('<li></li>');
      //titleli.addClass('ui-widget-header');
      var titlediv = $('div.module-title').last();
      titlediv.removeClass('module-title');
      var titlea = $('<a href="'+GetTopicURL()+'"/>');
      titlea.css('color', 'inherit');
      titlea[0].style.setProperty('background-color', 'inherit', 'important');
      $('h1', titlediv).wrapInner(titlea);
      titleli.append(titlediv);

      topiccontrols.append(titleli);
    } else {
      // remove remaining title
      $('div.module-title').last().remove();
    }
    
    // add 'goto to top/bottom'
    var gotobuttons = CreateGotoButtons();
    $('.toolset-right', topicbar).prepend(gotobuttons);
  }

  // move page title into breadcrumbs if not in thread/topic view
  var pagetitle = GetTitleDiv();
  if (pagetitle !== null && pagetitle.length) {
    pagetitle = pagetitle.first();

    if (!bread.is(':empty')) {
      $('<li class="vb-icon separator"></li>').appendTo(bread);
      $('<li class="crumb ellipsis"></li>').append($('h1', pagetitle).html()).appendTo(bread);

      pagetitle.remove();
    } else {
      $('.widget-content .b-button.b-button--secondary').after($('h1', pagetitle));
      pagetitle.remove();
    }
  }
  

  // adding last/first page to page controls
  $('.pagenav-controls form').each(function(index_pagenav, pagenavform) {
    var arrows = $('.horizontal-arrows', $(pagenavform));

    var firstpagea = $('a[rel="prev"]', arrows).clone();
    firstpagea.removeClass('arrow'); // prevent javascript code being added
    firstpagea.addClass('pagebound');
    firstpagea.addClass('unclutter-first-page');
    firstpagea.attr('title', 'First Page');
    firstpagea.attr('href', GetFirstPageURL(1));
    firstpagea.attr('data-page', '1');
    $('.vb-icon-arrow-left', firstpagea).clone().appendTo(firstpagea);

    var lastpagea = $('a[rel="next"]', arrows).clone();
    lastpagea.removeClass('arrow'); // prevent javascript code being added
    lastpagea.addClass('pagebound');
    lastpagea.addClass('unclutter-last-page');
    lastpagea.attr('title', 'Last Page');
    var lastpagenum = $('.pagetotal', $(pagenavform)).text();
    lastpagea.attr('href', GetLastPageURL(lastpagenum));
    lastpagea.attr('data-page', lastpagenum);
    $('.vb-icon-arrow-right', lastpagea).clone().appendTo(lastpagea);

    //setTimeout(function(){
    //firstpagea.addClass('arrow');
    //lastpagea.addClass('arrow');
    //firstpagea.off('click');
    //firstpagea.unbind('click');
    //}, 50);

    firstpagea.add(lastpagea).click(function(event) {
      var form = this.form || $(this).closest("form").get(0);

      // abort href handling for ajax buttons
      var element = $('.js-pagenum', form);
      var events = $(element).data('events');
      if (events && events['click'] != undefined) {
        event.preventDefault();
      }

      $(this).addClass('arrow');

      // easiest solution by manipulating page num box and trigger keypress
      var num22 = $(this).attr('data-page');
      $('.js-pagenum', form).val(num22);

      var e = jQuery.Event('keypress');
      e.which = 13; // # Some key code value
      e.keyCode  = 13;
      $('.js-pagenum', form).trigger(e);

      $(this).removeClass('arrow');

      setTimeout(function(){
        var currentpage = $('.js-pagenum', $(pagenavform)).val();
        var lastpage = $('.pagetotal', $(pagenavform)).text();
        $('.pagebound', pagenavform).each(function(){
          var reltag = $(this).attr('rel');
          if (reltag == 'next') {
            if (currentpage == lastpage && $(this).hasClass('h-disabled') == false)
              $(this).addClass('h-disabled');
            else if (currentpage != lastpage && $(this).hasClass('h-disabled') == true)
              $(this).removeClass('h-disabled');

          } else if (reltag == 'prev') {
            if (currentpage == 1 && $(this).hasClass('h-disabled') == false)
              $(this).addClass('h-disabled');
            else if (currentpage != 1 && $(this).hasClass('h-disabled') == true)
              $(this).removeClass('h-disabled');
          }

        });
      }, 50);
    });

    arrows.prepend(firstpagea);
    arrows.prepend(lastpagea);
  });

  // increase editor height
  /* TODO: NOT WORKING
  $('.cke_inner .cke_contents').each(function(index, editor) {
    console.log("editor too small");
    if ($(editor).height() < 100) {
      $(editor).css('height', $(editor).height() + 'px');
    }
  });
  */


  // remove margin from thread bar
  $('.widget-content').css('margin-top', '0px');

  // remove header
  $('#header').remove();

  // fixing line wrapping of links in sub-bar
  $('li > a', subbar).css('display', 'inline');

  // fixing widget offset
  $('.b-module.canvas-widget').css('min-height', '0px');
  //$('.b-module.canvas-widget').css('margin-bottom', '0px');
  $('.b-module.canvas-widget').each(function(index, widget){
    if (!$(widget).is(':visible') || $(widget).is(':hidden')) {
      $(widget).css('margin-bottom', '0px');
    } else {
      $(widget).css('margin-bottom', 'auto');
    }
  });
}

function momentProc(root) {

  if (root === null) {
    root = $(document);
  }

  $('time', root)
  .add('div.post-date:not(:has("*"))', root)
  .add('span.post-date:not(:has("*"))', root)
  .add('span.date:not(:has("*"))', root)
  .add('span.time:not(:has("*"))', root)
  .each(function(i, elem) {
    var d = moment($(elem).html(), 'MM/DD/YYYY, hh:mm a Z', true);
    if (d.isValid && $(elem).html().indexOf(',') >= 0) { // trying to prevent converting multiple times
      var originaltext = $(elem).html();
      originaltext += " (" + d.utc().fromNow() + ")";
      $(elem).attr('title', originaltext);
      $(elem).html(d.local().format("YYYY-MM-DD HH:mm"));
    }
  });

  $('.profile-info-item:contains("Last Activity")', root).each(function(i, elem) {
    var d = moment($(elem).html(), '[Last Activity:] MM/DD/YYYY, hh:mm a Z');
    $(elem).html(d.local().format("[Last Activity:] YYYY-MM-DD HH:mm"));
  });

  $('.profile-info-item:contains("Joined")', root).each(function(i, elem) {
    var d = moment($(elem).html(), '[Joined:] MM/DD/YYYY Z');
    $(elem).html(d.local().format("[Joined:] YYYY-MM-DD"));
  });
}

function UpdatePaginationLinks(root) {
  var parent = $(document);
  if (root !== null) {
    parent = root.parent;
  }

  var newlink = $(root).attr('href');
  if ($(root).hasClass('js-pagenav-last-button') == true) {
    $('a.unclutter-last-page', parent).attr('href', newlink);
  }
  
  if ($(root).hasClass('js-pagenav-first-button') == true) {
    $('a.unclutter-first-page', parent).attr('href', newlink);
  }
}

function GetTitleDiv() {
  var returndiv = null;
  $('.b-module.page-title-widget').each(function(index, div) {
    if (!$(div).hasClass('announcement-widget')) {
      if (returndiv === null) returndiv = $('.module-title', $(div));
    }
  });

  return returndiv;
}

function GetTopicURL() {
  var formref = $('.pagenav-controls > form');
  return formref && formref.first().attr('action');
}

function GetThreadURL() {
  var formref = $('.pagenav-controls > form');
  return formref && formref.first().attr('action');
}

function CreateGotoButtons() {
  var gotoli = $('<li />')
  var gotobuttons = $('<div class="h-clearfix js-button" />')
  .append('<a class="floating-control-sticky button-cloak" href="' + location.href + '#footer"><div class="label h-left">Bottom</div><div class="arrow vb-icon-wrapper h-left"><span class="vb-icon vb-icon-triangle-down-wide"></span></div></a>')
  .append('<a class="floating-control button-cloak h-margin-left-m" href="' + location.href + '#topic-module-top"><div class="label h-left">Top</div><div class="arrow vb-icon-wrapper h-left"><span class="vb-icon vb-icon-triangle-up-wide"></span></div></a>')
  gotoli.append(gotobuttons);
  return gotoli;
}

function GetFirstPageURL(pagenum) {
  if (pagenum === null) pagenum = 1;
  
  var pagelink = $('.pagenav-container a.js-pagenav-first-button').each(function(){
    var pagelink = $(this);
    if (pagelink.length && pagelink.attr('href') && pagelink.attr('href') != window.pageData.baseurl) {
      return pagelink.attr('href');
    }
  });
    
  return vBulletin.makePaginatedUrl(location.href, pagenum);
}

function GetLastPageURL(pagenum) {
  if (pagenum === null) pagenum = 99;
  
  var pagelink = $('.pagenav-container a.js-pagenav-last-button').each(function(){
    var pagelink = $(this);
    if (pagelink.length && pagelink.attr('href') && pagelink.attr('href') != window.pageData.baseurl) {
      return pagelink.attr('href');
    }
  });
                                                                       
  return vBulletin.makePaginatedUrl(location.href, pagenum);
}