MangaReader

Utility created for a better reading experience.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 = '[email protected]';
  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();