Greasy Fork is available in English.

AtCoder Formatter

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

  1. // ==UserScript==
  2. // @name AtCoder Formatter
  3. // @name:en AtCoder Formatter
  4. // @namespace
  5. // @version 1.5.0
  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.body.insertAdjacentHTML('afterbegin', `
  54. <div id="modal-${id}-format-warning" class="modal fade" tabindex="-1" role="dialog">
  55. <div class="modal-dialog" role="document">
  56. <div class="modal-content">
  57. <div class="modal-header">
  58. <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  59. <h4 class="modal-title">フォーマットの注意</h4>
  60. </div>
  61. <div class="modal-body">
  62. <p>このコンテストはまだ終了していません。</p>
  63. <p>フォーマットを行うとソースコードが外部に送信され、スクリプトの作成者が閲覧可能な状態になります。</p>
  64. <p>まだ公開されていないコンテストではフォーマットを行わないでください。</p>
  65. </div>
  66. <div class="modal-footer">
  67. <button class="btn btn-warning" id="${id}-force-format">フォーマットする</button>
  68. <button class="btn btn-success" id="${id}-cancel">キャンセル</button>
  69. </div>
  70. </div>
  71. </div>
  72. </div>
  73. `)
  74.  
  75. document.getElementById(`${id}-force-format`).addEventListener('click', async () => await formatInner(true))
  76. document.getElementById(`${id}-cancel`).addEventListener('click', () => removeModal(id))
  77. }
  78.  
  79. $(`#modal-${id}-format-warning`).modal('show')
  80. }
  81. const removeModal = (id) => {
  82. const modal = $(`#modal-${id}-format-warning`)
  83. modal.modal('hide')
  84. }
  85. const formatCode = async (event, pre, id, lang) => {
  86. const formatInner = async (modal = false) => {
  87. if (modal) removeModal(id)
  88.  
  89. event.target.disabled = true
  90. const data = document.getElementById(id)
  91. const result = await request(data.innerText, lang, event)
  92. event.target.disabled = false
  93. if (!result) return
  94.  
  95. if (pre.classList.contains('ace_editor')) {
  96. /** @type {AceAjax.Editor} */
  97. const editor = pre.env.editor
  98. editor.setValue(result)
  99. editor.clearSelection()
  100. return
  101. }
  102.  
  103. const nextPre = document.createElement('pre')
  104. nextPre.textContent = result
  105. nextPre.classList.add('prettyprint', `lang-${lang}`, 'linenums')
  106. pre.before(nextPre)
  107. const preId = pre.id
  108. pre.remove()
  109. if (preId) {
  110. nextPre.id = preId
  111. }
  112.  
  113. data.textContent = result
  114. PR.prettyPrint()
  115. }
  116.  
  117. const finished = endTime.toDate() < new Date()
  118. if (finished) {
  119. await formatInner()
  120. return
  121. }
  122.  
  123. showModal(id, formatInner)
  124. }
  125.  
  126. const formatTextArea = async (event, lang) => {
  127. const formatInner = async (modal = false) => {
  128. if (modal) removeModal(SOURCE_ID)
  129.  
  130. event.target.disabled = true
  131. const sw = $(".editor-buttons > button:nth-child(3)")
  132. const active = sw.attr('aria-pressed') === 'true'
  133.  
  134. const textarea = sourceCodeDiv.children('textarea#plain-textarea')
  135.  
  136.  
  137. if (!active) sw.trigger('click')
  138. const code = textarea.val()
  139. if (!active) sw.trigger('click')
  140.  
  141. const result = await request(code, lang, event)
  142. event.target.disabled = false
  143. if (!result) return
  144.  
  145. if (!active) sw.trigger('click')
  146. textarea.val(result)
  147. if (!active) sw.trigger('click')
  148. }
  149.  
  150. const finished = endTime.toDate() < new Date()
  151. if (finished) {
  152. await formatInner()
  153. return
  154. }
  155.  
  156. showModal(SOURCE_ID, formatInner)
  157. }
  158.  
  159. const buttonClass = endTime.toDate() < new Date() ? 'btn-info' : 'btn-danger'
  160.  
  161. const prePrettyPrint = Array.from(
  162. document.getElementsByClassName('prettyprint')
  163. )
  164. const preAceEditor = Array.from(
  165. document.getElementsByClassName('ace_editor')
  166. )
  167. for (const pre of prePrettyPrint.concat(preAceEditor)) {
  168. const next = pre.nextElementSibling
  169. if (next.className !== 'source-code-for-copy') continue
  170. const id = next.id
  171.  
  172. let adding = pre.previousElementSibling
  173. while (adding.className === 'div-btn-copy')
  174. adding = adding.previousElementSibling
  175.  
  176. adding.insertAdjacentHTML('afterend', createButtons(buttonClass, id))
  177. for (const lang of ['cpp', 'py', 'cs']) {
  178. document.getElementById(`${id}-fmt-${lang}`)
  179. .addEventListener('click', async (e) => await formatCode(e, pre, id, lang))
  180. }
  181. }
  182.  
  183. const sourceCodeDiv = $('#sourceCode')
  184. if (!sourceCodeDiv) return
  185.  
  186. $('.editor-buttons')[0].insertAdjacentHTML('beforeend', createButtons(buttonClass, SOURCE_ID))
  187. for (const lang of ['cpp', 'py', 'cs']) {
  188. document.getElementById(`source-fmt-${lang}`)
  189. .addEventListener('click', async (e) => await formatTextArea(e, lang))
  190. }
  191. })()