您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.
当前为
// ==UserScript== // https://greasyforks.org/scripts/26439-novelupdates-cover-preview/ // @name novelupdates Cover Preview // @namespace somethingthatshouldnotclashwithotherscripts // @include https://www.novelupdates.com/* // @include http://www.novelupdates.com/* // @include https://forum.novelupdates.com/* // @include http://forum.novelupdates.com/* // @version 1.7.1 // @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages. // @inject-into content // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-end // @license http://creativecommons.org/licenses/by-nc-sa/4.0/ // ==/UserScript== //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 = ".l-canvas, .breadcrumb"; //if unknown set empty ""; classname with leading dot seperated with comma let STYLESHEETHIJACKFORTITLE = ".widgettitle_nuf, .navTabs "; //if unknown set empty ""; classname with leading dot seperated with comma const PREDIFINEDNATIVTITLE = "Recommended by"; //forum, index const INDIVIDUALPAGETEST = "www.novelupdates.com/series/"; //matched with includes const IMAGELINKCONTAINERS = ".serieseditimg img, .seriesimg img"; //instead of single element class name with dot seperated with comma const IMAGEBLOCKER = "https://www.novelupdates.com/img/noimagefound.jpg"; //tested with includes() const CONTAINERNUMBER = 0; const seriePageTitle = ".seriestitlenu"; const seriePageVotes = ".seriesother > .uvotes"; const seriePageStatus = "#editstatus"; const seriePageGenre = "#seriesgenre"; const seriePageTags = "#showtags"; const isOnIndex = this.location.href == "https://www.novelupdates.com/" || this.location.href.startsWith("https://forum.novelupdates.com/") || this.location.href.startsWith("https://www.novelupdates.com/?pg=") || this.location.href.startsWith("https://www.novelupdates.com/group/"); //popup style next to container instead of next to linkitem 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 = true; //load serie page info and collect coverData const preloadImages = false; //preload images during collecting of coverData const eventListenerStyle = 0; //undefined/0 = forEach serieLink addeventlistener(mouseenter/mouseleave) / 1 = window addeventlistener(mousemove) //to know when to switch between popup next to link or next to container of link //#endregion //#region backend variables ^^^^ frontend settings over this line ^^^^ const version = "1.7.1"; 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:show 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");