WebEngage – Enhanced Sidebar

Because 'hover-to-see-anything' is not a lifestyle.

// ==UserScript==
// @name         WebEngage – Enhanced Sidebar
// @namespace    https://dashboard.ksa.webengage.com/*
// @version      2.5
// @license      MIT
// @author       Khaled Saif
// @description  Because 'hover-to-see-anything' is not a lifestyle.
// @match        https://dashboard.webengage.com/*
// @match        https://dashboard.ksa.webengage.com/*
// @match        https://dashboard.in.webengage.com/*
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  // =============== Constants ===============
  const STYLE_ID = 'pinned-sidebar-style';
  const MENU_SELECTOR = '.menu__list';
  const SHORTCUTS = [
    { txt: 'Email Campaigns', href: '/emails/campaign-list/all', icon: 'fl-mail' },
    { txt: 'Events', href: '/events', icon: 'fl-event' },
    { txt: 'Journeys', href: '/journeys/campaign-list/all', icon: 'fl-journey' },
  ];

  // =============== Inject CSS for Sidebar Behavior ===============
  function injectCSS() {
    if (document.getElementById(STYLE_ID)) return;

    const css = `
      /* Pin button */
      .pin-toggle {
        position: absolute;
        top: 10px;
        right: 10px;
        width: 24px;
        height: 24px;
        background: url('https://cdn-icons-png.flaticon.com/512/174/174849.png   ') no-repeat center / contain;
        cursor: pointer;
        opacity: 0.6;
        transition: all 0.2s ease;
      }

      .pin-toggle:hover,
      .pin-toggle.pinned {
        opacity: 1;
      }

      .pin-toggle.pinned {
        transform: rotate(45deg);
      }

      /* Force Fast-Access elements visible */
      li.fa-keep.fast-access-title,
      li.fa-keep.fast-access-separator,
      li.fa-keep .fast-access-search,
      li.fa-keep .fast-access-link > span,
      li.fa-keep .fast-access-link i {
        opacity: 1 !important;
        visibility: visible !important;
      }

      /* Fast-Access styling */
      li.fa-keep.fast-access-title {
        margin: 6px 0 4px;
        font-size: 12px;
        text-transform: uppercase;
        padding-left: 12px;
        opacity: .75;
      }

      li.fa-keep.fast-access-separator {
        border-bottom: 1px solid rgba(255,255,255,.1);
        margin: 6px 10px;
      }

      input.fast-access-search {
        width: 90%;
        margin: 6px 5%;
        padding: 6px;
        background: rgba(255,255,255,.1);
        color: #fff;
        border: 0;
        border-radius: 4px;
      }

      .fast-access-link {
        transition: background .15s;
        border-radius: 4px;
      }

      .fast-access-link:hover {
        background: rgba(255,255,255,.08);
      }

      .menu__element.filtered-out {
        display: none !important;
      }
    `;

    const style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  // =============== Inject Fast Access Menu + Search ===============
  function injectFastAccess(list) {
    if (list.querySelector('.fa-keep')) return;

    const base = (location.pathname.match(/^\/accounts\/[^/]+/)||[''])[0];
    let html = '<li class="menu__title margin-top-m"><p>Fast Access</p></li>';
    html += '<li class="fa-keep"><input type="text" placeholder="Search…" class="fast-access-search"></li>';

    SHORTCUTS.forEach(({txt, href, icon}) => {
      html += `
        <li class="menu__element fa-keep">
          <a class="menu__link fast-access-link" href="${base+href}">
            <i class="${icon}"></i><span>${txt}</span>
          </a>
        </li>`;
    });

    html += '<li class="fa-keep fast-access-separator"></li>';
    list.insertAdjacentHTML('afterbegin', html);

    const searchInput = list.querySelector('.fast-access-search');

    // Debounce to improve performance
    const debounce = (fn, delay) => {
      let timeout;
      return () => {
        clearTimeout(timeout);
        timeout = setTimeout(fn, delay);
      };
    };

    // Search filter + auto-expand
    searchInput.addEventListener('input', debounce(() => {
      const q = searchInput.value.toLowerCase();

      list.querySelectorAll('.menu__element:not(.fa-keep)').forEach(li => {
        const match = li.textContent.toLowerCase().includes(q);
        li.classList.toggle('filtered-out', !match);
      });

      list.querySelectorAll('.menu__group').forEach(group => {
        const hasVisibleItem = Array.from(group.querySelectorAll('li.menu__element'))
          .some(li => !li.classList.contains('filtered-out'));

        group.classList.toggle('menu__group--is-active', hasVisibleItem);
      });
    }, 200));
  }

  // =============== Inject Pin Toggle Icon ===============
function injectPinToggleButton() {
  const menu = document.querySelector('.menu');
  if (!menu || menu.querySelector('.pin-toggle')) return;

  const pinBtn = document.createElement('div');
  pinBtn.className = 'pin-toggle';
  pinBtn.title = 'Toggle Sidebar Visibility';

  // Restore state from localStorage
  const isPinned = localStorage.getItem('sidebarPinned') === 'true';
  if (isPinned) {
    pinBtn.classList.add('pinned');
    document.body.classList.add('has-minimized-menu-access');
  }

  // Click handler
  pinBtn.addEventListener('click', () => {
    const wasPinned = document.body.classList.contains('has-minimized-menu-access');

    document.body.classList.toggle('has-minimized-menu-access', !wasPinned);
    pinBtn.classList.toggle('pinned', !wasPinned);
    localStorage.setItem('sidebarPinned', !wasPinned ? 'true' : 'false');
  });

  menu.appendChild(pinBtn);
}

// =============== Body observer – only remove classes if NOT pinned ===============
const bodyObserver = new MutationObserver(muts => {
  muts.forEach(m => {
    if (m.type === 'attributes' && m.attributeName === 'class') {
      const isPinned = localStorage.getItem('sidebarPinned') === 'true';
      if (!isPinned) {
        let cls = document.body.className;
        if (cls.includes('has-minimized-menu')) {
          document.body.className = cls
            .replace(/has-minimized-menu/g, '')
            .replace(/has-minimized-menu-access/g, '')
            .trim();
        }
      }
    }
  });
});

bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
  // =============== Init Function ===============
  function init() {
    injectCSS();
    const list = document.querySelector(MENU_SELECTOR);
    if (!list) return;

    injectFastAccess(list);
    injectPinToggleButton();
  }

  // =============== Poll for Sidebar Load ===============
  const poll = setInterval(() => {
    if (document.querySelector(MENU_SELECTOR)) {
      clearInterval(poll);
      init();
    }
  }, 250);

  // =============== SPA Navigation Hook ===============
  const _push = history.pushState;
  history.pushState = function () {
    _push.apply(this, arguments);
    setTimeout(() => {
      const list = document.querySelector(MENU_SELECTOR);
      if (list) injectFastAccess(list);
      injectPinToggleButton();
    }, 100);
  };

  window.addEventListener('popstate', () => setTimeout(() => {
    const list = document.querySelector(MENU_SELECTOR);
    if (list) injectFastAccess(list);
    injectPinToggleButton();
  }, 100));

    // Start observing body class changes
bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });

})();