EhxVisited

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

Από την 28/12/2019. Δείτε την τελευταία έκδοση.

// ==UserScript==
// @name         EhxVisited
// @namespace    https://sleazyfork.org/en/users/285675-hauffen
// @version      2.53.50.1
// @description  E-H Visited, combined with ExVisited, and then better.
// @author       Hauffen
// @require      https://code.jquery.com/jquery-3.3.1.min.js
// @include      /https?:\/\/(e-|ex)hentai\.org\/.*/
// ==/UserScript==

(function() {
    window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
    window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: 'readwrite'};
    window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;

    if (!window.indexedDB) {
        console.warn("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
        return;
    }

    /*═════════════════════════════╗
    ║    Configuration Defaults    ║
    ╚═════════════════════════════*/
    var setStore = JSON.parse(localStorage.getItem('ehx-settings')) ? JSON.parse(localStorage.getItem('ehx-settings')) : JSON.parse('{"softHide":false, "minAdd":true, "minShow":false, "cssTT":false, "repPub":false, "visHide":false, "hidShow":false, "pFilter":false, "pLimit":0, "stFilter":false, "stLimit":0, "titleShow":true}');
    var filters = localStorage.getItem('ehx-filters') ? localStorage.getItem('ehx-filters') : '#\\[Erocolor\\]';
    var cssA = JSON.parse(localStorage.getItem('ehx-css')) ? JSON.parse(localStorage.getItem('ehx-css')) : JSON.parse('{"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;", "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;"}');
    var cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
	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==';
    /*════════════════════════════*/

    let db = null;
    var filterArr = [];
    var observer = new MutationObserver(e => {
        addCSS();
    });

    var spl = document.URL.split('/');
    var d1 = spl[3];
    var galleries, hidden;
    var ehxClearConfirm = 0, ehxhClearConfirm = 0;

    if (d1 == 'g') addGallery(); // Add the current page to galleries
    if (d1.substr(0, 1) == '?' || d1.substr(0, 1) == 'w' || d1.substr(0, 1) == '#' || d1.substr(0, 1) == 'f' || d1.substr(0, 1) == 'p' || d1.substr(0, 1) == 't' || !d1) {
        populateFilter();
        populate();
    }

    /**
     * Populate the custom filter array with user input converted into regular expressions
	 */
    function populateFilter() {
        filterArr = []; // Start fresh for when we call this again
        if (filters != '') {
            var tempArr = filters.split('\n');
            for (var i = 0; i < tempArr.length; i++) {
                if (tempArr[i].startsWith('#')) continue;
                filterArr.push(new RegExp(tempArr[i], 'i'));
            }
        }
    }

    /**
     * Add a gallery to our IndexedDB
	 */
    function addGallery() {
        const request = indexedDB.open('ehxvisited', 1);

        request.onupgradeneeded = e => { // Generate our database if it's not there
            db = e.target.result;

            if (!db.objectStoreNames.contains('galleries')) {
                db.createObjectStore('galleries', {keyPath: 'id'});
            }
            if (!db.objectStoreNames.contains('hidden')) {
                db.createObjectStore('hidden', {keyPath: 'id'});
            }
        };

        request.onsuccess = e => {
            db = e.target.result;

            var objStore = db.transaction('galleries', 'readwrite').objectStore('galleries');
            var openRequest = objStore.openCursor(spl[4] + '.' + spl[5]);

            openRequest.onsuccess = e => {
                var cursor = openRequest.result;
                if (cursor) { // Update entry if key exists
                    cursor.update({id: spl[4] + '.' + spl[5], visited: Date.now()});
                    console.log('EhxVisited: Updated ' + spl[4] + '.' + spl[5]);
                } else { // Otherwise, add entry
                    objStore.add({id: spl[4] + '.' + spl[5], visited: Date.now()});
                    console.log('EhxVisited: Added ' + spl[4] + '.' + spl[5]);
                }
            };

            openRequest.onerror = e => {
                console.log(`EhxVisited: Something bad happened with gallery ${spl[4]}.${spl[5]}: ${e.target.error}`);
            };
        };
    };

    /**
     * Updates the hidden gallery count in the header object
	 */
    function updateGListing() {
        // There's probably a better way to do this portion rather than iterate through all current objects, but runtime is negligible
        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
        var hAmount, vAmount, fAmount, pAmount, rAmount, hCount;
        hAmount = vAmount = fAmount = pAmount = rAmount = hCount = 0;
        for (var i = 0; i < list.length; i++) {
            if ($(list[i]).hasClass('ehx-hidden')) { // Count generic Hidden galleries
                if ($(list[i]).hasClass('ehx-visited')) vAmount++;
                hCount++;
            }
            if ($(list[i]).attr('data-jqstyle') != undefined) { // Count elements via JQ Style tags
                if ($(list[i]).attr('data-jqstyle').match('h')) hAmount++;
                if ($(list[i]).attr('data-jqstyle').match('f')) fAmount++;
                if ($(list[i]).attr('data-jqstyle').match('p')) pAmount++;
                if ($(list[i]).attr('data-jqstyle').match('r')) rAmount++;
            }
            if ($('#ehx-show').text() === 'Show') { // If Visited Galleries are hidden, count those as well
                if ($(list[i]).hasClass('ehx-visited')) {
                    vAmount++;
                    hCount++;
                }
            }
        }
        if (!setStore.softHide) {
            $('#hideCount').html('There ' + (hCount > 1 || hCount == 0 ? 'are ' : 'is ') + '<span>' + hCount + ' hidden ' + (hCount > 1 || hCount == 0 ? 'galleries' : 'gallery') + '</span> on this page.');
        } else {
            $('#hideCount').html('There are <span>0 hidden galleries</span> on this page.');
        }
        $('#hideCount > span').prop('title', 'Hidden: ' + hAmount + ' | Visited: ' + vAmount + ' | Filtered: ' + fAmount + ' | Page Limit: ' + pAmount + ' | Rating Limit: ' + rAmount);
    }

    /**
     * Convert the star count of a specified element to a double
	 * @param {Object} el - A specific element within the DOM
	 */
    function getStarNumber(el) {
        var stars = $(el).find('.ir').css('background-position');
        switch(stars) {
            case "0px -1px":
                return 5;
            case "0px -21px":
                return 4.5;
            case "-16px -1px":
                return 4;
            case "-16px -21px":
                return 3.5;
            case "-32px -1px":
                return 3;
            case "-32px -21px":
                return 2.5;
            case "-48px -1px":
                return 2;
            case "-48px -21px":
                return 1.5;
            case "-64px -1px":
                return 1;
            case "-64px -21px":
                return 0.5;
            default:
                return 0;
        }
    }

    /**
	 * Check a specified element through the filters individually, then apply jqstyle tags for CSS
	 * @param {Object} el - A specific element within the DOM
	 */
    function filterCheck(el) {
        if (filterArr.length > 0) {
            if (filterArr.some(rx => rx.test($(el).find('.glink').text()))) { // Test our gallery name through our regex filters
                if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
                if (!($(el).attr('data-jqstyle') || '').match('f')) addStyle($(el), 'f');
            } else if ($(el).hasClass('ehx-hidden')) {
                    removeStyle($(el), 'f');
            }
        } else if ($(el).hasClass('ehx-hidden')) { // Filter length is now null
            removeStyle($(el), 'f');
        }

        // Filter our galleries through the star limit filter
        if (setStore.stFilter && setStore.stLimit > 0) {
            if (getStarNumber(el) < setStore.stLimit) {
                if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
                if (!($(el).attr('data-jqstyle') || '').match('r')) addStyle($(el), 'r');
            } else if ($(el).hasClass('ehx-hidden')) {
                removeStyle($(el), 'r');
            }
        } else if ($(el).hasClass('ehx-hidden')) {
            removeStyle($(el), 'r');
        }

        var pages = 0; // Page Count is stored in a lot of different random elements throughout, there are probably better selectors for this
        if ($('.gl1e').length) { pages = $(el).find('.gl3e > div:nth-child(5)').text().split(' ')[0]; }
        else if ($('.gl1c').length) { pages = $(el).find('.gl4c > div:nth-child(2)').text().split(' ')[0]; }
        else if ($('.gl1t').length) { pages = $(el).find('.gl5t > div:nth-child(2) > div:nth-child(2)').text().split(' ')[0]; }
        else { pages = $(el).find('.gl2m > div:nth-child(2) > div:nth-child(2) > div:nth-child(2) > div:nth-child(2)').text().split(' ')[0]; }
        if (setStore.pFilter && setStore.pLimit > 0 && !$(el).attr('data-jqstyle').match('p')) {
            if (parseInt(pages) < parseInt(setStore.pLimit)) {
                if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
                if (!($(el).attr('data-jqstyle') || '').match('p')) addStyle($(el), 'p');
            } else if ($(el).hasClass('ehx-hidden')) {
                removeStyle($(el), 'p');
            }
        } else if ($(el).hasClass('ehx-hidden')) {
            removeStyle($(el), 'p');
        }
    }

    /**
     * Adds the specified style flag to a specified element
	 * @param {Object} el - A specific element within the DOM
	 * @param {String} flag - A character to mark an element for JQ Styling CSS rules
	 */
    function addStyle(el, flag) {
        if ($(el).attr('data-jqstyle')) {
            $(el).attr('data-jqstyle', $(el).attr('data-jqstyle') + flag);
        } else {
            $(el).attr('data-jqstyle', flag);
        }
    }

    /**
     * Removes the specified style flag from a specified element
	 * @param {Object} el - A specific element within the DOM
	 * @param {string} flag - A JQ Style flag to remove from an element
	 */
    function removeStyle(el, flag) {
        if ($(el).attr('data-jqstyle')) {
            $(el).attr('data-jqstyle', $(el).attr('data-jqstyle').replace(flag, ''));
            if (!$(el).attr('data-jqstyle')) $(el).removeClass('ehx-hidden'); // Replacing the flag brought jqstyle to blank
        } else { // data-jqstyle is blank
            $(el).removeClass('ehx-hidden');
        }
    }

    /**
     * Toggles a specified element's hidden status
	 * @param {String} tga - Full gallery URL
	 * @param {Object} el - A specific element within the DOM
	 */
    function toggleElement(tga, el) {
        const request = indexedDB.open('ehxvisited', 1);
        var tgid = tga.split('/')[4] + '.' + tga.split('/')[5];

        request.onsuccess = e => {
            db = e.target.result;
            var objStore = db.transaction('hidden', 'readwrite').objectStore('hidden');
            var openReq = objStore.openCursor(tgid);
            openReq.onsuccess = e => {
                var cursor = e.target.result;
                if (cursor) { // Gallery already exists within our hidden table
                    cursor.delete();
                    console.log('EhxVisited: Removed ' + tgid + ' from hidden list.');
                    $(el).css('display', '');
                    $(el).removeClass('ehx-hidden');
                    removeStyle($(el), 'h');
                    delete hidden.data[tgid]; // Remove gallery listing from our local store of hidden galleries
                } else {
                    objStore.put({id: tgid}); // Put the gallery into our hidden table
                    console.log('EhxVisited: Added ' + tgid + ' to hidden list.');
                    $(el).addClass('ehx-hidden');
                    if ($('#ehxh-show').text() === 'Hide') $(el).css('display', $('.gl1t').length ? 'flex' : 'table-row');
                    addStyle($(el), 'h');
                    hidden.data[tgid] = 1; // Add gallery listing to our local store of hidden galleries
                }
                updateGListing();
                $('#hLength').text(Object.keys(hidden.data).length);
            }
        }
        if ($('.glte > tbody > tr').first().hasClass('ehx-hidden') && !setStore.hidShow) $('.glte > tbody > tr:not(.ehx-hidden):first .imgHide').css('border-top', 'none'); // Because aesthetics
        else $('.imgHide').removeAttr('style');
    }

    /**
	 * Fill our local gallery listings so we can preform easier operations on the data.
	 * Also set up the majority of our global HTML elements and their functions.
	 */
    function populate() { // TODO: Separate the HTML entries from the population portion
		galleries = JSON.parse('{"data":{}}');
		hidden = JSON.parse('{"data":{}}');
        const request = indexedDB.open('ehxvisited', 1);

        request.onupgradeneeded = e => {
            db = e.target.result;

            if (!db.objectStoreNames.contains('galleries')) {
                db.createObjectStore('galleries', {keyPath: 'id'});
            }
            if (!db.objectStoreNames.contains('hidden')) {
                db.createObjectStore('hidden', {keyPath: 'id'});
            }
        };

        request.onsuccess = e => {
            db = e.target.result;
            var objStore = db.transaction('galleries', 'readonly').objectStore('galleries');
            var openReq = objStore.getAll();
            openReq.onsuccess = f => {
                console.log('EhxVisited: Populated global variables.');
                var transform = f.target.result;
                for (var i = 0; i < transform.length; i++) {
                    galleries.data[transform[i].id] = transform[i].visited; // Force matrix data into array data
                }
                var gLength = Object.keys(galleries.data).length
                var objStore2 = db.transaction('hidden', 'readonly').objectStore('hidden');
                var openReq2 = objStore2.getAll();
                openReq2.onsuccess = g => {
                    var transform2 = g.target.result;
                    for (i = 0; i < transform2.length; i++) {
                        hidden.data[transform2[i].id] = 1; // Force matrix data into array data
                    }
                    var hLength = Object.keys(hidden.data).length
                    $($('h1').text() == 'Favorites' ? '.ido > div:nth-child(3)' : '#toppane').append(
                        `<ehx id="ehx-controls">Galleries visited: <span id="gLength">` + gLength + `</span> ( <span id="ehx-menu-control"></span><a id="ehx-settings">Settings</a> )
                         <br/>Hidden Galleries: <span id="hLength">` + hLength + `</span><span id="ehxh-menu-control"></span></ehx>`);
                    if (!setStore.softHide) {
                        $('#ehx-menu-control').append('<a id="ehx-show">' + ((setStore.visHide) ? 'Show' : 'Hide') + '</a> / ');
                        $('#ehxh-menu-control').append(' ( <a id="ehxh-show">' + ((setStore.hidShow) ? 'Hide' : 'Show') + '</a> )');
                    }
                    $('#ehx-settings').click(e => {
                        e.preventDefault();
                        settings();
                    });
                    $('#ehx-show').click(e => {
                        var disp =  $('.ehx-visited.ehx-hidden').css('display');
                        if ($('#ehx-show').text() === 'Show') {
                            $('.ehx-visited').css({display: ''});
                            $('.ehx-visited.ehx-hidden').css({display: disp});
                        } else {
                            $('.ehx-visited').css({display: 'none'});
                            $('.ehx-visited.ehx-hidden').css({display: disp});
                        }
                        $('#ehx-show').text((i, t) => {
                            return t === 'Show' ? 'Hide' : 'Show';
                        });
                        updateGListing();
                        setStore.visHide = $('#ehx-show').text() === 'Show' ? true : false;
                        localStorage.setItem('ehx-settings', JSON.stringify(setStore)); // Update our stored settings
                    });
                    $('#ehxh-show').click(e => {
                        if ($('#ehxh-show').text() === 'Show') {
                            if (!$('.gl1t').length) { // For table view modes
                                $('.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')}); // Copy the display CSS of our closest element
                                $('.ehx-visited.ehx-hidden').css({display: $('.ehx-hidden').parent().find('tr').not('.ehx-hidden').css('display')});
                            } else { // Default display CSS for thumbnail
                                $('.ehx-hidden').css({display: 'flex'});
                                $('.ehx-visited.ehx-hidden').css({display: 'flex'});
                            }
                            $('.imgHide').removeAttr('style'); // aesthetics
                        } else {
                            $('.ehx-hidden').css({display: ''});
                            $('.ehx-visited.ehx-hidden').css({display: ''});
                            if ($('.glte > tbody > tr').first().hasClass('ehx-hidden')) $('.glte > tbody > tr:not(.ehx-hidden):first .imgHide').css('border-top', 'none'); // a e s t h e t i c s
                        }
                        $('#ehxh-show').text((i, t) => { // Toggle text
                            return t === 'Show' ? 'Hide' : 'Show';
                        });
                        updateGListing();
                        setStore.hidShow = $('#ehxh-show').text() === 'Hide' ? true : false;
                        localStorage.setItem('ehx-settings', JSON.stringify(setStore));
                    });
                    $('#ehx-controls').append('<br /><span id="hideCount"><span></span></span>');
					addCSS();
					updateGListing();
                    if ($('.glte > tbody > tr').first().hasClass('ehx-hidden') && !setStore.hidShow) $('.glte > tbody > tr:not(.ehx-hidden):first .imgHide').css('border-top', 'none'); // A E S T H E T I C S
                }
            }
        }
    }

    /**
	 * Our main function that does basically everything that we see.
	 * Appends our custom HTML objects to the main page.
	 * Adds CSS to elements based on whether they can be found in the populated local gallery listing.
	 */
    function addCSS() {
		observer.disconnect();
		var list = $('.itg tr').length ? $('tr').has('.glhide, .gldown, th') : $('.itg .gl1t');
		var gid, galleryId, onFavs;

		if (list.length) {
			if ($('h1').text() == 'Favorites') onFavs = 1;
			if ($('.gl1e').length) { // Extended
				for (var i = 0; i < list.length; i++) {
					gid = $(list[i]).find('.gl1e a').attr('href').split('/');
					galleryId = gid[4] + '.' + gid[5];

					if (galleries.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-visited')) { // Visited
                        $(list[i]).addClass('ehx-visited');
                        if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
                        if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text(buildTime(galleryId, false));
                        if (onFavs) {
                            $(list[i]).find('.gl3e div:last-child').append('<br/><ehx class="ehx-extended-favs">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
                        } else {
                            $(list[i]).find('.gl3e').append('<ehx class="ehx-extended">\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false) + '</ehx>');
                        }
					} else { // Never Visited
						if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
						if (setStore.repPub) $(list[i]).find('.gl3e div:nth-child(2)').text('Never Visited');
					}
					if (hidden.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
						$(list[i]).addClass('ehx-hidden');
						addStyle($(list[i]), 'h');
					}
					if ($(list[i]).children().length < 3 || ($(list[i]).children().length < 4 && onFavs)) { // 2 in normal, 3 in favorites
						$('<img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery">').prependTo($(list[i]).find('a').first().parents().eq(2)).click(e => { // Maybe closest('tr')
							toggleElement($(e.currentTarget).parent().find('a').attr('href'), $(e.currentTarget).parent());
						});
					}
					filterCheck($(list[i]));
				}
			} else if ($('.gl1c').length) { // Compact
				var borderColor = $('.gl1c').first().css('border-top-color');
				if ($('.itg tr:first-child').children().length < 5) { // We haven't appended our table head
					$('.itg th:nth-child(4)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">&#x2716</th>'); // X column
					if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th>Visited</th>');
					if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
				}
				for (i = 1; i < list.length; i++) {
					gid = $(list[i]).find('.glname a').attr('href').split('/');
					galleryId = gid[4] + '.' + gid[5];

					if (galleries.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-visited')) { // Visited
                        $(list[i]).addClass('ehx-visited');
                        if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
                        if (setStore.repPub) $(list[i]).find('.gl2c div:nth-child(3) div:first-child').text(buildTime(galleryId, false));
                        if (setStore.minAdd) {
                            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(0, 10) + '</ehx>');
                            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(0, 10) + '</ehx></td>');
                        }
					} else { // Never Visited
						if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
						if (setStore.repPub) $(list[i]).find('.gl2c > div:nth-child(3) > div:first-child').text('Never Visited');
						if ($(list[i]).children().length < 5 || ($(list[i]).children().length < 6 && onFavs)) {
							if (setStore.minAdd) $(list[i]).find('.gl2c').after('<td class="ehx-compact" style="border-color:' + borderColor + ';"></td>');
						}
					}
					if (hidden.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
                        $(list[i]).addClass('ehx-hidden');
                        addStyle($(list[i]), 'h');
					}
					var childCount = $(list[i]).children().length;
					if (childCount < 5 || (childCount < 6 && setStore.minAdd) || (childCount < 7 && onFavs)) { // Fuck this conditional
						$('<td class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
							toggleElement($(e.currentTarget).parent().find('.glname a').attr('href'), $(e.currentTarget).parent());
						});
					}
					filterCheck($(list[i]));
				}
			} else if ($('.gl1m').length) { // Minimal
				if ($('.itg tr:first-child').children().length < 7) { // We haven't appended our table head
					$('.itg th:nth-child(6)').after('<th style="text-align: center;" title="EhxVisited: Click to Show/Hide">&#x2716</th>'); // X Column
					if (setStore.minAdd) $('.itg th:nth-child(2)').after('<th title="EhxVisited: Hover for timestamps" style="text-align: center;">\uD83D\uDC41</th>');
					if (setStore.repPub) $('.itg th:nth-child(2)').text('Visited');
				}
				for (i = 1; i < list.length; i++) {
					gid = $(list[i]).find('.glname a').attr('href').split('/');
					galleryId = gid[4] + '.' + gid[5];

					if (galleries.data[galleryId] != undefined) { // Visited
                        if (!$(list[i]).hasClass('ehx-visited')) {
                            $(list[i]).addClass('ehx-visited');
                            if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
                            if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text(buildTime(galleryId, false));
                            if (setStore.minAdd) {
                                if (setStore.minShow) { // Maybe reevaluate this block
                                    if ($(list[i]).find('.ehx-minimal').length) $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
                                    else $(list[i]).find('.gl2m').after('<td class="ehx-minimal"><ehx title="EhxVisited: ' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx></td>');
                                } else {
                                   if ($(list[i]).find('.ehx-minimal').length) {
                                        $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
                                        $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
                                    } else $(list[i]).find('.gl2m').after('<td class="ehx-minimal" title="EhxVisited: ' + buildTime(galleryId, true) + '"><ehx>\uD83D\uDC41</ehx></td>');
                                }
                            }
                        }
					} else { // Never Visited
						if (setStore.cssTT) $(list[i]).find('.glname a').attr('title', 'Never Visited');
						if (setStore.repPub) $(list[i]).find('.gl2m div:nth-child(3)').text('Never Visited');
                        if ($(list[i]).children().length < 7 || ($(list[i]).children().length < 8 && onFavs)) {
						    if (setStore.minAdd) $(list[i]).find('.gl2m').after('<td class="ehx-minimal"></td>');
                        }
					}
					if (hidden.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
						$(list[i]).addClass('ehx-hidden');
						addStyle($(list[i]), 'h');
					}
					childCount = $(list[i]).children().length;
					if (childCount < 7 || (childCount < 8 && setStore.minAdd) || (childCount < 8 && onFavs) || (childCount < 9 && setStore.minAdd && onFavs)) { // Fuck this conditional harder
						$('<td class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></td>').appendTo($(list[i]).closest('tr')).click(e => {
							var el = $(e.currentTarget).closest('tr');
							toggleElement($(el).find('.glname a').attr('href'), $(el));
						});
					}
				}
			} else { // Thumbnail
				for (i = 0; i < list.length; i++) {
					gid = $(list[i]).find('.gl3t a').attr('href').split('/');
					galleryId = gid[4] + '.' + gid[5];

					if (galleries.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-visited')) { // Visited
							$(list[i]).addClass('ehx-visited');
							if (setStore.cssTT) $(list[i]).find('.glname').attr('title', '\uD83D\uDC41 ' + buildTime(galleryId, true));
                            if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text(buildTime(galleryId, false));
							$(list[i]).find('.gl5t').after('<ehx class="ehx-thumbnail">\uD83D\uDC41 ' + buildTime(galleryId, true) + '</ehx>');
					} else { // Never Visited
						if (setStore.cssTT) $(list[i]).find('.glname').attr('title', 'Never Visited');
                        if (setStore.repPub) $(list[i]).find('.gl5t div:first-child div:nth-child(2)').text('Never Visited');
					}
					if (hidden.data[galleryId] != undefined && !$(list[i]).hasClass('ehx-hidden')) {
						$(list[i]).addClass('ehx-hidden');
						addStyle($(list[i]), 'h');
					}
					if ($(list[i]).find('.gl5t').children().length < 3) {
						$('<div class="hideContainer"><img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery"></div>').appendTo($(list[i]).find('.gl5t')).on('click', e => {
							var el = $(e.currentTarget).parents().eq(1);
							toggleElement($(el).find('.gl3t a').attr('href'), $(el));
						});
					}
					filterCheck($(list[i]));
				}
			}
			updateGListing();
		} else { // No Elements pulled, invalid view
			displayAlert('No Valid Elements Detected', 5000, true);
			return;
		}

		if (setStore.visHide) {
			$('.ehx-visited').css({display: 'none'});
			$('#ehx-show').text('Show');
		}
		if (setStore.hidShow) {
			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
			else { // Unless you're an idiot and hid everything on the page
				if ($('.gl1t').length) { $('.ehx-hidden').css({display: 'flex'}); } // Use the default values
				else { $('.ehx-hidden').css({display: 'table-row'}); }
			}
			$('#ehxh-show').text('Hide');
		}
		observer.observe($('.itg').get(0), { // Reconnect the observer for changes
			childList: true,
			subtree: true
		});
	}

	/**
     * Build the time difference string
     * @param {String} gid - Gallery ID
	 * @param {Boolean} time - Include timeDifference in returned string
	 * @param {Boolean} abbrv - Abbreviate for timeDifference
     */
    function buildTime(gid, time, abbrv) {
        var d = new Date(galleries.data[gid]);
		var str = d.getFullYear().toString() + '-' + (d.getMonth() + 1) + '-' + d.getDate() + ' ' + d.getHours().toString().padStart(2, '0') + ':' + d.getMinutes().toString().padStart(2, '0');
		if (time) return timeDifference(gid, abbrv) + ' ' + str;
		return str;
    }

    /**
	 * Get time difference in words
	 * @param {Date} previous - Previous date to compare against Date.now()
	 * @param {Boolean} abbreviate - Should the text string have abbreviatated text
	 */
    function timeDifference(gallery, abbreviate) {
		var previous = galleries.data[gallery];
        var msPerMinute = 60 * 1000;
        var msPerHour = msPerMinute * 60;
        var msPerDay = msPerHour * 24;
        var msPerMonth = msPerDay * 30;
        var msPerYear = msPerDay * 365;
        var elapsed = Date.now() - previous;

        if (elapsed < msPerMinute) {
            return Math.round(elapsed / 1000) + ((typeof abbreviate !== 'undefined') ? '&nbsp;sec' : ' seconds ago');
        } else if (elapsed < msPerHour) {
            return Math.round(elapsed / msPerMinute) + ((typeof abbreviate !== 'undefined') ? '&nbsp;min' : ' minutes ago');
        } else if (elapsed < msPerDay) {
            return Math.round(elapsed / msPerHour) + ((typeof abbreviate !== 'undefined') ? '&nbsp;hrs' : ' hours ago');
        } else if (elapsed < msPerMonth) {
            return Math.round(elapsed / msPerDay) + ((typeof abbreviate !== 'undefined') ? '&nbsp;days' : ' days ago');
        } else if (elapsed < msPerYear) {
            return Math.round(elapsed / msPerMonth) + ((typeof abbreviate !== 'undefined') ? '&nbsp;mos' : ' months ago');
        } else {
            return Math.round(elapsed / msPerYear) + ((typeof abbreviate !== 'undefined') ? '&nbsp;yrs' : ' years ago');
        }
    }

	/**
	 * Displays a div at the top of the page with a message
	 * @param {String} message - A message to be displayed within the alert
	 * @param {Integer} timeout - Millseconds message should be displayed for
	 * @param {Boolean} error - Is this an error message
	 */
	function displayAlert(message, timeout, error) {
		var alert = $('<div class="notice ' + ((error) ? 'alert' : '') + '">EhxVisited: ' + message + '</div>');
		$(alert).hide().appendTo('body').fadeIn(1000);
		setTimeout(e => { $('.notice').fadeOut(1000, f => { $('.notice').remove(); }); }, timeout);
	}

    /**
	 * Fills a text area with formatted gallery data for export
	 * @param {IndexedDB Matrix} items - Raw IndexedDB getAll output
	 */
    function ehxExport(items) {
        var data = '';
        for (var i in items) {
            data += items[i].id + ':' + items[i].visited + ';';
        }
        $('#exportGalleries').val(''); // Clear text area
        $('#exportGalleries').val(data); // Fill with formatted data
		$('#exportGalleries').select();
    }

    /**
     * Apply visited CSS to an element on mouse down
     */
    $('.itg').on('mousedown', 'a', e => {
        if (e.which === 3) return; // Ignore right-clicks
        if (e.currentTarget.href.split('/')[3] === 'g') {
            galleries.data[e.currentTarget.href.split('/')[4] + '.' + e.currentTarget.href.split('/')[5]] = Date.now();
            $('#gLength').text(Object.keys(galleries.data).length);
            addCSS();
        }
    });

    /**
	 * Import user data into our indexedDB
	 * @param {String} store - An object store within the indexedDB
	 * @param {JSON Array} items - String of exported data to import
	 */
    function ehxImport(store, items) {
        var objStore2 = db.transaction(store, 'readwrite').objectStore(store);
        var count = 0, sp = '';

        sp = items.split(';');
        sp = sp.filter(Boolean); // Filter out any null ('') entries
        insertNext();

        /**
		 * Push entries into the specified indexedDB store
		 */
        function insertNext() {
            if (count < sp.length) {
                var str = sp[count].split(':');
                objStore2.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
                if (store == 'galleries') galleries.data[str[0]] = str[1];
                else hidden.data[str[0]] = str[1];
                ++count;
            } else {
				displayAlert('Imported ' + count + ' entries', 5000);
                console.log('EhxVisited: Merge Completed');
                $('#importGalleries').val('');
				$('#gLength').text(Object.keys(galleries.data).length);
                $('#hLength').text(Object.keys(hidden.data).length);
                addCSS();
            }
        }
    }

    /**
     * Remove our stylesheet with transient CSS, and then readd it with the updated CSS
     */
    function updateCSS() {
		cssD = (setStore.softHide) ? 'opacity:0.2; -webkit-opacity: 0.2;' : 'display: none;';
        $('#setStyle').remove();
        $(`<style id="setStyle" data-jqstyle="ehxVisited">`
        + (setStore.titleShow ? `div.gl4t:hover {
            overflow: visible;
            z-index: 3;
            position: relative;
            background: rgba(0, 0, 0, 0.5);
            height: auto;
            max-height: none;
        }
        div.gl1t {
            min-height: 429px;
            position: relative;
        }
        div.gl3t {
            position: absolute;
            left: 50%;
            margin-left: -125px;
            top: 38px;
        }` : ``) + `
        table.itg > tbody > tr.ehx-visited, .gl1t.ehx-visited { ` + cssA.visible + ` }
        table.itg > tbody > tr.ehx-visited.ehx-hidden, .gl1t.ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
        .ehx-hidden { ` + cssD + cssA.hidden + ` }
        .ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
        .ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
        .ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
        </style>`).appendTo('head');
    }

    /**
	 * Open the Settings menu and set up all necessary menu functions
	 */
    function settings() {
        const req = indexedDB.open('ehxvisited', 1);

        req.onsuccess = e => {
            if (db == null) db = e.target.result;
            var objStore = db.transaction('galleries', 'readwrite').objectStore('galleries');
            var openReq = objStore.getAll();
            openReq.onsuccess = e => {
                var len = e.target.result;
                // There's probably a much easier way to do this, or at least a nicer looking, more technical way
                var container = $(`
                <div class="overlay">
					<div class="settings">
						<nav class="topNav">
							<span id="setNotice" style="float: left; margin-left: 3px; font-weight: lighter; opacity: 0.5; -webkit-opacity: 0.5;"></span>
							<div>
								<div class="mencon">
									<button class="menu">Export</button>
									<div class="dropdown">
										<a id="ehx-export" href="javascript:;">Export Galleries</a>
										<a id="ehxh-export" href="javascript:;">Export Hidden Galleries</a>
									</div>
								</div>
								<div class="mencon">
									<button class="menu">Import</button>
									<div class="dropdown">
										<a id="ehx-import" href="javascript:;">Import Galleries</a>
										<a id="ehxh-import" href="javascript:;">Import Hidden Galleries</a>
									</div>
								</div>
								<a id="settings-close" href="javascript:;">&#128939</a>
							</div>
						</nav>
						<div class="section-container">
							<section>
								<fieldset>
									<legend>Settings</legend>
									<div>
										<label>
											<input type="checkbox" id="softHide" ` + (setStore.softHide ? `checked` : ``) + `>Soft Hide Galleries
										</label>
										<span>: Darken hidden galleries instead of removing them from view</span>
									</div>
									<div>
										<label>
											<input type="checkbox" id="minAdd" ` + (setStore.minAdd ? `checked` : ``) + `>Add Visited Column
										</label>
										<span>: Show visits in an additional column in Minimal/Minimal+ and Compact view modes</span>
										<div class="suboptions">
											<div>
												<span class="branch">&#8735</span>
												<label>
													<input type="checkbox" id="minShow" ` + (setStore.minShow ? `checked` : ``) + `>Minimal Show Text
												</label>
												<span>: Show visits as text instead of hovering tooltip in Minimal/Minimal+ view modes</span>
											</div>
										</div>
									</div>
									<div>
										<label>
											<input type="checkbox" id="cssTT" ` + (setStore.cssTT ? `checked` : ``) + `>CSS Tooltips
										</label>
										<span>: Replace gallery link tooltips with visited information in all view modes</span>
									</div>
									<div>
										<label>
											<input type="checkbox" id="repPub" ` + (setStore.repPub ? `checked` : ``) + `>Replace Published
										</label>
										<span>: Replace date published with date visited in Minimal/Minimal+ view modes</span>
									</div>
									<div>
										<label>
											<input type="checkbox" id="titleShow" ` + (setStore.titleShow ? `checked`: ``) + `>Show Full Title
										</label>
										<span>: Show the full title of a gallery on hover in Thumbnail view</span>
									</div>
								</fieldset>
								<fieldset>
									<legend>Custom CSS</legend>
									<h3>Visited Galleries
										<div class="control" id="visControls">
											<button id="resV">Reset CSS</button>
											<button id="ehx-clear">Clear Data</button>
										</div>
									</h3>
									<textarea id="visited" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.visible + `</textarea>
									<h3>Hidden Galleries
										<div class="control sControls" id="hideControls">
											<button id="resH">Reset CSS</button>
											<button id="ehxh-clear">Clear Data</button>
										</div>
									</h3>
									<textarea id="hidden" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.hidden + `</textarea>
									<div class="suboptions2">
										<button class="collapsible">Filtered Galleries</button>
										<div class="content">
											<textarea id="filtered" class="field" spellcheck="false">` + cssA.filter + `</textarea>
											<div class="control sControls">
												<button id="resF">Reset CSS</button>
											</div>
										</div>
										<button class="collapsible">Page Filtered</button>
										<div class="content">
											<textarea id="page" class="field" spellcheck="false"placeholder="Insert CSS">` + cssA.page + `</textarea>
											<div class="control sControls">
												<button id="resP">Reset CSS</button>
											</div>
										</div>
										<button class="collapsible">Rating Filtered</button>
										<div class="content">
											<textarea id="rating" class="field" spellcheck="false" placeholder="Insert CSS">` + cssA.rating + `</textarea>
											<div class="control sControls">
												<button id="resR">Reset CSS</button>
											</div>
										</div>
									</div>
								</fieldset>
								<fieldset>
									<legend>Filters</legend>
									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.
									<ul style="margin: 3px 0px; padding-left: 30px;">
										<li>E.G. <code>Ongoing</code> will filter out every gallery with <code>ongoing</code>, case-insensitive, in the title. <code>\\[Digital\\]</code> will filter out every gallery with <code>[Digital]</code>, case-insensitive, in the title.</li>
										<li>Lines starting with <code>#</code> will be ignored.</li>
									</ul>
									<textarea id="galFilter">` + filters + `</textarea>
									<div>
										<label>
											<input type="checkbox" id="pFilt" ` + (setStore.pFilter ? `checked` : ``) + `>Page Limit
										</label>
										<span>: Filter out any gallery with pages less than:
											<input id="pLim" type="number" min="1" value="` + setStore.pLimit + `" ` + (setStore.pFilter ? `` : `disabled`) + `/>
										</span>
									</div>
									<div>
										<label>
											<input type="checkbox" id="stFilt" ` + (setStore.stFilter ? `checked` : ``) + `>Minimum Rating
										</label>
										<span>: Filter out any gallery with a rating less than:
											<select id="stLim" ` + (setStore.stFilter ? `` : `disabled`) + `>
												<option>5</option>
												<option>4.5</option>
												<option>4</option>
												<option>3.5</option>
												<option>3</option>
												<option>2.5</option>
												<option>2</option>
												<option>1.5</option>
												<option>1</option>
											</select>
										</span>
									</div>
								</fieldset>
							</section>
							<section class="inactive">
								<fieldset style="padding-bottom: 2px; min-height: 474px;">
									<legend id="importTitle">Import Galleries</legend>
									<textarea id="importGalleries"></textarea>
									<div class="control" style="margin-top: 2px; margin-bottom: 4px;">
										<button class="close">Close</button>
										<button id="importConfirm">Import</button>
									</div>
								</fieldset>
							</section>
							<section class="inactive">
								<fieldset style="padding-bottom: 2px; min-height: 474px;">
									<legend id="exportTitle">Export Galleries</legend>
									<textarea id="exportGalleries"></textarea>
									<div class="control" style="margin-top: 2px; margin-bottom: 4px;">
										<button class="close">Close</button>
										<button id="exportCopy">Copy</button>
									</div>
								</fieldset>
							</section>
						</div>
						<div class="applyContainer">
							<div class="control" style="padding-right: 5px;">
								<button id="apply">Apply</button>
							</div>
						</div>
					</div>
				</div>`);
                $('body').append(container);
                if (!$('#minAdd').prop('checked')) { $('#minShow').prop('disabled', true); }
                $('#resV').click(e => { $('#visited').val('box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;'); }); // Default Values
                $('#resH').click(e => { $('#hidden').val('box-shadow: inset 0 0 0 500px rgba(255, 40, 0, .2) !important;'); });
                $('#resF').click(e => { $('#filtered').val('box-shadow: inset 0 0 0 500px rgba(200, 0, 100, .2) !important;'); });
                $('#resP').click(e => { $('#page').val('box-shadow: inset 0 0 0 500px rgba(0, 0, 180, .2) !important;'); });
                $('#resR').click(e => { $('#rating').val('box-shadow: inset 0 0 0 500px rgba(180, 80, 60, .2) !important;'); });
                $('#stLim').val(setStore.stLimit);
                $(document).on('change', 'input', e => { // Put the change listener on document since I suck at event propogation and bubbling
                    if ($('#minAdd').prop('checked')) {
                        $('#minShow').prop('disabled', false);
                    } else {
                        $('#minShow').prop('disabled', true);
                    }
                    if ($('#pFilt').prop('checked')) {
                        $('#pLim').prop('disabled', false);
                    } else {
                        $('#pLim').prop('disabled', true);
                    }
                    if ($('#stFilt').prop('checked')) {
                        $('#stLim').prop('disabled', false);
                    } else {
                        $('#stLim').prop('disabled', true);
                    }
                    if ($('#minAdd').is(e.target) || $('#minShow').is(e.target) || $('#repPub').is(e.target)) $('#setNotice').text('Applied Settings Will Take Effect On Reload');
                });
                $('#settings-close').click(e => $('.overlay').remove());
                $('body').click(e => {
                    if (e.target.className == "overlay") { // Exit if settings menu isn't clicked
                        $('.overlay').remove();
                    } else if (e.target.className != 'show' && e.target.className != 'menu') {
                        $('.show').removeClass('show');
                    }
                });
				$('#apply').click(e => applySettings());

                /**
				 * Parse our HTML options into a temporary JSON array and then stringify it into localStorage
				 */
                function applySettings() {
					setStore = { // Store this independantly, so it doesn't mess up table appends
						"softHide": $('#softHide').prop('checked'),
                        "minAdd": setStore.minAdd,
                        "minShow": setStore.minShow,
                        "cssTT": $('#cssTT').prop('checked'),
                        "repPub": setStore.repPub,
                        "visHide": $('#ehx-show').text() === "Show" ? true : false,
                        "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
                        "pFilter": $('#pFilt').prop('checked'),
                        "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
                        "stFilter": $('#stFilt').prop('checked'),
                        "stLimit": $('#stFilt').prop('checked') ? $('#stLim option:selected').text() : "0",
                        "titleShow": $('#titleShow').prop('checked')
					}
                    var tempSto = {
                        "softHide": $('#softHide').prop('checked'),
                        "minAdd": $('#minAdd').prop('checked'),
                        "minShow": $('#minShow').prop('checked'),
                        "cssTT": $('#cssTT').prop('checked'),
                        "repPub": $('#repPub').prop('checked'),
                        "visHide": $('#ehx-show').text() === "Show" ? true : false,
                        "hidShow": $('#ehxh-show').text() === "Hide" ? true : false,
                        "pFilter": $('#pFilt').prop('checked'),
                        "pLimit": $('#pFilt').prop('checked') ? $('#pLim').val() : "0",
                        "stFilter": $('#stFilt').prop('checked'),
                        "stLimit": $('#stFilt').prop('checked') ? $('#stLim option:selected').text() : "0",
                        "titleShow": $('#titleShow').prop('checked')
                    }
                    localStorage.setItem('ehx-settings', JSON.stringify(tempSto)); // Write settings to localStorage
                    var tempCss = {
                        "visible": $('#visited').val(),
                        "hidden": $('#hidden').val(),
                        "filter":  $('#filtered').val(),
                        "page": $('#page').val(),
                        "rating": $('#rating').val()
                    }
                    cssA = tempCss;
                    localStorage.setItem('ehx-css', JSON.stringify(tempCss));
                    filters = $('#galFilter').val().replace(/^\s*[\r\n]/gm, '');
                    populateFilter();
                    localStorage.setItem('ehx-filters', $('#galFilter').val().replace(/^\s*[\r\n]/gm, '')); // Remove null entries because bad things happen if they're there
                    updateCSS();
                    addCSS();
                    displayAlert('Applied Current Settings', 5000, false);
                }

                $('.collapsible').click(e => { // Expand our custom filtering CSS boxes
                    if ($('.active').length && !$('.active').is(e.target)) { // If a menu is open and it isn't the one we're clicking, close it
                        $('.active').next().css('max-height', '');
                        $('.active').toggleClass('active');
                    }
                    e.target.classList.toggle('active');
                    var content = e.target.nextElementSibling;
                    if (content.style.maxHeight){
                        content.style.maxHeight = null;
                    } else {
                        content.style.maxHeight = '500px';
                    }
                });

                $('#ehx-import').click(e => {
                    $('.section-container section').addClass('inactive');
					$('.section-container section:nth-child(2)').removeClass('inactive');
					$('#importTitle').text('Import Galleries');
					$('#importGalleries').val('');
				});
                $('#ehxh-import').click(e => {
                    $('.section-container section').addClass('inactive');
					$('.section-container section:nth-child(2)').removeClass('inactive');
					$('#importTitle').text('Import Hidden Galleries');
					$('#importGalleries').val('');
				});
				$('#importConfirm').click(e => ehxImport($('#importTitle').text().search(/hidden/i) != -1 ? 'hidden' : 'galleries', $('#importGalleries').val().replace(/^\s*[\r\n]/gm, '')));
				$('.close').click(e => {
                    $('.section-container section').addClass('inactive');
					$('.section-container section:first-child').removeClass('inactive');
				});

				$('#exportCopy').click(e => {
					$('#exportGalleries').select();
					document.execCommand('copy');
					displayAlert('Copied Text To Clipboard', 5000);
				});

                $('#ehx-export').click(e => {
                    $('.section-container section').addClass('inactive');
					$('.section-container section:nth-child(3)').removeClass('inactive');
					$('#exportTitle').text('Exported Galleries');
                    ehxExport(len);
                });

                $('#ehxh-export').click(e => {
                    var objStore2 = db.transaction('hidden', 'readwrite').objectStore('hidden');
                    var openReq = objStore2.getAll();
                    openReq.onsuccess = e => {
                        $('.section-container section').addClass('inactive');
						$('.section-container section:nth-child(3)').removeClass('inactive');
						$('#exportTitle').text('Exported Hidden Galleries');
                        ehxExport(e.target.result);
                    }
                });

                $('#ehx-clear').click(e => {
                    if (!ehxClearConfirm) { // Make sure to double check before deleting
                        ehxClearConfirm = 1;
                        $('#ehx-clear').append(': Are you sure?');
                    } else {
                        var objStore2 = db.transaction('galleries', 'readwrite').objectStore('galleries');
                        var openReq = objStore2.clear();
                        openReq.onsuccess = e => {
                            displayAlert('Cleared all entries', 5000, false);
							$('#ehx-clear').text('Clear Data');
                            galleries = JSON.parse('{"data":{}}');
                            $('#gLength').text(Object.keys(galleries.data).length);
                            $('.ehx-visited').removeClass('ehx-visited');
                            addCSS();
                        }
                    }
                });

                $('#ehxh-clear').click(e => {
                    var objStore2 = db.transaction('hidden', 'readwrite').objectStore('hidden');
                    var openReq = objStore2.getAll();
                    openReq.onsuccess = e => {
                        if (!ehxhClearConfirm) { // Make sure to double check before deleting
                            ehxhClearConfirm = 1;
                            $('#ehxh-clear').append(': Are you sure?');
                        } else {
                            var objStore3 = db.transaction('hidden', 'readwrite').objectStore('hidden');
                            var openReq = objStore3.clear();
                            openReq.onsuccess = e => {
                                displayAlert('Cleared all entries', 5000);
								$('#ehxh-clear').text('Clear Data');
                                hidden = JSON.parse('{"data":{}}');
                                $('#hLength').text(Object.keys(hidden.data).length);
                                $('.ehx-hidden').removeClass('ehx-hidden');
                                addCSS();
                            }
                        }
                    }
                });

                // Fancy function to make sure there's not more than one top menu item open
                $('.menu').click(e => {
                    if ($('.show').length) {
                        if ($('.show').prev().is(e.target)) { $(e.target).next().toggleClass('show'); }
                        else {
                            $('.show').removeClass('show');
                            $(e.target).next().toggleClass('show');
                        }
                    } else {
                        $(e.target).next().toggleClass('show');
                    }
                });
            }
        }
    }
    // The giant CSS block
    $(`<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/symbola" type="text/css"/>
    <style data-jqstyle='ehxVisited'>
#ehx-controls {
	padding: 3px 1px;
	text-align: center;
	display: block;
}
#ehx-settings, #ehx-show, #ehxh-show {
    cursor: pointer;
    text-decoration: underline;
}
#hideCount > span { border-bottom: 1px dotted currentColor; }
#importGalleries, #exportGalleries { min-height: 420px; }
#settings-close {
	text-decoration: none;
	position: absolute;
	top: -2px;
	right: 1.5px;
	font-size: 1.4em;
}
#visControls { top: -6px; }
div > .imgHide {
	cursor: pointer !important;
	position: absolute;
	bottom: 3px;
	left: 2px;
}
ehx { font-family:` + $('body').css('font-family') + `, arial, symbola, SymbolaRegular;  }
input[type="checkbox"] {
	-webkit-appearance: none;
	border: 1px solid #F1F1F1BB;
	padding: 5px;
	top: 4px;
	background-color: transparent;
}
input[type="checkbox"]:checked:after {
	content: '\\2714';
	position: absolute;
	top: -8px;
	left: 1px;
	font-size: 1.1em;
}
input[type="checkbox"]:focus { outline: none; }
input[type="checkbox"]:hover { cursor: pointer; }
nav > div {
	text-align: right;
	margin-right: 30px;
}
nav > div button {
	border: none !important;
	padding: 1px 20px 1px 10px !important;
	position: relative;
}
td.hideContainer .imgHide {
	cursor: pointer !important;
	vertical-align: middle;
}
.active:after { content: '\\2212' !important; }
.applyContainer {
	padding-top: 5px;
	padding-right: 8px;
	border-top: 1px solid threedface;
	width: 100%;
	position: relative;
	left: -4px;
}
.branch {
	position: absolute;
	left: 10px;
	top: 1px;
	margin-left: -3px;
}
.collapsible {
	cursor: pointer;
	width: 100%;
	border: 0;
	outline: none;
	text-align: left;
	font-size: 1.25em;
	background-color: rgba(0, 0, 0, 0);
	color: inherit;
	font-weight: bold;
	padding:5px 3px;
	position: relative;
}
.collapsible:after {
	content: '\\002B';
	font-weight:bold;
	float:right;
	margin-right:5px;
}
.collapsible:before {
	content: '';
	position: absolute;
	padding: 4px;
	border-bottom: 1px solid threedface;
	border-left: 1px solid threedface;
	top: 5px;
	left: -11px;
}
.collapsible:hover { background-color: rgba(255, 255, 255, 0.1); }
.content {
	max-height: 0;
	overflow: hidden;
	transition: all .2s ease-in-out;
	border-bottom: 1px solid threedface;
}
.content button {
	margin-top: 3px;
	margin-right: 10px;
}
.control {
	position: relative;
	float: right;
	right: -5px;
}
.dropdown {
	display: none;
	position: absolute;
	z-index: 999;
	min-width: 150px;
	padding: 2px;
	border-radius: 1px;
	box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
	right: 0px;
	border: 1px solid threedface;
	background: ` + $('.ido').css('background') + `;
	background: ` + $('.ido').css('backgroundColor') + `;
}
.dropdown a {
	display: block;
	text-decoration: none;
	padding-right:2px;
}
.dropdown a:hover { background: rgba(255,255,255,0.2); }
.ehx-compact {
	border-style: solid;
	border-width: 1px 0;
	text-align: center;
}
.ehx-extended {
	width: 120px;
	position: absolute;
	left: 3px;
	top: 172px;
	text-align: center;
	font-size: 8pt;
	line-height: 1.5;
}
.ehx-extended-favs {
	padding: 3px 1px;
	display: block;
	line-height: 1.5;
}
.ehx-minimal {
    border-left: 1px solid #6f6f6f4d;
    text-align: center;
}
.ehx-thumbnail {
	display: block;
	text-align: center;
	margin: 3px 0 5px;
	line-height: 12px;
}
.ehx-visited .gl3e { min-height: 206px; }
.ehx-visited .gl4e { min-height: 264px !important; }
.gl2c { width: 115px; }
.gltc td.hideContainer {
	border-bottom: 1px solid #6f6f6f4d;
	border-top: 1px solid #6f6f6f4d;

}
.glte .imgHide {
    cursor: pointer !important;
    padding: 4px 2px 0px 1px;
    border-top: 1px solid #6f6f6f;
}
.glte tr:nth-child(1) .imgHide {
    border-top: none;
}
.inactive { display: none; }
.mencon {
	display: inline-block;
	position: relative;
}
.menu:after {
	content: '\\2335';
	position: absolute;
	right: 5px;
	bottom: 1px;
}
.notice {
	position: fixed;
	width: 500px;
	height: 20px;
	top: 20px;
	left: 50%;
	transform: translate(-50%, 0px);
	background: rgba(70, 130, 180, 0.8);
	font-size: 1.2em;
	font-weight: bold;
	color: white;
	padding-top: 5px;
	border-radius: 8px;
	z-index: 999;
}
.notice.alert {
	background: rgba(165, 42, 42, 0.8);
}
.overlay {
	background: rgba(0,0,0,0.5);
	display: -webkit-flex;
	display: flex;
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	z-index: 100;
	font-size: 9pt;
}
.overlay button:not(.collapsible) {
	background-color: transparent;
	border-radius: 6px;
	border: 1px solid threedface;
	cursor: pointer;
	font-weight: bold;
	padding: 3px 20px;
	text-decoration: none;
	color: inherit;
	margin-left: 5px;
}
.overlay button:not(.collapsible):hover { background-color: rgba(255, 255, 255, 0.1); }
.overlay button:not(.collapsible):focus { outline: none; }
.sControls { top: -3px; }
.section-container {
	text-align: left;
	overflow: auto;
	margin-bottom: 5px;
	padding-bottom: 5px;
}
.section-container textarea:disabled, .section-container input:disabled, .section-container select:disabled {
	opacity: 0.6;
	-webkit-opacity: 0.6;
}
.section-container input[type="number"] {
	border: 1px solid #8d8d8d;
	margin-left:0px;
	text-align: center;
	width: 50px;
}
.section-container select { margin-left: 0px; }
.section-container code {
	color: #000;
	background-color: #FFF;
}
.section-container fieldset { padding-right: 18px; }
.settings {
	background: ` + $('.ido').css('background') + `;
	background: ` + $('.ido').css('backgroundColor') + `;
	box-sizing: border-box;
	height: 555px;
	max-height: 100%;
	width: 900px;
	max-width: 100%;
	margin: auto;
	padding: 5px;
	display: -webkit-flex;
	display: flex;
	-webkit-flex-direction: column;
	flex-direction: column;
	box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.5);
}
.settings nav {
	text-align: right;
	padding-bottom: 5px;
	font-weight: bold;
	position: relative;
}
.settings legend {
	font-size: 10pt;
	font-weight: bold;
}
.settings label {
	font-weight: bold;
	text-decoration: underline;
	cursor: pointer;
}
.settings h3 {
	margin: 3px;
	position: relative;
}
.settings input { vertical-align: -1px; }
.settings textarea {
	width: 100%;
	height: 50px;
	resize: vertical;
}
.show { display:block }
.suboptions { position: relative; }
.suboptions > div {
	position: relative;
	padding-left: 1.4em;
}
.suboptions2 {
	margin-left: 4px;
	padding-left: 10px;
	margin-right: -9px;
}
</style>`).appendTo('head');
    $(`<style id="setStyle" data-jqstyle="ehxVisited">`
+ (setStore.titleShow ? `div.gl4t:hover {
    overflow: visible;
    z-index: 3;
    position: relative;
    background: rgba(0, 0, 0, 0.5);
    height: auto;
    max-height: none;
}
div.gl1t {
    min-height: 429px;
    position: relative;
}
div.gl3t {
    position: absolute;
    left: 50%;
    margin-left: -125px;
    top: 38px;
}` : ``) + `
table.itg > tbody > tr.ehx-visited, .gl1t.ehx-visited { ` + cssA.visible + ` }
table.itg > tbody > tr.ehx-visited.ehx-hidden, .gl1t.ehx-visited.ehx-hidden { ` + cssA.hidden + ` }
.ehx-hidden { ` + cssD + cssA.hidden + ` }
.ehx-hidden[data-jqstyle*="f"] {` + cssA.filter + `}
.ehx-hidden[data-jqstyle*="p"] {` + cssA.page + `}
.ehx-hidden[data-jqstyle*="r"] {` + cssA.rating + `}
</style>`).appendTo('head');
})();