Skip to content

Instantly share code, notes, and snippets.

@mebeim
Last active May 17, 2022 15:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mebeim/b50951a0f4bbcaac0c1aa73e6a7cdc66 to your computer and use it in GitHub Desktop.
Save mebeim/b50951a0f4bbcaac0c1aa73e6a7cdc66 to your computer and use it in GitHub Desktop.
Fix new "responsive" and unusable design of StackOverflow user profiles
// ==UserScript==
// @name SO Layout Whack-A-Mole
// @description Fix new "responsive" and unusable design of StackOverflow user profiles
// @version 0.8.1
// @author Marco Bonelli
// @namespace https://mebeim.net
// @match https://stackoverflow.com/users/*/*
// @downloadURL https://gist.githubusercontent.com/mebeim/b50951a0f4bbcaac0c1aa73e6a7cdc66/raw/so-layout-whack-a-mole.user.js
// @updateURL https://gist.githubusercontent.com/mebeim/b50951a0f4bbcaac0c1aa73e6a7cdc66/raw/so-layout-whack-a-mole.user.js
// @grant none
// ==/UserScript==
/**
* CHANGELOG
* v0.8.1: Minor style adjustments for the "Answers" page
* v0.8: Remove patches for "All actions" and Votes pages as they now have a decent layout
* v0.7: Update answers page style to include answer date
* v0.6: They finally (kind of) fixed the layout of the reputation page, yay!
* v0.5: fix answers page link alignment and title length overflowing container
* v0.4: fix reputation page number sizes and date alignment
* v0.3: fix incorrect styling of answers ans questions panels in summary
* page after clicking on a different sort
* v0.2: add update url; fix text color of vote counts in answers page
*/
(function() {
'use strict'
function patchBadgeTrophies(el) {
if (el.classList.contains('spotAward')) {
const container = el.parentElement.parentElement
const txt = container.innerText.split('\n')
container.innerHTML = `<span style="font-size: 1.3em; margin-right: .3em;">${txt[0]}</span>${txt[1]}`
container.style.fontSize = '1.1em'
} else if (el.classList.contains('spotAwardLg')) {
el.parentElement.parentElement.removeChild(el.parentElement)
}
}
function patchProfilePage() {
// Remove "more..." button if the "About" section doesn't need it... SMH
document.querySelectorAll('.d-flex > .s-btn.v-hidden').forEach(el => {
el.parentElement.parentElement.removeChild(el.parentElement)
})
// Remove badge trophies and replace the header with simple text
document.querySelectorAll('.svg-spot').forEach(patchBadgeTrophies)
const globalStyle = document.createElement('style')
globalStyle.innerText = `
.p12 {
padding: 3px 5px !important;
}
.g24 {
gap: 15px !important;
}
`
document.head.appendChild(globalStyle)
}
function patchSummaryPage() {
function removeIfEmpty(el) {
if (el.querySelector('.s-empty-state') || el.textContent.includes('user has not earned any')) {
const p = el.parentElement
p.parentElement.removeChild(p)
}
}
// Remove some panels if empty
document.querySelectorAll('#user-panel-badges').forEach(removeIfEmpty)
document.querySelectorAll('#user-panel-following').forEach(removeIfEmpty)
document.querySelectorAll('#user-panel-bookmarks').forEach(removeIfEmpty)
document.querySelectorAll('#user-panel-bounties').forEach(removeIfEmpty)
document.querySelectorAll('#user-panel-votes').forEach(removeIfEmpty)
// Remove badge trophies and replace the header with simple text
document.querySelectorAll('.svg-spot').forEach(patchBadgeTrophies)
const globalStyle = document.createElement('style')
globalStyle.innerText = `
.g24 {
gap: 15px !important;
}
.p12 {
padding: 1px 3px !important;
}
.pr12.pr12 {
padding-right: 0 !important;
}
.bc-black-075 {
border: none !important;
}
.s-badge__votes {
min-width: 20px !important;
height: 20px !important;
margin-right: 0 !important;
}
.s-badge__rep {
height: 20px !important;
}
.answer-hyperlink, .question-hyperlink {
font-size: 1em !important;
}
.flex--item:has(> .answer-hyperlink) {
padding: 0 !important;
}
.fl-grow1 {
padding: 5px !important;
}
#user-panel-articles {
display: none !important;
}
.js-highlight-box-badges .d-flex.jc-space-between.h100.h100.h100 {
height: 75% !important;
}
#user-panel-votes .p16 {
padding: 0 !important;
}
/* Remove redundant top "Summary" title */
#user-tab-summary > div:first-child {
display: none !important;
}
`
document.head.appendChild(globalStyle)
}
function patchSummaryOrProfilePage() {
// This page is "Profile" for our profile, "Summary" if we are not logged in or we are
// looking at another user. Consistency FTW!
const sel = document.querySelector('.s-navigation--item.is-selected')
if (sel.textContent.includes('Profile'))
patchProfilePage()
else
patchSummaryPage()
}
function patchAnswersPage() {
const acceptedDiv = `<div class="goddamn-vote-count goddamn-accepted"></div>`;
const notAcceptedDiv = `<div class="goddamn-vote-count"></div>`;
function parseVotes(s) {
return parseInt(s) + (s.includes('k') ? 'k' : '')
}
function patchStuff() {
document.querySelectorAll('.s-post-summary:not(.patched)').forEach(el => {
const container = el.querySelector('.s-post-summary--stats')
const accepted = !!(el.querySelector('.has-accepted-answer'))
const votes = parseVotes(el.querySelector('.s-post-summary--stats-item-number').textContent)
const timeEl = el.querySelector('time.s-user-card--time')
const todel = [
'.s-post-summary--stats-item-number',
'.s-post-summary--stats-item-unit',
'.s-post-summary--stats-item.has-answers.has-accepted-answer',
'.s-post-summary--meta-tags .subcommunity-avatar',
'.s-post-summary--meta-tags .s-popover',
]
todel.map(el.querySelector.bind(el)).forEach(x => {
if (x)
x.parentElement.removeChild(x)
})
timeEl.parentElement.removeChild(timeEl)
container.innerHTML = (accepted ? acceptedDiv : notAcceptedDiv) + timeEl.textContent.replace(/^answered\s+/, '')
container.querySelector('.goddamn-vote-count').textContent = votes
el.classList.add('patched')
})
}
patchStuff()
const observer = new MutationObserver(patchStuff)
observer.observe(document.body, {childList: true, subtree: true})
const globalStyle = document.createElement('style')
globalStyle.innerText = `
.bc-black-100 {
border: none !important;
}
.s-post-summary {
padding: 4px 4px 2px 4px !important;
}
.s-post-summary--content {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.s-post-summary--content-title {
display: inline-block !important;
font-size: 1.15em !important;
margin: 4px 0 !important;
padding: 0 !important;
}
.goddamn-vote-count {
font-size: 1.1em !important;
display: inline-block !important;
padding: 1px 4px !important;
border-radius: 2px;
color: var(--fc-dark);
}
.goddamn-vote-count.goddamn-accepted {
border: 1px solid #62b47d !important;
background-color: #62b47d;
color: var(--highlight-bg);
}
.goddamn-vote-count:not(.goddamn-accepted) {
border: 1px solid grey !important;
border-radius: 2px;
}
.s-post-summary--meta-tags {
display: inline;
position: absolute !important;
right: 2px;
padding-left: 25px;
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, var(--theme-background-color) 20px, var(--theme-background-color) 100%);
margin-top: -2.2em;
}
/* Keep only the 2 most relevant tags */
.post-tag:not(:nth-child(-n + 2)) {
display: none;
}
`
document.head.appendChild(globalStyle)
}
function patchTagsPage() {
const globalStyle = document.createElement('style')
globalStyle.innerText = `
#user-tab-tags {
width: 45%;
}
.p12 {
padding: 3px 5px !important;
}
`
document.head.appendChild(globalStyle)
}
function patchBookmarksPage() {
const acceptedDiv = `<div class="s-post-summary--stats-item has-answers has-accepted-answer"></div>`;
const notAcceptedDiv = `<div class="s-post-summary--stats-item"></div>`;
function parseVotes(s) {
return parseInt(s) + (s.includes('k') ? 'k' : '')
}
function patchStuff() {
if (document.querySelector('.s-post-summary.patched'))
return
document.querySelectorAll('.s-post-summary').forEach(el => {
const stats = el.querySelector('.s-post-summary--stats')
const accepted = !!(el.querySelector('.s-post-summary--stats-item.has-answers.has-accepted-answer'))
const question = el.querySelector('.s-post-summary--content a[href^="/questions"]')
const tags = el.querySelector('.s-post-summary--meta-tags')
let votes = 0
el.querySelectorAll('.s-post-summary--stats .s-post-summary--stats-item').forEach(el => {
if (el.textContent.includes('votes'))
votes = parseVotes(el.textContent)
})
el.removeChild(el.querySelector('.s-post-summary--content'))
el.classList.add('patched')
stats.innerHTML = accepted ? acceptedDiv : notAcceptedDiv
stats.querySelector('.s-post-summary--stats-item').innerText = votes
question.parentElement.removeChild(question)
stats.appendChild(question)
stats.appendChild(tags)
})
}
patchStuff()
const observer = new MutationObserver(patchStuff)
observer.observe(document.body, {childList: true, subtree: true})
const globalStyle = document.createElement('style')
globalStyle.innerText = `
.bc-black-100 {
border: none !important;
}
.s-post-summary {
padding: 0 !important;
border: none !important;
}
.s-post-summary:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.s-post-summary--stats {
font-size: 1.15em !important;
margin: 2px 0 !important;
}
.s-post-summary--stats-item {
display: inline !important;
padding: 1px 4px !important;
margin: 0 4px 0 0 !important;
}
.s-post-summary--stats-item.has-accepted-answer {
color: #0f0f0f !important;
}
.s-post-summary--stats-item:not(.has-accepted-answer) {
color: white;
border: 1px solid grey !important;
border-radius: 2px;
}
.s-post-summary--meta-tags {
position: absolute !important;
right: 0;
}
/* Keep only the 2 most relevant tags */
.post-tag:not(:nth-child(-n + 2)) {
display: none;
}
`
document.head.appendChild(globalStyle)
}
function patchBadgesPage() {
const globalStyle = document.createElement('style')
globalStyle.innerText = `
#user-tab-badges .flex--item {
margin: 0 5px !important;
}
`
document.head.appendChild(globalStyle)
}
const patchMap = [
[/tab=profile/ , patchProfilePage ],
[/^[^?]+$/ , patchSummaryOrProfilePage],
[/tab=summary/ , patchSummaryPage ],
[/tab=topactivity/, patchSummaryPage ],
[/tab=answers/ , patchAnswersPage ],
[/tab=tags/ , patchTagsPage ],
[/tab=bookmarks/ , patchBookmarksPage ],
[/tab=badges/ , patchBadgesPage ],
]
for (const [rexp, func] of patchMap) {
if (rexp.test(location.href))
func()
}
})();
@iOSjNaGeR
Copy link

thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment