GitLab Review Tags

Insert predefined review tags with emoji and tooltips (shown below) in GitLab MRs

  1. // ==UserScript==
  2. // @name GitLab Review Tags
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Insert predefined review tags with emoji and tooltips (shown below) in GitLab MRs
  6. // @exclude https://gitlab.com/*/merge_requests/new*
  7. // @match https://gitlab.com/*/merge_requests/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=gitlab.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. const TAGS = [
  18. { label: '[question]', emoji: '❓', emojiCode: ':question:', title: 'I’m asking for clarification, not blocking approval.' },
  19. { label: '[nitpick]', emoji: '🔍', emojiCode: ':mag:', title: 'Very minor; doesn’t block, can fix later or skip.' },
  20. { label: '[info]', emoji: 'ℹ️', emojiCode: ':information_source:', title: 'Providing context or background, no action needed.' },
  21. { label: '[suggestion]', emoji: '💡', emojiCode: ':bulb:', title: 'Optional improvement idea, not a requirement.' },
  22. { label: '[blocking]', emoji: '⛔', emojiCode: ':no_entry:', title: 'This must be addressed before approval.' },
  23. { label: '[trust]', emoji: '🤝', emojiCode: ':handshake:', title: 'I didn’t check this part deeply but I trust your expertise.' },
  24. { label: '[follow-up]', emoji: '📌', emojiCode: ':pushpin:', title: 'Let’s merge, but create a ticket to improve or clean up later.' },
  25. { label: '[praise]', emoji: '🙌', emojiCode: ':raised_hands:', title: 'Explicitly calling out something well done or elegant.' },
  26. { label: '[discussion]', emoji: '🗣️', emojiCode: ':speaking_head:', title: 'Not blocking, but we should talk as a team about this approach or pattern.' }
  27. ];
  28.  
  29. const STYLE = `
  30. :root {
  31. --tooltip-bg: #fafafa;
  32. --tooltip-color: #303030;
  33. --tag-bg: #fafafa;
  34. --tag-border: #bfbfbf;
  35. --button-border: #bfbfbf;
  36. --button-bg: #fafafa;
  37. --button-hover-bg: #dbdbdb;
  38. --button-hover-border: #428fdc;
  39. }
  40.  
  41. body.gl-dark {
  42. --tooltip-bg: var(--gray-10);
  43. --tooltip-color: var(--gray-950);
  44. --tag-bg: var(--gray-10);
  45. --tag-border: var(--gray-100);
  46. --button-border: var(--gray-100);
  47. --button-bg: #1f1f1f;
  48. --button-hover-bg: #303030;
  49. --button-hover-border: #428fdc;
  50. }
  51.  
  52. .gl-tag-inserter {
  53. display: grid;
  54. grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  55. gap: 6px;
  56. margin: 6px 0;
  57. padding: 6px;
  58. background-color: var(--tag-bg);
  59. border: 1px solid var(--tag-border);
  60. border-radius: 8px;
  61. font-family: system-ui, sans-serif;
  62. animation: fadeIn 0.3s ease-out;
  63. }
  64.  
  65. .gl-tag-inserter button {
  66. background-color: var(--button-bg);
  67. border: 1px solid var(--button-border);
  68. border-radius: 20px;
  69. padding: 6px 10px;
  70. font-size: 13px;
  71. cursor: pointer;
  72. transition: all 0.2s ease;
  73. display: flex;
  74. align-items: center;
  75. justify-content: center;
  76. gap: 6px;
  77. white-space: nowrap;
  78. position: relative;
  79. }
  80.  
  81. .gl-tag-inserter button:hover {
  82. background-color: var(--button-hover-bg);
  83. border: 1px solid var(--button-hover-border);
  84. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  85. }
  86.  
  87. .gl-tag-inserter button::after {
  88. content: attr(data-tooltip);
  89. position: absolute;
  90. top: 30px;
  91. left: 50%;
  92. transform: translateX(-50%);
  93. opacity: 0;
  94. background-color: var(--tooltip-bg);
  95. color: var(--tooltip-color);
  96. padding: 6px 8px;
  97. border-radius: 6px;
  98. font-size: 12px;
  99. white-space: pre-wrap;
  100. pointer-events: none;
  101. transition: opacity 0.2s ease, transform 0.2s ease;
  102. z-index: 9999;
  103. width: 120px;
  104. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
  105. }
  106.  
  107. .gl-tag-inserter button:hover::after {
  108. opacity: 1;
  109. transform: translateX(-50%) translateY(4px);
  110. }
  111.  
  112. @keyframes fadeIn {
  113. from { opacity: 0; transform: translateY(-4px); }
  114. to { opacity: 1; transform: translateY(0); }
  115. }
  116. `;
  117.  
  118. function injectStyle() {
  119. if (document.getElementById('gl-tag-style')) return;
  120. const style = document.createElement('style');
  121. style.id = 'gl-tag-style';
  122. style.innerText = STYLE;
  123. document.head.appendChild(style);
  124. }
  125.  
  126. function createTagButton(tag, textarea) {
  127. const btn = document.createElement('button');
  128. btn.innerHTML = `${tag.emoji} ${tag.label}`;
  129. btn.setAttribute('data-tooltip', tag.title);
  130. btn.addEventListener('click', (e) => {
  131. e.preventDefault();
  132. const prefix = `${tag.emojiCode} ${tag.label} `;
  133. if (!textarea.value.startsWith(prefix)) {
  134. const newValue = prefix + textarea.value;
  135. textarea.value = newValue;
  136.  
  137. textarea.dispatchEvent(new Event('input', { bubbles: true }));
  138. textarea.dispatchEvent(new Event('change', { bubbles: true }));
  139.  
  140. textarea.focus();
  141. }
  142. });
  143. return btn;
  144. }
  145.  
  146. function injectButtonsAboveTextarea(textarea) {
  147. if (textarea.dataset.glTagInjected) return;
  148.  
  149. const wrapper = document.createElement('div');
  150. wrapper.className = 'gl-tag-inserter';
  151. TAGS.forEach(tag => wrapper.appendChild(createTagButton(tag, textarea)));
  152.  
  153. textarea.parentNode.insertBefore(wrapper, textarea);
  154. textarea.dataset.glTagInjected = 'true';
  155. }
  156.  
  157. function scanAndInject() {
  158. document.querySelectorAll('textarea').forEach(injectButtonsAboveTextarea);
  159. }
  160.  
  161. injectStyle();
  162.  
  163. const observer = new MutationObserver(() => {
  164. scanAndInject();
  165. });
  166.  
  167. observer.observe(document.body, { childList: true, subtree: true });
  168.  
  169. scanAndInject();
  170. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址