// ==UserScript==
// @name Hide Test Number
// @namespace cfpp
// @version 2.6.0
// @description Hides test numbers in verdicts on Codeforces
// @author LeoRiether (Modified by GitHub Copilot)
// @source https://github.com/LeoRiether/CodeforcesPP
// @icon https://github.com/LeoRiether/CodeforcesPP/raw/master/assets/cf%2B%2B%20logo.png
// @match *://codeforces.com/*
// @grant unsafeWindow
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// ==/UserScript==
(function () {
'use strict';
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}!`);
}
});
}
}());