Gitea: copy commit reference

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

Versión del día 7/10/2023. Echa un vistazo a la versión más reciente.

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