您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.
当前为
// ==UserScript== // @name atcoder-standings-lang // @namespace iilj // @version 2020.11.12.0 // @description AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです. // @author iilj // @supportURL https://github.com/iilj/atcoder-standings-lang/issues // @match https://atcoder.jp/contests/*/standings* // ==/UserScript== /* globals $ */ /** * ユーザID/言語ごとの提出数 * @typedef {Object} UserLangEntry * @property {string} user_id ユーザ ID * @property {string} language 言語名 * @property {number} count 提出数 */ (() => { 'use strict'; /** @type {Map<string, UserLangEntry[]>} */ const userLangEntryMap = new Map(); const fetchLangJson = async () => { console.log('@kenkooooさんありがとう'); console.log('うえすぎさんありがとう'); // 2e5 個くらい要素があるのでキャッシュする const res = await fetch("https://kenkoooo.com/atcoder/resources/lang.json", { cache: 'force-cache' }); /** @type {UserLangEntry[]} */ const userLangEntries = await res.json(); // prepare map userLangEntries.forEach(userLangEntry => { if (userLangEntryMap.has(userLangEntry.user_id)) { userLangEntryMap.get(userLangEntry.user_id).push(userLangEntry); } else { userLangEntryMap.set(userLangEntry.user_id, [userLangEntry]); } }); // sort arrays userLangEntryMap.forEach(userLangArray => { userLangArray.sort((a, b) => b.count - a.count); // in place }); }; /** @type {(anchor: HTMLAnchorElement) => void} */ const updateAnchor = (anchor) => { if (!anchor.href.includes('/users/')) return; const user_id = anchor.text.trim(); if (!userLangEntryMap.has(user_id)) return; const userLangArray = userLangEntryMap.get(user_id); const tooltipHtml = userLangArray.map((userLangEntry) => `${userLangEntry.language} : ${userLangEntry.count}` ).join('<br>'); let langHtml = userLangArray[0].language; if (userLangArray.length >= 2) { langHtml += '<div style="font-size:10px;display:inline;">' + `/${userLangArray[1].language}` + (userLangArray.length >= 3 ? `/${userLangArray[2].language}` : '') + ' </div>'; } anchor.insertAdjacentHTML('beforeend', '/' + '<div data-toggle="tooltip" data-html="true" data-placement="right" style="font-size:12px;display:inline;" title="' + tooltipHtml + '">' + langHtml + '</div>'); }; /** @type {(tbody: HTMLTableSectionElement) => void} */ const updateTable = (tbody) => { tbody.querySelectorAll('.username').forEach(anchor => { updateAnchor(anchor); }); $('[data-toggle="tooltip"]').tooltip(); }; /** @type {HTMLTableSectionElement} */ let tbody = null; const tableObserver = new MutationObserver(() => { updateTable(tbody); }); const parentObserver = new MutationObserver(async () => { tbody = document.getElementById('standings-tbody'); if (tbody) { parentObserver.disconnect(); await fetchLangJson(); updateTable(tbody); tableObserver.observe( tbody, { childList: true } ) } }); parentObserver.observe( document.getElementById('vue-standings'), { childList: true, subtree: true } ); })();