// ==UserScript== // @name:zh-CN Vue API 看板 (^3.0.0) // @name Vue API Dashboard (^3.0.0) // @namespace https://github.com/xianghongai/Vue-API-Dashboard // @version 0.0.2 // @description:zh-CN 更方便的查看 Vue API // @description Better view for Vue API // @author Nicholas Hsiang / 山茶树和葡萄树 // @icon https://haleclipse.org/favicon.ico // @match https://v3.cn.vuejs.org/api/* // @match https://v3.vuejs.org/api/* // @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js // @grant none // @downloadURL https://update.greasyfork.icu/scripts/429826/Vue%20API%20Dashboard%20%28%5E300%29.user.js // @updateURL https://update.greasyfork.icu/scripts/429826/Vue%20API%20Dashboard%20%28%5E300%29.meta.js // ==/UserScript== (() => { "use strict"; const titleText = "Vue API Dashboard"; const gridSelector = ".sidebar .sidebar-links-extend"; const girdIsList = false; // 如果提取的是一个 Node 数组 const columnSelector = ".sidebar-links-extend>li"; const columnTitleSelector = ".sidebar-links-extend>li>a.sidebar-link"; const menuListSelector = ".sidebar-sub-headers"; const menuItemSelector = ".sidebar-sub-headers li"; const menuItemActionSelector = null; const helpEnable = false; const helpSelector = ""; // 使用本扩展的样式风格,将会替换原站点的菜单风格 const customStyleEnable = true; // Dark & Light const cloneNodeEnable = true; // 保留原 DOM 节点? const compactColumnEnable = true; // 紧凑模式,将会合并一些少的列 const compactColumnLimit = 13; // 多列数据组合上限 function initialExtraStyle() { return ` .hs-dashboard__toggle { top: 5px; } `; } /* ------------------------------------------------------------------------- */ let wrapperEle = null; let themeSwitchEle = null; let themeSwitchForm = null; const bodyContainer = document.querySelector("body"); function initialDashboard() { initialToggle(); initialStyle(initialExtraStyle); initialMenu(cloneNodeEnable); initialHelp(); handleEvent(); handleTheme(true); } let interval = null; function ready() { replaceSideBar(); const originEle = document.querySelector(gridSelector); if (originEle) { clearInterval(interval); // Dashboard initialDashboard(); // Other } } interval = setInterval(ready, 1000); function replaceSideBar() { $(".links").css("right","2.5rem"); $(".sidebar").append(''); } // #region MENU /** 生成 Menu */ function initialMenu(clone) { // Wrapper wrapperEle = document.createElement("section"); wrapperEle.classList.add("hs-dashboard__wrapper", "hs-hide"); if (customStyleEnable) { wrapperEle.setAttribute("id", "hs-dashboard"); } // Header const headerEle = document.createElement("header"); headerEle.classList.add("hs-dashboard__header"); // Title → Header const titleEle = document.createElement("h1"); titleEle.classList.add("hs-dashboard__title"); titleEle.innerText = titleText || ""; headerEle.appendChild(titleEle); // Theme → Header if (customStyleEnable) { const themeEle = document.createElement("div"); themeEle.classList.add("hs-theme-switch"); themeEle.innerHTML = initialThemeTpl(); headerEle.appendChild(themeEle); } // Menu const containerEle = document.createElement("div"); containerEle.classList.add("hs-dashboard__container"); // 1. 先从页面上获取 DOM 生成 gird let gridEle = null; let nodeTemp = null; if (girdIsList) { gridEle = document.createElement("div"); const gridListEle = document.querySelectorAll(gridSelector); gridListEle && gridListEle.forEach((element) => { nodeTemp = clone ? element.cloneNode(true) : element; gridEle.appendChild(nodeTemp); }); } else { nodeTemp = document.querySelector(gridSelector); gridEle = clone ? nodeTemp.cloneNode(true) : nodeTemp; gridEle && nodeTemp.removeAttribute("id"); } gridEle.classList.add("hs-dashboard__grid"); // 追加新的样式 // Menu → Container containerEle.appendChild(gridEle); // 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__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"); }); // 2.5 menu item action if (menuItemActionSelector) { const actionEle = containerEle.querySelector(menuItemActionSelector); const menuItemTemp = getParents(actionEle, menuItemSelector); menuItemTemp.classList.add("hs-active"); } if (compactColumnEnable) { const { columns, layout } = compactColumn(containerEle, compactColumnLimit); const ul = document.createElement("ul"); ul.classList.add("hs-dashboard__grid"); Array.isArray(layout) && layout.forEach((item) => { const li = document.createElement("li"); li.classList.add("hs-dashboard__column"); if (Array.isArray(item)) { item.forEach((index) => { const columnItem = columns[index]; const title = columnItem.querySelector(".hs-dashboard__title"); const list = columnItem.querySelector(".hs-dashboard__list"); title && li.appendChild(title); list && li.appendChild(list); }); } else { const columnItem = columns[item]; const title = columnItem.querySelector(".hs-dashboard__title"); const list = columnItem.querySelector(".hs-dashboard__list"); title && li.appendChild(title); list && li.appendChild(list); } ul.appendChild(li); }); containerEle.removeChild(gridEle); containerEle.appendChild(ul); } // header,container → wrapper wrapperEle.appendChild(headerEle); wrapperEle.appendChild(containerEle); // wrapper → body bodyContainer.appendChild(wrapperEle); } function compactColumn(containerEle, limit) { // 只能按列去查,有的列里面是没有 list 的 let columns = containerEle.querySelectorAll(".hs-dashboard__column"); let columnCount = []; // 相邻的数相加不超过指定值,就合并到一个新数组,将组成新的 column let layout = []; // 计算出来的新的数据布局方式 if (columns && columns.length) { columns.forEach((element) => { const listItem = element.querySelectorAll(".hs-dashboard__item"); columnCount.push(listItem.length); }); /** * DESIGN NOTES * * 相邻的数相加 * * 1. 将相邻的坐标存放在 arr * 2. 计算 arr 中坐标的数据量是否超过指定值 * 3. 没超过,继续往 arr 推坐标 * 4. 原先没超过,新的一进来就超过了,说明原先的已经到了阈值,原先的可以合并了推到布局中,但新的要记录下来,参与下一轮计算 * 5. 下一个本身已经超过了阈值,看原先是否有参与计算的,然后各自推到布局中 */ limit = limit || 12; let arr = []; // 待合并的对象 let acc = 0; // 累加判断是否临界 const length = columnCount.length; // 是否到最后 columnCount.forEach((item, index) => { // 1. 新的值临界 if (item > limit) { // 原先的是一个待合并的集合,还是只是一个单独的值 if (arr.length > 1) { layout.push(arr); } else if (arr.length === 1) { layout.push(arr[0]); } layout.push(index); arr = []; acc = 0; } else { // 计算总的数据量 acc += item; // 总数据量临界 if (acc > limit) { if (arr.length) { if (arr.length > 1) { layout.push(arr); } else { layout.push(arr[0]); } } // 新的值参与下一次计算 arr = [index]; acc = item; } else { // 新的值没有临界 arr.push(index); } } if (index === length - 1 && arr.length) { layout.push(arr); } }); } return { columns, layout }; } // #endregion MENU // #region Event /** 注册事件 */ function handleEvent() { if (!wrapperEle) { wrapperEle = document.querySelector(".hs-dashboard__wrapper"); } if (!themeSwitchEle) { themeSwitchEle = document.querySelector(".hs-theme-switch"); } if (!themeSwitchForm) { themeSwitchForm = document.querySelector(".hs-theme-switch__form-control"); } bodyContainer.addEventListener("click", (event) => { const targetEle = event.target; const itemEle = getParents(targetEle, ".hs-dashboard__item"); const isItem = hasClass(targetEle, "hs-dashboard__item"); const isItemWrapper = 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"); const isTheme = getParents(targetEle, ".hs-theme-switch") || hasClass(targetEle, "hs-theme-switch"); if (itemEle || isItem || isItemWrapper) { window.setTimeout(() => { clearStyle(wrapperEle); }, 300); handleItemClick(itemEle, isItem, targetEle); } else if (isToggle) { wrapperEle.classList.toggle("hs-hide"); bodyContainer.classList.toggle("hs-body-overflow_hide"); } else if (isHelp) { clearStyle(wrapperEle); handleHelp(); } else if (isTheme) { handleTheme(); } }); } /** 导航点击 */ function handleItemClick(itemEle, isItem, targetEle) { let itemTemp = null; if (itemEle) { itemTemp = itemEle; } else if (isItem) { itemTemp = targetEle; } if (itemTemp) { const items = wrapperEle.querySelectorAll(".hs-dashboard__item"); items.forEach((element) => { element.classList.remove("hs-active"); element.querySelector("a").classList.remove("active"); }); itemTemp.classList.add("hs-active"); } } /** 退出预览 */ 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(param) { let tpl = initialStyleTpl(); const headEle = document.head || document.getElementsByTagName("head")[0]; const styleEle = document.createElement("style"); let str = null; if (typeof param === "function") { str = param(); } else if (typeof param === "string") { str = param; } if (typeof str === "string") { tpl += str; } styleEle.type = "text/css"; if (styleEle.styleSheet) { styleEle.styleSheet.cssText = tpl; } else { styleEle.appendChild(document.createTextNode(tpl)); } headEle.appendChild(styleEle); } /** 样式表 */ function initialStyleTpl() { return ` :root { --item-height: 36px; --hs-font-size-base: 15px; --hs-global-spacing: 1rem; --hs-color-primary: #1890ff; --hs-spacing-horizontal: var(--hs-global-spacing); --hs-color-white: #fff; --hs-color-black: #000; --hs-color-gray-0: var(--hs-color-white); --hs-color-gray-100: #f5f6f7; --hs-color-gray-200: #ebedf0; --hs-color-gray-300: #dadde1; --hs-color-gray-400: #ccd0d5; --hs-color-gray-500: #bec3c9; --hs-color-gray-600: #8d949e; --hs-color-gray-700: #606770; --hs-color-gray-800: #444950; --hs-color-gray-900: #1c1e21; --hs-color-gray-1000: var(--hs-color-black); --hs-color-emphasis-0: var(--hs-color-gray-0); --hs-color-emphasis-100: var(--hs-color-gray-100); --hs-color-emphasis-200: var(--hs-color-gray-200); --hs-color-emphasis-300: var(--hs-color-gray-300); --hs-color-emphasis-400: var(--hs-color-gray-400); --hs-color-emphasis-500: var(--hs-color-gray-500); --hs-color-emphasis-600: var(--hs-color-gray-600); --hs-color-emphasis-700: var(--hs-color-gray-700); --hs-color-emphasis-800: var(--hs-color-gray-800); --hs-color-emphasis-900: var(--hs-color-gray-900); --hs-color-emphasis-1000: var(--hs-color-gray-1000); } .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: 15px; right: 5px; } .hs-dashboard__toggle-item { position: relative; width: 28px; height: 28px; margin-top: 10px; margin-bottom: 10px; overflow: hidden; line-height: 1 !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 svg{ position: absolute; top: 50%; left: 50%; z-index: 9; transform: translate(-50%, -50%); 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: var(--hs-font-size-base); } .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 { position: relative; padding-top: 10px; text-align: center; } .hs-dashboard__header .hs-dashboard__title { margin: 0; padding-top: 10px; padding-bottom: 10px; font-size: 1em; font-weight: normal; } /* #region theme */ .hs-theme-switch { display: flex; touch-action: pan-x; position: relative; background-color: #fff; border: 0; margin: 0; padding: 0; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-tap-highlight-color: transparent; cursor: pointer; } .hs-theme-switch { width: 50px; height: 24px; padding: 0; border-radius: 30px; background-color: #4d4d4d; transition: all 0.2s ease; } .hs-dashboard__header .hs-theme-switch { position: absolute; top: 10px; left: 10px; } .hs-theme-switch__style { position: relative; width: 24px; height: 24px; line-height: 1; font-size: 20px; text-align: center; } .hs-theme-switch__icon svg { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .hs-theme-switch__thumb { position: absolute; top: 1px; left: 1px; width: 22px; height: 22px; border: 1px solid #ff7938; border-radius: 50%; background-color: #fafafa; box-sizing: border-box; transition: all 0.25s ease; } .hs-theme-switch_checked .hs-theme-switch__thumb { left: 27px; border-color: #4d4d4d; } .hs-toggle-screenreader-only { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } /* #endregion theme */ /* #region grid */ .hs-dashboard__grid { display: flex; justify-content: space-around; margin: 0; padding: 0 40px; list-style: none; } .hs-dashboard__column { padding-right: 10px; padding-left: 10px; } .hs-dashboard__column a { display: block; padding-left: 20px !important; padding-right: 40px !important; text-decoration: none; } .hs-dashboard__container ul:not(.hs-dashboard__grid) { padding: 0; } .hs-dashboard__container li { padding-left: 0 !important; list-style: none; } .hs-dashboard__column .hs-dashboard__title { display: block; padding-left: var(--hs-spacing-horizontal) !important; padding-right: calc(var(--hs-spacing-horizontal) * 2) !important; text-align: left; margin-top: 10px !important; } .hs-dashboard__column .hs-dashboard__list { margin-top: 10px !important; } .hs-dashboard__column .hs-dashboard__list+.hs-dashboard__title { margin-top: var(--hs-global-spacing); padding-top: var(--hs-global-spacing); } .hs-dashboard__column .hs-dashboard__list .hs-dashboard__item { margin: 0 !important; padding-left: 0 !important; padding-right: 0 !important; height: var(--item-height); line-height: var(--item-height); } /* #endregion grid */ /* #region custom */ #hs-dashboard.hs-dashboard__wrapper { transition: all 0.2s ease; } #hs-dashboard .hs-dashboard__column .hs-dashboard__title { font-size: 14px; line-height: 1.5715; color: rgba(0, 0, 0, 0.45); } #hs-dashboard a { overflow: hidden; white-space: nowrap; font-size: 14px; text-overflow: ellipsis; text-decoration: none; color: rgba(0, 0, 0, 0.85); transition: color 0.3s ease; } #hs-dashboard a:hover { color: var(--hs-color-primary); text-decoration: none; outline: 0; } /* light */ #hs-dashboard.hs-dashboard__wrapper_light { color: #161616; background-color: #fff; } #hs-dashboard.hs-dashboard__wrapper_light .hs-dashboard__list+.hs-dashboard__title { border-top: 1px solid var(--hs-color-gray-300); } /* dark */ #hs-dashboard.hs-dashboard__wrapper_dark { color: #fff; background-color: #161616; } #hs-dashboard.hs-dashboard__wrapper_dark .hs-dashboard__list+.hs-dashboard__title { border-top: 1px solid var(--hs-color-gray-600); } #hs-dashboard.hs-dashboard__wrapper_dark .hs-dashboard__title { font-weight: bold; color: #fff; } #hs-dashboard.hs-dashboard__wrapper_dark a { color: #fff; } #hs-dashboard.hs-dashboard__wrapper_dark a:hover { color: var(--hs-color-primary); } #hs-dashboard .hs-dashboard__item.active, #hs-dashboard .hs-dashboard__item.active a, #hs-dashboard .hs-dashboard__item .active, #hs-dashboard .hs-dashboard__item.hs-active, #hs-dashboard .hs-dashboard__item.hs-active a { color: var(--hs-color-primary); } #hs-dashboard .hs-dashboard__item.hs-active { background-color: #e6f7ff; } #hs-dashboard .hs-dashboard__item { position: relative; } #hs-dashboard .hs-dashboard__item::after { content: ' '; position: absolute; top: 0; right: 0; bottom: 0; border-right: 3px solid var(--hs-color-primary); transform: scaleY(0.0001); transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1), -webkit-transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1); opacity: 0; } #hs-dashboard .hs-dashboard__item.hs-active::after { transform: scaleY(1); opacity: 1; transition: transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1), opacity 0.15s cubic-bezier(0.645, 0.045, 0.355, 1), -webkit-transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1); } /* #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 `
`; } // #endregion TOGGLE // #region THEME function handleTheme(isInit) { if (isInit) { const theme = localStorage.getItem("hs_dashboard_theme"); if (theme && theme === "dark") { themeSwitchForm.checked = true; } else { themeSwitchForm.checked = false; } } else { themeSwitchForm.click(); } const checked = themeSwitchForm.checked; if (checked) { localStorage.setItem("hs_dashboard_theme", "dark"); wrapperEle.classList.add("hs-dashboard__wrapper_dark"); wrapperEle.classList.remove("hs-dashboard__wrapper_light"); themeSwitchEle.classList.add("hs-theme-switch_checked"); } else { localStorage.setItem("hs_dashboard_theme", "light"); wrapperEle.classList.add("hs-dashboard__wrapper_light"); wrapperEle.classList.remove("hs-dashboard__wrapper_dark"); themeSwitchEle.classList.remove("hs-theme-switch_checked"); } } function initialThemeTpl() { return `
`; } // #endregion THEME // #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 queryDirectChildren(parent, selector) { const nodes = parent.querySelectorAll(selector); const filteredNodes = [].slice.call(nodes).filter((item) => item.parentNode.closest(selector) === parent.closest(selector)); return filteredNodes; } // #endregion })();