Greasy Fork is available in English.

MangaReader

Utility created for a better reading experience.

// ==UserScript==
// @name     MangaReader
// @description Utility created for a better reading experience.
// @version  1.5.1
// @author              Midefos
// @namespace           https://github.com/Midefos
// @match               https://lectortmo.com/*
// @match               https://*.com/news/*
// @match               https://*.com/viewer/*
// ==/UserScript==

/* jshint esversion: 6 */

// PAGE

function isTMO() {
  const url = extractUrl();
  return url.includes('lectortmo');
}

function redirectTMO() {
  if (isReader && !isTMO()) {
    window.history.back();
  }
}

function isReader() {
  const url = extractUrl();
  return url.includes('viewer') || url.includes('news');
}

// INITIATOR

function bind() {
  bindMangaReader();
  bindNavigation();

  bindDashboard();
  bindConfiguration();
  bindStats();
}

function bindDashboard() {
  bindFollow();
  bindEpisode();
}

function bindConfiguration() {
  bindDark();
  bindCascade();
  bindStepper();
  bindAutoscroll();
  bindShortcuts();
  bindContactMe();
}

function bindStats() {
  startStats();
}

function app() {
  redirectTMO();

  createMenu();
  bind();
  toggleWelcome();

  scrollToFollow();
}

// HTML AND CSS TEMPLATE

function createMenu() {
  document.querySelector('#app').insertAdjacentHTML(
    'afterbegin',
    `<nav id="midefos-manga-reader">
        <div id="menu">
          <button class="primary" title="Toggle menu" id="toggleMangaReader">🗙</button>
          <button class="primary" title="Previous episode" id="prevChapter">⬅</button>
          <button class="primary" title="Next episode" id="nextChapter">➡</button>
          <button id="autoscrollState" title="Autoscroll state" class="off">🖱</button>
          <button id="saveFollow" title="Follow serie" class="off">❤️</button>
          <button id="saveEpisode" title="Save episode" class="off">⭐</button>
        </div>
      
        <div id="content">
          <div id="dashboard">
            <div id="welcome">
              <span>
                <h5>Welcome to the app!</h5> 
                Here will be appear:
                <ul>
                  <li>Your favorite episodes.</li>
                  <li>Your following series.</li>
                  <li>Some functionalities!</li>
                </ul>
                Please contact me if you find any bugs or have suggestions!
              </span>
            </div>

            <div id="autoscrollForm">
              <span>Autoscroll: </span>
              <select id="autoscrollVelocity">
                <option value="15">Slow</option>
                <option value="25">Normal</option>
                <option value="35">Fast</option>
              </select>
              <button id="toggleScroll" class="on">Enable</button>
            </div>

            <div id="followsContainer">
              <h5>❤️ Following:</h5>
              <ul id="followsList">
              </ul>
            </div>

            <div id="episodesContainer">
              <h5>⭐ Favorite episodes:</h5>
              <ul id="episodesList">
              </ul>
            </div>
          </div>

          <div id="configuration">
            <div>
              <input id="toggleDark" type="checkbox"><span>Always dark</span>
            </div>
      
            <div>
              <input id="toggleCascade" type="checkbox"><span>Always cascade</span>
            </div>
      
            <div>
              <input id="toggleStepper" type="checkbox"><span>Stepper</span>
            </div>
    
            <div>
              <input id="toggleAutoscroll" type="checkbox"><span>Autoscroll</span>
            </div>

            <div>
              <input id="toggleShortcuts" type="checkbox"><span>Shortcuts</span>
                <ul id="shortcutsInfo">
                  <li>esc: menu</li>
                  <li id="autoscrollShortcutInfo">space: autoscroll</li>
                  <li id="stepperShortcutInfo">tab: step</li>
                </ul>
            </div>

            <div>
              <button id="contactMe" class="primary">Contact me!</button>
            </div>
          </div>

          <div id="stats">
            <div>
              <h5>Current stats:</h5>
              <ul>
                <li id="totalTime">Time reading: <span id="totalTimeQuantity">Loading...</span></li>
                <li id="currentTime">Current serie: <span id="currentTimeQuantity">Loading...</span></li>
              </ul>
            </div>

            <div>
              <h5>All stats:</h5>
              <ul id="statsContainer"></ul>
            </div>
          </div>
        </div>

        <div id="footer">
          <button class="primary" title="Dashboard" id="toggleDashboard">📑</button>
          <button class="primary" title="Configuration" id="toggleConfiguration">🔧</button>
          <button class="primary" title="Stats" id="toggleStats">📊</button>
        </div>
    </nav>
      
      <style>
      #midefos-manga-reader {
        background: rgba(52, 58, 64, 0.9);
        position: fixed;
        color: white;
        margin: 15px;
        z-index: 5;
        border-radius: 10px;
        border: 2px solid rgb(34, 34, 34);
        width: 225px;
      }
      #midefos-manga-reader div {
        font-size: 12px;
      }
      #midefos-manga-reader ul {
        font-size: 11px;
        margin-bottom: 0;
        padding-left: 20px;
      }
      #menu, #footer{
        display: flex;
        justify-content: center;
        padding: 5px 0;
      }
      #footer[style*='display: block'] {
        display: flex !important;
      }
      #menu button, #footer button {
        margin: 0px 3px;
        padding: 0px;
        min-width: 30px;
        font-size: 16px;
      }
      #midefos-manga-reader h5 {
        color: white;
        font-weight: bold;
        font-size: 14px;
        text-align: center;
        margin-bottom: 3px;
      }
      #content{
          border-top: 2px solid rgb(34, 34, 34);
          border-bottom: 2px solid rgb(34, 34, 34);
      }
      #content > div > div {
          padding: 3px 0;
      }
      #content > div > div:not(:last-of-type) {
          border-bottom: 1px solid rgb(34, 34, 34);
      }
      #content #dashboard > div,
      #content #configuration > div,
      #content #stats > div {
        padding: 4px 10px;
      }
      #midefos-manga-reader input {
        position: relative;
        top: 1px;
      }
      #midefos-manga-reader input + span {
        margin-left: 5px;
      }
      #midefos-manga-reader input[type="text"] {
        width: 75px;
      }
      button.primary, button.off, button.on {
        cursor: pointer;
      }
      button.primary {
        color: white;
        background: rgb(41, 87, 186);
        border-color: rgb(41, 87, 186);
        transition: all ease-out 150ms;
      }
      button.primary:hover {
        background: rgb(34, 72, 155);
        border-color: rgb(34, 72, 155);
      }
      .on {
        color: white;
        background: green;
        border-color: green;
      }
      .off {
        color: white;
        background: darkred;
        border-color: darkred;
      }
      #midefos-manga-reader li[style*='display: block'] {
        display: list-item !important;
      }
      #episodesContainer, #followsContainer {
        display: none;
      }
      #episodesContainer a, .removeEpisode,
      #followsContainer a, .removeFollow,
      .removeStat{
          color: white;
          font-size: 11px;
      }
      #episodesContainer a, #followsContainer a {
          text-decoration: underline;
      }
      .removeEpisode, .removeFollow, .removeStat {
          margin: 0;
          padding: 0;
      }
      </style>`,
  );
}

// SETTINGS

function replacer(key, value) {
  if (value instanceof Map) {
    return {
      dataType: 'Map',
      value: Array.from(value.entries()),
    };
  }
  return value;
}

function reviver(key, value) {
  if (typeof value === 'object' && value !== null) {
    if (value.dataType === 'Map') {
      return new Map(value.value);
    }
  }
  return value;
}

function saveCurrentSettings() {
  const settings = {
    settings: {
      displayMenu: isVisibleMangaReader(),
      toggleDark: getToggleDark().checked,
      toggleCascade: getToggleCascade().checked,
      toggleStepper: getToggleStepper().checked,
      toggleAutoscroll: getToggleAutoscroll().checked,
      autoscrollVelocity: getAutoscrollVelocityValue(),
      toggleShortcuts: getToggleShortcuts().checked,
    },
    episodes: getEpisodes(),
    follows: getFollows(),
    stats: getStatsData(),
  };
  saveSettings(settings);
}

function getDefaultSettings() {
  return {
    settings: {
      displayMenu: true,
      toggleDark: true,
      toggleCascade: true,
      toggleStepper: true,
      toggleAutoscroll: false,
      autoscrollVelocity: 25,
      toggleShortcuts: true,
    },
    episodes: new Map(),
    follows: new Map(),
    stats: new Map(),
  };
}

function saveSettings(settings) {
  localStorage.setItem('midefos-manga-reader', JSON.stringify(settings, replacer));
}

function getSettings() {
  const settings = JSON.parse(localStorage.getItem('midefos-manga-reader'), reviver);
  if (settings) return settings;

  const defaultSettings = getDefaultSettings();
  saveSettings(defaultSettings);
  return defaultSettings;
}

function getSetting(settingName) {
  const settings = getSettings();
  return settings.settings[settingName];
}

// EPISODE

function getEpisodesContainer() {
  return document.querySelector('#episodesContainer');
}

function getEpisodesList() {
  return document.querySelector('#episodesList');
}

function getEpisodes() {
  const settings = getSettings();
  return settings.episodes;
}

function getEpisodeTemplate(id, episode) {
  return `<li>
            <a data-id="${id}" href="${episode.url}">${trim(episode.manga, 26)} - ${episode.number}</a>
            <button class='off removeEpisode' title='Remove favorite'>🗙</button>
          </li>`;
}

function printEpisode(id, episode) {
  const episodesContainer = getEpisodesContainer();
  showElement(episodesContainer);

  const episodesList = getEpisodesList();
  episodesList.insertAdjacentHTML('beforeend', getEpisodeTemplate(id, episode));

  if (isCurrentEpisode(id)) {
    getSaveEpisode().classList.replace('off', 'on');
  }
}

function isCurrentEpisode(id) {
  return extractId() === id;
}

function printEpisodes() {
  const episodes = getEpisodes();
  episodes.forEach((episode, id) => {
    printEpisode(id, episode);
  });
}

function removeEpisode(id) {
  const episodeAnchor = document.querySelector(`a[data-id="${id}"]`);
  episodeAnchor.parentNode.remove();

  if (isCurrentEpisode(id)) {
    getSaveEpisode().classList.replace('on', 'off');
  }
  const removeEpisode = document.querySelector('.removeEpisode');
  if (!removeEpisode) {
    const episodesContainer = getEpisodesContainer();
    hideElement(episodesContainer);
  }
}

function saveEpisode(id, episode) {
  if (!existEpisode(id)) {
    const settings = getSettings();
    settings.episodes.set(id, episode);
    saveSettings(settings);
    printEpisode(id, episode);
  }
}

function existEpisode(id) {
  const episodes = getEpisodes();
  return episodes.has(id);
}

function deleteEpisode(id) {
  if (existEpisode(id)) {
    const settings = getSettings();
    settings.episodes.delete(id);
    saveSettings(settings);
    removeEpisode(id);
  }
}

function extractId() {
  const url = extractUrl();
  let id = url.substr(url.indexOf('/viewer/') + '/viewer/'.length);
  id = id.substr(0, id.indexOf('/'));
  return id;
}

function extractManga() {
  return document.querySelector('section h1').textContent;
}

function extractEpisodeNumber() {
  let mangaEpisode = document.querySelector('section h2').textContent;
  mangaEpisode = mangaEpisode.substr(
    mangaEpisode.indexOf(' Capítulo ') + ' Capítulo '.length,
  );
  mangaEpisode = mangaEpisode.substr(0, mangaEpisode.indexOf(' Subido'));
  mangaEpisode = mangaEpisode.trim();
  return Number(mangaEpisode);
}

function extractUrl() {
  return window.location.href;
}

function toggleWelcome() {
  const contentElements = Array.from(
    document.querySelectorAll('#dashboard > div:not(#welcome)'),
  ).find((element) => getComputedStyle(element).display !== 'none');

  const welcomeInfo = document.querySelector('#welcome');
  if (contentElements) {
    hideElement(welcomeInfo);
  } else {
    showElement(welcomeInfo);
  }
}

function extractEpisode() {
  return {
    url: cleanUrl(),
    manga: extractManga(),
    number: extractEpisodeNumber(),
  };
}

function extractIdRemove(element) {
  return element.parentNode.querySelector('a').getAttribute('data-id');
}

function getSaveEpisode() {
  return document.querySelector('#saveEpisode');
}

function getPrevChapter() {
  return document.querySelector('.chapter-prev a');
}

function getNextChapter() {
  return document.querySelector('.chapter-next a');
}

function bindEpisode() {
  const prevChapterButton = document.querySelector('#prevChapter');
  const prevChapter = getPrevChapter();
  if (prevChapter) {
    prevChapterButton.addEventListener('click', () => {
      prevChapter.click();
    });
  } else {
    hideElement(prevChapterButton);
  }

  const nextChapterButton = document.querySelector('#nextChapter');
  const nextChapter = getNextChapter();
  if (nextChapter) {
    nextChapterButton.addEventListener('click', () => {
      nextChapter.click();
    });
  } else {
    hideElement(nextChapterButton);
  }

  const saveEpisodeButton = getSaveEpisode();
  saveEpisodeButton.addEventListener('click', () => {
    const id = extractId();
    const episode = extractEpisode();
    saveEpisode(id, episode);
    toggleWelcome();
  });
  printEpisodes();

  if (!isReader()) {
    hideElement(prevChapterButton);
    hideElement(nextChapterButton);
    hideElement(saveEpisodeButton);
  }

  document.querySelector('body').addEventListener('click', (event) => {
    if (event.target.classList.contains('removeEpisode')) {
      const id = extractIdRemove(event.target);
      deleteEpisode(id);
      toggleWelcome();
    }
  });
}

// FOLLOWS

function bindBeforeUnload() {
  window.addEventListener('beforeunload', saveFollowScroll);
}

function saveFollowScroll() {
  const name = extractManga();
  if (existFollow(name)) {
    const currentScroll = window.scrollY;
    const follow = getFollow(name);
    follow.scrollY = currentScroll;

    const settings = getSettings();
    settings.follows.set(name, follow);
    saveSettings(settings);
  }
}

function removeBeforeUnload() {
  window.addEventListener('beforeunload', saveFollowScroll);
}

function scrollToFollow() {
  const name = extractManga();
  if (existFollow(name)) {
    const follow = getFollow(name);
    const scroll = follow.scrollY || 0;
    window.scroll({
      top: scroll,
      left: 0,
      behavior: 'smooth',
    });
  }
}

function getFollowsList() {
  return document.querySelector('#followsList');
}

function getFollowsContainer() {
  return document.querySelector('#followsContainer');
}

function getFollowTemplate(episode) {
  return `<li>
            <a data-name="${episode.manga}" href="${episode.url}">${trim(episode.manga, 26)} - ${episode.number}</a>
            <button class='off removeFollow' title='Remove follow'>🗙</button>
          </li>`;
}

function printFollow(episode) {
  const followsContainer = getFollowsContainer();
  showElement(followsContainer);
  const existEpisode = followsContainer.querySelector(
    `a[data-name="${episode.manga}"]`,
  );
  if (existEpisode) existEpisode.parentNode.remove();
  const followsList = getFollowsList();
  followsList.insertAdjacentHTML('beforeend', getFollowTemplate(episode));

  if (isCurrentFollow(episode.manga)) {
    getSaveFollow().classList.replace('off', 'on');
    bindBeforeUnload();
  }
}

function isCurrentFollow(name) {
  if (!isReader()) {
    return false;
  }
  return extractManga() === name;
}

function printFollows() {
  const follows = getFollows();
  follows.forEach((episode, name) => {
    printFollow(episode);
  });
}

function removeFollow(name) {
  const followsContainer = getFollowsContainer();
  const episodeAnchor = followsContainer.querySelector(`a[data-name="${name}"]`);
  episodeAnchor.parentNode.remove();

  if (isCurrentFollow(name)) {
    getSaveFollow().classList.replace('on', 'off');
    removeBeforeUnload();
  }

  const removeFollow = document.querySelector('.removeFollow');
  if (!removeFollow) {
    hideElement(followsContainer);
  }
}

function getFollows() {
  const settings = getSettings();
  return settings.follows;
}

function getFollow(name) {
  if (existFollow(name)) {
    return getFollows().get(name);
  }
  return null;
}

function existFollow(name) {
  const follows = getFollows();
  return follows.has(name);
}

function saveFollow() {
  const episode = extractEpisode();
  const mangaName = episode.manga;
  if (!existFollow(mangaName)) {
    const settings = getSettings();
    settings.follows.set(mangaName, episode);
    saveSettings(settings);
    printFollow(episode);
  }
}

function deleteFollow(name) {
  if (existFollow(name)) {
    const settings = getSettings();
    settings.follows.delete(name);
    saveSettings(settings);
    removeFollow(name);
  }
}

function updateFollow() {
  const name = extractManga();
  if (existFollow(name)) {
    const settings = getSettings();
    const savedFollow = settings.follows.get(name);
    const currentEpisodeNumber = extractEpisodeNumber();
    if (currentEpisodeNumber > savedFollow.number) {
      const currentEpisode = extractEpisode();
      settings.follows.set(name, currentEpisode);
      saveSettings(settings);
      printFollow(currentEpisode);
    }
  }
}

function getSaveFollow() {
  return document.querySelector('#saveFollow');
}

function extractNameRemove(element) {
  return element.parentNode
    .querySelector('*[data-name]')
    .getAttribute('data-name');
}

function bindFollow() {
  const followButton = getSaveFollow();
  followButton.addEventListener('click', () => {
    saveFollow();
    toggleWelcome();
  });

  if (!isReader()) {
    hideElement(followButton);
  } else {
    updateFollow();
  }
  printFollows();

  document.querySelector('body').addEventListener('click', (event) => {
    if (event.target.classList.contains('removeFollow')) {
      const name = extractNameRemove(event.target);
      deleteFollow(name);
      toggleWelcome();
    }
  });
}

// DARK

function setDarkTheme() {
  localStorage.setItem('theme', 'dark');
  document.querySelector('body').classList.add('dark-mode');
  const navbar = document.querySelector('.navbar');
  if (navbar) {
    navbar.classList.remove('navbar-light', 'bg-light');
    navbar.classList.add('navbar-dark', 'bg-dark');
  }
}

function getToggleDark() {
  return document.querySelector('#toggleDark');
}

function performDark() {
  if (getToggleDark().checked) {
    setDarkTheme();
  }
}

function bindDark() {
  const toggleDark = getToggleDark();
  toggleDark.addEventListener('change', () => {
    performDark();
    saveCurrentSettings();
  });

  toggleDark.checked = getSetting('toggleDark');
  performDark();
}

// CASCADE

const cascadeText = 'cascade';
function isCascade() {
  return window.location.href.includes(cascadeText);
}

function cleanUrl() {
  let url = extractUrl();
  url = url.replace('paginated', cascadeText);
  url = url.substr(0, url.lastIndexOf(cascadeText) + cascadeText.length);
  return url;
}

function navigateToCascade() {
  if (!isCascade()) {
    window.location.href = cleanUrl();
  }
}

function getToggleCascade() {
  return document.querySelector('#toggleCascade');
}

function performToggleCascade() {
  if (!isReader()) {
    return;
  }

  if (getToggleCascade().checked) {
    navigateToCascade();
  }
}

function bindCascade() {
  const toggleCascade = getToggleCascade();
  toggleCascade.addEventListener('change', (event) => {
    performToggleCascade();
    saveCurrentSettings();
  });

  toggleCascade.checked = getSetting('toggleCascade');
  performToggleCascade();
}

// STEPPER

function getStepperShortcutInfo() {
  return document.querySelector('#stepperShortcutInfo');
}

function performToggleStepper() {
  const body = document.querySelector('body');
  const enabled = getToggleStepper().checked;
  if (enabled && isReader()) {
    if (getToggleShortcuts().checked) {
      body.addEventListener('keydown', _keyStepper);
    }
  }
  const stepperShortcutInfo = getStepperShortcutInfo();
  if (enabled) {
    showElement(stepperShortcutInfo);
  } else {
    hideElement(stepperShortcutInfo);
    body.removeEventListener('keydown', _keyStepper);
  }
}

function getToggleStepper() {
  return document.querySelector('#toggleStepper');
}

function bindStepper() {
  const toggleStepper = getToggleStepper();
  toggleStepper.addEventListener('click', () => {
    performToggleStepper();
    saveCurrentSettings();
  });
  toggleStepper.checked = getSetting('toggleStepper');
  performToggleStepper();
}

// AUTOSCROLL

function getAutoscrollShortcutInfo() {
  return document.querySelector('#autoscrollShortcutInfo');
}

function getAutoscrollForm() {
  return document.querySelector('#autoscrollForm');
}

function toggleScroll() {
  if (isAutoscrollRunning()) {
    clearAutoscroll();
  } else {
    initAutoscroll();
  }
}

function performToggleAutoscroll() {
  const body = document.querySelector('body');
  const enabled = getToggleAutoscroll().checked;

  const autoscrollForm = getAutoscrollForm();
  const autoscrollState = getAutoscrollState();
  if (enabled && isReader()) {
    showElement(autoscrollState);
    showElement(autoscrollForm);
    if (getToggleShortcuts().checked) {
      body.addEventListener('keypress', _keyAutoscroll);
    }
  } else {
    hideElement(autoscrollState);
    hideElement(autoscrollForm);
  }

  const autoscrollShortcutInfo = getAutoscrollShortcutInfo();
  if (enabled) {
    showElement(autoscrollShortcutInfo);
  } else {
    hideElement(autoscrollShortcutInfo);
    body.removeEventListener('keypress', _keyAutoscroll);
  }
}

function getToggleScroll() {
  return document.querySelector('#toggleScroll');
}

function clearAutoscroll() {
  clearInterval(autoscrollInterval);
  autoscrollInterval = null;

  getAutoscrollState().classList.replace('on', 'off');
  const toggleScroll = getToggleScroll();
  toggleScroll.classList.replace('off', 'on');
  toggleScroll.textContent = 'Enable';
}

function isAutoscrollRunning() {
  return autoscrollInterval !== null;
}

function initAutoscroll() {
  autoscrollInterval = setInterval(() => {
    window.scroll({
      top: window.scrollY + getAutoscrollVelocityValue(),
      left: 0,
      behavior: 'smooth',
    });
  }, 150);

  getAutoscrollState().classList.replace('off', 'on');
  const toggleScroll = getToggleScroll();
  toggleScroll.classList.replace('on', 'off');
  toggleScroll.textContent = 'Disable';
}

function getAutoscrollState() {
  return document.querySelector('#autoscrollState');
}

function getToggleAutoscroll() {
  return document.querySelector('#toggleAutoscroll');
}

function getAutoscrollVelocity() {
  return document.querySelector('#autoscrollVelocity');
}

function getAutoscrollVelocityValue() {
  return (
    Number(document.querySelector('#autoscrollVelocity').value)
    || getDefaultSettings().autoscrollVelocity
  );
}

let autoscrollInterval = null;
function bindAutoscroll() {
  const autoscrollVelocity = getAutoscrollVelocity();
  autoscrollVelocity.addEventListener('change', (event) => {
    if (!Number.isNaN(event.target.value)) saveCurrentSettings();
  });
  autoscrollVelocity.value = getSetting('autoscrollVelocity');

  const toggleScrollButton = getToggleScroll();
  toggleScrollButton.addEventListener('click', () => {
    toggleScroll();
  });

  const toggleAutoscroll = getToggleAutoscroll();
  toggleAutoscroll.addEventListener('click', () => {
    performToggleAutoscroll();
    saveCurrentSettings();
    toggleWelcome();
  });
  toggleAutoscroll.checked = getSetting('toggleAutoscroll');
  performToggleAutoscroll();
}

// DISPLAY MENU

function showElement(element) {
  element.style.display = 'block';
}

function hideElement(element) {
  element.style.display = 'none';
}

function isVisible(element) {
  return element.style.display !== 'none';
}

function toggleDisplay(element) {
  if (!isVisible(element)) {
    showElement(element);
  } else {
    hideElement(element);
  }
}

function isVisibleMangaReader() {
  return isVisible(getContent());
}

function getContent() {
  return document.querySelector('#midefos-manga-reader #content');
}

function getFooter() {
  return document.querySelector('#midefos-manga-reader #footer');
}

function toggleMangaReader() {
  const mangaElements = [getContent(), getFooter()];
  mangaElements.forEach((element) => {
    toggleDisplay(element);
  });
}

function bindMangaReader() {
  const mangaReaderButton = document.querySelector('#toggleMangaReader');
  mangaReaderButton.addEventListener('click', () => {
    toggleMangaReader();
    saveCurrentSettings();
  });

  if (!getSetting('displayMenu')) toggleMangaReader();
}

// NAVIGATION

function getDashboard() {
  return document.querySelector('#midefos-manga-reader #dashboard');
}

function getConfiguration() {
  return document.querySelector('#midefos-manga-reader #configuration');
}

function getStats() {
  return document.querySelector('#midefos-manga-reader #stats');
}

function getContentElements() {
  return [getConfiguration(), getDashboard(), getStats()];
}

function hideContentElements() {
  const elements = getContentElements();
  elements.forEach((element) => {
    hideElement(element);
  });
}

function showDashboard() {
  hideContentElements();
  const dashboard = getDashboard();
  showElement(dashboard);
}

function showConfiguration() {
  hideContentElements();
  const configuration = getConfiguration();
  showElement(configuration);
}

function showStats() {
  hideContentElements();
  const stats = getStats();
  showElement(stats);
}

function bindNavigation() {
  showDashboard();

  const dashboardButton = document.querySelector('#toggleDashboard');
  dashboardButton.addEventListener('click', () => {
    showDashboard();
  });

  const configurationButton = document.querySelector('#toggleConfiguration');
  configurationButton.addEventListener('click', () => {
    showConfiguration();
  });

  const statsButton = document.querySelector('#toggleStats');
  statsButton.addEventListener('click', () => {
    showStats();
  });
}

// SHORTCUTS

function getToggleShortcuts() {
  return document.querySelector('#toggleShortcuts');
}

function clearShortcuts() {
  const body = document.querySelector('body');
  body.removeEventListener('keyup', _keyMangaReader);
  body.removeEventListener('keydown', _keyStepper);
  body.removeEventListener('keypress', _keyAutoscroll);
  hideElement(document.querySelector('#shortcutsInfo'));
}

function _keyAutoscroll(event) {
  if (event.key === ' ' || event.key === 'Spacebar') {
    event.preventDefault();
    toggleScroll();
  }
}

function _keyMangaReader(event) {
  if (event.key === 'Escape') {
    toggleMangaReader();
  }
}

function _keyStepper(event) {
  if (event.key === 'Tab') {
    event.preventDefault();
    event.stopPropagation();
    performStep();
  }
}

function performStep() {
  const percentage = 0.7;
  window.scroll({
    top: window.scrollY + Math.floor(window.innerHeight * percentage),
    left: 0,
    behavior: 'smooth',
  });
}

function initShortcuts() {
  const body = document.querySelector('body');
  body.addEventListener('keyup', _keyMangaReader);

  const shortcutsInfo = document.querySelector('#shortcutsInfo');
  showElement(shortcutsInfo);

  if (isReader()) {
    if (getToggleStepper().checked) {
      body.addEventListener('keydown', _keyStepper);
    }
    if (getToggleAutoscroll().checked) {
      body.addEventListener('keypress', _keyAutoscroll);
    }
  }
}

function performToggleShortcuts() {
  if (getToggleShortcuts().checked) {
    initShortcuts();
  } else {
    clearShortcuts();
  }
}

function bindShortcuts() {
  const toggleShortcuts = getToggleShortcuts();
  toggleShortcuts.addEventListener('change', (event) => {
    performToggleShortcuts();
    saveCurrentSettings();
  });
  toggleShortcuts.checked = getSetting('toggleShortcuts');
  performToggleShortcuts();
}

// CONTACT ME

function bindContactMe() {
  const contactMeButton = document.querySelector('#contactMe');
  contactMeButton.addEventListener('click', (event) => {
    contactMe();
  });
}

function contactMe() {
  const email = 'midefos@gmail.com';
  const subject = 'About your MangaReader script';
  window.open(`mailto:${email}?subject=${subject}`, '_self');
}

// STATS

function createDefaultStats() {
  return {
    manga: extractManga(),
    milliseconds: 0,
  };
}

function getStatsData() {
  const settings = getSettings();
  return settings.stats;
}

function getCurrentStats() {
  const stats = getStatsData();
  const name = extractManga();
  const mangaStats = stats.get(name);
  if (mangaStats) {
    return mangaStats;
  }
  return createDefaultStats();
}

function isTabActive() {
  return !document.hidden;
}

function printStats() {
  const stats = getStatsData();

  if (isReader()) {
    const name = extractManga();
    stats.delete(name);
  }
  stats.forEach((stat, name) => printStat(stat));
}

function getStatTemplate(stat) {
  return `<li>
            <span data-name="${stat.manga}">${trim(stat.manga)} - ${formatMilliseconds(stat.milliseconds)}</span>
            <button class='off removeStat' title='Remove stat'>🗙</button
          </li>`;
}

function printStat(stat) {
  const statsContainer = document.querySelector('#statsContainer');
  statsContainer.insertAdjacentHTML('beforeend', getStatTemplate(stat));
}

function removeStat(name) {
  const episodeAnchor = document.querySelector(`#statsContainer *[data-name="${name}"]`);
  episodeAnchor.parentNode.remove();
}

function printTotalTimeStats() {
  const totalTimeQuantity = document.querySelector('#totalTimeQuantity');
  totalTimeQuantity.innerText = formatMilliseconds(computeTotalTime());
}

function printCurrentStats() {
  if (isReader()) {
    const stats = getCurrentStats();
    const currentTimeQuantity = document.querySelector('#currentTimeQuantity');
    currentTimeQuantity.innerText = formatMilliseconds(stats.milliseconds);
  } else {
    hideElement(document.querySelector('#currentTime'));
  }
  printTotalTimeStats();
}

function computeTotalTime() {
  const stats = getStatsData();
  let totalMilliseconds = 0;
  stats.forEach((stat, name) => {
    totalMilliseconds += stat.milliseconds;
  });
  return totalMilliseconds;
}

function formatMilliseconds(milliseconds) {
  const hours = Math.floor((milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
  const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
  const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
  return `${addZero(hours)}:${addZero(minutes)}:${addZero(seconds)}`;
}

function addZero(number) {
  const formattedNumber = number.toString();
  if (formattedNumber.length === 1) {
    return `0${formattedNumber}`;
  }
  return formattedNumber;
}

function trim(string, length = 20) {
  return string.length >= length ? `${string.substring(0, length - 3)}...` : string;
}

function startStats() {
  if (isReader()) {
    saveUsage();
  }

  printStats();
  printCurrentStats();
  bindStatsData();
}

function saveUsage() {
  const milliseconds = 1000;
  setInterval(() => {
    if (isTabActive()) {
      const settings = getSettings();
      const mangaStats = getCurrentStats();
      const name = extractManga();
      mangaStats.milliseconds += milliseconds;
      settings.stats.set(name, mangaStats);
      saveSettings(settings);
      printCurrentStats();
    }
  }, milliseconds);
}

function deleteStat(name) {
  const settings = getSettings();
  settings.stats.delete(name);
  saveSettings(settings);
  removeStat(name);
}

function bindStatsData() {
  document.querySelector('body').addEventListener('click', (event) => {
    if (event.target.classList.contains('removeStat')) {
      const name = extractNameRemove(event.target);
      deleteStat(name);
      printCurrentStats();
    }
  });
}

app();