novelupdates Cover Preview

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

当前为 2020-11-13 提交的版本,查看 最新版本

// ==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/*
// @include     https://forum.novelupdates.com/*
// @include     http://forum.novelupdates.com/*
// @version     1.4.4
// @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.
// @inject-into content
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @grant       GM_listValues
// @run-at   	document-end
// @license     http://creativecommons.org/licenses/by-nc-sa/4.0/
// ==/UserScript==
const MAXCACHEAGE = 7 * 24 * 60 * 60 * 1000; // Max Age before Cached data gets overridden with current data. Max Age is 7 days in milliseconds  //days * h * min  * sec * ms
let STYLESHEETHIJACKFORBACKGROUND = ".l-canvas"; //if unknown set empty ""; classname with leading dot
let STYLESHEETHIJACKFORTITLE = '.widgettitle_nuf'; //if unknown set empty ""; classname with leading dot
const DEFAULTTITLEBACKGROUNDCOLOR = '#2c3e50'; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR = '#ccc'; //if no hijack class style available use plain color

const SELECTOR1 = 'td a'; //index/group/readinglist pages , forum
const SELECTOR2 = '.wpb_wrapper > a, .messageContent 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/";
const IMAGELINKCONTAINERS = '.serieseditimg, .seriesimg'; //instead of single element class name with dot
const IMAGELINKCONTAINERSnonJquery = 'serieseditimg seriesimg'; //instead of single element class name with dot
const IMAGEBLOCKER = "www.novelupdates.com/img/noimagefound.jpg"; //tested with string.match(). no need for prefixed http https in url. Can even be just the file name
const CONTAINERNUMBER = 0;
const preloadUrlRequests = true;
const preloadImages = false;
//^^^^	frontend settings over this line	^^^^
const version = "1.4.4";
const forceUpdate = false;
const RE = /\s*,\s*/; //Regex for split and remove empty spaces
const defaultHeight = "400px";
const IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE);
let currentTitelHover;
let style = 1;
let popover, popover2, popoverTitle, popoverContent, popoverCoverImg;
let concatenatedSelectorAll = document.querySelectorAll(concatSelector());
let offsetToBottomBorderY = 22; //offset to bottom border
let offsetToRightBorderX = 10; //offset to right border

//console.log(this.location)
//console.log(this.location.href)
const isOnIndex = this.location.href == "https://www.novelupdates.com/" || this.location.href.startsWith("https://www.novelupdates.com/?pg=") == 1
//console.log("isOnIndex: " + isOnIndex)

//get value from key. Decide if timestamp is older than MAXCACHEAGE than look for new image
function GM_getCachedValue(key) {
    const DEBUG = false;
    const currentTime = Date.now();
    const rawCover = GM_getValue(key, null);
    DEBUG && console.group("GM_getCachedValue")
    DEBUG && console.log("rawCover: " + rawCover)
    let result = null;
    if (rawCover === null || rawCover == "null") {
        result = null;
    }
    else {
        let coverData;
        try { //is json parseable data? if not delete for refreshing
            coverData = JSON.parse(rawCover);
            DEBUG && console.log("coverData: " + coverData)
            DEBUG && console.log(coverData)
            if (!(coverData.url && coverData.title && coverData.cachedTime)) //has same variable definitions?
            {
                GM_deleteValue(key);
                result = null;
            }
        } catch (e) {
            GM_deleteValue(key);
            result = null;
        }


        const measuredTimedifference = currentTime - coverData.cachedTime;
        if (measuredTimedifference < MAXCACHEAGE) {
            result = { url: coverData.url, title: coverData.title };
        }
        else {
            {
                GM_deleteValue(key);
                result = null;
            }
        }
    }
    DEBUG && console.groupEnd("GM_getCachedValue")
    DEBUG && console.log(result)
    return result;
}

//set value and currenttime for key
function GM_setCachedValue(key, coverData) {
    const DEBUG = false;
    const cD = {
        url: coverData.url,
        title: coverData.title,
        cachedTime: Date.now()
    };
    GM_setValue(key, JSON.stringify(cD));
    DEBUG && console.group("GM_setCachedValue")
    DEBUG && console.log("save coverdata")
    DEBUG && console.log(cD)
    DEBUG && console.group("GM_setCachedValue")
}

function inBlocklist(link) {
    if (IMAGEBLOCKERARRAY)
        if (IMAGEBLOCKERARRAY.length > 0)
            for (let i = 0; i < IMAGEBLOCKERARRAY.length; i++)
                if (IMAGEBLOCKERARRAY[i] !== "")
                    if (link.match(IMAGEBLOCKERARRAY[i]))
                        return true;
    return false;
}

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

    return result;
}
//https://medium.com/@alexcambose/js-offsettop-property-is-not-great-and-here-is-why-b79842ef7582
const getOffset = (element, horizontal = false) => {
    if (!element) return 0;
    return getOffset(element.offsetParent, horizontal) + (horizontal ? element.offsetLeft : element.offsetTop);
}

// popupPositioning function
function popupPos(event) {
    const DEBUG = false;
    DEBUG && console.group("popupPos style:" + style)
    const nativElement = event.target;


    let X, Y;
    let distanceToBottom, distanceToRight;
    //console.log(element.parents()[0])
    DEBUG && console.log(nativElement)
    //console.log(nativElement.parentElement)
    let parentElement = nativElement.parentElement;
    //let computedFontSizeJquery = parseInt(window.getComputedStyle(element.parents()[0]).fontSize);
    const computedFontSize = parseInt(window.getComputedStyle(parentElement).fontSize);
    //console.log(computedFontSize);

    //Initialising variables (multiple usages)
    let scrollPosY = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop;
    let scrollPosX = window.scrollX || window.scrollLeft || document.getElementsByTagName("html")[0].scrollLeft;
    // console.log(scrollPosX)
    const scrollTop = scrollPosY; // windowCached.scrollTop();
    const scrollLeft = scrollPosX; //windowCached.scrollLeft();
    //var elementPopup = this[0]; //this = ontop of jquery object
    let elementImg = popover.getElementsByTagName("img");

    DEBUG && console.log(popover)
    DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight)
    DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight)
    DEBUG && console.log(elementImg)
    if (elementImg) {
        DEBUG && console.log(elementImg)
    }


    DEBUG && console.group("rects")
    DEBUG && console.log(nativElement.getBoundingClientRect())
    DEBUG && console.log(parentElement.getBoundingClientRect())
    DEBUG && console.groupEnd("rects")
    const parentRect = parentElement.getBoundingClientRect();
    const nativRect = nativElement.getBoundingClientRect();
    const popoverRect = popover.getBoundingClientRect();
    X = scrollPosX;
    Y = scrollPosY;
    if (isOnIndex) //show next to table cell
    {
        X += parentRect.left + parentRect.width;
        Y += parentRect.top;
    }
    else  //show at link
    {
        X += nativRect.left + nativRect.width;
        Y += nativRect.top;
    }
    DEBUG && console.log(popoverRect)

    DEBUG && console.group("calc vertical offset");
    distanceToBottom = Y - scrollPosY + popoverRect.height - (window.innerHeight - offsetToBottomBorderY);
    //console.log("distanceToBottom: " + distanceToBottom)
    if (distanceToBottom > 0) {//bottom offset
        Y -= distanceToBottom;
    }
    //console.log("Y: " + Y + ", scrollPosY: " + scrollPosY);
    if (Y < scrollPosY + offsetToBottomBorderY) { //top offset
        Y = scrollPosY + offsetToBottomBorderY;
    }
    DEBUG && console.groupEnd("calc vertical offset");
    //console.log(popover.getBoundingClientRect())
    DEBUG && console.group("calc horizontal offset");

    const maxRightPos = scrollPosX + window.innerWidth;
    const popoverRightSide = X + popoverRect.width + offsetToRightBorderX;
    distanceToRight = popoverRightSide - maxRightPos;
    DEBUG && console.log("X: " + X + ", popoverRightSide: " + popoverRightSide +
        ", maxRightPos: " + maxRightPos +
        ", distanceToRight: " + distanceToRight +
        ", popoverRect.width: " + popoverRect.width + ", scrollPosX: " + scrollPosX);
    if (distanceToRight > 0) {
        X -= distanceToRight + offsetToRightBorderX;
    }
    /*
    if (X < scrollPosX + offsetToRightBorderX) {
        X = scrollPosX + offsetToRightBorderX;
    }
    */
    DEBUG && console.groupEnd("calc horizontal offset");

    popover.style.top = Y + 'px';
    popover.style.left = X + 'px';

    const popoverHeight = offsetToBottomBorderY * 2;
    const popoverWidth = offsetToRightBorderX * 2;

    popover.style.height = "calc(100% - " + popoverHeight + "px)";
    popover.style.width = "calc(100% - " + popoverWidth + "px)";

    DEBUG && console.log(popover.getBoundingClientRect())
    DEBUG && console.log("window.innerHeight: " + window.innerHeight + ", window.innerWidth: " + window.innerWidth +
        ", maxRightPos: " + maxRightPos + ", popoverHeight: " + popoverHeight)
    //popover.show();
    showPopOver();
    DEBUG && console.groupEnd("popupPos")
    //console.log("final popup position "+X+' # '+Y);
    return this;
};
popupPosBackup = function (event, nativElement) {
    const DEBUG = false;
    DEBUG && console.group("popupPos style:" + style)


    let offsetToBottomBorderY = 22; //offset to bottom border
    let offsetToRightBorderX = 5; //offset to right border
    let X, Y;
    let hoveredSelectedPosX, hoveredSelectedPosY;
    let distanceToBottom, distanceToRight;
    //console.log(element.parents()[0])
    DEBUG && console.log(nativElement)
    //console.log(nativElement.parentElement)
    let parentElement = nativElement.parentElement;
    //let computedFontSizeJquery = parseInt(window.getComputedStyle(element.parents()[0]).fontSize);
    const computedFontSize = parseInt(window.getComputedStyle(parentElement).fontSize);
    //console.log(computedFontSize);

    //Initialising variables (multiple usages)
    let scrollPosY = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop;
    let scrollPosX = window.scrollX || window.scrollLeft || document.getElementsByTagName("html")[0].scrollLeft;
    // console.log(scrollPosX)
    const scrollTop = scrollPosY; // windowCached.scrollTop();
    const scrollLeft = scrollPosX; //windowCached.scrollLeft();
    //var elementPopup = this[0]; //this = ontop of jquery object
    let elementPopup = popover;
    let elementImg = elementPopup.getElementsByTagName("img");

    DEBUG && console.log(popover)
    DEBUG && console.log(elementPopup)
    DEBUG && console.log("elementPopup.offsetHeight: " + elementPopup.offsetHeight)
    DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight)
    DEBUG && console.log(elementImg)
    if (elementImg) {
        DEBUG && console.log(elementImg)
    }
    //var elementParentOffset = element.parents().offset();
    let elementParentOffset = {
        top: getOffset(parentElement),
        left: getOffset(parentElement, true)
    }

    DEBUG && console.log(elementParentOffset)

    //var elementParentOuterHeight = element.parents().outerHeight();
    //var elementParentOuterWidth = element.parents().outerWidth();
    let elementParentOuterHeight = parentElement.offsetHeight;
    let elementParentOuterWidth = parentElement.offsetWidth;

    //var elementOffset = element.offset();
    let elementOffset = {
        top: getOffset(nativElement),
        left: getOffset(nativElement, true)
    }
    console.log(nativElement.getBoundingClientRect())
    console.log("elementParentOuterWidth: " + elementParentOuterWidth)
    console.log(parentElement.getBoundingClientRect())
    console.log(elementOffset)
    //var elementOuterHeight = element.outerHeight();
    let elementOuterHeight = nativElement.offsetHeight;
    //var elementOuterWidth = element.outerWidth();
    //var elementRect = element[0].getBoundingClientRect();
    //var elementParentRect = element.parents()[0].getBoundingClientRect();
    if (style == 1) //index: position next to parent table cell (SELECTOR1)
    {
        hoveredSelectedPosX = elementParentOffset.left + elementParentOuterWidth; //link position + tablecell width; + elementOuterWidth;
        hoveredSelectedPosY = elementParentOffset.top + computedFontSize; //link position + tablecell height; + elementOuterHeight;
        //console.log("height " + elementParentOuterHeight  + ' - ' + elementOuterHeight);
    } else if (style == 2) //recommendations: position next to link height and parent (SELECTOR2) width
    {
        hoveredSelectedPosX = elementOffset.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;
    console.log("hoveredSelectedPos x: " + X + ", y: " + Y)

    // Distance to the right
    distanceToRight = window.innerWidth - (X - scrollLeft);
    DEBUG && console.log("distanceToRight: " + distanceToRight + ", elementPopup.outerWidth: " + elementPopup.offsetWidth)
    // Tooltip too close to the right?
    if (distanceToRight < elementPopup.offsetWidth - offsetToRightBorderX)
        X += distanceToRight - elementPopup.offsetWidth - offsetToRightBorderX; //elementPopup

    //console.log(document.body.clientHeight)
    DEBUG && console.log("window.innerHeight: " + window.innerHeight)
    // Distance to the bottom
    distanceToBottom = window.innerHeight - (Y - scrollTop);
    DEBUG && console.log("distanceToBottom: " + distanceToBottom)
    DEBUG && console.log("elementPopup.offsetHeight: " + elementPopup.offsetHeight)
    DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight)
    DEBUG && console.log("parentElement.offsetHeight: " + parentElement.offsetHeight)
    let imgElement = elementPopup.getElementsByTagName("img")[0]
    DEBUG && console.log(imgElement)
    //console.log(elementPopup)

    // Tooltip too close to the bottom?
    if (distanceToBottom < elementPopup.offsetHeight - offsetToBottomBorderY) {
        Y += distanceToBottom - elementPopup.offsetHeight - offsetToBottomBorderY;

        const diffH = window.innerHeight - Y;
        console.log("Y: " + Y + ", diffH: " + diffH + ", window.innerHeight: " + window.innerHeight, "+, elementPopup.getBoundingClientRect().y: ")

        if (Y < 0) {
            Y = 0;
        }
    }

    //console.log("Distance to the bottom " + distanceToBottom+" elementPopupHeight " +elementPopup.outerHeight()+ "\nDistance to the right " + distanceToRight+ " elementPopupouterWidth " +elementPopup.outerWidth());

    elementPopup.style.top = Y + 'px';
    elementPopup.style.left = X + 'px';
    console.log(popover.getBoundingClientRect())
    //popover.show();
    showPopOver();
    DEBUG && console.groupEnd("popupPos")
    //console.log("final popup position "+X+' # '+Y);
    return this;
};

//.wpb_wrapper a = title links on individual seriepage

//http://youmightnotneedjquery.com/
//https://latteandcode.medium.com/vanilla-js-addeventlistener-queryselector-and-closest-e7e5503b6418
function changeStyleType(styleType) {
    style = styleType;
}

changeStyleType(2);
async function parseSeriePage(elementUrl, title, event) {
    const DEBUG = false;
    DEBUG && console.group("parseSeriePage: " + elementUrl)
    let retrievedImgLink;
    let PromiseResult = new Promise(async function (resolve, reject) {
        DEBUG && console.log("elementUrl: " + elementUrl)
        DEBUG && console.log(elementUrl)
        const coverData = GM_getCachedValue(elementUrl);

        //DEBUG && console.log("elementUrl: " + elementUrl);
        //DEBUG && console.log("retrievedImgLink cache value: " + retrievedImgLink)

        if (coverData !== null) {//retrievedImgLink !== null || retrievedImgLink!==undefined && 
            //currentTitelHover = coverData.title;
            DEBUG && console.log(coverData)
            retrievedImgLink = coverData.url;
            DEBUG && console.log("parseSeriePage has cached retrievedImgLink: " + retrievedImgLink)
            return resolve(coverData);
            //resolve(retrievedImgLink);
        }
        else {
            // DEBUG && console.log(coverData)
            DEBUG && console.log(" - retrievedImgLink cache empty. make ajax request try to save image of page into cache: " + elementUrl);

            function onLoad(xhr) {

                const domDocument = xhr.response;
                //const parser = new DOMParser();
                // const domDocument = parser.parseFromString(xhr.responseText, 'text/html');
                DEBUG && console.log(domDocument);

                try {
                    DEBUG && console.group("parseSeriePage onLoad: " + title)
                    if (!domDocument || domDocument === undefined) {
                        console.log(xhr);
                        console.log(xhr.response);
                        console.log(domDocument)
                    }

                    const temp = domDocument.querySelectorAll(IMAGELINKCONTAINERS);
                    const imageLinkByTag = temp[0].getElementsByTagName("img");
                    const imagelink = imageLinkByTag[CONTAINERNUMBER].getAttribute("src");
                    const serieTitle = domDocument.querySelector(".seriestitlenu").textContent;

                    DEBUG && console.log(serieTitle)
                    DEBUG && console.log('save imageUrl as retrievedImgLink ' + imagelink);
                    let cData = { url: imagelink, title: serieTitle };
                    retrievedImgLink = imagelink;
                    //currentTitelHover = serieTitle;
                    GM_setCachedValue(elementUrl, cData); //cache imageurl link
                    DEBUG && console.groupEnd("parseSeriePage onLoad")
                    return resolve(cData);
                    //resolve(imagelink);

                } catch (error) {
                    console.log("error: GM_xmlhttpRequest can not get xhr.response")
                    console.log(error);
                    // showPopupLoadingSpinner(serieTitle, 1);
                    return reject(elementUrl);
                }

            }

            function onError() {
                const err = new Error('GM_xmlhttpRequest could not load ' + elementUrl + "; url does not exist?");
                console.log(err);
                return reject(err);
            }

            GM_xmlhttpRequest({
                method: "GET",
                responseType: 'document',
                url: elementUrl,
                onload: onLoad,
                onerror: onError,
            });
        }
    });
    DEBUG && console.log(PromiseResult)

    if (retrievedImgLink) {
        DEBUG && console.log("has retrievedImgLink: " + retrievedImgLink)
    }
    else {
        DEBUG && console.log("has retrievedImgLink: " + retrievedImgLink)
        if (currentTitelHover == title) {
            //console.log(PromiseResult)
            //console.log("showPopupLoadingSpinner parseSeriePage: " + title)
            showPopupLoadingSpinner(title, event);
            //console.log("showPopupLoadingSpinner parseSeriePage after showPopupLoadingSpinner function: " + title)
        }
    }
    await PromiseResult;
    DEBUG && console.log(PromiseResult)
    //after GM_xmlhttpRequest PromiseResult
    DEBUG && console.groupEnd("parseSeriePage: " + elementUrl)
    return PromiseResult;
}

main();

function checkDataVersion() {
    //Remove possible incompatible old data
    const DEBUG = false;
    const dataVersion = GM_getValue("version", null)
    DEBUG && console.log("dataVersion: " + dataVersion)

    if (dataVersion === null || dataVersion != version || forceUpdate) {
        const oldValues = GM_listValues();
        DEBUG && console.log("oldValues.length: " + oldValues.length)
        for (let i = 0; i < oldValues.length; i++) {
            GM_deleteValue(oldValues[i]);
            //console.log(oldValues[i])
        }
        DEBUG && console.log(oldValues);
        GM_setValue("version", version);
    }
}

function main() {
    checkDataVersion();

    // const DEBUG = false;

    loadStyleSheets();
    createPopover();

    function uniq(a) {
        return Array.from(new Set(a));
    }

    if (preloadUrlRequests) {
        var getNovelLinks = function () {
            const links = Array.from(
                document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]')
            );

            return links;
        };
        const links = getNovelLinks();
        const novelUrlList = links.map(function (el) {
            // console.log('novelUrlList ' + el.href);
            return el.href;
        });

        const uniqueNovelUrlList = uniq(novelUrlList);
        /*
        console.group("uniqueNovelUrlList")
        console.log(uniqueNovelUrlList);
        console.groupEnd()
        */
        /*
        const imageUrlList = uniqueNovelUrlList.map(function (elementUrl) {
            parseSeriePage(elementUrl)
                .then(function (imgUrl) {
                    if (preloadImages) {
                        let img = document.createElement("img"); //put img into dom. Let the image preload in background
                        img.src = imgUrl;
                        DEBUG && console.log("onpageload cache init " + imgUrl);
                    }
                }, function (Error) {
                    DEBUG && console.log(Error + ' failed to fetch ' + elementUrl);
                });
            // console.log("imageUrlList " + elementUrl);
        });
        */
    }
}

function loadStyleSheets() {
    //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 the loading spinner
    GM_addStyle(`
            @keyframes rotate {
                    to {transform: rotate(360deg);}
                }
                
            @keyframes dash {
                0% {
                stroke-dasharray: 1, 150;
                stroke-dashoffset: 0;
                }
                50% {
                stroke-dasharray: 90, 150;
                stroke-dashoffset: -35;
                }
                100% {
                stroke-dasharray: 90, 150;
                stroke-dashoffset: -124;
                }
            }
        
            .popoverContent {  
                display:flex;
                position: relative;
                width: 100%;
                height: 100%;  
                border: 1px solid #000;
                text-align: center !important;
                justify-content: center;
                justify-items: center;
                background-color:#ccc;
                align-items: center;


                min-height:0;
                min-width:0;
                /*max-height:inherit;
                max-width:inherit;
                height:100%;
                width:100%;*/
                flex:1;
                padding:1px;
            }
            .spinnerRotation{
                animation: rotate 2s linear infinite;
            }
            .spinner {
                /*
                z-index: 2;
                position: absolute;
                top: 0;
                left: 0;
                margin: 0;*/
                width: 100%;
                height: 100%;
            }
            
            .spinner .path{
                stroke: hsl(210, 70%, 75%);
                stroke-linecap: round;
                animation: dash 1.5s ease-in-out infinite;
            }

            .blackFont {
                color:#000;
            }
            .whiteFont {
                color:#fff
            }
            .defaultTitleStyle {
                color:#fff;
                padding:5px 0;
                height:auto;
                display:inline-block;
                width:100%;
                max-width:auto;
                text-align:center !important;
                justify-content: center;
                justify-items: center;
                border-radius:8px 8px 0 0;
            }
            .defaultBackgroundStyle {
                align-items:center;
                pointer-events:none; 
                width:100%; 
                height:100%; 
                max-width:100%; 
                max-height:100%;
                text-align:center !important;
                justify-content: center;
                justify-items: center;
            }
            .ImgFitDefault{
                object-fit: contain;
                width:100%;
                height:100%;
            }

            #popover{
                height:100%;
                width:100%;
                margin:0 0 22px 0;
                border: 1px solid #000;
                border-radius:10px 10px 5px 5px;
                position:absolute;
                z-index:10;
                box-shadow: 0px 0px 5px #7A7A7A;

                display: flex;
                flex-direction: column;
                text-align: center !important;
                justify-content: center;
                justify-items: center;
            }
            .popoverTitle{
                height:auto;
                display:inline-block;
                width:100%;
                max-width:auto;
                
            }
            .popoverCoverImg{
                /*min-height:0;
                min-width:0;
                max-height:inherit;
                max-width:inherit;
                height:100%;
                width:100%;*/
                flex:0;
                padding:5px;
            }

            `);
    function styleSheetContainsClass(f) {
        var localDomainCheck = '^http://' + document.domain;
        var localDomainCheckHttps = '^https://' + document.domain;
        // DEBUG && console.log("Domain check with: " + localDomainCheck);
        var hasStyle = false;
        var stylename = f;
        var fullStyleSheets = document.styleSheets;
        // DEBUG && console.log("start styleSheetContainsClass " + stylename);
        if (fullStyleSheets) {
            for (let i = 0; i < fullStyleSheets.length - 1; i++) {
                //DEBUG && console.log("loop fullStyleSheets " + stylename);
                let styleSheet = fullStyleSheets[i];
                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) || styleSheet.href.match(localDomainCheckHttps)) {
                            if (styleSheet.cssRules) {
                                //DEBUG && console.log("styleSheet.cssRules.length: " + styleSheet.cssRules.length)
                                for (let rulePos = 0; rulePos < styleSheet.cssRules.length - 1; rulePos++) {
                                    if (styleSheet.cssRules[rulePos] !== undefined) {
                                        // DEBUG && console.log("styleSheet.cssRules[rulePos] "+ stylename);
                                        if (styleSheet.cssRules[rulePos].selectorText) {
                                            //  console.log(styleSheet.cssRules[rulePos].selectorText)
                                            if (styleSheet.cssRules[rulePos].selectorText == stylename) {
                                                // console.log('styleSheet class has been found - class: ' + stylename);
                                                hasStyle = true; //break;
                                                break; //return hasStyle;
                                            }
                                        }  //else DEBUG && console.log("undefined styleSheet.cssRules[rulePos] "+rulePos +" - "+ stylename);
                                    }
                                    //else DEBUG && console.log("loop undefined styleSheet.cssRules[rulePos] "+ stylename);
                                }
                            } //else DEBUG && console.log("undefined styleSheet.cssRules "+ stylename);
                        }
                    //   DEBUG && console.log("stylesheet url " + styleSheet.href);
                } //else DEBUG && console.log("undefined styleSheet "+ stylename);
                if (hasStyle) break;
            }
        } //else console.log("undefined fullStyleSheets=document.styleSheets "+ stylename);
        if (!hasStyle)
            console.log("styleSheet class has not been found - style: " + stylename);
        return hasStyle;
    }

    if (STYLESHEETHIJACKFORBACKGROUND !== "")
        if (!styleSheetContainsClass(STYLESHEETHIJACKFORBACKGROUND))
            STYLESHEETHIJACKFORBACKGROUND = "";
        else {
            //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll
            //works only from firefox 77
            //STYLESHEETHIJACKFORBACKGROUND = STYLESHEETHIJACKFORBACKGROUND.replaceAll(".", " ").trim()
            STYLESHEETHIJACKFORBACKGROUND = STYLESHEETHIJACKFORBACKGROUND.replace(/\./g, ' ').trim();
        }
    if (STYLESHEETHIJACKFORTITLE !== "")
        if (!styleSheetContainsClass(STYLESHEETHIJACKFORTITLE))
            STYLESHEETHIJACKFORTITLE = "";
        else {
            //STYLESHEETHIJACKFORTITLE = STYLESHEETHIJACKFORTITLE.replaceAll(".", " ").trim()
            STYLESHEETHIJACKFORTITLE = STYLESHEETHIJACKFORTITLE.replace(/\./g, ' ').trim();
        }
}

function createPopover() {
    let bodyElement = document.getElementsByTagName("BODY")[0];

    popover = document.createElement("div");
    popover.id = "popover";

    popoverTitle = document.createElement("header");
    popoverContent = document.createElement("content");
    popoverCoverImg = document.createElement("coverImg");
    popover.appendChild(popoverTitle);
    popover.appendChild(popoverContent);
    //popover.appendChild(popoverCoverImg);


    popover.className = (STYLESHEETHIJACKFORBACKGROUND + ' defaultBackgroundStyle').trim();
    popoverContent.className = "popoverContent blackFont";


    popover.style.maxHeight = defaultHeight;
    popover.style.maxWidth = defaultHeight;
    popover.style.backgroundColor = DEFAULTBACKGROUNDCOLOR;
    //console.log(popover)
    //console.log(popover.style)
    popoverTitle.className = (STYLESHEETHIJACKFORTITLE + ' defaultTitleStyle').trim();
    popoverTitle.style.backgroundColor = DEFAULTTITLEBACKGROUNDCOLOR;

    popoverCoverImg.className = "popoverCoverImg";


    bodyElement.insertAdjacentElement("beforeend", popover);
}


function stylesheetForTitle() {
    if (STYLESHEETHIJACKFORTITLE !== "")
        return 'class="' + STYLESHEETHIJACKFORTITLE + ' defaultTitleStyle"';
    else
        return 'class="defaultTitleStyle" style="background-color:' + DEFAULTTITLEBACKGROUNDCOLOR + '"';
}

function stylesheetForBackground() {
    if (STYLESHEETHIJACKFORBACKGROUND !== "")
        return 'class="' + STYLESHEETHIJACKFORBACKGROUND + ' defaultBackgroundStyle"';
    else
        return 'class="defaultBackgroundStyle" style="background-color:' + DEFAULTBACKGROUNDCOLOR + '"';
}

function showPopupLoadingSpinner(title, event, notification = "") {
    const DEBUG = false;
    if (currentTitelHover == title) {
        // console.group("showPopupLoadingSpinner")
        //popover.empty();
        //popover.innerHTML = "";
        DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
        popoverTitle.textContent = title;
        //popoverTitle.innerHTML = '<svg class="spinner" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M9.00967 5.12761H11.0097C12.1142 5.12761 13.468 5.89682 14.0335 6.8457L16.5089 11H21.0097C21.562 11 22.0097 11.4477 22.0097 12C22.0097 12.5523 21.562 13 21.0097 13H16.4138L13.9383 17.1543C13.3729 18.1032 12.0191 18.8724 10.9145 18.8724H8.91454L12.4138 13H5.42485L3.99036 15.4529H1.99036L4.00967 12L4.00967 11.967L2.00967 8.54712H4.00967L5.44417 11H12.5089L9.00967 5.12761Z"  fill="currentColor" /><circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle></svg>';

        popoverCoverImg.innerHTML = "";

        if (notification != "") {
            popoverContent.innerHTML = notification;
            popoverContent.className = "popoverContent blackFont";

        }
        else {
            popoverContent.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">     
            <g transform="translate(25, 25)">         
            <circle class="" cx="0" cy="0" r="25" fill="black" stroke-width="5" />
            <circle class="path" cx="0" cy="0" r="23" fill="none" stroke-width="5">
                <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0" to="360"  dur="1.6s" repeatCount="indefinite" />
            </circle>
            </g>
            <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" style="fill:#fff;font-size:11px">Loading </text> 
        </svg>`

            //popoverContent.innerHTML = '<div class="forground" style="z-index: 3;">Loading Data</div><svg class="spinner" viewBox="0 0 50 50"><circle class="" cx="25" cy="25" r="22" fill="black" stroke-width="5"></circle><circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle></svg>';
            popoverContent.className = "popoverContent whiteFont";
        }
        DEBUG && console.log(popover)
        //   DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
        popupPos(event);
        //  console.groupEnd("showPopupLoadingSpinner")
    }

}

function refreshPopover(title, link, e) {
    const DEBUG = false;
    /*	clear popup
    *	append title and image into popup
    *	when img loading is finished reposition (popupPos) to element/border
    */
    DEBUG && console.log("currentTitelHover: " + currentTitelHover)
    if (currentTitelHover == title) //popup only gets refreshed when currentTitelHover == title
    {
        DEBUG && console.group("refreshPopover");

        if (inBlocklist(link)) {
            popoverTitle.text = title;
            popoverContent.innerHTML = "Blocked Image<br />No Cover Image<br />Unwanted Image";
            //popoverContent.className = "";
            //   popupPos(e, element, style, nativElement);

        } else {
            let imgElement = new Image();//document.createElement("img");
            imgElement.src = link;
            //   let aspectRatio = imgElement.naturalWidth / imgElement.naturalHeight;
            //  DEBUG && console.log("aspectRatio: " + aspectRatio)
            popoverTitle.textContent = title;
            //popoverContent.innerHTML = "";
            // popoverContent.className = "";

            //   if (aspectRatio <= 1)
            {
                //popoverCoverImg.innerHTML = '<img src="' + link + '" class="ImgFitDefault imgFitHeight" ></img>';
                popoverContent.innerHTML = '<img src="' + link + '" class="ImgFitDefault" ></img>';
            }
            /*
                                else {
                                    //popoverCoverImg.innerHTML = '<img src="' + link + '" class="ImgFitDefault imgFitWidth" style="width:' + defaultHeight + '; "></img>';
                                    popoverContent.innerHTML = '<img src="' + link + '" class="ImgFitDefault" style="width:' + defaultHeight + '; "></img>';
                                }*/

        }
        DEBUG && console.groupEnd("refreshPopover");
        //if (currentTitelHover == title)
        popupPos(e);
    }


};

function ajaxLoadImageUrlAndShowPopup(elementUrl, title, e) {
    const DEBUG = false;
    //console.log("mouseenter")
    // console.group("ajaxLoadImageUrlAndShowPopup")
    return parseSeriePage(elementUrl, title, e)
        .then(function (coverData) {
            //GM_getCachedValue
            DEBUG && console.group("ajaxLoadImageUrlAndShowPopup after parseSeriePage")

            DEBUG && console.log(coverData)
            const imgUrl = coverData.url;
            let serieTitle = title;
            if (!title)  //pure link without title get title of seriepage
                serieTitle = coverData.title;
            DEBUG && console.log("elementUrl: " + elementUrl)
            DEBUG && console.log("imgUrl: " + imgUrl);
            DEBUG && console.log("title: " + title + ", serieTitle: " + serieTitle)
            loadImageFromBrowser(elementUrl, serieTitle, imgUrl, e)

            DEBUG && console.groupEnd("ajaxLoadImageUrlAndShowPopup after parseSeriePage")
        }, function (Error) {
            DEBUG && console.log(Error + ' failed to fetch ' + elementUrl);
        });
    // console.groupEnd("ajaxLoadImageUrlAndShowPopup")
};
function mouseEnterPopup(e) {

    if (!e.target.matches(concatSelector())) return;
    const DEBUG = false;
    DEBUG && console.group("mouseEnterPopup")
    //let element = undefined;//$(this);
    //let nativElement = e.target//this;
    //console.log(this)
    //console.log(e.target)
    let Href = this.href;// element.attr('href');
    if (Href.search(INDIVIDUALPAGETEST) != -1) //only trigger for links that point to serie pages
    {
        //popup loading spinner


        //async wait until image is loaded before refreshing popup


        //console.log(this)
        // console.log(this.text) //shortTitle
        //  console.log(this.title) //LongTitle
        let shortSerieTitle = this.text; //element.text(); //get linkname
        //console.log(this)
        //console.log(shortSerieTitle)

        //move native title to custom data attribute. Suppress nativ title popup
        if (!this.getAttribute('datatitle')) {
            this.setAttribute('datatitle', this.getAttribute('title'));
            this.removeAttribute('title');
        }

        let serieTitle = this.getAttribute('datatitle');//element.attr('datatitle'); //try to get nativ title if available from datatitle
        //console.log(serieTitle)
        if (serieTitle === null || serieTitle == "null") //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

        //console.log(serieTitle)
        //console.log(Href)
        const coverData = GM_getCachedValue(Href);

        if ((currentTitelHover === null || currentTitelHover == "null") && coverData !== null) {
            //console.log(coverData)
            currentTitelHover = coverData.title;
        }
        ajaxLoadImageUrlAndShowPopup(Href, currentTitelHover, e);

    }
    DEBUG && console.groupEnd("mouseEnterPopup")
}

function loadImageFromBrowser(elementUrl, serieTitle, imgUrl, e) {
    const DEBUG = false;
    //console.group("loadImageFromBrowser")
    //let imageLoaded = false;
    let img = document.createElement("img"); //put img into dom. Let the image preload in background
    const isActivePopup = (currentTitelHover == serieTitle);

    img.onload = () => {
        DEBUG && console.group("loadImageFromBrowser img.onload: " + serieTitle)
        DEBUG && console.log("imgurl: " + imgUrl);

        //DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight);
        // GM_setCachedValue(elementUrl, imgUrl); //cache imageurl link
        DEBUG && console.log(elementUrl + " url has been found and is written to temporary cache.\n" + imgUrl + ' successfully cached.'); // for testing purposes                      
        // imageLoaded = true;
        //  if (img.complete)
        DEBUG && console.log("loaded img.src: " + img.src + ", currentTitelHover: " + currentTitelHover + ", isActivePopup: " + isActivePopup)
        if (isActivePopup) {
            DEBUG && console.log("refreshPopover")
            refreshPopover(serieTitle, imgUrl, e); //popup only gets refreshed when currentTitelHover == serieTitle      
        }
        DEBUG && console.groupEnd("loadImageFromBrowser img.onload")
    };

    img.onerror = (error) => {
        console.group("loadImageFromBrowser img.onerror: " + serieTitle)
        console.log(error);
        const errorMessage = "browser blocked/has error loading the file: <br />" + decodeURIComponent(error.target.src);
        console.log(errorMessage)
        console.log("look in the developer console if 'net::ERR_BLOCKED_BY_CLIENT' is displayed or manually check if the imagelink still exists");
        if (isActivePopup)
            showPopupLoadingSpinner(serieTitle, e, errorMessage);
        console.groupEnd("loadImageFromBrowser img.onerror")
    }
    img.src = imgUrl;
    DEBUG && console.log(img.src)
    if (!img.complete && isActivePopup)  //if image not available/cached in browser show loading pinner
    {
        DEBUG && console.log("loadImageFromBrowser image not completely loaded yet. Show loading spinner : " + serieTitle)
        showPopupLoadingSpinner(serieTitle, e);
    }
    if (img.complete) {
        DEBUG && console.log("loadImageFromBrowser preload completed: " + serieTitle)
        DEBUG && console.log(img.src)
    }
    // console.log("imageLoaded: " + imageLoaded)
    // console.groupEnd("loadImageFromBrowser")
    // return imageLoaded;
}


//when selected link is entered load imageurl and write popover content
concatenatedSelectorAll.forEach(function (selector) {
    selector.addEventListener("mouseenter", mouseEnterPopup)
})

function hidePopOver() {
    popover.style.visibility = "hidden";
    currentTitelHover = undefined;
}
function showPopOver() {
    // popover.style.display = "flex";
    popover.style.visibility = "visible";
}
function hideOnMouseLeave(e) {
    if (!e.target.matches(concatSelector())) return;
    //popover.hide();
    hidePopOver();
}
//hide and empty popup when mouse is not over title
concatenatedSelectorAll.forEach(function (selector) {
    selector.addEventListener("mouseleave", hideOnMouseLeave)
})

window.addEventListener("blur", hidePopOver);

window.onunload = function () {
    window.removeEventListener("blur", hidePopOver);


    //possible memoryleaks?
    concatenatedSelectorAll.forEach(function (selector) {
        selector.removeEventListener("mouseleave", hideOnMouseLeave)
    })
    concatenatedSelectorAll.forEach(function (selector) {
        selector.removeEventListener("mouseenter", mouseEnterPopup)
    })



}

QingJ © 2025

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