您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hi, Element Plus Component Dashboard
当前为
// ==UserScript== // @name Hi, Element Plus Component Dashboard🚀 // @namespace https://github.com/xianghongai/Tampermonkey-UserScript // @version 1.0.2 // @description Hi, Element Plus Component Dashboard // @author Nicholas Hsiang // @match *://element-plus.org/* // @icon https://element-plus.org/images/element-plus-logo-small.svg // @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 = '.logo-container img.logo'; const menuSelector = '.sidebar'; let wrapperEl = null; main(); /** * Main function to execute when the script is loaded. */ function main() { ready(() => { poll(menuSelector, handler, 500); }); } /** * Toggle the target element. */ function handler() { const closeSpan = document.createElement('span'); closeSpan.className = 'x-toggle'; closeSpan.innerHTML = icon(); closeSpan.addEventListener('click', (event) => { // hold shift key to reset if (event.shiftKey) { wrapperEl.removeAttribute('id'); wrapperEl.style.display = 'block'; return; } // init if (!wrapperEl || wrapperEl.id !== 'x-menu-wrapper') { wrapperEl = dashboard(); // add component item click event listener componentItemClickEventListener(wrapperEl, '.link'); handleComponentPageClass(wrapperEl); return; } wrapperEl.style.display = wrapperEl.style.display === 'none' ? 'grid' : 'none'; }); document.body.appendChild(closeSpan); navClickEventListener(wrapperEl); } /** * Click the navbar menu element, handle the component page. * @param {Element} wrapperEl - The wrapper element */ function navClickEventListener(wrapperEl) { const navSelector = '.navbar-menu'; const navEl = document.querySelector(navSelector); if (navEl) { navEl.addEventListener('click', () => { handleComponentPageClass(wrapperEl); }); } } function handleComponentPageClass(wrapperEl) { if (window.location.href.includes('component')) { wrapperEl.classList.add('x-dashboard-component'); } else { wrapperEl.classList.remove('x-dashboard-component'); } } /** * Click the target element. * @param {Element} currentElement - The target element * @param {string} selector - The selector of the target element */ function componentItemClickEventListener(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'); // 获取所有 sidebar-group 元素(排除第一个) const groupSelector = '.sidebar-group:not(:first-child)'; const groupEl = Array.from(wrapperEl.querySelectorAll(groupSelector)); const lengths = []; groupEl.forEach((item) => { const itemSelector = 'a.link'; const itemEl = Array.from(item.querySelectorAll(itemSelector)); const length = itemEl.length; const titleSelector = '.sidebar-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: 18px; right: 16px; 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: 55px !important; right: 0 !important; bottom: 0 !important; left: 0 !important; z-index: 9999 !important; max-width: 100% !important; width: 100% !important; max-height: calc(100vh - 55px) !important; padding: 0 !important; background: #fff !important; /* border-block-start: 1px solid rgba(5, 5, 5, 0.06) !important; */ } #x-menu-wrapper .sidebar-groups { display: grid !important; grid-auto-flow: column !important; grid-auto-columns: 220px !important; max-width: max-content !important; gap: 16px !important; overflow: auto; margin-inline: auto !important; border-inline-end: none !important; } #x-menu-wrapper .doc-content-side { display: none !important; } #x-menu-wrapper .sidebar-group__title { font-size: 12px !important; margin-block-end: 4px !important; } #x-menu-wrapper.x-dashboard-component .sidebar-group:nth-child(1) { display: none !important; } #x-menu-wrapper .sidebar-group { padding-block-start: 16px !important; } #x-menu-wrapper .sidebar-group .link { padding: 6px 0 !important; } #x-menu-wrapper .sidebar-group .link-text { font-size: 12px !important; font-weight: 400 !important; } `; GM_addStyle(style); })();