EhxVisited

E-H Visited, combined with ExVisited, and then better.

  1. // ==UserScript==
  2. // @name EhxVisited
  3. // @namespace https://sleazyfork.org/en/users/285675-hauffen
  4. // @version 2.62.26.13
  5. // @description E-H Visited, combined with ExVisited, and then better.
  6. // @author Hauffen
  7. // @require https://code.jquery.com/jquery-3.3.1.min.js
  8. // @match *://exhentai.org/*
  9. // @match *://e-hentai.org/*
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
  15. window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: 'readwrite'};
  16. window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
  17.  
  18. if (!window.indexedDB) {
  19. console.warn("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
  20. return;
  21. }
  22.  
  23. /*═════════════════════════════╗
  24. ║ Configuration Defaults ║
  25. ╚═════════════════════════════*/
  26. var setStore = localStorage.getItem('ehx-settings') ? JSON.parse(localStorage.getItem('ehx-settings')) : {"softHide": false, "minAdd": true, "minShow": false, "cssTT": false, "repPub": false, "visHide": false, "hidShow": false, "pFilter": false, "pLimit": 0, "stFilter": false, "stLimit": 0, "titleShow": true, "titleOn": false, "tagPreview": false, "autoUp": false, "autoTime": 0, "lastBack": 0};
  27. var filters = '';
  28. var cssA = localStorage.getItem('ehx-css') ? JSON.parse(localStorage.getItem('ehx-css')) : {"visible": "box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;", "hidden": "box-shadow: inset 0 0 0 500px rgba(255, 40, 0, .2) !important;", "download": "box-shadow: inset 0 0 0 500px rgba(30, 180, 60, .2) !important;", "filter": "box-shadow: inset 0 0 0 500px rgba(200, 0, 100, .2) !important;", "page" :"box-shadow: inset 0 0 0 500px rgba(0, 0, 180, .2) !important;", "rating" :"box-shadow: inset 0 0 0 500px rgba(180, 80, 60, .2) !important;", "uploader": "box-shadow: inset 0 0 0 500px rgba(222, 184, 135, .2) !important;", "tag": "box-shadow: inset 0 0 0 500px rgba(180, 130, 60, .2) !important;"};
  29. var cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
  30. var cssC = localStorage.getItem('ehx-cssc') ? JSON.parse(localStorage.getItem('ehx-cssc')) : {"custom": ""};
  31. const img_hide = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIhSURBVDjLlZPrThNRFIWJicmJz6BWiYbIkYDEG0JbBiitDQgm0PuFXqSAtKXtpE2hNuoPTXwSnwtExd6w0pl2OtPlrphKLSXhx07OZM769qy19wwAGLhM1ddC184+d18QMzoq3lfsD3LZ7Y3XbE5DL6Atzuyilc5Ciyd7IHVfgNcDYTQ2tvDr5crn6uLSvX+Av2Lk36FFpSVENDe3OxDZu8apO5rROJDLo30+Nlvj5RnTlVNAKs1aCVFr7b4BPn6Cls21AWgEQlz2+Dl1h7IdA+i97A/geP65WhbmrnZZ0GIJpr6OqZqYAd5/gJpKox4Mg7pD2YoC2b0/54rJQuJZdm6Izcgma4TW1WZ0h+y8BfbyJMwBmSxkjw+VObNanp5h/adwGhaTXF4NWbLj9gEONyCmUZmd10pGgf1/vwcgOT3tUQE0DdicwIod2EmSbwsKE1P8QoDkcHPJ5YESjgBJkYQpIEZ2KEB51Y6y3ojvY+P8XEDN7uKS0w0ltA7QGCWHCxSWWpwyaCeLy0BkA7UXyyg8fIzDoWHeBaDN4tQdSvAVdU1Aok+nsNTipIEVnkywo/FHatVkBoIhnFisOBoZxcGtQd4B0GYJNZsDSiAEadUBCkstPtN3Avs2Msa+Dt9XfxoFSNYF/Bh9gP0bOqHLAm2WUF1YQskwrVFYPWkf3h1iXwbvqGfFPSGW9Eah8HSS9fuZDnS32f71m8KFY7xs/QZyu6TH2+2+FAAAAABJRU5ErkJggg==';
  32. /*════════════════════════════*/
  33.  
  34. try {
  35. filters = localStorage.getItem('ehx-filters') ? JSON.parse(localStorage.getItem('ehx-filters')) : {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999", "tag:": "#female:farting"}; // JSON.parse isn't a fan of unique characters
  36. } catch (e) {
  37. displayAlert('EhxVisited: Filter Error, See Console', 5000, true);
  38. console.log('EhxVisited: JSON.parse Error - ' + e.message);
  39. filters = {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999", "tag:": "#female:farting"};
  40. }
  41. if (filters.tag == undefined) filters = {"title": filters.title, "uploader": filters.uploader, "tag": "#female:farting"}; // For fixing new updates
  42.  
  43. let db = null, $ = window.jQuery;
  44. var filterArr = [], uploaderArr = [], tagArr = [];
  45. var observer = new MutationObserver(e => {
  46. addCSS();
  47. });
  48.  
  49. var spl = document.URL.split('/');
  50. var galleries, hidden, down, cache, tempCache, element, title;
  51. var activeStore, activeStoreTitle;
  52. var d1 = spl[3], reload = 0, tags = [];
  53. const category = {doujinshi: 'ct2', manga: 'ct3', artistcg: 'ct4', gamecg: 'ct5', western: 'cta', nonh: 'ct9', imageset: 'ct6', cosplay: 'ct7', asianporn: 'ct8', misc: 'ct1'};
  54. const elem = {0: '.gl3m a', 1: '.gl3m a', 2: '.gl3c a', 3: '.gl1e a', 4: '.gl3t a'};
  55. const fileSizeLabels = [ "B", "KB", "MB", "GB" ];
  56.  
  57. if (d1 == 'g') {
  58. addGallery('galleries', spl[4] + '.' + spl[5]); // Add the current page to galleries
  59. }
  60. if (d1.startsWith('gallerytorrents')) $('#torrentinfo a').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().t));
  61. if (d1.startsWith('archiver')) {
  62. $(':submit').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().token));
  63. $('a').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().token));
  64. }
  65. if ((d1.substr(0, 1).match(/[?#funptw]/i) && !d1.startsWith('toplist') && !d1.startsWith('upld')) || Object.keys(category).includes(d1) || !d1) {
  66. $('<div class="ehx-alertContainer"></div>').appendTo('body');
  67. populate();
  68. } else return;
  69.  
  70. /**
  71. * Add a gallery to our IndexedDB
  72. */
  73. function addGallery(store, gid) {
  74. const request = indexedDB.open('ehxvisited', 2);
  75.  
  76. request.onupgradeneeded = e => { // Generate our database if it's not there
  77. db = e.target.result;
  78.  
  79. if (!db.objectStoreNames.contains('galleries')) db.createObjectStore('galleries', {keyPath: 'id'});
  80. if (!db.objectStoreNames.contains('hidden')) db.createObjectStore('hidden', {keyPath: 'id'});
  81. if (!db.objectStoreNames.contains('down')) db.createObjectStore('down', {keyPath: 'id'});
  82. };
  83.  
  84. request.onsuccess = e => {
  85. db = e.target.result;
  86.  
  87. var objStore = db.transaction(store, 'readwrite').objectStore(store);
  88. var openRequest = objStore.openCursor(gid);
  89.  
  90. openRequest.onsuccess = e => {
  91. var cursor = openRequest.result;
  92. if (cursor) { // Update entry if key exists
  93. cursor.update({id: gid, visited: Date.now()});
  94. console.log('EhxVisited: Updated ' + gid);
  95. } else { // Otherwise, add entry
  96. objStore.add({id: gid, visited: Date.now()});
  97. console.log('EhxVisited: Added ' + gid);
  98. }
  99. };
  100.  
  101. openRequest.onerror = e => {
  102. console.log(`EhxVisited: Something bad happened with gallery ${gid}: ${e.target.error}`);
  103. };
  104. };
  105. }
  106.  
  107. /**
  108. * Fill our local gallery listings so we can preform easier operations on the data.
  109. * Also set up the majority of our global HTML elements and their functions.
  110. */
  111. function populate() { // TODO: Separate the HTML entries from the population portion
  112. populateFilter();
  113. galleries = []
  114. hidden = []
  115. down = []
  116. cache = [];
  117.  
  118. if (setStore.autoUp) {
  119. var diff;
  120. switch (setStore.autoTime) {
  121. case 0:
  122. diff = 1;
  123. break;
  124. case 1:
  125. diff = 7;
  126. break;
  127. case 2:
  128. diff = 30;
  129. break;
  130. default:
  131. diff = 0;
  132. break;
  133. }
  134. if ((Date.now() - setStore.lastBack) / (1000 * 3600 * 24) >= diff || setStore.lastBack == undefined) {
  135. setStore.lastBack = Date.now();
  136. localStorage.setItem('ehx-settings', JSON.stringify(setStore));
  137. ehxExportF();
  138. }
  139. }
  140.  
  141. const request = indexedDB.open('ehxvisited', 2);
  142.  
  143. request.onupgradeneeded = e => {
  144. db = e.target.result;
  145.  
  146. if (!db.objectStoreNames.contains('galleries')) db.createObjectStore('galleries', {keyPath: 'id'});
  147. if (!db.objectStoreNames.contains('hidden')) db.createObjectStore('hidden', {keyPath: 'id'});
  148. if (!db.objectStoreNames.contains('down')) db.createObjectStore('down', {keyPath: 'id'});
  149. };
  150.  
  151. request.onsuccess = e => { // TODO: See about multiple transactions, or just do these async
  152. db = e.target.result;
  153. var objStore = db.transaction('galleries', 'readonly').objectStore('galleries');
  154. var openReq = objStore.getAll();
  155. openReq.onsuccess = f => {
  156. console.log('EhxVisited: Populated global variables.');
  157. var transform = f.target.result;
  158. for (var i = 0; i < transform.length; i++) {
  159. galleries[transform[i].id] = transform[i].visited; // Force matrix data into array data
  160. }
  161. var gLength = Object.keys(galleries).length
  162. var objStore2 = db.transaction('hidden', 'readonly').objectStore('hidden');
  163. var openReq2 = objStore2.getAll();
  164. openReq2.onsuccess = g => {
  165. var transform2 = g.target.result;
  166. for (i = 0; i < transform2.length; i++) {
  167. hidden[transform2[i].id] = 1; // Force matrix data into array data
  168. }
  169. galleries = sortObj(galleries);
  170. var hLength = Object.keys(hidden).length
  171. var objStore3 = db.transaction('down', 'readonly').objectStore('down');
  172. var openReq3 = objStore3.getAll();
  173. openReq3.onsuccess = h => {
  174. var transform3 = h.target.result;
  175. for (i = 0; i < transform3.length; i++) {
  176. down[transform3[i].id] = 1;
  177. }
  178. $($('h1').text() == 'Favorites' ? '.ido > div:nth-child(3)' : '#toppane').append(
  179. `<ehx id="ehx-controls">Galleries visited: <span id="gLength">` + gLength + `</span> ( <span id="ehx-menu-control"></span><a id="ehx-settings">Settings</a> )
  180. <br/>Hidden Galleries: <span id="hLength">` + hLength + `</span><span id="ehxh-menu-control"></span></ehx>`);
  181. if (!setStore.softHide) {
  182. $('#ehx-menu-control').append('<a id="ehx-show">' + ((setStore.visHide) ? 'Show' : 'Hide') + '</a> / ');
  183. $('#ehxh-menu-control').append(' ( <a id="ehxh-show">' + ((setStore.hidShow) ? 'Hide' : 'Show') + '</a> )');
  184. }
  185. $('#ehx-settings').click(e => {
  186. e.preventDefault();
  187. settings();
  188. });
  189. $('#ehx-show').click(e => {
  190. var disp = $('.ehx-visited.ehx-hidden').css('display');
  191. if ($('#ehx-show').text() === 'Show') {
  192. $('.ehx-visited').css({display: ''});
  193. $('.ehx-visited.ehx-hidden').css({display: disp});
  194. } else {
  195. $('.ehx-visited').css({display: 'none'});
  196. $('.ehx-visited.ehx-hidden').css({display: disp});
  197. }
  198. $('#ehx-show').text((i, t) => {
  199. return t === 'Show' ? 'Hide' : 'Show';
  200. });
  201. updateGListing();
  202. setStore.visHide = $('#ehx-show').text() === 'Show' ? true : false;
  203. localStorage.setItem('ehx-settings', JSON.stringify(setStore)); // Update our stored settings
  204. });
  205. $('#ehxh-show').click(e => {
  206. if ($('#ehxh-show').text() === 'Show') {
  207. if (!$('.gl1t').length) { // For table view modes
  208. $('.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')}); // Copy the display CSS of our closest element
  209. $('.ehx-visited.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')});
  210. } else { // Default display CSS for thumbnail
  211. $('.ehx-hidden').css({display: 'flex'});
  212. $('.ehx-visited.ehx-hidden').css({display: 'flex'});
  213. }
  214. } else {
  215. $('.ehx-hidden').css({display: ''});
  216. $('.ehx-visited.ehx-hidden').css({display: ''});
  217. }
  218. $('#ehxh-show').text((i, t) => { // Toggle text
  219. return t === 'Show' ? 'Hide' : 'Show';
  220. });
  221. updateGListing();
  222. setStore.hidShow = $('#ehxh-show').text() === 'Hide' ? true : false;
  223. localStorage.setItem('ehx-settings', JSON.stringify(setStore));
  224. });
  225. $('#ehx-controls').append('<br /><span id="ehx-hideCount"><span></span></span>');
  226. addCSS();
  227. updateGListing();
  228. }
  229. }
  230. }
  231. }
  232. }
  233.  
  234. /**
  235. * Populate the custom filter array with user input converted into regular expressions
  236. */
  237. function populateFilter() {
  238. filterArr = []; // Start fresh for when we call this again
  239. uploaderArr = [];
  240. tagArr = [];
  241. if (filters.title != '') pushRegex('title', filterArr);
  242. if (filters.uploader != '') pushRegex('uploader', uploaderArr);
  243. if (filters.tag != '') pushRegex('tag', tagArr);
  244.  
  245. function pushRegex(store, arr) {
  246. var tempArr = filters[store].split('\n');
  247. for (var i = 0; i < tempArr.length; i++) {
  248. if (tempArr[i].startsWith('#')) continue;
  249. var regex;
  250. try {
  251. regex = new RegExp(tempArr[i], 'i');
  252. } catch(e) {
  253. displayAlert('Invalid Regex On Line ' + (i + 1), 5000, true);
  254. continue;
  255. }
  256. arr.push(regex);
  257. }
  258. }
  259. }
  260.  
  261. /**
  262. * Updates the hidden gallery count in the header object
  263. */
  264. function updateGListing() {
  265. var list = $('.itg .gl1t').length > 0 ? $('.itg .gl1t') : $('table.itg>tbody>tr').has('.glhide, .gldown, th'); // Get the proper elements depending on our view mode
  266. var hCount = $('.ehx-hidden').length, vAmount = $('.ehx-hidden.ehx-visited').length;
  267. var hAmount = $('div[data-jqstyle*="h"]').length, fAmount = $('div[data-jqstyle*="f"]').length, pAmount = $('div[data-jqstyle*="p"]').length, rAmount = $('div[data-jqstyle*="r"]').length, uAmount = $('div[data-jqstyle*="u"]').length, tAmount = $('div[data-jqstyle*="t"]').length;
  268. if (!setStore.softHide) {
  269. $('#ehx-hideCount').html('There ' + (hCount > 1 || hCount == 0 ? 'are ' : 'is ') + '<span>' + hCount + ' hidden ' + (hCount > 1 || hCount == 0 ? 'galleries' : 'gallery') + '</span> on this page.');
  270. } else {
  271. $('ehx-hideCount').html('There are <span>0 hidden galleries</span> on this page.');
  272. }
  273. $('#ehx-hideCount > span').prop('title', 'Hidden: ' + hAmount + ' | Visited: ' + vAmount + ' | Filtered: ' + fAmount + ' | Page Limit: ' + pAmount + ' | Rating Limit: ' + rAmount + ' | Uploader: ' + uAmount + ' | Tags: ' + tAmount);
  274. $('#hLength').text(Object.keys(hidden).length);
  275. $('#gLength').text(Object.keys(galleries).length);
  276. }
  277.  
  278. /**
  279. * Our main function that does basically everything that we see.
  280. * Appends our custom HTML objects to the main page.
  281. * Adds CSS to elements based on whether they can be found in the populated local gallery listing.
  282. */
  283. async function addCSS() { // TODO: Probably refactor this block again
  284. observer.disconnect();
  285. var list = $('.itg tr').length ? $('tr').has('.glhide, .gldown, th') : $('.itg .gl1t');
  286. var gid, galleryId, onFavs;
  287.  
  288. if (list.length) {
  289. if ($('h1').text() == 'Favorites') onFavs = 1;
  290. element = elem[onFavs ? $(".searchnav div > select").eq(1).prop('selectedIndex') : $(".searchnav div > select > option:selected").index()]
  291. let tempCache = [];
  292. generateCacheListing(element, tempCache);
  293. tempCache = tempCache.filter(Boolean);
  294. if (tempCache.length) {
  295. if (tempCache.length > 25) {
  296. while (tempCache.length > 0) {
  297. let request = {"method": "gdata", "gidlist": tempCache.splice(0, 25), "namespace": 1}
  298. await generateCacheRequest(request);
  299. }
  300. } else {
  301. let request = {"method": "gdata", "gidlist": tempCache, "namespace": 1}
  302. await generateCacheRequest(request);
  303. }
  304. }
  305.  
  306. if ($('.gl1e').length) { // Extended
  307. for (var i = 0; i < list.length; i++) {
  308. gid = $(list[i]).find('.gl1e a').attr('href').split('/');
  309. galleryId = gid[4] + '.' + gid[5];
  310.  
  311. if (galleries[galleryId] != undefined) { // Visited
  312. if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields if we haven't already
  313. $(list[i]).addClass('ehx-visited');
  314. addStyle($(list[i]), 'v');
  315. if (onFavs) {
  316. $(list[i]).find('.gl3e div:last-child').append('<br/><ehx class="ehx-extended-favs">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
  317. } else {
  318. $(list[i]).find('.gl3e').append('<ehx class="ehx-extended">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
  319. }
  320. } else { // Otherwise, just update the timestamp
  321. if (onFavs) {
  322. $(list[i]).find('ehx-extended-favs').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
  323. } else {
  324. $(list[i]).find('ehx-extended').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
  325. }
  326. }
  327.  
  328. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
  329. if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text(buildTime(galleryId, false));
  330. } else { // Never Visited
  331. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
  332. if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text('Never Visited');
  333. $(list[i]).removeClass('ehx-visited');
  334. if ($(list[i]).find('.ehx-extended').length) $(list[i]).find('.ehx-extended').remove();
  335. }
  336.  
  337. if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
  338. $(list[i]).addClass('ehx-hidden');
  339. addStyle($(list[i]), 'h');
  340. }
  341.  
  342. if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
  343. $(list[i]).addClass('ehx-downloaded');
  344. addStyle($(list[i]), 'd');
  345. }
  346.  
  347. if (!$(list[i]).find('.ehx-imgHide').length) {
  348. $('<img class="ehx-imgHide" src="' + img_hide + '" title="Show/Hide Gallery">').appendTo($(list[i]).find('.gl2e > div')).click(e => { // Maybe closest('tr')
  349. toggleElement($(e.currentTarget).parents().eq(2).find('a').attr('href'), $(e.currentTarget).parents().eq(2));
  350. });
  351. }
  352. filterCheck($(list[i]));
  353. }
  354. } else if ($('.gl1c').length) { // Compact
  355. var borderColor = $('.gl1c').first().css('border-top-color');
  356. if ($('.itg tr:first-child').children().length < 5) { // We haven't appended our table head
  357. $('.itg th:nth-child(4)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">&#x2716</th>'); // X column
  358. if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th>Visited</th>');
  359. if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
  360. }
  361.  
  362. for (i = 1; i < list.length; i++) {
  363. gid = $(list[i]).find('.glname a').attr('href').split('/');
  364. galleryId = gid[4] + '.' + gid[5];
  365.  
  366. if (galleries[galleryId] != undefined) { // Visited
  367. if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields
  368. $(list[i]).addClass('ehx-visited');
  369. addStyle($(list[i]), 'v');
  370. if (setStore.minAdd) {
  371. if ($(list[i]).find('.ehx-compact').length) $(list[i]).find('ehx-compact').html('<ehx>' + timeDifference(galleryId, true) + '<br>' + buildTime(galleryId, false).substring(11) + '<br>' + buildTime(galleryId, false).substring(2, 10) + '</ehx>');
  372. else $(list[i]).find('.gl2c').after('<td class="ehx-compact" style="border-color:' + borderColor + ';"><ehx>' + timeDifference(galleryId, true) + '<br>' + buildTime(galleryId, false).substring(11) + '<br>' + buildTime(galleryId, false).substring(2, 10) + '</ehx></td>');
  373. }
  374. } else { // Otherwise update timestamp
  375. $(list[i]).find('ehx-compact').html('<ehx>' + timeDifference(galleryId, true) + '<br>' + buildTime(galleryId, false).substring(11) + '<br>' + buildTime(galleryId, false).substring(2, 10) + '</ehx>');
  376. }
  377.  
  378. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
  379. if (setStore.repPub) $(list[i]).find('.gl2c div:nth-child(3) div:first-child').text(buildTime(galleryId, false));
  380. } else { // Never Visited
  381. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
  382. if (setStore.repPub) $(list[i]).find('.gl2c > div:nth-child(3) > div:first-child').text('Never Visited');
  383. if ($(list[i]).children().length < 5 || ($(list[i]).children().length < 6 && onFavs)) {
  384. if (setStore.minAdd) $(list[i]).find('.gl2c').after('<td class="ehx-compact" style="border-color:' + borderColor + ';"></td>');
  385. }
  386. $(list[i]).removeClass('ehx-visited');
  387. }
  388.  
  389. if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
  390. $(list[i]).addClass('ehx-hidden');
  391. addStyle($(list[i]), 'h');
  392. }
  393.  
  394. if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
  395. $(list[i]).addClass('ehx-downloaded');
  396. addStyle($(list[i]), 'd');
  397. }
  398.  
  399. if (!$(list[i]).find('.ehx-imgHide').length) {
  400. $('<td class="hideContainer"><img class="ehx-imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
  401. toggleElement($(e.currentTarget).parent().find('.glname a').attr('href'), $(e.currentTarget).parent());
  402. });
  403. }
  404. filterCheck($(list[i]));
  405. }
  406. } else if ($('.gl1m').length) { // Minimal
  407. if ($('.itg tr:first-child').children().length < 7) { // We haven't appended our table head
  408. $('.itg th:nth-child(6)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">&#x2716</th>'); // X Column
  409. if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th title="EhxVisited: Hover for timestamps" style="text-align: center;">\uD83D\uDC41</th>');
  410. if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
  411. }
  412.  
  413. for (i = 1; i < list.length; i++) {
  414. gid = $(list[i]).find('.glname a').attr('href').split('/');
  415. galleryId = gid[4] + '.' + gid[5];
  416.  
  417. if (galleries[galleryId] != undefined) { // Visited
  418. if (!$(list[i]).hasClass('ehx-visited')) { // Append fields
  419. $(list[i]).addClass('ehx-visited');
  420. addStyle($(list[i]), 'v');
  421. if (setStore.minAdd) {
  422. if (setStore.minShow) {
  423. if ($(list[i]).find('.ehx-minimal').length) $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
  424. else $(list[i]).find('.gl2m').after('<td class="ehx-minimal"><ehx title="EhxVisited: ' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx></td>');
  425. } else {
  426. if ($(list[i]).find('.ehx-minimal').length) {
  427. $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
  428. $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
  429. } else $(list[i]).find('.gl2m').after('<td class="ehx-minimal" title="EhxVisited: ' + buildTime(galleryId, true) + '"><ehx>\uD83D\uDC41</ehx></td>');
  430. }
  431. }
  432. } else { // Update our timestamps
  433. if (setStore.minAdd) {
  434. if (setStore.minShow) {
  435. $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
  436. } else {
  437. $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
  438. $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
  439. }
  440. }
  441. }
  442.  
  443. if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
  444. if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text(buildTime(galleryId, false));
  445. } else { // Never Visited
  446. if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', 'Never Visited');
  447. if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text('Never Visited');
  448. if ($(list[i]).children().length < 7 || ($(list[i]).children().length < 8 && onFavs)) {
  449. if (setStore.minAdd) $(list[i]).find('.gl2m').after('<td class="ehx-minimal"></td>');
  450. }
  451. $(list[i]).removeClass('ehx-visited');
  452. }
  453.  
  454. if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
  455. $(list[i]).addClass('ehx-hidden');
  456. addStyle($(list[i]), 'h');
  457. }
  458.  
  459. if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
  460. $(list[i]).addClass('ehx-downloaded');
  461. addStyle($(list[i]), 'd');
  462. }
  463.  
  464. if (!$(list[i]).find('.ehx-imgHide').length) {
  465. $('<td class="hideContainer"><img class="ehx-imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
  466. var el = $(e.currentTarget).closest('tr');
  467. toggleElement($(el).find('.glname a').attr('href'), $(el));
  468. });
  469. }
  470. filterCheck($(list[i]));
  471. }
  472. } else { // Thumbnail
  473. for (i = 0; i < list.length; i++) {
  474. gid = $(list[i]).find('.gl3t a').attr('href').split('/');
  475. galleryId = gid[4] + '.' + gid[5];
  476.  
  477. if (galleries[galleryId] != undefined) { // Visited
  478. if (!$(list[i]).hasClass('ehx-visited')) {
  479. $(list[i]).addClass('ehx-visited');
  480. addStyle($(list[i]), 'v');
  481. if (setStore.titleShow) $(list[i]).find('.gl5t').append('<div style="position: absolute; top: 45px;"><ehx class="ehx-thumbnail">\uD83D\uDC41 ' + buildTime(galleryId, true) + '</ehx></div>');
  482. else $(list[i]).find('.gl5t').after('<ehx class="ehx-thumbnail">\uD83D\uDC41 ' + buildTime(galleryId, true) + '</ehx>');
  483. } else {
  484. $(list[i]).find('.ehx-thumbnail').text('\uD83D\uDC41 ' + buildTime(galleryId, true));
  485. }
  486.  
  487. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
  488. if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text(buildTime(galleryId, false));
  489. } else { // Never Visited
  490. if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
  491. if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text('Never Visited');
  492. $(list[i]).removeClass('ehx-visited');
  493. if ($(list[i]).find('.ehx-thumbnail').length) $(list[i]).find('.ehx-thumbnail').parentElement.remove();
  494. }
  495.  
  496. if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
  497. $(list[i]).addClass('ehx-hidden');
  498. addStyle($(list[i]), 'h');
  499. }
  500.  
  501. if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
  502. $(list[i]).addClass('ehx-downloaded');
  503. addStyle($(list[i]), 'd');
  504. }
  505.  
  506. if (!$(list[i]).find('.ehx-imgHide').length) {
  507. $('<div class="hideContainer_t"><img class="ehx-imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></div>').appendTo($(list[i]).find('.gl5t')).on('click', e => {
  508. var el = $(e.currentTarget).parents().eq(1);
  509. toggleElement($(el).find('.gl3t a').attr('href'), $(el));
  510. });
  511. }
  512. filterCheck($(list[i]));
  513. }
  514. }
  515. updateGListing();
  516. } else { // No Elements pulled, invalid view
  517. displayAlert('No Valid Elements Detected', 5000, true);
  518. return;
  519. }
  520.  
  521. if (setStore.visHide) {
  522. $('.ehx-visited').css({display: 'none'});
  523. $('#ehx-show').text('Show');
  524. }
  525.  
  526. if (setStore.hidShow) {
  527. if ($('.ehx-hidden').length < 25) { $('.ehx-hidden').css({display: $('.ehx-hidden').siblings().not('.ehx-hidden').css('display')}) } // Make sure there are elements on the page
  528. else { // Unless you're an idiot and hid everything on the page
  529. if ($('.gl1t').length) { $('.ehx-hidden').css({display: 'flex'}); } // Use the default values
  530. else { $('.ehx-hidden').css({display: 'table-row'}); }
  531. }
  532. $('#ehxh-show').text('Hide');
  533. }
  534.  
  535. observer.observe($('.itg').get(0), { // Reconnect the observer for changes
  536. childList: true,
  537. subtree: true
  538. });
  539. }
  540.  
  541. /**
  542. * Apply visited CSS to an element on click
  543. */
  544. $('.itg').on('click', 'a', e => {
  545. if (e.which === 3) return; // Ignore right-clicks
  546. if (e.currentTarget.href.split('/')[3] === 'g') {
  547. galleries[e.currentTarget.href.split('/')[4] + '.' + e.currentTarget.href.split('/')[5]] = Date.now();
  548. $('#gLength').text(Object.keys(galleries).length);
  549. addCSS();
  550. }
  551. });
  552.  
  553. $('.itg').on('auxclick', 'a', e => {
  554. if (e.which === 3) return; // Ignore right-clicks
  555. if (e.currentTarget.href.split('/')[3] === 'g') {
  556. galleries[e.currentTarget.href.split('/')[4] + '.' + e.currentTarget.href.split('/')[5]] = Date.now();
  557. $('#gLength').text(Object.keys(galleries).length);
  558. addCSS();
  559. }
  560. });
  561.  
  562. if(setStore.tagPreview && $(".searchnav div > select > option:selected").index() != 3) {
  563. var $tagP = $('<div id="tagPreview">');
  564. $tagP.css({
  565. position: 'absolute',
  566. zIndex: '2',
  567. visiblility: 'hidden !important',
  568. maxWidth: '400px',
  569. background: window.getComputedStyle(document.getElementsByClassName('ido')[0]).backgroundColor,
  570. border: '1px solid #000',
  571. padding: '10px'
  572. });
  573. $tagP.appendTo("body");
  574. $('#tagPreview').css('visibility', 'hidden');
  575.  
  576. element = elem[$(".searchnav div > select > option:selected").index()];
  577.  
  578. $('.itg').on('mouseover', `${element}`, function(e) {
  579. //if(document.getElementById('tagPreview').children.length > 2) { $tagP.empty(); }
  580.  
  581. title = this.children[0].title; // Save the title so we can put it back later, probably unnecessary
  582. this.children[0].title = ""; // Clear the title so we don't have it over our new window
  583.  
  584. var str = this.href.split('/');
  585. generateTagPreview(cache[str[4] + '.' + str[5]]);
  586.  
  587. var posY, posX = (e.pageX + 432 < screen.width) ? e.pageX + 10 : e.pageX - 412;
  588. var scrollHeight = $(document).height();
  589. var scrollPosition = $(window).height() + $(window).scrollTop();
  590.  
  591. if ((scrollHeight - scrollPosition) < (scrollHeight / 10)) { posY = (e.pageY + 300 < scrollHeight) ? e.pageY + 10 : e.pageY - 300; }
  592. else { posY = e.pageY + 10; }
  593.  
  594. $tagP.css({
  595. left: posX,
  596. top: posY,
  597. border: '1px solid ' + window.getComputedStyle(document.getElementsByTagName("a")[0]).getPropertyValue("color"),
  598. visibility: 'visible'
  599. });
  600. $('#tagPreview').css('visibility', 'visible');
  601. }).on('mousemove', `${element}`,function(e) {
  602. var posY, posX = (e.pageX + 432 < screen.width) ? e.pageX + 10 : e.pageX - 412;
  603. var scrollHeight = $(document).height();
  604. var scrollPosition = $(window).height() + $(window).scrollTop();
  605.  
  606. if ((scrollHeight - scrollPosition) < (scrollHeight / 10)) { posY = (e.pageY + document.getElementById('tagPreview').offsetHeight < window.innerHeight) ? e.pageY + 10 : e.pageY - 10 - document.getElementById('tagPreview').offsetHeight; }
  607. else { posY = e.pageY + 10; }
  608.  
  609. $tagP.css({
  610. visibility:'visible',
  611. top: posY,
  612. left: posX
  613. });
  614. $('#tagPreview').css('visibility', 'visible');
  615. }).on('mouseout', `${element}`, function() {
  616. this.children[0].title = title; // Put the saved title back
  617. $tagP.css({
  618. visibility:'hidden'
  619. });
  620. $('#tagPreview').css('visibility', 'hidden');
  621. $tagP.empty(); // Clear out the tag
  622. });
  623.  
  624. $(document).on('scroll', function() {
  625. $('#tagPreview').css('visibility', 'hidden');
  626. });
  627. }
  628.  
  629. /**
  630. * Open the Settings menu and set up all necessary menu functions
  631. */
  632. function settings() {
  633. // There's probably a much easier way to do this, or at least a nicer looking, more technical way
  634. var container = $(`
  635. <div class="ehx-overlay">
  636. <div class="ehx-settings">
  637. <nav id="ehx-topNav">
  638. <button id="ehx-home" style="float: left; border: none;">Main</button>
  639. <span id="setNotice" style="width: 100%; margin-left: 8px; margin-top: 2px; font-weight: lighter; opacity: 0.5; -webkit-opacity: 0.5; text-align: center; position: absolute; left: 0;">` + (reload ? `Applied Settings Will Take Effect On Reload` : ``) + `</span>
  640. <div>
  641. <div class="mencon">
  642. <button class="ehx-menu">Export</button>
  643. <div class="ehx-dropdown">
  644. <a id="ehx-export">Export Galleries</a>
  645. <a id="ehxh-export">Export Hidden Galleries</a>
  646. <a id="ehxd-export">Export DL Galleries</a>
  647. <a id="ehxf-export">Export All As File</a>
  648. </div>
  649. </div>
  650. <div class="mencon">
  651. <button class="ehx-menu">Import</button>
  652. <div class="ehx-dropdown">
  653. <a id="ehx-import">Import Galleries</a>
  654. <a id="ehxh-import">Import Hidden Galleries</a>
  655. <a id="ehxd-import">Import DL Galleries</a>
  656. <a id="ehxf-import">Import From File</a>
  657. <input id="ehxf-input" type="file" style="display: none;" />
  658. </div>
  659. </div>
  660. <a id="ehx-settings-close">&#128939</a>
  661. </div>
  662. </nav>
  663. <div class="section-container">
  664. <section>
  665. <fieldset>
  666. <legend>Settings</legend>
  667. <div>
  668. <label>
  669. <input type="checkbox" class="ehxCheck" id="softHide" ` + (setStore.softHide ? `checked` : ``) + `>Soft Hide Galleries
  670. </label>
  671. <span>: Darken hidden galleries instead of removing them from view</span>
  672. </div>
  673. <div>
  674. <label>
  675. <input type="checkbox" class="ehxCheck" id="minAdd" ` + (setStore.minAdd ? `checked` : ``) + `>Add Visited Column
  676. </label>
  677. <span>: Show visits in an additional column in Minimal/Minimal+ and Compact view modes</span>
  678. <div class="suboptions">
  679. <div>
  680. <span class="ehx-branch">&#8735</span>
  681. <label>
  682. <input type="checkbox" class="ehxCheck" id="minShow" ` + (setStore.minShow ? `checked` : ``) + `>Minimal Show Text
  683. </label>
  684. <span>: Show visits as text instead of hovering tooltip in Minimal/Minimal+ view modes</span>
  685. </div>
  686. </div>
  687. </div>
  688. <div>
  689. <label>
  690. <input type="checkbox" class="ehxCheck" id="cssTT" ` + (setStore.cssTT ? `checked` : ``) + `>CSS Tooltips
  691. </label>
  692. <span>: Replace gallery link tooltips with visited information in all view modes</span>
  693. </div>
  694. <div>
  695. <label>
  696. <input type="checkbox" class="ehxCheck" id="repPub" ` + (setStore.repPub ? `checked` : ``) + `>Replace Published
  697. </label>
  698. <span>: Replace date published with date visited in Minimal/Minimal+ view modes</span>
  699. </div>
  700. <div>
  701. <label>
  702. <input type="checkbox" class="ehxCheck" id="titleShow" ` + (setStore.titleShow ? `checked` : ``) + `>Show Full Title
  703. </label>
  704. <span>: Show the full title of a gallery on hover in Thumbnail view</span>
  705. <div class="suboptions">
  706. <div>
  707. <span class="ehx-branch">&#8735</span>
  708. <label>
  709. <input type="checkbox" class="ehxCheck" id="titleOn" ` + (setStore.titleOn ? `checked` : ``) + `>Always Show Titles
  710. </label>
  711. <span>: Show full title of a gallery always in Thumbnail view</span>
  712. </div>
  713. </div>
  714. </div>
  715. <div>
  716. <label>
  717. <input type="checkbox" class="ehxCheck" id="tagOn" ` + (setStore.tagPreview ? `checked` : ``) + `>Tag Preview
  718. </label>
  719. <span>: Show a tag listing for a gallery on mouse over, excludes Extended view</span>
  720. </div>
  721. <div>
  722. <label>
  723. <input type="checkbox" class="ehxCheck" id="autoUpdate" ` + (setStore.autoUp ? `checked` : ``) + `>Auto Backup IndexedDB
  724. </label
  725. <span>: Automatically download a backup file every:
  726. <select id="bLim" ` + (setStore.autoUp ? `` : `disabled`) + `>
  727. <option>Day</option>
  728. <option>Week</option>
  729. <option>Month</option>
  730. </select>
  731. </span>
  732. </div>
  733. </fieldset>
  734. <fieldset>
  735. <legend>Custom CSS<span style="margin-left:10px"><a id="ehx-adv-css">Advanced</a></span></legend>
  736. <h3>Visited Galleries
  737. <div class="ehx-control" id="ehx-visControls">
  738. <button id="visHistory">View History</button>
  739. <button id="resV">Reset CSS</button>
  740. <button id="ehx-clear">Clear Data</button>
  741. </div>
  742. </h3>
  743. <textarea id="visited" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.visible + `</textarea>
  744. <h3>Hidden Galleries
  745. <div class="ehx-control hideControls">
  746. <button id="hidHistory">View</button>
  747. <button id="resH">Reset CSS</button>
  748. <button id="ehxh-clear">Clear Data</button>
  749. </div>
  750. </h3>
  751. <textarea id="hidden" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.hidden + `</textarea>
  752. <h3>Downloaded Galleries
  753. <div class="ehx-control hideControls">
  754. <button id="dowHistory">View</button>
  755. <button id="resD">Reset CSS</button>
  756. <button id="ehxd-clear">Clear Data</button>
  757. </div>
  758. </h3>
  759. <textarea id="downloaded" class="field" spellcheck="false">` + cssA.download + `</textarea>
  760. <div class="suboptions2">
  761. <button class="ehx-collapsible">Title Filtered Galleries</button>
  762. <div class="ehx-content">
  763. <textarea id="filtered" class="field" spellcheck="false">` + cssA.filter + `</textarea>
  764. <div class="ehx-control sControls">
  765. <button id="resF">Reset CSS</button>
  766. </div>
  767. </div>
  768. <button class="ehx-collapsible">Uploader Filtered Galleries</button>
  769. <div class="ehx-content">
  770. <textarea id="ufiltered" class="field" spellcheck="false">` + cssA.uploader + `</textarea>
  771. <div class="ehx-control sControls">
  772. <button id="resU">Reset CSS</button>
  773. </div>
  774. </div>
  775. <button class="ehx-collapsible">Page Filtered</button>
  776. <div class="ehx-content">
  777. <textarea id="page" class="field" spellcheck="false"placeholder="Insert CSS">` + cssA.page + `</textarea>
  778. <div class="ehx-control sControls">
  779. <button id="resP">Reset CSS</button>
  780. </div>
  781. </div>
  782. <button class="ehx-collapsible">Rating Filtered</button>
  783. <div class="ehx-content">
  784. <textarea id="rating" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.rating + `</textarea>
  785. <div class="ehx-control sControls">
  786. <button id="resR">Reset CSS</button>
  787. </div>
  788. </div>
  789. <button class="ehx-collapsible">Tag Filtered</button>
  790. <div class="ehx-content">
  791. <textarea id="tag" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.tag + `</textarea>
  792. <div class="ehx-control sControls">
  793. <button id="resT">Reset CSS</button>
  794. </div>
  795. </div>
  796. </div>
  797. </fieldset>
  798. <fieldset>
  799. <legend>Filters</legend>
  800. Use one <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions">regular expression</a> per line to filter out matching galleries.
  801. <ul style="margin: 3px 0px; padding-left: 30px;">
  802. <li>E.G. <code>Ongoing</code> will filter out every gallery with <code>ongoing</code>, case-insensitive, in the title. <code>\\[Sample\\]</code> will filter out every gallery with <code>[Sample]</code>, case-insensitive, in the title.</li>
  803. <li>Lines starting with <code>#</code> will be ignored.</li>
  804. </ul>
  805. <textarea id="galFilter">` + filters.title + `</textarea>
  806. <h3>Uploader Filter</h3>
  807. <textarea id="upFilter">` + filters.uploader + `</textarea>
  808. <h3>Tag Filter</h3>
  809. <textarea id="tagFilter">` + filters.tag + `</textarea>
  810. <div>
  811. <label>
  812. <input type="checkbox" class="ehxCheck" id="pFilt" ` + (setStore.pFilter ? `checked` : ``) + `>Page Limit
  813. </label>
  814. <span>: Filter out any gallery with pages less than:
  815. <input id="pLim" type="number" min="1" value="` + setStore.pLimit + `" ` + (setStore.pFilter ? `` : `disabled`) + `/>
  816. </span>
  817. </div>
  818. <div>
  819. <label>
  820. <input type="checkbox" class="ehxCheck" id="stFilt" ` + (setStore.stFilter ? `checked` : ``) + `>Minimum Rating
  821. </label>
  822. <span>: Filter out any gallery with a rating less than:
  823. <input type="number" step="0.01" min="0" max="5" id="stLim" ` + (setStore.stFilter ? `` : `disabled`) + ` />
  824. </span>
  825. </div>
  826. </fieldset>
  827. </section>
  828. <section class="inactive">
  829. <fieldset style="padding-bottom: 2px;">
  830. <legend id="importTitle">Import Galleries</legend>
  831. <textarea id="ehx-importGalleries"></textarea>
  832. <div class="ehx-control" style="margin-top: 2px; margin-bottom: 4px; width: 100%; text-align: right;">
  833. <label style="float: left;">
  834. <input type="checkbox" class="ehxCheck" id="rawUrl">Import Raw URLs
  835. </label>
  836. <button class="close">Close</button>
  837. <button id="importConfirm">Import</button>
  838. <button id="importFF">From File</button>
  839. <input id="ehxf-sInput" type="file" style="display: none;" />
  840. </div>
  841. </fieldset>
  842. </section>
  843. <section class="inactive">
  844. <fieldset style="padding-bottom: 2px;">
  845. <legend id="exportTitle">Export Galleries</legend>
  846. <textarea id="ehx-exportGalleries"></textarea>
  847. <div class="ehx-control" style="margin-top: 2px; margin-bottom: 4px;">
  848. <button class="close">Close</button>
  849. <button id="exportCopy">Copy</button>
  850. <button id="exportSave">Save</button>
  851. </div>
  852. </fieldset>
  853. </section>
  854. <section class="inactive">
  855. <fieldset>
  856. <legend id="history" style="margin-left: 5px;"></legend>
  857. <div id="listingContainer">
  858. </div>
  859. </fieldset>
  860. </section>
  861. <section class="inactive">
  862. <fieldset style="padding-bottom: 2px;">
  863. <legend>Advanced CSS</legend>
  864. <textarea id="ehx-advCss">` + cssC.custom + `</textarea>
  865. <div class="ehx-control" style="margin-top: 2px; margin-bottom: 4px;">
  866. <button class="close">Close</button>
  867. <button id="advCssClear">Clear</button>
  868. <button id="advCssSave">Save</button>
  869. </div>
  870. </fieldset>
  871. </section>
  872. </div>
  873. <div class="applyContainer">
  874. <div class="ehx-control" id="applyCon" style="padding-right: 5px;">
  875. <button id="ehx-apply">Apply</button>
  876. </div>
  877. </div>
  878. </div>
  879. </div>`);
  880. $('body').append(container);
  881. $('body').addClass('noscroll');
  882. galleries = sortObj(galleries);
  883. if (!$('#minAdd').prop('checked')) { $('#minShow').prop('disabled', true); }
  884. if (!$('#titleShow').prop('checked')) { $('#titleOn').prop('disabled', true); }
  885. $('#resV').click(e => { $('#visited').val('box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;'); }); // Default Values
  886. $('#resH').click(e => { $('#hidden').val('box-shadow: inset 0 0 0 500px rgba(255, 40, 0, .2) !important;'); });
  887. $('#resD').click(e => { $('#downloaded').val('box-shadow: inset 0 0 0 500px rgba(30, 180, 60, .2) !important;'); });
  888. $('#resF').click(e => { $('#filtered').val('box-shadow: inset 0 0 0 500px rgba(200, 0, 100, .2) !important;'); });
  889. $('#resU').click(e => { $('#ufiltered').val('box-shadow: inset 0 0 0 500px rgba(222, 184, 135, .2) !important;'); });
  890. $('#resP').click(e => { $('#page').val('box-shadow: inset 0 0 0 500px rgba(0, 0, 180, .2) !important;'); });
  891. $('#resR').click(e => { $('#rating').val('box-shadow: inset 0 0 0 500px rgba(180, 80, 60, .2) !important;'); });
  892. $('#resT').click(e => { $('#tag').val('box-shadow: inset 0 0 0 500px rgba(180, 130, 60, .2) !important'); });
  893. $('#stLim').val(setStore.stLimit);
  894. document.getElementById('bLim').selectedIndex = setStore.autoTime;
  895. $(document).on('change', 'input', e => { // Put the change listener on document since I suck at event propogation and bubbling
  896. if ($('#minAdd').prop('checked')) $('#minShow').prop('disabled', false);
  897. else $('#minShow').prop('disabled', true);
  898.  
  899. if ($('#pFilt').prop('checked')) $('#pLim').prop('disabled', false);
  900. else $('#pLim').prop('disabled', true);
  901.  
  902. if ($('#stFilt').prop('checked')) $('#stLim').prop('disabled', false);
  903. else $('#stLim').prop('disabled', true);
  904.  
  905. if ($('#titleShow').prop('checked')) $('#titleOn').prop('disabled', false);
  906. else $('#titleOn').prop('disabled', true);
  907.  
  908. if ($('#autoUpdate').prop('checked')) $('#bLim').prop('disabled', false);
  909. else $('#bLim').prop('disabled', true);
  910.  
  911. if ($('#minAdd').is(e.target) || $('#minShow').is(e.target) || $('#repPub').is(e.target) || $('#titleShow').is(e.target) || $('#titleOn').is(e.target) || $('#cssTT').is(e.target) || $('#tagOn').is(e.target) || $('#autoUpdate').is(e.target)) {
  912. $('#setNotice').text('Applied Settings Will Take Effect On Reload');
  913. reload = 1;
  914. }
  915. });
  916. $('#bLim').change(e => {
  917. $('#setNotice').text('Applied Settings Will Take Effect On Reload');
  918. reload = 1;
  919. });
  920. $('#ehx-settings-close').click(e => {
  921. $('.ehx-overlay').remove();
  922. $('body').removeClass('noscroll');
  923. });
  924. $('body').click(e => {
  925. if (e.target.className == "ehx-overlay") { // Exit if settings menu isn't clicked
  926. $('.ehx-overlay').remove();
  927. } else if (e.target.className != 'show' && e.target.className != 'ehx-menu') {
  928. $('.show').removeClass('show');
  929. }
  930. if (!$('.ehx-overlay').length) $('body').removeClass('noscroll');
  931. });
  932. $('#ehx-apply').click(e => applySettings());
  933.  
  934. /**
  935. * Parse our HTML options into a temporary JSON array and then stringify it into localStorage
  936. */
  937. function applySettings() { // TODO: Sort this shit out into one block
  938. setStore = { // Store this independantly, so it doesn't mess up table appends
  939. "softHide": $('#softHide').prop('checked'),
  940. "minAdd": setStore.minAdd,
  941. "minShow": setStore.minShow,
  942. "cssTT": $('#cssTT').prop('checked'),
  943. "repPub": setStore.repPub,
  944. "visHide": $('#ehx-show').text() === "Show" ? true : false,
  945. "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
  946. "pFilter": $('#pFilt').prop('checked'),
  947. "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
  948. "stFilter": $('#stFilt').prop('checked'),
  949. "stLimit": $('#stFilt').prop('checked') ? $('#stLim').val() : "0",
  950. "titleShow": setStore.titleShow,
  951. "titleOn": setStore.titleOn,
  952. "tagPreview": $('#tagOn').prop('checked'),
  953. "autoUp": $('#autoUpdate').prop('checked'),
  954. "autoTime": $('#autoUpdate').prop('checked') ? $('#bLim').prop('selectedIndex') : "-1",
  955. "lastBack": setStore.lastBack
  956. }
  957. var tempSto = {
  958. "softHide": $('#softHide').prop('checked'),
  959. "minAdd": $('#minAdd').prop('checked'),
  960. "minShow": $('#minShow').prop('checked'),
  961. "cssTT": $('#cssTT').prop('checked'),
  962. "repPub": $('#repPub').prop('checked'),
  963. "visHide": $('#ehx-show').text() === "Show" ? true : false,
  964. "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
  965. "pFilter": $('#pFilt').prop('checked'),
  966. "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
  967. "stFilter": $('#stFilt').prop('checked'),
  968. "stLimit": $('#stFilt').prop('checked') ? $('#stLim').val() : "0",
  969. "titleShow": $('#titleShow').prop('checked'),
  970. "titleOn": $('#titleOn').prop('checked'),
  971. "tagPreview": $('#tagOn').prop('checked'),
  972. "autoUp": $('#autoUpdate').prop('checked'),
  973. "autoTime": $('#autoUpdate').prop('checked') ? $('#bLim').prop('selectedIndex') : "-1",
  974. "lastBack": setStore.lastBack
  975. }
  976. localStorage.setItem('ehx-settings', JSON.stringify(tempSto)); // Write settings to localStorage
  977. var tempCss = {
  978. "visible": $('#visited').val(),
  979. "hidden": $('#hidden').val(),
  980. "download": $('#downloaded').val(),
  981. "filter": $('#filtered').val(),
  982. "page": $('#page').val(),
  983. "rating": $('#rating').val(),
  984. "tag": $('#tag').val(),
  985. "uploader": $('#ufiltered').val()
  986. }
  987. cssA = tempCss;
  988. localStorage.setItem('ehx-css', JSON.stringify(tempCss));
  989. var tempFilt = { // Remove null entries because bad things happen if they're there
  990. "title": $('#galFilter').val().replace(/^\s*[\r\n]/gm, ''),
  991. "uploader": $('#upFilter').val().replace(/^\s*[\r\n]/gm, ''),
  992. "tag": $('#tagFilter').val().replace(/^\s*[\r\n]/gm, '')
  993. }
  994. filters = tempFilt;
  995. populateFilter();
  996. localStorage.setItem('ehx-filters', JSON.stringify(tempFilt));
  997. updateCSS();
  998. addCSS();
  999. displayAlert('Applied Current Settings', 5000, false);
  1000. }
  1001.  
  1002. $('.ehx-collapsible').click(e => { // Expand our custom filtering CSS boxes
  1003. if ($('.ehx-active').length && !$('.ehx-active').is(e.target)) { // If a menu is open and it isn't the one we're clicking, close it
  1004. $('.ehx-active').next().css('max-height', '');
  1005. $('.ehx-active').toggleClass('ehx-active');
  1006. }
  1007. e.target.classList.toggle('ehx-active');
  1008. var content = e.target.nextElementSibling;
  1009. if (content.style.maxHeight) content.style.maxHeight = null;
  1010. else content.style.maxHeight = '500px';
  1011. });
  1012.  
  1013. function swapContainer(index) {
  1014. $('.section-container section').addClass('inactive');
  1015. $('.section-container section:nth-child(' + index + ')').removeClass('inactive');
  1016. if (index == 2) $('#ehx-importGalleries').val('');
  1017. }
  1018.  
  1019. $('#ehx-import').click(e => {
  1020. swapContainer(2);
  1021. activeStore = galleries;
  1022. activeStoreTitle = 'galleries';
  1023. $('#importTitle').text('Import Galleries');
  1024. });
  1025. $('#ehxh-import').click(e => {
  1026. swapContainer(2);
  1027. activeStore = hidden;
  1028. activeStoreTitle = 'hidden';
  1029. $('#importTitle').text('Import Hidden Galleries');
  1030. });
  1031. $('#ehxd-import').click(e => {
  1032. swapContainer(2);
  1033. activeStore = down;
  1034. activeStoreTitle = 'down';
  1035. $('#importTitle').text('Import Downloaded Galleries');
  1036. });
  1037. $('#ehxf-import').click(e => {
  1038. $('#ehxf-input').trigger('click');
  1039. });
  1040. $('#ehxf-input').change(e => {
  1041. var reader = new FileReader();
  1042. reader.onload = function(event) {
  1043. console.log('EhxVisited: Loaded File Successfully');
  1044. ehxImportF(reader.result, null)
  1045. };
  1046. reader.readAsText($('#ehxf-input')[0].files[0]);
  1047. });
  1048. $('#ehxf-sInput').change(e => {
  1049. var reader = new FileReader();
  1050. reader.onload = function(event) {
  1051. console.log('EhxVisited: Loaded File Successfully');
  1052. ehxImportF(reader.result, activeStoreTitle)
  1053. };
  1054. reader.readAsText($('#ehxf-sInput')[0].files[0]);
  1055. });
  1056. $('#importConfirm').click(e => ehxImport($('#ehx-importGalleries').val().replace(/^\s*[\r\n]/gm, ''), $('#rawUrl').prop('checked')));
  1057. $('#importFF').click(e => { $('#ehxf-sInput').trigger('click'); });
  1058.  
  1059. $('.close').click(e => {
  1060. $('.section-container section').addClass('inactive');
  1061. $('.section-container section:first-child').removeClass('inactive');
  1062. if (!$('#ehx-apply').length) $('.applyContainer').append($('<div class="ehx-control" id="applyCon" style="padding-right: 5px;"><button id="ehx-apply">Apply</button></div>'));
  1063. });
  1064. $('#ehx-home').click(e => {
  1065. $('.section-container section').addClass('inactive');
  1066. $('.section-container section:first-child').removeClass('inactive');
  1067. $('#pages').remove();
  1068. if (!$('#ehx-apply').length) $('.applyContainer').append($('<div class="ehx-control" id="applyCon" style="padding-right: 5px;"><button id="ehx-apply">Apply</button></div>'));
  1069. });
  1070.  
  1071. $('#exportCopy').click(e => {
  1072. $('#ehx-exportGalleries').select();
  1073. document.execCommand('copy');
  1074. displayAlert('Copied Text To Clipboard', 5000);
  1075. });
  1076. $('#exportSave').click(e => {
  1077. ehxExportSF(activeStoreTitle);
  1078. });
  1079. $('#ehx-export').click(e => {
  1080. $('.section-container section').addClass('inactive');
  1081. $('.section-container section:nth-child(3)').removeClass('inactive');
  1082. $('#exportTitle').text('Exported Galleries');
  1083. ehxExport('galleries');
  1084. activeStoreTitle = 'galleries';
  1085. });
  1086. $('#ehxh-export').click(e => {
  1087. $('.section-container section').addClass('inactive');
  1088. $('.section-container section:nth-child(3)').removeClass('inactive');
  1089. $('#exportTitle').text('Exported Hidden Galleries');
  1090. ehxExport('hidden');
  1091. activeStoreTitle = 'hidden';
  1092. });
  1093. $('#ehxd-export').click(e => {
  1094. $('.section-container section').addClass('inactive');
  1095. $('.section-container section:nth-child(3)').removeClass('inactive');
  1096. $('#exportTitle').text('Exported Downloaded Galleries');
  1097. ehxExport('down');
  1098. activeStoreTitle = 'down';
  1099. });
  1100. $('#ehxf-export').click(e => {
  1101. ehxExportF();
  1102. });
  1103.  
  1104. $('#ehx-adv-css').click(e => {
  1105. $('#applyCon').remove();
  1106. $('.section-container section').addClass('inactive');
  1107. $('.section-container section:nth-child(5)').removeClass('inactive');
  1108. });
  1109. $('#advCssClear').click(e => { $('#ehx-advCss').val(""); });
  1110. $('#advCssSave').click(e => {
  1111. cssC.custom = $('#ehx-advCss').val();
  1112. localStorage.setItem('ehx-cssc', JSON.stringify(cssC))
  1113. updateCSS();
  1114. addCSS();
  1115. displayAlert('Applied Custom CSS', 5000, false);
  1116. });
  1117.  
  1118. $('#visHistory').click(e => {
  1119. activeStore = galleries;
  1120. activeStoreTitle = 'galleries';
  1121. generateHistory('Viewed Galleries');
  1122. });
  1123. $('#hidHistory').click(e => {
  1124. activeStore = hidden;
  1125. activeStoreTitle = 'hidden';
  1126. generateHistory('Hidden Galleries');
  1127. });
  1128. $('#dowHistory').click(e => {
  1129. activeStore = down;
  1130. activeStoreTitle = 'down';
  1131. generateHistory('Downloaded Galleries');
  1132. });
  1133.  
  1134. function generateHistory(text) {
  1135. $('#history').text(text);
  1136. $('#applyCon').remove();
  1137.  
  1138. var pageSelect = '<div id="pages">Page <select id="pageCount">';
  1139. for (var i = 0; i < Math.ceil(Object.keys(activeStore).length / 25); i++) {
  1140. pageSelect += '<option>' + (i + 1) + '</option>';
  1141. }
  1142.  
  1143. pageSelect += '</select> of ' + Math.ceil(Object.keys(activeStore).length / 25) + ' pages</div>';
  1144. $('.section-container section').addClass('inactive');
  1145. $('.section-container section:nth-child(4)').removeClass('inactive');
  1146. $('.applyContainer').append($.parseHTML(pageSelect));
  1147.  
  1148. var str = [];
  1149. for (i = 0; i < 25; i++) str[i] = Object.keys(activeStore)[i];
  1150. if (Object.keys(activeStore).length > 0) generateRequest(str);
  1151. }
  1152.  
  1153. $('.applyContainer').on('change', 'select', e => {
  1154. var offset = $('#pageCount option:selected').text() - 1;
  1155. var maxLength = ((offset * 25) + 25 <= Object.keys(activeStore).length) ? (offset * 25) + 25 : Object.keys(activeStore).length;
  1156. var str = [];
  1157. var count = 0;
  1158. for (var i = offset * 25; i < maxLength; i++) str[count++] = Object.keys(activeStore)[i];
  1159. generateRequest(str);
  1160. });
  1161.  
  1162. $('#ehx-clear').click(e => { // TODO: I want to condense these three blocks -----
  1163. if (confirm('Are you sure you wish to clear all visited galleries?')) { // Make sure to double check before deleting
  1164. var objStore2 = db.transaction('galleries', 'readwrite').objectStore('galleries');
  1165. var openReq = objStore2.clear();
  1166. openReq.onsuccess = e => {
  1167. displayAlert('Cleared all entries', 5000, false);
  1168. $('#ehx-clear').text('Clear Data');
  1169. galleries = JSON.parse('{"data":{}}');
  1170. $('#gLength').text(Object.keys(galleries).length);
  1171. $('.ehx-visited').removeClass('ehx-visited');
  1172. addCSS();
  1173. }
  1174. }
  1175. });
  1176. $('#ehxh-clear').click(e => {
  1177. var objStore2 = db.transaction('hidden', 'readwrite').objectStore('hidden');
  1178. var openReq = objStore2.getAll();
  1179. openReq.onsuccess = e => {
  1180. if (confirm('Are you sure you wish to clear all hidden galleries?')) {
  1181. var objStore3 = db.transaction('hidden', 'readwrite').objectStore('hidden');
  1182. var openReq = objStore3.clear();
  1183. openReq.onsuccess = e => {
  1184. displayAlert('Cleared all entries', 5000);
  1185. $('#ehxh-clear').text('Clear Data');
  1186. hidden = JSON.parse('{"data":{}}');
  1187. $('#hLength').text(Object.keys(hidden).length);
  1188. $('.ehx-hidden').removeClass('ehx-hidden');
  1189. addCSS();
  1190. }
  1191. }
  1192. }
  1193. });
  1194. $('#ehxd-clear').click(e => {
  1195. var objStore2 = db.transaction('down', 'readwrite').objectStore('down');
  1196. var openReq = objStore2.getAll();
  1197. openReq.onsuccess = e => {
  1198. if (confirm('Are you sure you wish to clear all downloaded galleries?')) {
  1199. var objStore3 = db.transaction('down', 'readwrite').objectStore('down');
  1200. var openReq = objStore3.clear();
  1201. openReq.onsuccess = e => {
  1202. displayAlert('Cleared all entries', 5000);
  1203. $('#ehxd-clear').text('Clear Data');
  1204. $('.ehx-downloaded').removeClass('ehx-downloaded');
  1205. addCSS();
  1206. }
  1207. }
  1208. }
  1209. }); // ------//
  1210.  
  1211. // Make sure there's not more than one top menu item open
  1212. $('.ehx-menu').click(e => {
  1213. if ($('.show').length) {
  1214. if ($('.show').prev().is(e.target)) { $(e.target).next().toggleClass('show'); }
  1215. else {
  1216. $('.show').removeClass('show');
  1217. $(e.target).next().toggleClass('show');
  1218. }
  1219. } else $(e.target).next().toggleClass('show');
  1220. });
  1221. }
  1222.  
  1223. /**
  1224. * Check a specified element through the filters individually, then apply jqstyle tags for CSS
  1225. * @param {Object} el - A specific element within the DOM
  1226. */
  1227. function filterCheck(el) { // TODO: See about sorting out this clusterfuck
  1228. var gToken = $(el).find("a[href*='/g/']").attr('href').split('/');
  1229. filterArrCheck(filterArr, cache[gToken[4] + '.' + gToken[5]].title, el, 'f');
  1230.  
  1231. var upload = cache[gToken[4] + '.' + gToken[5]].uploader;
  1232. filterArrCheck(uploaderArr, upload, el, 'u');
  1233. filterArrCheck(tagArr, cache[gToken[4] + '.' + gToken[5]].tags, el, 't');
  1234.  
  1235. // Filter our galleries through the star limit filter
  1236. if (setStore.stFilter && setStore.stLimit > 0) {
  1237. if (cache[gToken[4] + '.' + gToken[5]].rating < setStore.stLimit) {
  1238. if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
  1239. if (!($(el).attr('data-jqstyle') || '').match('r')) addStyle($(el), 'r');
  1240. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'r');
  1241. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'r');
  1242.  
  1243.  
  1244. var pages = cache[gToken[4] + '.' + gToken[5]].filecount;
  1245. if (setStore.pFilter && setStore.pLimit > 0) {
  1246. if (parseInt(pages) < parseInt(setStore.pLimit)) {
  1247. if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
  1248. if (!($(el).attr('data-jqstyle') || '').match('p')) addStyle($(el), 'p');
  1249. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'p');
  1250. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'p');
  1251. }
  1252.  
  1253. function filterArrCheck(array, test, el, flag) {
  1254. if (array.length) {
  1255. if (array.some(rx => rx.test(test))) {
  1256. if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
  1257. if (!($(el).attr('data-jqstyle') || '').match(flag)) addStyle($(el), flag);
  1258. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), flag);
  1259. } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), flag);
  1260. }
  1261.  
  1262. /**
  1263. * Adds the specified style flag to a specified element
  1264. * @param {Object} el - A specific element within the DOM
  1265. * @param {String} flag - A character to mark an element for JQ Styling CSS rules
  1266. */
  1267. function addStyle(el, flag) {
  1268. if ($(el).attr('data-jqstyle')) $(el).attr('data-jqstyle', $(el).attr('data-jqstyle') + flag);
  1269. else $(el).attr('data-jqstyle', flag);
  1270. }
  1271.  
  1272. /**
  1273. * Removes the specified style flag from a specified element
  1274. * @param {Object} el - A specific element within the DOM
  1275. * @param {string} flag - A JQ Style flag to remove from an element
  1276. */
  1277. function removeStyle(el, flag) {
  1278. if ($(el).attr('data-jqstyle')) {
  1279. $(el).attr('data-jqstyle', $(el).attr('data-jqstyle').replace(flag, ''));
  1280. if (!$(el).attr('data-jqstyle')) $(el).removeClass('ehx-hidden'); // Replacing the flag brought jqstyle to blank
  1281. } else $(el).removeClass('ehx-hidden');
  1282. }
  1283.  
  1284. /**
  1285. * Toggles a specified element's hidden status
  1286. * @param {String} tga - Full gallery URL
  1287. * @param {Object} el - A specific element within the DOM
  1288. */
  1289. function toggleElement(tga, el) {
  1290. const request = indexedDB.open('ehxvisited', 2);
  1291. var tgid = tga.split('/')[4] + '.' + tga.split('/')[5];
  1292.  
  1293. request.onsuccess = e => {
  1294. db = e.target.result;
  1295. var objStore = db.transaction('hidden', 'readwrite').objectStore('hidden');
  1296. var openReq = objStore.openCursor(tgid);
  1297. openReq.onsuccess = e => {
  1298. var cursor = e.target.result;
  1299. if (cursor) { // Gallery already exists within our hidden table
  1300. cursor.delete();
  1301. console.log('EhxVisited: Removed ' + tgid + ' from hidden list.');
  1302. $(el).css('display', '');
  1303. $(el).removeClass('ehx-hidden');
  1304. removeStyle($(el), 'h');
  1305. delete hidden[tgid]; // Remove gallery listing from our local store of hidden galleries
  1306. } else {
  1307. objStore.put({id: tgid}); // Put the gallery into our hidden table
  1308. console.log('EhxVisited: Added ' + tgid + ' to hidden list.');
  1309. $(el).addClass('ehx-hidden');
  1310. if ($('#ehxh-show').text() === 'Hide') $(el).css('display', $('.gl1t').length ? 'flex' : 'table-row');
  1311. addStyle($(el), 'h');
  1312. hidden[tgid] = 1; // Add gallery listing to our local store of hidden galleries
  1313. }
  1314. updateGListing();
  1315. }
  1316. }
  1317. }
  1318.  
  1319. /**
  1320. * Deletes the item from the history tab and database
  1321. * TODO: Combine this and toggleElement into a more generic function
  1322. * @param {String} href - URL of the gallery
  1323. * @param {HTML Element} el - The parent element to apply CSS to
  1324. */
  1325. function deleteHistory(href, el) {
  1326. const request = indexedDB.open('ehxvisited', 2);
  1327. var tgid = href.split('/')[4] + '.' + href.split('/')[5];
  1328.  
  1329. request.onsuccess = e => {
  1330. db = e.target.result;
  1331. var objStore = db.transaction(activeStoreTitle, 'readwrite').objectStore(activeStoreTitle);
  1332. var openReq = objStore.delete(tgid);
  1333. openReq.onsuccess = e => {
  1334. console.log('EhxVisited: Removed ' + tgid + ' from ' + activeStoreTitle + ' DB.');
  1335. delete activeStore[tgid];
  1336. displayAlert("Removed " + tgid + " from the database", 5000, false);
  1337. $(el).addClass('removed');
  1338. updateGListing();
  1339. }
  1340. }
  1341. delete activeStore[tgid];
  1342. addCSS();
  1343. }
  1344.  
  1345. /**
  1346. * Sorts a dictionary based off the value of a key
  1347. * @param {Dictionary} obj - A dictionary
  1348. * @return {Dictionary} sorted_obj - A sorted dictionary
  1349. */
  1350. function sortObj(obj) {
  1351. var items = Object.keys(obj).map(k => [k, obj[k]]); // Convert Dictionary into an array of arrays
  1352. items.sort((a, b) => b[1] - a[1]); // Sort by value of original key value pair
  1353. var sorted_obj = {};
  1354. $.each(items, (k, v) => {
  1355. sorted_obj[v[0]] = v[1] // Put the items back into Dictionary form
  1356. })
  1357. return (sorted_obj);
  1358. }
  1359.  
  1360. /**
  1361. * Returns an object with the current URL parameters
  1362. */
  1363. function getUrlVars() {
  1364. var vars = {};
  1365. var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
  1366. vars[key] = value;
  1367. });
  1368. return vars;
  1369. }
  1370.  
  1371. /**
  1372. * Convert the star count of a specified element to a double
  1373. * @param {Object} el - A specific element within the DOM
  1374. */
  1375. function getStarNumber(el, transpose) {
  1376. var starCount = {5: '0px -1px', 4.5: '0px -21px', 4: '-16px -1px', 3.5: '-16px -21px', 3: '-32px -1px', 2.5: '-32px -21px', 2: '-48px -1px', 1.5: '-48px -21px', 1: '-64px -1px', 0.5: '-64px -21px'};
  1377. if (!transpose) {
  1378. var stars = $(el).find('.ir').css('background-position');
  1379. return Object.keys(starCount).find(key => starCount[key] === stars);
  1380. } else return starCount[(Math.round(el * 2) / 2).toFixed(1)]; // Ratings are given in x.xx numbers, but we need either whole integers, or half integers
  1381. }
  1382.  
  1383. /**
  1384. * Build the time difference string
  1385. * @param {String} gid - Gallery ID
  1386. * @param {Boolean} time - Include timeDifference in returned string
  1387. * @param {Boolean} abbrv - Abbreviate for timeDifference
  1388. */
  1389. function buildTime(gid, time, abbrv) {
  1390. var d = new Date(galleries[gid]);
  1391. var str = d.getFullYear().toString() + '-' + (d.getMonth() + 1).toString().padStart(2, '0') + '-' + d.getDate().toString().padStart(2, '0') + ' ' + d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0');
  1392. if (time) return timeDifference(gid, abbrv) + ' ' + str;
  1393. return str;
  1394. }
  1395.  
  1396. /**
  1397. * Get time difference in words
  1398. * @param {Date} previous - Previous date to compare against Date.now()
  1399. * @param {Boolean} abbreviate - Should the text string have abbreviatated text
  1400. */
  1401. function timeDifference(gallery, abbreviate) {
  1402. var previous = galleries[gallery];
  1403. var msPerMinute = 60 * 1000;
  1404. var msPerHour = msPerMinute * 60;
  1405. var msPerDay = msPerHour * 24;
  1406. var msPerMonth = msPerDay * 30;
  1407. var msPerYear = msPerDay * 365;
  1408. var elapsed = Date.now() - previous;
  1409. var count;
  1410.  
  1411. if (elapsed < msPerMinute) {
  1412. return Math.round(elapsed / 1000) + ((typeof abbreviate !== 'undefined') ? '&nbsp;sec' : Math.round(elapsed / 1000) > 1 ? ' seconds ago' : ' second ago');
  1413. } else if (elapsed < msPerHour) {
  1414. return Math.round(elapsed / msPerMinute) + ((typeof abbreviate !== 'undefined') ? '&nbsp;min' : Math.round(elapsed / msPerMinute) > 1 ? ' minutes ago' : ' minute ago');
  1415. } else if (elapsed < msPerDay) {
  1416. return Math.round(elapsed / msPerHour) + ((typeof abbreviate !== 'undefined') ? Math.round(elapsed / msPerHour) > 1 ? '&nbsp;hrs' : '&nbsp;hr' : Math.round(elapsed / msPerHour) > 1 ? ' hours ago' : ' hour ago');
  1417. } else if (elapsed < msPerMonth) {
  1418. return Math.round(elapsed / msPerDay) + ((typeof abbreviate !== 'undefined') ? Math.round(elapsed / msPerDay) > 1 ? '&nbsp;days' : '&nbsp;day' : Math.round(elapsed / msPerDay) > 1 ? ' days ago' : ' day ago');
  1419. } else if (elapsed < msPerYear) {
  1420. return Math.round(elapsed / msPerMonth) + ((typeof abbreviate !== 'undefined') ? Math.round(elapsed / msPerMonth) > 1 ? '&nbsp;mos' : '&nbsp;mo' : Math.round(elapsed / msPerMonth) > 1 ? ' months ago' : ' month ago');
  1421. } else {
  1422. return Math.round(elapsed / msPerYear) + ((typeof abbreviate !== 'undefined') ? Math.round(elapsed / msPerYear) > 1 ? '&nbsp;yrs' : '&nbsp;yr' : Math.round(elapsed / msPerYear) > 1 ? ' years ago' : ' year ago');
  1423. }
  1424. }
  1425.  
  1426. /**
  1427. * Displays a div at the top of the page with a message
  1428. * @param {String} message - A message to be displayed within the alert
  1429. * @param {Integer} timeout - Millseconds message should be displayed for
  1430. * @param {Boolean} error - Is this an error message
  1431. */
  1432. function displayAlert(message, timeout, error) {
  1433. var alert = $('<div class="ehx-notice ' + ((error) ? 'alert' : '') + '">EhxVisited: ' + message + '</div>');
  1434. $(alert).hide().appendTo('.ehx-alertContainer').fadeIn(1000);
  1435. setTimeout(e => { $('.ehx-notice').fadeOut(1000, f => { $('.ehx-notice').remove(); }); }, timeout);
  1436. }
  1437.  
  1438. /**
  1439. * Generate the JSON request for the E-H API
  1440. * @param {IndexedDB Keys} data - Object keys within the data portion of our matrices
  1441. */
  1442. function generateRequest(data) {
  1443. $('#listingContainer').empty();
  1444. var reqList = []; // We use an array for our gidlist, since the API can handle up to 25 galleries per request
  1445. for (var i = 0; i < data.length; i++) {
  1446. if (data[i] == undefined) continue;
  1447. var str = data[i].split('.'); // Split the key to match request specifications of galleryID, galleryToken
  1448. reqList[i] = [str[0], str[1]];
  1449. }
  1450. var request = {"method": "gdata", "gidlist": reqList, "namespace": 1};
  1451.  
  1452. var req = new XMLHttpRequest();
  1453. req.onreadystatechange = e => {
  1454. if (req.readyState == 4) {
  1455. if (req.status == 200) {
  1456. var apirsp = JSON.parse(req.responseText);
  1457. //console.log(apirsp);
  1458. for (var i = 0; i < apirsp.gmetadata.length; i++) generateListing(apirsp.gmetadata[i]);
  1459. } else {
  1460. console.error();
  1461. displayAlert("Request Failed", 5000, true);
  1462. }
  1463. }
  1464. }
  1465. 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
  1466. req.send(JSON.stringify(request));
  1467. }
  1468.  
  1469. function generateCacheRequest(request) {
  1470. return new Promise(function (resolve, reject) {
  1471. var req = new XMLHttpRequest();
  1472. req.onreadystatechange = e => {
  1473. if (req.readyState == 4) {
  1474. if (req.status == 200) {
  1475. var apirsp;
  1476. try {
  1477. apirsp = JSON.parse(req.responseText);
  1478. } catch (e) {
  1479. console.log('EhxVisited: Error' + e.message);
  1480. }
  1481. //console.log(apirsp);
  1482. for (var i = 0; i < apirsp.gmetadata.length; i++) cache[apirsp.gmetadata[i].gid + '.' + apirsp.gmetadata[i].token] = apirsp.gmetadata[i];
  1483. resolve();
  1484. } else {
  1485. console.error();
  1486. reject(req.status);
  1487. }
  1488. }
  1489. }
  1490. req.open("POST", document.location.origin + "/api.php", true);
  1491. req.send(JSON.stringify(request));
  1492. });
  1493. }
  1494.  
  1495. /**
  1496. * Generate the HTML code for each individual listing in history views
  1497. * @param {JSON Array} glisting - E-H API response item for a specified gallery
  1498. */
  1499. function generateListing(glisting) {
  1500. var d = new Date(glisting.posted * 1000);
  1501. generateTags(glisting);
  1502. let taglist = "<table><tbody>";
  1503. for (const namespace in tags) {
  1504. taglist += `<tr><td class="tc">${namespace}:</td><td>`;
  1505. for (var i = 0; i < tags[namespace].length; i++) {
  1506. 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>`;
  1507. }
  1508. taglist += "</td></tr>";
  1509. }
  1510. taglist += "</tbody></table>";
  1511. let torrentList = "";
  1512. if (glisting.torrentcount > 0) {
  1513. torrentList = `<div class='torrents'style='border-top:1px solid #000; padding: 10px 10px 5px 10px; margin-right: 10px;'><p><span class='halp' title="If these links don't work, this gallery likely had a parent gallery">Possible Torrents:</span></p>`;
  1514. for (var j = 0; j < glisting.torrentcount; j++) {
  1515. torrentList += `<div><img src="` + (window.location.hostname.indexOf("exhentai") >= 0 ? "https://exhentai.org/img/mr.gif" : "https://ehgt.org/g/mr.gif") + `" /><a class="tlink" title="` + glisting.torrents[j].name + `" href="` + generateTorrentLink(glisting, j) + `">` + glisting.torrents[j].name + `</a>&#9;<span>` + getPrettyFileSize(glisting.torrents[j].fsize) + `</span><span style="float: right; position: relative; top: 3px;">Added ` + getTimestampDateString(glisting.torrents[j].added * 1000) + `</span></p></div>`;
  1516. }
  1517. torrentList += "</div>";
  1518. }
  1519. // TODO: See about replacing the custom date with a buildTime call
  1520. var listing = $(`
  1521. <div class="ehx-listing">
  1522. <div class="thumb">
  1523. <a href="` + document.location.origin + '/g/' + glisting.gid + '/' + glisting.token + `">
  1524. <img src="` + glisting.thumb + `" />
  1525. </a>
  1526. </div>
  1527. <div class="listBody">
  1528. <div class="title" style="width: 90%">
  1529. <a href="` + document.location.origin + '/g/' + glisting.gid + '/' + glisting.token + '/">' + glisting.title + `</a>
  1530. <p>` + glisting.title_jpn + `</p>
  1531. </div>
  1532. <div class="taglist">` + taglist + torrentList + `</div>
  1533. <div class="ehx-category">
  1534. <div class="cn ` + category[glisting.category.toLowerCase().replace(/ /g, '').replace(/-/g, '')] + `">
  1535. <a href="` + document.location.origin + '/' + glisting.category.toLowerCase().replace(/ /g, '') + '">' + glisting.category + `</a>
  1536. </div>
  1537. <div class="ehx-date">
  1538. ` + getTimestampDateString(d).slice(0, 16) + `
  1539. </div>
  1540. </div>
  1541. <div class="rating">
  1542. <div>
  1543. <a href="` + document.location.origin + '/uploader/' + glisting.uploader + '">' + glisting.uploader + `</a>
  1544. </div>
  1545. <div>
  1546. ` + glisting.filecount + ` pages
  1547. </div>
  1548. <div class="ir" style="float: right; background-position: ` + getStarNumber(glisting.rating, true) + `"></div>
  1549. </div>
  1550. <div class="tagToggle"><a>Show Tags and Torrents &#x25BE;</a></div>
  1551. </div>
  1552. </div>`);
  1553. $('#listingContainer').append(listing);
  1554. $('.tagToggle > a').on('click', e => {
  1555. if ($(e.currentTarget).parents().eq(1).find('.taglist').css('display') == 'none') {
  1556. e.stopImmediatePropagation(); // Have to put this in because apparently, this event wants to fire like 12 times in a row
  1557. $(e.currentTarget).parents().eq(1).find('.taglist').css('display', 'block');
  1558. $(e.currentTarget).parents().eq(2).css('height', 'unset');
  1559. $(e.currentTarget).parents().eq(2).css('height', parseInt($(e.currentTarget).parents().eq(2).css('height').substr(0, 3)) + 70 + 'px');
  1560. $(e.currentTarget).text('Hide Tags and Torrents \u25B4');
  1561. } else {
  1562. e.stopImmediatePropagation(); // I have no idea why it does that, it just does
  1563. $(e.currentTarget).parents().eq(1).find('.taglist').css('display', 'none');
  1564. $(e.currentTarget).parents().eq(2).css('height', '140px');
  1565. $(e.currentTarget).text('Show Tags and Torrents \u25BE');
  1566. }
  1567. });
  1568. $('<div class="imgContainer"><img class="ehx-imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></div>').appendTo($('.listBody').last()).on('click', e => {
  1569. deleteHistory($(e.currentTarget).parents().eq(1).find('a').attr('href'), $(e.currentTarget).parents().eq(1));
  1570. });
  1571. }
  1572.  
  1573. function generateCacheListing(element, tempCache) {
  1574. for (var i = 0; i < $(element).length; i++) {
  1575. let split = $(element)[i].href.split('/');
  1576. if (cache[split[4] + '.' + split[5]] == undefined) tempCache[i] = [split[4], split[5]];
  1577. }
  1578. return tempCache;
  1579. }
  1580.  
  1581. /**
  1582. * Generate the inner HTML for the tag preview window
  1583. * @param {JSON} apirsp - The E-H API response
  1584. */
  1585. function generateTagPreview(apirsp) {
  1586. generateTags(apirsp); // We actually have to generate the tag list from the raw JSON file
  1587. document.getElementById('tagPreview').innerHTML = `<h1 id="gn">${apirsp.title}</h1><h1 id="gj">${apirsp.title_jpn}</h1>`;
  1588. let taglist = "<div id='taglist' style='height:fit-content;'><table><tbody>";
  1589. for (const namespace in tags) {
  1590. taglist += `<tr><td class="tc">${namespace}:</td><td>`;
  1591. for (var i = 0; i < tags[namespace].length; i++) {
  1592. 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>`;
  1593. }
  1594. taglist += "</td></tr>";
  1595. }
  1596. taglist += "</tbody></table></div>";
  1597. document.getElementById('tagPreview').innerHTML = document.getElementById('tagPreview').innerHTML + taglist;
  1598. }
  1599.  
  1600. /**
  1601. * Converts the tag listing within the API response to a categorized array
  1602. * @param {JSON} apirsp - The E-H API response
  1603. */
  1604. function generateTags(apirsp) {
  1605. tags = {}; // Reset the tags array for each new tag listing
  1606. if (Array.isArray(apirsp.tags)) {
  1607. for (const jsonTag of apirsp.tags) {
  1608. const stringTag = getJsonString(jsonTag);
  1609. if (stringTag === null) { continue; }
  1610.  
  1611. const {tag, namespace} = getTagAndNamespace(stringTag);
  1612.  
  1613. let namespaceTags;
  1614. if (tags.hasOwnProperty(namespace)) { namespaceTags = tags[namespace]; }
  1615. else {
  1616. namespaceTags = [];
  1617. tags[namespace] = namespaceTags;
  1618. }
  1619. namespaceTags.push(tag);
  1620. }
  1621. }
  1622. }
  1623.  
  1624. function generateTorrentLink(glisting, index) {
  1625. if (window.location.hostname.indexOf("exhentai") > 0) return window.location.origin + "/torrent/" + glisting.gid + "/" + glisting.torrents[index].hash + ".torrent";
  1626. else return "https://ehtracker.org/get/" + glisting.gid + "/" + glisting.torrents[index].hash + ".torrent";
  1627. }
  1628.  
  1629. function getTagAndNamespace(tag) {
  1630. const pattern = /^(?:([^:]*):)?([\w\W]*)$/;
  1631. const match = pattern.exec(tag);
  1632. return (match !== null) ?
  1633. ({ tag: match[2], namespace: match[1] || "misc" }) :
  1634. ({ tag: tag, namespace: "misc" });
  1635. }
  1636.  
  1637. function toProperCase(text) {
  1638. return text.replace(/(^|\W)(\w)/g, (m0, m1, m2) => `${m1}${m2.toUpperCase()}`);
  1639. }
  1640.  
  1641. function getJsonString(value) {
  1642. if (typeof(value) === "string") { return value; }
  1643. if (typeof(value) === "undefined" || value === null) { return value; }
  1644. return `${value}`;
  1645. }
  1646.  
  1647. function getPrettyFileSize(bytes) {
  1648. const ii = fileSizeLabels.length - 1;
  1649. let i = 0;
  1650. while (i < ii && bytes >= 1024) {
  1651. bytes /= 1024;
  1652. ++i;
  1653. }
  1654. return `${bytes.toFixed(i === 0 ? 0 : 2)} ${fileSizeLabels[i]}`;
  1655. }
  1656.  
  1657. function getTimestampDateString(timestamp) {
  1658. const date = new Date(timestamp);
  1659. const year = date.getUTCFullYear().toString();
  1660. const month = (date.getUTCMonth() + 1).toString().padStart(2, "0");
  1661. const day = date.getUTCDate().toString().padStart(2, "0");
  1662. const hour = date.getUTCHours().toString().padStart(2, "0");
  1663. const minute = date.getUTCMinutes().toString().padStart(2, "0");
  1664. const seconds = date.getUTCSeconds().toString().padStart(2, "0");
  1665. return `${year}-${month}-${day} ${hour}:${minute}:${seconds}`;
  1666. }
  1667.  
  1668. /**
  1669. * Import user data into our indexedDB
  1670. * @param {String} items - String of exported data to import
  1671. * @param {Boolean} raw - Boolean representing if we're importing raw URLs
  1672. */
  1673. function ehxImport(items, raw) {
  1674. const req = indexedDB.open('ehxvisited', 2);
  1675. req.onsuccess = e => {
  1676. if (db == null) db = e.target.result;
  1677. var objStore = db.transaction(activeStoreTitle, 'readwrite').objectStore(activeStoreTitle);
  1678. var count = 0, sp = '';
  1679.  
  1680. if (raw) {
  1681. var textArr = items.split('\n');
  1682. var tempStr = '';
  1683. for (var i = 0; i < textArr.length; i++) {
  1684. var str = textArr[i].split('/')
  1685. tempStr += str[4] + "." + str[5] + ":" + Date.now() + ";";
  1686. }
  1687. sp = tempStr.split(';'); // I just pulled this block from a function I wrote, it could be adapted better
  1688. } else { sp = items.split(';'); }
  1689. sp = sp.filter(Boolean); // Filter out any null ('') entries
  1690. insertNext();
  1691.  
  1692. /**
  1693. * Push entries into the specified indexedDB store
  1694. */
  1695. function insertNext() {
  1696. if (count < sp.length) {
  1697. var str = sp[count].split(':');
  1698. objStore.put({id: str[0], visited: parseInt(str[1])}).onsuccess = insertNext; // Update the record if it's there, or add it if it's not, then continue
  1699. activeStore[str[0]] = str[1];
  1700. ++count;
  1701. } else {
  1702. displayAlert('Imported ' + count + ' entries', 5000);
  1703. console.log('EhxVisited: Merge Completed');
  1704. updateGListing();
  1705. addCSS();
  1706. }
  1707. }
  1708. }
  1709. }
  1710.  
  1711. function ehxImportF(data, store) {
  1712. const req = indexedDB.open('ehxvisited', 2);
  1713. req.onsuccess = e => {
  1714. if (db == null) db = e.target.result;
  1715. var transaction = db.transaction(db.objectStoreNames, 'readwrite')
  1716. var sp = '', splData;
  1717.  
  1718. // Split file by delimiter
  1719. splData = data.split('\n');
  1720.  
  1721. // Split galleries
  1722. sp = (store == null) ? splData[0].split(';') : data.split(';');
  1723. sp = sp.filter(Boolean);
  1724. activeStore = galleries;
  1725. if (store == null || store == 'galleries') insertNext('galleries');
  1726.  
  1727. // Split hidden
  1728. sp = (store == null) ? splData[1].split(';') : data.split(';');
  1729. sp = sp.filter(Boolean);
  1730. activeStore = hidden;
  1731. if (store == null || store == 'hidden') insertNext('hidden');
  1732. // Split downloaded
  1733. sp = (store == null) ? splData[2].split(';') : data.split(';')
  1734. sp = sp.filter(Boolean);
  1735. activeStore = down;
  1736. if (store == null || store == 'down') insertNext('down');
  1737.  
  1738. function insertNext(objStore) {
  1739. var count = 0;
  1740. sp.forEach(function(s) {
  1741. var str = s.split(':');
  1742. var request = transaction.objectStore(objStore).put({id: str[0], visited: parseInt(str[1])}); // Update the record if it's there, or add it if it's not, then continue
  1743. activeStore[str[0]] = str[1];
  1744. request.onsuccess = e => {
  1745. count++;
  1746. if (count == sp.length) { callback(); }
  1747. }
  1748. })
  1749. function callback() {
  1750. var countRequest = transaction.objectStore(objStore).count();
  1751. countRequest.onsuccess = e => {
  1752. displayAlert('Merged ' + countRequest.result + ' entries', 5000);
  1753. console.log('EhxVisited: Merge Completed');
  1754. updateGListing();
  1755. addCSS();
  1756. }
  1757. }
  1758. }
  1759. }
  1760. req.onerror = function(event) {
  1761. console.log(event.target.errorCode);
  1762. }
  1763. }
  1764.  
  1765. /**
  1766. * Fills a text area with formatted gallery data for export
  1767. * @param {IndexedDB Title} store - The name of an indexedDB store
  1768. */
  1769. function ehxExport(store) {
  1770. const req = indexedDB.open('ehxvisited', 2);
  1771. req.onsuccess = e => {
  1772. if (db == null) db = e.target.result;
  1773. var objStore = db.transaction(store, 'readonly').objectStore(store);
  1774. var openReq = objStore.getAll();
  1775. openReq.onsuccess = e => {
  1776. var data = '';
  1777. for (var i in e.target.result) {
  1778. data += e.target.result[i].id + ':' + e.target.result[i].visited + ';';
  1779. }
  1780. $('#ehx-exportGalleries').val(data); // Fill with formatted data
  1781. }
  1782. }
  1783. }
  1784.  
  1785. function ehxExportSF(store) {
  1786. const req = indexedDB.open('ehxvisited', 2);
  1787. req.onsuccess = e => {
  1788. if (db == null) db = e.target.result;
  1789. var objStore = db.transaction(store, 'readonly').objectStore(store);
  1790. var openReq = objStore.getAll();
  1791. openReq.onsuccess = e => {
  1792. var data = '';
  1793. for (var i in e.target.result) {
  1794. data += e.target.result[i].id + ':' + e.target.result[i].visited + ';';
  1795. }
  1796. var a = document.createElement('a');
  1797. document.body.appendChild(a);
  1798. a.style = 'display: none';
  1799. var blob = new Blob([data], {type: "octet/stream"}),
  1800. urlD = window.URL.createObjectURL(blob);
  1801. a.href = urlD;
  1802. a.download = 'ehxvisited-' + store + '-' + getTimestampDateString(Date.now()) + '.txt';
  1803. a.click();
  1804. window.URL.revokeObjectURL(urlD);
  1805. a.remove();
  1806. }
  1807. }
  1808. }
  1809.  
  1810. function ehxExportF() {
  1811. var data = '';
  1812. const req = indexedDB.open('ehxvisited', 2);
  1813. req.onsuccess = e => {
  1814. if (db == null) db = e.target.result;
  1815. var objStore = db.transaction('galleries', 'readonly').objectStore('galleries');
  1816. var openReq = objStore.getAll();
  1817. openReq.onsuccess = f => {
  1818. for (var i in f.target.result) {
  1819. data += f.target.result[i].id + ':' + f.target.result[i].visited + ';';
  1820. }
  1821. data += '\n';
  1822. var objStore2 = db.transaction('hidden', 'readonly').objectStore('hidden');
  1823. var openReq2 = objStore2.getAll();
  1824. openReq2.onsuccess = g => {
  1825. for (i in g.target.result) {
  1826. data += g.target.result[i].id + ':' + g.target.result[i].visited + ';';
  1827. }
  1828. data += '\n';
  1829. var objStore3 = db.transaction('down', 'readonly').objectStore('down');
  1830. var openReq3 = objStore3.getAll();
  1831. openReq3.onsuccess = h => {
  1832. for (i in h.target.result) {
  1833. data += h.target.result[i].id + ':' + h.target.result[i].visited + ';';
  1834. }
  1835. var a = document.createElement('a');
  1836. document.body.appendChild(a);
  1837. a.style = 'display: none';
  1838. var blob = new Blob([data], {type: "octet/stream"}),
  1839. urlD = window.URL.createObjectURL(blob);
  1840. a.href = urlD;
  1841. a.download = 'ehxvisited-' + getTimestampDateString(Date.now()) + '.txt';
  1842. a.click();
  1843. window.URL.revokeObjectURL(urlD);
  1844. a.remove();
  1845. }
  1846. }
  1847. }
  1848. }
  1849. }
  1850.  
  1851. /**
  1852. * Remove our stylesheet with transient CSS, and then re-add it with the updated CSS
  1853. */
  1854. function updateCSS() {
  1855. cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
  1856. $('#setStyle').remove();
  1857. $('#ehxAdv').remove();
  1858. $(`<style id="setStyle" data-jqstyle="ehxVisited">
  1859. table.itg > tbody > .ehx-visited, .ehx-visited { ` + cssA.visible + ` }
  1860. table.itg > tbody > .ehx-visited.ehx-hidden, .ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
  1861. .ehx-hidden { ` + cssD + cssA.hidden + ` }
  1862. .ehx-downloaded { ` + cssA.download + ` }
  1863. .ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
  1864. .ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
  1865. .ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
  1866. .ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
  1867. .ehx-hidden[data-jqstyle*="t"] {` + cssA.tag + `}
  1868. </style>`).appendTo('head');
  1869. $(`<style id="ehxAdv" data-jqstyle="ehxVisited">` + cssC.custom + `</style>`).appendTo('head');
  1870. }
  1871.  
  1872. // The giant CSS block
  1873. $(`<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/symbola" type="text/css"/>
  1874. <style data-jqstyle='ehxVisited'>
  1875. #ehx-controls {
  1876. padding: 3px 1px;
  1877. text-align: center;
  1878. display: block;
  1879. }
  1880. #ehx-settings, #ehx-show, #ehxh-show {
  1881. cursor: pointer;
  1882. text-decoration: underline;
  1883. }
  1884. #ehx-hideCount > span { border-bottom: 1px dotted currentColor; }
  1885. #ehx-importGalleries, #ehx-exportGalleries, #ehx-advCss { min-height: 414px; }
  1886. #ehx-settings-close {
  1887. text-decoration: none;
  1888. position: absolute;
  1889. top: 0px;
  1890. right: 5px;
  1891. font-size: 1.4em;
  1892. }
  1893. @-moz-document url-prefix() {
  1894. #ehx-settings-close {
  1895. top: -2px;
  1896. -webkit-text-stroke: 1px;
  1897. }
  1898. }
  1899. #ehx-export, #ehxh-export, #ehx-import, #ehxh-import, #ehx-settings-close { cursor: pointer; }
  1900. #ehx-topNav {
  1901. border-bottom: 1px solid threedface;
  1902. left: -4px;
  1903. min-width: 898px;
  1904. }
  1905. #ehx-visControls { top: -6px; }
  1906. div > .ehx-imgHide {
  1907. cursor: pointer !important;
  1908. position: absolute;
  1909. bottom: 3px;
  1910. left: 2px;
  1911. }
  1912. ehx { font-family:` + $('body').css('font-family') + `, arial, symbola, SymbolaRegular; }
  1913. input[type="checkbox"].ehxCheck {
  1914. -webkit-appearance: none;
  1915. border: 1px solid #F1F1F1BB;
  1916. padding: 5px;
  1917. top: 4px;
  1918. background-color: transparent;
  1919. }
  1920. input[type="checkbox"].ehxCheck:checked:after {
  1921. content: '\\2714';
  1922. position: absolute;
  1923. top: -8px;
  1924. left: 1px;
  1925. font-size: 1.1em;
  1926. }
  1927. input[type="checkbox"].ehxCheck:focus { outline: none; }
  1928. input[type="checkbox"].ehxCheck:hover { cursor: pointer; }
  1929. nav > div {
  1930. text-align: right;
  1931. margin-right: 30px;
  1932. }
  1933. nav > div button {
  1934. border: none !important;
  1935. padding: 1px 20px 1px 10px !important;
  1936. position: relative;
  1937. }
  1938. section:nth-child(4) fieldset {
  1939. padding: 0px;
  1940. min-height: 467px;
  1941. }
  1942. td.hideContainer .ehx-imgHide {
  1943. cursor: pointer !important;
  1944. vertical-align: middle;
  1945. }
  1946. .ehx-active:after { content: '\\2212' !important; }
  1947. .ehx-alertContainer {
  1948. position: fixed;
  1949. width: 100%;
  1950. z-index: 200;
  1951. top: 0px;
  1952. font-size: 8pt;
  1953. }
  1954. .applyContainer {
  1955. padding-top: 5px;
  1956. padding-right: 8px;
  1957. border-top: 1px solid threedface;
  1958. width: 100%;
  1959. position: relative;
  1960. left: -4px;
  1961. }
  1962. .ehx-branch {
  1963. position: absolute;
  1964. left: 10px;
  1965. top: 1px;
  1966. margin-left: -3px;
  1967. }
  1968. .ehx-category {
  1969. text-align: center;
  1970. position: absolute;
  1971. left: 115px;
  1972. bottom: 3px;
  1973. line-height: 20px;
  1974. }
  1975. .ehx-collapsible {
  1976. cursor: pointer;
  1977. width: 100%;
  1978. border: 0;
  1979. outline: none;
  1980. text-align: left;
  1981. font-size: 1.25em;
  1982. background-color: rgba(0, 0, 0, 0);
  1983. color: inherit;
  1984. font-weight: bold;
  1985. padding:5px 3px;
  1986. position: relative;
  1987. }
  1988. .ehx-collapsible:after {
  1989. content: '\\002B';
  1990. font-weight:bold;
  1991. float:right;
  1992. margin-right:5px;
  1993. }
  1994. .ehx-collapsible:before {
  1995. content: '';
  1996. position: absolute;
  1997. padding: 4px;
  1998. border-bottom: 1px solid threedface;
  1999. border-left: 1px solid threedface;
  2000. top: 5px;
  2001. left: -11px;
  2002. }
  2003. .ehx-collapsible:hover { background-color: rgba(255, 255, 255, 0.1); }
  2004. .ehx-content {
  2005. max-height: 0;
  2006. overflow: hidden;
  2007. transition: all .2s ease-in-out;
  2008. border-bottom: 1px solid threedface;
  2009. }
  2010. .ehx-content button {
  2011. margin-top: 3px;
  2012. margin-right: 10px;
  2013. }
  2014. .ehx-control {
  2015. position: relative;
  2016. float: right;
  2017. right: -5px;
  2018. }
  2019. .ehx-date {
  2020. font-style: italic;
  2021. font-weight: bold;
  2022. }
  2023. .ehx-dropdown {
  2024. display: none;
  2025. position: absolute;
  2026. z-index: 999;
  2027. min-width: 150px;
  2028. padding: 2px;
  2029. border-radius: 1px;
  2030. box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
  2031. right: 0px;
  2032. border: 1px solid threedface;
  2033. background: ` + $('.ido').css('background') + `;
  2034. background: ` + $('.ido').css('backgroundColor') + `;
  2035. }
  2036. .ehx-dropdown a {
  2037. display: block;
  2038. text-decoration: none;
  2039. padding-right:2px;
  2040. }
  2041. .ehx-dropdown a:hover { background: rgba(255,255,255,0.2); }
  2042. .ehx-compact {
  2043. border-style: solid;
  2044. border-width: 1px 0;
  2045. text-align: center;
  2046. }
  2047. .ehx-extended {
  2048. width: 120px;
  2049. position: absolute;
  2050. left: 3px;
  2051. top: 172px;
  2052. text-align: center;
  2053. font-size: 8pt;
  2054. line-height: 1.5;
  2055. }
  2056. .ehx-extended-favs {
  2057. padding: 3px 1px;
  2058. display: block;
  2059. line-height: 1.5;
  2060. }
  2061. .ehx-minimal {
  2062. border-left: 1px solid #6f6f6f4d;
  2063. text-align: center;
  2064. }
  2065. .ehx-thumbnail {
  2066. display: block;
  2067. text-align: center;
  2068. margin: 3px 0 5px;
  2069. line-height: 12px;
  2070. }
  2071. .ehx-visited .gl3e { min-height: 206px; }
  2072. .ehx-visited .gl4e { min-height: 264px !important; }
  2073. .gl2c { width: 115px; }
  2074. .gltc ehx { white-space: nowrap; }
  2075. .gltc td.hideContainer {
  2076. border-bottom: 1px solid #6f6f6f4d;
  2077. border-top: 1px solid #6f6f6f4d;
  2078.  
  2079. }
  2080. .gl5t {
  2081. height: 63px;
  2082. position: relative;
  2083. }
  2084. .glte .ehx-imgHide {
  2085. cursor: pointer !important;
  2086. padding: 4px 2px 0px 1px;
  2087. top: 3px;
  2088. right: 5px;
  2089. left: initial;
  2090. bottom: initial;
  2091. }`
  2092. + ((setStore.titleShow && !setStore.titleOn) ? `.itg > .gl1t > a:first-of-type {
  2093. overflow: hidden;
  2094. min-height: 32px;
  2095. max-height: 32px;
  2096. margin: 6px 4px 0;
  2097. position: relative;
  2098. display: block;
  2099. }
  2100. .itg > .gl1t > a:first-of-type > .glname {
  2101. overflow: visible;
  2102. min-height: auto;
  2103. max-height: none;
  2104. margin: 0;
  2105. }
  2106. .itg > .gl1t:hover > a:first-of-type,
  2107. .itg > .gl1t:hover > .glname {
  2108. overflow: visible;
  2109. z-index: 10;
  2110. }
  2111. .itg > .gl1t:hover > a:first-of-type > .glname,
  2112. .itg > .gl1t:hover > .glft > div:nth-child(1) {
  2113. padding-bottom: 0.25em;
  2114. height: fit-content;
  2115. height: -moz-fit-content;
  2116. background: rgba(0,0,0,0.5);
  2117. }
  2118. div.gl4t {
  2119. font-weight: bold;
  2120. text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
  2121. }` : ``) + (setStore.titleOn ? `.itg > .gl1t > a:first-of-type {
  2122. margin: 6px 4px 0;
  2123. position: relative;
  2124. display: block;
  2125. }
  2126. .itg > .gl1t > a:first-of-type > .glname {
  2127. overflow: visible;
  2128. min-height: auto;
  2129. max-height: none;
  2130. margin: 0;
  2131. }
  2132. .itg > .gl1t:hover > a:first-of-type,
  2133. .itg > .gl1t:hover > .glname {
  2134. overflow: visible;
  2135. z-index: 10;
  2136. }
  2137. .itg > .gl1t:hover > a:first-of-type > .glname,
  2138. .itg > .gl1t:hover > .glft > div:nth-child(1) {
  2139. height: fit-content;
  2140. height: -moz-fit-content;
  2141. background: rgba(0,0,0,0.5);
  2142. }
  2143. .itg > .gl1t > .glft > div:nth-child(1) {
  2144. padding-bottom: 0.25em;
  2145. }
  2146. div.gl4t {
  2147. height: fit-content !important;
  2148. height: -moz-fit-content !important;
  2149. max-height: none;
  2150. padding-bottom: 0.25em;
  2151. font-weight: bold;
  2152. text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
  2153. }` : ``) + `
  2154. .hideContainer_t { top: -17px; }
  2155. .hideControls { top: -3px; }
  2156. .imgContainer > .ehx-imgHide {
  2157. cursor: pointer !important;
  2158. position: absolute;
  2159. top: 1px;
  2160. right: 4px;
  2161. bottom: unset;
  2162. left: unset;
  2163. }
  2164. .inactive { display: none; }
  2165. .listBody {
  2166. width: 100%;
  2167. height: 100%;
  2168. vertical-align: top;
  2169. position: relative;
  2170. }
  2171. .ehx-listing {
  2172. width: 100%;
  2173. height: 140px;
  2174. border-bottom: 1px solid threedface;
  2175. margin-top: 10px;
  2176. padding-bottom: 5px;
  2177. }
  2178. .ehx-listing a {
  2179. text-decoration: none;
  2180. position: relative;
  2181. z-index: 1;
  2182. }
  2183. .ehx-listing:last-child { border-bottom: none; }
  2184. .mencon {
  2185. display: inline-block;
  2186. position: relative;
  2187. }
  2188. .ehx-menu:after {
  2189. content: '\\2335';
  2190. position: absolute;
  2191. right: 5px;
  2192. bottom: 1px;
  2193. }
  2194. .noscroll { overflow: hidden; }
  2195. .ehx-notice {
  2196. position: relative;
  2197. width: 500px;
  2198. height: 20px;
  2199. top: 20px;
  2200. left: 50%;
  2201. transform: translate(-50%, 0px);
  2202. background: rgba(70, 130, 180, 0.8);
  2203. font-size: 1.2em;
  2204. font-weight: bold;
  2205. color: white;
  2206. padding-top: 5px;
  2207. border-radius: 8px;
  2208. z-index: 999;
  2209. text-align: center;
  2210. }
  2211. .ehx-notice.alert {
  2212. background: rgba(165, 42, 42, 0.8);
  2213. }
  2214. .ehx-notice:not(:first-child) {
  2215. top: 30px;
  2216. }
  2217. .ehx-overlay {
  2218. background: rgba(0,0,0,0.5);
  2219. display: -webkit-flex;
  2220. display: flex;
  2221. position: fixed;
  2222. top: 0;
  2223. left: 0;
  2224. width: 100%;
  2225. height: 100%;
  2226. z-index: 100;
  2227. font-size: 9pt;
  2228. }
  2229. .ehx-overlay button:not(.ehx-collapsible) {
  2230. background-color: transparent;
  2231. border-radius: 6px;
  2232. border: 1px solid threedface;
  2233. cursor: pointer;
  2234. font-weight: bold;
  2235. padding: 3px 20px;
  2236. text-decoration: none;
  2237. color: inherit;
  2238. margin-left: 5px;
  2239. }
  2240. .ehx-overlay button:not(.ehx-collapsible):hover { background-color: rgba(255, 255, 255, 0.1); }
  2241. .ehx-overlay button:not(.ehx-collapsible):focus { outline: none; }
  2242. .rating {
  2243. text-align: right;
  2244. line-height: 18px;
  2245. position: absolute;
  2246. right: 5px;
  2247. bottom: 8px;
  2248. }
  2249. .removed {
  2250. opacity: 0.5;
  2251. -webkit-opacity: 0.5;
  2252. pointer-events: none;
  2253. }
  2254. .sControls {
  2255. top: -3px;
  2256. margin-bottom: 5px;
  2257. }
  2258. @-moz-document url-prefix() {
  2259. .sControls {
  2260. top: initial;
  2261. margin-top: 3px;
  2262. margin-bottom: 5px;
  2263. }
  2264. }
  2265. .section-container {
  2266. text-align: left;
  2267. overflow: auto;
  2268. margin: 5px 0px 5px 0px;
  2269. padding-bottom: 5px;
  2270. }
  2271. .section-container textarea:disabled, .section-container input:disabled, .section-container select:disabled {
  2272. opacity: 0.6;
  2273. -webkit-opacity: 0.6;
  2274. }
  2275. .section-container input[type="number"] {
  2276. border: 1px solid #8d8d8d;
  2277. margin-left:0px;
  2278. text-align: center;
  2279. width: 50px;
  2280. }
  2281. .section-container select { margin-left: 0px; }
  2282. .section-container code {
  2283. color: #000;
  2284. background-color: #FFF;
  2285. }
  2286. .section-container fieldset { padding-right: 18px; }
  2287. .ehx-settings {
  2288. background: ` + $('.ido').css('background') + `;
  2289. background: ` + $('.ido').css('backgroundColor') + `;
  2290. box-sizing: border-box;
  2291. height: 555px;
  2292. max-height: 100%;
  2293. width: 900px;
  2294. max-width: 100%;
  2295. margin: auto;
  2296. padding: 5px;
  2297. display: -webkit-flex;
  2298. display: flex;
  2299. -webkit-flex-direction: column;
  2300. flex-direction: column;
  2301. box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.5);
  2302. }
  2303. .ehx-settings nav {
  2304. text-align: right;
  2305. padding-bottom: 5px;
  2306. font-weight: bold;
  2307. position: relative;
  2308. }
  2309. .ehx-settings legend {
  2310. font-size: 10pt;
  2311. font-weight: bold;
  2312. }
  2313. .ehx-settings label {
  2314. font-weight: bold;
  2315. text-decoration: underline;
  2316. cursor: pointer;
  2317. }
  2318. .ehx-settings h3 {
  2319. margin: 3px;
  2320. position: relative;
  2321. }
  2322. .ehx-settings input { vertical-align: -1px; }
  2323. .ehx-settings textarea {
  2324. width: 100%;
  2325. height: 50px;
  2326. resize: vertical;
  2327. margin-bottom: 5px;
  2328. }
  2329. .show { display:block }
  2330. .suboptions { position: relative; }
  2331. .suboptions > div {
  2332. position: relative;
  2333. padding-left: 1.4em;
  2334. }
  2335. .suboptions2 {
  2336. margin-left: 4px;
  2337. padding-left: 10px;
  2338. margin-right: -9px;
  2339. }
  2340. .taglist {
  2341. display: none;
  2342. padding-left: 120px;
  2343. }
  2344. .tagToggle {
  2345. position: absolute;
  2346. left: 50%;
  2347. bottom: 8px;
  2348. cursor: pointer;
  2349. }
  2350. .thumb {
  2351. display: inline-block;
  2352. width: 100px;
  2353. margin: 0px 10px;
  2354. float: left;
  2355. }
  2356. .thumb img {
  2357. max-width: 100%;
  2358. max-height: 135px;
  2359. }
  2360. .title { font-size: 12pt; }
  2361. .title p {
  2362. font-size: 0.6em;
  2363. overflow: hidden;
  2364. text-overflow: ellipsis;
  2365. white-space: nowrap;
  2366. }
  2367. .tlink {
  2368. display: inline-block;
  2369. top: 3px;
  2370. margin-right: 1.5px;
  2371. padding-left: 3px;
  2372. width: 400px;
  2373. white-space: nowrap;
  2374. text-overflow: ellipsis;
  2375. overflow: hidden;
  2376. }
  2377. </style>`).appendTo('head');
  2378. $(`<style id="setStyle" data-jqstyle="ehxVisited">
  2379. table.itg > tbody > tr.ehx-visited, .gl1t.ehx-visited { ` + cssA.visible + ` }
  2380. table.itg > tbody > tr.ehx-visited.ehx-hidden, .gl1t.ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
  2381. .ehx-hidden { ` + cssD + cssA.hidden + ` }
  2382. .ehx-downloaded { ` + cssA.download + ` }
  2383. .ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
  2384. .ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
  2385. .ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
  2386. .ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
  2387. .ehx-hidden[data-jqstyle*="t"] {` + cssA.tag + `}
  2388. </style>`).appendTo('head');
  2389. $(`<style id="ehxAdv" data-jqstyle="ehxVisited">` + cssC.custom + `</style>`).appendTo('head');
  2390. })();