您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Cards: number keys to answer; Enter/~/etc. Collections: number keys to Play a deck.
当前为
// ==UserScript== // @name UCalgary Card & Collection Keyboard Shortcuts // @version 3.1 // @description Cards: number keys to answer; Enter/~/etc. Collections: number keys to Play a deck. // @match https://cards.ucalgary.ca/card/* // @match https://cards.ucalgary.ca/collection/* // @match https://cards.ucalgary.ca/collection* // @grant none // @namespace https://greasyfork.org/users/1331386 // ==/UserScript== (function () { 'use strict'; /* ─────────────── helper ─────────────── */ const isCardPage = location.pathname.startsWith('/card/'); const isCollectionPage = location.pathname.startsWith('/collection/') && location.pathname !== '/collection'; const isCollectionRootPage = location.pathname === '/collection' // common 0-19 index from keyboard (Shift+1-0 for 10-19) function getOptionIndex(e) { const map = { Digit1: 0, Digit2: 1, Digit3: 2, Digit4: 3, Digit5: 4, Digit6: 5, Digit7: 6, Digit8: 7, Digit9: 8, Digit0: 9, }; if (!(e.code in map)) return null; return map[e.code] + (e.shiftKey ? 10 : 0); } /* ─────────────── /card/ logic ─────────────── */ function addCardHints() { document.querySelectorAll('form.question').forEach(form => { form.querySelectorAll('.option label').forEach((label, i) => { if (!label.dataset.hinted) { label.insertAdjacentHTML( 'afterbegin', `<span style="font-weight:600;margin-right:4px;">(${i + 1})</span>` ); label.dataset.hinted = 'true'; } }); }); } function clearSelections() { document.querySelectorAll('form.question input[type="radio"], form.question input[type="checkbox"]') .forEach(inp => (inp.checked = false)); } function handleEnter() { const submitBtn = document.querySelector('form.question .submit button'); const nextBtn = document.querySelector('#next'); const reviewBtn = document.querySelector('div.actions span.review-buttons a.save'); if (submitBtn && submitBtn.offsetParent !== null) { submitBtn.click(); } else if (nextBtn && nextBtn.offsetParent !== null) { nextBtn.click(); } else if (reviewBtn && reviewBtn.offsetParent !== null) { reviewBtn.click(); } } /* ─────────────── /collection/ logic ─────────────── */ function addCollectionHints() { document.querySelectorAll('table.table-striped tbody tr').forEach((row, i) => { const name = row.querySelector('a.deck-name'); if (name && !name.dataset.hinted) { name.insertAdjacentHTML( 'afterbegin', `<span style="font-weight:600;margin-right:4px;">(${i + 1})</span>` ); name.dataset.hinted = 'true'; } }); } function playDeck(index) { const buttons = document.querySelectorAll('a.btn.action.save'); const btn = buttons[index + 1]; if (!btn) return; btn.scrollIntoView({ behavior: 'smooth', block: 'center' }); /* -------- open in a centred 1 000 × 800 px window -------- */ const width = 1000; const height = 800; const left = Math.round((screen.width - width) / 2); const top = Math.round((screen.height - height) / 2); const features = [ 'noopener', // security: no access back to this window 'noreferrer', // (optional) hide referrer 'scrollbars=yes', // allow scrolling 'resizable=yes', // let user resize `width=${width}`, `height=${height}`, `left=${left}`, `top=${top}` ].join(','); const win = window.open(btn.href, '_blank', features); if (win) win.focus(); // bring the new window to the front } /* ─── /collection root page ─ ENTER = first *visible* Details ─── */ function openFirstBagDetails() { // .bag wrappers are what get “display:none” when filtered const bags = document.querySelectorAll('.bag'); for (const bag of bags) { // offsetParent === null → element (or an ancestor) is display:none if (bag.offsetParent === null) continue; const detailsBtn = bag.querySelector('a.btn.deck-details'); if (detailsBtn) { window.open(detailsBtn.href, '_blank', 'noopener'); break; } } } /* ─────────────── key handler ─────────────── */ document.addEventListener('keydown', e => { const index = getOptionIndex(e); if (isCollectionRootPage && e.key === 'Enter') { openFirstBagDetails(); e.preventDefault(); // stop the page’s default handling return; } if (isCardPage) { if (index !== null) { const radios = document.querySelectorAll('form.question input[type="radio"]'); const checks = document.querySelectorAll('form.question input[type="checkbox"]'); if (radios[index]) { radios[index].checked = true; radios[index].scrollIntoView({ behavior: 'smooth', block: 'center' }); } if (checks[index]) { checks[index].checked = !checks[index].checked; checks[index].scrollIntoView({ behavior: 'smooth', block: 'center' }); } return; } if (e.key === 'Enter') handleEnter(); if (e.key === '~') clearSelections(); } if (isCollectionPage && index !== null) { playDeck(index); } }); /* ─────────────── observers / init ─────────────── */ function initHints() { if (isCardPage) addCardHints(); if (isCollectionPage) addCollectionHints(); } window.addEventListener('load', initHints); document.addEventListener('DOMContentLoaded', initHints); new MutationObserver(initHints).observe(document.body, { childList: true, subtree: true }); })();