atcoder-standings-lang

AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.

Verzia zo dňa 10.11.2020. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

  1. // ==UserScript==
  2. // @name atcoder-standings-lang
  3. // @namespace iilj
  4. // @version 2020.11.10.0
  5. // @description AtCoder の順位表に最多提出言語を追加します.uesugi6111 さん作のスクリプトが元ネタです.
  6. // @author iilj
  7. // @supportURL https://github.com/iilj/atcoder-standings-lang/issues
  8. // @match https://atcoder.jp/contests/*/standings*
  9. // ==/UserScript==
  10.  
  11. /* globals $ */
  12.  
  13. /**
  14. * ユーザID/言語ごとの提出数
  15. * @typedef {Object} UserLangEntry
  16. * @property {string} user_id ユーザ ID
  17. * @property {string} language 言語名
  18. * @property {number} count 提出数
  19. */
  20.  
  21. (() => {
  22. 'use strict';
  23.  
  24. /** @type {Map<string, UserLangEntry[]>} */
  25. const userLangEntryMap = new Map();
  26.  
  27. const fetchLangJson = async () => {
  28. console.log('@kenkooooさんありがとう');
  29. // 2e5 個くらい要素があるのでキャッシュする
  30. const res = await fetch("https://kenkoooo.com/atcoder/resources/lang.json", { cache: 'force-cache' });
  31. /** @type {UserLangEntry[]} */
  32. const userLangEntries = await res.json();
  33.  
  34. // prepare map
  35. userLangEntries.forEach(userLangEntry => {
  36. if (userLangEntryMap.has(userLangEntry.user_id)) {
  37. userLangEntryMap.get(userLangEntry.user_id).push(userLangEntry);
  38. } else {
  39. userLangEntryMap.set(userLangEntry.user_id, [userLangEntry]);
  40. }
  41. });
  42.  
  43. // sort arrays
  44. userLangEntryMap.forEach(userLangArray => {
  45. userLangArray.sort((a, b) => b.count - a.count); // in place
  46. });
  47. };
  48.  
  49. /** @type {(anchor: HTMLAnchorElement) => void} */
  50. const updateAnchor = (anchor) => {
  51. if (!anchor.href.includes('/users/')) return;
  52.  
  53. const user_id = anchor.text.trim();
  54. if (!userLangEntryMap.has(user_id)) return;
  55.  
  56. const userLangArray = userLangEntryMap.get(user_id);
  57.  
  58. const tooltipHtml = userLangArray.map((userLangEntry) =>
  59. `${userLangEntry.language} : ${userLangEntry.count}`
  60. ).join('<br>');
  61.  
  62. let langHtml = userLangArray[0].language;
  63. if (userLangArray.length >= 2) {
  64. langHtml += '<div style="font-size:10px;display:inline;">'
  65. + `/${userLangArray[1].language}`
  66. + (userLangArray.length >= 3 ? `/${userLangArray[2].language}` : '')
  67. + ' </div>';
  68. }
  69.  
  70. anchor.insertAdjacentHTML('beforeend',
  71. '/'
  72. + '<div data-toggle="tooltip" data-html="true" data-placement="right" style="font-size:12px;display:inline;" title="' + tooltipHtml + '">'
  73. + langHtml
  74. + '</div>');
  75. $('[data-toggle="tooltip"]').tooltip();
  76. };
  77.  
  78. /** @type {(tbody: HTMLTableSectionElement) => void} */
  79. const updateTable = (tbody) => {
  80. tbody.querySelectorAll('.username').forEach(anchor => {
  81. updateAnchor(anchor);
  82. });
  83. };
  84.  
  85. /** @type {HTMLTableSectionElement} */
  86. let tbody = null;
  87.  
  88. const tableObserver = new MutationObserver(() => {
  89. updateTable(tbody);
  90. });
  91. const parentObserver = new MutationObserver(async () => {
  92. tbody = document.getElementById('standings-tbody');
  93. if (tbody) {
  94. parentObserver.disconnect();
  95. await fetchLangJson();
  96. updateTable(tbody);
  97. tableObserver.observe(
  98. tbody,
  99. { childList: true }
  100. )
  101. }
  102. });
  103. parentObserver.observe(
  104. document.getElementById('vue-standings'),
  105. { childList: true, subtree: true }
  106. );
  107. })();