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.0
  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. let rfap = null;
  22. let obtainRFAP = () => {
  23. return rfap || (rfap = new Promise(r => requestAnimationFrame(r)));
  24. }
  25. const matcher = h => typeof h == 'string' && h.length > 19 && h.startsWith('https://github.com/') && /^https\:\/\/github\.com\/([\w\d\-\.]+)\/([\w\d\-\.]+)/.exec(h);
  26. const pageInfo = {
  27. ready: false,
  28. matched: false,
  29. owner: '',
  30. repo: '',
  31. originalOwner: '',
  32. originalRepo: '',
  33. url: '',
  34. };
  35.  
  36. const PromiseExternal = ((resolve_, reject_) => {
  37. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  38. return class PromiseExternal extends Promise {
  39. constructor(cb = h) {
  40. super(cb);
  41. if (cb === h) {
  42. /** @type {(value: any) => void} */
  43. this.resolve = resolve_;
  44. /** @type {(reason?: any) => void} */
  45. this.reject = reject_;
  46. }
  47. }
  48. };
  49. })();
  50.  
  51. let pageOriginalP = new PromiseExternal();
  52.  
  53. document.addEventListener('DOMContentLoaded', () => {
  54.  
  55.  
  56. // Define the XPath expression
  57. var xpathExpression = "//span[contains(text(), 'forked from')]";
  58.  
  59. // Use document.evaluate to find the matching element
  60. var result = document.evaluate(xpathExpression, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
  61.  
  62. var spanElement = result.singleNodeValue;
  63. // Check if a matching element was found
  64. if (spanElement instanceof Element) {
  65. setTimeout(() => {
  66.  
  67. const a = spanElement.querySelector('a[href]');
  68. if (a) {
  69. const h = a.href;
  70. let m = matcher(h);
  71.  
  72. if (m) {
  73. pageInfo.originalOwner = m[1];
  74. pageInfo.originalRepo = m[2];
  75. pageInfo.originalRepoUrl = `https://github.com/${pageInfo.originalOwner}/${pageInfo.originalRepo}/`;
  76. pageOriginalP.resolve();
  77. }
  78.  
  79. }
  80. }, 1);
  81. }
  82.  
  83.  
  84. }, false);
  85.  
  86.  
  87.  
  88. let qk = 0;
  89. let ck = 0;
  90.  
  91. const process = async () => {
  92. let qt = ++qk;
  93. await pageOriginalP.then();
  94. if (qt !== qk) return;
  95. let ct = ++ck;
  96. await obtainRFAP().then();
  97. if (ct !== ck) return;
  98.  
  99. for (const s of document.querySelectorAll(`a[href^="${pageInfo.originalRepoUrl}"]`)) {
  100. const h = s.getAttribute('href');
  101. let m = matcher(h);
  102. if (m) {
  103. s.setAttribute('href', h.replace(`${pageInfo.originalRepoUrl}`, `${pageInfo.repoUrl}`))
  104. }
  105. }
  106.  
  107. }
  108.  
  109. const mo = new MutationObserver((entries) => {
  110. if (!pageInfo.ready) {
  111. pageInfo.ready = true;
  112. let pageUrl = `${location.origin}${location.pathname}`;
  113. pageInfo.url = pageUrl;
  114. let m = matcher(pageUrl);
  115. if (m) {
  116. pageInfo.matched = true;
  117. pageInfo.owner = m[1];
  118. pageInfo.repo = m[2];
  119. pageInfo.repoUrl = `https://github.com/${pageInfo.owner}/${pageInfo.repo}/`;
  120. }
  121. }
  122. if (pageInfo.matched) {
  123. process();
  124. }
  125. });
  126.  
  127. mo.observe(document, { subtree: true, childList: true });
  128.  
  129. })();