一个让vjudge更美观更便捷的脚本(顶栏约90%不透明、Element 风格边框)。提交弹窗内代码框:等宽字体 + 高度 + Tab 缩进(不加载外部编辑器,避免 CSP/加载失败导致无法输入)。兼容 Competitive Companion。
// ==UserScript==
// @name Vjudge Better!
// @namespace https://github.com/1000ttank/vjudge-better
// @version 1.5.3
// @description 一个让vjudge更美观更便捷的脚本(顶栏约90%不透明、Element 风格边框)。提交弹窗内代码框:等宽字体 + 高度 + Tab 缩进(不加载外部编辑器,避免 CSP/加载失败导致无法输入)。兼容 Competitive Companion。
// @author 1000ttank
// @match https://vjudge.net/*
// @icon https://vjudge.net/favicon.ico
// @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js
// @run-at document-body
// @license MIT
// @grant GM_addStyle
// ==/UserScript==
(function () {
const PAGE_TYPES = [
/\/problem$/, /\/problem\/[^\/]+$/, /\/problem\/[^\/]+\/origin$/, /\/problem\/description\/[^\/]+$/,
/\/status$/, /\/solution\/[^\/]+\/origin$/, /\/solution\/[^\/]+\/[^\/]+$/,
/\/contest$/, /\/contest\/[^\/]+$/, /\/contest\/statistic$/,
/\/workbook$/, /\/article\/create$/, /\/article\/[^\/]+$/,
/\/user$/, /\/user\/[^\/]+$/,
/\/group$/, /\/group\/[^\/]+$/,
/\/comment$/, /\/message$/
];
function detectPageType(path) {
const cleanPath = path.replace(/\/$/, "");
return PAGE_TYPES.findIndex(re => re.test(cleanPath));
}
const pageId = detectPageType(location.pathname);
if (pageId === -1 && location.pathname !== "/") {
console.warn("Unknown VJudge page – possibly 404.");
}
/* 题面 iframe 内:不要重复铺 bg.jpg(与父页整页背景错位叠两层);html/body 透明,只保留毛玻璃看见父页背景 */
if (window.self !== window.top) {
GM_addStyle(`
html, body {
background-color: transparent !important;
background-image: none !important;
}
main, .container, .container-fluid, .container-md, .container-lg, .content, .body-content {
background-color: transparent !important;
background-image: none !important;
}
html::-webkit-scrollbar,
body::-webkit-scrollbar {
width: 10px;
height: 10px;
}
html::-webkit-scrollbar-track,
body::-webkit-scrollbar-track {
background: transparent;
}
html::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.5);
border-radius: 5px;
}
html::-webkit-scrollbar-thumb:hover,
body::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.65);
}
html {
scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
scrollbar-width: thin;
}
#prob-ads, #img-support {
display: none !important;
}
.card,
.list-group-item,
.btn-secondary,
.page-link,
.dropdown-menu,
.modal-content,
.form-control,
.tab-content {
background-color: rgba(255, 255, 255, 0.65) !important;
backdrop-filter: blur(6px);
border-radius: 0.5rem;
}
.card:hover,
.dropdown-menu:hover {
background-color: rgba(255, 255, 255, 0.85) !important;
}
/* 题面页:底层铺满毛玻璃,题目内容在上层,避免 card 外围透明露底 */
body .card {
position: relative !important;
isolation: isolate !important;
background: transparent !important;
backdrop-filter: none !important;
}
body .card::before {
content: "" !important;
position: absolute !important;
inset: 0 !important;
z-index: 0 !important;
border-radius: inherit !important;
background-color: rgba(255, 255, 255, 0.65) !important;
backdrop-filter: blur(6px) !important;
-webkit-backdrop-filter: blur(6px) !important;
pointer-events: none !important;
}
body .card > * {
position: relative !important;
z-index: 1 !important;
}
body .card:hover::before {
background-color: rgba(255, 255, 255, 0.85) !important;
}
body .card:hover {
background-color: transparent !important;
backdrop-filter: none !important;
}
.list-group-item.active,
a.list-group-item.active,
.list-group-item.my-contest-item.active {
background-color: rgba(0, 123, 255, 0.75) !important;
color: #fff !important;
border-color: rgba(0, 123, 255, 0.75) !important;
font-weight: 600;
}
.form-control {
border: 1px solid #ccc;
}
.page-item.active .page-link,
.page-item.active button.page-link,
button.page-link[aria-current="page"],
a.page-link[aria-current="page"] {
background-color: rgba(0, 123, 255, 0.75) !important;
color: #fff !important;
border-color: rgba(0, 123, 255, 0.75) !important;
}
`);
if (pageId === 3) {
GM_addStyle("dd {background-color: rgba(255,255,255,0.7) !important; border-radius: 4px !important;}");
}
return;
}
const pagesNeedingBg = new Set([0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]);
const frostedBackground = `body {
background: #f0f0f0 url(http://static.rqnoj.cn/images/bg.jpg) no-repeat center top fixed !important;
background-size: cover !important;
}`;
if (pagesNeedingBg.has(pageId) || pageId === -1) {
GM_addStyle(frostedBackground);
/* 用 padding 顶开固定顶栏即可;勿 prepend 占位 nav,以免多一层 DOM 影响扩展等对 body 子节点结构的假设(如 Competitive Companion 解析 outerHTML) */
GM_addStyle(`body { padding-top: 60px !important; box-sizing: border-box !important; }`);
}
if (pageId === 3) {
GM_addStyle("dd {background-color: rgba(255,255,255,0.7) !important; border-radius: 4px !important;}");
}
/* VJudge 顶栏为 <nav id="top-nav" class="navbar navbar-dark bg-dark ...">,需覆盖 Bootstrap 深色主题 */
GM_addStyle(`
nav#top-nav.navbar,
nav#top-nav.navbar.bg-dark,
nav#top-nav.navbar.navbar-dark,
#top-nav.prod {
/* 菜单栏约 90% 不透明 + 毛玻璃 */
background-color: rgba(255, 255, 255, 0.9) !important;
background-image: none !important;
--bs-navbar-color: #212529;
--bs-navbar-hover-color: #000000;
--bs-navbar-disabled-color: rgba(33, 37, 41, 0.58);
--bs-navbar-active-color: #000000;
--bs-navbar-brand-color: #212529;
--bs-navbar-brand-hover-color: #000000;
border-bottom: 1px solid #e4e7ed !important;
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
position: fixed !important;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
}
#header,
#header.navbar,
header.navbar {
background-color: rgba(255, 255, 255, 0.9) !important;
background-image: none !important;
border-bottom: 1px solid #e4e7ed !important;
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
}
.navbar.navbar-dark:not(#top-nav),
.navbar.bg-dark:not(#top-nav) {
background-color: rgba(255, 255, 255, 0.9) !important;
border-bottom: 1px solid #e4e7ed !important;
backdrop-filter: blur(12px) saturate(180%);
-webkit-backdrop-filter: blur(12px) saturate(180%);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08) !important;
}
#top-nav .navbar-brand,
#top-nav .navbar-brand span,
#top-nav .nav-link,
#top-nav .navbar-nav .nav-link,
.navbar.navbar-dark#top-nav .nav-link,
.navbar.navbar-dark#top-nav .navbar-brand {
color: #212529 !important;
font-weight: 500;
}
#top-nav .nav-link:hover,
#top-nav .nav-link:focus,
#top-nav .navbar-brand:hover {
color: #000000 !important;
background-color: rgba(0, 0, 0, 0.06) !important;
}
#top-nav .navbar-toggler {
border-color: rgba(0, 0, 0, 0.4) !important;
}
#top-nav .navbar-toggler-icon {
filter: invert(1) brightness(0.35);
}
/* 顶栏当前路由:仅加粗,不要蓝底 */
#top-nav .nav-item.active > .nav-link,
#top-nav .nav-item.active > .nav-link.active,
#top-nav .navbar .nav-link.active {
background-color: transparent !important;
background-image: none !important;
color: #212529 !important;
font-weight: 700 !important;
}
#top-nav .nav-item.active > .nav-link:hover,
#top-nav .nav-item.active > .nav-link:focus {
background-color: rgba(0, 0, 0, 0.05) !important;
color: #000000 !important;
}
.navbar a,
.navbar .nav-link,
.navbar-brand {
color: #212529 !important;
font-weight: 500;
}
.navbar a:hover,
.navbar .nav-link:hover {
color: #000000 !important;
}
.navbar .nav-item.active > .nav-link {
font-weight: 700 !important;
}
/* 分页 / 按钮等主色高亮(非顶栏) */
.btn.active,
.dropdown-item.active,
.tag.active {
background-color: #007bff !important;
color: #fff !important;
}
/* 分页当前页:Bootstrap 主色 #007bff,填充 75% 不透明(色相不变) */
.page-item.active .page-link,
.page-item.active button.page-link,
button.page-link[aria-current="page"],
a.page-link[aria-current="page"] {
background-color: rgba(0, 123, 255, 0.75) !important;
color: #fff !important;
border-color: rgba(0, 123, 255, 0.75) !important;
}
/* 题面/页面滚动条:白色 50% 透明(整页滚动,不用固定层以免无法滚动) */
html::-webkit-scrollbar,
body::-webkit-scrollbar,
#frame-description-container::-webkit-scrollbar,
#contest_problem .tab-content::-webkit-scrollbar,
#contest_problem::-webkit-scrollbar {
width: 10px;
height: 10px;
}
html::-webkit-scrollbar-track,
body::-webkit-scrollbar-track,
#frame-description-container::-webkit-scrollbar-track,
#contest_problem .tab-content::-webkit-scrollbar-track,
#contest_problem::-webkit-scrollbar-track {
background: transparent;
}
html::-webkit-scrollbar-thumb,
body::-webkit-scrollbar-thumb,
#frame-description-container::-webkit-scrollbar-thumb,
#contest_problem .tab-content::-webkit-scrollbar-thumb,
#contest_problem::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.5);
border-radius: 5px;
}
html::-webkit-scrollbar-thumb:hover,
body::-webkit-scrollbar-thumb:hover,
#frame-description-container::-webkit-scrollbar-thumb:hover,
#contest_problem .tab-content::-webkit-scrollbar-thumb:hover,
#contest_problem::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.65);
}
html {
scrollbar-color: rgba(255, 255, 255, 0.5) transparent;
scrollbar-width: thin;
}
#prob-ads, #img-support {
display: none !important;
}
.card,
.list-group-item,
.btn-secondary,
.page-link,
.dropdown-menu,
.modal-content,
.form-control,
.tab-content {
background-color: rgba(255, 255, 255, 0.65) !important;
backdrop-filter: blur(6px);
border-radius: 0.5rem;
}
.card:hover,
.dropdown-menu:hover {
background-color: rgba(255, 255, 255, 0.85) !important;
}
/* 顶栏「帮助」「语言」等下拉(须写在通用 .dropdown-menu 之后):背后强模糊,面板约 85% 不透明 */
#top-nav .dropdown-menu,
.navbar-nav.top-nav-secondary .dropdown-menu,
.switch-locale-dropdown {
background-color: rgba(255, 255, 255, 0.85) !important;
backdrop-filter: blur(20px) saturate(180%) !important;
-webkit-backdrop-filter: blur(20px) saturate(180%) !important;
border: 1px solid rgba(0, 0, 0, 0.06) !important;
box-shadow: 0 8px 24px rgba(0, 21, 41, 0.12) !important;
}
#top-nav .dropdown-menu:hover,
.navbar-nav.top-nav-secondary .dropdown-menu:hover,
.switch-locale-dropdown:hover {
background-color: rgba(255, 255, 255, 0.9) !important;
}
html[data-bs-theme="dark"] #top-nav .dropdown-menu,
html[data-bs-theme="dark"] .navbar-nav.top-nav-secondary .dropdown-menu,
html[data-bs-theme="dark"] .switch-locale-dropdown {
background-color: rgba(33, 37, 41, 0.85) !important;
border-color: rgba(255, 255, 255, 0.12) !important;
}
/* 比赛题面:.tab-content 底层整块毛玻璃,题目列/侧栏在上层,避免外围透明圈露背景 */
#contest_problem .tab-content {
position: relative !important;
isolation: isolate !important;
background: transparent !important;
backdrop-filter: none !important;
}
#contest_problem .tab-content::before {
content: "" !important;
position: absolute !important;
inset: 0 !important;
z-index: 0 !important;
border-radius: 0.5rem !important;
background-color: rgba(255, 255, 255, 0.65) !important;
backdrop-filter: blur(6px) !important;
-webkit-backdrop-filter: blur(6px) !important;
pointer-events: none !important;
}
#contest_problem .tab-content:hover::before {
background-color: rgba(255, 255, 255, 0.85) !important;
}
#contest_problem .tab-content > * {
position: relative !important;
z-index: 1 !important;
}
#contest_problem .tab-content .card {
background: transparent !important;
backdrop-filter: none !important;
}
#contest_problem .tab-content .card::before {
content: none !important;
}
/* 单独题目页:含题面 iframe 的卡片 — 底层毛玻璃、题目在上 */
main .card:has(#frame-description-container),
.card:has(#frame-description-container) {
position: relative !important;
isolation: isolate !important;
background: transparent !important;
backdrop-filter: none !important;
}
main .card:has(#frame-description-container)::before,
.card:has(#frame-description-container)::before {
content: "" !important;
position: absolute !important;
inset: 0 !important;
z-index: 0 !important;
border-radius: 0.5rem !important;
background-color: rgba(255, 255, 255, 0.65) !important;
backdrop-filter: blur(6px) !important;
-webkit-backdrop-filter: blur(6px) !important;
pointer-events: none !important;
}
main .card:has(#frame-description-container):hover::before,
.card:has(#frame-description-container):hover::before {
background-color: rgba(255, 255, 255, 0.85) !important;
}
main .card:has(#frame-description-container) > *,
.card:has(#frame-description-container) > * {
position: relative !important;
z-index: 1 !important;
}
/* 侧栏 list-group 选中项:主色蓝 75% 不透明,白字 */
.list-group-item.active,
a.list-group-item.active,
.list-group-item.my-contest-item.active {
background-color: rgba(0, 123, 255, 0.75) !important;
color: #fff !important;
border-color: rgba(0, 123, 255, 0.75) !important;
font-weight: 600;
}
.form-control {
border: 1px solid #ccc;
}
.body-footer {
color: #333;
background: transparent !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
padding: 1em;
border-top: none !important;
box-shadow: none !important;
}
@media (max-width: 768px) {
.navbar {
font-size: 14px;
}
}
/* 全站主区默认至少一屏;比赛页取消,避免概览/状态等 Tab 下被垫出大块空白 */
main, .container, .content, .body-content, .container-fluid {
min-height: 100vh !important;
}
body:has(#contest_problem) main.container,
body:has(#contest-tabs) main.container,
#contest_problem,
#contest_problem > .container,
#contest_problem .tab-content,
#contest_problem .tab-pane,
#contest_status,
#contest_overview,
#listStatus_wrapper {
min-height: unset !important;
}
/* 仅含题面+侧栏的那一行:顶对齐,避免侧栏列被 iframe 撑高后竖线/边框「跟到」页面底部 */
#contest_problem > .row:has(#prob-right-panel) {
align-items: flex-start !important;
}
/* 比赛:包住「概览/题目/…」Tab 与 .tab-content 的那一行(毛玻璃框主体),顶对齐减轻竖向被内容撑出长边线 */
.container-md:has(#contest-tabs) > .row:has(#contest-tabs),
.container:has(#contest-tabs) > .row:has(#contest-tabs) {
align-items: flex-start !important;
}
#prob-right-panel {
overflow-x: visible !important;
}
/* 题面行容器:不再做左右 25px 拉宽,仅保留无装饰边 */
#frame-description-container {
max-width: none !important;
box-sizing: border-box !important;
position: relative !important;
background: transparent !important;
border: none !important;
box-shadow: none !important;
border-radius: 0 !important;
}
#frame-description-container iframe[src*="problem/description"] {
width: 100% !important;
max-width: 100% !important;
box-sizing: border-box !important;
border: none !important;
vertical-align: top !important;
}
#time-info,
body #time-info.row.card {
margin-left: -25px !important;
margin-right: -25px !important;
width: calc(100% + 50px) !important;
max-width: none !important;
box-sizing: border-box !important;
position: relative !important;
}
`);
let footerCreditDone = false;
function appendFooterCreditOnce() {
if (footerCreditDone) return;
const $f = $("div.body-footer");
if (!$f.length) return;
footerCreditDone = true;
$f.append(
'<p style="text-align:center">Theme enhanced by <a href="https://github.com/1000ttank/vjudge-better" target="_blank">vjudge-better</a></p>'
);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", appendFooterCreditOnce);
} else {
appendFooterCreditOnce();
}
window.addEventListener("load", appendFooterCreditOnce);
setTimeout(appendFooterCreditOnce, 400);
setTimeout(appendFooterCreditOnce, 900);
})();
/* 比赛 Tab 内容框加宽(已取消题面左右 25px 拉宽与 .tab-content max-height) */
(function contestProblemWiden() {
if (window.self !== window.top) {
return;
}
try {
/* 若仍需完全关闭本段布局脚本以配合外部工具,可在控制台执行:localStorage.setItem("vjudge-better-disable-layout-js","1") 后刷新 */
if (localStorage.getItem("vjudge-better-disable-layout-js") === "1") {
return;
}
} catch (e) {
/* ignore */
}
const EXPAND_EACH = 25;
const EXTRA = EXPAND_EACH * 2;
const overflowFixed = new WeakSet();
function liftOverflowChain(el) {
let n = el && el.parentElement;
for (let i = 0; i < 8 && n && n !== document.documentElement; i++) {
if (overflowFixed.has(n)) {
n = n.parentElement;
continue;
}
const st = getComputedStyle(n);
if (st.overflowX === "hidden" || st.overflow === "hidden" || st.overflow === "clip") {
n.style.setProperty("overflow-x", "visible", "important");
n.style.setProperty("overflow", "visible", "important");
overflowFixed.add(n);
}
n = n.parentElement;
}
}
function applyWiden(el) {
if (!el || !el.style) return;
el.style.setProperty("margin-left", `-${EXPAND_EACH}px`, "important");
el.style.setProperty("margin-right", `-${EXPAND_EACH}px`, "important");
el.style.setProperty("width", `calc(100% + ${EXTRA}px)`, "important");
el.style.setProperty("max-width", "none", "important");
el.style.setProperty("box-sizing", "border-box", "important");
el.style.setProperty("position", "relative", "important");
liftOverflowChain(el);
}
/** 清除此前脚本写在题面容器/iframe 上的拉宽内联样式 */
function resetContestFrameInlineStyles() {
const host = document.getElementById("frame-description-container");
if (!host) return;
["margin-left", "margin-right", "width", "max-width"].forEach((p) => host.style.removeProperty(p));
host.querySelectorAll("iframe").forEach((ifr) => {
["margin-left", "margin-right", "width", "max-width"].forEach((p) => ifr.style.removeProperty(p));
});
delete host.dataset.vjudgeWidenOnce;
delete host.dataset.vjudgeWidenBase;
}
/**
* 比赛主内容「框」:#contest-tabs 与 .tab-content 所在 .row,按外层 container 宽度 +50px 左右各扩 25px。
*(脚本里 .tab-content 的毛玻璃样式形成视觉上的大框,不是时间条。)
*/
function applyWidenContestTabShell() {
const tabs = document.getElementById("contest-tabs");
if (!tabs) return;
const row = tabs.closest(".row");
const wrap = tabs.closest(".container-md, .container-lg, .container, .container-fluid");
if (!row || !wrap || !row.style) return;
const w = Math.round(wrap.getBoundingClientRect().width);
if (w <= 0) return;
row.style.setProperty("margin-left", `-${EXPAND_EACH}px`, "important");
row.style.setProperty("margin-right", `-${EXPAND_EACH}px`, "important");
row.style.setProperty("width", `${w + EXTRA}px`, "important");
row.style.setProperty("max-width", "none", "important");
row.style.setProperty("box-sizing", "border-box", "important");
row.style.setProperty("align-items", "flex-start", "important");
row.dataset.vjudgeTabShellBase = String(w);
liftOverflowChain(row);
}
function isDescIframe(el) {
if (!el || el.tagName !== "IFRAME") return false;
const src = el.getAttribute("src") || "";
return /problem\/description/i.test(src);
}
/**
* Competitive Companion 的 VirtualJudgeProblemParser 用 documentElement.outerHTML 解析,
* 并依赖 #prob-right-panel iframe 的 src 与内联 style(如 display)。勿对该区域 iframe 写拉宽等内联样式。
*/
function isUnderProbRightPanel(el) {
return !!(el && el.closest && el.closest("#prob-right-panel"));
}
function resetIframeFillContainer(el) {
if (!el || !el.style) return;
el.style.setProperty("margin-left", "0", "important");
el.style.setProperty("margin-right", "0", "important");
el.style.setProperty("width", "100%", "important");
el.style.setProperty("max-width", "100%", "important");
el.style.setProperty("box-sizing", "border-box", "important");
}
let contestShellResizeObs;
/** 监听比赛外层 container,同步 Tab 框宽度与题面 iframe 容器宽度 */
function attachContestShellResizeObserver() {
const wrap = document.querySelector(
".container-md:has(#contest-tabs), .container:has(#contest-tabs), .container-lg:has(#contest-tabs), .container-fluid:has(#contest-tabs)"
);
if (!wrap || typeof ResizeObserver === "undefined") return;
if (contestShellResizeObs) {
contestShellResizeObs.disconnect();
contestShellResizeObs = null;
}
contestShellResizeObs = new ResizeObserver(() => {
applyWidenContestTabShell();
});
contestShellResizeObs.observe(wrap);
}
function ensureContestMutationObserver() {
const cr = document.getElementById("contest_problem");
if (!cr || cr.dataset.vjudgeMoAttached === "1") return;
cr.dataset.vjudgeMoAttached = "1";
const mo = new MutationObserver(() => {
if (
document.getElementById("frame-description-container") ||
document.getElementById("contest-tabs")
) {
scan();
}
});
mo.observe(cr, { childList: true, subtree: true });
}
function getContestTabContentEl() {
const tabs = document.getElementById("contest-tabs");
if (!tabs || !tabs.parentElement) return null;
return tabs.parentElement.querySelector(".tab-content");
}
/** 清除旧版脚本写在 .tab-content 上的 max-height / overflow-y */
function resetTabContentHeightLock() {
const tc = getContestTabContentEl();
if (tc) {
tc.style.removeProperty("max-height");
tc.style.removeProperty("overflow-y");
}
}
function ensureTabShownListener() {
const tabs = document.getElementById("contest-tabs");
if (!tabs || tabs.dataset.vjudgeTabShownListener === "1") return;
tabs.dataset.vjudgeTabShownListener = "1";
tabs.addEventListener("shown.bs.tab", () => {
resetTabContentHeightLock();
resetContestFrameInlineStyles();
setTimeout(scan, 0);
});
}
function scan() {
const contestTabs = document.getElementById("contest-tabs");
if (contestTabs) {
const shellWrap = contestTabs.closest(
".container-md, .container-lg, .container, .container-fluid"
);
const tw = shellWrap ? Math.round(shellWrap.getBoundingClientRect().width) : 0;
const tabRow = contestTabs.closest(".row");
const prevShell = tabRow && tabRow.dataset.vjudgeTabShellBase;
if (tabRow && tw > 0 && (prevShell !== String(tw) || !tabRow.dataset.vjudgeTabShellOnce)) {
applyWidenContestTabShell();
tabRow.dataset.vjudgeTabShellOnce = "1";
}
attachContestShellResizeObserver();
ensureTabShownListener();
resetTabContentHeightLock();
}
const frameHost = document.getElementById("frame-description-container");
if (frameHost) {
frameHost.querySelectorAll("iframe").forEach((el) => {
if (isDescIframe(el) && !isUnderProbRightPanel(el)) {
resetIframeFillContainer(el);
}
});
liftOverflowChain(frameHost);
} else {
document.querySelectorAll("iframe").forEach((el) => {
if (isDescIframe(el) && !isUnderProbRightPanel(el)) {
applyWiden(el);
liftOverflowChain(el);
}
});
}
ensureContestMutationObserver();
}
function onRouteChange() {
resetTabContentHeightLock();
resetContestFrameInlineStyles();
const host = document.getElementById("frame-description-container");
if (host) {
delete host.dataset.vjudgeWidenOnce;
delete host.dataset.vjudgeWidenBase;
}
const tabs = document.getElementById("contest-tabs");
const tabRow = tabs && tabs.closest(".row");
if (tabRow) {
delete tabRow.dataset.vjudgeTabShellOnce;
delete tabRow.dataset.vjudgeTabShellBase;
}
setTimeout(scan, 0);
setTimeout(scan, 300);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", scan);
} else {
scan();
}
window.addEventListener("load", scan);
window.addEventListener("hashchange", onRouteChange);
window.addEventListener("popstate", onRouteChange);
setTimeout(scan, 400);
/* 从旧版脚本升级时清一次题面内联拉宽 */
setTimeout(resetContestFrameInlineStyles, 1200);
})();
/* 提交代码弹窗:强化原生 #submit-solution(不加载 Monaco:站点 CSP/AMD 易导致加载失败;先前曾隐藏 textarea 造成无法输入) */
(function vjudgeSubmitCodeArea() {
if (window.self !== window.top) {
return;
}
function clearLegacyMonacoHide(ta) {
if (!ta || ta.dataset.vjudgeMonacoLegacyCleared === "1") {
return;
}
if (ta.style.opacity === "0" || ta.getAttribute("aria-hidden") === "true") {
ta.style.cssText = "";
ta.removeAttribute("aria-hidden");
const host = document.getElementById("vjudge-monaco-submit-host");
if (host && host.parentNode) {
host.parentNode.removeChild(host);
}
}
ta.dataset.vjudgeMonacoLegacyCleared = "1";
}
GM_addStyle(`
#submit-solution {
font-family: ui-monospace, SFMono-Regular, "Cascadia Code", "Segoe UI Mono", Menlo, Monaco, Consolas, monospace !important;
font-size: 14px !important;
line-height: 1.5 !important;
tab-size: 4 !important;
-moz-tab-size: 4 !important;
min-height: 380px !important;
resize: vertical !important;
background: rgba(255, 255, 255, 0.95) !important;
color: #1e1e1e !important;
border: 1px solid #ced4da !important;
border-radius: 0.375rem !important;
padding: 10px 12px !important;
box-sizing: border-box !important;
}
html[data-bs-theme="dark"] #submit-solution,
.modal[data-bs-theme="dark"] #submit-solution {
background: rgba(30, 30, 30, 0.95) !important;
color: #d4d4d4 !important;
border-color: rgba(255, 255, 255, 0.2) !important;
}
#submit-solution:focus {
outline: 2px solid rgba(0, 123, 255, 0.45) !important;
outline-offset: 0 !important;
}
`);
let tabBound = false;
function insertAtCursor(ta, text) {
const start = ta.selectionStart;
const end = ta.selectionEnd;
const v = ta.value;
ta.value = v.slice(0, start) + text + v.slice(end);
const pos = start + text.length;
ta.selectionStart = ta.selectionEnd = pos;
}
function onSubmitSolutionKeydown(ev) {
if (ev.key !== "Tab" || ev.defaultPrevented) {
return;
}
const ta = ev.target;
if (!ta || ta.id !== "submit-solution") {
return;
}
ev.preventDefault();
if (ev.shiftKey) {
return;
}
insertAtCursor(ta, " ");
}
function tryBindTab() {
const ta = document.getElementById("submit-solution");
if (ta) {
clearLegacyMonacoHide(ta);
}
if (tabBound || !ta) {
return;
}
tabBound = true;
document.addEventListener("keydown", onSubmitSolutionKeydown, true);
}
document.addEventListener(
"shown.bs.modal",
(ev) => {
if (ev.target && ev.target.querySelector && ev.target.querySelector("#submit-solution")) {
tryBindTab();
}
},
true
);
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", tryBindTab);
} else {
tryBindTab();
}
setTimeout(tryBindTab, 500);
})();