Memrise Review Selection

Review the words you select (for a given level)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           Memrise Review Selection
// @description    Review the words you select (for a given level)
// @match          http://*.memrise.com/course/*
// @match          https://*.memrise.com/course/*
// @run-at         document-end
// @version        1.0
// @grant          none
// @namespace      https://greasyfork.org/users/213706
// ==/UserScript==		

/* jshint esversion:6 */

function main() {
  var ReviewSelection = {};

  //+------------------------------------------------------
  //|
  //| Handles the Level's details page
  //|  https://decks.memrise.com/course/ID_COURSE/SLUG/ID_LEVEL/
  //|
  //+------------------------------------------------------

  ReviewSelection.list = {
    HIDDEN: true,
    reviewBtn: null,

    /**
     * Init elements
     */
    init() {

      // Add CSS
      var styles = `
          .review-show, .review-10 {
            cursor: pointer;
						margin-right: 5px;
          }
          .reviewSelection {
            background-image: none;
            text-shadow: none;
          }
          .reviewSelection.hide {
            display: none;
          }`;

      var styleSheet = document.createElement("style");
      styleSheet.type = "text/css";
      styleSheet.innerText = styles;
      document.head.appendChild(styleSheet);

      // Add the button "review" (to toggle checkboxes)
      var toggleBtn = document.createElement('a');
      toggleBtn.setAttribute('title', "Select the words to review");
      toggleBtn.setAttribute('class', "review-show mini pull-left");
      toggleBtn.innerText = 'Review';

      document.querySelector('.things-header').appendChild(toggleBtn)
              .addEventListener('click', ReviewSelection.list.toggleCheckbox);

      // Add the button "+10 last" (to check the 10 mast checkboxes)
      var checkLastBtn = document.createElement('a');
      checkLastBtn.setAttribute('title', "Select the 10 last words");
      checkLastBtn.setAttribute('class', "review-10 mini pull-left hide");
      checkLastBtn.innerText = '+10 last';

      ReviewSelection.list.checkLastBtn = document.querySelector('.things-header').appendChild(checkLastBtn);
			ReviewSelection.list.checkLastBtn.addEventListener('click', ReviewSelection.list.check10last);

      // Add the button "review the selected words" to the end of the list
      var reviewBtn = document.createElement('button');
      reviewBtn.setAttribute('type', 'button');
      reviewBtn.setAttribute('class', 'btn-primary btn-large btn-block reviewSelection hide');
      reviewBtn.innerText = 'Review the selected words';

      ReviewSelection.list.reviewBtn = document.querySelector('.things-footer').appendChild(reviewBtn);
			ReviewSelection.list.reviewBtn.addEventListener('click', ReviewSelection.list.launchReviewSession);

      // Hide "review the selected words" when clicking on "Ignore"
      document.querySelector('.ignore-show').addEventListener('click', function(){
        ReviewSelection.list.reviewBtn.classList.add('hide');
        ReviewSelection.list.HIDDEN = true;
        ReviewSelection.list.checkLastBtn.classList.add('hide');
      });
    },

    /**
     * Toggle display/hide checkboxes
     */
    toggleCheckbox() {
      ReviewSelection.list.HIDDEN = !ReviewSelection.list.HIDDEN;
      ReviewSelection.list.selectWords(ReviewSelection.list.HIDDEN);
      
      if(ReviewSelection.list.HIDDEN) {
      	ReviewSelection.list.checkLastBtn.classList.add('hide');
      } else {
        ReviewSelection.list.checkLastBtn.classList.remove('hide');
      }
    },
    
    /**
     * Check the last 10 words to review
     */
    check10last() {
      var things = document.querySelectorAll('.thing'),
          c      = 0;
          n      = 0;

      for(let i=things.length-1; i>=0; i--) {
        let thing     = things[i],
            thinguser = thing.querySelector('.thinguser');

        if(thinguser.firstElementChild.classList.contains('ico-seed')) {
        	continue;
        }
        if(!thing.querySelector('input').checked) {
          thing.querySelector('input').checked = true;
          c++;
        }
        if(++n == 10) {
          if(c == 0) {
          	n = 0;
          } else {
          	break;
          }
        }
      }
    },

    /**
     * Show or hide the checkboxes
     */
    selectWords(hide) {
      if(typeof hide != 'boolean') {
        hide = false;
      }

      var things       = document.querySelectorAll('.thing'),
          showDelay    = hide ? 'block' : 'none',
          showCheckbox = hide ? 'none' : 'block';

      for(let i=0; i<things.length; i++) {
        let thing     = things[i],
            thinguser = thing.querySelector('.thinguser');

        if(thinguser.firstElementChild.classList.contains('ico-seed')) {
        	continue;
        }
        thinguser.style.display = showDelay;
        thing.querySelector('.ignore-ui').style.display = showCheckbox;
      }

      if(hide) {
        ReviewSelection.list.reviewBtn.classList.add('hide');
      } else {
        ReviewSelection.list.reviewBtn.classList.remove('hide');
      }
    },

    /**
     * Retrieve the list of checked boxes
     * Save the selection in the localStorage
     * Then start a review session
     */
    launchReviewSession() {

      // Get the selection
      var divs      = document.querySelectorAll('.ignore-ui'),
          selection = {},
          c         = 0,
          n         = 0;

      for(let i=0; i<divs.length; i++) {
        let div = divs[i];
        if(!div.classList.contains('pull-right') || div.style.display !== 'block' || !div.firstElementChild) {
          continue;
        }
        if(div.firstElementChild.checked) {
          let id = div.parentNode.getAttribute('data-learnable-id');
          selection[id] = 1;
          c++;
        }
        n++;
      }
      divs = null;
      if(c == 0) {
        alert('Please select the words to review');
        return;
      }

      // Save the selection to localStorage      
      var url  = window.location.pathname,
          data = {time: Date.now(), list: selection, ntotal: n};

      [,idCourse,idxLevel] = url.match(/\/course\/(\d+)\/[^/]+\/(\d+)/);

      localStorage.setItem('reviewSession_' + idCourse + '_' + idxLevel, JSON.stringify(data));

      // Redirect to review
      url += 'garden/classic_review/?selection';
      window.location.href = url;
    }
  };

  
  //+------------------------------------------------------
  //|
  //| Handles the Review page
  //|  https://decks.memrise.com/course/ID_COURSE/SLUG/ID_LEVEL/garden/classic_review/*?selection
  //|
  //+------------------------------------------------------

  ReviewSelection.session = {
    isLoaded: false,
    key: '',

    /**
     * Init elements
     */
  	init() {

      // Check that we have a selection in memory
      var idCourse   = MEMRISE.garden.session_params.course_id,
          idxLevel   = MEMRISE.garden.session_params.level_index;

      ReviewSelection.session.key = 'reviewSession_' + idCourse + '_' + idxLevel;

      var dataFilter = localStorage.getItem(ReviewSelection.session.key);
      if(!dataFilter) {
        window.location = ReviewSelection.session.get_redirect_url();
      	return;
      }

      // Retrieve our selection
      //localStorage.removeItem(ReviewSelection.session.key);

      try {
        ReviewSelection.session.dataFilter = JSON.parse(dataFilter);
      } catch(e) {
        window.location = ReviewSelection.session.get_redirect_url();
				return;
      }

      // Whenever we receive a JSON response:
      // Filter the data
      $.ajaxSetup({
        converters: {
        	'text json': function(txt) {
            var data = JSON.parse(txt);
            
            if(data.boxes) {
            	ReviewSelection.session.filter(data);
            }
            return data;
          }
        }
      });
    },

    /**
     * Returns the URL of the current level details
     */
    get_redirect_url() {
      return `/course/${MEMRISE.garden.session_params.course_id}/${MEMRISE.garden.session_params.clouse_slug}/${MEMRISE.garden.session_params.level_index}/`;
    },

    /**
     * Filter the retrieved review data
     * To only include the selection
     */
    filter(data) {
      
      // Filter
      var boxes  = [],
          learns = [];

      for(let i=0; i<data.boxes.length; i++) {
        let box          = data.boxes[i],
            learnable_id = box.learnable_id;

        if(ReviewSelection.session.dataFilter.list[learnable_id]) {
          box.template  = 'sentinel';
          box.review_me = true;
          boxes.push(box);
          learns.push(data.learnables[i]);
        }
      }

      // Randomize
      var tmp, rand,
          n = boxes.length;

      while(n > 1) {
        rand = Math.floor(Math.random() * n);
        n--;

        tmp          = boxes[n];
        boxes[n]     = boxes[rand];
        boxes[rand]  = tmp;

        tmp          = learns[n];
        learns[n]    = learns[rand];
        learns[rand] = tmp;
      }

      // Save
      data.boxes             = boxes;
      data.learnables        = learns;
      data.session.num_items = boxes.length;
      data.session.slug      = 'classic_review';
  	}
  };
  
  //+------------------------------------------------------
  //|
  //| Init
  //|
  //+------------------------------------------------------

  if(MEMRISE.garden) {
    if(window.location.search == '?selection') {
      MEMRISE.garden.session_params.session_slug = 'preview'; // retrieve every words of the current level
	  	ReviewSelection.session.init();
    }

  } else if(document.querySelector('.things-header')) {
  	ReviewSelection.list.init();
  }
}

// Inject JS directly in page to prevent limitations of access
var script = document.createElement('script');

script.setAttribute("type", "application/javascript");
script.appendChild(document.createTextNode('('+ main +')();'));
document.body.appendChild(script);