NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Habitica Decluttered Stable // @description Changes Habitica's stable layout to be cleaner, and hides missing creatures by default. // @author Daniel Causebrook // @copyright 2020, Daniel Causebrook // @license MIT // @version 1.2 // @include https://habitica.com/* // ==/UserScript== // Configure this script's behaviour here. const USER_CONFIG = { STABLE: { /** * Enabling this sets the stable to hide missing creatures by default! */ HIDE_MISSING: { ENABLED: true }, /** * Enabling this tries to put creature types on one row if you have few * enough of each. */ COLLAPSE_ROWS: { ENABLED: true }, /** * Enabling this will load all creatures automatically. * * It can also remove the "Show More" / "Show Less" buttons. * * Warning: this may cause performance issues (especially in Firefox). Test * it out by navigating to and from the stable tab a few times. */ EXPAND_ALL: { ENABLED: false, HIDE_BUTTONS: true, }, }, }; const CREATURE_ROW_SELECTOR = "div.stable > div.standard-page > div:not(:first-child):not(.drawer-container)"; function addStyle(css) { var headElems = document.getElementsByTagName('head'); if(headElems.length === 0) return; var style = document.createElement('style'); style.type = 'text/css'; style.innerHTML = css; headElems[0].appendChild(style); } // Add flexbox CSS styles to page. if(USER_CONFIG.STABLE.COLLAPSE_ROWS.ENABLED) { addStyle(` ` + CREATURE_ROW_SELECTOR + ` { display: flex; flex-flow: row wrap; } ` + CREATURE_ROW_SELECTOR + ` > h4 { width: 100%; } ` + CREATURE_ROW_SELECTOR + ` > div.btn-show-more { align-self: center; margin: 0 1.71em 12px 0; max-width: 100px; } `); } var clickedButtons = new Set(); var showMoreLabel = null; /** * Clicks all "Show More" buttons, and hides all buttons. * * If a button label is not recognised, no action is taken. Tracks processed * nodes in `clickedButtons`, and will skip any that have already been checked. */ function clickButtons(buttons) { for(const btn of buttons) { if(!clickedButtons.has(btn)) { var btnText = btn.innerHTML.trim().toLowerCase(); if(showMoreLabel === null) showMoreLabel = btnText; if(btnText === showMoreLabel) { btn.click(); if(USER_CONFIG.STABLE.EXPAND_ALL.HIDE_BUTTONS) { btn.style.display = "none"; } } else { if(USER_CONFIG.STABLE.EXPAND_ALL.HIDE_BUTTONS) { btn.style.display = "none"; } } clickedButtons.add(btn); } } } var processing = false; var hideMissingToggled = false; var inStable = false; /** * Checks the document for certain nodes in the stable and modifies them * if found. * * Will not run if called inside itself to avoid infinite recursion caused by * being called inside itself. */ var callback = function (nutationList, observer) { if(!processing) { processing = true; if(window.location.pathname.match(/^\/inventory\/stable/) !== null) { // We are on the stable page. inStable = true; // Switches the "Hide Missing" toggle to on. if(USER_CONFIG.STABLE.HIDE_MISSING.ENABLED) { var stableHideMissing = document.querySelector("div.stable > div.standard-sidebar > div.form > div.form-group:last-child input"); if(stableHideMissing !== null && !hideMissingToggled) { stableHideMissing.checked = true; stableHideMissing.dispatchEvent(new Event('change', {'bubbles': true})); hideMissingToggled = true; } } // Expands all creature sections. if(USER_CONFIG.STABLE.EXPAND_ALL.ENABLED) { var stableShowMoreButtons = document.querySelectorAll(CREATURE_ROW_SELECTOR + " > div.btn-show-more"); clickButtons(stableShowMoreButtons); } } else { if(inStable) { // We've just switched away from the stable page, reset state. hideMissingToggled = false; clickedButtons.clear(); inStable = false; } } processing = false; } } // Setup a MutationObserver to watch the document for the nodes we need. if(USER_CONFIG.STABLE.HIDE_MISSING.ENABLED || USER_CONFIG.STABLE.EXPAND_ALL.ENABLED) { var targetNode = document.querySelector('body'); var observerConfig = { childList: true, subtree: true, }; var observer = new MutationObserver(callback); observer.observe(targetNode, observerConfig); }