Greasy Fork is available in English.

Ant Design Components Dashboard (React) (^4.0.0) / Ant Design 组件看板

Better view for Ant Design (React) / 更方便的查看 Ant Design (React) 组件

// ==UserScript==
// @name         Ant Design Components Dashboard (React) (^4.0.0) / Ant Design 组件看板
// @namespace    https://github.com/xianghongai/Ant-Design-Components-Dashboard-React
// @version      0.0.7
// @description  Better view for Ant Design (React) / 更方便的查看 Ant Design (React) 组件
// @author       Nicholas Hsiang / 山茶树和葡萄树
// @icon         https://xinlu.ink/favicon.ico
// @match        https://4x.ant.design/*
// @grant        none

// ==/UserScript==
(function () {
  "use strict";
  // NOTE: 已知问题:
  // 当离开“组件”页面进入“设计、文档、资源”时,会引发 DOMException 异常,需要刷新页面才会生效

  const bodyContainer = document.querySelector("body");

  const titleText = "Ant Design of React (4.x)";
  // 有的站点能够直接从菜单 root 操作
  // 有的则不能,因为他们在菜单切换时,是通过 Markdown 动态生态,需要插入到 root 层,不然报错
  const gridSelector = ".aside-container.menu-site";
  const columnSelector = ".ant-menu-item-group";
  const columnTitleSelector = ".ant-menu-item-group-title";

  const menuListSelector = ".ant-menu-item-group-list";
  const menuItemSelector = ".ant-menu-item-group-list .ant-menu-item";
  const helpEnable = true;
  const helpSelector = ".api-container";
  const removeSelector = gridSelector + ">li:not(.ant-menu-item-group)";

  const cloneNodeEnable = true; // 保留原 DOM 节点? 有的站点上设置 true 会造成刷新

  let interval = null;
  let timeout = null;

  let created = false;

  // #region 点击 Nav

  /** 导航菜单点击事件 */
  function handleMenuSiteNav(event) {
    const eventTarget = event.target;
    const tagName = eventTarget.tagName.toLowerCase();

    if (tagName === "a") {
      enterOrLeave(eventTarget.href);
    }
  }

  const menuSiteNavHandler = debounce(handleMenuSiteNav, 500);

  /** 导航菜单绑定事件 */
  function initialMenuSiteNavEvent() {
    var menuSite = document.querySelector("#nav");
    menuSite.addEventListener("click", menuSiteNavHandler);
  }

  // #endregion 点击 Nav

  // #region 组件页面 24 栅格
  function resetLayout(type) {
    const pageSider = document.querySelector(".main-wrapper>.ant-row>.main-menu");
    const pageContainer = document.querySelector(".main-wrapper>.ant-row>.ant-col+.ant-col");

    if (!pageSider || !pageContainer) {
      return false;
    }

    switch (type) {
      case "in":
        pageSider.classList.add("hs-hide");
        pageContainer.classList.remove("ant-col-md-18", "ant-col-lg-18", "ant-col-xl-19", "ant-col-xxl-20");
        pageContainer.classList.add("ant-col-md-24", "ant-col-lg-24", "ant-col-xl-24", "ant-col-xxl-24");
        break;
      default:
        pageSider.classList.remove("hs-hide");
        pageContainer.classList.remove("ant-col-md-24", "ant-col-lg-24", "ant-col-xl-24", "ant-col-xxl-24");
        pageContainer.classList.add("ant-col-md-18", "ant-col-lg-18", "ant-col-xl-19", "ant-col-xxl-20");
        break;
    }
  }
  // #endregion 组件页面 24 栅格

  // #region 看当前 URL 是不是组件页面
  function enterOrLeave(href = window.location.href) {
    if (href.includes("components")) {
      console.log("Ant Design Components Dashboard (React) (^4.0.0)");
      bodyContainer.classList.add("hs-page__component");
      resetLayout("in");

      if (created === false) {
        created = true;
        timeout = window.setTimeout(() => {
          initialDashboard();
        }, 500);
      }
    } else {
      bodyContainer.classList.remove("hs-page__component");
      resetLayout("off");
    }
  }
  // #endregion 看当前 URL 是不是组件页面

  // #region MENU
  /** 生成 Menu */
  function initialMenu() {
    // Wrapper
    const wrapperEle = document.createElement("section");
    wrapperEle.classList.add("hs-dashboard__wrapper", "hs-hide");

    // Header
    const headerEle = document.createElement("header");
    headerEle.classList.add("hs-dashboard__header");

    // Title
    const titleEle = document.createElement("h1");
    titleEle.classList.add("hs-dashboard__title");
    titleEle.innerText = titleText || "";

    // Title → Header
    headerEle.appendChild(titleEle);

    // Menu
    const containerEle = document.createElement("div");
    containerEle.classList.add("hs-dashboard__container");

    // 0. 移除一些不要的元素
    if (removeSelector) {
      const removeEle = document.querySelectorAll(removeSelector);
      if (removeEle) {
        removeEle.forEach((element) => {
          // element.remove();
          element.classList.add("hs-hide");
        });
      }
    }

    // 1. 先从页面上获取 DOM
    let gridEle = null;

    if (cloneNodeEnable) {
      gridEle = document.querySelector(gridSelector).cloneNode(true);
    } else {
      gridEle = document.querySelector(gridSelector);
    }

    let menuEle = document.createElement("nav");

    menuEle.setAttribute("class", gridEle.className);
    menuEle.classList.add("hs-dashboard__grid");

    let menuItemsEle = gridEle.querySelectorAll(columnSelector);

    menuItemsEle.forEach((element) => {
      menuEle.appendChild(element);
    });

    // Menu → Container
    containerEle.appendChild(menuEle);

    // 2. 内部元素追加新的样式
    // 2.1 column
    const columnEle = containerEle.querySelectorAll(columnSelector);
    columnEle.forEach((element) => {
      element.classList.add("hs-dashboard__column");
    });

    // 2.2 title
    const columnTitleEle = containerEle.querySelectorAll(columnTitleSelector);
    columnTitleEle.forEach((element) => {
      element.classList.add("hs-dashboard__item-title");
    });

    // 2.3 menu list
    const menuListEle = containerEle.querySelectorAll(menuListSelector);
    menuListEle.forEach((element) => {
      element.classList.add("hs-dashboard__list");
    });

    // 2.4 menu item
    const menuItemEle = containerEle.querySelectorAll(menuItemSelector);
    menuItemEle.forEach((element) => {
      element.classList.add("hs-dashboard__item");
    });

    // header,container → wrapper
    wrapperEle.appendChild(headerEle);
    wrapperEle.appendChild(containerEle);

    // wrapper → body
    bodyContainer.appendChild(wrapperEle);
  }
  // #endregion MENU

  // #region Event
  /** 注册事件 */
  function handleEvent() {
    const wrapperEle = document.querySelector(".hs-dashboard__wrapper");

    const toggleMenuBtn = document.querySelector('.hs-dashboard__toggle-menu');
    const toggleHelpBtn = document.querySelector('.hs-dashboard__toggle-help');

    function handler(event) {
      const targetEle = event.target;

      const isItem = getParents(targetEle, ".hs-dashboard__item") || hasClass(targetEle, "hs-dashboard__item") || (getParents(targetEle, ".hs-dashboard__column") && getParents(targetEle, ".hs-dashboard__list"));

      const isToggle = getParents(targetEle, ".hs-dashboard__toggle-menu") || hasClass(targetEle, "hs-dashboard__toggle-menu");

      const isHelp = getParents(targetEle, ".hs-dashboard__toggle-help") || hasClass(targetEle, "hs-dashboard__toggle-help");

      if (isItem) {
        clearStyle(wrapperEle);
      } else if (isToggle) {
        wrapperEle.classList.toggle("hs-hide");
        bodyContainer.classList.toggle("hs-body-overflow_hide");
      } else if (isHelp) {
        clearStyle(wrapperEle);
        handleHelp();
      }
    }

    bodyContainer.addEventListener("click", handler);

    document.addEventListener('keydown', function (event) {
      if (event.key === 'Tab' || event.code === 'Tab') {
        event.preventDefault();
        event.stopPropagation();
        toggleMenuBtn?.click();
      }
      // else if (event.key === 'Escape' || event.code === 'Escape') {
      //   toggleMenuBtn.click();
      // }
      else if (event.key === 'F1' || event.code === 'F1') {
        event.preventDefault();
        event.stopPropagation();
        toggleHelpBtn?.click();
      }
    });

  }

  function clearStyle(wrapperEle) {
    wrapperEle.classList.add("hs-hide");
    bodyContainer.classList.remove("hs-body-overflow_hide");
  }
  // #endregion Event

  // #region HELP
  /** 是否启用‘页面滚动至指定位置’ */
  function initialHelp() {
    if (!helpEnable) {
      const ele = document.querySelector(".hs-dashboard__toggle-help");
      ele.classList.add("hs-hide");
    }
  }

  /** 页面滚动至指定位置 */
  function handleHelp() {
    if (!helpSelector) {
      return false;
    }

    const helpEle = document.querySelector(helpSelector);
    const top = helpEle.getBoundingClientRect().top + window.pageYOffset;

    window.scrollTo({
      top,
      behavior: "smooth",
    });
  }
  // #endregion HELP

  // #region STYLE
  /** 添加样式 */
  function initialStyle() {
    const tpl = initialStyleTpl();
    const headEle = document.head || document.getElementsByTagName("head")[0];
    const styleEle = document.createElement("style");

    styleEle.type = "text/css";

    if (styleEle.styleSheet) {
      styleEle.styleSheet.cssText = tpl;
    } else {
      styleEle.appendChild(document.createTextNode(tpl));
    }

    headEle.appendChild(styleEle);
  }

  /** 样式表 */
  function initialStyleTpl() {
    return `
          .hs-hide {
            display: none !important;
          }

          .hs-body-overflow_hide {
            height: 100% !important;
            overflow: hidden !important;
          }

          /* #region toggle */
          .hs-dashboard__toggle {
            position: fixed;
            z-index: 99999;
            top: 5px;
            right: 5px;
          }

          .hs-dashboard__toggle-item {
            width: 28px;
            height: 28px;
            margin-top: 10px;
            margin-bottom: 10px;
            overflow: hidden;
            line-height: 30px !important;
            border-radius: 50%;
            border: 1px solid #ccc;
            text-align: center;
            color: #555;
            background-color: #fff;
            cursor: pointer;
            transition: all 0.2s;
          }

          .hs-dashboard__toggle-item:hover {
            border-color: #aaa;
            color: #111;
          }

          .hs-dashboard__toggle-icon {
            font-style: normal !important;
          }
          /* #endregion toggle */

          /* #region wrapper */
          .hs-dashboard__wrapper {
            position: fixed;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
            z-index: 99998;
            overflow-y: auto;
            background-color: #fff;
            font-size: 16px;
          }

          .hs-dashboard__wrapper::-webkit-scrollbar {
            width: 8px;
            height: 6px;
            background: rgba(0, 0, 0, 0.1);
          }

          .hs-dashboard__wrapper::-webkit-scrollbar-thumb {
            background: rgba(0, 0, 0, 0.3);
          }

          .hs-dashboard__wrapper::-webkit-scrollbar-track {
            background: rgba(0, 0, 0, 0.1);
          }
          /* #endregion wrapper */

          .hs-dashboard__header {
            padding-top: 10px;
            text-align: center;
          }

          .hs-dashboard__title {
            margin: 0;
            padding-top: 10px;
            padding-bottom: 10px;
            font-size: 1em;
            font-weight: normal;
          }

          /* #region grid */
          .hs-dashboard__grid {
            display: flex;
            justify-content: space-evenly;
            /* justify-content: space-around; */
            margin: 0;
            padding: 0;
            list-style: none;
          }

          .hs-dashboard__column {
            padding-right: 10px;
            padding-left: 10px;
          }

          .hs-dashboard__column a {
            text-decoration: none;
          }

          .hs-dashboard__column {
            list-style: none;
          }

          .hs-dashboard__column ul {
            padding: 0;
          }

          .hs-dashboard__column li {
            list-style: none;
          }

          .hs-dashboard__column .hs-dashboard__item-title {
            display: block;
            margin-top: 0 !important;
          }

          /* #endregion grid */

          /* #region custom */
          .fixed-widgets {
            z-index: 9;
          }
          body[data-theme='dark'] .hs-dashboard__wrapper,
          body[data-theme='dark'] .hs-menu-wrapper.ant-menu {
            color: rgba(255,255,255,0.65);
            background-color: #141414;
          }

          body[data-theme='dark'] .hs-dashboard__title {
            color: rgba(255,255,255,0.65);
          }

          .hs-dashboard__column .hs-dashboard__list .hs-dashboard__item,
          .hs-dashboard__column .hs-dashboard__list .ant-menu-item {
            height: 36px;
            line-height: 36px;
            margin-top: 0;
            margin-bottom: 0;
          }
          /* #endregion custom */
  `;
  }
  // #endregion STYLE

  // #region TOGGLE
  /** 生成 Dashboard 开关 */
  function initialToggle() {
    const tpl = initialToggleTpl();
    const ele = document.createElement("section");
    // ele.className = 'hs-dashboard__toggle';
    // ele.setAttribute("class", "hs-dashboard__toggle");
    ele.classList.add("hs-dashboard__toggle");
    ele.innerHTML = tpl;

    // toggle → body
    bodyContainer.appendChild(ele);
  }
  /** Dashboard 开关 DOM */
  function initialToggleTpl() {
    return `
  <!-- menu -->
  <div class="hs-dashboard__toggle-item hs-dashboard__toggle-menu">
    <i class="hs-dashboard__toggle-icon">
      <svg
        viewBox="64 64 896 896"
        focusable="false"
        data-icon="appstore"
        width="1em"
        height="1em"
        fill="currentColor"
        aria-hidden="true"
      >
        <path
          d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"
        ></path>
      </svg>
    </i>
  </div>
  <!-- api -->
  <div class="hs-dashboard__toggle-item hs-dashboard__toggle-help">
    <i class="hs-dashboard__toggle-icon">
      <svg
        viewBox="64 64 896 896"
        focusable="false"
        class=""
        data-icon="bulb"
        width="1em"
        height="1em"
        fill="currentColor"
        aria-hidden="true"
      >
        <path
          d="M632 888H392c-4.4 0-8 3.6-8 8v32c0 17.7 14.3 32 32 32h192c17.7 0 32-14.3 32-32v-32c0-4.4-3.6-8-8-8zM512 64c-181.1 0-328 146.9-328 328 0 121.4 66 227.4 164 284.1V792c0 17.7 14.3 32 32 32h264c17.7 0 32-14.3 32-32V676.1c98-56.7 164-162.7 164-284.1 0-181.1-146.9-328-328-328zm127.9 549.8L604 634.6V752H420V634.6l-35.9-20.8C305.4 568.3 256 484.5 256 392c0-141.4 114.6-256 256-256s256 114.6 256 256c0 92.5-49.4 176.3-128.1 221.8z"
        ></path>
      </svg>
    </i>
  </div>
`;
  }
  // #endregion TOGGLE

  // #region COMMON
  function hasClass(el, className) {
    if (el.classList) {
      return el.classList.contains(className);
    } else {
      return !!el.className.match(new RegExp("(\\s|^)" + className + "(\\s|$)"));
    }
  }

  function getParents(elem, selector) {
    // Element.matches() polyfill
    if (!Element.prototype.matches) {
      Element.prototype.matches =
        Element.prototype.matchesSelector ||
        Element.prototype.mozMatchesSelector ||
        Element.prototype.msMatchesSelector ||
        Element.prototype.oMatchesSelector ||
        Element.prototype.webkitMatchesSelector ||
        function (s) {
          var matches = (this.document || this.ownerDocument).querySelectorAll(s),
            i = matches.length;
          while (--i >= 0 && matches.item(i) !== this) { }
          return i > -1;
        };
    }

    // Get the closest matching element
    for (; elem && elem !== document; elem = elem.parentNode) {
      if (elem.matches(selector)) return elem;
    }
    return null;
  }

  function debounce(callback, delay) {
    var timer = null;

    return function () {
      if (timer) return;

      callback.apply(this, arguments);
      timer = setTimeout(() => (timer = null), delay);
    };
  }

  function throttle(callback, delay) {
    let isThrottled = false,
      args,
      context;

    function wrapper() {
      if (isThrottled) {
        args = arguments;
        context = this;
        return;
      }

      isThrottled = true;
      callback.apply(this, arguments);

      setTimeout(() => {
        isThrottled = false;
        if (args) {
          wrapper.apply(context, args);
          args = context = null;
        }
      }, delay);
    }

    return wrapper;
  }

  // #endregion

  function initialDashboard() {
    window.clearTimeout(timeout);
    initialToggle();
    initialStyle();
    initialMenu();
    initialHelp();
    handleEvent();
  }

  function ready() {
    const originEle = document.querySelector(gridSelector);

    if (originEle) {
      window.clearInterval(interval);
      initialMenuSiteNavEvent();
      enterOrLeave();
    }
  }

  interval = window.setInterval(ready, 1000);
})();