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

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

  1. // ==UserScript==
  2. // @name Ant Design Components Dashboard (React) (^4.0.0) / Ant Design 组件看板
  3. // @namespace https://github.com/xianghongai/Ant-Design-Components-Dashboard-React
  4. // @version 0.0.7
  5. // @description Better view for Ant Design (React) / 更方便的查看 Ant Design (React) 组件
  6. // @author Nicholas Hsiang / 山茶树和葡萄树
  7. // @icon https://xinlu.ink/favicon.ico
  8. // @match https://4x.ant.design/*
  9. // @grant none
  10.  
  11. // ==/UserScript==
  12. (function () {
  13. "use strict";
  14. // NOTE: 已知问题:
  15. // 当离开“组件”页面进入“设计、文档、资源”时,会引发 DOMException 异常,需要刷新页面才会生效
  16.  
  17. const bodyContainer = document.querySelector("body");
  18.  
  19. const titleText = "Ant Design of React (4.x)";
  20. // 有的站点能够直接从菜单 root 操作
  21. // 有的则不能,因为他们在菜单切换时,是通过 Markdown 动态生态,需要插入到 root 层,不然报错
  22. const gridSelector = ".aside-container.menu-site";
  23. const columnSelector = ".ant-menu-item-group";
  24. const columnTitleSelector = ".ant-menu-item-group-title";
  25.  
  26. const menuListSelector = ".ant-menu-item-group-list";
  27. const menuItemSelector = ".ant-menu-item-group-list .ant-menu-item";
  28. const helpEnable = true;
  29. const helpSelector = ".api-container";
  30. const removeSelector = gridSelector + ">li:not(.ant-menu-item-group)";
  31.  
  32. const cloneNodeEnable = true; // 保留原 DOM 节点? 有的站点上设置 true 会造成刷新
  33.  
  34. let interval = null;
  35. let timeout = null;
  36.  
  37. let created = false;
  38.  
  39. // #region 点击 Nav
  40.  
  41. /** 导航菜单点击事件 */
  42. function handleMenuSiteNav(event) {
  43. const eventTarget = event.target;
  44. const tagName = eventTarget.tagName.toLowerCase();
  45.  
  46. if (tagName === "a") {
  47. enterOrLeave(eventTarget.href);
  48. }
  49. }
  50.  
  51. const menuSiteNavHandler = debounce(handleMenuSiteNav, 500);
  52.  
  53. /** 导航菜单绑定事件 */
  54. function initialMenuSiteNavEvent() {
  55. var menuSite = document.querySelector("#nav");
  56. menuSite.addEventListener("click", menuSiteNavHandler);
  57. }
  58.  
  59. // #endregion 点击 Nav
  60.  
  61. // #region 组件页面 24 栅格
  62. function resetLayout(type) {
  63. const pageSider = document.querySelector(".main-wrapper>.ant-row>.main-menu");
  64. const pageContainer = document.querySelector(".main-wrapper>.ant-row>.ant-col+.ant-col");
  65.  
  66. if (!pageSider || !pageContainer) {
  67. return false;
  68. }
  69.  
  70. switch (type) {
  71. case "in":
  72. pageSider.classList.add("hs-hide");
  73. pageContainer.classList.remove("ant-col-md-18", "ant-col-lg-18", "ant-col-xl-19", "ant-col-xxl-20");
  74. pageContainer.classList.add("ant-col-md-24", "ant-col-lg-24", "ant-col-xl-24", "ant-col-xxl-24");
  75. break;
  76. default:
  77. pageSider.classList.remove("hs-hide");
  78. pageContainer.classList.remove("ant-col-md-24", "ant-col-lg-24", "ant-col-xl-24", "ant-col-xxl-24");
  79. pageContainer.classList.add("ant-col-md-18", "ant-col-lg-18", "ant-col-xl-19", "ant-col-xxl-20");
  80. break;
  81. }
  82. }
  83. // #endregion 组件页面 24 栅格
  84.  
  85. // #region 看当前 URL 是不是组件页面
  86. function enterOrLeave(href = window.location.href) {
  87. if (href.includes("components")) {
  88. console.log("Ant Design Components Dashboard (React) (^4.0.0)");
  89. bodyContainer.classList.add("hs-page__component");
  90. resetLayout("in");
  91.  
  92. if (created === false) {
  93. created = true;
  94. timeout = window.setTimeout(() => {
  95. initialDashboard();
  96. }, 500);
  97. }
  98. } else {
  99. bodyContainer.classList.remove("hs-page__component");
  100. resetLayout("off");
  101. }
  102. }
  103. // #endregion 看当前 URL 是不是组件页面
  104.  
  105. // #region MENU
  106. /** 生成 Menu */
  107. function initialMenu() {
  108. // Wrapper
  109. const wrapperEle = document.createElement("section");
  110. wrapperEle.classList.add("hs-dashboard__wrapper", "hs-hide");
  111.  
  112. // Header
  113. const headerEle = document.createElement("header");
  114. headerEle.classList.add("hs-dashboard__header");
  115.  
  116. // Title
  117. const titleEle = document.createElement("h1");
  118. titleEle.classList.add("hs-dashboard__title");
  119. titleEle.innerText = titleText || "";
  120.  
  121. // Title → Header
  122. headerEle.appendChild(titleEle);
  123.  
  124. // Menu
  125. const containerEle = document.createElement("div");
  126. containerEle.classList.add("hs-dashboard__container");
  127.  
  128. // 0. 移除一些不要的元素
  129. if (removeSelector) {
  130. const removeEle = document.querySelectorAll(removeSelector);
  131. if (removeEle) {
  132. removeEle.forEach((element) => {
  133. // element.remove();
  134. element.classList.add("hs-hide");
  135. });
  136. }
  137. }
  138.  
  139. // 1. 先从页面上获取 DOM
  140. let gridEle = null;
  141.  
  142. if (cloneNodeEnable) {
  143. gridEle = document.querySelector(gridSelector).cloneNode(true);
  144. } else {
  145. gridEle = document.querySelector(gridSelector);
  146. }
  147.  
  148. let menuEle = document.createElement("nav");
  149.  
  150. menuEle.setAttribute("class", gridEle.className);
  151. menuEle.classList.add("hs-dashboard__grid");
  152.  
  153. let menuItemsEle = gridEle.querySelectorAll(columnSelector);
  154.  
  155. menuItemsEle.forEach((element) => {
  156. menuEle.appendChild(element);
  157. });
  158.  
  159. // Menu → Container
  160. containerEle.appendChild(menuEle);
  161.  
  162. // 2. 内部元素追加新的样式
  163. // 2.1 column
  164. const columnEle = containerEle.querySelectorAll(columnSelector);
  165. columnEle.forEach((element) => {
  166. element.classList.add("hs-dashboard__column");
  167. });
  168.  
  169. // 2.2 title
  170. const columnTitleEle = containerEle.querySelectorAll(columnTitleSelector);
  171. columnTitleEle.forEach((element) => {
  172. element.classList.add("hs-dashboard__item-title");
  173. });
  174.  
  175. // 2.3 menu list
  176. const menuListEle = containerEle.querySelectorAll(menuListSelector);
  177. menuListEle.forEach((element) => {
  178. element.classList.add("hs-dashboard__list");
  179. });
  180.  
  181. // 2.4 menu item
  182. const menuItemEle = containerEle.querySelectorAll(menuItemSelector);
  183. menuItemEle.forEach((element) => {
  184. element.classList.add("hs-dashboard__item");
  185. });
  186.  
  187. // header,container → wrapper
  188. wrapperEle.appendChild(headerEle);
  189. wrapperEle.appendChild(containerEle);
  190.  
  191. // wrapper → body
  192. bodyContainer.appendChild(wrapperEle);
  193. }
  194. // #endregion MENU
  195.  
  196. // #region Event
  197. /** 注册(不可用)事件 */
  198. function handleEvent() {
  199. const wrapperEle = document.querySelector(".hs-dashboard__wrapper");
  200.  
  201. const toggleMenuBtn = document.querySelector('.hs-dashboard__toggle-menu');
  202. const toggleHelpBtn = document.querySelector('.hs-dashboard__toggle-help');
  203.  
  204. function handler(event) {
  205. const targetEle = event.target;
  206.  
  207. const isItem = getParents(targetEle, ".hs-dashboard__item") || hasClass(targetEle, "hs-dashboard__item") || (getParents(targetEle, ".hs-dashboard__column") && getParents(targetEle, ".hs-dashboard__list"));
  208.  
  209. const isToggle = getParents(targetEle, ".hs-dashboard__toggle-menu") || hasClass(targetEle, "hs-dashboard__toggle-menu");
  210.  
  211. const isHelp = getParents(targetEle, ".hs-dashboard__toggle-help") || hasClass(targetEle, "hs-dashboard__toggle-help");
  212.  
  213. if (isItem) {
  214. clearStyle(wrapperEle);
  215. } else if (isToggle) {
  216. wrapperEle.classList.toggle("hs-hide");
  217. bodyContainer.classList.toggle("hs-body-overflow_hide");
  218. } else if (isHelp) {
  219. clearStyle(wrapperEle);
  220. handleHelp();
  221. }
  222. }
  223.  
  224. bodyContainer.addEventListener("click", handler);
  225.  
  226. document.addEventListener('keydown', function (event) {
  227. if (event.key === 'Tab' || event.code === 'Tab') {
  228. event.preventDefault();
  229. event.stopPropagation();
  230. toggleMenuBtn?.click();
  231. }
  232. // else if (event.key === 'Escape' || event.code === 'Escape') {
  233. // toggleMenuBtn.click();
  234. // }
  235. else if (event.key === 'F1' || event.code === 'F1') {
  236. event.preventDefault();
  237. event.stopPropagation();
  238. toggleHelpBtn?.click();
  239. }
  240. });
  241.  
  242. }
  243.  
  244. function clearStyle(wrapperEle) {
  245. wrapperEle.classList.add("hs-hide");
  246. bodyContainer.classList.remove("hs-body-overflow_hide");
  247. }
  248. // #endregion Event
  249.  
  250. // #region HELP
  251. /** 是否启用‘页面滚动至指定位置’ */
  252. function initialHelp() {
  253. if (!helpEnable) {
  254. const ele = document.querySelector(".hs-dashboard__toggle-help");
  255. ele.classList.add("hs-hide");
  256. }
  257. }
  258.  
  259. /** 页面滚动至指定位置 */
  260. function handleHelp() {
  261. if (!helpSelector) {
  262. return false;
  263. }
  264.  
  265. const helpEle = document.querySelector(helpSelector);
  266. const top = helpEle.getBoundingClientRect().top + window.pageYOffset;
  267.  
  268. window.scrollTo({
  269. top,
  270. behavior: "smooth",
  271. });
  272. }
  273. // #endregion HELP
  274.  
  275. // #region STYLE
  276. /** 添加样式 */
  277. function initialStyle() {
  278. const tpl = initialStyleTpl();
  279. const headEle = document.head || document.getElementsByTagName("head")[0];
  280. const styleEle = document.createElement("style");
  281.  
  282. styleEle.type = "text/css";
  283.  
  284. if (styleEle.styleSheet) {
  285. styleEle.styleSheet.cssText = tpl;
  286. } else {
  287. styleEle.appendChild(document.createTextNode(tpl));
  288. }
  289.  
  290. headEle.appendChild(styleEle);
  291. }
  292.  
  293. /** 样式表 */
  294. function initialStyleTpl() {
  295. return `
  296. .hs-hide {
  297. display: none !important;
  298. }
  299.  
  300. .hs-body-overflow_hide {
  301. height: 100% !important;
  302. overflow: hidden !important;
  303. }
  304.  
  305. /* #region toggle */
  306. .hs-dashboard__toggle {
  307. position: fixed;
  308. z-index: 99999;
  309. top: 5px;
  310. right: 5px;
  311. }
  312.  
  313. .hs-dashboard__toggle-item {
  314. width: 28px;
  315. height: 28px;
  316. margin-top: 10px;
  317. margin-bottom: 10px;
  318. overflow: hidden;
  319. line-height: 30px !important;
  320. border-radius: 50%;
  321. border: 1px solid #ccc;
  322. text-align: center;
  323. color: #555;
  324. background-color: #fff;
  325. cursor: pointer;
  326. transition: all 0.2s;
  327. }
  328.  
  329. .hs-dashboard__toggle-item:hover {
  330. border-color: #aaa;
  331. color: #111;
  332. }
  333.  
  334. .hs-dashboard__toggle-icon {
  335. font-style: normal !important;
  336. }
  337. /* #endregion toggle */
  338.  
  339. /* #region wrapper */
  340. .hs-dashboard__wrapper {
  341. position: fixed;
  342. top: 0;
  343. right: 0;
  344. bottom: 0;
  345. left: 0;
  346. z-index: 99998;
  347. overflow-y: auto;
  348. background-color: #fff;
  349. font-size: 16px;
  350. }
  351.  
  352. .hs-dashboard__wrapper::-webkit-scrollbar {
  353. width: 8px;
  354. height: 6px;
  355. background: rgba(0, 0, 0, 0.1);
  356. }
  357.  
  358. .hs-dashboard__wrapper::-webkit-scrollbar-thumb {
  359. background: rgba(0, 0, 0, 0.3);
  360. }
  361.  
  362. .hs-dashboard__wrapper::-webkit-scrollbar-track {
  363. background: rgba(0, 0, 0, 0.1);
  364. }
  365. /* #endregion wrapper */
  366.  
  367. .hs-dashboard__header {
  368. padding-top: 10px;
  369. text-align: center;
  370. }
  371.  
  372. .hs-dashboard__title {
  373. margin: 0;
  374. padding-top: 10px;
  375. padding-bottom: 10px;
  376. font-size: 1em;
  377. font-weight: normal;
  378. }
  379.  
  380. /* #region grid */
  381. .hs-dashboard__grid {
  382. display: flex;
  383. justify-content: space-evenly;
  384. /* justify-content: space-around; */
  385. margin: 0;
  386. padding: 0;
  387. list-style: none;
  388. }
  389.  
  390. .hs-dashboard__column {
  391. padding-right: 10px;
  392. padding-left: 10px;
  393. }
  394.  
  395. .hs-dashboard__column a {
  396. text-decoration: none;
  397. }
  398.  
  399. .hs-dashboard__column {
  400. list-style: none;
  401. }
  402.  
  403. .hs-dashboard__column ul {
  404. padding: 0;
  405. }
  406.  
  407. .hs-dashboard__column li {
  408. list-style: none;
  409. }
  410.  
  411. .hs-dashboard__column .hs-dashboard__item-title {
  412. display: block;
  413. margin-top: 0 !important;
  414. }
  415.  
  416. /* #endregion grid */
  417.  
  418. /* #region custom */
  419. .fixed-widgets {
  420. z-index: 9;
  421. }
  422. body[data-theme='dark'] .hs-dashboard__wrapper,
  423. body[data-theme='dark'] .hs-menu-wrapper.ant-menu {
  424. color: rgba(255,255,255,0.65);
  425. background-color: #141414;
  426. }
  427.  
  428. body[data-theme='dark'] .hs-dashboard__title {
  429. color: rgba(255,255,255,0.65);
  430. }
  431.  
  432. .hs-dashboard__column .hs-dashboard__list .hs-dashboard__item,
  433. .hs-dashboard__column .hs-dashboard__list .ant-menu-item {
  434. height: 36px;
  435. line-height: 36px;
  436. margin-top: 0;
  437. margin-bottom: 0;
  438. }
  439. /* #endregion custom */
  440. `;
  441. }
  442. // #endregion STYLE
  443.  
  444. // #region TOGGLE
  445. /** 生成 Dashboard 开关 */
  446. function initialToggle() {
  447. const tpl = initialToggleTpl();
  448. const ele = document.createElement("section");
  449. // ele.className = 'hs-dashboard__toggle';
  450. // ele.setAttribute("class", "hs-dashboard__toggle");
  451. ele.classList.add("hs-dashboard__toggle");
  452. ele.innerHTML = tpl;
  453.  
  454. // toggle → body
  455. bodyContainer.appendChild(ele);
  456. }
  457. /** Dashboard 开关 DOM */
  458. function initialToggleTpl() {
  459. return `
  460. <!-- menu -->
  461. <div class="hs-dashboard__toggle-item hs-dashboard__toggle-menu">
  462. <i class="hs-dashboard__toggle-icon">
  463. <svg
  464. viewBox="64 64 896 896"
  465. focusable="false"
  466. data-icon="appstore"
  467. width="1em"
  468. height="1em"
  469. fill="currentColor"
  470. aria-hidden="true"
  471. >
  472. <path
  473. 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"
  474. ></path>
  475. </svg>
  476. </i>
  477. </div>
  478. <!-- api -->
  479. <div class="hs-dashboard__toggle-item hs-dashboard__toggle-help">
  480. <i class="hs-dashboard__toggle-icon">
  481. <svg
  482. viewBox="64 64 896 896"
  483. focusable="false"
  484. class=""
  485. data-icon="bulb"
  486. width="1em"
  487. height="1em"
  488. fill="currentColor"
  489. aria-hidden="true"
  490. >
  491. <path
  492. 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"
  493. ></path>
  494. </svg>
  495. </i>
  496. </div>
  497. `;
  498. }
  499. // #endregion TOGGLE
  500.  
  501. // #region COMMON
  502. function hasClass(el, className) {
  503. if (el.classList) {
  504. return el.classList.contains(className);
  505. } else {
  506. return !!el.className.match(new RegExp("(\\s|^)" + className + "(\\s|$)"));
  507. }
  508. }
  509.  
  510. function getParents(elem, selector) {
  511. // Element.matches() polyfill
  512. if (!Element.prototype.matches) {
  513. Element.prototype.matches =
  514. Element.prototype.matchesSelector ||
  515. Element.prototype.mozMatchesSelector ||
  516. Element.prototype.msMatchesSelector ||
  517. Element.prototype.oMatchesSelector ||
  518. Element.prototype.webkitMatchesSelector ||
  519. function (s) {
  520. var matches = (this.document || this.ownerDocument).querySelectorAll(s),
  521. i = matches.length;
  522. while (--i >= 0 && matches.item(i) !== this) { }
  523. return i > -1;
  524. };
  525. }
  526.  
  527. // Get the closest matching element
  528. for (; elem && elem !== document; elem = elem.parentNode) {
  529. if (elem.matches(selector)) return elem;
  530. }
  531. return null;
  532. }
  533.  
  534. function debounce(callback, delay) {
  535. var timer = null;
  536.  
  537. return function () {
  538. if (timer) return;
  539.  
  540. callback.apply(this, arguments);
  541. timer = setTimeout(() => (timer = null), delay);
  542. };
  543. }
  544.  
  545. function throttle(callback, delay) {
  546. let isThrottled = false,
  547. args,
  548. context;
  549.  
  550. function wrapper() {
  551. if (isThrottled) {
  552. args = arguments;
  553. context = this;
  554. return;
  555. }
  556.  
  557. isThrottled = true;
  558. callback.apply(this, arguments);
  559.  
  560. setTimeout(() => {
  561. isThrottled = false;
  562. if (args) {
  563. wrapper.apply(context, args);
  564. args = context = null;
  565. }
  566. }, delay);
  567. }
  568.  
  569. return wrapper;
  570. }
  571.  
  572. // #endregion
  573.  
  574. function initialDashboard() {
  575. window.clearTimeout(timeout);
  576. initialToggle();
  577. initialStyle();
  578. initialMenu();
  579. initialHelp();
  580. handleEvent();
  581. }
  582.  
  583. function ready() {
  584. const originEle = document.querySelector(gridSelector);
  585.  
  586. if (originEle) {
  587. window.clearInterval(interval);
  588. initialMenuSiteNavEvent();
  589. enterOrLeave();
  590. }
  591. }
  592.  
  593. interval = window.setInterval(ready, 1000);
  594. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址