nhentai Navigation Improvements

Clone search page navigation on top for mobile and options for faster tag filtering in top bar

  1. // ==UserScript==
  2. // @name nhentai Navigation Improvements
  3. // @description Clone search page navigation on top for mobile and options for faster tag filtering in top bar
  4. // @namespace xspeed.net
  5. // @license MIT
  6. // @version 4
  7. // @icon https://nhentai.net/favicon.ico
  8. // @match *://nhentai.net/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. "use strict";
  13.  
  14. function showTextPrompt(msg) {
  15. const text = document.createElement("p");
  16. text.innerText = msg;
  17.  
  18. const input = document.createElement("input");
  19. input.type = "search";
  20.  
  21. const link = document.createElement("i");
  22. link.className = "fa fa-paste";
  23.  
  24. const button = document.createElement("button");
  25. button.className = "btn btn-primary btn-square";
  26. button.type = "submit";
  27. button.appendChild(link);
  28.  
  29. const form = document.createElement("form");
  30. form.className = "search";
  31. form.role = "search";
  32. form.method = "dialog";
  33. form.appendChild(input);
  34. form.appendChild(button);
  35.  
  36. const dialog = document.createElement("dialog");
  37. dialog.appendChild(text);
  38. dialog.appendChild(form);
  39.  
  40. const style = getComputedStyle(document.body);
  41. dialog.style.backgroundColor = style.getPropertyValue("background-color");
  42. dialog.style.color = style.getPropertyValue("color");
  43.  
  44. document.body.appendChild(dialog);
  45. dialog.showModal();
  46.  
  47. return new Promise((resolve, reject) => form.addEventListener('submit', () => {
  48. resolve(input.value);
  49. dialog.remove();
  50. }, { once: true }));
  51. }
  52.  
  53. function stopEvent(event) {
  54. event.stopImmediatePropagation();
  55. event.preventDefault();
  56. }
  57.  
  58. async function onChangeTags(event, tags) {
  59. stopEvent(event);
  60.  
  61. const input = document.querySelector("input[type=search]");
  62. const str = tags ?? await showTextPrompt("Specify tags, separate by commas");
  63.  
  64. if (str) {
  65. str.split(',').map(x => x.trim()).filter(x => x).forEach(x => adjustSearch("tag", x, false));
  66.  
  67. document.querySelector("button[type=submit]").click();
  68. }
  69. }
  70.  
  71. function adjustSearch(type, item, add) {
  72. const input = document.querySelector("input[type=search]");
  73. item = wrap(item);
  74. const act = " " + (add ? item : `-${type}:${item}`);
  75. const inv = " " + (add ? `-${type}:${item}` : item);
  76.  
  77. if (input.value.indexOf(inv) != -1) input.value = input.value.replace(inv, act);
  78. else if (input.value.indexOf(act) == -1) input.value += act;
  79.  
  80. input.value = input.value.replaceAll(' ', ' ');
  81. sessionStorage.setItem("lastSearch", input.value);
  82. }
  83.  
  84. function wrap(txt) {
  85. return txt.indexOf(" ") == -1 ? txt : `"${txt}"`;
  86. }
  87.  
  88. function clearSearch(event) {
  89. stopEvent(event);
  90.  
  91. document.querySelector("input[type=search]").value = "";
  92. sessionStorage.removeItem("lastSearch");
  93. }
  94.  
  95. function setupBtn(elem) {
  96. if (!elem) return;
  97.  
  98. let item = elem.firstChild.cloneNode(true);
  99.  
  100. let link = item.firstChild;
  101. link.href = "#";
  102. link.innerText = "Block default";
  103. link.addEventListener("click", e => onChangeTags(e, "bbm,netorare,vore,scat,guro"));
  104.  
  105. elem.insertBefore(item, elem.firstChild);
  106.  
  107. item = elem.firstChild.cloneNode(true);
  108.  
  109. link = item.firstChild;
  110. link.href = "#";
  111. link.innerText = "Block tags";
  112. link.addEventListener("click", onChangeTags);
  113.  
  114. elem.insertBefore(item, elem.firstChild);
  115.  
  116. item = elem.firstChild.cloneNode(true);
  117.  
  118. link = item.firstChild;
  119. link.href = "#";
  120. link.innerText = "Clear search";
  121. link.addEventListener("click", e => clearSearch(e));
  122.  
  123. elem.insertBefore(item, elem.firstChild);
  124. }
  125.  
  126. function onTagClick(event, elem, add) {
  127. stopEvent(event);
  128.  
  129. const data = elem.pathname.split("/").filter(x => x);
  130. adjustSearch(data[0], data[1].replaceAll("-", " "), add);
  131. }
  132.  
  133. function setupTag(elem) {
  134. if (!elem.querySelector(".count")) return document.createTextNode("");
  135.  
  136. const container = document.createElement("span");
  137. container.addEventListener("click", e => stopEvent(e));
  138. container.className = "name";
  139.  
  140. const add = document.createElement("a");
  141. add.href = "#";
  142. add.addEventListener("click", e => onTagClick(e, elem, true));
  143. add.className = "fa fa-plus";
  144. add.style.padding = "0.25em 0em";
  145. add.style.marginRight = "0.4em";
  146.  
  147. const del = document.createElement("a");
  148. del.href = "#";
  149. del.addEventListener("click", e => onTagClick(e, elem));
  150. del.className = "fa fa-minus";
  151. del.style.padding = "0.25em 0em";
  152.  
  153. container.appendChild(add);
  154. container.appendChild(del);
  155. return container;
  156. }
  157.  
  158. (function() {
  159. const content = document.getElementById("content");
  160. const pagination = document.querySelector("section.pagination");
  161. const input = document.querySelector("input[type=search]");
  162.  
  163. if (content && pagination) {
  164. const clone = pagination.cloneNode(true);
  165.  
  166. const spacer = clone.querySelector(".ios-mobile-webkit-bottom-spacing");
  167. if (spacer) spacer.remove();
  168.  
  169. content.insertBefore(clone, content.firstChild);
  170. }
  171.  
  172. if (!input.value) {
  173. input.value = sessionStorage.getItem("lastSearch") ?? "english ";
  174. }
  175. else if (input.value.trim() != "english") {
  176. sessionStorage.setItem("lastSearch", input.value);
  177. }
  178.  
  179. setupBtn(document.querySelector("ul.menu"));
  180. setupBtn(document.querySelector("ul.dropdown-menu"));
  181.  
  182. document.querySelectorAll(".tags>.tag").forEach(x => x.appendChild(setupTag(x)));
  183. })();