Gitea: copy commit reference

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

As of 2023-10-05. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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