NHentai Improved

Partially fade/remove non-english, HQ thumbnails, mark as read, subs, version grouping etc.

  1. // ==UserScript==
  2. // @name NHentai Improved
  3. // @namespace Hentiedup
  4. // @version 2.1.3
  5. // @description Partially fade/remove non-english, HQ thumbnails, mark as read, subs, version grouping etc.
  6. // @author Hentiedup
  7. // @license unlicense
  8. // @match https://nhentai.net/*
  9. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_deleteValue
  13. // @grant GM_listValues
  14. // @grant GM_addStyle
  15. // @grant GM_registerMenuCommand
  16. // @grant GM_openInTab
  17. // @icon https://i.imgur.com/1lihxY2.png
  18. // @noframes
  19. // ==/UserScript==
  20.  
  21. (() => {
  22. //#region - Assets
  23. const readImg = '<svg style="vertical-align: middle;" width="15" height="15" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book" class="svg-inline--fa fa-book fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 360V24c0-13.3-10.7-24-24-24H96C43 0 0 43 0 96v320c0 53 43 96 96 96h328c13.3 0 24-10.7 24-24v-16c0-7.5-3.5-14.3-8.9-18.7-4.2-15.4-4.2-59.3 0-74.7 5.4-4.3 8.9-11.1 8.9-18.6zM128 134c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm0 64c0-3.3 2.7-6 6-6h212c3.3 0 6 2.7 6 6v20c0 3.3-2.7 6-6 6H134c-3.3 0-6-2.7-6-6v-20zm253.4 250H96c-17.7 0-32-14.3-32-32 0-17.6 14.4-32 32-32h285.4c-1.9 17.1-1.9 46.9 0 64z"></path></svg>';
  24. const unreadImg = '<svg style="vertical-align: middle;" width="15" height="15" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="book-open" class="svg-inline--fa fa-book-open fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M542.22 32.05c-54.8 3.11-163.72 14.43-230.96 55.59-4.64 2.84-7.27 7.89-7.27 13.17v363.87c0 11.55 12.63 18.85 23.28 13.49 69.18-34.82 169.23-44.32 218.7-46.92 16.89-.89 30.02-14.43 30.02-30.66V62.75c.01-17.71-15.35-31.74-33.77-30.7zM264.73 87.64C197.5 46.48 88.58 35.17 33.78 32.05 15.36 31.01 0 45.04 0 62.75V400.6c0 16.24 13.13 29.78 30.02 30.66 49.49 2.6 149.59 12.11 218.77 46.95 10.62 5.35 23.21-1.94 23.21-13.46V100.63c0-5.29-2.62-10.14-7.27-12.99z"></path></svg>';
  25. const flagEn = "https://i.imgur.com/vSnHmmi.gif";
  26. const flagJp = "https://i.imgur.com/GlArpuS.gif";
  27. const flagCh = "https://i.imgur.com/7B55DYm.gif";
  28. //#endregion
  29.  
  30. //#region - Initialize settings
  31. //Non-english settings
  32. const remove_non_english = GM_getValue("remove_non_english", false);
  33. const partially_fade_all_non_english = GM_getValue("partially_fade_all_non_english", true);
  34. const non_english_fade_opacity = GM_getValue("non_english_fade_opacity", 0.3);
  35. //Comic reader system
  36. const comic_reader_improved_zoom = GM_getValue("comic_reader_improved_zoom", true);
  37. const remember_zoom_level = GM_getValue("remember_zoom_level", true);
  38. const zoom_level = Number(GM_getValue("zoom_level", 1.0));
  39. //Browse sections
  40. const browse_thumbnail_width = GM_getValue("browse_thumbnail_width", 0);
  41. const browse_thumnail_container_width = GM_getValue("browse_thumnail_container_width", 0);
  42. const load_high_quality_browse_thumbnails = GM_getValue("load_high_quality_browse_thumbnails", true);
  43. const infinite_load = GM_getValue("infinite_load", true);
  44. //Comic pages view
  45. const pages_thumbnail_width = GM_getValue("pages_thumbnail_width", 0);
  46. const pages_thumnail_container_width = GM_getValue("pages_thumnail_container_width", 0);
  47. const load_high_quality_pages_thumbnails = GM_getValue("load_high_quality_pages_thumbnails", true);
  48. const max_image_reload_attempts = 20; //for load_high_quality_browse_thumbnails and load_high_quality_pages_thumbnails
  49. //Mark as read system
  50. const mark_as_read_system_enabled = GM_getValue("mark_as_read_system_enabled", true);
  51. const marked_as_read_fade_opacity = GM_getValue("marked_as_read_fade_opacity", 0.3);
  52. const read_tag_font_size = GM_getValue("read_tag_font_size", 15);
  53. //Subscription system
  54. const subscription_system_enabled = GM_getValue("subscription_system_enabled", true);
  55. //Alt version grouping
  56. const version_grouping_enabled = GM_getValue("version_grouping_enabled", true);
  57. const version_grouping_filter_brackets = GM_getValue("version_grouping_filter_brackets", false);
  58. const auto_group_on_page_comics = GM_getValue("auto_group_on_page_comics", true);
  59. //#endregion
  60. //#region - native blacklist settings
  61. const remove_native_blacklisted = GM_getValue("remove_native_blacklisted", true);
  62. //#endregion
  63.  
  64. //#region - Initialize Sets (stored as strigified arrays)
  65. //Mark as read system
  66. let MARSet = new Set(TryParseJSON(GM_getValue("MARArrayString", "[]"), []));
  67. //Subsription system
  68. let SubsSet = new Set(TryParseJSON(GM_getValue("SubArrayString", "[]"), []));
  69. //#endregion
  70.  
  71. //#region - Other script global scope variables
  72. let infinite_load_isLoadingNextPage = false;
  73. const nativeBlacklist = [];
  74. //#endregion
  75.  
  76. //#region - Apply some initial stylesheets
  77. AddNonEnglishStylesheets();
  78. AddImprovedReaderZoomStylesheets();
  79. AddBrowseThumbnailStylesheets();
  80. AddPagesThumbnailStylesheets();
  81. AddMARStylesheets();
  82. AddSubscriptionStylesheets();
  83. AddAltVersionStylesheets();
  84. AddInfiniteLoadStylesheets();
  85. AddSettingsStylesheets();
  86. //#endregion
  87.  
  88. //#region - code for all pages
  89. //add tampermonkey menu item to access NHI settings
  90. GM_registerMenuCommand("Settings", () => {
  91. GM_openInTab("https://nhentai.net/nhi-settings/", { active: true });
  92. });
  93.  
  94. if (subscription_system_enabled) {
  95. //Add Subscriptions button in the header to take user to custom Subscriptions page
  96. $(".menu.right").prepend('<li title="Subscriptions"><a id="header-subs-button" href="/subscriptions/"><i class="fa fa-heartbeat"></i><span> Subscriptions</span></a></li>');
  97. }
  98.  
  99. const currentPagePath = window.location.pathname;
  100. //#endregion
  101.  
  102. //#region - code for all pages with list of comics
  103. if ($(".container.index-container, #favcontainer.container, #recent-favorites-container, #related-container").length !== 0) {
  104. //Blacklisted removal
  105. HandleBlacklistedRemoval();
  106. //Non-english handling
  107. HandleAllNonEnglishOnPage();
  108. //Mark as read system
  109. if (mark_as_read_system_enabled) {
  110. //Mark all comics on page that have been read with READ visual tag
  111. //possible problems with too long selectors? - splitting it up to chunks of 50
  112. const parts = [];
  113. for (let i = 0, count = MARSet.size; i < count; i += 50)
  114. parts.push([...MARSet].slice(i, i + 50));
  115. for (let i = 0, count = parts.length; i < count; i++) {
  116. const readPartSelector = parts[i].join("'], .cover[href='");
  117. $(`.cover[href='${readPartSelector}']`).addClass("marked-as-read").append("<div class='readTag'>READ</div>");
  118. }
  119. }
  120. //Load HQ thumbnails for all comics on page
  121. if (load_high_quality_browse_thumbnails) {
  122. $(".container.index-container > .gallery > .cover > img, #favcontainer.container > .gallery-favorite > .gallery > .cover > img, #related-container.container > .gallery > .cover > img").on("load", OnLoadCoverReplaceHQ);
  123. }
  124. //Add alt version buttons and auto group on-page comics
  125. if (version_grouping_enabled) {
  126. AddVersionGroupingButtonsToJQuerySelector($(".gallery"));
  127.  
  128. if (auto_group_on_page_comics)
  129. GroupAltVersionsOnPage();
  130. }
  131.  
  132. InfiniteLoadHandling();
  133. }
  134. //#endregion
  135.  
  136. //#region - url path specific code
  137. //Comic info page
  138. if (/^\/g\/\d+?\/$/.test(currentPagePath)) {
  139. //Mark as read system
  140. if (mark_as_read_system_enabled) {
  141. if (MARSet.has(currentPagePath)) //if item is marked as read (path == item. e.g. "/g/1234/")
  142. $(".buttons").append(`<a href="#" id="nhi-mar-button" class="btn btn-secondary btn-nhi-mark-as-unread">${unreadImg} <span style="vertical-align: middle;">Mark as unread</span></a>`); //..add unmark button
  143. else
  144. $(".buttons").append(`<a href="#" id="nhi-mar-button" class="btn btn-secondary btn-nhi-mark-as-read">${readImg} <span style="vertical-align: middle;">Mark as read</span></a>`); //...add mark button
  145.  
  146. AddMARClickListeners();
  147. }
  148. //Subscriptions system
  149. if (subscription_system_enabled) {
  150. $(".tag").each((i, el) => {
  151. if (SubsSet.has($(el).attr("href")))
  152. $(el).addClass("subbedTag");
  153. });
  154. }
  155. //HQ thumbnails
  156. if (load_high_quality_pages_thumbnails) {
  157. $("#thumbnail-container .thumb-container > .gallerythumb > img")?.on("load", OnLoadCoverReplaceHQ);
  158. }
  159. }
  160. //Comic reader page
  161. else if (comic_reader_improved_zoom && /^\/g\/\d+?\/\d+?\/$/.test(currentPagePath)) {
  162. HandleReaderImprovedZoom();
  163. }
  164. //User pages
  165. else if (subscription_system_enabled && currentPagePath.startsWith("/users/")) {
  166. //User page
  167. const pageUserName = $(".user-info > h1")?.text()?.trim();
  168. //current page user == current logged in user
  169. if (pageUserName && pageUserName === $("nav ul.menu.right a[href^='/users/']")?.text()?.replace(/<.+?>/g, "")?.trim()) {
  170. $(".user-info > div").before(`<p><b>Comics marked as read: </b>${MARSet.size}</p>`); //add number of comics read to page
  171. $("#user-container > .user-info a[href$='/edit']").before(`<a href="/nhi-settings/" id="nhi-nh-settings-button" type="button" class="btn btn-primary"><img src="https://i.imgur.com/TI45bnl.png" /><span>NHI Settings<span/></a>`);
  172. }
  173. }
  174. //Subscriptions page
  175. else if (currentPagePath === "/subscriptions/") {
  176. RenderSubscriptionsPage();
  177. }
  178. //NHI Settings page
  179. else if (currentPagePath === "/nhi-settings/") {
  180. RenderNHISettingsPage();
  181. }
  182. //artist/group/tag/language/category pages
  183. else if (currentPagePath.startsWith("/artist/") || currentPagePath.startsWith("/group/") || currentPagePath.startsWith("/tag/") || currentPagePath.startsWith("/language/") || currentPagePath.startsWith("/category/")) {
  184. //if subscribed
  185. if (SubsSet.has(currentPagePath.split("popular")[0])) //on these pages, the path before anything starting with "popular" should always match the saved value
  186. $("h1:first").append('<a href="#" id="nhi-subscribe-button" class="btn btn-secondary btn-nhi-unsub"><span style="vertical-align: middle;">Unsubscribe</span></a>'); //...add unsub button
  187. else
  188. $("h1:first").append('<a href="#" id="nhi-subscribe-button" class="btn btn-secondary btn-nhi-sub"><span style="vertical-align: middle;">Subscribe</span></a>'); //...add sub button
  189.  
  190. AddSubsClickListeners();
  191. }
  192. //#endregion
  193.  
  194.  
  195.  
  196.  
  197. //#region - FUNCTIONS
  198.  
  199. //#region - Misc functions
  200. function TryParseJSON(jsonString, defaultValue = null) {
  201. try {
  202. return JSON.parse(jsonString);
  203. } catch {
  204. return defaultValue;
  205. }
  206. }
  207. //#endregion
  208.  
  209. //#region - Blacklist related functions
  210. function HandleBlacklistedRemoval() {
  211. if (remove_native_blacklisted) {
  212. //remove all on-page comics marked as blacklisted
  213. $(".gallery.blacklisted").remove();
  214.  
  215. //Record list of blacklisted tags. Needed for infinite load.
  216. if (infinite_load) {
  217. //The native blacklisted tags can be found on one of the on page inline scripts...
  218. const scripts = $("script:not([src])");
  219. let tags = [];
  220. for (script in scripts) {
  221. tags = $(script)?.html()?.split("blacklisted_tags: [")?.[1]?.split("]")?.[0];
  222. if (tags)
  223. break;
  224. }
  225. if (tags) {
  226. try {
  227. nativeBlacklist.push(...JSON.parse(`[${tags}]`));
  228. console.log(nativeBlacklist);
  229. } catch {
  230. console.log("NHI: failed to parse blacklisted tags");
  231. }
  232. }
  233. else {
  234. console.log("NHI: failed find blacklisted tags");
  235. }
  236. }
  237. }
  238. }
  239. //#endregion
  240.  
  241. //#region - Settings related functions
  242. function AddSettingsStylesheets() {
  243. GM_addStyle(`
  244. #nhi-nh-settings-button {
  245. background-color: #1b2a37;
  246. display: inline-flex;
  247. align-items: center;
  248. justify-content: center;
  249. gap: 8px;
  250. padding: 8px 16px;
  251. cursor: pointer;
  252. transition: color 0.3s ease, filter 0.3s ease;
  253. }
  254. #nhi-nh-settings-button:hover,
  255. #nhi-nh-settings-button:active {
  256. color: #ed2553;
  257. filter: brightness(1.4);
  258. }
  259. #nhi-nh-settings-button > img {
  260. height: 25px;
  261. }
  262.  
  263. .nhi-settings-page {
  264. display: flex;
  265. justify-content: center;
  266. flex-wrap: wrap;
  267. gap: 10px;
  268. max-width: 100%;
  269. }
  270. .nhi-settings-page .nhi-settings-section {
  271. text-align: left;
  272. display: block;
  273. padding: 5px;
  274. }
  275. .nhi-settings-page .nhi-settings-section.nhi-settings-save-section {
  276. padding-top: 15px;
  277. display: flex;
  278. align-items: center;
  279. }
  280. .nhi-settings-page #saveButt {
  281. background-color: green;
  282. margin-right: 10px;
  283. }
  284. .nhi-settings-page .nhi-settings-export-container .nhi-settings-section {
  285. padding: 0;
  286. }
  287. .nhi-settings-page .nhi-settings-section h2 {
  288. margin: 5px 0;
  289. }
  290. .nhi-settings-page .nhi-settings-section label {
  291. margin: 5px;
  292. }
  293. .nhi-settings-page .nhi-settings-section label input {
  294. margin-right: 5px;
  295. }
  296. .nhi-settings-page .nhi-settings-section input[type="number"],
  297. .nhi-settings-page .nhi-settings-section input[type="text"] {
  298. width: 80px;
  299. height: 25px;
  300. border-radius: 3px;
  301. text-align: center;
  302. padding: 0;
  303. }
  304. .nhi-settings-page .nhi-settings-section sub {
  305. display: block;
  306. line-height: 1em;
  307. }
  308. .nhi-settings-page a {
  309. color: lightblue !important;
  310. text-decoration: underline;
  311. cursor: pointer;
  312. }
  313. .nhi-settings-export-container p {
  314. margin: 0;
  315. }
  316. #NHIImportFile {
  317. display: block;
  318. margin-top: 5px;
  319. }
  320. `);
  321. }
  322. function RenderNHISettingsPage() {
  323. //create page skeleton
  324. $("head title").html('NHI Settings').prop("style", "font-size: 3.5em;");
  325. $("#content").html(`
  326. <div class="nhi-settings-main">
  327. <form id="nhi-settings-form">
  328. <div class="nhi-settings-section">
  329. <h2>Non-English Settings</h2>
  330. <label><input name="remove_non_english" type="checkbox" ${remove_non_english ? "checked" : ""}><span>Remove Non-English</span></label>
  331. <label><input name="partially_fade_all_non_english" type="checkbox" ${partially_fade_all_non_english ? "checked" : ""}><span>Partially Fade Non-English</span></label>
  332. <label><input name="non_english_fade_opacity" type="number" min="0" max="1" step="0.1" placeholder="0.3 - 1.0" value="${non_english_fade_opacity}"><span>Fade Opacity</span></label>
  333. </div>
  334. <div class="nhi-settings-section">
  335. <h2>Browse Section Settings</h2>
  336. <label><input name="infinite_load" type="checkbox" ${infinite_load ? "checked" : ""}><span>Dynamically load more comics as you scroll</span></label>
  337. <label><input name="browse_thumbnail_width" type="number" min="0" placeholder="0" value="${browse_thumbnail_width}"><span>Thumbnail Width</span></label>
  338. <label><input name="browse_thumnail_container_width" type="number" min="0" placeholder="0" value="${browse_thumnail_container_width}"><span>Thumbnail Container Width</span></label>
  339. <label><input name="load_high_quality_browse_thumbnails" type="checkbox" ${load_high_quality_browse_thumbnails ? "checked" : ""}><span>HQ Thumbnails</span></label>
  340. </div>
  341. <div class="nhi-settings-section">
  342. <h2>Comic Pages Section Settings</h2>
  343. <label><input name="pages_thumbnail_width" type="number" min="0" placeholder="0" value="${pages_thumbnail_width}"><span>Thumbnail Width</span></label>
  344. <label><input name="pages_thumnail_container_width" type="number" min="0" placeholder="0" value="${pages_thumnail_container_width}"><span>Thumbnail Container Width</span></label>
  345. <label><input name="load_high_quality_pages_thumbnails" type="checkbox" ${load_high_quality_pages_thumbnails ? "checked" : ""}><span>HQ Thumbnails</span></label>
  346. </div>
  347. <div class="nhi-settings-section">
  348. <h2>Comic Reader Settings</h2>
  349. <label><input name="comic_reader_improved_zoom" type="checkbox" ${comic_reader_improved_zoom ? "checked" : ""}><span>Improved Zoom</span></label>
  350. <label><input name="remember_zoom_level" type="checkbox" ${remember_zoom_level ? "checked" : ""}><span>Remember Zoom Level</span></label>
  351. </div>
  352. <div class="nhi-settings-section">
  353. <h2>Mark As Read Settings</h2>
  354. <label><input name="mark_as_read_system_enabled" type="checkbox" ${mark_as_read_system_enabled ? "checked" : ""}><span>Enabled</span></label>
  355. <label><input name="marked_as_read_fade_opacity" type="number" min="0" max="1" step="0.1" placeholder="0.3 - 1.0" value="${marked_as_read_fade_opacity}"><span>Fade Opacity</span></label>
  356. <label><input name="read_tag_font_size" type="number" min="0" placeholder="15" value="${read_tag_font_size}"><span>Read Tag Font Size</span></label>
  357. </div>
  358. <div class="nhi-settings-section">
  359. <h2>Subscription Settings</h2>
  360. <label><input name="subscription_system_enabled" type="checkbox" ${subscription_system_enabled ? "checked" : ""}><span>Enabled</span></label>
  361. </div>
  362. <div class="nhi-settings-section">
  363. <h2>Version Grouping Settings</h2>
  364. <label><input name="version_grouping_enabled" type="checkbox" ${version_grouping_enabled ? "checked" : ""}><span>Enabled</span></label>
  365. <label style="margin-bottom: 0;"><input name="version_grouping_filter_brackets" type="checkbox" ${version_grouping_filter_brackets ? "checked" : ""}><span>Filter out normal brackets for version searches</span></label>
  366. <sub>(square brackets are always filtered out regardless of this setting)</sub>
  367. <label style="margin-bottom: 0;"><input name="auto_group_on_page_comics" type="checkbox" ${auto_group_on_page_comics ? "checked" : ""}><span>Automatically group on-page comics</span></label>
  368. <sub>(Doesn't search the site, just current page)</sub>
  369. </div>
  370. <div class="nhi-settings-section">
  371. <h2>Native Blacklist Settings</h2>
  372. <a id="adjust-blacklist-link" href="#" target="_blank">Adjust Blacklist</a>
  373. <label><input name="remove_native_blacklisted" type="checkbox" ${remove_native_blacklisted ? "checked" : ""}><span>Remove blacklisted comics</span></label>
  374. </div>
  375. <div class="nhi-settings-section nhi-settings-save-section">
  376. <input id="saveButt" style="line-height: 30px; height: 30px;" type="submit" class="btn btn-primary" value="Save" />
  377. <span id="savedIndicator" style="display: none; color: green; font-weight: 600;">Saved!</span>
  378. </div>
  379. </form>
  380. <hr />
  381. <div class="nhi-settings-export-container">
  382. <div class="nhi-settings-section">
  383. <h2 style="margin-bottom: 0;">Import/Export Settings</h2>
  384. <p>Also Imports/Exports data</p>
  385. <p>(comics marked as read and subscriptions)</p>
  386. </br>
  387. <button id="exportButt" style="line-height: 30px; height: 30px;" type="button" class="btn btn-primary">Export</button>
  388. <button id="importButt" style="line-height: 30px; height: 30px;" type="button" class="btn btn-primary">Import</button>
  389. <input id="NHIImportFile" type="file" style="display: none;">
  390. </div>
  391. </div>
  392. </div>
  393. `).addClass("nhi-settings-page").before("<h1>NHentai Improved Settings</h1>");
  394.  
  395. //set adjust-blacklist-link href
  396. $("#adjust-blacklist-link").attr("href", `${$("nav .menu.right [href^='/users/']").attr("href")}/blacklist`);
  397.  
  398. //Handle disables/fadeouts
  399. //set onChange event handlers
  400. $(".nhi-settings-section input[name='remove_non_english']").on("change", (e) => {
  401. $(".nhi-settings-section input[name='partially_fade_all_non_english']").prop("disabled", e.target.checked).closest("label").fadeTo(200, e.target.checked ? 0.5 : 1);
  402. $(".nhi-settings-section input[name='non_english_fade_opacity']").prop("disabled", e.target.checked).closest("label").fadeTo(200, e.target.checked ? 0.5 : 1);
  403. });
  404. $(".nhi-settings-section input[name='comic_reader_improved_zoom']").on("change", (e) => {
  405. $(".nhi-settings-section input[name='remember_zoom_level']").prop("disabled", !e.target.checked).closest("label").fadeTo(200, !e.target.checked ? 0.5 : 1);
  406. });
  407. $(".nhi-settings-section input[name='mark_as_read_system_enabled']").on("change", (e) => {
  408. $(".nhi-settings-section input[name='marked_as_read_fade_opacity']").prop("disabled", !e.target.checked).closest("label").fadeTo(200, !e.target.checked ? 0.5 : 1);
  409. $(".nhi-settings-section input[name='read_tag_font_size']").prop("disabled", !e.target.checked).closest("label").fadeTo(200, !e.target.checked ? 0.5 : 1);
  410. });
  411. $(".nhi-settings-section input[name='version_grouping_enabled']").on("change", (e) => {
  412. $(".nhi-settings-section input[name='version_grouping_filter_brackets']").prop("disabled", !e.target.checked).closest("label").fadeTo(200, !e.target.checked ? 0.5 : 1).parent().find("sub").fadeTo(200, !e.target.checked ? 0.5 : 1);
  413. $(".nhi-settings-section input[name='auto_group_on_page_comics']").prop("disabled", !e.target.checked).closest("label").fadeTo(200, !e.target.checked ? 0.5 : 1).parent().find("sub").fadeTo(200, !e.target.checked ? 0.5 : 1);
  414. });
  415. //trigger all the onChange event handlers to setup initial states
  416. $(`
  417. .nhi-settings-section input[name='remove_non_english'],
  418. .nhi-settings-section input[name='comic_reader_improved_zoom'],
  419. .nhi-settings-section input[name='mark_as_read_system_enabled'],
  420. .nhi-settings-section input[name='version_grouping_enabled']
  421. `).trigger("change");
  422.  
  423. //Handle save
  424. $("#nhi-settings-form").on("submit", (e) => {
  425. e.preventDefault();
  426. const data = e.target;
  427. GM_setValue("remove_non_english", data.remove_non_english.checked);
  428. GM_setValue("partially_fade_all_non_english", data.partially_fade_all_non_english.checked);
  429. GM_setValue("non_english_fade_opacity", data.non_english_fade_opacity.value);
  430. GM_setValue("infinite_load", data.infinite_load.checked);
  431. GM_setValue("browse_thumbnail_width", data.browse_thumbnail_width.value);
  432. GM_setValue("browse_thumnail_container_width", data.browse_thumnail_container_width.value);
  433. GM_setValue("load_high_quality_browse_thumbnails", data.load_high_quality_browse_thumbnails.checked);
  434. GM_setValue("pages_thumbnail_width", data.pages_thumbnail_width.value);
  435. GM_setValue("pages_thumnail_container_width", data.pages_thumnail_container_width.value);
  436. GM_setValue("load_high_quality_pages_thumbnails", data.load_high_quality_pages_thumbnails.checked);
  437. GM_setValue("comic_reader_improved_zoom", data.comic_reader_improved_zoom.checked);
  438. GM_setValue("remember_zoom_level", data.remember_zoom_level.checked);
  439. GM_setValue("mark_as_read_system_enabled", data.mark_as_read_system_enabled.checked);
  440. GM_setValue("marked_as_read_fade_opacity", data.marked_as_read_fade_opacity.value);
  441. GM_setValue("read_tag_font_size", data.read_tag_font_size.value);
  442. GM_setValue("subscription_system_enabled", data.subscription_system_enabled.checked);
  443. GM_setValue("version_grouping_enabled", data.version_grouping_enabled.checked);
  444. GM_setValue("version_grouping_filter_brackets", data.version_grouping_filter_brackets.checked);
  445. GM_setValue("auto_group_on_page_comics", data.auto_group_on_page_comics.checked);
  446. GM_setValue("remove_native_blacklisted", data.remove_native_blacklisted.checked);
  447.  
  448. $("#savedIndicator").hide().fadeIn(200, function () {
  449. setTimeout(() => {
  450. $(this).fadeOut(200);
  451. }, 5000);
  452. });
  453. });
  454.  
  455. //Handle Export
  456. $("#exportButt").click((e) => {
  457. e.preventDefault();
  458. const jsonObj = {
  459. "remove_non_english": GM_getValue("remove_non_english", false),
  460. "partially_fade_all_non_english": GM_getValue("partially_fade_all_non_english", true),
  461. "non_english_fade_opacity": GM_getValue("non_english_fade_opacity", 0.3),
  462. "infinite_load": GM_getValue("infinite_load", true),
  463. "browse_thumbnail_width": GM_getValue("browse_thumbnail_width", 0),
  464. "browse_thumnail_container_width": GM_getValue("browse_thumnail_container_width", 0),
  465. "load_high_quality_browse_thumbnails": GM_getValue("load_high_quality_browse_thumbnails", true),
  466. "pages_thumbnail_width": GM_getValue("pages_thumbnail_width", 0),
  467. "pages_thumnail_container_width": GM_getValue("pages_thumnail_container_width", 0),
  468. "load_high_quality_pages_thumbnails": GM_getValue("load_high_quality_pages_thumbnails", true),
  469. "comic_reader_improved_zoom": GM_getValue("comic_reader_improved_zoom", true),
  470. "remember_zoom_level": GM_getValue("remember_zoom_level", true),
  471. "mark_as_read_system_enabled": GM_getValue("mark_as_read_system_enabled", true),
  472. "marked_as_read_fade_opacity": GM_getValue("marked_as_read_fade_opacity", 0.3),
  473. "read_tag_font_size": GM_getValue("read_tag_font_size", 15),
  474. "subscription_system_enabled": GM_getValue("subscription_system_enabled", true),
  475. "version_grouping_enabled": GM_getValue("version_grouping_enabled", true),
  476. "version_grouping_filter_brackets": GM_getValue("version_grouping_filter_brackets", false),
  477. "auto_group_on_page_comics": GM_getValue("auto_group_on_page_comics", true),
  478. "remove_native_blacklisted": GM_getValue("remove_native_blacklisted", true),
  479. "MARArrayString": GM_getValue("MARArrayString", "[]"),
  480. "SubArrayString": GM_getValue("SubArrayString", "[]")
  481. };
  482. SaveToFile(`NHI-Backup_${new Date().toISOString().replace(/:/g, "-")}.nhi2`, JSON.stringify(jsonObj, null, 2));
  483. });
  484.  
  485. //Handle Import
  486. $("#importButt").click((e) => {
  487. e.preventDefault();
  488. $("#NHIImportFile").animate({ height: 'toggle' }, 200);
  489. });
  490. $("#NHIImportFile").change((event) => {
  491. if (typeof window.FileReader !== 'function')
  492. throw ("The file API isn't supported on this browser.");
  493. const input = event.target;
  494. if (!input)
  495. throw ("The browser does not properly implement the event object");
  496. if (!input.files)
  497. throw ("This browser does not support the `files` property of the file input.");
  498.  
  499. const file = input.files[0];
  500. if (!file.name.endsWith(".nhi") && !file.name.endsWith(".nhi2")) {
  501. $("#NHIImportFile").val('');
  502. throw ("Invalid file extension");
  503. }
  504.  
  505. const fr = new FileReader();
  506. fr.onload = (ev) => {
  507. const importedData = ev.target.result;
  508. if (importedData != null && confirm("File received. Import this file?")) {
  509. if (file.name.endsWith(".nhi2"))
  510. ImportNewNHIFile(importedData);
  511. else
  512. ImportOldNHIFile(importedData);
  513. }
  514. else
  515. $("#NHIImportFile").val('');
  516. };
  517. fr.readAsText(file);
  518. });
  519. }
  520. function ImportNewNHIFile(dataAsString) {
  521. const jsonObj = JSON.parse(dataAsString);
  522. GM_setValue("remove_non_english", jsonObj.remove_non_english);
  523. GM_setValue("partially_fade_all_non_english", jsonObj.partially_fade_all_non_english);
  524. GM_setValue("non_english_fade_opacity", jsonObj.non_english_fade_opacity);
  525. GM_setValue("infinite_load", jsonObj.infinite_load);
  526. GM_setValue("browse_thumbnail_width", jsonObj.browse_thumbnail_width);
  527. GM_setValue("browse_thumnail_container_width", jsonObj.browse_thumnail_container_width);
  528. GM_setValue("load_high_quality_browse_thumbnails", jsonObj.load_high_quality_browse_thumbnails);
  529. GM_setValue("pages_thumbnail_width", jsonObj.pages_thumbnail_width);
  530. GM_setValue("pages_thumnail_container_width", jsonObj.pages_thumnail_container_width);
  531. GM_setValue("load_high_quality_pages_thumbnails", jsonObj.load_high_quality_pages_thumbnails);
  532. GM_setValue("comic_reader_improved_zoom", jsonObj.comic_reader_improved_zoom);
  533. GM_setValue("remember_zoom_level", jsonObj.remember_zoom_level);
  534. GM_setValue("mark_as_read_system_enabled", jsonObj.mark_as_read_system_enabled);
  535. GM_setValue("marked_as_read_fade_opacity", jsonObj.marked_as_read_fade_opacity);
  536. GM_setValue("read_tag_font_size", jsonObj.read_tag_font_size);
  537. GM_setValue("subscription_system_enabled", jsonObj.subscription_system_enabled);
  538. GM_setValue("version_grouping_enabled", jsonObj.version_grouping_enabled);
  539. GM_setValue("version_grouping_filter_brackets", jsonObj.version_grouping_filter_brackets);
  540. GM_setValue("auto_group_on_page_comics", jsonObj.auto_group_on_page_comics);
  541. GM_setValue("remove_native_blacklisted", jsonObj.remove_native_blacklisted);
  542. GM_setValue("MARArrayString", jsonObj.MARArrayString);
  543. GM_setValue("SubArrayString", jsonObj.SubArrayString);
  544.  
  545. location.reload();
  546. }
  547. function ImportOldNHIFile(dataAsString) {
  548. const dataString = dataAsString.replace(/(\r?\n|\r)/g, "").trim(); //remove newlines and trim
  549.  
  550. if (dataString.indexOf("|||||") < 0) {
  551. alert("invalid data");
  552. return;
  553. }
  554.  
  555. const dataArr = dataString.split("|||||");
  556.  
  557. if (dataArr.length > 0) {
  558. GM_setValue("SubArrayString", dataArr[0]);
  559. console.log("NHI - SubArrayString imported");
  560. }
  561. if (dataArr.length > 1) {
  562. GM_setValue("MARArrayString", dataArr[1]);
  563. console.log("NHI - MARArrayString imported");
  564. }
  565. if (dataArr.length > 2) {
  566. GM_setValue("BlockTagArrayString", dataArr[2]);
  567. console.log("NHI - BlockTagArrayString imported");
  568. }
  569.  
  570. if (dataArr.length > 3) {
  571. GM_setValue("remove_non_english", (String(dataArr[3]) == "true"));
  572. console.log("NHI - remove_non_english imported");
  573. }
  574. if (dataArr.length > 4) {
  575. GM_setValue("partially_fade_all_non_english", (String(dataArr[4]) == "true"));
  576. console.log("NHI - partially_fade_all_non_english imported");
  577. }
  578. if (dataArr.length > 5) {
  579. GM_setValue("non_english_fade_opacity", dataArr[5]);
  580. console.log("NHI - non_english_fade_opacity imported");
  581. }
  582.  
  583. if (dataArr.length > 6) {
  584. GM_setValue("load_high_quality_browse_thumbnails", (String(dataArr[6]) == "true"));
  585. console.log("NHI - load_high_quality_browse_thumbnails imported");
  586. }
  587. if (dataArr.length > 7) {
  588. GM_setValue("browse_thumbnail_width", dataArr[7]);
  589. console.log("NHI - browse_thumbnail_width imported");
  590. }
  591. if (dataArr.length > 8) {
  592. GM_setValue("browse_thumnail_container_width", dataArr[8]);
  593. console.log("NHI - browse_thumnail_container_width imported");
  594. }
  595.  
  596. if (dataArr.length > 9) {
  597. GM_setValue("load_high_quality_pages_thumbnails", (String(dataArr[9]) == "true"));
  598. console.log("NHI - load_high_quality_pages_thumbnails imported");
  599. }
  600. if (dataArr.length > 10) {
  601. GM_setValue("pages_thumbnail_width", dataArr[10]);
  602. console.log("NHI - pages_thumbnail_width imported");
  603. }
  604. if (dataArr.length > 11) {
  605. GM_setValue("pages_thumnail_container_width", dataArr[11]);
  606. console.log("NHI - pages_thumnail_container_width imported");
  607. }
  608.  
  609. if (dataArr.length > 12) {
  610. GM_setValue("max_image_reload_attempts", dataArr[12]);
  611. console.log("NHI - max_image_reload_attempts imported");
  612. }
  613.  
  614. if (dataArr.length > 13) {
  615. GM_setValue("mark_as_read_system_enabled", (String(dataArr[13]) == "true"));
  616. console.log("NHI - mark_as_read_system_enabled imported");
  617. }
  618. if (dataArr.length > 14) {
  619. GM_setValue("marked_as_read_fade_opacity", dataArr[14]);
  620. console.log("NHI - marked_as_read_fade_opacity imported");
  621. }
  622. if (dataArr.length > 15) {
  623. GM_setValue("read_tag_font_size", dataArr[15]);
  624. console.log("NHI - read_tag_font_size imported");
  625. }
  626.  
  627. if (dataArr.length > 16) {
  628. GM_setValue("subscription_system_enabled", (String(dataArr[16]) == "true"));
  629. console.log("NHI - subscription_system_enabled imported");
  630. }
  631.  
  632. if (dataArr.length > 17) {
  633. GM_setValue("version_grouping_enabled", (String(dataArr[17]) == "true"));
  634. console.log("NHI - version_grouping_enabled imported");
  635. }
  636. if (dataArr.length > 18) {
  637. GM_setValue("version_grouping_filter_brackets", (String(dataArr[18]) == "true"));
  638. console.log("NHI - version_grouping_filter_brackets imported");
  639. }
  640. if (dataArr.length > 19) {
  641. GM_setValue("auto_group_on_page_comics", (String(dataArr[19]) == "true"));
  642. console.log("NHI - auto_group_on_page_comics imported");
  643. }
  644.  
  645. if (dataArr.length > 20) {
  646. GM_setValue("comic_reader_improved_zoom", (String(dataArr[20]) == "true"));
  647. console.log("NHI - comic_reader_improved_zoom imported");
  648. }
  649. if (dataArr.length > 21) {
  650. GM_setValue("remember_zoom_level", (String(dataArr[21]) == "true"));
  651. console.log("NHI - remember_zoom_level imported");
  652. }
  653. if (dataArr.length > 22) {
  654. GM_setValue("zoom_level", Number(dataArr[22]));
  655. console.log("NHI - zoom_level imported");
  656. }
  657. if (dataArr.length > 23) {
  658. GM_setValue("tag_blocking_enabled", (String(dataArr[23]) == "true"));
  659. console.log("NHI - tag_blocking_enabled imported");
  660. }
  661. if (dataArr.length > 24) {
  662. GM_setValue("tag_blocking_fade_only", (String(dataArr[24]) == "true"));
  663. console.log("NHI - tag_blocking_fade_only imported");
  664. }
  665. if (dataArr.length > 25) {
  666. GM_setValue("infinite_load", (String(dataArr[25]) == "true"));
  667. console.log("NHI - infinite_load imported");
  668. }
  669. location.reload();
  670. }
  671. function SaveToFile(filename, dataString) {
  672. const blob = new Blob([dataString], { type: 'application/json' });
  673. if (window.navigator.msSaveOrOpenBlob) {
  674. window.navigator.msSaveBlob(blob, filename);
  675. }
  676. else {
  677. const elem = window.document.createElement('a');
  678. elem.href = window.URL.createObjectURL(blob);
  679. elem.download = filename;
  680. document.body.appendChild(elem);
  681. elem.click();
  682. document.body.removeChild(elem);
  683. }
  684. }
  685. //#endregion
  686.  
  687. //#region - Mark as read system related functions
  688. function AddMARStylesheets() {
  689. if (mark_as_read_system_enabled) {
  690. GM_addStyle(`
  691. .marked-as-read > img, .marked-as-read > .caption {
  692. opacity: ${marked_as_read_fade_opacity};
  693. }
  694.  
  695. .readTag {
  696. border-radius: 10px;
  697. padding: 5px 10px;
  698. position: absolute;
  699. background-color: rgba(0,0,0,.7);
  700. color: rgba(255,255,255,.8);
  701. bottom: 7.5px;
  702. right: 7.5px;
  703. font-size: ${read_tag_font_size}px;
  704. font-weight: 900;
  705. opacity: 1;
  706. }
  707.  
  708. #info >.buttons > .btn-nhi-mark-as-read, #info >.buttons > .btn-nhi-mark-as-read:visited {
  709. background-color: #3d9e48;
  710. }
  711. #info >.buttons > .btn-nhi-mark-as-read:active, #info >.buttons > .btn-nhi-mark-as-read:hover {
  712. background-color: #52bc5e;
  713. }
  714.  
  715. #info >.buttons > .btn-nhi-mark-as-unread, #info >.buttons > .btn-nhi-mark-as-unread:visited {
  716. background-color: rgb(218, 53, 53);
  717. }
  718. #info >.buttons > .btn-nhi-mark-as-unread:active, #info >.buttons > .btn-nhi-mark-as-unread:hover {
  719. background-color: #e26060;
  720. }
  721.  
  722. .gallery {
  723. position: relative;
  724. }
  725. `);
  726. }
  727. }
  728. function AddMARClickListeners() {
  729. $("#nhi-mar-button").click(function () {
  730. //check if we are adding or deleting
  731. const markingAsRead = $(this).hasClass("btn-nhi-mark-as-read");
  732.  
  733. //get the array again to make sure we have an up to date array (since other tabs could have modified it since loading this page)
  734. MARSet = new Set(TryParseJSON(GM_getValue("MARArrayString", "[]"), []));
  735.  
  736. //add or delete
  737. if (markingAsRead)
  738. MARSet.add(currentPagePath);
  739. else
  740. MARSet.delete(currentPagePath);
  741.  
  742. //save changes
  743. const MARArrayString = JSON.stringify([...MARSet]); //covert set to array string
  744. GM_setValue("MARArrayString", MARArrayString); //save string
  745.  
  746. //update button
  747. if (markingAsRead)
  748. $(this).html(`${unreadImg} <span style="vertical-align: middle;">Mark as unread</span>`).removeClass('btn-nhi-mark-as-read').addClass('btn-nhi-mark-as-unread');
  749. else
  750. $(this).html(`${readImg} <span style="vertical-align: middle;">Mark as read</span>`).removeClass('btn-nhi-mark-as-unread').addClass('btn-nhi-mark-as-read');
  751. });
  752. }
  753. //#endregion
  754.  
  755. //#region - Infinite load related functions
  756. function AddInfiniteLoadStylesheets() {
  757. if (infinite_load) {
  758. GM_addStyle(`
  759. #NHI_loader_icon {
  760. height: 355px;
  761. line-height: 355px;
  762. }
  763. #NHI_loader_icon > div {
  764. display: inline-flex;
  765. }
  766. .loader {
  767. color: #ed2553;
  768. font-size: 10px;
  769. width: 1em;
  770. height: 1em;
  771. border-radius: 50%;
  772. position: relative;
  773. text-indent: -9999em;
  774. animation: mulShdSpin 1.3s infinite linear;
  775. transform: translateZ(0);
  776. }
  777.  
  778. @keyframes mulShdSpin {
  779. 0%,
  780. 100% {
  781. box-shadow: 0 -3em 0 0.2em,
  782. 2em -2em 0 0em, 3em 0 0 -1em,
  783. 2em 2em 0 -1em, 0 3em 0 -1em,
  784. -2em 2em 0 -1em, -3em 0 0 -1em,
  785. -2em -2em 0 0;
  786. }
  787. 12.5% {
  788. box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em,
  789. 3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em,
  790. -2em 2em 0 -1em, -3em 0 0 -1em,
  791. -2em -2em 0 -1em;
  792. }
  793. 25% {
  794. box-shadow: 0 -3em 0 -0.5em,
  795. 2em -2em 0 0, 3em 0 0 0.2em,
  796. 2em 2em 0 0, 0 3em 0 -1em,
  797. -2em 2em 0 -1em, -3em 0 0 -1em,
  798. -2em -2em 0 -1em;
  799. }
  800. 37.5% {
  801. box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
  802. 3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em,
  803. -2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
  804. }
  805. 50% {
  806. box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
  807. 3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em,
  808. -2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
  809. }
  810. 62.5% {
  811. box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
  812. 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0,
  813. -2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
  814. }
  815. 75% {
  816. box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em,
  817. 3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em,
  818. -2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
  819. }
  820. 87.5% {
  821. box-shadow: 0em -3em 0 0, 2em -2em 0 -1em,
  822. 3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em,
  823. -2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
  824. }
  825. }
  826. `);
  827. }
  828. }
  829. function InfiniteLoadHandling() {
  830. /*TODO:
  831. - Handle auto version grouping...
  832. Sometimes there are comics that are named something overly simple that causes tons of others to get grouped with it erroneously.
  833. Infinite load would make this issue way worse... How to handle this...
  834. Could group comcis only on a page by page basis.
  835. - Handle Favorites page -> infinite load disabled there for now
  836. */
  837. if (infinite_load) {
  838. //if found paginator on page (also, specifically not enabled on favorites page for now)
  839. const paginator = $(".pagination");
  840. if (paginator?.length && currentPagePath !== "/favorites/") {
  841. const lastPageNum = Number.parseInt(paginator.find(".last").attr("href").split("page=")[1]);
  842. //build the fetch url without the page number
  843. const queryWithNoPage = window.location.search.replace(/[\?\&]page=\d+/, "").replace(/^\&/, "?");
  844. const finalUrlWithoutPageNum = `${window.location.pathname + queryWithNoPage + (queryWithNoPage.length ? "&" : "?")}page=`;
  845.  
  846. //on scroll event,
  847. $(window).scroll(() => {
  848. //if we are near page bottom,
  849. if ($(window).scrollTop() + (window.visualViewport?.height || $(window).height()) >= $(document).height() - 15) {
  850. const loadingPageNum = Number.parseInt($(".pagination > .page.current:last").attr("href").split("page=")[1]) + 1;
  851. TryLoadInNextPageComics(loadingPageNum, lastPageNum, finalUrlWithoutPageNum);
  852. }
  853. });
  854.  
  855. //While page is smaller than viewport, i.e. can't be scrolled, keep loading in pages
  856. const autoLoadWhileScrollNotAvailableInterval = setInterval(() => {
  857. //clear this interval if all pages are loaded
  858. const loadingPageNum = Number.parseInt($(".pagination > .page.current:last").attr("href").split("page=")[1]) + 1;
  859. if (loadingPageNum > lastPageNum) {
  860. clearInterval(autoLoadWhileScrollNotAvailableInterval);
  861. }
  862. else {
  863. const doc = document.documentElement;
  864. if (doc.scrollHeight <= doc.clientHeight) {
  865. TryLoadInNextPageComics(loadingPageNum, lastPageNum, finalUrlWithoutPageNum);
  866. }
  867. }
  868. }, 200);
  869. }
  870. }
  871. }
  872. function TryLoadInNextPageComics(pageNumToLoad, lastPageNum, fetchUrlWithoutPageNum, retryNum = 0, maxFetchAttempts = 5) {
  873. //Do not start another load if one is already running (retry attempts are let through)
  874. if (retryNum === 0 && infinite_load_isLoadingNextPage)
  875. return;
  876. //don't try to load from a page > lastpage
  877. if (pageNumToLoad > lastPageNum)
  878. return;
  879.  
  880. //console.log(`NHI infinite load - starting page ${pageNumToLoad} load`);
  881. infinite_load_isLoadingNextPage = true;
  882.  
  883. //add loader icon visual
  884. $(".index-container:not(.advertisement, .index-popular)").first().append('<div id="NHI_loader_icon" class="gallery"><div><span class="loader"></span></div></div>');
  885.  
  886. $.get({
  887. url: fetchUrlWithoutPageNum + pageNumToLoad,
  888. dataType: "html"
  889. }, (data) => {
  890. //for each comic fetched
  891. $(data).find("div.gallery").each((i, el) => {
  892. //blacklist handling
  893. if (remove_native_blacklisted) {
  894. //probably won't work, but if the comic has blacklisted class, don't add it
  895. if ($(el).hasClass("blacklisted"))
  896. return;
  897. //check if this comic has any blacklisted tags and if so, don't add it
  898. const tags = $(el).attr("data-tags").trim().split(" ");
  899. if (nativeBlacklist.some(nblTag => tags.includes(String(nblTag)))) {
  900. return;
  901. }
  902. }
  903.  
  904. //if already on page (excluding the page 1 popular section), don't add again
  905. if ($(`.container:not(.index-popular) .cover[href='${$(el).find(".cover").attr("href")}']`).length)
  906. return;
  907.  
  908. //If removing or fading non-english
  909. if (remove_non_english || partially_fade_all_non_english) {
  910. //Mark with english class/tag + if removing non-english...
  911. if (!MarkIfEnglish($(el)) && remove_non_english) {
  912. return; //...don't insert this comic
  913. }
  914. }
  915.  
  916. //set thumbnail src = data-src
  917. $(el).find("img").attr("src", $(el).find("img").attr("data-src"));
  918.  
  919. //HQ thumbnail onLoad
  920. if (load_high_quality_browse_thumbnails)
  921. $(el).find(".cover > img").on("load", OnLoadCoverReplaceHQ);
  922.  
  923. //check if read, and mark as such
  924. if (mark_as_read_system_enabled) {
  925. const cover = $(el).find("a.cover");
  926. const item = cover.attr("href");
  927. if (MARSet.has(item))
  928. cover.addClass("marked-as-read").append("<div class='readTag'>READ</div>");
  929. }
  930.  
  931. //add version grouping buttons
  932. if (version_grouping_enabled)
  933. AddVersionGroupingButtonsToJQuerySelector($(el));
  934.  
  935. //finally add the modified comic on to page
  936. $(".index-container:not(.advertisement, .index-popular)").first().append(el);
  937. });
  938.  
  939. //after adding all comics from fetched page, mark that page as "current" in the paginator to clearly show the user all the pages currently loaded
  940. const paginatorItem = $(`.pagination > .page[href$='page=${pageNumToLoad}']`);
  941. if (paginatorItem?.length)
  942. paginatorItem.addClass("current");
  943. else
  944. $(".pagination > .next").before(`<a href="${fetchUrlWithoutPageNum}${pageNumToLoad}" class="page current">${pageNumToLoad}</a>`);
  945.  
  946. $("#NHI_loader_icon").remove();
  947. //console.log(`NHI infinite load - page ${pageNumToLoad} load successful`);
  948. infinite_load_isLoadingNextPage = false;
  949.  
  950. }).fail((jqXHR, textStatus, errorThrown) => {
  951. if (retryNum < maxFetchAttempts) {
  952. console.log(`NHI: Infinite load - Failed loading page ${pageNumToLoad} - ${textStatus} | ${errorThrown} - retrying... (retry ${retryNum + 1})`);
  953. TryLoadInNextPageComics(pageNumToLoad, lastPageNum, fetchUrlWithoutPageNum, retryNum + 1, 5);
  954. }
  955. else {
  956. $("#NHI_loader_icon").remove();
  957. console.log(`NHI: Infinite load - Failed loading page ${pageNumToLoad} - ${textStatus} | ${errorThrown} - Giving up after ${retryNum} retries.`);
  958. infinite_load_isLoadingNextPage = false;
  959. }
  960. });
  961.  
  962. }
  963. //#endregion
  964.  
  965. //#region - Alt version related functions
  966. function AddAltVersionStylesheets() {
  967. if (version_grouping_enabled) {
  968. GM_addStyle(`
  969. .overlayFlag
  970. {
  971. position: absolute;
  972. display: inline-block;
  973. top: 3px;
  974. left: 3px;
  975. z-index: 3;
  976. width: 18px;
  977. height: 12px;
  978. }
  979. .numOfVersions {
  980. border-radius: 10px;
  981. padding: 5px 10px;
  982. position: absolute;
  983. background-color: rgba(0,0,0,.7);
  984. color: rgba(255,255,255,.8);
  985. top: 7.5px;
  986. left: 105px;
  987. font-size: 12px;
  988. font-weight: 900;
  989. opacity: 1;
  990. width: 40px;
  991. z-index: 2;
  992. display: none;
  993. }
  994. .findVersionButton {
  995. border-radius: 10px;
  996. padding: 5px 10px;
  997. position: absolute;
  998. background-color: rgba(0,0,0,.4);
  999. color: rgba(255,255,255,.8);
  1000. bottom: 7.5px;
  1001. left: 7.5px;
  1002. font-size: 12px;
  1003. font-weight: 900;
  1004. opacity: 1;
  1005. width: 125px;
  1006. z-index: 2;
  1007. cursor: pointer;
  1008. }
  1009. .versionNextButton {
  1010. border-radius: 10px;
  1011. padding: 5px 10px;
  1012. position: absolute;
  1013. background-color: rgba(0,0,0,.7);
  1014. color: rgba(255,255,255,.8);
  1015. top: 7.5px;
  1016. right: 7.5px;
  1017. font-size: 12px;
  1018. font-weight: 900;
  1019. opacity: 1;
  1020. display: none;
  1021. z-index: 2;
  1022. cursor: pointer;
  1023. }
  1024. .versionPrevButton {
  1025. border-radius: 10px;
  1026. padding: 5px 10px;
  1027. position: absolute;
  1028. background-color: rgba(0,0,0,.7);
  1029. color: rgba(255,255,255,.8);
  1030. top: 7.5px;
  1031. left: 7.5px;
  1032. font-size: 12px;
  1033. font-weight: 900;
  1034. opacity: 1;
  1035. z-index: 2;
  1036. display: none;
  1037. cursor: pointer;
  1038. }
  1039.  
  1040. .findVersionButton:focus, .findVersionButton:hover, .findVersionButton:active,
  1041. .versionNextButton:focus, .versionNextButton:hover, .versionNextButton:active,
  1042. .versionPrevButton:focus, .versionPrevButton:hover, .versionPrevButton:active
  1043. {
  1044. background-color: rgba(50,50,50,1);
  1045. }
  1046. `);
  1047. }
  1048. }
  1049. function AddVersionGroupingButtonsToJQuerySelector(JQSelector) {
  1050. JQSelector.append([
  1051. "<div class='findVersionButton'>Find Alt Versions</div>",
  1052. "<div class='numOfVersions'>1/1</div>",
  1053. "<div class='versionNextButton'>►</div>",
  1054. "<div class='versionPrevButton'>◄</div>"
  1055. ]);
  1056. $(JQSelector).find(".findVersionButton").click(AddAltVersionsToThis);
  1057. $(JQSelector).find(".versionPrevButton").click(PrevAltVersion);
  1058. $(JQSelector).find(".versionNextButton").click(NextAltVersion);
  1059. }
  1060. function AddAltVersionsToThis(e) {
  1061. e.preventDefault();
  1062. const altVBtn = $(this);
  1063. const originalComic = $(this).parent(); //.gallery
  1064. const originalTitle = originalComic.find(".cover:visible > .caption").text();
  1065. $.get(BuildUrl(originalTitle), (data) => {
  1066. const found = $(data).find(".container > .gallery");
  1067. if (!found || found.length <= 0) {
  1068. alert("error reading data");
  1069. return;
  1070. }
  1071. originalComic.find(".cover").remove();
  1072. try {
  1073. //iterate over each alt comic found
  1074. for (let i = 0; i < found.length; i++) {
  1075. //fade or remove non-english
  1076. if (partially_fade_all_non_english || remove_non_english) {
  1077. const isEnglish = MarkIfEnglish($(found[i]));
  1078. if (!isEnglish && remove_non_english)
  1079. continue;
  1080. }
  1081. //add some missing flags
  1082. if ($(found[i]).attr("data-tags").split(" ").includes("12227")) //en
  1083. $(found[i]).find(".caption").append(`<img class="overlayFlag" src="${flagEn}">`);
  1084. else if ($(found[i]).attr("data-tags").split(" ").includes("6346")) //jp
  1085. $(found[i]).find(".caption").append(`<img class="overlayFlag" src="${flagJp}">`);
  1086. else if ($(found[i]).attr("data-tags").split(" ").includes("29963")) //ch
  1087. $(found[i]).find(".caption").append(`<img class="overlayFlag" src="${flagCh}">`);
  1088.  
  1089. //MAR
  1090. if (mark_as_read_system_enabled) {
  1091. //re-load MARSet to have up to date data
  1092. MARSet = new Set(TryParseJSON(GM_getValue("MARArrayString", "[]"), []));
  1093. const cover = $(found[i]).find(".cover");
  1094. //if MARSet has this comic, mark it as read
  1095. if (MARSet.has(cover.attr("href"))) {
  1096. cover.append("<div class='readTag'>READ</div>");
  1097. cover.addClass("marked-as-read");
  1098. }
  1099. }
  1100.  
  1101. //HQ thumbnail == Always using HQ thumbnails with Alt covers, because using normal covers would still require work to handle.
  1102. //TODO: Could add handling for non HQ covers later if I can be bothered
  1103. const coverImg = $(found[i]).find(".cover > img");
  1104. //if gallery item is missing data-src, add src to to data-src so that the generic method "ReplaceCoverImage" that relies on data-src can handle it
  1105. if (!$(coverImg).attr("data-src"))
  1106. $(coverImg).attr("data-src", $(coverImg).attr("src"));
  1107.  
  1108. ReplaceCoverImage($(coverImg));
  1109. originalComic.append($(found[i]).find(".cover"));
  1110. }
  1111. }
  1112. catch (er) {
  1113. alert(`error modifying data: ${er}`);
  1114. return;
  1115. }
  1116. originalComic.find(".cover:not(:first)").css("display", "none");
  1117. originalComic.find(".versionPrevButton, .versionNextButton, .numOfVersions").show(200);
  1118. originalComic.find(".numOfVersions").text(`1/${found.length}`);
  1119. altVBtn.hide(200);
  1120. }).fail((e) => {
  1121. alert(`error getting data: ${e}`);
  1122. });
  1123. }
  1124. function GroupAltVersionsOnPage() {
  1125. let i = 0;
  1126. let found = $(".container > .gallery");
  1127. while (!!found && i < found.length) {
  1128. AddAltVersionsToThisFromPage(found[i]);
  1129. i++;
  1130. found = $(".container > .gallery");
  1131. }
  1132. }
  1133. function AddAltVersionsToThisFromPage(target) {
  1134. const targetComic = $(target); //.gallery
  1135. targetComic.addClass("nhi-on-page-alt-ignoreThis");
  1136. const targetTitle = targetComic.find(".cover > .caption").text();
  1137. if (!targetTitle || targetTitle.length <= 0)
  1138. return;
  1139. const otherGalleries = $(".container > .gallery:not(.nhi-on-page-alt-ignoreThis)");
  1140. let numOfValid = 0;
  1141. for (let i = 0; i < otherGalleries.length; i++) //loop through galleries
  1142. {
  1143. const comicsInGallery = $(otherGalleries[i]).find(".cover");
  1144. let addAll = false;
  1145. for (let j = 0; j < comicsInGallery.length; j++) //loop through comics in the gallery
  1146. {
  1147. if (StringIncludesAllSearchTermsAfterCleanup($(comicsInGallery[j]).find(".caption").text(), targetTitle)) {
  1148. addAll = true; //if any of them match
  1149. break;
  1150. }
  1151. }
  1152. if (addAll) //if any matched
  1153. {
  1154. for (let j = 0; j < comicsInGallery.length; j++)
  1155. targetComic.append($(comicsInGallery[j])); //add all
  1156. $(otherGalleries[i]).addClass("nhi-on-page-alt-deleteThis");
  1157. numOfValid += comicsInGallery.length;
  1158. }
  1159. }
  1160. numOfValid++; //+1 because of the original target comic
  1161. targetComic.removeClass("nhi-on-page-alt-deleteThis"); //ensure the original target comic gallery doesn't get removed
  1162. targetComic.removeClass("nhi-on-page-alt-ignoreThis");
  1163. $(".nhi-on-page-alt-deleteThis").remove(); //remove all the galleries whose comics got inserted to target comic's gallery
  1164. //setup alt switching buttons if there is more than 1 comic in gallery
  1165. if (numOfValid > 1) {
  1166. targetComic.find(".cover:not(:first)").css("display", "none");
  1167. targetComic.find(".versionPrevButton, .versionNextButton, .numOfVersions").show(200);
  1168. targetComic.find(".numOfVersions").text(`1/${numOfValid}`);
  1169. }
  1170. }
  1171. function NextAltVersion(e) { SwitchAltVersion(e, this, true) }
  1172. function PrevAltVersion(e) { SwitchAltVersion(e, this, false) }
  1173. function SwitchAltVersion(ev, htmlEl, next) {
  1174. ev.preventDefault();
  1175. const toHide = $(htmlEl).parent().find(".cover").filter(":visible");
  1176. let toShow = next ? toHide.next() : toHide.prev();
  1177. if (!toShow || toShow.length <= 0)
  1178. return;
  1179. if (!toShow.is(".cover"))
  1180. toShow = next ? toHide.nextUntil(".cover", ":last").next() : toHide.prevUntil(".cover", ":last").prev();
  1181. if (!toShow || toShow.length <= 0)
  1182. return;
  1183. toHide.hide(100);
  1184. toShow.show(100);
  1185. const n = $(htmlEl).parent().find(".numOfVersions");
  1186. n.text(`${Number(n.text().split("/")[0]) + (next ? 1 : -1)}/${n.text().split("/")[1]}`);
  1187. }
  1188. function StringIncludesAllSearchTermsAfterCleanup(string, search) {
  1189. const cleanString = CleanupSearchString(string);
  1190. const cleanSearch = CleanupSearchString(search);
  1191. if (cleanString.length === 0 || cleanSearch.length === 0)
  1192. return false;
  1193.  
  1194. const searches = cleanSearch.split(" ");
  1195. //console.log(cleanString + " ::: includes all::: " + searches);
  1196. for (let i = 0; i < searches.length; i++)
  1197. if (!!searches[i] && searches[i].length > 0 && !cleanString.includes(searches[i]))
  1198. return false
  1199. return true;
  1200. }
  1201. function CleanupSearchString(title) {
  1202. let cleanTitle = title.replace(/\[.*?\]/g, "");
  1203. cleanTitle = cleanTitle.replace(/\【.*?\】/g, "");
  1204. if (version_grouping_filter_brackets)
  1205. cleanTitle = cleanTitle.replace(/\(.*?\)/g, "");
  1206. return cleanTitle.trim();
  1207. }
  1208. function BuildUrl(title) {
  1209. let url = CleanupSearchString(title);
  1210.  
  1211. url = url.trim();
  1212. //replace all instances of one or more consecutive symbol charactes padded by either whitespace or string start/end with a single space (except kanji)
  1213. url = url.replace(/(^|\s){1}([^\w\s\d\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFFEF\u4E00-\u9FAF\u2605-\u2606\u2190-\u2195\u203B]|\_)+?(\s|$){1}/g, " ");
  1214. url = url.replace(/\s+/g, '" "'); //wrap all terms with ""
  1215. url = `"${url}"`;
  1216. url = encodeURIComponent(url);
  1217. url = `https://nhentai.net/search/?q=${url}`;
  1218. return url;
  1219. }
  1220. //#endregion
  1221.  
  1222. //#region - Subscription system related functions
  1223. function AddSubscriptionStylesheets() {
  1224. if (subscription_system_enabled) {
  1225. GM_addStyle(`
  1226. #tags .subbedTag * {
  1227. background: unset;
  1228. }
  1229. #tags .subbedTag, #tags .subbedTag:visited {
  1230. background-color: #2c5030;
  1231. }
  1232. #tags .subbedTag:active, #tags .subbedTag:hover {
  1233. background-color: #416144;
  1234. }
  1235.  
  1236. #nhi-subscribe-button.btn-nhi-sub, #nhi-subscribe-button.btn-nhi-sub:visited {
  1237. background-color: #3d9e48;
  1238. }
  1239. #nhi-subscribe-button.btn-nhi-sub:active, #nhi-subscribe-button.btn-nhi-sub:hover {
  1240. background-color: #52bc5e;
  1241. }
  1242.  
  1243. #nhi-subscribe-button.btn-nhi-unsub, #nhi-subscribe-button.btn-nhi-unsub:visited {
  1244. background-color: rgb(218, 53, 53);
  1245. }
  1246. #nhi-subscribe-button.btn-nhi-unsub:active, #nhi-subscribe-button.btn-nhi-unsub:hover {
  1247. background-color: #e26060;
  1248. }
  1249.  
  1250. #sub-content ul {
  1251. text-align: left;
  1252. }
  1253. #sub-content li {
  1254. box-sizing: border-box;
  1255. display: inline-block;
  1256. width: 25%;
  1257. text-align: center;
  1258. padding: 5px 20px;
  1259. }
  1260.  
  1261. #nhi-subscribe-button {
  1262. height: auto;
  1263. line-height: initial;
  1264. cursor: pointer;
  1265. font-size: 0.5em;
  1266. padding: 6px 12px;
  1267. }
  1268. @media only screen and (max-width: 1345px) {
  1269. .menu.right a[href^='/logout/'] span { display: none; }
  1270. }
  1271. @media only screen and (max-width: 1270px) {
  1272. .menu.right a[href^='/users/'] span { display: none; }
  1273. }
  1274. @media only screen and (max-width: 670px) {
  1275. .menu.right a[href='/favorites/'] span { display: none; }
  1276. }
  1277. @media only screen and (max-width: 600px) {
  1278. .menu.right a[href^='/logout/'] span { display: inline; }
  1279. .menu.right a[href^='/users/'] span { display: inline; }
  1280. .menu.right a[href='/favorites/'] span { display: inline; }
  1281. }
  1282.  
  1283. .nhi-subscriptions-page > .tag-container {
  1284. margin-bottom: 40px;
  1285. }
  1286. `);
  1287. }
  1288. }
  1289. function AddSubsClickListeners() {
  1290. $("#nhi-subscribe-button").click(function () {
  1291. //get proper sub item value
  1292. const subItem = currentPagePath.split("popular")[0];
  1293.  
  1294. //check if we are subbing or unsubbing
  1295. const isSubbing = $(this).hasClass("btn-nhi-sub");
  1296.  
  1297. //get the array again to make sure we have an up to date array (since other tabs could have modified it since loading this page)
  1298. SubsSet = new Set(TryParseJSON(GM_getValue("SubArrayString", "[]"), []));
  1299.  
  1300. //sub or unsub
  1301. if (isSubbing)
  1302. SubsSet.add(subItem);
  1303. else
  1304. SubsSet.delete(subItem);
  1305.  
  1306. //save changes
  1307. const SubArrayString = JSON.stringify([...SubsSet]); //covert set to array string
  1308. GM_setValue("SubArrayString", SubArrayString); //save string
  1309.  
  1310. //update button
  1311. if (isSubbing)
  1312. $(this).html('<span style="vertical-align: middle;">Unsubscribe</span>').removeClass('btn-nhi-sub').addClass('btn-nhi-unsub');
  1313. else
  1314. $(this).html('<span style="vertical-align: middle;">Subscribe</span>').removeClass('btn-nhi-unsub').addClass('btn-nhi-sub');
  1315. });
  1316. }
  1317. function RenderSubscriptionsPage() {
  1318. //create page skeleton
  1319. $("head title").html('Subscriptions').prop("style", "font-size: 3.5em;");
  1320. $("#content").html(
  1321. `<h1>Subscriptions</h1>
  1322. <h2>Artists</h2>
  1323. <div class="container tag-container artist-section"></div>
  1324. <h2>Groups</h2>
  1325. <div class="container tag-container group-section"></div>
  1326. <h2>Tags</h2>
  1327. <div class="container tag-container tag-section"></div>
  1328. <h2>Languages</h2>
  1329. <div class="container tag-container language-section"></div>
  1330. <h2>Categories</h2>
  1331. <div class="container tag-container category-section"></div>`
  1332. ).addClass("nhi-subscriptions-page");
  1333.  
  1334. //add subs to page
  1335. for (const subItem of SubsSet) {
  1336. $(`.container.${subItem.split("/")[1]}-section`).append(`<a class="tag" href="${subItem}"><span class="name">${subItem.split("/")[2]}</span><span class="count">...</span></a>`);
  1337. }
  1338.  
  1339. //hide empty sections
  1340. $(".nhi-subscriptions-page > .tag-container").each((i, el) => {
  1341. if ($(el).is(":empty")) {
  1342. $(el).hide();
  1343. $(el).prev("h2").hide();
  1344. }
  1345. });
  1346.  
  1347. //load counts
  1348. $(".tag > .count").each((i, el) => {
  1349. SubsPageLoadTagCountWithDelay($(el), i * 200);
  1350. });
  1351. $(".tag > .count").hover(function () {
  1352. SubsPageLoadTagCountWithDelay($(this), 0);
  1353. });
  1354. }
  1355. function SubsPageLoadTagCountWithDelay(elem, delay) {
  1356. setTimeout(() => {
  1357. if (!elem.hasClass("count-fetch-in-progress") && !elem.hasClass("count-fetched")) {
  1358. elem.addClass("count-fetch-in-progress");
  1359. $.ajax({
  1360. url: elem.parent().prop("href"),
  1361. method: "GET"
  1362. }).done((data) => {
  1363. const found = $(data).find("h1 .tag > .count").text();
  1364. if (found != null && found.length > 0)
  1365. elem.text(found);
  1366. else
  1367. console.log(`failed finding tag from: ${elem.parent().prop("href")}`);
  1368.  
  1369. elem.addClass("count-fetched");
  1370. }).fail(() => {
  1371. console.log(`failed getting page: ${elem.parent().prop("href")}`);
  1372. }).always(() => { elem.removeClass("count-fetch-in-progress"); });
  1373. }
  1374. }, delay);
  1375. }
  1376. //#endregion
  1377.  
  1378. //#region - Non-english related functions
  1379. function AddNonEnglishStylesheets() {
  1380. if (!remove_non_english && partially_fade_all_non_english) {
  1381. GM_addStyle(`
  1382. .gallery > .cover:not(.is-english) > img,
  1383. .gallery > .cover:not(.is-english) > .caption
  1384. {
  1385. opacity: ${non_english_fade_opacity};
  1386. }
  1387. `);
  1388. }
  1389. }
  1390. function HandleAllNonEnglishOnPage() {
  1391. if (remove_non_english || partially_fade_all_non_english) {
  1392. $(".gallery").each((i, el) => {
  1393. if (!MarkIfEnglish($(el)) && remove_non_english)
  1394. $(el).remove();
  1395. });
  1396. }
  1397. }
  1398. function MarkIfEnglish(JQGalleryElement) {
  1399. if (JQGalleryElement?.length) {
  1400. //check for english tag
  1401. let isEnglish = JQGalleryElement.attr("data-tags").split(" ").includes("12227");
  1402. //if tag not found, check the title, since NH has started leaving out tags randomly...
  1403. if (!isEnglish) {
  1404. const title = JQGalleryElement.find(".cover > .caption").text();
  1405. if (/[\(\[]english[\)\]]/ig.test(title)) {
  1406. console.log(`NHI: Found comic that was not tagged as english (${title}), but includes '(English)' or '[English]' in the title. Adding missing tag and is-english class...`);
  1407. JQGalleryElement.attr("data-tags", `${JQGalleryElement.attr("data-tags")} 12227`);
  1408. isEnglish = true;
  1409. }
  1410. }
  1411. if (isEnglish) {
  1412. JQGalleryElement.find(".cover").addClass("is-english");
  1413. return true;
  1414. }
  1415. return false;
  1416. }
  1417. }
  1418. //#endregion
  1419.  
  1420. //#region - Reader related functions
  1421. function HandleReaderImprovedZoom() {
  1422. let prevVal = 1.0;
  1423. if (remember_zoom_level)
  1424. prevVal = zoom_level;
  1425. let curVal = prevVal;
  1426.  
  1427. SetReaderImageScale(curVal);
  1428.  
  1429. //make sure the current zoom-level stays between pages
  1430. new MutationObserver((mutations) => {
  1431. for (let i = 0; i < mutations.length; i++)
  1432. if (mutations[i].type === 'attributes')
  1433. SetReaderImageScale(curVal);
  1434. }).observe($("#image-container > a")[0], { attributes: true, childList: false, characterData: false });
  1435.  
  1436. const zoomOut = () => {
  1437. curVal = prevVal - 0.1;
  1438. if (curVal < 0.1)
  1439. curVal = 0.1;
  1440.  
  1441. SetReaderImageScale(curVal);
  1442. prevVal = curVal;
  1443. };
  1444. const zoomIn = () => {
  1445. curVal = prevVal + 0.1;
  1446. if (curVal > 3)
  1447. curVal = 3;
  1448.  
  1449. SetReaderImageScale(curVal);
  1450. prevVal = curVal;
  1451. }
  1452. $('body').on('keydown', (e) => {
  1453. if (e.key === '+') { zoomIn(); }
  1454. else if (e.key === '-') { zoomOut() }
  1455. });
  1456. $("section.reader-bar button.reader-zoom-out").click((e) => {
  1457. e.preventDefault();
  1458. e.stopPropagation();
  1459. zoomOut();
  1460. });
  1461. $("section.reader-bar button.reader-zoom-in").click((e) => {
  1462. e.preventDefault();
  1463. e.stopPropagation();
  1464. zoomIn();
  1465. });
  1466. }
  1467. function SetReaderImageScale(scale) {
  1468. $("section.reader-bar .zoom-level > .value").html(scale.toFixed(1));
  1469. $("#image-container img").css("width", 1280 * scale);
  1470. GM_setValue("zoom_level", scale);
  1471. }
  1472. //#endregion
  1473.  
  1474. //#region - HQ cover related functions
  1475. function AddImprovedReaderZoomStylesheets() {
  1476. if (comic_reader_improved_zoom) {
  1477. GM_addStyle("html.reader #image-container img { max-width: 100% !important; }");
  1478. }
  1479. }
  1480. function AddBrowseThumbnailStylesheets() {
  1481. //#region - Apparently fixes issues with too long cover images +more?
  1482. //TODO: This is old code. Not sure if this is actually needed and not entirely sure what it does. Should test.
  1483.  
  1484. if (browse_thumbnail_width > 0) {
  1485. GM_addStyle(`
  1486. .gallery, .gallery > .cover {
  1487. max-height: ${browse_thumbnail_width * 1.42}px;
  1488. }
  1489. `);
  1490. }
  1491. GM_addStyle(`
  1492. .gallery > .cover {
  1493. overflow: hidden;
  1494. padding: 0 !important;
  1495. }
  1496. .gallery > .cover > img {
  1497. position: relative;
  1498. min-height: 100%;
  1499. }
  1500.  
  1501. .container.index-container, #favcontainer {
  1502. text-align: center;
  1503. }
  1504. .gallery > .cover > img {
  1505. width: 100%;
  1506. }
  1507. `);
  1508. //#endregion
  1509.  
  1510. if (browse_thumnail_container_width > 0) {
  1511. GM_addStyle(`
  1512. /*browsing comics*/
  1513. .container.index-container, #favcontainer {
  1514. width: ${browse_thumnail_container_width}px;
  1515. }
  1516. `);
  1517. }
  1518. if (browse_thumbnail_width > 0) {
  1519. GM_addStyle(`
  1520. /*browsing comics*/
  1521. .container.index-container > div.gallery, #favcontainer > .gallery-favorite {
  1522. width: ${browse_thumbnail_width}px;
  1523. }
  1524. `);
  1525. }
  1526. if (browse_thumnail_container_width > 0) {
  1527. GM_addStyle(`
  1528. /*browsing comics*/
  1529. .container.index-container, #favcontainer {
  1530. max-width: 100%;
  1531. }
  1532. `);
  1533. }
  1534. }
  1535. function AddPagesThumbnailStylesheets() {
  1536. if (pages_thumnail_container_width > 0) {
  1537. GM_addStyle(`
  1538. /*view comic pages*/
  1539. #thumbnail-container {
  1540. max-width: 100%;
  1541. width: ${pages_thumnail_container_width}px;
  1542. }
  1543. `);
  1544. }
  1545. if (pages_thumbnail_width > 0) {
  1546. GM_addStyle(`
  1547. /*view comic pages*/
  1548. div.thumb-container img {
  1549. width: ${pages_thumbnail_width}px;
  1550. }
  1551. `);
  1552. }
  1553. //#region
  1554. //TODO: This is old code. Not sure if this is actually needed and not entirely sure what it does. Should test.
  1555. GM_addStyle(`
  1556. /*view comic pages*/
  1557. div.thumb-container {
  1558. width: auto;
  1559. }
  1560. #thumbnail-container {
  1561. text-align: center;
  1562. }
  1563. `);
  1564. //#endregion
  1565. }
  1566. function OnLoadCoverReplaceHQ() {
  1567. //TODO: Throws errors all over the place... Seems to works for most images though. if it fails the original lower res image remains so failing is okay-ish
  1568. $(this).off("load");
  1569. ReplaceCoverImage($(this));
  1570. }
  1571. function ReplaceCoverImage(coverImg, addMultiTry = true) {
  1572. //TODO: Throws errors all over the place... Seems to works for most images though. if it fails the original lower res image remains so failing is okay-ish
  1573. if (addMultiTry)
  1574. AddMultiTryErrorHandlingForCoverImageLoad(coverImg);
  1575.  
  1576. //set src and try to load
  1577. const iNum = (Number.parseInt($(coverImg).attr("img-reloads") || 0) % 4) + 1;
  1578. const newsrc = ConvertThumbnailURL($(coverImg).attr("data-src"), iNum);
  1579. $(coverImg).attr("src", newsrc);
  1580. }
  1581. function AddMultiTryErrorHandlingForCoverImageLoad(coverImg) {
  1582. $(coverImg).off("error");
  1583. $(coverImg).on("error", function () {
  1584. //count reload attempts
  1585. let attempts = Number.parseInt($(this).attr("img-reloads") || 1);
  1586. if (attempts >= max_image_reload_attempts) //after x attempts, give up
  1587. {
  1588. $(this).off("error");
  1589. console.log(`gave up on: ${$(this).attr("src")}`);
  1590. return;
  1591. }
  1592.  
  1593. const iNum = (Number.parseInt($(this).attr("img-reloads") || 0) % 4) + 1;
  1594. const newsrc = ConvertThumbnailURL($(this).attr("data-src"), iNum);
  1595. $(this).attr("src", newsrc); //reload
  1596. attempts++;
  1597. $(this).attr("img-reloads", attempts);
  1598. console.log(`image reload attempt ${attempts} for: ${$(this).attr("src")}`);
  1599. });
  1600. }
  1601. function ConvertThumbnailURL(url, iNum = 1) {
  1602. return url?.replace(/\/\/t\d*?\./g, `//i${iNum}.`).replace("thumb.jpg", "1.jpg").replace("thumb.png", "1.png").replace("t.jpg", ".jpg").replace("t.png", ".png").replace("thumb.webp", "1.webp").replace("t.webp", ".webp");
  1603. }
  1604. //#endregion
  1605.  
  1606. //#endregion
  1607. })();