novelupdates Cover Preview

Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.

目前為 2017-01-15 提交的版本,檢視 最新版本

// ==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/*
// @version     1.2.4
// @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novel pages.
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @run-at   	document-end
// @require     http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// @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 24 hour in milliseconds  //days * h * min  * sec * ms
const SELECTOR1 = 'td a'; //index/group/readinglist pages
const SELECTOR2 = '.wpb_wrapper > a'; //individual serie pages recommendation titles
const PREDIFINEDNATIVTITLE = "^Recommended by"; //in case native title is used to display something different
const INDIVIDUALPAGETEST = "novelupdates.com/series/";
var STYLESHEETHIJACKFORBACKGROUND = "breadcrumb_nu"; //if unknown set empty ""; classname without leading dot
var STYLESHEETHIJACKFORTITLE = "tbl_sort"; //if unknown set empty ""; classname without leading dot
const DEFAULTTITLEBACKGROUNDCOLOR = '#aac'; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR = '#ccc'; //if no hijack class style available use plain color
const IMAGELINKCONTAINER = 'serieseditimg';
const IMAGELINKCONTAINERANONYM = 'seriesimg'; //sContainer
const CONTAINERNUMBER = 0;
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
//^^^^	frontend settings over this line	^^^^


const HASERROR = true;
const RE = /\s*,\s*/; //Regex for split and remove empty spaces
var IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE);
var onHover = false;
var currentTitelHover;
var windowCached = $(window);
var concatenatedSelectors = $(concatSelector());
var style = 1;
var popover;

function concatSelector() {
	var result;
	if (SELECTOR1)
		result = SELECTOR1;
	if (SELECTOR2) {
		if (SELECTOR1) //in case selector1 is missing
			result += ', ';
		result += SELECTOR2;
	}

	return result;
}

// popupPositioning function
jQuery.fn.popupPos = function(event, element, style) {
	var offsetToBottomBorderY = 5; //offset to bottom border
	var offsetToRightBorderX = 5; //offset to right border
	var X, Y;
	var hoveredSelectedPosX, hoveredSelectedPosY;
	var distanceToBottom, distanceToRight;

	//Initialising variables (multiple usages)
	var scrollTop = windowCached.scrollTop();
	var scrollLeft = windowCached.scrollLeft();
	var elementPopup = $(this);
	var elementParentOffset = element.parents().offset();
	var elementParentOuterHeight = element.parents().outerHeight();
	var elementParentOuterWidth = element.parents().outerWidth();
	var elementOffset = element.offset();
	var elementOuterHeight = element.outerHeight();
	var elementOuterWidth = element.outerWidth();

	if (style == 1) //index: position next to parent table cell (SELECTOR1)
	{
		hoveredSelectedPosX = elementParentOffset.left + elementParentOuterWidth; //link position + tablecell width; + elementOuterWidth;;
		hoveredSelectedPosY = elementParentOffset.top + elementParentOuterHeight; //link position + tablecell height; + elementOuterHeight;
	} else if (style == 2) //recommendations: position next to link height and parent (SELECTOR2) width
	{
		hoveredSelectedPosX = elementParentOffset.left + elementParentOuterWidth; //elementOffset.left + elementOuterWidth;
		hoveredSelectedPosY = elementOffset.top + elementOuterHeight;
	} else { //position to mouse hover position
		hoveredSelectedPosX = event.pageX; // + offsetToRightBorderX;
		hoveredSelectedPosY = event.pageY; // + offsetToBottomBorderY;
	}
	X = hoveredSelectedPosX;
	Y = hoveredSelectedPosY;

	// Distance to the right
	distanceToRight = windowCached.width() - (X + offsetToRightBorderX - scrollLeft);
	// Tooltip too close to the right?
	if (distanceToRight < elementPopup.outerWidth() + offsetToRightBorderX)
		X += distanceToRight - elementPopup.outerWidth();

	// Distance to the bottom
	distanceToBottom = windowCached.height() - (Y + offsetToBottomBorderY - scrollTop);
	// Tooltip too close to the bottom?
	if (distanceToBottom < elementPopup.outerHeight() + offsetToBottomBorderY) //(offsetToBottomBorderY + elementPopup.outerHeight())
		Y += distanceToBottom - elementPopup.outerHeight();

	//console.log("Distance to the bottom " + distanceToBottom+" elementPopupHeight " +elementPopup.outerHeight()+ "\nDistance to the right " + distanceToRight+ " elementPopupouterWidth " +elementPopup.outerWidth());
	//Tooltip over top border?
	//if(Y + offsetToBottomBorderY < scrollTop) Y = scrollTop + offsetToBottomBorderY;
	//if(X + offsetToRightBorderX < scrollLeft) X = scrollLeft + offsetToRightBorderX;
	this.css('top', Y + 'px');
	this.css('left', X + 'px');
	popover.show();
	//console.log("final popup position "+X+' # '+Y);
	return this;
};

//.wpb_wrapper a = title links on individual seriepage
$(SELECTOR2).mouseenter(function(e) {
	style = 2;
});
//td a = links in table cells (index and group page)
$(SELECTOR1).mouseenter(function(e) {
	style = 1;
});

windowCached.on('load', function() {
	function styleSheetContainsClass(f) {
		var localDomainCheck = '^http://' + document.domain;
		//console.log("Domain check with: " + localDomainCheck);
		var hasStyle = false;
		var stylename = '.' + f;
		var fullStyleSheets = document.styleSheets;
		// console.log("start styleSheetContainsClass " + stylename);
		if (fullStyleSheets) {
			for (var i = 0; i < fullStyleSheets.length - 1; i++) {
				//console.log("loop fullStyleSheets " + stylename);
				var styleSheet = fullStyleSheets[i];
				//var stylehref = ""+styleSheet.href;
				//console.log("style test : " + styleSheet.href);
				//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)) {
							if (styleSheet.cssRules) {
								for (var rulePos = 0; rulePos < styleSheet.cssRules.length - 1; rulePos++) {

									if (styleSheet.cssRules[rulePos] !== undefined) {
										// console.log("styleSheet.cssRules[rulePos] "+ stylename);
										if (styleSheet.cssRules[rulePos].selectorText) {
											if (styleSheet.cssRules[rulePos].selectorText == stylename) {
												console.log('styleSheet class has been found - style: ' + stylename);
												hasStyle = true; //break;
												return hasStyle;
											}
										} // else console.log("undefined styleSheet.cssRules[rulePos] "+ stylename);
									}
									// else console.log("loop undefined styleSheet.cssRules[rulePos] "+ stylename);
								}
							} //else console.log("undefined styleSheet.cssRules "+ stylename);
						}
					//console.log("stylesheet url " + styleSheet.href);
				} //else console.log("undefined styleSheet "+ stylename);
				if (hasStyle) break;
			}
		} //else console.log("undefined fullStyleSheets "+ stylename);

		console.log("styleSheet class has not been found - style: " + stylename);
		return hasStyle;
	}
	if (STYLESHEETHIJACKFORBACKGROUND !== "")
		if (!styleSheetContainsClass(STYLESHEETHIJACKFORBACKGROUND))
			STYLESHEETHIJACKFORBACKGROUND = "";
	if (STYLESHEETHIJACKFORTITLE !== "")
		if (!styleSheetContainsClass(STYLESHEETHIJACKFORTITLE))
			STYLESHEETHIJACKFORTITLE = "";

	$('body').append('<div ID="popover" ' + stylesheetForBackground() + '></div>');
	popover = $('#popover');
	popover.css('position', 'absolute');
	popover.css('z-index', '10');
	popover.css('box-shadow', '0px 0px 5px #7A7A7A');

	//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 die loading spinner
	var style = document.createElement('style');
	style.type = 'text/css';
	style.innerHTML = 'body {}';
	document.getElementsByTagName('head')[0].appendChild(style);
	this.stylesheet = document.styleSheets[document.styleSheets.length - 1];

	try {
		this.stylesheet.insertRule(`
@keyframes spin {
100% {
transform: rotate(360deg);
}
}`, this.stylesheet.cssRules.length);
	} catch (e) {
		alert('error');
	}
});

function stylesheetForTitle() {
	if (STYLESHEETHIJACKFORTITLE !== "")
		return 'class="' + STYLESHEETHIJACKFORTITLE + '" style="display:inline-block;width:100%;text-align:center !important"';
	else
		return 'style="background-color:' + DEFAULTTITLEBACKGROUNDCOLOR + ';display:inline-block;width:100%;text-align:center !important"';
}

function stylesheetForBackground() {
	if (STYLESHEETHIJACKFORBACKGROUND !== "")
		return 'class="' + STYLESHEETHIJACKFORBACKGROUND + '" style="display:flex !important;flex-direction: column; align-items:center;pointer-events:none; width:auto; height:auto; max-width:500px; max-height:500px;"';
	else
		return 'style="background-color:' + DEFAULTBACKGROUNDCOLOR + ';display:flex !important;flex-direction: column; align-items:center;pointer-events:none; width:auto; height:auto; max-width:500px; max-height:500px;"';
}


//when selected link is entered load imageurl and write popover content
concatenatedSelectors.mouseenter(function(e) {
	var element = $(this);
	var Href = element.attr('href');

	if (Href.search(INDIVIDUALPAGETEST) != -1) //only trigger for links that point to serie pages
	{

		//get value from key. Decide if timestamp is older than MAXCACHEAGE than look for new image
		var GM_getCachedValue = function(key) {
			var currentTime = Date.now();

			var keyCache = key + 'cachedTime';
			if (GM_getValue(keyCache)) //has previous timestamp for key
			{
				var measuredTimedifference = currentTime - parseInt(GM_getValue(keyCache));
				if (measuredTimedifference < MAXCACHEAGE) {
					if (GM_getValue(key)) {
						return GM_getValue(key); //when time difference smaller MAXCACHEAGE and value available return value
					} else
						GM_deleteValue(keyCache); //if no value available delete previous key with timestamp
				} else { //when time difference bigger than MAXCACHEAGE delete timestamp and value;
					GM_deleteValue(keyCache);
					GM_deleteValue(key);
				}

			} else //when no timestamp available return false
				return false;
		};

		//set value and currenttime for key
		var GM_setCachedValue = function(key, value) {
			var currentTime = Date.now(); // current datetime in milliseconds
			GM_setValue(key, value);
			GM_setValue(key + 'cachedTime', currentTime);
		};

		var inBlocklist = function(link) {
			if (IMAGEBLOCKERARRAY)
				if (IMAGEBLOCKERARRAY.length > 0)
					for (var i = 0; i < IMAGEBLOCKERARRAY.length; i++)
						if (IMAGEBLOCKERARRAY[i] !== "")
							if (link.match(IMAGEBLOCKERARRAY[i]))
								return true;
			return false;
		};

		var refreshPopover = function(title, link) {
			/*	clear popup
			 *	append title and image into popup
			 *	when img loading is finished reposition (popupPos) to element/border
			 */
			if (currentTitelHover == title) //popup only gets refreshed when currentTitelHover == title
			{
				popover.empty();
				if (inBlocklist(link)) {
					popover.append('<div ' + stylesheetForTitle() + '>' + title + '</div>Blocked Image / No Cover Image / Unwanted Image');
					popover.popupPos(e, element, style);
				} else {
					popover.append('<div ' + stylesheetForTitle() + '>' + title + '</div><img src="' + link + '" style="margin:5px; width:auto; height:400px !important; max-width:100%; max-height:100%; align-items: stretch;align-self: stretch;object-fit: contain;"></img>');
					$('#popover img').on('load', function() {
						//console.log(Href + "onload is executed"); // for testing purposes
						if (onHover) //is mouse still hovering over same title after loading finishes?
							popover.popupPos(e, element, style);
					});
				}
			}
		};

		//popup loading spinner
		var showPopupLoadingSpinner = function(title, error = false) {
			popover.empty();
			if (error)
				popover.append('<div ' + stylesheetForTitle() + '>' + title + '</div><div style="position: relative;width:150px; height:150px;color:#fff;display: flex; justify-content: center; flex-direction: column; text-align: center;">iamgelink getElementsByClassName is invalid</div>');
			else
				popover.append('<div ' + stylesheetForTitle() + '>' + title + '</div><div style="position: relative;width:150px; height:150px;color:#fff;display: flex; justify-content: center; flex-direction: column; text-align: center;">Getting image url<div style="z-index: -100;position:absolute;top:0;left:0;background-color:#000; box-sizing: border-box; width: 150px; height: 150px; border-radius: 100%; border: 10px solid rgba(255, 255, 255, 0.2); border-top-color: #FFF; animation: spin 1s infinite linear;"></div></div>');
			popover.popupPos(e, element, style);
		};

		//async wait until image is loaded before refreshing popup
		var ajaxLoadImageUrlAndShowPopup = function(url, title) {
			$.ajax({
				url: Href,
				type: "GET",
				dataType: 'text',
				success: function(data) {
					if (!data && data == " ") {
						console.log('response to ' + Href + ' was empty');
						showPopupLoadingSpinner(serieTitle, 1);
					} else {
						//vvv get imageurl link vvv
						var htmlData = $('<div>').html(data)[0];
						var imagelinkLoggedIn = htmlData.getElementsByClassName(IMAGELINKCONTAINER)[CONTAINERNUMBER];
						var imagelink;

						if (imagelinkLoggedIn) //need to consider different img classes for logged in and anonymous users
							imagelink = imagelinkLoggedIn.getElementsByTagName('img')[0].src;
						else
							imagelink = htmlData.getElementsByClassName(IMAGELINKCONTAINERANONYM)[CONTAINERNUMBER].getElementsByTagName('img')[0].src;
						//console.log("imagelink: " + imagelink);
						// ^^^ get imageurl link ^^^

						refreshPopover(serieTitle, imagelink); //popup only gets refreshed when currentTitelHover == serieTitle

						// cache info
						GM_setCachedValue(Href, imagelink); //cache imageurl link
						console.log(Href + " url has been found and is written to temporary cache.\n" + imagelink + ' successfully cached.'); // for testing purposes
					}

				}
			});
		};

		onHover = true;

		var shortSerieTitle = element.text(); //get linkname

		//move native title to custom data attribute. Suppress nativ title
		if (!element.attr('datatitle')) {
			element.attr('datatitle', element.attr('title'));
			element.removeAttr('title');
		}

		var serieTitle = element.attr('datatitle'); //try to get nativ title if available from datatitle
		if (!serieTitle) //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

		var retrievedImgLink = GM_getCachedValue(Href); //was imageurl cached?

		if (retrievedImgLink) {
			refreshPopover(serieTitle, retrievedImgLink); //popup only gets refreshed when currentTitelHover == serieTitle
			console.log(retrievedImgLink + ' on the page ' + Href + " has been found and retrieved from the cache."); // for testing purposes
		} else {
			showPopupLoadingSpinner(serieTitle);
			ajaxLoadImageUrlAndShowPopup(Href, serieTitle);
		}
	}
});

//hide and empty popup when mouse is not over title
concatenatedSelectors.mouseleave(function() { //close popup when mouse leaves titlelink
	//popover.empty();
	popover.hide();
	onHover = false;
});
$(document).mouseleave(function() { //force close when mouse is outside window and previous mouseleave does not get called
	popover.hide();
	onHover = false;
});

windowCached.blur(function() { //chrome fix -> force close when mouse is outside window alt + tab
	popover.hide();
	onHover = false;
});

QingJ © 2025

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