Github Repository Tab Creator

Template Script to Create your own Repository Tab

  1. // ==UserScript==
  2. // @name Github Repository Tab Creator
  3. // @namespace Violentmonkey Scripts
  4. // @match *://github.com/*/*
  5. // @grant none
  6. // @version 1.0.1
  7. // @author Der_Floh
  8. // @description Template Script to Create your own Repository Tab
  9. // @license MIT
  10. // @icon https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png
  11. // @homepageURL https://greasyfork.org/de/scripts/468808-github-repository-tab-creator
  12. // @supportURL https://greasyfork.org/de/scripts/468808-github-repository-tab-creator/feedback
  13. // ==/UserScript==
  14.  
  15. // jshint esversion: 8
  16. console.info(`%cRunning: ${GM_info.script.name} v${GM_info.script.version}`, 'color: DodgerBlue');
  17.  
  18.  
  19.  
  20. const name1 = "EXAMPLE-NAME"; // <-- Name for the tab (has to be something)
  21. const icon1 = "https://somewebsite/EXAMPLE-ICON.png"; // <-- icon to use for the tab (possible: image link, svg string, html element, empty for no icon)
  22. createTabElem(name1, icon1, (event) => {
  23. const currentLocation = window.location.toString();
  24. const subpage = "EXAMPLE-SUBPAGE"; // <-- Subpage to navigate to (for example https://github.com/username/repositoryname/SUBPAGE)
  25. const link = `/${getUsername(currentLocation)}/${getRepositoryname(currentLocation)}/${subpage}`;
  26. window.location.href = link;
  27. });
  28.  
  29. // Working Example:
  30. /*const name2 = "Stars"
  31. const icon2 = '<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-star d-inline-block mr-2"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"></path></svg>';
  32. createTabElem(name2, icon2, (event) => {
  33. const currentLocation = window.location.toString();
  34. const subpage = "stargazers";
  35. const link = `/${getUsername(currentLocation)}/${getRepositoryname(currentLocation)}/${subpage}`;
  36. window.location.href = link;
  37. });*/
  38.  
  39.  
  40.  
  41. // | ------------+-------------------+------------ |
  42. // | ------------| Given Functions |------------ |
  43. // V ------------+-------------------+------------ V
  44.  
  45. /**
  46. * Gets the username of a github url
  47. * @param {string} url - The url from which to get the username
  48. * @returns {string} The username of the given github url
  49. */
  50. function getUsername(url) {
  51. const parsedUrl = new URL(url);
  52. const pathSegments = parsedUrl.pathname.split('/');
  53.  
  54. if (pathSegments.length >= 3)
  55. return pathSegments[1];
  56.  
  57. return null;
  58. }
  59.  
  60. /**
  61. * Gets the repositoryname of a github url
  62. * @param {string} url - The url from which to get the repositoryname
  63. * @returns {string} The repositoryname of the given github url
  64. */
  65. function getRepositoryname(url) {
  66. const parsedUrl = new URL(url);
  67. const pathSegments = parsedUrl.pathname.split('/');
  68.  
  69. if (pathSegments.length >= 3)
  70. return pathSegments[2];
  71.  
  72. return null;
  73. }
  74.  
  75. /**
  76. * Adds a new Tab to a Repository Page
  77. * @param {string} name - The name of the tab
  78. * @param {Element} svg - The svg element that specifies the icon of the tab
  79. * @param {Function} callback - function that specifies what should happen
  80. * @returns {Element} The created Tab
  81. */
  82. async function createTabElem(name, svg, callback) {
  83. const isStargazers = await handleLocalStorage(name);
  84. const menubar = document.querySelector('ul[class="UnderlineNav-body list-style-none"]');
  85. const id = menubar.querySelectorAll('li[class="d-inline-flex"]').length;
  86. let li = document.createElement("li");
  87. li.dataset.dataViewComponent = true;
  88. const a = document.createElement("a");
  89. a.id = name.toLowerCase() + "-tab";
  90. a.dataset.tabItem = `i${id}${name}-tab`;
  91. a.dataset.pjax = "#repo-content-pjax-container";
  92. a.dataset.turboFrame = "repo-content-turbo-frame";
  93. a.dataset.hotkey = "g c";
  94. a.dataset.analyticsEvent = '{"category":"Underline navbar","action":"Click tab","label":"Settings","target":"UNDERLINE_NAV.TAB"}';
  95. a.dataset.viewComponent = "true";
  96. a.classList.add("UnderlineNav-item", "no-wrap", "js-responsive-underlinenav-item", "js-selected-navigation-item");
  97. a.addEventListener('click', (event) => {
  98. event.preventDefault();
  99. localStorage.setItem(name.toLowerCase(), true);
  100. callback(event);
  101. });
  102. if (isStargazers)
  103. a.setAttribute("aria-current", "page");
  104.  
  105. if (svg && svg != "") {
  106. let svgElem;
  107. if (isElement(svg)) {
  108. svgElem = svg.cloneNode(true);
  109. } else if (svg.startsWith("http")) {
  110. svgElem = document.createElement("img");
  111. svgElem.src = svg;
  112. } else {
  113. svgElem = document.createElement("div");
  114. svgElem.innerHTML = svg;
  115. svgElem.style.display = "flex";
  116. svgElem.style.justifyContent = "center";
  117. svgElem.style.alignItems = "center";
  118. }
  119. svgElem.style.maxWidth = "16px";
  120. svgElem.style.maxHeight = "16px";
  121. a.appendChild(svgElem);
  122. }
  123. const span1 = document.createElement("span");
  124. span1.dataset.content = name;
  125. span1.textContent = name;
  126. a.appendChild(span1);
  127. const span2 = document.createElement("span");
  128. span2.id = "code-repo-tab-count";
  129. span2.dataset.pjaxReplace = "";
  130. span2.dataset.turboReplace = "";
  131. span2.title = "Not available";
  132. span2.dataset.viewComponent = true;
  133. span2.classList.add("Counter");
  134. a.appendChild(span2);
  135. li.appendChild(a);
  136. menubar.appendChild(li);
  137. return li;
  138. }
  139.  
  140. /**
  141. * Handles the current selected tab inside the localStorage
  142. * @param {string} tabName - The tabname to handle
  143. * @returns {boolean} True if the tab is selected
  144. */
  145. async function handleLocalStorage(tabName) {
  146. const isStargazers = localStorage.getItem(tabName.toLowerCase()) == "true";
  147. if (isStargazers)
  148. localStorage.removeItem(tabName.toLowerCase());
  149.  
  150. window.addEventListener("load", () => {
  151. if (!isStargazers)
  152. return;
  153. const tab = document.getElementById(tabName.toLowerCase() + "-tab");
  154. const intervalId = setInterval(() => {
  155. if (tab.getAttribute("aria-current") == "page")
  156. clearInterval(intervalId);
  157. if (isStargazers)
  158. tab.setAttribute("aria-current", "page");
  159. }, 10);
  160. });
  161.  
  162. return isStargazers;
  163. }
  164.  
  165. /**
  166. * Checks wether a variable is an Element
  167. * @param {object} variable - The variable to check
  168. * @returns {boolean} True if variable is an Element
  169. */
  170. function isElement(variable) {
  171. return variable instanceof Element;
  172. }