AngularJS API 看板 (^1.0.0)

更方便的查看 AngularJS API

  1. // ==UserScript==
  2. // @name:zh-CN AngularJS API 看板 (^1.0.0)
  3. // @name AngularJS API Dashboard (^1.0.0)
  4. // @namespace https://github.com/xianghongai/AngularJS-API-Dashboard
  5. // @version 0.0.1
  6. // @description:zh-CN 更方便的查看 AngularJS API
  7. // @description Better view for AngularJS API
  8. // @author Nicholas Hsiang / 山茶树和葡萄树
  9. // @icon https://xinlu.ink/favicon.ico
  10. // @match https://docs.angularjs.org/api
  11. // @match https://docs.angularjs.org/api/*
  12. // @grant none
  13. // ==/UserScript==
  14. (() => {
  15. "use strict";
  16.  
  17. const titleText = "AngularJS API Dashboard";
  18.  
  19. const gridSelector = ".nav-list.naked-list";
  20. const girdIsList = false; // 如果提取的是一个 Node 数组
  21. const columnSelector = ".nav-index-group";
  22. const columnTitleSelector = ".nav-index-group>a.nav-index-group-heading";
  23. const menuListSelector = ".aside-nav";
  24. const menuItemSelector = ".aside-nav .nav-index-listing";
  25.  
  26. const menuItemActionSelector = null;
  27.  
  28. const helpEnable = false;
  29. const helpSelector = "";
  30.  
  31. // 使用本扩展的样式风格,将会替换原站点的菜单风格
  32. const customStyleEnable = true; // Dark & Light
  33. const cloneNodeEnable = true; // 保留原 DOM 节点?
  34.  
  35. const compactColumnEnable = true; // 紧凑模式,将会合并一些少的列
  36. const compactColumnLimit = 20; // 多列数据组合上限
  37.  
  38. // 一列中的 list 过多,提供两种拆分成多列方式
  39. // 1. 这一列内,如果 list 是 li.item.title + li.item 形式 (都有一个共同的 item 类名),按 .title 拆分
  40. // 2. 指定一列个数,按个数拆分
  41. const splitColumn = [
  42. {
  43. enable: true,
  44. splitBy: "length", // selector, length
  45. nthChild: 1,
  46. stoppingSelector: ".nav-index-section", // 目前仅支持 class
  47. stoppingLength: 26,
  48. maxLength: 40,
  49. },
  50. ];
  51.  
  52. function initialExtraStyle() {
  53. return `
  54. .hs-dashboard__toggle {
  55. top: 5px;
  56. }
  57. .hs-dashboard__grid {
  58. justify-content: stretch !important;
  59. }
  60. `;
  61. }
  62.  
  63. /* ------------------------------------------------------------------------- */
  64.  
  65. let wrapperEle = null;
  66. let themeSwitchEle = null;
  67. let themeSwitchForm = null;
  68.  
  69. const bodyContainer = document.querySelector("body");
  70.  
  71. function initialDashboard() {
  72. initialToggle();
  73. initialStyle(initialExtraStyle);
  74. initialMenu(cloneNodeEnable);
  75. initialHelp();
  76. handleEvent();
  77. handleTheme(true);
  78. }
  79.  
  80. let interval = null;
  81.  
  82. function ready() {
  83. const originEle = document.querySelector(gridSelector);
  84.  
  85. if (originEle) {
  86. clearInterval(interval);
  87. // Dashboard
  88. initialDashboard();
  89. // Other
  90. }
  91. }
  92.  
  93. interval = setInterval(ready, 1000);
  94.  
  95. // #region MENU
  96. /** 生成 Menu */
  97. function initialMenu(clone) {
  98. // Wrapper
  99. wrapperEle = document.createElement("section");
  100. wrapperEle.classList.add("hs-dashboard__wrapper", "hs-hide");
  101.  
  102. if (customStyleEnable) {
  103. wrapperEle.setAttribute("id", "hs-dashboard");
  104. }
  105.  
  106. // Header
  107. const headerEle = document.createElement("header");
  108. headerEle.classList.add("hs-dashboard__header");
  109.  
  110. // Title → Header
  111. const titleEle = document.createElement("h1");
  112. titleEle.classList.add("hs-dashboard__title");
  113. titleEle.innerText = titleText || "";
  114. headerEle.appendChild(titleEle);
  115.  
  116. // Theme → Header
  117. if (customStyleEnable) {
  118. const themeEle = document.createElement("div");
  119. themeEle.classList.add("hs-theme-switch");
  120. themeEle.innerHTML = initialThemeTpl();
  121. headerEle.appendChild(themeEle);
  122. }
  123.  
  124. // Menu
  125. const containerEle = document.createElement("div");
  126. containerEle.classList.add("hs-dashboard__container");
  127.  
  128. // 1. 先从页面上获取 DOM 生成 gird
  129. let gridEle = null;
  130. let nodeTemp = null;
  131.  
  132. if (girdIsList) {
  133. gridEle = document.createElement("div");
  134. const gridListEle = document.querySelectorAll(gridSelector);
  135. gridListEle &&
  136. gridListEle.forEach((element) => {
  137. nodeTemp = clone ? element.cloneNode(true) : element;
  138. gridEle.appendChild(nodeTemp);
  139. });
  140. } else {
  141. nodeTemp = document.querySelector(gridSelector);
  142. gridEle = clone ? nodeTemp.cloneNode(true) : nodeTemp;
  143. gridEle && nodeTemp.removeAttribute("id");
  144. }
  145.  
  146. gridEle.classList.add("hs-dashboard__grid"); // 追加新的样式
  147.  
  148. // Menu → Container
  149. containerEle.appendChild(gridEle);
  150.  
  151. // 2. 内部元素追加新的样式
  152. // 2.1 column
  153. const columnEle = containerEle.querySelectorAll(columnSelector);
  154. columnEle.forEach((element) => {
  155. element.classList.add("hs-dashboard__column");
  156. });
  157.  
  158. // 2.2 title
  159. const columnTitleEle = containerEle.querySelectorAll(columnTitleSelector);
  160. columnTitleEle.forEach((element) => {
  161. element.classList.add("hs-dashboard__title");
  162. });
  163.  
  164. // 2.3 menu list
  165. const menuListEle = containerEle.querySelectorAll(menuListSelector);
  166. menuListEle.forEach((element) => {
  167. element.classList.add("hs-dashboard__list");
  168. });
  169.  
  170. // 2.4 menu item
  171. const menuItemEle = containerEle.querySelectorAll(menuItemSelector);
  172. menuItemEle.forEach((element) => {
  173. element.classList.add("hs-dashboard__item");
  174. });
  175.  
  176. // 2.5 menu item action
  177. if (menuItemActionSelector) {
  178. const actionEle = containerEle.querySelector(menuItemActionSelector);
  179. const menuItemTemp = getParents(actionEle, menuItemSelector);
  180. menuItemTemp.classList.add("hs-active");
  181. }
  182.  
  183. // 2.6 split column
  184. splitColumn.forEach((item) => {
  185. item.enable && splitNewColumn(containerEle, item);
  186. });
  187.  
  188. if (compactColumnEnable) {
  189. const { columns, layout } = compactColumn(containerEle, compactColumnLimit);
  190.  
  191. const ul = document.createElement("ul");
  192. ul.classList.add("hs-dashboard__grid");
  193.  
  194. Array.isArray(layout) &&
  195. layout.forEach((item) => {
  196. const li = document.createElement("li");
  197. li.classList.add("hs-dashboard__column");
  198.  
  199. if (Array.isArray(item)) {
  200. item.forEach((index) => {
  201. const columnItem = columns[index];
  202. const title = columnItem.querySelector(".hs-dashboard__title");
  203. const list = columnItem.querySelector(".hs-dashboard__list");
  204. title && li.appendChild(title);
  205. list && li.appendChild(list);
  206. });
  207. } else {
  208. const columnItem = columns[item];
  209. const title = columnItem.querySelector(".hs-dashboard__title");
  210. const list = columnItem.querySelector(".hs-dashboard__list");
  211. title && li.appendChild(title);
  212. list && li.appendChild(list);
  213. }
  214.  
  215. ul.appendChild(li);
  216. });
  217.  
  218. containerEle.removeChild(gridEle);
  219. containerEle.appendChild(ul);
  220. }
  221.  
  222. // header,container → wrapper
  223. wrapperEle.appendChild(headerEle);
  224. wrapperEle.appendChild(containerEle);
  225.  
  226. // wrapper → body
  227. bodyContainer.appendChild(wrapperEle);
  228. }
  229.  
  230. function compactColumn(containerEle, limit) {
  231. // 只能按列去查,有的列里面是没有 list 的
  232. let columns = containerEle.querySelectorAll(".hs-dashboard__column");
  233. let columnCount = []; // 相邻的数相加不超过指定值,就合并到一个新数组,将组成新的 column
  234. let layout = []; // 计算出来的新的数据布局方式
  235.  
  236. if (columns && columns.length) {
  237. columns.forEach((element) => {
  238. const listItem = element.querySelectorAll(".hs-dashboard__item");
  239. columnCount.push(listItem.length);
  240. });
  241.  
  242. /**
  243. * DESIGN NOTES
  244. *
  245. * 相邻的数相加
  246. *
  247. * 1. 将相邻的坐标存放在 arr
  248. * 2. 计算 arr 中坐标的数据量是否超过指定值
  249. * 3. 没超过,继续往 arr 推坐标
  250. * 4. 原先没超过,新的一进来就超过了,说明原先的已经到了阈值,原先的可以合并了推到布局中,但新的要记录下来,参与下一轮计算
  251. * 5. 下一个本身已经超过了阈值,看原先是否有参与计算的,然后各自推到布局中
  252. */
  253.  
  254. limit = limit || 12;
  255.  
  256. let arr = []; // 待合并的对象
  257. let acc = 0; // 累加判断是否临界
  258. const length = columnCount.length; // 是否到最后
  259.  
  260. columnCount.forEach((item, index) => {
  261. // 1. 新的值临界
  262. if (item > limit) {
  263. // 原先的是一个待合并的集合,还是只是一个单独的值
  264. if (arr.length > 1) {
  265. layout.push(arr);
  266. } else if (arr.length === 1) {
  267. layout.push(arr[0]);
  268. }
  269.  
  270. layout.push(index);
  271.  
  272. arr = [];
  273. acc = 0;
  274. } else {
  275. // 计算总的数据量
  276. acc += item;
  277.  
  278. // 总数据量临界
  279. if (acc > limit) {
  280. if (arr.length) {
  281. if (arr.length > 1) {
  282. layout.push(arr);
  283. } else {
  284. layout.push(arr[0]);
  285. }
  286. }
  287.  
  288. // 新的值参与下一次计算
  289. arr = [index];
  290. acc = item;
  291. } else {
  292. // 新的值没有临界
  293. arr.push(index);
  294. }
  295. }
  296.  
  297. if (index === length - 1 && arr.length) {
  298. layout.push(arr);
  299. }
  300. });
  301. }
  302.  
  303. return { columns, layout };
  304. }
  305.  
  306. /**
  307. * 拆解列
  308. * @param {String} splitColumnSelector - 哪一列需要拆解
  309. * @param {String} ColumnSearchesSelector - 列中的列表项选择器
  310. * @param {String} stoppingSelector - 分拆的选择器
  311. *
  312. * DESIGN NOTE:
  313. * 某一列 column 过多,拆解到多个 column 中
  314. * 指定列的 nth,其内部按条件进行拆解
  315. * 在指定列的下一个位置(平级)生成新的 column
  316. */
  317.  
  318. /**
  319. *
  320. * @param {Node} containerEle
  321. * @param {Object} splitColumnConfig
  322. * @param {string} splitColumnConfig.splitBy - 通过哪种方式拆分,selector, length
  323. * @param {number} splitColumnConfig.nthChild - 哪一列要拆分
  324. * @param {string} splitColumnConfig.stoppingSelector - 按哪个 class selector 拆分
  325. * @param {number} splitColumnConfig.stoppingLength - 按多少长度拆分
  326. */
  327. function splitNewColumn(containerEle, splitColumnConfig) {
  328. const { splitBy, nthChild, stoppingSelector, stoppingLength, maxLength } = splitColumnConfig;
  329. const splitStoppingCls = stoppingSelector.slice(1);
  330. const nthChildSelector = `.hs-dashboard__grid .hs-dashboard__column:nth-child(${nthChild})`;
  331. const splitColumnEle = containerEle.querySelector(nthChildSelector);
  332.  
  333. let itemsEle = splitColumnEle.querySelectorAll(".hs-dashboard__item");
  334. const columnLength = itemsEle.length;
  335.  
  336. const StoppingIndex = [];
  337.  
  338. if (splitBy === "length") {
  339. // 取余
  340. const surplus = columnLength % stoppingLength;
  341. // 整除部分
  342. const aliquot = columnLength - surplus;
  343. // 求次数
  344. const step = aliquot / stoppingLength;
  345. let surplusMerged = false;
  346. let stepIndex = 0;
  347.  
  348. for (; stepIndex <= step; stepIndex += 1) {
  349. let index = stoppingLength * stepIndex;
  350. // 有点多余的塞在最后一列
  351. if (stepIndex === step && surplus + stoppingLength <= maxLength) {
  352. surplusMerged = true;
  353. index = columnLength;
  354. }
  355. StoppingIndex.push(index);
  356. }
  357.  
  358. if (!surplusMerged) {
  359. StoppingIndex.push(columnLength);
  360. }
  361.  
  362. console.dir(StoppingIndex);
  363. } else if (splitBy === "selector") {
  364. itemsEle.forEach((element, index) => {
  365. if (element.classList.contains(splitStoppingCls)) {
  366. StoppingIndex.push(index);
  367. }
  368. });
  369.  
  370. StoppingIndex.push(columnLength);
  371. }
  372.  
  373. // 记录跳跃点,形成起止区间
  374. let prevStoppingIndex = 0;
  375.  
  376. for (let index = StoppingIndex.length - 1; index >= 0; index -= 1) {
  377. const indexItem = StoppingIndex[index];
  378. prevStoppingIndex = StoppingIndex[index - 1];
  379. if (index > 1) {
  380. // Create 模式
  381. // const attribute = element.getAttribute('attrName');
  382. // element.setAttribute('attrName', 'value');
  383. // node.innerHTML = '';
  384.  
  385. // Clone 模式
  386. let columnEle = splitColumnEle.cloneNode(false);
  387. const titleEle = splitColumnEle.querySelector(".hs-dashboard__title").cloneNode(false);
  388. const listEle = splitColumnEle.querySelector(".hs-dashboard__list").cloneNode(false);
  389.  
  390. columnEle.appendChild(titleEle);
  391.  
  392. for (let i = prevStoppingIndex; i < indexItem; i++) {
  393. const element = itemsEle[i];
  394. listEle.appendChild(element);
  395. }
  396. columnEle.appendChild(listEle);
  397.  
  398. splitColumnEle.before(columnEle);
  399. splitColumnEle.insertAdjacentElement("afterend", columnEle);
  400. }
  401. }
  402. }
  403.  
  404. // #endregion MENU
  405.  
  406. // #region Event
  407. /** 注册事件 */
  408. function handleEvent() {
  409. if (!wrapperEle) {
  410. wrapperEle = document.querySelector(".hs-dashboard__wrapper");
  411. }
  412.  
  413. if (!themeSwitchEle) {
  414. themeSwitchEle = document.querySelector(".hs-theme-switch");
  415. }
  416.  
  417. if (!themeSwitchForm) {
  418. themeSwitchForm = document.querySelector(".hs-theme-switch__form-control");
  419. }
  420.  
  421. bodyContainer.addEventListener("click", (event) => {
  422. const targetEle = event.target;
  423.  
  424. const itemEle = getParents(targetEle, ".hs-dashboard__item");
  425.  
  426. const isItem = hasClass(targetEle, "hs-dashboard__item");
  427.  
  428. const isItemWrapper = getParents(targetEle, ".hs-dashboard__column") && getParents(targetEle, ".hs-dashboard__list");
  429.  
  430. const isToggle = getParents(targetEle, ".hs-dashboard__toggle-menu") || hasClass(targetEle, "hs-dashboard__toggle-menu");
  431.  
  432. const isHelp = getParents(targetEle, ".hs-dashboard__toggle-help") || hasClass(targetEle, "hs-dashboard__toggle-help");
  433.  
  434. const isTheme = getParents(targetEle, ".hs-theme-switch") || hasClass(targetEle, "hs-theme-switch");
  435.  
  436. if (itemEle || isItem || isItemWrapper) {
  437. window.setTimeout(() => {
  438. clearStyle(wrapperEle);
  439. }, 300);
  440.  
  441. handleItemClick(itemEle, isItem, targetEle);
  442. } else if (isToggle) {
  443. wrapperEle.classList.toggle("hs-hide");
  444. bodyContainer.classList.toggle("hs-body-overflow_hide");
  445. } else if (isHelp) {
  446. clearStyle(wrapperEle);
  447. handleHelp();
  448. } else if (isTheme) {
  449. handleTheme();
  450. }
  451. });
  452. }
  453.  
  454. /** 导航点击 */
  455. function handleItemClick(itemEle, isItem, targetEle) {
  456. let itemTemp = null;
  457.  
  458. if (itemEle) {
  459. itemTemp = itemEle;
  460. } else if (isItem) {
  461. itemTemp = targetEle;
  462. }
  463.  
  464. if (itemTemp) {
  465. const items = wrapperEle.querySelectorAll(".hs-dashboard__item");
  466. items.forEach((element) => {
  467. element.classList.remove("hs-active");
  468. element.querySelector("a").classList.remove("active");
  469. });
  470. itemTemp.classList.add("hs-active");
  471. }
  472. }
  473.  
  474. /** 退出预览 */
  475. function clearStyle(wrapperEle) {
  476. wrapperEle.classList.add("hs-hide");
  477. bodyContainer.classList.remove("hs-body-overflow_hide");
  478. }
  479. // #endregion Event
  480.  
  481. // #region HELP
  482. /** 是否启用‘页面滚动至指定位置’ */
  483. function initialHelp() {
  484. if (!helpEnable) {
  485. const ele = document.querySelector(".hs-dashboard__toggle-help");
  486. ele.classList.add("hs-hide");
  487. }
  488. }
  489.  
  490. /** 页面滚动至指定位置 */
  491. function handleHelp() {
  492. if (!helpSelector) {
  493. return false;
  494. }
  495.  
  496. const helpEle = document.querySelector(helpSelector);
  497. const top = helpEle.getBoundingClientRect().top + window.pageYOffset;
  498.  
  499. window.scrollTo({
  500. top,
  501. behavior: "smooth",
  502. });
  503. }
  504. // #endregion HELP
  505.  
  506. // #region STYLE
  507. /** 添加样式 */
  508. function initialStyle(param) {
  509. let tpl = initialStyleTpl();
  510. const headEle = document.head || document.getElementsByTagName("head")[0];
  511. const styleEle = document.createElement("style");
  512.  
  513. let str = null;
  514.  
  515. if (typeof param === "function") {
  516. str = param();
  517. } else if (typeof param === "string") {
  518. str = param;
  519. }
  520.  
  521. if (typeof str === "string") {
  522. tpl += str;
  523. }
  524.  
  525. styleEle.type = "text/css";
  526.  
  527. if (styleEle.styleSheet) {
  528. styleEle.styleSheet.cssText = tpl;
  529. } else {
  530. styleEle.appendChild(document.createTextNode(tpl));
  531. }
  532.  
  533. headEle.appendChild(styleEle);
  534. }
  535.  
  536. /** 样式表 */
  537. function initialStyleTpl() {
  538. return `
  539.  
  540. :root {
  541. --item-height: 36px;
  542. --hs-font-size-base: 15px;
  543. --hs-global-spacing: 1rem;
  544. --hs-color-primary: #1890ff;
  545. --hs-spacing-horizontal: var(--hs-global-spacing);
  546.  
  547. --hs-color-white: #fff;
  548. --hs-color-black: #000;
  549. --hs-color-gray-0: var(--hs-color-white);
  550. --hs-color-gray-100: #f5f6f7;
  551. --hs-color-gray-200: #ebedf0;
  552. --hs-color-gray-300: #dadde1;
  553. --hs-color-gray-400: #ccd0d5;
  554. --hs-color-gray-500: #bec3c9;
  555. --hs-color-gray-600: #8d949e;
  556. --hs-color-gray-700: #606770;
  557. --hs-color-gray-800: #444950;
  558. --hs-color-gray-900: #1c1e21;
  559. --hs-color-gray-1000: var(--hs-color-black);
  560. --hs-color-emphasis-0: var(--hs-color-gray-0);
  561. --hs-color-emphasis-100: var(--hs-color-gray-100);
  562. --hs-color-emphasis-200: var(--hs-color-gray-200);
  563. --hs-color-emphasis-300: var(--hs-color-gray-300);
  564. --hs-color-emphasis-400: var(--hs-color-gray-400);
  565. --hs-color-emphasis-500: var(--hs-color-gray-500);
  566. --hs-color-emphasis-600: var(--hs-color-gray-600);
  567. --hs-color-emphasis-700: var(--hs-color-gray-700);
  568. --hs-color-emphasis-800: var(--hs-color-gray-800);
  569. --hs-color-emphasis-900: var(--hs-color-gray-900);
  570. --hs-color-emphasis-1000: var(--hs-color-gray-1000);
  571. }
  572. .hs-hide {
  573. display: none !important;
  574. }
  575.  
  576. .hs-body-overflow_hide {
  577. height: 100% !important;
  578. overflow: hidden !important;
  579. }
  580.  
  581. /* #region toggle */
  582. .hs-dashboard__toggle {
  583. position: fixed;
  584. z-index: 99999;
  585. top: 15px;
  586. right: 5px;
  587. }
  588.  
  589. .hs-dashboard__toggle-item {
  590. position: relative;
  591. width: 28px;
  592. height: 28px;
  593. margin-top: 10px;
  594. margin-bottom: 10px;
  595. overflow: hidden;
  596. line-height: 1 !important;
  597. border-radius: 50%;
  598. border: 1px solid #ccc;
  599. text-align: center;
  600. color: #555;
  601. background-color: #fff;
  602. cursor: pointer;
  603. transition: all 0.2s;
  604. }
  605.  
  606. .hs-dashboard__toggle-item:hover {
  607. border-color: #aaa;
  608. color: #111;
  609. }
  610.  
  611. .hs-dashboard__toggle-icon svg{
  612. position: absolute;
  613. top: 50%;
  614. left: 50%;
  615. z-index: 9;
  616. transform: translate(-50%, -50%);
  617. font-style: normal !important;
  618. }
  619. /* #endregion toggle */
  620.  
  621. /* #region wrapper */
  622. .hs-dashboard__wrapper {
  623. position: fixed;
  624. top: 0;
  625. right: 0;
  626. bottom: 0;
  627. left: 0;
  628. z-index: 99998;
  629. overflow-y: auto;
  630. background-color: #fff;
  631. font-size: var(--hs-font-size-base);
  632. }
  633.  
  634. .hs-dashboard__wrapper::-webkit-scrollbar {
  635. width: 8px;
  636. height: 6px;
  637. background: rgba(0, 0, 0, 0.1);
  638. }
  639.  
  640. .hs-dashboard__wrapper::-webkit-scrollbar-thumb {
  641. background: rgba(0, 0, 0, 0.3);
  642. }
  643.  
  644. .hs-dashboard__wrapper::-webkit-scrollbar-track {
  645. background: rgba(0, 0, 0, 0.1);
  646. }
  647. /* #endregion wrapper */
  648.  
  649. .hs-dashboard__header {
  650. position: relative;
  651. padding-top: 10px;
  652. text-align: center;
  653. }
  654.  
  655. .hs-dashboard__header .hs-dashboard__title {
  656. margin: 0;
  657. padding-top: 10px;
  658. padding-bottom: 10px;
  659. font-size: 1em;
  660. font-weight: normal;
  661. }
  662.  
  663. /* #region theme */
  664. .hs-theme-switch {
  665. display: flex;
  666. touch-action: pan-x;
  667. position: relative;
  668. background-color: #fff;
  669. border: 0;
  670. margin: 0;
  671. padding: 0;
  672. user-select: none;
  673. -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  674. -webkit-tap-highlight-color: transparent;
  675. cursor: pointer;
  676. }
  677.  
  678. .hs-theme-switch {
  679. width: 50px;
  680. height: 24px;
  681. padding: 0;
  682. border-radius: 30px;
  683. background-color: #4d4d4d;
  684. transition: all 0.2s ease;
  685. }
  686.  
  687. .hs-dashboard__header .hs-theme-switch {
  688. position: absolute;
  689. top: 10px;
  690. left: 10px;
  691. }
  692.  
  693. .hs-theme-switch__style {
  694. position: relative;
  695. width: 24px;
  696. height: 24px;
  697. line-height: 1;
  698. font-size: 20px;
  699. text-align: center;
  700. }
  701.  
  702. .hs-theme-switch__icon svg {
  703. position: absolute;
  704. top: 50%;
  705. left: 50%;
  706. transform: translate(-50%, -50%);
  707. }
  708.  
  709. .hs-theme-switch__thumb {
  710. position: absolute;
  711. top: 1px;
  712. left: 1px;
  713. width: 22px;
  714. height: 22px;
  715. border: 1px solid #ff7938;
  716. border-radius: 50%;
  717. background-color: #fafafa;
  718. box-sizing: border-box;
  719. transition: all 0.25s ease;
  720. }
  721.  
  722. .hs-theme-switch_checked .hs-theme-switch__thumb {
  723. left: 27px;
  724. border-color: #4d4d4d;
  725. }
  726.  
  727. .hs-toggle-screenreader-only {
  728. border: 0;
  729. clip: rect(0 0 0 0);
  730. height: 1px;
  731. margin: -1px;
  732. overflow: hidden;
  733. padding: 0;
  734. position: absolute;
  735. width: 1px;
  736. }
  737. /* #endregion theme */
  738.  
  739. /* #region grid */
  740. .hs-dashboard__grid {
  741. display: flex;
  742. justify-content: space-around;
  743. margin: 0;
  744. padding: 0 40px;
  745. list-style: none;
  746. }
  747.  
  748. .hs-dashboard__column {
  749. padding-right: 10px;
  750. padding-left: 10px;
  751. }
  752.  
  753. .hs-dashboard__column a {
  754. display: block;
  755. padding-left: 20px !important;
  756. padding-right: 40px !important;
  757. text-decoration: none;
  758. }
  759.  
  760. .hs-dashboard__container ul:not(.hs-dashboard__grid) {
  761. padding: 0;
  762. }
  763.  
  764. .hs-dashboard__container li {
  765. padding-left: 0 !important;
  766. list-style: none;
  767. }
  768.  
  769. .hs-dashboard__column .hs-dashboard__title {
  770. display: block;
  771. padding-left: var(--hs-spacing-horizontal) !important;
  772. padding-right: calc(var(--hs-spacing-horizontal) * 2) !important;
  773. text-align: left;
  774. margin-top: 10px !important;
  775. }
  776.  
  777. .hs-dashboard__column .hs-dashboard__list {
  778. margin-top: 10px !important;
  779. }
  780.  
  781. .hs-dashboard__column .hs-dashboard__list+.hs-dashboard__title {
  782. margin-top: var(--hs-global-spacing);
  783. padding-top: var(--hs-global-spacing);
  784. }
  785.  
  786. .hs-dashboard__column .hs-dashboard__list .hs-dashboard__item {
  787. margin: 0 !important;
  788. padding-left: 0 !important;
  789. padding-right: 0 !important;
  790. height: var(--item-height);
  791. line-height: var(--item-height);
  792. }
  793. /* #endregion grid */
  794.  
  795. /* #region custom */
  796. #hs-dashboard.hs-dashboard__wrapper {
  797. transition: all 0.2s ease;
  798. }
  799.  
  800. #hs-dashboard .hs-dashboard__column .hs-dashboard__title {
  801. font-size: 14px;
  802. line-height: 1.5715;
  803. color: rgba(0, 0, 0, 0.45);
  804. }
  805.  
  806. #hs-dashboard a {
  807. overflow: hidden;
  808. white-space: nowrap;
  809. font-size: 14px;
  810. text-overflow: ellipsis;
  811. text-decoration: none;
  812. color: rgba(0, 0, 0, 0.85);
  813. transition: color 0.3s ease;
  814. }
  815.  
  816. #hs-dashboard a:hover {
  817. color: var(--hs-color-primary);
  818. text-decoration: none;
  819. outline: 0;
  820. }
  821.  
  822. /* light */
  823. #hs-dashboard.hs-dashboard__wrapper_light {
  824. color: #161616;
  825. background-color: #fff;
  826. }
  827.  
  828. #hs-dashboard.hs-dashboard__wrapper_light .hs-dashboard__list+.hs-dashboard__title {
  829. border-top: 1px solid var(--hs-color-gray-300);
  830. }
  831.  
  832. /* dark */
  833. #hs-dashboard.hs-dashboard__wrapper_dark {
  834. color: #fff;
  835. background-color: #161616;
  836. }
  837.  
  838. #hs-dashboard.hs-dashboard__wrapper_dark .hs-dashboard__list+.hs-dashboard__title {
  839. border-top: 1px solid var(--hs-color-gray-600);
  840. }
  841.  
  842. #hs-dashboard.hs-dashboard__wrapper_dark .hs-dashboard__title {
  843. font-weight: bold;
  844. color: #fff;
  845. }
  846.  
  847. #hs-dashboard.hs-dashboard__wrapper_dark a {
  848. color: #fff !important;
  849. }
  850.  
  851. #hs-dashboard.hs-dashboard__wrapper_dark a:hover {
  852. color: var(--hs-color-primary);
  853. }
  854.  
  855. /* #hs-dashboard .hs-dashboard__item.active, */
  856. /* #hs-dashboard .hs-dashboard__item.active a, */
  857. /* #hs-dashboard .hs-dashboard__item .active, */
  858. #hs-dashboard .hs-dashboard__item.hs-active,
  859. #hs-dashboard .hs-dashboard__item.hs-active a {
  860. color: var(--hs-color-primary) !important;
  861. }
  862.  
  863. #hs-dashboard .hs-dashboard__item.hs-active {
  864. background-color: #e6f7ff;
  865. }
  866.  
  867. #hs-dashboard .hs-dashboard__item {
  868. position: relative;
  869. }
  870.  
  871. #hs-dashboard .hs-dashboard__item::after {
  872. content: ' ';
  873. position: absolute;
  874. top: 0;
  875. right: 0;
  876. bottom: 0;
  877. border-right: 3px solid var(--hs-color-primary);
  878. transform: scaleY(0.0001);
  879. transition: transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1),
  880. opacity 0.15s cubic-bezier(0.215, 0.61, 0.355, 1),
  881. -webkit-transform 0.15s cubic-bezier(0.215, 0.61, 0.355, 1);
  882. opacity: 0;
  883. }
  884.  
  885. #hs-dashboard .hs-dashboard__item.hs-active::after {
  886. transform: scaleY(1);
  887. opacity: 1;
  888. transition: transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1),
  889. opacity 0.15s cubic-bezier(0.645, 0.045, 0.355, 1),
  890. -webkit-transform 0.15s cubic-bezier(0.645, 0.045, 0.355, 1);
  891. }
  892. /* #endregion custom */
  893.  
  894. `;
  895. }
  896. // #endregion STYLE
  897.  
  898. // #region TOGGLE
  899. /** 生成 Dashboard 开关 */
  900. function initialToggle() {
  901. const tpl = initialToggleTpl();
  902. const ele = document.createElement("section");
  903. // ele.className = 'hs-dashboard__toggle';
  904. // ele.setAttribute("class", "hs-dashboard__toggle");
  905. ele.classList.add("hs-dashboard__toggle");
  906. ele.innerHTML = tpl;
  907.  
  908. // toggle → body
  909. bodyContainer.appendChild(ele);
  910. }
  911. /** Dashboard 开关 DOM */
  912. function initialToggleTpl() {
  913. return `
  914. <!-- menu -->
  915. <div class="hs-dashboard__toggle-item hs-dashboard__toggle-menu">
  916. <i class="hs-dashboard__toggle-icon">
  917. <svg
  918. viewBox="64 64 896 896"
  919. focusable="false"
  920. data-icon="appstore"
  921. width="1em"
  922. height="1em"
  923. fill="currentColor"
  924. aria-hidden="true"
  925. >
  926. <path
  927. 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"
  928. ></path>
  929. </svg>
  930. </i>
  931. </div>
  932. <!-- api -->
  933. <div class="hs-dashboard__toggle-item hs-dashboard__toggle-help">
  934. <i class="hs-dashboard__toggle-icon">
  935. <svg
  936. viewBox="64 64 896 896"
  937. focusable="false"
  938. class=""
  939. data-icon="bulb"
  940. width="1em"
  941. height="1em"
  942. fill="currentColor"
  943. aria-hidden="true"
  944. >
  945. <path
  946. 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"
  947. ></path>
  948. </svg>
  949. </i>
  950. </div>
  951. `;
  952. }
  953. // #endregion TOGGLE
  954.  
  955. // #region THEME
  956. function handleTheme(isInit) {
  957. if (isInit) {
  958. const theme = localStorage.getItem("hs_dashboard_theme");
  959.  
  960. if (theme && theme === "dark") {
  961. themeSwitchForm.checked = true;
  962. } else {
  963. themeSwitchForm.checked = false;
  964. }
  965. } else {
  966. themeSwitchForm.click();
  967. }
  968.  
  969. const checked = themeSwitchForm.checked;
  970.  
  971. if (checked) {
  972. localStorage.setItem("hs_dashboard_theme", "dark");
  973. wrapperEle.classList.add("hs-dashboard__wrapper_dark");
  974. wrapperEle.classList.remove("hs-dashboard__wrapper_light");
  975. themeSwitchEle.classList.add("hs-theme-switch_checked");
  976. } else {
  977. localStorage.setItem("hs_dashboard_theme", "light");
  978. wrapperEle.classList.add("hs-dashboard__wrapper_light");
  979. wrapperEle.classList.remove("hs-dashboard__wrapper_dark");
  980. themeSwitchEle.classList.remove("hs-theme-switch_checked");
  981. }
  982. }
  983.  
  984. function initialThemeTpl() {
  985. return `
  986. <input type="checkbox" class="hs-toggle-screenreader-only hs-theme-switch__form-control" title="Dark mode" />
  987. <div class="hs-theme-switch__style hs-theme-switch__style_dark">
  988. <i class="hs-theme-switch__icon">
  989. <svg
  990. t="1588325093630"
  991. class="icon"
  992. viewBox="0 0 1024 1024"
  993. version="1.1"
  994. xmlns="http://www.w3.org/2000/svg"
  995. p-id="11008"
  996. width="1em"
  997. height="1em"
  998. >
  999. <path
  1000. d="M483.555556 964.266667c-164.977778 0-315.733333-85.333333-398.222223-224.711111 19.911111 2.844444 39.822222 2.844444 56.888889 2.844444 275.911111 0 500.622222-224.711111 500.622222-500.622222 0-68.266667-14.222222-133.688889-39.822222-193.422222 201.955556 54.044444 347.022222 238.933333 347.022222 449.422222 0 256-210.488889 466.488889-466.488888 466.488889z"
  1001. fill="#F7FF53"
  1002. p-id="11009"
  1003. ></path>
  1004. <path
  1005. d="M631.466667 73.955556c179.2 62.577778 301.511111 230.4 301.511111 423.822222 0 247.466667-201.955556 449.422222-449.422222 449.422222-147.911111 0-281.6-71.111111-364.088889-187.733333H142.222222c284.444444 0 517.688889-233.244444 517.688889-517.688889 0-56.888889-8.533333-113.777778-28.444444-167.822222M571.733333 22.755556C605.866667 88.177778 625.777778 162.133333 625.777778 241.777778c0 267.377778-216.177778 483.555556-483.555556 483.555555-31.288889 0-59.733333-2.844444-88.177778-8.533333 79.644444 156.444444 241.777778 264.533333 429.511112 264.533333 267.377778 0 483.555556-216.177778 483.555555-483.555555C967.111111 261.688889 796.444444 65.422222 571.733333 22.755556z"
  1006. fill="#303133"
  1007. p-id="11010"
  1008. ></path>
  1009. <path
  1010. d="M787.911111 455.111111c-5.688889-2.844444-8.533333-8.533333-5.688889-14.222222 5.688889-17.066667-2.844444-42.666667-19.911111-48.355556-17.066667-5.688889-39.822222 8.533333-45.511111 22.755556-2.844444 5.688889-8.533333 8.533333-14.222222 5.688889-5.688889-2.844444-8.533333-8.533333-5.688889-14.222222 8.533333-25.6 42.666667-45.511111 73.955555-34.133334 28.444444 11.377778 39.822222 48.355556 31.288889 73.955556-2.844444 5.688889-8.533333 8.533333-14.222222 8.533333"
  1011. fill="#303133"
  1012. p-id="11011"
  1013. ></path>
  1014. <path
  1015. d="M608.711111 620.088889c-14.222222 0-28.444444-2.844444-39.822222-11.377778-31.288889-22.755556-31.288889-65.422222-31.288889-68.266667 0-8.533333 8.533333-17.066667 17.066667-17.066666s17.066667 8.533333 17.066666 17.066666 2.844444 31.288889 17.066667 39.822223c11.377778 8.533333 25.6 8.533333 45.511111 0 8.533333-2.844444 19.911111 2.844444 22.755556 11.377777 2.844444 8.533333-2.844444 19.911111-11.377778 22.755556-14.222222 2.844444-25.6 5.688889-36.977778 5.688889zM571.733333 540.444444z"
  1016. fill="#FF2929"
  1017. p-id="11012"
  1018. ></path>
  1019. <path
  1020. d="M810.666667 588.8c-5.688889 19.911111-36.977778 28.444444-68.266667 19.911111-31.288889-8.533333-54.044444-34.133333-48.355556-54.044444 5.688889-19.911111 36.977778-28.444444 68.266667-19.911111 34.133333 11.377778 54.044444 34.133333 48.355556 54.044444"
  1021. fill="#FFA450"
  1022. p-id="11013"
  1023. ></path>
  1024. <path
  1025. d="M864.711111 270.222222c14.222222 42.666667 19.911111 91.022222 19.911111 136.533334 0 258.844444-213.333333 466.488889-477.866666 466.488888-96.711111 0-187.733333-28.444444-264.533334-76.8 82.488889 93.866667 204.8 156.444444 344.177778 156.444445C736.711111 952.888889 938.666667 756.622222 938.666667 512c0-88.177778-28.444444-173.511111-73.955556-241.777778z"
  1026. fill="#FF7938"
  1027. p-id="11014"
  1028. ></path>
  1029. </svg>
  1030. </i>
  1031. </div>
  1032. <div class="hs-theme-switch__style hs-theme-switch__style_light">
  1033. <i class="hs-theme-switch__icon">
  1034. <svg
  1035. t="1588324703446"
  1036. class="icon"
  1037. viewBox="0 0 1024 1024"
  1038. version="1.1"
  1039. xmlns="http://www.w3.org/2000/svg"
  1040. p-id="6232"
  1041. width="1em"
  1042. height="1em"
  1043. >
  1044. <path
  1045. d="M792.35 835.94l-128.09-30.32c-17.73-4.2-36.12 3.66-45.34 19.37l-66.64 113.52c-15.83 26.97-54.67 27.4-71.1 0.79l-69.14-112.02c-9.57-15.5-28.13-22.95-45.76-18.36l-127.39 33.15c-30.26 7.88-58.03-19.29-50.83-49.72l30.32-128.09c4.2-17.73-3.66-36.12-19.37-45.34L85.49 552.28c-26.97-15.83-27.4-54.67-0.79-71.1l112.02-69.14c15.5-9.57 22.95-28.13 18.36-45.76l-33.15-127.39c-7.88-30.26 19.29-58.03 49.72-50.83l128.09 30.32c17.73 4.2 36.12-3.66 45.34-19.37l66.64-113.52c15.83-26.97 54.67-27.4 71.1-0.79l69.14 112.02c9.57 15.5 28.13 22.95 45.76 18.36l127.39-33.15c30.26-7.88 58.03 19.29 50.83 49.72l-30.32 128.09c-4.2 17.73 3.66 36.12 19.37 45.34l113.52 66.64c26.97 15.83 27.4 54.67 0.79 71.1l-112.02 69.14c-15.5 9.57-22.95 28.13-18.36 45.76l33.15 127.39c7.88 30.26-19.29 58.03-49.72 50.83z"
  1046. fill="#FF7938"
  1047. p-id="6233"
  1048. ></path>
  1049. <path
  1050. d="M512 512m-207.66 0a207.66 207.66 0 1 0 415.32 0 207.66 207.66 0 1 0-415.32 0Z"
  1051. fill="#F7FF53"
  1052. p-id="6234"
  1053. ></path>
  1054. <path
  1055. d="M442.78 468.74m-25.96 0a25.96 25.96 0 1 0 51.92 0 25.96 25.96 0 1 0-51.92 0Z"
  1056. fill="#303133"
  1057. p-id="6235"
  1058. ></path>
  1059. <path
  1060. d="M581.22 468.74m-25.96 0a25.96 25.96 0 1 0 51.92 0 25.96 25.96 0 1 0-51.92 0Z"
  1061. fill="#303133"
  1062. p-id="6236"
  1063. ></path>
  1064. <path
  1065. d="M442.78 582.02s17.31 48.31 69.22 48.31 69.22-48.31 69.22-48.31H442.78z"
  1066. fill="#FF2929"
  1067. p-id="6237"
  1068. ></path>
  1069. </svg>
  1070. </i>
  1071. </div>
  1072. <div class="hs-theme-switch__thumb"></div>
  1073. `;
  1074. }
  1075. // #endregion THEME
  1076.  
  1077. // #region COMMON
  1078. function hasClass(el, className) {
  1079. if (el.classList) {
  1080. return el.classList.contains(className);
  1081. } else {
  1082. return !!el.className.match(new RegExp("(\\s|^)" + className + "(\\s|$)"));
  1083. }
  1084. }
  1085.  
  1086. function getParents(elem, selector) {
  1087. // Element.matches() polyfill
  1088. if (!Element.prototype.matches) {
  1089. Element.prototype.matches =
  1090. Element.prototype.matchesSelector ||
  1091. Element.prototype.mozMatchesSelector ||
  1092. Element.prototype.msMatchesSelector ||
  1093. Element.prototype.oMatchesSelector ||
  1094. Element.prototype.webkitMatchesSelector ||
  1095. function (s) {
  1096. var matches = (this.document || this.ownerDocument).querySelectorAll(s),
  1097. i = matches.length;
  1098. while (--i >= 0 && matches.item(i) !== this) {}
  1099. return i > -1;
  1100. };
  1101. }
  1102.  
  1103. // Get the closest matching element
  1104. for (; elem && elem !== document; elem = elem.parentNode) {
  1105. if (elem.matches(selector)) return elem;
  1106. }
  1107. return null;
  1108. }
  1109.  
  1110. function queryDirectChildren(parent, selector) {
  1111. const nodes = parent.querySelectorAll(selector);
  1112. const filteredNodes = [].slice.call(nodes).filter((item) => item.parentNode.closest(selector) === parent.closest(selector));
  1113. return filteredNodes;
  1114. }
  1115. // #endregion
  1116. })();