Enhance some features of the Self-managed Gitlab

Enhance some features of the Self-managed Gitlab, such as the CI/CD settings page, the merge request create/edit page etc.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name Enhance some features of the Self-managed Gitlab
// @name:zh 强化自托管 Gitlab 能力
// @name:zh-CN 强化自托管 Gitlab 能力
// @name:zh-Hans 强化自托管 Gitlab 能力
// @name:zh-TW 強化自託管 Gitlab 能力
// @name:zh-HK 強化自託管 Gitlab 能力
// @name:zh-Hant 強化自託管 Gitlab 能力
// @namespace https://greatest.deepsurf.us/users/1133279
// @description Enhance some features of the Self-managed Gitlab, such as the CI/CD settings page, the merge request create/edit page etc.
// @description:zh 强化自托管 Gitlab 的一些功能,如 CI/CD 设置页面、合并请求创建/编辑页面等。
// @description:zh-CN 强化自托管 Gitlab 的一些功能,如 CI/CD 设置页面、合并请求创建/编辑页面等。
// @description:zh-Hans 强化自托管 Gitlab 的一些功能,如 CI/CD 设置页面、合并请求创建/编辑页面等。
// @description:zh-TW 強化自託管 Gitlab 的一些功能,如 CI/CD 設置頁面、合併請求創建/編輯頁面等。
// @description:zh-HK 強化自託管 Gitlab 的一些功能,如 CI/CD 設置頁面、合併請求創建/編輯頁面等。
// @description:zh-Hant 強化自託管 Gitlab 的一些功能,如 CI/CD 設置頁面、合併請求創建/編輯頁面等。
// @icon data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGhlaWdodD0iNTEycHgiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB3aWR0aD0iNTEycHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxnIGlkPSJfeDMxXzQ0LWdpdGxhYiI+PGc+PGcgaWQ9IlhNTElEXzZfIj48Zz48Zz48cGF0aCBkPSJNNTIuNzUyLDIwNS41MDFsMjAzLjE4LDI2NC4wN2wtMjIyLjctMTY1LjI5Yy02LjExLTQuNTktOC43Mi0xMi41OC02LjM4LTE5Ljc3MWwyNS44Ny03OS4wNSAgICAgICBMNTIuNzUyLDIwNS41MDF6IiBzdHlsZT0iZmlsbDojRkNBMzI2OyIvPjwvZz48Zz48cG9seWdvbiBwb2ludHM9IjE3MS4zMDIsMjA1LjQ2MSAyNTYuMDEyLDQ2OS41NDEgMjU1LjkzMiw0NjkuNTcxIDUyLjc1MiwyMDUuNTAxIDUyLjgxMiwyMDUuNDYxICAgICAgICAgICAgICIgc3R5bGU9ImZpbGw6I0ZDNkQyNjsiLz48L2c+PGc+PHBvbHlnb24gcG9pbnRzPSIzNDAuNzMxLDIwNS40NjEgMjU2LjAyMSw0NjkuNTcxIDI1Ni4wMTIsNDY5LjU0MSAxNzEuMzAyLDIwNS40NjEgMTcxLjM5MiwyMDUuNDYxICAgICAgICAzNDAuNjQyLDIwNS40NjEgICAgICAiIHN0eWxlPSJmaWxsOiNFMjQzMjk7Ii8+PC9nPjxnPjxwb2x5Z29uIHBvaW50cz0iNDU5LjI5MiwyMDUuNTAxIDI1Ni4wMjEsNDY5LjU3MSAzNDAuNzMxLDIwNS40NjEgNDU5LjIzMSwyMDUuNDYxICAgICAgIiBzdHlsZT0iZmlsbDojRkM2RDI2OyIvPjwvZz48Zz48cGF0aCBkPSJNNDg1LjE5MSwyODQuNTExYzIuMjQsNy4xOS0wLjI3LDE1LjE4MS02LjQ3LDE5Ljc3MWwtMjIyLjcsMTY1LjI5bDIwMy4yNzEtMjY0LjA3bDAuMDI5LTAuMDQgICAgICAgTDQ4NS4xOTEsMjg0LjUxMXoiIHN0eWxlPSJmaWxsOiNGQ0EzMjY7Ii8+PC9nPjxnPjxwYXRoIGQ9Ik00MDguNDcyLDQ4LjQyMWw1MC43NiwxNTcuMDRoLTExOC41aC0wLjA5bDUwLjg1LTE1Ny4wNCAgICAgICBDMzk0LjM2MSw0MC40MzEsNDA1LjY4Miw0MC40MzEsNDA4LjQ3Miw0OC40MjF6IiBzdHlsZT0iZmlsbDojRTI0MzI5OyIvPjwvZz48Zz48cGF0aCBkPSJNMTcxLjM5MiwyMDUuNDYxaC0wLjA5SDUyLjgxMmw1MC43Ni0xNTcuMDRjMi44Ny03Ljk5LDE0LjE5LTcuOTksMTYuOTgsMCAgICAgICBDMTIwLjU1Miw0OC40MjEsMTcxLjMwMiwyMDUuNDYxLDE3MS4zOTIsMjA1LjQ2MXoiIHN0eWxlPSJmaWxsOiNFMjQzMjk7Ii8+PC9nPjwvZz48L2c+PC9nPjwvZz48ZyBpZD0iTGF5ZXJfMSIvPjwvc3ZnPg==
// @version 12
// @author Arylo
// @include /^https:\/\/(git(lab)?|code)\.[^/]+\/.*\/-\/settings\/ci_cd$/
// @include /^https:\/\/(git(lab)?|code)\.[^/]+\/.*\/-\/merge_requests\/new\b/
// @include /^https:\/\/(git(lab)?|code)\.[^/]+\/.*\/-\/merge_requests\/\d+/edit\b/
// @include /^https:\/\/(git(lab)?|code)\.[^/]+\/dashboard\/merge_requests\b/
// @license MIT
// @homepage https://greatest.deepsurf.us/zh-CN/scripts/519026
// @supportURL https://greatest.deepsurf.us/zh-CN/scripts/519026/feedback
// @run-at document-end
// @grant GM_addStyle
// @grant GM_setClipboard
// ==/UserScript==
"use strict";
(() => {
  // src/monkey/polyfill/GM.ts
  var thisGlobal = window;
  if (typeof thisGlobal.GM === "undefined") {
    thisGlobal.GM = {};
  }
  function getGMWindow() {
    return thisGlobal;
  }

  // src/monkey/polyfill/GM_addStyle.ts
  var w = getGMWindow();
  if (typeof w.GM_addStyle === "undefined") {
    w.GM_addStyle = function GM_addStyle2(cssContent) {
      const head = document.getElementsByTagName("head")[0];
      if (head) {
        const styleElement = document.createElement("style");
        styleElement.setAttribute("type", "text/css");
        styleElement.textContent = cssContent;
        head.appendChild(styleElement);
        return styleElement;
      }
      return null;
    };
  }
  if (typeof w.GM.addStyle === "undefined") {
    w.GM.addStyle = GM_addStyle;
  }

  // src/monkey/gitlab-enhance/settings/ci_cd.css
  var ci_cd_default = ".content-wrapper nav{max-width:100%}.content-wrapper .container-fluid{max-width:100%}.ci-variable-table table colgroup col:nth-child(3){width:100px}.ci-variable-table table colgroup col:nth-child(4){width:200px}.ci-variable-table table colgroup col:nth-child(5){width:50px}\n";

  // src/monkey/gitlab-enhance/settings/ci_cd.ts
  if (location.pathname.endsWith("/-/settings/ci_cd")) {
    setTimeout(() => GM_addStyle(ci_cd_default), 25);
  }

  // src/monkey/gitlab-enhance/dashboard/merge_requests.ts
  var hyperlinkResource = () => {
    const mergeRequests = $(".merge-request:not([hyperlinked])");
    mergeRequests.each((_, mergeRequestEle) => {
      const href = $(".js-prefetch-document", mergeRequestEle).attr("href");
      if (!href) return;
      const resourceUrl = href.replace(/\/-\/merge_requests\/\d+$/, "");
      const rawRefEle = $(".issuable-reference", mergeRequestEle);
      const rawRefName = rawRefEle.text();
      const [resourceName, number] = rawRefName.split("!");
      rawRefEle.html(`<a href="${resourceUrl}">${resourceName}</a>!${number}`);
      $(mergeRequestEle).attr("hyperlinked", "");
    });
  };
  if (location.pathname.endsWith("/dashboard/merge_requests")) {
    $(".issuable-list").on("mouseenter", hyperlinkResource);
    setTimeout(hyperlinkResource, 1e3);
  }

  // src/monkey/gitlab-enhance/utils.ts
  var getButtonElement = (text2) => {
    const classnames = "gl-font-sm! gl-ml-3 gl-button btn btn-default btn-sm";
    return $(`<a class="${classnames}">${text2}</a>`);
  };

  // packages/MdGenerator/MdTools.ts
  var INDENT_SPACE_LENGTH = 2;
  function indent(level, content = "") {
    return `${Array((level - 1) * INDENT_SPACE_LENGTH).fill(" ").join("")}${content}`;
  }
  function enter() {
    return "\n";
  }
  var header = (level) => (text2 = "") => `${Array(level).fill("#").join("")} ${text2}`.trim();
  function h1(...args) {
    return header(1)(...args);
  }
  function h2(...args) {
    return header(2)(...args);
  }
  function h3(...args) {
    return header(3)(...args);
  }
  function h4(...args) {
    return header(4)(...args);
  }
  function h5(...args) {
    return header(5)(...args);
  }
  function text(content = "") {
    return content;
  }
  function anchor(key, url) {
    return `[${key}]: ${url}`;
  }
  function hyperlink(label, url) {
    return `[${label}](${url})`;
  }
  function hyperlinkWithKey(label, key) {
    return `[${label}][${key}]`;
  }
  function image(url, alt = "") {
    return `!${hyperlink(alt, url)}`;
  }
  function imageByKey(key, alt = "") {
    return `!${hyperlinkWithKey(alt, key)}`;
  }
  function listItem(text2 = "") {
    return `- ${text2}`.trimEnd();
  }
  function taskItem(text2 = "", { selected = false } = {}) {
    return `[${selected ? "x" : " "}] ${text2}`.trimEnd();
  }

  // packages/MdGenerator/index.ts
  function getOption(options, key, defaultValue) {
    return options[key] ?? defaultValue;
  }
  function getLevelFromOptions(options = {}) {
    const { level = 1 } = options;
    return level;
  }
  function cloneDeep(value) {
    return JSON.parse(JSON.stringify(value));
  }
  var Template = class {
    constructor(text2) {
      this.anchorMap = {};
      this.templateContent = text2;
      this.headerNextUtils = {
        text: this.text.bind(this),
        image: this.image.bind(this),
        hyperlink: this.hyperlink.bind(this),
        listItem: this.listItem.bind(this),
        taskItem: this.taskItem.bind(this)
      };
      this.contentNextUtils = {
        ...this.headerNextUtils,
        end: this.emptyLine.bind(this)
      };
    }
    h1(text2) {
      this.text(h1(text2));
      this.emptyLine();
      return this.headerNextUtils;
    }
    h2(text2) {
      this.text(h2(text2));
      this.emptyLine();
      return this.headerNextUtils;
    }
    h3(text2) {
      this.text(h3(text2));
      this.emptyLine();
      return this.headerNextUtils;
    }
    h4(text2) {
      this.text(h4(text2));
      this.emptyLine();
      return this.headerNextUtils;
    }
    h5(text2) {
      this.text(h5(text2));
      this.emptyLine();
      return this.headerNextUtils;
    }
    [Symbol.toStringTag]() {
      return [
        this.templateContent,
        this.templateContent && !/\n$/.test(this.templateContent) && enter(),
        Object.keys(this.anchorMap).length && enter(),
        Object.entries(this.anchorMap).map(([key, value]) => anchor(key, value)).join(enter()),
        Object.keys(this.anchorMap).length && enter()
      ].filter(Boolean).join("").replace(/\n(\s*\n){2,}/g, "\n\n");
    }
    text(content = "", opts) {
      this.templateContent += indent(getLevelFromOptions(opts), `${text(content)}${enter()}`);
      return this.contentNextUtils;
    }
    listItem(text2 = "", opts) {
      return this.text(listItem(text2), { level: getLevelFromOptions(opts) });
    }
    taskItem(text2 = "", opts) {
      return this.listItem(taskItem(text2, { selected: getOption(opts || {}, "selected", false) }), { level: getLevelFromOptions(opts) });
    }
    hyperlink(text2, link, opts) {
      let content;
      const anchorKey = getOption(opts || {}, "anchorKey", "");
      if (typeof anchorKey === "string" && anchorKey.length) {
        anchor(anchorKey, link);
        content = hyperlinkWithKey(text2, anchorKey);
      } else {
        content = hyperlink(text2, link);
      }
      return this.text(content, { level: getLevelFromOptions(opts) });
    }
    image(url, opts) {
      let content;
      const anchorKey = getOption(opts || {}, "anchorKey", "");
      const alt = getOption(opts || {}, "alt", "");
      if (typeof anchorKey === "string" && anchorKey.length) {
        this.anchor(anchorKey, url);
        content = imageByKey(alt, anchorKey);
      } else {
        content = image(alt, url);
      }
      return this.text(content, { level: getLevelFromOptions(opts) });
    }
    table(opts) {
      const tableMap = { header: [], body: [] };
      const actionMap = {
        header: (row) => {
          row.forEach(({ key, title }) => {
            tableMap.header.push({ key, title });
          });
          return actionMap;
        },
        body: (row) => {
          tableMap.body.push(row);
          return actionMap;
        },
        end: () => {
          if (!tableMap.header.length) return "";
          const { header: header2, body } = tableMap;
          const headerContent = header2.map(({ title }) => `|${title}`).join("") + "|";
          this.text(headerContent, { level: getLevelFromOptions(opts) });
          const separator = header2.map(() => "|--").join("") + "|";
          this.text(separator, { level: getLevelFromOptions(opts) });
          body.forEach((row) => {
            const content = header2.map(({ key }) => `|${(row[key] ?? "").toString().replace(/\|/g, "|")}`).join("") + "|";
            this.text(content, { level: getLevelFromOptions(opts) });
          });
        }
      };
      return actionMap;
    }
    anchor(key, link) {
      this.anchorMap[key] = link;
    }
    emptyLine() {
      if (!this.templateContent.endsWith(enter())) {
        this.templateContent += enter();
      }
      this.templateContent += enter();
    }
    modify(callback) {
      this.templateContent = callback(cloneDeep(this.templateContent));
      return this.contentNextUtils;
    }
  };
  var genTemplate = (callback = () => {
  }) => {
    return readTemplate("", callback);
  };
  var readTemplate = (text2, callback = () => {
  }) => {
    const templateInst = new Template(cloneDeep(text2));
    callback(templateInst);
    return templateInst[Symbol.toStringTag]();
  };

  // src/monkey/gitlab-enhance/merge_requests/template.css
  var template_default = ".gl-display-flex:has([for=merge_request_description]){align-items:baseline}\n";

  // src/monkey/gitlab-enhance/merge_requests/template.ts
  var getBranchType = () => {
    const fromBranchName = $(".align-self-center code:not([data-branch-name])").text();
    const prefixBranchName = fromBranchName.split("/")[0].toLowerCase();
    switch (prefixBranchName) {
      case "feature":
      case "feat":
        return 0 /* FEATURE */;
      case "fix":
      case "bugfix":
        return 1 /* BUGFIX */;
      case "hotfix":
        return 2 /* HOTFIX */;
      case "devops":
      case "chore":
      case "test":
      case "doc":
      case "docs":
        return 3 /* TASKS */;
      default:
        return 4 /* OTHERS */;
    }
  };
  var generateTemplate = () => {
    const branchType = getBranchType();
    return genTemplate((utils) => {
      utils.h2("Type").taskItem("Feature (Story/Refactor)", { selected: branchType === 0 /* FEATURE */ }).taskItem(`Bugfix`, { selected: branchType === 1 /* BUGFIX */ }).taskItem(`Hotfix (Production Issues)`, { selected: branchType === 2 /* HOTFIX */ }).taskItem(`Tasks (DevOps / Unit Test / Document Update)`, { selected: branchType === 3 /* TASKS */ }).taskItem(`Others`, { selected: branchType === 4 /* OTHERS */ }).end();
      utils.h2("Description");
      if (branchType !== 0 /* FEATURE */) {
        utils.h3("Why (Why does this happen?)").listItem().end();
        utils.h3("How (How can we avoid or solve it?)").listItem().end();
      }
      utils.h3("What (What did you do this time?)").listItem().end();
      if (branchType !== 3 /* TASKS */) {
        utils.h3("Results (Screenshot, etc)");
        utils.h4("Before modification");
        utils.h4("After modification");
        utils.h2("Affected Zone").listItem("Affected Module(s):").listItem("Affected URL(s):").end();
      }
      utils.h2("External resources (Mention, Resolves, or Closes)");
    });
  };
  var appendTemplateButton = () => {
    GM_addStyle(template_default);
    const text2 = "Copy Template";
    const btnElement = getButtonElement(text2);
    const templateContent = generateTemplate();
    const hint = $('<span class="gl-font-sm! gl-ml-3 gl-text-secondary"></span>');
    let setTimeoutId;
    btnElement.on("click", async () => {
      hint.remove();
      setTimeoutId && clearTimeout(setTimeoutId);
      hint.text("Copying...");
      btnElement.after(hint);
      await GM_setClipboard(templateContent, "text", () => {
        hint.text("Copied!");
        setTimeoutId = setTimeout(() => hint.remove(), 3e3);
      });
    });
    $("*:has(>[for=merge_request_description])").append(btnElement);
  };

  // src/monkey/gitlab-enhance/merge_requests/new.ts
  var appendAsTitleButton = () => {
    $(".commit-content").each((_, el) => {
      const titleElements = $(".item-title", el);
      const title = titleElements.text();
      const btnElement = getButtonElement("As title");
      btnElement.on("click", () => {
        $("input[data-testid=issuable-form-title-field]").val(title);
        $("input[data-testid=issuable-form-title-field]").focus();
      });
      $(".committer", el).before(btnElement);
    });
  };
  if (location.pathname.endsWith("/-/merge_requests/new")) {
    setTimeout(() => {
      appendTemplateButton();
      appendAsTitleButton();
    }, 1e3);
  }

  // src/monkey/gitlab-enhance/merge_requests/edit.ts
  if (/\/-\/merge_requests\/\d+\/edit$/.test(location.pathname)) {
    setTimeout(() => {
      appendTemplateButton();
    }, 1e3);
  }
})();