AtCoderLanguageButtons

Add buttons to select language.

As of 2021-02-15. See the latest version.

  1. // ==UserScript==
  2. // @name AtCoderLanguageButtons
  3. // @namespace http://atcoder.jp/
  4. // @version 0.2.1
  5. // @description Add buttons to select language.
  6. // @author magurofly
  7. // @match https://atcoder.jp/contests/*/tasks/*
  8. // @match https://atcoder.jp/contests/*/submit*
  9. // @match https://atcoder.jp/contests/*/custom_test*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. const VERSION = "0.2.1";
  18.  
  19. // 設定があればロード
  20. const config = (() => {
  21. let buttons = new Set();
  22.  
  23. return {
  24. load() {
  25. const config_json = GM_getValue("config", "null");
  26. if (config_json) {
  27. try {
  28. const data = JSON.parse(config_json);
  29. buttons = new Set(data.buttons);
  30. } catch (e) {
  31. console.error("AtCoderLanguageButtons: Invalid JSON", config_json);
  32. }
  33. }
  34. },
  35.  
  36. save() {
  37. const data = { buttons: [...buttons], };
  38. const config_json = JSON.stringify(data);
  39. GM_setValue("config", config_json);
  40. console.info("AtCoderLanguageButtons: saved");
  41. },
  42.  
  43. isSet(lang) {
  44. return buttons.has(lang);
  45. },
  46.  
  47. set(lang, flag) {
  48. if (flag) {
  49. buttons.add(lang);
  50. } else {
  51. buttons.delete(lang);
  52. }
  53. },
  54. };
  55. })();
  56.  
  57. // 表示する
  58. const view = (() => {
  59. const modal = document.createElement("section");
  60. modal.className = "modal fade";
  61. modal.innerHTML = `
  62. <div class="modal-dialog">
  63. <div class="modal-content">
  64. <div class="modal-header">
  65. <button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
  66. <h4 class="modal-title">AtCoderLanguageButtons</h4>
  67. </div>
  68. <div class="modal-body">
  69. <p>ボタンを表示する言語を選択してください</p>
  70. <form id="atcoder-language-buttons-config">
  71. <div id="atcoder-language-buttons-config-languages"></div>
  72. </form>
  73. </div>
  74. </div>
  75. </div>
  76. `;
  77.  
  78. const choices = new Map();
  79. const group = document.createElement("span");
  80.  
  81. return {
  82. init() {
  83. this.initView();
  84. this.initConfigWindow();
  85. this.updateButtons();
  86. },
  87.  
  88. initView() {
  89. const container = document.getElementById("select-lang");
  90. container.appendChild(group);
  91.  
  92. const openButton = document.createElement("button");
  93. openButton.type = "button";
  94. openButton.className = "glyphicon glyphicon-cog";
  95. openButton.addEventListener("click", _e => {
  96. this.showConfig();
  97. });
  98. container.appendChild(openButton);
  99.  
  100. const select = $("#select-lang select[name='data.LanguageId']");
  101. select.on("change", _e => {
  102. this.highlightButtons();
  103. });
  104. },
  105.  
  106. initConfigWindow() {
  107. document.body.appendChild(modal);
  108. const langs = document.getElementById("atcoder-language-buttons-config-languages");
  109. for (const langOption of document.querySelectorAll("#select-lang option[value]")) {
  110. const checkbox = document.createElement("input");
  111. checkbox.type = "checkbox";
  112. checkbox.value = langOption.value;
  113. checkbox.addEventListener("change", _e => {
  114. config.set(checkbox.value, checkbox.checked);
  115. config.save();
  116. this.updateButtons();
  117. });
  118. choices.set(langOption.value, {checkbox, label: langOption.textContent});
  119.  
  120. const checkboxLabel = document.createElement("label");
  121. checkboxLabel.appendChild(checkbox);
  122. checkboxLabel.appendChild(document.createTextNode(langOption.textContent));
  123.  
  124. const checkboxGroup = document.createElement("div");
  125. checkboxGroup.className = "checkbox";
  126. checkboxGroup.appendChild(checkboxLabel);
  127.  
  128. langs.appendChild(checkboxGroup);
  129. }
  130. },
  131.  
  132. updateButtons() {
  133. $(group).empty();//for (const child of group.children) group.removeChild(child);
  134.  
  135. for (const [languageId, {label}] of choices.entries()) {
  136. if (!config.isSet(languageId)) continue;
  137. const button = document.createElement("button");
  138. button.type = "button";
  139. button.className = "btn btn-default";
  140. button.dataset.languageId = languageId;
  141. button.textContent = label;
  142. button.addEventListener("click", _e => {
  143. //TODO: Set languageId
  144. $("#select-lang select[name='data.LanguageId']").val(languageId).trigger("change");
  145. });
  146. group.appendChild(button);
  147. }
  148.  
  149. this.highlightButtons();
  150. },
  151.  
  152. highlightButtons() {
  153. const select = $("#select-lang select[name='data.LanguageId']");
  154. for (const button of group.children) {
  155. button.classList.remove("btn-default", "btn-success");
  156. if (select.val() == button.dataset.languageId) {
  157. button.classList.add("btn-success");
  158. } else {
  159. button.classList.add("btn-default");
  160. }
  161. }
  162. },
  163.  
  164. showConfig() {
  165. for (const [languageId, {checkbox}] of choices.entries()) {
  166. checkbox.checked = config.isSet(languageId);
  167. }
  168. $(modal).modal();
  169. },
  170. };
  171. })();
  172.  
  173. // 初期化
  174. config.load();
  175. view.init();
  176.  
  177. console.info(`AtCoderLanguageButtons v${VERSION}`);
  178. })();