Quizlet Reshuffle

Reshuffles in learn and flashcards with a button or keybind.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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

})();