您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhance some features of the Self-managed Gitlab, such as the CI/CD settings page, the merge request create/edit page etc.
// ==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://gf.qytechs.cn/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://gf.qytechs.cn/zh-CN/scripts/519026 // @supportURL https://gf.qytechs.cn/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); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址