Reddit Enhancer

Extends Reddit and adds some useful Functions. (Requires frisch's UserScript Extender)

As of 2017-01-17. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Reddit Enhancer
// @namespace   http://null.frisch-live.de/
// @version     1.681
// @description Extends Reddit and adds some useful Functions. (Requires frisch's UserScript Extender)
// @author      frisch
// @include     http://*.reddit.com/*
// @include     https://*.reddit.com/*
// @exclude     https://*.reddit.com/message/*
// @exclude     http://*.reddit.com/message/*
// @exclude     https://*.reddit.com/search?*
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_openInTab
// ==/UserScript==
console.log("Initializing Reddit Enhancer...");

var settings = {};

var enumKeys = {
    BACKSPACE: 8,
    TAB: 9,
    ENTER: 13,
    SHIFT: 16,
    CTRL: 17,
    ALT: 18,
    PAUSE: 19,
    CAPS_LOCK: 20,
    ESCAPE: 27,
    SPACE: 32,
    PAGE_UP: 33,
    PAGE_DOWN: 34,
    END: 35,
    HOME: 36,
    LEFT_ARROW: 37,
    UP_ARROW: 38,
    RIGHT_ARROW: 39,
    DOWN_ARROW: 40,
    INSERT: 45,
    DELETE: 46,
    KEY_0: 48,
    KEY_1: 49,
    KEY_2: 50,
    KEY_3: 51,
    KEY_4: 52,
    KEY_5: 53,
    KEY_6: 54,
    KEY_7: 55,
    KEY_8: 56,
    KEY_9: 57,
    KEY_A: 65,
    KEY_B: 66,
    KEY_C: 67,
    KEY_D: 68,
    KEY_E: 69,
    KEY_F: 70,
    KEY_G: 71,
    KEY_H: 72,
    KEY_I: 73,
    KEY_J: 74,
    KEY_K: 75,
    KEY_L: 76,
    KEY_M: 77,
    KEY_N: 78,
    KEY_O: 79,
    KEY_P: 80,
    KEY_Q: 81,
    KEY_R: 82,
    KEY_S: 83,
    KEY_T: 84,
    KEY_U: 85,
    KEY_V: 86,
    KEY_W: 87,
    KEY_X: 88,
    KEY_Y: 89,
    KEY_Z: 90,
    LEFT_META: 91,
    RIGHT_META: 92,
    SELECT: 93,
    NUMPAD_0: 96,
    NUMPAD_1: 97,
    NUMPAD_2: 98,
    NUMPAD_3: 99,
    NUMPAD_4: 100,
    NUMPAD_5: 101,
    NUMPAD_6: 102,
    NUMPAD_7: 103,
    NUMPAD_8: 104,
    NUMPAD_9: 105,
    MULTIPLY: 106,
    ADD: 107,
    SUBTRACT: 109,
    DECIMAL: 110,
    DIVIDE: 111,
    F1: 112,
    F2: 113,
    F3: 114,
    F4: 115,
    F5: 116,
    F6: 117,
    F7: 118,
    F8: 119,
    F9: 120,
    F10: 121,
    F11: 122,
    F12: 123,
    NUM_LOCK: 144,
    SCROLL_LOCK: 145,
    SEMICOLON: 186,
    EQUALS: 187,
    COMMA: 188,
    DASH: 189,
    PERIOD: 190,
    FORWARD_SLASH: 191,
    GRAVE_ACCENT: 192,
    OPEN_BRACKET: 219,
    BACK_SLASH: 220,
    CLOSE_BRACKET: 221,
    SINGLE_QUOTE: 222
};

// User Script Enhancer Properties and Functions
var jq = document.fExt.jq;
var fExtCreateStyle = document.fExt.createStyle;
var fExtAddSub = document.fExt.ctxMenu.addCtxSub;
var fExtMessage = document.fExt.message;
var fExtAddCtxItem = document.fExt.ctxMenu.addCtxItem;
var fExtAddCtxSeparator = document.fExt.ctxMenu.addSeparator;
var fExtClipboard = document.fExt.clipboard;
var fExtGetSelection = document.fExt.getSelection;
var fExtPopup = document.fExt.popup;
var fExtSetLoading = document.fExt.setLoading;

var typeCheck = Object.prototype.toString;
var numberOfSitesLoaded = 1;
var isHiding = false;

var ctxItemUsrHdr, ctxItemUsrHdrPg, ctxItemUsrUnhdr, ctxItemSbHdr, ctxItemSbUnhdr, ctxItemKeywHdr, ctxItemKeywUnhdr, ctxUndo, ctxRedo, ctxItemCpyHtml, ctxItemSett;
var upVoteLinks, downVoteLinks, rSubRedditLinks, rAuthorLinks, postList;

var loc = location.href;
var isSinglePost = location.href.indexOf("/comments/") > 0 || location.href.indexOf("/submit$") > 0;
var isUserPage = location.href.indexOf("/user/") > 0 || location.href.indexOf("/u/") > 0;
var isUserSubmittedPage = isUserPage && location.href.indexOf("/submitted") > 0;
var isMyMulti = location.href.indexOf("/me/m/") > 0;
var isFriendsPage = location.href.indexOf("/r/friends") > 0;
var isSubmitPage = location.href.indexOf("/submit?") > 0;
var isMySavedPage = location.href.indexOf(settings.accountName + "/saved") > 0;
var hiderElements = [];
var headerHeight = jq("#header").outerHeight(true);
var hideLinks = [];
var userHideLinks = [];
var subHideLinks = [];
var duplicateLinks = [];
var keywordsHideLinks = [];
var rTitleLinks;
var rEnhSub = fExtAddSub("Reddit Enhancer", undefined);
var pstItems = [];
pstItems.onlyVisible = function(){
    return this.filter(function(item){
        return jq("#" + item).is(":visible");
    });
};
pstItems.Next = function(steps){
    return GetFromArray(pstItems.onlyVisible(), pstItems.selected, steps ? steps : 1);
};
pstItems.Previous = function(steps){
    return GetFromArray(pstItems.onlyVisible(), pstItems.selected, steps ? steps * -1 : -1);
};

function GetFromArray(array, item, steps) {
    if(array.length === 0 || jq.inArray(item, array) < 0)
        return undefined;

    var ind = 0;
    if(item)
        ind = array.indexOf(item);

    if(ind < 0)
        ind = 0;

    var stepIndex = (steps < 0 ? steps * -1 : steps);
    var modifier = (steps < 0 ? -1 : 1);

    ind += steps;
    while(ind >= array.length)
        ind -= array.length;

    if(ind < 0)
        ind = array.length - 1;

    return array[ind] === item ? undefined : array[ind];
}

var reloadCancelled = true;
var resourcesLinks = [];
var subColors = {};
var settingTranslation = [
    { key: "accountName", translation: "Your Profilename", hint: "Required to determine some pages (e.g. your saved pages)" },

    { key: "markNSFW", translation: "Mark NSFW posts", hint: "Highlights NSFW posts" },
    { key: "replaceTopNew", translation: "Replace Top with New", hint: "Instead of landing on the Top-Page of a reddit, you'll be redirected to the New-Page instead" },
    { key: "sortByVotes", translation: "Sort by Votes", hint: "Sorts the posts by votes" },
    { key: "autoExpandSelectedPost", translation: "Autoexpand Posts", hint: "Causes selected Posts to automatically collapse/expand" },
    { key: "removeDuplicates", translation: "Remove Duplicates", hint: "Removes duplicate posts by checking the link/source of a post" },
    { key: "maximumNumberOfSitesToLoad", translation: "Autoload # of Pages", hint: "Let's you load more than one page at once. Currently buggy and will be worked on." },

    { key: "animationItemTimeout", translation: "Animation Speed", hint: "" },
    { key: "hideItemTimeout", translation: "Posthiding Timeout", hint: "" },
    { key: "imagePreloadingTimeout", translation: "Preloading timeout", hint: "" },

    { key: "hideUsers", translation: "Hide Users", hint: "If true, users in the 'Users to hide'-list will be hidden" },
    { key: "hideUsersNSFWonly", translation: "Hide Users NSFW posts only", hint: "Only hide NSFW posts by the users" },
    { key: "usersToHide", translation: "Users to hide", hint: "" },

    { key: "hideSubs", translation: "Hide Subs", hint: "If true, subsreddits in the 'Subs to hide'-list will be hidden" },
    { key: "subsToHide", translation: "Subs to hide", hint: "" },

    { key: "hideKeywords", translation: "Hide Keywords", hint: "If true, posts containing any keyword fromt he 'Keywords to hide'-list will be hidden" },
    { key: "keywordsToHide", translation: "Keywords to hide", hint: "" },

    { key: "panelView", translation: "Panelview", hint: "Not yet implemented" },

    { key: "shortcuts", translation: "Keyboard Shortcuts", hint: "" },
    { key: "voteUp", translation: "Upvote selected Post", hint: "" },
    { key: "previousPost", translation: "Select previous Post", hint: "" },
    { key: "voteDown", translation: "Downvote selected Post", hint: "" },
    { key: "toggleSelected", translation: "Expand/Collapse selected Post", hint: "" },
    { key: "hideSelected", translation: "Hide selected Post", hint: "" },
    { key: "nextPost", translation: "Select next Post", hint: "" },
    { key: "hideAll", translation: "Hide all Posts", hint: "Also reloads the page after hiding all posts (can be cancelled)" },

    { key: "previousImage", translation: "Display previous Image", hint: "Used for RES-Albums" },
    { key: "nextImage", translation: "Display next Image", hint: "Used for RES-Albums" },

    { key: "zoomIn", translation: "Zoom in", hint: "Used for Images" },
    { key: "zoomOut", translation: "Zoom out", hint: "Used for Images" },
    { key: "rotateLeft", translation: "Rotate left", hint: "Used for Images" },
    { key: "rotateRight", translation: "Rotate right", hint: "Used for Images" }
];

// Images
var imgBooks = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTMyQTVEQUUyQTIyMTFFMTlGMThGNDk3QjgyMTJCNjUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTMyQTVEQUYyQTIyMTFFMTlGMThGNDk3QjgyMTJCNjUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFMzJBNURBQzJBMjIxMUUxOUYxOEY0OTdCODIxMkI2NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFMzJBNURBRDJBMjIxMUUxOUYxOEY0OTdCODIxMkI2NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgQa4kwAAAGQSURBVHjazFaBjYMwDIQXAzACI4BYoN2AblA2oBO0neD5CWADukG7QAUbPCNkA95+HZKVJyGlIL0lK62d+OLLEfCHYfDYfN/3pixN0x0NFbl6Pp+J96KN9QPTBAIIafgkPxryEY8E3rsAfhiKZDR8W0AuyLeunQUTu2SadgjxbnPyEPkY+VjkOX4EtbdZIBS5j0XJrrTwonXRiLVX8pLiHMsQ8106qgDSkR9G7ie66NBlBPpCw/lW+HvQgcZCewJRmFyQn/UuUCQTwJ6FGaPqzjTZQ6EIMcUbwOJWxHV6CyjVLgZYMRH7QvFGioRAHhpVmZPqLFaDjvH3iekVVDWiS/fnSDcIQ9L1e4ZQYjsH8kpH0hoCUBBPaJiTC9UtBootuR5yVouoczQWRiKUuQkQn9seir1PURqsBKRfRd5WQO0q8nawWXmvKYb/B1RuUD//A0TyPCHxEA9cv3ADvC6hmvUY8Oe+gsQrYHAEueFmV05fQQuNb/XyXTHUDlSVb6uOiuR4lfcTG2CQzrb+R4ABALaxlqMMGvrUAAAAAElFTkSuQmCC";
var imgDuplicate = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RDQxOEI0NzM3NDlBMTFFMTk1RDhCRjg3NzZFMkRGMjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RDQxOEI0NzQ3NDlBMTFFMTk1RDhCRjg3NzZFMkRGMjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpENDE4QjQ3MTc0OUExMUUxOTVEOEJGODc3NkUyREYyMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpENDE4QjQ3Mjc0OUExMUUxOTVEOEJGODc3NkUyREYyMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ph2iTEwAAAGJSURBVHjatFbBcYMwEBSEAsiXV0ogwydPpwPTgd0BVMCkAruDuAOnA7sBZsgvT//40oFzx6wyZwfQacA7cyMBkvZuTzoRGEKWZWtqdmQvZhp7so+6rjvjiQAkR485Ddm7L1mESOwC7O2XeQAiIdcoyRLSsnRX7tDHYIJktrSRYqJKWnKowNiU7ETPN2ThFAMNjjXSCkcsLFmsIpJwbRL6fr57dUMWOiZ3MzdbT+YVkRJDjqUU1S5akoUUeBb5XVHDVpEVoXkQkLO9z/Z2grxPsbVXkC9nIs4xfTOL5AgkJyTeRvDvgC8hHZ+zHNWgpPZAFg/VujmRHMWB3kKmDdl5ESIhV08Asli8K2cT3ZEc0F5ETWQJm9EcoUpr8LcgIvmEdWMkluiCfqUk4y18wIIlCNlex0isdCWS2ifX7vuJO4o934jEG83VHqIq5yIyF3K0hbjkGufPiSL5V0vg+z8hb2fNgfXNYX9hYmxl1wh8vJqB/Mk1om3bnyRJvqn7NlRaFGpsWfJfAQYApiSqoZCrNjYAAAAASUVORK5CYII=";
var imgDelCat = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG5SURBVEiJ3ZaxbhRBDIY/r45yOtImokVKc+N5gm0okiIFxUVpUiHlEWgipeENkNIkDQI6CiK60zZ0Z6p7ACQKmvAGic4UzMBmdTmSvRUI/mZ31rP+x/5tayTG+MDdD6qqOnJ3ZQCIiC0Wi5ci8srMrgBG7n4gImfuPgQHAO6uLZ/nAFVVVUeDMXTQ9l0Nla5laPselRczkyFJVPWGFtWQzlfh/yMaLftY8lt06+b7NqzS+e9G1D3ZEBX5T2n0GXgLfDCzj63vx8CzslgnoivgRQjhsZk975BgZichhK2ylhjjjdP3QUpp4u57QBk5JiLvZrPZm7JnaeruClV9yI/pvNMxPXL3p6q6Dxya2bc+Gn0NIWw1TXPdIvkEbAIbec8l8CXbzoHdPhGdNk1znVKatEjqTDTNe+pMNAV2UkqT3n2UNSETbJrZXFXrvH+uqtvZhrvvraNREX4DmKpqbWZzgEwy5Vcq9c9NbxExgBjjk3v+a/l5CdQlXaq6nSOrsw3AZDweH4rI2T0Ijs3sJPfOa35fDFFE9qXHdetneavqe1aXdwQuzGxX1rlmrWjYggtyw65FVHCXEfQdGszctvjNerkAAAAASUVORK5CYII=";
var imgAbort = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowNDZCQkYwMzM2Q0ExMUUyQjY5RUUwNzIwODVEMzBFMyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowNDZCQkYwNDM2Q0ExMUUyQjY5RUUwNzIwODVEMzBFMyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjA0NkJCRjAxMzZDQTExRTJCNjlFRTA3MjA4NUQzMEUzIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjA0NkJCRjAyMzZDQTExRTJCNjlFRTA3MjA4NUQzMEUzIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Mya9PgAAAUlJREFUeNpi/P//PwM9ACPdLWJkZMSqwNjYWBJIxQKxOxAbALEQVOodEF8A4p1AvPjs2bPPsemHm4/LIqAFfECqGYgzgZiVgIN/A/F0IK4FWviJaIuAlmgBqc1ArERiCN0DYl+gZdcIWgS15CAQi5AZHW+A2B5mGVaLoMF1ngyfYPOZISgYYeYzoSlopoIlDFAzmrGmOhMTE1DqekhExBMLQAlE/syZM8/RfRRLRUsYoGbFwjjIFrnTIJ/CzWRBEjTAolABiD+SYPB7NL4BNouEsGj8CEw5H4i1BZhq0YWEsAUdTQGyRe9oYP47bEEHKiCd0MMcS3CQAi5g89FOGvhoJzaLFkMzGQMVM+xiDIug9cl0Klo0HbmOQk91tdACkVJwD2oW7hqWLtUELSs+rBkWqtAQiCcRmUB+Q9UaIlsysI2TYdOuAwgwAC/sonL067x5AAAAAElFTkSuQmCC";
var imgDelUser = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAWhJREFUeNrEVottgzAQxVEGcDdwN4jEAA0bZAPMBFEmiDJB1AkKE2QEvAAqGzQjMELP1SOyELbPMlKfdLL4nN/5Pg9EEUBZlkdazmR2lWQTmSH7HIbBFAkQARJNy1fAtyGyNouISA60fDP8K+7Jdp77Z2agNfdEPqIT0/+USySZ/jKXaHP8O9HI9B9zibjDaHKJOqZ/t4UyPCLta2hYqy2a4QJtW4O932yidRG9e+kchLeG8CqyJ2rXufIUa28VCcQG0ZNp512F6x7Pg6I6R6k9aXsnu3ueL9HSyRqxQnBFGny4ITV9QomqPQgkasERyRbBpKAWILHRHRgOE6XhjXx+nJqMnu6Uzp5Pe6IHk8SVHLdJLmsfP5RhTq/aR+qxxCtCh+xOm06RT8jfiVIgaVOFZtAL8qAm7lIUGNApGjdroiWqUlQY/xMjuq9gzpERiz8fW68P5PcYE1RMvo4Na1TrOOBq3a8AAwDKrW/Ty1UcbgAAAABJRU5ErkJggg==";
var imgTree = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6REIzNzg4NjJDMjdBMTFFMTgyQTdCNUFCM0MyNUJCMTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6REIzNzg4NjNDMjdBMTFFMTgyQTdCNUFCM0MyNUJCMTYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4REUwQTc2QkMyNzgxMUUxODJBN0I1QUIzQzI1QkIxNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4REUwQTc2Q0MyNzgxMUUxODJBN0I1QUIzQzI1QkIxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqK2+9QAAAEGSURBVHjavJWNDYMgEIWFCewGHcHECRihG+gGdhLdQDdoN6ALkLpBR5AN2iM5EtI09fg5LyFEJH4+jnsnqgLRtu0GU/1ni5VVmaj33osCas4wvfb2iUyIU6JhNGygGIiLnByNCLGUzSJRzQxTh48KlZVVBJAhgPTGmAdBlRWUGoCPnRDiADOuX2F9ov6gpNQAQpoAssRAyEeHEB1A+tgjp+ZIo7LVHVnKBZIRFuMgCtRYTlB2UEEWi1Nzg5SHYbHygCAvLj/+pnUpMEmpaoTdv2BDDEgkWtAYWNBCcZcSpnqBcePsRx5mCTaW3WGfRzQ+f+1Xtsb3o6Vv7CCEvY/yut1a/AgwACqvUiICIj/gAAAAAElFTkSuQmCC";

// Variables - End

// Initialization - Start
GMLoad();

// Initialization - Styles - Start
fExtCreateStyle("#header { z-index: 107; }");
fExtCreateStyle("#siteTable { padding: 0 64px; }");
fExtCreateStyle("body.with-listing-chooser>.content, body.with-listing-chooser .footer-parent { margin-left: 0 !important; }");
fExtCreateStyle("div.listing-chooser { left: 0 !important; }");
fExtCreateStyle("div.side { right: 0; }");
fExtCreateStyle("div.side, div.listing-chooser { z-index: 106 !important; position: fixed !important; height: auto; top: " + headerHeight + "px !important; margin: 0; border: 1px solid black; width: 50px !important; overflow-y: hidden !important; padding: 0 !important; }");
fExtCreateStyle("div.side:hover { width: 500px !important; overflow-y: auto !important; max-height: 100vh; }");
fExtCreateStyle("div.listing-chooser:hover { width: 500px !important; overflow-y: auto !important; max-height: 100vh; }");
fExtCreateStyle("span.domain { font-weight: bold; }");

fExtCreateStyle("div.content { padding-top: 80px  !important; }");
fExtCreateStyle(".tabButton { color: #333333 !important; background-color: rgb(252, 255, 215) !important; border: 1px solid black; }");
fExtCreateStyle(".spacerLi { width: 50px; }");
fExtCreateStyle("#postNavigation { position: fixed !important; top: 27px; width: 500px; margin: 0 auto; display: block; z-index: 108; /*font-size: 12px; font-weight: bold; */ left: 0; right: 0; } ");
fExtCreateStyle("#postNavigation a { background-color: #ffffff;  border: 1px solid black;  padding: 8px 14px;  margin: 0 1px; }");
fExtCreateStyle("#postNavigation a:hover { background-color: #dfdfdf; }");
fExtCreateStyle(".pstSelected, .pstSelected * { z-index: 105; }");

fExtCreateStyle(".midcol { width: 32px !important; }");
fExtCreateStyle(".RES-keyNav-activeElement, .RES-keyNav-activeElement .md-container { background-color: transparent !important; }");
fExtCreateStyle("div.md { border: none !important; }");

if (isSinglePost) {
    fExtCreateStyle(".livePreview { width: 1600px !important; }");
    fExtCreateStyle(".livePreview p, .livePreview ol { width: 1500px !important; }");
    fExtCreateStyle("div.usertext-edit { max-width: 50%; };");
    fExtCreateStyle("div.usertext-edit div.md { width: 100%; max-width: 100%; }");
    fExtCreateStyle("div.usertext-edit div.md textarea { width: 100%; }");
} else {
    fExtCreateStyle(".md { max-width: none !important; }");
    fExtCreateStyle(".madeVisible *,div.expando * { z-index: 105; }");
    fExtCreateStyle("a.madeVisible img, a.madeVisible video { max-height: 100vh !important; max-width: 95vw !important;}");
    fExtCreateStyle("body { margin-bottom: 650px !important; }");
    fExtCreateStyle("img.RESImage { border-bottom: #000 solid 3px !important; padding: 0 !important; }");
    fExtCreateStyle("div.thing { min-height: 80px; padding: 5px 0; margin: 0 0 8px 0; border: 1px solid black; }");
    fExtCreateStyle("div.thing.pstSelected div.entry>div:not(.expando-button) { background-color: #fff !important; padding: 10px 20px; border: 1px solid black; }");
    fExtCreateStyle("div.thing div.entry img, div.thing div.entry video { display: none !important; }");
    fExtCreateStyle("div.thing.pstSelected div.entry img, div.thing.pstSelected div.entry video { display: block !important; }");
    if (settings.markNSFW) {
        fExtCreateStyle(".NsfwPost { background-color: rgb(255, 81, 81) !important; }");
        fExtCreateStyle(".NsfwItem .madeVisible { display: none; }");
        fExtCreateStyle(".pstSelected .NsfwItem .madeVisible { display: initial !important; }");
        fExtCreateStyle(".pstSelected .NsfwItem .madeVisible div { width: auto !important; }");
        fExtCreateStyle(".pstSelected .NsfwItem * { z-index: 104 !important; }");
    }
    fExtCreateStyle(".entry .buttons li { line-height: inherit !important; width: 100px; }");
    fExtCreateStyle(".thingUserSubInfo { padding: 4px 8px; height: 26px; display: block; }");
    fExtCreateStyle(".thingUserSubInfo>* { float: left; clear: none; border: 1px solid black; margin: 0 2px; }");
    fExtCreateStyle(".thingUserSubInfo * { font-size: 12px !important; }");
    fExtCreateStyle(".thingUserSubInfo .nsfw-stamp { display: inline !important; }");
    fExtCreateStyle(".thingUserSubInfo p.tagline { width: 450px; }");
    fExtCreateStyle(".thingUserSubInfo a.subreddit { width: 160px; }");
    fExtCreateStyle("ul.flat-list.buttons { float: left; clear: none; padding: 0; position: absolute; left: 870px; }");
    fExtCreateStyle("ul.flat-list.buttons>li { cursor: pointer; }");
    fExtCreateStyle("ul.flat-list.buttons>li, .thingUserSubInfo p.tagline, .thingUserSubInfo a.subreddit { padding: 6px !important; background-color: #eeeeee !important;  text-align: center; }");
    fExtCreateStyle("ul.flat-list.buttons>li:hover, .thingUserSubInfo p.tagline:hover, .thingUserSubInfo a.subreddit:hover { background-color: #cee3f8 !important; }");
    //fExtCreateStyle("a.subreddit { float: left; background-color: white !important; color: #888; font-weight: bold; padding: 0 4px !important; }");
    // fExtCreateStyle("p.tagline { float: left; position: relative; background-color: white; color: #888; font-weight: bold; padding: 0 1px; }");
    fExtCreateStyle("p.title { color: #000000 !important; float: left; clear: left; margin: 0; font-weight: bold; font-size: 15px !important; }");
    fExtCreateStyle("p.title a { color: #000000 !important; }");
    //fExtCreateStyle("ul.flat-list.buttons { padding-bottom: 40px; width: 800px; margin: 20px auto 0 auto; font-size: larger; text-align: center; left: 0; right: 0; clear: both; }");
    //fExtCreateStyle("ul.flat-list.buttons li { padding: 4px; background-color: #fff; margin: 2px; border: 1px solid #999; }");
    fExtCreateStyle(".usertext * { z-index: 1; }");
    fExtCreateStyle("div.thing div.entry div.expando:not(.expando-uninitialized) { display: block !important; }");
    fExtCreateStyle("div.thing div.entry div.expando span { display: none; }");
}
fExtCreateStyle(".toggleHidden, div.thing.hidden { background: repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(255,255,255,.5) 35px, rgba(255,255,255,.5) 70px); }");

if(settings.hideUsers)
    fExtCreateStyle(".userHidden .author { text-decoration: line-through; }");
if(settings.hideSubs)
    fExtCreateStyle(".subsHidden .subreddit { text-decoration: line-through; }");

fExtCreateStyle(".userHidden a.title, .subsHidden a.title, .duplicateHidden a.title { background-position: 0 !important; background-repeat: no-repeat !important; padding: 8px 8px 8px 26px; }");
fExtCreateStyle(".userHidden a.title { background-image: url('" + imgDelUser + "') !important; }");
fExtCreateStyle(".subsHidden a.title { background-image: url('" + imgDelCat + "') !important; }");
fExtCreateStyle(".duplicateHidden a.title { background-image: url('" + imgDuplicate + "') !important; }");

fExtCreateStyle("ul.tabmenu .disabled { background-color: #dfdfdf !important; }");

jq("body").css("margin-bottom", "0");
// Initialization - Styles - End

// Initialization - Controls - Start
var rPostNavigation = jq('<div id="postNavigation"><a href="#" id="topPost">Top</a><a href="#" id="frPost">&lt;&lt;</a><a href="#" id="prevPost">&lt;</a><a href="#" id="nextPost">&gt;</a><a href="#" id="ffPost">&gt;&gt;</a><a href="#" id="botPost">Bottom</a></div>');
rPostNavigation.appendTo("body");

var rTabMenu = jq("ul.tabmenu");
jq('<li class="spacerLi"></li>').appendTo(rTabMenu);

jq('<li><span class="">All Posts:</span></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" title="Updvotes all displayed posts" id="upvoter">Upvote</a></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" title="Downvotes all displayed posts" id="downvoter">Downvote</a></li>').appendTo(rTabMenu);

jq('<li><a href="#" class="choice tabButton collapsed" title="Expands all posts" id="expander">Expand</a></li>').appendTo(rTabMenu);

jq('<li class="spacerLi"></li>').appendTo(rTabMenu);
jq('<li><span class="">Hide:</span></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="background-color: rgb(255, 138, 138) !important;" title="Hides all displayed posts which prevents them from showing up again" id="hideAll">All (<span>?</span>)</a></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="display: none; background-color: rgb(255, 138, 138) !important;" title="Hides all displayed posts which prevents them from showing up again" id="hideSelected">Selected</a></li>').appendTo(rTabMenu);

jq('<li class="spacerLi"></li>').appendTo(rTabMenu);
jq('<li><span class="">Toggle hidden:</span></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="background-color: rgb(175, 255, 171) !important;" title="Toggles the hidden User posts" id="toggleUserHidden">Users (<span>?</span>)</a></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="background-color: rgb(175, 255, 171) !important;" title="Toggles the hidden Sub posts" id="toggleSubsHidden">Subs (<span>?</span>)</a></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="background-color: rgb(175, 255, 171) !important;" title="Toggles the hidden Keyword posts" id="toggleKeywordsHidden">Keywords (<span>?</span>)</a></li>').appendTo(rTabMenu);
jq('<li><a href="#" class="choice tabButton" style="background-color: rgb(175, 255, 171) !important;" title="Toggles the duplicate posts" id="toggleDuplicateHidden">Duplicates (<span>?</span>)</a></li>').appendTo(rTabMenu);

jq('<li class="spacerLi"></li>').appendTo(rTabMenu);
if (loc.indexOf("show=all") < 0) {
    jq('<li><a href="' + loc.replace('#', '') + '?show=all" title="Show all posts of this sub, including previously hidden posts" class="choice tabButton" style="background-color: rgb(187, 187, 255) !important;" id="showAll">Show All</a></li>').appendTo(rTabMenu);
}

if (isSinglePost)
    jq("#upvoter,#downvoter,#expander,#hideAll,#hideSelected,#toggleUserHidden,#toggleSubsHidden,#toggleKeywordsHidden,#toggleDuplicateHidden").addClass("disabled");

fExtCreateStyle("#rEnhSettings { position: fixed; height: 750px; top: 50px; border: 1px solid black; width: 750px; padding: 12px; z-index: 1000; background-color: #FEFEFE; right: 0; left: 0; margin: 0 auto; }");
fExtCreateStyle("#rEnhSettings table { }");
fExtCreateStyle("#rEnhSettings table td.label, #rEnhSettings table td.value { }");
fExtCreateStyle("#rEnhSettings table td.label { float: left; clear: right; font-size: larger; }");
fExtCreateStyle("#rEnhSettings table td.value { float: right; clear: left; }");
fExtCreateStyle("#rEnhSettings table thead { font-size: large; }");
fExtCreateStyle("#rEnhSettings div.tableContainer { overflow-y: scroll; height: 700px; }");
fExtCreateStyle("#rEnhSettings div.settingsButtons { height: 50px;  }");
fExtCreateStyle("#rEnhSettings * { z-index: 1000; margin: 4px; }");
fExtCreateStyle("#rEnhSettings td.label {  }");
fExtCreateStyle("#rEnhSettings a { padding: 4px; border: 1px solid grey; float: right; }");

// Initialization - Controls - Settings Dialog
var settingsDialog = '<div style="display:none" id="rEnhSettings"><div class="tableContainer"><table cellspacing="0" cellpadding="0" border="0" width="700px"><colgroup><col width="200px" /><col width="auto" /></colgroup>';
settingsDialog += '<thead><tr><th class="label">Setting</th><th class="setting">Value</th></tr></thead>';

function BuildSettings(settingsObject) {
    for (var key in settingsObject) {
        var varType = typeof settingsObject[key];

        if (varType === "object") {
            if (typeCheck.call(settingsObject[key]) === '[object Array]')
                AddSettingToDialog(key, "list");
            else {
                settingsDialog += '<tr><td class="label" colspan="2"><b>' + key + '</b></td></tr>';

                if (key === 'shortcuts') {
                    for (var shortcut in settingsObject[key]) {
                        AddSettingToDialog(shortcut, 'key');
                    }
                } else BuildSettings(settingsObject[key]);
            }
        } else AddSettingToDialog(key, varType);
    }
}

function getTranslationObject(key) {
    for (var i in settingTranslation) {
        if (settingTranslation[i].key === key) {
            return settingTranslation[i];
        }
    }

    return { key: key, translation: key, hint: "" };
}

function AddSettingToDialog(key, varType) {
    var transObject = getTranslationObject(key);
    settingsDialog += '<tr title="' + transObject.hint + '">';
    if (varType === "list") {
        settingsDialog += '<td class="label" colspan="2">' + transObject.translation + '</td>';
        settingsDialog += '</tr>';
        settingsDialog += '<tr title="' + transObject.hint + '">';
        settingsDialog += '<td class="setting" colspan="2"><textarea name="' + key + '" class="value" cols="80" rows="7" title="Separate entries by using comma"></textarea></td>';
    } else {
        settingsDialog += '<td class="label">' + transObject.translation + '</td>';
        if (varType === "key") {
            settingsDialog += '<td><select class="value" name="' + key + '">';
            for (var knownKey in enumKeys) {
                settingsDialog += '<option value="' + enumKeys[knownKey] + '">' + knownKey + '</option>';
            }
            settingsDialog += '</select></td>';
        } else {
            var inputType;
            switch (varType) {
                case "boolean":
                    inputType = "checkbox";
                    break;
                case "number":
                    inputType = "number";
                    break;
                case "string":
                    inputType = "text";
                    break;
                default:
                    console.log("Cannot add setting " + key + " to settings dialog because unkown type " + varType);
                    return;
            }

            settingsDialog += '<td class="setting">' + '<input type="' + inputType + '" class="value" name="' + key + '"></td>';
        }
    }
    settingsDialog += '</tr>';
}

BuildSettings(settings);

settingsDialog += '</table></div>';
settingsDialog += '<div class="settingsButtons"><a href="#" id="renhSettingsClose">Close</a><a href="#" id="renhSettingsSave">Save</a></div>';
settingsDialog += '</div>';
jq("body").append(settingsDialog);
// Initialization - Controls - End

// Moving the navigation buttons
var jqNavButtons = jq("div.nav-buttons");
if (jqNavButtons) {
    if (jqNavButtons.length === 1) {
        GrabContent(jq("body").html());
    } else MainInitialization();
} else MainInitialization();

function MainInitialization() {
    // Removal of sponsor divs
    jq("[class^='sponsor'").remove();

    upVoteLinks = jq(".up:not(.archived)");
    downVoteLinks = jq(".down:not(.archived)");
    rSubRedditLinks = jq("a.subreddit");
    rAuthorLinks = jq("a.author");
    postList = jq("#siteTable");

    // Replacing images on imgur from those faggots who can't link properly
    /*
    var replaceAttr = ["href", "data-href-url", "data-outbound-url"];
    jq("a.thumbnail, a.imgScanned, a.title").each(function(index, item) {
        var jqItem = jq(item);
        for(var attrInd in replaceAttr) {
            var tmpAttr = jqItem.attr(replaceAttr[attrInd]);
            if(tmpAttr === undefined)
                continue;
            tmpAttr = tmpAttr.replace("gallery/", "");
            tmpAttr = tmpAttr.replace("/new","");
            tmpAttr = tmpAttr.replace("https","http");
            tmpAttr = tmpAttr.replace("http://i.","http://");
            jqItem.attr(replaceAttr[attrInd], tmpAttr);
        }
    });
    */

    // Holy crap that new outbound url remapping is annoying af... here let's fix this shit
    jq("a[data-href-url]").each(function() {
        var jqThis = jq(this);
        var currentDataHref = this.getAttribute("data-href-url");
        this.setAttribute("data-outbound-url", currentDataHref);
        this.setAttribute("href", currentDataHref);
    });

    if (settings.replaceTopNew) {
        jq("a.subreddit").each(function(index, item) {
            var hrefLink = item.getAttribute("href");
            if (!hrefLink.match(".*/$")) {
                hrefLink = hrefLink + "/";
            }
            hrefLink = hrefLink + "new";
            item.setAttribute("href", hrefLink);
        });
    }

    jq("span.nsfw-stamp").each(function(index, item) {
        var jqItem = jq(item).parent("li");
        var parent = jqItem.parents("ul.flat-list.buttons");
        if (settings.markNSFW) {
            jqItem.parents("div.entry:first").addClass("NsfwItem");
        }
        jqItem.detach().appendTo(parent);
        jqItem.parents("div.thing").addClass("NsfwPost");
    });

    jq("a.comments.empty").each(function(index, item) {
        Mark(jq(item), "uncommented");
    });

    jq("span.domain a").each(function(index, item) {
        var jqItem = jq(item);
        var parentItem = jqItem.parents("div.entry");
        var txt = jqItem.text();

        if (txt.indexOf("youtu") >= 0)
            Mark(parentItem, "video");
        else if (txt.indexOf("imgur") >= 0 || txt.indexOf("gfycat") >= 0)
            Mark(parentItem, "image");
    });

    // remove Gild and Share button
    jq("li.give-gold-button, li.share").remove();
    jq("li a:contains('promoted')").parent().remove();
    jq("li a:contains('gilded')").parent().remove();
    jq("li a:contains('rising')").parent().remove();
    jq("li a:contains('controversial')").parent().remove();

    // highlight hide/unhide
    var hideSubsFromLocation = false;
    var hideUsersFromLocation = false;

    if (!isMyMulti && !isMySavedPage && !isSinglePost && !isFriendsPage) {
        hideSubsFromLocation = true;
        hideUsersFromLocation = true;
    }

    if (!isSinglePost) {
        if (!isUserPage && settings.sortByVotes) {
            jq("div.deleted").fadeOut();

            var pstThings = postList.children("#siteTable div.thing");
            pstThings.sort(function(a, b) {
                var scoreA = parseInt(jq(a).find("div.score.unvoted").text());
                var scoreB = parseInt(jq(b).find("div.score.unvoted").text());

                if (isNaN(scoreA))
                    scoreA = 0;
                else if (scoreA <= 0)
                    scoreA -= 1;
                if (isNaN(scoreB))
                    scoreB = 0;
                else if (scoreB <= 0)
                    scoreB -= 1;


                if (scoreA > scoreB)
                    return -1;
                else if (scoreA < scoreB)
                    return 1;
                else
                    return 0;
            });

            pstThings.detach();
            pstThings.appendTo(postList);
        }

        postList.children("#siteTable div.thing").each(function(index, item) {
            pstItems.push(item.getAttribute("id"));
        });

        if (pstItems.length > 0) {
            var visiblePosts = pstItems.onlyVisible();
            if(visiblePosts.length > 0){
                pstItems.first = visiblePosts[0];
                pstItems.last = visiblePosts[visiblePosts.length - 1];
            }
        }

        postList.find("a.title").each(function() {
            var text = this.text;
            if (text.length > 160) {
                var spacerInd = text.substring(0, 140).lastIndexOf(" ");
                this.setAttribute("title", "..." + text.substring(spacerInd));
                this.text = text.substring(0, spacerInd) + "... (more)";
            }
        });

        for(var i = 0; i < pstItems.length; i++){
            var jqThing = jq("#" + pstItems[i]);
            var tagline = jqThing.find("p.tagline");
            tagline.detach();
            var subReddit = tagline.find("a.subreddit");
            subReddit.detach();
            var first = jqThing.find("div.entry p:first");
            var jqDiv = jq("<span class='thingUserSubInfo'></span>");
            jqDiv.insertBefore(first);
            var flatlistButtons = jqThing.find("ul.flat-list.buttons");
            flatlistButtons.detach();

            tagline.appendTo(jqDiv);
            subReddit.appendTo(jqDiv);
            flatlistButtons.appendTo(jqDiv);


            if (settings.removeDuplicates) {
                var resource = jqThing.find("a.thumbnail").get(0).href;
                if(resource) {
                    if (resourcesLinks.indexOf(resource) >= 0) {
                        if (!jqThing.hasClass("duplicateHidden"))
                            jqThing.addClass("duplicateHidden");

                        AddToHideLinks(jqThing);
                        duplicateLinks.push(jqThing);
                    } else
                        resourcesLinks.push(resource);
                }
            }
        }

        jq("div.thing.promotedlink").each(function() {
            AddToHideLinks(jq(this));
        });

        if (!isFriendsPage && !isUserSubmittedPage) {
            rAuthorLinks.each(function(index, item) {
                var jqItem = jq(item);
                var userName = jqItem.text().replace("/r/", "");
                var userIndex = jq.inArray(userName, settings.usersToHide);
                var itemThing = jqItem.parents("div.thing");
                if (userIndex >= 0) {
                    var hideThisPost = settings.hideUsers &&
                        !isUserPage &&
                        (settings.hideUsersNSFWonly && jqItem.parents("div.entry").hasClass("NsfwItem") || !settings.hideUsersNSFWonly);

                    if (hideThisPost)
                        AddToHideLinks(itemThing);

                    userHideLinks.push(itemThing);
                    itemThing.addClass("userHidden");
                }
            });
        }

        var hideThisPost = settings.hideKeywords && !isUserPage;
        for (var iKW = 0; iKW < settings.keywordsToHide.length; iKW++) {
            var kwItem = settings.keywordsToHide[iKW];
            if (kwItem) {
                jq("a.title:contains(" + kwItem + ")").each(function(jndex, jtem) {
                    var thing = jq(jtem).parents("div.thing");

                    if (hideThisPost)
                        AddToHideLinks(thing);

                    keywordsHideLinks.push(thing);
                    if (!thing.hasClass("keywordsHidden"))
                        thing.addClass("keywordsHidden");
                });
            }
        }
    }

    if (loc.indexOf("/r/all") >= 0) {
        rSubRedditLinks.each(function(index, item) {
            var subName = item.text.replace("/r/", "");
            var subIndex = jq.inArray(subName, settings.subsToHide);
            var itemThing = jq(item).parents("div.thing");
            if (subIndex >= 0) {
                if (hideSubsFromLocation)
                    AddToHideLinks(itemThing);
                subHideLinks.push(itemThing);
                itemThing.addClass("subsHidden");
            }
        });
    }

    rAuthorLinks.each(function(index, item) {
        var href = item.getAttribute("href");
        if (href.indexOf("/submitted") === -1)
            item.setAttribute("href", href + "/submitted/");
    });

    jq("#toggleSubsHidden span").text(subHideLinks.length);
    jq("#toggleUserHidden span").text(userHideLinks.length);
    jq("#toggleKeywordsHidden span").text(keywordsHideLinks.length);
    jq("#toggleDuplicateHidden span").text(duplicateLinks.length);

    InitializeContextMenu();

    HideAndAction(hideLinks, 0, null, "enhance");

    if (!isSinglePost) {
        if (!isSubmitPage) {
            if (document.hasFocus())
                MakeActivePost(undefined, pstItems.first);
            else {
                focusFirstPost = function() {
                    setTimeout(function() {
                        MakeActivePost(undefined, pstItems.first);
                    }, settings.animationItemTimeout);
                    window.removeEventListener("focus", focusFirstPost, true);
                };
                window.addEventListener("focus", focusFirstPost, true);
            }

        }

        var subs = jq("a.subreddit");
        if (subs.length > 0) {
            subs.each(function(index, item) {
                var jqItem = jq(item);
                var subText = jqItem.text().replace("/r/", "");
                var subColor = subColors[subText];
                var eleParent = jqItem.parent().parent();

                if (!subColor) {
                    subColor = getRandomColor();
                    subColors[subText] = subColor;
                }

                //jqItem.detach().prependTo(eleParent);
                jqItem.text(subText);

                jqItem.parents("div.thing").css("background-color", subColor);
            });
        } else {
            jq("span.domain a").each(function(index, item) {
                var jqItem = jq(item);
                var subText = jqItem.text().replace("/r/", "");
                var subColor = subColors[subText];
                var eleParent = jqItem.parent().parent();

                if (!subColor) {
                    subColor = getRandomColor();
                    subColors[subText] = subColor;
                }

                jqItem.parents("div.thing").css("background-color", subColor);
            });
        }
    }

    // Marks Rating of Posts
    jq("div.thing div.midcol").each(function(index, item) {
        var jqItem = jq(item);
        var pts = jqItem.find("div.score.unvoted").text();
        if (pts > 50) jqItem.css("background-color", "#99FF99");
        else if (pts > 0) jqItem.css("background-color", "#CCFFCC");
        else if (pts < -10) jqItem.css("background-color", "#FF9999");
        else if (pts < 0) jqItem.css("background-color", "#FFCCCC");
    });

    jq(".last-clicked").removeClass("last-clicked");
    jq(".promotedlink").remove();

    if (userHideLinks.length === 0) {
        jq("#toggleUserHidden").attr("style", "background-color: #808080 !important; color: #CFCFCF; border: 1px solid black;");
    } else {
        jq("#toggleUserHidden").click(function(e) {
            ToggleVisibility(e, this, ".userHidden");
            return false;
        });
    }
    if (subHideLinks.length === 0) {
        jq("#toggleSubsHidden").attr("style", "background-color: #808080 !important; color: #CFCFCF; border: 1px solid black;");
    } else {
        jq("#toggleSubsHidden").click(function(e) {
            ToggleVisibility(e, this, ".subsHidden");
            return false;
        });
    }
    if (duplicateLinks.length === 0) {
        jq("#toggleDuplicateHidden").attr("style", "background-color: #808080 !important; color: #CFCFCF; border: 1px solid black;");
    } else {
        jq("#toggleDuplicateHidden").click(function(e) {
            ToggleVisibility(e, this, ".duplicateHidden");
            return false;
        });
    }
    if (keywordsHideLinks.length === 0) {
        jq("#toggleKeywordsHidden").attr("style", "background-color: #808080 !important; color: #CFCFCF; border: 1px solid black;");
    } else {
        jq("#toggleKeywordsHidden").click(function(e) {
            ToggleVisibility(e, this, ".keywordsHidden");
            return false;
        });
    }

    jq("div.nav-buttons").remove();

    InitializeEventHandlers();

    fExtMessage(undefined);
}

// Initialization - End

// Events - Start
function HideAllClick(e) {
    if(e)
        e.preventDefault();

    isHiding = true;

    var siteTable = jq("#siteTable");
    siteTable.css("display", "block");
    postList.css("display", "block");
    siteTable.css("height", siteTable.outerHeight(true) + "px");

    jq("div.thing").each(function(index, thing) {
        var jqThing = jq(thing);
        var offs = jqThing.offset();
        var top = parseInt(offs.top);
        var width = jqThing.width();
        var height = jqThing.height();

        setTimeout(function() {
            jqThing.css({
                top: top,
                left: 0,
                width: width,
                height: height,
                position: "absolute"
            });
        }, settings.hideItemTimeout);
    });


    reloadCancelled = false;
    setTimeout(function() {
        if(pstItems.selected)
            pstItems.jqSelected.animate({ scrollTop: pstItems.jqSelected.offset().top }, 0);

        HideAndAction(hiderElements, 0, null, "reload");
    }, settings.hideItemTimeout);
    jq(this).hide();

    return false;
}

function HideSelectedClick(e) {
    if(e)
        e.preventDefault();

    if(pstItems.selected)
        Hide(jq("#" + pstItems.selected), false, false);

    return false;
}

// Kontext-Menü
jq("#fExtContextMenu").on("fExtContextMenuOpening", function(event, actor) {
    var txt = actor.text();
    var usrIndex = jq.inArray(txt, settings.usersToHide);
    var usrTxt = 'Select a User';
    var subIndex = jq.inArray(txt, settings.subsToHide);
    var subTxt = 'Select a Subreddit';
    var bCanHide = actor.hasClass("author") && usrIndex < 0;
    var bCanUnhide = !bCanHide && actor.hasClass("author") && usrIndex >= 0;

    ctxItemUsrHdr.Toggle(bCanHide);
    ctxItemUsrHdrPg.Toggle(bCanHide);
    ctxItemUsrUnhdr.Toggle(bCanUnhide);
    if (actor.hasClass("author"))
        usrTxt = txt;

    ctxItemUsrHdr.ItemText("Hide User - " + usrTxt);
    ctxItemUsrHdrPg.ItemText("Hide User and open Page - " + usrTxt);
    ctxItemUsrUnhdr.ItemText("Unhide User - " + usrTxt);

    bCanHide = actor.hasClass("subreddit") && subIndex < 0;
    bCanUnhide = !bCanHide && actor.hasClass("subreddit") && subIndex >= 0;

    ctxItemSbHdr.Toggle(bCanHide);
    ctxItemSbUnhdr.Toggle(bCanUnhide);
    if (actor.hasClass("subreddit"))
        subTxt = txt;

    ctxItemSbHdr.ItemText("Hide Sub - " + subTxt);
    ctxItemSbUnhdr.ItemText("Unhide Sub - " + subTxt);

    txt = fExtGetSelection();
    var keywIndex = jq.inArray(txt, settings.keywordsToHide);
    var keywTxt = 'Select word(s)';
    bCanHide = txt !== "" && keywIndex < 0;
    bCanUnhide = !bCanHide && txt !== "" && keywIndex >= 0;

    ctxItemKeywHdr.Toggle(bCanHide);
    ctxItemKeywUnhdr.Toggle(bCanUnhide);
    ctxItemKeywHdr.ItemText("Hide Keywords - '" + (txt || keywTxt) + "'");
    ctxItemKeywUnhdr.ItemText("Unhide Keywords - '" + (txt || keywTxt) + "'");

    ctxRedo.Toggle(ctxRedo.find("li.ctxItem").length > 0);
    ctxUndo.Toggle(ctxUndo.find("li.ctxItem").length > 0);
});

if (!isSinglePost && !isSubmitPage) {
    jq(document).on("click", "div.thing", function(e) {
        if (e.target.nodeName != "A" && jq.inArray("arrow", e.target.classList) === -1 && jq.inArray("expando-button", e.target.classList) && jq.inArray("pstSelected", e.target.classList) === -1) {
            MakeActivePost(pstItems.selected, this.id);
        }
    });

    jq(document).bind("keydown", handleShortcut);
    jq(document).on("focusin", "input, textarea, select", function(e) {
        jq(document).unbind("keydown", handleShortcut);
    });
    jq(document).on("focusout", "input, textarea, select", function(e) {
        jq(document).bind("keydown", handleShortcut);
    });

    jq("#topPost").click(function() {
        MakeActivePost(pstItems.selected, pstItems.first);
        return false;
    });

    jq("#botPost").click(function() {
        MakeActivePost(pstItems.selected, pstItems.last);
        return false;
    });

    jq("#prevPost").click(function() {
        MakeActivePost(pstItems.selected, pstItems.Previous());
        return false;
    });

    jq("#nextPost").click(function() {
        MakeActivePost(pstItems.selected, pstItems.Next());
        return false;
    });

    jq("#frPost").click(function() {
        var pst = pstItems.selected;
        var pstNew = pst;
        pstNew = pstItems.Previous(5);

        MakeActivePost(pst, pstNew);
        return false;
    });

    jq("#ffPost").click(function() {
        var pst = pstItems.selected;
        var pstNew = pst;

        MakeActivePost(pstItems.selected, pstItems.Next(5));
        return false;
    });
}

// Events - Settings
function SetSettingsValues(settingsObject) {
    for (var key in settingsObject) {
        var varType = typeof settingsObject[key];

        SetSettingValue(key, varType, settingsObject[key]);
    }
}

function SetSettingValue(key, varType, value) {
    var settingsElement = jq("#rEnhSettings").find("[name='" + key + "']");

    switch (varType) {
        case "boolean":
            if (value)
                settingsElement.attr("checked", "true");
            else
                settingsElement.removeAttr("checked");
            break;
        case "number":
            settingsElement.val(value);
            break;
        case "string":
            settingsElement.val(value);
            break;
        case "object":
            if (typeCheck.call(value) === '[object Array]')
                settingsElement.val(value.toString());
            else
                SetSettingsValues(value);
            break;
        default:
            console.log("Cannot get setting " + key + " to settings dialog because unkown type " + varType);
            break;
    }
}

function ShowSettings() {
    SetSettingsValues(settings);

    jq("#rEnhSettings").show();
    return false;
}

function SaveSettingsDialog(e) {
    e.preventDefault();

    jq("#rEnhSettings").find("input, textarea, select").each(function() {
        var val;
        var key = this.name;
        var tag = this.tagName;

        switch (tag) {
            case "INPUT":
                var type = this.type;
                switch (type) {
                    case "checkbox":
                        val = Boolean(this.checked);
                        break;
                    case "number":
                        val = parseFloat(this.value);
                        break;
                    case "text":
                        val = this.value;
                        break;
                    default:
                        console.log("unhandled input type " + type + " for ey " + key);
                        break;
                }
                break;
            case "TEXTAREA":
                val = this.value.split(",");
                break;
            case "SELECT":
                val = parseInt(this.value);
                break;
            default:
                break;
        }

        if (val !== undefined) {
            if (key in settings)
                settings[key] = val;
            else if (key in settings.shortcuts)
                settings.shortcuts[key] = val;
        }
    });
    GMSave();

    fExtPopup("Settings saved!");

    return false;
}
// Events - End

// Functions - Start
function handleShortcut(e) {
    switch (e.keyCode) {
        case settings.shortcuts.voteUp:
            Vote(pstItems.selected, "up");
            break;
        case settings.shortcuts.previousPost:
            jq("#prevPost").click();
            break;
        case settings.shortcuts.voteDown:
            Vote(pstItems.selected, "down");
            break;
        case settings.shortcuts.nextImage:
            var imgActorNext = jq(".pstSelected div.res-gallery-individual-controls div.res-gallery-next");
            if(imgActorNext.length > 0)
                imgActorNext.get(0).click();
            break;
        case settings.shortcuts.toggleSelected:
            if (pstItems.selected)
                pstItems.jqSelected.find(".expando-button").get(0).click();
            break;
        case settings.shortcuts.previousImage:
            var imgActorPrev = jq(".pstSelected div.res-gallery-individual-controls div.res-gallery-previous");
            if(imgActorPrev.length > 0)
                imgActorPrev.get(0).click();
            break;
        case settings.shortcuts.hideSelected:
            HideSelectedClick();
            break;
        case settings.shortcuts.nextPost:
            NextPostClick();
            break;
        case settings.shortcuts.hideAll:
            if (reloadCancelled)
                HideAllClick();
            else
                reloadCancelled = true;
            break;
        case settings.shortcuts.zoomIn: // num +
            if (pstItems.selected)
                pstItems.jqSelected.find("img, video, iframe").each(function() { document.fExt.zoomIn(this, 20); });
            break;
        case settings.shortcuts.zoomOut: // num -
            if (pstItems.selected)
                pstItems.jqSelected.find("img, video, iframe").each(function() { document.fExt.zoomOut(this, 20); });
            break;
        case settings.shortcuts.rotateLeft: // num /
            if (pstItems.selected)
                pstItems.jqSelected.find("img, video, iframe").each(function() { document.fExt.rotate(this, -90); });
            break;
        case settings.shortcuts.rotateRight: // num *
            if (pstItems.selected)
                pstItems.jqSelected.find("img, video, iframe").each(function() { document.fExt.rotate(this, 90); });
            break;
        default:
            return true;
    }
    return false;
}

function InitializeHider() {
    jq("form.hide-button input[value=hidden]").each(function(index, item) {
        var jqItem = jq(item);
        var jqThing = jqItem.parents("div.thing");
        jqThing.find("form.hide-button a").attr("thing-id", jqThing.attr("id"));
        hiderElements.push(jqThing);
    });

    if (hiderElements.length === 0)
        jq("#hideAll").attr("style", "background-color: #808080 !important; color: #CFCFCF; border: 1px solid black;");

    jq("#hideAll").text("All (" + hiderElements.length + ")");
}

function VoteAndNext(sender, voteLinks, ind) {
    var nextVoteItem = voteLinks[ind];
    jq(nextVoteItem).click();

    ind++;
    if (voteLinks.length > ind) {
        animateControl(sender);

        setTimeout(function() {
            VoteAndNext(sender, voteLinks, ind);
        }, settings.animationItemTimeout);
    } else {
        jq("#downvoter").text("downvote");
        jq("#upvoter").text("upvote");
    }
}

function ExpandAndNext(expandLinks, ind) {
    var jqExpander = jq("#expander");
    var expandItem = expandLinks[ind];
    jq(expandItem).click();

    if (expandLinks.length > ind) {
        animateControl(jqExpander);
        setTimeout(function() {
            ExpandAndNext(expandLinks, ind + 1);
        }, settings.animationItemTimeout);
    }
    else  {
        if(jqExpander.hasClass("collapsed")) {
            jqExpander.text("Collapse");
            jqExpander.removeClass("collapsed");
            jqExpander.addClass("expanded");
        }
        else {
            jqExpander.text("Expand");
            jqExpander.addClass("collapsed");
            jqExpander.removeClass("expanded");
        }
    }
}

function animateControl(sender) {
    var txt = jq(sender).text();
    if (txt.length >= 3)
        txt = ".";
    else
        txt = txt + ".";

    jq(sender).text(txt);
}

function HideAndAction(hideLinks, index, previousLink, action) {
    var jqLink = jq(previousLink);
    var item = hideLinks[index];
    if (jqLink.find("form.hide-button").length === 0) {
        if (hideLinks.length > index) {
            var perc = index * 100 / hideLinks.length;
            fExtSetLoading(perc);
            if (action !== "hideObject")
                fExtMessage(action + " (" + index + "/" + hideLinks.length + ") ...");

            Hide(item, action === "reload" || (action == "enhance" && jq(item).find("a.author.friend").length > 0 && duplicateLinks.indexOf(item) < 0));

            var actArgs = [hideLinks, index + 1, previousLink, action];
            ActionWhenElementIsInvisible(item, HideAndAction, actArgs);
        } else {
            isHiding = false;
            fExtSetLoading(100);
            fExtMessage(undefined);
            switch (action) {
                case "reload":
                    ReloadLooper(5);
                    break;
                case "enhance":
                    if (pstItems.onlyVisible().length === 0)
                        ReloadLooper(5);
                    else
                        InitializeHider();
                    break;
                case "nextPost":
                    if (pstItems.onlyVisible().length === 0)
                        ReloadLooper(5);
                    else
                        jq("#nextPost").click();
                    break;
                default:
                    break;
            }
        }
    } else setTimeout(function() { HideAndAction(hideLinks, index, previousLink, action); }, settings.hideItemTimeout);
}

function ReloadLooper(loops) {
    if (reloadCancelled) {
        fExtMessage();
        return;
    } else {
        if (loops === 0) {
            jq("html, body").animate({ scrollTop: 0 }, 0);

            fExtMessage("Reloading...");
            setTimeout(function() {
                location.reload();
            }, 1000);
        } else {
            var msg = "Reloading in " + loops + " seconds... (click to cancel)";
            fExtMessage(msg);
            document.title = msg;
            setTimeout(function() {
                ReloadLooper(loops - 1);
            }, 1000);
        }
    }
}

function Hide(item, displayAfter, handled) {
    var jqHideButton = item.find("form.hide-button a");
    var actArgs = [item.attr("id"), pstItems.Next(), item.text()];

    if(!handled) {
        jqHideButton.unbind("click", handleHideClick);

        jqHideButton.click();

        var args = [item, displayAfter, true];
        AddUndo(Unhide, args, Hide, args, item.find("p.title").text());

        jqHideButton.bind("click", handleHideClick);
    }

    item.hide();

    if (!isHiding && pstItems.selected === item.attr("id")) {
        ActionWhenElementIsInvisible(item, MakeActivePost, actArgs);
    }

    if (displayAfter) {
        ActionWhenElementIsInvisible(item, item.show, undefined);
    }

    if(pstItems.onlyVisible().length === 0) {
        reloadCancelled = false;
        ReloadLooper(5);
    }

    return true;
}

function Unhide(item, displayAfter, handled) {
    jq(item).show();

    if(!handled){
        var args = [item, displayAfter, true];
        AddUndo(Hide, args, Unhide, args, item.find("p.title").text());
    }

    return true;
}

function Mark(item, itemType) {
    item.addClass("markedItem");
    item.addClass(itemType);
}

function MakeActivePost(oldId, newId) {
    if (oldId !== undefined && newId !== undefined && oldId === newId)
        return false;

    if (oldId) {
        var jqOldPost = jq("#" + oldId);
        jqOldPost.removeClass("pstSelected");

        if (settings.autoExpandSelectedPost) {
            var jsCollapser = jqOldPost.find(".expando-button.expanded").get(0);
            if (jsCollapser)
                jsCollapser.click();
        }
    }

    if (newId) {
        if (newId !== pstItems.selected) {
            pstItems.selected = newId;
            pstItems.jqSelected = jq("#" + newId);
            if (!pstItems.jqSelected.hasClass("pstSelected"))
                pstItems.jqSelected.addClass("pstSelected");

            if (settings.autoExpandSelectedPost) {
                var jsExpander = pstItems.jqSelected.find(".expando-button.collapsed").get(0);
                if (jsExpander)
                    jsExpander.click();
            }
        }

        scrollTo(pstItems.jqSelected);
    }
    else {
        pstItems.selected = undefined;
        pstItems.jqSelected = undefined;
    }
}

var preloadComplete = 0;
var preloadingImages = jq("a.title[href*='imgur']:not(.thumbnail)");
preloadingImages.each(function(index, item) {
    var src = item.href;
    if (src.indexOf("https://") < 0) {
        src = src.replace("http://", "https://");
        item.setAttribute("href", src);
    }
    preload(index, preloadingImages.length, src);
});

function preload(index, count, src) {
    var imageNumber = index + 1;
    var msg = "Loading Image " + imageNumber + " of " + count + ": ";
    //console.log(msg + src);
    var image = new Image();
    image.onload = function() {
        image.remove();
        preloadComplete++;
        //console.log(preloadComplete + " of " + count + " images completed.");
    };
    image.onerror = function() {
        if (image.src.indexOf("https://") >= 0) {
            image.src = image.src.replace("https://", "http://");
        } else {
            image.remove();
            preloadComplete++;
        }
        //console.log(preloadComplete + " of " + count + " images completed.");
    };
    image.src = src;
}

function scrollTo(item) {
    var jqItem = jq(item);
    var offs = jqItem.offset();
    var topPos = offs.top - headerHeight;

    jq("html, body").animate({ scrollTop: topPos }, settings.animationTimeout);
}

function AddToHideLinks(elm) {
    if (!elm.hasClass("toggleHidden"))
        elm.addClass("toggleHidden");

    if (jq.inArray(elm, hideLinks) < 0)
        hideLinks.push(elm);
}

function Vote(id, direction) {
    if (id !== undefined) {
        var element;

        switch (direction) {
            case "up":
                element = jq("#" + id).find(".arrow.up, .arrow.upmod");
                break;
            case "down":
                element = jq("#" + id).find(".arrow.down, .arrow.downmod");
                break;
            default:
                break;
        }

        if (element.length > 0) {
            element.click();
        }
    }
}

function AddUndo(fnUndo, argsUndo, fnRedo, argsRedo, suffix) {
    if (ctxUndo.Items.length === 10) {
        var removeThis = ctxUndo.Items[0];
        ctxUndo.Items.splice(0, 1);
        removeThis.remove();
    }
    if(!suffix)
        suffix = "";
    else if(suffix.length > 25)
        suffix = suffix.substring(0, 25) + "...";

    var ctxItem = fExtAddCtxItem(suffix && suffix.length > 0 ? fnRedo.name + " - " + suffix : fnRedo.name, ctxUndo);
    ctxItem.fnUndo = fnUndo;
    ctxItem.argsUndo = argsUndo;
    ctxItem.fnRedo = fnRedo;
    ctxItem.argsRedo = argsRedo;
    ctxItem.Action = function(event, sender, actor) {
        sender.fnUndo.apply(this, sender.argsUndo);
        AddRedo(sender.fnUndo, sender.argsUndo, sender.fnRedo, sender.argsRedo, suffix);
        sender.remove();
    };
}

function AddRedo(fnUndo, argsUndo, fnRedo, argsRedo, suffix) {
    if (ctxRedo.Items.length === 10) {
        ctxRedo.Items[0].remove();
        ctxRedo.Items.splice(0, 1);
    }
    if(!suffix)
        suffix = "";
    else if(suffix.length > 25)
        suffix = suffix.substring(0, 25) + "...";

    var ctxItem = fExtAddCtxItem(suffix && suffix.length > 0 ? fnUndo.name + " - " + suffix : fnUndo.name, ctxRedo);
    ctxItem.fnUndo = fnUndo;
    ctxItem.argsUndo = argsUndo;
    ctxItem.fnRedo = fnRedo;
    ctxItem.argsRedo = argsRedo;
    ctxItem.Action = function(event, sender, actor) {
        sender.fnRedo.apply(this, sender.argsRedo);
        AddUndo(sender.fnUndo, sender.argsUndo, sender.fnRedo, sender.argsRedo, suffix);
        sender.remove();
    };
}

function ToggleVisibility(event, sender, className) {
    event.preventDefault();
    if (jq(sender).data("state") === "shown") {
        jq(sender).data("state","hidden");
        jq(className).hide();
    } else {
        jq(sender).data("state","shown");
        jq(className).show();
    }
}

function HideObject(containingArray, arrayName, text, selector) {
    if (jq.inArray(text, containingArray) < 0) {
        fExtPopup("Adding '" + text + "' from '" + arrayName + "' to hide.");
        containingArray.push(text);
        GMSave();

        var links = [];
        var selectNext = false;

        jq(selector).parents("div.thing").each(function(index, item) {
            var jqItem = jq(item);
            selectNext = selectNext || item.id === pstItems.selected;

            if (jqItem.find("form.hide-button").length > 0)
                links.push(jqItem);
        });

        HideAndAction(links, 0, null, "hideObject");

        return true;
    }
    return false;
}

function UnhideObject(containingArray, arrayName, text, selector) {
    var arrayIndex = jq.inArray(text, containingArray);
    if (arrayIndex >= 0) {
        fExtPopup("Removing '" + text + "' from '" + arrayName + "'.");
        containingArray.splice(arrayIndex, 1);
        GMSave();
        return true;
    }
    return false;
}

function GrabContent(htmlContent) {
    fExtMessage("Loading additional Pages (" + numberOfSitesLoaded + " of max " + settings.maximumNumberOfSitesToLoad + ")");
    var navButtonNext = jq(htmlContent).find("div.nav-buttons .next-button a").get(0);
    if (numberOfSitesLoaded === 1) {
        var navButtonPrev = jq(htmlContent).find("div.nav-buttons .prev-button a").get(0);
        if (navButtonPrev)
            jq(navButtonPrev).prepend().appendTo("#postNavigation");
    }

    jq(htmlContent).find("div.nav-buttons").remove();

    if (numberOfSitesLoaded < settings.maximumNumberOfSitesToLoad && navButtonNext) {
        numberOfSitesLoaded += 1;
        var srcUrl = navButtonNext.href;
        //siteTable
        jq.ajax({
            url: srcUrl,
            method: "get",
            success: function(data) {
                jq(data).find("div#siteTable").find("div.thing").each(function() {
                    jq(this).detach().appendTo("div#siteTable");
                });

                GrabContent(data);
            },
            error: function(data) {
                console.log(data);
            }
        });
    } else {
        if (navButtonNext)
            jq(navButtonNext).detach().appendTo("#postNavigation");

        fExtMessage(numberOfSitesLoaded + " Pages loaded, starting initialization...");

        MainInitialization();
    }
}

function InitializeContextMenu() {
    ctxItemUsrHdr = fExtAddCtxItem("Hide User", rEnhSub);
    ctxItemUsrHdr.Action = function(event, sender, actor) {
        var user = actor.text();
        var selector = "a.author:contains(" + user + ")";
        var args = [settings.usersToHide, "Users", user, selector];
        if (HideObject.apply(sender, args))
            AddUndo(UnhideObject, args, HideObject, args, user);
    };

    ctxItemUsrHdrPg = fExtAddCtxItem("Hide User and open Userpage", rEnhSub);
    ctxItemUsrHdrPg.Action = function(event, sender, actor) {
        ctxItemUsrHdr.Action(event, sender, actor);
        GM_openInTab("https://www.reddit.com/u/" + actor.text() + "/submitted/", true);
    };

    ctxItemUsrUnhdr = fExtAddCtxItem("Unhide User", rEnhSub);
    ctxItemUsrUnhdr.Action = function(event, sender, actor) {
        var user = actor.text();
        var selector = "a.author:contains(" + user + ")";
        var args = [settings.usersToHide, "Users", user, selector];
        if (UnhideObject.apply(sender, args)) {
            jq("a.author:contains(" + user + ")").parents("div.thing").each(function(index, item) {
                jq(item).show();
            });
            AddUndo(HideObject, args, UnhideObject, args, user);
        }
    };

    fExtAddCtxSeparator(rEnhSub);

    ctxItemSbHdr = fExtAddCtxItem("Hide Sub", rEnhSub);
    ctxItemSbHdr.Action = function(event, sender, actor) {
        var sub = actor.text().replace("/r/", "");
        var selector = "a.subreddit:contains(" + sub + ")";
        var args = [settings.subsToHide, "Subs", sub, selector];
        if (HideObject.apply(sender, args))
            AddUndo(UnhideObject, args, HideObject, args, sub);
    };

    ctxItemSbUnhdr = fExtAddCtxItem("Unhide Sub", rEnhSub);
    ctxItemSbUnhdr.Action = function(event, sender, actor) {
        var sub = actor.text().replace("/r/", "");
        var selector = "a.subreddit:contains(" + sub + ")";
        var args = [settings.subsToHide, "Subs", sub, selector];
        if (UnhideObject.apply(sender, args)) {
            jq(selector).parents("div.thing").each(function(index, item) {
                jq(item).show();
            });
            AddUndo(HideObject, args, UnhideObject, args, sub);
        }
    };

    fExtAddCtxSeparator(rEnhSub);

    ctxItemKeywHdr = fExtAddCtxItem("Hide Keywords", rEnhSub);
    ctxItemKeywHdr.Action = function(event, sender, actor) {
        var keyword = fExtGetSelection().trim();
        var selector = "a.title:contains(" + keyword + ")";
        var args = [settings.keywordsToHide, "Keywords", keyword, selector];
        if (HideObject.apply(sender, args))
            AddUndo(UnhideObject, args, HideObject, args, keyword);
    };

    ctxItemKeywUnhdr = fExtAddCtxItem("Unhide Keywords", rEnhSub);
    ctxItemKeywUnhdr.Action = function(event, sender, actor) {
        var keyword = fExtGetSelection();
        var selector = "a.title:contains(" + keyword + ")";
        var args = [settings.keywordsToHide, "Keywords", keyword, selector];
        if (UnhideObject.apply(sender, args)) {
            jq(selector).parents("div.thing").each(function(index, item) {
                jq(item).show();
            });
            AddUndo(HideObject, args, UnhideObject, args, keyword);
        }
    };

    fExtAddCtxSeparator(rEnhSub);

    // Undo/Redo
    ctxUndo = fExtAddSub("Undo", rEnhSub);
    ctxRedo = fExtAddSub("Redo", rEnhSub);

    fExtAddCtxSeparator(rEnhSub);

    ctxItemCpyHtml = fExtAddCtxItem("Copy HTML", rEnhSub);
    ctxItemCpyHtml.Action = function(event, sender, actor) {
        fExtClipboard("Copy", jq('div.content').html());
    };

    fExtAddCtxSeparator(rEnhSub);

    ctxItemSett = fExtAddCtxItem("REnhancer Settings", rEnhSub);
    ctxItemSett.Action = function() { ShowSettings(); };

    // Copy links from Domain
    fExtAddCtxSeparator(rEnhSub);

    var ctxSubCpyDom = fExtAddSub("Copy Domain Links", rEnhSub);
    var objCpyDom = [];

    jq("span.domain a[href*='/domain/']").each(function() {
        var parentLink = jq(this).parents("div.thing").find("a").get(0);
        var domainName = this.pathname;
        domainName = domainName.replace("/domain/", "");
        domainName = domainName.substring(0, domainName.length - 1);
        var dictCpyDom;
        for (var ind in objCpyDom) {
            var currCpyDom = objCpyDom[ind];
            if (currCpyDom.Domain === domainName) {
                dictCpyDom = currCpyDom;
                break;
            }
        }

        if (dictCpyDom !== undefined) {
            dictCpyDom.Links.push(parentLink.href);
        } else {
            dictCpyDom = {
                Domain: domainName,
                Links: [parentLink.href],
            };
            objCpyDom.push(dictCpyDom);
        }
    });

    objCpyDom.sort(function(a, b) {
        return a.Domain.localeCompare(b.Domain);
    });

    for (var ind in objCpyDom) {
        var objDomain = objCpyDom[ind];
        var ctxCpyDom = fExtAddCtxItem(objDomain.Domain, ctxSubCpyDom);
        ctxCpyDom.Domain = objDomain;
        ctxCpyDom.Action = function() {
            var cpyText = "";
            for (var lnkInd in this.Domain.Links) {
                cpyText += this.Domain.Links[lnkInd];
                cpyText += "\n";
            }
            fExtClipboard("Copy", cpyText);
            fExtPopup("Copied " + this.Domain.Links.length + " links.");
        };
    }
}

function InitializeEventHandlers() {
    jq("#fExtMessage").click(function() { reloadCancelled = true; });
    jq("#hideAll").click(HideAllClick);
    jq("#hideSelected").click(HideSelectedClick);
    jq("#expander").click(function(e) {
        e.preventDefault();
        if(jq("#expander").hasClass("expanded"))
            ExpandAndNext(jq("div.expando-button.expanded:visible"), 0);
        else
            ExpandAndNext(jq("div.expando-button.collapsed:visible"), 0);
        return false;
    });
    jq("#renhSettingsSave").click(SaveSettingsDialog);
    jq("#upvoter").click(function(e) {
        e.preventDefault();
        VoteAndNext(jq(this), upVoteLinks, 0);
        return false;
    });
    jq("#downvoter").click(function(e) {
        e.preventDefault();
        VoteAndNext(jq(this), downVoteLinks, 0);
        return false;
    });
    jq("#renhSettingsClose").click(function(e) {
        jq("#rEnhSettings").hide();
        return false;
    });

    jq("form.hide-button a").bind("click", handleHideClick);

    jq("ul.flat-list.buttons>li").click(function(e){
        if(e.target.tagName !== "A")
            jq(e.target).find("a,form").first().click();
    });
}

function handleHideClick(e){
    var jqTarget = jq(e.target);
    var jqThing = jq("#" + e.target.getAttribute("thing-id"));

    var args = [jqThing, false, true];
    AddUndo(Unhide, args, Hide, args, jqThing.find("p.title").text());

    Hide(jqThing, undefined, true);
}

// Functions - Load and Save
function GMLoadValue(valueName) {
    var ret = GM_getValue(valueName);
    if (ret !== undefined)
        return ret.toString().split(",");

    return [];
}

function GMSave() {
    GM_setValue("settings", JSON.stringify(settings));
}

function GMLoad() {
    settings = { // Defaults
        accountName: "enter your account name here, e.g. throwaway001",
        markNSFW: true,
        replaceTopNew: false,
        sortByVotes: false,
        autoExpandSelectedPost: true,
        removeDuplicates: true,
        maximumNumberOfSitesToLoad: 1,

        animationItemTimeout: 500,
        imagePreloadingTimeout: 250,
        hideItemTimeout: 750,

        hideUsers: true,
        hideUsersNSFWonly: true,
        usersToHide: undefined,

        hideSubs: true,
        subsToHide: undefined,

        hideKeywords: true,
        keywordsToHide: undefined,

        shortcuts: {
            nextPost: 98,
            previousPost: 104,
            toggleSelected: 101,
            hideSelected: 99,

            hideAll: 97,

            voteUp: 105,
            voteDown: 103,

            nextImage: 102,
            previousImage: 100,
            zoomIn: 107,
            zoomOut: 109,
            rotateLeft: 111,
            rotateRight: 106,
        },

        panelView: false,
    };

    var lSettings = GM_getValue("settings");
    if (lSettings !== undefined) {
        lSettings = JSON.parse(lSettings.toString());
        for (var k in lSettings) {
            var varType = typeof lSettings[k];
            var varTypeB = typeof settings[k];
            switch (varType) {
                case "boolean":
                    break;
                case "number":
                    break;
                case "string":
                    break;
                default:
                    break;
            }
            settings[k] = lSettings[k];
        }
    }

    if (!settings.usersToHide)
        settings.usersToHide = GMLoadValue("usersToHide");
    if (!settings.subsToHide)
        settings.subsToHide = GMLoadValue("subsToHide");
    if (!settings.keywordsToHide)
        settings.keywordsToHide = GMLoadValue("keywordsToHide");
}

function ActionWhenElementIsInvisible(element, action, args) {
    if (element.hasClass("hidden") || element.find("a[text=hidden]").length === 1) {
        action.apply(element, args);
    } else {
        setTimeout(function() {
            ActionWhenElementIsInvisible(element, action, args);
        }, settings.hideTimeout);
    }
}

// Functions - Misc
function grabExtension(src) {
    var start, end;

    start = src.lastIndexOf(".") + 1;
    if (src.lastIndexOf("?") >= 0)
        end = src.lastIndexOf("?");
    else
        end = src.length;

    return src.substring(start, end);
}

function colorObject(r, g, b, a){
    var rv = {
        red: 0,
        blue: 0,
        green: 0,
        alpha: 100
    };

    if(r)
        rv.red = parseInt(r);
    if(g)
        rv.green = parseInt(g);
    if(b)
        rv.blue = parseInt(b);
    if(a)
        rv.alpha = parseInt(a);

    rv.getRgba = function(){
        return "rgba(" + this.red + "," + this.green + "," + this.blue + ", " + (this.alpha / 100) + ")";
    };
    rv.getHex = function(){
        var strR = this.red.toString(16);
        var strG = this.blue.toString(16);
        var strB = this.green.toString(16);
        if(strR.length === 1) strR = "0" + strR;
        if(strG.length === 1) strG = "0" + strG;
        if(strB.length === 1) strB = "0" + strB;

        return "#" + strR + strG + strB;
    };

    return rv;
}

function getRandomColor() {
    var color = '#';

    // Neu rgb-style
    var min = 155, max = 255;
    var cO = colorObject(Math.random() * (max - min) + min, Math.random() * (max - min) + min, Math.random() * (max - min) + min);

    if(cO.red > cO.green && cO.red > cO.blue)
        color = getRandomColor();
    else
        color = cO.getHex();

    return color;
}
// Functions - End