Greasy Fork is available in English.

AtCoder Copy Contest ID

Add a button to copy the contest ID to the clipboard in AtCoder contest pages

  1. // ==UserScript==
  2. // @name AtCoder Copy Contest ID
  3. // @name:ja AtCoder Copy Contest ID
  4. // @namespace https://github.com/xe-o
  5. // @version 0.4
  6. // @description Add a button to copy the contest ID to the clipboard in AtCoder contest pages
  7. // @description:ja AtCoderコンテストページのナビゲーションバーへ、コンテストIDをコピーするためのボタンを追加します
  8. // @author XERO
  9. // @license MIT
  10. // @match https://atcoder.jp/*
  11. // @grant GM_setClipboard
  12. // @run-at document-idle
  13. // ==/UserScript==
  14.  
  15. const INIT_LABEL = "Copy Contest ID";
  16. const COPIED_LABEL = "Copied!";
  17. const FAILED_LABEL = "Failed to copy";
  18. const ICONS = {
  19. COPY: "glyphicon-copy",
  20. OK: "glyphicon-ok",
  21. REMOVE: "glyphicon-remove",
  22. };
  23.  
  24. const copyContestId = () => {
  25. const $ = (selector, baseElement = document) =>
  26. baseElement.querySelector(selector);
  27. const getContestId = () => window.location.pathname.split("/")[2];
  28. const navbar = $(".navbar-nav");
  29. const style = document.createElement("style");
  30. style.innerHTML = `
  31. @media (max-width: 991px) {
  32. .contest-title {
  33. width: auto;
  34. }
  35. }
  36. @media (max-width: 1150px) {
  37. .contest-title {
  38. width: auto;
  39. max-width: 580px;
  40. overflow: hidden;
  41. white-space: nowrap;
  42. text-overflow: ellipsis;
  43. }
  44. #copy-button-text {
  45. display: none;
  46. }
  47. }
  48. @media (max-width: 1000px) {
  49. .contest-title {
  50. width: auto;
  51. max-width: 300px;
  52. overflow: hidden;
  53. white-space: nowrap;
  54. text-overflow: ellipsis;
  55. }
  56. }
  57.  
  58. @media (max-width: 767px) {
  59. .contest-title {
  60. width: auto;
  61. max-width: none;
  62. }
  63. #copy-button-text {
  64. display: inline;
  65. }
  66. }
  67.  
  68. .header-nav .j-menu_gnav a {
  69. overflow: hidden;
  70. white-space: nowrap;
  71. text-overflow: ellipsis;
  72. }
  73. `;
  74.  
  75. const addCopyButton = () => {
  76. if (!navbar) {
  77. console.error("Failed to find navbar element.");
  78. return;
  79. }
  80. navbar.insertAdjacentHTML(
  81. "beforeend",
  82. `
  83. <li>
  84. <a id="copy-contest-id-button" style="cursor: pointer; font-size: 12px">
  85. <span class="glyphicon ${ICONS.COPY}" style="margin-right: 2px" aria-hidden="true"></span>
  86. <span id="copy-button-text">${INIT_LABEL}</span>
  87. </a>
  88. </li>
  89. `
  90. );
  91. };
  92.  
  93. const setupCopyButton = () => {
  94. const button = $("#copy-contest-id-button");
  95. const icon = $(".glyphicon", button);
  96. const label = $("#copy-button-text");
  97. let isCopying = false;
  98.  
  99. const resetButton = () => {
  100. icon.classList.replace(ICONS.OK, ICONS.COPY, ICONS.REMOVE);
  101. label.textContent = INIT_LABEL;
  102. isCopying = false;
  103. };
  104.  
  105. const copyToClipboard = async () => {
  106. if (isCopying) return;
  107. isCopying = true;
  108.  
  109. try {
  110. await GM_setClipboard(getContestId(), {
  111. type: "text",
  112. mimetype: "text/plain",
  113. });
  114. icon.classList.replace(ICONS.COPY, ICONS.OK);
  115. label.textContent = COPIED_LABEL;
  116. } catch (error) {
  117. console.error(`Failed to copy contest ID: ${error}`);
  118. icon.classList.replace(ICONS.COPY, ICONS.REMOVE);
  119. label.textContent = FAILED_LABEL;
  120. } finally {
  121. setTimeout(resetButton, 1800);
  122. }
  123. };
  124.  
  125. button.addEventListener("click", copyToClipboard);
  126. };
  127.  
  128. document.head.appendChild(style);
  129. addCopyButton();
  130. setupCopyButton();
  131. };
  132.  
  133. copyContestId();