Git Markdown Content Navigation

Provide directory navigation of the markdown file content of the github/gitee website.

  1. // ==UserScript==
  2. // @name Git Markdown Content Navigation
  3. // @name:zh-CN Git Markdown 文件内容导航
  4. // @namespace https://github.com/wang1212/user-script/blob/main/git-markdown-content-navigation
  5. // @version 0.3.1
  6. // @description Provide directory navigation of the markdown file content of the github/gitee website.
  7. // @description:zh-cn 提供 github/gitee 网站 markdown 文件内容的目录导航。
  8. // @author wang1212
  9. // @match http*://github.com/*
  10. // @match http*://gitee.com/*
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_openInTab
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @supportURL https://github.com/wang1212/user-script/blob/main/git-markdown-content-navigation
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. (function () {
  20. 'use strict';
  21.  
  22. const log = console.log;
  23.  
  24. /* ------------------------------- Register menu configuration item ----------------------------- */
  25.  
  26. let menu_item_value_switch;
  27. let menu_item_id_switch;
  28. let menu_item_id_feedback;
  29. let menu_item_id_source;
  30.  
  31. function registerMenuCommand() {
  32. //
  33. if (menu_item_id_switch) GM_unregisterMenuCommand(menu_item_id_switch);
  34.  
  35. menu_item_value_switch = GM_getValue('menu_item_value_switch');
  36. menu_item_id_switch = GM_registerMenuCommand(
  37. !menu_item_value_switch ? '默认显示 [点击切换]' : '默认隐藏 [点击切换]',
  38. function () {
  39. GM_setValue('menu_item_value_switch', !menu_item_value_switch);
  40. location.reload();
  41. }
  42. );
  43.  
  44. //
  45. if (menu_item_id_source) GM_unregisterMenuCommand(menu_item_id_source);
  46.  
  47. menu_item_id_source = GM_registerMenuCommand('源码 [GitHub]', function () {
  48. GM_openInTab(
  49. 'https://github.com/wang1212/user-script/tree/main/git-markdown-content-navigation',
  50. { active: true, insert: true, setParent: true }
  51. );
  52. });
  53.  
  54. //
  55. if (menu_item_id_feedback) GM_unregisterMenuCommand(menu_item_id_feedback);
  56.  
  57. menu_item_id_feedback = GM_registerMenuCommand('反馈 & 更新', function () {
  58. GM_openInTab(
  59. 'https://gf.qytechs.cn/scripts/421316-git-markdown-content-navigation',
  60. { active: true, insert: true, setParent: true }
  61. );
  62. });
  63. }
  64.  
  65. registerMenuCommand();
  66.  
  67. /* ------------------------------- Init ----------------------------- */
  68.  
  69. const href = location.href;
  70.  
  71. const matchGithub = /github/;
  72. const matchGithubRepository = /https?:\/\/github.com\/.+\/.+/;
  73. const matchGitee = /gitee/;
  74. const matchGiteeRepository = /https?:\/\/gitee.com\/.+\/.+/;
  75.  
  76. const isGithub = !!href.match(matchGithub);
  77. const isGitee = !!href.match(matchGitee);
  78. const isRepositoryPage = !!(
  79. href.match(matchGithubRepository) || href.match(matchGiteeRepository)
  80. );
  81.  
  82. /* ------------------------------- Parse MarkDown file content navigation ----------------------------- */
  83.  
  84. function updateMarkdownFileContentNavigation() {
  85. let navBarElem = document.querySelector('.wang1212_md-content-nav');
  86.  
  87. // Remove existing
  88. navBarElem && navBarElem.remove();
  89.  
  90. if (!isRepositoryPage) return;
  91.  
  92. // titles
  93. const titles = getMarkDownContentTitles();
  94. if (!titles.length) return;
  95.  
  96. // navBar button
  97. navBarElem = document.createElement('div');
  98. navBarElem.classList.add('wang1212_md-content-nav');
  99. navBarElem.title = 'Markdown 文件内容导航';
  100.  
  101. navBarElem.innerText = 'N';
  102.  
  103. // Panel
  104. const navBarPanelElem = document.createElement('div');
  105. navBarPanelElem.classList.add('wang1212_md-content-nav_panel');
  106.  
  107. navBarPanelElem.innerHTML = '';
  108.  
  109. // draw titles
  110. titles.forEach((title) => {
  111. const level = +title.tagName.substr(-1);
  112. navBarPanelElem.innerHTML += `
  113. <p class="wang1212_md-content-nav_to-anchor" style="font-size: ${
  114. 1 - ((level - 1) * 0).toFixed(2)
  115. }rem; margin: 0; padding-left: ${((level - 1) * 0.5).toFixed(
  116. 2
  117. )}rem" data-anchor="${title.anchorId}">
  118. ${title.text}
  119. </p>
  120. `;
  121. });
  122.  
  123. // --- CSS Style ---
  124. const styleElem = document.createElement('style');
  125. styleElem.type = 'text/css';
  126. styleElem.innerHTML = `
  127. .wang1212_md-content-nav {
  128. position: fixed;
  129. right: 1rem;
  130. bottom: 3.5rem;
  131. z-index: 1999;
  132. width: 2rem;
  133. height: 2rem;
  134. color: white;
  135. font-size: 1.5rem;
  136. line-height: 2rem;
  137. text-align: center;
  138. background-color: rgb(36, 41, 46);
  139. cursor: pointer;
  140. }
  141. .wang1212_md-content-nav_panel {
  142. position: absolute;
  143. right: 0;
  144. bottom: 2rem;
  145. display: block;
  146. width: 20rem;
  147. height: 75vh;
  148. padding: 0.5rem;
  149. overflow: auto;
  150. color: #999;
  151. text-align: left;
  152. background: white;
  153. box-shadow: rgba(0, 0, 0, 0.25) 0 0 0.5rem 0;
  154. }
  155. .wang1212_md-content-nav_to-anchor {
  156. line-height: 1.6 !important;
  157. transition: all 0.4s linear;
  158. }
  159. .wang1212_md-content-nav_to-anchor:hover {
  160. color: rgb(0, 0, 0);
  161. transform: translateX(4px);
  162. }
  163. `;
  164.  
  165. navBarElem.appendChild(navBarPanelElem);
  166. document.body.appendChild(navBarElem);
  167. document.head.appendChild(styleElem);
  168.  
  169. // --- Event ---
  170. // Show/Hide
  171. navBarElem.addEventListener(
  172. 'click',
  173. (e) => {
  174. if (e.target !== navBarElem) return;
  175.  
  176. if (navBarPanelElem.style.display === 'none') {
  177. navBarPanelElem.style.display = 'block';
  178. } else {
  179. navBarPanelElem.style.display = 'none';
  180. }
  181. },
  182. false
  183. );
  184. if (menu_item_value_switch) {
  185. navBarPanelElem.style.display = 'none';
  186. }
  187.  
  188. // fly to view
  189. navBarPanelElem.addEventListener(
  190. 'click',
  191. (e) => {
  192. if (!e.target.classList.contains('wang1212_md-content-nav_to-anchor'))
  193. return;
  194.  
  195. const anchorElem = document.getElementById(e.target.dataset.anchor);
  196. if (!anchorElem) return;
  197.  
  198. anchorElem.scrollIntoView({ behavior: 'smooth', block: 'start' });
  199. },
  200. false
  201. );
  202. }
  203.  
  204. /* ------------------------------- To Top ----------------------------- */
  205.  
  206. // to top button
  207. function updateGoToTopButton() {
  208. let toTopElem = document.querySelector('.wang1212_to-top');
  209.  
  210. // Remove existing
  211. toTopElem && toTopElem.remove();
  212.  
  213. // toTop button
  214. toTopElem = document.createElement('div');
  215. toTopElem.classList.add('wang1212_to-top');
  216. toTopElem.title = '回到顶部';
  217.  
  218. toTopElem.innerText = '↑';
  219.  
  220. // --- CSS Style ---
  221. const styleElem = document.createElement('style');
  222. styleElem.type = 'text/css';
  223. styleElem.innerHTML = `
  224. .wang1212_to-top {
  225. position: fixed;
  226. right: 1rem;
  227. bottom: 1rem;
  228. z-index: 1999;
  229. width: 2rem;
  230. height: 2rem;
  231. color: white;
  232. font-size: 1.5rem;
  233. line-height: 2rem;
  234. text-align: center;
  235. background-color: rgb(36, 41, 46);
  236. cursor: pointer;
  237. }
  238. `;
  239.  
  240. document.body.appendChild(toTopElem);
  241. document.head.appendChild(styleElem);
  242.  
  243. // --- Event ---
  244. // fly to view
  245. toTopElem.addEventListener(
  246. 'click',
  247. () => {
  248. document.body.scrollIntoView({ behavior: 'smooth' });
  249. },
  250. false
  251. );
  252. }
  253.  
  254. /* ------------------------------- Utils ----------------------------- */
  255.  
  256. // parse titles
  257. function getMarkDownContentTitles() {
  258. let rootElem = document.querySelector('.markdown-body');
  259.  
  260. if (!rootElem) return [];
  261.  
  262. const anchors = rootElem.querySelectorAll('a.anchor');
  263.  
  264. if (!anchors.length) return [];
  265.  
  266. const titles = [];
  267.  
  268. anchors.forEach((elem) => {
  269. const parentElem = elem.parentElement;
  270.  
  271. titles.push({
  272. tagName: parentElem.tagName,
  273. text: parentElem.textContent,
  274. anchorId: elem.id,
  275. });
  276. });
  277.  
  278. return titles;
  279. }
  280.  
  281. /* ------------------------------- Load ----------------------------- */
  282.  
  283. function load() {
  284. updateMarkdownFileContentNavigation();
  285. updateGoToTopButton();
  286. }
  287.  
  288. // Monitor page reload
  289. document.addEventListener('pjax:end', load, false);
  290. // see docs: https://turbo.hotwired.dev/reference/events
  291. // see https://github.com/refined-github/refined-github/issues/5719
  292. //document.addEventListener('turbo:render', load, false)
  293. document.documentElement.addEventListener('turbo:render', load, false);
  294.  
  295. if (isGitee) {
  296. // Monitor page modify
  297. const observer = new MutationObserver(load);
  298.  
  299. observer.observe(document.querySelector('.tree-holder'), {
  300. childList: true,
  301. subtree: false,
  302. });
  303. }
  304.  
  305. //
  306. load();
  307. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址