Greasy Fork is available in English.

Atcoder Shorter Affiliation

AtCoderの順位表の「所属」列をスリムにするUserScript

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Atcoder Shorter Affiliation
// @namespace    https://github.com/e6nlaq/atcoder-shorter-affiliation
// @version      1.0.0
// @author       e6nlaq
// @description  AtCoderの順位表の「所属」列をスリムにするUserScript
// @license      MIT
// @copyright    (C) 2026 e6nlaq
// @match        https://atcoder.jp/contests/*/standings*
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// ==/UserScript==

(function () {
  'use strict';

  function ansiRegex({ onlyFirst = false } = {}) {
    const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
    const osc = `(?:\\u001B\\][\\s\\S]*?${ST})`;
    const csi = "[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
    const pattern = `${osc}|${csi}`;
    return new RegExp(pattern, onlyFirst ? void 0 : "g");
  }
  const regex = ansiRegex();
  function stripAnsi(string) {
    if (typeof string !== "string") {
      throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
    }
    if (!string.includes("\x1B") && !string.includes("›")) {
      return string;
    }
    return string.replace(regex, "");
  }
  const ambiguousRanges = [161, 161, 164, 164, 167, 168, 170, 170, 173, 174, 176, 180, 182, 186, 188, 191, 198, 198, 208, 208, 215, 216, 222, 225, 230, 230, 232, 234, 236, 237, 240, 240, 242, 243, 247, 250, 252, 252, 254, 254, 257, 257, 273, 273, 275, 275, 283, 283, 294, 295, 299, 299, 305, 307, 312, 312, 319, 322, 324, 324, 328, 331, 333, 333, 338, 339, 358, 359, 363, 363, 462, 462, 464, 464, 466, 466, 468, 468, 470, 470, 472, 472, 474, 474, 476, 476, 593, 593, 609, 609, 708, 708, 711, 711, 713, 715, 717, 717, 720, 720, 728, 731, 733, 733, 735, 735, 768, 879, 913, 929, 931, 937, 945, 961, 963, 969, 1025, 1025, 1040, 1103, 1105, 1105, 8208, 8208, 8211, 8214, 8216, 8217, 8220, 8221, 8224, 8226, 8228, 8231, 8240, 8240, 8242, 8243, 8245, 8245, 8251, 8251, 8254, 8254, 8308, 8308, 8319, 8319, 8321, 8324, 8364, 8364, 8451, 8451, 8453, 8453, 8457, 8457, 8467, 8467, 8470, 8470, 8481, 8482, 8486, 8486, 8491, 8491, 8531, 8532, 8539, 8542, 8544, 8555, 8560, 8569, 8585, 8585, 8592, 8601, 8632, 8633, 8658, 8658, 8660, 8660, 8679, 8679, 8704, 8704, 8706, 8707, 8711, 8712, 8715, 8715, 8719, 8719, 8721, 8721, 8725, 8725, 8730, 8730, 8733, 8736, 8739, 8739, 8741, 8741, 8743, 8748, 8750, 8750, 8756, 8759, 8764, 8765, 8776, 8776, 8780, 8780, 8786, 8786, 8800, 8801, 8804, 8807, 8810, 8811, 8814, 8815, 8834, 8835, 8838, 8839, 8853, 8853, 8857, 8857, 8869, 8869, 8895, 8895, 8978, 8978, 9312, 9449, 9451, 9547, 9552, 9587, 9600, 9615, 9618, 9621, 9632, 9633, 9635, 9641, 9650, 9651, 9654, 9655, 9660, 9661, 9664, 9665, 9670, 9672, 9675, 9675, 9678, 9681, 9698, 9701, 9711, 9711, 9733, 9734, 9737, 9737, 9742, 9743, 9756, 9756, 9758, 9758, 9792, 9792, 9794, 9794, 9824, 9825, 9827, 9829, 9831, 9834, 9836, 9837, 9839, 9839, 9886, 9887, 9919, 9919, 9926, 9933, 9935, 9939, 9941, 9953, 9955, 9955, 9960, 9961, 9963, 9969, 9972, 9972, 9974, 9977, 9979, 9980, 9982, 9983, 10045, 10045, 10102, 10111, 11094, 11097, 12872, 12879, 57344, 63743, 65024, 65039, 65533, 65533, 127232, 127242, 127248, 127277, 127280, 127337, 127344, 127373, 127375, 127376, 127387, 127404, 917760, 917999, 983040, 1048573, 1048576, 1114109];
  const fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510];
  const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101589, 101631, 101662, 101760, 101874, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110882, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110951, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128728, 128732, 128735, 128747, 128748, 128756, 128764, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129674, 129678, 129734, 129736, 129736, 129741, 129756, 129759, 129770, 129775, 129784, 131072, 196605, 196608, 262141];
  const isInRange = (ranges, codePoint) => {
    let low = 0;
    let high = Math.floor(ranges.length / 2) - 1;
    while (low <= high) {
      const mid = Math.floor((low + high) / 2);
      const i = mid * 2;
      if (codePoint < ranges[i]) {
        high = mid - 1;
      } else if (codePoint > ranges[i + 1]) {
        low = mid + 1;
      } else {
        return true;
      }
    }
    return false;
  };
  const minimumAmbiguousCodePoint = ambiguousRanges[0];
  const maximumAmbiguousCodePoint = ambiguousRanges.at(-1);
  const minimumFullWidthCodePoint = fullwidthRanges[0];
  const maximumFullWidthCodePoint = fullwidthRanges.at(-1);
  const minimumWideCodePoint = wideRanges[0];
  const maximumWideCodePoint = wideRanges.at(-1);
  const commonCjkCodePoint = 19968;
  const [wideFastPathStart, wideFastPathEnd] = findWideFastPathRange(wideRanges);
  function findWideFastPathRange(ranges) {
    let fastPathStart = ranges[0];
    let fastPathEnd = ranges[1];
    for (let index = 0; index < ranges.length; index += 2) {
      const start = ranges[index];
      const end = ranges[index + 1];
      if (commonCjkCodePoint >= start && commonCjkCodePoint <= end) {
        return [start, end];
      }
      if (end - start > fastPathEnd - fastPathStart) {
        fastPathStart = start;
        fastPathEnd = end;
      }
    }
    return [fastPathStart, fastPathEnd];
  }
  const isAmbiguous = (codePoint) => {
    if (codePoint < minimumAmbiguousCodePoint || codePoint > maximumAmbiguousCodePoint) {
      return false;
    }
    return isInRange(ambiguousRanges, codePoint);
  };
  const isFullWidth = (codePoint) => {
    if (codePoint < minimumFullWidthCodePoint || codePoint > maximumFullWidthCodePoint) {
      return false;
    }
    return isInRange(fullwidthRanges, codePoint);
  };
  const isWide = (codePoint) => {
    if (codePoint >= wideFastPathStart && codePoint <= wideFastPathEnd) {
      return true;
    }
    if (codePoint < minimumWideCodePoint || codePoint > maximumWideCodePoint) {
      return false;
    }
    return isInRange(wideRanges, codePoint);
  };
  function validate(codePoint) {
    if (!Number.isSafeInteger(codePoint)) {
      throw new TypeError(`Expected a code point, got \`${typeof codePoint}\`.`);
    }
  }
  function eastAsianWidth(codePoint, { ambiguousAsWide = false } = {}) {
    validate(codePoint);
    if (isFullWidth(codePoint) || isWide(codePoint) || ambiguousAsWide && isAmbiguous(codePoint)) {
      return 2;
    }
    return 1;
  }
  const segmenter = new Intl.Segmenter();
  const zeroWidthClusterRegex = new RegExp("^(?:\\p{Default_Ignorable_Code_Point}|\\p{Control}|\\p{Format}|\\p{Mark}|\\p{Surrogate})+$", "v");
  const leadingNonPrintingRegex = new RegExp("^[\\p{Default_Ignorable_Code_Point}\\p{Control}\\p{Format}\\p{Mark}\\p{Surrogate}]+", "v");
  const rgiEmojiRegex = new RegExp("^\\p{RGI_Emoji}$", "v");
  const unqualifiedKeycapRegex = /^[\d#*]\u20E3$/;
  const extendedPictographicRegex = new RegExp("\\p{Extended_Pictographic}", "gu");
  function isDoubleWidthNonRgiEmojiSequence(segment) {
    if (segment.length > 50) {
      return false;
    }
    if (unqualifiedKeycapRegex.test(segment)) {
      return true;
    }
    if (segment.includes("‍")) {
      const pictographics = segment.match(extendedPictographicRegex);
      return pictographics !== null && pictographics.length >= 2;
    }
    return false;
  }
  function baseVisible(segment) {
    return segment.replace(leadingNonPrintingRegex, "");
  }
  function isZeroWidthCluster(segment) {
    return zeroWidthClusterRegex.test(segment);
  }
  function trailingHalfwidthWidth(segment, eastAsianWidthOptions) {
    let extra = 0;
    if (segment.length > 1) {
      for (const char of segment.slice(1)) {
        if (char >= "＀" && char <= "￯") {
          extra += eastAsianWidth(char.codePointAt(0), eastAsianWidthOptions);
        }
      }
    }
    return extra;
  }
  function stringWidth(input, options = {}) {
    if (typeof input !== "string" || input.length === 0) {
      return 0;
    }
    const {
      ambiguousIsNarrow = true,
      countAnsiEscapeCodes = false
    } = options;
    let string = input;
    if (!countAnsiEscapeCodes && (string.includes("\x1B") || string.includes("›"))) {
      string = stripAnsi(string);
    }
    if (string.length === 0) {
      return 0;
    }
    if (/^[\u0020-\u007E]*$/.test(string)) {
      return string.length;
    }
    let width = 0;
    const eastAsianWidthOptions = { ambiguousAsWide: !ambiguousIsNarrow };
    for (const { segment } of segmenter.segment(string)) {
      if (isZeroWidthCluster(segment)) {
        continue;
      }
      if (rgiEmojiRegex.test(segment) || isDoubleWidthNonRgiEmojiSequence(segment)) {
        width += 2;
        continue;
      }
      const codePoint = baseVisible(segment).codePointAt(0);
      width += eastAsianWidth(codePoint, eastAsianWidthOptions);
      width += trailingHalfwidthWidth(segment, eastAsianWidthOptions);
    }
    return width;
  }
  var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
  var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  function applyTruncation() {
    const maxLength = _GM_getValue("maxLength", 32);
    const spans = document.querySelectorAll(
      ".standings-affiliation"
    );
    for (const span of Array.from(spans)) {
      if (!span.dataset.originalText) {
        span.dataset.originalText = span.textContent || "";
      }
      const text = span.dataset.originalText;
      const currentWidth = stringWidth(text);
      if (currentWidth > maxLength) {
        let truncatedText = "";
        let runningWidth = 0;
        const ellipsis = "…";
        const ellipsisWidth = stringWidth(ellipsis);
        const targetWidth = maxLength - ellipsisWidth;
        for (const char of text) {
          const charWidth = stringWidth(char);
          if (runningWidth + charWidth > targetWidth) break;
          runningWidth += charWidth;
          truncatedText += char;
        }
        const newText = truncatedText + ellipsis;
        if (span.textContent !== newText) {
          span.textContent = newText;
          span.title = text;
        }
      } else {
        if (span.textContent !== text) {
          span.textContent = text;
          span.removeAttribute("title");
        }
      }
    }
  }
  function init() {
    _GM_registerMenuCommand("表示幅の設定 (AtCoder Shorter Affiliation)", () => {
      const currentN = _GM_getValue("maxLength", 32);
      const input = prompt(
        "所属の表示を省略する表示幅(半角基準)を入力してください:",
        String(currentN)
      );
      if (input !== null) {
        const newN = parseInt(input, 10);
        if (!Number.isNaN(newN) && newN > 0) {
          _GM_setValue("maxLength", newN);
          applyTruncation();
        } else {
          alert("有効な正整数を入力してください。");
        }
      }
    });
    const observer = new MutationObserver(() => {
      applyTruncation();
    });
    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
    applyTruncation();
  }
  init();

})();