Dicoding Focus Mode - Calm Module Sidebar

Hide progress and streak tracking so Dicoding stays focused on learning navigation.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         Dicoding Focus Mode - Calm Module Sidebar
// @namespace    local.dicoding.focus
// @version      1.2.0
// @description  Hide progress and streak tracking so Dicoding stays focused on learning navigation.
// @author       Thesedays
// @license      MIT
// @match        https://www.dicoding.com/academies/*/tutorials/*
// @match        https://dicoding.com/academies/*/tutorials/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const STYLE_ID = 'dicoding-focus-mode-style';
  const ROOT_SELECTOR = '#modules-list';
  const PROGRESS_HEADER_SELECTOR = `${ROOT_SELECTOR} .sidebar-navigation-v3__content__header`;
  const CATEGORY_ITEM_SELECTOR = `${ROOT_SELECTOR} .modules-category-list__list-item`;
  const MODULE_LIST_SELECTOR = `${ROOT_SELECTOR} .module-list__list`;
  const ITEM_STATUS_SELECTOR = `${ROOT_SELECTOR} .module-list__list-item__status`;
  const STREAK_SELECTOR = '.nav-streak, #streakDropdown, .streak-dropdown-menu';

  function addStyle() {
    if (document.getElementById(STYLE_ID)) {
      return;
    }

    const styleTarget = document.head || document.documentElement;
    if (!styleTarget) {
      return;
    }

    const style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = `
      ${ROOT_SELECTOR} .modules-list__progress,
      ${ROOT_SELECTOR} .modules-list__progress + div,
      ${CATEGORY_ITEM_SELECTOR} > div:last-child:not(:first-child),
      ${ITEM_STATUS_SELECTOR},
      ${STREAK_SELECTOR} {
        display: none !important;
      }

      ${CATEGORY_ITEM_SELECTOR} > div:first-child {
        width: 100% !important;
      }

      ${ROOT_SELECTOR} .module-list__list-item__link {
        margin-left: 0 !important;
      }
    `;

    styleTarget.appendChild(style);
  }

  function hideProgressElements() {
    document.querySelectorAll(PROGRESS_HEADER_SELECTOR).forEach((header) => {
      if (header.querySelector('.modules-list__progress, [role="progressbar"]')) {
        header.remove();
      }
    });

    document.querySelectorAll(`${ROOT_SELECTOR} [role="progressbar"]`).forEach((progressbar) => {
      progressbar.remove();
    });

    document.querySelectorAll(ITEM_STATUS_SELECTOR).forEach((status) => {
      status.remove();
    });

    document.querySelectorAll(`${CATEGORY_ITEM_SELECTOR} > div:last-child:not(:first-child)`).forEach((status) => {
      status.remove();
    });
  }

  function hideStreakElements() {
    document.querySelectorAll('#streakDropdown').forEach((streakDropdown) => {
      const streakContainer = streakDropdown.closest('.nav-streak, .nav-item, li');
      if (streakContainer) {
        streakContainer.remove();
        return;
      }

      streakDropdown.remove();
    });

    document.querySelectorAll('.streak-dropdown-menu').forEach((streakMenu) => {
      streakMenu.remove();
    });
  }

  function escapeSelectorValue(value) {
    if (window.CSS && typeof window.CSS.escape === 'function') {
      return window.CSS.escape(value);
    }

    return value.replace(/["\\]/g, '\\$&');
  }

  function getCategoryToggle(moduleList) {
    if (!moduleList.id) {
      return null;
    }

    return document.querySelector(`${ROOT_SELECTOR} [data-target="#${escapeSelectorValue(moduleList.id)}"]`);
  }

  function setModuleListExpanded(moduleList, shouldExpand) {
    const toggle = getCategoryToggle(moduleList);

    moduleList.classList.toggle('show', shouldExpand);

    if (toggle) {
      toggle.classList.toggle('collapsed', !shouldExpand);
      toggle.setAttribute('aria-expanded', String(shouldExpand));
    }
  }

  function applyAccordion() {
    const root = document.querySelector(ROOT_SELECTOR);
    if (!root || root.dataset.dicodingFocusAccordionReady === 'true') {
      return;
    }

    const moduleLists = Array.from(root.querySelectorAll(MODULE_LIST_SELECTOR));
    if (moduleLists.length === 0) {
      return;
    }

    const activeModuleList = moduleLists.find((moduleList) => moduleList.querySelector('.js-current-module'))
      || root.querySelector(`${MODULE_LIST_SELECTOR}.show`);
    if (!activeModuleList) {
      return;
    }

    moduleLists.forEach((moduleList) => {
      setModuleListExpanded(moduleList, moduleList === activeModuleList);
    });

    root.addEventListener('click', (event) => {
      const toggle = event.target.closest('[data-toggle="collapse"][data-target^="#module-"]');
      if (!toggle || !root.contains(toggle)) {
        return;
      }

      const targetSelector = toggle.getAttribute('data-target');
      if (!targetSelector) {
        return;
      }

      const target = root.querySelector(targetSelector);
      if (!target) {
        return;
      }

      const shouldExpand = !target.classList.contains('show');

      window.setTimeout(() => {
        Array.from(root.querySelectorAll(MODULE_LIST_SELECTOR)).forEach((moduleList) => {
          setModuleListExpanded(moduleList, moduleList === target && shouldExpand);
        });
      }, 0);
    });

    root.dataset.dicodingFocusAccordionReady = 'true';
  }

  function run() {
    addStyle();
    hideProgressElements();
    hideStreakElements();
    applyAccordion();
  }

  run();

  function startObserver() {
    const observerTarget = document.documentElement || document;
    const observer = new MutationObserver(run);

    observer.observe(observerTarget, {
      childList: true,
      subtree: true,
    });
  }

  startObserver();
})();