Reddit visited link remover

hides links you already visited on reddit and offers keyboard navigatin

当前为 2016-12-25 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name            Reddit visited link remover
// @version         0.8.5
// @namespace       [email protected]
// @description     hides links you already visited on reddit and offers keyboard navigatin
// @include         *.reddit.com/*
// @exclude         *.reddit.com/r/*/comments/*
// @exclude         *.reddit.com/user/*
// @exclude         *.reddit.com/message/*
// @exclude         *.reddit.com/reddits/*
// @exclude         *.reddit.com/prefs/*
// @require         https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/jquery-ui.min.js

// ==/UserScript==

// Keyboard Shortcuts:
// 
// - h: Open next unread post in new tab
// - j: Mark next unread post as read (+minimize)
// - k: Mark previous unread post as read (+minimize)
// - g: Toggle pop width (default 50% - 100% browser window width)
//
// - l: Open comments of active post
// - u: up vote first unread post
// - m: down vote first unread post
//

var cached_links_time = 60*60*24*2; // Two days - how long should visited links be saved
var cached_links_count = 1000; // Only save this amount of links (due to performance reasons)
var fade_time = 500; // time it takes until the visited link is completely invisible
var show_footer = true; // show how many links are currently cached at the buttom of reddit.com
var toggle_links_button = true; // show button to show / hide links (hide_links has to be true)
var hide_links = false; // removes links completely (after the next page reload)
var add_manual_hide_link = true; // adds a link to the frontpage to manually hide posts
var activate_popup = false; // activate popup functionality

var popup_width_min = $(window).width() / 2; // Half browser window size
var popup_width_max = $(window).width(); // Full browser window size
var popup_height = $(window).height(); // Full browser window height

// ----------------------------------------------------------

$("head").append (
    '<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.4/themes/cupertino/jquery-ui.min.css" rel="stylesheet" type="text/css">'
);

//--- Add our custom dialog using jQuery.
$("body").append ('<div id="gmOverlayDialog"></div>');

$('<span class="separator">|</span><ul class="flat-list hover"><li><a href="#" id="hidesidebar">hide sidebar</a></li></ul>').insertAfter("#header-bottom-right .flat-list.hover");

if (toggle_links_button && hide_links) {
    $("body").append ('<center><div id="show_links" class="show_links"><button>Toggle links</button></div> <br></center>');
}

if (add_manual_hide_link) {
    $('ul.flat-list.buttons').append('<li><a class="manHideLink" href="#">mark as read</a></li>');
}

var stateMachine;
window.addEventListener ("load", function () {

        stateMachine = new GM_LinkTrack();

        if (show_footer) {
            var numLinks    = stateMachine.GetVisitedLinkCount();
            $("body").append ('<center><p>' + numLinks + ' links cached. (max '+ cached_links_count + ')</p><br></center>');
        }

    },
    false
);

var activeElement;
var popupSizeToggle = 0;
var popupWidth = popup_width_min;
document.addEventListener("keypress", function(e) {
    
    if(!e) e=window.event;

    var isShift = e.shiftKey;

    var key = e.which;

    // http://www.w3schools.com/jsref/event_key_which.asp

    if (key == 106) { // j key
        // shrink next unshrinked link
        var element = getCorrectElement();
        //console.log("element: " + element + "e0: " + element[0]);
        if ( typeof element === "undefined" ) {  
            nextPage();
        } else if ( typeof element[0] === "undefined" ) {  
            nextPage();
        }

        stateMachine.LinkIsNewPub(element);
        setActiveElement(element);

        if ($("#gmOverlayDialog").dialog( "isOpen" )) {
            openLinkPopup(element);
        }

    } else if (key == 104) { // h key        
        
        if ( (!$("#gmOverlayDialog").dialog( "instance" )) && (activate_popup) ) {
            // open popup and open next unshrinked link
            var element = getCorrectElement();
            stateMachine.LinkIsNewPub(element);
            openLinkPopup(element);
            setActiveElement(element);
        } else if(!activate_popup) {
            // pop disable, open link in new tab
            var element = getCorrectElement();
            stateMachine.LinkIsNewPub(element);
            setActiveElement(element);
            openLink(element);
        } else {
            // close popup
            $("#gmOverlayDialog").html('<div id="gmOverlayDialog"></div>');
            $("#gmOverlayDialog").dialog( "destroy" );
        }

    } else if (key == 107) { // k key
        // open previous link in popup
        
        var element = getElementBeforeActive();
        stateMachine.LinkIsNewPub(element);
        setActiveElement(element);

        if ($("#gmOverlayDialog").dialog("isOpen")) {    
            openLinkPopup(element);
        }
        
    } else if (key == 103) { // g key
        // toggle popup size
        if ($("#gmOverlayDialog").dialog("isOpen")) {
            if (popupSizeToggle == 0) {
                popupWidth = popup_width_max;
                popupSizeToggle = 1;
            } else {
                popupWidth = popup_width_min;
                popupSizeToggle = 0;
            }
            $("#gmOverlayDialog").dialog ({
                 width: popupWidth
            });
        }
    }else if (key == 117) { // u key
        // up vote active post
        upVote(getActiveElement());
    } else if (key == 109) { // m key
        // down vote active post
        downVote(getActiveElement());
    }  else if (key == 108) { // l key
        // open comments of active post
        openLinkComments(getActiveElement());
    }

}, true);

function setActiveElement(elm) {
    resetActiveElement();
    activeElement = elm;
    elm.addClass('rvlr_active');
    elm.closest('div.thing:visible').find(".rank").css({
        "color": "#FFF",
        "background-color": "#FF4500",
        "border-radius": "50%",
        "text-align": "center",
        "padding": "2px"
    });
}

function resetActiveElement() {
    if (activeElement) {
        activeElement.removeClass('rvlr_active');
        activeElement.closest('div.thing:visible').find(".rank").css({
            "background-color": "inherit",
            "padding": "inherit"
        });
    };
}

function getActiveElement() {
    return activeElement;
}

function isActiveElement(elm) {
    if (elm.hasClass('rvlr_active')) {
        return true;
    }
    return false;
}

function getCorrectElement() {
    var element;

    var aElement = getActiveElement();
    if (typeof aElement !== "undefined") {
        element = getElementAfterActive();
    }

    if (typeof element === "undefined") {
        element = getFirstUnreadLink();
    }

    return element;   
}


function nextPage() {
    $('a[rel*=next]')[0].click();
}

function getFirstUnreadLink() {
    var currentElement;
    $('#siteTable a.title').each(function() {
        if ( ($(this).closest('div.thing').hasClass('shrinked') === false) && ($(this).closest('div.thing').is(":hidden") === false) )  {
            currentElement = $(this);
            return false;
        }
    });
    return currentElement;
}

function getLastShrinkedLink() {
    var element;
    $('#siteTable a.title').each(function() {
        if ( ($(this).closest('div.thing').hasClass('shrinked') === true) && ($(this).closest('div.thing').is(":hidden") === false) )  {
            element = $(this);
        }
    });
    return element;
}

function getElementBeforeActive() {
    var element;

    element = $('.rvlr_active').closest('div.thing').prevAll('div.thing:visible').eq(0).find('a.title');

    return element;
}

function getElementAfterActive() {
    var element;

    element = $('.rvlr_active').closest('div.thing').nextAll('div.thing:visible').eq(0).find('a.title');

    return element;
}

function openLink(elm) {
    var href = elm.attr('href');
    window.open(href, '_blank');
}

function openLinkPopup(elm) {

    var href = elm.attr('href');
    var title = elm.text();

    var html = '<iframe style="border: 0px;" src="'+href+'" width="100%" height="100%"></iframe>';

    // check url to convert them if necessary
    var ytId = getYoutubeId(href); // Youtube

    if (ytId) {
        var palyerWidthh = ( $(window).width() / 2 ) * 0.75;
        html = '<iframe width="100%" height="'+palyerWidthh+'" src="//www.youtube.com/embed/' + ytId + '?autoplay=1" frameborder="0" allowfullscreen></iframe>';
    }

    $("#gmOverlayDialog").html(html);

    if (!$("#gmOverlayDialog").dialog( "instance" )) {
        $("#gmOverlayDialog").dialog ({
            autoOpen:   false,
            modal:      false,
            position:   {
               my: "right top",
               at: "right top",
               of: window, 
               collision: "none"
            },
            minWidth:   400,
            minHeight:  200,
            zIndex:     3666,
            show: {
                effect: "fade",
                duration: 500
            },
            hide: {
                effect: "fade",
                duration: 200
            }
        }).dialog ("widget").draggable ("option", "containment", "none");
    }

    $("#gmOverlayDialog").dialog ({
        title:      title,
        height:     popup_height,
        width:      popupWidth
    })

    $("#gmOverlayDialog").dialog('open');
}

$( window ).resize(function() {
    if ($("#gmOverlayDialog").dialog( "instance" )) {
        if ($("#gmOverlayDialog").dialog( "isOpen" )) {
            $("#gmOverlayDialog").dialog ({
                height:     popup_height,
                width:      popupWidth,
                position:   {
                   my: "right top",
                   at: "right top",
                   of: window
                },
            })
        }
    }
});

$( window ).scroll(function() {
    if ($("#gmOverlayDialog").dialog( "instance" )) {
        if ($("#gmOverlayDialog").dialog( "isOpen" )) {
            $("#gmOverlayDialog").dialog ({
                position:   {
                   my: "right top",
                   at: "right top",
                   of: window
                },
            })
        }
    }
});

function getYoutubeId(url) {
    var regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/;
    var match = url.match(regExp);

    if (match && match[2].length == 11) {
        return match[2];
    } else {
        return 0;
    }
}

function openLinkComments(elm) {
    var href = elm.closest('div.thing').find("a.comments").attr('href');
    //window.location = href;
    window.open(href, '_blank');
}

function upVote(elm) {
    elm.closest('div.thing').find("div.up, div.upmod").click();
}

function downVote(elm) {
    elm.closest('div.thing').find("div.down, div.downmod").click();
}

function GM_LinkTrack () {
    var visitedLinkArry = [];
    var numVisitedLinks = 0;
    var link_count = 0;
    var current_timestamp = new Date().getTime();

    var sortedLocalStorage = SortLocalStorage();

    // Get visited link-list from storage.
    for (var J = sortedLocalStorage.length - 1;  J >= 0;  --J) {

        var item = sortedLocalStorage[J];

        var four_weeks = cached_links_time*1000;

        // Get saved links
        if (/^Visited_\d+.*/i.test (item) ) {

            var regex = /^Visited_(\d+).*/;
            var old_timestamp = regex.exec(item)[1];

            var regex2 = /^Visited_\d+_(.*)/;
            var value = regex2.exec(item)[1];

            var regex3 = /^(Visited_\d+)_.*/;
            var itemName = regex3.exec(item)[1];

            //console.log(numVisitedLinks + " " + item+ " t: " + old_timestamp + " v: " + value + " n: " + itemName);

            if (value == '#') {
                localStorage.removeItem (itemName);
                break;
            }

            if (link_count >= cached_links_count) {
                localStorage.removeItem (itemName);
                //console.log(numVisitedLinks + " " + value + "t: " + timeConverter(old_timestamp));
                link_count--;
            }
            link_count++;

            // check link age
            if ( (current_timestamp - old_timestamp) < four_weeks ) {
                visitedLinkArry.push (value);
                numVisitedLinks++;

                //console.log(numVisitedLinks + " " + localStorage[itemName] + " t: " +old_timestamp[1]);

                if (hide_links) {
                    $('a[href*="' + value + '"]').closest('div.thing').fadeOut(fade_time);
                } else {
                    //$('a[href="' + localStorage[itemName] + '"]').closest('div').fadeOut(fade_time);
                    shrinkLinks($('a[href*="' + value + '"]'));
                }

            } else {
                // too old, remove from storage 
                localStorage.removeItem (itemName);
            }
        }
    }

    function SortLocalStorage() {

        var localStorageArray = [];

        if(localStorage.length > 0) {
            for (i=0; i<localStorage.length; i++){
                localStorageArray[i] = localStorage.key(i)+ "_" +localStorage.getItem(localStorage.key(i));
            }
        }

        return localStorageArray.sort();
    }

    function timeConverter(UNIX_timestamp) {
     var a = new Date(UNIX_timestamp*1000);
     var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
         var year = a.getFullYear();
         var month = months[a.getMonth()];
         var date = a.getDate();
         var hour = a.getHours();
         var min = a.getMinutes();
         var sec = a.getSeconds();
         var time = date+','+month+' '+year+' '+hour+':'+min+':'+sec ;
         return time;
    }

    function ShowLinks () {
        for (var J = localStorage.length - 1;  J >= 0;  --J) {
            var itemName    = localStorage.key (J);

            // Get saved links
            if (/^Visited_\d+$/i.test (itemName) ) {
                $('a[href="' + localStorage[itemName] + '"]').closest('div.thing').fadeToggle(fade_time);
            }
        }
    }

    this.LinkIsNewPub = function (linkObj) {
        LinkIsNew(linkObj);
    };

    function LinkIsNew (linkObj) {
        var href = linkObj.attr('href');

        href = href.replace(/^(http|https)\:\/\//g, '');       

        if (visitedLinkArry.indexOf (href) == -1) {
            visitedLinkArry.push (href);
        
            var timestamp = new Date().getTime();

            var itemName    = 'Visited_' + timestamp;
            localStorage.setItem (itemName, href);
            numVisitedLinks++;
            
            // Hide links imideately after klicked. Makes it impossible to see commenst afterward.
            //$('a[href="' + href + '"]').closest('div').fadeOut(fade_time);
        }
        centerView(linkObj);
        shrinkLinks(linkObj);
        setActiveElement(linkObj);

        return true;
    }

    function shrinkLinks (linkObj) {

        // Alter the look of clicked links imideately
        var mainLinkElement = linkObj.closest('div.thing');

        // Remove elements that are nor needed in the smll view
        mainLinkElement.find('p.tagline').remove();
        mainLinkElement.find('a.thumbnail').hide(fade_time, function () {
            $(this).remove();
        });
        mainLinkElement.find('span.domain').remove();
        mainLinkElement.find('div.expando-button').remove();

        mainLinkElement.find('li a.manHideLink').remove();


        // Realign elements for the small view
        var animObj = {"queue": false, "duration": fade_time};

        mainLinkElement.find('p.title').css({'float': 'left', 'font-size': '9px', 'margin-left': '10px', 'font-weight': 'normal', 'margin-top': '4px'});

        mainLinkElement.find('a.title').animate({'font-size': '9px'}, animObj);
        mainLinkElement.find('ul.flat-list.buttons').animate({'margin-left': '10px', 'font-size': '9px', 'margin-top': '3px'}, animObj);
        mainLinkElement.find('span.rank').animate({'margin-top': '1px', 'font-size': '10px'}, animObj);

        mainLinkElement.find('div.midcol').animate({'width': '90px'}, animObj);
        mainLinkElement.find('div.arrow').css({'float': 'left'});

        mainLinkElement.find('div.score').css({'float': 'left'});
        mainLinkElement.find('div.score').animate({'font-size': '10px', 'padding-left': '10px', 'padding-right': '10px', 'margin-top': '3px', 'width': '30px'}, animObj);

        mainLinkElement.css({'padding-left': '10px', 'opacity': '0.4', 'margin': '2px'});

        mainLinkElement.addClass('shrinked');

        return true;
    }
    
    this.GetVisitedLinkCount = function () {
        return numVisitedLinks;
    };

    function centerView(elm) {

        var offsetTop = $(elm).offset().top;
        var newTopPos = offsetTop - $(window).scrollTop();
        var elemLimitPos = $(window).height() / 2;

        //console.log("offsetTop: " + offsetTop);
        //console.log("newTopPos: " + newTopPos);        
        //console.log("elemLimitPos: " + elemLimitPos);

        if ( newTopPos > elemLimitPos ) {
            $('html,body').animate({
                scrollTop: offsetTop - 50
            }, 800);
        }
    }

    $('a.title').click(function() {
        LinkIsNew($(this));
    });
 
    $('#hidesidebar').click(function(event) {
        event.preventDefault();

        $(".side").toggle();
    });

    if (toggle_links_button && hide_links) {
        $('#show_links button').click (function() {
            ShowLinks();
        });
    }

    if (add_manual_hide_link) {
        $('a.manHideLink').click (function(event) {
            event.preventDefault();
            var $titleLink = $(this).closest('div.thing').find('a.title');

            LinkIsNew($titleLink);
        });
    }
}