Adds buttons to copy sample inputs/outputs
// ==UserScript==
// @name AtCoder: All Samples At Once
// @namespace https://atcoder.jp/
// @version 2.2
// @name:ja AtCoder: すべての入出力を一度でコピー
// @license MIT
// @description Adds buttons to copy sample inputs/outputs
// @description:ja サンプル入出力を一度にコピーできるボタンを追加します
// @match https://atcoder.jp/contests/*/tasks/*
// @grant GM_setClipboard
// ==/UserScript==
(() => {
'use strict';
function g(t) {
return [...document.querySelectorAll("h3")]
.filter(h => h.textContent.includes(t))
.map(h => {
const p = h.parentElement.querySelector("pre");
return p ? p.innerText.trim() : "";
})
.filter(Boolean);
}
function c(text) {
if (typeof GM_setClipboard !== "undefined") {
GM_setClipboard(text);
} else {
navigator.clipboard.writeText(text);
}
}
function btn(title, text) {
const b = document.createElement("button");
b.textContent = title + " Copy";
b.className = "btn btn-default btn-sm";
b.onclick = () => {
c(text);
b.textContent = "Copied!";
setTimeout(() => (b.textContent = title + " Copy"), 1000);
};
return b;
}
function findInsertPoint() {
const headers = [...document.querySelectorAll("h3")];
const inputHeader = headers.find(h => h.textContent.trim() === "入力");
const outputHeader = headers.find(h => h.textContent.trim() === "出力");
return outputHeader
? outputHeader.nextElementSibling
: (inputHeader ? inputHeader.nextElementSibling : document.querySelector("#task-statement"));
}
const target = document.querySelector("#task-statement");
if (!target || document.querySelector("#sample-copy-buttons")) return;
const ins = g("入力例");
const outs = g("出力例");
const allIn = ins.length + "\n" + ins.join("\n");
const allOut = "==========\n" + outs.join("\n==========\n") + "\n==========";
const wrap = document.createElement("div");
wrap.id = "sample-copy-buttons";
wrap.style.margin = "10px 0";
wrap.style.padding = "8px";
wrap.style.border = "1px solid #ddd";
wrap.style.background = "#fff";
wrap.style.display = "flex";
wrap.style.gap = "10px";
wrap.style.alignItems = "center";
wrap.appendChild(btn("Inputs", allIn));
wrap.appendChild(btn("Outputs", allOut));
// ----------------------------
// Submit button (fixed)
// ----------------------------
const submit = document.createElement("a");
submit.textContent = "Submit";
submit.className = "btn btn-default btn-sm";
submit.style.textDecoration = "none";
submit.style.display = "inline-flex";
submit.style.alignItems = "center";
const contestMatch = location.href.match(/contests\/([^/]+)\//);
const contest = contestMatch ? contestMatch[1] : "";
// Reliable task detection (FIX)
const taskInput = document.querySelector('input[name="data.TaskScreenName"]');
const task = taskInput ? taskInput.value : "";
submit.href = `https://atcoder.jp/contests/${contest}/submit?taskScreenName=${task}`;
wrap.appendChild(submit);
const insertPoint = findInsertPoint();
if (insertPoint && insertPoint.parentNode) {
insertPoint.parentNode.insertBefore(wrap, insertPoint.nextSibling);
} else {
target.appendChild(wrap);
}
// ----------------------------
// Ctrl + Enter shortcut
// ----------------------------
document.addEventListener("keydown", (e) => {
if (e.ctrlKey && e.key === "Enter") {
const active = document.activeElement;
const isTyping =
active &&
(active.tagName === "TEXTAREA" || active.tagName === "INPUT");
if (isTyping) return;
e.preventDefault();
location.href = submit.href;
}
});
})();