您需要先安装一个扩展,例如 篡改猴、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 });
- })();