Skip to content

Instantly share code, notes, and snippets.

@poke
Last active April 14, 2020 08:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save poke/1fc94f494893b05b39c8446da0979446 to your computer and use it in GitHub Desktop.
Save poke/1fc94f494893b05b39c8446da0979446 to your computer and use it in GitHub Desktop.
[User script] StackExchange: Close hammer warning
// ==UserScript==
// @id stackexchange-hammer-warning@poke
// @name StackExchange: Close hammer warning
// @description Show a warning when attempting to close a question as duplicate and you have no gold badge to hammer the question.
// @namespace poke
// @version 1.2.0
// @author Patrick Westerhoff
// @include *://*.stackexchange.com/questions/*
// @include *://*.stackexchange.com/review/close/*
// @include *://meta.serverfault.com/questions/*
// @include *://meta.serverfault.com/review/close/*
// @include *://meta.stackoverflow.com/questions/*
// @include *://meta.stackoverflow.com/review/close/*
// @include *://meta.superuser.com/questions/*
// @include *://meta.superuser.com/review/close/*
// @include *://serverfault.com/questions/*
// @include *://serverfault.com/review/close/*
// @include *://stackoverflow.com/questions/*
// @include *://stackoverflow.com/review/close/*
// @include *://superuser.com/questions/*
// @include *://superuser.com/review/close/*
// @homepageURL https://gist.github.com/poke/1fc94f494893b05b39c8446da0979446
// @updateURL https://gist.github.com/poke/1fc94f494893b05b39c8446da0979446/raw/stackexchange-hammer-warning.user.js
// @run-at document-end
// ==/UserScript==
const getUserBadges = (function () {
function fetchUserBadges () {
const userId = document.querySelector('.my-profile, .profile-me').href.match(/\/users\/(\d+)\//)[1];
return fetch(new URL(`/ajax/users/tab/${userId}?tab=badges&sort=class`, window.location).href)
.then(result => result.text())
.then(responseText => {
const doc = new DOMParser().parseFromString(responseText, 'text/html');
const badges = new Set();
for (const badge of doc.querySelectorAll('.user-badges .badge-tag .badge1')) {
badges.add(badge.parentNode.innerText.trim());
}
return badges;
});
}
let userBadges = null;
return async () => {
if (!userBadges) {
userBadges = await fetchUserBadges();
}
return userBadges;
};
})();
function initializeQuestion() {
const closeLink = document.querySelector('.close-question-link');
if (!closeLink) {
return;
}
const observer = new MutationObserver(mutations => {
const mutation = mutations.find(m => m.addedNodes.length === 1 && m.addedNodes[0].id === 'popup-close-question');
if (!mutation) {
return;
}
const closePopup = mutation.addedNodes[0];
const duplicateOption = closePopup.querySelector('input[name="closeReasonId"][value="Duplicate"]');
if (!duplicateOption) {
return;
}
const hammerIcon = createHammerIcon();
hammerIcon.setAttribute('class', 'hammer-icon');
const duplicateOptionDescription = duplicateOption.closest('li').querySelector('.s-description');
duplicateOptionDescription.parentNode.insertBefore(hammerIcon, duplicateOptionDescription);
const questionTags = Array.prototype.map.call(document.querySelectorAll('.post-taglist .post-tag'),
tag => decodeURIComponent(tag.href.match(/\/([^\/]+)$/)[1]));
// get user badges to check whether the question can be hammered
getUserBadges().then(badges => {
const duplicatePane = closePopup.querySelector('.close-as-duplicate-pane');
const canHammer = questionTags.some(t => badges.has(t));
hammerIcon.setAttribute('class', canHammer ? 'hammer-icon yes' : 'hammer-icon no');
duplicateOption.closest('li').title = canHammer ? 'You can hammer this question' : 'You cannot hammer this question';
if (canHammer) {
const submitButton = closePopup.querySelector('.js-popup-submit');
const paneObserver = new MutationObserver(() => {
submitButton.textContent = duplicatePane.classList.contains('popup-active-pane') ? 'Close question' : 'Vote to close';
});
paneObserver.observe(duplicatePane, { attributes: true, attributeFilter: ['class'] });
} else {
const hammerWarning = document.createElement('div');
hammerWarning.className = 'hammer-warning';
hammerWarning.appendChild(createHammerIcon());
hammerWarning.appendChild(document.createElement('strong')).appendChild(document.createTextNode('Heads-up: '));
hammerWarning.appendChild(document.createTextNode('This question cannot be hammered because you do not have a gold badge for any of the question’s tags.'));
duplicatePane.insertBefore(hammerWarning, duplicatePane.firstChild);
}
});
});
observer.observe(closeLink.parentNode, { childList: true });
}
function createHammerIcon () {
const hammer = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
hammer.setAttribute('viewBox', '0 0 32 32');
hammer.innerHTML = '<path d="M14.4 13c.8-.7 2-2 2-3.2l3.2-2c.5-.4.4-1.3 0-1.8L13.8.4c-.6-.5-1.5-.5-2 0l-2 3.2c-1.5.2-2.4 1.2-4 2.7-1.4 1.5-2 2-2.4 3.8L.4 12c-.5.4-.5 1.5 0 2L6 19.6c.5.5 1.4.5 1.8 0l2-3.3c1.4 0 2.6-1 3.3-2l15.6 17.3c.5.5 1.2.5 1.6 0l1.5-1.5c.4-.3.4-1 0-1.4L14.3 13z"/>';
return hammer;
}
// initialize
const reviewContent = document.querySelector('.review-content');
if (reviewContent) {
const reviewObserver = new MutationObserver(mutations => {
if (mutations.some(m => Array.prototype.find.call(m.addedNodes, x => x.className === 'mainbar-full'))) {
initializeQuestion();
}
});
reviewObserver.observe(reviewContent, { childList: true });
}
else {
initializeQuestion();
}
const style = document.createElement('style');
style.textContent = `
.hammer-icon {
fill: #CCC;
width: 1.1em;
height: 1.1em;
position: absolute;
margin-left: .5em;
margin-top: .1em;
}
.hammer-icon.yes {
fill: #090;
}
.hammer-icon.no {
fill: #900;
}
.hammer-warning {
margin-bottom: 10px;
padding: 8px;
background: #FDD;
border: 1px solid #900;
border-left-width: 5px;
}
.hammer-warning > svg {
width: 16px;
height: 16px;
float: left;
margin-right: .5em;
}
`;
document.getElementsByTagName('head')[0].appendChild(style);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment