您需要先安装一个扩展,例如 篡改猴、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 1.5.4 // @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages. // @inject-into content // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_listValues // @run-at document-end // @license http://creativecommons.org/licenses/by-nc-sa/4.0/ // ==/UserScript== const MAXCACHEAGE = 7 * 24 * 60 * 60 * 1000; // Max Age before Cached data gets overridden with current data. Max Age is 3 day in milliseconds //days * h * min * sec * ms let STYLESHEETHIJACKFORBACKGROUND = ".l-canvas"; //if unknown set empty ""; classname with leading dot let STYLESHEETHIJACKFORTITLE = '.widgettitle_nuf'; //if unknown set empty ""; classname with leading dot const DEFAULTTITLEBACKGROUNDCOLOR = '#2c3e50'; //if no hijack class style available use plain color const DEFAULTBACKGROUNDCOLOR = '#ccc'; //if no hijack class style available use plain color //const SELECTOR1 = 'td a'; //index/group/readinglist pages , forum //const SELECTOR2 = '.wpb_wrapper > a, .messageContent a'; //individual serie pages recommendation titles //, .signature a //links in forum signatures const PREDIFINEDNATIVTITLE = "^Recommended by"; //in case native title is used to display something different const INDIVIDUALPAGETEST = "novelupdates.com/series/"; const maxWaitingTime = 120; const IMAGELINKCONTAINERS = '.serieseditimg, .seriesimg'; //instead of single element class name with dot //const IMAGELINKCONTAINERSnonJquery = 'serieseditimg seriesimg'; //instead of single element class name with dot const IMAGEBLOCKER = "www.novelupdates.com/img/noimagefound.jpg"; //tested with string.match(). no need for prefixed http https in url. Can even be just the file name const CONTAINERNUMBER = 0; const preloadUrlRequests = true; const preloadImages = false; const isOnIndex = this.location.href == "https://www.novelupdates.com/" || this.location.href.startsWith("https://www.novelupdates.com/?pg=") == 1 const isOnReadingListIndex = this.location.href.startsWith("https://www.novelupdates.com/user/"); //to know when to switch between popup next to link or next to container of link //^^^^ frontend settings over this line ^^^^ const version = "1.5.4"; const forceUpdate = false; const RE = /\s*,\s*/; //Regex for split and remove empty spaces const defaultHeight = "400"; //in pixel const IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE); let showDetails = false; let ALLSERIENODES;// = document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]'); const offsetToBottomBorderY = 22; //offset to bottom border const offsetToRightBorderX = 10; //offset to right border let currentTitelHover, currentCoverData, currentPopupEvent; let popover, popover2, popoverTitle, popoverContent, popoverCoverImg; //console.log(this.location) //console.log(this.location.href) //console.log("isOnIndex: " + isOnIndex) //get value from key. Decide if timestamp is older than MAXCACHEAGE than look for new image function GM_getCachedValue(key) { const DEBUG = false; const currentTime = Date.now(); const rawCover = GM_getValue(key, null); DEBUG && console.group("GM_getCachedValue") DEBUG && console.log("rawCover: " + rawCover) let result = null; if (rawCover === null || rawCover == "null") { result = null; } else { let coverData; try { //is json parseable data? if not delete for refreshing coverData = JSON.parse(rawCover); DEBUG && console.log("coverData: " + coverData) DEBUG && console.log(coverData) if (!(coverData.url && coverData.title && coverData.cachedTime)) //has same variable definitions? { GM_deleteValue(key); result = null; } } catch (e) { GM_deleteValue(key); result = null; } const measuredTimedifference = currentTime - coverData.cachedTime; if (measuredTimedifference < MAXCACHEAGE) { result = { url: coverData.url, title: coverData.title, votes: coverData.votes, status: coverData.status, genre: coverData.genre, showTags: coverData.showTags }; } else { { GM_deleteValue(key); result = null; } } } DEBUG && console.groupEnd("GM_getCachedValue") DEBUG && console.log(result) return result; } //set value and currenttime for key function GM_setCachedValue(key, coverData) { const DEBUG = false; const cD = { url: coverData.url, title: coverData.title, votes: coverData.votes, status: coverData.status, genre: coverData.genre, showTags: coverData.showTags, cachedTime: Date.now() }; GM_setValue(key, JSON.stringify(cD)); DEBUG && console.group("GM_setCachedValue") DEBUG && console.log("save coverdata") DEBUG && console.log(cD) DEBUG && console.group("GM_setCachedValue") } function inBlocklist(link) { if (IMAGEBLOCKERARRAY) if (IMAGEBLOCKERARRAY.length > 0) for (let i = 0; i < IMAGEBLOCKERARRAY.length; i++) if (IMAGEBLOCKERARRAY[i] !== "") if (link.match(IMAGEBLOCKERARRAY[i])) return true; return false; } //https://medium.com/@alexcambose/js-offsettop-property-is-not-great-and-here-is-why-b79842ef7582 const getOffset = (element, horizontal = false) => { if (!element) return 0; return getOffset(element.offsetParent, horizontal) + (horizontal ? element.offsetLeft : element.offsetTop); } function getRectOffset(rect) { return { Rx: rect.left + rect.width, Ry: rect.top } } function chooseAndGetRectOffset(nativElement) { let targetedRect; if (isOnIndex || isOnReadingListIndex) { targetedRect = nativElement.parentElement.getBoundingClientRect(); } else { targetedRect = nativElement.getBoundingClientRect(); } return getRectOffset(targetedRect); } function getDistanceToBottom(Y, scrollPosY, popoverRect) { return Y - scrollPosY + popoverRect.height - (window.innerHeight - offsetToBottomBorderY); } function getPopupPos(event) { const DEBUG = false; const scrollPosY = window.scrollY || window.scrollTop || document.getElementsByTagName("html")[0].scrollTop; const scrollPosX = window.scrollX || window.scrollLeft || document.getElementsByTagName("html")[0].scrollLeft; //console.log(event) const nativElement = event.target; const parentElement = nativElement.parentElement; let X, Y; let distanceToBottom, distanceToRight; //console.log(element.parents()[0]) DEBUG && console.log(nativElement) X = scrollPosX; Y = scrollPosY; DEBUG && console.group("rects") DEBUG && console.log(nativElement.getBoundingClientRect()) DEBUG && console.log(parentElement.getBoundingClientRect()) DEBUG && console.groupEnd("rects") const popoverRect = popover.getBoundingClientRect(); const { Rx, Ry } = chooseAndGetRectOffset(nativElement); X += Rx; Y += Ry; DEBUG && console.log(popoverRect) DEBUG && console.group("calc vertical offset"); distanceToBottom = getDistanceToBottom(Y, scrollPosY, popoverRect); //console.log("distanceToBottom: " + distanceToBottom) if (distanceToBottom > 0) {//bottom offset Y -= distanceToBottom; } //console.log("Y: " + Y + ", scrollPosY: " + scrollPosY); if (Y < scrollPosY + offsetToBottomBorderY) { //top offset Y = scrollPosY + offsetToBottomBorderY; } DEBUG && console.groupEnd("calc vertical offset"); //console.log(popover.getBoundingClientRect()) DEBUG && console.group("calc horizontal offset"); const maxRightPos = scrollPosX + window.innerWidth; const popoverRightSide = X + popoverRect.width + offsetToRightBorderX; distanceToRight = popoverRightSide - maxRightPos; DEBUG && console.log("X: " + X + ", popoverRightSide: " + popoverRightSide + ", maxRightPos: " + maxRightPos + ", distanceToRight: " + distanceToRight + ", popoverRect.width: " + popoverRect.width + ", scrollPosX: " + scrollPosX); if (distanceToRight > 0) { X -= distanceToRight + offsetToRightBorderX; } /* if (X < scrollPosX + offsetToRightBorderX) { X = scrollPosX + offsetToRightBorderX; } */ DEBUG && console.groupEnd("calc horizontal offset"); return { Px: X, Py: Y } } // popupPositioning function function popupPos(event) { const DEBUG = false; DEBUG && console.group("popupPos style:" + style) //console.log(nativElement.parentElement) //let computedFontSizeJquery = parseInt(window.getComputedStyle(element.parents()[0]).fontSize); //const computedFontSize = parseInt(window.getComputedStyle(parentElement).fontSize); //console.log(computedFontSize); //Initialising variables (multiple usages) // console.log(scrollPosX) //var elementPopup = this[0]; //this = ontop of jquery object let elementImg = popover.getElementsByTagName("img"); DEBUG && console.log(popover) DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight) DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight) DEBUG && console.log(elementImg) if (elementImg) { DEBUG && console.log(elementImg) } 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) showPopOver(); DEBUG && console.groupEnd("popupPos") //console.log("final popup position "+X+' # '+Y); return this; }; async function parseSeriePage(elementUrl, title = undefined, event = undefined) { const DEBUG = false; DEBUG && console.group("parseSeriePage: " + elementUrl) let retrievedImgLink; let PromiseResult = new Promise(async function (resolve, reject) { DEBUG && console.log("elementUrl: " + elementUrl) DEBUG && console.log(elementUrl) const coverData = GM_getCachedValue(elementUrl); //DEBUG && console.log("elementUrl: " + elementUrl); //DEBUG && console.log("retrievedImgLink cache value: " + retrievedImgLink) if (coverData !== null) {//retrievedImgLink !== null || retrievedImgLink!==undefined && //currentTitelHover = coverData.title; DEBUG && console.log(coverData) retrievedImgLink = coverData.url; DEBUG && console.log("parseSeriePage has cached retrievedImgLink: " + retrievedImgLink) return resolve(coverData); //resolve(retrievedImgLink); } else { // DEBUG && console.log(coverData) DEBUG && console.log(" - retrievedImgLink cache empty. make ajax request try to save image of page into cache: " + elementUrl); function onLoad(xhr) { const domDocument = xhr.response; //const parser = new DOMParser(); // const domDocument = parser.parseFromString(xhr.responseText, 'text/html'); DEBUG && console.log(domDocument); try { DEBUG && console.group("parseSeriePage onLoad: " + title) if (!domDocument || domDocument === undefined) { console.log(xhr); console.log(xhr.response); console.log(domDocument) } const temp = domDocument.querySelectorAll(IMAGELINKCONTAINERS); const imageLinkByTag = temp[0].getElementsByTagName("img"); const imagelink = imageLinkByTag[CONTAINERNUMBER].getAttribute("src"); const serieTitle = domDocument.querySelector(".seriestitlenu").textContent; const serieVotes = domDocument.querySelector(".seriesother > .uvotes").textContent; const serieStatus = domDocument.querySelector("#editstatus").textContent; const serieGenre = domDocument.querySelector("#seriesgenre").textContent; const serieShowtags = domDocument.querySelector("#showtags").textContent; DEBUG && console.log(serieTitle) DEBUG && console.log(serieVotes) DEBUG && console.log(serieStatus) DEBUG && console.log(serieGenre) DEBUG && console.log(serieShowtags) DEBUG && console.log('save imageUrl as retrievedImgLink ' + imagelink); let cData = { url: imagelink, title: serieTitle, votes: serieVotes, status: serieStatus, genre: serieGenre, showTags: serieShowtags }; retrievedImgLink = imagelink; //currentTitelHover = serieTitle; GM_setCachedValue(elementUrl, cData); //cache imageurl link DEBUG && console.log(elementUrl + " url has been found and is written to temporary cache.\n" + imagelink + ' successfully cached.'); // for testing purposes DEBUG && console.groupEnd("parseSeriePage onLoad") return resolve(cData); //resolve(imagelink); } catch (error) { console.log("error: GM_xmlhttpRequest can not get xhr.response") console.log(error); // showPopupLoadingSpinner(serieTitle, 1); return reject(elementUrl); } } function onError() { const err = new Error('GM_xmlhttpRequest could not load ' + elementUrl + "; url does not exist?"); console.log(err); return reject(err); } GM_xmlhttpRequest({ method: "GET", responseType: 'document', url: elementUrl, onload: onLoad, onerror: onError, }); } }); //DEBUG && console.log(PromiseResult) if (retrievedImgLink) { DEBUG && console.log("has retrievedImgLink: " + retrievedImgLink) } else { //DEBUG && console.log("retrievedImgLink still loading ") if (currentTitelHover == title) { //console.log(PromiseResult) //console.log("showPopupLoadingSpinner parseSeriePage: " + title) if (event) showPopupLoadingSpinner(title, event); //console.log("showPopupLoadingSpinner parseSeriePage after showPopupLoadingSpinner function: " + title) } } DEBUG && console.groupEnd("parseSeriePage: " + elementUrl) await PromiseResult; //DEBUG && console.log(PromiseResult) //after GM_xmlhttpRequest PromiseResult return PromiseResult; } function checkDataVersion() { //Remove possible incompatible old data const DEBUG = false; const dataVersion = GM_getValue("version", null) DEBUG && console.log("dataVersion: " + dataVersion) if (dataVersion === null || dataVersion != version || forceUpdate) { const oldValues = GM_listValues(); DEBUG && console.log("oldValues.length: " + oldValues.length) for (let i = 0; i < oldValues.length; i++) { GM_deleteValue(oldValues[i]); //console.log(oldValues[i]) } DEBUG && console.log(oldValues); GM_setValue("version", version); } } function preloadCoverData() { const DEBUG = false; updateSerieNodes(); DEBUG && console.log("preloadCoverData"); if (preloadUrlRequests) { const novelLinks = Array.from( ALLSERIENODES //document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]') ); DEBUG && console.log(novelLinks); DEBUG && console.log("parseSeriePage for each url with a link to individual seriepage"); novelLinks.map(function (el) { //console.log(el) const elementUrl = el.href; // console.log(elementUrl) el.removeEventListener("mouseenter", mouseEnterPopup) el.removeEventListener("mouseleave", hideOnMouseLeave) el.addEventListener("mouseenter", mouseEnterPopup) el.addEventListener("mouseleave", hideOnMouseLeave) parseSeriePage(elementUrl).then(function (coverData) { if (preloadImages) { console.log("preloadCoverData preloadImages: " + preloadImages) /* let img = document.createElement("img"); //put img into dom. Let the image preload in background img.onload = () => { DEBUG && console.log("onpageload cache init previewImage " + coverData.url); } img.src = coverData.url */ console.log(coverData) loadImageFromBrowser(coverData); } }, function (Error) { DEBUG && console.log(Error + ' failed to fetch ' + el); }); }); } } function loadStyleSheets() { //circle spinner from http://codepen.io/Beaugust/pen/DByiE //add additional stylesheet for "@keyframe spin" into head after document finishes loading //@keyframes spin is used for the loading spinner GM_addStyle(` @keyframes rotate { to {transform: rotate(360deg);} } @keyframes dash { 0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; } 50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; } 100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; } } .popoverContent { display:flex; position: relative; width: 100%; height: 100%; border: 1px solid #000; text-align: center !important; justify-content: center; justify-items: center; background-color:#ccc; align-items: center; min-height:0; min-width:0; /*max-height:inherit; max-width:inherit; height:100%; width:100%;*/ flex:1; padding:1px; } .spinnerRotation{ animation: rotate 2s linear infinite; } .spinner { /* z-index: 2; position: absolute; top: 0; left: 0; margin: 0;*/ width: 100%; height: 100%; } .spinner .path{ stroke: hsl(210, 70%, 75%); stroke-linecap: round; animation: dash 1.5s ease-in-out infinite; } .blackFont { color:#000; } .whiteFont { color:#fff } .defaultTitleStyle { color:#fff; padding:5px 0; height:auto; display:inline-block; width:100%; max-width:auto; text-align:center !important; justify-content: center; justify-items: center; border-radius:8px 8px 0 0; } .defaultBackgroundStyle { align-items:center; pointer-events:none; width:100%; height:100%; max-width:100%; max-height:100%; text-align:center !important; justify-content: center; justify-items: center; } .ImgFitDefault{ object-fit: contain; width:100%; height:100%; } #popover{ height:100%; width:100%; margin:0 0 22px 0; border: 1px solid #000; border-radius:10px 10px 5px 5px; position:absolute; z-index:10; box-shadow: 0px 0px 5px #7A7A7A; display: flex; flex-direction: column; text-align: center !important; justify-content: center; justify-items: center; } .popoverDetail{ flex-direction:unset !important; } .popoverTitleDetail{ height:100% !important; width:auto !important; max-width:70% !important; } .popoverTitle{ height:auto; display:inline-block; width:100%; max-width:auto; } .popoverCoverImg{ /*min-height:0; min-width:0; max-height:inherit; max-width:inherit; height:100%; width:100%;*/ flex:0; padding:5px; } .smallText{ font-size: 0.8em; } .wordBreak { word-wrap: break-word !important; word-break: break-word; } `); function styleSheetContainsClass(f) { var localDomainCheck = '^http://' + document.domain; var localDomainCheckHttps = '^https://' + document.domain; // DEBUG && console.log("Domain check with: " + localDomainCheck); var hasStyle = false; var stylename = f; var fullStyleSheets = document.styleSheets; // DEBUG && console.log("start styleSheetContainsClass " + stylename); if (fullStyleSheets) { for (let i = 0; i < fullStyleSheets.length - 1; i++) { //DEBUG && console.log("loop fullStyleSheets " + stylename); let styleSheet = fullStyleSheets[i]; if (styleSheet != null) { if (styleSheet.href !== null) //https://gold.xitu.io/entry/586c67c4ac502e12d631836b "However since FF 3.5 (or thereabouts) you don't have access to cssRules collection when the file is hosted on a different domain" -> Access error for Firefox based browser. script error not continuing if (styleSheet.href.match(localDomainCheck) || styleSheet.href.match(localDomainCheckHttps)) { if (styleSheet.cssRules) { //DEBUG && console.log("styleSheet.cssRules.length: " + styleSheet.cssRules.length) for (let rulePos = 0; rulePos < styleSheet.cssRules.length - 1; rulePos++) { if (styleSheet.cssRules[rulePos] !== undefined) { // DEBUG && console.log("styleSheet.cssRules[rulePos] "+ stylename); if (styleSheet.cssRules[rulePos].selectorText) { // console.log(styleSheet.cssRules[rulePos].selectorText) if (styleSheet.cssRules[rulePos].selectorText == stylename) { // console.log('styleSheet class has been found - class: ' + stylename); hasStyle = true; //break; break; //return hasStyle; } } //else DEBUG && console.log("undefined styleSheet.cssRules[rulePos] "+rulePos +" - "+ stylename); } //else DEBUG && console.log("loop undefined styleSheet.cssRules[rulePos] "+ stylename); } } //else DEBUG && console.log("undefined styleSheet.cssRules "+ stylename); } // DEBUG && console.log("stylesheet url " + styleSheet.href); } //else DEBUG && console.log("undefined styleSheet "+ stylename); if (hasStyle) break; } } //else console.log("undefined fullStyleSheets=document.styleSheets "+ stylename); if (!hasStyle) console.log("styleSheet class has not been found - style: " + stylename); return hasStyle; } if (STYLESHEETHIJACKFORBACKGROUND !== "") if (!styleSheetContainsClass(STYLESHEETHIJACKFORBACKGROUND)) STYLESHEETHIJACKFORBACKGROUND = ""; else { //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll //works only from firefox 77 //STYLESHEETHIJACKFORBACKGROUND = STYLESHEETHIJACKFORBACKGROUND.replaceAll(".", " ").trim() STYLESHEETHIJACKFORBACKGROUND = STYLESHEETHIJACKFORBACKGROUND.replace(/\./g, ' ').trim(); } if (STYLESHEETHIJACKFORTITLE !== "") if (!styleSheetContainsClass(STYLESHEETHIJACKFORTITLE)) STYLESHEETHIJACKFORTITLE = ""; else { //STYLESHEETHIJACKFORTITLE = STYLESHEETHIJACKFORTITLE.replaceAll(".", " ").trim() STYLESHEETHIJACKFORTITLE = STYLESHEETHIJACKFORTITLE.replace(/\./g, ' ').trim(); } } function createPopover() { let bodyElement = document.getElementsByTagName("BODY")[0]; popover = document.createElement("div"); popover.id = "popover"; popoverTitle = document.createElement("header"); popoverContent = document.createElement("content"); // popoverCoverImg = document.createElement("coverImg"); popover.appendChild(popoverTitle); popover.appendChild(popoverContent); //popover.appendChild(popoverCoverImg); popover.className = (STYLESHEETHIJACKFORBACKGROUND + ' defaultBackgroundStyle').trim(); popoverContent.className = "popoverContent blackFont"; popover.style.maxHeight = defaultHeight + "px"; popover.style.maxWidth = defaultHeight + "px"; popover.style.backgroundColor = DEFAULTBACKGROUNDCOLOR; //console.log(popover) //console.log(popover.style) popoverTitle.className = (STYLESHEETHIJACKFORTITLE + ' defaultTitleStyle').trim(); popoverTitle.style.backgroundColor = DEFAULTTITLEBACKGROUNDCOLOR; //popoverCoverImg.className = "popoverCoverImg"; bodyElement.insertAdjacentElement("beforeend", popover); } function stylesheetForTitle() { if (STYLESHEETHIJACKFORTITLE !== "") return 'class="' + STYLESHEETHIJACKFORTITLE + ' defaultTitleStyle"'; else return 'class="defaultTitleStyle" style="background-color:' + DEFAULTTITLEBACKGROUNDCOLOR + '"'; } function stylesheetForBackground() { if (STYLESHEETHIJACKFORBACKGROUND !== "") return 'class="' + STYLESHEETHIJACKFORBACKGROUND + ' defaultBackgroundStyle"'; else return 'class="defaultBackgroundStyle" style="background-color:' + DEFAULTBACKGROUNDCOLOR + '"'; } function showPopupLoadingSpinner(title, event, notification = "", coverData = undefined) { const DEBUG = false; if (currentTitelHover == title) { // console.group("showPopupLoadingSpinner") //popover.empty(); //popover.innerHTML = ""; DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight); if (coverData !== undefined) { //console.log("showPopupLoadingSpinner") //console.log(coverData) adjustPopupTitleDetail(coverData, title); } else popoverTitle.textContent = title; if (notification != "") { popoverContent.innerHTML = notification; popoverContent.className = "popoverContent blackFont wordBreak"; } else { popoverContent.innerHTML = `<svg class="spinner" viewBox="0 0 50 50"> <g transform="translate(25, 25)"> <circle class="" cx="0" cy="0" r="25" fill="black" stroke-width="5" /> <circle class="path" cx="0" cy="0" r="23" fill="none" stroke-width="5"> <animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0" to="360" dur="1.6s" repeatCount="indefinite" /> </circle> </g> <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" style="fill:#fff;font-size:11px">Loading </text> </svg>` //popoverContent.innerHTML = '<div class="forground" style="z-index: 3;">Loading Data</div><svg class="spinner" viewBox="0 0 50 50"><circle class="" cx="25" cy="25" r="22" fill="black" stroke-width="5"></circle><circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle></svg>'; popoverContent.className = "popoverContent whiteFont"; } DEBUG && console.log(popover) // DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight); //console.log(event) popupPos(event); // console.groupEnd("showPopupLoadingSpinner") } } /** * update popupContent and reposition to link * * @param {*} title * @param {*} link * @param {*} e event */ function refreshPopover(coverData, e = undefined) { //only call when isActivePopup const DEBUG = false; DEBUG && console.log("currentTitelHover: " + currentTitelHover) DEBUG && console.group("refreshPopover"); const link = coverData.url; const title = coverData.title; //console.log(coverData) //console.log(e) // popoverTitle.textContent = title; // console.log(link) if (inBlocklist(link)) { popoverContent.innerHTML = "Blocked Image<br />No Cover Image<br />Unwanted Image"; } else { let imgElement = new Image();//document.createElement("img"); imgElement.src = link; popoverContent.innerHTML = '<img src="' + link + '" class="ImgFitDefault" ></img>'; } adjustPopupTitleDetail(coverData); DEBUG && console.groupEnd("refreshPopover"); //if (currentTitelHover == title) if (e !== undefined) popupPos(e); }; const reRating = new RegExp('([0-9\.]+) \/ ([0-9\.]+)'); const reVoteCount = new RegExp('([0-9]+) votes') function getRatingNumber(ratingString) { //const ratingString = "Rating(3.3 / 5.0, 1940 votes)" 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; } } let ratingNumber if (matches && matches.length == 3 && hasVotes) { //console.log(matches[1]) ratingNumber = matches[1]; } return ratingNumber; } const reChapters = new RegExp('([0-9\.]+)( wn)? chapters'); const reChaptersNumberBehind = new RegExp('chapter ([0-9\.]+)'); function getChapters(statusString) { let 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]; } } let result; if (chapterCount) { result = chapterCount + webnovel + " Chapters" } return result; } function getCompletedState(statusString) { let result = false; if (statusString.toLowerCase().includes("complete")) {//complete | completed result = true; } return result; } function geOngoingState(statusString) { let result = false; if (statusString.toLowerCase().includes("ongoing")) { result = true; } return result; } async function adjustPopupTitleDetail(coverData, title = undefined) { let titleToShow=""; popoverTitle.textContent = ""; if (coverData && coverData.title) titleToShow = coverData.title; else if (title !== undefined) titleToShow = title; popoverTitle.textContent = titleToShow; //console.log("adjustPopupTitleDetail - showDetails: " + showDetails) if (showDetails) { //console.log("showDetails should be true") if (coverData.votes && coverData.votes.length > 0) { popoverTitle.innerHTML += '<hr />Rating: ' + coverData.votes + '<hr />Status: ' + coverData.status + ''; } if (coverData.genre && coverData.showTags) { popoverTitle.innerHTML += '<hr />Genre: ' + coverData.genre + "<hr />Tags: " + coverData.showTags; } popoverTitle.innerHTML += "<hr /><span>[Press Key 1 to hide details]</span>" } else { //console.log("showDetails should be false") //if (coverData.votes && coverData.votes.length > 0) { let rating = getRatingNumber(coverData.votes); let chapters = getChapters(coverData.status); let completed = getCompletedState(coverData.status); let ongoing = geOngoingState(coverData.status); if (rating || chapters || completed || ongoing) { //console.log(rating) //console.log(chapters) //console.log(completed) //console.log(ongoing) if (rating !== undefined) rating += "★ "; if (chapters !== undefined) chapters = chapters + " "; else chapters = ""; if (completed) completed = "🗹 "; else completed = ""; //https://www.utf8icons.com/ if (ongoing) ongoing = "✎ "; else ongoing = ""; popoverTitle.innerHTML = titleToShow+ '<span class="smallText" style="white-space: nowrap;"> [' + rating + chapters + completed + ongoing + ']</span>'; } /* popoverTitle.innerHTML += '<span class="smallText"> [' + rating + '★] ' + chapters + '</span> '; if (completed) { popoverTitle.innerHTML += "🗹 " } if (ongoing) { popoverTitle.innerHTML += "✎ " }*/ } popoverTitle.innerHTML += '<br /><span class="smallText">[Press Key 1 to show details]</span>' } } function loadCoverData(coverData, title, e) { const DEBUG = false; //GM_getCachedValue DEBUG && console.group("loadCoverData") // const coverData = GM_getCachedValue(Href); DEBUG && console.log(coverData) const imgUrl = coverData.url; let serieTitle = title; if (!title || coverData.title) //pure link without title get title of seriepage serieTitle = coverData.title; DEBUG && console.log("imgUrl: " + imgUrl); DEBUG && console.log("title: " + title + ", serieTitle: " + serieTitle) if ((currentTitelHover === null || currentTitelHover == "null" || currentTitelHover === undefined) && coverData !== null) { //console.log(coverData) currentTitelHover = coverData.title; } if (coverData !== undefined && coverData !== null) currentCoverData = coverData; if (e) loadImageFromBrowser(coverData, imgUrl, serieTitle, e, title) DEBUG && console.groupEnd("loadCoverData") } function ajaxLoadImageUrlAndShowPopup(elementUrl, title, e) { //console.log("mouseenter") // console.group("ajaxLoadImageUrlAndShowPopup") return parseSeriePage(elementUrl, title, e) .then(function (coverData) { loadCoverData(coverData, title, e) }, function (Error) { console.log(Error + ' failed to fetch ' + elementUrl); }); // console.groupEnd("ajaxLoadImageUrlAndShowPopup") }; function imageLoaded(coverData, hoveredTitleLink, serieTitle = undefined, e = undefined) { const DEBUG = false; const hasMouseEnterEvent = serieTitle && (e !== undefined); const isActivePopup = ((currentTitelHover !== undefined) && (hoveredTitleLink !== undefined)) && (currentTitelHover == hoveredTitleLink) && hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData DEBUG && console.group("loadImageFromBrowser img.onload: " + serieTitle) DEBUG && console.log("finished loading imgurl: " + coverData.url); DEBUG && console.log("currentTitelHover: " + currentTitelHover + ", isActivePopup: " + isActivePopup) DEBUG && console.log("isActivePopup: " + isActivePopup) if (isActivePopup) { DEBUG && console.log("refreshPopover") refreshPopover(coverData, e); //popup only gets refreshed when currentTitelHover == serieTitle } DEBUG && console.groupEnd("loadImageFromBrowser img.onload") } function imageLoadingError(coverData, error, hoveredTitleLink, serieTitle = undefined, e = undefined) { console.group("loadImageFromBrowser img.onerror: " + serieTitle) const hasMouseEnterEvent = serieTitle && (e !== undefined); const isActivePopup = ((currentTitelHover !== undefined) && (hoveredTitleLink !== undefined)) && (currentTitelHover == hoveredTitleLink) && hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData console.log(error); const errorMessage = "browser blocked/has error loading the file: <br />" + decodeURIComponent(error.target.src); console.log(errorMessage) //console.log(window) console.log(navigator) //console.log(navigator.userAgent) const useragentString = navigator.userAgent; console.log("useragentString: " + useragentString) const isChrome = useragentString.includes("Chrome") if (isChrome) console.log("look in the developer console if 'net::ERR_BLOCKED_BY_CLIENT' is displayed or manually check if the imagelink still exists"); else console.log("image loading most likely blocked by browser or addon. Check if the imagelink still exists"); if (isActivePopup) showPopupLoadingSpinner(serieTitle, e, errorMessage, coverData); console.groupEnd("loadImageFromBrowser img.onerror") } function loadImageFromBrowser(coverData, imgUrl, serieTitle = undefined, e = undefined, hoveredTitleLink = undefined) { const DEBUG = false; //console.group("loadImageFromBrowser") let img = document.createElement("img"); //put img into dom. Let the image preload in background const hasMouseEnterEvent = serieTitle && (e !== undefined); //console.log(currentCoverData) //console.log(coverData) const isActivePopup = ((currentTitelHover !== undefined) && (hoveredTitleLink !== undefined)) && (currentTitelHover == hoveredTitleLink) && hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == 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 = imgUrl; 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 if (isActivePopup) { DEBUG && console.log("loadImageFromBrowser image not completely loaded yet. Show loading spinner : " + serieTitle) showPopupLoadingSpinner(serieTitle, e); } } // console.groupEnd("loadImageFromBrowser") } function mouseEnterPopup(e) { //if (!e.target.matches(concatSelector())) return; const DEBUG = false; DEBUG && console.group("mouseEnterPopup") //let element = undefined;//$(this); //let nativElement = e.target//this; //console.log(this) //console.log(e.target) let Href = this.href;// element.attr('href'); if (Href.search(INDIVIDUALPAGETEST) != -1) //only trigger for links that point to serie pages { //console.log(this) //console.log(this.text) //shortTitle //console.log(this.title) //LongTitle let shortSerieTitle = this.text; //element.text(); //get linkname //console.log(this) //console.log(shortSerieTitle) //move native title to custom data attribute. Suppress nativ title popup if (!this.getAttribute('datatitle')) { this.setAttribute('datatitle', this.getAttribute('title')); this.removeAttribute('title'); } let serieTitle = this.getAttribute('datatitle');//element.attr('datatitle'); //try to get nativ title if available from datatitle //console.log(serieTitle) if (serieTitle === null || serieTitle == "null") //has no set nativ long title -> use (available shortend) linkname serieTitle = shortSerieTitle; else //no need to run check if it is already shortSerieTitle if (serieTitle.match(PREDIFINEDNATIVTITLE)) //catch on individual serie page nativ title begins with "Recommended by" x people -> use linkname serieTitle = shortSerieTitle; currentTitelHover = serieTitle; //mark which titel is currently hovered currentPopupEvent = e; //console.log(serieTitle) //console.log(Href) //console.log(currentCoverData) ajaxLoadImageUrlAndShowPopup(Href, currentTitelHover, e); } DEBUG && console.groupEnd("mouseEnterPopup") } function hidePopOver() { popover.style.visibility = "hidden"; currentTitelHover = undefined; currentCoverData = undefined; } function showPopOver() { // popover.style.display = "flex"; popover.style.visibility = "visible"; } function hideOnMouseLeave() { //if (!e.target.matches(concatSelector())) return; //popover.hide(); hidePopOver(); } function updateSerieNodes() { if (ALLSERIENODES) { ALLSERIENODES.forEach(function (selector) { selector.removeEventListener("mouseleave", hideOnMouseLeave); selector.removeEventListener("mouseenter", mouseEnterPopup); }) } ALLSERIENODES = document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]'); } function switchDetailsAndUpdatePopup() { const DEBUG = false; DEBUG && console.group("switchDetailsAndUpdatePopup") changeToNewDetailStyle(); //console.log(currentCoverData) DEBUG && console.log("switchDetails refreshPopup") DEBUG && console.log(currentCoverData) if (currentCoverData && currentCoverData !== undefined) { refreshPopover(currentCoverData, currentPopupEvent); //update on detail change } console.groupEnd("switchDetails") } function changeToNewDetailStyle(toggleDetails = true) { if (toggleDetails) showDetails = !showDetails; //console.log("switch showDetails to : " + showDetails) localStorage.setItem("showDetails", showDetails); if (showDetails) { popover.classList.add("popoverDetail") popover.style.maxWidth = defaultHeight * 2 + "px"; popoverTitle.classList.add("popoverTitleDetail") } else { popover.classList.remove("popoverDetail") popover.style.maxWidth = defaultHeight + "px"; popoverTitle.classList.remove("popoverTitleDetail") } } function reactToKeyPressWhenPopupVisible(event) { //console.log(event) //console.log(currentTitelHover) if (currentTitelHover && currentTitelHover !== undefined) { if (event.key == "1") { //switchDetailsAndUpdatePopup(); switchDetailsAndUpdatePopup() } } } window.addEventListener("blur", hidePopOver); window.addEventListener("keypress", reactToKeyPressWhenPopupVisible); window.onunload = function () { window.removeEventListener("blur", hidePopOver); window.removeEventListener("keypress", reactToKeyPressWhenPopupVisible) //possible memoryleaks? updateSerieNodes(); observer.disconnect(); } const debouncedpreloadCoverData = debounce(preloadCoverData, 100); // Options for the observer (which mutations to observe) const config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = function (mutationsList, observer) { // Use traditional 'for loops' for IE 11 for (const mutation of mutationsList) { if (mutation.type === 'childList') { // console.log('A child node has been added or removed.'); //debouncedTest() debouncedpreloadCoverData(); hidePopOver(); } else if (mutation.type === 'attributes') { // console.log('The ' + mutation.attributeName + ' attribute was modified.'); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); function debounce(func, timeout) { let timer; return (...args) => { const next = () => func(...args); if (timer) { clearTimeout(timer); } timer = setTimeout(next, timeout > 0 ? timeout : 300); }; }; function main() { checkDataVersion(); loadStyleSheets(); createPopover(); hidePopOver(); showDetails = localStorage.getItem("showDetails") == "true"; //console.log("localStorage state showDetails: " + showDetails) changeToNewDetailStyle(false); //console.log("isOnReadingListIndex: " + isOnReadingListIndex) if (isOnReadingListIndex) { let targetNode = document.getElementById("profile_content3"); //console.dir(targetNode) observer.observe(targetNode, config); //observe for update before running debouncedwaitForReadingList(); } else { preloadCoverData(); } } main();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址