· 5 years ago · Jan 10, 2021, 08:32 PM
1// ==UserScript==
2// @name EhxVisited
3// @namespace https://sleazyfork.org/en/users/285675-hauffen
4// @version 2.56.11.1
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// @include /https?:\/\/(e-|ex)hentai\.org\/.*/
9// ==/UserScript==
10
11(function() {
12 window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
13 window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: 'readwrite'};
14 window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
15
16 if (!window.indexedDB) {
17 console.warn("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
18 return;
19 }
20
21 /*═════════════════════════════╗
22 ║ Configuration Defaults ║
23 ╚═════════════════════════════*/
24 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};
25 var filters = '';
26 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;"};
27 var cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
28 var 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==';
29 /*════════════════════════════*/
30
31 try { // Converting the old format to the new format // This can probably be removed now since it was only for transitioning
32 filters = localStorage.getItem('ehx-filters') ? JSON.parse(localStorage.getItem('ehx-filters')) : {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999"}; // JSON.parse isn't a fan of unique characters
33 } catch (e) {
34 filters = localStorage.getItem('ehx-filters') ? {"title": localStorage.getItem('ehx-filters'), "uploader": "#xcaliber9999"} : {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999"};
35 }
36
37 let db = null, $ = window.jQuery;
38 var filterArr = [], uploaderArr = [];
39 var observer = new MutationObserver(e => {
40 addCSS();
41 });
42
43 var spl = document.URL.split('/');
44 var d1 = spl[3];
45 var galleries, hidden, down;
46 var activeStore, activeStoreTitle;
47 var ehxClearConfirm = 0, ehxhClearConfirm = 0, ehxdClearConfirm = 0, reload = 0, offset = 0;
48 const category = {doujinshi: 'ct2', manga: 'ct3', artistcg: 'ct4', gamecg: 'ct5', western: 'cta', nonh: 'ct9', imageset: 'ct6', cosplay: 'ct7', asianporn: 'ct8', misc: 'ct1'};
49
50 if (d1 == 'g') addGallery('galleries', spl[4] + '.' + spl[5]); // Add the current page to galleries
51 if (d1.startsWith('gallerytorrents')) $('#torrentinfo a').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().t));
52 if (d1.startsWith('archiver')) {
53 $(':submit').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().token));
54 $('a').click(e => addGallery('down', getUrlVars().gid + '.' + getUrlVars().token));
55 }
56 if ((d1.substr(0, 1).match(/[?#fptw]/i) && !d1.startsWith('toplist')) || !d1 ) {
57 $('<div class="alertContainer"></div>').appendTo('body');
58 populate();
59 }
60
61 /**
62 * Populate the custom filter array with user input converted into regular expressions
63 */
64 function populateFilter() {
65 filterArr = []; // Start fresh for when we call this again
66 uploaderArr = [];
67 if (filters.title != '') pushRegex('title', filterArr);
68 if (filters.uploader != '') pushRegex('uploader', uploaderArr);
69
70 function pushRegex(store, arr) {
71 var tempArr = filters[store].split('\n');
72 for (var i = 0; i < tempArr.length; i++) {
73 if (tempArr[i].startsWith('#')) continue;
74 var regex;
75 try {
76 regex = new RegExp(tempArr[i], 'i');
77 } catch(e) {
78 displayAlert('Invalid Regex On Line ' + (i + 1), 5000, true);
79 continue;
80 }
81 arr.push(regex);
82 }
83 }
84 }
85
86 /**
87 * Add a gallery to our IndexedDB
88 */
89 function addGallery(store, gid) {
90 const request = indexedDB.open('ehxvisited', 2);
91
92 request.onupgradeneeded = e => { // Generate our database if it's not there
93 db = e.target.result;
94
95 if (!db.objectStoreNames.contains('galleries')) db.createObjectStore('galleries', {keyPath: 'id'});
96 if (!db.objectStoreNames.contains('hidden')) db.createObjectStore('hidden', {keyPath: 'id'});
97 if (!db.objectStoreNames.contains('down')) db.createObjectStore('down', {keyPath: 'id'});
98 };
99
100 request.onsuccess = e => {
101 db = e.target.result;
102
103 var objStore = db.transaction(store, 'readwrite').objectStore(store);
104 var openRequest = objStore.openCursor(gid);
105
106 openRequest.onsuccess = e => {
107 var cursor = openRequest.result;
108 if (cursor) { // Update entry if key exists
109 cursor.update({id: gid, visited: Date.now()});
110 console.log('EhxVisited: Updated ' + gid);
111 } else { // Otherwise, add entry
112 objStore.add({id: gid, visited: Date.now()});
113 console.log('EhxVisited: Added ' + gid);
114 }
115 };
116
117 openRequest.onerror = e => {
118 console.log(`EhxVisited: Something bad happened with gallery ${gid}: ${e.target.error}`);
119 };
120 };
121 };
122
123 /**
124 * Updates the hidden gallery count in the header object
125 */
126 function updateGListing() {
127 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
128 var hCount = $('.ehx-hidden').length, vAmount = $('.ehx-hidden.ehx-visited').length;
129 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;
130 if (!setStore.softHide) {
131 $('#hideCount').html('There ' + (hCount > 1 || hCount == 0 ? 'are ' : 'is ') + '<span>' + hCount + ' hidden ' + (hCount > 1 || hCount == 0 ? 'galleries' : 'gallery') + '</span> on this page.');
132 } else {
133 $('#hideCount').html('There are <span>0 hidden galleries</span> on this page.');
134 }
135 $('#hideCount > span').prop('title', 'Hidden: ' + hAmount + ' | Visited: ' + vAmount + ' | Filtered: ' + fAmount + ' | Page Limit: ' + pAmount + ' | Rating Limit: ' + rAmount + ' | Uploader: ' + uAmount);
136 $('#hLength').text(Object.keys(hidden).length);
137 $('#gLength').text(Object.keys(galleries).length);
138 }
139
140 /**
141 * Convert the star count of a specified element to a double
142 * @param {Object} el - A specific element within the DOM
143 */
144 function getStarNumber(el, transpose) {
145 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'};
146 if (!transpose) {
147 var stars = $(el).find('.ir').css('background-position');
148 return Object.keys(starCount).find(key => starCount[key] === stars);
149 } 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
150 }
151
152 /**
153 * Check a specified element through the filters individually, then apply jqstyle tags for CSS
154 * @param {Object} el - A specific element within the DOM
155 */
156 function filterCheck(el) { // TODO: See about sorting out this clusterfuck
157 if (filterArr.length) {
158 if (filterArr.some(rx => rx.test($(el).find('.glink').text()))) { // Test our gallery name through our regex filters
159 if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
160 if (!($(el).attr('data-jqstyle') || '').match('f')) addStyle($(el), 'f');
161 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'f');
162 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'f');
163
164 var upload = ''; // Uploader name is stored in one of two elements based on view modes, not displayed in Thumbnail view
165 if ($('.gl1e').length) upload = $(el).find('.gl3e > div:nth-child(4)').text();
166 else if ($('.gl1c').length || $('.gl1m').length) upload = $(el).find('.glhide > div a').text();
167
168 if (uploaderArr.length) {
169 if (uploaderArr.some(rx => rx.test(upload))) {
170 if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
171 if (!($(el).attr('data-jqstyle') || '').match('u')) addStyle($(el), 'u');
172 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'u');
173 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'u');
174
175 // Filter our galleries through the star limit filter
176 if (setStore.stFilter && setStore.stLimit > 0) {
177 if (getStarNumber(el, false) < setStore.stLimit) {
178 if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
179 if (!($(el).attr('data-jqstyle') || '').match('r')) addStyle($(el), 'r');
180 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'r');
181 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'r');
182
183 var pages = 0; // Page Count is stored in a lot of different random elements throughout, there are probably better selectors for this
184 if ($('.gl1e').length) pages = $(el).find('.gl3e > div:nth-child(5)').text().split(' ')[0];
185 else if ($('.gl1c').length) pages = $(el).find('.gl4c > div:nth-child(2)').text().split(' ')[0];
186 else if ($('.gl1t').length) pages = $(el).find('.gl5t > div:nth-child(2) > div:nth-child(2)').text().split(' ')[0];
187 else pages = $(el).find('.gl2m > div:nth-child(2) > div:nth-child(2) > div:nth-child(2) > div:nth-child(2)').text().split(' ')[0];
188
189 if (setStore.pFilter && setStore.pLimit > 0) {
190 if (parseInt(pages) < parseInt(setStore.pLimit)) {
191 if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
192 if (!($(el).attr('data-jqstyle') || '').match('p')) addStyle($(el), 'p');
193 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'p');
194 } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'p');
195 }
196
197 /**
198 * Adds the specified style flag to a specified element
199 * @param {Object} el - A specific element within the DOM
200 * @param {String} flag - A character to mark an element for JQ Styling CSS rules
201 */
202 function addStyle(el, flag) {
203 if ($(el).attr('data-jqstyle')) $(el).attr('data-jqstyle', $(el).attr('data-jqstyle') + flag);
204 else $(el).attr('data-jqstyle', flag);
205 }
206
207 /**
208 * Removes the specified style flag from a specified element
209 * @param {Object} el - A specific element within the DOM
210 * @param {string} flag - A JQ Style flag to remove from an element
211 */
212 function removeStyle(el, flag) {
213 if ($(el).attr('data-jqstyle')) {
214 $(el).attr('data-jqstyle', $(el).attr('data-jqstyle').replace(flag, ''));
215 if (!$(el).attr('data-jqstyle')) $(el).removeClass('ehx-hidden'); // Replacing the flag brought jqstyle to blank
216 } else $(el).removeClass('ehx-hidden');
217 }
218
219 /**
220 * Toggles a specified element's hidden status
221 * @param {String} tga - Full gallery URL
222 * @param {Object} el - A specific element within the DOM
223 */
224 function toggleElement(tga, el) {
225 const request = indexedDB.open('ehxvisited', 2);
226 var tgid = tga.split('/')[4] + '.' + tga.split('/')[5];
227
228 request.onsuccess = e => {
229 db = e.target.result;
230 var objStore = db.transaction('hidden', 'readwrite').objectStore('hidden');
231 var openReq = objStore.openCursor(tgid);
232 openReq.onsuccess = e => {
233 var cursor = e.target.result;
234 if (cursor) { // Gallery already exists within our hidden table
235 cursor.delete();
236 console.log('EhxVisited: Removed ' + tgid + ' from hidden list.');
237 $(el).css('display', '');
238 $(el).removeClass('ehx-hidden');
239 removeStyle($(el), 'h');
240 delete hidden[tgid]; // Remove gallery listing from our local store of hidden galleries
241 } else {
242 objStore.put({id: tgid}); // Put the gallery into our hidden table
243 console.log('EhxVisited: Added ' + tgid + ' to hidden list.');
244 $(el).addClass('ehx-hidden');
245 if ($('#ehxh-show').text() === 'Hide') $(el).css('display', $('.gl1t').length ? 'flex' : 'table-row');
246 addStyle($(el), 'h');
247 hidden[tgid] = 1; // Add gallery listing to our local store of hidden galleries
248 }
249 updateGListing();
250 }
251 }
252 }
253
254 /**
255 * Deletes the item from the history tab and database
256 * TODO: Combine this and toggleElement into a more generic function
257 * @param {String} href - URL of the gallery
258 * @param {HTML Element} el - The parent element to apply CSS to
259 */
260 function deleteHistory(href, el) {
261 const request = indexedDB.open('ehxvisited', 2);
262 var tgid = href.split('/')[4] + '.' + href.split('/')[5];
263
264 request.onsuccess = e => {
265 db = e.target.result;
266 var objStore = db.transaction(activeStoreTitle, 'readwrite').objectStore(activeStoreTitle);
267 var openReq = objStore.delete(tgid);
268 openReq.onsuccess = e => {
269 console.log('EhxVisited: Removed ' + tgid + ' from ' + activeStoreTitle + ' DB.');
270 delete activeStore[tgid];
271 displayAlert("Removed " + tgid + " from the database", 5000, false);
272 $(el).addClass('removed');
273 updateGListing();
274 }
275 }
276 delete activeStore[tgid];
277 addCSS();
278 }
279
280 /**
281 * Sorts a dictionary based off the value of a key
282 * @param {Dictionary} obj - A dictionary
283 * @return {Dictionary} sorted_obj - A sorted dictionary
284 */
285 function sortObj(obj) {
286 var items = Object.keys(obj).map(k => [k, obj[k]]); // Convert Dictionary into an array of arrays
287 items.sort((a, b) => b[1] - a[1]); // Sort by value of original key value pair
288 var sorted_obj = {};
289 $.each(items, (k, v) => {
290 sorted_obj[v[0]] = v[1] // Put the items back into Dictionary form
291 })
292 return (sorted_obj);
293 }
294
295 /**
296 * Returns an object with the current URL parameters
297 */
298 function getUrlVars() {
299 var vars = {};
300 var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m, key, value) {
301 vars[key] = value;
302 });
303 return vars;
304 }
305
306 /**
307 * Fill our local gallery listings so we can preform easier operations on the data.
308 * Also set up the majority of our global HTML elements and their functions.
309 */
310 function populate() { // TODO: Separate the HTML entries from the population portion
311 populateFilter();
312 galleries = [];
313 hidden = [];
314 down = [];
315 const request = indexedDB.open('ehxvisited', 2);
316
317 request.onupgradeneeded = e => {
318 db = e.target.result;
319
320 if (!db.objectStoreNames.contains('galleries')) db.createObjectStore('galleries', {keyPath: 'id'});
321 if (!db.objectStoreNames.contains('hidden')) db.createObjectStore('hidden', {keyPath: 'id'});
322 if (!db.objectStoreNames.contains('down')) db.createObjectStore('down', {keyPath: 'id'});
323 };
324
325 request.onsuccess = e => { // TODO: See about multiple transactions, or just do these async
326 db = e.target.result;
327 var objStore = db.transaction('galleries', 'readonly').objectStore('galleries');
328 var openReq = objStore.getAll();
329 openReq.onsuccess = f => {
330 console.log('EhxVisited: Populated global variables.');
331 var transform = f.target.result;
332 for (var i = 0; i < transform.length; i++) {
333 galleries[transform[i].id] = transform[i].visited; // Force matrix data into array data
334 }
335 var gLength = Object.keys(galleries).length
336 var objStore2 = db.transaction('hidden', 'readonly').objectStore('hidden');
337 var openReq2 = objStore2.getAll();
338 openReq2.onsuccess = g => {
339 var transform2 = g.target.result;
340 for (i = 0; i < transform2.length; i++) {
341 hidden[transform2[i].id] = 1; // Force matrix data into array data
342 }
343 galleries = sortObj(galleries);
344 var hLength = Object.keys(hidden).length
345 var objStore3 = db.transaction('down', 'readonly').objectStore('down');
346 var openReq3 = objStore3.getAll();
347 openReq3.onsuccess = h => {
348 var transform3 = h.target.result;
349 for (i = 0; i < transform3.length; i++) {
350 down[transform3[i].id] = 1;
351 }
352 $($('h1').text() == 'Favorites' ? '.ido > div:nth-child(3)' : '#toppane').append(
353 `<ehx id="ehx-controls">Galleries visited: <span id="gLength">` + gLength + `</span> ( <span id="ehx-menu-control"></span><a id="ehx-settings">Settings</a> )
354 <br/>Hidden Galleries: <span id="hLength">` + hLength + `</span><span id="ehxh-menu-control"></span></ehx>`);
355 if (!setStore.softHide) {
356 $('#ehx-menu-control').append('<a id="ehx-show">' + ((setStore.visHide) ? 'Show' : 'Hide') + '</a> / ');
357 $('#ehxh-menu-control').append(' ( <a id="ehxh-show">' + ((setStore.hidShow) ? 'Hide' : 'Show') + '</a> )');
358 }
359 $('#ehx-settings').click(e => {
360 e.preventDefault();
361 settings();
362 });
363 $('#ehx-show').click(e => {
364 var disp = $('.ehx-visited.ehx-hidden').css('display');
365 if ($('#ehx-show').text() === 'Show') {
366 $('.ehx-visited').css({display: ''});
367 $('.ehx-visited.ehx-hidden').css({display: disp});
368 } else {
369 $('.ehx-visited').css({display: 'none'});
370 $('.ehx-visited.ehx-hidden').css({display: disp});
371 }
372 $('#ehx-show').text((i, t) => {
373 return t === 'Show' ? 'Hide' : 'Show';
374 });
375 updateGListing();
376 setStore.visHide = $('#ehx-show').text() === 'Show' ? true : false;
377 localStorage.setItem('ehx-settings', JSON.stringify(setStore)); // Update our stored settings
378 });
379 $('#ehxh-show').click(e => {
380 if ($('#ehxh-show').text() === 'Show') {
381 if (!$('.gl1t').length) { // For table view modes
382 $('.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')}); // Copy the display CSS of our closest element
383 $('.ehx-visited.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')});
384 } else { // Default display CSS for thumbnail
385 $('.ehx-hidden').css({display: 'flex'});
386 $('.ehx-visited.ehx-hidden').css({display: 'flex'});
387 }
388 } else {
389 $('.ehx-hidden').css({display: ''});
390 $('.ehx-visited.ehx-hidden').css({display: ''});
391 }
392 $('#ehxh-show').text((i, t) => { // Toggle text
393 return t === 'Show' ? 'Hide' : 'Show';
394 });
395 updateGListing();
396 setStore.hidShow = $('#ehxh-show').text() === 'Hide' ? true : false;
397 localStorage.setItem('ehx-settings', JSON.stringify(setStore));
398 });
399 $('#ehx-controls').append('<br /><span id="hideCount"><span></span></span>');
400 addCSS();
401 updateGListing();
402 }
403 }
404 }
405 }
406 }
407
408 /**
409 * Our main function that does basically everything that we see.
410 * Appends our custom HTML objects to the main page.
411 * Adds CSS to elements based on whether they can be found in the populated local gallery listing.
412 */
413 function addCSS() { // TODO: Probably refactor this block again
414 observer.disconnect();
415 var list = $('.itg tr').length ? $('tr').has('.glhide, .gldown, th') : $('.itg .gl1t');
416 var gid, galleryId, onFavs;
417
418 if (list.length) {
419 if ($('h1').text() == 'Favorites') onFavs = 1;
420 if ($('.gl1e').length) { // Extended
421 for (var i = 0; i < list.length; i++) {
422 gid = $(list[i]).find('.gl1e a').attr('href').split('/');
423 galleryId = gid[4] + '.' + gid[5];
424
425 if (galleries[galleryId] != undefined) { // Visited
426 if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields if we haven't already
427 $(list[i]).addClass('ehx-visited');
428 addStyle($(list[i]), 'v');
429 if (onFavs) {
430 $(list[i]).find('.gl3e div:last-child').append('<br/><ehx class="ehx-extended-favs">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
431 } else {
432 $(list[i]).find('.gl3e').append('<ehx class="ehx-extended">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
433 }
434 } else { // Otherwise, just update the timestamp
435 if (onFavs) {
436 $(list[i]).find('ehx-extended-favs').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
437 } else {
438 $(list[i]).find('ehx-extended').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
439 }
440 }
441
442 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
443 if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text(buildTime(galleryId, false));
444 } else { // Never Visited
445 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
446 if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text('Never Visited');
447 $(list[i]).removeClass('ehx-visited');
448 if ($(list[i]).find('.ehx-extended').length) $(list[i]).find('.ehx-extended').remove();
449 }
450
451 if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
452 $(list[i]).addClass('ehx-hidden');
453 addStyle($(list[i]), 'h');
454 }
455
456 if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
457 $(list[i]).addClass('ehx-downloaded');
458 addStyle($(list[i]), 'd');
459 }
460
461 if (!$(list[i]).find('.imgHide').length) {
462 $('<img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery">').appendTo($(list[i]).find('.gl2e > div')).click(e => { // Maybe closest('tr')
463 toggleElement($(e.currentTarget).parents().eq(2).find('a').attr('href'), $(e.currentTarget).parents().eq(2));
464 });
465 }
466 filterCheck($(list[i]));
467 }
468 } else if ($('.gl1c').length) { // Compact
469 var borderColor = $('.gl1c').first().css('border-top-color');
470 if ($('.itg tr:first-child').children().length < 5) { // We haven't appended our table head
471 $('.itg th:nth-child(4)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">✖</th>'); // X column
472 if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th>Visited</th>');
473 if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
474 }
475
476 for (i = 1; i < list.length; i++) {
477 gid = $(list[i]).find('.glname a').attr('href').split('/');
478 galleryId = gid[4] + '.' + gid[5];
479
480 if (galleries[galleryId] != undefined) { // Visited
481 if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields
482 $(list[i]).addClass('ehx-visited');
483 addStyle($(list[i]), 'v');
484 if (setStore.minAdd) {
485 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>');
486 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>');
487 }
488 } else { // Otherwise update timestamp
489 $(list[i]).find('ehx-compact').html('<ehx>' + timeDifference(galleryId, true) + '<br>' + buildTime(galleryId, false).substring(11) + '<br>' + buildTime(galleryId, false).substring(2, 10) + '</ehx>');
490 }
491
492 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
493 if (setStore.repPub) $(list[i]).find('.gl2c div:nth-child(3) div:first-child').text(buildTime(galleryId, false));
494 } else { // Never Visited
495 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
496 if (setStore.repPub) $(list[i]).find('.gl2c > div:nth-child(3) > div:first-child').text('Never Visited');
497 if ($(list[i]).children().length < 5 || ($(list[i]).children().length < 6 && onFavs)) {
498 if (setStore.minAdd) $(list[i]).find('.gl2c').after('<td class="ehx-compact" style="border-color:' + borderColor + ';"></td>');
499 }
500 $(list[i]).removeClass('ehx-visited');
501 }
502
503 if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
504 $(list[i]).addClass('ehx-hidden');
505 addStyle($(list[i]), 'h');
506 }
507
508 if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
509 $(list[i]).addClass('ehx-downloaded');
510 addStyle($(list[i]), 'd');
511 }
512
513 if (!$(list[i]).find('.imgHide').length) {
514 $('<td class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
515 toggleElement($(e.currentTarget).parent().find('.glname a').attr('href'), $(e.currentTarget).parent());
516 });
517 }
518 filterCheck($(list[i]));
519 }
520 } else if ($('.gl1m').length) { // Minimal
521 if ($('.itg tr:first-child').children().length < 7) { // We haven't appended our table head
522 $('.itg th:nth-child(6)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">✖</th>'); // X Column
523 if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th title="EhxVisited: Hover for timestamps" style="text-align: center;">\uD83D\uDC41</th>');
524 if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
525 }
526
527 for (i = 1; i < list.length; i++) {
528 gid = $(list[i]).find('.glname a').attr('href').split('/');
529 galleryId = gid[4] + '.' + gid[5];
530
531 if (galleries[galleryId] != undefined) { // Visited
532 if (!$(list[i]).hasClass('ehx-visited')) { // Append fields
533 $(list[i]).addClass('ehx-visited');
534 addStyle($(list[i]), 'v');
535 if (setStore.minAdd) {
536 if (setStore.minShow) {
537 if ($(list[i]).find('.ehx-minimal').length) $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
538 else $(list[i]).find('.gl2m').after('<td class="ehx-minimal"><ehx title="EhxVisited: ' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx></td>');
539 } else {
540 if ($(list[i]).find('.ehx-minimal').length) {
541 $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
542 $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
543 } else $(list[i]).find('.gl2m').after('<td class="ehx-minimal" title="EhxVisited: ' + buildTime(galleryId, true) + '"><ehx>\uD83D\uDC41</ehx></td>');
544 }
545 }
546 } else { // Update our timestamps
547 if (setStore.minAdd) {
548 if (setStore.minShow) {
549 $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
550 } else {
551 $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
552 $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
553 }
554 }
555 }
556
557 if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
558 if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text(buildTime(galleryId, false));
559 } else { // Never Visited
560 if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', 'Never Visited');
561 if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text('Never Visited');
562 if ($(list[i]).children().length < 7 || ($(list[i]).children().length < 8 && onFavs)) {
563 if (setStore.minAdd) $(list[i]).find('.gl2m').after('<td class="ehx-minimal"></td>');
564 }
565 $(list[i]).removeClass('ehx-visited');
566 }
567
568 if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
569 $(list[i]).addClass('ehx-hidden');
570 addStyle($(list[i]), 'h');
571 }
572
573 if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
574 $(list[i]).addClass('ehx-downloaded');
575 addStyle($(list[i]), 'd');
576 }
577
578 if (!$(list[i]).find('.imgHide').length) {
579 $('<td class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
580 var el = $(e.currentTarget).closest('tr');
581 toggleElement($(el).find('.glname a').attr('href'), $(el));
582 });
583 }
584 filterCheck($(list[i]));
585 }
586 } else { // Thumbnail
587 for (i = 0; i < list.length; i++) {
588 gid = $(list[i]).find('.gl3t a').attr('href').split('/');
589 galleryId = gid[4] + '.' + gid[5];
590
591 if (galleries[galleryId] != undefined) { // Visited
592 if (!$(list[i]).hasClass('ehx-visited')) {
593 $(list[i]).addClass('ehx-visited');
594 addStyle($(list[i]), 'v');
595 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>');
596 else $(list[i]).find('.gl5t').after('<ehx class="ehx-thumbnail">\uD83D\uDC41 ' + buildTime(galleryId, true) + '</ehx>');
597 } else {
598 $(list[i]).find('.ehx-thumbnail').text('\uD83D\uDC41 ' + buildTime(galleryId, true));
599 }
600
601 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
602 if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text(buildTime(galleryId, false));
603 } else { // Never Visited
604 if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
605 if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text('Never Visited');
606 $(list[i]).removeClass('ehx-visited');
607 if ($(list[i]).find('.ehx-thumbnail').length) $(list[i]).find('.ehx-thumbnail').parentElement.remove();
608 }
609
610 if (hidden[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
611 $(list[i]).addClass('ehx-hidden');
612 addStyle($(list[i]), 'h');
613 }
614
615 if (down[galleryId] != undefined && !$(list[i]).hasClass('ehx-downloaded')) {
616 $(list[i]).addClass('ehx-downloaded');
617 addStyle($(list[i]), 'd');
618 }
619
620 if (!$(list[i]).find('.imgHide').length) {
621 $('<div class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></div>').appendTo($(list[i]).find('.gl5t')).on('click', e => {
622 var el = $(e.currentTarget).parents().eq(1);
623 toggleElement($(el).find('.gl3t a').attr('href'), $(el));
624 });
625 }
626 filterCheck($(list[i]));
627 }
628 }
629 updateGListing();
630 } else { // No Elements pulled, invalid view
631 displayAlert('No Valid Elements Detected', 5000, true);
632 return;
633 }
634
635 if (setStore.visHide) {
636 $('.ehx-visited').css({display: 'none'});
637 $('#ehx-show').text('Show');
638 }
639
640 if (setStore.hidShow) {
641 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
642 else { // Unless you're an idiot and hid everything on the page
643 if ($('.gl1t').length) { $('.ehx-hidden').css({display: 'flex'}); } // Use the default values
644 else { $('.ehx-hidden').css({display: 'table-row'}); }
645 }
646 $('#ehxh-show').text('Hide');
647 }
648
649 observer.observe($('.itg').get(0), { // Reconnect the observer for changes
650 childList: true,
651 subtree: true
652 });
653 }
654
655 /**
656 * Build the time difference string
657 * @param {String} gid - Gallery ID
658 * @param {Boolean} time - Include timeDifference in returned string
659 * @param {Boolean} abbrv - Abbreviate for timeDifference
660 */
661 function buildTime(gid, time, abbrv) {
662 var d = new Date(galleries[gid]);
663 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');
664 if (time) return timeDifference(gid, abbrv) + ' ' + str;
665 return str;
666 }
667
668 /**
669 * Get time difference in words
670 * @param {Date} previous - Previous date to compare against Date.now()
671 * @param {Boolean} abbreviate - Should the text string have abbreviatated text
672 */
673 function timeDifference(gallery, abbreviate) {
674 var previous = galleries[gallery];
675 var msPerMinute = 60 * 1000;
676 var msPerHour = msPerMinute * 60;
677 var msPerDay = msPerHour * 24;
678 var msPerMonth = msPerDay * 30;
679 var msPerYear = msPerDay * 365;
680 var elapsed = Date.now() - previous;
681
682 if (elapsed < msPerMinute) {
683 return Math.round(elapsed / 1000) + ((typeof abbreviate !== 'undefined') ? ' sec' : ' seconds ago');
684 } else if (elapsed < msPerHour) {
685 return Math.round(elapsed / msPerMinute) + ((typeof abbreviate !== 'undefined') ? ' min' : ' minutes ago');
686 } else if (elapsed < msPerDay) {
687 return Math.round(elapsed / msPerHour) + ((typeof abbreviate !== 'undefined') ? ' hrs' : ' hours ago');
688 } else if (elapsed < msPerMonth) {
689 return Math.round(elapsed / msPerDay) + ((typeof abbreviate !== 'undefined') ? ' days' : ' days ago');
690 } else if (elapsed < msPerYear) {
691 return Math.round(elapsed / msPerMonth) + ((typeof abbreviate !== 'undefined') ? ' mos' : ' months ago');
692 } else {
693 return Math.round(elapsed / msPerYear) + ((typeof abbreviate !== 'undefined') ? ' yrs' : ' years ago');
694 }
695 }
696
697 /**
698 * Displays a div at the top of the page with a message
699 * @param {String} message - A message to be displayed within the alert
700 * @param {Integer} timeout - Millseconds message should be displayed for
701 * @param {Boolean} error - Is this an error message
702 */
703 function displayAlert(message, timeout, error) {
704 var alert = $('<div class="notice ' + ((error) ? 'alert' : '') + '">EhxVisited: ' + message + '</div>');
705 $(alert).hide().appendTo('.alertContainer').fadeIn(1000);
706 setTimeout(e => { $('.notice').fadeOut(1000, f => { $('.notice').remove(); }); }, timeout);
707 }
708
709 /**
710 * Apply visited CSS to an element on mouse down
711 */
712 $('.itg').on('mouseup', 'a', e => {
713 if (e.which === 3) return; // Ignore right-clicks
714 if (e.currentTarget.href.split('/')[3] === 'g') {
715 galleries[e.currentTarget.href.split('/')[4] + '.' + e.currentTarget.href.split('/')[5]] = Date.now();
716 $('#gLength').text(Object.keys(galleries).length);
717 addCSS();
718 }
719 });
720
721 /**
722 * Generate the JSON request for the E-H API
723 * @param {IndexedDB Keys} data - Object keys within the data portion of our matrices
724 */
725 function generateRequest(data) {
726 $('#listingContainer').empty();
727 var reqList = []; // We use an array for our gidlist, since the API can handle up to 25 galleries per request
728 for (var i = 0; i < data.length; i++) {
729 if (data[i] == undefined) continue;
730 var str = data[i].split('.'); // Split the key to match request specifications of galleryID, galleryToken
731 reqList[i] = [str[0], str[1]];
732 }
733 var request = {"method": "gdata", "gidlist": reqList, "namespace": 1};
734
735 var req = new XMLHttpRequest();
736 req.onreadystatechange = e => {
737 if (req.readyState == 4) {
738 if (req.status == 200) {
739 var apirsp = JSON.parse(req.responseText);
740 //console.log(apirsp);
741 for (var i = 0; i < apirsp.gmetadata.length; i++) generateListing(apirsp.gmetadata[i]);
742 } else {
743 console.error();
744 displayAlert("Request Failed", 5000, true);
745 }
746 }
747 }
748 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
749 req.send(JSON.stringify(request));
750 }
751
752 /**
753 * Generate the HTML code for each individual listing in history views
754 * @param {JSON Array} glisting - E-H API response item for a specified gallery
755 */
756 function generateListing(glisting) {
757 var d = new Date(glisting.posted * 1000);
758 // TODO: See about replacing the custom date with a buildTime call
759 var listing = $(`
760 <div class="listing">
761 <div class="thumb">
762 <a href="` + document.location.origin + '/g/' + glisting.gid + '/' + glisting.token + `">
763 <img src="` + glisting.thumb + `" />
764 </a>
765 </div>
766 <div class="listBody">
767 <div class="title" style="width: 90%">
768 <a href="` + document.location.origin + '/g/' + glisting.gid + '/' + glisting.token + '/">' + glisting.title + `</a>
769 </div>
770 <div class="category">
771 <div class="cn ` + category[glisting.category.toLowerCase().replace(/ /g, '').replace(/-/g, '')] + `">
772 <a href="` + document.location.origin + '/' + glisting.category.toLowerCase().replace(/ /g, '') + '">' + glisting.category + `</a>
773 </div>
774 <div class="date">
775 ` + 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') + `
776 </div>
777 </div>
778 <div class="rating">
779 <div>
780 <a href="` + document.location.origin + '/uploader/' + glisting.uploader + '">' + glisting.uploader + `</a>
781 </div>
782 <div>
783 ` + glisting.filecount + ` pages
784 </div>
785 <div class="ir" style="float: right; background-position: ` + getStarNumber(glisting.rating, true) + `"></div>
786 </div>
787 </div>
788 </div>`);
789 $('#listingContainer').append(listing);
790 $('<div class="imgContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></div>').appendTo($('.listBody').last()).on('click', e => {
791 deleteHistory($(e.currentTarget).parents().eq(1).find('a').attr('href'), $(e.currentTarget).parents().eq(1));
792 });
793 }
794
795 /**
796 * Import user data into our indexedDB
797 * @param {String} items - String of exported data to import
798 */
799 function ehxImport(items) {
800 const req = indexedDB.open('ehxvisited', 2);
801 req.onsuccess = e => {
802 if (db == null) db = e.target.result;
803 var objStore = db.transaction(activeStoreTitle, 'readwrite').objectStore(activeStoreTitle);
804 var count = 0, sp = '';
805
806 sp = items.split(';');
807 sp = sp.filter(Boolean); // Filter out any null ('') entries
808 insertNext();
809
810 /**
811 * Push entries into the specified indexedDB store
812 */
813 function insertNext() {
814 if (count < sp.length) {
815 var str = sp[count].split(':');
816 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
817 activeStore[str[0]] = str[1];
818 ++count;
819 } else {
820 displayAlert('Imported ' + count + ' entries', 5000);
821 console.log('EhxVisited: Merge Completed');
822 updateGListing();
823 addCSS();
824 }
825 }
826 }
827 }
828
829 /**
830 * Fills a text area with formatted gallery data for export
831 * @param {IndexedDB Title} store - The name of an indexedDB store
832 */
833 function ehxExport(store) {
834 const req = indexedDB.open('ehxvisited', 2);
835 req.onsuccess = e => {
836 if (db == null) db = e.target.result;
837 var objStore = db.transaction(store, 'readonly').objectStore(store);
838 var openReq = objStore.getAll();
839 openReq.onsuccess = e => {
840 var data = '';
841 for (var i in e.target.result) {
842 data += e.target.result[i].id + ':' + e.target.result[i].visited + ';';
843 }
844 $('#exportGalleries').val(data); // Fill with formatted data
845 }
846 }
847 }
848
849 /**
850 * Remove our stylesheet with transient CSS, and then re-add it with the updated CSS
851 */
852 function updateCSS() {
853 cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
854 $('#setStyle').remove();
855 $(`<style id="setStyle" data-jqstyle="ehxVisited">
856 table.itg > tbody > .ehx-visited, .ehx-visited { ` + cssA.visible + ` }
857 table.itg > tbody > .ehx-visited.ehx-hidden, .ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
858 .ehx-hidden { ` + cssD + cssA.hidden + ` }
859 .ehx-downloaded { ` + cssA.download + ` }
860 .ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
861 .ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
862 .ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
863 .ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
864 </style>`).appendTo('head');
865 }
866
867 /**
868 * Open the Settings menu and set up all necessary menu functions
869 */
870 function settings() {
871 // There's probably a much easier way to do this, or at least a nicer looking, more technical way
872 var container = $(`
873 <div class="overlay">
874 <div class="settings">
875 <nav id="topNav">
876 <button id="home" style="float: left; border: none;">Main</button>
877 <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>
878 <div>
879 <div class="mencon">
880 <button class="menu">Export</button>
881 <div class="dropdown">
882 <a id="ehx-export">Export Galleries</a>
883 <a id="ehxh-export">Export Hidden Galleries</a>
884 <a id="ehxd-export">Export DL Galleries</a>
885 </div>
886 </div>
887 <div class="mencon">
888 <button class="menu">Import</button>
889 <div class="dropdown">
890 <a id="ehx-import">Import Galleries</a>
891 <a id="ehxh-import">Import Hidden Galleries</a>
892 <a id="ehxd-import">Import DL Galleries</a>
893 </div>
894 </div>
895 <a id="settings-close">🞫</a>
896 </div>
897 </nav>
898 <div class="section-container">
899 <section>
900 <fieldset>
901 <legend>Settings</legend>
902 <div>
903 <label>
904 <input type="checkbox" id="softHide" ` + (setStore.softHide ? `checked` : ``) + `>Soft Hide Galleries
905 </label>
906 <span>: Darken hidden galleries instead of removing them from view</span>
907 </div>
908 <div>
909 <label>
910 <input type="checkbox" id="minAdd" ` + (setStore.minAdd ? `checked` : ``) + `>Add Visited Column
911 </label>
912 <span>: Show visits in an additional column in Minimal/Minimal+ and Compact view modes</span>
913 <div class="suboptions">
914 <div>
915 <span class="branch">∟</span>
916 <label>
917 <input type="checkbox" id="minShow" ` + (setStore.minShow ? `checked` : ``) + `>Minimal Show Text
918 </label>
919 <span>: Show visits as text instead of hovering tooltip in Minimal/Minimal+ view modes</span>
920 </div>
921 </div>
922 </div>
923 <div>
924 <label>
925 <input type="checkbox" id="cssTT" ` + (setStore.cssTT ? `checked` : ``) + `>CSS Tooltips
926 </label>
927 <span>: Replace gallery link tooltips with visited information in all view modes</span>
928 </div>
929 <div>
930 <label>
931 <input type="checkbox" id="repPub" ` + (setStore.repPub ? `checked` : ``) + `>Replace Published
932 </label>
933 <span>: Replace date published with date visited in Minimal/Minimal+ view modes</span>
934 </div>
935 <div>
936 <label>
937 <input type="checkbox" id="titleShow" ` + (setStore.titleShow ? `checked`: ``) + `>Show Full Title
938 </label>
939 <span>: Show the full title of a gallery on hover in Thumbnail view</span>
940 </div>
941 </fieldset>
942 <fieldset>
943 <legend>Custom CSS</legend>
944 <h3>Visited Galleries
945 <div class="control" id="visControls">
946 <button id="visHistory">View History</button>
947 <button id="resV">Reset CSS</button>
948 <button id="ehx-clear">Clear Data</button>
949 </div>
950 </h3>
951 <textarea id="visited" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.visible + `</textarea>
952 <h3>Hidden Galleries
953 <div class="control hideControls">
954 <button id="hidHistory">View</button>
955 <button id="resH">Reset CSS</button>
956 <button id="ehxh-clear">Clear Data</button>
957 </div>
958 </h3>
959 <textarea id="hidden" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.hidden + `</textarea>
960 <h3>Downloaded Galleries
961 <div class="control hideControls">
962 <button id="dowHistory">View</button>
963 <button id="resD">Reset CSS</button>
964 <button id="ehxd-clear">Clear Data</button>
965 </div>
966 </h3>
967 <textarea id="downloaded" class="field" spellcheck="false">` + cssA.download + `</textarea>
968 <div class="suboptions2">
969 <button class="collapsible">Title Filtered Galleries</button>
970 <div class="content">
971 <textarea id="filtered" class="field" spellcheck="false">` + cssA.filter + `</textarea>
972 <div class="control sControls">
973 <button id="resF">Reset CSS</button>
974 </div>
975 </div>
976 <button class="collapsible">Uploader Filtered Galleries</button>
977 <div class="content">
978 <textarea id="ufiltered" class="field" spellcheck="false">` + cssA.uploader + `</textarea>
979 <div class="control sControls">
980 <button id="resU">Reset CSS</button>
981 </div>
982 </div>
983 <button class="collapsible">Page Filtered</button>
984 <div class="content">
985 <textarea id="page" class="field" spellcheck="false"placeholder="Insert CSS">` + cssA.page + `</textarea>
986 <div class="control sControls">
987 <button id="resP">Reset CSS</button>
988 </div>
989 </div>
990 <button class="collapsible">Rating Filtered</button>
991 <div class="content">
992 <textarea id="rating" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.rating + `</textarea>
993 <div class="control sControls">
994 <button id="resR">Reset CSS</button>
995 </div>
996 </div>
997 </div>
998 </fieldset>
999 <fieldset>
1000 <legend>Filters</legend>
1001 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.
1002 <ul style="margin: 3px 0px; padding-left: 30px;">
1003 <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>
1004 <li>Lines starting with <code>#</code> will be ignored.</li>
1005 </ul>
1006 <textarea id="galFilter">` + filters.title + `</textarea>
1007 <h3>Uploader Filter<span style="font-weight: lighter; font-size: .8em; opacity: 0.5; -webkit-opacity: 0.5; margin-left: 8px;">(Doesn't Apply To Thumbnail View)</span></h3>
1008 <textarea id="upFilter">` + filters.uploader + `</textarea>
1009 <div>
1010 <label>
1011 <input type="checkbox" id="pFilt" ` + (setStore.pFilter ? `checked` : ``) + `>Page Limit
1012 </label>
1013 <span>: Filter out any gallery with pages less than:
1014 <input id="pLim" type="number" min="1" value="` + setStore.pLimit + `" ` + (setStore.pFilter ? `` : `disabled`) + `/>
1015 </span>
1016 </div>
1017 <div>
1018 <label>
1019 <input type="checkbox" id="stFilt" ` + (setStore.stFilter ? `checked` : ``) + `>Minimum Rating
1020 </label>
1021 <span>: Filter out any gallery with a rating less than:
1022 <select id="stLim" ` + (setStore.stFilter ? `` : `disabled`) + `>
1023 <option>5</option>
1024 <option>4.5</option>
1025 <option>4</option>
1026 <option>3.5</option>
1027 <option>3</option>
1028 <option>2.5</option>
1029 <option>2</option>
1030 <option>1.5</option>
1031 <option>1</option>
1032 </select>
1033 </span>
1034 </div>
1035 </fieldset>
1036 </section>
1037 <section class="inactive">
1038 <fieldset style="padding-bottom: 2px;">
1039 <legend id="importTitle">Import Galleries</legend>
1040 <textarea id="importGalleries"></textarea>
1041 <div class="control" style="margin-top: 2px; margin-bottom: 4px;">
1042 <button class="close">Close</button>
1043 <button id="importConfirm">Import</button>
1044 </div>
1045 </fieldset>
1046 </section>
1047 <section class="inactive">
1048 <fieldset style="padding-bottom: 2px;">
1049 <legend id="exportTitle">Export Galleries</legend>
1050 <textarea id="exportGalleries"></textarea>
1051 <div class="control" style="margin-top: 2px; margin-bottom: 4px;">
1052 <button class="close">Close</button>
1053 <button id="exportCopy">Copy</button>
1054 </div>
1055 </fieldset>
1056 </section>
1057 <section class="inactive">
1058 <fieldset>
1059 <legend id="history" style="margin-left: 5px;"></legend>
1060 <div id="listingContainer">
1061 </div>
1062 </fieldset>
1063 </section>
1064 </div>
1065 <div class="applyContainer">
1066 <div class="control" id="applyCon" style="padding-right: 5px;">
1067 <button id="apply">Apply</button>
1068 </div>
1069 </div>
1070 </div>
1071 </div>`);
1072 $('body').append(container);
1073 $('body').addClass('noscroll');
1074 galleries = sortObj(galleries);
1075 if (!$('#minAdd').prop('checked')) { $('#minShow').prop('disabled', true); }
1076 $('#resV').click(e => { $('#visited').val('box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;'); }); // Default Values
1077 $('#resH').click(e => { $('#hidden').val('box-shadow: inset 0 0 0 500px rgba(255, 40, 0, .2) !important;'); });
1078 $('#resD').click(e => { $('#downloaded').val('box-shadow: inset 0 0 0 500px rgba(30, 180, 60, .2) !important;'); });
1079 $('#resF').click(e => { $('#filtered').val('box-shadow: inset 0 0 0 500px rgba(200, 0, 100, .2) !important;'); });
1080 $('#resU').click(e => { $('#ufiltered').val('box-shadow: inset 0 0 0 500px rgba(222, 184, 135, .2) !important;'); });
1081 $('#resP').click(e => { $('#page').val('box-shadow: inset 0 0 0 500px rgba(0, 0, 180, .2) !important;'); });
1082 $('#resR').click(e => { $('#rating').val('box-shadow: inset 0 0 0 500px rgba(180, 80, 60, .2) !important;'); });
1083 $('#stLim').val(setStore.stLimit);
1084 $(document).on('change', 'input', e => { // Put the change listener on document since I suck at event propogation and bubbling
1085 if ($('#minAdd').prop('checked')) $('#minShow').prop('disabled', false);
1086 else $('#minShow').prop('disabled', true);
1087
1088 if ($('#pFilt').prop('checked')) $('#pLim').prop('disabled', false);
1089 else $('#pLim').prop('disabled', true);
1090
1091 if ($('#stFilt').prop('checked')) $('#stLim').prop('disabled', false);
1092 else $('#stLim').prop('disabled', true);
1093
1094 if ($('#minAdd').is(e.target) || $('#minShow').is(e.target) || $('#repPub').is(e.target) || $('#titleShow').is(e.target) || $('#cssTT').is(e.target)) {
1095 $('#setNotice').text('Applied Settings Will Take Effect On Reload');
1096 reload = 1;
1097 }
1098 });
1099 $('#settings-close').click(e => {
1100 $('.overlay').remove();
1101 $('body').removeClass('noscroll');
1102 });
1103 $('body').click(e => {
1104 if (e.target.className == "overlay") { // Exit if settings menu isn't clicked
1105 $('.overlay').remove();
1106 } else if (e.target.className != 'show' && e.target.className != 'menu') {
1107 $('.show').removeClass('show');
1108 }
1109 if (!$('.overlay').length) $('body').removeClass('noscroll');
1110 });
1111 $('#apply').click(e => applySettings());
1112
1113 /**
1114 * Parse our HTML options into a temporary JSON array and then stringify it into localStorage
1115 */
1116 function applySettings() { // TODO: Sort this shit out into one block
1117 setStore = { // Store this independantly, so it doesn't mess up table appends
1118 "softHide": $('#softHide').prop('checked'),
1119 "minAdd": setStore.minAdd,
1120 "minShow": setStore.minShow,
1121 "cssTT": $('#cssTT').prop('checked'),
1122 "repPub": setStore.repPub,
1123 "visHide": $('#ehx-show').text() === "Show" ? true : false,
1124 "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
1125 "pFilter": $('#pFilt').prop('checked'),
1126 "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
1127 "stFilter": $('#stFilt').prop('checked'),
1128 "stLimit": $('#stFilt').prop('checked') ? $('#stLim option:selected').text() : "0",
1129 "titleShow": setStore.titleShow
1130 }
1131 var tempSto = {
1132 "softHide": $('#softHide').prop('checked'),
1133 "minAdd": $('#minAdd').prop('checked'),
1134 "minShow": $('#minShow').prop('checked'),
1135 "cssTT": $('#cssTT').prop('checked'),
1136 "repPub": $('#repPub').prop('checked'),
1137 "visHide": $('#ehx-show').text() === "Show" ? true : false,
1138 "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
1139 "pFilter": $('#pFilt').prop('checked'),
1140 "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
1141 "stFilter": $('#stFilt').prop('checked'),
1142 "stLimit": $('#stFilt').prop('checked') ? $('#stLim option:selected').text() : "0",
1143 "titleShow": $('#titleShow').prop('checked')
1144 }
1145 localStorage.setItem('ehx-settings', JSON.stringify(tempSto)); // Write settings to localStorage
1146 var tempCss = {
1147 "visible": $('#visited').val(),
1148 "hidden": $('#hidden').val(),
1149 "download": $('#downloaded').val(),
1150 "filter": $('#filtered').val(),
1151 "page": $('#page').val(),
1152 "rating": $('#rating').val(),
1153 "uploader": $('#ufiltered').val()
1154 }
1155 cssA = tempCss;
1156 localStorage.setItem('ehx-css', JSON.stringify(tempCss));
1157 var tempFilt = { // Remove null entries because bad things happen if they're there
1158 "title": $('#galFilter').val().replace(/^\s*[\r\n]/gm, ''),
1159 "uploader": $('#upFilter').val().replace(/^\s*[\r\n]/gm, '')
1160 }
1161 filters = tempFilt;
1162 populateFilter();
1163 localStorage.setItem('ehx-filters', JSON.stringify(tempFilt));
1164 updateCSS();
1165 addCSS();
1166 displayAlert('Applied Current Settings', 5000, false);
1167 }
1168
1169 $('.collapsible').click(e => { // Expand our custom filtering CSS boxes
1170 if ($('.active').length && !$('.active').is(e.target)) { // If a menu is open and it isn't the one we're clicking, close it
1171 $('.active').next().css('max-height', '');
1172 $('.active').toggleClass('active');
1173 }
1174 e.target.classList.toggle('active');
1175 var content = e.target.nextElementSibling;
1176 if (content.style.maxHeight) content.style.maxHeight = null;
1177 else content.style.maxHeight = '500px';
1178 });
1179
1180 function swapContainer(index) {
1181 $('.section-container section').addClass('inactive');
1182 $('.section-container section:nth-child(' + index + ')').removeClass('inactive');
1183 if (index == 2) $('#importGalleries').val('');
1184 }
1185
1186 $('#ehx-import').click(e => {
1187 swapContainer(2);
1188 activeStore = galleries;
1189 activeStoreTitle = 'galleries';
1190 $('#importTitle').text('Import Galleries');
1191 });
1192 $('#ehxh-import').click(e => {
1193 swapContainer(2);
1194 activeStore = hidden;
1195 activeStoreTitle = 'hidden';
1196 $('#importTitle').text('Import Hidden Galleries');
1197 });
1198 $('#ehxd-import').click(e => {
1199 swapContainer(2);
1200 activeStore = down;
1201 activeStoreTitle = 'down';
1202 $('#importTitle').text('Import Downloaded Galleries');
1203 });
1204 $('#importConfirm').click(e => ehxImport($('#importGalleries').val().replace(/^\s*[\r\n]/gm, '')));
1205
1206 $('.close').click(e => {
1207 $('.section-container section').addClass('inactive');
1208 $('.section-container section:first-child').removeClass('inactive');
1209 });
1210 $('#home').click(e => {
1211 $('.section-container section').addClass('inactive');
1212 $('.section-container section:first-child').removeClass('inactive');
1213 $('#pages').remove();
1214 if (!$('#apply').length) $('.applyContainer').append($('<div class="control" id="applyCon" style="padding-right: 5px;"><button id="apply">Apply</button></div>'));
1215 });
1216
1217 $('#exportCopy').click(e => {
1218 $('#exportGalleries').select();
1219 document.execCommand('copy');
1220 displayAlert('Copied Text To Clipboard', 5000);
1221 });
1222 $('#ehx-export').click(e => {
1223 $('.section-container section').addClass('inactive');
1224 $('.section-container section:nth-child(3)').removeClass('inactive');
1225 $('#exportTitle').text('Exported Galleries');
1226 ehxExport('galleries');
1227 });
1228 $('#ehxh-export').click(e => {
1229 $('.section-container section').addClass('inactive');
1230 $('.section-container section:nth-child(3)').removeClass('inactive');
1231 $('#exportTitle').text('Exported Hidden Galleries');
1232 ehxExport('hidden');
1233 });
1234 $('#ehxd-export').click(e => {
1235 $('.section-container section').addClass('inactive');
1236 $('.section-container section:nth-child(3)').removeClass('inactive');
1237 $('#exportTitle').text('Exported Downloaded Galleries');
1238 ehxExport('down');
1239 });
1240
1241 $('#visHistory').click(e => {
1242 activeStore = galleries;
1243 activeStoreTitle = 'galleries';
1244 generateHistory('Viewed Galleries');
1245 });
1246 $('#hidHistory').click(e => {
1247 activeStore = hidden;
1248 activeStoreTitle = 'hidden';
1249 generateHistory('Hidden Galleries');
1250 });
1251 $('#dowHistory').click(e => {
1252 activeStore = down;
1253 activeStoreTitle = 'down';
1254 generateHistory('Downloaded Galleries');
1255 });
1256
1257 function generateHistory(text) {
1258 $('#history').text(text);
1259 $('#applyCon').remove();
1260
1261 var pageSelect = '<div id="pages">Page <select id="pageCount">';
1262 for (var i = 0; i < Math.ceil(Object.keys(activeStore).length / 25); i++) {
1263 pageSelect += '<option>' + (i + 1) + '</option>';
1264 }
1265
1266 pageSelect += '</select> of ' + Math.ceil(Object.keys(activeStore).length / 25) + ' pages</div>';
1267 $('.section-container section').addClass('inactive');
1268 $('.section-container section:nth-child(4)').removeClass('inactive');
1269 $('.applyContainer').append($.parseHTML(pageSelect));
1270
1271 var str = [];
1272 for (i = 0; i < 25; i++) str[i] = Object.keys(activeStore)[i];
1273 if (Object.keys(activeStore).length > 0) generateRequest(str);
1274 }
1275
1276 $('.applyContainer').on('change', 'select', e => {
1277 var offset = $('#pageCount option:selected').text() - 1;
1278 var maxLength = ((offset * 25) + 25 <= Object.keys(activeStore).length) ? (offset * 25) + 25 : Object.keys(activeStore).length;
1279 var str = [];
1280 var count = 0;
1281 for (var i = offset * 25; i < maxLength; i++) str[count++] = Object.keys(activeStore)[i];
1282 generateRequest(str);
1283 });
1284
1285 $('#ehx-clear').click(e => { // TODO: I want to condense these three blocks -----
1286 if (!ehxClearConfirm) { // Make sure to double check before deleting
1287 ehxClearConfirm = 1;
1288 $('#ehx-clear').append(': Are you sure?');
1289 } else {
1290 var objStore2 = db.transaction('galleries', 'readwrite').objectStore('galleries');
1291 var openReq = objStore2.clear();
1292 openReq.onsuccess = e => {
1293 displayAlert('Cleared all entries', 5000, false);
1294 $('#ehx-clear').text('Clear Data');
1295 galleries = JSON.parse('{"data":{}}');
1296 $('#gLength').text(Object.keys(galleries).length);
1297 $('.ehx-visited').removeClass('ehx-visited');
1298 addCSS();
1299 }
1300 }
1301 });
1302 $('#ehxh-clear').click(e => {
1303 var objStore2 = db.transaction('hidden', 'readwrite').objectStore('hidden');
1304 var openReq = objStore2.getAll();
1305 openReq.onsuccess = e => {
1306 if (!ehxhClearConfirm) { // Make sure to double check before deleting
1307 ehxhClearConfirm = 1;
1308 $('#ehxh-clear').append(': Are you sure?');
1309 } else {
1310 var objStore3 = db.transaction('hidden', 'readwrite').objectStore('hidden');
1311 var openReq = objStore3.clear();
1312 openReq.onsuccess = e => {
1313 displayAlert('Cleared all entries', 5000);
1314 $('#ehxh-clear').text('Clear Data');
1315 hidden = JSON.parse('{"data":{}}');
1316 $('#hLength').text(Object.keys(hidden).length);
1317 $('.ehx-hidden').removeClass('ehx-hidden');
1318 addCSS();
1319 }
1320 }
1321 }
1322 });
1323 $('#ehxd-clear').click(e => {
1324 var objStore2 = db.transaction('down', 'readwrite').objectStore('down');
1325 var openReq = objStore2.getAll();
1326 openReq.onsuccess = e => {
1327 if (!ehxdClearConfirm) {
1328 ehxdClearConfirm = 1;
1329 $('#ehxd-clear').append(': Are you sure?');
1330 } else {
1331 var objStore3 = db.transaction('down', 'readwrite').objectStore('down');
1332 var openReq = objStore3.clear();
1333 openReq.onsuccess = e => {
1334 displayAlert('Cleared all entries', 5000);
1335 $('#ehxd-clear').text('Clear Data');
1336 $('.ehx-downloaded').removeClass('ehx-downloaded');
1337 addCSS();
1338 }
1339 }
1340 }
1341 }); // ------//
1342
1343 // Make sure there's not more than one top menu item open
1344 $('.menu').click(e => {
1345 if ($('.show').length) {
1346 if ($('.show').prev().is(e.target)) { $(e.target).next().toggleClass('show'); }
1347 else {
1348 $('.show').removeClass('show');
1349 $(e.target).next().toggleClass('show');
1350 }
1351 } else $(e.target).next().toggleClass('show');
1352 });
1353 }
1354 // The giant CSS block
1355 $(`<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/symbola" type="text/css"/>
1356 <style data-jqstyle='ehxVisited'>
1357#ehx-controls {
1358 padding: 3px 1px;
1359 text-align: center;
1360 display: block;
1361}
1362#ehx-settings, #ehx-show, #ehxh-show {
1363 cursor: pointer;
1364 text-decoration: underline;
1365}
1366#hideCount > span { border-bottom: 1px dotted currentColor; }
1367#importGalleries, #exportGalleries { min-height: 414px; }
1368#settings-close {
1369 text-decoration: none;
1370 position: absolute;
1371 top: 0px;
1372 right: 5px;
1373 font-size: 1.4em;
1374}
1375@-moz-document url-prefix() {
1376 #settings-close {
1377 top: -2px;
1378 -webkit-text-stroke: 1px;
1379 }
1380}
1381#ehx-export, #ehxh-export, #ehx-import, #ehxh-import, #settings-close { cursor: pointer; }
1382#topNav {
1383 border-bottom: 1px solid threedface;
1384 left: -4px;
1385 min-width: 898px;
1386}
1387#visControls { top: -6px; }
1388div > .imgHide {
1389 cursor: pointer !important;
1390 position: absolute;
1391 bottom: 3px;
1392 left: 2px;
1393}
1394ehx { font-family:` + $('body').css('font-family') + `, arial, symbola, SymbolaRegular; }
1395input[type="checkbox"] {
1396 -webkit-appearance: none;
1397 border: 1px solid #F1F1F1BB;
1398 padding: 5px;
1399 top: 4px;
1400 background-color: transparent;
1401}
1402input[type="checkbox"]:checked:after {
1403 content: '\\2714';
1404 position: absolute;
1405 top: -8px;
1406 left: 1px;
1407 font-size: 1.1em;
1408}
1409input[type="checkbox"]:focus { outline: none; }
1410input[type="checkbox"]:hover { cursor: pointer; }
1411nav > div {
1412 text-align: right;
1413 margin-right: 30px;
1414}
1415nav > div button {
1416 border: none !important;
1417 padding: 1px 20px 1px 10px !important;
1418 position: relative;
1419}
1420section:nth-child(4) fieldset {
1421 padding: 0px;
1422 min-height: 467px;
1423}
1424td.hideContainer .imgHide {
1425 cursor: pointer !important;
1426 vertical-align: middle;
1427}
1428.active:after { content: '\\2212' !important; }
1429.alertContainer {
1430 position: fixed;
1431 width: 100%;
1432 z-index: 200;
1433 top: 0px;
1434 font-size: 8pt;
1435}
1436.applyContainer {
1437 padding-top: 5px;
1438 padding-right: 8px;
1439 border-top: 1px solid threedface;
1440 width: 100%;
1441 position: relative;
1442 left: -4px;
1443}
1444.branch {
1445 position: absolute;
1446 left: 10px;
1447 top: 1px;
1448 margin-left: -3px;
1449}
1450.category {
1451 text-align: center;
1452 position: absolute;
1453 left: 115px;
1454 bottom: 3px;
1455 line-height: 20px;
1456}
1457.collapsible {
1458 cursor: pointer;
1459 width: 100%;
1460 border: 0;
1461 outline: none;
1462 text-align: left;
1463 font-size: 1.25em;
1464 background-color: rgba(0, 0, 0, 0);
1465 color: inherit;
1466 font-weight: bold;
1467 padding:5px 3px;
1468 position: relative;
1469}
1470.collapsible:after {
1471 content: '\\002B';
1472 font-weight:bold;
1473 float:right;
1474 margin-right:5px;
1475}
1476.collapsible:before {
1477 content: '';
1478 position: absolute;
1479 padding: 4px;
1480 border-bottom: 1px solid threedface;
1481 border-left: 1px solid threedface;
1482 top: 5px;
1483 left: -11px;
1484}
1485.collapsible:hover { background-color: rgba(255, 255, 255, 0.1); }
1486.content {
1487 max-height: 0;
1488 overflow: hidden;
1489 transition: all .2s ease-in-out;
1490 border-bottom: 1px solid threedface;
1491}
1492.content button {
1493 margin-top: 3px;
1494 margin-right: 10px;
1495}
1496.control {
1497 position: relative;
1498 float: right;
1499 right: -5px;
1500}
1501.date {
1502 font-style: italic;
1503 font-weight: bold;
1504}
1505.dropdown {
1506 display: none;
1507 position: absolute;
1508 z-index: 999;
1509 min-width: 150px;
1510 padding: 2px;
1511 border-radius: 1px;
1512 box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
1513 right: 0px;
1514 border: 1px solid threedface;
1515 background: ` + $('.ido').css('background') + `;
1516 background: ` + $('.ido').css('backgroundColor') + `;
1517}
1518.dropdown a {
1519 display: block;
1520 text-decoration: none;
1521 padding-right:2px;
1522}
1523.dropdown a:hover { background: rgba(255,255,255,0.2); }
1524.ehx-compact {
1525 border-style: solid;
1526 border-width: 1px 0;
1527 text-align: center;
1528}
1529.ehx-extended {
1530 width: 120px;
1531 position: absolute;
1532 left: 3px;
1533 top: 172px;
1534 text-align: center;
1535 font-size: 8pt;
1536 line-height: 1.5;
1537}
1538.ehx-extended-favs {
1539 padding: 3px 1px;
1540 display: block;
1541 line-height: 1.5;
1542}
1543.ehx-minimal {
1544 border-left: 1px solid #6f6f6f4d;
1545 text-align: center;
1546}
1547.ehx-thumbnail {
1548 display: block;
1549 text-align: center;
1550 margin: 3px 0 5px;
1551 line-height: 12px;
1552}
1553.ehx-visited .gl3e { min-height: 206px; }
1554.ehx-visited .gl4e { min-height: 264px !important; }
1555.gl2c { width: 115px; }
1556.gltc ehx { white-space: nowrap; }
1557.gltc td.hideContainer {
1558 border-bottom: 1px solid #6f6f6f4d;
1559 border-top: 1px solid #6f6f6f4d;
1560
1561}
1562.glte .imgHide {
1563 cursor: pointer !important;
1564 padding: 4px 2px 0px 1px;
1565 top: 3px;
1566 right: 5px;
1567 left: initial;
1568 bottom: initial;
1569}`
1570 + (setStore.titleShow ? `div.gl4t:hover {
1571 overflow: visible;
1572 z-index: 3;
1573 position: relative;
1574 background: rgba(0, 0, 0, 0.5);
1575 height: auto;
1576 max-height: none;
1577}
1578div.gl1t {
1579 min-height: 455px;
1580 position: relative;
1581}
1582div.gl3t {
1583 position: absolute;
1584 left: 50%;
1585 margin-left: -125px;
1586 top: 38px;
1587}
1588div.gl4t {
1589 font-weight: bold;
1590 text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
1591}
1592div.gl5t {
1593 position: absolute;
1594 bottom: 25px;
1595 left: 50%;
1596 margin-left: -100px;
1597}` : ``) + `
1598.hideControls { top: -3px; }
1599.imgContainer > .imgHide {
1600 cursor: pointer !important;
1601 position: absolute;
1602 top: 1px;
1603 right: 4px;
1604 bottom: unset;
1605 left: unset;
1606}
1607.inactive { display: none; }
1608.listBody {
1609 width: 100%;
1610 height: 100%;
1611 vertical-align: top;
1612 position: relative;
1613}
1614.listing {
1615 width: 100%;
1616 height: 140px;
1617 border-bottom: 1px solid threedface;
1618 margin-top: 10px;
1619 padding-bottom: 5px;
1620}
1621.listing a {
1622 text-decoration: none;
1623 position: relative;
1624 z-index: 1;
1625}
1626.listing:last-child { border-bottom: none; }
1627.mencon {
1628 display: inline-block;
1629 position: relative;
1630}
1631.menu:after {
1632 content: '\\2335';
1633 position: absolute;
1634 right: 5px;
1635 bottom: 1px;
1636}
1637.noscroll { overflow: hidden; }
1638.notice {
1639 position: relative;
1640 width: 500px;
1641 height: 20px;
1642 top: 20px;
1643 left: 50%;
1644 transform: translate(-50%, 0px);
1645 background: rgba(70, 130, 180, 0.8);
1646 font-size: 1.2em;
1647 font-weight: bold;
1648 color: white;
1649 padding-top: 5px;
1650 border-radius: 8px;
1651 z-index: 999;
1652 text-align: center;
1653}
1654.notice.alert {
1655 background: rgba(165, 42, 42, 0.8);
1656}
1657.notice:not(:first-child) {
1658 top: 30px;
1659}
1660.overlay {
1661 background: rgba(0,0,0,0.5);
1662 display: -webkit-flex;
1663 display: flex;
1664 position: fixed;
1665 top: 0;
1666 left: 0;
1667 width: 100%;
1668 height: 100%;
1669 z-index: 100;
1670 font-size: 9pt;
1671}
1672.overlay button:not(.collapsible) {
1673 background-color: transparent;
1674 border-radius: 6px;
1675 border: 1px solid threedface;
1676 cursor: pointer;
1677 font-weight: bold;
1678 padding: 3px 20px;
1679 text-decoration: none;
1680 color: inherit;
1681 margin-left: 5px;
1682}
1683.overlay button:not(.collapsible):hover { background-color: rgba(255, 255, 255, 0.1); }
1684.overlay button:not(.collapsible):focus { outline: none; }
1685.rating {
1686 text-align: right;
1687 line-height: 18px;
1688 position: absolute;
1689 right: 5px;
1690 bottom: 8px;
1691}
1692.removed {
1693 opacity: 0.5;
1694 -webkit-opacity: 0.5;
1695 pointer-events: none;
1696}
1697.sControls {
1698 top: -3px;
1699 margin-bottom: 5px;
1700}
1701@-moz-document url-prefix() {
1702 .sControls {
1703 top: initial;
1704 margin-top: 3px;
1705 margin-bottom: 5px;
1706 }
1707}
1708.section-container {
1709 text-align: left;
1710 overflow: auto;
1711 margin: 5px 0px 5px 0px;
1712 padding-bottom: 5px;
1713}
1714.section-container textarea:disabled, .section-container input:disabled, .section-container select:disabled {
1715 opacity: 0.6;
1716 -webkit-opacity: 0.6;
1717}
1718.section-container input[type="number"] {
1719 border: 1px solid #8d8d8d;
1720 margin-left:0px;
1721 text-align: center;
1722 width: 50px;
1723}
1724.section-container select { margin-left: 0px; }
1725.section-container code {
1726 color: #000;
1727 background-color: #FFF;
1728}
1729.section-container fieldset { padding-right: 18px; }
1730.settings {
1731 background: ` + $('.ido').css('background') + `;
1732 background: ` + $('.ido').css('backgroundColor') + `;
1733 box-sizing: border-box;
1734 height: 555px;
1735 max-height: 100%;
1736 width: 900px;
1737 max-width: 100%;
1738 margin: auto;
1739 padding: 5px;
1740 display: -webkit-flex;
1741 display: flex;
1742 -webkit-flex-direction: column;
1743 flex-direction: column;
1744 box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.5);
1745}
1746.settings nav {
1747 text-align: right;
1748 padding-bottom: 5px;
1749 font-weight: bold;
1750 position: relative;
1751}
1752.settings legend {
1753 font-size: 10pt;
1754 font-weight: bold;
1755}
1756.settings label {
1757 font-weight: bold;
1758 text-decoration: underline;
1759 cursor: pointer;
1760}
1761.settings h3 {
1762 margin: 3px;
1763 position: relative;
1764}
1765.settings input { vertical-align: -1px; }
1766.settings textarea {
1767 width: 100%;
1768 height: 50px;
1769 resize: vertical;
1770 margin-bottom: 5px;
1771}
1772.show { display:block }
1773.suboptions { position: relative; }
1774.suboptions > div {
1775 position: relative;
1776 padding-left: 1.4em;
1777}
1778.suboptions2 {
1779 margin-left: 4px;
1780 padding-left: 10px;
1781 margin-right: -9px;
1782}
1783.thumb {
1784 display: inline-block;
1785 width: 100px;
1786 margin: 0px 10px;
1787 float: left;
1788}
1789.thumb img {
1790 max-width: 100%;
1791 max-height: -webkit-fill-available;
1792}
1793.title { font-size: 12pt; }
1794</style>`).appendTo('head');
1795 $(`<style id="setStyle" data-jqstyle="ehxVisited">
1796table.itg > tbody > tr.ehx-visited, .gl1t.ehx-visited { ` + cssA.visible + ` }
1797table.itg > tbody > tr.ehx-visited.ehx-hidden, .gl1t.ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
1798.ehx-hidden { ` + cssD + cssA.hidden + ` }
1799.ehx-downloaded { ` + cssA.download + ` }
1800.ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
1801.ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
1802.ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
1803.ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
1804</style>`).appendTo('head');
1805})();