GitHub: Redirect forked repo links to own repo

12/8/2023, 9:09:51 PM

目前為 2023-12-08 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name GitHub: Redirect forked repo links to own repo
  3. // @namespace Violentmonkey Scripts
  4. // @match https://github.com/*
  5. // @grant none
  6. // @version 0.1.4
  7. // @author CY Fung
  8. // @description 12/8/2023, 9:09:51 PM
  9. // @run-at document-start
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. /**
  14. *
  15. * This is to change links in markdown files from the original repo links to your forked repo links
  16. *
  17. **/
  18.  
  19. (() => {
  20.  
  21.  
  22. let rafPromise = null;
  23. const rafFn = (typeof webkitRequestAnimationFrame !== 'undefined' ? webkitRequestAnimationFrame : requestAnimationFrame).bind(window); // eslint-disable-line no-undef, no-constant-condition
  24.  
  25. const getRafPromise = () => rafPromise || (rafPromise = new Promise(resolve => {
  26. rafFn(hRes => {
  27. rafPromise = null;
  28. resolve(hRes);
  29. });
  30. }));
  31.  
  32. const matcher = h => typeof h == 'string' && h.length > 19 && h.startsWith('https://github.com/') && /^https\:\/\/github\.com\/([\w\d\-\.]+)\/([\w\d\-\.]+)/.exec(h);
  33. const pageInfo = {
  34. ready: false,
  35. matched: false,
  36. owner: '',
  37. repo: '',
  38. originalOwner: '',
  39. originalRepo: '',
  40. url: '',
  41. };
  42.  
  43. const PromiseExternal = ((resolve_, reject_) => {
  44. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  45. return class PromiseExternal extends Promise {
  46. constructor(cb = h) {
  47. super(cb);
  48. if (cb === h) {
  49. /** @type {(value: any) => void} */
  50. this.resolve = resolve_;
  51. /** @type {(reason?: any) => void} */
  52. this.reject = reject_;
  53. }
  54. }
  55. };
  56. })();
  57.  
  58. let pageOriginalP = new PromiseExternal();
  59.  
  60. const obtainOriginalRepo = () => {
  61.  
  62.  
  63. // Define the XPath expression
  64. var xpathExpression = "//span[contains(text(), 'forked from')]";
  65.  
  66. // Use document.evaluate to find the matching element
  67. var result = document.evaluate(xpathExpression, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  68.  
  69. var spanElement = result.singleNodeValue;
  70. // Check if a matching element was found
  71. if (spanElement instanceof Element) {
  72.  
  73. const a = spanElement.querySelector('a[href]');
  74. if (a) {
  75. const h = a.href;
  76. let m = matcher(h);
  77.  
  78. if (m) {
  79. pageInfo.originalOwner = m[1];
  80. pageInfo.originalRepo = m[2];
  81. pageInfo.originalRepoUrl = `https://github.com/${pageInfo.originalOwner}/${pageInfo.originalRepo}/`;
  82. pageOriginalP.resolve();
  83. }
  84.  
  85. }
  86. }
  87.  
  88. }
  89.  
  90. document.addEventListener('DOMContentLoaded', () => {
  91.  
  92. obtainOriginalRepo();
  93.  
  94. pageInfo.ready = false;
  95. pageInfo.matched = false;
  96. setTimeout(baseProcess, 1);
  97.  
  98.  
  99. }, false);
  100.  
  101.  
  102.  
  103. let qk = 0;
  104. let ck = 0;
  105.  
  106. const baseProcess = () => {
  107. let pageUrl = `${location.origin}${location.pathname}`;
  108. if (pageInfo.url !== pageUrl) {
  109. pageInfo.url = pageUrl;
  110. pageInfo.ready = false;
  111. pageInfo.matched = false;
  112. pageOriginalP = new PromiseExternal();
  113. obtainOriginalRepo();
  114. }
  115. if (!pageInfo.ready && pageInfo.url) {
  116. pageInfo.ready = true;
  117. let m = matcher(pageInfo.url);
  118. if (m) {
  119. pageInfo.matched = true;
  120. pageInfo.owner = m[1];
  121. pageInfo.repo = m[2];
  122. pageInfo.repoUrl = `https://github.com/${pageInfo.owner}/${pageInfo.repo}/`;
  123. }
  124. }
  125. if (pageInfo.ready && pageInfo.matched) {
  126. process();
  127. }
  128. };
  129.  
  130. const process = async () => {
  131. let qt = ++qk;
  132. await pageOriginalP.then();
  133. if (qt !== qk) return;
  134. let ct = ++ck;
  135. await getRafPromise().then();
  136. if (ct !== ck) return;
  137.  
  138. for (const s of document.querySelectorAll(`a[href^="${pageInfo.originalRepoUrl}"]`)) {
  139. const h = s.getAttribute('href');
  140. let m = matcher(h);
  141. if (m) {
  142. s.setAttribute('href', h.replace(`${pageInfo.originalRepoUrl}`, `${pageInfo.repoUrl}`))
  143. }
  144. }
  145.  
  146. }
  147.  
  148. const mo = new MutationObserver((entries) => {
  149. baseProcess();
  150. });
  151.  
  152. mo.observe(document, { subtree: true, childList: true });
  153.  
  154. })();