EhxVisited

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

Tính đến 05-01-2020. Xem phiên bản mới nhất.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

Bạn sẽ cần cài đặt một tiện ích mở rộng như Tampermonkey hoặc Violentmonkey để cài đặt kịch bản này.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         EhxVisited
// @namespace    https://sleazyfork.org/en/users/285675-hauffen
// @version      2.53.64.3
// @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 = 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};
    var filters = '';
    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;", "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;"};
    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==';
    /*════════════════════════════*/

    try { // Converting the old format to the new format
        filters = localStorage.getItem('ehx-filters') ? JSON.parse(localStorage.getItem('ehx-filters')) : {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999"}; // JSON.parse isn't a fan of unique characters
    } catch (e) {
        filters = localStorage.getItem('ehx-filters') ? {"title": localStorage.getItem('ehx-filters'), "uploader": "#xcaliber9999"} : {"title": "#\\[Erocolor\\]", "uploader": "#xcaliber9999"};
    }

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

    var spl = document.URL.split('/');
    var d1 = spl[3];
    var galleries, hidden;
    var ehxClearConfirm = 0, ehxhClearConfirm = 0, reload = 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) {
        $('<div class="alertContainer"></div>').appendTo('body');
        populate();
        populateFilter();
    }

    /**
     * Populate the custom filter array with user input converted into regular expressions
	 */
    function populateFilter() {
        filterArr = []; // Start fresh for when we call this again
        uploaderArr = [];
        if (filters.title != '') { // There's probably some way that I could condense this
            var tempArr = filters.title.split('\n');
            for (var i = 0; i < tempArr.length; i++) {
                if (tempArr[i].startsWith('#')) continue;
                var regex;
                try {
                    regex = new RegExp(tempArr[i], 'i');
                } catch(e) {
                    displayAlert('Invalid Regex On Line ' + (i + 1), 5000, true);
                    continue;
                }
                filterArr.push(regex);
            }
        }
        if (filters.uploader != '') {
            var tempArr2 = filters.uploader.split('\n');
            for (i = 0; i < tempArr2.length; i++) {
                if (tempArr2[i].startsWith('#')) continue;
                try {
                    regex = new RegExp(tempArr2[i], 'i');
                } catch (e) {
                    displayAlert('Invalid Regex On Line ' + (i + 1), 5000, true);
                    continue;
                }
                uploaderArr.push(regex);
            }
        }
    }

    /**
     * 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, uAmount, hCount;
        hAmount = vAmount = fAmount = pAmount = rAmount = uAmount = 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')) { // 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 ($(list[i]).attr('data-jqstyle').match('u')) uAmount++;
            }
            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 + ' | Uploader: ' + uAmount);
    }

    /**
     * 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) {
            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')) removeStyle($(el), 'f');

        var upload = ''; // Uploader name is stored in one of two elements based on view modes, not displayed in Thumbnail view
        if ($('.gl1e').length) upload = $(el).find('.gl3e > div:nth-child(4)').text();
        else if ($('.gl1c').length || $('.gl1m').length) upload = $(el).find('.glhide > div a').text();

        if (uploaderArr.length) {
            if (uploaderArr.some(rx => rx.test(upload))) {
                if (!$(el).hasClass('ehx-hidden')) $(el).addClass('ehx-hidden');
                if (!($(el).attr('data-jqstyle') || '').match('u')) addStyle($(el), 'u');
            } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'u');
        } else if ($(el).hasClass('ehx-hidden')) removeStyle($(el), 'u');

        // 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) {
            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 $(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);
            }
        }
    }

    /**
	 * 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'});
                            }
                        } else {
                            $('.ehx-hidden').css({display: ''});
                            $('.ehx-visited.ehx-hidden').css({display: ''});
                        }
                        $('#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();
                }
            }
        }
    }

    /**
	 * 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) { // Visited
                        if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields if we haven't already
                            $(list[i]).addClass('ehx-visited');
                            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 { // Otherwise, just update the timestamp
                            if (onFavs) {
                                $(list[i]).find('ehx-extended-favs').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
                            } else {
                                $(list[i]).find('ehx-extended').text('\uD83D\uDC41 ' + timeDifference(galleryId) +'<br>' + buildTime(galleryId, false));
                            }
                        }

                        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));
					} 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]).find('.imgHide').length) {
						$('<img class="imgHide" src="' + img_hide + '" title="Show/Hide Gallery">').appendTo($(list[i]).find('.gl2e > div')).click(e => { // Maybe closest('tr')
							toggleElement($(e.currentTarget).parents().eq(2).find('a').attr('href'), $(e.currentTarget).parents().eq(2));
						});
					}
					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) { // Visited
                        if (!$(list[i]).hasClass('ehx-visited')) { // Append our fields
                            $(list[i]).addClass('ehx-visited');
                            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(2, 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(2, 10) + '</ehx></td>');
                            }
                        } else { // Otherwise update timestamp
                            $(list[i]).find('ehx-compact').html('<ehx>' + timeDifference(galleryId, true) + '<br>' + buildTime(galleryId, false).substring(11) + '<br>' + buildTime(galleryId, false).substring(2, 10) + '</ehx>');
                        }

                        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));
					} 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');
					}

					if (!$(list[i]).find('.imgHide').length) {
						$('<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')) { // Append fields
                            $(list[i]).addClass('ehx-visited');
                            if (setStore.minAdd) {
                                if (setStore.minShow) {
                                    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 { // Update our timestamps
                            if (setStore.minAdd) {
                                if (setStore.minShow) {
                                    $(list[i]).find('.ehx-minimal').html('<ehx title="' + buildTime(galleryId, false) +'">' + timeDifference(galleryId, true) + '</ehx>');
                                } else {
                                    $(list[i]).find('.ehx-minimal').html('<ehx>\uD83D\uDC41</ehx>');
                                    $(list[i]).find('.ehx-minimal').attr('title', 'EhxVisited: ' + buildTime(galleryId, true));
                                }
                            }
                        }

                        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));
					} 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');
					}

					if (!$(list[i]).find('.imgHide').length) {
						$('<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));
						});
					}
                    filterCheck($(list[i]));
				}
			} 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) { // Visited
                        if (!$(list[i]).hasClass('ehx-visited')) {
							$(list[i]).addClass('ehx-visited');
							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>');
                            else $(list[i]).find('.gl5t').after('<ehx class="ehx-thumbnail">\uD83D\uDC41 ' + buildTime(galleryId, true) + '</ehx>');
                        } else {
                            $(list[i]).find('.ehx-thumbnail').text('\uD83D\uDC41 ' + buildTime(galleryId, true));
                        }

                        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));
					} 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('.imgHide').length) {
						$('<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).toString().padStart(2, '0') + '-' + d.getDate().toString().padStart(2, '0') + ' ' + 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('.alertContainer').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">
        table.itg > tbody > .ehx-visited, .ehx-visited { ` + cssA.visible + ` }
        table.itg > tbody > .ehx-visited.ehx-hidden, .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 + `}
        .ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
        </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 id="topNav">
							<span id="setNotice" style="float: left; margin-left: 8px; margin-top: 2px; font-weight: lighter; opacity: 0.5; -webkit-opacity: 0.5;">` + (reload ? `Applied Settings Will Take Effect On Reload` : ``) + `</span>
							<div>
								<div class="mencon">
									<button class="menu">Export</button>
									<div class="dropdown">
										<a id="ehx-export">Export Galleries</a>
										<a id="ehxh-export">Export Hidden Galleries</a>
									</div>
								</div>
								<div class="mencon">
									<button class="menu">Import</button>
									<div class="dropdown">
										<a id="ehx-import">Import Galleries</a>
										<a id="ehxh-import">Import Hidden Galleries</a>
									</div>
								</div>
								<a id="settings-close">&#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">Title 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">Uploader Filtered Galleries</button>
                                        <div class="content">
                                            <textarea id="ufiltered" class="field" spellcheck="false">` + cssA.uploader + `</textarea>
                                            <div class="control sControls">
                                                <button id="resU">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>\\[Sample\\]</code> will filter out every gallery with <code>[Sample]</code>, case-insensitive, in the title.</li>
										<li>Lines starting with <code>#</code> will be ignored.</li>
									</ul>
									<textarea id="galFilter">` + filters.title + `</textarea>
                                    <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>
                                    <textarea id="upFilter">` + filters.uploader + `</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;'); });
                $('#resU').click(e => { $('#ufiltered').val('box-shadow: inset 0 0 0 500px rgba(222, 184, 135, .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) || $('#titleShow').is(e.target) || $('#cssTT').is(e.target)) {
                        $('#setNotice').text('Applied Settings Will Take Effect On Reload');
                        reload = 1;
                    }
                });
                $('#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": setStore.titleShow
					}
                    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(),
                        "uploader": $('#ufiltered').val()
                    }
                    cssA = tempCss;
                    localStorage.setItem('ehx-css', JSON.stringify(tempCss));
                    var tempFilt = { // Remove null entries because bad things happen if they're there
                        "title": $('#galFilter').val().replace(/^\s*[\r\n]/gm, ''),
                        "uploader": $('#upFilter').val().replace(/^\s*[\r\n]/gm, '')
                    }
                    filters = tempFilt;
                    populateFilter();
                    localStorage.setItem('ehx-filters', JSON.stringify(tempFilt));
                    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: 0px;
	right: 5px;
	font-size: 1.4em;
}
@-moz-document url-prefix() {
    #settings-close {
        top: -2px;
        -webkit-text-stroke: 1px;
    }
}
#ehx-export, #ehxh-export, #ehx-import, #ehxh-import, #settings-close { cursor: pointer; }
#topNav {
    border-bottom: 1px solid threedface;
    left: -4px;
    min-width: 898px;
}
#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; }
.alertContainer {
    position: fixed;
    width: 100%;
    z-index: 200;
    top: 0px;
}
.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 ehx { white-space: nowrap; }
.gltc td.hideContainer {
	border-bottom: 1px solid #6f6f6f4d;
	border-top: 1px solid #6f6f6f4d;

}
.glte .imgHide {
    cursor: pointer !important;
    padding: 4px 2px 0px 1px;
    top: 3px;
    right: 5px;
    left: initial;
    bottom: initial;
}`
 + (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: 455px;
    position: relative;
}
div.gl3t {
    position: absolute;
    left: 50%;
    margin-left: -125px;
    top: 38px;
}
div.gl4t {
    font-weight: bold;
    text-shadow: -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000;
}
div.gl5t {
    position: absolute;
    bottom: 25px;
    left: 50%;
    margin-left: -100px;
}` : ``) + `
.inactive { display: none; }
.mencon {
	display: inline-block;
	position: relative;
}
.menu:after {
	content: '\\2335';
	position: absolute;
	right: 5px;
	bottom: 1px;
}
.notice {
	position: relative;
	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);
}
.notice:not(:first-child) {
    top: 30px;
}
.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; }
@-moz-document url-prefix() {
    .sControls {
        top: initial;
        margin-top: 3px;
        margin-bottom: 5px;
    }
}
.section-container {
	text-align: left;
	overflow: auto;
	margin: 5px 0px 5px 0px;
	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">
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 + `}
.ehx-hidden[data-jqstyle*="u"] {` + cssA.uploader + `}
</style>`).appendTo('head');
})();