CF Hide Test Number

Hide “on test X” suffix in Codeforces verdicts

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         CF Hide Test Number
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Hide “on test X” suffix in Codeforces verdicts
// @author       SanguineChameleon
// @match        https://codeforces.com/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    GM_addStyle(`
span[class='verdict-format-judged'] {
    display: none !important;
}
td[class^='time-consumed-cell'] {
    font-size: 0px !important;
}
td[class^='memory-consumed-cell'] {
    font-size: 0px !important;
}
`)
})();

(function() {
    'use strict';

    const css = `
.verdict-format-judged,
.diagnosticsHint {
  display: none !important;
}`;
    if (typeof GM_addStyle === 'function') {
        GM_addStyle(css);
    } else {
        const s = document.createElement('style');
        s.textContent = css;
        (document.head || document.documentElement).appendChild(s);
    }

    const pluckRE = / on (pre)?test ?\d*$/;
    function pluck(s) {
        return s.replace(pluckRE, '');
    }

    const CF = (unsafeWindow || window).Codeforces;
    if (CF && CF.showMessage) {
        const _old = CF.showMessage;
        CF.showMessage = msg => _old(pluck(msg));
    }

    function stripNode(el) {
        try {
            const txt = el.childNodes[0];
            txt.nodeValue = pluck(txt.nodeValue);
        } catch {}
    }

    document.addEventListener('DOMContentLoaded', () => {
        document.querySelectorAll('.verdict-rejected span, .verdict-waiting span')
                .forEach(stripNode);

        const catcher = (unsafeWindow || window).submissionsEventCatcher;
        if (catcher && catcher.subscribe) {
            const channel = catcher.channels[0];
            catcher.subscribe(channel, data => {
                if (data.t === 's') {
                    const sel = `[data-a='${data.d[0]}'] .status-verdict-cell span`;
                    const el = document.querySelector(sel);
                    if (el) stripNode(el);
                }
            });
        }
    });
})();



(function () {
  'use strict';

  // 🛑 Prevent any “on test X” span from ever showing
  const earlyHideCSS = ".verdict-format-judged, .diagnosticsHint { display: none !important; }";
  if (typeof GM_addStyle === 'function') {
    GM_addStyle(earlyHideCSS);
  } else {
    const s = document.createElement('style');
    s.textContent = earlyHideCSS;
    (document.head || document.documentElement).appendChild(s);
  }

  const env = {"NODE_ENV":"production","VERSION":"2.4.0","TARGET":"userscript"};

  /**
   * @file Utilities to manipulate the DOM
   */
  function isEvent(str) {
    return str.length > 2 && str[0] == 'o' && str[1] == 'n' && str[2] >= 'A' && str[2] <= 'Z';
  }

  var dom = {
    $(query, element) {
      return (element || document).querySelector(query);
    },

    $$(query, element) {
      return (element || document).querySelectorAll(query);
    },

    on(element, event, handler, options) {
      element.addEventListener(event, handler, options || {});
    },

    /**
     * Works like React.createElement
     * Doesn't support a set of features, but should work for most purposes
     */
    element(tag, props, ...children) {
      let el;

      if (typeof tag === 'string') {
        el = document.createElement(tag);
        Object.assign(el, props); // Some properties like data-* and onClick won't do anything here...

        if (props) {
          // ...so we have to consider them here
          for (let key in props) {
            if (key.startsWith('data-') || key == 'for') el.setAttribute(key, props[key]);else if (isEvent(key)) el.addEventListener(key.substr(2).toLowerCase(), props[key]);
          }
        }
      } else if (typeof tag === 'function') {
        el = tag(props);
      }

      for (let c of children) {
        if (typeof c === 'string') {
          el.appendChild(document.createTextNode(c));
        } else if (c instanceof Array) {
          el.append(...c);
        } else if (c) {
          el.appendChild(c);
        }
      }

      return el;
    },

    fragment(...children) {
      let frag = document.createDocumentFragment();

      for (let c of children) {
        if (typeof c === 'string') {
          frag.appendChild(document.createTextNode(c));
        } else if (c instanceof Array) {
          for (let cc of c) frag.appendChild(cc);
        } else if (c) {
          frag.appendChild(c);
        }
      }

      return frag;
    },

    isEditable(element) {
      const unselectable = ["button", "checkbox", "color", "file", "hidden", "image", "radio", "reset", "submit"];
      const isEditableInput = element.tagName == "INPUT" && unselectable.indexOf(element.type) == -1;
      const isTextarea = element.tagName == "TEXTAREA";
      const isSelect = element.tagName == "SELECT";
      return isEditableInput || isTextarea || isSelect || element.isContentEditable;
    }

  };

  /**
   * The same as Ramda's tryCatch:
   * `tryCatch` takes two functions, a `tryer` and a `catcher`. The returned
   * function evaluates the `tryer`; if it does not throw, it simply returns the
   * result. If the `tryer` *does* throw, the returned function evaluates the
   * `catcher` function and returns its result. Note that for effective
   * composition with this function, both the `tryer` and `catcher` functions
   * must return the same type of results.
   *
   * @param {Function} tryer The function that may throw.
   * @param {Function} catcher The function that will be evaluated if `tryer` throws.
   * @return {Function} A new function that will catch exceptions and send then to the catcher.
   */

  function tryCatch(tryer, catcher) {
    return (...args) => {
      try {
        return tryer(...args);
      } catch (err) {
        return catcher(err);
      }
    };
  }
  /**
   * Returns a new function that, when called, will try to call `fn`.
   * If `fn` throws, `def` will be returned instead
   * @param {Function} fn The function to try executing
   * @param {any} def The default value to return if `fn` throws
   * @return {Function}
   */

  function safe(fn, def) {
    return (...args) => {
      try {
        return fn(...args);
      } catch {
        return def;
      }
    };
  }
  /**
   * Takes a list of functions and returns a function that executes them in
   * left-to-right order, passing the return value of one to the next
   * @param {[Function]} fns The functions to be piped
   * @return {Function} The piped composition of the input functions
   */

  const pipe = (...fns) => arg => fns.reduce((acc, f) => f(acc), arg);
  /**
   * Curried version of Array.prototype.map
   */

  const map = fn => arr => [].map.call(arr, fn);
  /**
   * Curried version of Array.prototype.forEach
   */

  const forEach = fn => arr => [].forEach.call(arr, fn);
  /**
   * Flattens one level of a list
   * @param {[[a]]} list
   * @return {[a]}
   */

  function flatten(list) {
    const len = xs => xs && typeof xs.length === 'number' ? xs.length : 1;

    const n = list.reduce((acc, xs) => acc + len(xs), 0);
    let res = new Array(n);
    let p = 0;

    for (let i = 0; i < list.length; i++) {
      if (list[i] && list[i].length >= 0) {
        for (let j = 0; j < list[i].length; j++) res[p++] = list[i][j];
      } else {
        res[p++] = list[i];
      }
    }

    return res;
  }
  function once(fn) {
    let result,
        ran = false;
    return function (...args) {
      if (!ran) {
        ran = true;
        result = fn(...args);
      }

      return result;
    };
  }
  const capitalize = str => str[0].toUpperCase() + str.slice(1).toLowerCase();
  const nop = function () {};
  /**
   * Formats a keyboard event to a shortcut string
   * It's in Functional.js because putting it in shortcuts.js created a circular dependency, and I don't like warnings in my builds
   * @param {KeyboardEvent} event
   * @returns {String} a formatted shortcut string from the event, like "Ctrl+Shift+P"
   */

  function formatShortcut(event) {
    let res = "";
    if (event.metaKey) res += 'Meta+';
    if (event.ctrlKey) res += 'Ctrl+';
    if (event.altKey) res += 'Alt+';
    if (event.shiftKey) res += 'Shift+';
    res += event.key == ' ' ? 'Space' : capitalize(event.key);
    return res;
  }
  /**
   * Returns a debounced function that fires no more than once in a `delay` ms period
   * @param {Function} fn the function to debounce
   * @param {Number} delay the delay in milliseconds
   */

  function debounce(fn, delay) {
    let timeout;
    return function debounced(...args) {
      clearTimeout(timeout);
      timeout = setTimeout(() => {
        timeout = undefined;
        fn(...args);
      }, delay);
    };
  }
  async function profile(fn) {
    return fn();
  }

  /**
   * @file Minimalistic event-bus
   */
  let listeners = {};
  function listen(event, callback) {
    if (!listeners[event]) listeners[event] = [];
    listeners[event].push(callback);
  }
  async function fire(event, data) {
    const results = (listeners[event] || []).map(async cb => cb(data));
    return Promise.all(results);
  }

  const version = env.VERSION;
  /**
   * Decorates a function so, when called, it only runs when the DOM has loaded
   * @example
   * let write_sum = ready((x, y) => document.write(x + y));
   * write_sum(1, 2); // only writes when the DOM has loaded
   * @type (...a -> b) -> ...a -> Promise<b>
   */

  const ready = fn => (...args) => {
    if (document.readyState == 'complete') {
      return Promise.resolve(fn(...args));
    }

    return new Promise(res => document.addEventListener('DOMContentLoaded', () => res(fn(...args)), {
      once: true
    }));
  };
  /**
   * @type Function -> Promise
   */

  const run_when_ready = fn => ready(fn)();
  const userHandle = once(ready(function () {
    const handle = dom.$('.lang-chooser').children[1].children[0].innerText.trim();
    return handle == 'Enter' ? 'tourist' : handle;
  }));

  var shared = /*#__PURE__*/Object.freeze({
      __proto__: null,
      version: version,
      ready: ready,
      run_when_ready: run_when_ready,
      userHandle: userHandle
  });

  const global =  typeof unsafeWindow !== 'undefined' && unsafeWindow;
  const storage = {
    get: key => Promise.resolve(localStorage.getItem(key)).then(safe(JSON.parse, {})),
    set: (key, value) => Promise.resolve(localStorage.setItem(key, JSON.stringify(value))),
    propagate: async function () {}
  };

  var userscript = /*#__PURE__*/Object.freeze({
      __proto__: null,
      global: global,
      storage: storage
  });

  let env$1 = {};

  {
    env$1 = { ...shared,
      ...userscript
    };
  }

  var env$2 = env$1;

  function _extends() {
    _extends = Object.assign || function (target) {
      for (var i = 1; i < arguments.length; i++) {
        var source = arguments[i];

        for (var key in source) {
          if (Object.prototype.hasOwnProperty.call(source, key)) {
            target[key] = source[key];
          }
        }
      }

      return target;
    };

    return _extends.apply(this, arguments);
  }

  function prop(title, type, id, data) {
    return {
      title,
      type,
      id,
      data
    };
  }
  let configProps = [
      prop('Hide "on test X" in verdicts', 'toggle', 'hideTestNumber'),
  ];
  function scProp(title, id) {
    return {
      title,
      id
    };
  }
  let shortcutProps = [
      scProp('Hide Test Number', 'hideTestNumber')
  ];

  const Toggle = ({
    config,
    id,
    pushChange,
    pullChange
  }) => {
    let checkbox = dom.element("input", {
      id: id,
      checked: config[id],
      type: "checkbox",
      onChange: e => pushChange(id, e.target.checked)
    });
    pullChange(id, value => checkbox.checked = value);
    return dom.element(dom.fragment, null, checkbox, dom.element("span", null));
  };

  function Prop({
    title,
    type,
    id,
    data,
    config,
    pushChange,
    pullChange
  }) {
    let props = {
      config,
      id,
      pushChange,
      pullChange
    };
    const table = {
      toggle: () => dom.element(Toggle, props)
    };
    let el = table[type]();
    return dom.element("label", {
      className: type,
      for: id
    }, title, el);
  }

  function Shortcut({
    title,
    id,
    shortcuts,
    pushChange
  }) {
    const pushDebounced = debounce(pushChange, 250);

    function handleKeyDown(e) {
      e.preventDefault();
      let sc = formatShortcut(e);
      if (sc != 'Escape') {
        e.target.value = sc;
        pushDebounced(id, sc);
      }
    }

    return dom.element("label", {
      className: "shortcut",
      for: `sc-${id}`
    }, title, dom.element("input", {
      id: `sc-${id}`,
      value: shortcuts[id],
      type: "text",
      onKeyDown: handleKeyDown
    }));
  }

  function Config({
    config,
    pushChange = nop,
    pullChange = nop
  }) {
    return configProps.map(p => dom.element(Prop, _extends({}, p, {
      config: config,
      pushChange: pushChange,
      pullChange: pullChange
    })));
  }
  function Shortcuts({
    shortcuts,
    pushChange = nop
  }) {
    return shortcutProps.map(p => dom.element(Shortcut, _extends({}, p, {
      shortcuts: shortcuts,
      pushChange: pushChange
    })));
  }

  let config = {};
  function save() {
    localStorage.cfpp = JSON.stringify(config);
  }
  function commit(id) {
    fire(id, config[id]);
    save();
  }
  const get = key => config[key];
  function set(key, value) {
    if (config[key] == value) return;
    config[key] = value;
    commit(key);
  }
  const toggle = key => set(key, !config[key]);
  const defaultConfig = {
    hideTestNumber: false,
    shortcuts: {
      hideTestNumber: "Ctrl+Shift+H"
    }
  };
  function load() {
    config = safe(JSON.parse, {})(localStorage.cfpp);
    config = Object.assign({}, defaultConfig, config);
    {
      save();
    }
    listen('request config change', ({
      id,
      value
    }) => {
      config[id] = value;
      fire(id, value);
    });
  }

  const createUI =  env$2.ready(function () {
    if (!dom.$('.lang-chooser')) return;

    function pushShortcut(id, value) {
      config.shortcuts[id] = value;
      commit('shortcuts');
    }

    let modal = dom.element("div", {
      className: "cfpp-config cfpp-modal cfpp-hidden"
    }, dom.element("div", {
      className: "cfpp-modal-background",
      onClick: closeUI
    }), dom.element("div", {
      className: "cfpp-modal-inner"
    }, dom.element(Config, {
      config: config,
      pushChange: set,
      pullChange: listen
    }), dom.element("span", {
      className: "hr"
    }), dom.element(Shortcuts, {
      shortcuts: config.shortcuts,
      pushChange: pushShortcut
    })));

    let modalBtn = dom.element("a", {
      className: "cfpp-config-btn"
    }, "[++]");
    dom.on(modalBtn, 'click', ev => {
      ev.preventDefault();
      modal.classList.remove('cfpp-hidden');
    });
    dom.on(document, 'keyup', keyupEvent => {
      if (keyupEvent.key == 'Escape') closeUI();
    });

    document.body.appendChild(modal);
    dom.$('.lang-chooser').children[0].prepend(modalBtn);
  });
  function closeUI() {
    dom.$('.cfpp-config').classList.add('cfpp-hidden');
    save();
  }

  var commonCSS = "@keyframes fadeIn{from{opacity:0;}to{opacity:1;}}.cfpp-hidden{display:none;}.cfpp-config-btn{font-size:22px!important;cursor:pointer;}.cfpp-config>.cfpp-modal-inner{width:auto; min-width: 300px;}.cfpp-modal{box-sizing:border-box;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:101;}.cfpp-modal-background{position:absolute;top:0;left:0;width:100vw;height:100vh;background:#00000087;animation:fadeIn 0.15s forwards;}.cfpp-modal-inner>label{position:relative;margin-bottom:0.5em;display:flex;flex-direction:row;justify-content:space-between;user-select:none;}.cfpp-modal-inner input[type=text]{width:32%;} .cfpp-modal-inner input[type=checkbox]{visibility:hidden;}.cfpp-modal-inner .toggle>span{position:absolute;width:1.4rem;height:1.4rem;top:calc(50% - 0.7rem);right:0;display:inline-block;border:thin solid #188ecd;border-radius:100%;background:#eee;transition:background 0.2s;}.cfpp-modal-inner .toggle>input:checked + span{background:#188ecd;}.cfpp-modal-inner .toggle>span:before{content:\"✓\";position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:#eee;font-size:0.8em;}.cfpp-modal-inner{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:auto;min-width:350px;max-height:80vh;background:white;padding:2em;border-radius:6px;overflow:auto;animation:fadeIn 0.15s forwards;}.hr{display:block;border-top:1px solid #7f7f7f52;width:calc(100% + 4em);margin:.5em -2em;}.verdict-hide-number .verdict-format-judged,.verdict-hide-number .diagnosticsHint{display:none!important;}";

  async function injectStyle(css) {
    let style = dom.element("style", {
      className: "cfpp-style"
    }, css);
    (document.body || document.head || document.documentElement).appendChild(style);
    return style;
  }
  const addStyle = typeof GM_addStyle === 'function' ? GM_addStyle : injectStyle;

  async function applyCommonStyles() {
    addStyle(commonCSS);
  }

  const pluckVerdictRegex = / on (pre)?test ?\d*$/;
  const pluckVerdict = s => s.replace(pluckVerdictRegex, '');
  const pluckVerdictOnNode = safe(n => {
    let c = n.childNodes[0];
    c.nodeValue = pluckVerdict(c.nodeValue);
  }, '');
  let ready$1 = false;
  function init() {
    if (ready$1) return;
    ready$1 = true;
    let _showMessage = env$2.global.Codeforces.showMessage;
    env$2.global.Codeforces.showMessage = function (message) {
      if (get('hideTestNumber')) {
        message = pluckVerdict(message);
      }
      _showMessage(message);
    };
    if (env$2.global.submissionsEventCatcher) {
      const channel = env$2.global.submissionsEventCatcher.channels[0];
      env$2.global.submissionsEventCatcher.subscribe(channel, data => {
        if (!get('hideTestNumber')) return;
        if (data.t === 's') {
          const el = dom.$(`[data-a='${data.d[0]}'] .status-verdict-cell span`);
          pluckVerdictOnNode(el);
        }
      });
    }
  }
  const install$a = env$2.ready(function () {
    if (!get('hideTestNumber')) return;
    init();
    document.documentElement.classList.add('verdict-hide-number');
    dom.$$('.verdict-rejected,.verdict-waiting').forEach(pluckVerdictOnNode);
  });
  function uninstall$7() {
    if (!document.documentElement.classList.contains('verdict-hide-number')) return;
    document.documentElement.classList.remove('verdict-hide-number');
    dom.$$('.verdict-rejected,.verdict-waiting').forEach(e => {
      // This might not perfectly restore the original text if it was complex,
      // but attempts to add back a generic "on test"
      // if (e.childNodes[0] && !e.childNodes[0].nodeValue.includes('on test')) {
      //      e.childNodes[0].nodeValue += ' on test ';
      // }
    });
  }

  var verdict_test_number = /*#__PURE__*/Object.freeze({
      __proto__: null,
      init: init,
      install: install$a,
      uninstall: uninstall$7
  });

  function install$b() {
    const id2Fn = {
      hideTestNumber: () => toggle('hideTestNumber')
    };
    let id2Shortcut = get('shortcuts');
    function convert(i2s, i2f) {
      let s2f = {};
      for (let id in i2s) {
        let shortcut = i2s[id].toLowerCase();
        let fn = i2f[id];
        s2f[shortcut] = fn;
      }
      return s2f;
    }
    let shortcut2Fn = convert(id2Shortcut, id2Fn);
    listen('shortcuts', newId2Shortcut => shortcut2Fn = convert(newId2Shortcut, id2Fn));
    dom.on(document, 'keydown', e => {
      if (dom.isEditable(document.activeElement)) return;
      let sc = formatShortcut(e).toLowerCase();
      const fn = shortcut2Fn[sc];
      if (fn) {
        e.preventDefault();
        e.stopPropagation();
        fn();
      }
    });
  }

  var shortcuts = /*#__PURE__*/Object.freeze({
      __proto__: null,
      install: install$b
  });

  profile(run);

  async function run() {
    console.log("Codeforces++ (Hide Test Number Only) is running!");
    load();
    createUI();

    let modules = [
      [verdict_test_number, 'hideTestNumber'],
      [shortcuts, 'shortcuts'], // 'shortcuts' is the config key for the shortcuts object
    ];

    let moduleNames = ['verdict_test_number', 'shortcuts'];

    function registerConfigCallback(m, id) {
      listen(id, value => {
        // For 'hideTestNumber' (boolean)
        if (id === 'hideTestNumber') {
          if (value === true || value === false) {
              value ? m.install() : (m.uninstall || nop)();
          } else { // Should not happen for a boolean toggle
              (m.uninstall || nop)();
               m.install(value);
          }
        } else if (id === 'shortcuts') { // For shortcuts object
          (m.uninstall || nop)(); // uninstall might not be needed or defined for shortcuts
          m.install(value); // shortcuts.install handles the new shortcut values
        }
      });
    }

    modules.forEach(([m, configID], index) => {
      tryCatch(m.install, e => console.log(`Error installing module #${moduleNames[index]}:`, e))();
      if (configID) {
        registerConfigCallback(m, configID);
      }
    });

    applyCommonStyles();

    env$2.run_when_ready(function () {
      const v = get('version');
      if (v != env$2.version) {
        set('version', env$2.version);
        env$2.global.Codeforces.showMessage(`Codeforces++ (Hide Test Number Only) was updated to version ${env$2.version}!`);
      }
    });
  }
}());