novelupdates Cover Preview

Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.

当前为 2017-01-14 提交的版本,查看 最新版本

// ==UserScript==
// https://gf.qytechs.cn/scripts/26439-novelupdates-cover-preview/
// @name        novelupdates Cover Preview
// @namespace   somethingthatshouldnotclashwithotherscripts
// @include     https://www.novelupdates.com/*
// @include     http://www.novelupdates.com/*
// @version     1.2
// @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @run-at   	document-end
// @require     http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// @license     http://creativecommons.org/licenses/by-nc-sa/4.0/
// ==/UserScript==
// article a; http://forum.novelupdates.com/ ; crossDomain: true, xhrFields to delete before upload
const MAXCACHEAGE = 7 * 24 * 60 * 60 * 1000; // Max Age before Cached data gets overridden with current data. Max Age is 24 hour in milliseconds  //days * h * min  * sec * ms

const SELECTOR1 = 'td a'; //index/group/readinglist pages
const SELECTOR2 = '.wpb_wrapper > a'; //individual serie pages recommendation titles
const PREDIFINEDNATIVTITLE = "^Recommended by"; //in case native title is used to display something different
const INDIVIDUALPAGETEST = "novelupdates.com/series/";
var STYLESHEETHIJACKFORBACKGROUND = "breadcrumb_nu"; //if unknown set empty ""; classname without leading dot
var STYLESHEETHIJACKFORTITLE = "tbl_sort"; //if unknown set empty ""; classname without leading dot
const DEFAULTTITLEBACKGROUNDCOLOR = '#aac'; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR ='#ccc';	//if no hijack class style available use plain color
const IMAGELINKCONTAINER = 'serieseditimg';
const IMAGELINKCONTAINERANONYM = 'seriesimg'; //sContainer
const CONTAINERNUMBER = 0;
const IMAGEBLOCKER = "http://www.novelupdates.com/img/noimagefound.jpg";

const HASERROR = true;
const RE = /\s*,\s*/; //Regex for split and remove empty spaces
const IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE);

function concatSelector()
{
    var result;
    if(SELECTOR1)
        result = SELECTOR1;
    if(SELECTOR2)
    {
        if(SELECTOR1) //in case selector1 is missing
            result += ', ';
        result += SELECTOR2;
    }

    return result;
}

//get value from key. Decide if timestamp is older than MAXCACHEAGE than look for new image
function GM_getCachedValue(key)
{
    var currentTime = Date.now();

    var keyCache = key+'cachedTime';
    if(GM_getValue(keyCache)) //has previous timestamp for key
    {
        var measuredTimedifference = currentTime-parseInt(GM_getValue(keyCache));
        if(measuredTimedifference < MAXCACHEAGE )
        {
            if(GM_getValue(key))	
            {
                return GM_getValue(key); //when time difference smaller MAXCACHEAGE and value available return value
            }
            else
                GM_deleteValue(keyCache);	//if no value available delete previous key with timestamp
        }
        else
        {	//when time difference bigger than MAXCACHEAGE delete timestamp and value;
            GM_deleteValue(keyCache);
            GM_deleteValue(key);
        }

    }
    else	//when no timestamp available return false
        return false;
}

//set value and currenttime for key
function GM_setCachedValue(key, value)
{
    var currentTime = Date.now();  // current datetime in milliseconds
    GM_setValue(key, value);
    GM_setValue(key+'cachedTime', currentTime);
}

var onHover = false;
var currentTitelHover;

// popupPositioning function
jQuery.fn.popupPos = function (event, element, style) {
    var offsetToBottomBorderY = 5; //offset to bottom border
    var offsetToRightBorderX = 5;  //offset to right border
    var X,Y;
    var hoveredSelectedPosX,hoveredSelectedPosY;
    var distanceToBottom, distanceToRight;

    //Initialising variables (multiple usages)
    var scrollTop = $(window).scrollTop();
    var scrollLeft = $(window).scrollLeft();
    var elementPopup = $(this);
    var elementParentOffset = element.parents().offset();
    var elementParentOuterHeight = element.parents().outerHeight();
    var elementParentOuterWidth = element.parents().outerWidth();
    var elementOffset = element.offset();
    var elementOuterHeight = element.outerHeight();
    var elementOuterWidth = element.outerWidth();

    if(style==1) //index: position next to parent table cell (SELECTOR1)
    {
        hoveredSelectedPosX = elementParentOffset.left + elementParentOuterWidth;	//link position + tablecell width; + elementOuterWidth;;
        hoveredSelectedPosY = elementParentOffset.top + elementParentOuterHeight;	//link position + tablecell height; + elementOuterHeight;       
    }
    else if(style==2) //recommendations: position next to link height and parent (SELECTOR2) width
    {
        hoveredSelectedPosX = elementParentOffset.left + elementParentOuterWidth; //elementOffset.left + elementOuterWidth;
        hoveredSelectedPosY = elementOffset.top + elementOuterHeight;
    }
    else
    { //position to mouse hover position
        hoveredSelectedPosX = event.pageX;// + offsetToRightBorderX;
        hoveredSelectedPosY = event.pageY;// + offsetToBottomBorderY;
    }
    X = hoveredSelectedPosX; 
    Y = hoveredSelectedPosY;

    // Distance to the right
    distanceToRight = $(window).width() - (X + offsetToRightBorderX - scrollLeft);
    // Tooltip too close to the right?
    if(distanceToRight < elementPopup.outerWidth() + offsetToRightBorderX)
        X += distanceToRight - elementPopup.outerWidth();

    // Distance to the bottom
    distanceToBottom = $(window).height() - (Y + offsetToBottomBorderY - scrollTop);
    // Tooltip too close to the bottom?
    if(distanceToBottom < elementPopup.outerHeight() + offsetToBottomBorderY) //(offsetToBottomBorderY + elementPopup.outerHeight())
        Y += distanceToBottom - elementPopup.outerHeight();

    //console.log("Distance to the bottom " + distanceToBottom+" elementPopupHeight " +elementPopup.outerHeight()+ "\nDistance to the right " + distanceToRight+ " elementPopupouterWidth " +elementPopup.outerWidth());
    //Tooltip over top border?
    //if(Y + offsetToBottomBorderY < scrollTop) Y = scrollTop + offsetToBottomBorderY;
    //if(X + offsetToRightBorderX < scrollLeft) X = scrollLeft + offsetToRightBorderX;
    this.css('top', Y + 'px');
    this.css('left', X + 'px');
    //console.log("final popup position "+X+' # '+Y);
    return this;
};



var style=1;
//.wpb_wrapper a = title links on individual seriepage
$(SELECTOR2).mouseenter(function (e) {
    style=2;
});
//td a = links in table cells (index and group page)
$(SELECTOR1).mouseenter(function (e) {
    style=1;
});

$(window).on('load', function () {
    function styleSheetContainsClass(f)
    {
        var localDomainCheck = '^http://'+document.domain;
        //console.log("Domain check with: " + localDomainCheck);
        var hasStyle = false;
        var stylename = '.'+f;
        var fullStyleSheets = document.styleSheets;
        // console.log("start styleSheetContainsClass " + stylename);
        if(fullStyleSheets)
        {
            for (var i = 0; i < fullStyleSheets.length-1; i++){
                //console.log("loop fullStyleSheets " + stylename); //hier im bereich firefox bug?
                var styleSheet = fullStyleSheets[i];			
                //var stylehref = ""+styleSheet.href;
                //console.log("style test : " + styleSheet.href);
                //if(styleSheet != null)
                {
                    if(styleSheet.href != null) //https://gold.xitu.io/entry/586c67c4ac502e12d631836b "However since FF 3.5 (or thereabouts) you don't have access to cssRules collection when the file is hosted on a different domain" -> Access error for Firefox based browser. script error not continuing
                        if(styleSheet.href.match(localDomainCheck) )
                        {
                            if(styleSheet.cssRules)
                            {
                                for(var rulePos = 0; rulePos < styleSheet.cssRules.length-1; rulePos++)
                                {

                                    if(styleSheet.cssRules[rulePos] !== undefined)
                                    {
                                        // console.log("styleSheet.cssRules[rulePos] "+ stylename);
                                        if(styleSheet.cssRules[rulePos].selectorText)
                                        {
                                            if(styleSheet.cssRules[rulePos].selectorText == stylename) 
                                            {
                                                console.log('styleSheet class has been found - style: ' + stylename);			
                                                hasStyle = true; //break;
                                                return hasStyle;
                                            }
                                        }// else console.log("undefined styleSheet.cssRules[rulePos] "+ stylename);
                                    }
                                    // else console.log("loop undefined styleSheet.cssRules[rulePos] "+ stylename);
                                }
                            } //else console.log("undefined styleSheet.cssRules "+ stylename);
                        }
                    //console.log("stylesheet url " + styleSheet.href);
                } //else console.log("undefined styleSheet "+ stylename);
                if(hasStyle) break;
            }
        } //else console.log("undefined fullStyleSheets "+ stylename);

        console.log("styleSheet class has not been found - style: " + stylename);
        return hasStyle;
    }
	if(STYLESHEETHIJACKFORBACKGROUND !="")
		if (!styleSheetContainsClass(STYLESHEETHIJACKFORBACKGROUND) )
			STYLESHEETHIJACKFORBACKGROUND = "";
	if(STYLESHEETHIJACKFORTITLE !="")
		if (!styleSheetContainsClass(STYLESHEETHIJACKFORTITLE) )
			STYLESHEETHIJACKFORTITLE = "";

    $('body').append('<div ID="popover" '+stylesheetForBackground()+'></div>');
    $('#popover').css('position', 'absolute');
    $('#popover').css('z-index', '10');
    $('#popover').css('box-shadow', '0px 0px 5px #7A7A7A');

    //circle spinner from http://codepen.io/Beaugust/pen/DByiE
    //add additional stylesheet for "@keyframe spin" into head after document finishes loading
    //@keyframes spin is used for die loading spinner
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = 'body {}';
    document.getElementsByTagName('head')[0].appendChild(style);
    this.stylesheet = document.styleSheets[document.styleSheets.length-1];

    try {
        this.stylesheet.insertRule(`
@keyframes spin {
100% {
transform: rotate(360deg);
}
}`, this.stylesheet.cssRules.length);
    } catch (e) {alert('error');}
});

function stylesheetForTitle()
{
    if(STYLESHEETHIJACKFORTITLE != "")
        return 'class="' + STYLESHEETHIJACKFORTITLE + '" style="display:inline-block;width:100%;text-align:center !important"';
    else
        return 'style="background-color:' + DEFAULTTITLEBACKGROUNDCOLOR+';display:inline-block;width:100%;text-align:center !important"';
}

function stylesheetForBackground()
{
    if(STYLESHEETHIJACKFORBACKGROUND != "")
        return 'class="'+ STYLESHEETHIJACKFORBACKGROUND+'" style="display:flex !important;flex-direction: column; align-items:center;pointer-events:none; width:auto; height:auto; max-width:500px; max-height:500px;"';
    else
		return 'style="background-color:'+DEFAULTBACKGROUNDCOLOR+';display:flex !important;flex-direction: column; align-items:center;pointer-events:none; width:auto; height:auto; max-width:500px; max-height:500px;"';
}


//when selected link is entered load imageurl and write popover content
$(concatSelector()).mouseenter(function (e) {
    var element = $(this);
    var Href = element.attr('href');

    if (Href.search(INDIVIDUALPAGETEST) != -1)  //only trigger for links that point to serie pages
    { 
		function inBlocklist(link)
        {			
            if(IMAGEBLOCKERARRAY)
                if(IMAGEBLOCKERARRAY.length > 0)
                    for(var i=0; i < IMAGEBLOCKERARRAY.length; i++)
                        if( link == IMAGEBLOCKERARRAY[i] )
                            return true;
            return false;
        }

        function refreshPopover(title, link)
        {
        /*	clear popup
		*	append title and image into popup
		*	when img loading is finished reposition (popupPos) to element/border
		*/
            if(currentTitelHover == title) //popup only gets refreshed when currentTitelHover == title
            {
                $('#popover').empty();
                if( inBlocklist(link) )
                {
                    $('#popover').append('<div '+stylesheetForTitle()+'>'+title+'</div>Blocked Image / No Cover Image / Unwanted Image');
                    $('#popover').popupPos(e, element, style);
                }
                else
                {					
                    $('#popover').append('<div '+stylesheetForTitle()+'>'+title+'</div><img src="' + link + '" style="margin:5px; width:auto; height:400px !important; max-width:100%; max-height:100%; align-items: stretch;align-self: stretch;object-fit: contain;"></img>');
                    $('#popover img').on('load', function () {
                        //console.log(Href + "onload is executed"); // for testing purposes
                        if(onHover)		//is mouse still hovering over same title after loading finishes?
                            $('#popover').popupPos(e, element, style);
                    });
                }	
            }
        }

        //popup loading spinner
        function showPopupLoadingSpinner(title, error = false)
        {			
            $('#popover').empty();
            if(error)
                $('#popover').append('<div '+stylesheetForTitle()+'>'+title+'</div><div style="position: relative;width:150px; height:150px;color:#fff;display: flex; justify-content: center; flex-direction: column; text-align: center;">iamgelink getElementsByClassName is invalid</div>');
            else
                $('#popover').append('<div '+stylesheetForTitle()+'>'+title+'</div><div style="position: relative;width:150px; height:150px;color:#fff;display: flex; justify-content: center; flex-direction: column; text-align: center;">Getting image url<div style="z-index: -100;position:absolute;top:0;left:0;background-color:#000; box-sizing: border-box; width: 150px; height: 150px; border-radius: 100%; border: 10px solid rgba(255, 255, 255, 0.2); border-top-color: #FFF; animation: spin 1s infinite linear;"></div></div>');
            $('#popover').popupPos(e, element, style);
        }

        //async wait until image is loaded before refreshing popup
        function ajaxLoadImageUrlAndShowPopup(url, title)
        {
            $.ajax({
                url: Href,
                type: "GET",
                dataType: 'text',
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                success: function (data) {
                    if (!data && data == " ") {
                        console.log('response to ' + Href + ' was empty');
                        showPopupLoadingSpinner(serieTitle,1);
                    }
                    else
                    {
                        //vvv get imageurl link vvv
                        var htmlData = $('<div>').html(data)[0];
                        var imagelinkLoggedIn = htmlData.getElementsByClassName(IMAGELINKCONTAINER)[CONTAINERNUMBER];
                        var imagelink;

                        if(imagelinkLoggedIn) //need to consider different img classes for logged in and anonymous users
                            imagelink = imagelinkLoggedIn.getElementsByTagName('img')[0].src;
                        else
                            imagelink = htmlData.getElementsByClassName(IMAGELINKCONTAINERANONYM)[CONTAINERNUMBER].getElementsByTagName('img')[0].src;
                        //console.log("imagelink: " + imagelink);
                        // ^^^ get imageurl link ^^^
                        
                        refreshPopover(serieTitle, imagelink); //popup only gets refreshed when currentTitelHover == serieTitle
                        
                        // cache info
                        GM_setCachedValue(Href, imagelink);		//cache imageurl link
                        console.log(Href + " url has been found and is written to temporary cache.\n"+ imagelink + ' successfully cached.'); // for testing purposes
                    }

                }
            });
        }

        onHover = true;
        $('#popover').show();

        var shortSerieTitle = element.text(); //get linkname

        //move native title to custom data attribute. Suppress nativ title
        if(!element.attr('datatitle'))
        {
            element.attr('datatitle', element.attr('title'));
            element.removeAttr('title');			
        }

        var serieTitle = element.attr('datatitle'); //try to get nativ title if available from datatitle
        if(!serieTitle)		//has no set nativ long title -> use (available shortend) linkname
            serieTitle = shortSerieTitle;
        else //no need to run check if it is already shortSerieTitle
            if(serieTitle.match(PREDIFINEDNATIVTITLE))	//catch on individual serie page nativ title begins with "Recommended by" x people -> use linkname
                serieTitle = shortSerieTitle;

        currentTitelHover = serieTitle;		//mark which titel is currently hovered		

        var retrievedImgLink = GM_getCachedValue(Href); //was imageurl cached?

        if (retrievedImgLink) 
        {
            refreshPopover(serieTitle, retrievedImgLink); //popup only gets refreshed when currentTitelHover == serieTitle
            console.log(retrievedImgLink +' on the page '+ Href + " has been found and retrieved from the cache."); // for testing purposes
        }
        else 
        {			
            showPopupLoadingSpinner(serieTitle);
            ajaxLoadImageUrlAndShowPopup(Href, serieTitle);
        }

    }
});

//hide and empty popup when mouse is not over title
$(concatSelector()).mouseleave(function () { //close popup when mouse leaves titlelink
    //$('#popover').empty();
    $('#popover').hide();
    onHover = false;
});
$(document).mouseleave(function () { //force close when mouse is outside window and previous mouseleave does not get called
    $('#popover').hide();
    onHover = false;
});

$(window).blur(function () { //chrome fix -> force close when mouse is outside window alt + tab
    $('#popover').hide();
    onHover = false;
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址