// ==UserScript== // @name gitlab-booster // @namespace vite-plugin-monkey // @version 1.2.0 // @author // @description Boost productivity for code reviewers on gitlab // @license AGPL-3.0-or-later // @icon https://www.google.com/s2/favicons?sz=64&domain=gitlab.com // @homepage https://github.com/braineo/gitlab-booster#readme // @homepageURL https://github.com/braineo/gitlab-booster#readme // @source https://github.com/braineo/gitlab-booster.git // @supportURL https://github.com/braineo/gitlab-booster/issues // @match https://gitlab.com/* // @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js // @grant GM_addElement // @grant window.onurlchange // @downloadURL none // ==/UserScript== (function ($) { 'use strict'; var _GM_addElement = /* @__PURE__ */ (() => typeof GM_addElement != "undefined" ? GM_addElement : void 0)(); const waitForKeyElements = function(selectorOrFunction, callback, waitOnce, interval, maxIntervals) { if (typeof waitOnce === "undefined") { waitOnce = true; } if (typeof interval === "undefined") { interval = 300; } if (typeof maxIntervals === "undefined") { maxIntervals = -1; } if (typeof waitForKeyElements.namespace === "undefined") { waitForKeyElements.namespace = Date.now().toString(); } var targetNodes = typeof selectorOrFunction === "function" ? selectorOrFunction() : document.querySelectorAll(selectorOrFunction); var targetsFound = targetNodes && targetNodes.length > 0; if (targetsFound) { targetNodes.forEach(function(targetNode) { var attrAlreadyFound = `data-userscript-${waitForKeyElements.namespace}-alreadyFound`; var alreadyFound = targetNode.getAttribute(attrAlreadyFound) || false; if (!alreadyFound) { var cancelFound = callback(targetNode); if (cancelFound) { targetsFound = false; } else { targetNode.setAttribute(attrAlreadyFound, "true"); } } }); } if (maxIntervals !== 0 && !(targetsFound && waitOnce)) { maxIntervals -= 1; setTimeout(function() { waitForKeyElements( selectorOrFunction, callback, waitOnce, interval, maxIntervals ); }, interval); } }; _GM_addElement("link", { rel: "stylesheet", href: "https://cdn.jsdelivr.net/npm/nf-sauce-code-pro@2.1.3/nf-font.min.css" }); const getApiUrl = (url) => { return `${window.location.origin}/api/v4${url}`; }; async function fetchGitLabData(url) { const response = await fetch(url, { headers: { "Content-Type": "application/json" } }); if (!response.ok) { console.error("Failed to fetch GitLab data:", response.statusText); return null; } return await response.json(); } let currentUser; function createThreadsBadge(element, badgeClassName, resolved, resolvable) { const li = $("
  • ").addClass("issuable-comments d-none d-sm-flex").prependTo(element); $("").addClass( `gl-badge badge badge-pill badge-${badgeClassName} sm has-tooltip` ).text(`${resolved}/${resolvable} threads resolved`).prependTo(li); } function createThreadActionBadges(element, action) { const li = $("
  • ").addClass("issuable-comments d-none d-sm-flex").prependTo(element); const createIconText = (icon, title, text, badgeClassName) => { return $("", { title, class: `gl-badge badge badge-pill ${badgeClassName ? `badge-${badgeClassName}` : ""} sm has-tooltip` }).css({ "font-family": "SauceCodePro Mono" }).text(`${icon} ${text ?? ""}`); }; if (action.waitForOursCount) { createIconText( "", "need your response", action.waitForOursCount.toString(), "danger" ).prependTo(li); } if (action.waitForTheirsCount) { createIconText( "", "wait for response", action.waitForTheirsCount.toString(), "muted" ).prependTo(li); } if (action.otherUnresolvedCount) { createIconText( "", "other threads", action.otherUnresolvedCount.toString(), "warning" ).prependTo(li); } if (action.needUserReview) { createIconText("", "need your review", void 0, "danger").prependTo( li ); } } function createDiffStat(element, fileCount, addLineCount, deleteLinCount) { $("
    ").css({ display: "flex", "flex-direction": "row", gap: "3px" }).append( $("
    ", { class: "diff-stats-group" }).append( $("", { class: "gl-text-gray-500 bold", text: `${fileCount} files` }) ), $("
    ", { class: "diff-stats-group gl-text-green-600 gl-display-flex gl-align-items-center bold" }).append($("").text("+"), $("").text(`${addLineCount}`)), $("
    ", { class: "diff-stats-group gl-text-red-500 gl-display-flex gl-align-items-center bold" }).append($("").text("-"), $("").text(`${deleteLinCount}`)) ).prependTo(element); } function createIssueCardMergeRequestInfo(element, opened, total) { const inline = $("").appendTo(element); $("
    ", { class: "issue-milestone-details gl-flex gl-max-w-15 gl-gap-2 gl-mr-3 gl-inline-flex gl-max-w-15 gl-cursor-help gl-items-center gl-align-bottom gl-text-sm gl-text-gray-500" }).append( $("", { title: "Merge requests" }).css({ "font-family": "SauceCodePro Mono", "font-size": "1.1rem" }).text(""), $("", { class: "gl-inline-block gl-truncate gl-font-bold" }).text(total === 0 ? "-/-" : `${total - opened}/${total}`) ).appendTo(inline); } function ensurePanelLayout() { const layout = document.querySelector("div.layout-page"); if (!layout) { return; } $(layout).css({ display: "flex", height: "100vh", overflow: "hidden" }); const content = document.querySelector("div.content-wrapper"); if (!content) { return; } $(content).css({ overflowY: "scroll" }); } function ensureSidePanel(panelName, url) { const buttonId = `close-${panelName.toLowerCase().replaceAll(" ", "-")}`; if (!document.querySelector(`#${buttonId}`)) { const topBar = document.querySelector(".top-bar-container"); if (!topBar) { return; } $(topBar).append( $("