您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View all editorials of the AtCoder contest in one page.
当前为
// ==UserScript== // @name View All Editorials // @name:ja 解説ぜんぶ見る // @description View all editorials of the AtCoder contest in one page. // @description:ja AtCoderコンテストの解説ページに、すべての問題の解説をまとめて表示します。 // @version 1.4.5 // @icon https://www.google.com/s2/favicons?domain=atcoder.jp // @match https://atcoder.jp/contests/*/editorial // @match https://atcoder.jp/contests/*/editorial?* // @grant GM_addStyle // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/jquery.timeago.min.js // @namespace https://gitlab.com/w0mbat/user-scripts // @author w0mbat // ==/UserScript== (async function () { 'use strict'; console.log(`🐻 "View All Editorials" start execution. 🐻`) // Utils const appendHeadChild = (tagName, options) => Object.assign(document.head.appendChild(document.createElement(tagName)), options); const addScript = (src) => new Promise((resolve) => { appendHeadChild('script', { src, type: 'text/javascript', onload: resolve }); }); const addStyleSheet = (src) => new Promise((resolve) => { appendHeadChild('link', { rel: 'stylesheet', href: src, onload: resolve }); }); const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // KaTeX const loadKaTeX = async () => await addStyleSheet("https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css"); const kaTexOptions = { delimiters: [ { left: "$$", right: "$$", display: true }, { left: "$", right: "$", display: false }, { left: "\\(", right: "\\)", display: false }, { left: "\\[", right: "\\]", display: true } ], ignoredTags: ["script", "noscript", "style", "textarea", "code", "option"], ignoredClasses: ["prettyprint", "source-code-for-copy"], throwOnError: false }; const renderKaTeX = (rootDom) => { /* global renderMathInElement */ renderMathInElement && renderMathInElement(rootDom, kaTexOptions); }; // code-prettify const loadPrettifier = async () => { await addScript("https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?autorun=false"); }; /* global PR */ const runPrettifier = () => PR.prettyPrint(); // jQuery TimeAgo const loadTimeAgo = async () => { /* global LANG */ if (LANG == 'ja') await addScript("https://cdn.jsdelivr.net/npm/[email protected]/locales/jquery.timeago.ja.min.js"); }; const renderTimeAgo = () => { /* global $ */ $("time.timeago").timeago(); $('.tooltip-unix').each(function () { var unix = parseInt($(this).attr('title'), 10); if (1400000000 <= unix && unix <= 5000000000) { var date = new Date(unix * 1000); $(this).attr('title', date.toLocaleString()); } }); $('[data-toggle="tooltip"]').tooltip(); }; // Editorials Loader const editorialBodyQuery = "#main-container > div.row > div:nth-child(2)"; const scrape = (doc) => [ doc.querySelector(`${editorialBodyQuery} > div:nth-of-type(1)`), doc.querySelector(`${editorialBodyQuery} > div:nth-of-type(2)`), ]; const fetchEditorial = async (link) => { const response = await fetch(link.href); if (!response.ok) throw "Fetch failed"; const [content, history] = scrape(new DOMParser().parseFromString(await response.text(), 'text/html')); if (!content || !history) throw "Scraping failed"; return [content, history]; }; const renderEditorial = (link, content, history) => { const div = link.parentNode.appendChild(document.createElement('div')); div.classList.add('🐻-editorial-content'); div.appendChild(content); div.appendChild(history); renderKaTeX(div); renderTimeAgo(); runPrettifier(); }; const loadEditorial = async (link) => { const [content, history] = await fetchEditorial(link); renderEditorial(link, content, history); }; // Lazy Loading const Timer = (callback, interval) => { let id = undefined; return { start: () => { if (id) return; callback(); id = setInterval(callback, interval); }, stop: () => { if (!id) return; clearInterval(id); id = undefined; }, }; }; const Queue = (task, interval) => { const set = new Set(); let timer = Timer(() => { for (const element of set) { task(element); set.delete(element); break; } if (set.size == 0) timer.stop(); }, interval); return { add: (element) => { set.add(element); timer.start(); }, remove: (element) => set.delete(element), }; }; let unobserveEditorialLink = undefined; const queue = Queue(async (link) => { await loadEditorial(link) .catch(ex => console.warn(`🐻 Something wrong: "${link.href}", ${ex}`)); unobserveEditorialLink(link); }, 200); const intersectionCallback = async (entries) => { for (const entry of entries) { if (entry.isIntersecting) queue.add(entry.target); else queue.remove(entry.target); } }; const observeEditorialLinks = (links) => { const observer = new IntersectionObserver(intersectionCallback); unobserveEditorialLink = (link) => observer.unobserve(link); links.forEach(e => observer.observe(e)); }; // initialize const init = async () => { GM_addStyle(` pre code { tab-size: 4; } ${editorialBodyQuery} > ul > li { font-size: larger; } .🐻-editorial-content { margin-top: 0.3em; font-size: smaller; } `); await loadKaTeX(); await loadPrettifier(); await loadTimeAgo(); }; // main await init(); const filter4InternalEditorialLink = (link) => link.href.match(/\/contests\/.+\/editorial\//); const links = Array.prototype.filter.call(document.getElementsByTagName('a'), filter4InternalEditorialLink); if (links.length > 0) observeEditorialLinks(links); console.log(`🐻 "View All Editorials" end execution. 🐻`) })();