mangaupdates Cover Preview

Previews covers in mangaupdates.com when hovering over hyperlinks that lead to serie pages.

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

// ==UserScript==
// https://gf.qytechs.cn/scripts/26439-novelupdates-cover-preview/
// @name        mangaupdates Cover Preview
// @namespace   szMangaupdatesCoverPreview
// @include     https://www.mangaupdates.com/*
// @include     http://www.mangaupdates.com/*
// @version     1.6.4
// @description Previews covers in mangaupdates.com when hovering over hyperlinks that lead to serie 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 3 day in milliseconds  //days * h * min  * sec * ms
let STYLESHEETHIJACKFORBACKGROUND = ".frow2"; //if unknown set empty ""; classname with leading dot
let STYLESHEETHIJACKFORTITLE = '.small_bold, .side_content_row'; //if unknown set empty ""; classname with leading dot seperated by comma
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 PREDIFINEDNATIVTITLE = "Click for series info, Series Info"; //forum, index
const INDIVIDUALPAGETEST = "series.html?id=";  // /series\.html\?id\=[0-9]*$/;
const IMAGELINKCONTAINERS = '.sContent img'; //instead of single element class name with dot
const IMAGEBLOCKER = "images/stat_increase.gif, images/stat_decrease.gif"; //tested with string.match(). no need for prefixed http https in url. Can even be just the file name
const CONTAINERNUMBER = 0;
const seriePageTitle = ".releasestitle " //.seriestitlenu
const seriePageVotes = "#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(35)" //.seriesother > .uvotes
const seriePageStatus = "#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(20)" //#editstatus
const seriePageGenre = "#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(5)" //#seriesgenre
const seriePageTags = "#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(8) ul" //#showtags
const isOnIndex = this.location.href == "https://www.mangaupdates.com/" ||
    this.location.href.startsWith("https://www.mangaupdates.com/releases.html") ||
    this.location.href.startsWith("https://www.mangaupdates.com/groups.html?") ||
    this.location.href.startsWith("https://www.mangaupdates.com/mylist.html") ||
    this.location.href.startsWith("https://www.mangaupdates.com/authors.html?id=")

const preloadUrlRequests = false;
const preloadImages = false;
const eventListenerStyle = 0; //undefined/0 forEach serieLink addeventlistener(mouseenter/mouseleave) / 1 window addeventlistener(mousemove)

const isOnReadingListIndex = this.location.href.startsWith("https://www.novelupdates.com/user/");
//to know when to switch between popup next to link or next to container of link
//^^^^	frontend settings over this line	^^^^
const version = "1.6.4";
const forceUpdate = false;
const debugCSSClasses = false; //log if css class is accessible else default include
const lastUpdateCheck = 28 * 24 * 60 * 60 * 1000; //recheck if CSS available

const maxWaitingTime = 120;
const RE = /\s*,\s*/; //Regex for split and remove empty spaces
const REGEX_DOTCOMMA = /[.,]/g;
const defaultHeight = "400"; //in pixel
const IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE);
const PREDIFINEDNATIVTITLEARRAY = PREDIFINEDNATIVTITLE.split(RE);
const STYLESHEETHIJACKFORBACKGROUNDARRAY = STYLESHEETHIJACKFORBACKGROUND.split(RE);
const STYLESHEETHIJACKFORTITLEARRAY = STYLESHEETHIJACKFORTITLE.split(RE);
let refreshInitValues = false;
let showDetails = false;
let ALLSERIENODES;// = document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]');
const offsetToBottomBorderY = 22; //offset to bottom border
const offsetToRightBorderX = 10; //offset to right border
let currentTitelHover, currentCoverData, currentPopupEvent;
let popover, popover2, popoverTitle, popoverContent, popoverCoverImg;

//console.log(this.location)
//console.log(this.location.href)

//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,
                votes: coverData.votes,
                status: coverData.status,
                genre: coverData.genre,
                showTags: coverData.showTags
            };
        }
        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,
        votes: coverData.votes,
        status: coverData.status,
        genre: coverData.genre,
        showTags: coverData.showTags,
        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) {
    //console.log(link)
    if (IMAGEBLOCKERARRAY) {
        const hasBlocker = IMAGEBLOCKERARRAY.includes(link);
        if (hasBlocker) return true;
        //console.log(hasBlocker);
    }

    return false;
}

//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);
}

function getRectOffset(rect) {
    return { Rx: rect.left + rect.width, Ry: rect.top }
}
function chooseAndGetRectOffset(nativElement) {
    let targetedRect;
    if (isOnIndex || isOnReadingListIndex) {
        targetedRect = nativElement.parentElement.getBoundingClientRect();
    }
    else {
        targetedRect = nativElement.getBoundingClientRect();
    }
    return getRectOffset(targetedRect);
}
function getDistanceToBottom(Y, scrollPosY, popoverRect) {
    return Y - scrollPosY + popoverRect.height - (window.innerHeight - offsetToBottomBorderY);
}
function getPopupPos(event) {
    const DEBUG = false;

    const scrollPosY = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop;
    const scrollPosX = window.scrollX || window.scrollLeft || document.getElementsByTagName("html")[0].scrollLeft;
    //console.log(event)
    const nativElement = event.target;
    const parentElement = nativElement.parentElement;

    let X, Y;
    let distanceToBottom, distanceToRight;

    //console.log(element.parents()[0])
    DEBUG && console.log(nativElement)

    X = scrollPosX;
    Y = scrollPosY;

    DEBUG && console.group("rects")
    DEBUG && console.log(nativElement.getBoundingClientRect())
    DEBUG && console.log(parentElement.getBoundingClientRect())
    DEBUG && console.groupEnd("rects")
    const popoverRect = popover.getBoundingClientRect();
    const { Rx, Ry } = chooseAndGetRectOffset(nativElement);
    X += Rx;
    Y += Ry;
    DEBUG && console.log(popoverRect)

    DEBUG && console.group("calc vertical offset");
    distanceToBottom = getDistanceToBottom(Y, scrollPosY, popoverRect);
    //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");
    return { Px: X, Py: Y }
}
// popupPositioning function
function popupPos(event) {
    const DEBUG = false;
    DEBUG && console.group("popupPos style:" + style)
    showPopOver();

    //let computedFontSizeJquery = parseInt(window.getComputedStyle(element.parents()[0]).fontSize);
    //const computedFontSize = parseInt(window.getComputedStyle(parentElement).fontSize);
    //console.log(computedFontSize);

    // console.log(scrollPosX)
    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)
    }


    const { Px, Py } = getPopupPos(event)


    popover.style.top = Py + 'px';
    popover.style.left = Px + 'px';

    const popoverHeightMargin = offsetToBottomBorderY * 2;
    const popoverWidthMargin = offsetToRightBorderX * 2;

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

    DEBUG && console.log(popover.getBoundingClientRect())
    DEBUG && console.log("window.innerHeight: " + window.innerHeight + ", window.innerWidth: " + window.innerWidth +
        ", maxRightPos: " + maxRightPos + ", popoverHeightMargin: " + popoverHeightMargin)


    DEBUG && console.groupEnd("popupPos")
    //console.log("final popup position "+X+' # '+Y);
    return this;
};

async function parseSeriePage(elementUrl, hoveredTitle = undefined, event = undefined) {
    const DEBUG = false;
    DEBUG && console.group("parseSeriePage: " + elementUrl)
    let retrievedImgLink;
    let PromiseResult = new Promise(async function (resolve, reject) {
        DEBUG && console.log("elementUrl: " + 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: " + hoveredTitle)
                    if (!domDocument || domDocument === undefined) {
                        console.log(xhr);
                        console.log(xhr.response);
                        console.log(domDocument)
                    }

                    const temp = domDocument.querySelectorAll(IMAGELINKCONTAINERS);
                    DEBUG && console.log(temp)
                    /*
                    const imageLinkByTag = temp.getElementsByTagName("img");
                    console.log(imageLinkByTag)*/
                    let imagelink = temp[CONTAINERNUMBER]
                    if (imagelink !== undefined)
                        imagelink = imagelink.getAttribute("src");
                    let serieTitle = domDocument.querySelector(seriePageTitle);
                    let serieVotes = domDocument.querySelector(seriePageVotes);
                    let serieStatus = domDocument.querySelector(seriePageStatus);
                    let serieGenre = domDocument.querySelector(seriePageGenre);
                    let serieShowtags = domDocument.querySelector(seriePageTags);
                    if (serieTitle && serieTitle !== undefined) serieTitle = serieTitle.textContent;
                    if (serieVotes && serieVotes !== undefined) serieVotes = serieVotes.textContent;
                    if (serieStatus && serieStatus !== undefined) serieStatus = serieStatus.textContent;
                    if (serieGenre && serieGenre !== undefined) serieGenre = serieGenre.textContent;
                    if (serieShowtags && serieShowtags !== undefined) serieShowtags = serieShowtags.textContent;

                    DEBUG && console.log(serieTitle)
                    DEBUG && console.log(serieVotes)
                    DEBUG && console.log(serieStatus)
                    DEBUG && console.log(serieGenre)
                    DEBUG && console.log(serieShowtags)
                    DEBUG && console.log('save imageUrl as retrievedImgLink ' + imagelink);
                    let cData = {
                        url: imagelink,
                        title: serieTitle,
                        votes: serieVotes,
                        status: serieStatus,
                        genre: serieGenre,
                        showTags: serieShowtags
                    };
                    retrievedImgLink = imagelink;
                    //currentTitelHover = serieTitle;
                    GM_setCachedValue(elementUrl, cData); //cache imageurl link
                    DEBUG && console.log(elementUrl + " url has been found and is written to temporary cache.\n" + imagelink + ' successfully cached.'); // for testing purposes
                    DEBUG && console.groupEnd("parseSeriePage onLoad")
                    return resolve(cData);
                    //resolve(imagelink);

                } catch (error) {
                    console.log("error: GM_xmlhttpRequest can not get xhr.response or script is not compatible")
                    console.log(error);
                    // showPopupLoadingSpinner(serieTitle, 1);
                    DEBUG && console.groupEnd("parseSeriePage onLoad")
                    return reject(elementUrl);
                }

            }

            function onError() {
                const err = new Error('GM_xmlhttpRequest could not load ' + elementUrl + "; script is not compatible or url does not exists.");
                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("retrievedImgLink still loading ")
        if (currentTitelHover !== undefined && currentTitelHover == hoveredTitle) {

            //console.log(PromiseResult)
            //console.log("showPopupLoadingSpinner parseSeriePage: " + title)
            if (event)
                showPopupLoadingSpinner(hoveredTitle, event);
            //console.log("showPopupLoadingSpinner parseSeriePage after showPopupLoadingSpinner function: " + title)
        }
    }
    DEBUG && console.groupEnd("parseSeriePage: " + elementUrl)
    await PromiseResult;
    //DEBUG && console.log(PromiseResult)
    //after GM_xmlhttpRequest PromiseResult

    return PromiseResult;
}


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 === undefined ||
        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 preloadCoverData() {
    const DEBUG = false;

    updateSerieNodes();
    DEBUG && console.log(ALLSERIENODES);

    DEBUG && console.log("preloadCoverData");
    /*
    const novelLinks = Array.from(
        ALLSERIENODES //document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]')
    );
    DEBUG && console.log(novelLinks);*/


    DEBUG && console.log("parseSeriePage for each url with a link to individual seriepage");
    ALLSERIENODES.map(function (el) {
        //console.log(el)
        const elementUrl = el.href;
        // console.log(elementUrl)
        if (eventListenerStyle === undefined || eventListenerStyle === null || eventListenerStyle == 0) {
            el.removeEventListener("mouseenter", mouseEnterPopup)
            el.removeEventListener("mouseleave", hideOnMouseLeave)
            el.addEventListener("mouseenter", mouseEnterPopup)
            el.addEventListener("mouseleave", hideOnMouseLeave)
        }

        if (preloadUrlRequests) {
            parseSeriePage(elementUrl).then(function (coverData) {
                if (preloadImages) {
                    console.log("preloadCoverData preloadImages: " + preloadImages)
                    /*
                    let img = document.createElement("img"); //put img into dom. Let the image preload in background
                    img.onload = () => {
                        DEBUG && console.log("onpageload cache init previewImage " + coverData.url);
                    }
                    img.src = coverData.url
                    */
                    console.log(coverData)
                    loadImageFromBrowser(coverData);
                }

            }, function (Error) {
                DEBUG && console.log(Error + ' failed to fetch ' + el);
            });
        }

    });

}
function styleSheetContainsClass(f) {
    const DEBUG = false;
    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);
                                    //DEBUG && console.log(styleSheet.cssRules[rulePos])
                                    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;
}

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;
                    }
                }

                .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 {
                    padding:5px 0;
                    height:auto;
                    display:inline-block;
                    width:100%;
                    max-width:auto;
                    text-align:center !important;
                    justify-content: center;
                    justify-items: center;
                    border: 0 !important;
                    border-bottom: 1px solid #000 !important;
                    border-radius:10px 10px 0 0 !important;                    
                }
                .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;
                }
                .popoverContent {
                    display:flex;
                    position: relative;
                    width: 100%;
                    height: 100%;
                    text-align: center !important;
                    justify-content: center;
                    justify-items: center;
                    align-items: center;
                    min-height:0;
                    min-width:0;
                    flex:1;
                    padding:1px;
                }
                .popoverDetail{
                    flex-direction:unset !important;
                }
                .popoverTitleDetail{
                    height:100% !important;
                    width:auto !important;
                    max-width:65% !important;
                    border-radius: 10px 0 0 5px !important;
                    border:0 !important;
                    border-right: 1px solid #000 !important;
                }
                
                .popoverTitle{
                    height:auto;
                    display:inline-block;
                    width:100%;
                    max-width:auto;

                }
                .smallText{
                    font-size: 0.8em;
                }
                .wordBreak {
                    word-wrap: break-word !important;
                    word-break: break-word;
                }

                `);


    const lastUpdated = GM_getValue("lastUpdated");
    const currentTime = Date.now();
    const timeDifference = currentTime - lastUpdated;
    //console.log({lastUpdated,currentTime,timeDifference})
    //console.log("timeDifference: " + timeDifference)
    if (lastUpdated === null || lastUpdated === undefined ||
        timeDifference > lastUpdateCheck) {
        GM_setValue("lastUpdated", currentTime);
        refreshInitValues = true;
        // console.log("set lastUpdated to now")
    }
    //console.log(refreshInitValues);
    const cachedBackgroundClasses = GM_getValue("STYLESHEETHIJACKFORBACKGROUND");
    if (debugCSSClasses) {
        if (STYLESHEETHIJACKFORBACKGROUND !== "" && (refreshInitValues || cachedBackgroundClasses === undefined || forceUpdate)) {
            let styleSheetToAddBackground = "";
            for (let i = 0; i < STYLESHEETHIJACKFORBACKGROUNDARRAY.length; i++) {
                if (styleSheetContainsClass(STYLESHEETHIJACKFORBACKGROUNDARRAY[i])) {
                    console.log("+ has found class: " + STYLESHEETHIJACKFORBACKGROUNDARRAY[i])
                    styleSheetToAddBackground += STYLESHEETHIJACKFORBACKGROUNDARRAY[i];
                }
                else {
                    console.log("- has not found class: " + STYLESHEETHIJACKFORBACKGROUNDARRAY[i])
                }
            }
            STYLESHEETHIJACKFORBACKGROUND = styleSheetToAddBackground.replace(REGEX_DOTCOMMA, ' ').trim();
            GM_setValue("STYLESHEETHIJACKFORBACKGROUND", STYLESHEETHIJACKFORBACKGROUND)
            //console.log("STYLESHEETHIJACKFORBACKGROUND: " + STYLESHEETHIJACKFORBACKGROUND)
        }
        else {
            STYLESHEETHIJACKFORBACKGROUND = cachedBackgroundClasses;
            console.log("cachedBackgroundClasses: " + cachedBackgroundClasses)
        }
        const cachedTitleClasses = GM_getValue("STYLESHEETHIJACKFORTITLE");
        if (STYLESHEETHIJACKFORTITLE !== "" && (refreshInitValues || cachedTitleClasses === undefined || forceUpdate)) {
            let styleSheetToAddTitle = "";
            for (let i = 0; i < STYLESHEETHIJACKFORTITLEARRAY.length; i++) {
                if (styleSheetContainsClass(STYLESHEETHIJACKFORTITLEARRAY[i])) {
                    console.log("+ has found class: " + STYLESHEETHIJACKFORTITLEARRAY[i])
                    styleSheetToAddTitle += STYLESHEETHIJACKFORTITLEARRAY[i];
                }
                else {
                    console.log("- has not found class: " + STYLESHEETHIJACKFORTITLEARRAY[i])
                }

            }
            STYLESHEETHIJACKFORTITLE = styleSheetToAddTitle.replace(REGEX_DOTCOMMA, ' ').trim();
            GM_setValue("STYLESHEETHIJACKFORTITLE", STYLESHEETHIJACKFORTITLE)
            //console.log("STYLESHEETHIJACKFORTITLE: " + STYLESHEETHIJACKFORTITLE)
        }
        else {
            STYLESHEETHIJACKFORTITLE = cachedTitleClasses;
            console.log("cachedTitleClasses: " + cachedTitleClasses)
        }
    }
    else {
        //console.log("not debugging CSS classes")
        STYLESHEETHIJACKFORBACKGROUND = STYLESHEETHIJACKFORBACKGROUND.replace(REGEX_DOTCOMMA, ' ').trim();
        STYLESHEETHIJACKFORTITLE = STYLESHEETHIJACKFORTITLE.replace(REGEX_DOTCOMMA, ' ').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";
    if (!STYLESHEETHIJACKFORBACKGROUND && DEFAULTBACKGROUNDCOLOR && DEFAULTBACKGROUNDCOLOR != "")
        popover.style.backgroundColor = DEFAULTBACKGROUNDCOLOR;

    popover.style.maxHeight = defaultHeight + "px";
    popover.style.maxWidth = defaultHeight + "px";

    //console.log(popover)
    //console.log(popover.style)
    popoverTitle.className = (STYLESHEETHIJACKFORTITLE + ' defaultTitleStyle').trim();
    if (!STYLESHEETHIJACKFORTITLE && DEFAULTTITLEBACKGROUNDCOLOR && DEFAULTTITLEBACKGROUNDCOLOR != "") {
        popoverTitle.style.backgroundColor = DEFAULTTITLEBACKGROUNDCOLOR;
        popoverTitle.style.color = "#fff";
    }
    popover.addEventListener("mouseleave", hideOnMouseLeave);
    popover.style.left = 0;
    popover.style.top = 0; //avoid invisible popover outside regular site height

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



function showPopupLoadingSpinner(title, event, notification = "", coverData = undefined) {
    const DEBUG = false;
    if (currentTitelHover !== undefined && currentTitelHover == title) {
        // console.group("showPopupLoadingSpinner")
        //popover.empty();
        //popover.innerHTML = "";
        DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
        if (coverData !== undefined) {
            //console.log("showPopupLoadingSpinner")
            //console.log(coverData)
            adjustPopupTitleDetail(coverData, title);
        }
        else
            popoverTitle.textContent = title;

        if (notification != "") {
            popoverContent.innerHTML = notification;
            popoverContent.className = "popoverContent wordBreak";//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);
        //console.log(event)
        popupPos(event);
        //  console.groupEnd("showPopupLoadingSpinner")
    }

}
/**
 * update popupContent and reposition to link
 *
 * @param {*} title
 * @param {*} link
 * @param {*} e event
 */
function refreshPopover(coverData, e = undefined) {
    //only call when isActivePopup
    const DEBUG = false;
    DEBUG && console.log("currentTitelHover: " + currentTitelHover)

    DEBUG && console.group("refreshPopover");
    const link = coverData.url;
    const title = coverData.title;
    //console.log(coverData)
    //console.log(e)
    // popoverTitle.textContent = title;
    // console.log(link)
    if (inBlocklist(link)) {

        popoverContent.innerHTML = "Blocked Image<br />No Cover Image<br />Unwanted Image";
    } else {
        let imgElement = new Image();//document.createElement("img");
        imgElement.src = link;
        popoverContent.innerHTML = '<img src="' + link + '" class="ImgFitDefault" ></img>';
    }
    adjustPopupTitleDetail(coverData);

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

const reRating = new RegExp('([0-9\.]+) \/ ([0-9\.]+)');
const reVoteCount = new RegExp('([0-9]+) votes')
function getRatingNumber(ratingString) {
    //const ratingString = "Rating(3.3 / 5.0, 1940 votes)"
    let ratingNumber;
    if (ratingString) {
        const matches = ratingString.match(reRating)
        const matchesVotes = ratingString.toLowerCase().match(reVoteCount)
        //console.log(matches)
        //console.log(matches.length)
        let hasVotes = true;
        // console.log(matchesVotes)
        if (matchesVotes && matchesVotes.length > 1) {
            //console.log(matchesVotes[1])
            if (matchesVotes[1] == 0) {
                hasVotes = false;
            }
        }

        if (matches && matches.length == 3 && hasVotes) {
            //console.log(matches[1])
            ratingNumber = matches[1];
        }
    }


    return ratingNumber;
}
const reChapters = new RegExp('([0-9\.]+)( wn)? chapters');
const reChaptersNumberBehind = new RegExp('chapter ([0-9\.]+)');
function getChapters(statusString) {
    let result;
    if (statusString) {
        let chapterCount;
        const matches = statusString.toLowerCase().match(reChapters);
        let webnovel = "";
        if (matches && matches.length >= 2) {
            chapterCount = matches[1];
            if (matches[2]) {
                webnovel = " WN";
            }
        }
        if (!chapterCount) {
            const matchesBehind = statusString.toLowerCase().match(reChaptersNumberBehind);
            if (matchesBehind && matchesBehind.length >= 2) {
                chapterCount = matchesBehind[1];
            }
        }

        if (chapterCount) {
            result = chapterCount + webnovel + " Chapters"
        }
    }


    return result;
}

function getCompletedState(statusString) {
    let result = false;
    if (statusString && statusString.toLowerCase().includes("complete")) {//complete | completed
        result = true;
    }
    return result;
}
function geOngoingState(statusString) {
    let result = false;
    if (statusString && statusString.toLowerCase().includes("ongoing")) {
        result = true;
    }
    return result;
}

async function adjustPopupTitleDetail(coverData, title = undefined) {

    let titleToShow = "";
    popoverTitle.textContent = "";

    if (coverData && coverData.title)
        titleToShow = coverData.title;
    else if (title !== undefined) titleToShow = title;
    //popoverTitle.textContent = titleToShow;
    //console.log("adjustPopupTitleDetail - showDetails: " + showDetails)
    let completeDetails = titleToShow;
    if (showDetails) {
        //console.log("showDetails should be true")
        if (coverData.votes && coverData.votes.length > 0) {
            completeDetails += '<hr />Rating: ' + coverData.votes;
        }
        if (coverData.status) {
            completeDetails += '<hr />Status: ' + coverData.status;
        }
        if (coverData.genre) {
            completeDetails += '<hr />Genre: ' + coverData.genre;
        }
        if (coverData.showTags) {
            completeDetails += "<hr />Tags: " + coverData.showTags;
        }
        completeDetails += "<hr /><span>[Press Key 1 to hide details]</span>";
    }
    else {
        //console.log("showDetails should be false")

        let rating = getRatingNumber(coverData.votes);
        let chapters = getChapters(coverData.status);
        let completed = getCompletedState(coverData.status);
        let ongoing = geOngoingState(coverData.status);
        if (rating || chapters || completed || ongoing) {

            //console.log(rating)
            //console.log(chapters)
            //console.log(completed)
            //console.log(ongoing)

            if (rating !== undefined) rating += "★ "; else rating = "";
            if (chapters !== undefined) chapters = chapters + " "; else chapters = "";
            if (completed) completed = "🗹 "; else completed = ""; //https://www.utf8icons.com/
            if (ongoing) ongoing = "✎ "; else ongoing = "";

            completeDetails += '<span class="smallText" style="white-space: nowrap;"> [' +
                rating +
                chapters +
                completed +
                ongoing +
                ']</span>';

        }

        completeDetails += '<br /><span class="smallText">[Press Key 1 to show details]</span>';
        //popoverTitle.innerHTML = completeDetails;
    }
    popoverTitle.innerHTML = completeDetails;
}
function setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, e) {
    const DEBUG = false;
    //GM_getCachedValue
    DEBUG && console.group("setCurrentCoverDataAndLoadImage")
    // const coverData = GM_getCachedValue(Href);

    DEBUG && console.log(coverData)
    let serieTitle = hoveredTitle;
    if (!hoveredTitle || coverData.title)  //pure link without title get title of seriepage
        serieTitle = coverData.title;
    DEBUG && console.log("imgUrl: " + imgUrl);
    DEBUG && console.log("hoveredTitle: " + hoveredTitle + ", serieTitle: " + serieTitle)

    if (coverData !== undefined && coverData !== null)
        currentCoverData = coverData;

    if (e)
        loadImageFromBrowser(coverData, serieTitle, e, hoveredTitle)

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

function ajaxLoadImageUrlAndShowPopup(elementUrl, hoveredTitle, e) {
    //console.log("mouseenter")
    // console.group("ajaxLoadImageUrlAndShowPopup")
    return parseSeriePage(elementUrl, hoveredTitle, e)
        .then(function (coverData) {
            setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, e)
        }, function (Error) {
            console.log(Error + ' failed to fetch ' + elementUrl);
        });
    // console.groupEnd("ajaxLoadImageUrlAndShowPopup")
};

function imageLoaded(coverData, hoveredTitleLink, serieTitle = undefined, e = undefined) {
    const DEBUG = false;
    const hasMouseEnterEvent = serieTitle && (e !== undefined);
    const isActivePopup = ((currentTitelHover !== undefined) &&
        (hoveredTitleLink !== undefined)) &&
        (currentTitelHover == hoveredTitleLink) &&
        hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData
    DEBUG && console.group("loadImageFromBrowser img.onload: " + serieTitle)
    DEBUG && console.log("finished loading imgurl: " + coverData.url);
    DEBUG && console.log("currentTitelHover: " + currentTitelHover + ", isActivePopup: " + isActivePopup)
    DEBUG && console.log("isActivePopup: " + isActivePopup)
    if (isActivePopup) {
        DEBUG && console.log("refreshPopover")
        refreshPopover(coverData, e); //popup only gets refreshed when currentTitelHover == serieTitle
    }
    DEBUG && console.groupEnd("loadImageFromBrowser img.onload")
}

function imageLoadingError(coverData, error, hoveredTitleLink, serieTitle = undefined, e = undefined) {
    console.group("loadImageFromBrowser img.onerror: " + serieTitle)
    const hasMouseEnterEvent = serieTitle && (e !== undefined);
    const isActivePopup = ((currentTitelHover !== undefined) && (hoveredTitleLink !== undefined)) && (currentTitelHover == hoveredTitleLink) && hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData
    console.log(error);
    const errorMessage = "browser blocked/has error loading the file: <br />" + decodeURIComponent(error.target.src);
    console.log(errorMessage)
    //console.log(window)
    console.log(navigator)
    //console.log(navigator.userAgent)
    const useragentString = navigator.userAgent;
    console.log("useragentString: " + useragentString)
    const isChrome = useragentString.includes("Chrome")
    if (isChrome)
        console.log("look in the developer console if 'net::ERR_BLOCKED_BY_CLIENT' is displayed or manually check if the imagelink still exists");
    else
        console.log("image loading most likely blocked by browser or addon. Check if the imagelink still exists");

    if (isActivePopup)
        showPopupLoadingSpinner(serieTitle, e, errorMessage, coverData);
    console.groupEnd("loadImageFromBrowser img.onerror")
}

function loadImageFromBrowser(coverData, serieTitle = undefined, e = undefined, hoveredTitleLink = undefined) {
    const DEBUG = false;
    //console.group("loadImageFromBrowser")
    let img = document.createElement("img"); //put img into dom. Let the image preload in background
    const hasMouseEnterEvent = serieTitle && (e !== undefined);
    //console.log(currentCoverData)
    //console.log(coverData)

    DEBUG && console.log("loadImageFromBrowser")
    DEBUG && console.log(hasMouseEnterEvent)
    img.onload = () => { imageLoaded(coverData, hoveredTitleLink, serieTitle, e) };

    img.onerror = (error) => { imageLoadingError(coverData, error, hoveredTitleLink, serieTitle, e) }

    img.src = coverData.url;

    if (img.complete) {
        DEBUG && console.log("loadImageFromBrowser preload completed: " + serieTitle)
        DEBUG && console.log(img.src)
    } else {//if image not available/cached in browser show loading pinner
        const isActivePopup = (currentCoverData !== undefined &&
            (currentTitelHover !== undefined) &&
            (hoveredTitleLink !== undefined)) &&
            (currentTitelHover == hoveredTitleLink) &&
            hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData

        if (isActivePopup) {
            DEBUG && console.log("loadImageFromBrowser image not completely loaded yet. Show loading spinner : " + serieTitle)
            showPopupLoadingSpinner(serieTitle, e);
        }
    }
    // console.groupEnd("loadImageFromBrowser")
}

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)
    const target = e.target;
    let Href = target.href;// element.attr('href');
    if (Href.includes(INDIVIDUALPAGETEST)) //only trigger for links that point to serie pages
    {
        //console.log(this)
        //console.log(this.text) //shortTitle
        //console.log(this.title) //LongTitle
        let shortSerieTitle = target.text; //element.text(); //get linkname
        //console.log(this)
        //console.log(shortSerieTitle)

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

        let serieTitle = target.getAttribute('datatitle');//element.attr('datatitle'); //try to get nativ title if available from datatitle
        //console.log(serieTitle)
        if (serieTitle === null || //has no set nativ long title -> use (available shortend) linkname
            serieTitle == "null" ||
            PREDIFINEDNATIVTITLEARRAY.some(nativTitle => serieTitle.includes(nativTitle))) //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
        currentPopupEvent = e;
        //console.log(serieTitle)
        //console.log(Href)


        //console.log(currentCoverData)
        ajaxLoadImageUrlAndShowPopup(Href, currentTitelHover, e);

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

function hidePopOver() {
    popover.style.visibility = "hidden";
    //popover.style.height = "0";
    //popover.style.width = "0";
    //console.group("hidePopOver")
    //console.log("currentTitelHover: " + currentTitelHover)
    currentTitelHover = undefined;
    currentCoverData = undefined;

    popoverContent.innerHTML =""; //remove infinite spinner animation when not shown
    //console.log("currentTitelHover: " + currentTitelHover)
    //console.groupEnd("hidePopOver")
}
function showPopOver() {
    // popover.style.display = "flex";
    //popover.style.height = "100%";
    // popover.style.width = "100%";
    popover.style.visibility = "visible";
}
function hideOnMouseLeave() {
    //if (!e.target.matches(concatSelector())) return;
    //popover.hide();
    hidePopOver();
}

/*
 * get links into ALLSERIENODES and convert this nodearray to array
 *
 */
function updateSerieNodes() {
    if (ALLSERIENODES) {
        ALLSERIENODES.forEach(function (selector) {
            if (eventListenerStyle === undefined || eventListenerStyle === null || eventListenerStyle == 0) {
                selector.removeEventListener("mouseleave", hideOnMouseLeave);
                selector.removeEventListener("mouseenter", mouseEnterPopup);
            }
        })
    }
    ALLSERIENODES = Array.from(document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]'));

    /*
    console.log(ALLSERIENODES)

    const sliceItemCount = 100;
    if (ALLSERIENODES.length > sliceItemCount) {
        ALLSERIENODES = ALLSERIENODES.slice(0, sliceItemCount);
    }
    console.log(ALLSERIENODES)
    */
}
function switchDetailsAndUpdatePopup() {
    const DEBUG = false;
    DEBUG && console.group("switchDetailsAndUpdatePopup")
    changeToNewDetailStyle();
    //console.log(currentCoverData)
    DEBUG && console.log("switchDetails refreshPopup")
    DEBUG && console.log(currentCoverData)
    if (currentCoverData && currentCoverData !== undefined) {
        refreshPopover(currentCoverData, currentPopupEvent); //update on detail change
    }

    console.groupEnd("switchDetails")


}
function changeToNewDetailStyle(toggleDetails = true) {
    if (toggleDetails)
        showDetails = !showDetails;
    //console.log("switch showDetails to : " + showDetails)
    localStorage.setItem("showDetails", showDetails);
    if (showDetails) {
        popover.classList.add("popoverDetail")
        popover.style.maxWidth = defaultHeight * 2 + "px";
        popoverTitle.classList.add("popoverTitleDetail")
    }
    else {
        popover.classList.remove("popoverDetail")
        popover.style.maxWidth = defaultHeight + "px";
        popoverTitle.classList.remove("popoverTitleDetail")
    }

}
function reactToKeyPressWhenPopupVisible(event) {
    //console.log(event)
    //console.log(currentTitelHover)
    if (currentTitelHover && currentTitelHover !== undefined) {
        if (event.key == "1") {
            //switchDetailsAndUpdatePopup();
            switchDetailsAndUpdatePopup()
        }

    }
}
window.addEventListener("blur", hidePopOver);
window.addEventListener("keypress", reactToKeyPressWhenPopupVisible);
window.onunload = function () {
    window.removeEventListener("blur", hidePopOver);
    window.removeEventListener("keypress", reactToKeyPressWhenPopupVisible);
    popover.removeEventListener("mouseleave", hideOnMouseLeave);
    //possible memoryleaks?
    updateSerieNodes();
    observer.disconnect();
}
const debouncedpreloadCoverData = debounce(preloadCoverData, 100);
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };

// Callback function to execute when mutations are observed
const callback = function (mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
            // console.log('A child node has been added or removed.');
            //debouncedTest()
            debouncedpreloadCoverData();
            hidePopOver();
        }
        else if (mutation.type === 'attributes') {
            //   console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);

function debounce(func, timeout) {
    let timer;
    return (...args) => {
        const next = () => func(...args);
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(next, timeout > 0 ? timeout : 300);
    };
};

//https://gist.github.com/peduarte/969217eac456538789e8fac8f45143b4#file-index-js
function throttle(func, wait = 100) {
    let timer = null;
    return function (...args) {
        if (timer === null) {
            timer = setTimeout(() => {
                func.apply(this, args);
                timer = null;
            }, wait);
        }
    };
}
function main() {
    checkDataVersion();


    loadStyleSheets();
    createPopover();
    hidePopOver();
    showDetails = localStorage.getItem("showDetails") == "true";
    //console.log("localStorage state showDetails: " + showDetails)
    changeToNewDetailStyle(false);
    //console.log("isOnReadingListIndex: " + isOnReadingListIndex)
    if (isOnReadingListIndex) {
        let targetNode = document.getElementById("profile_content3");
        //console.dir(targetNode)
        observer.observe(targetNode, config); //observe for update before running debouncedwaitForReadingList();
    }
    else {
        preloadCoverData();
    }
}
main();

//assumption that a single eventlistener is more performant than dozens of mouseEnter/MouseLeave events
//https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/#web-performance
//https://davidwalsh.name/event-delegate
//https://web.archive.org/web/20170121035049/http://jsperf.com/click-perf
//https://stackoverflow.com/questions/29836326/is-using-a-universal-document-addeventlistenerclick-f-listener-slower-or-wea
/*
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
*/
let lastTarget;
function getHoveredItem(e) {
    if (eventListenerStyle == 1) {
        if (e.target &&
            e.target != lastTarget &&
            e.target.nodeName == "A" &&
            e.target.href && e.target.href.includes(INDIVIDUALPAGETEST)) {

            lastTarget = e.target;
            //console.group("target A")
            //console.log(e.target.text)
            //console.log(e)

            mouseEnterPopup(e)
            //console.groupEnd();

        }
        else {
            if (e.target.nodeName != "A") {
                lastTarget = undefined;
                hideOnMouseLeave();
            }

        }
    }

}
const throttledGetHoveredItem = throttle(getHoveredItem, 50)
if (eventListenerStyle == 1)
    window.addEventListener("mousemove", throttledGetHoveredItem)

QingJ © 2025

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