// ==UserScript==
// https://gf.qytechs.cn/scripts/26513-mangaupdates-cover-preview/
// @name mangaupdates Cover Preview
// @namespace szMangaupdatesCoverPreview
// @include https://www.mangaupdates.com/*
// @include http://www.mangaupdates.com/*
// @version 1.8.4
// @description Previews covers in mangaupdates.com when hovering over hyperlinks that lead to serie pages.
// @inject-into content
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_listValues
// @run-at document-end
// @license http://creativecommons.org/licenses/by-nc-sa/4.0/
// ==/UserScript==
//console.log("cover preview start");
"use strict";
//#region frontend settings
const MAXCACHEAGE = 90 * 24 * 60 * 60 * 1000; // Max Age before Cached data of serieinfo gets overridden with current data. Max Age is 90 days in milliseconds //days * h * min * sec * ms
const DEFAULTTITLEBACKGROUNDCOLOR = "#2c3e50"; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR = "#ccc"; //if no hijack class style available use plain color
let STYLESHEETHIJACKFORBACKGROUND = ".frow2"; //if unknown set empty ""; classname with leading dot seperated with comma
let STYLESHEETHIJACKFORTITLE = ".small_bold, .side_content_row"; //if unknown set empty ""; classname with leading dot seperated with comma
const PREDIFINEDNATIVTITLE = "Click for series info, Series Info"; //forum, index
const INDIVIDUALPAGETEST = "series.html?id="; // /series\.html\?id\=[0-9]*$/;
const IMAGELINKCONTAINERS = ".sContent img"; //instead of single element class name with dot
const IMAGEBLOCKER = "images/stat_increase.gif, images/stat_decrease.gif"; //tested with includes(). no need for prefixed http https in url. Can even be just the file name
const CONTAINERNUMBER = 0;
const seriePageTitle = ".releasestitle "; //.seriestitlenu
//since there are no ids or identifiable class selectable i could only grab the content by manually counting and selecting the corresponding divs
const seriePageVotes =
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(35)"; //.seriesother > .uvotes
const seriePageStatus =
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(20)"; //#editstatus
const seriePageGenre =
"#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(5)"; //#seriesgenre
const seriePageTags =
"#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(8) ul"; //#showtags
const seriePageDescription =
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(2)"; //#div_desc_link"; id gets deleted by script... //"#editdescription";
const isOnIndex =
this.location.href == "https://www.mangaupdates.com/" ||
this.location.href.startsWith("https://www.mangaupdates.com/releases.html") ||
this.location.href.startsWith("https://www.mangaupdates.com/groups.html?") ||
this.location.href.startsWith("https://www.mangaupdates.com/mylist.html") ||
this.location.href.startsWith(
"https://www.mangaupdates.com/authors.html?id="
);
const isOnReadingListIndex = this.location.href.startsWith(
"https://www.novelupdates.com/user/"
);
const targetContainerIDToObserve = "profile_content3"; //update eventlistener on list change of page isOnReadingListIndex
const preloadUrlRequests = false;
const preloadImages = false;
const eventListenerStyle = 0; //undefined/0 forEach serieLink addeventlistener(mouseenter/mouseleave) / 1 window addeventlistener(mousemove)
//#endregion
//#region backend variables ^^^^ frontend settings over this line ^^^^
const version = "1.8.4";
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 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 = [];
//#endregion
//console.log("after variable settings");
//console.log(this.location)
//console.log(this.location.href)
//console.log("isOnIndex: " + isOnIndex)
//#region helper functions
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
//get value from key. Decide if timestamp is older than MAXCACHEAGE than look for new image
function GM_getCachedValue(key) {
const DEBUG = false;
const currentTime = Date.now();
const rawCover = GM_getValue(key, null);
DEBUG && console.group("GM_getCachedValue");
DEBUG && console.log("rawCover: " + rawCover);
let result = null;
if (rawCover === null || rawCover == "null") {
result = null;
} else {
let coverData;
try {
//is json parseable data? if not delete for refreshing
coverData = JSON.parse(rawCover);
DEBUG && console.log("coverData: " + coverData);
DEBUG && console.log(coverData);
if (!(coverData.url && coverData.title && coverData.cachedTime)) {
//has same variable definitions?
GM_deleteValue(key);
result = null;
}
} catch (e) {
GM_deleteValue(key);
result = null;
}
const measuredTimedifference = currentTime - coverData.cachedTime;
if (measuredTimedifference < MAXCACHEAGE) {
result = {
url: coverData.url,
title: coverData.title,
votes: coverData.votes,
status: coverData.status,
genre: coverData.genre,
showTags: coverData.showTags,
description: coverData.description,
};
} 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,
description: coverData.description,
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 console.log("Wrong querySelector for " + queryName + ". not: " + query);
return result;
}
async function getCoverDataFromUrl(elementUrl) {
const DEBUG = false;
let PromiseResult = new Promise(async function (resolve, reject) {
DEBUG && console.log("elementUrl: " + elementUrl);
// DEBUG && console.log(coverData)
DEBUG &&
console.log(
" - retrievedImgLink cache empty. make ajax request try to save image of page into cache: " +
elementUrl
);
function onLoad(xhr) {
//https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299)
if (xhr.status >= 200 && xhr.status < 399) {
const domDocument = xhr.response;
//const parser = new DOMParser();
// const domDocument = parser.parseFromString(xhr.responseText, 'text/html');
DEBUG && console.log(domDocument);
try {
DEBUG && console.group("parseSeriePage onLoad: " + currentTitelHover);
if (!domDocument || domDocument === undefined) {
console.log(xhr);
console.log(xhr.response);
console.log(domDocument);
}
const temp = domDocument.querySelectorAll(IMAGELINKCONTAINERS);
DEBUG && console.log(temp);
/*
const imageLinkByTag = temp.getElementsByTagName("img");
console.log(imageLinkByTag)*/
let imagelink = temp[CONTAINERNUMBER];
if (imagelink !== undefined)
imagelink = imagelink.getAttribute("src");
let serieTitle = domDocument.querySelector(seriePageTitle);
let serieVotes = domDocument.querySelector(seriePageVotes);
let serieStatus = domDocument.querySelector(seriePageStatus);
let serieGenre = domDocument.querySelector(seriePageGenre);
let serieShowtags = domDocument.querySelector(seriePageTags);
let serieDescription = domDocument.querySelector(
seriePageDescription
);
serieTitle = tryToGetTextContent(
serieTitle,
seriePageTitle,
"seriePageTitle"
);
serieVotes = tryToGetTextContent(
serieVotes,
seriePageVotes,
"seriePageVotes"
);
serieStatus = tryToGetTextContent(
serieStatus,
seriePageStatus,
"seriePageStatus"
);
serieGenre = tryToGetTextContent(
serieGenre,
seriePageGenre,
"seriePageGenre"
);
serieShowtags = tryToGetTextContent(
serieShowtags,
seriePageTags,
"seriePageTags"
);
serieDescription = tryToGetTextContent(
serieDescription,
seriePageDescription,
"seriePageDescription"
);
DEBUG && console.log(serieTitle);
DEBUG && console.log(serieVotes);
DEBUG && console.log(serieStatus);
DEBUG && console.log(serieGenre);
DEBUG && console.log(serieShowtags);
DEBUG && console.log(serieDescription);
DEBUG &&
console.log("save imageUrl into coverData.url: " + imagelink);
let cData = {
url: imagelink,
title: serieTitle,
votes: serieVotes,
status: serieStatus,
genre: serieGenre,
showTags: serieShowtags,
description: serieDescription,
};
//currentTitelHover = serieTitle;
GM_setCachedValue(elementUrl, cData); //cache imageurl link
DEBUG &&
console.log(
elementUrl +
" url has been found and is written to temporary cache.\n" +
imagelink +
" successfully cached."
); // for testing purposes
DEBUG && console.groupEnd("parseSeriePage onLoad");
return resolve(cData);
//resolve(imagelink);
} catch (error) {
console.log(
"error: GM_xmlhttpRequest can not get xhr.response or script is not compatible"
);
console.log(error);
// showPopupLoadingSpinner(serieTitle, 1);
DEBUG && console.groupEnd("parseSeriePage onLoad");
return reject(xhr);
}
}
}
function onError(error) {
console.log(error);
const err = new Error(
"GM_xmlhttpRequest could not load " +
elementUrl +
"; script is not compatible or url does not exists."
);
console.log(err);
return reject(err);
}
GM_xmlhttpRequest({
method: "GET",
responseType: "document",
url: elementUrl,
onload: onLoad,
onerror: onError,
});
return undefined; //reject("status error")
});
return PromiseResult;
}
async function parseSeriePage(
elementUrl,
forceReload = false,
hoveredTitle = undefined,
event = undefined
) {
const DEBUG = false;
DEBUG && console.group("parseSeriePage: " + elementUrl);
const coverData = GM_getCachedValue(elementUrl);
let retrievedImgLink;
let PromiseResult;
if (!forceReload && coverData !== null && coverData.url) {
retrievedImgLink = coverData.url;
DEBUG &&
console.log(
"parseSeriePage has cached retrievedImgLink: " + retrievedImgLink
);
PromiseResult = coverData;
} else {
/*
console.group("parseSeriePage vars before getCoverDataFromUrl")
console.log("elementUrl: " + elementUrl+", forceReload: " + forceReload+", hoveredTitle: " + hoveredTitle);
console.log(event);
console.groupEnd();
*/
showPopupLoadingSpinner(hoveredTitle, hoveredTitle, event);
PromiseResult = getCoverDataFromUrl(elementUrl);
}
DEBUG && console.groupEnd("parseSeriePage: " + elementUrl);
PromiseResult = await PromiseResult;
//console.log(PromiseResult)
//DEBUG && console.log(PromiseResult)
//after GM_xmlhttpRequest PromiseResult
return PromiseResult;
}
function preloadCoverData() {
const DEBUG = false;
updateSerieNodes();
DEBUG && console.log(ALLSERIENODES);
DEBUG && console.log("preloadCoverData");
/*
const novelLinks = Array.from(
ALLSERIENODES //document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]')
);
DEBUG && console.log(novelLinks);*/
DEBUG &&
console.log(
"before parseSeriePage for each url with a link to individual seriepage"
);
if (ALLSERIENODES && ALLSERIENODES.length > 0) {
ALLSERIENODES.map(function (el) {
//console.log(el)
const elementUrl = el.href;
// console.log(elementUrl)
if (
eventListenerStyle === undefined ||
eventListenerStyle === null ||
eventListenerStyle == 0
) {
el.removeEventListener("mouseenter", mouseEnterPopup);
el.removeEventListener("mouseleave", hideOnMouseLeave);
el.addEventListener("mouseenter", mouseEnterPopup);
el.addEventListener("mouseleave", hideOnMouseLeave);
}
if (preloadUrlRequests) {
DEBUG && console.log("start parseSeriePage");
parseSeriePage(elementUrl).then(
function (coverData) {
if (coverData !== undefined) {
if (preloadImages) {
console.log("preloadCoverData preloadImages: " + preloadImages);
/*
let img = document.createElement("img"); //put img into dom. Let the image preload in background
img.onload = () => {
DEBUG && console.log("onpageload cache init previewImage " + coverData.url);
}
img.src = coverData.url
*/
console.log(coverData);
loadImageFromBrowser(coverData);
}
}
},
function (Error) {
DEBUG && console.log(Error + " failed to fetch " + el);
}
);
}
});
}
}
function addStyles() {
GM_addStyle(`
@keyframes rotate {
to {transform: rotate(360deg);}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
.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;
}
.ImgFitDefault{
object-fit: contain;
min-width: 0;
min-height: 0;
max-height: 400px;
max-width: 400px;
width:100%;
height:100%;
padding:2px;
}
#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;
}
.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;
}
.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 = (
STYLESHEETHIJACKFORBACKGROUND + " defaultBackgroundStyle"
).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) {
// 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"; //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" ></img>';
}
adjustPopupTitleDetail(coverData);
DEBUG && console.groupEnd("refreshPopover");
DEBUG && console.log(e);
//if (currentTitelHover == title)
if (e !== undefined) {
popupPos(e);
}
}
}
//#region get serieDetails
function getRatingNumber(ratingString) {
//const ratingString = "Rating(3.3 / 5.0, 1940 votes)"
let ratingNumber;
if (ratingString) {
const matches = ratingString.match(reRating);
const matchesVotes = ratingString.toLowerCase().match(reVoteCount);
//console.log(matches)
//console.log(matches.length)
let hasVotes = true;
// console.log(matchesVotes)
if (matchesVotes && matchesVotes.length > 1) {
//console.log(matchesVotes[1])
if (matchesVotes[1] == 0) {
hasVotes = false;
}
}
if (matches && matches.length == 3 && hasVotes) {
//console.log(matches[1])
ratingNumber = matches[1];
}
}
return ratingNumber;
}
function getChapters(statusString) {
let result;
if (statusString) {
let chapterCount;
let lowerCaseStatusString = statusString.toLowerCase();
const matches = lowerCaseStatusString.match(reChapters);
let webnovel = "";
let hasVolumenInString = false;
if (matches && matches.length >= 2) {
chapterCount = matches[1];
if (matches[2]) {
webnovel = " WN";
}
}
if (!chapterCount) {
const matchesBehind = lowerCaseStatusString.match(reChaptersNumberBehind);
if (matchesBehind && matchesBehind.length >= 2) {
chapterCount = matchesBehind[1];
}
}
if (!chapterCount) {
const matchesNumbers = lowerCaseStatusString.match(reChaptersOnlyNumbers); //example string "6892(Ongoing)"
if (matchesNumbers && matchesNumbers.length >= 2) {
chapterCount = matchesNumbers[1];
}
}
if (lowerCaseStatusString.includes("vol")) hasVolumenInString = true;
if (chapterCount) {
let numberType = " Chapters";
if (hasVolumenInString) numberType = " Vol";
result = chapterCount + webnovel + numberType;
}
}
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.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 completed = getCompletedState(coverData.status);
let ongoing = getOngoingState(coverData.status);
if (rating || chapters || completed || ongoing) {
//console.log(rating)
//console.log(chapters)
//console.log(completed)
//console.log(ongoing)
if (rating !== undefined) rating += "★ ";
else rating = "";
if (chapters !== undefined) chapters = chapters + " ";
else chapters = "";
if (completed) completed = "🗹 ";
else completed = ""; //https://www.utf8icons.com/
if (ongoing) ongoing = "✎ ";
else ongoing = "";
completeDetails +=
'<span class="' +
smallTextStyle +
'" style="white-space: nowrap;"> [' +
rating +
chapters +
completed +
ongoing +
"]</span>";
}
return completeDetails;
}
async function adjustPopupTitleDetail(coverData, title = undefined) {
let titleToShow = "";
popoverTitle.textContent = "";
if (coverData && coverData.title) titleToShow = coverData.title;
else if (title !== undefined) titleToShow = title;
//popoverTitle.textContent = titleToShow;
//console.log("adjustPopupTitleDetail - showDetails: " + showDetails)
let completeDetails = "";
if (showDetails) {
//console.log("showDetails should be true")
completeDetails +=
'<span class="' +
mediumTextStyle +
'" style="height:100%;display:flex;flex-direction:column">' +
titleToShow +
" " +
'<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 +
'">' +
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");
// const coverData = GM_getCachedValue(Href);
DEBUG && console.log(coverData);
let serieTitle = hoveredTitle;
if (!hoveredTitle || coverData.title)
//pure link without title get title of seriepage
serieTitle = coverData.title;
DEBUG &&
console.log(
"hoveredTitle: " + hoveredTitle + ", serieTitle: " + serieTitle
);
if (
coverData !== undefined &&
coverData !== null &&
hoveredTitle == currentTitelHover
)
currentCoverData = coverData;
if (e) loadImageFromBrowser(coverData, e, serieTitle, hoveredTitle);
DEBUG && console.groupEnd("setCurrentCoverDataAndLoadImage");
}
function ajaxLoadImageUrlAndShowPopup(
forceReload = false,
elementUrl,
hoveredTitle,
e
) {
const currentEvent = e;
//console.log(currentEvent)
//console.log("mouseenter")
// console.group("ajaxLoadImageUrlAndShowPopup")
return parseSeriePage(
elementUrl,
forceReload,
hoveredTitle,
currentEvent
).then(
function (coverData) {
if (coverData !== undefined) {
setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, currentEvent);
}
},
function (Error) {
console.log(Error);
console.log(Error + " failed to fetch " + elementUrl);
}
);
// console.groupEnd("ajaxLoadImageUrlAndShowPopup")
}
function imageLoaded(
coverData,
hoveredTitleLink,
serieTitle = undefined,
e = undefined
) {
const DEBUG = false;
const hasMouseEnterEvent = serieTitle && e !== undefined;
const isActivePopup =
currentTitelHover !== undefined &&
hoveredTitleLink !== undefined &&
currentTitelHover == hoveredTitleLink &&
hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData
DEBUG && console.group("loadImageFromBrowser img.onload: " + serieTitle);
DEBUG && console.log("finished loading imgurl: " + coverData.url);
DEBUG &&
console.log(
"currentTitelHover: " +
currentTitelHover +
", isActivePopup: " +
isActivePopup
);
DEBUG && console.log("isActivePopup: " + isActivePopup);
if (isActivePopup) {
DEBUG && console.log("refreshPopover");
refreshPopover(coverData, e); //popup only gets refreshed when currentTitelHover == serieTitle
}
DEBUG && console.groupEnd("loadImageFromBrowser img.onload");
}
function imageLoadingError(
coverData,
error,
hoveredTitleLink,
serieTitle = undefined,
e = undefined
) {
console.group("loadImageFromBrowser img.onerror: " + serieTitle);
/*
const hasMouseEnterEvent = serieTitle && e !== undefined;
const isActivePopup =
currentTitelHover !== undefined &&
hoveredTitleLink !== undefined &&
currentTitelHover == hoveredTitleLink &&
hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData
console.log("isActivePopup: " + isActivePopup);*/
console.log("hoveredTitleLink:" + hoveredTitleLink);
console.log(error);
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);
};
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() {
if (ALLSERIENODES && ALLSERIENODES.length > 0) {
ALLSERIENODES.forEach(function (selector) {
if (
eventListenerStyle === undefined ||
eventListenerStyle === null ||
eventListenerStyle == 0
) {
selector.removeEventListener("mouseleave", hideOnMouseLeave);
selector.removeEventListener("mouseenter", mouseEnterPopup);
}
});
}
const serieLinkNodes = document.querySelectorAll(
'a[href*="' + INDIVIDUALPAGETEST + '"]'
);
//console.log(serieLinkNodes)
ALLSERIENODES = Array.from(serieLinkNodes);
//console.log(ALLSERIENODES)
/*
console.log(ALLSERIENODES)
const sliceItemCount = 100;
if (ALLSERIENODES.length > sliceItemCount) {
ALLSERIENODES = ALLSERIENODES.slice(0, sliceItemCount);
}
console.log(ALLSERIENODES)
*/
}
function switchDetailsAndUpdatePopup() {
const DEBUG = false;
DEBUG && console.group("switchDetailsAndUpdatePopup");
changeToNewDetailStyle();
//console.log(currentCoverData)
DEBUG && console.log("switchDetails refreshPopup");
if (currentCoverData !== undefined) {
//has CoverData from seriePage loaded?
DEBUG && console.log(currentCoverData);
//refreshPopover(currentCoverData, currentPopupEvent); //update on detail change
loadImageFromBrowser(
currentCoverData,
currentPopupEvent,
currentTitelHover,
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(
currentCoverData,
currentPopupEvent,
currentTitelHover,
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');
if (Href.includes(INDIVIDUALPAGETEST)) {
//only trigger for links that point to serie pages
//console.log(this)
//console.log(this.text) //shortTitle
//console.log(this.title) //LongTitle
let shortSerieTitle = target.text; //element.text(); //get linkname
//console.log(this)
//console.log(shortSerieTitle)
const dataTitle = target.getAttribute("datatitle");
const linkTitle = target.getAttribute("title");
//console.log("linkTitle: " + linkTitle)
const hasDataTitle =
dataTitle === null ||
dataTitle == "null" ||
dataTitle === undefined ||
!dataTitle;
//move native title to custom data attribute. Suppress nativ title popup
if (linkTitle !== null && hasDataTitle) {
target.setAttribute("datatitle", linkTitle);
target.removeAttribute("title");
}
let serieTitle = target.getAttribute("datatitle"); //element.attr('datatitle'); //try to get nativ title if available from datatitle
//console.log(serieTitle)
if (
serieTitle === null || //has no set nativ long title -> use (available shortend) linkname
serieTitle == "null" ||
PREDIFINEDNATIVTITLEARRAY.some((nativTitle) =>
serieTitle.includes(nativTitle)
)
)
//catch on individual serie page nativ title begins with "Recommended by" x people -> use linkname
serieTitle = shortSerieTitle;
if (serieTitle === undefined || serieTitle === null || serieTitle == "")
//image link: example link which content is only the cover image https://www.mangaupdates.com/series.html?letter=A
serieTitle = Href;
currentTitelHover = serieTitle; //mark which titel is currently hovered
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)
ajaxLoadImageUrlAndShowPopup(forceReload, Href, currentTitelHover, e);
}
}
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(
currentCoverData,
currentPopupEvent,
currentTitelHover,
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(
currentCoverData,
currentPopupEvent,
currentTitelHover,
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?
updateSerieNodes();
observer.disconnect();
};
if (eventListenerStyle == 1)
window.addEventListener("mousemove", throttledGetHoveredItem);
}
//assumption that a single eventlistener is more performant than dozens of mouseEnter/MouseLeave events
//https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/#web-performance
//https://davidwalsh.name/event-delegate
//https://web.archive.org/web/20170121035049/http://jsperf.com/click-perf
//https://stackoverflow.com/questions/29836326/is-using-a-universal-document-addeventlistenerclick-f-listener-slower-or-wea
/*
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
*/
function getHoveredItem(e) {
if (eventListenerStyle == 1) {
if (
e.target &&
e.target != lastTarget &&
e.target.nodeName == "A" &&
e.target.href &&
e.target.href.includes(INDIVIDUALPAGETEST)
) {
lastTarget = e.target;
//console.group("target A")
//console.log(e.target.text)
//console.log(e)
mouseEnterPopup(e);
//console.groupEnd();
} else {
if (e.target.nodeName != "A") {
lastTarget = undefined;
hideOnMouseLeave();
}
}
}
}
document.addEventListener("DOMContentLoaded", main());
//#endregion
function main() {
const DEBUG = false;
DEBUG && console.log("started main function of coverPreview");
//#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");