您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.
当前为
// ==UserScript== // https://gf.qytechs.cn/scripts/26439-novelupdates-cover-preview/ // @name novelupdates Cover Preview // @namespace somethingthatshouldnotclashwithotherscripts // @include https://www.novelupdates.com/* // @include http://www.novelupdates.com/* // @include https://forum.novelupdates.com/* // @include http://forum.novelupdates.com/* // @version 2.0.2 // @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 = ".breadcrumb, .l-submain, .pageContent"; //if unknown set empty ""; classname with leading dot seperated with comma //l-submain-h //, .l-submain 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 seriePageChapters = undefined; const seriePageGenre = "#seriesgenre"; const seriePageTags = "#showtags"; const seriePageDescription = "#editdescription"; const isOnIndex = this.location.href == "https://www.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 externalLinks = { "www.scribblehub.com/series/": { IMAGELINKCONTAINERS: ".fic_image img", //instead of single element class name with dot seperated with comma IMAGEBLOCKER: "", //"https://www.novelupdates.com/img/noimagefound.jpg"; //tested with includes() CONTAINERNUMBER: 0, seriePageTitle: ".fic_title", seriePageVotes: "#ratefic_user > span > span", seriePageStatus: ".fic_stats > span:nth-child(3)", seriePageGenre: ".wi_fic_genre", seriePageTags: ".wi_fic_showtags_inner", // seriePageDescription: ".wi_fic_desc", }, "www.webnovel.com/book/": { IMAGELINKCONTAINERS: ".g_thumb > img", //instead of single element class name with dot seperated with comma IMAGEBLOCKER: "", //"https://www.novelupdates.com/img/noimagefound.jpg"; //tested with includes() CONTAINERNUMBER: 0, seriePageTitle: ".det-info > div:nth-child(2) > h2", seriePageVotes: "._score > strong", seriePageStatus: ".det-hd-detail > strong > span", seriePageGenre: ".det-hd-detail > a > span", seriePageTags: ".m-tags", seriePageDescription: ".det-abt > div > p", }, "www.royalroad.com/fiction/": { IMAGELINKCONTAINERS: ".fic-header > div > img", //instead of single element class name with dot seperated with comma IMAGEBLOCKER: "", //"https://www.novelupdates.com/img/noimagefound.jpg"; //tested with includes() CONTAINERNUMBER: 0, seriePageTitle: ".fic-title > h1", seriePageVotes: undefined, seriePageStatus: ".fiction-info > div > div:nth-child(2) > div > span:nth-child(2)", seriePageGenre: ".tags", seriePageTags: undefined, // seriePageDescription: ".description > div > p", }, /* not possible for the details and haven't seen an api example with different div row counts: https://bato.to/series/72644, https://bato.to/series/74357 data not at the same position and no unique selector available */ "bato.to/series/": { seriePageTitle: ".item-title", IMAGELINKCONTAINERS: ".attr-cover > img", //instead of single element class name with dot seperated with comma CONTAINERNUMBER: 0, seriePageDescription: ".attr-main > pre", seriePageChapters: ".episode-list > div.head > h4", seriePageGenre: ".attr-main > div:nth-child(3) > span", /* IMAGEBLOCKER: "", //"https://www.novelupdates.com/img/noimagefound.jpg"; //tested with includes() seriePageVotes: undefined, seriePageStatus:".attr-main > div:nth-child(5)", seriePageTags: undefined, */ }, "mangadex.org/title/": { /* not possible no unique identification possible (no id or unique class for a container) depending on serie div count changing since not all values are always used data not at the same position and no unique selector available exception using available mangadex api */ }, }; 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 = "2.0.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 reChaptersOnlyNumbers = new RegExp("([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 smallHeight = "250"; 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 ALLEXTERNALLINKNODES = []; let previousTitelHover, currentTitelHover, currentCoverData, currentPopupEvent; let popover, popoverTitle, popoverContent; let lastTarget; let isShowingSpinnerAnimation = false; let showDescription = false; let showSmaller = false; let showHotkeys = false; let autoScrollCoverData = true; let coverDataContainer; let mediumTextStyle = "mediumText"; let smallTextStyle = "smallText"; let pressedKeys = []; let mangaDexTAGS; //#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, chapters: coverData.chapters, genre: coverData.genre, showTags: coverData.showTags, description: coverData.description, isExternal: coverData.isExternal, }; } 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, chapters: coverData.chapters, genre: coverData.genre, showTags: coverData.showTags, description: coverData.description, isExternal: coverData.isExternal, 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.maxHeight = "min(400px,calc(100% - " + popoverHeightMargin + "px))"; popover.style.maxWidth = "min(800px,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; autoScrollData(); } } function tryToGetTextContent(element, query, queryName) { let result = element; if (result && result !== undefined) result = result.textContent; else if (element !== null && element !== undefined) { console.log( "Wrong or changed querySelector for " + queryName + ". not: " + query ); } return result; } function getTargetDomain(individualSiteLink) { let domain = ""; if (individualSiteLink) { console.log(individualSiteLink); domain = individualSiteLink.slice(0, individualSiteLink.indexOf("/")); console.log(domain); } return domain; } function getMangaDexID(link) { const DEBUG = false; let ID; if (link) { DEBUG && console.group("getMangaDexID"); DEBUG && console.log(link); const isMangaDexLink = link.indexOf("mangadex.org/title/"); if (isMangaDexLink >= 0) { const stringFromID = link.slice(isMangaDexLink + 19); DEBUG && console.log(stringFromID); ID = stringFromID.slice(0, stringFromID.indexOf("/")); DEBUG && console.log(ID); } DEBUG && console.groupEnd(); } return ID; } async function getCoverDataFromUrl( elementUrl, external = false, individualPage = undefined ) { 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 ); async 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); } let temp; /* const imageLinkByTag = temp.getElementsByTagName("img"); console.log(imageLinkByTag)*/ let imagelink; let serieTitle; let serieVotes; let serieStatus; let serieChapters; let serieGenre; let serieShowtags; let serieDescription; //console.log(externalLinks); const externalLinkKeys = Object.keys(externalLinks); const isExternal = externalLinkKeys.some((key) => xhr.finalUrl.includes(key) ); //console.log(xhr); let hasExternalTargetPage = externalLinks[individualPage]; const mangaDexID = getMangaDexID(xhr.finalUrl); DEBUG && console.log( "isExternal: " + isExternal + ", hasExternalTargetPage: " + hasExternalTargetPage + " for individualPage: " + individualPage ); /* const externalLinkKeys = Object.keys(externalLinks); console.log(externalLinkKeys);*/ let targetDomain; if (isExternal) { if (individualPage != undefined) { DEBUG && console.group("external link"); if (mangaDexID) { const mangaDexCoverData = await getMangaDexIndexData( mangaDexID ); //console.log(mangaDexCoverData); imagelink = mangaDexCoverData.url; serieTitle = mangaDexCoverData.title; serieVotes = mangaDexCoverData.votes; serieStatus = mangaDexCoverData.status; serieChapters = mangaDexCoverData.chapters; serieGenre = mangaDexCoverData.genre; serieShowtags = mangaDexCoverData.tags; serieDescription = mangaDexCoverData.description; } else { //console.log(domDocument); //external links const targetPage = hasExternalTargetPage; targetDomain = getTargetDomain(individualPage); //console.log(targetPage); temp = domDocument.querySelectorAll( targetPage.IMAGELINKCONTAINERS ); DEBUG && console.log(temp); imagelink = temp[targetPage.CONTAINERNUMBER]; serieTitle = domDocument.querySelector( targetPage.seriePageTitle ); serieVotes = domDocument.querySelector( targetPage.seriePageVotes ); serieStatus = domDocument.querySelector( targetPage.seriePageStatus ); serieChapters = domDocument.querySelector( targetPage.seriePageChapters ); serieGenre = domDocument.querySelector( targetPage.seriePageGenre ); serieShowtags = domDocument.querySelector( targetPage.seriePageTags ); serieDescription = domDocument.querySelector( targetPage.seriePageDescription ); serieTitle = tryToGetTextContent( serieTitle, targetPage.seriePageTitle, "seriePageTitle" ); serieVotes = tryToGetTextContent( serieVotes, targetPage.seriePageVotes, "seriePageVotes" ); serieStatus = tryToGetTextContent( serieStatus, targetPage.seriePageStatus, "seriePageStatus" ); serieChapters = tryToGetTextContent( serieChapters, targetPage.seriePageChapters, "seriePageChapters" ); serieGenre = tryToGetTextContent( serieGenre, targetPage.seriePageGenre, "seriePageGenre" ); serieShowtags = tryToGetTextContent( serieShowtags, targetPage.seriePageTags, "seriePageTags" ); serieDescription = tryToGetTextContent( serieDescription, targetPage.seriePageDescription, "seriePageDescription" ); } DEBUG && console.groupEnd("external link"); } } else { //internal links DEBUG && console.group("internal link"); targetDomain = getTargetDomain(INDIVIDUALPAGETEST); temp = domDocument.querySelectorAll(IMAGELINKCONTAINERS); //console.log(IMAGELINKCONTAINERS); //console.log(temp); DEBUG && console.log(temp); imagelink = temp[CONTAINERNUMBER]; //console.log(imagelink); serieTitle = domDocument.querySelector(seriePageTitle); serieVotes = domDocument.querySelector(seriePageVotes); serieStatus = domDocument.querySelector(seriePageStatus); serieChapters = domDocument.querySelector(seriePageChapters); serieGenre = domDocument.querySelector(seriePageGenre); serieShowtags = domDocument.querySelector(seriePageTags); serieDescription = domDocument.querySelector(seriePageDescription); serieTitle = tryToGetTextContent( serieTitle, seriePageTitle, "seriePageTitle" ); serieVotes = tryToGetTextContent( serieVotes, seriePageVotes, "seriePageVotes" ); serieStatus = tryToGetTextContent( serieStatus, seriePageStatus, "seriePageStatus" ); serieChapters = tryToGetTextContent( serieChapters, seriePageChapters, "seriePageChapters" ); serieGenre = tryToGetTextContent( serieGenre, seriePageGenre, "seriePageGenre" ); serieShowtags = tryToGetTextContent( serieShowtags, seriePageTags, "seriePageTags" ); serieDescription = tryToGetTextContent( serieDescription, seriePageDescription, "seriePageDescription" ); DEBUG && console.groupEnd("internal link"); } if (imagelink !== undefined) { if (!mangaDexID) imagelink = imagelink.getAttribute("src"); DEBUG && console.log(imagelink); if (imagelink.startsWith("//")) imagelink = "https://" + imagelink.slice(2); if (imagelink.startsWith("/")) { DEBUG && console.log(targetDomain); DEBUG && console.log(imagelink); imagelink = targetDomain + imagelink; } } DEBUG && console.log(serieTitle); DEBUG && console.log(serieVotes); DEBUG && console.log(serieStatus); DEBUG && console.log(serieChapters); DEBUG && console.log(serieGenre); DEBUG && console.log(serieShowtags); DEBUG && console.log(serieDescription); //console.log("save imageUrl into coverData.url: " + imagelink); let cData = { url: imagelink, title: serieTitle, votes: serieVotes, status: serieStatus, chapters: serieChapters, genre: serieGenre, showTags: serieShowtags, description: serieDescription, isExternal: isExternal, }; DEBUG && console.log(cData); //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 await PromiseResult; } async function parseSeriePage( elementUrl, forceReload = false, hoveredTitle = undefined, event = undefined, external = false, targetPage = undefined ) { const DEBUG = false; DEBUG && console.group("parseSeriePage: " + elementUrl); const coverData = GM_getCachedValue(elementUrl); /* console.group("parseSeriePage get coverData for " + elementUrl); console.log(coverData); console.groupEnd("parseSeriePage: " + 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, external, targetPage); } DEBUG && console.groupEnd("parseSeriePage: " + elementUrl); PromiseResult = await PromiseResult; //console.log(PromiseResult) //DEBUG && console.log(PromiseResult) //after GM_xmlhttpRequest PromiseResult return PromiseResult; } function removeEventListenerFromNodes(targetNodeArray, external = false) { if (targetNodeArray && targetNodeArray.length > 0) { //console.log(targetNodeArray); targetNodeArray.map(function (el) { if ( eventListenerStyle === undefined || eventListenerStyle === null || eventListenerStyle == 0 ) { el.removeEventListener("mouseenter", mouseEnterPopup); el.removeEventListener("mouseleave", hideOnMouseLeave); } }); } } function preloadForIndividualPageTest( targetNodeArray = [], IndividualTargetToTest, external = false ) { const DEBUG = false; DEBUG && console.log(targetNodeArray); 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 (targetNodeArray && targetNodeArray.length > 0) { //console.log(targetNodeArray); targetNodeArray.map(function (el) { //console.log(el) const elementUrl = el.href; // console.log(elementUrl) if ( eventListenerStyle === undefined || eventListenerStyle === null || eventListenerStyle == 0 ) { //console.log(el); //TODO external overwrite/removes previous mouseEnterPopup? el.addEventListener("mouseenter", mouseEnterPopup); el.addEventListener("mouseleave", hideOnMouseLeave); /* if (external) el.setAttribute("coverDataExternalTarget", IndividualTargetToTest);*/ } }); } } function preloadCoverData() { const DEBUG = false; //#region create complete nodelist ALLSERIENODES = []; ALLEXTERNALLINKNODES = []; const externalLinkKeys = Object.keys(externalLinks); updateSerieNodes(ALLSERIENODES, INDIVIDUALPAGETEST); if (externalLinks && externalLinkKeys.length > 0) { for (let i = 0; i < externalLinkKeys.length; i++) { updateSerieNodes(ALLEXTERNALLINKNODES, externalLinkKeys[i], true); } } //#endregion removeEventListenerFromNodes(ALLSERIENODES); preloadForIndividualPageTest(ALLSERIENODES, INDIVIDUALPAGETEST); //console.log(externalLinks); //console.log(externalLinkKeys[0]); //console.log(externalLinkKeys.length); removeEventListenerFromNodes(ALLEXTERNALLINKNODES); if (externalLinks && externalLinkKeys.length > 0) { for (let i = 0; i < externalLinkKeys.length; i++) { preloadForIndividualPageTest( ALLEXTERNALLINKNODES, externalLinkKeys[i], true ); } } } 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; } } .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 8px; min-height:unset; 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; line-height:1.4em; } .defaultTitleStyleSmall { line-height:1.2em; } .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; height:auto; padding:0; background-color:#fff; } .ImgFitDefault{ object-fit: contain; min-width: 0; min-height: 0; max-height: 400px; max-width: 400px; width:100%; height:100%; padding:2px; position:unset; } #coverPreviewAutoScroll#style-4::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); background-color: #F5F5F5; } #coverPreviewAutoScroll::-webkit-scrollbar { width: 2px; background-color: #F5F5F5; } #coverPreviewAutoScroll::-webkit-scrollbar-thumb { background-color: #888; } #coverPreviewAutoScroll{ scrollbar-width: thin; scrollbar-color: #888 #F5F5F5; } #popover{ /* min() not compatible with firefox 56 max-height: min(400px, (100vh - (100vh - 100%) - 44px)); max-width: min(400px, calc(100vw - (100vw - 100%))); */ max-height: calc(100vh - (100vh - 100%) - 44px); max-width: calc(100vw - (100vw - 100%)); min-height: 0; min-width: 0; /*height: 400px;*/ 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; text-align: center !important; justify-content: start; justify-items: center; display: flex; flex-shrink: 1; flex-direction: column; } .isExternalContent{ border:2px solid red !important; } .popoverContent { text-align: center !important; justify-content: center; justify-items: center; align-items: center; display: flex; flex-direction: column; min-height: 0; min-width: 0; padding: 1px !important; width: 100%; height: 100%; flex: 1; padding:1px !important; } .popoverDetail{ flex-direction:unset !important; height:400px; } .coverDataTitle{ border-bottom:1px solid white; padding:2px 0; } .containerPadding{ justify-items:center; padding:10px } .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; /*overflow:auto;*/ word-break: break-word; /* word wrap/break long links/texts */ } .smallText{ font-size: 0.8em; } .mediumText{ font-size: 0.98em; } .small_smallText{ display:inline-block; /* line height not working if the element is not a block */ font-size: 0.82em; line-height: 1.4em; } .small_mediumText{ display:inline-block; font-size: 0.78em; line-height: 1.2em; } .wordBreak { word-wrap: break-word !important; word-break: break-word; } .borderTop { width:100%; border-top:1px solid#fff; margin: 2px 0; } `); } 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 setPopoverHeight() { //https://developer.mozilla.org/en-US/docs/Web/CSS/min() not compatible with firefox 56 let targetHeight = defaultHeight; if (showSmaller) targetHeight = smallHeight; const minHeightValue = "min(" + targetHeight + "px, (100vh - (100vh - 100%) - " + offsetToBottomBorderY * 2 + "px))"; if (CSS.supports("max-Height", minHeightValue)) { //console.log("supports min()"); popover.style.maxHeight = minHeightValue; } else { console.log("does not support CSS min() for max-Height"); popover.style.maxHeight = "calc(100vh - (100vh - 100%) - " + offsetToBottomBorderY * 2 + "px))"; popover.style.height = targetHeight + "px"; } } function setPopoverWidth() { let targetHeight = defaultHeight; if (showSmaller) targetHeight = smallHeight; if (showDetails) { popover.classList.add("popoverDetail"); popoverTitle.classList.add("popoverTitleDetail"); const minWidthValue = "min(" + targetHeight * 2 + "px, (100vw - (100vw - 100%) - " + offsetToRightBorderX * 2 + "px))"; const supportsCSSMin = CSS.supports("max-Width", minWidthValue); if (supportsCSSMin) { //console.log("supports min()"); popover.style.maxWidth = minWidthValue; } else { console.log("does not support CSS min() for max-Width"); popover.style.maxWidth = "calc(100vw - (100vw - 100%) - " + offsetToRightBorderX * 2 + "px))"; popover.style.width = targetHeight * 2 + "px"; } } else { popover.classList.remove("popoverDetail"); popoverTitle.classList.remove("popoverTitleDetail"); const minWidthValue = "min(" + targetHeight + "px, (100vw - (100vw - 100%) - " + offsetToRightBorderX * 2 + "px))"; const supportsCSSMin = CSS.supports("max-Width", minWidthValue); if (supportsCSSMin) { popover.style.maxWidth = minWidthValue; } else { popover.style.maxWidth = "calc(100vw - (100vw - 100%) - " + offsetToRightBorderX * 2 + "px))"; //popover.style.height = targetHeight + "px !important"; popover.style.width = targetHeight + "px"; } } } function createPopover() { let bodyElement = document.getElementsByTagName("BODY")[0]; popover = document.createElement("div"); popover.id = "popover"; popoverTitle = document.createElement("header"); popoverContent = document.createElement("content"); popover.appendChild(popoverTitle); popover.appendChild(popoverContent); popover.className = ( "defaultBackgroundStyle " + STYLESHEETHIJACKFORBACKGROUND ).trim(); popoverContent.className = "popoverContent blackFont"; if ( !STYLESHEETHIJACKFORBACKGROUND && DEFAULTBACKGROUNDCOLOR && DEFAULTBACKGROUNDCOLOR != "" ) popover.style.backgroundColor = DEFAULTBACKGROUNDCOLOR; //setPopoverHeight(); //setPopoverWidth(); setTimeout(setPopoverHeight, 500); //hack. why is a wait time needed? setTimeout(setPopoverWidth, 500); //hack. Can not apply style.height without a short wait time in older firefox 56 //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); /* console.log("popover.style.height: " + popover.style.height); popover.style.minHeight="0px"; popover.style.position="absolute"; popover.style.height = '333px'; console.log("popover.style.height: " + popover.style.height); setPopoverHeight();*/ } 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) { popover.classList.remove("isExternalContent"); //last link was external. remove isExternal class style // 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 != "") { isShowingSpinnerAnimation = false; popoverContent.innerHTML = notification; popoverContent.className = "popoverContent wordBreak"; //blackfont } else { isShowingSpinnerAnimation = true; 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 " + STYLESHEETHIJACKFORBACKGROUND; //whitefont } DEBUG && console.log(popover); // DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight); //console.log(event) popupPos(event); // console.groupEnd("showPopupLoadingSpinner") } } //#region adapted code from scrollToTarget of https://htmldom.dev/scroll-to-an-element-smoothly/ let direction = 1; let pauseTimeDifference = null; let currentPercent = null; let percentBeforeStyleChange; let hasChangedStyle = false; let requestId; let startTime = null; const scrollToTarget = function (node, duration = 7000) { const DEBUG = false; let scrollOverflow = node.scrollHeight - node.offsetHeight; const updateStartValues = function (percent, currentTime) { if (percent) { DEBUG && console.group("updateStartValues"); scrollOverflow = node.scrollHeight - node.offsetHeight; startTime = currentTime - pauseTimeDifference; pauseTimeDifference = null; // if (direction == 1) startPos = scrollOverflow * percent; // else startPos = scrollOverflow * (1 - percent); DEBUG && console.log("percent: " + percent + ", startPos: "); let time = currentTime - startTime; //console.log("pauseTimeDifference, time: " + time); let targetPercent = Math.min(time / duration, 1); DEBUG && console.log( "percent after pause: " + targetPercent + ", percent: " + percent + ", direction: " + direction + ", scrolltop percent: " ); DEBUG && console.groupEnd("updateStartValues"); } }; const loop = function (currentTime) { if (!startTime) { startTime = currentTime; } //console.log("scrollOverflow: " + scrollOverflow); //#region set StartValues if (currentPercent != undefined && currentPercent !== null) { DEBUG && console.log( "currentPercent:" + currentPercent + ", direction: " + direction ); updateStartValues(currentPercent, currentTime); currentPercent = null; } if (hasChangedStyle) { DEBUG && console.log("hasChangedStyle"); updateStartValues(percentBeforeStyleChange, currentTime); hasChangedStyle = false; } //#endregion // Elapsed time in miliseconds let time = currentTime - startTime; const percent = Math.min(time / duration, 1); let targetScrollTop, targetScrollTopPercent; if (direction == 1) { targetScrollTopPercent = easeInOutQuad(percent); } else { targetScrollTopPercent = 1 - easeInOutQuad(percent); } targetScrollTop = scrollOverflow * targetScrollTopPercent; //console.log(targetScrollTop +", percent: " + percent) node.scrollTo(0, targetScrollTop, "auto"); pauseTimeDifference = currentTime - startTime; if (autoScrollCoverData && popoverVisible) { //#region loop Animation const insideContainerValue = targetScrollTop <= scrollOverflow && targetScrollTop >= 0; if (time < duration && insideContainerValue) { // Continue moving //requestId = window.requestAnimationFrame(loop); //startPos = 0; } else { //startPos=0; startTime = currentTime; direction *= -1; } percentBeforeStyleChange = percent; requestId = window.requestAnimationFrame(loop); //#endregion } else { //#region pause animation //console.group("loop scrolldata before pause"); window.cancelAnimationFrame(requestId); //pauseTimeDifference = currentTime - startTime; currentPercent = percent; DEBUG && console.log( "scrollPos before pause: " + node.scrollTop + ", percent: " + percent + ", direction: " + direction + ", targetScrollTop: " + targetScrollTop ); //console.groupEnd("loop scrolldata before pause"); //#endregion } }; //start animation requestId = window.requestAnimationFrame(loop); }; const easeInOutQuad = (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t); //https://gist.github.com/gre/1650294 //#endregion function autoScrollData() { coverDataContainer = document.getElementById("coverPreviewAutoScroll"); setStartScrollPosition(); if (autoScrollCoverData) { if (coverDataContainer) { /*console.log( "coverDataContainer.offsetHeight: " + coverDataContainer.offsetHeight + ", coverDataContainer.scrollHeight: " + coverDataContainer.scrollHeight );*/ const hasOverflowValue = coverDataContainer.scrollHeight > coverDataContainer.offsetHeight; if (hasOverflowValue) { if (requestId) window.cancelAnimationFrame(requestId); //console.log("currentPercent: " + currentPercent); scrollToTarget(coverDataContainer); } } } } function resetAutoScroll() { //autoScrollCoverData = true; direction = 1; currentPercent = null; startTime = null; pauseTimeDifference = null; hasChangedStyle = false; //console.log(requestId); if (requestId) window.cancelAnimationFrame(requestId); } function setStartScrollPosition() { const DEBUG = false; if (coverDataContainer && currentPercent) { let scrollOverflow = coverDataContainer.scrollHeight - coverDataContainer.offsetHeight; DEBUG && console.log( "scrollOverflow: " + scrollOverflow + ", currentPercent: " + currentPercent ); let targetScrollTop, targetScrollTopPercent; if (direction == 1) { targetScrollTopPercent = easeInOutQuad(currentPercent); } else { targetScrollTopPercent = 1 - easeInOutQuad(currentPercent); } targetScrollTop = scrollOverflow * targetScrollTopPercent; DEBUG && console.log("targetScrollTop: " + targetScrollTop); DEBUG && console.log( "coverDataContainer.scrollTop: " + coverDataContainer.scrollTop ); coverDataContainer.scrollTop = targetScrollTop; DEBUG && console.log( "coverDataContainer.scrollTop: " + coverDataContainer.scrollTop ); } } function refreshPopover(coverData, e = undefined) { //only call when isActivePopup const DEBUG = false; if (coverData && coverData !== undefined) { isShowingSpinnerAnimation = false; DEBUG && console.log("currentTitelHover: " + currentTitelHover); DEBUG && console.group("refreshPopover"); const link = coverData.url; const title = coverData.title; //console.log(coverData) //console.log(e) // popoverTitle.textContent = title; // console.log(link) if (link === undefined || inBlocklist(link)) { popoverContent.innerHTML = '<div class="containerPadding">Blocked Image<br />No Cover Image<br />Unwanted Image</div>'; } else { let imgElement = new Image(); //document.createElement("img"); imgElement.src = link; popoverContent.innerHTML = '<img src="' + link + '" class="ImgFitDefault ' + STYLESHEETHIJACKFORBACKGROUND + '" ></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) { const DEBUG = false; let result; if (statusString && statusString.length > 0) { DEBUG && console.group("getChapters"); let chapterCount; let lowerCaseStatusString = statusString.toLowerCase(); DEBUG && console.log("lowerCaseStatusString: " + lowerCaseStatusString); const matches = lowerCaseStatusString.match(reChapters); let webnovel = ""; let hasVolumenInString = false; if (matches && matches.length >= 2) { chapterCount = matches[1]; if (matches[2]) { webnovel = " WN"; } } DEBUG && console.log("chapterCount reChapters: " + chapterCount); if (!chapterCount) { const matchesBehind = lowerCaseStatusString.match(reChaptersNumberBehind); if (matchesBehind && matchesBehind.length >= 2) { chapterCount = matchesBehind[1]; } } DEBUG && console.log("chapterCount reChaptersNumberBehind: " + chapterCount); if (!chapterCount) { const matchesNumbers = lowerCaseStatusString.match(reChaptersOnlyNumbers); //example string "6892(Ongoing)" if (matchesNumbers && matchesNumbers.length >= 2) { chapterCount = matchesNumbers[1]; } } DEBUG && console.log("chapterCount reChaptersOnlyNumbers: " + chapterCount); if (lowerCaseStatusString.includes("vol")) hasVolumenInString = true; if (chapterCount) { let numberType = " Chapters"; if (hasVolumenInString) numberType = " Vol"; result = chapterCount + webnovel + numberType; } DEBUG && console.groupEnd(); } DEBUG && console.log("result: " + result); 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 (showDescription) { if (coverData.description && coverData.description.length > 0) { completeDetails += '<div class="borderTop">Description: ' + coverData.description + "</div>"; } else { completeDetails += '<div class="borderTop">Description: Description Empty or error in coverData. Please reload seriepage info</div>'; } } else { if (coverData.votes) { completeDetails += '<div class="borderTop">Rating: ' + coverData.votes + "</div>"; } if (coverData.status) { completeDetails += '<div class="borderTop">Status: ' + coverData.status + "</div>"; } if (coverData.chapters) { completeDetails += '<div class="borderTop">Chapters: ' + coverData.chapters + "</div>"; } if (coverData.genre) { completeDetails += '<div class="borderTop">Genre: ' + coverData.genre + "</div>"; } if (coverData.showTags) { completeDetails += '<div class="borderTop">Tags: ' + coverData.showTags + "</div>"; } } return completeDetails; } function getShortendDetailsString(coverData) { let completeDetails = ""; let rating = getRatingNumber(coverData.votes); let chapters = getChapters(coverData.status); let serieChapters = getChapters(coverData.chapters); let completed = getCompletedState(coverData.status); let ongoing = getOngoingState(coverData.status); //console.log(rating) //console.log(chapters) //console.log(serieChapters) //console.log(completed) //console.log(ongoing) if (rating || chapters || serieChapters || completed || ongoing) { if (rating !== undefined) rating += "★ "; else rating = ""; //console.log(coverData); if (chapters !== undefined) chapters = chapters + " "; else chapters = ""; if (serieChapters !== undefined) serieChapters = serieChapters + " "; else serieChapters = ""; //console.log("chapters: " + chapters); //console.log("serieChapters: " + serieChapters); if (serieChapters != "") chapters = ""; if (completed) completed = "🗹 "; else completed = ""; //https://www.utf8icons.com/ if (ongoing) ongoing = "✎ "; else ongoing = ""; completeDetails += '<span class="' + smallTextStyle + '" style="white-space: nowrap;"> [' + rating + chapters + serieChapters + 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 = ""; let externalIcon = ""; if (coverData.isExternal) { externalIcon = '<span style="background-color:darkred">🔗</span> '; popover.classList.add("isExternalContent"); } else { popover.classList.remove("isExternalContent"); } if (showDetails) { //console.log("showDetails should be true") completeDetails += '<span class="' + mediumTextStyle + '" style="height:100%;display:flex;flex-direction:column"><span class="coverDataTitle">' + externalIcon + titleToShow + "</span> " + '<div style="overflow:auto;" id="coverPreviewAutoScroll">' + getDetailsString(coverData); +"</div>"; //autoscroll completeDetails += '<div class="borderTop ' + smallTextStyle + '">[KeyH show hotkey list]<br />[Key1 Switch detailed and simple popup] [Key2 Switch between description and tags] [Key3 small and big popup style] </div></span>'; } else { completeDetails = '<span class="' + mediumTextStyle + '">' + externalIcon + titleToShow + " " + getShortendDetailsString(coverData); completeDetails += ' <span class="' + smallTextStyle + '">[KeyH hotkey list]</span></span>'; } //popoverTitle.innerHTML = completeDetails; popoverTitle.innerHTML = completeDetails; } //#endregion function setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, e) { const DEBUG = false; //GM_getCachedValue DEBUG && console.group("setCurrentCoverDataAndLoadImage"); 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 && hoveredTitle == currentTitelHover ) currentCoverData = coverData; if (e) { loadImageFromBrowser({ coverData: currentCoverData, e: e, serieTitle: serieTitle, hoveredTitleLink: hoveredTitle, }); } DEBUG && console.groupEnd("setCurrentCoverDataAndLoadImage"); } function ajaxLoadImageUrlAndShowPopup( forceReload = false, elementUrl, hoveredTitle, e, external = false, targetPage = undefined ) { const currentEvent = e; //console.log(currentEvent) //console.log("mouseenter") // console.group("ajaxLoadImageUrlAndShowPopup") return parseSeriePage( elementUrl, forceReload, hoveredTitle, currentEvent, external, targetPage ).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); let filename = ""; console.log("coverData.url: " + coverData.url); if (coverData.url != "undefined") { filename = decodeURIComponent(coverData.url); } else { filename = "undefined"; } const errorMessage = '<div class="containerPadding">browser blocked/has error loading the file: <br />' + filename + "</div>"; 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/reload the coverdata" ); else console.log( "image loading most likely blocked by browser or addon. Check if the imagelink still exists/reload the coverdata" ); // if (isActivePopup) showPopupLoadingSpinner( hoveredTitleLink, serieTitle, e, errorMessage, coverData ); console.groupEnd("loadImageFromBrowser img.onerror"); } function loadImageFromBrowser({ coverData, e = undefined, serieTitle = 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); }; //console.log(coverData) if (coverData !== undefined) { 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, "", coverData); // } } } // 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; if (isShowingSpinnerAnimation) 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( targetNodeArray = [], individualLinksToTest, external = false ) { if (targetNodeArray && targetNodeArray.length > 0) { targetNodeArray.forEach(function (selector) { if ( eventListenerStyle === undefined || eventListenerStyle === null || eventListenerStyle == 0 ) { selector.removeEventListener("mouseleave", hideOnMouseLeave); //selector.removeEventListener("mouseenter", mouseEnterPopup); } }); } let serieLinkNodes = document.querySelectorAll( 'a[href*="' + individualLinksToTest + '"]' ); //console.log(serieLinkNodes) serieLinkNodes = Array.from(serieLinkNodes); if (serieLinkNodes && serieLinkNodes.length > 0) { //console.log(serieLinkNodes); serieLinkNodes.map(function (el) { //console.log(el) const elementUrl = el.href; // console.log(elementUrl) if (external) el.setAttribute("coverDataExternalTarget", individualLinksToTest); if (preloadUrlRequests) { DEBUG && console.log("start parseSeriePage"); let targetPage = undefined; if (external) { targetPage = individualLinksToTest; // console.log("targetPage: " + targetPage); } // console.log("external: " + external); parseSeriePage(elementUrl, false, undefined, external, targetPage).then( function (coverData) { if (coverData !== undefined) { if (preloadImages) { console.log("preloadCoverData preloadImages: " + preloadImages); console.log(coverData); loadImageFromBrowser({ coverData: coverData }); } } }, function (Error) { DEBUG && console.log(Error + " failed to fetch " + el); } ); } }); } targetNodeArray.push(...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"); if (currentCoverData !== undefined) { //has CoverData from seriePage loaded? DEBUG && console.log(currentCoverData); //refreshPopover(currentCoverData, currentPopupEvent); //update on detail change loadImageFromBrowser({ coverData: currentCoverData, e: currentPopupEvent, serieTitle: currentTitelHover, hoveredTitleLink: currentTitelHover, }); } console.groupEnd("switchDetails"); } function switchTagsDescriptionAndUpdatePopup() { const DEBUG = false; if (showDetails) { showDescription = !showDescription; //console.log("switch showDetails to : " + showDetails) GM_setValue("showDescription", showDescription); if (currentCoverData !== undefined) { //has CoverData from seriePage loaded? DEBUG && console.log(currentCoverData); //refreshPopover(currentCoverData, currentPopupEvent); //update on detail change loadImageFromBrowser({ coverData: currentCoverData, e: currentPopupEvent, serieTitle: currentTitelHover, hoveredTitleLink: currentTitelHover, }); } } } function changeToNewDetailStyle(toggleDetails = true) { if (toggleDetails) showDetails = !showDetails; //console.log("switch showDetails to : " + showDetails) GM_setValue("showDetails", showDetails); //localStorage.setItem("showDetails", showDetails); //https://developer.mozilla.org/en-US/docs/Web/CSS/min() not compatible with firefox 56 setPopoverWidth(); setPopoverHeight(); } //#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) { e.preventDefault(); const target = e.target; let Href = target.href; // element.attr('href'); let coverDataExternalTarget = target.getAttribute( "coverDataExternalTarget" ); //console.log("coverDataExternalTarget: " + coverDataExternalTarget); if (Href && coverDataExternalTarget === undefined) { //no preloadUrlRequests happend const externalLinkKeys = Object.keys(externalLinks); const isExternal = externalLinkKeys.some((key) => Href.includes(key)); if (isExternal) { el.setAttribute("coverDataExternalTarget", IndividualTargetToTest); coverDataExternalTarget = IndividualTargetToTest; } else el.setAttribute("coverDataExternalTarget", null); } DEBUG && console.log( "Href: " + Href + ", INDIVIDUALPAGETEST: " + INDIVIDUALPAGETEST ); if ( Href && (Href.includes(INDIVIDUALPAGETEST) || (coverDataExternalTarget !== undefined && coverDataExternalTarget !== null && Href.includes(coverDataExternalTarget))) ) { //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 const wasOverDifferentLink = currentTitelHover != previousTitelHover; if (wasOverDifferentLink) { resetAutoScroll(); autoScrollCoverData = true; } if (currentTitelHover != undefined) previousTitelHover = currentTitelHover; currentPopupEvent = e; //console.log(serieTitle) //console.log(Href) //console.log(currentCoverData) //console.log("currentTitelHover: " + currentTitelHover) const external = coverDataExternalTarget !== undefined && coverDataExternalTarget !== null; let targetPage; //console.log("external: " + external); //console.log("coverDataExternalTarget: " + coverDataExternalTarget); if (external) targetPage = coverDataExternalTarget; //console.log("targetPage: " + targetPage); ajaxLoadImageUrlAndShowPopup( forceReload, Href, currentTitelHover, e, external, targetPage ); } } DEBUG && console.groupEnd("mouseEnterPopup"); } function forceReload(forceReload = true) { mouseEnterPopup(currentPopupEvent, forceReload); } function updatePopoverSize() { setPopoverHeight(); setPopoverWidth(); if (showSmaller) { mediumTextStyle = "small_mediumText"; smallTextStyle = "small_smallText"; popoverTitle.classList.add("defaultTitleStyleSmall"); } else { popoverTitle.classList.remove("defaultTitleStyleSmall"); mediumTextStyle = "mediumText"; smallTextStyle = "smallText"; } if (currentCoverData !== undefined) { //refreshPopover(currentCoverData, currentPopupEvent); loadImageFromBrowser({ coverData: currentCoverData, e: currentPopupEvent, serieTitle: currentTitelHover, hoveredTitleLink: currentTitelHover, }); /* , currentTitelHover, currentTitelHover */ } else if (currentTitelHover !== undefined) { //currentCoverData not yet set showPopupLoadingSpinner( currentTitelHover, currentTitelHover, currentPopupEvent ); } } function showHotkeyList() { if (!showHotkeys) { if (currentCoverData !== undefined) // refreshPopover(currentCoverData, currentPopupEvent); loadImageFromBrowser({ coverData: currentCoverData, e: currentPopupEvent, serieTitle: currentTitelHover, hoveredTitleLink: currentTitelHover, }); /* */ } else { popoverContent.innerHTML = '<div class="' + mediumTextStyle + '" style="text-align:start">Key 1: Switch detailed And simple popup<br />' + `Key 2: Switch between description and tags<br /> Key 3: Switch between small and big popup style<br /> Key 4: Pause/unpause autoscrolling coverData<br/> Key 5: Reload coverdata of seriepage<br /> Key 6: Clear all cover data info<br /> Key H: Show this hotkey list during holding of key h </div>`; if (currentCoverData !== undefined) popupPos(currentPopupEvent); } } 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 "5": forceReload(); break; case "6": resetDatabase(); preloadCoverData(); forceReload(); break; case "2": switchTagsDescriptionAndUpdatePopup(); resetAutoScroll(); autoScrollCoverData = true; autoScrollData(); break; case "3": showSmaller = !showSmaller; GM_setValue("showSmaller", showSmaller); updatePopoverSize(); hasChangedStyle = true; break; case "4": autoScrollCoverData = !autoScrollCoverData; if (autoScrollCoverData) autoScrollData(); break; case "h": showHotkeys = true; showHotkeyList(); break; } } } } function releaseKey(event) { const key = event.key; //console.log(pressedKeys) pressedKeys.splice(pressedKeys.indexOf(key), 1); if (event.key == "h") { showHotkeys = false; showHotkeyList(); } //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? ALLSERIENODES = []; updateSerieNodes(ALLSERIENODES, INDIVIDUALPAGETEST); 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 getMangaDexTags() { const DEBUG = false; function onLoad(xhr) { //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299) if (xhr.status >= 200 && xhr.status < 399) { DEBUG && console.log(xhr); let tempJSON = JSON.parse(xhr.responseText); if ((tempJSON.status = "OK")); { mangaDexTAGS = tempJSON.data; } DEBUG && console.log(mangaDexTAGS); } } function onError(error) { console.log(error); const err = new Error( "GM_xmlhttpRequest could not load " + "https://mangadex.org/api/v2/tag" + "; script is not compatible or url does not exists." ); console.log(err); return reject(err); } GM_xmlhttpRequest({ method: "GET", responseType: "json", url: "https://mangadex.org/api/v2/tag", onload: onLoad, onerror: onError, }); } function getMangaDexNamedTag(tags, type = "Genre") { const DEBUG = false; DEBUG && console.group("getMangaDexGenre"); let namedTags = ""; DEBUG && console.log(mangaDexTAGS); DEBUG && console.log(tags); DEBUG && console.log("tags.length: " + tags.length); if (tags && tags.length > 0) { for (let i = 0; i < tags.length; i++) { let searchTag = tags[i]; //console.log(searchTag) const tag = mangaDexTAGS[searchTag]; if (tag.group == type) { //console.log(tag) namedTags += " " + tag.name; } } } DEBUG && console.groupEnd(); //console.log(namedTags) return namedTags; } async function getAllMangaDexTags() { const DEBUG = false; let PromiseResult = new Promise(async function (resolve, reject) { function onLoad(xhr) { //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299) if (xhr.status >= 200 && xhr.status < 399) { DEBUG && console.group("getMangaDexIndexData onLoad"); DEBUG && console.log(xhr); let tempJSON = JSON.parse(xhr.responseText); mangaDexTAGS; if ((tempJSON.status = "OK")); { mangaDexTAGS = tempJSON.data; } DEBUG && console.log(mangaDexTAGS); return resolve(mangaDexTAGS); } } function onError(error) { console.log(error); const err = new Error( "GM_xmlhttpRequest could not load " + "https://mangadex.org/api/v2/tag" + "; script is not compatible or url does not exists." ); console.log(err); return reject(err); } GM_xmlhttpRequest({ method: "GET", responseType: "json", url: "https://mangadex.org/api/v2/tag", onload: onLoad, onerror: onError, }); return undefined; //reject("status error") }); return PromiseResult; } async function getMangaDexChapterCount(id) { const DEBUG = false; if (id) { let PromiseResult = new Promise(async function (resolve, reject) { function onLoad(xhr) { //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299) let mangaDexData; if (xhr.status >= 200 && xhr.status < 399) { DEBUG && console.group("getMangaDexChapterCount onLoad"); DEBUG && console.log(xhr); let tempJSON = JSON.parse(xhr.responseText); if ((tempJSON.status = "OK")); { mangaDexData = tempJSON.data; } DEBUG && console.log(mangaDexData); DEBUG && console.log(mangaDexData.chapters.length); DEBUG && console.groupEnd("getMangaDexChapterCount onLoad"); } if (mangaDexData) return resolve(mangaDexData.chapters.length); else return reject( "api has not return data value for :" + "https://mangadex.org/api/v2/manga/" + id + "/chapters" ); } function onError(error) { console.log(error); const err = new Error( "GM_xmlhttpRequest could not load " + "https://mangadex.org/api/v2/tag" + "; script is not compatible or url does not exists." ); console.log(err); return reject(err); } GM_xmlhttpRequest({ method: "GET", responseType: "json", url: "https://mangadex.org/api/v2/manga/" + id + "/chapters", onload: onLoad, onerror: onError, }); return undefined; //reject("status error") }); return PromiseResult; } } async function getMangaDexIndexData(id) { const DEBUG = false; if (id) { let PromiseResult = new Promise(async function (resolve, reject) { async function onLoad(xhr) { //https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299) if (xhr.status >= 200 && xhr.status < 399) { DEBUG && console.group("getMangaDexIndexData onLoad"); DEBUG && console.log(xhr); let tempJSON = JSON.parse(xhr.responseText); let mangaDexData; if ((tempJSON.status = "OK")); { mangaDexData = tempJSON.data; } DEBUG && console.log(mangaDexData); let serieGenre = getMangaDexNamedTag(mangaDexData.tags, "Genre"); let serieTags = getMangaDexNamedTag(mangaDexData.tags, "Theme"); let serieChapters = await getMangaDexChapterCount(id); DEBUG && console.log(serieGenre); DEBUG && console.log(serieTags); let status; switch (mangaDexData.publication.status) { case 1: status = "Ongoing"; break; case 2: status = "Completed"; break; } let cData = { url: mangaDexData.mainCover, title: mangaDexData.title, votes: mangaDexData.rating.bayesian.toString(), status: status, chapters: serieChapters.toString(), genre: serieGenre, tags: serieTags, description: mangaDexData.description, isExternal: true, }; DEBUG && console.log(cData); DEBUG && console.groupEnd("getMangaDexIndexData onLoad"); return resolve(cData); } } function onError(error) { console.log(error); const err = new Error( "GM_xmlhttpRequest could not load " + "https://mangadex.org/api/v2/tag" + "; script is not compatible or url does not exists." ); console.log(err); return reject(err); } GM_xmlhttpRequest({ method: "GET", responseType: "json", url: "https://mangadex.org/api/v2/manga/" + id, onload: onLoad, onerror: onError, }); return undefined; //reject("status error") }); return PromiseResult; } } function main() { getAllMangaDexTags(); const DEBUG = false; DEBUG && console.log("started main function of coverPreview"); //#region get greasemonkey settings for popup DEBUG && console.log("before starting checkDataVersion"); checkDataVersion(); showDetails = GM_getValue("showDetails"); showDescription = GM_getValue("showDescription"); showSmaller = GM_getValue("showSmaller"); //#endregion DEBUG && console.log("before starting setStyleClasses"); addStyles(); setStyleClasses(); DEBUG && console.log("before starting createPopover"); createPopover(); DEBUG && console.log("before starting hidePopOver"); //#region preset needed for older browser //#endregion hidePopOver(); if (showSmaller) { //console.log("show smaller style"); updatePopoverSize(); } //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");
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址