Tag Preview

Show tags on hover.

  1. // ==UserScript==
  2. // @name Tag Preview
  3. // @version 2.02
  4. // @description Show tags on hover.
  5. // @author Hauffen
  6. // @runat document-start
  7. // @include /https?:\/\/(e-|ex)hentai\.org\/.*/
  8. // @require https://code.jquery.com/jquery-3.3.1.min.js
  9. // @namespace https://greasyfork.org/users/285675
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. let $ = window.jQuery, tags = {};
  14. const elem = {0: '.gl3m a', 1: '.gl3m a', 2: '.gl3c a', 4: '.gl3t a'};
  15. const defaultNamespace = "misc";
  16. const spl = document.URL.split('/');
  17. var element, title;
  18.  
  19. if ((spl[3].substr(0, 1).match(/[?#fptw]/i) && !spl[3].startsWith('toplist')) || !spl[3]) {
  20. if ($('#tagPreview')) return;
  21. var $tagP = $('<div id="tagPreview">');
  22. $tagP.css({
  23. position: 'absolute',
  24. zIndex: '2',
  25. visiblility: 'hidden !important',
  26. maxWidth: '400px',
  27. background: window.getComputedStyle(document.getElementsByClassName('ido')[0]).backgroundColor,
  28. border: '1px solid #000',
  29. padding: '10px'
  30. });
  31. $tagP.appendTo("body");
  32. $('#tagPreview').css('visibility', 'hidden');
  33.  
  34. element = elem[$(".searchnav div > select > option:selected").index()];
  35.  
  36. $('.itg').on('mouseover', `${element}`, function(e) {
  37. if(document.getElementById('tagPreview').children.length > 2) { $tagP.empty(); }
  38.  
  39. title = this.children[0].title; // Save the title so we can put it back later, probably unnecessary
  40. this.children[0].title = ""; // Clear the title so we don't have it over our new window
  41.  
  42. var str = this.href.split('/');
  43. generateRequest(str[4], str[5]);
  44.  
  45. var posY, posX = (e.pageX + 432 < screen.width) ? e.pageX + 10 : e.pageX - 412;
  46. var scrollHeight = $(document).height();
  47. var scrollPosition = $(window).height() + $(window).scrollTop();
  48.  
  49. if ((scrollHeight - scrollPosition) < (scrollHeight / 10)) { posY = (e.pageY + 300 < scrollHeight) ? e.pageY + 10 : e.pageY - 300; }
  50. else { posY = e.pageY + 10; }
  51.  
  52. $tagP.css({
  53. left: posX,
  54. top: posY,
  55. border: '1px solid ' + window.getComputedStyle(document.getElementsByTagName("a")[0]).getPropertyValue("color"),
  56. visibility: 'visible'
  57. });
  58. $('#tagPreview').css('visibility', 'visible');
  59. }).on('mousemove', `${element}`,function(e) {
  60. var posY, posX = (e.pageX + 432 < screen.width) ? e.pageX + 10 : e.pageX - 412;
  61. var scrollHeight = $(document).height();
  62. var scrollPosition = $(window).height() + $(window).scrollTop();
  63.  
  64. if ((scrollHeight - scrollPosition) < (scrollHeight / 10)) { posY = (e.pageY + document.getElementById('tagPreview').offsetHeight < window.innerHeight) ? e.pageY + 10 : e.pageY - 10 - document.getElementById('tagPreview').offsetHeight; }
  65. else { posY = e.pageY + 10; }
  66.  
  67. $tagP.css({
  68. visibility:'visible',
  69. top: posY,
  70. left: posX
  71. });
  72. $('#tagPreview').css('visibility', 'visible');
  73. }).on('mouseout', `${element}`, function() {
  74. this.children[0].title = title; // Put the saved title back
  75. $tagP.css({
  76. visibility:'hidden'
  77. });
  78. $('#tagPreview').css('visibility', 'hidden');
  79. $tagP.empty(); // Clear out the tag
  80. });
  81.  
  82. $(document).on('scroll', function() {
  83. $('#tagPreview').css('visibility', 'hidden');
  84. });
  85. } else {
  86. return;
  87. }
  88.  
  89. /**
  90. * Generate the inner HTML for the tag preview window
  91. * @param {JSON} apirsp - The E-H API response
  92. */
  93. function generateListing(apirsp) {
  94. generateTags(apirsp); // We actually have to generate the tag list from the raw JSON file
  95. $('#tagPreview').append(`<h1 id="gn">${apirsp.title}</h1><h1 id="gj">${apirsp.title_jpn}</h1>`);
  96. let taglist = "<div id='taglist' style='height:fit-content;'><table><tbody>";
  97. for (const namespace in tags) {
  98. taglist += `<tr><td class="tc">${namespace}:</td><td>`;
  99. for (var i = 0; i < tags[namespace].length; i++) {
  100. taglist += `<div id="td_${namespace}:${tags[namespace][i]}" class="gt" style="opacity:1.0"><a id="ta_${namespace}:${tags[namespace][i]}" href="${document.location.origin}/tag/${namespace}:${tags[namespace][i]}">${tags[namespace][i]}</a></div>`;
  101. }
  102. taglist += "</td></tr>";
  103. }
  104. taglist += "</tbody></table></div>";
  105. $('#tagPreview').append(taglist);
  106. }
  107.  
  108. /**
  109. * Converts the tag listing within the API response to a categorized array
  110. * @param {JSON} apirsp - The E-H API response
  111. */
  112. function generateTags(apirsp) {
  113. tags = {}; // Reset the tags array for each new tag listing
  114. if (Array.isArray(apirsp.tags)) {
  115. for (const jsonTag of apirsp.tags) {
  116. const stringTag = getJsonString(jsonTag);
  117. if (stringTag === null) { continue; }
  118.  
  119. const {tag, namespace} = getTagAndNamespace(stringTag);
  120.  
  121. let namespaceTags;
  122. if (tags.hasOwnProperty(namespace)) { namespaceTags = tags[namespace]; }
  123. else {
  124. namespaceTags = [];
  125. tags[namespace] = namespaceTags;
  126. }
  127. namespaceTags.push(tag);
  128. }
  129. }
  130. }
  131.  
  132. /**
  133. * Generate the JSON request for the E-H API
  134. * @param {Integer} gid - The gallery ID
  135. * @param {String} token - The gallery token
  136. */
  137. function generateRequest(gid, token) {
  138. var reqList = [[gid, token]]; // We use an array for our gidlist, since the API can handle up to 25 galleries per request
  139. var request = {"method": "gdata", "gidlist": reqList, "namespace": 1};
  140.  
  141. var req = new XMLHttpRequest();
  142. req.onreadystatechange = e => {
  143. if (req.readyState == 4) {
  144. if (req.status == 200) {
  145. var apirsp = JSON.parse(req.responseText);
  146. for (var i = 0; i < apirsp.gmetadata.length; i++) generateListing(apirsp.gmetadata[i]);
  147. } else { console.error(); }
  148. }
  149. }
  150. req.open("POST", document.location.origin + "/api.php", true); // Due to CORS, we need to use the API on the same domain as the script
  151. req.send(JSON.stringify(request));
  152. }
  153.  
  154. // Helper functions
  155. function getTagAndNamespace(tag) {
  156. const pattern = /^(?:([^:]*):)?([\w\W]*)$/;
  157. const match = pattern.exec(tag);
  158. return (match !== null) ?
  159. ({ tag: match[2], namespace: match[1] || defaultNamespace }) :
  160. ({ tag: tag, namespace: defaultNamespace });
  161. }
  162.  
  163. function toProperCase(text) {
  164. return text.replace(/(^|\W)(\w)/g, (m0, m1, m2) => `${m1}${m2.toUpperCase()}`);
  165. }
  166.  
  167. function getJsonString(value) {
  168. if (typeof(value) === "string") { return value; }
  169. if (typeof(value) === "undefined" || value === null) { return value; }
  170. return `${value}`;
  171. }
  172. })();