Hi, Ant Design Component Dashboard (5.x)
当前为
// ==UserScript==
// @name Hi, Ant Design Component Dashboard (5.x)🚀
// @namespace https://github.com/xianghongai/Tampermonkey-UserScript
// @version 1.0.1
// @description Hi, Ant Design Component Dashboard (5.x)
// @author Nicholas Hsiang
// @match *://ant.design/*
// @icon https://gw.alipayobjects.com/zos/rmsportal/rlpTLlbMzTNYuZGGCVYM.png
// @grant GM_addStyle
// @grant GM_info
// @run-at document-end
// @grant unsafeWindow
// @license MIT
// ==/UserScript==
(function () {
'use strict';
console.log(GM_info.script.name);
const logoSelector = 'img[alt="logo"]';
const menuSelector = '.ant-app>main>.ant-col:nth-child(1)';
let wrapperEl = null;
main();
/**
* Main function to execute when the script is loaded.
*/
function main() {
ready(() => {
poll(menuSelector, handler, 500);
});
}
/**
* Handle the target element.
*/
function handler() {
toggle();
}
/**
* Toggle the target element.
*/
function toggle() {
const closeSpan = document.createElement('span');
closeSpan.className = 'x-toggle';
closeSpan.innerHTML = icon();
closeSpan.addEventListener('click', (event) => {
if (event.shiftKey) {
wrapperEl.removeAttribute('id');
wrapperEl.style.display = 'block';
return;
}
if (!wrapperEl || wrapperEl.id !== 'x-menu-wrapper') {
wrapperEl = dashboard();
click(wrapperEl, '.ant-menu-item');
return;
}
wrapperEl.style.display = wrapperEl.style.display === 'none' ? 'block' : 'none';
});
document.body.appendChild(closeSpan);
}
/**
* Click the target element.
* @param {Element} currentElement - The target element
* @param {string} selector - The selector of the target element
*/
function click(currentElement, selector) {
currentElement.addEventListener('click', (event) => {
if (matches(event.target, selector)) {
currentElement.style.display = 'none';
}
});
}
/**
* Create the dashboard element.
* @returns {Element} - The dashboard element
*/
function dashboard() {
wrapperEl = document.querySelector(menuSelector);
wrapperEl.setAttribute('id', 'x-menu-wrapper');
const groupSelector = '.ant-menu>.ant-menu-item-group';
const groupEl = Array.from(wrapperEl.querySelectorAll(groupSelector));
const lengths = [];
groupEl.forEach((item) => {
const itemSelector = '.ant-menu-item-group-list>.ant-menu-item';
const itemEl = Array.from(item.querySelectorAll(itemSelector));
const length = itemEl.length;
const titleSelector = '.ant-menu-item-group-title';
const titleEl = item.querySelector(titleSelector);
const title = titleEl.textContent;
titleEl.textContent = `${title} (${length})`;
lengths.push(length);
});
const sum = lengths.reduce((acc, curr) => acc + curr, 0);
const sumText = `🚀 共有组件 ${sum} 个`;
const logoEl = document.querySelector(logoSelector);
if (logoEl) {
logoEl.title = sumText;
}
console.log(sumText);
return wrapperEl;
}
/**
* Execute a function when the document is ready.
* @param {function} eventHandler - Function to execute when the document is ready
*/
function ready(eventHandler) {
if (document.readyState !== 'loading') {
eventHandler();
} else {
document.addEventListener('DOMContentLoaded', eventHandler);
}
}
/**
* Wait for an element to be found on the page using polling.
* @param {string} selector - CSS selector for the element to wait for
* @param {function} callback - Function to execute when the element is found
* @param {number} maxAttempts - Maximum number of attempts to find the element
* @returns {number} intervalId - ID of the interval used to poll for the element
*/
function poll(selector, callback, maxAttempts = 10) {
let attempts = 0;
const intervalId = setInterval(() => {
attempts++;
const element = document.querySelector(selector);
if (element) {
clearInterval(intervalId);
if (callback && typeof callback === 'function') {
callback(element);
}
} else if (attempts >= maxAttempts) {
clearInterval(intervalId);
console.log(`Element ${selector} not found after ${maxAttempts} attempts.`);
}
}, 1000);
return intervalId;
}
/**
* Check if an element matches a CSS selector.
* @param {Element} currentElement - The element to check for a match
* @param {string} selector - CSS selector to match against
* @returns {boolean} - True if the selector matches, false otherwise
*/
function matches(currentElement, selector) {
while (currentElement !== null && currentElement !== document.body) {
if (currentElement.matches(selector)) {
return true;
}
currentElement = currentElement.parentElement;
}
// 检查 body 元素
return document.body.matches(selector);
}
function icon() {
return `<?xml version="1.0" encoding="UTF-8"?><svg width="18" height="18" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 4H6C4.89543 4 4 4.89543 4 6V18C4 19.1046 4.89543 20 6 20H18C19.1046 20 20 19.1046 20 18V6C20 4.89543 19.1046 4 18 4Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M18 28H6C4.89543 28 4 28.8954 4 30V42C4 43.1046 4.89543 44 6 44H18C19.1046 44 20 43.1046 20 42V30C20 28.8954 19.1046 28 18 28Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M42 4H30C28.8954 4 28 4.89543 28 6V18C28 19.1046 28.8954 20 30 20H42C43.1046 20 44 19.1046 44 18V6C44 4.89543 43.1046 4 42 4Z" fill="#2F88FF" stroke="#333" stroke-width="3" stroke-linejoin="round"/><path d="M28 28H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M36 36H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><path d="M28 44H44" stroke="#333" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
}
const style = `
.x-toggle {
position: fixed;
top: 24px;
right: 680px;
z-index: 99999;
cursor: pointer;
opacity: 0.8;
transition: opacity 0.3s ease-in-out;
}
.x-toggle:hover {
opacity: 1;
}
#x-menu-wrapper {
position: fixed !important;
top: 64px !important;
right: 0 !important;
bottom: 0 !important;
left: 0 !important;
z-index: 9999 !important;
max-width: 100% !important;
max-height: calc(100vh - 64px) !important;
padding: 16px !important;
background: #fff !important;
border-block-start: 1px solid rgba(5, 5, 5, 0.06) !important;
}
#x-menu-wrapper .ant-menu {
display: grid !important;
grid-auto-flow: column !important;
grid-auto-columns: max-content !important;
max-width: max-content !important;
gap: 16px !important;
overflow: auto;
margin-inline: auto !important;
border-inline-end: none !important;
}
#x-menu-wrapper .ant-menu > .ant-menu-item-only-child {
display: none !important;
}
#x-menu-wrapper .ant-menu > .ant-menu-item-group {
display: grid !important;
grid-template-rows: auto 1fr;
overflow: hidden !important;
}
#x-menu-wrapper .ant-menu > .ant-menu-item-group > .ant-menu-item-group-list {
overflow: auto !important;
}
#x-menu-wrapper .ant-menu > .ant-menu-item-group > .ant-menu-item-group-list > .ant-menu-item {
height: 28px !important;
line-height: 28px !important;
}
`;
GM_addStyle(style);
})();