Quizlet Reshuffle

Reshuffles in learn and flashcards with a button or keybind.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();

})();