AtCoder Efficient Layout

入力形式やサンプルを横並びにします。

As of 15.10.2022. See ბოლო ვერსია.

  1. // ==UserScript==
  2. // @name AtCoder Efficient Layout
  3. // @namespace https://atcoder.jp/
  4. // @version 0.1
  5. // @description 入力形式やサンプルを横並びにします。
  6. // @author magurofly
  7. // @match https://atcoder.jp/contests/*/tasks/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=atcoder.jp
  9. // @grant unsafeWindow
  10. // @license CC0-1.0 Universal
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const ioStyleSummary = "入出力形式";
  17. const sampleWidth = 60;
  18. const sampleWidthUnit = "vw";
  19. const sampleMargin = "0 1em";
  20. const sampleSummary = "入出力例";
  21.  
  22. const doc = unsafeWindow.document;
  23.  
  24. // 入出力形式より後にある hr を全部消す
  25. for (const hr of doc.querySelectorAll(".io-style ~ hr")) {
  26. hr.parentElement.removeChild(hr);
  27. }
  28.  
  29. // 入出力形式より後にある .part を取得(入出力例のはず)
  30. const samples = doc.querySelectorAll(".io-style ~ .part");
  31. const lazyContainer = parentElement => {
  32. let row = parentElement.querySelector(".samples-row");
  33. if (!row) {
  34. const container = newDetails(sampleSummary);
  35. container.className = "samples-container";
  36. parentElement.appendChild(container);
  37. row = doc.createElement("div");
  38. row.className = "samples-row";
  39. container.appendChild(row);
  40. }
  41. return row;
  42. };
  43. if (samples.length % 2 == 0) {
  44. // 偶数個なら 2 個ずつまとめて横並びにする
  45. for (let i = 0; i < samples.length; i += 2) {
  46. const input = samples[i];
  47. const output = samples[i + 1];
  48. const container = lazyContainer(input.parentElement, "samples-container");
  49. const col = doc.createElement("div");
  50. col.appendChild(input.parentElement.removeChild(input));
  51. col.appendChild(output.parentElement.removeChild(output));
  52. container.appendChild(col);
  53. }
  54. } else {
  55. // 奇数個ならまとめずに横並びにする
  56. for (let i = 0; i < samples.length; i += 2) {
  57. const element = samples[i];
  58. const container = lazyContainer(element.parentElement, "samples-container");
  59. container.appendChild(element.parentElement.removeChild(element));
  60. }
  61. }
  62. const sampleCount = doc.getElementsByClassName("samples-container")[0].children.length;
  63.  
  64. // 入出力形式を details に入れる
  65. const ioStyle = doc.querySelector(".io-style");
  66. const ioStyleDetails = newDetails(ioStyleSummary);
  67. ioStyle.parentElement.insertBefore(ioStyleDetails, ioStyle);
  68. ioStyleDetails.appendChild(ioStyle.parentElement.removeChild(ioStyle));
  69.  
  70. const globalCSS = `
  71. /* 入出力形式を横に並べる */
  72. .io-style {
  73. display: flex;
  74. justify-content: space-between;
  75. }
  76. .io-style > * {
  77. width: 100%;
  78. }
  79.  
  80. /* サンプルを横に並べる */
  81. .samples-container {
  82. width: 100%;
  83. overflow-x: auto;
  84. }
  85. .samples-row {
  86. display: flex;
  87. width: ${sampleWidth * sampleCount}${sampleWidthUnit};
  88. }
  89. .samples-row > * {
  90. width: ${sampleWidth}${sampleWidthUnit};
  91. margin: ${sampleMargin};
  92. }
  93. `;
  94.  
  95. doc.head.appendChild(document.createElement("style")).textContent = globalCSS;
  96.  
  97. function newDetails(summaryText, open = true) {
  98. const details = doc.createElement("details");
  99. details.open = open;
  100. const summary = doc.createElement("summary");
  101. summary.textContent = summaryText;
  102. details.appendChild(summary);
  103. return details;
  104. }
  105. })();