🏠 Home 

novelupdates Cover Preview

Previews covers in novelupdates.com when hovering over hyperlinks that lead to novelupdates seriepages and a few external pages.


Install this script?
// ==UserScript==
// https://greasyfork.org/scripts/26439-novelupdates-cover-preview/
// @name        novelupdates Cover Preview
// @namespace   somethingthatshouldnot#####withotherscripts
// @version     2.6.0
// @description Previews covers in novelupdates.com when hovering over hyperlinks that lead to novelupdates seriepages and a few external pages.
// @author      SZ
// @supportURL  https://greasyfork.org/de/scripts/26439-novelupdates-cover-preview/feedback
// @website     https://forum.novelupdates.com/threads/novel-updates-userscript-to-preview-cover-images-on-greasyfork.117240/
// @include     https://www.novelupdates.com/*
// @include     http://www.novelupdates.com/*
// @include     https://forum.novelupdates.com/*
// @include     http://forum.novelupdates.com/*
// @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==
(function () {
//console.log("cover preview start");
"use strict";
//#region frontend settings
const MAXCACHEAGE = 90 * 24 * 60 * 60 * 1000; // Max Age before Cached data of serieinfo gets overridden with current data. Max Age is 90 days in milliseconds  //days * h * min  * sec * ms
const DEFAULTTITLEBACKGROUNDCOLOR = "#2c3e50"; //if no hijack class style available use plain color
const DEFAULTBACKGROUNDCOLOR = "#ccc"; //if no hijack class style available use plain color
let STYLESHEETHIJACKFORBACKGROUND = " .l-submain, .breadcrumb"; //if unknown set empty ""; classname with leading dot seperated with comma  //.pageContent
//index: l-submain;forum: breadcrumb;
let STYLESHEETHIJACKFORTITLE = ".widgettitle_nuf, .navTabs "; //if unknown set empty ""; classname with leading dot seperated with comma
//index: widgettitle_nuf; forum:navTabs
const PREDIFINEDNATIVTITLE = "Recommended by";
/* forum, index
display shorttitle or serielink url before coverdata from individualpage is loaded
can have other starting strings seperated by a comma
for example mangaupdates is using "Click for series info, Series Info"
*/
const isOnIndex = false; //now autodetect display style by checking container of link is tablecell "td"
/*
this.location.href == "https://www.novelupdates.com/" ||
this.location.href.startsWith("https://www.novelupdates.com/?pg=");
this.location.href.startsWith("https://www.novelupdates.com/group/"); //popup style next to container instead of next to linkitem
*/
const targetContainerIDArrayToObserve = [
"profile_content3",
"messageList",
"myTable",
];
/*
observer needed for ajax changed page data. (here personal readinglist tab change and in the forum a quickedit of a post)
update eventlistener on content change if element id found: isOnReadingListIndex [#profile_content3], in forum[#messageList] and on index[myTable] on tablesorting
Attach container to a MutationObserver function which refreshes the eventlistener on links to seriepages
*/
const internalLink = {
"www.novelupdates.com/series/": {
serieRegex: "([\\w-]+/?).*",
seriePageTitle: ".seriestitlenu",
IMAGELINKCONTAINERS: ".serieseditimg img, .seriesimg img", //instead of single element class name with dot seperated with comma
//CONTAINERNUMBER: 0, //in case that the query for IMAGELINKCONTAINERS has multiple img node r###lts it can be selected by CONTAINERNUMBER or with img:nth-child(index) inside the query
//the same can be used for external links
seriePageVotes: ".seriesother > .uvotes",
seriePageStatus: "#editstatus",
seriePageGenre: "#seriesgenre",
seriePageTags: "#showtags", //
seriePageDescription: "#editdescription",
serieAlternativeNames: "#editassociated",
serieReadingListIcon: ".sticon img",
serieReadingListTitle: ".sttitle > a",
},
};
const internalLinkKey = Object.keys(internalLink);
const INDIVIDUALPAGETEST = internalLinkKey[0]; //"www.novelupdates.com/series/"; //matched with includes
/*
max possible externalLinkObject to insert into externalLinks
{
"individualSiteLink":{
seriePageTitle:undefined,
IMAGELINKCONTAINERS:undefined,
CONTAINERNUMBER:0,
seriePageDescription:undefined,
seriePageStatus:undefined,
seriePageChapters:undefined,
seriePageVotes:undefined,
seriePageGenre:undefined,
seriePageTags:undefined,
serieRegex:undefined,
}
}
*/
const defaultRateLimitQueryAfterSeconds = 0.5;
const defaultSerieRegex = "([0-9]+)(?!\\w)([/]+.*)?"; //block popup generation for externalLink without appended serie id
// example: "link/"(id); "link/"(id)/; "link/"(id)/anything
//not "link/"/; "link/"(id)something; "link/"(id)something/
const externalLinks = {
//#region site urls with public api access implemented
/* https://mangadex.org/thread/351011
chapter counts https://mangadex.org/api/v2/manga/" + id + "/chapters
genre/tags id to name: https://mangadex.org/api/v2/tag
*/
"mangadex.org/title/": {
serieRegex: "([0-9]+)", //block popup generation for mangadex.org/title/ link without serie id
mainAPI: "https://mangadex.org/api/v2/manga/",
/* not possible no unique identification possible (no id or unique class for a container)
depending on serie div count changing since not all values are always used
data not at the same position and no unique selector available
using public mangadex api
*/
},
"mangadex.org/manga/": {
//alternative address same function
serieRegex: "([0-9]+)",
mainAPI: "https://mangadex.org/api/v2/manga/",
},
/* http://www.tvmaze.com/api
alternative names http://api.tvmaze.com/shows/(id)/akas
episode counts http://api.tvmaze.com/shows/(id)/episodes
*/
"www.tvmaze.com/shows/": {
serieRegex: "([0-9]+)",
mainAPI: "http://api.tvmaze.com/shows/",
//http://www.tvmaze.com/api#rate-limiting
rateLimitCount: 20,
rateLimitTimeInSeconds: 10,
//rateLimitQueryAfterSeconds:"0.5" //if not set, but both other values are available will be calculated into rateLimitQueryAfterSeconds = (rateLimitTimeInSeconds/rateLimitCount)
//if incomplete will use defaultRateLimitQueryAfterSeconds
},
//#endregion
//https://www.wlnupdates.com/api-docs
"wlnupdates.com/series-id/": {
serieRegex: "([0-9]+)",
mainAPI: "https://www.wlnupdates.com/api",
/* using public api. no need to update if only page layout changes
seriePageTitle: "h2",
IMAGELINKCONTAINERS: ".imgDiv img",
seriePageDescription: ".description",
seriePageStatus: ".updating-current", //updating-stalled
seriePageChapters: ".orig_status",
seriePageVotes: ".br-current-rating",
seriePageGenre: "#genre-container > .rowContents",
seriePageTags: "#tag-container > .rowContents",
*/
//serieRegex:undefined,
},
"www.scribblehub.com/series/": {
/*
https://www.scribblehub.com/series/211234/the-impossible-fate-that-leads-to-a-god-of-a-new-world/
exception linkid not forwared to url. linkID needs tobe number+/+stringuntil next slash
https://www.scribblehub.com/series/211234
*/
serieRegex: "([0-9]+(?=/)[/]?[\\w-]+[/]?)", //get #ID + / + string
serieRegex2: "([0-9]+)", //if has not detailed regex get only #ID
IMAGELINKCONTAINERS: ".fic_image img", //instead of single element class name with dot seperated with comma
seriePageTitle: ".fic_title",
seriePageVotes: "#ratefic_user > span",
seriePageStatus: ".fic_stats > span:nth-child(3)",
seriePageGenre: ".wi_fic_genre",
seriePageTags: ".wi_fic_showtags_inner", //
seriePageDescription: ".wi_fic_desc",
},
"www.webnovel.com/book/": {
serieRegex: "([\\w'-]+[/]?).*", //domain/ + spring-blooms-when-i'm-with-you_12023713305779105
IMAGELINKCONTAINERS: ".g_thumb > img", //instead of single element class name with dot seperated with comma
seriePageTitle: ".det-info > div:nth-child(2) > h2",
seriePageVotes: "._score > strong",
seriePageStatus: ".det-hd-detail > strong > span",
seriePageGenre: ".det-hd-detail > a > span",
seriePageTags: ".m-tags",
seriePageDescription: ".det-abt > div > p",
},
"royalroad.com/fiction/": {
serieRegex: "([0-9]+)", //"([\\w-]+[/]?).*",
//alternative serieRegex:"([0-9]+)(?=\/)""  would block detection if string after domain is "21220mother-of-learning"
IMAGELINKCONTAINERS: ".fic-header > div > img", //instead of single element class name with dot seperated with comma
seriePageTitle: ".fic-title > h1",
seriePageVotes: undefined,
seriePageStatus:
".fiction-info > div > div:nth-child(2) > div > span:nth-child(2)",
seriePageGenre: ".tags",
seriePageTags: undefined, //
seriePageDescription: ".description",
},
"royalroadl.com/fiction/": {
//old domain? forwarded to standard domain
serieRegex: "([0-9]+)",
IMAGELINKCONTAINERS: ".fic-header > div > img", //instead of single element class name with dot seperated with comma
seriePageTitle: ".fic-title > h1",
seriePageVotes: undefined,
seriePageStatus:
".fiction-info > div > div:nth-child(2) > div > span:nth-child(2)",
seriePageGenre: ".tags",
seriePageTags: undefined, //
seriePageDescription: ".description",
},
// haven't found a recent used  link no need to activate for incomplete data
/*
"wuxiaworld.com/novel/":{
seriePageTitle: ".novel-body h2",
IMAGELINKCONTAINERS: ".novel-left img", //instead of single element class name with dot seperated with comma
seriePageStatus: ".novel-body > div:nth-child(2)",
seriePageGenre: ".genres",
seriePageDescription: ".novel-bottom > div:nth-child(4) > div",
},*/
"www.mtlnovel.com/": {
serieRegex: "([\\w-]+[/]?).*",
seriePageTitle: ".entry-title",
IMAGELINKCONTAINERS: ".nov-head > amp-img", //instead of single element class name with dot seperated with comma
seriePageVotes: ".star-avg",
seriePageStatus: ".info tr:nth-child(3) > td:nth-child(3)",
seriePageChapters: ".info-wrap div:nth-child(2)",
seriePageGenre: "#currentgen",
seriePageTags: ".info tr:nth-child(6)",
seriePageDescription: ".desc",
serieAlternativeNames: ".info tr:nth-child(2) > td:nth-child(3)",
},
/*
not possible for the details and haven't seen an api
example with different div row counts: https://bato.to/series/72644, https://bato.to/series/74357
data not at the same position and no unique selector available
*/
"bato.to/series/": {
serieRegex: "([0-9]+[/]?)",
seriePageTitle: ".item-title",
IMAGELINKCONTAINERS: ".attr-cover > img", //instead of single element class name with dot seperated with comma
seriePageDescription: ".attr-main > pre",
seriePageChapters: ".episode-list > div.head > h4",
seriePageGenre: ".attr-main > div:nth-child(3) > span",
serieAlternativeNames: ".alias-set",
},
"mangaupdates.com/series.html?id=": {
serieRegex: "([0-9]+)",
seriePageTitle: ".releasestitle",
serieAlternativeNames:
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(11)",
IMAGELINKCONTAINERS:
"#main_content > div:nth-child(2) > div > div:nth-child(4) img", //instead of single element class name with dot seperated with comma
//IMAGEBLOCKER: "images/stat_increase.gif, images/stat_decrease.gif",
//imageblocker was needed for previous non unique IMAGELINKCONTAINERS selector. ("".sContent img") since a page without cover would query the next available image (in this case rate up/down icon)
//CONTAINERNUMBER: 0,
seriePageVotes:
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(35)",
seriePageStatus:
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(20)",
seriePageGenre:
"#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(5)",
seriePageTags:
"#main_content > div:nth-child(2) > div > div:nth-child(4) > div:nth-child(8) ul", //
seriePageDescription:
"#main_content > div:nth-child(2) > div > div:nth-child(3) > div:nth-child(2)",
},
"mydramalist.com/": {
serieRegex: "([0-9]+-[\\w-]+[/]?).*",
seriePageTitle: ".film-title",
serieAlternativeNames: ".mdl-aka-titles",
IMAGELINKCONTAINERS: ".film-cover img",
seriePageVotes: "#show-detailsxx .hfs",
seriePageStatus:
".content-side > div:nth-child(2) > div:nth-child(2) li:nth-child(4)",
seriePageGenre: ".show-genres",
seriePageTags: ".show-tags", //
seriePageDescription: ".show-synopsis",
},
"www.imdb.com/title/": {
serieRegex: "(tt[0-9]+[/]?).*",
seriePageTitle: ".title_wrapper h1",
IMAGELINKCONTAINERS: ".poster img",
seriePageVotes: ".imdbRating > .ratingValue",
seriePageStatus: ".title_wrapper .subtext",
//seriePageGenre non static position. can be pushed down by Taglines box if available
seriePageTags: "#titleStoryLine > div:nth-child(6)",
seriePageDescription: "#titleStoryLine > div > p > span",
},
"www.tv.com/shows/": {
serieRegex: "([\\w-]+[/]?).*",
seriePageTitle: ".show_head > h1",
IMAGELINKCONTAINERS: ".image_border img",
seriePageVotes: ".score",
seriePageStatus: ".tagline",
seriePageGenre: ".categories > p",
seriePageTags: ".themes > p",
seriePageDescription: ".description > p",
},
"wiki.d-addicts.com/": {
serieRegex: "([\\w-]+)",
seriePageTitle: "#content .title",
serieAlternativeNames:
"#mw-content-text > .mw-parser-output > ul > li:nth-child(1)",
IMAGELINKCONTAINERS: ".thumbinner img",
seriePageVotes: ".voteboxrate",
seriePageStatus:
"#mw-content-text > .mw-parser-output > ul > li:nth-child(6)",
seriePageChapters:
"#mw-content-text > .mw-parser-output > ul > li:nth-child(4)",
seriePageGenre:
"#mw-content-text > .mw-parser-output > ul > li:nth-child(3)",
seriePageDescription: "#mw-content-text p",
},
"asianwiki.com/": {
serieRegex: "([\\w()-]+).*",
seriePageTitle: ".article > h1",
serieAlternativeNames: "#mw-content-text > ul > li:nth-child(2)",
IMAGELINKCONTAINERS: ".thumbimage",
seriePageVotes: "#w4g_rb_area-1",
seriePageStatus: "#mw-content-text > ul > li:nth-child(8)",
seriePageChapters: "#mw-content-text > ul > li:nth-child(7)",
seriePageDescription: "#mw-content-text > ul ~ p", //next p sibling after ul
},
/*
not possible.
example https://myanimelist.net/manga/2 and https://myanimelist.net/manga/11 Alternative Titles is changing div child count and no unique selector possible
no anonymous public api access available
"https://myanimelist.net/manga/":{
seriePageTitle: ".h1-title",
IMAGELINKCONTAINERS: ".borderClass > div > div img",
seriePageVotes: ".score-label",
//serieAlternativeNames: ".borderClass > div > div:nth-child(8)",
//seriePageStatus:".borderClass > div > div:nth-child(16)",
// seriePageGenre:".borderClass > div > div:nth-child(17)",
seriePageDescription: 'span[itemprop="description"',
}
*/
};
const externalLinkKeys = Object.keys(externalLinks);
const defaultshowIconNextToLink = false;
let preloadUrlRequestsDefault; //if not set will default to true in release
let deactivatePreloadUrlRequestOnUrls = [
"wiki.d-addicts.com",
"https://forum.novelupdates.com/threads/novel-updates-userscript-to-preview-cover-images-on-greasyfork.117240/", //deactivated urlPreload on this forum thread to stop bombardedment of requesting domain access in tampermonkey since all external links are listed in the nu forum post.
];
const preloadImagesDefault = false;
let useReadingListIconAndTitle = false;
const eventListenerStyle = 0; //undefined/0 forEach serieLink addeventlistener(mouseenter/mouseleave) / 1 window addeventlistener(mousemove)
//#endregion end of frontend settings
/* not available in firefox
would have liked to automatically set preloadImages to true for wifi and non metered connections with speeds over 2MB/s
console.log(navigator);
let connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
let type = connection.effectiveType;
function updateConnectionStatus() {
console.log(connection)
console.log("Connection type changed from " + type + " to " + connection.effectiveType);
type = connection.effectiveType;
if(connection.saveData==false){
preloadUrlRequests = true;
if(type=="wifi" || preloadImagesDefault)
preloadImages = true;
}
}
updateConnectionStatus();
connection.addEventListener('change', updateConnectionStatus);
*/
//#region backend variables ^^^^	frontend settings over this line	^^^^
const version = "2.3.2";
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 settingsToKeepOnDataReset = [
"showDescription",
"showDetails",
"showSmaller",
"useReadingListIconAndTitle",
"showIconNextToLink",
];
//console.log(GM_info);
//console.log(GM_info.script.name);
const isReleaseVersion = GM_info.script.name == "novelupdates Cover Preview";
//console.log("isReleaseVersion: " + isReleaseVersion);
let preloadUrlRequests =
isReleaseVersion &&
(preloadUrlRequestsDefault === undefined || preloadUrlRequestsDefault);
let preloadImages = false || (preloadImagesDefault && isReleaseVersion);
//console.log("preloadUrlRequests: " + preloadUrlRequests)
let showIconNextToLink = defaultshowIconNextToLink;
//const maxWaitingTime = 120;
const linkIconEnum = {
popupPossibleNotLoadedOrMarkedForPreloading: 1,
popupMarkedForPreloading: 2,
popupLoading: 3,
popupHasCoverData: 4,
error: 5,
};
Object.freeze(linkIconEnum);
const emptyCoverData = {
url: undefined,
title: undefined,
alternativeNames: undefined,
votes: undefined,
status: undefined,
chapters: undefined,
genre: undefined,
showTags: undefined,
description: undefined,
isExternal: undefined,
readingListIcon: undefined,
readingListTitle: undefined,
};
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[s]?[ ]*[(]?[ ]*([0-9.]+)");
const reChaptersOnlyNumbers = new RegExp("([0-9.]+)");
const reRating = new RegExp("([0-9.]+) / ([0-9.]+)");
const reRatingSingleNumber = new RegExp("([0-9.]+)");
const reVoteCount = new RegExp("([0-9.]+)[ ]*(votes|ratings|users)"); //
//const reStripHTMLLiteral = "(<[\/]?(?!span|b|i|p|br[^\w]+\s*)(([\/]?[^>]+))>)";
const reWhiteListStripHTML = new RegExp(
"(<[/]?\\b(?!(b|i|br|p)[^\\w]+\\s*)(([/]?[^>]+))>)",
"g"
); //needed escape character changes remove escaping for / (\/) , specialFunction additional escaping for()
//const reWhiteListHTML = /(<[\/]?\b(?!(b|u|i|br|p)[^\w]+\s*)(([\/]?[^>]+))>)/g   // /(<[\/]?\b(?!span|b|i|p|br[^\w]+\s*)(([\/]?[^>]+))>)/g;
const offsetToBottomBorderY = 22; //offset to bottom border
const offsetToRightBorderX = 10; //offset to right border
const defaultHeight = 400; //in pixel
const smallHeight = 250;
//const IMAGEBLOCKERARRAY = IMAGEBLOCKER.split(RE);
const PREDIFINEDNATIVTITLEARRAY = PREDIFINEDNATIVTITLE.split(RE);
const STYLESHEETHIJACKFORBACKGROUNDARRAY = STYLESHEETHIJACKFORBACKGROUND.split(
RE
);
const STYLESHEETHIJACKFORTITLEARRAY = STYLESHEETHIJACKFORTITLE.split(RE);
let refreshInitValues = false;
let showDetails = false;
let popoverVisible = false; //not all links have a title or text(img link) to set currentTitelHover. Manual state saving needed
let ALLSERIENODES = []; // = document.querySelectorAll('a[href*="' + INDIVIDUALPAGETEST + '"]');
let ALLEXTERNALLINKNODES = [];
let previousTitelHover,
currentTitelHover,
currentCoverData,
currentPopupEvent;
let popover, popoverTitle, popoverContent, arrowContainer;
let lastTarget;
let isShowingSpinnerAnimation = false;
let showDescription = false;
let showSmaller = false;
let showHotkeys = false;
let showAlternativeNames = false;
let autoScrollCoverData = true;
let coverDataContainer = [];
let mediumTextStyle = "mediumText";
let smallTextStyle = "smallText";
let pressedKeys = [];
let mangaDexTAGS;
let currentOpenedUrl;
let popoverBackgroundColor, popoverForegroundColor;
let popoverBorderWidth;
let popoverBorderColor;
const supportsCSSMin = CSS.supports("max-Height", "min(400px, 100%)");
const arrowWidthInPx = 40;
//#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 key request: " + key);
DEBUG && console.log("rawCover: " + rawCover);
let r###lt = null;
if (rawCover === null || rawCover == "null") {
r###lt = 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.title && coverData.cachedTime)) {
//has same variable definitions?
GM_deleteValue(key);
r###lt = null;
}
} catch (e) {
GM_deleteValue(key);
r###lt = null;
}
const measuredTimedifference = currentTime - coverData.cachedTime;
if (measuredTimedifference < MAXCACHEAGE) {
r###lt = {
url: coverData.url,
title: coverData.title,
alternativeNames: coverData.alternativeNames,
votes: coverData.votes,
status: coverData.status,
chapters: coverData.chapters,
genre: coverData.genre,
showTags: coverData.showTags,
description: coverData.description,
isExternal: coverData.isExternal,
readingListIcon: coverData.readingListIcon,
readingListTitle: coverData.readingListTitle,
};
} else {
GM_deleteValue(key);
r###lt = null;
}
}
DEBUG && console.groupEnd("GM_getCachedValue");
DEBUG && console.log(r###lt);
return r###lt;
}
//set value and currenttime for key
function GM_setCachedValue(key, coverData) {
const DEBUG = false;
const cD = {
url: coverData.url,
title: coverData.title,
alternativeNames: coverData.alternativeNames,
votes: coverData.votes,
status: coverData.status,
chapters: coverData.chapters,
genre: coverData.genre,
showTags: coverData.showTags,
description: coverData.description,
isExternal: coverData.isExternal,
readingListIcon: coverData.readingListIcon,
readingListTitle: coverData.readingListTitle,
cachedTime: Date.now(),
};
GM_setValue(key, JSON.stringify(cD));
DEBUG && console.group("GM_setCachedValue key: " + key);
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?
*/
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()
hidePopOver();
debouncedpreloadCoverData();
} 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);
//https://math.stackexchange.com/questions/814950/how-can-i-rotate-a-coordinate-around-a-circle
/**
*
*
* @param {*} cx = pivot
* @param {*} cy = pivot
* @param {*} mx = offset
* @param {*} my = offset
* @param {*} angle
* @returns
*/
function getRotatedPoint(cx, cy, mx, my, angle) {
let x, y;
angle = ((angle % 360) / 180) * Math.PI;
x = ((mx - cx) * Math.cos(angle) - (my - cy) * Math.sin(angle) + cx) | 0;
y = ((mx - cx) * Math.sin(angle) + (my - cy) * Math.cos(angle) + cy) | 0;
return { x: x, y: y };
}
//https://stackoverflow.com/questions/1887104/how-to-get-the-background-color-of-an-html-element
const getComputedStyle = function (element, property) {
return window.getComputedStyle
? window.getComputedStyle(element, null).getPropertyValue(property)
: element.style[
property.replace(/-([a-z])/g, function (g) {
return g[1].toUpperCase();
})
];
};
//adjusted from https://stackoverflow.com/questions/34980574/how-to-extract-color-values-from-rgb-string-in-javascript/34980657
function getRGBValues(str) {
var vals = str.substring(str.indexOf("(") + 1, str.length - 1).split(", ");
return vals;
}
//adjusted from https://gist.github.com/JordanDelcros/518396da1c13f75ee057
function blendColorsToRGBA(colors = []) {
var base = [0, 0, 0, 0];
var mix;
var added;
while ((added = colors.shift())) {
//console.log(added)
//console.log(typeof added)
if (typeof added == "string") {
const arrayColor = getRGBValues(added);
//console.log(arrayColor)
added = arrayColor;
}
//console.log(added[3])
//console.log(typeof added[3])
if (typeof added[3] === "undefined") {
//if rgb add alpha 1
added[3] = 1;
}
//console.log(added)
//console.log(base)
// check if both alpha channels exist.
if (base[3] && added[3]) {
mix = [0, 0, 0, 0];
// alpha
mix[3] = 1 - (1 - added[3]) * (1 - base[3]);
//console.log(added)
//console.log(base)
// red
mix[0] = Math.round(
(added[0] * added[3]) / mix[3] +
(base[0] * base[3] * (1 - added[3])) / mix[3]
);
// green
mix[1] = Math.round(
(added[1] * added[3]) / mix[3] +
(base[1] * base[3] * (1 - added[3])) / mix[3]
);
// blue
mix[2] = Math.round(
(added[2] * added[3]) / mix[3] +
(base[2] * base[3] * (1 - added[3])) / mix[3]
);
//console.log(mix);
} else if (added) {
//console.log(added)
mix = added;
} else {
//console.log(base)
mix = base;
}
//console.log(mix)
base = mix;
}
//console.log(mix)
return "rgba(" + mix + ")";
}
//#endregion helper functions
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();
//resetSettings();
}
}
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++) {
if (!settingsToKeepOnDataReset.includes(oldValues[i])) {
GM_deleteValue(oldValues[i]);
} else {
//console.log("keep setting")
//console.log(oldValues[i])
}
//console.log(oldValues[i])
}
GM_setValue("version", version);
DEBUG && console.log(oldValues);
!isReleaseVersion && console.log("Finished clearing CoverData");
}
function resetSettings() {
for (let i = 0; i < settingsToKeepOnDataReset.length; i++) {
GM_deleteValue(settingsToKeepOnDataReset[i]);
}
GM_setValue("version", version);
}
//https://www.w3resource.com/javascript-exercises/javascript-math-exercise-27.php
function pointDirection(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI;
}
function checkIsTableElement(element) {
if (
element.tagName == "TD" ||
element.parentElement.tagName == "TD" ||
element.className.includes("col-") || //tailwind column styles  needed for mangaupdates
element.parentElement.className.includes("col-")
) {
return true;
}
return false;
}
function showBorderArrowBox(
event,
arrowWidth = 20,
upside = false,
isExternal = false,
arrowTableSideRight = true
) {
const DEBUG = false;
/*
- getPopover Rect
- generate circleBox
- generate arrow
- rotate arrow to target
*/
const nativElement = event.target;
let targetElement = nativElement;
let isInChildContainer = false;
let tableElement = checkIsTableElement(nativElement);
DEBUG && console.group("showBorderArrowBox");
const elementLinkHasNoText = targetElement.textContent.length == 0;
//elementLinkHasNoText
if (isOnIndex || elementLinkHasNoText || tableElement) {
isInChildContainer = true;
targetElement = nativElement.parentElement; //get container element/table cell
}
/*
if(elementLinkHasNoText){
targetElement = nativElement.parentElement.parentElement;
console.log(targetElement);
}
*/
const targetRectBounds = targetElement.getBoundingClientRect();
let targetRect = targetRectBounds;
//https://stackoverflow.com/questions/52712760/get-how-many-lines-a-textarea-has
/*
TODO
novelupdates forum/xenforum script is somehow changing element with class "ugc" which prevents getting the pseudo element a::before from getClientRects()
*/
DEBUG && console.log(targetElement);
const targetLineRects = targetElement.getClientRects();
let firstLineRect, lastLineRect;
let firstRectIndex;
DEBUG && console.log(targetRect);
//console.log(targetLineRects);
firstRectIndex = 0;
if (targetLineRects.length > 0) {
if (showIconNextToLink && targetLineRects.length > 1) {
firstRectIndex = 1;
}
targetRect = targetLineRects[firstRectIndex];
firstLineRect = targetRect;
lastLineRect = targetLineRects[targetLineRects.length - 1];
}
//if (lastLineRect != firstLineRect)
if (lastLineRect) targetRect = lastLineRect;
if (!upside) {
if (firstLineRect) targetRect = firstLineRect;
}
let popoverRect = popover.getBoundingClientRect();
DEBUG && console.log(popoverRect);
DEBUG && console.log(targetRect);
DEBUG && console.log(targetRectBounds);
const scrollPosY =
window.scrollY ||
window.scrollTop ||
document.getElementsByTagName("html")[0].scrollTop;
const scrollPosX =
window.scrollX ||
window.scrollLeft ||
document.getElementsByTagName("html")[0].scrollLeft;
arrowContainer.style.position = "absolute";
arrowContainer.style.zIndex = "8";
arrowContainer.style.height = arrowWidth + "px";
arrowContainer.style.width = arrowWidth + "px";
let posY = 0;
let rotateArrowInDegree = 0;
let targetPosY, sourcePosY;
let targetXSourcePosition;
let popupSourceX;
const targetHalfHeight = targetRect.height / 2;
const halfArrowWidth = arrowWidth / 2;
let halfWidth = arrowWidth / 2;
const quarterArrowWidth = arrowWidth / 4;
let arrowContainerTopPositionLocal;
let targetRectTopDistance = targetRect.top - popoverRect.top;
let targetElementDistanceY =
targetRectTopDistance - targetRect.height / 4 + 1;
const minYPosOnPopup = -quarterArrowWidth;
const maxYPosOnPopup =
popoverRect.height - (arrowWidth - quarterArrowWidth);
if (targetElementDistanceY < minYPosOnPopup) {
targetElementDistanceY = minYPosOnPopup;
}
if (targetElementDistanceY > maxYPosOnPopup) {
targetElementDistanceY = maxYPosOnPopup;
}
//if (isExternal) externalStyle = "isExternalContentArrow ";
//|| elementLinkHasNoText
if (isOnIndex || tableElement) {
//#region "index/container layout"
DEBUG && console.log("index/container layout");
DEBUG && console.log(targetRect);
DEBUG && console.log("targetRect.height: " + targetRect.height);
const upperBorder = popoverRect.top - quarterArrowWidth;
const lowerBorder =
popoverRect.bottom - halfArrowWidth - quarterArrowWidth;
DEBUG &&
console.log(
"targetRect.height: " +
targetRect.height +
", targetHalfHeight: " +
targetHalfHeight
);
targetPosY = targetRect.top; //targetRectYMiddle;
sourcePosY = targetPosY;
//#region get targetPointY
DEBUG &&
console.log(
"targetPosY: " +
targetPosY +
", upperBorder: " +
upperBorder +
", lowerBorder: " +
lowerBorder
);
if (targetPosY < upperBorder) {
sourcePosY = upperBorder;
DEBUG && console.log("set targetPosY = upperBorder");
}
if (targetPosY > lowerBorder) {
sourcePosY = lowerBorder;
DEBUG && console.log("set  targetPosY = lowerBorder");
}
//#endregion
//#region limit popover sourceY position
//sourcePosY = targetPosY;
//#endregion
DEBUG &&
console.log(
"targetPosY: " +
targetPosY +
", sourcePosY: " +
sourcePosY +
", scrollPosY: " +
scrollPosY +
", top: " +
(scrollPosY + targetPosY)
);
DEBUG &&
console.log(
"targetElementDistanceY: " +
targetElementDistanceY +
",  popoverRect.top: " +
popoverRect.top +
", targetRect.top: " +
targetRect.top
);
arrowContainerTopPositionLocal = popoverRect.top + targetElementDistanceY;
arrowContainer.style.top =
scrollPosY + arrowContainerTopPositionLocal + "px"; //sourcePosY
//#endregion arrow vertical position
//#region arrow on leftOrRightSide of popover
if (arrowTableSideRight) {
arrowContainer.style.left =
scrollPosX + popoverRect.left - halfWidth + "px";
} else {
arrowContainer.style.left =
scrollPosX + popoverRect.left + popoverRect.width - halfWidth + "px";
}
//#endregion arrow on leftOrRightSide of popover
DEBUG &&
console.log(
"posY: " +
posY +
", popoverRect.top + posY + arrowWidth: " +
(popoverRect.top + posY + arrowWidth)
);
targetXSourcePosition = targetRect.right;
popupSourceX = popoverRect.left;
rotateArrowInDegree =
pointDirection(
popupSourceX,
sourcePosY,
targetXSourcePosition - arrowWidth,
targetPosY
) - 45;
//width:100%;height:100%;
//#endregion "index/container layout"
} else {
//#region "under/over link layout"
DEBUG && console.log("under/over link layout");
//#region horizontal
//#region targetPosX
// let targetX = targetRect.right;
let diffXTarget = targetRect.right - popoverRect.left;
const diffXTargetWithHalfwidth = diffXTarget + halfWidth;
if (diffXTarget - halfWidth < 0) diffXTarget = halfWidth;
let targetXSourcePosition = targetRect.right - diffXTargetWithHalfwidth;
if (diffXTarget > targetRect.width) {
targetXSourcePosition = targetRect.left;
}
//#endregion targetPosX
//#region popoverPosX
popupSourceX = popoverRect.left - halfWidth;
console.log(
"diffXTargetWithHalfwidth: " +
diffXTargetWithHalfwidth +
", targetRect.width: " +
targetRect.width
);
if (diffXTargetWithHalfwidth > targetRect.width) {
popupSourceX = targetXSourcePosition;
}
//#endregion popoverPosX
//#region move arrowHead in targetDirection
if (diffXTarget < halfWidth) {
targetXSourcePosition -= halfWidth;
}
//#endregion move arrowHead in targetDirection
if (targetXSourcePosition) {
DEBUG &&
console.log(
"diffXTarget: " +
diffXTarget +
", popupSourceX: " +
popupSourceX +
", targetRect.width: " +
targetRect.width +
", targetXSourcePosition: " +
targetXSourcePosition
);
}
//#endregion
//#region vertical position over/under popup
arrowContainer.style.left = scrollPosX + popupSourceX + "px";
if (upside) {
//arrow at topside
let popoverOutsideTargetHorizontal = 0;
DEBUG &&
console.log(
"popoverOutsideTargetHorizontal: " + popoverOutsideTargetHorizontal
);
arrowContainer.style.top =
scrollPosY + popoverRect.top - arrowWidth / 2 + "px";
rotateArrowInDegree =
pointDirection(
popupSourceX,
popoverRect.top + posY + arrowWidth,
targetXSourcePosition, //targetX+popoverOutsideTargetHorizontal
targetRect.top + arrowWidth
) - 45;
//width:100%;height:100%;
} else {
//arrow at bottom
arrowContainer.style.top =
scrollPosY + popoverRect.bottom - arrowWidth / 2 + "px";
rotateArrowInDegree =
pointDirection(
popupSourceX,
popoverRect.bottom + posY + halfWidth,
targetXSourcePosition,
targetRect.bottom + halfWidth
) - 45;
//width:100%;height:100%;
}
//#endregion vertical position over/under popup
//#endregion "under/over link layout"
}
let targetRotation = rotateArrowInDegree - 45;
// let targetSourceAbsoluteDiffY = sourcePosY - targetPosY;
let targetDiffX = popupSourceX - targetXSourcePosition;
DEBUG && console.log("isInChildContainer:" + isInChildContainer);
if (isInChildContainer) {
const nativRect = nativElement.getBoundingClientRect();
targetDiffX = popupSourceX - nativRect.right - halfArrowWidth;
DEBUG && console.log(nativRect);
}
DEBUG &&
console.log(
"popupSourceX: " +
popupSourceX +
", targetXSourcePosition: " +
targetXSourcePosition +
", targetDiffX: " +
targetDiffX
);
let containerOffsetX = 12; //12;
let offsetPointOnArrow = { x: 20, y: 40 + containerOffsetX };
let pivotOnArrow = { x: 20, y: 20 }; //svg viewbox height/width = 24
let newSourcePointXY = getRotatedPoint(
pivotOnArrow.x,
pivotOnArrow.y,
offsetPointOnArrow.x,
offsetPointOnArrow.y,
rotateArrowInDegree - 45
);
DEBUG && console.log("newSourcePointXY: " + newSourcePointXY);
DEBUG && console.log(newSourcePointXY);
let x1, y1, x2, y2;
let rectDistance = 0;
// if (targetRect.top < popoverRect.top)
{
rectDistance = targetRect.top - popoverRect.top;
}
let arrowOffsetY = targetRectTopDistance - targetElementDistanceY;
DEBUG &&
console.log(
"popoverRect.top: " +
popoverRect.top +
", targetRect.top: " +
targetRect.top +
", popoverRect.bottom: " +
popoverRect.bottom +
", targetRect.bottom: " +
targetRect.bottom +
", rectDistance: " +
rectDistance +
", targetDiffX: " +
targetDiffX +
", targetElementDistanceY: " +
targetElementDistanceY +
", popoverRect.height: " +
popoverRect.height +
", arrowContainerTopPositionLocal: " +
arrowContainerTopPositionLocal +
", arrowOffsetY: " +
arrowOffsetY +
", targetRectTopDistance: " +
targetRectTopDistance
);
const circleRadius = 1;
x1 = -targetDiffX + circleRadius; //-(targetDiffX-newSourcePointXY.x)-newSourcePointXY.x//targetRect.right; //0 - containerOffsetX;
y1 = arrowOffsetY + targetHalfHeight; //;//rectDistance; // + targetHalfHeight  //targetElementDistanceY  arrowContainerTopPositionLocal-popoverRect.top
DEBUG &&
console.log(
"y1: " +
y1 +
", rectDistance: " +
rectDistance +
", targetHalfHeight: " +
targetHalfHeight
);
//(targetRect.height/2) + targetElementDistanceY;//targetRect.top+targetRect.height/2;//targetSourceAbsoluteDiffY;  //
x2 = newSourcePointXY.x;
y2 = newSourcePointXY.y;
DEBUG &&
console.log(
"newSourcePointXY.x: " +
newSourcePointXY.x +
", newSourcePointXY.y: " +
newSourcePointXY.y +
", targetX: " +
x2 +
", targetY: " +
y2 +
", targetElementDistanceY: " +
targetElementDistanceY
);
let point =
'<circle cx="' +
newSourcePointXY.x +
'" cy="' +
newSourcePointXY.y +
'" r="' +
circleRadius +
'" stroke="red" fill="transparent" stroke-width="1"/>' +
'<circle cx="' +
x1 +
'" cy="' +
y1 +
'" r="' +
circleRadius +
'" stroke="green" fill="transparent" stroke-width="1"/>';
let line = //startY
//'<line x1="'+(x1-targetDiffX+arrowWidth-quarterArrowWidth)+'" y1="'+(y1-(arrowWidth-quarterArrowWidth))+'" x2="'+(x2-targetDiffX)+'" y2="'+(y2)+'" stroke="black" stroke-width="1"/></svg>';
'<line x1="' +
x1 +
'" y1="' +
y1 +
'" x2="' +
x2 +
'" y2="' +
y2 +
'" stroke="black" stroke-width="1"/>';
let additionalLineSVG = "";
if (isOnIndex || tableElement) {
additionalLineSVG =
'<div style="width:100%;height:100%;position:absolute;top:0;left:0;"><svg xmlns="http://www.w3.org/2000/svg" style="overflow:visible">' +
line +
point +
"</svg></div>";
}
let borderColor = "black";
if (isExternal) borderColor = "red";
arrowContainer.innerHTML =
'<div style="width:40px;height:40px;transform: rotate(' +
targetRotation +
'deg);padding:2px">' +
'<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-big-down" style="width:100%;height:100%;overflow:visible" viewBox="0 0 24 24" stroke-width="1" vector-effect="non-scaling-stroke" stroke="currentColor" fill="' +
popoverForegroundColor +
'" stroke-linecap="round" stroke-linejoin="round" transform="translate(0 ' +
containerOffsetX +
')">' +
//'<rect width="100%" height="100%" style="fill:rgb(100,155,255);stroke-width:1;stroke:rgb(0,0,0)" />' +
'<filter id="shadow"><feDropShadow dx="0.5" dy="-0.5" stdDeviation="0.8"/></filter>' +
'<circle cx="12" cy="12" r="13" stroke="' +
borderColor +
'" fill="' +
popoverBackgroundColor +
'" stroke-width="' +
popoverBorderWidth +
'" style="filter:url(#shadow);"/>' +
'<path d="M 15 0 v 15 h 3.586 a 1 1 0 0 1 0.707 1.707 l -6.586 6.586 a 1 1 0 0 1 -1.414 0 l -6.586 -6.586 a 1 1 0 0 1 0.707 -1.707 h 3.586 L 9 0 z" transform=""/>' +
"</svg></div>" +
additionalLineSVG;
DEBUG && console.log("scrollPosY: " + scrollPosY);
arrowContainer.style.pointerEvents = "none";
DEBUG && console.log("posY: " + posY);
// rotateArrowInDegree+=-135; //https://www.w3schools.com/howto/howto_css_arrows.asp
DEBUG && console.log("rotateArrowInDegree: " + rotateArrowInDegree);
DEBUG && console.groupEnd("showBorderArrowBox");
//arrowContainer.style.visibility = "visible";
//arrowContainer.classList.remove("hidePopover");
}
function getPopupPos(event, arrowWidth = 0, isExternal = false) {
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;
let targetHeight = defaultHeight;
if (showSmaller) targetHeight = smallHeight;
let targetWidth = targetHeight;
if (showDetails) targetWidth = targetHeight * 2;
//console.log("targetWidth: " + targetWidth)
//console.log(event)
popover.style.maxHeight = ""; //reset to max height
//popover.style.maxWidth = "";  //reset to max height
const popoverRect = popover.getBoundingClientRect();
const nativElement = event.target;
let targetElement = nativElement;
let tableElement = checkIsTableElement(nativElement);
//console.log("nativElement.classList")
//console.log(nativElement.classList)
//console.log(nativElement.className)
//console.log('nativElement.classList.contains("externalLink") ' + nativElement.className.split(" ").some(classname => classname.startsWith("externalL")))
//console.log('nativElement.className.includes("externalL") ' + nativElement.className.includes("externalL"))
//
const elementLinkHasNoText = targetElement.textContent.length == 0;
// || elementLinkHasNoText
if (isOnIndex || elementLinkHasNoText || tableElement) {
DEBUG &&
console.log(
"(isOnIndex || elementLinkHasNoText || tableElement) -> targetElement = nativElement.parentElement"
);
targetElement = nativElement.parentElement; //get container element/table cell
}
let X, Y;
DEBUG && console.group("getPopupPos");
DEBUG &&
console.log(
"elementLinkHasNoText: " +
elementLinkHasNoText +
", targetElement.textContent: " +
targetElement.textContent +
" targetElement.textContent.length: " +
targetElement.textContent.length
);
DEBUG && console.log(nativElement.parentElement);
DEBUG && console.log(nativElement.parentElement.tagName);
DEBUG && console.log(targetElement);
let targetRect = targetElement.getBoundingClientRect();
const targetLineRects = targetElement.getClientRects();
let firstLineRect, lastLineRect, firstRectIndex;
DEBUG && console.log("targetLineRects.length: " + targetLineRects.length);
for (let i = 0; i < targetLineRects.length; i++) {}
if (targetLineRects.length > 0) {
firstRectIndex = 0;
if (showIconNextToLink && targetLineRects.length > 1) {
firstRectIndex = 1; //TODO to double check if needed sometimes the pseudo element(state icon) seems to be counted in the DOMRectList
//chrome https://www.novelupdates.com/reading-list/?list=0
//vs https://forum.novelupdates.com/threads/chapter-numbering-error.119015/ pseudo element not counted?
}
//console.log(targetLineRects)
firstLineRect = targetLineRects[firstRectIndex];
lastLineRect = targetLineRects[targetLineRects.length - 1];
//TODO check if line.bottom is visible in container(edge case word wrapped link in container with overflow:hidden) else loop check previous line.
} else {
//case empty text link with only an image
firstLineRect = targetRect;
}
if (lastLineRect) targetRect = lastLineRect;
const Rx = targetRect.right;
const Ry = targetRect.bottom;
DEBUG && console.group("debug rects");
DEBUG && console.log("scrollPosX: " + scrollPosX);
DEBUG && console.log("scrollPosY: " + scrollPosY);
DEBUG &&
console.log(
"document.body.offsetHeight: " +
document.body.offsetHeight +
", document.body.scrollHeight: " +
document.body.scrollHeight
);
DEBUG && console.log("window.innerHeight: " + window.innerHeight);
DEBUG && console.log("window.innerWidth: " + window.innerWidth);
DEBUG && console.log(targetElement);
DEBUG && console.log(targetLineRects);
DEBUG && console.log(firstLineRect);
DEBUG && console.log(lastLineRect);
DEBUG && console.log("Rx: " + Rx + ", Ry: " + Ry);
DEBUG && console.groupEnd();
const nonOverlappingArrowWidth = (arrowWidth * 3) / 4;
const marginY = offsetToBottomBorderY + nonOverlappingArrowWidth;
let distanceToTopFromLink = firstLineRect.top - nonOverlappingArrowWidth;
let distanceToBottomFromLink =
window.innerHeight -
targetRect.bottom -
offsetToBottomBorderY +
nonOverlappingArrowWidth; //lastLineRect -> targetRect
let distanceToRightFromlink = window.innerWidth - targetRect.right;
let distanceToLeftFromlink = targetRect.left;
let arrowTableSideRight = true;
//reset width/height
//popover.style.height="100%";
if (supportsCSSMin) {
popover.style.height = "";
popover.style.width = "";
} else {
popover.style.height = targetHeight + "px";
popover.style.width = targetWidth + "px";
}
//#region set to top/bottom position
DEBUG && console.group("top/bottom placement");
let upside = false;
let targetYPosition = 0;
let maxHeight = targetHeight;
let maxHeightInsideAvailableSpace;
//#region  calculate maxHeight between targetHeight and image/popover height
popover.style.maxHeight = targetHeight + "px";
//setPopoverHeight(targetHeight,0);
if (popoverRect.height < targetHeight) {
maxHeight = popoverRect.height;
} else maxHeight = targetHeight;
//#endregion
maxHeightInsideAvailableSpace = maxHeight;
if (!(isOnIndex || tableElement)) {
//#region "not on index/table cell td"
DEBUG && console.log("not on index/table cell td");
/*
bottom bigger than top || bottomSpaceIsBiggerThanTargetHeight
-> bottom bigger than full height
-> bottom smaller than full height
bottom smaller than top
-> top bigger than full height
-> top smaller than full height
*/
const targetHeightMarginAdded = targetHeight + marginY;
console.log("targetHeightMarginAdded: " + targetHeightMarginAdded);
const bottomHasMoreSpaceThanTop =
distanceToBottomFromLink > distanceToTopFromLink;
const bottomSpaceIsBiggerThanTargetHeight =
distanceToBottomFromLink > targetHeight + marginY; //maxHeight;
DEBUG &&
console.log(
"bottomHasMoreSpaceThanTop: " +
bottomHasMoreSpaceThanTop +
", distanceToTopFromLink: " +
distanceToTopFromLink +
", distanceToBottomFromLink: " +
distanceToBottomFromLink
);
console.log(
"bottomSpaceIsBiggerThanTargetHeight: " +
bottomSpaceIsBiggerThanTargetHeight +
", targetHeight: " +
targetHeight +
", maxHeight: " +
maxHeight +
", popoverRect.height: " +
popoverRect.height +
", marginY: " +
marginY +
", distanceToBottomFromLink: " +
distanceToBottomFromLink +
", targetHeight + marginY: " +
targetHeightMarginAdded
);
if (bottomHasMoreSpaceThanTop || bottomSpaceIsBiggerThanTargetHeight) {
//arrow at top, popup under link
//#region bottom is bigger than top - show on bottom
upside = true;
DEBUG && console.group("bottom is bigger than top");
console.log(
"lastLineRect.bottom: " +
lastLineRect.bottom +
", nonOverlappingArrowWidth: " +
nonOverlappingArrowWidth
);
targetYPosition = lastLineRect.bottom + nonOverlappingArrowWidth;
// let targetYAdjustment;
if (!bottomSpaceIsBiggerThanTargetHeight) {
DEBUG &&
console.log(
"show reduced height. spacer under link is smaller than target height"
);
maxHeight = distanceToBottomFromLink - marginY; //targetHeight;
}
DEBUG && console.groupEnd();
//#endregion bottom is bigger than top - show on bottom
} else {
//arrow at bottom
//#region "bottom has less space than top" -> show on top
DEBUG && console.group("bottom has less space than top");
//"show full height BetweenFirstLineAndTop"
//targetYPosition = firstLineRect.top - maxHeight - halfArrowWidth;
//maxHeightInsideAvailableSpace = maxHeight;
console.log(popoverRect);
console.log(
"targetHeight: " +
targetHeight +
", popoverRect.height: " +
popoverRect.height +
", maxHeight without offset reduction: " +
maxHeight +
", distanceToTopFromLink: " +
distanceToTopFromLink +
", distanceToTopFromLink < maxHeight: " +
(distanceToTopFromLink < maxHeight) +
", marginY: " +
marginY
);
if (distanceToTopFromLink < maxHeight + marginY) {
//#region "available maxHeight is smaller than targeted maxHeight -show reduced height BetweenFirstLineAndTop"
console.log(
"maxHeight is smaller than distanceToTopFromLink -show reduced height BetweenFirstLineAndTop"
);
targetYPosition =
firstLineRect.top - maxHeight - nonOverlappingArrowWidth;
maxHeightInsideAvailableSpace = distanceToTopFromLink; //reduced maxHeight
console.log(
"maxHeightReducedToAvailableSpace?: " +
maxHeightInsideAvailableSpace +
", targetYPosition: " +
targetYPosition
);
console.log(
"targetYPosition: " + targetYPosition + ", marginY: " + marginY
);
if (targetYPosition < marginY) {
//if targetYPosition(firstLineRect.top - maxHeight - marginY;) smaller marginY
console.log(
"reposition smaller than marginY. Set at offset marginY"
);
DEBUG &&
console.log(
"targetheight overflows at top side, maxHeight before margin change: " +
maxHeight +
", new maxHeightInsideAvailableSpace: " +
maxHeightInsideAvailableSpace
);
const magicNumber = 3; //weird. container position relative top=-3 why needed?
targetYPosition =
offsetToBottomBorderY - magicNumber + nonOverlappingArrowWidth;
maxHeightInsideAvailableSpace = distanceToTopFromLink - marginY;
}
//#endregion "maxHeight is smaller than distanceToTopFromLink -show reduced height BetweenFirstLineAndTop"
} else {
//#region "show full height BetweenFirstLineAndTop"
DEBUG && console.log("show full height BetweenFirstLineAndTop");
DEBUG &&
console.log(
"maxHeight: " +
maxHeight +
", nonOverlappingArrowWidth: " +
nonOverlappingArrowWidth
);
const magicNumber = 3;
maxHeightInsideAvailableSpace = maxHeight + nonOverlappingArrowWidth;
targetYPosition =
firstLineRect.top - maxHeightInsideAvailableSpace - magicNumber;
//#endregion "show full height BetweenFirstLineAndTop"
}
DEBUG &&
console.log(
"targetYPosition: " +
targetYPosition +
", maxHeightInsideAvailableSpace: " +
maxHeightInsideAvailableSpace +
", distanceToTopFromLink: " +
distanceToTopFromLink +
", targetHeight: " +
targetHeight +
", firstLineRect.top: " +
firstLineRect.top +
", scrollPosY: " +
scrollPosY +
",  popoverRect.height: " +
popoverRect.height
);
DEBUG && console.groupEnd();
//#endregion "bottom has less space than top"
}
//#endregion "not on index/table cell td"
} else {
//#region "on index/table cell td"
DEBUG && console.log("on index/table cell td");
//auto height push up along table column
DEBUG &&
console.log(
", distanceToBottomFromLink: " +
distanceToBottomFromLink +
", targetHeight: " +
targetHeight
);
targetYPosition = lastLineRect.top;
const overFlowPxY = maxHeight - distanceToBottomFromLink;
if (overFlowPxY > 0) {
DEBUG &&
console.log("space under link smaller targetHeight. move popover up");
DEBUG &&
console.log("lastLineRect.top: " + lastLineRect.top + ", Y" + Y);
targetYPosition -= overFlowPxY;
}
const posOverTopBorder = targetYPosition < offsetToBottomBorderY;
if (posOverTopBorder) {
//top offset
DEBUG &&
console.log("overflows top border set to offsetToBottomBorderY");
targetYPosition = offsetToBottomBorderY;
}
//#endregion "on index/table cell td"
}
//Y += scrollPosY;
DEBUG &&
console.log(
"maxHeight: " + maxHeight + ", targetYPosition: " + targetYPosition
);
setPopoverHeight(maxHeightInsideAvailableSpace, targetYPosition);
Y = targetYPosition + scrollPosY;
DEBUG && console.log("popover.style.height: " + popover.style.height);
DEBUG && console.groupEnd();
//#endregion set to top/bottom position
//#region select from which line to calculate the  right position
if (!upside) {
targetRect = firstLineRect;
distanceToRightFromlink = window.innerWidth - targetRect.right;
}
//#endregion
//#region set to left/right position
DEBUG && console.group("left/right placement");
if (!(isOnIndex || tableElement)) {
DEBUG &&
console.log(
"placement on non table cell/container list. non index list"
);
X = targetRect.right;
DEBUG &&
console.log(
"full calculated width for non reposition: " +
(X + distanceToRightFromlink + offsetToRightBorderX)
);
DEBUG &&
console.log(
"lastLineRect.right: " +
targetRect.right +
", distanceToRightFromlink: " +
distanceToRightFromlink +
", targetWidth: " +
targetWidth
);
if (distanceToRightFromlink - offsetToRightBorderX * 2 > targetWidth) {
DEBUG &&
console.log(
"full popup width with padding without reposition: " + targetWidth
);
} else {
let diffX =
targetWidth - distanceToRightFromlink + offsetToRightBorderX * 2;
DEBUG &&
console.log("touch right side, move left of amount diffX: " + diffX);
//move left amount diffX
X -= diffX;
if (X + targetWidth < targetRect.left) {
X = targetRect.left - targetWidth;
}
}
} else {
DEBUG && console.log("on Index/tablecell");
distanceToRightFromlink = distanceToRightFromlink - arrowWidth;
X = targetRect.right;
const rightBiggerThenLeft =
distanceToLeftFromlink < distanceToRightFromlink;
const containerWidthWithPadding =
targetWidth + arrowWidth + offsetToRightBorderX;
DEBUG &&
console.log(
"distanceToRightFromlink: " +
distanceToRightFromlink +
", containerWidthWithPadding: " +
containerWidthWithPadding
);
if (
rightBiggerThenLeft ||
distanceToRightFromlink > containerWidthWithPadding
) {
DEBUG &&
console.log(
"right side space bigger than left side. distanceToRightFromlink: " +
distanceToRightFromlink
);
const availableSpaceRight =
distanceToRightFromlink + offsetToRightBorderX * 2;
DEBUG &&
console.log(
"targetWidth: " +
targetWidth +
", containerWidthWithPadding: " +
containerWidthWithPadding
);
if (availableSpaceRight < containerWidthWithPadding) {
DEBUG &&
console.log(
"right space smaller than targetWidth. distanceToRightFromlink: " +
distanceToRightFromlink
);
//X = lastLineRect.right;
popover.style.width =
distanceToRightFromlink - offsetToRightBorderX * 2 + "px";
} else {
DEBUG &&
console.log(
"right space bigger than targetWidth+offset" +
", distanceToRightFromlink: " +
distanceToRightFromlink +
", offsetToRightBorderX: " +
offsetToRightBorderX +
", availableSpaceRight : " +
availableSpaceRight +
", containerWidthWithPadding: " +
containerWidthWithPadding
);
popover.style.width = targetWidth + "px";
// X = lastLineRect.right;
}
} else {
DEBUG && console.log("right side smaller than left side");
X = targetRect.left - containerWidthWithPadding;
arrowTableSideRight = false;
if (distanceToLeftFromlink > containerWidthWithPadding) {
DEBUG && console.log("move to left side of container");
} else {
DEBUG && console.log("shrink popupwidth");
if (X < offsetToRightBorderX) {
X = offsetToRightBorderX;
popover.style.width =
distanceToLeftFromlink -
offsetToRightBorderX * 2 -
arrowWidth +
"px";
}
}
//X = targetRect.left-;
}
}
DEBUG &&
console.log("X: " + X + ", popover.style.width: " + popover.style.width);
DEBUG && console.groupEnd();
//#endregion
DEBUG && console.groupEnd();
//let tableMargin=0;
//if(isOnIndex || tableElement)
//tableMargin = arrowWidth;
//popover.style.top = Y + "px";
// popover.style.left = X +tableMargin+ "px";
/*
showBorderArrowBox(
event,
arrowWidth,
upside,
isExternal,
arrowTableSideRight
);*/
return {
Px: X,
Py: Y,
upside: upside,
arrowTableSideRight: arrowTableSideRight,
};
}
function sleep() {
return new Promise(requestAnimationFrame);
}
// popupPositioning function
async function popupPos(event, isExternal = false) {
const DEBUG = false;
DEBUG && console.group("popupPos showSmaller:" + showSmaller);
if (event && event !== undefined) {
showPopOver();
await sleep();
await sleep();
DEBUG && console.group("popover rect");
DEBUG && console.log(popover);
DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
DEBUG && console.log("popover[0].offsetHeight: " + popover.offsetHeight);
DEBUG && console.groupEnd("popover rect");
const { Px, Py, upside, arrowTableSideRight } = getPopupPos(
event,
arrowWidthInPx,
isExternal
);
let tableMargin = 0;
let tableElement = checkIsTableElement(event.target);
if (isOnIndex || tableElement) tableMargin = arrowWidthInPx;
popover.style.left = Px + tableMargin + "px";
if (!upside) {
//popover.style.bottom = "unset";
popover.style.top = Py + "px";
} else {
//popover.style.bottom="unset";
popover.style.top = Py + "px";
}
showBorderArrowBox(
event,
arrowWidthInPx,
upside,
isExternal,
arrowTableSideRight
);
//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
);
DEBUG && console.groupEnd("popupPos");
//console.log("final popup position "+X+' # '+Y);
// return this;
autoScrollData();
autoScrollData("coverPreviewContentAutoScroll");
}
}
function tryToGetTextContent(element, query, queryName) {
let r###lt = element;
if (r###lt && r###lt !== undefined) {
r###lt = r###lt.innerHTML; //changed from textContent to innerHTML to get html tags(b/i/p/br) and text line breaks
r###lt = r###lt.replace(reWhiteListStripHTML, ""); //strip all other html tags
} else if (element !== null && element !== undefined) {
console.log(
"Wrong or changed querySelector for " + queryName + ". not: " + query
);
}
return r###lt;
}
function getTargetDomain(individualSiteLink) {
let domain = "";
if (individualSiteLink) {
//console.log(individualSiteLink);
let hasSlashIndex = individualSiteLink.indexOf("/");
if (hasSlashIndex) hasSlashIndex = hasSlashIndex + 1;
else hasSlashIndex = individualSiteLink.length - 1;
domain = individualSiteLink.slice(0, hasSlashIndex);
}
return domain;
}
function getLinkID(link, individualPage) {
const DEBUG = false;
let ID, domainInfrontOfID;
let stringFromID;
let boolHasSlashAfterID = false;
if (link && individualPage) {
//example individualPage = "mangadex.org/title/"
DEBUG && console.group("getLinkID");
DEBUG && console.log(link);
const isApiLink = link.indexOf(individualPage);
let linkAfterDomain;
if (isApiLink >= 0) {
let indexID = isApiLink + individualPage.length;
domainInfrontOfID = link.slice(0, indexID);
linkAfterDomain = link.slice(indexID);
DEBUG && console.log("linkAfterDomain: " + linkAfterDomain);
if (individualPage && externalLinks[individualPage]) {
DEBUG &&
console.log(
"externalLink: " +
individualPage +
", match with linkAfterDomain to get full ID: " +
linkAfterDomain
);
//externalLinks
if (externalLinks[individualPage].serieRegex) {
stringFromID = linkAfterDomain.match(
externalLinks[individualPage].serieRegex
);
}
DEBUG && console.log(stringFromID);
DEBUG && console.log("stringFromID: " + stringFromID);
if (stringFromID === null) {
if (externalLinks[individualPage].serieRegex2) {
DEBUG &&
console.log("getLinkID serieRegex2 for: " + individualPage);
stringFromID = linkAfterDomain.match(
externalLinks[individualPage].serieRegex2
);
} else {
stringFromID = linkAfterDomain.match(defaultSerieRegex);
}
}
//stringFromID = link.slice(indexID);
} else {
DEBUG &&
console.log(
"internallink, match with linkAfterDomain to get full ID: " +
linkAfterDomain
);
//internalLink
stringFromID = linkAfterDomain.match(
internalLink[INDIVIDUALPAGETEST].serieRegex
);
//stringFromID = link.slice(indexID);
}
DEBUG &&
console.log(
"domainInfrontOfID: " +
domainInfrontOfID +
", stringFromID: " +
stringFromID
);
if (stringFromID.length > 0) {
if (stringFromID[1]) {
stringFromID = stringFromID[1]; //exact match
} else {
stringFromID = stringFromID[0]; //complete matching string after domain
}
}
DEBUG && console.log(stringFromID);
DEBUG && console.log(stringFromID);
/*
let hasSlashAfterID = stringFromID.indexOf("/");
boolHasSlashAfterID = hasSlashAfterID>=0;
if (boolHasSlashAfterID) ID = stringFromID.slice(0, hasSlashAfterID);
else */
//ID = stringFromID;
//DEBUG && console.log(ID);
}
DEBUG && console.groupEnd();
}
return { ID: stringFromID, Domain: domainInfrontOfID }; //,hasSlashAfterID:boolHasSlashAfterID
}
async function getCoverDataFromUrl(
elementUrl,
external = false,
individualPage = undefined
) {
const DEBUG = false;
DEBUG && console.group("getCoverDataFromUrl for: " + currentTitelHover);
const isExternal = external;
let coverData;
let linkID;
let hasApiAccess;
if (isExternal) hasApiAccess = externalLinks[individualPage].mainAPI;
else {
hasApiAccess = internalLink[INDIVIDUALPAGETEST].mainAPI; //todo change with individualPage. Forward individualPage for internal site
}
//console.log(hasApiAccess);
//console.log("xhr.finalUrl: " + xhr.finalUrl +", elementUrl: " + elementUrl)
DEBUG && console.log("elementUrl: " + elementUrl);
elementUrl = getLinkToSeriePage(elementUrl, individualPage);
DEBUG && console.log("getLinkToSeriePage() -> elementUrl: " + elementUrl);
if (hasApiAccess) {
const { ID } = getLinkID(elementUrl, individualPage);
linkID = ID;
//console.log(linkID);
DEBUG && console.log("linkID: " + linkID);
}
DEBUG && console.log("elementUrl: " + elementUrl);
let targetDomain;
let hasExternalTargetPage;
let targetPage;
//#region check is external link
DEBUG &&
console.log(
"isExternal: " + isExternal + ", individualPage: " + individualPage
);
if (isExternal && individualPage) {
hasExternalTargetPage = externalLinks[individualPage];
if (hasExternalTargetPage) {
targetPage = hasExternalTargetPage;
targetDomain = getTargetDomain(individualPage);
}
} else {
targetPage = internalLink[internalLinkKey[0]];
//targetDomain = getTargetDomain(INDIVIDUALPAGETEST); //for now leave empty (since only one internal link possible)
}
DEBUG &&
console.log(
"hasExternalTargetPage: " +
hasExternalTargetPage +
", targetDomain: " +
targetDomain
);
//#endregion
let apiData;
if (hasApiAccess) {
DEBUG &&
console.log("hasApiAccess for individualPage: " + individualPage);
switch (individualPage) {
//API access
case "mangadex.org/manga/":
case "mangadex.org/title/":
//url: "https://mangadex.org/api/v2/manga/" + id,
apiData = await getCoverDataFromMangaDex(linkID);
break;
case "www.tvmaze.com/shows/":
apiData = await getCoverDataFromTVmaze(linkID);
break;
case "wlnupdates.com/series-id/":
apiData = await getCoverDataFromWLNupdates(linkID);
break;
}
} else {
DEBUG &&
console.log(
"before getCoverDataFromParsingTargetUrl - elementUrl: " + elementUrl
);
//coverData = Object.assign({}, emptyCoverData);
apiData = await getCoverDataFromParsingTargetUrl(
elementUrl,
targetPage,
isExternal
);
DEBUG && console.log(apiData);
}
DEBUG && console.log(apiData);
let imagelink;
if (apiData !== undefined) {
coverData = apiData;
imagelink = coverData.url;
}
//console.log(imagelink);
//console.log("save imageUrl into coverData.url: " + imagelink);
let externalUrl;
//console.log("targetDomain: " + targetDomain)
if (isExternal && targetDomain) externalUrl = targetDomain;
imagelink = processRelativeImageLink(imagelink, targetDomain, isExternal);
//console.log("serieAlternativeNames: " + serieAlternativeNames)
let cData;
DEBUG && console.log("externalUrl: " + externalUrl);
DEBUG && console.log(coverData);
//#region merge final complete coverData
if (coverData !== undefined) {
cData = coverData;
cData.isExternal = externalUrl;
cData.url = imagelink;
//console.log(coverData);
//console.log(cData);
}
//#endregion merge final complete coverData
DEBUG && console.log(cData);
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 cData;
}
function processRelativeImageLink(imagelink, targetDomain, isExternal) {
const DEBUG = false;
//#region adjust relative link to absolute domain
if (imagelink && imagelink !== undefined && imagelink !== null) {
if (imagelink.tagName && imagelink.tagName == "IMG") {
imagelink = imagelink.getAttribute("src");
}
if (imagelink instanceof HTMLElement) {
let hasSrc = imagelink.getAttribute("src");
if (hasSrc) imagelink = imagelink.getAttribute("src");
}
DEBUG && console.log(imagelink);
if (imagelink.startsWith("//")) {
imagelink = "https://" + imagelink.slice(2);
}
if (imagelink.startsWith("/")) {
DEBUG && console.log(targetDomain);
DEBUG && console.log(imagelink);
imagelink = targetDomain + imagelink;
}
if (
isExternal &&
!(imagelink.startsWith("http://") || imagelink.startsWith("https://"))
) {
//if relativeLink on external site change to absolute link
imagelink = "https://" + imagelink;
}
}
//#endregion
return imagelink;
}
function rejectErrorStatusMessage(xhr) {
let rejectNotification = "";
//https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
switch (true) {
case xhr.status == 400:
rejectNotification = "bad request: xhr response == " + xhr.status;
break;
case xhr.status == 401:
rejectNotification = "unauthorized: xhr response == " + xhr.status;
break;
case xhr.status == 402:
rejectNotification = "Payment Required: xhr response == " + xhr.status;
break;
case xhr.status == 403:
rejectNotification = "Forbidden: xhr response == " + xhr.status;
break;
case xhr.status == 404:
rejectNotification =
xhr.finalUrl + "<br/>page not found: xhr response == " + xhr.status;
break;
case xhr.status == 408:
rejectNotification = "request timeout: xhr response == " + xhr.status;
break;
case xhr.status == 410:
rejectNotification =
"page gone and not available: xhr response == " + xhr.status;
break;
case xhr.status == 425:
rejectNotification =
"page request too early: xhr response == " + xhr.status;
break;
case xhr.status == 429:
rejectNotification =
"rate limit reached. Please notify to increase/adjust rate limit to setting for this domain: xhr response == " +
xhr.status;
break;
case xhr.status == 451:
rejectNotification =
"page was removed cause of legal reasons: xhr response == " +
xhr.status;
break;
case xhr.status == 500:
{
console.log(xhr.responseHeaders);
let serverMessage = xhr.responseHeaders
.toString()
.match(new RegExp("server:(.*)"));
if (serverMessage != null)
rejectNotification =
"page has an internal server error: xhr response == " +
xhr.status +
"<br />" +
serverMessage;
}
break;
case xhr.status == 503:
rejectNotification =
"page Unavailable. Down for maintenance or overloaded: xhr response == " +
xhr.status;
break;
case xhr.status == 511:
rejectNotification =
"page Network Authentication Required: xhr response == " + xhr.status;
break;
default:
rejectNotification = xhr;
}
return rejectNotification;
}
function setLinkState(element, state = undefined, preloadUrlRequest = false) {
const DEBUG = false;
if (element) {
let hasText = element.textContent != "";
if (showIconNextToLink) {
/*  0: popup possible/no coverdata preloaded; if preloadUrlRequest true set inactive preloading icon
1: currently active loading coverData link
2: coverData preloaded
*/
const externalTarget = element.getAttribute("coverDataExternalTarget");
let elementUrl = getLinkToSeriePage(element.href, externalTarget);
DEBUG && console.group("link: " + elementUrl);
DEBUG && console.log("state before check: " + state);
if (state === undefined) {
const coverData = GM_getCachedValue(elementUrl);
DEBUG && console.log(coverData);
if (
coverData === undefined ||
coverData === null ||
coverData === "null" ||
preloadUrlRequest
) {
state = linkIconEnum.popupPossibleNotLoadedOrMarkedForPreloading; //no coverData wating for interaction or forced reloading/preloading
} else {
state = linkIconEnum.popupHasCoverData; //coverData available and loaded
}
}
DEBUG && console.log("state: " + state);
DEBUG && console.groupEnd();
element.classList.remove(
"hasCoverPreviewPopup",
"loadingUrlPreload",
"loadingUrl",
"hasLoadedCoverPreviewPopup"
);
switch (state) {
case linkIconEnum.popupPossibleNotLoadedOrMarkedForPreloading: //popup possible/no coverdata preloaded; if preloadUrlRequest true set inactive preloading icon
if (hasText) {
if (preloadUrlRequest) {
element.classList.add("loadingUrlPreload");
} else {
element.classList.add("hasCoverPreviewPopup");
}
}
break;
case linkIconEnum.popupLoading: //currently loading coverData
if (hasText) element.classList.add("loadingUrl");
break;
case linkIconEnum.popupHasCoverData: //coverData preloaded
if (hasText) element.classList.add("hasLoadedCoverPreviewPopup");
break;
case linkIconEnum.error:
//don't add any icon
//console.log("set state to linkError")
break;
}
} else {
//if not showIconNextToLink -> cleanup icons
element.classList.remove(
"hasCoverPreviewPopup",
"loadingUrlPreload",
"loadingUrl",
"hasLoadedCoverPreviewPopup"
);
}
}
}
function setLinkStateOfSameLinks(element, state, preloadUrlRequest = false) {
const DEBUG = false;
const elementUrl = element.href;
//console.log("elementUrl: " + elementUrl)
//console.log(elementUrl)
const sameLinks = document.querySelectorAll('a[href="' + elementUrl + '"]');
DEBUG && console.group("setLinkStateOfSameLinks: " + elementUrl);
DEBUG && console.log(sameLinks);
DEBUG && console.log("sameLinks.length: " + sameLinks.length);
DEBUG && console.groupEnd();
if (sameLinks.length > 0) {
for (let i = 0; i < sameLinks.length; i++) {
DEBUG && console.log(sameLinks[i]);
setLinkState(sameLinks[i], state, preloadUrlRequest);
}
}
}
function getLinkToSeriePage(elementUrl, individualPage = undefined) {
const DEBUG = false;
if (individualPage) {
let linkID;
const { ID, Domain } = getLinkID(elementUrl, individualPage);
linkID = ID;
//console.log(linkID);
elementUrl = Domain + linkID; //in case of direct chapter link provided with linkIK
DEBUG &&
console.log(
"new elementUrl: " +
elementUrl +
", individualPage: " +
individualPage +
", linkID: " +
linkID +
", Domain: " +
Domain
);
}
return elementUrl;
}
async function parseSeriePage(
element,
forceReload = false,
hoveredTitle = undefined,
event = undefined,
external = false,
targetPage = undefined
) {
const DEBUG = false;
DEBUG &&
console.log("before getLinkToSeriePage - element.href: " + element.href);
const elementUrl = getLinkToSeriePage(element.href, targetPage);
DEBUG && console.group("parseSeriePage: " + elementUrl);
DEBUG && console.log("elementUrl: " + elementUrl);
let coverData;
if (!forceReload) coverData = GM_getCachedValue(elementUrl);
// let retrievedImgLink;
let PromiseR###lt;
DEBUG && console.log(coverData);
if (
!forceReload &&
coverData !== undefined &&
coverData !== null &&
coverData.title
) {
//retrievedImgLink = coverData.url;
DEBUG &&
console.log(
"parseSeriePage has cached coverData  for: " +
coverData.title +
" at " +
elementUrl
);
PromiseR###lt = coverData;
} else {
//console.log("before showPopupLoadingSpinner")
showPopupLoadingSpinner(hoveredTitle, hoveredTitle, event);
//console.log("before getCoverDataFromUrl - elementUrl: " + elementUrl)
PromiseR###lt = getCoverDataFromUrl(elementUrl, external, targetPage);
}
DEBUG && console.groupEnd("parseSeriePage: " + elementUrl);
PromiseR###lt = await PromiseR###lt;
setLinkStateOfSameLinks(element, linkIconEnum.popupHasCoverData); //coverData loading finished
DEBUG && console.log(PromiseR###lt);
//DEBUG && console.log(PromiseR###lt)
//after GM_xmlhttpRequest PromiseR###lt
return PromiseR###lt;
}
//renamed targetNodeArray -> arrayTargetNode to remove compatibility mode in tampermonkey
function removeEventListenerFromNodes(arrayTargetNode, external = false) {
if (arrayTargetNode && arrayTargetNode.length > 0) {
//console.log(targetNodeArray);
arrayTargetNode.map(function (el) {
if (
eventListenerStyle === undefined ||
eventListenerStyle === null ||
eventListenerStyle == 0
) {
el.removeEventListener("mouseenter", mouseEnterPopup);
el.removeEventListener("mouseleave", hideOnMouseLeave);
}
});
}
}
function wait(ms) {
return new Promise((resolve, reject) => setTimeout(resolve, ms));
}
//renamed targetNodeArray -> arrayTargetNode to remove compatibility setting in tampermonkey
async function preloadForIndividualPageTest(
arrayTargetNode = [],
individualLinksToTest,
external = false,
forceReload = false
) {
const DEBUG = false;
DEBUG && console.log(arrayTargetNode);
DEBUG && console.log("preloadCoverData");
DEBUG &&
console.log(
"before parseSeriePage for each url with a link to individual seriepage"
);
//#region addEventlistener
if (arrayTargetNode && arrayTargetNode.length > 0) {
//console.log(targetNodeArray);
arrayTargetNode.map(function (el) {
//console.log(el)
// const elementUrl = el.href;
//const externalTarget = el.getAttribute("coverDataExternalTarget");
// console.log(elementUrl)
if (
eventListenerStyle === undefined ||
eventListenerStyle === null ||
eventListenerStyle == 0
) {
//console.log(el); //TODO external overwrite/removes previous mouseEnterPopup?
el.addEventListener("mouseenter", mouseEnterPopup);
el.addEventListener("mouseleave", hideOnMouseLeave);
/* if (external)
el.setAttribute("coverDataExternalTarget", IndividualTargetToTest);*/
}
});
}
//#endregion addEventlistener
//#region preload
if (arrayTargetNode && arrayTargetNode.length > 0) {
let nodeArrayIndex = 0;
//rateLimitQueryAfterSeconds:"0.5"
//if not set, but both other values are available will be calculated into rateLimitTimeInSeconds/rateLimitCount
let hasRateLimitQueryAfterSeconds,
hasRateLimitTimeInSeconds,
hasRateLimitCount,
hasRateLimitQueryAfterMS;
if (external) {
//externalLinks[individualLinksToTest]["lastQuery"] = Date.now();
hasRateLimitQueryAfterSeconds =
externalLinks[individualLinksToTest].rateLimitQueryAfterSeconds;
if (!hasRateLimitQueryAfterSeconds) {
hasRateLimitTimeInSeconds =
externalLinks[individualLinksToTest].rateLimitTimeInSeconds;
hasRateLimitCount =
externalLinks[individualLinksToTest].rateLimitCount;
if (hasRateLimitTimeInSeconds && hasRateLimitCount) {
hasRateLimitQueryAfterSeconds =
hasRateLimitTimeInSeconds / hasRateLimitCount;
} else {
hasRateLimitQueryAfterSeconds = defaultRateLimitQueryAfterSeconds;
}
}
} else {
hasRateLimitQueryAfterSeconds =
internalLink[individualLinksToTest].rateLimitQueryAfterSeconds;
if (!hasRateLimitQueryAfterSeconds) {
hasRateLimitTimeInSeconds =
internalLink[individualLinksToTest].rateLimitTimeInSeconds;
hasRateLimitCount =
internalLink[individualLinksToTest].rateLimitCount;
if (hasRateLimitTimeInSeconds && hasRateLimitCount) {
hasRateLimitQueryAfterSeconds =
hasRateLimitTimeInSeconds / hasRateLimitCount;
} else {
hasRateLimitQueryAfterSeconds = defaultRateLimitQueryAfterSeconds;
}
}
}
if (hasRateLimitQueryAfterSeconds) {
hasRateLimitQueryAfterMS = hasRateLimitQueryAfterSeconds * 1000;
}
DEBUG &&
console.log("hasRateLimitQueryAfterMS: " + hasRateLimitQueryAfterMS);
if (
arrayTargetNode.length > 0 &&
(forceReload || preloadUrlRequests) &&
!deactivatePreloadUrlRequestOnUrls.includes(individualLinksToTest)
) {
while (nodeArrayIndex < arrayTargetNode.length) {
const element = arrayTargetNode[nodeArrayIndex];
const elementUrl = getLinkToSeriePage(
element.href,
individualLinksToTest
);
DEBUG && console.log("elementUrl: " + elementUrl);
DEBUG &&
console.log(
"start parseSeriePage for links of domain: " +
individualLinksToTest
);
let targetPage = undefined;
if (external) {
targetPage = individualLinksToTest;
//console.log("targetPage: " + targetPage);
}
// console.log("external: " + external);
const hoveredTitle = undefined;
const event = undefined;
const coverData = GM_getCachedValue(elementUrl);
DEBUG &&
console.group(
"preloadForIndividualPageTest GM_getCachedValue elementUrl: " +
elementUrl
);
DEBUG && console.log("elementUrl: " + elementUrl);
DEBUG && console.log(coverData);
DEBUG && console.groupEnd();
/*
if (!(coverData!==undefined && coverData !== null && coverData != "null"))
{//has coverData
setLinkState(element, 0, forceReload);//set inactive preloadingMarker or preloaded Data
}else{
setLinkState(element, 1, forceReload); //active loading icon
}*/
setLinkState(element, linkIconEnum.popupLoading, forceReload); //active loading icon
const promiseParsingPage = parseSeriePage(
element,
forceReload,
hoveredTitle,
event,
external,
targetPage
).then(
function (coverData) {
if (coverData !== undefined) {
if (preloadImages) {
DEBUG &&
console.log(
"preloadCoverData preloadImages: " + preloadImages
);
DEBUG && console.log(coverData);
loadImageFromBrowser({ coverData: coverData });
}
}
},
function (Error) {
DEBUG && console.log(Error + " failed to fetch " + element);
GM_deleteValue(element.href);
setLinkStateOfSameLinks(element, linkIconEnum.error);
}
);
if (hasRateLimitQueryAfterMS) {
// const coverData = GM_getCachedValue(elementUrl);
//console.log("preloaded url: " + elementUrl);
if (
!(
coverData !== undefined &&
coverData !== null &&
coverData != "null"
)
) {
await Promise.all([
promiseParsingPage,
wait(hasRateLimitQueryAfterMS),
]);
} else {
setLinkState(element, undefined, forceReload); //coverData already loaded
}
}
nodeArrayIndex++;
}
}
}
}
async function preloadCoverData(forceReload = false) {
const DEBUG = false;
//#region create complete nodelist
ALLSERIENODES = [];
ALLEXTERNALLINKNODES = [];
let allPreloadingPromises = [];
//console.log("preloadCoverData forceReload: " + forceReload)
updateSerieNodes(ALLSERIENODES, INDIVIDUALPAGETEST, forceReload);
if (externalLinks && externalLinkKeys.length > 0) {
for (let i = 0; i < externalLinkKeys.length; i++) {
updateSerieNodes(
ALLEXTERNALLINKNODES,
externalLinkKeys[i],
forceReload,
true
);
}
}
//#endregion
//#region add eventlistener mouseenter/leave to links and preloadCoverData if preloading set to true
removeEventListenerFromNodes(ALLSERIENODES);
allPreloadingPromises.push(
preloadForIndividualPageTest(
ALLSERIENODES,
INDIVIDUALPAGETEST,
false,
forceReload
)
);
//console.log(externalLinks);
//console.log(externalLinkKeys[0]);
//console.log(externalLinkKeys.length);
removeEventListenerFromNodes(ALLEXTERNALLINKNODES);
//console.log(ALLEXTERNALLINKNODES)
if (ALLEXTERNALLINKNODES.length > 0) {
if (externalLinks && externalLinkKeys.length > 0) {
for (let i = 0; i < externalLinkKeys.length; i++) {
allPreloadingPromises.push(
preloadForIndividualPageTest(
ALLEXTERNALLINKNODES.filter((link) =>
link.href.includes(externalLinkKeys[i])
),
externalLinkKeys[i],
true,
forceReload
)
);
}
}
}
//#endregion
if (forceReload) {
await Promise.all(allPreloadingPromises);
console.log(
"has finished preloading all links on this page: " +
window.location.href
);
}
}
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;
}
#popover_arrow{
margin: unset;
padding: unset;
text-size-adjust: unset;
line-height: unset;
font-family: unset;
font: unset;
opacity: 1;
visibility:visible;
transition: visibility 0.2s, opacity 0.2s linear;
}
.arrowCSS{
box-sizing: border-box;
border: 1px solid #000;
box-shadow:1px 1px 0px #7A7A7A;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 0;
margin: unset;
text-size-adjust: unset;
line-height: unset;
font-family: unset;
font: unset;
}
@keyframes dotsLoading{
0%{
opacity: 0;
}
50%{
opacity: 1;
}
100%{
opacity: 0;
}
}
#dotLoading1{
animation: dotsLoading 1s infinite;
}
#dotLoading2{
animation: dotsLoading 1s infinite;
animation-delay: 0.2s;
}
#dotLoading3{
animation: dotsLoading 1s infinite;
animation-delay: 0.4s;
}
.loadingUrl, .loadingUrlPreload, .hasCoverPreviewPopup, .hasLoadedCoverPreviewPopup{
/* position:relative;
display: inline;*/
padding:0 !important;
margin:0 !important;
}
.loadingUrl:link, .loadingUrlPreload:link, .hasCoverPreviewPopup:link, .hasLoadedCoverPreviewPopup:link{
padding:0 !important;
margin:0 !important;
}
.loadingUrl::before{
content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message-dots" width="14" height="14" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M0 0h24v24H0z" stroke="none" fill="none"/><path stroke="red" d="M4 21v-13a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v6a3 3 0 0 1 -3 3h-9l-4 4" /><line x1="12" y1="11" x2="12" y2="11.01" stroke="red" fill="red" id="dotLoading1" /><line x1="8" y1="11" x2="8" y2="11.01" stroke="red" fill="red" id="dotLoading2" /><line x1="16" y1="11" x2="16" y2="11.01" stroke="red" fill="red" id="dotLoading3" /></svg>');
}
.loadingUrlPreload::before{
content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message-dots" width="14" height="14" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 21v-13a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v6a3 3 0 0 1 -3 3h-9l-4 4"/><line x1="12" y1="11" x2="12" y2="11.01" stroke="red" fill="red" id="dotLoading1" /><line x1="8" y1="11" x2="8" y2="11.01" stroke="red" fill="red" id="dotLoading2" /><line x1="16" y1="11" x2="16" y2="11.01" stroke="red" fill="red" id="dotLoading3" /></svg>');
}
/* https://tablericons.com/ "message" without newlines set width/height to 12px; removed two lines to get empty popup */
.hasCoverPreviewPopup::before{
content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message" width="12px" height="12px" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 21v-13a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v6a3 3 0 0 1 -3 3h-9l-4 4" /></svg>');
}
/* https://tablericons.com/ "message" without newlines set width/height to 12px */
.hasLoadedCoverPreviewPopup::before{
content:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-message" width="12px" height="12px" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 21v-13a3 3 0 0 1 3 -3h10a3 3 0 0 1 3 3v6a3 3 0 0 1 -3 3h-9l-4 4" /><line x1="8" y1="9" x2="16" y2="9" /><line x1="8" y1="13" x2="14" y2="13" /></svg>');
}
.blackFont {
color:#000;
}
.whiteFont {
color:#fff
}
.defaultTitleStyle {
box-sizing: border-box;
padding:5px 8px;
min-height:unset;
height:auto;
display:inline-block;
width:100%;
/*max-width:auto;*/
text-align:center !important;
justify-content: center;
justify-items: center;
border: 0 !important;
border-bottom: 1px solid #000 !important;
border-radius:10px 10px 0 0 !important;
line-height:1.4em;
}
.defaultTitleStyleSmall {
line-height:1.2em;
}
.defaultBackgroundStyle {
align-items:center;
pointer-events:none;
/*width:100%;
height:100%;*/
max-width:100%;
max-height:100%;
text-align:center !important;
justify-content: center;
justify-items: center;
height:auto;
padding:0;
background-color:#fff;
}
.ImgFitDefault{
object-fit: contain;
min-width: 0;
min-height: 0;
max-height: 400px;
max-width: 400px;
width:100%;
height:100%;
margin:2px;
padding:0;
position:unset;
border-radius: 10%;
}
#coverPreviewAutoScroll#style-4::-webkit-scrollbar-track,#coverPreviewContentAutoScroll::#style-4::-webkit-scrollbar-track
{
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: #F5F5F5;
}
#coverPreviewAutoScroll::-webkit-scrollbar,#coverPreviewContentAutoScroll::-webkit-scrollbar
{
width: 2px;
background-color: #F5F5F5;
}
#coverPreviewAutoScroll::-webkit-scrollbar-thumb, #coverPreviewContentAutoScroll::-webkit-scrollbar-thumb
{
background-color: #888;
}
#coverPreviewAutoScroll{
overflow:auto;
scrollbar-width: thin;
scrollbar-color: #888 #F5F5F5;
}
#coverPreviewContentAutoScroll{
display:block;
overflow:auto;
scrollbar-width: thin;
scrollbar-color: #888 #F5F5F5;
}
#popover{
box-sizing: border-box;
overflow: hidden;
/* 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;
box-shadow: 0px 0px 5px #7A7A7A;
position:absolute;
z-index:10;
text-align: center !important;
justify-content: start;
justify-items: center;
display: flex;
flex-shrink: 1;
flex-direction: column;
opacity: 1;
transition: visibility 0.2s, opacity 0.2s linear;
}
.hidePopover {
visibility:hidden !important;
opacity: 0 !important;
transition: visibility 0.4s, opacity 0.4s linear !important;
}
.isExternalContent{
border:2px solid red !important;
}
.isExternalContentArrow{
border:2px solid red !important;
border-width:0 2px 2px 0 !important
}
.popoverContent {
box-sizing: border-box;
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;
border-radius:0;
}
.popoverDetail{
flex-direction:unset !important;
height:400px;
}
.coverDataTitle{
border-bottom:1px solid white;
padding:2px 0;
}
.containerPadding{
justify-items:center;
padding:10px
}
.popoverTitleDetail{
height:100% !important;
width:auto !important;
max-width:65% !important;
border-radius: 10px 0 0 5px !important;
border:0 !important;
border-right: 1px solid #000 !important;
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(
adjustedTargetHeigt = 400,
targetTopPxYPosition = 0
) {
//https://developer.mozilla.org/en-US/docs/Web/CSS/min() not compatible with firefox 56
let targetHeight = defaultHeight;
if (showSmaller) targetHeight = smallHeight;
if (adjustedTargetHeigt < targetHeight) targetHeight = adjustedTargetHeigt;
const minHeightValue =
"min(" +
targetHeight +
"px, (100vh - (100vh - 100%) - " +
offsetToBottomBorderY * 2 +
"px - " +
targetTopPxYPosition +
"px))";
if (supportsCSSMin) {
//console.log("supports min()");
popover.style.maxHeight = minHeightValue;
popover.style.height = "";
} 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(adjustedTargetWidth = 400, targetTopPxPostion = 0) {
let targetHeight = defaultHeight;
if (showSmaller) targetHeight = smallHeight;
// if (adjustedTargetWidth < targetHeight) targetHeight = adjustedTargetWidth;
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);
arrowContainer = document.createElement("div");
arrowContainer.id = "popover_arrow";
bodyElement.appendChild(arrowContainer);
popover.className = (
"defaultBackgroundStyle " + STYLESHEETHIJACKFORBACKGROUND
).trim();
popoverContent.className =
"popoverContent blackFont " + STYLESHEETHIJACKFORBACKGROUND;
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
hidePopOver();
bodyElement.insertAdjacentElement("beforeend", popover);
popoverBorderWidth = 0.6; //getComputedStyle(popoverTitle,"border-bottom-width")
updateArrowStyle(); //Forum
updateArrowColor();
/*
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 updateArrowStyle() {
//console.log("updateArrowStyle");
popoverForegroundColor = getComputedStyle(popover, "background-color");
let popoverContentForegroundColor = getComputedStyle(
popoverContent,
"background-color"
);
const blendedForegroundColor = blendColorsToRGBA([
popoverForegroundColor,
popoverContentForegroundColor,
]);
popoverBackgroundColor = getComputedStyle(popoverTitle, "background-color");
/* console.log(
"backgroundColor of popoverTitle: " +
popoverBackgroundColor +
", popoverBorderWidth: " +
popoverBorderWidth +
", popoverForegroundColor: " +
popoverForegroundColor +
", popoverContentForegroundColor: " +
popoverContentForegroundColor +
", blendedForegroundColor: " +
blendedForegroundColor
);*/
popoverForegroundColor = blendedForegroundColor;
}
function updateArrowColor() {
let styleSheetIDElement = "mesh-nightmode-css";
let linkElement = document.getElementById(styleSheetIDElement);
//console.log(linkElement);
function loadedCb() {
//console.log("finished loading stylesheet " + linkElement.href);
updateArrowStyle();
}
function errorCb(error) {
console.log(error);
console.log("error loading stylesheet: " + linkElement.href);
}
if (linkElement) {
//console.log(linkElement.href);
let url = linkElement.href;
linkElement.href = "";
linkElement.removeEventListener("loaddata", loadedCb); // or: file.onload = loadedCb;
linkElement.onload = () => {
loadedCb();
};
linkElement.onerror = (error) => {
errorCb(error);
};
linkElement.href = url;
if (linkElement.complete) {
loadedCb();
}
}
}
function showPopupLoadingSpinner(
hoveredTitleLink,
title,
event,
notification = "",
coverData = undefined
) {
const DEBUG = false;
//console.log(event)
const isActivePopup =
currentTitelHover !== undefined &&
hoveredTitleLink !== undefined &&
currentTitelHover == hoveredTitleLink;
/*
console.group("showPopupLoadingSpinner")
//"currentCoverData: " +currentCoverData +
console.log("currentTitelHover: " + currentTitelHover+", hoveredTitleLink: " + hoveredTitleLink+", currentTitelHover == hoveredTitleLink: " + (currentTitelHover == hoveredTitleLink))
console.log("isActivePopup: " + isActivePopup)
console.groupEnd();*/
if (isActivePopup) {
popover.classList.remove("isExternalContent"); //last link was external. remove isExternal class style
// console.group("showPopupLoadingSpinner")
//popover.empty();
//popover.innerHTML = "";
DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
if (coverData !== undefined) {
//console.log("showPopupLoadingSpinner")
DEBUG && console.log(coverData);
adjustPopupTitleDetail(coverData, title);
} else popoverTitle.textContent = title;
if (notification != "") {
isShowingSpinnerAnimation = false;
popoverContent.innerHTML =
'<div id="coverPreviewContentAutoScroll" class="popoverContent ">' +
notification +
"</div>";
popoverContent.className =
"popoverContent wordBreak " + STYLESHEETHIJACKFORBACKGROUND; //blackfont
} else {
isShowingSpinnerAnimation = true;
popoverContent.innerHTML = `<svg class="spinner" viewBox="0 0 50 50">
<g transform="translate(25, 25)">
<circle class="" cx="0" cy="0" r="25" fill="black" stroke-width="5" />
<circle class="path" cx="0" cy="0" r="23" fill="none" stroke-width="5">
<animateTransform attributeType="xml" attributeName="transform" type="rotate" from="0" to="360"  dur="1.6s" repeatCount="indefinite" />
</circle>
</g>
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" style="fill:#fff;font-size:11px">Loading </text>
</svg>`;
//popoverContent.innerHTML = '<div class="forground" style="z-index: 3;">Loading Data</div><svg class="spinner" viewBox="0 0 50 50"><circle class="" cx="25" cy="25" r="22" fill="black" stroke-width="5"></circle><circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle></svg>';
popoverContent.className =
"popoverContent " + STYLESHEETHIJACKFORBACKGROUND; //whitefont
}
DEBUG && console.log(popover);
//   DEBUG && console.log("popover.offsetHeight: " + popover.offsetHeight);
//console.log(event)
if (coverData) {
popupPos(event, coverData.isExternal);
} else 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 (idToScroll, 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[idToScroll] = window.requestAnimationFrame(loop);
//#endregion
} else {
//#region pause animation
//console.group("loop scrolldata before pause");
window.cancelAnimationFrame(requestId[idToScroll]);
//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[idToScroll] = 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(idToScroll = "coverPreviewAutoScroll") {
coverDataContainer[idToScroll] = document.getElementById(idToScroll);
setStartScrollPosition(idToScroll);
if (autoScrollCoverData) {
if (coverDataContainer[idToScroll]) {
/*console.log(
"coverDataContainer.offsetHeight: " +
coverDataContainer.offsetHeight +
", coverDataContainer.scrollHeight: " +
coverDataContainer.scrollHeight
);*/
const hasOverflowValue =
coverDataContainer[idToScroll].scrollHeight >
coverDataContainer[idToScroll].offsetHeight;
if (hasOverflowValue) {
if (requestId[idToScroll])
window.cancelAnimationFrame(requestId[idToScroll]);
//console.log("currentPercent: " + currentPercent);
scrollToTarget(idToScroll, coverDataContainer[idToScroll]);
}
}
}
}
function resetAutoScroll(idToScroll = "coverPreviewAutoScroll") {
//autoScrollCoverData = true;
direction = 1;
currentPercent = null;
startTime = null;
pauseTimeDifference = null;
hasChangedStyle = false;
//console.log(requestId);
if (requestId[idToScroll])
window.cancelAnimationFrame(requestId[idToScroll]);
}
function setStartScrollPosition(idToScroll) {
const DEBUG = false;
if (coverDataContainer[idToScroll] && currentPercent) {
let scrollOverflow =
coverDataContainer[idToScroll].scrollHeight -
coverDataContainer[idToScroll].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[idToScroll].scrollTop
);
coverDataContainer[idToScroll].scrollTop = targetScrollTop;
DEBUG &&
console.log(
"coverDataContainer.scrollTop: " +
coverDataContainer[idToScroll].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 ||
link === null ||
link == "" ||
link == "undefined"
) {
popoverContent.innerHTML =
'<div class="containerPadding">No Cover Image found</div>';
} else {
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, coverData.isExternal);
}
}
}
//#region get serieDetails
function getRatingNumber(ratingString) {
//const ratingString = "Rating(3.3 / 5.0, 1940 votes)"
let ratingNumber;
if (ratingString) {
const matchesVotes = ratingString.toLowerCase().match(reVoteCount);
const matches = ratingString.match(reRating); //"Rating(3.3 / 5.0, 1940 votes)"
const matchesSingleNumber = ratingString.match(reRatingSingleNumber); //4.5
//console.log(matches)
//console.log(matchesSingleNumber)
//console.log(matches.length)
let hasVoteCountBigger0 = true;
let hasVoteString = false;
// console.log(matchesVotes)
if (matchesVotes && matchesVotes.length > 1) {
//console.log(matchesVotes[1])
hasVoteString = true;
if (matchesVotes[1] == 0 || matchesVotes[1] == "0") {
//console.log("no vote count")
hasVoteCountBigger0 = false;
}
}
if (matches && matches.length == 3 && hasVoteCountBigger0) {
//display rating when vote count found and more than 0
//console.log(matches[1])
ratingNumber = matches[1];
} else {
//no votecount found
//no rating in relation to max rating -> search for single number
//console.log(matchesSingleNumber[1])
if (
hasVoteCountBigger0 &&
matchesSingleNumber &&
matchesSingleNumber.length == 2
) {
ratingNumber = matchesSingleNumber[1];
}
}
}
return ratingNumber;
}
function getChapters(statusString) {
//TODO "Episodes" instead of chapter for tv series
const DEBUG = false;
let r###lt;
if (statusString && statusString.length > 0) {
DEBUG && console.group("getChapters");
let chapterCount;
let lowerCaseStatusString = statusString.toLowerCase();
DEBUG && console.log("lowerCaseStatusString: " + lowerCaseStatusString);
const matches = lowerCaseStatusString.match(reChapters);
let webnovel = "";
let hasVolumenInString = false;
let hasChapterInString = false;
if (matches && matches.length >= 2) {
hasChapterInString = true;
chapterCount = matches[1];
if (matches[2]) {
webnovel = " WN";
}
}
DEBUG && console.log("chapterCount reChapters: " + chapterCount);
if (!chapterCount) {
const matchesBehind = lowerCaseStatusString.match(
reChaptersNumberBehind
);
if (matchesBehind && matchesBehind.length >= 2) {
hasChapterInString = true;
chapterCount = matchesBehind[1];
}
}
DEBUG &&
console.log("chapterCount reChaptersNumberBehind: " + chapterCount);
if (!chapterCount) {
const matchesNumbers = lowerCaseStatusString.match(
reChaptersOnlyNumbers
); //example string "6892(Ongoing)"
if (matchesNumbers && matchesNumbers.length >= 2) {
chapterCount = matchesNumbers[1];
}
}
DEBUG &&
console.log("chapterCount reChaptersOnlyNumbers: " + chapterCount);
if (lowerCaseStatusString.includes("vol")) hasVolumenInString = true;
if (chapterCount) {
let numberType = " Chapters";
if (hasVolumenInString && !hasChapterInString) numberType = " Vol";
r###lt = chapterCount + webnovel + numberType;
}
DEBUG && console.groupEnd();
}
DEBUG && console.log("r###lt: " + r###lt);
return r###lt;
}
function getCompletedState(statusString) {
let r###lt = false;
if (statusString && statusString.toLowerCase().includes("complete")) {
//complete | completed
r###lt = true;
}
return r###lt;
}
function getOngoingState(statusString) {
let r###lt = false;
if (statusString && statusString.toLowerCase().includes("ongoing")) {
r###lt = true;
}
return r###lt;
}
function getDetailsString(coverData) {
let completeDetails = "";
if (showDescription) {
if (coverData.description && coverData.description.length > 0) {
completeDetails +=
'<div class="borderTop">Description: ' +
coverData.description +
"</div>";
} else {
completeDetails +=
'<div class="borderTop">Description: Description Empty or error in coverData. Please reload seriepage info</div>';
}
} else {
if (coverData.votes) {
completeDetails +=
'<div class="borderTop">Rating: ' + coverData.votes + "</div>";
}
if (coverData.status) {
completeDetails +=
'<div class="borderTop">Status: ' + coverData.status + "</div>";
}
if (coverData.chapters) {
completeDetails +=
'<div class="borderTop">Chapters: ' + coverData.chapters + "</div>";
}
if (coverData.genre) {
completeDetails +=
'<div class="borderTop">Genre: ' + coverData.genre + "</div>";
}
if (coverData.showTags) {
completeDetails +=
'<div class="borderTop">Tags: ' + coverData.showTags + "</div>";
}
}
return completeDetails;
}
function getShortendDetailsString(coverData) {
let completeDetails = "";
let rating = getRatingNumber(coverData.votes);
let chapters = getChapters(coverData.status);
let serieChapters = getChapters(coverData.chapters);
let completed = getCompletedState(coverData.status);
let ongoing = getOngoingState(coverData.status);
//console.log(rating)
//console.log(chapters)
//console.log(serieChapters)
//console.log(completed)
//console.log(ongoing)
if (rating || chapters || serieChapters || completed || ongoing) {
if (rating !== undefined) rating += "★ ";
else rating = "";
//console.log(coverData);
if (chapters !== undefined) chapters = chapters + " ";
else chapters = "";
if (serieChapters !== undefined) serieChapters = serieChapters + " ";
else serieChapters = "";
//console.log("chapters: " + chapters);
//console.log("serieChapters: " + serieChapters);
if (serieChapters != "") chapters = "";
if (completed) completed = "🗹 ";
else completed = ""; //https://www.utf8icons.com/
if (ongoing) ongoing = "✎ ";
else ongoing = "";
completeDetails +=
'<span class="' +
smallTextStyle +
'" style="white-space: nowrap;"> [' +
rating +
chapters +
serieChapters +
completed +
ongoing +
"]</span>";
}
return completeDetails;
}
async function adjustPopupTitleDetail(coverData, title = undefined) {
let titleToShow = "";
popoverTitle.textContent = "";
if (coverData && coverData.title) titleToShow = coverData.title;
else if (title !== undefined) titleToShow = title;
//popoverTitle.textContent = titleToShow;
//console.log("adjustPopupTitleDetail - showDetails: " + showDetails)
let completeDetails = "";
let externalIcon = "";
let showReadingListIcon = "";
//console.log(coverData.readingListIcon);
//console.log(coverData.readingListTitle)
if (useReadingListIconAndTitle && !coverData.isExternal) {
//console.log(coverData.readingListIcon)
// showReadingListIcon="[&nbsp;] ";
if (coverData.readingListIcon !== undefined) {
if (showDetails) {
showReadingListIcon =
'<img src="' +
coverData.readingListIcon +
'" width="16px"; height="16px" /> ';
} else {
showReadingListIcon =
'<img src="' +
coverData.readingListIcon +
'" width="12px"; height="12px" /> ';
}
}
}
//console.log(coverData)
if (coverData.isExternal) {
externalIcon = '<span style="background-color:darkred">🔗</span> ';
popover.classList.add("isExternalContent");
} else {
popover.classList.remove("isExternalContent");
}
let alternativeNames = "";
if (showDetails) {
//console.log("showDetails should be true")
let showExternalLink = "";
let showReadingListTitle = "";
if (useReadingListIconAndTitle && !coverData.isExternal) {
showReadingListTitle = "";
if (coverData.readingListTitle) {
showReadingListTitle =
"<div>" +
showReadingListIcon +
" " +
coverData.readingListTitle +
"</div>";
} else {
showReadingListTitle =
"<div>[&nbsp;] not in a reading list or logged in</div>";
}
}
if (coverData.alternativeNames && coverData.alternativeNames != "") {
alternativeNames = " [Key A]";
}
if (coverData.isExternal) {
showExternalLink =
' <div style="background-color:darkred" class="coverDataTitle">🔗[' +
coverData.isExternal +
"]</div>";
}
completeDetails +=
'<span class="' +
mediumTextStyle +
'" style="height:100%;display:flex;flex-direction:column"><span class="coverDataTitle"><b>' +
titleToShow +
"</b>" +
alternativeNames +
showReadingListTitle +
"</span> " +
showExternalLink +
'<div 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 {
if (coverData.alternativeNames && coverData.alternativeNames != "") {
alternativeNames = " [A]";
}
completeDetails =
'<span class="' +
mediumTextStyle +
'">' +
externalIcon +
showReadingListIcon +
"<b>" +
titleToShow +
"</b>" +
alternativeNames +
" " +
getShortendDetailsString(coverData);
completeDetails +=
' <span class="' +
smallTextStyle +
'">[KeyH hotkey list]</span></span>';
}
//popoverTitle.innerHTML = completeDetails;
popoverTitle.innerHTML = completeDetails;
}
//#endregion
function setCurrentCoverDataAndLoadImage(coverData, hoveredTitle, e) {
const DEBUG = false;
//GM_getCachedValue
DEBUG && console.group("setCurrentCoverDataAndLoadImage");
DEBUG && console.log(coverData);
let serieTitle = hoveredTitle;
if (!hoveredTitle || coverData.title) {
//pure link without title get title of seriepage
serieTitle = coverData.title;
}
DEBUG &&
console.log(
"hoveredTitle: " + hoveredTitle + ", serieTitle: " + serieTitle
);
if (
coverData !== undefined &&
coverData !== null &&
hoveredTitle == currentTitelHover
) {
currentCoverData = coverData;
}
if (e) {
loadImageFromBrowser({
coverData: currentCoverData,
e: e,
serieTitle: serieTitle,
hoveredTitleLink: hoveredTitle,
});
}
DEBUG && console.groupEnd("setCurrentCoverDataAndLoadImage");
}
function ajaxLoadImageUrlAndShowPopup(
forceReload = false,
element,
hoveredTitle,
e,
external = false,
targetPage = undefined
) {
const currentEvent = e;
//console.log(currentEvent)
//console.log("mouseenter")
// console.group("ajaxLoadImageUrlAndShowPopup")
return parseSeriePage(
element,
forceReload,
hoveredTitle,
currentEvent,
external,
targetPage
).then(
function (coverData) {
if (coverData !== undefined) {
setCurrentCoverDataAndLoadImage(
coverData,
hoveredTitle,
currentEvent
);
}
},
function (Error) {
const elementUrl = element.href;
console.log(Error);
let errorMessage = '"(' + Error + ')" failed to fetch ' + elementUrl;
console.log("errorMessage: " + errorMessage);
setLinkStateOfSameLinks(element, linkIconEnum.error);
showPopupLoadingSpinner(
hoveredTitle,
hoveredTitle,
currentEvent,
errorMessage
);
}
);
// 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,
errorText = undefined,
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(errorText);
let filename = "";
console.log("coverData.url: " + coverData.url);
if (coverData.url !== undefined && coverData.url != "undefined") {
filename = decodeURIComponent(coverData.url);
} else {
filename = "";
}
let additionalText = "";
if (errorText) additionalText = errorText;
let errorMessage =
'<div class="containerPadding">browser blocked/has error loading the cover: <br />' +
filename +
"<br />" +
additionalText +
"</div>";
if (filename == "") {
errorMessage =
'<div class="containerPadding">target site has no coverImage<br />[no image tag found]<br /></div>';
}
console.log("errorMessage: " + 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");
}
async 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 = async (error) => {
let imageCanBeLoaded = checkImageServerState(coverData.url);
console.log("imageCanBeLoaded r###lt before await: ");
console.log(imageCanBeLoaded);
imageCanBeLoaded = await imageCanBeLoaded;
// console.log("imageCanBeLoaded after await: " + imageCanBeLoaded)
DEBUG &&
console.log(
"imageCanBeLoaded: " +
imageCanBeLoaded +
", coverData.url: " +
coverData.url
);
console.log(
"imageCanBeLoaded: " +
imageCanBeLoaded +
", coverData.url: " +
coverData.url
);
let errorMessage;
if (imageCanBeLoaded) {
errorMessage =
"image fetching is possible, but image is blocked from loading.<br />Check for example ublock/umatrix or similar if domain is allowed to load images or image sizes exceeds allowed media size";
} else {
errorMessage =
"image could not be loaded. Check in console.log what error state checkImageServerState() produces";
}
console.group("img node has loading error");
console.log(error);
console.log("errorMessage: " + errorMessage);
console.groupEnd();
imageLoadingError(
coverData,
errorMessage,
hoveredTitleLink,
serieTitle,
e
);
};
//console.log(coverData)
if (coverData !== undefined) {
if (coverData.url !== undefined && coverData.url != "undefined") {
img.src = coverData.url;
if (img.complete) {
DEBUG &&
console.log(
"loadImageFromBrowser preload completed: " + serieTitle
);
DEBUG && console.log(img.src);
} else {
//if image not available/cached in browser show loading pinner
/*
const isActivePopup =
currentCoverData !== undefined &&
currentTitelHover !== undefined &&
hoveredTitleLink !== undefined &&
currentTitelHover == hoveredTitleLink &&
hasMouseEnterEvent; //currentTitelHover == hoveredTitleLink currentCoverData == coverData
//console.log(e)
if (isActivePopup) {
*/
DEBUG &&
console.log(
"loadImageFromBrowser image not completely loaded yet. Show loading spinner : " +
serieTitle
);
showPopupLoadingSpinner(
hoveredTitleLink,
serieTitle,
e,
"",
coverData
);
//  }
}
} else {
imageLoadingError(
coverData,
"coverData has no image",
hoveredTitleLink,
serieTitle,
e
);
}
}
// console.groupEnd("loadImageFromBrowser")
}
function hidePopOver() {
// popover.style.visibility = "hidden";
//arrowContainer.style.visibility = "hidden";
arrowContainer.classList.add("hidePopover");
popover.classList.add("hidePopover");
//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";
//popover.style.opacity="1";
popover.classList.remove("hidePopover");
arrowContainer.classList.remove("hidePopover");
popoverVisible = true;
}
function hideOnMouseLeave() {
//if (!e.target.matches(concatSelector())) return;
//popover.hide();
hidePopOver();
}
/*
* get links into ALLSERIENODES and convert this nodearray to array
*
*/
//renamed targetNodeArray -> arrayTargetNode to remove compatibility setting in tampermonkey () misdetected? (targetNodeArray.foreach, targetNodeArray.map, targetNodeArray.push)
function updateSerieNodes(
arrayTargetNode = [],
individualLinksToTest,
forceReload = false,
external = false
) {
const DEBUG = false;
if (arrayTargetNode && arrayTargetNode.length > 0) {
arrayTargetNode.forEach(function (selector) {
if (
eventListenerStyle === undefined ||
eventListenerStyle === null ||
eventListenerStyle == 0
) {
selector.removeEventListener("mouseleave", hideOnMouseLeave);
//selector.removeEventListener("mouseenter", mouseEnterPopup);
}
});
}
let serieLinkNodes = document.querySelectorAll(
':not(.digg_pagination) > a[href*="' + individualLinksToTest + '"]' //not(.digg_pagination) > fix to block linking from pagination
);
//console.log(serieLinkNodes)
serieLinkNodes = Array.from(serieLinkNodes);
let prunedSerieLinkNodes = [];
if (serieLinkNodes && serieLinkNodes.length > 0) {
//console.log(serieLinkNodes);
serieLinkNodes.map(function (el) {
//console.log(el)
const elementUrl = el.href;
//#region if has serieRegex check for  externalLink+serieRegex match
let hasLinkMatch = false;
if (external) {
hasLinkMatch = externalLinkKeys.some((key) => {
let completeKey = key;
/*
console.log("key: " + key);
console.log(externalLinks[key]);
console.log(
'externalLinks["serieRegex"]: ' + externalLinks[key]["serieRegex"]
);*/
let indexOfDomain = elementUrl.indexOf(key);
let characterAfterIndividualPage = elementUrl.slice(
indexOfDomain + key.length
); //get characters after targetAdress to parse ID
if (indexOfDomain >= 0 && characterAfterIndividualPage.length > 0) {
DEBUG &&
console.log(
"elementUrl: " +
elementUrl +
", key: " +
key +
", indexOfDomain on key " +
indexOfDomain +
",characterAfterIndividualPage: " +
characterAfterIndividualPage
);
if (externalLinks[key].serieRegex !== undefined) {
completeKey = externalLinks[key].serieRegex;
//console.log("serieRegex regex: " + completeKey)
/*  const hasMatch =  xhr.finalUrl.match(new RegExp(completeKey));
return hasMatch; //performance exclusion regex only used for a few links
*/
} else {
completeKey = defaultSerieRegex;
//console.log("default regex: " + completeKey)
}
let hasMatch = characterAfterIndividualPage.match(
new RegExp(completeKey)
);
if (hasMatch === null || hasMatch.length == 0) {
if (externalLinks[key].serieRegex2 !== undefined) {
hasMatch = characterAfterIndividualPage.match(
new RegExp(externalLinks[key].serieRegex2)
);
}
}
//console.log(hasMatch)
return hasMatch !== null && hasMatch.length > 1;
//return elementUrl.includes(key);
}
return false; //no further serieid available
});
} else {
let indexOfDomain = elementUrl.indexOf(INDIVIDUALPAGETEST);
let characterAfterIndividualPage = elementUrl.slice(
indexOfDomain + INDIVIDUALPAGETEST.length
); //get characters after targetAdress to parse ID
if (characterAfterIndividualPage.length > 0) hasLinkMatch = true; //internal without serieRegex
}
//console.log("hasLinkMatch: " + hasLinkMatch);
//#endregion
if (hasLinkMatch) {
prunedSerieLinkNodes.push(el);
if (external) {
el.setAttribute("coverDataExternalTarget", individualLinksToTest);
}
setLinkState(el, undefined, forceReload || preloadUrlRequests);
}
// console.log(elementUrl)
});
}
arrayTargetNode.push(...prunedSerieLinkNodes);
//console.log(ALLSERIENODES)
/*
console.log(ALLSERIENODES)
const sliceItemCount = 100;
if (ALLSERIENODES.length > sliceItemCount) {
ALLSERIENODES = ALLSERIENODES.slice(0, sliceItemCount);
}
console.log(ALLSERIENODES)
*/
}
function switchShowIconNextToLink() {
showIconNextToLink = !showIconNextToLink;
GM_setValue("showIconNextToLink", showIconNextToLink);
preloadCoverData();
updateCurrentPopupContent();
}
function switchDetailsAndUpdatePopup() {
const DEBUG = false;
DEBUG && console.group("switchDetailsAndUpdatePopup");
changeToNewDetailStyle();
//console.log(currentCoverData)
DEBUG && console.log("switchDetails refreshPopup");
updateCurrentPopupContent();
console.groupEnd("switchDetails");
}
function switchTagsDescriptionAndUpdatePopup() {
const DEBUG = false;
if (showDetails) {
showDescription = !showDescription;
//console.log("switch showDetails to : " + showDetails)
GM_setValue("showDescription", showDescription);
updateCurrentPopupContent();
}
}
function switchShowReadingListIconAndTitle() {
useReadingListIconAndTitle = !useReadingListIconAndTitle;
GM_setValue("useReadingListIconAndTitle", useReadingListIconAndTitle);
updateCurrentPopupContent();
}
function updateCurrentPopupContent() {
const DEBUG = false;
if (currentCoverData !== undefined) {
DEBUG && console.log(currentCoverData);
loadImageFromBrowser({
coverData: currentCoverData,
e: currentPopupEvent,
serieTitle: currentTitelHover,
hoveredTitleLink: currentTitelHover,
});
} else if (currentTitelHover !== undefined) {
//currentCoverData not yet set
showPopupLoadingSpinner(
currentTitelHover,
currentTitelHover,
currentPopupEvent
);
}
}
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();
//console.log("changeToNewDetailStyle - setPopoverHeight();");
//setPopoverHeight();
updatePopoverSize();
}
//#region eventListener
function mouseEnterPopup(e, forceReload = false) {
//if (!e.target.matches(concatSelector())) return;
const DEBUG = false;
DEBUG && console.group("mouseEnterPopup");
//let element = undefined;//$(this);
//let nativElement = e.target//this;
//console.log(this)
//console.log(e)
if (e !== undefined) {
e.preventDefault();
const target = e.target;
let Href = target.href; // element.attr('href');
let coverDataExternalTarget = target.getAttribute(
"coverDataExternalTarget"
);
//console.log("coverDataExternalTarget: " + coverDataExternalTarget);
if (Href && coverDataExternalTarget === undefined) {
//TODO double check if needed
//no preloadUrlRequests happend
/*
const externalLinkKeys = Object.keys(externalLinks);
const isExternal = externalLinkKeys.some((key) => Href.includes(key));
if (isExternal) {
target.setAttribute("coverDataExternalTarget", key);
coverDataExternalTarget = key;
} else target.setAttribute("coverDataExternalTarget", null);
*/
}
DEBUG &&
console.log(
"Href: " + Href + ", INDIVIDUALPAGETEST: " + INDIVIDUALPAGETEST
);
if (
Href &&
(Href.includes(INDIVIDUALPAGETEST) ||
(coverDataExternalTarget !== undefined &&
coverDataExternalTarget !== null &&
Href.includes(coverDataExternalTarget)))
) {
//only trigger for links that point to serie pages
//console.log(this)
//console.log(this.text) //shortTitle
//console.log(this.title) //LongTitle
let shortSerieTitle = target.text; //element.text(); //get linkname
//console.log(this)
//console.log(shortSerieTitle)
const dataTitle = target.getAttribute("datatitle");
const linkTitle = target.getAttribute("title");
//console.log("linkTitle: " + linkTitle)
const hasDataTitle =
dataTitle === null ||
dataTitle == "null" ||
dataTitle === undefined ||
!dataTitle;
//move native title to custom data attribute. Suppress nativ title popup
if (linkTitle !== null && hasDataTitle) {
target.setAttribute("datatitle", linkTitle);
target.removeAttribute("title");
}
let serieTitle = target.getAttribute("datatitle"); //element.attr('datatitle'); //try to get nativ title if available from datatitle
//console.log(serieTitle)
if (
serieTitle === null || //has no set nativ long title -> use (available shortend) linkname
serieTitle == "null" ||
PREDIFINEDNATIVTITLEARRAY.some((nativTitle) =>
serieTitle.includes(nativTitle)
)
) {
//catch on individual serie page nativ title begins with "Recommended by" x people -> use linkname
serieTitle = shortSerieTitle;
}
if (
serieTitle === undefined ||
serieTitle === null ||
serieTitle == ""
) {
//image link: example link which content is only the cover image https://www.mangaupdates.com/series.html?letter=A
serieTitle = Href;
}
currentTitelHover = serieTitle; //mark which titel is currently hovered
const wasOverDifferentLink = currentTitelHover != previousTitelHover;
if (wasOverDifferentLink) {
resetAutoScroll();
autoScrollCoverData = true;
}
if (currentTitelHover != undefined) {
previousTitelHover = currentTitelHover;
}
currentPopupEvent = e;
//console.log(serieTitle)
//console.log(Href)
//console.log(currentCoverData)
//console.log("currentTitelHover: " + currentTitelHover)
const external =
coverDataExternalTarget !== undefined &&
coverDataExternalTarget !== null;
let targetPage;
//console.log("external: " + external);
//console.log("coverDataExternalTarget: " + coverDataExternalTarget);
if (external) targetPage = coverDataExternalTarget;
//console.log("targetPage: " + targetPage);
let mainSerieHref = getLinkToSeriePage(Href, targetPage);
let hasCoverData = GM_getCachedValue(mainSerieHref);
if (!hasCoverData || forceReload) {
setLinkStateOfSameLinks(
target,
linkIconEnum.popupLoading,
forceReload
);
} else {
if (forceReload) {
setLinkStateOfSameLinks(target, undefined, forceReload);
}
}
ajaxLoadImageUrlAndShowPopup(
forceReload,
target, //Href
currentTitelHover,
e,
external,
targetPage
);
}
}
DEBUG && console.groupEnd("mouseEnterPopup");
}
function forceReload(forceReload = true) {
mouseEnterPopup(currentPopupEvent, forceReload);
}
function updatePopoverSize() {
//console.log("updatePopoverSize - setPopoverHeight();");
//setPopoverHeight(); //happens after updateCurrentPopupContent update
setPopoverWidth();
if (showSmaller) {
mediumTextStyle = "small_mediumText";
smallTextStyle = "small_smallText";
popoverTitle.classList.add("defaultTitleStyleSmall");
} else {
popoverTitle.classList.remove("defaultTitleStyleSmall");
mediumTextStyle = "mediumText";
smallTextStyle = "smallText";
}
updateCurrentPopupContent();
}
function showAlternativeNamesList() {
if (!showAlternativeNames || showAlternativeNames == "") {
if (currentCoverData !== undefined) updateCurrentPopupContent();
} else {
if (
currentCoverData.alternativeNames &&
currentCoverData.alternativeNames != ""
) {
let alternativeNames = "";
alternativeNames = currentCoverData.alternativeNames;
popoverContent.innerHTML =
'<div id="coverPreviewContentAutoScroll" class="popoverContent ' +
STYLESHEETHIJACKFORBACKGROUND +
" " +
mediumTextStyle +
'" style="text-align:start !important; width:100%;"><b>Alternative Titles:</b><br />' +
alternativeNames +
"</div>";
if (currentCoverData !== undefined) popupPos(currentPopupEvent);
autoScrollData("coverPreviewContentAutoScroll");
}
}
}
function showHotkeyList() {
if (!showHotkeys) {
if (currentCoverData !== undefined) {
loadImageFromBrowser({
coverData: currentCoverData,
e: currentPopupEvent,
serieTitle: currentTitelHover,
hoveredTitleLink: currentTitelHover,
});
}
} else {
popoverContent.innerHTML =
'<div id="coverPreviewContentAutoScroll" class="popoverContent ' +
STYLESHEETHIJACKFORBACKGROUND +
" " +
mediumTextStyle +
'" style="text-align:start !important">[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 hovered link<br />
[Key 6]: Reload all links of current Page<br/>
[Key 9]: Clear all cover data info<br />
[Key A]: If available will show <b>a</b>lternative titles during holding of key A<br />
[Key I]: Toggle coverPreview pre/loading state <b>i</b>con displaying next to link<br />
[Key P]: Switch displaying of readinglist icon of <b>p</b>ersonal lists<br />
[Key H]: Show this <b>h</b>otkey list during holding of key H<br />
</div>`;
if (currentCoverData !== undefined) popupPos(currentPopupEvent);
autoScrollData("coverPreviewContentAutoScroll");
}
}
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":
{
const _forceReload = true;
preloadCoverData(_forceReload);
}
break;
case "9":
resetDatabase();
preloadCoverData();
forceReload();
break;
case "2":
switchTagsDescriptionAndUpdatePopup();
resetAutoScroll();
autoScrollCoverData = true;
autoScrollData();
autoScrollData("coverPreviewContentAutoScroll");
break;
case "3":
showSmaller = !showSmaller;
GM_setValue("showSmaller", showSmaller);
updatePopoverSize();
hasChangedStyle = true;
break;
case "4":
autoScrollCoverData = !autoScrollCoverData;
if (autoScrollCoverData) {
autoScrollData();
autoScrollData("coverPreviewContentAutoScroll");
}
break;
case "h":
showHotkeys = true;
showHotkeyList();
break;
case "a":
showAlternativeNames = true;
showAlternativeNamesList();
break;
case "p":
switchShowReadingListIconAndTitle();
updateCurrentPopupContent();
//forceReload();
break;
case "i":
switchShowIconNextToLink();
break;
}
}
}
}
function releaseKey(event) {
const key = event.key;
//console.log(pressedKeys)
pressedKeys.splice(pressedKeys.indexOf(key), 1);
if (event.key == "h") {
showHotkeys = false;
showHotkeyList();
}
if (event.key == "a") {
showAlternativeNames = false;
showAlternativeNamesList();
}
//console.log(pressedKeys)
}
function prepareEventListener() {
window.addEventListener("blur", hidePopOver);
window.addEventListener("keypress", reactToKeyPressWhenPopupVisible); //keypress gets repeated during keydown
window.addEventListener("keyup", releaseKey);
if (
targetContainerIDArrayToObserve &&
targetContainerIDArrayToObserve.length > 0
) {
for (let i = 0; i < targetContainerIDArrayToObserve.length; i++) {
let targetNodeList = document.getElementById(
targetContainerIDArrayToObserve[i]
); //forum.novelupdates.com quickedit change. ajax content change
if (targetNodeList) {
observer.observe(targetNodeList, config);
}
}
}
let themeSelector = document.getElementById("wi_themes");
if (themeSelector)
themeSelector.addEventListener("change", updateArrowColor);
window.onunload = function () {
window.removeEventListener("blur", hidePopOver);
window.removeEventListener("keypress", reactToKeyPressWhenPopupVisible);
window.addEventListener("keyup", releaseKey);
popover.removeEventListener("mouseleave", hideOnMouseLeave);
if (themeSelector)
themeSelector.removeEventListener("change", updateArrowColor);
//possible memoryleaks?
ALLSERIENODES = [];
updateSerieNodes(ALLSERIENODES, INDIVIDUALPAGETEST);
observer.disconnect();
};
if (eventListenerStyle == 1) {
window.addEventListener("mousemove", throttledGetHoveredItem);
}
}
//assumption that a single eventlistener is more performant than dozens of mouseEnter/MouseLeave events
//https://gomakethings.com/why-event-delegation-is-a-better-way-to-listen-for-events-in-vanilla-js/#web-performance
//https://davidwalsh.name/event-delegate
//https://web.archive.org/web/20170121035049/http://jsperf.com/click-perf
//https://stackoverflow.com/questions/29836326/is-using-a-universal-document-addeventlistenerclick-f-listener-slower-or-wea
/*
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
*/
function getHoveredItem(e) {
if (eventListenerStyle == 1) {
if (
e.target &&
e.target != lastTarget &&
e.target.nodeName == "A" &&
e.target.href &&
e.target.href.includes(INDIVIDUALPAGETEST)
) {
lastTarget = e.target;
//console.group("target A")
//console.log(e.target.text)
//console.log(e)
mouseEnterPopup(e);
//console.groupEnd();
} else {
if (e.target.nodeName != "A") {
lastTarget = undefined;
hideOnMouseLeave();
}
}
}
}
document.addEventListener("DOMContentLoaded", main());
//#endregion
//#region mangaDex api
function getMangaDexNamedTag(tags, type = "Genre") {
const DEBUG = false;
DEBUG && console.group("getMangaDex" + type);
let namedTags = "";
DEBUG && console.log(mangaDexTAGS);
DEBUG && console.log(tags);
DEBUG && console.log("tags.length: " + tags.length);
if (tags && tags.length > 0) {
for (let i = 0; i < tags.length; i++) {
let searchTag = tags[i];
//console.log(searchTag)
const tag = mangaDexTAGS[searchTag];
if (tag.group == type) {
//console.log(tag)
namedTags += " " + tag.name;
}
}
}
DEBUG && console.groupEnd();
DEBUG && console.log(namedTags);
return namedTags;
}
async function requestDataFromAPIpostCall(
apiPoint,
postData,
hasDataContainer = undefined
) {
const DEBUG = false;
let PromiseR###lt = new Promise(async function (resolve, reject) {
function onLoad(xhr) {
DEBUG && console.log(xhr);
//https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299)
switch (true) {
case xhr.status == 304:
console.log("xhr.status == 304: getting data from browser cache");
//fall through to status==200 ok
case xhr.status >= 200 && xhr.status < 399:
{
DEBUG &&
console.group("requestDataFromAPIpostCall onLoad: " + apiPoint);
DEBUG && console.log(xhr);
let tempJSON = JSON.parse(xhr.responseText);
let apiData;
DEBUG && console.log(tempJSON);
if ((tempJSON.status = "OK")) {
if (hasDataContainer) apiData = tempJSON[hasDataContainer];
else apiData = tempJSON;
}
DEBUG && console.log(apiData);
return resolve(apiData);
}
break;
default:
/*
console.group("error in getCoverDataFromUrl");
console.log(xhr);
console.groupEnd();*/
return reject(rejectErrorStatusMessage(xhr));
}
}
function onError(error) {
console.log(error);
const err = new Error(
"GM_xmlhttpRequest could not load " +
apiPoint +
"; script is not compatible or url does not exists."
);
console.log(err);
return reject(err);
}
GM_xmlhttpRequest({
method: "POST",
data: JSON.stringify(postData),
url: apiPoint,
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
dataType: "json",
contentType: "application/json",
overrideMimeType: "application/json",
onload: onLoad,
onerror: onError,
});
return undefined; //reject("status error")
});
PromiseR###lt = await PromiseR###lt;
return PromiseR###lt;
}
async function checkImageServerState(url) {
const DEBUG = false;
let PromiseR###lt = new Promise(async function (resolve, reject) {
function onLoad(xhr) {
DEBUG && console.log("checkImageServerState");
DEBUG && console.log(xhr);
DEBUG && console.log("xhr.status: " + xhr.status);
switch (true) {
case xhr.status == 304:
console.log(
"xhr.status == 304: getting data from browser cache for " +
xhr.finalUrl
);
case xhr.status >= 200 && xhr.status < 399: {
DEBUG && console.log("no error");
return resolve(true);
}
default:
/*
console.group("error in getCoverDataFromUrl");
console.log(xhr);
console.groupEnd();*/
console.log(rejectErrorStatusMessage(xhr));
return reject(false);
}
}
function onError(error) {
console.log(error);
const err = new Error(
"checkImageServerState() could not load " +
url +
"; script is not compatible or url does not exists."
);
console.log(err);
return reject(false);
}
GM_xmlhttpRequest({
method: "HEAD",
url: url,
onload: onLoad,
onerror: onError,
// onreadystatechange,
// onprogress
});
return "error or skipped processing GM_xmlhttpRequest() in checkImageServerState()"; //reject("status error")
});
//  if(PromiseR###lt!=undefined)
{
PromiseR###lt = await PromiseR###lt;
return PromiseR###lt;
}
//  return "checkImageServerState function error";
}
async function getDataFromAPI(
apiPoint,
{ hasDataContainer = undefined, responsetype = "json" } = {}
) {
const DEBUG = false;
DEBUG && console.log("responsetype", responsetype);
let PromiseR###lt = new Promise(async function (resolve, reject) {
function onLoad(xhr) {
DEBUG && console.log("xhr", xhr);
//console.log(xhr.responseHeaders)
DEBUG && console.log("xhr.status: " + xhr.status);
//https://developer.mozilla.org/en-US/docs/Web/HTTP/Status Successful responses (200–299)
switch (true) {
case xhr.status == 304:
DEBUG && console.log(
"xhr.status == 304: getting data from browser cache for " +
xhr.finalUrl
);
//fall through to status==200 ok
case xhr.status >= 200 && xhr.status < 399: {
DEBUG && console.group("getDataFromAPI onLoad: " + apiPoint);
//DEBUG && console.log(xhr);
let apiData;
DEBUG && console.log("responsetype", responsetype);
switch (responsetype) {
case "json":
{
let tempJSON = JSON.parse(xhr.responseText);
DEBUG && console.log("tempJSON", tempJSON);
if ((tempJSON.status = "OK")) {
if (hasDataContainer) apiData = tempJSON[hasDataContainer];
else apiData = tempJSON;
}
}
break;
case "Document":
{
const domDocument = xhr.response;
//tampermonkey 4.12.6130 seems to deprecate some old api? document response is now undefined
//only responseXML would be usable
apiData = domDocument;
}
break;
case "text":
{
const domText = xhr.responseText;
let parser = new DOMParser()
const domDocument = parser.parseFromString(
domText,
"text/html"
);
apiData = domDocument;
}
break;
default:
const domDocument = xhr.responseText;
apiData = domDocument;
break;
}
DEBUG && console.log("apiData", apiData);
if (apiData !== undefined) return resolve(apiData);
else
return reject(
"no apiData error for responsetype: " + responsetype
);
}
default:
/*
console.group("error in getCoverDataFromUrl");
console.log(xhr);
console.groupEnd();*/
return reject(rejectErrorStatusMessage(xhr));
}
}
function onError(error) {
console.log(error);
const err = new Error(
"GM_xmlhttpRequest could not load " +
apiPoint +
"; script is not compatible or url does not exists."
);
console.log(err);
return reject(err);
}
function onreadystatechange(stateChangeResponse) {
console.log(stateChangeResponse);
}
function onprogress(progressResponse) {
//console.log(progressResponse)
}
GM_xmlhttpRequest({
method: "GET",
//overrideMimeType:'text/html;',
responseType: responsetype,
url: apiPoint,
onload: onLoad,
onerror: onError,
// onreadystatechange,
// onprogress
});
return undefined; //reject("status error")
});
PromiseR###lt = await PromiseR###lt;
return PromiseR###lt;
}
async function getCoverDataFromMangaDex(id) {
const DEBUG = false;
if (id) {
if (mangaDexTAGS === undefined || mangaDexTAGS === null) {
//only needed one time per session
mangaDexTAGS = getDataFromAPI("https://mangadex.org/api/v2/tag", {
hasDataContainer: "data",
}); //getAllMangaDexTags
}
DEBUG && console.log(mangaDexTAGS);
let mangaDexData = getDataFromAPI(
"https://mangadex.org/api/v2/manga/" + id,
{ hasDataContainer: "data" }
);
DEBUG && console.log(mangaDexData);
let serieChapters = getDataFromAPI(
"https://mangadex.org/api/v2/manga/" + id + "/chapters",
{ hasDataContainer: "data" }
); //await getMangaDexChapterCount(id);
DEBUG && console.log(serieChapters);
[mangaDexTAGS, mangaDexData, serieChapters] = await Promise.all([
mangaDexTAGS,
mangaDexData,
serieChapters,
]);
let serieGenre = getMangaDexNamedTag(mangaDexData.tags, "Genre");
let serieTags = getMangaDexNamedTag(mangaDexData.tags, "Theme");
DEBUG && console.log(mangaDexTAGS);
DEBUG && console.log(serieChapters);
if (serieChapters && serieChapters.chapters) {
serieChapters = serieChapters.chapters.length;
}
DEBUG && console.log("serieChapters: " + serieChapters);
let status;
DEBUG && console.log(mangaDexData);
switch (mangaDexData.publication.status) {
case 1:
status = "Ongoing";
break;
case 2:
status = "Completed";
break;
}
let cData = Object.assign({}, emptyCoverData);
cData.url = mangaDexData.mainCover;
cData.title = mangaDexData.title;
cData.alternativeNames = mangaDexData.altTitles.join("<br /> ");
cData.votes = mangaDexData.rating.bayesian.toString();
cData.status = status;
cData.chapters = serieChapters.toString();
cData.genre = serieGenre;
cData.showTags = serieTags;
cData.description = mangaDexData.description;
return cData;
}
return undefined;
}
//#endregion mangadex api
//#region http://api.tvmaze.com/
async function getCoverDataFromTVmaze(id) {
const DEBUG = false;
if (id) {
let apiData = getDataFromAPI("http://api.tvmaze.com/shows/" + id);
DEBUG && console.log(apiData);
let serieAlternativeNames = getDataFromAPI(
"http://api.tvmaze.com/shows/" + id + "/akas"
);
let episodes = getDataFromAPI(
"http://api.tvmaze.com/shows/" + id + "/episodes"
);
[apiData, serieAlternativeNames, episodes] = await Promise.all([
apiData,
serieAlternativeNames,
episodes,
]);
DEBUG && console.log(serieAlternativeNames);
if (serieAlternativeNames !== undefined) {
serieAlternativeNames = serieAlternativeNames
.map((e) => e.name)
.join("<br /> ");
}
///console.log(serieAlternativeNames);
let serieEpisodeCount;
if (episodes && episodes.length) {
serieEpisodeCount = episodes.length.toString();
}
let targetImageUrl;
if (apiData.image.medium) targetImageUrl = apiData.image.medium;
else targetImageUrl = apiData.image.original;
let serieRating;
if (apiData.rating && apiData.rating.average) {
serieRating = apiData.rating.average.toString();
}
/*
let cData = {
url: targetImageUrl,
title: apiData.name,
alternativeNames: serieAlternativeNames,
votes: serieRating,
//status: apiData.status, //status property value is overwritten by GM_xmlhttpRequest
chapters: serieEpisodeCount,
genre: apiData.genres,
description: apiData.summary,
};
*/
let cData = Object.assign({}, emptyCoverData);
cData.url = targetImageUrl;
cData.title = apiData.name;
cData.alternativeNames = serieAlternativeNames;
cData.votes = serieRating;
//cData.status = status; //status property value is overwritten by GM_xmlhttpRequest
cData.chapters = serieEpisodeCount;
cData.genre = apiData.genres;
//cData.showTags = serieTags;
cData.description = apiData.summary;
DEBUG && console.log(cData);
return cData;
}
return undefined;
}
//#endregion http://api.tvmaze.com/
//#region https://www.wlnupdates.com/api-docs
async function getCoverDataFromWLNupdates(linkID) {
const DEBUG = false;
if (linkID) {
let apiData = await requestDataFromAPIpostCall(
"https://www.wlnupdates.com/api",
{ id: linkID, mode: "get-series-id" },
"data"
);
DEBUG && console.log(apiData);
let imagelink;
if (apiData.covers.length > 0) imagelink = apiData.covers[0].url;
let serieTitle = apiData.title;
let serieAlternativeNames = apiData.alternatenames.join("<br />");
let serieVotes;
let serieStatus;
if (apiData.ratin_count > 0) serieVotes = apiData.rating.avg;
if (apiData.most_recent) {
serieStatus = "most_recent: " + apiData.most_recent;
}
//console.log(apiData.releases);
//console.log("apiData.releases: " + apiData.releases.length);
let serieChapters;
if (apiData && apiData.releases && apiData.releases.length) {
serieChapters = apiData.releases.length;
}
if (apiData.type) serieChapters += " (" + apiData.type + ")";
let serieGenre = apiData.genres.map((e) => e.genre).join(", ");
let serieShowtags = apiData.tags.map((e) => e.tag).join(", ");
let serieDescription = apiData.description;
let cData = Object.assign({}, emptyCoverData);
cData.url = imagelink;
cData.title = serieTitle;
cData.alternativeNames = serieAlternativeNames;
cData.votes = serieVotes;
cData.status = serieStatus;
cData.chapters = serieChapters;
cData.genre = serieGenre;
cData.showTags = serieShowtags;
cData.description = serieDescription;
DEBUG && console.log(cData);
return cData;
}
return undefined;
}
//#endregion https://www.wlnupdates.com/api-docs
//#region getCoverData from parsing document of url
async function getCoverDataFromParsingTargetUrl(
elementUrl,
targetPage,
isExternal = false
) {
const DEBUG = false;
if (targetPage) {
DEBUG &&
console.log(
"getCoverDataFromParsingTargetUrl - elementUrl: " + elementUrl
);
let domDocument = await getDataFromAPI(elementUrl, {
responsetype: "text",
});
DEBUG && console.log(domDocument);
//getData from parsed site by query selectors
let temp;
let imagelink;
let containerNumber = 0;
let serieTitle;
let serieAlternativeNames;
let serieVotes;
let serieStatus;
let serieChapters;
let serieGenre;
let serieShowtags;
let serieDescription;
let serieReadingListIcon, serieReadingListTitle;
//#region general selectors for internal and external sites
DEBUG && console.group("internal/external link selectors");
DEBUG && console.log("targetPage: " + targetPage);
//console.log(domDocument);
//external links
//console.log(targetPage);
temp = domDocument.querySelectorAll(targetPage.IMAGELINKCONTAINERS);
DEBUG && console.log(temp);
if (targetPage.CONTAINERNUMBER) {
containerNumber = targetPage.CONTAINERNUMBER;
}
imagelink = temp[containerNumber];
//console.log(imagelink)
serieTitle = domDocument.querySelector(targetPage.seriePageTitle);
serieAlternativeNames = domDocument.querySelector(
targetPage.serieAlternativeNames
);
serieVotes = domDocument.querySelector(targetPage.seriePageVotes);
serieStatus = domDocument.querySelector(targetPage.seriePageStatus);
serieChapters = domDocument.querySelector(targetPage.seriePageChapters);
serieGenre = domDocument.querySelector(targetPage.seriePageGenre);
serieShowtags = domDocument.querySelector(targetPage.seriePageTags);
serieDescription = domDocument.querySelector(
targetPage.seriePageDescription
);
serieTitle = tryToGetTextContent(
serieTitle,
targetPage.seriePageTitle,
"seriePageTitle"
);
serieAlternativeNames = tryToGetTextContent(
serieAlternativeNames,
targetPage.serieAlternativeNames,
"serieAlternativeNames"
);
//console.log(targetPage.seriePageVotes)
serieVotes = tryToGetTextContent(
serieVotes,
targetPage.seriePageVotes,
"seriePageVotes"
);
//console.log(serieVotes)
serieStatus = tryToGetTextContent(
serieStatus,
targetPage.seriePageStatus,
"seriePageStatus"
);
serieChapters = tryToGetTextContent(
serieChapters,
targetPage.seriePageChapters,
"seriePageChapters"
);
//console.log(targetPage.seriePageGenre)
serieGenre = tryToGetTextContent(
serieGenre,
targetPage.seriePageGenre,
"seriePageGenre"
);
//console.log(serieGenre)
serieShowtags = tryToGetTextContent(
serieShowtags,
targetPage.seriePageTags,
"seriePageTags"
);
serieDescription = tryToGetTextContent(
serieDescription,
targetPage.seriePageDescription,
"seriePageDescription"
);
DEBUG && console.groupEnd("internal/external link selectors");
//#endregion general selectors for internal and external sites
//#region internal extra selectors
DEBUG && console.group("internal extra selectors");
if (!isExternal) {
//hasInternalTargetPage- > targetPage
//console.log(serieReadingListTitle)
if (useReadingListIconAndTitle && targetPage.serieReadingListIcon) {
serieReadingListIcon = domDocument.querySelector(
targetPage.serieReadingListIcon
);
}
//console.log("hasInternalTargetPage.serieReadingListTitle: " + hasInternalTargetPage.serieReadingListTitle)
if (useReadingListIconAndTitle && targetPage.serieReadingListTitle) {
serieReadingListTitle = domDocument.querySelector(
targetPage.serieReadingListTitle
);
}
//console.log(targetPage.serieReadingListTitle)
//console.log(serieReadingListTitle)
serieReadingListTitle = tryToGetTextContent(
serieReadingListTitle,
targetPage.serieReadingListTitle,
"serieReadingListTitle"
);
if (serieReadingListTitle == "Add series to...") {
serieReadingListTitle = undefined;
}
//console.log(serieReadingListIcon)
if (
serieReadingListIcon !== undefined &&
serieReadingListIcon !== null &&
serieReadingListIcon.tagName == "IMG"
) {
serieReadingListIcon = serieReadingListIcon.getAttribute("src");
//console.log("serieReadingListIcon: " + serieReadingListIcon)
}
if (
serieReadingListIcon === null ||
serieReadingListIcon ==
"//www.novelupdates.com/wp-content/themes/ndupdates-child/js/selectico/addme.png"
) {
serieReadingListIcon = undefined;
}
//console.log("serieReadingListTitle: " + serieReadingListTitle)
}
DEBUG && console.groupEnd("internal extra selectors");
//#endregion internal extra selectors
let cData = Object.assign({}, emptyCoverData);
cData.url = imagelink;
cData.title = serieTitle;
cData.alternativeNames = serieAlternativeNames;
cData.description = serieDescription;
cData.status = serieStatus;
cData.chapters = serieChapters;
cData.genre = serieGenre;
cData.showTags = serieShowtags;
cData.votes = serieVotes;
cData.readingListIcon = serieReadingListIcon;
cData.readingListTitle = serieReadingListTitle;
DEBUG && console.log(cData);
return cData;
}
return undefined;
}
//#endregion getCoverData from parsing document of url
function main() {
const DEBUG = false;
//console.log(window.location)
currentOpenedUrl = window.location.href;
//console.log("preloadUrlRequests: " + preloadUrlRequests)
for (let i = 0; i < deactivatePreloadUrlRequestOnUrls.length; i++) {
//no need to check on each link hover
if (deactivatePreloadUrlRequestOnUrls[i] == currentOpenedUrl) {
preloadUrlRequests = false;
}
}
//console.log("preloadUrlRequests: " + preloadUrlRequests)
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");
useReadingListIconAndTitle = GM_getValue("useReadingListIconAndTitle");
showIconNextToLink = GM_getValue("showIconNextToLink");
//deactivatePreloadUrlRequestOnUrls = GM_getValue("deactivatePreloadUrlRequestOnUrls");
if (showDetails === undefined || showDetails == "undefined") {
showDetails = false;
}
if (showDescription === undefined || showDescription == "undefined") {
showDescription = false;
}
if (showSmaller === undefined || showSmaller == "undefined") {
showSmaller = false;
}
if (
useReadingListIconAndTitle === undefined ||
useReadingListIconAndTitle == "undefined"
) {
useReadingListIconAndTitle = false;
}
if (showIconNextToLink === undefined || showIconNextToLink == "undefined") {
showIconNextToLink = defaultshowIconNextToLink;
}
//#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);
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");
})();