你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式
(我已經安裝了使用者樣式管理器,讓我安裝!)
// ==UserScript==
// @name Codeforces: Export All Problems to PDF
// @namespace https://github.com/AnonMiraj
// @author ezzeldin
// @license GPL3
// @description Export all Codeforces contest problems to a printable PDF, styled like CPC contests.
// @match https://codeforces.com/group/*/contest/*
// @match https://codeforces.com/gym/*
// @match https://codeforces.com/contest/*
// @grant none
// @esversion 11
// @version 1.0
// ==/UserScript==
(function () {
'use strict';
if (!document.querySelector('table.datatable') && !document.querySelector('table.problems')) return;
const container = document.querySelector('div[style*="text-align: right"] a[href*="/problems"]')?.parentElement;
if (!container) return;
const originalHTML = document.body.innerHTML;
const pdfLink = document.createElement('a');
pdfLink.textContent = '💾 Save All as PDF';
pdfLink.href = '#';
pdfLink.style.marginLeft = '15px';
pdfLink.style.color = '#0066cc';
pdfLink.style.textDecoration = 'none';
pdfLink.style.cursor = 'pointer';
container.appendChild(pdfLink);
pdfLink.addEventListener('click', async (e) => {
e.preventDefault();
await exportPDF(pdfLink);
});
async function exportPDF(link) {
const originalText = link.textContent;
link.style.pointerEvents = 'none';
link.textContent = 'Fetching…';
const selectors = [
'table.datatable tr td.id a',
'table.datatable tr td.index a',
'table.problems tr td.id a',
'table.problems tr td.index a',
'table.datatable tr a[href*="/problem/"]',
'table.problems tr a[href*="/problem/"]'
];
const anchors = Array.from(document.querySelectorAll(selectors.join(',')));
const seen = new Set(), links = [];
anchors.forEach(a => {
if (!seen.has(a.href)) {
seen.add(a.href);
links.push(a.href);
}
});
if (!links.length) return alert('No problem links found!');
const problemsHTML = await Promise.all(
links.map(url => fetchProblem(url))
);
document.body.innerHTML = problemsHTML
.map((html, i) => html || `<div><h2>Problem ${links[i].split('/').pop()} failed to load.</h2></div>`);
document.querySelectorAll('.test-example-line-even, .test-example-line-odd').forEach(el => {
el.classList.remove('test-example-line-even', 'test-example-line-odd');
});
document.querySelectorAll('.problem-statement').forEach((prob, i, arr) => {
const inputs = prob.querySelectorAll('.input');
const outputs = prob.querySelectorAll('.output');
if (inputs.length && outputs.length) {
const table = document.createElement('table');
table.className = 'samples-table';
table.style.width = '100%';
table.style.borderCollapse = 'collapse';
table.style.marginBottom = '20px';
const header = table.insertRow();
['Standard Input', 'Standard Output'].forEach(text => {
const th = document.createElement('th');
th.textContent = text;
th.style.border = '1px solid black';
th.style.padding = '8px';
th.style.textAlign = 'center';
th.style.width = '50%';
header.appendChild(th);
});
for (let j = 0; j < Math.min(inputs.length, outputs.length); j++) {
const row = table.insertRow();
[inputs[j], outputs[j]].forEach(el => {
const cell = row.insertCell();
cell.style.border = '1px solid black';
cell.style.padding = '8px';
cell.style.verticalAlign = 'top';
cell.appendChild(el);
});
}
const sample = prob.querySelector('.sample-tests') || prob.querySelector('.sample-test') || prob;
sample?.parentNode?.insertBefore(table, sample.nextSibling);
prob.querySelector('.sample-tests')?.remove();
prob.querySelector('.sample-test')?.remove();
}
if (i < arr.length - 1) {
const pageBreak = document.createElement('div');
pageBreak.style.pageBreakBefore = 'always';
prob.parentNode.insertBefore(pageBreak, prob.nextSibling);
}
});
const style = document.createElement('style');
style.textContent = `
.samples-table th, .samples-table td {
border: 1px solid black;
padding: 8px;
vertical-align: top;
text-align: left;
}
.samples-table pre {
white-space: pre-wrap;
word-wrap: break-word;
font-size: 12px;
margin: 0;
}
@media print {
.samples-table th, .samples-table td {
border: 1px solid black;
padding: 8px;
}
}
`;
document.head.appendChild(style);
['#MathJax_Message', '.status-bar', '#status', '.print-hide']
.forEach(sel => document.querySelectorAll(sel).forEach(el => el.style.display = 'none'));
const finalize = () => setTimeout(() => {
document.getElementById('MathJax_Message')?.style.setProperty('display', 'none', 'important');
window.print();
setTimeout(() => {
document.body.innerHTML = originalHTML;
link.style.pointerEvents = '';
link.textContent = originalText;
}, 500);
}, 500);
if (window.MathJax) {
try {
if (MathJax.typesetPromise) {
await MathJax.typesetPromise([document.body]);
finalize();
} else if (MathJax.Hub?.Queue) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, document.body], finalize);
} else finalize();
} catch (e) {
finalize();
}
} else finalize();
link.style.pointerEvents = '';
link.textContent = originalText;
}
async function fetchProblem(url, retries = 2) {
try {
const res = await fetch(url);
const txt = await res.text();
const doc = new DOMParser().parseFromString(txt, 'text/html');
const prob = doc.querySelector('.problem-statement');
if (!prob) throw new Error('Missing problem-statement');
prob.querySelectorAll('.input-output-copier').forEach(el => el.remove());
const header = doc.querySelector('.header');
let title = 'Unknown Problem';
let inputFile = 'standard input';
let outputFile = 'standard output';
let timeLimit = 'N/A';
let memoryLimit = 'N/A';
if (header) {
title = header.querySelector('.title')?.textContent.trim() || title;
inputFile = header.querySelector('.input-file')?.textContent.replace('input', '').trim() || inputFile;
outputFile = header.querySelector('.output-file')?.textContent.replace('output', '').trim() || outputFile;
timeLimit = header.querySelector('.time-limit')?.textContent.replace('time limit per test', '').trim() || timeLimit;
memoryLimit = header.querySelector('.memory-limit')?.textContent.replace('memory limit per test', '').trim() || memoryLimit;
header.remove();
}
const h2 = document.createElement('h2');
h2.textContent = title;
h2.style.marginBottom = '0.2em';
h2.style.textAlign = 'left';
prob.insertBefore(h2, prob.firstChild);
const metaDiv = document.createElement('div');
metaDiv.className = 'problem-metadata';
metaDiv.style.marginBottom = '1em';
metaDiv.innerHTML = `
<pre style="font-family: monospace; margin-left: 20px; font-size: 14px;">
Input file:\t${inputFile}
Output file:\t${outputFile}
Time limit:\t${timeLimit}
Memory limit:\t${memoryLimit}
</pre>
`;
prob.insertBefore(metaDiv, prob.firstChild.nextSibling);
prob.querySelectorAll('.input .title, .output .title').forEach(el => el.remove());
return prob.outerHTML;
} catch (err) {
if (retries > 0) {
await new Promise(r => setTimeout(r, 400));
return fetchProblem(url, retries - 1);
}
return null;
}
}
})();