Gitea: copy commit reference

Adds a "Copy commit reference" button to every commit page on Gitea and Forgejo websites.

目前为 2024-01-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Gitea: copy commit reference
  3. // @namespace https://andrybak.dev
  4. // @version 4
  5. // @license AGPL-3.0-only
  6. // @author Andrei Rybak
  7. // @description Adds a "Copy commit reference" button to every commit page on Gitea and Forgejo websites.
  8. // @icon https://about.gitea.com/favicon.ico
  9. // @homepageURL https://github.com/rybak/copy-commit-reference-userscript
  10. // @supportURL https://github.com/rybak/copy-commit-reference-userscript/issues
  11. // @match https://gitea.com/*/commit/*
  12. // @match https://git.plastiras.org/*/commit/*
  13. // @match https://projects.blender.org/*/commit/*
  14. // @match https://codeberg.org/*/commit/*
  15. // @match https://next.forgejo.org/*/commit/*
  16. // @require https://cdn.jsdelivr.net/gh/rybak/userscript-libs@e86c722f2c9cc2a96298c8511028f15c45180185/waitForElement.js
  17. // @require https://cdn.jsdelivr.net/gh/rybak/copy-commit-reference-userscript@c7f2c3b96fd199ceee46de4ba7eb6315659b34e3/copy-commit-reference-lib.js
  18. // @grant none
  19. // ==/UserScript==
  20.  
  21. /*
  22. * Copyright (C) 2023 Andrei Rybak
  23. *
  24. * This program is free software: you can redistribute it and/or modify
  25. * it under the terms of the GNU Affero General Public License as published
  26. * by the Free Software Foundation, version 3.
  27. *
  28. * This program is distributed in the hope that it will be useful,
  29. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. * GNU Affero General Public License for more details.
  32. *
  33. * You should have received a copy of the GNU Affero General Public License
  34. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  35. */
  36.  
  37. (function () {
  38. 'use strict';
  39.  
  40. /**
  41. * Implementation for Gitea and its fork Forgejo.
  42. *
  43. * Example URLs for testing:
  44. * - https://git.plastiras.org/Tha_14/Antidote/commit/f84a08f1ac5312acc9ccedff25e6957e575f03ff
  45. * - https://codeberg.org/forgejo/forgejo/commit/e35d92de1f37bea0a593093678f093845955e3fc
  46. * - https://codeberg.org/forgejo/forgejo/commit/a4369782e1cfbbc6f588c0cda5776ee823b0e493
  47. */
  48. class Gitea extends GitHosting {
  49. getTargetSelector() {
  50. return '.commit-header h3 + div';
  51. }
  52.  
  53. wrapButtonContainer(container) {
  54. container.style.marginRight = '0.5rem';
  55. return container;
  56. }
  57.  
  58. wrapButton(button) {
  59. /*
  60. * Mimicking Gitea's "Browse Source" button, but without class 'primary',
  61. * because there shouldn't be too many primary buttons. Class 'basic' is
  62. * for styling like most of the buttons on the commit pages.
  63. */
  64. button.classList.add('ui', 'tiny', 'button', 'basic');
  65. const maybeNativeIcon = document.querySelector('.svg.octicon-copy');
  66. if (maybeNativeIcon) {
  67. /*
  68. * Some instances of Gitea don't have the copy icons,
  69. * e.g. https://projects.blender.org
  70. */
  71. const icon = maybeNativeIcon.cloneNode(true);
  72. icon.style.verticalAlign = 'middle';
  73. icon.style.marginTop = '-4px';
  74. button.insertBefore(document.createTextNode(" "), button.childNodes[0]);
  75. button.insertBefore(icon, button.childNodes[0]);
  76. }
  77. return button;
  78. }
  79.  
  80. addButtonContainerToTarget(target, buttonContainer) {
  81. // to the left of Gitea's "Browse Source" button
  82. target.insertBefore(buttonContainer, target.querySelector('.ui.primary.tiny.button'));
  83. }
  84.  
  85. /**
  86. * Styles adapted from GitHub's CSS classes ".tooltipped::before"
  87. * and ".tooltipped-s::before".
  88. *
  89. * @returns {HTMLElement}
  90. */
  91. #createTooltipTriangle() {
  92. const triangle = document.createElement('div');
  93. triangle.style.position = 'absolute';
  94. triangle.style.zIndex = '1000001';
  95. triangle.style.bottom = '-15px'; // not -16px to look better at different zoom levels
  96. triangle.style.left = '14px'; // to align with .left of `checkmark`
  97. triangle.style.height = '0';
  98. triangle.style.width = '0';
  99. /*
  100. * Borders connect at 45° angle => when only top border is colored,
  101. * it's a trapezoid. But with width=0, the bottom edge of trapezoid
  102. * has length 0, so it's a downwards triangle.
  103. *
  104. * bgColor from Gitea CSS classes
  105. */
  106. triangle.style.border = '8px solid transparent';
  107. triangle.style.borderTopColor = 'var(--color-tooltip-bg)';
  108. return triangle;
  109. }
  110.  
  111. createCheckmark() {
  112. const checkmark = super.createCheckmark();
  113. checkmark.style.left = '0.2rem'; // to put emoji right above the button's icon
  114. checkmark.style.bottom = 'calc(100% + 1.2rem)'; // to mimic native tooltips shown above the buttons
  115. /*
  116. * Look and feel from CSS classes of Tippy -- a library (?)
  117. * used by Gitea.
  118. */
  119. checkmark.style.zIndex = '9999';
  120. checkmark.style.backgroundColor = 'var(--color-tooltip-bg)';
  121. checkmark.style.color = 'var(--color-tooltip-text)';
  122. checkmark.style.borderRadius = 'var(--border-radius)';
  123. checkmark.style.fontSize = '1rem';
  124. checkmark.style.padding = '.5rem 1rem';
  125. checkmark.appendChild(this.#createTooltipTriangle());
  126. return checkmark;
  127. }
  128.  
  129. getFullHash() {
  130. const browseButton = document.querySelector('.commit-header h3 + div > a');
  131. const lastSlashIndex = browseButton.href.lastIndexOf('/');
  132. return browseButton.href.slice(lastSlashIndex + 1);
  133. }
  134.  
  135. getDateIso(hash) {
  136. const timeTag = document.querySelector('#authored-time relative-time');
  137. return timeTag.datetime.slice(0, 'YYYY-MM-DD'.length);
  138. }
  139.  
  140. getCommitMessage(hash) {
  141. const subj = document.querySelector('.commit-summary').innerText;
  142. const bodyElement = document.querySelector('.commit-body');
  143. if (!bodyElement) {
  144. return subj;
  145. }
  146. const body = bodyElement.childNodes[0].innerText;
  147. return subj + '\n\n' + body;
  148. }
  149.  
  150. static #getIssuesUrl() {
  151. return document.querySelector('.header-wrapper > .ui.tabs.container > .tabular.menu.navbar a[href$="/issues"]').href;
  152. }
  153.  
  154. convertPlainSubjectToHtml(plainTextSubject, hash) {
  155. if (!plainTextSubject.includes('#')) {
  156. return plainTextSubject;
  157. }
  158. const issuesUrl = Gitea.#getIssuesUrl();
  159. return plainTextSubject.replaceAll(/#([0-9]+)/g, `<a href="${issuesUrl}/\$1">#\$1</a>`);
  160. }
  161. }
  162.  
  163. CopyCommitReference.runForGitHostings(new Gitea());
  164. })();