MAL Redesigned Reviews

Modernizes MAL reviews, giving it a major design overhaul and adding new functionality.

// ==UserScript==
// @name         MAL Redesigned Reviews
// @namespace    http://tampermonkey.net/
// @version      1.6.1
// @description  Modernizes MAL reviews, giving it a major design overhaul and adding new functionality.
// @author       SomeNewGuy
// @match        https://myanimelist.net/manga/*
// @match        https://myanimelist.net/anime/*
// @match        https://myanimelist.net/reviews/*
// @match        https://myanimelist.net/profile/*/reviews
// @icon         https://cdn.myanimelist.net/images/favicon.ico
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(() => {

  const selectorsToRemove = [
    '.tag.well-written.btn-label.js-btn-label',
    '.tag.informative.btn-label.js-btn-label',
    '.tag.creative.btn-label.js-btn-label',
    '.tag.confusing.btn-label.js-btn-label',
    '.btn-showall.js-btn-showall',
    '.tag.funny',
    '.tag.funny.btn-label.js-btn-label',
    '.open'
  ];

  const removeElements = () => {
    selectorsToRemove.forEach(selector => {
      document.querySelectorAll(selector).forEach(element => element.remove());
    });
  };

const generateScore = rating => {
  const scoreDiv = document.createElement('div');
  scoreDiv.className = 'floatRightHeader';
  const ratingText = document.createElement('span');
  ratingText.textContent = "Reviewer's Rating: ";
  const ratingNumElement = document.createElement('span');
  ratingNumElement.className = 'rating-num';
  ratingNumElement.textContent = rating;
  scoreDiv.append(ratingText, ratingNumElement);

  const htmlElement = document.documentElement;
  const isDarkMode = htmlElement.classList.contains('appearance-dark', 'dark-mode');
  if (isDarkMode) {
    ratingText.style.color = '#9fbdff';
    ratingNumElement.style.color = '#d0d0d0';
  } else {
    ratingText.style.color = '#26448f';
    ratingNumElement.style.color = 'rgb(45 43 43)';
  }

  ratingText.style.fontWeight = 'bold';
  ratingNumElement.style.fontSize = '1.20em';
  ratingNumElement.style.fontWeight = 'bold';

  return scoreDiv;
};

  const processReviewElements = () => {
    const reviewElements = document.querySelectorAll('.review-element');
    reviewElements.forEach(reviewElement => {
      const reviewTags = reviewElement.querySelector('.tags');
      const ratingNumElement = reviewElement.querySelector('.rating > .num');
      const ratingNum = ratingNumElement.textContent;
      const scoreElement = generateScore(ratingNum);
      reviewTags.appendChild(scoreElement);

      ratingNumElement.style.display = 'none';
      ratingNumElement.parentElement.style.display = 'none';
    });
  };

  const hideElements = () => {
    const hiddenElements = document.querySelectorAll('.rating.mt20.mb20.js-hidden');
    hiddenElements.forEach(hiddenElement => {
      hiddenElement.style.cssText = 'display: none; visibility: hidden; position: absolute; left: -9999px';
    });

    const noticeElements = document.querySelectorAll('.notice.mb12.js-hidden');
    noticeElements.forEach(noticeElement => {
      noticeElement.style.display = 'none';
      noticeElement.textContent = '';
    });
  };

  const toggleElements = () => {
    const toggleCheckbox = document.getElementById('toggleCheckbox');
    const elementsToShowHide = document.querySelectorAll('.btn-reaction.js-btn-reaction:not(.nice)');

    elementsToShowHide.forEach(element => {
      element.style.display = toggleCheckbox.checked ? 'inline-block' : 'none';
    });

    localStorage.setItem('toggleCheckboxState', toggleCheckbox.checked);
  };

  const toggleTextMagnification = () => {
    const toggleCheckboxTextMagnification = document.getElementById('toggleCheckboxTextMagnification');
    const textElements = document.querySelectorAll('.text');

    textElements.forEach(element => {
      element.removeEventListener('mouseover', handleTextMagnification);
      element.removeEventListener('mouseout', handleTextMagnification);
    });

    if (toggleCheckboxTextMagnification.checked) {
      textElements.forEach(element => {
        element.addEventListener('mouseover', handleTextMagnification);
        element.addEventListener('mouseout', handleTextMagnification);
      });
    }

    localStorage.setItem('toggleCheckboxTextMagnificationState', toggleCheckboxTextMagnification.checked);
  };

  const handleTextMagnification = event => {
    const paragraph = event.target.closest('.text');
    if (paragraph) {
      if (event.type === 'mouseover') {
        paragraph.style.transition = 'font-size 0.3s ease-in-out';
        paragraph.style.fontSize = '1.3em';
      } else if (event.type === 'mouseout') {
        paragraph.style.transition = 'font-size 0.3s ease-in-out';
        paragraph.style.fontSize = '';
      }
    }
  };

  let reviewElements = document.querySelectorAll('.review-element');

  reviewElements.forEach(function(reviewElement) {
    let textElement = reviewElement.querySelector('.text');
    let spoilerTag = reviewElement.querySelector('.tag.spoiler');
    let readMoreButton = reviewElement.querySelector('.js-readmore');
    let showLessButton = reviewElement.querySelector('.js-showless');

    if (spoilerTag && textElement) {
      textElement.style.filter = 'blur(4px)';
      textElement.style.transition = 'filter 0.5s ease';

      let originalText = textElement.textContent;

      readMoreButton.addEventListener('click', function() {
        textElement.style.filter = 'none';
      });

      showLessButton.addEventListener('click', function() {
        textElement.style.filter = 'blur(4px)';
      });

      spoilerTag.innerHTML = 'Review contains spoilers';
      readMoreButton.innerHTML = '<i class="fas fa-angle-down"></i>Show the review';
    }
  });

  processReviewElements();
  hideElements();
  removeElements();

  const toggleCheckbox = document.createElement('input');
  toggleCheckbox.id = 'toggleCheckbox';
  toggleCheckbox.type = 'checkbox';
  toggleCheckbox.checked = localStorage.getItem('toggleCheckboxState') === 'true';
  toggleCheckbox.addEventListener('change', toggleElements);

  const toggleLabel = document.createElement('label');
  toggleLabel.htmlFor = 'toggleCheckbox';
  toggleLabel.textContent = 'Enable all the emojis';

  const toggleContainer = document.createElement('div');
  toggleContainer.style.marginTop = '10px';
  toggleContainer.style.textAlign = 'center';
  toggleContainer.appendChild(toggleCheckbox);
  toggleContainer.appendChild(toggleLabel);

  const toggleCheckboxTextMagnification = document.createElement('input');
  toggleCheckboxTextMagnification.id = 'toggleCheckboxTextMagnification';
  toggleCheckboxTextMagnification.type = 'checkbox';
  toggleCheckboxTextMagnification.checked = localStorage.getItem('toggleCheckboxTextMagnificationState') === 'true';
  toggleCheckboxTextMagnification.addEventListener('change', toggleTextMagnification);

  const toggleLabelTextMagnification = document.createElement('label');
  toggleLabelTextMagnification.htmlFor = 'toggleCheckboxTextMagnification';
  toggleLabelTextMagnification.textContent = 'Enable Text Magnification';

  const toggleContainerTextMagnification = document.createElement('div');
  toggleContainerTextMagnification.style.marginTop = '10px';
  toggleContainerTextMagnification.style.textAlign = 'center';
  toggleContainerTextMagnification.appendChild(toggleCheckboxTextMagnification);
  toggleContainerTextMagnification.appendChild(toggleLabelTextMagnification);

  const pageFooter = document.querySelector('footer');
  pageFooter.appendChild(toggleContainer);
  pageFooter.appendChild(toggleContainerTextMagnification);

  const buttons = document.querySelectorAll('.btn-reaction');
  buttons.forEach(button => {
  button.style.transition = 'transform 0.3s ease-in-out';
  button.addEventListener('mouseover', () => {
    button.style.transform = 'scale(1.1)';
  });
  button.addEventListener('mouseout', () => {
    button.style.transform = 'scale(1)';
  });
});

  toggleElements();
  toggleTextMagnification();

  const settingsText = document.createElement('p');
  settingsText.textContent = 'MAL Redesigned Reviews - Version 1.6';
  settingsText.style.fontWeight = 'bold';
  settingsText.style.textAlign = 'center';
  settingsText.style.marginTop = '10px';
  settingsText.style.marginBottom = '10px';

  const pageFooterTitle = document.querySelector('footer');
  pageFooter.insertBefore(settingsText, toggleContainer);
  pageFooter.appendChild(toggleContainerTextMagnification);

  const toggleContainerTextMagnificationGar = document.createElement('div');
  toggleContainerTextMagnification.className = 'toggle-container';
  toggleContainerTextMagnification.style.marginBottom = '10px';

  const style = document.createElement('style');
  style.innerHTML = `
  .review-element {
    position: relative;
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 8px;
    margin-bottom: 8px;
    margin-top: 8px;
    background-color: #f9f9f9;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s ease-in-out;
  }

  .review-element:hover {
    transform: translateY(-4px);
  }

  .thumbbody {
    display: flex;
    align-items: flex-start;
  }

  .thumb {
    position: absolute;
    top: 8px;
    left: 8px;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
  }

  .thumb img {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    margin-right: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s ease-in-out;
  }

  .thumb img:hover {
    transform: scale(1.05);
  }

  .body {
    margin-left: 56px;
  }

  .update_at {
    color: #999;
    font-size: 11px;
    display: flex;
    align-items: center;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
  }

  .username {
    display: flex;
    align-items: center;
    margin-bottom: 4px;
  }

  .username a {
    color: #1c439b;
    font-size: 12px;
    font-weight: bold;
    text-decoration: none;
    opacity: 1;
    transform: translateY(0);
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
  }

  .floatRightHeader {
    color: #a7b7cc;
    font-weight: bold;
    font-size: 12px;
    opacity: 1;
    transform: translateY(0);
  }

  .rating {
    font-size: 12px;
    font-weight: bold;
    margin-left: 4px;
    opacity: 1;
    transform: translateY(0);
  }

  .text {
    margin-top: 8px;
    margin-bottom: 4px;
    font-size: 12px;
    line-height: 1.3;
    opacity: 1;
    transform: translateY(0);
    color: #ccc;
  }

  .text.magnified {
    font-size: 14px;
    font-weight: bold;
  }
   .anime-info-review__header, .manga-info-review__header {
   padding: 10px !important;
   padding-bottom: 20px !important;
   background-color: #f6f6f6;
   border-radius: 8px;
  }
  .left, .right {
  margin-top: 7px !important;
  }
`;

const htmlElement = document.documentElement;
const isDarkMode = htmlElement.classList.contains('appearance-dark', 'dark-mode');

if (isDarkMode) {
  style.innerHTML += `
    .review-element {
      border-color: #000;
    }
    .thumbbody .body .reaction-box {
      background-color: #101010;
    }
    .dark-mode .review-element {
      background-color: #1e1d1d;
    }
    .update_at,
    .username a,
    .floatRightHeader,
    .rating,
    .text {
      color: #999;
    }
    .thumb img {
      box-shadow: 0 2px 4px rgba(255, 255, 255, 0.1);
      transition: transform 0.3s ease-in-out !important;
    }
    .thumb img:hover {
      transform: scale(1.05) !important;
    }
    .anime-info-review__header, .manga-info-review__header {
      padding: 10px !important;
      padding-bottom: 20px !important;
      border-radius: 8px;
    }
    .left, .right {
      margin-top: 7px !important;
    }
    .recommended a {
      color: #9fbdff !important;
    }
    .not-recommended a {
      color: #ff9fa1 !important;
    }
    .mixed-feelings a {
      color: #a3a3a3 !important;
    }
    .recommended__bar {
      background: #9fbdff !important;
    }
    .mixed_feeling__bar {
      background: #a3a3a3 !important;
    }
    .not_recommended__bar {
      background: #ff9fa1 !important;
    }
  `;
}

document.head.appendChild(style);

  const tagRestylePs = document.querySelectorAll('.tag.preliminary, .tag.spoiler');
  tagRestylePs.forEach(function(tagRestyle) {
  if (isDarkMode) {
    tagRestyle.style.backgroundColor = '#1e1d1d';
    tagRestyle.style.borderColor = '#1e1d1d';
  } else {
    tagRestyle.style.backgroundColor = '#f9f9f9';
    tagRestyle.style.borderColor = '#f9f9f9';
  }
});

  const tagRestyleP = document.querySelectorAll('.tag.preliminary');
  tagRestyleP.forEach(function(tagRestyle) {
  tagRestyle.style.color = '#2db039';
});

function createCheckbox(id, labelText) {
  const checkbox = document.createElement('input');
  checkbox.type = 'checkbox';
  checkbox.id = id;

  const label = document.createElement('label');
  label.setAttribute('for', id);
  label.textContent = labelText;

  const spacingSpan = document.createElement('span');
  spacingSpan.style.marginLeft = '8px';

  return { checkbox, label, spacingSpan };
}

function toggleVisibility(checkbox, elements, localStorageKey) {
  const toggleElementVisibility = () => {
    const displayStyle = checkbox.checked ? 'block' : 'none';
    elements.forEach((element) => {
      element.style.display = displayStyle;
    });

    localStorage.setItem(localStorageKey, checkbox.checked);
  };

  const applyVisibilityState = () => {
    const savedCheckboxState = localStorage.getItem(localStorageKey);
    if (savedCheckboxState !== null) {
      checkbox.checked = JSON.parse(savedCheckboxState);
      toggleElementVisibility();
    }
  };

  checkbox.addEventListener('change', toggleElementVisibility);
  applyVisibilityState();
}

const container = document.createElement('div');
container.style.cssText = 'text-align: center; margin-top: 10px;';

const reactionCheckbox = createCheckbox('toggleCheckbox', 'Show the reaction count');
container.append(reactionCheckbox.checkbox, reactionCheckbox.label, reactionCheckbox.spacingSpan);

const reactionElements = document.querySelectorAll('.icon-reaction.js-icon-reaction');
reactionElements.forEach(element => {
  element.style.display = 'none'; // Hide reaction box by default
});
toggleVisibility(reactionCheckbox.checkbox, reactionElements, 'checkboxState');

const giftCheckbox = createCheckbox('toggleGiftCheckbox', 'Show the gift button');
container.append(giftCheckbox.checkbox, giftCheckbox.label, giftCheckbox.spacingSpan);

const giftElements = document.querySelectorAll('.gift');
giftElements.forEach(element => {
  element.style.display = 'none'; // Hide gift button by default
});
toggleVisibility(giftCheckbox.checkbox, giftElements, 'giftCheckboxState');

document.body.append(container);

const blankSpaceDiv = document.createElement('div');
blankSpaceDiv.style.height = '10px';
document.body.append(blankSpaceDiv);
})();