AtCoder Formatter

AtCoder の解説コードなどをフォーマットできるようにします.

Tính đến 14-11-2022. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name AtCoder Formatter
  3. // @name:en AtCoder Formatter
  4. // @namespace
  5. // @version 1.4.1
  6. // @description AtCoder の解説コードなどをフォーマットできるようにします.
  7. // @description:en Add formatting buttons to source codes on AtCoder.
  8. // @author kichi2004
  9. // @match https://atcoder.jp/contests/*
  10. // @grant none
  11. // @namespace kichi2004.jp
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. async function request(code, lang) {
  18. const res = await fetch(`https://formatter.api.kichi2004.jp/format?lang=${lang}`, {
  19. body: code,
  20. method: 'POST'
  21. })
  22. if (!res.ok) {
  23. alert('Formatting Request Failed!')
  24. return
  25. }
  26. const json = await res.json()
  27. if (json['status'] === 'error') {
  28. alert('Formatting Error!\n' + json['error'])
  29. return
  30. }
  31. return json['result']
  32. }
  33.  
  34. function createButtons(className, id) {
  35. return `
  36. <div class="btn-group" role="group">
  37. <button type="button" class="btn ${className} btn-sm" title="C/C++ のソースコードをフォーマット" id="${id}-fmt-cpp">
  38. C/C++
  39. </button>
  40. <button type="button" class="btn ${className} btn-sm" title="Python のソースコードをフォーマット" id="${id}-fmt-py">
  41. Python
  42. </button>
  43. <button type="button" class="btn ${className} btn-sm" title="C# のソースコードをフォーマット" id="${id}-fmt-cs">
  44. C#
  45. </button>
  46. </div>`;
  47. }
  48. const SOURCE_ID = 'source'
  49.  
  50. ;(async function () {
  51. const showModal = (id, formatInner) => {
  52. if (!document.getElementById(`modal-${id}-format-warning`)) {
  53. document.createht
  54. document.body.insertAdjacentHTML('afterbegin', `
  55. <div id="modal-${id}-format-warning" class="modal fade" tabindex="-1" role="dialog">
  56. <div class="modal-dialog" role="document">
  57. <div class="modal-content">
  58. <div class="modal-header">
  59. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  60. <h4 class="modal-title">フォーマットの注意</h4>
  61. </div>
  62. <div class="modal-body">
  63. <p>このコンテストはまだ終了していません。</p>
  64. <p>フォーマットを行うとソースコードが外部に送信され、スクリプトの作成者が閲覧可能な状態になります。</p>
  65. <p>まだ公開されていないコンテストではフォーマットを行わないでください。</p>
  66. </div>
  67. <div class="modal-footer">
  68. <button class="btn btn-warning" id="${id}-force-format">フォーマットする</button>
  69. <button class="btn btn-success" id="${id}-cancel">キャンセル</button>
  70. </div>
  71. </div>
  72. </div>
  73. </div>
  74. `)
  75.  
  76. document.getElementById(`${id}-force-format`).addEventListener('click', async () => await formatInner(true))
  77. document.getElementById(`${id}-cancel`).addEventListener('click', () => removeModal(id))
  78. }
  79.  
  80. $(`#modal-${id}-format-warning`).modal('show')
  81. }
  82. const removeModal = (id) => {
  83. const modal = $(`#modal-${id}-format-warning`)
  84. modal.modal('hide')
  85. }
  86. const formatCode = async (event, pre, id, lang) => {
  87. const formatInner = async (modal = false) => {
  88. if (modal) removeModal(id)
  89.  
  90. event.target.disabled = true
  91. const data = document.getElementById(id)
  92. const result = await request(data.innerText, lang, event)
  93. event.target.disabled = false
  94. if (!result) return
  95.  
  96. const nextPre = document.createElement('pre')
  97. nextPre.textContent = result
  98. nextPre.classList.add('prettyprint', `lang-${lang}`, 'linenums')
  99. pre.before(nextPre)
  100. const preId = pre.id
  101. pre.remove()
  102. if (preId) {
  103. nextPre.id = preId
  104. }
  105.  
  106. data.textContent = result
  107. PR.prettyPrint()
  108. }
  109.  
  110. const finished = endTime.toDate() < new Date()
  111. if (finished) {
  112. await formatInner()
  113. return
  114. }
  115.  
  116. showModal(id, formatInner)
  117. }
  118.  
  119. const formatTextArea = async (event, lang) => {
  120. const formatInner = async (modal = false) => {
  121. if (modal) removeModal(SOURCE_ID)
  122.  
  123. event.target.disabled = true
  124. const sw = $(".editor-buttons > p:nth-child(2) > button")
  125. const active = sw.attr('aria-pressed') === 'true'
  126.  
  127. const textarea = sourceCodeDiv.children('textarea.plain-textarea')
  128.  
  129. if (!active) sw.trigger('click')
  130. const code = textarea.val()
  131. if (!active) sw.trigger('click')
  132.  
  133. const result = await request(code, lang, event)
  134. event.target.disabled = false
  135. if (!result) return
  136.  
  137. if (!active) sw.trigger('click')
  138. textarea.val(result)
  139. if (!active) sw.trigger('click')
  140. }
  141.  
  142. const finished = endTime.toDate() < new Date()
  143. if (finished) {
  144. await formatInner()
  145. return
  146. }
  147.  
  148. showModal(SOURCE_ID, formatInner)
  149. }
  150.  
  151. const buttonClass = endTime.toDate() < new Date() ? 'btn-info' : 'btn-danger'
  152.  
  153. for (const pre of document.getElementsByClassName('prettyprint')) {
  154. const next = pre.nextElementSibling
  155. if (next.className !== 'source-code-for-copy') continue
  156. const id = next.id
  157.  
  158. let adding = pre.previousElementSibling
  159. while (adding.className === 'div-btn-copy')
  160. adding = adding.previousElementSibling
  161.  
  162. adding.insertAdjacentHTML('afterend',createButtons(buttonClass, id))
  163. for (const lang of ['cpp', 'py', 'cs']) {
  164. document.getElementById(`${id}-fmt-${lang}`)
  165. .addEventListener('click', async (e) => await formatCode(e, pre, id, lang))
  166. }
  167. }
  168.  
  169. const sourceCodeDiv = $('#sourceCode')
  170. if (!sourceCodeDiv) return
  171.  
  172. $('.editor-buttons')[0].insertAdjacentHTML('beforeend', createButtons(buttonClass, SOURCE_ID))
  173. for (const lang of ['cpp', 'py', 'cs']) {
  174. document.getElementById(`source-fmt-${lang}`)
  175. .addEventListener('click', async (e) => await formatTextArea(e, lang))
  176. }
  177. })()