89
\$\begingroup\$

Soo... I recently read a meta-post about the inherent problems of reputation. It listed some things like:

  • Confirmation bias.
  • Demotivating for low-rep users.
  • Hubris and elitism of high-rep users over low-rep users.
  • Incorrect representation of competence.
  • ...

So I thought to myself: "How would the network be, just without reputation?"

I grabbed the inspector, checked questions, my profile page and the active page and how reputation is displayed.

Then I quickly hacked together the following small user-script (ECMA6) for Greasemonkey that would hide all reputation from sight:

// ==UserScript==
// @name         Reputation Hider
// @namespace    http://gihub.com/Vogel612/ReputationHider
// @version      0.1
// @description  Hides all reputation displays from StackExchange sites
// @author       Vogel612
// @grant        none
// @include      /https?:\/\/(meta\.)?.*\.stackexchange\.com\/.*/
// @include      /https?:\/\/(stackoverflow|serverfault|superuser|askubuntu)\.com\/.*/
// ==/UserScript==
/* jshint -W097 */
'use strict';

let slice = function(collection) {
  return [].slice.call(collection);   
}

let repClasses = [ "rep", "reputation", "reputation-score" ];

let hideRepz = function() {
  repClasses.forEach(clazz => 
    slice(document.getElementsByClassName(clazz))
      .forEach(el => { 
        // this results in minor inconsistencies in CSS, but alas
        el.style.visibility = 'hidden';
      })
    );
}

hideRepz();
// add event listener for live-updates
document.addEventListener("DOMNodeInserted", hideRepz);

This has been running for a while now for me and I like to say it's a refreshing change. That aside:

It hides all reputation numbers on the page. If you're curious you can still find them of course. Also it looks funny on /questions when the active timers are outdented differently, but that's just my OCD going mad there.
It also checks for new questions you load over the notification.

What can I do better in that userscript?

\$\endgroup\$
7
  • 63
    \$\begingroup\$ Welcome to Stack Exchange, the network where everything's made up and the points don't matter. \$\endgroup\$
    – SylvainD
    Commented Feb 15, 2016 at 15:59
  • 23
    \$\begingroup\$ Why are you using JS for this? Wouldn't custom CSS solve this better (and much easier)? \$\endgroup\$
    – Bergi
    Commented Feb 15, 2016 at 16:18
  • 8
    \$\begingroup\$ You could do the same from a userscript actually. Just document.head.appendChild(document.createElement("style")).textContent = ".rep, .reputation, .reputation-scope { visibility: hidden; }"; \$\endgroup\$
    – Bergi
    Commented Feb 15, 2016 at 16:44
  • 6
    \$\begingroup\$ Will you post this on StackApps? \$\endgroup\$
    – A.L
    Commented Feb 16, 2016 at 12:08
  • 1
    \$\begingroup\$ @A.L. I didn't intend to originally, but now I am considering. I would add some more functionality though before doing that. Feel free to post yourself, even though it's 3 lines of CSS for that matter \$\endgroup\$
    – Vogel612
    Commented Feb 16, 2016 at 12:10

6 Answers 6

54
\$\begingroup\$

It would be better to do this with a user style, e.g. using the Stylus extension, like this:

.rep, .reputation, .reputation-score {
    visibility: hidden;
}

One advantage of user styles over scripts is that they're applied as soon as the page starts to load, so you won't see the rep counts flicker briefly while the page is loading. Also, user styles will automatically apply also to new dynamic content loaded e.g. via Ajax (such as new answers posted while you're viewing the page).

If you insist on using a user script, it's possibly to emulate user styles by using @run-at document-start and injecting a custom style element into the page early, like this:

// ==UserScript==
// @name        Hide Stack Exchange reputation (demo)
// @namespace   http://vyznev.net/
// @description A demonstration of using style injection to emulate user styles
// @author      Ilmari Karonen
// @version     1.0
// @license     Public domain
// @match       *://*.stackexchange.com/*
// @match       *://*.stackoverflow.com/*
// @match       *://*.superuser.com/*
// @match       *://*.serverfault.com/*
// @match       *://*.stackapps.com/*
// @match       *://*.mathoverflow.net/*
// @match       *://*.askubuntu.com/*
// @homepageURL http://codereview.stackexchange.com/a/120100
// @grant       none
// @run-at      document-start
// ==/UserScript==

var css =
    ".rep, .reputation, .reputation-score {\n" +
    "    visibility: hidden;\n" +
    "}\n";

var style = document.createElement('style');
style.textContent = css;
(document.head || document.documentElement).appendChild(style);

I use this method in my Stack Overflow Unofficial Patch (SOUP) user script (which combines a bunch of more or less minor interface bugfixes and improvements together) to let me efficiently apply both CSS-only and JS-based tweaks from a single user script.

One detail worth noting is that styles injected this way will generally end up being included before any actual site style sheets on the page, which means that they will be overridden by site CSS selectors of equal specificity. In some cases, you may need to use !important and/or various specificity hacks to make sure that your custom styles will take precedence.

\$\endgroup\$
1
  • 16
    \$\begingroup\$ If ES6 is available, best use template strings instead of concatenating. \$\endgroup\$
    – Joseph
    Commented Feb 16, 2016 at 14:59
29
\$\begingroup\$

You could use querySelectorAll instead of getElementsByClassName. That way, you have a bit more flexibility in targeting elements. It also allows multiple selectors. That way, you don't have to loop twice.

Instead of [].slice which uses an unnecessary array as host, try using Array.prototype.slice.call instead. Longer, but without that extra array.

Also, try using display:none as visibility just hides the element but maintain layout. This way, there's no awkward whitespace where the reputation was, making the layout all natural.

Seeing that you put ES6, we can use the handy dandy arrow functions as well.

'use strict';

const REPUTATION_ELEMENTS = '.rep, .reputation, .reputation-score';

function getElementArray(selector){
  return Array.prototype.slice.call(document.querySelectorAll(selector));
}

document.addEventListener("DOMNodeInserted", event => {
  getElementArray(REPUTATION_ELEMENTS).forEach( element => {
    element.style.display = 'none';
  });
});
\$\endgroup\$
5
  • 6
    \$\begingroup\$ The display:noneadvice is worth gold. One small thing: this doesn't run when loading the page by default. That's just a really minor thing. Also I recall hearing something about querySelectorAll being slower than the more specialized calls, but I might be wrong. Thanks for the review :) \$\endgroup\$
    – Vogel612
    Commented Feb 15, 2016 at 15:28
  • 8
    \$\begingroup\$ If you're using ES6, you should rather use a for of loop instead of forEach. \$\endgroup\$
    – Bergi
    Commented Feb 15, 2016 at 16:17
  • 2
    \$\begingroup\$ Another way to hide things: element.hidden = true. ciu, whatwg \$\endgroup\$
    – deltab
    Commented Feb 15, 2016 at 17:54
  • 1
    \$\begingroup\$ @Vogel612 Yup, it's relatively slower than the older element selection APIs, but for a few elements it's probably negligible and more familiar because of jQuery. Speaking of jQuery, not sure if possible, but if the userscript can utilize the one that is already on the page, it can be as simple as $(selector).hide(). \$\endgroup\$
    – Joseph
    Commented Feb 16, 2016 at 1:24
  • \$\begingroup\$ @Bergi forEach just came out of instinct, especially with arrays. :D \$\endgroup\$
    – Joseph
    Commented Feb 16, 2016 at 1:29
16
\$\begingroup\$

Update url

UserScripts have the magical ability to update themselves if you provide a link to a raw version of your script as a UserScript header:

Note: You actually have to increase the version, or it won't update.

// ==UserScript==
// @name         Reputation Hider
// @namespace    http://gihub.com/Vogel612/ReputationHider
// @version      0.1
// @downloadURL
// @updateURL

Method

I'm not particularly a fan of your method.

Checking for DOM updates before scanning through everything all over again.

It would make more sense to simply inject a stylesheet to override SE's stylesheets.

Much more labour performant.


ES6 features

I like the use of ES6 in your code, but you haven't used it to its full potential:

let slice = function(collection) {
  return [].slice.call(collection);   
}

Instead of using the old function(collection){} way, you can use (collection) => ():

let slice = (collection) => [].slice.call(collection)

The same thing applies to the function in your main loop.


Slicing and Dicing

Currently, you only use slice with your element selection.
You should combine the two!

slice(document.getElementsByClassName(clazz))
let fetchElements(clazz) => [].slice.call(document.getElementsByClassName(clazz))

Or hey, let's take it a step further and do some concatenation magic:

let fetchElements = (clazzArrey) =>
    clazzArrey.map((clazz) => [].slice.call(document.getElementsByClassName(clazz)))

Alternatively, you can use string concatenation to make one huge selector string and use querySelectorAll as Joseph described.

I find that querySelector* can have worse performance, but its always been edge cases that trigger that for me.


Future gazing

This isn't usually a review point I add, however, I think you could add support for other fields, and make a handy dandy dialog box to choose which you want to hide. That'd be a StackApps worthy creation in my eyes.

\$\endgroup\$
7
\$\begingroup\$

Instead of document.getElementsByClassName, you can use document.querySelectorAll(CSS selector).

Edit(Reply to @200_success): You can use anyone of those. Just I prefer document.querySelectorAll since I think it is better a little.

  1. document.getElementsByClassName has 31 letters, and document.querySelectorAll has 25 letters.
  2. document.querySelectorAll uses CSS to get DOM elements, but document.getElementsByClassName does not.
  3. IE 8+ supports document.querySelectorAll(IE 8 only supports CSS2 selectors), but document.getElementsByClassName is for IE 9+.

And you don't have to make an Array from the DOMNodeList using slice, since a DOMNodeList object is Array-like. i.e. you can use it as an Array using Array.prototype.forEach.call(DOMNodeList, callbackFunction).

window.addEventListener("load", _ => {
    Array.prototype.forEach.call(document.querySelectorAll(".rep, .reputation, .reputation-score, .-flair"), element => {
            element.style.display = "none";
        });
});
\$\endgroup\$
1
5
\$\begingroup\$

Since you're using ES6, you could replace your slice function with Array.from like this (implementing Josephs changes too);

let hideRepz = function() {
  repClasses.forEach(clazz => 
    Array.from(document.querySelectorAll(`.${clazz}`))
      .forEach(el => { 
        el.style.display = 'none';
      })
    );
}
\$\endgroup\$
3
  • 4
    \$\begingroup\$ If you're using ES6, you should rather use a for of loop instead of forEach. \$\endgroup\$
    – Bergi
    Commented Feb 15, 2016 at 16:17
  • 3
    \$\begingroup\$ @Bergi Other than the "it's there, so let's use it" argument, is there a concrete reason why for(..of..) is preferable? \$\endgroup\$
    – spender
    Commented Feb 16, 2016 at 2:17
  • 1
    \$\begingroup\$ It's somewhere between "more concise, more elegant, more performant, less to type, easier to understand" - if there was an adjective like "pythonic" for JS, it would be that. The technical arguments are that it doesn't require intermediate arrays and that you can break from it; basically forEach is deprecated with ES6. \$\endgroup\$
    – Bergi
    Commented Feb 16, 2016 at 13:44
5
\$\begingroup\$

Avoid using deprecated events

Mutation Events (DOMNodeInserted) is deprecated and you should use Mutation Observers instead.

Sample usage:

function processNewNodes(topNode) {
    var nodes = topNode.querySelectorAll(selector);
    // ...
}

var observer = new MutationObserver(function(mutations, observer) {
    mutations.forEach(function(mutation) {
        Array.prototype.forEach.call(mutation.addedNodes, processNewNodes);
    });
});

observer.observe(document, {
    subtree: true,
    childList: true
});
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.