NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @namespace https://openuserjs.org/users/TedCart // @name BeastSaberFilterHelper // @copyright 2020, TedCart (https://openuserjs.org/users/TedCart) // @version 1.0 // @license MIT // @description Help filter by difficulty or author while browsing songs on bsaber.com // @author You // @match https://bsaber.com/newest/* // @match https://bsaber.com/curator-recommended/* // @grant none // @run-at document-end // ==/UserScript== // ==OpenUserJS== // @author TedCart // ==/OpenUserJS== // link to source: // https://openuserjs.org/scripts/TedCart/BeastSaberFilterHelper/source (function () { 'use strict'; // import { mainBack, mainFront, specialBack, specialFront } from "./colors.js" const addCustomStyleTag = () => { const newStyle = document.createElement("style"); newStyle.setAttribute('type', 'text/css'); // classToColor background, color, padding, margin newStyle.innerHTML = ` /* DO NOT EDIT BELOW THIS POINT */ /* BEGIN CUSTOM CSS */ body { margin-top: 0; } .post-content > .row.row-fluid { display: flex; flex-direction: column; align-items: center; } #restore-mapper-button-list { max-width: 200px; } #restore-mapper-button-list button { display: block; margin: 4px 0 4px 28px; } /* END CUSTOM CSS */ `; document.head.appendChild(newStyle); }; const selectors = { singlePost: '.mapper_id.vcard' , postContentSelector: 'table tr:nth-of-type(2) td:nth-of-type(2)' , parentNodeCount: 5 }; function elementIsHidden(el) { return (el.offsetParent === null) } function getCurrentElement (elementList) { if (!elementList) return const positionArray = []; elementList.forEach(e => positionArray.push(e.getBoundingClientRect().top)); let targetIndex = positionArray.findIndex(e => e > -10); if (targetIndex === -1) return return elementList[targetIndex] } // end of scrollToPreviousElement function screenIsScrolledToTop () { const body = document.querySelector('body'); return body ? (body.getBoundingClientRect().top > -10) // within 10 pixels of the top : undefined } function screenIsScrolledToBottom () { const html = document.querySelector('html'); const body = document.querySelector('body'); // html.clientHeight is the height of the visible window // body.clientHeight is the height of all elements combined return (html && body) ? ((body.clientHeight + body.getBoundingClientRect().top) < (html.clientHeight + 5)) : undefined } function elementScrollJump (ev) { if (document.activeElement.tagName === "INPUT") return if (document.activeElement.tagName === "TEXTAREA") return const upList = [ 38 // up arrow , 87 // w key ]; const downList = [ 40 // down arrow , 83 // s key ]; const clickList = [ 39 // right arrow , 68 // d key ]; const activeKeyCode = ev.keyCode; if (!activeKeyCode || [...upList, ...downList, ...clickList].indexOf(activeKeyCode) === -1 ) { return } const postList = []; const postElements = document.querySelectorAll(selectors.singlePost); postElements.forEach(e => {if (!elementIsHidden(e)) {postList.push(e);}}); if (clickList.indexOf(activeKeyCode) !== -1 && selectors.linkSelector) { const targetEl = getCurrentElement(postList); let linkElement = targetEl.querySelector(selectors.linkSelector).parentNode; if (!linkElement) return linkElement.setAttribute('target', '_blank'); linkElement.click(); return } ev.preventDefault(); if (upList.indexOf(activeKeyCode) !== -1) { // console.log("Scrolling to previous element...") scrollToPreviousElement(postList); } else if (downList.indexOf(activeKeyCode) !== -1) { // console.log("Scrolling to next element...") scrollToNextElement(postList); } } // end of elementScrollJump function scrollToPreviousElement (elementList) { if (!elementList) return const positionArray = []; let targetIndex = elementList.length - 1; // if you're at the very top (within 10 pixels), go the very bottom if (screenIsScrolledToTop()) { window.scrollTo(0,document.body.scrollHeight); } else if (screenIsScrolledToBottom()) { // if you're at the very bottom (within 5 pixels), go to last element (or the very top) elementList[elementList.length - 1] ? elementList[elementList.length - 1].scrollIntoView() : window.scrollTo(0,0); } else { elementList.forEach(e => positionArray.push(e.getBoundingClientRect().top)); targetIndex = positionArray.findIndex(e => e > -10); if (targetIndex === 0) { window.scrollTo(0,0); } else if (targetIndex >= 1) { elementList[targetIndex - 1].scrollIntoView(); } } // end else return elementList[targetIndex] } // end of scrollToPreviousElement function scrollToNextElement (elementList) { if (!elementList) return const positionArray = []; let targetIndex = 0; // if you're at the very bottom (within 5 pixels), go the very top if (screenIsScrolledToBottom()) { window.scrollTo(0,0); } else if (screenIsScrolledToTop()) { // if you're at the very top (within 10 pixels), go the first element (or the very bottom) elementList[0] ? elementList[0].scrollIntoView() : window.scrollTo(0,document.body.scrollHeight); } else { elementList.forEach(e => positionArray.push(e.getBoundingClientRect().top)); targetIndex = positionArray.findIndex(e => e > 10); if (targetIndex === -1) { window.scrollTo(0,document.body.scrollHeight); } else { elementList[targetIndex].scrollIntoView(); } } // end else return elementList[targetIndex] } // end of scrollToNextElement // import { addCustomModalStyleTag } from "./_styles.js" const modalBlockId = "draggable-modal-block"; const collapsingArrowSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" width="17px" height="17px"> <g fill="none" fill-rule="evenodd"> <path d="M3.29175 4.793c-.389.392-.389 1.027 0 1.419l2.939 2.965c.218.215.5.322.779.322s.556-.107.769-.322l2.93-2.955c.388-.392.388-1.027 0-1.419-.389-.392-1.018-.392-1.406 0l-2.298 2.317-2.307-2.327c-.194-.195-.449-.293-.703-.293-.255 0-.51.098-.703.293z" fill="#FFFFFF"> </path> </g> </svg>`; function getMainModalElement () { return document.querySelector(`#${modalBlockId}`) } const maxListHeight = Math.max( Math.floor(window.innerHeight / 2) , 200 ); function createModalBlock (startPositionOptions) { addCustomModalStyleTag(startPositionOptions); const modalBlock = document.createElement('div'); modalBlock.setAttribute('id', modalBlockId); if (startPositionOptions.top) { modalBlock.style.top = `${startPositionOptions.top}`; } if (startPositionOptions.left) { modalBlock.style.left = `${startPositionOptions.left}`; } // modalBlock.setAttribute('title', "Click and hold to drag...") document.querySelector('body').prepend(modalBlock); modalBlock.onmousedown = moveModalContainer; modalBlock.ondragstart = () => false; // This function came from https://javascript.info/mouse-drag-and-drop function moveModalContainer (event) { if (event.target.tagName === "INPUT") return if (event.target.tagName === "BUTTON") return if (event.target.tagName === "SPAN") return if (event.target.tagName === "SECTION") return if (event.target.tagName === "LABEL") return if (event.target.tagName === "SVG") return if (event.target.tagName === "G") return if (event.target.tagName === "PATH") return // if (event.target.tagName === "LI") return // const modalBlock = document.querySelector(`#${modalBlockId}`) // if (!modalBlock) return const top = document.querySelector('html').scrollTop; let shiftX = event.clientX - modalBlock.getBoundingClientRect().left; let shiftY = event.clientY - modalBlock.getBoundingClientRect().top; modalBlock.style.position = 'fixed'; modalBlock.style.zIndex = 1000; document.body.append(modalBlock); moveAt(event.pageX, event.pageY); // moves the modalBlock at (pageX, pageY) coordinates // taking initial shifts into account function moveAt(pageX, pageY) { modalBlock.style.left = pageX - shiftX + 'px'; modalBlock.style.top = (pageY - top) - shiftY + 'px'; } function onMouseMove(event) { moveAt(event.pageX, event.pageY); } // move the modalBlock on mousemove document.addEventListener('mousemove', onMouseMove); // drop the modalBlock, remove unneeded handlers modalBlock.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); modalBlock.onmouseup = null; }; }; // end moveModalContainer // return modalBlock } function createCollapsibleSectionContainer (sectionTitle, idPrefix) { const el = getMainModalElement(); if (!el) return const firstAttempt = el.querySelector(`#${idPrefix}-filter-container`); if (firstAttempt) return firstAttempt function toggleSectionVisibility (ev) { const clickableDiv = ev.target.closest(`#${idPrefix}-filter-container > .section-header`); if (!clickableDiv) return; ev.preventDefault(); const containerDiv = document.querySelector(`#${idPrefix}-filter-container`); const isOpen = /open/.test(containerDiv.className); if (isOpen) { containerDiv.classList.remove('open'); localStorage.setItem(`${idPrefix}IsHidden`, 'true'); } else { containerDiv.classList.add('open'); localStorage.setItem(`${idPrefix}IsHidden`, 'false'); } } // end toggleSectionVisibility // Putting this click listener on the document is (ironically) better for performance // https://gomakethings.com/detecting-click-events-on-svgs-with-vanilla-js-event-delegation/ document.addEventListener('click', toggleSectionVisibility); const sectionContainerDiv = document.createElement('section'); sectionContainerDiv.setAttribute('id',`${idPrefix}-filter-container`); if (!JSON.parse(localStorage.getItem(`${idPrefix}IsHidden`) || 'false')) { sectionContainerDiv.setAttribute('class','open'); } const sectionHeaderDiv = document.createElement('section'); sectionHeaderDiv.setAttribute('class','section-header'); const toggleArrowDiv = document.createElement('section'); toggleArrowDiv.setAttribute('class','toggle-arrow'); toggleArrowDiv.innerHTML = collapsingArrowSvg; sectionHeaderDiv.append(toggleArrowDiv); const sectionHeaderText = document.createElement('span'); sectionHeaderText.setAttribute('class','modal-section-header'); sectionHeaderText.innerText = sectionTitle; sectionHeaderDiv.append(sectionHeaderText); sectionContainerDiv.append(sectionHeaderDiv); el.append(sectionContainerDiv); return sectionContainerDiv } // end createCollapsibleSectionContainer function addCustomModalStyleTag (startPositionOptions={}) { const newStyle = document.createElement("style"); newStyle.setAttribute('type', 'text/css'); // classToColor background, color, padding, margin if ( !startPositionOptions.top && !startPositionOptions.left) { startPositionOptions.top = "40px"; startPositionOptions.left = "10px"; } newStyle.innerHTML = ` svg { pointer-events: none; } .modal-button { text-align: center; margin:auto; display: block; min-width: 7em; border: 1px solid #777; padding: 3px 5px; border-radius: 5px; } .modal-section-header { display: inline-block; cursor: pointer; font-size: 1.2em; user-select: none; } .hide-button { margin-bottom:5px; /* background: #DDD; */ /* color:#222; */ border: 1px solid #777; border-radius: 2px; padding: 3px; } .live-button { margin-bottom:5px; font-weight: 800; font-size: 107%; border: 1px solid #777; border-radius: 3px; padding: 3px; color: #EEE; background: #2762a6; } .modal-input-list-button { background: #000; color: #DDD; } #draggable-modal-block { position: fixed; ${startPositionOptions.top ? "top: " + startPositionOptions.top + ";" : ''} ${startPositionOptions.left ? "left: " + startPositionOptions.left + ";" : ''} padding: 15px 5px; min-height: 20px; min-width: 20px; background: #333333BB; color: #DDD; z-index: 1000; border: solid transparent 1px; border-radius: 8px; transition: 500ms; opacity: .2; } #draggable-modal-block:hover { background: #333333BB; transition: 0ms; opacity: 1; } .deactivated-filters#draggable-modal-block button, .deactivated-filters#draggable-modal-block section { display: none; } .deactivated-filters#draggable-modal-block button:nth-child(1) { display: block; } .input-modal-checkbox { display:inline-block; margin-left: 25px; } .modal-input-list label { display: inline-block; margin: 0 0 0 8px; font-size: 1em; transition: 250ms; user-select: none; } .modal-input-list label:hover, .modal-input-list li:hover label { color: #FFF; transition: 50ms; } .modal-input-list li { margin: 0; } section.section-header { display: flex; } .toggle-arrow { display: flex; justify-content: space-around; align-items: center; height: 25px; width: 25px; border-radius: 2px; margin: 0; transition: 300 ms; cursor: pointer; /* transform: rotate(-90deg); */ opacity: .8; } .toggle-arrow:hover { opacity: 1; background: black; } .toggle-arrow svg { transform: rotate(-90deg); transition: 300ms; } .open .toggle-arrow svg { transform: rotate(0deg); } .modal-input-list { opacity: 0; display: none; visibility: hidden; transition: visibility 0s lineaer 0.1s, opacity 0.3s ease; padding: 0; margin: 0; max-height: ${maxListHeight}px; overflow-y: auto; } .open .modal-input-list { display: block; visibility: visible; opacity: 1; transition-delay: 0s; } .modal-input-list li { display: block; } `; document.head.appendChild(newStyle); } function createCheckboxWithLabel (options) { /* options example: { id: "modal-checkbox-example-id" , label: "Example Checkbox" , checked: true } */ options = options || {}; const modalBlock = getMainModalElement(); if (!modalBlock) return [] let newCheckbox; if (options && options.id) newCheckbox = modalBlock.querySelector(`#${options.id}`); if (newCheckbox) { console.log("not creating duplicate input", options.id); return [] // don't create duplicates } newCheckbox = document.createElement('input'); newCheckbox.setAttribute('type',`checkbox`); newCheckbox.setAttribute('class',`input-modal-checkbox`); newCheckbox.oninput = function(e) { this.setAttribute('value', newCheckbox.checked); this.blur(); }; for (const key in options) { if (key === 'label') continue if (key === 'checked') { newCheckbox.checked = !!options[key]; } else { newCheckbox.setAttribute(key, options[key]); } } // end for loop const newLabel = document.createElement('label'); newLabel.innerText = options.label; // newLabel.onclick = function () { newCheckbox.checked = !newCheckbox.checked } newLabel.onclick = function () { newCheckbox.click(); }; return [newCheckbox, newLabel] } function createNewCheckboxListItem (el, options) { const newListItem = document.createElement('li'); const newCheckboxElements = createCheckboxWithLabel(options); if (newCheckboxElements.length > 0) { newCheckboxElements.forEach(el => newListItem.append(el)); el.append(newListItem); } } function createSpan (options) { /* options example: { id: "modal-checkbox-example-id" , label: "Example Checkbox" , checked: true } */ options = options || {}; const modalBlock = getMainModalElement(); if (!modalBlock) return let newSpan; if (options && options.id) newSpan = modalBlock.querySelector(`#${options.id}`); if (newSpan) { console.log("not creating duplicate span", options.id); return // don't create duplicates } newSpan = document.createElement('span'); for (const key in options) { newSpan.setAttribute(key, options[key]); } // end for loop return newSpan } function createNewCheckboxListItemWithCount (el, countOptions, checkboxOptions) { const newListItem = document.createElement('li'); const newCheckboxElements = createCheckboxWithLabel(checkboxOptions); if (newCheckboxElements.length > 0) { const countElement = createSpan(countOptions); if (countElement) newCheckboxElements.unshift(countElement); newCheckboxElements.forEach(el => newListItem.append(el)); el.append(newListItem); } } // import * as foo from './src/foo.js' const activeIntervals = {}; const intervalCounts = {}; // console.log('Doing all the stuff!') const noDisplayString = "display:none;"; const hideButtonAttributes = { specialId: "hide-posts-special-button" , hideLabel: "Deactivate Filters" , showLabel: "Activate Filters" }; // Options: (provide checkbox for each) // - Hide duplicate songs by same mapper // - Hide songs by any mapper you choose // - Hide songs without selected difficulty // - Hide songs with less than x upvotes (user input) const elementSelector = { mapperName: selectors.singlePost , mapperParentNodeCount: selectors.parentNodeCount , postContent: '.post-content' , restoreContainerId: 'restore-mappers-container' , difficultyContainerId: 'difficulty-selector-container' }; const diffSelector = { easy: 'a.post-difficulty[href="/songs/?difficulty=easy"]' , normal: 'a.post-difficulty[href="/songs/?difficulty=normal"]' , hard: 'a.post-difficulty[href="/songs/?difficulty=hard"]' , expert: 'a.post-difficulty[href="/songs/?difficulty=expert"]' , expertPlus: 'a.post-difficulty[href="/songs/?difficulty=expert-plus"]' }; const hideMapperButtonAttributes = { specialId: "hide-mapper-button" , label: "Hide<br>Mapper" }; addCustomStyleTag(); createModalBlock({top: '150px', left: '45px'}); createHideButton(); createDifficultyCheckboxes(diffSelector); createRestoreMapperButtons(getHiddenMappers()); const bod = document.querySelector('body'); bod.addEventListener("keydown", elementScrollJump); putMutationObserverOnMainElement(bod, hidePosts); // =========================================================================== // Begin support functions // Nothing invoked beneath this point // =========================================================================== function getHiddenMappers() { const rawJSON = localStorage.getItem('hiddenMappers') || '[]'; return JSON.parse(rawJSON) } function getRequiredDifficulties() { const rawJSON = localStorage.getItem('requiredDifficulties') || '[]'; return JSON.parse(rawJSON) } function hasRequiredDifficulty (el, key) { return !!el.querySelector(diffSelector[key]) } function getHideStatus() { const rawJSON = localStorage.getItem('isHidingPosts') || 'false'; return JSON.parse(rawJSON) } function updateRequiredDifficulties () { const inputList = document.querySelectorAll('.modal-input-list.difficulties .input-modal-checkbox'); if (!inputList) return const curRequiredDifficulties = []; for (let i = 0; i < inputList.length; i++) { const curInput = inputList[i]; if (curInput.checked) curRequiredDifficulties.push(curInput.id.replace(/^modal-checkbox-/, '')); } // end for loop localStorage.setItem('requiredDifficulties' , JSON.stringify(curRequiredDifficulties) ); return curRequiredDifficulties } // end updateRequiredDifficulties function hidePosts () { const postElements = document.querySelectorAll(selectors.singlePost); if (!postElements) return const hideBadPosts = getHideStatus(); console.log(`${hideBadPosts ? 'Hiding' : 'Revealing'} those posts...`); createHideButton(); updateRequiredDifficulties(); const counts = { hiddenMappers: {} , songList: [] }; getHiddenMappers().forEach(e => counts.hiddenMappers[e] = 0); postElements.forEach((el, curIndex) => { let targetEl = el; for (let i = 0; i < selectors.parentNodeCount; i++) { targetEl = targetEl.parentNode; } // end for loop counts.curIndex = curIndex; const isHidden = !isValidElement(targetEl, counts); if (isHidden && hideBadPosts) { targetEl.style = noDisplayString; } else { targetEl.style = ""; // (targetEl.style || '').replace("display:none;", "") } }); // end postElements forEach // updatePostCounts(counts.hiddenMappers) createHideMapperButtons(); createRestoreMapperButtons(getHiddenMappers(), counts.hiddenMappers); } // end hidePosts function function createHideFunction() { return () => { localStorage.setItem('isHidingPosts', JSON.stringify(!getHideStatus())); hidePosts(); } // end callback function } // end createHideFunction function isValidElement (el, counts) { const mapperNameEl = el.querySelector(selectors.singlePost); const curMapperName = mapperNameEl ? mapperNameEl.innerText.trim() : 'NO MAPPER NAME'; // return FALSE for duplicates const entryTitleEl = el.querySelector('.entry-title'); const curSongName = entryTitleEl ? entryTitleEl.innerText.trim() : 'NO SONG TITLE'; const curSongByMapper = `${curSongName} by mapper ${curMapperName}`; counts.songList.push(curSongByMapper); if (counts.songList.indexOf(curSongByMapper) !== counts.curIndex) { // console.log(`Hiding duplicate map: ${curSongByMapper}`) return false } // return FALSE for a hidden mapper (and adjust their count) if (getHiddenMappers().indexOf(curMapperName) !== -1) { counts.hiddenMappers[curMapperName]++; // console.log(`Hiding map by ${curMapperName}...`) return false } // return FALSE for missing required difficulty const requiredDifficulties = getRequiredDifficulties(); if (requiredDifficulties.length > 0 && requiredDifficulties.every(diff => !hasRequiredDifficulty(el, diff))) { // console.log(`Hiding "${curSongByMapper}"`) // console.log(`Hiding song with missing difficulties: ${curSongByMapper}`) return false } return true } // end of isValidElement function createHideButton () { const buttonContainerElement = getMainModalElement(); if (!buttonContainerElement) { console.log('well shit'); return } let hideButtonElement = buttonContainerElement.querySelector(`#${hideButtonAttributes.specialId}`); const buttonJustCreated = !hideButtonElement; if (!hideButtonElement) { console.log("creating hide button..."); hideButtonElement = document.createElement('button'); hideButtonElement.id = hideButtonAttributes.specialId; hideButtonElement.addEventListener('click', createHideFunction()); } hideButtonElement.innerText = getHideStatus() ? hideButtonAttributes.hideLabel : hideButtonAttributes.showLabel; hideButtonElement.className = getHideStatus() ? 'modal-button hide-button' : 'modal-button live-button'; if (buttonJustCreated) buttonContainerElement.prepend(hideButtonElement); } // end createHideButton function createDifficultyCheckboxes (difficultyList) { const difficultyContainerDiv = createCollapsibleSectionContainer("Required Difficulties", "difficulty"); const inputListContainer = document.createElement('ul'); inputListContainer.setAttribute('class','modal-input-list difficulties'); inputListContainer.onclick="event.stopPropagation()"; inputListContainer.onmousedown="event.stopPropagation()"; const requiredDifficulties = getRequiredDifficulties(); for (const key in difficultyList) { const checkboxOptions = { id: `modal-checkbox-${key}` , label: key , checked: !!(requiredDifficulties.indexOf(key) !== -1) }; createNewCheckboxListItem(inputListContainer, checkboxOptions); } // end for loop putMutationObserverOnInputList(inputListContainer, hidePosts); difficultyContainerDiv.append(inputListContainer); } // end createDifficultyCheckboxes function putMutationObserverOnInputList (el, callback) { let inputEl = el; if (!inputEl) return // configuration of the observer: const inputElConfig = { attributes: true , characterData: true , subtree: true }; // create an observer instance const mainElementObserver = new MutationObserver(callback); // end MutationObserver // pass in the target node, as well as the observer options mainElementObserver.observe(inputEl, inputElConfig); } // end putMutationObserverOnInputList function createHideMapperButtons () { const mapperElements = document.querySelectorAll(selectors.singlePost); mapperElements.forEach(el => { const buttonContainerDiv = el.parentNode; // If you already have the button, just stop if (buttonContainerDiv.querySelector(`#${hideMapperButtonAttributes.specialId}`)) return const mapperName = el.innerText.trim(); const newButton = document.createElement('button'); newButton.id = hideMapperButtonAttributes.specialId; newButton.innerHTML = hideMapperButtonAttributes.label; newButton.className = 'hide-button'; newButton.addEventListener('click', createHideMapperFunction(mapperName)); buttonContainerDiv.prepend(newButton); }); // end mapperElements forEach } // end createHideMapperButtons function createHideMapperFunction(mapperName) { return () => { const hiddenMappers = getHiddenMappers(); if (hiddenMappers.indexOf(mapperName) === -1) { hiddenMappers.push(mapperName); localStorage.setItem('hiddenMappers', JSON.stringify(hiddenMappers)); } // end if mapperName hidePosts(); } // end callback function } // end createHideMapperFunction function createRestoreMapperButtons (hiddenMappers, counts={}) { const restoreMapperContainerDiv = createCollapsibleSectionContainer("Restore Mappers", "restore-mapper"); let listContainerDiv = document.querySelector(`#restore-mapper-button-list`); if (!listContainerDiv) { listContainerDiv = document.createElement('section'); listContainerDiv.id = `restore-mapper-button-list`; listContainerDiv.className = 'modal-input-list'; restoreMapperContainerDiv.append(listContainerDiv); } // const noHiddenMappersMessage = "No mappers are hidden" // // const noHiddenMappersStyle = "margin-left: 25px; font-style: italic;" // if (hiddenMappers && hiddenMappers.length === 0) { // listContainerDiv.innerHTML = noHiddenMappersMessage // // listContainerDiv.style = noHiddenMappersStyle // } else { // listContainerDiv.innerHTML = listContainerDiv.innerHTML.replace(noHiddenMappersMessage, '') // // listContainerDiv.style = listContainerDiv.style.replace(noHiddenMappersStyle, '') // } hiddenMappers.forEach(mapperName => { const specialButtonId = `restore-mapper-${mapperName.replace(/[^0-9a-zA-Z]/g,'')}`; // don't make the same button twice if (listContainerDiv.querySelector(`#${specialButtonId}`)) return const newRestoreButton = document.createElement('button'); newRestoreButton.id = specialButtonId; // newRestoreButton.innerText = `${counts[mapperName] || 0} - ${mapperName}` newRestoreButton.innerText = mapperName; newRestoreButton.className = 'hide-button'; newRestoreButton.addEventListener('click', createRestoreFunction(mapperName, specialButtonId)); listContainerDiv.append(newRestoreButton); }); // end hiddenMappers forEach } // end createRestoreMapperButtons function createRestoreFunction(mapperName, specialButtonId) { return () => { const hiddenMappers = getHiddenMappers(); const mapperIndex = hiddenMappers.indexOf(mapperName); if (mapperIndex !== -1) { hiddenMappers.splice(mapperIndex, 1); localStorage.setItem('hiddenMappers', JSON.stringify(hiddenMappers)); } // end if mapperName const restoreMapperButton = document.querySelector(`#${specialButtonId}`); if (restoreMapperButton) restoreMapperButton.remove(); hidePosts(); } // end callback function } // end createRestoreFunction function startMOInterval (sectionName, callback, options={}) { activeIntervals[sectionName] = setInterval(() => { putObserverOnEl(sectionName, callback, options); }, 700); } // end startMOInterval function putObserverOnEl (key, callback, options) { intervalCounts[key] = intervalCounts[key] || 0; intervalCounts[key]++; if (intervalCounts[key] > 8) { console.log(`Did not find element for "${key}" after ${intervalCounts[key]} attempts`); clearInterval(activeIntervals[key]); activeIntervals[key] = undefined; return } let el = document.querySelector(elementSelector[key]); if (!el) return for (let i = 0; i < (elementSelector.mapperParentNodeCount + 1); i++) el = el.parentNode; if (!el) return callback(el); const elConfig = options.config || { childList: true }; const elObserver = options.observer || new MutationObserver((mutations) => { if (options.msg) console.log(options.msg); callback(el); }); // end MutationObserver elObserver.observe(el, elConfig); console.log(`Successfully put observer on "${key}"`); clearInterval(activeIntervals[key]); activeIntervals[key] = undefined; } // end function putObserverOnEl function putMutationObserverOnMainElement (el, callback) { startMOInterval("mapperName" , callback , { config: { childList: true // , subtree: true } // , msg: "processing ..." } ); } // end putMutationObserverOnMainElement }());