// ==UserScript== // @name Reduce Clutter // @description Revert updates that make the page more cluttered or less accessible // @homepage https://github.com/samliew/SO-mod-userscripts // @author Samuel Liew // @version 4.3.13 // // @match https://*.stackoverflow.com/* // @match https://*.serverfault.com/* // @match https://*.superuser.com/* // @match https://*.askubuntu.com/* // @match https://*.mathoverflow.net/* // @match https://*.stackapps.com/* // @match https://*.stackexchange.com/* // @match https://stackoverflowteams.com/* // // @exclude https://stackoverflow.com/admin/dashboard // @exclude https://api.stackexchange.com/* // @exclude https://data.stackexchange.com/* // @exclude https://contests.stackoverflow.com/* // @exclude https://winterbash*.stackexchange.com/* // @exclude *chat.* // @exclude *blog.* // @exclude */tour // // @require https://raw.githubusercontent.com/samliew/SO-mod-userscripts/master/lib/se-ajax-common.js // @require https://raw.githubusercontent.com/samliew/SO-mod-userscripts/master/lib/common.js // // @run-at document-start // ==/UserScript== /* globals StackExchange */ /// 'use strict'; // Show announcement bar if it does not contain these keywords (lowercase) const blacklistedAnnouncementWords = ['podcast', 'listen', 'tune', 'research', 'blog']; // Hide ads/clickbaity blog posts titles if they contain these keywords (lowercase) const blacklistedBlogWords = ['the loop', 'podcast', 'worst', 'bad', 'surprise', 'trick', 'terrible', 'will change', 'actually', 'team', 'try', 'free', 'easy', 'easier', 'e.p.', 'ep.']; // Append styles to header addStylesheet(` /* Fix all Q&A links having same colour even when visited */ body .s-link { color: var(--theme-link-color); } body .s-link:visited { color: var(--theme-link-color-visited); } /* Fix comment upvote and flag always showing https://meta.stackexchange.com/q/312794 */ ul.comments-list .comment-voting, ul.comments-list .comment-flagging { visibility: hidden; } ul.comments-list .comment:hover .comment-voting, ul.comments-list .comment:hover .comment-flagging, ul.comments-list .comment-up-on { visibility: visible; } .popup-flag-comment { visibility: visible; } /* Make comment edited icon same color as timestamp https://meta.stackoverflow.com/q/371313 */ .s-link, .iconPencilSm { color: #9199a1; } /* Revert change to permanent "edit tags" link https://meta.stackoverflow.com/q/374024 */ .post-taglist #edit-tags { opacity: 0.5; } .post-layout:hover .post-taglist #edit-tags { opacity: 1; } /* Remove Products menu in the top bar https://meta.stackoverflow.com/q/386393 */ .s-topbar--logo + ol, .s-topbar--logo + [role="presentation"], .s-topbar ol.s-navigation[role="presentation"], .js-top-bar ol.s-navigation[role="presentation"] { display: none; } /* Hide announcements bar before page load, then check text for blacklisted words after page load https://meta.stackoverflow.com/q/390709 */ #announcement-banner { display: none; } /* Hide newsletter sidebar ad to reclaim vertical space https://meta.stackoverflow.com/q/360450 */ #newsletter-ad { display: none; } /* Hide "new contributor" popover Hide "new contributor" displaying twice on a post (post author and when commenting) https://meta.stackoverflow.com/q/372877 */ .js-new-contributor-popover { display: none; } .comments .new-contributor-indicator { display: none; } /* Better "duplicates edited list" in question revisions page Works with "betterDuplicatesEditedList()" function below https://meta.stackoverflow.com/q/400817 */ .js-revisions .js-revision-comment.somu-duplicates-edited { display: block; padding-top: 5px; } .js-revisions .js-revision-comment.somu-duplicates-edited ul { margin-bottom: 0; } .js-revisions .js-revision-comment.somu-duplicates-edited li { padding-top: 0; } .js-revisions .originals-of-duplicate li { position: relative; cursor: initial; list-style-type: none; } .js-revisions .originals-of-duplicate li:before { display: block; position: absolute; top: 0; left: -16px; font-size: 1.2em; font-weight: bold; content: '•'; color: var(--black-300); } .js-revisions .js-revision-comment.somu-duplicates-edited .originals-of-duplicate li.somu-dupe-added:before { content: '+'; color: var(--green-600); left: -18px; } .js-revisions .js-revision-comment.somu-duplicates-edited .originals-of-duplicate li.somu-dupe-removed:before { content: '–'; color: var(--red-600); top: -2px; left: -18px; } .js-revisions .d-flex.ai-center.fw-wrap { display: block; } /* Hide follow post tooltip popup https://meta.stackexchange.com/q/345661 */ .js-follow-post ~ .s-popover { display: none; } /* Set a variable max-height for code blocks https://meta.stackoverflow.com/q/397012 */ .js-post-body pre, .wmd-preview pre { max-height: 80vh; } /* Remove edit button from question closed notice https://meta.stackexchange.com/q/349479 */ .js-post-notice .mt24:last-child { display: none; } /* Revert large margins on .s-prose https://meta.stackexchange.com/q/353446 */ .s-prose { margin-bottom: 1.4em; line-height: 1.4em; } .s-prose blockquote { margin-left: 0px; line-height: 1.4em; } .s-prose:not(.reason) > * { margin-bottom: 1em; } /* Switch back to yellow background color for blockquotes https://meta.stackexchange.com/q/343919 https://meta.stackexchange.com/q/344874 */ .s-prose blockquote { margin-left: 0; margin-right: 0; padding: 1em; padding-left: 1.2em; background-color: var(--yellow-050); color: inherit; } /* Fix some z-indexes to prevent them from being in front of (close) dialogs */ .s-btn-group .s-btn.is-selected { z-index: unset; } /* Expand profile descriptions on hover without using scrollbars in a small area */ #user-card .profile-user--about { max-height: auto; height: auto; overflow: hidden; } #user-card .profile-user--bio { height: 240px; overflow: hidden; } #user-card > .grid > .flex--item:hover .profile-user--bio { height: auto; } /* Revert post menu to lowercase */ #edit-tags, .js-post-menu > .grid > .flex--item > a, .js-post-menu > .grid > .flex--item > button { text-transform: lowercase; } /* Remove cookie consent banner */ .js-consent-banner { display: none; } /* Collectives - TylerH - https://meta.stackoverflow.com/a/423500 */ /* Hides the collective content on the right sidebar */ #sidebar > div.s-sidebarwidget.js-join-leave-container { display: none; } #sidebar > .sidebar-subcommunity { display: none; } /* Hides the "recommended by " verbiage */ .js-endorsements { display: none; } /* Hides the trophy for Collectives ranking next to usernames in user cards*/ div.user-details > a[href^="/collectives"] { display: none; } ul.s-user-card--awards li a[href^="/collectives/"] { display: none; } /* Hides collective buttons on the tag line */ a.subcommunity-avatar, a.subcommunity-topic-avatar { display: none; } div.js-community-icons { display: none; } /* Other Collectives elements */ /* Hides collectives in the question header */ #question-header + div .flex--item.fc-light:last-child { display: none; } /* Hide post reactions (Teams) https://meta.stackoverflow.com/q/398367 */ .js-reactions { display: none; } /* Hides teams in the left sidebar */ .nav-links .js-create-team-cta, .nav-links .js-create-team-cta ~ li { display: none; } /* Hide blocked ads still taking up 300px in right sidebar */ .js-sidebar-zone { min-height: 0; } `.replace(/ !important;/g, ';').replace(/;/g, ' !important;'), false); // end stylesheet // On page load document.addEventListener('DOMContentLoaded', function (evt) { // Make jQuery available when running in a userscript sandbox // See https://github.com/samliew/SO-mod-userscripts/issues/112 if (typeof unsafeWindow !== 'undefined' && window !== unsafeWindow) { window.jQuery = unsafeWindow.jQuery; window.$ = unsafeWindow.jQuery; } // If rep notification is displaying low values, remove it let repBadge = document.querySelector('.js-achievements-button .indicator-badge'); if (repBadge) { let repCount = Number(repBadge.innerText); if (repCount > -5 && repCount < 5) repBadge.parentNode.removeChild(repBadge); } showAnnouncementIfNotBlacklisted(); hideClickbaityBlogPosts(); stripUnnecessaryTracking(); revertRelatedQuestions(); initShortenBadgeCounts(); initFixBrokenImages(); betterDuplicatesEditedList(); }); /* * [feature] * Hide the announcement bar if it contains blacklisted keywords (usually used to promote podcasts or blog posts) */ function showAnnouncementIfNotBlacklisted() { const annBar = document.getElementById('announcement-banner'); if (annBar) { const annText = annBar.innerText.trim().toLowerCase(); const isBlacklisted = blacklistedAnnouncementWords && blacklistedAnnouncementWords.some(v => annText.includes(v)); // Show announcement bar when it doesn't contain blacklisted keywords if (!isBlacklisted) { annBar.style.setProperty('display', 'block', 'important'); } else { console.log('Reduce Clutter: Announcement bar has been blocked.', annText); } } } /* * [feature] * Hide click-baity blog posts from sidebar. If there are no remaining items, remove the blog section. */ function hideClickbaityBlogPosts() { // Hide clickbaity featured blog post titles from sidebar const blogheader = $('.s-sidebarwidget__yellow .s-sidebarwidget--header').filter((i, el) => el.innerText.includes('Blog')); if (blogheader.length) { let itemsRemoved = 0; let items = blogheader.nextAll('li').find('a[href^="https://stackoverflow.blog"]').each(function (i, el) { const blogTitle = (el.title || el.innerText).toLowerCase().trim(); const isBlacklisted = blacklistedBlogWords && blacklistedBlogWords.some(v => blogTitle.includes(v)); const isSponsored = el.nextElementSibling?.innerText.includes('sponsored'); if (isBlacklisted || isSponsored) { $(this).parents('li').remove(); itemsRemoved++; console.log('Reduce Clutter: Featured blogpost has been blocked.', blogTitle); } }); // if no items remaining, remove "Blog" heading if (items.length == itemsRemoved) { blogheader.remove(); } } } /* * [feature] * Remove tracking from elements (e.g.: links and buttons) and abort tracking requests */ function stripUnnecessaryTracking() { // Remove Google analytics tracking window.ga = function () { }; window.GoogleAnalyticsObject = ga; // Remove Facebook tracking if (navigator.sendBeacon) { navigator.sendBeacon = function () { }; } // Abort tracking requests if (typeof $.ajaxPrefilter === 'function') { $.ajaxPrefilter(function (options, originalOptions, jqXHR) { const isTrackingRequest = [ '/gps/event', '/jobs/n/prizm/event', ].some(url => options.url.startsWith(url)); if (isTrackingRequest) jqXHR.abort(); }); } const doFindAndStrip = () => { // Strip unnecessary tracking let trackedElemCount = 0; $('.js-gps-track, [data-ga], [data-gps-track], a[href*="utm_"], a[href*="_medium"], a[href*="_source"]').each(function (i, el) { this.classList.remove('js-gps-track'); el.dataset.ga = ''; el.dataset.gpsTrack = ''; el.removeAttribute('data-ga'); el.removeAttribute('data-gps-track'); // Specify which query params to remove from link let params = new URLSearchParams(el.search); params.delete('utm_source'); params.delete('utm_medium'); params.delete('utm_campaign'); params.delete('utm_content'); params.delete('so_source'); params.delete('so_medium'); el.search = params.toString(); trackedElemCount++; }); if(trackedElemCount) console.log('Reduce Clutter: Removed tracking data from ' + trackedElemCount + ' elements.'); // Strip unnecessary query params from Q&A links in search results let trackedQaCount = 0; const linkTrackingRegex = /[?&]((cb|lq|rq)=\d+|r(ef)?=.*)/i; $('#content a, #sidebar a').each(function (i, el) { let isTracking = false; if (el.dataset.searchsession || el.dataset.tracker) { el.dataset.searchsession = ''; el.dataset.tracker = ''; isTracking = true; } if (el.search && linkTrackingRegex.test(el.search)) { el.href = el.getAttribute('href').replace(linkTrackingRegex, ''); isTracking = true; } if (isTracking) trackedQaCount++; }); $('.js-search-results').off('mousedown touchstart'); if(trackedQaCount) console.log('Reduce Clutter: Removed tracking data from ' + trackedQaCount + ' Q&A links.'); }; // Once on page load, after a short delay setTimeout(doFindAndStrip, 2000); // Strip tracking data from elements that are added dynamically $(document).ajaxStop(function () { doFindAndStrip(); }); } /* * [feature] * Improve the display of duplicates edited list on the post revisions page */ function betterDuplicatesEditedList() { $('.js-revisions .js-revision-comment').not('.somu-duplicates-edited').each(function (i, el) { // Duplicates list edit revisions if (el.innerText.includes('duplicates list edited from')) { let replacedHtml = el.innerHTML .replace('duplicates list edited from', 'duplicates list edited from to'; $(this).addClass('somu-duplicates-edited').find('a').wrap('
  • '); // Highlight changes $(this).find('li').each(function () { this.dataset.linkedpostid = $(this).children('a').attr('href').match(/\/(\d+)\//)[1]; }); const firstList = $(this).children('.originals-of-duplicate').first(); const secondList = $(this).children('.originals-of-duplicate').last(); // Find removals firstList.children('li').each(function (i, el) { const removed = !secondList.children('li').get().map(v => v.dataset.linkedpostid).some(id => el.dataset.linkedpostid === id); $(this).toggleClass('somu-dupe-removed', removed); }); // Find additions secondList.children('li').each(function (i, el) { const added = !firstList.children('li').get().map(v => v.dataset.linkedpostid).some(id => el.dataset.linkedpostid === id); $(this).toggleClass('somu-dupe-added', added); }); } }); } /* * [revert] * Revert the "Related" questions module under unanswered questions back to the sidebar * See https://meta.stackoverflow.com/q/423143 (2023-02-10) */ function revertRelatedQuestions() { const module = $('#inline_related_var_a_more, #inline_related_var_a_less').first(); if (!module.length) return; // Set respective classes // module, move to sidebar module.removeClass('pt32 px16 d-none') .addClass('module sidebar-related') .insertBefore($('#feed-link, .zone-container-sidebar, #hireme', '#sidebar').first()) .insertAfter($('#sidebar .sidebar-linked')); // optimal position, but falls back to above slots // module title module.children('.fs-body3') .removeClass('pb8 fs-body3') .addClass('fs-subheading mt16 mb16 pb2') .text((i, v) => v.replace(' questions', '')); // list module.children().last() .removeClass('bar-lg ba bc-black-150 js-gps-inline-related-questions') .addClass('related'); // list items module.find('.spacer') .removeClass('bb bc-black-150'); // links const links = module.find('.spacer > a') .removeClass('p12') .addClass('ai-start pr0'); // score links.children('.s-badge') .addClass('mr8'); // title wrapper links.children('.fl-grow1, .pr12') .removeClass('pr12') .addClass('ml2 h100 ai-center'); // title (originally truncated) module.find('.break-word, .fs-body1, .v-truncate1') .removeClass('break-word fs-body1 m0 pl16 v-truncate1') .attr('title', ''); // Remove expand link if ($('#inline_related_var_a_more').length > 1) $('#inline_related_var_a_less').remove(); $('#inline_related_see_more').parent().remove(); console.log('Reduce Clutter: Moved Related questions module back to sidebar.'); } /* * [feature] * Fix badge counts that are too long and take up too much space causing wrapping */ function initShortenBadgeCounts() { function findAndShortenBadgeCounts() { $('.badgecount').not('[data-shortbadgecounts]').attr('data-shortbadgecounts', '').text((i, v) => v.length <= 3 ? v : v.replace(/\d{3}$/, 'k')); } findAndShortenBadgeCounts(); $(document).ajaxStop(findAndShortenBadgeCounts); // on page update } /* * [bugfix] * Some images (e.g.: avatars) may be broken or blocked - this will replace them with a transparent image with a gray background */ function initFixBrokenImages() { function fixBrokenImages() { // Apply to newly-loaded unprocessed images $('img').not('[js-error-check]').attr('js-error-check', '').each(function (i, img) { const originalImg = img.src; // When image throws an error, set to transparent svg with gray background img.addEventListener('error', function (evt) { img.setAttribute('data-original-image', originalImg); img.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E"; // https://stackoverflow.com/a/26896684 img.style.background = 'var(--black-100)'; img.classList.add('img-haserror'); }); // Workaround for cached images, swap the source so we can catch any image errors after setting the event listener img.src = '#'; img.src = originalImg; }); } fixBrokenImages(); $(document).ajaxStop(fixBrokenImages); // on page update }