ac-hide-official-rating-icon

AtCoder公式のレーティングアイコンを非表示にできるようにする拡張機能

  1. // ==UserScript==
  2. // @name ac-hide-official-rating-icon
  3. // @namespace https://atcoder.jp/
  4. // @version 1.02
  5. // @description AtCoder公式のレーティングアイコンを非表示にできるようにする拡張機能
  6. // @author konchanksu
  7. // @license MIT
  8. // @match https://atcoder.jp/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. const SHOW_RATING_ICON_FG = 'showRatingIconFg';
  13. const SHOW_RATING_ICON_STANDINGS_FG = 'showRatingIconProfileFg';
  14. const CHECKBOX_STATE_KEY = 'ac-hide-rating-icon-config';
  15. class LocalStorageController {
  16. constructor() {
  17. this.changeState = true;
  18. if (!localStorage.hasOwnProperty(CHECKBOX_STATE_KEY)) {
  19. this.saveRadioState({
  20. showRatingIconFg: false,
  21. showRatingIconProfileFg: false,
  22. });
  23. }
  24. }
  25. saveRadioState(flags) {
  26. this.changeState = true;
  27. localStorage.setItem(CHECKBOX_STATE_KEY, JSON.stringify(flags));
  28. }
  29. getRadioState() {
  30. if (this.changeState) {
  31. const jsonString = localStorage.getItem(CHECKBOX_STATE_KEY);
  32. const object = JSON.parse(jsonString);
  33. const needSave = !object.hasOwnProperty(SHOW_RATING_ICON_FG) ||
  34. !object.hasOwnProperty(SHOW_RATING_ICON_STANDINGS_FG);
  35. this.showRatingIconFg = object.hasOwnProperty(SHOW_RATING_ICON_FG)
  36. ? object[SHOW_RATING_ICON_FG]
  37. : true;
  38. this.showRatingIconProfileFg = object.hasOwnProperty(SHOW_RATING_ICON_STANDINGS_FG)
  39. ? object[SHOW_RATING_ICON_STANDINGS_FG]
  40. : true;
  41. if (needSave) {
  42. this.saveRadioState({
  43. showRatingIconFg: this.showRatingIconFg,
  44. showRatingIconProfileFg: this.showRatingIconProfileFg,
  45. });
  46. this.changeState = false;
  47. }
  48. }
  49. return {
  50. showRatingIconFg: this.showRatingIconFg,
  51. showRatingIconProfileFg: this.showRatingIconProfileFg,
  52. };
  53. }
  54. }
  55.  
  56. const RATING_ICON_CLASSES = [
  57. 'user-rating-stage-l',
  58. 'user-rating-stage-m',
  59. 'user-rating-stage-s',
  60. ];
  61. const CURRENT_LANGUAGE = (() => {
  62. const dropdown_toggle = document.getElementsByClassName('dropdown-toggle');
  63. const isIncludeEn = Array.prototype.filter.call(dropdown_toggle, (element) => element.textContent.includes('English')).length !== 0;
  64. return isIncludeEn ? 'EN' : 'JA'; // default JA
  65. })();
  66. const IS_CURRENT_LANGUAGE_JA = CURRENT_LANGUAGE === 'JA';
  67. const CONFIG_DROPDOWN_JA = {
  68. title: ' ac-hide-icon 設定',
  69. radio: [
  70. {
  71. title: 'レーティングアイコンを非表示にしない',
  72. id: 'any',
  73. showRatingIconFg: true,
  74. showRatingIconProfileFg: true,
  75. },
  76. {
  77. title: 'プロフィールページのみ非表示にする',
  78. id: 'only-not-standings',
  79. showRatingIconFg: true,
  80. showRatingIconProfileFg: false,
  81. },
  82. {
  83. title: 'レーティングアイコンを全て非表示にする',
  84. id: 'all',
  85. showRatingIconFg: false,
  86. showRatingIconProfileFg: false,
  87. },
  88. ],
  89. };
  90. const CONFIG_DROPDOWN_EN = {
  91. title: ' ac-hide-icon',
  92. radio: [
  93. {
  94. title: 'show the rating icon',
  95. id: 'any',
  96. showRatingIconFg: true,
  97. showRatingIconProfileFg: true,
  98. },
  99. {
  100. title: 'hide the rating icon only on the profile page',
  101. id: 'only-not-standings',
  102. showRatingIconFg: true,
  103. showRatingIconProfileFg: false,
  104. },
  105. {
  106. title: 'hide the rating icon',
  107. id: 'all',
  108. showRatingIconFg: false,
  109. showRatingIconProfileFg: false,
  110. },
  111. ],
  112. };
  113. const CONFIG_DROPDOWN = IS_CURRENT_LANGUAGE_JA
  114. ? CONFIG_DROPDOWN_JA
  115. : CONFIG_DROPDOWN_EN;
  116. const MODAL_HTML_BASE = `<div id="modal-ac-hide-icon-settings" class="modal fade" tabindex="-1" role="dialog">
  117. <div class="modal-dialog" role="document">
  118. <div class="modal-content">
  119. <div class="modal-header">
  120. <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  121. <span aria-hidden="true">×</span>
  122. </button>
  123. <h4 class="modal-title">
  124. ${CONFIG_DROPDOWN['title']}
  125. </h4>
  126. </div>
  127. <div class="modal-body">
  128. <div class="container-fluid">
  129. <div class="settings-row row" id="ac-hide-rating-icon-radio">
  130. </div>
  131. </div>
  132. </div>
  133. <div class="modal-footer">
  134. <button type="button" class="btn btn-default" data-dismiss="modal">close</button>
  135. </div>
  136. </div>
  137. </div>`;
  138. const RADIO_HTML_BASE = (text, id) => `<div class="radio">
  139. <label>\
  140. <input type="radio" name="ac-hide-rating-icon-config" id="ac-hide-rating-icon-${id}" required> ${text}
  141. </label>
  142. </div>`;
  143. const localStorageController = new LocalStorageController();
  144. function isDropDownMenu() {
  145. return (document.getElementsByClassName('header-mypage_btn').length === 0 ||
  146. CURRENT_LANGUAGE === 'EN');
  147. }
  148. function createRadio() {
  149. const radioState = localStorageController.getRadioState();
  150. CONFIG_DROPDOWN['radio'].forEach((element) => {
  151. var _a;
  152. const { title, id, showRatingIconFg, showRatingIconProfileFg } = element;
  153. const radio = RADIO_HTML_BASE(title, id);
  154. (_a = document
  155. .querySelector('#ac-hide-rating-icon-radio')) === null || _a === void 0 ? void 0 : _a.insertAdjacentHTML('afterbegin', radio);
  156. const currentRadio = document.getElementById(`ac-hide-rating-icon-${id}`);
  157. currentRadio.addEventListener('click', () => {
  158. localStorageController.saveRadioState({
  159. showRatingIconFg,
  160. showRatingIconProfileFg,
  161. });
  162. currentRadio.checked = true;
  163. checkAndHideIcons();
  164. });
  165. if (radioState['showRatingIconFg'] === showRatingIconFg &&
  166. radioState['showRatingIconProfileFg'] === showRatingIconProfileFg) {
  167. currentRadio.checked = true;
  168. }
  169. });
  170. }
  171. function createModal() {
  172. var _a;
  173. (_a = document
  174. .querySelector('body')) === null || _a === void 0 ? void 0 : _a.insertAdjacentHTML('afterbegin', MODAL_HTML_BASE);
  175. createRadio();
  176. }
  177. function controlIcons(hideFg) {
  178. RATING_ICON_CLASSES.forEach((ratingIconClass) => {
  179. const ratingIcons = document.getElementsByClassName(ratingIconClass);
  180. Array.prototype.forEach.call(ratingIcons, (element) => {
  181. element.style.display = hideFg ? 'none' : 'unset';
  182. });
  183. });
  184. }
  185. function showHeaderSettingForDropDown() {
  186. const headerMyPageList = document.getElementsByClassName('dropdown-menu')[1];
  187. const newElement = createHeaderSettingElementForDropDown();
  188. const positionIndex = 6;
  189. if (positionIndex >= headerMyPageList.children.length) {
  190. headerMyPageList.appendChild(newElement);
  191. }
  192. else {
  193. headerMyPageList.insertBefore(newElement, headerMyPageList.children[positionIndex]);
  194. }
  195. }
  196. function showHeaderSetting() {
  197. const headerMyPageList = document.getElementsByClassName('header-mypage_list')[0];
  198. const newElement = createHeaderSettingElement();
  199. const positionIndex = 5;
  200. if (headerMyPageList) {
  201. if (positionIndex >= headerMyPageList.children.length) {
  202. headerMyPageList.appendChild(newElement);
  203. }
  204. else {
  205. headerMyPageList.insertBefore(newElement, headerMyPageList.children[positionIndex]);
  206. }
  207. }
  208. }
  209. function createGlyphicon() {
  210. const innerSpanTag = document.createElement('span');
  211. ['glyphicon', 'glyphicon-wrench'].forEach((tag) => innerSpanTag.classList.add(tag));
  212. return innerSpanTag;
  213. }
  214. function createIcon() {
  215. const innerITag = document.createElement('i');
  216. ['a-icon', 'a-icon-setting'].forEach((tag) => innerITag.classList.add(tag));
  217. return innerITag;
  218. }
  219. function createHeaderATag() {
  220. const aTag = document.createElement('a');
  221. const text = document.createTextNode(CONFIG_DROPDOWN['title']);
  222. aTag.appendChild(text);
  223. aTag.setAttribute('data-toggle', 'modal');
  224. aTag.setAttribute('data-target', '#modal-ac-hide-icon-settings');
  225. return aTag;
  226. }
  227. function createHeaderSettingElementForDropDown() {
  228. const element = document.createElement('li');
  229. const innerATag = createHeaderATag();
  230. const innerSpanTag = createGlyphicon();
  231. element.appendChild(innerATag);
  232. innerATag.insertBefore(innerSpanTag, innerATag.firstChild);
  233. return element;
  234. }
  235. function createHeaderSettingElement() {
  236. const element = document.createElement('li');
  237. const innerATag = createHeaderATag();
  238. const innerTag = IS_CURRENT_LANGUAGE_JA ? createIcon() : createGlyphicon();
  239. element.appendChild(innerATag);
  240. innerATag.insertBefore(innerTag, innerATag.firstChild);
  241. return element;
  242. }
  243. function observeLoadingHideClassForStandings(hide) {
  244. const target = document.getElementsByClassName('loading-hide');
  245. if (target) {
  246. const observer = new MutationObserver(() => {
  247. // 読み込み後は standings-tbody に順位情報が格納されるため、それを参照する
  248. observeStandings(hide);
  249. controlIcons(hide);
  250. });
  251. observer.observe(target[1], {
  252. childList: true,
  253. });
  254. observeStandings(hide);
  255. }
  256. }
  257. function observeStandings(hide) {
  258. const target = document.getElementById('standings-tbody');
  259. if (target) {
  260. const observer = new MutationObserver(() => {
  261. controlIcons(hide);
  262. });
  263. observer.observe(target, {
  264. childList: true,
  265. });
  266. }
  267. }
  268. function checkAndHideIcons() {
  269. const { showRatingIconFg, showRatingIconProfileFg } = localStorageController.getRadioState();
  270. const url = window.location.href;
  271. controlIcons(!showRatingIconFg ||
  272. (url.match(/.*users.*/) !== null && !showRatingIconProfileFg));
  273. if (url.match(/.*standings/g)) {
  274. observeLoadingHideClassForStandings(!showRatingIconFg);
  275. }
  276. }
  277. function main() {
  278. checkAndHideIcons();
  279. // 設定作る系
  280. createModal();
  281. if (isDropDownMenu()) {
  282. showHeaderSettingForDropDown();
  283. }
  284. else {
  285. showHeaderSetting();
  286. }
  287. }
  288. (function () {
  289. if (document.readyState === 'loading') {
  290. document.addEventListener('DOMContentLoaded', () => main());
  291. }
  292. else {
  293. main();
  294. }
  295. })();
  296. //# sourceMappingURL=index.js.map

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址