Greasy Fork is available in English.
AtCoder の提出一覧の現在のソート列に ▲ または ▼ を表示します。
// ==UserScript==
// @name AtCoder Submissions Sort Marker
// @namespace http://tampermonkey.net/
// @version 2026.07.07
// @description AtCoder の提出一覧の現在のソート列に ▲ または ▼ を表示します。
// @author Not_Leonian
// @match https://atcoder.jp/contests/*/submissions*
// @icon https://www.google.com/s2/favicons?domain=atcoder.jp
// @run-at document-idle
// @license MIT
// ==/UserScript==
(() => {
const CONFIG = {
defaultOrderBy: "created",
defaultDesc: true,
activeClassName: "ac-submissions-sort-active",
markerClassName: "ac-submissions-sort-marker",
};
const PAGE_PATH_RE = /^\/contests\/[^/]+\/submissions(?:\/me)?\/?$/;
function isSubmissionsPage() {
return PAGE_PATH_RE.test(location.pathname);
}
function parseBooleanParam(value) {
return /^(?:true|1)$/i.test(value || "");
}
function getCurrentSort() {
const params = new URLSearchParams(location.search);
const hasOrderBy = params.has("orderBy");
const orderBy = params.get("orderBy") || CONFIG.defaultOrderBy;
const desc = params.has("desc")
? parseBooleanParam(params.get("desc"))
: !hasOrderBy && orderBy === CONFIG.defaultOrderBy
? CONFIG.defaultDesc
: false;
return { orderBy, desc };
}
function hrefOrderBy(anchor) {
const href = anchor.getAttribute("href");
if (!href) return null;
try {
return new URL(href, location.origin).searchParams.get("orderBy");
} catch {
return null;
}
}
function clearExistingMarkers(root = document) {
root.querySelectorAll(`.${CONFIG.markerClassName}`).forEach((node) => {
node.remove();
});
root.querySelectorAll(`.${CONFIG.activeClassName}`).forEach((node) => {
node.classList.remove(CONFIG.activeClassName);
node.removeAttribute("aria-sort");
});
}
function findSubmissionTable() {
return document.querySelector(".panel-submission table.table");
}
function findHeaderCellForOrder(table, orderBy) {
const firstHeaderRow = table.querySelector("thead tr:first-child");
if (!firstHeaderRow) return null;
for (const anchor of firstHeaderRow.querySelectorAll("th a[href]")) {
if (hrefOrderBy(anchor) === orderBy) {
return anchor.closest("th");
}
}
return null;
}
function applySortMarker() {
if (!isSubmissionsPage()) return;
const table = findSubmissionTable();
if (!table) return;
clearExistingMarkers(table);
const { orderBy, desc } = getCurrentSort();
const headerCell = findHeaderCellForOrder(table, orderBy);
if (!headerCell) return;
headerCell.classList.add(CONFIG.activeClassName);
headerCell.setAttribute("aria-sort", desc ? "descending" : "ascending");
const marker = document.createElement("span");
marker.className = CONFIG.markerClassName;
marker.textContent = desc ? "▼" : "▲";
marker.setAttribute("aria-hidden", "true");
headerCell.appendChild(marker);
}
function injectStyle() {
if (document.getElementById("ac-submissions-sort-marker-style")) return;
const style = document.createElement("style");
style.id = "ac-submissions-sort-marker-style";
style.textContent = `
.panel-submission table thead tr:first-child th.${CONFIG.activeClassName} {
position: relative;
padding-right: 1.6em !important;
}
.panel-submission table thead tr:first-child th .${CONFIG.markerClassName} {
position: absolute;
right: 0.45em;
top: 50%;
transform: translateY(-50%);
font-size: 0.9em;
line-height: 1;
color: #333;
pointer-events: none;
user-select: none;
}
`;
document.head.appendChild(style);
}
function main() {
injectStyle();
applySortMarker();
}
main();
})();