Git Markdown 文件内容导航

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

当前为 2022-03-20 提交的版本,查看 最新版本

  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.0
  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(
  48. '源码 [GitHub]',
  49. function() {
  50. GM_openInTab('https://github.com/wang1212/user-script/tree/main/git-markdown-content-navigation', { 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(
  58. '反馈 & 更新',
  59. function() {
  60. GM_openInTab('https://greasyfork.org/scripts/421316-git-markdown-content-navigation', { 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 = !!(href.match(matchGithubRepository) || href.match(matchGiteeRepository))
  79.  
  80. /* ------------------------------- Parse MarkDown file content navigation ----------------------------- */
  81.  
  82. function updateMarkdownFileContentNavigation() {
  83. let navBarElem = document.querySelector('.wang1212_md-content-nav')
  84.  
  85. // Remove existing
  86. navBarElem && navBarElem.remove()
  87.  
  88. if (!isRepositoryPage) return
  89.  
  90. // titles
  91. const titles = getMarkDownContentTitles()
  92. if (!titles.length) return
  93.  
  94. // navBar button
  95. navBarElem = document.createElement('div')
  96. navBarElem.classList.add('wang1212_md-content-nav')
  97. navBarElem.title = 'Markdown 文件内容导航'
  98.  
  99. navBarElem.innerText = 'N'
  100.  
  101. // Panel
  102. const navBarPanelElem = document.createElement('div')
  103. navBarPanelElem.classList.add('wang1212_md-content-nav_panel')
  104.  
  105. navBarPanelElem.innerHTML = ''
  106.  
  107. // draw titles
  108. titles.forEach((title) => {
  109. const level = +title.tagName.substr(-1)
  110. navBarPanelElem.innerHTML += `
  111. <p class="wang1212_md-content-nav_to-anchor" style="font-size: ${1 - ((level - 1) * 0).toFixed(2)}rem; margin: 0; padding-left: ${((level - 1) * 0.5).toFixed(
  112. 2
  113. )}rem" data-anchor="${title.anchorId}">
  114. ${title.text}
  115. </p>
  116. `
  117. })
  118.  
  119. // --- CSS Style ---
  120. const styleElem = document.createElement('style')
  121. styleElem.type = 'text/css'
  122. styleElem.innerHTML = `
  123. .wang1212_md-content-nav {
  124. position: fixed;
  125. right: 1rem;
  126. bottom: 3.5rem;
  127. z-index: 1999;
  128. width: 2rem;
  129. height: 2rem;
  130. color: white;
  131. font-size: 1.5rem;
  132. line-height: 2rem;
  133. text-align: center;
  134. background-color: rgb(36, 41, 46);
  135. cursor: pointer;
  136. }
  137. .wang1212_md-content-nav_panel {
  138. position: absolute;
  139. right: 0;
  140. bottom: 2rem;
  141. display: block;
  142. width: 20rem;
  143. height: 75vh;
  144. padding: 0.5rem;
  145. overflow: auto;
  146. color: #999;
  147. text-align: left;
  148. background: white;
  149. box-shadow: rgba(0, 0, 0, 0.25) 0 0 0.5rem 0;
  150. }
  151. .wang1212_md-content-nav_to-anchor {
  152. line-height: 1.6 !important;
  153. transition: all 0.4s linear;
  154. }
  155. .wang1212_md-content-nav_to-anchor:hover {
  156. color: rgb(0, 0, 0);
  157. transform: translateX(4px);
  158. }
  159. `
  160.  
  161. navBarElem.appendChild(navBarPanelElem)
  162. document.body.appendChild(navBarElem)
  163. document.head.appendChild(styleElem)
  164.  
  165. // --- Event ---
  166. // Show/Hide
  167. navBarElem.addEventListener(
  168. 'click',
  169. (e) => {
  170. if (e.target !== navBarElem) return
  171.  
  172. if (navBarPanelElem.style.display === 'none') {
  173. navBarPanelElem.style.display = 'block'
  174. } else {
  175. navBarPanelElem.style.display = 'none'
  176. }
  177. },
  178. false
  179. )
  180. if (menu_item_value_switch) {
  181. navBarPanelElem.style.display = 'none';
  182. }
  183.  
  184. // fly to view
  185. navBarPanelElem.addEventListener(
  186. 'click',
  187. (e) => {
  188. if (!e.target.classList.contains('wang1212_md-content-nav_to-anchor')) return
  189.  
  190. const anchorElem = document.getElementById(e.target.dataset.anchor)
  191. if (!anchorElem) return
  192.  
  193. anchorElem.scrollIntoView({ behavior: 'smooth', block: 'start' })
  194. },
  195. false
  196. )
  197. }
  198.  
  199. /* ------------------------------- To Top ----------------------------- */
  200.  
  201. // to top button
  202. function updateGoToTopButton() {
  203. let toTopElem = document.querySelector('.wang1212_to-top')
  204.  
  205. // Remove existing
  206. toTopElem && toTopElem.remove()
  207.  
  208. // toTop button
  209. toTopElem = document.createElement('div')
  210. toTopElem.classList.add('wang1212_to-top')
  211. toTopElem.title = '回到顶部'
  212.  
  213. toTopElem.innerText = '↑'
  214.  
  215. // --- CSS Style ---
  216. const styleElem = document.createElement('style')
  217. styleElem.type = 'text/css'
  218. styleElem.innerHTML = `
  219. .wang1212_to-top {
  220. position: fixed;
  221. right: 1rem;
  222. bottom: 1rem;
  223. z-index: 1999;
  224. width: 2rem;
  225. height: 2rem;
  226. color: white;
  227. font-size: 1.5rem;
  228. line-height: 2rem;
  229. text-align: center;
  230. background-color: rgb(36, 41, 46);
  231. cursor: pointer;
  232. }
  233. `
  234.  
  235. document.body.appendChild(toTopElem)
  236. document.head.appendChild(styleElem)
  237.  
  238. // --- Event ---
  239. // fly to view
  240. toTopElem.addEventListener(
  241. 'click',
  242. () => {
  243. document.body.scrollIntoView({ behavior: 'smooth' })
  244. },
  245. false
  246. )
  247. }
  248.  
  249. /* ------------------------------- Utils ----------------------------- */
  250.  
  251. // parse titles
  252. function getMarkDownContentTitles() {
  253. let rootElem = document.querySelector('.markdown-body')
  254.  
  255. if (!rootElem) return []
  256.  
  257. const anchors = rootElem.querySelectorAll('a.anchor')
  258.  
  259. if (!anchors.length) return []
  260.  
  261. const titles = []
  262.  
  263. anchors.forEach((elem) => {
  264. const parentElem = elem.parentElement
  265.  
  266. titles.push({
  267. tagName: parentElem.tagName,
  268. text: parentElem.textContent,
  269. anchorId: elem.id,
  270. })
  271. })
  272.  
  273. return titles
  274. }
  275.  
  276. /* ------------------------------- Load ----------------------------- */
  277.  
  278. function load() {
  279. updateMarkdownFileContentNavigation()
  280. updateGoToTopButton()
  281. }
  282.  
  283. // Monitor page reload
  284. document.addEventListener('pjax:end', load, false)
  285.  
  286. if (isGitee) {
  287. // Monitor page modify
  288. const observer = new MutationObserver(load)
  289.  
  290. observer.observe(document.querySelector('.tree-holder'), { childList: true, subtree: false })
  291. }
  292.  
  293. //
  294. load()
  295. })()