2

Yet another update/rewrite of Rob W’s script.

Improvements relative to the older version:

  • no jQuery dependencies – only modern, vanilla JavaScript APIs, making it lightweight and compatible with sandboxing user-script managers (like current Greasemonkey)
  • allows clicking on the separated vote counts to go back to the total score again
  • usable even before page loads in full, thanks to @run-at document-start
  • uses actual minus signs

Script code:

// ==UserScript==
// @name           Stack Exchange: Show/hide vote counts for all
// @grant          none
// @run-at         document-start
// @match          https://*.stackexchange.com/*
// @match          https://*.superuser.com/*
// @match          https://*.stackoverflow.com/*
// @match          https://*.mathoverflow.net/*
// @match          https://*.serverfault.com/*
// @match          https://*.askubuntu.com/*
// @match          https://stackapps.com/*
// @exclude        https://api.stackexchange.com/*
// @exclude        https://data.stackexchange.com/*
// @exclude        https://openid.stackexchange.com/*
// @exclude        https://contests.stackoverflow.com/*
// @exclude        /^https?:\/\/winterbash\d{4,}\.stackexchange\.com\//
// ==/UserScript==

document.addEventListener('DOMContentLoaded', ev => {
  document.head.insertAdjacentHTML('beforeend', `
    <style type="text/css">
      .js-vote-count {
        cursor: pointer;
      }
    </style>
  `);
}, { once: true });

const tsa = typeof cloneInto !== 'undefined'
    ? x => cloneInto(x, unsafeWindow, { cloneFunctions: true })
    : x => x;
const showToast = (text, options) =>
  unsafeWindow.StackExchange.helpers.showToast(text, tsa(options));

const fetchVotes = async (postId) => {
  const resp = await fetch(
    `https://api.stackexchange.com/2.0/posts/${postId}?${new URLSearchParams({
      filter: '!)q3b*aB43Xc',
      key: 'DwnkTjZvdT0qLs*o8rNDWw((',
      site: location.host,
    })}`
  );

  const json = await resp.json();
  if (resp.status === 200) {
    const signed = (sign, val) => 0 < val && val < 100 ? `${sign}${val}` : `${val}`;
    return {
      up: signed('+', json.items[0].up_vote_count),
      down: signed('\u2212', json.items[0].down_vote_count),
      total: json.items[0].up_vote_count - json.items[0].down_vote_count,
    };
  } else {
    throw new Error(json.error_message);
  }
};

document.addEventListener('click', async ev => {
  const target = ev.target.closest('.js-vote-count');
  if (!target)
    return;

  // do not fetch vote counts again if we are
  // already fetching them (or if native scripts are)
  if (target.querySelector('[aria-busy="true"]'))
    return;

  // when the vote counts are already fetched, hide them
  if (target.querySelector('.vote-count-separator')) {
    target.innerHTML = `${+target.dataset.value}`.replaceAll('-', '\u2212');
    return;
  }

  const postNode = target.closest('.question[data-questionid], .answer[data-answerid]');
  const postId = postNode.dataset.questionid ?? postNode.dataset.answerid;

  target.insertAdjacentHTML('beforeend', `
    <div class="s-spinner fc-black-200 s-spinner__sm" role="alert" aria-busy="true">
        <div style="font-size: 0px; color: transparent; float: left;">loading...</div>
    </div>
  `);
  const spinner = target.querySelector(':last-child');

  try {
    const { up, down, total } = await fetchVotes(postId);
    target.dataset.value = total;
    target.setAttribute('aria-label', `${up} up / ${down} down`);
    target.innerHTML = `
      <div class="fc-green-600">${up}</div>
      <div class="vote-count-separator"></div>
      <div class="fc-red-600">${down}</div>
    `;
  } catch (e) {
    console.error("[SE:shvca] %o", e);
    spinner?.remove();
    showToast(`${e}`, {
      type: 'danger',
      transient: true,
    });
  }
}, false);

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.