GitHub菜单

为GitHub头添加更多的菜单项,让你能够快速抵达你想要的页面。

  1. // ==UserScript==
  2. // @name GitHub菜单
  3. // @name:zh-CN GitHub菜单
  4. // @name:en GitHub Menu
  5. // @description 为GitHub头添加更多的菜单项,让你能够快速抵达你想要的页面。
  6. // @description:zh-CN 为GitHub头添加更多的菜单项,让你能够快速抵达你想要的页面。
  7. // @description:en Add more menu items on the header of GitHub to quickly reach the page you want.
  8. // @namespace https://github.com/HaleShaw
  9. // @version 1.2.1
  10. // @author HaleShaw
  11. // @copyright 2020+, HaleShaw (https://github.com/HaleShaw)
  12. // @license AGPL-3.0-or-later
  13. // @homepage https://github.com/HaleShaw/TM-GitHubMenu
  14. // @supportURL https://github.com/HaleShaw/TM-GitHubMenu/issues
  15. // @contributionURL https://www.jianwudao.com/
  16. // @icon https://github.githubassets.com/favicon.ico
  17. // @match *://github.com/*
  18. // @match *://github.wdf.sap.corp/*
  19. // @match *://github.tools.sap/*
  20. // @compatible Chrome
  21. // @run-at document-idle
  22. // @grant none
  23. // ==/UserScript==
  24.  
  25. // ==OpenUserJS==
  26. // @author HaleShaw
  27. // @collaborator HaleShaw
  28. // ==/OpenUserJS==
  29.  
  30. (function () {
  31. 'use strict';
  32.  
  33. // 如果你想在Github企业版上使用它,你需要将你的Github企业版域名添加到脚本上方的match列表中。
  34. // If you want to use it at GitHub Enterprise, you should add the domain of GitHub Enterprise in the match list.
  35.  
  36. const urlSeparator = "/";
  37. const defaultAccountName = "HaleShaw";
  38. const defaultAClassName = "HeaderMenu-link no-underline py-3 d-block d-lg-inline-block";
  39. const defaultLiClassName = "border-bottom border-lg-bottom-0 mr-0 mr-lg-3";
  40. const menuArr = ["Stars", "Profile", "Repositories", "Watching", "Settings"];
  41. let hasLogin = false;
  42.  
  43. main();
  44.  
  45. function main() {
  46. logInfo(GM_info.script.name, GM_info.script.version);
  47. let accountName = getAccountName();
  48. if (accountName == undefined) {
  49. console.warn("Failed to get account name, default account name [" + defaultAccountName + "] will be used");
  50. accountName = defaultAccountName;
  51. } else {
  52. hasLogin = true;
  53. }
  54. const menu = getMenu(hasLogin);
  55. if (menu != undefined) {
  56. addMenuItem(menu, accountName, hasLogin);
  57. }
  58. }
  59.  
  60. /**
  61. * Log the title and version at the front of the console.
  62. * @param {String} title title.
  63. * @param {String} version script version.
  64. */
  65. function logInfo(title, version) {
  66. const titleStyle = "color:white;background-color:#606060";
  67. const versionStyle = "color:white;background-color:#1475b2";
  68. const logTitle = " " + title + " ";
  69. const logVersion = " " + version + " ";
  70. console.log("%c" + logTitle + "%c" + logVersion, titleStyle, versionStyle);
  71. }
  72.  
  73. /**
  74. * Add menu item.
  75. */
  76. function addMenuItem(menu, accountName, hasLogin) {
  77. const aClassName = getAClassName(menu, hasLogin);
  78. if (hasLogin) {
  79. for (let i = 0; i < menuArr.length; i++) {
  80. let menuItem = createMenuA(menuArr[i], aClassName, accountName);
  81. menu.appendChild(menuItem);
  82. }
  83. } else if ("github.com" == document.domain) {
  84. // If it is not logged in, and only the domain is 'github.com', then add the menu.
  85. const liClassName = getLiClassName(menu);
  86. for (let i = 0; i < menuArr.length - 2; i++) {
  87. let menuItem = createMenuLi(menuArr[i], liClassName, aClassName, accountName);
  88. menu.appendChild(menuItem);
  89. }
  90. }
  91. }
  92.  
  93. /**
  94. * Create the menu item A.
  95. * @param {String} buttonName button name.
  96. * @param {String} aClassname className of A.
  97. * @param {String} accountName account name.
  98. */
  99. function createMenuA(buttonName, aClassname, accountName) {
  100. const menuItem = document.createElement("a");
  101. menuItem.text = buttonName;
  102. menuItem.className = aClassname;
  103. menuItem.href = getURL(buttonName, accountName);
  104. menuItem.target = "_blank";
  105. return menuItem;
  106. }
  107.  
  108. /**
  109. * Create the menu item LI.
  110. * @param {String} buttonName button name.
  111. * @param {String} liClassName className of LI.
  112. * @param {String} aClassName className of A.
  113. * @param {String} accountName account name.
  114. */
  115. function createMenuLi(buttonName, liClassName, aClassName, accountName) {
  116. const menuItem = document.createElement("li");
  117. menuItem.className = liClassName;
  118. const menuA = createMenuA(buttonName, aClassName, accountName);
  119. menuItem.appendChild(menuA);
  120. return menuItem;
  121. }
  122.  
  123. /**
  124. * Get the URL of the button by the button name and account name.
  125. * @param {String} buttonName button name.
  126. * @param {String} accountName account name.
  127. */
  128. function getURL(buttonName, accountName) {
  129. let url;
  130. switch (buttonName) {
  131. case "Watching":
  132. url = location.origin + urlSeparator + "watching";
  133. break;
  134. case "Settings":
  135. url = location.origin + urlSeparator + "settings/profile";
  136. break;
  137. case "Stars":
  138. url = location.origin + urlSeparator + accountName + "?tab=stars";
  139. break;
  140. case "Profile":
  141. url = location.origin + urlSeparator + accountName;
  142. break;
  143. case "Repositories":
  144. url = location.origin + urlSeparator + accountName + "?tab=repositories";
  145. break;
  146. default:
  147. break;
  148. }
  149. return url;
  150. }
  151.  
  152. /**
  153. * Get account name.
  154. */
  155. function getAccountName() {
  156. let accountName = getAccountNameWay1();
  157. if (accountName == undefined) {
  158. accountName = getAccountNameWay2();
  159. }
  160. return accountName;
  161. }
  162.  
  163. /**
  164. * The first way to get account name.
  165. */
  166. function getAccountNameWay1() {
  167. let accountName;
  168. const detailsEle = document.getElementsByClassName("details-overlay details-reset js-feature-preview-indicator-container");
  169. if (detailsEle && detailsEle != undefined && detailsEle[0] != undefined) {
  170. const data = detailsEle[0].getAttribute("data-feature-preview-indicator-src");
  171. if (data != undefined) {
  172. const dataArr = data.split("/");
  173. accountName = dataArr[2];
  174. }
  175. }
  176. return accountName;
  177. }
  178.  
  179. /**
  180. * The second way to get account name.
  181. */
  182. function getAccountNameWay2() {
  183. let accountName;
  184. const dropdownItems = document.querySelectorAll("a[class=dropdown-item]");
  185. const itemLength = dropdownItems.length;
  186. if (itemLength != 0) {
  187. let accountHref;
  188. for (let i = 0; i < itemLength; i++) {
  189. const profileAttrValue = "Header, go to profile, text:your profile";
  190.  
  191. const attrValue = dropdownItems[i].getAttribute("data-ga-click");
  192. if (profileAttrValue == attrValue) {
  193. accountHref = dropdownItems[i].href;
  194. break;
  195. }
  196. }
  197. if (accountHref != undefined) {
  198. const splitArr = accountHref.split("/");
  199. accountName = splitArr[splitArr.length - 1];
  200. }
  201. }
  202. return accountName;
  203. }
  204.  
  205. /**
  206. * Get the menu object.
  207. * @param {Boolean} hasLogin Whether it is logged in.
  208. */
  209. function getMenu(hasLogin) {
  210. const navs = document.getElementsByTagName("nav");
  211. if (navs && navs != undefined && navs[0] != undefined && "Global" == navs[0].getAttribute("aria-label")) {
  212. return hasLogin ? navs[0] : navs[0].children[0];
  213. }
  214. }
  215.  
  216. /**
  217. * Get the className of menu item A.
  218. * @param {Object} menu menu.
  219. * @param {Boolean} hasLogin hasLogin.
  220. */
  221. function getAClassName(menu, hasLogin) {
  222. let className;
  223. const items = menu.children;
  224. if (hasLogin) {
  225. for (let i = 0; i < items.length; i++) {
  226. if ("A" == items[i].tagName && "Explore" == items[i].innerText) {
  227. className = items[i].className;
  228. break;
  229. }
  230. }
  231. } else {
  232. for (let i = 0; i < items.length; i++) {
  233. if ("LI" == items[i].tagName && "A" == items[i].children[0].tagName) {
  234. className = items[i].children[0].className;
  235. break;
  236. }
  237. }
  238. }
  239. return className ? className : defaultAClassName;
  240. }
  241.  
  242. /**
  243. * Get the className of the menu item LI.
  244. * @param {Object} menu menu.
  245. */
  246. function getLiClassName(menu) {
  247. let className;
  248. const items = menu.children;
  249. for (let i = 0; i < items.length; i++) {
  250. if ("LI" == items[i].tagName && "A" == items[i].children[0].tagName) {
  251. className = items[i].className;
  252. break;
  253. }
  254. }
  255. return className ? className : defaultLiClassName;
  256. }
  257. })();