您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
AtCoder Problems 上に表示される問題にユーザが独自のマーカー(解説ACなど)を付けられるようにします
// ==UserScript== // @name AtCoder Problems Marker // @namespace iilj // @version 2020.6.26.1 // @description AtCoder Problems 上に表示される問題にユーザが独自のマーカー(解説ACなど)を付けられるようにします // @author iilj // @supportURL https://github.com/iilj/AtCoderProblemsExt/issues // @match https://kenkoooo.com/atcoder/* // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.ui.position.js // @resource css_contextmenu https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.7.1/jquery.contextMenu.min.css // @grant GM_addStyle // @grant GM_getResourceText // ==/UserScript== /* globals $ */ (function () { 'use strict'; GM_addStyle(GM_getResourceText('css_contextmenu')); GM_addStyle(` td.table-problem.apm-ac-after-reading-answer { background-color: #ffff88 !important; } `); /** * key for localStorage to save ac-after-reading-answer state * @type {string} */ const localStorageKey = "apm-hashset"; /** * json string loaded from localStorage, or to save on localStorage * @type {string} */ let json; /** * hashset to store whether the problem is marked as ac-after-reading-answer or not * @type {{[key:string]: number}} */ let hash; /** * user id of AtCoder * @type {string} */ let userId; /** * load ac-after-reading-answer state from localStorage */ const loadHash = () => { json = localStorage.getItem(localStorageKey); hash = json ? JSON.parse(json) : {}; } /** * save ac-after-reading-answer state to localStorage */ const saveHash = () => { json = JSON.stringify(hash); localStorage.setItem(localStorageKey, json); } /** * url of problem -> key of hash * * @param {string} href * @returns {string | null} key of hash if valid url supplied, otherwise null */ const href2key = (href) => { let result if (result = href.match(/^https?:\/\/atcoder\.jp\/contests\/([^\/]+)\/tasks\/([^\/]+)$/)) { return `${userId}/${result[1]}/${result[2]}`; } return null; }; $.contextMenu({ selector: '.table-problem', callback: (key, options) => { switch (key) { case "ac_after_reading_answer": { const td = options.$trigger; const href = td.find('a[href]').attr('href'); const key = href2key(href); if (key in hash) { delete hash[key]; } else { hash[key] = 0; } td.toggleClass('apm-ac-after-reading-answer'); saveHash(); break; } case "about": alert("解説ACした問題を Table ページ上でマークできます.マークした結果はブラウザに保存されます."); } }, items: { "ac_after_reading_answer": { name: "解説AC On/Off" }, "sep1": "---------", "about": { name: "About" } } }); /** * Table 表示ページで "Show Accepted" の変更検知に利用する MutationObserver * * @type {MutationObserver} */ let tableObserver; /** * Table 表示ページで表のセルの色を塗り分ける. * * @date 2020-01-27 * @param {string} userId */ const processTable = () => { const tableChanged = () => { if (tableObserver) { tableObserver.disconnect(); } document.querySelectorAll('.table-problem').forEach(td => { const lnk = td.querySelector('a[href]'); if (!lnk) { return; } const key = href2key(lnk.href); if (!key) { return; } if (key in hash) { td.classList.add('apm-ac-after-reading-answer'); } }); if (tableObserver) { document.querySelectorAll('.react-bs-container-body').forEach(div => { tableObserver.observe(div, { childList: true, subtree: true }); }); } }; tableObserver = new MutationObserver(mutations => tableChanged()); tableChanged(); document.querySelectorAll('.react-bs-container-body').forEach(div => { tableObserver.observe(div, { childList: true, subtree: true }); }); } /** * ページ URL が変化した際のルートイベントハンドラ. * * @date 2020-01-27 */ const hrefChanged = () => { if (tableObserver) { tableObserver.disconnect(); } /** @type {RegExpMatchArray} */ let result; if (result = location.href.match(/^https?:\/\/kenkoooo\.com\/atcoder\/#\/table\/([^/?#]+)/)) { userId = result[1]; processTable(); } }; // main loadHash(); let href = location.href; const observer = new MutationObserver(mutations => { if (href === location.href) { return; } // href changed href = location.href; hrefChanged(); }); observer.observe(document, { childList: true, subtree: true }); hrefChanged(); })();