mangaupdates Cover Preview

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// https://greasyfork.org/scripts/26513-mangaupdates-cover-preview/
// @name        mangaupdates Cover Preview
// @namespace   szMangaupdatesCoverPreview
// @include     https://www.mangaupdates.com/*
// @include     http://www.mangaupdates.com/*
// @version     1.7.0
// @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==

//console.log("cover preview start");
"use strict";
//#region frontend settings
const MAXCACHEAGE = 90 * 24 * 60 * 60 * 1000; // Max Age before Cached data of serieinfo gets overridden with current data. Max Age is 90 days in milliseconds  //days * h * min  * sec * ms
const DEFAULTTITLEBACKGROUNDCOLOR = "#2c3e50"; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR = "#ccc"; //if no hijack class style available use plain color
let STYLESHEETHIJACKFORBACKGROUND = ".frow2"; //if unknown set empty ""; classname with leading dot seperated with comma
let STYLESHEETHIJACKFORTITLE = ".small_bold, .side_content_row"; //if unknown set empty ""; classname with leading dot seperated with comma

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 includes(). no need for prefixed http https in url. Can even be just the file name
const CONTAINERNUMBER = 0;
const seriePageTitle = ".releasestitle "; //.seriestitlenu
//since there are no ids or identifiable class selectable i could only grab the content by manually counting and selecting the corresponding divs
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 isOnReadingListIndex = this.location.href.startsWith(
  "https://www.novelupdates.com/user/"
);
const targetContainerIDToObserve = "profile_content3"; //update eventlistener on list change of page isOnReadingListIndex

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

//#region backend variables ^^^^	frontend settings over this line	^^^^
const version = "1.7.0";
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 reChapters = new RegExp("([0-9.]+)( wn)? chapters");
const reChaptersNumberBehind = new RegExp("chapter ([0-9.]+)");
const reRating = new RegExp("([0-9.]+) / ([0-9.]+)");
const reVoteCount = new RegExp("([0-9]+) votes");
const offsetToBottomBorderY = 22; //offset to bottom border
const offsetToRightBorderX = 10; //offset to right border
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 popoverVisible = false; //not all links have a title or text(img link) to set currentTitelHover. Manual state saving needed
let ALLSERIENODES = []; // = document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]');
let currentTitelHover, currentCoverData, currentPopupEvent;
let popover, popover2, popoverTitle, popoverContent, popoverCoverImg;
let lastTarget;
let pressedKeys = [];
//#endregion
//console.log("after variable settings");
//console.log(this.location)
//console.log(this.location.href)

//console.log("isOnIndex: " + isOnIndex)

//#region helper functions

// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };

//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 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) {
    const styleSheetsLengthToLoop = fullStyleSheets.length - 1;
    for (let i = 0; i < styleSheetsLengthToLoop; i++) {
      //DEBUG && console.log("loop fullStyleSheets " + stylename);
      let styleSheet = fullStyleSheets[i];
      if (
        styleSheet != null &&
        styleSheet.href !== null &&
        (styleSheet.href.match(localDomainCheck) ||
          styleSheet.href.match(localDomainCheckHttps)) &&
        styleSheet.cssRules //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
      ) {
        //DEBUG && console.log("styleSheet.cssRules.length: " + styleSheet.cssRules.length)
        const ruleLengthToLoop = styleSheet.cssRules.length - 1;
        for (let rulePos = 0; rulePos < ruleLengthToLoop; 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;
}

// Callback function to execute when mutations are observed
/* https://www.freecodecamp.org/news/var-let-and-const-whats-the-difference/
const function can not be overwritten //https://stackoverflow.com/questions/54915917/overwrite-anonymous-const-function-in-javascript
https://www.digitalocean.com/community/tutorials/understanding-hoisting-in-javascript
function callback  () is anonymous var function which can be overwritten?
*/
//https://medium.com/@alexcambose/js-offsettop-property-is-not-great-and-here-is-why-b79842ef7582
function getOffset(element, horizontal = false) {
  if (!element) return 0;
  return (
    getOffset(element.offsetParent, horizontal) +
    (horizontal ? element.offsetLeft : element.offsetTop)
  );
}
const debounce = function (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
const throttle = function (func, wait = 100) {
  let timer = null;
  return function (...args) {
    if (timer === null) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, wait);
    }
  };
};

const callbackMutationObserver = 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(callbackMutationObserver);
const debouncedpreloadCoverData = debounce(preloadCoverData, 100);
const throttledGetHoveredItem = throttle(getHoveredItem, 50);
//#endregion
function inBlocklist(link) {
  //console.log(link)
  if (IMAGEBLOCKERARRAY) {
    const hasBlocker = IMAGEBLOCKERARRAY.includes(link);
    if (hasBlocker) return true;
    //console.log(hasBlocker);
  }

  return false;
}
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
  ) {
    resetDatabase();
  }
}
function resetDatabase() {
  const DEBUG = false;
  const oldValues = GM_listValues();
  DEBUG && console.log("oldValues.length: " + oldValues.length);
  const oldValuesLengthToLoop = oldValues.length;
  for (let i = 0; i < oldValuesLengthToLoop; i++) {
    GM_deleteValue(oldValues[i]);
    //console.log(oldValues[i])
  }
  DEBUG && console.log(oldValues);
  GM_setValue("version", version);
}
function chooseAndGetRectOffset(nativElement) {
  let targetedRect;
  if (isOnIndex || isOnReadingListIndex) {
    targetedRect = nativElement.parentElement.getBoundingClientRect();
  } else {
    targetedRect = nativElement.getBoundingClientRect();
  }
  return getRectOffset(targetedRect);
}

//https://dmitripavlutin.com/differences-between-arrow-and-regular-functions/
const getDistanceToBottom = (Y, scrollPosY, popoverRect) => {
  return (
    Y -
    scrollPosY +
    popoverRect.height -
    (window.innerHeight - offsetToBottomBorderY)
  );
};

function getDistanceToBottom2(Y, scrollPosY, popoverRect) {
  return (
    Y -
    scrollPosY +
    popoverRect.height -
    (window.innerHeight - offsetToBottomBorderY)
  );
}

function getRectOffset(rect) {
  return { Rx: rect.left + rect.width, Ry: rect.top };
}
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);
  if (event && event !== undefined) {
    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 getCoverDataFromUrl(elementUrl) {
  const DEBUG = false;
  let PromiseResult = new Promise(async function (resolve, reject) {
    DEBUG && console.log("elementUrl: " + elementUrl);

    // 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) {
      //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299)
      if (xhr.status >= 200 && xhr.status < 399) {
        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: " + currentTitelHover);
          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 into coverData.url: " + imagelink);
          let cData = {
            url: imagelink,
            title: serieTitle,
            votes: serieVotes,
            status: serieStatus,
            genre: serieGenre,
            showTags: serieShowtags,
          };
          //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(xhr);
        }
      }
    }

    function onError(error) {
      console.log(error);
      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,
    });

    return undefined; //reject("status error")
  });
  return PromiseResult;
}
async function parseSeriePage(
  elementUrl,
  forceReload = false,
  hoveredTitle = undefined,
  event = undefined
) {
  const DEBUG = false;
  DEBUG && console.group("parseSeriePage: " + elementUrl);
  const coverData = GM_getCachedValue(elementUrl);

  let retrievedImgLink;
  let PromiseResult;
  if (!forceReload && coverData !== null && coverData.url) {
    retrievedImgLink = coverData.url;
    DEBUG &&
      console.log(
        "parseSeriePage has cached retrievedImgLink: " + retrievedImgLink
      );
    PromiseResult = coverData;
  } else {
    /*
    console.group("parseSeriePage vars before getCoverDataFromUrl")
    console.log("elementUrl: " + elementUrl+", forceReload: " + forceReload+", hoveredTitle: " + hoveredTitle);
    console.log(event);
    console.groupEnd();
    */
    showPopupLoadingSpinner(hoveredTitle, hoveredTitle, event);
    PromiseResult = getCoverDataFromUrl(elementUrl);
  }

  DEBUG && console.groupEnd("parseSeriePage: " + elementUrl);
  PromiseResult = await PromiseResult;
  //console.log(PromiseResult)
  //DEBUG && console.log(PromiseResult)
  //after GM_xmlhttpRequest PromiseResult

  return PromiseResult;
}

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(
      "before parseSeriePage for each url with a link to individual seriepage"
    );
  if (ALLSERIENODES && ALLSERIENODES.length > 0) {
    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) {
        DEBUG && console.log("start parseSeriePage");
        parseSeriePage(elementUrl).then(
          function (coverData) {
            if (coverData !== undefined) {
              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 addStyles() {
  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;
  }  
  `);
}
function setStyleClasses() {
  const lastUpdated = GM_getValue("lastUpdated");
  const currentTime = Date.now();
  const timeDifference = currentTime - lastUpdated;
  const cachedBackgroundClasses = GM_getValue("STYLESHEETHIJACKFORBACKGROUND");
  //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);

  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(
  hoveredTitleLink,
  title,
  event,
  notification = "",
  coverData = undefined
) {
  const DEBUG = false;
  //console.log(event)
  const isActivePopup =
    currentTitelHover !== undefined &&
    hoveredTitleLink !== undefined &&
    currentTitelHover == hoveredTitleLink;
  /*
    console.group("showPopupLoadingSpinner")
    //"currentCoverData: " +currentCoverData +
    console.log("currentTitelHover: " + currentTitelHover+", hoveredTitleLink: " + hoveredTitleLink+", currentTitelHover == hoveredTitleLink: " + (currentTitelHover == hoveredTitleLink))
    console.log("isActivePopup: " + isActivePopup)
    console.groupEnd();*/
  if (isActivePopup) {
    // 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")
  }
}

function refreshPopover(coverData, e = undefined) {
  //only call when isActivePopup
  const DEBUG = false;
  if (coverData && coverData !== undefined) {
    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");
    DEBUG && console.log(e);
    //if (currentTitelHover == title)
    if (e !== undefined) popupPos(e);
  }
}

//#region get serieDetails
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;
}
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 getOngoingState(statusString) {
  let result = false;
  if (statusString && statusString.toLowerCase().includes("ongoing")) {
    result = true;
  }
  return result;
}
function getDetailsString(coverData) {
  let completeDetails = "";
  if (coverData.votes) {
    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;
  }
  return completeDetails;
}
function getShortendDetailsString(coverData) {
  let completeDetails = "";
  let rating = getRatingNumber(coverData.votes);
  let chapters = getChapters(coverData.status);
  let completed = getCompletedState(coverData.status);
  let ongoing = getOngoingState(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>";
  }
  return completeDetails;
}
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")
    completeDetails += getDetailsString(coverData);
    completeDetails +=
      '<hr /><span class="smallText">[Press Key 1 to hide details] [Press Key 2 to force reload serie info] [Press Key 3 to refresh all]</span>';
  } else {
    //console.log("showDetails should be false")
    completeDetails += getShortendDetailsString(coverData);
    completeDetails +=
      '<br /><span class="smallText">[Key1:hide details, Key2: reload info, Key3: refresh all]</span>';
  }
  //popoverTitle.innerHTML = completeDetails;

  popoverTitle.innerHTML = completeDetails;
}
//#endregion

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(
      "hoveredTitle: " + hoveredTitle + ", serieTitle: " + serieTitle
    );

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

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

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

function ajaxLoadImageUrlAndShowPopup(
  forceReload = false,
  elementUrl,
  hoveredTitle,
  e
) {
  const currentEvent = e;
  //console.log(currentEvent)
  //console.log("mouseenter")
  // console.group("ajaxLoadImageUrlAndShowPopup")
  return parseSeriePage(
    elementUrl,
    forceReload,
    hoveredTitle,
    currentEvent
  ).then(
    function (coverData) {
      if (coverData !== undefined) {
        setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, currentEvent);
      }
    },
    function (Error) {
      console.log(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("isActivePopup: " + isActivePopup);*/
  console.log("hoveredTitleLink:" + hoveredTitleLink);
  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(
    hoveredTitleLink,
    serieTitle,
    e,
    errorMessage,
    coverData
  );
  console.groupEnd("loadImageFromBrowser img.onerror");
}

function loadImageFromBrowser(
  coverData,
  serieTitle = undefined,
  e = undefined,
  hoveredTitleLink = undefined
) {
  const DEBUG = false;
  //console.log(e)
  //console.group("loadImageFromBrowser")
  let img = document.createElement("img"); //put img into dom. Let the image preload in background
  const hasMouseEnterEvent = hoveredTitleLink !== undefined && 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
    //console.log(e)
    if (isActivePopup) {      
        */
    DEBUG &&
      console.log(
        "loadImageFromBrowser image not completely loaded yet. Show loading spinner : " +
          serieTitle
      );
    showPopupLoadingSpinner(hoveredTitleLink, serieTitle, e);
    //  }
  }
  // console.groupEnd("loadImageFromBrowser")
}

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;
  popoverVisible = false;

  popoverContent.innerHTML = ""; //remove infinite spinner animation when popup not shown
  pressedKeys = []; //window blur release keys
  //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";
  popoverVisible = true;
}
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.length > 0) {
    ALLSERIENODES.forEach(function (selector) {
      if (
        eventListenerStyle === undefined ||
        eventListenerStyle === null ||
        eventListenerStyle == 0
      ) {
        selector.removeEventListener("mouseleave", hideOnMouseLeave);
        selector.removeEventListener("mouseenter", mouseEnterPopup);
      }
    });
  }
  const serieLinkNodes = document.querySelectorAll(
    'a[href*="' + INDIVIDUALPAGETEST + '"]'
  );
  //console.log(serieLinkNodes)
  ALLSERIENODES = Array.from(serieLinkNodes);
  //console.log(ALLSERIENODES)
  /*
      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);
  refreshPopover(currentCoverData, currentPopupEvent); //update on detail change

  console.groupEnd("switchDetails");
}
function changeToNewDetailStyle(toggleDetails = true) {
  if (toggleDetails) showDetails = !showDetails;
  //console.log("switch showDetails to : " + showDetails)
  GM_setValue("showDetails", 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");
  }
}

//#region eventListener
function mouseEnterPopup(e, forceReload = false) {
  //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)
  if (e !== undefined) {
    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)
      const dataTitle = target.getAttribute("datatitle");
      const linkTitle = target.getAttribute("title");
      //console.log("linkTitle: " + linkTitle)
      const hasDataTitle =
        dataTitle === null ||
        dataTitle == "null" ||
        dataTitle === undefined ||
        !dataTitle;
      //move native title to custom data attribute. Suppress nativ title popup
      if (linkTitle !== null && hasDataTitle) {
        target.setAttribute("datatitle", linkTitle);
        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;
      if (serieTitle === undefined || serieTitle === null || serieTitle == "")
        //image link: example link which content is only the cover image https://www.mangaupdates.com/series.html?letter=A
        serieTitle = Href;
      currentTitelHover = serieTitle; //mark which titel is currently hovered
      currentPopupEvent = e;
      //console.log(serieTitle)
      //console.log(Href)

      //console.log(currentCoverData)
      //console.log("currentTitelHover: " + currentTitelHover)
      ajaxLoadImageUrlAndShowPopup(forceReload, Href, currentTitelHover, e);
    }
  }
  DEBUG && console.groupEnd("mouseEnterPopup");
}
function forceReload(event) {
  const forceReload = true;
  mouseEnterPopup(currentPopupEvent, forceReload);
}

function reactToKeyPressWhenPopupVisible(event) {
  //console.log(event);
  //console.log(currentTitelHover)
  const key = event.key;
  if (popoverVisible) {
    if (!pressedKeys.includes(key)) {
      //console.log(event);
      pressedKeys.push(key);
      switch (key) {
        case "1":
          switchDetailsAndUpdatePopup();
          break;
        case "2":
          forceReload(event);
          break;
        case "3":
          resetDatabase();
          preloadCoverData();
          forceReload(event);
          break;
      }
    }
  }
}
function releaseKey(event) {
  const key = event.key;
  //console.log(pressedKeys)
  pressedKeys.splice(pressedKeys.indexOf(key), 1);
  //console.log(pressedKeys)
}
function prepareEventListener() {
  window.addEventListener("blur", hidePopOver);
  window.addEventListener("keypress", reactToKeyPressWhenPopupVisible); //keypress gets repeated during keydown
  window.addEventListener("keyup", releaseKey);
  window.onunload = function () {
    window.removeEventListener("blur", hidePopOver);
    window.removeEventListener("keypress", reactToKeyPressWhenPopupVisible);
    window.addEventListener("keyup", releaseKey);
    popover.removeEventListener("mouseleave", hideOnMouseLeave);
    //possible memoryleaks?
    updateSerieNodes();
    observer.disconnect();
  };
  if (eventListenerStyle == 1)
    window.addEventListener("mousemove", throttledGetHoveredItem);
}

//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).
  */

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

document.addEventListener("DOMContentLoaded", main());
//#endregion

function main() {
  const DEBUG = false;
  DEBUG && console.log("started main function of coverPreview");
  DEBUG && console.log("before starting checkDataVersion");
  checkDataVersion();

  DEBUG && console.log("before starting setStyleClasses");
  addStyles();
  setStyleClasses();
  DEBUG && console.log("before starting createPopover");
  createPopover();
  DEBUG && console.log("before starting hidePopOver");
  hidePopOver();
  showDetails = GM_getValue("showDetails");
  //if(showDetails) showDetails = JSON.parse(showDetails);
  //showDetails = localStorage.getItem("showDetails") == "true";
  //console.log("localStorage state showDetails: " + showDetails)
  DEBUG && console.log("before starting changeToNewDetailStyle");
  changeToNewDetailStyle(false);
  //console.log("isOnReadingListIndex: " + isOnReadingListIndex)
  if (isOnReadingListIndex) {
    DEBUG && console.log("before starting observer");
    let targetNode = document.getElementById(targetContainerIDToObserve);
    //console.dir(targetNode)
    observer.observe(targetNode, config); //observe for update before running debouncedwaitForReadingList();
  } else {
    DEBUG && console.log("before starting preloadCoverData");
    preloadCoverData();
  }
  DEBUG && console.log("before starting prepareEventListener");
  prepareEventListener();
  DEBUG && console.log("finished main function of coverPreview");
}
//console.log("cover preview end");