您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a "Copy commit reference" button to every commit page on GitLab.
当前为
// ==UserScript== // @name GitLab: copy commit reference // @namespace https://andrybak.dev // @license AGPL-3.0-only // @version 7 // @description Adds a "Copy commit reference" button to every commit page on GitLab. // @homepageURL https://gitlab.com/andrybak/copy-commit-reference-userscript // @supportURL https://gitlab.com/andrybak/copy-commit-reference-userscript/-/issues // @author Andrei Rybak // @match https://gitlab.com/*/-/commit/* // @match https://invent.kde.org/*/-/commit/* // @match https://gitlab.gnome.org/*/-/commit/* // @icon https://gitlab.com/assets/favicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png // @require https://cdn.jsdelivr.net/gh/rybak/userscript-libs@e86c722f2c9cc2a96298c8511028f15c45180185/waitForElement.js // @require https://cdn.jsdelivr.net/gh/rybak/copy-commit-reference-userscript@4f71749bc0d302d4ff4a414b0f4a6eddcc6a56ad/copy-commit-reference-lib.js // @grant none // ==/UserScript== /* * Copyright (C) 2023 Andrei Rybak * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ (function () { 'use strict'; /* * Implementation for GitLab. * * Example URLs for testing: * - https://gitlab.com/andrybak/resoday/-/commit/b82824ec6dc3f14c3711104bf0ffd792c86d19ba * - https://invent.kde.org/education/kturtle/-/commit/8beecff6f76a4afc74879c46517d00657d8426f9 */ class GitLab extends GitHosting { static #HEADER_SELECTOR = 'main#content-body .page-content-header > .header-main-content'; getTargetSelector() { return GitLab.#HEADER_SELECTOR; } getButtonTagName() { return 'button'; // like GitLab's "Copy commit SHA" } wrapButton(button) { const copyShaButtonIcon = document.querySelector(`${GitLab.#HEADER_SELECTOR} > button > svg[data-testid="copy-to-clipboard-icon"]`); const icon = copyShaButtonIcon.cloneNode(true); button.replaceChildren(icon); // is just icon enough? button.classList.add('btn-sm', 'btn-default', 'btn-default-tertiary', 'btn-icon', 'btn', 'btn-clipboard', 'gl-button'); button.setAttribute('data-toggle', 'tooltip'); // this is needed to have a fancy tooltip in style of other UI button.setAttribute('data-placement', 'bottom'); // this is needed so that the fancy tooltip appears below the button button.style = 'border: 1px solid darkgray;'; button.title = this.getButtonText() + " to clipboard"; return button; } getFullHash() { const copyShaButton = document.querySelector(`${GitLab.#HEADER_SELECTOR} > button`); return copyShaButton.getAttribute('data-clipboard-text'); } getDateIso(hash) { // careful not to select <time> tag for "Committed by" const authorTimeTag = document.querySelector(`${GitLab.#HEADER_SELECTOR} > .d-sm-inline + time`); return authorTimeTag.getAttribute('datetime').slice(0, 'YYYY-MM-DD'.length); } getCommitMessage(hash) { /* * Even though vast majority will only need `subj`, gather everything and * let downstream code handle paragraph splitting. */ const subj = document.querySelector('.commit-box .commit-title').innerText; const maybeBody = document.querySelector('.commit-box .commit-description'); if (maybeBody == null) { // some commits have only a single-line message return subj; } const body = maybeBody.innerText; return subj + '\n\n' + body; } addButtonContainerToTarget(target, buttonContainer) { const authoredSpanTag = target.querySelector('span.d-sm-inline'); target.insertBefore(buttonContainer, authoredSpanTag); // add spacer to make text "authored" not stick to the button target.insertBefore(document.createTextNode(" "), authoredSpanTag); } /* * GitLab has a complex interaction with library ClipboardJS: * * - https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/helpers/button_helper.rb#L31-68 * - https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/assets/javascripts/behaviors/copy_to_clipboard.js#L63-94 * * and the native tooltips are even more complicated. */ createCheckmark() { const checkmark = super.createCheckmark(); checkmark.style.left = 'calc(100% + 0.3rem)'; checkmark.style.lineHeight = '1.5'; checkmark.style.padding = '0.5rem 1.5rem'; checkmark.style.textAlign = 'center'; checkmark.style.width = 'auto'; checkmark.style.borderRadius = '3px'; checkmark.style.fontSize = '0.75rem'; checkmark.style.fontFamily = '"Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"'; if (document.body.classList.contains('gl-dark')) { checkmark.style.backgroundColor = '#dcdcde'; checkmark.style.color = '#1f1e24'; } else { checkmark.style.backgroundColor = '#000'; checkmark.style.color = '#fff'; } return checkmark; } /** * @returns {string} */ static #getIssuesUrl() { const newUiIssuesLink = document.querySelector('nav a[href$="/issues"]'); if (newUiIssuesLink) { return newUiIssuesLink.href; } const oldUiIssuesLink = document.querySelector('aside a[href$="/issues"]'); return oldUiIssuesLink.href; } async convertPlainSubjectToHtml(plainTextSubject, commitHash) { const escapedHtml = await super.convertPlainSubjectToHtml(plainTextSubject, hash); if (!escapedHtml.includes('#')) { return escapedHtml; } const issuesUrl = GitLab.#getIssuesUrl(); return escapedHtml.replaceAll(/#([0-9]+)/g, `<a href="${issuesUrl}/\$1">#\$1</a>`); } } CopyCommitReference.runForGitHostings(new GitLab()); })();