您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将 Element Plus 菜单转换为 Dashboard 交互 (按 Shift 键点击可还原为默认菜单)
当前为
- // ==UserScript==
- // @name Hi, Element Plus Component Dashboard🚀
- // @namespace https://github.com/xianghongai/Tampermonkey-UserScript
- // @version 1.0.3
- // @description 将 Element Plus 菜单转换为 Dashboard 交互 (按 Shift 键点击可还原为默认菜单)
- // @author Nicholas Hsiang
- // @match *://element-plus.org/*
- // @icon https://avatars.githubusercontent.com/u/68583457
- // @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 navSelector = '.navbar-menu';
- const menuSelector = '.sidebar';
- const groupSelector = '.sidebar-group:not(:first-child)';
- const componentItemSelector = '.link';
- const titleSelector = '.sidebar-group__title';
- let wrapperElement = null;
- main();
- /**
- * Main function to execute when the script is loaded.
- */
- function main() {
- ready(() => {
- poll(navSelector, handler, 500);
- });
- }
- const wrapperId = 'x-menu-wrapper';
- const wrapperClassName = 'x-menu-wrapper';
- /**
- * Toggle the target element.
- */
- function handler() {
- const toggleElement = document.createElement('span');
- toggleElement.className = 'x-toggle';
- toggleElement.innerHTML = icon();
- toggleElement.addEventListener('click', (event) => {
- // hold shift key to reset
- if (event.shiftKey) {
- wrapperElement.removeAttribute('id');
- wrapperElement.style.display = 'block';
- return;
- }
- // init
- if (!wrapperElement || wrapperElement.id !== wrapperId) {
- wrapperElement = setMenuWrapper();
- // add event listener to component item
- componentItemClickEventListener(wrapperElement, componentItemSelector);
- // handle component page class (hide 'overview' menu item)
- handleComponentPageClass(wrapperElement);
- return;
- }
- wrapperElement.style.display = wrapperElement.style.display === 'none' ? 'grid' : 'none';
- });
- document.body.appendChild(toggleElement);
- // add event listener to navbar
- navClickEventListener();
- }
- /**
- * Click the navbar menu element, handle the component page (hide 'overview' menu item).
- */
- function navClickEventListener() {
- const navElement = document.querySelector(navSelector);
- if (navElement) {
- navElement.addEventListener('click', () => {
- wrapperElement = document.querySelector(menuSelector);
- handleComponentPageClass(wrapperElement);
- });
- }
- }
- /**
- * Handle the component page class.
- * @param {Element} wrapperElement - The wrapper element
- */
- function handleComponentPageClass(wrapperElement) {
- if (window.location.href.includes('component')) {
- wrapperElement.classList.add(wrapperClassName);
- } else {
- wrapperElement.classList.remove(wrapperClassName);
- }
- }
- /**
- * Click the component item, hide the menu wrapper.
- * @param {Element} wrapperElement - The wrapper element
- * @param {string} componentItemSelector - The selector of the component item
- */
- function componentItemClickEventListener(wrapperElement, componentItemSelector) {
- wrapperElement.addEventListener('click', (event) => {
- if (matches(event.target, componentItemSelector)) {
- wrapperElement.style.display = 'none';
- }
- });
- }
- /**
- * Set the menu wrapper element.
- * @returns {Element} - The menu wrapper element
- */
- function setMenuWrapper() {
- wrapperElement = document.querySelector(menuSelector);
- wrapperElement.setAttribute('id', wrapperId);
- // 获取所有 sidebar-group 元素(排除第一个)
- const groupElements = Array.from(wrapperElement.querySelectorAll(groupSelector));
- const componentCounts = [];
- groupElements.forEach((item) => {
- const itemSelector = 'a.link';
- const itemElements = Array.from(item.querySelectorAll(itemSelector));
- const length = itemElements.length;
- const titleElement = item.querySelector(titleSelector);
- const title = titleElement.textContent;
- titleElement.textContent = `${title} (${length})`;
- componentCounts.push(length);
- });
- const totalCount = componentCounts.reduce((acc, curr) => acc + curr, 0);
- const totalText = `🚀 共有组件 ${totalCount} 个`;
- const logoElement = document.querySelector(logoSelector);
- if (logoElement) {
- logoElement.title = totalText;
- }
- console.log(totalText);
- return wrapperElement;
- }
- /**
- * 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;
- }
- #${wrapperId} {
- 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; */
- }
- #${wrapperId} .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;
- }
- #${wrapperId} .doc-content-side {
- display: none !important;
- }
- #${wrapperId} .sidebar-group__title {
- font-size: 12px !important;
- margin-block-end: 4px !important;
- }
- #${wrapperId}.${wrapperClassName} .sidebar-group:nth-child(1) {
- display: none !important;
- }
- #${wrapperId} .sidebar-group {
- padding-block-start: 16px !important;
- }
- #${wrapperId} .sidebar-group .link {
- padding: 6px 8px !important;
- }
- #${wrapperId} .sidebar-group .link-text {
- font-size: 12px !important;
- font-weight: 400 !important;
- }
- `;
- GM_addStyle(style);
- })();