Gitea: copy commit reference

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

目前為 2023-10-05 提交的版本,檢視 最新版本

  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. })();