Greasy Fork is available in English.

Lichess show mistakes on the board

If your move is a blunder or inaccuracy, a red circle will signal this on the board.

// ==UserScript==
// @name     Lichess show mistakes on the board
// @description If your move is a blunder or inaccuracy, a red circle will signal this on the board.
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
// @grant    none
// @include     http://*.lichess.org/*
// @include  https://lichess.org/*
// @version     1.6
// @namespace https://greasyfork.org/users/729712
// ==/UserScript==

let $ = jQuery.noConflict();

$(document).ready(() => {
  setTimeout(() => {
    init();
  }, 500);
});

let clickedElement;

const COLORS = {
  '?': '#E69F00',
  '?!': '#56B4E9',
  '??': '#ff3d00',
};

const MOVE_TYPES = {
  Blunder: '??',
  Mistake: '?!',
  Inaccuracy: '?',
};

/**
 * Highlight the move type on the board (OTB)
 */
const highlightMoveTypeOTB = () => {
  clearBoardIndicators();
  const activeMove = getActiveMove();

  if (activeMove) {
    let moveType;

    moveType = getMoveType(activeMove.innerText);

    const lm = $('.last-move')[0];
    if (moveType) {
      const color = COLORS[moveType];
      lm.innerHTML = `<div>
   <div style="width: 20px;height: 20px;background: ${color};position: absolute;right: 0;border-radius: 10px;"></div>
   <div style="position: absolute;right: 0px;top: 0px;font-weight: bold;width: 20px;height: 20px;/*! align-content: space-evenly; */text-align: center;line-height: 20px;color: black;">${moveType}</div>
 	</div>`;
    } else {
      lm.innerHTML = '';
    }
  } else {
    console.log('no active move found');
  }
};

/**
 * Returns the move type
 */
const getMoveType = (text) => {
  if (text.indexOf(MOVE_TYPES.Blunder) !== -1) return MOVE_TYPES.Blunder;
  if (text.indexOf(MOVE_TYPES.Mistake) !== -1) return MOVE_TYPES.Mistake;
  if (text.indexOf(MOVE_TYPES.Inaccuracy) !== -1) return MOVE_TYPES.Inaccuracy; // check this last!
  return null;
};

/**
 * Clears the board indicators. in case we move away from the main line via explorer
 */
const clearBoardIndicators = () => {
  $('.last-move')[0].innerHTML = '';
  $('.last-move')[1].innerHTML = '';
};

/**
 * determines if a button is a review navigation button
 */
const isReviewNavButton = (but) => {
  const goodActions = ['first', 'prev', 'next', 'last'];
  return goodActions.indexOf(but.dataset.act) !== -1;
};

/**
 * returns all the navigation buttons << < > >>
 */
const getNavButtons = () => {
  const navButtons = [];
  const allButtons = document.getElementsByTagName('button');

  for (let i = 0; i < allButtons.length; i++) {
    if (isReviewNavButton(allButtons[i])) {
      navButtons.push(allButtons[i]);
    }
  }
  return navButtons;
};

/**
 * Returns the active move
 */
const getActiveMove = () => {
  const activeElements = document.getElementsByClassName('active');
  for (let i = 0; i < activeElements.length; i++) {
    if (activeElements[i].tagName === 'MOVE') {
      return activeElements[i];
    }
  }
  return null;
};

/**
 * Highlight the move types in the moves list
 */
const highlightMoveTypeITML = () => {
  const moves = document.getElementsByTagName('move');
  for (let i = 0; i < moves.length; i++) {
    const moveType = getMoveType(moves[i].innerText);
    if (moveType) {
      moves[i].style.color = COLORS[moveType];
    }
  }
};

const init = () => {
  // initial highlight of the moves
  highlightMoveTypeITML();

  // set the listeners for the action buttons
  const navButtons = getNavButtons();
  navButtons.forEach((button) => {
    button.onclick = () => {
      highlightMoveTypeOTB();
      highlightMoveTypeITML();
    };
  });
//   $(document).mousedown((ev) => {
//     clickedElement = ev.target;
//   });

  // tricky one. a user can also click on the bad move and we need to show the move, or on the
  // engine line and we need to clear the mistakes. this will double trigger the render when
  // you click on the arrows
  $(window).mouseup((ev) => {
//     const isMove = clickedElement.tagName === "MOVE" || clickedElement.parentElement.tagName === "MOVE";
//     const isChoiceButton = ev.target.parentElement.classList == "choices";

    setTimeout(()=> {
      clearBoardIndicators();
      highlightMoveTypeOTB();
      highlightMoveTypeITML();
    }, 50);

  });

  // set the listeners for the key down / up
  document.onkeyup = function (e) {
    switch (e.which) {
      case 32: // spacebar
      case 37: // left
      case 38: // up
      case 39: // right
      case 40: // down
        clearBoardIndicators();
        highlightMoveTypeOTB();
        highlightMoveTypeITML();
        break;
      default: // exit this handler for other keys
    }
  };
};