Reshuffles in learn and flashcards with a button or keybind.
// ==UserScript==
// @name Quizlet Reshuffle
// @namespace quizlet-reshuffle
// @version 1.0
// @description Reshuffles in learn and flashcards with a button or keybind.
// @author Richard Rogalski
// @match https://quizlet.com/*/learn*
// @match https://quizlet.com/*/flashcards*
// @grant none
// @run-at document-idle
// @license 0BSD
// ==/UserScript==
(function () {
'use strict';
// Config: how long to wait before each task (change if having problems)
const WAIT_MS = 0;
// Config: change key combo here (always functions)
const KEYBIND = { ctrl: true, alt: false, shift: false, meta: false, key: 'R' }; // meta == ⌘ on macOS
// Config: change key combo here (functions when not in a text box)
const KEYBIND2 = 'R';
function isTyping() {
const el = document.activeElement;
if (!el) return false;
const tag = el.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA') return true;
if (el.isContentEditable) return true;
return false;
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
function clickAway() {
document.body.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
document.body.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
document.body.dispatchEvent(new MouseEvent('click', { bubbles: true }));
}
function getOptionsButton() {
return document.querySelector('button[aria-label="Options"]');
}
function getShuffleButton() {
return document.querySelector('button[aria-label="Shuffle"]');
}
function isShuffleOn(btn) {
return btn?.getAttribute('aria-pressed') === 'true';
}
async function forceReshuffle() {
let shuffleBtn = getShuffleButton();
const optionsBtn = getOptionsButton();
let cards = true;
if (!shuffleBtn) cards = false;
//if (!optionsBtn) return;
// Open Options
if (!cards) optionsBtn.click();
// Wait for Shuffle to exist
for (let i = 0; i < 10; i++) {
await sleep(WAIT_MS);
shuffleBtn = getShuffleButton();
if (shuffleBtn) break;
}
if (!shuffleBtn) return;
// Turn shuffle OFF if ON
if (isShuffleOn(shuffleBtn)) {
shuffleBtn.click();
clickAway();
await sleep(WAIT_MS);
// Reopen options
if (!cards) optionsBtn.click();
await sleep(WAIT_MS);
shuffleBtn = getShuffleButton();
}
// Turn shuffle ON
shuffleBtn?.click();
// click-away to close menu and commit state
await sleep(WAIT_MS);
clickAway();
//console.log('Deck reshuffled');
}
function createFloatingButton() {
const btn = document.createElement('button');
btn.textContent = 'Reshuffle';
Object.assign(btn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
zIndex: 9999,
padding: '10px 14px',
background: '#2e3856',
color: '#d9dde8',
border: 'none',
borderRadius: '12px',
fontSize: '.875rem',
cursor: 'pointer',
boxShadow: '0 6px 16px rgba(0,0,0,0.25)'
});
btn.addEventListener('click', forceReshuffle);
document.body.appendChild(btn);
}
function matchesKeybind(e) {
if (isTyping()) {
if (!!KEYBIND.ctrl !== !!e.ctrlKey) return false;
if (!!KEYBIND.alt !== !!e.altKey) return false;
if (!!KEYBIND.shift !== !!e.shiftKey) return false;
if (!!KEYBIND.meta !== !!e.metaKey) return false;
return e.key && e.key.toLowerCase() === KEYBIND.key.toLowerCase();
} return e.key && e.key.toLowerCase() === KEYBIND2.toLowerCase();
}
window.addEventListener('keydown', function (e) {
if (matchesKeybind(e)) {
e.preventDefault();
e.stopPropagation();
forceReshuffle();
}
}, true);
createFloatingButton();
})();