Jira Enhance

Enhance Jira Features: Copy Commit Message

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

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

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         Jira Enhance
// @namespace    https://github.com/codeshareman/useful-scripts.git
// @version      2025-05-11
// @description  Enhance Jira Features: Copy Commit Message
// @author       zz_captain_zz
// @match        // TODO: match your jira issue link
// @include      // TODO: match your jira issue link
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAHXRFWHRqaXJhLXN5c3RlbS1pbWFnZS10eXBlAGF2YXRhcuQCGmEAAAT1SURBVHja7Vj7U1R1FPevSBNQJMCSSHkoOmZOWtlMU2mvGWd0QlkhGZCXUmrAIpSigwiRmRmgJCK+ISRQWBgVSwFXWFNEIclYaJNdiKWFZTndc5u1/d5z9+6rH2zmnpkzs7Nz7rnn8/2ex+fcKfA/lykyABmADEAGIAOQAcgAniQAV5rvgDKrgtHcfZUwOTnptNPR0THI+vwk8aPV6h/bDA2NQlV1C3y26xQkbymBxJRiyMyugNNnf4JHj/50H8ANdQ94+UbDU17rH6uX7wZo73jgtNOaH9qY51HDF30MRqMJxsbMkJtXCf7PxREbq870/wjSM4/z9i4DMJsnYPkKJXG6c/dppwHExn9Dnk9XloPJZAZFzH6Y6r3ebvC2uur93TA8POp6DeQXVhNni5ZsA4vFcRoZhowQNC+ReXaqdxSob/bA/q9r+d/OBG/VNA64VPaKAui6pwVf/42Mo6d9FFx6dTsEUFunJkG8/GoGf5IhC7Yw/3vPioaMHcf59Lzb1QcHDtaC3+xYxiYwKB60/XrXAFgsFv76hIFgkTmSuIRD5Lm8/CpQt/8C02duYP7fllZGmkPx4QZyS7UX1K630SPfNdJCXJgqmUZ6wwi8EJbM3twMBfT0DICqSUPS6uqPncRH/4ABvARAS482uQ6gr28QAufEExDXW+/ZdVZ38Saxf+udXTzothvd3A0oGADXrncRHwO/G/iuZ+vjfE2b6wDwatcpCklAn2Ycs+tsU9K3xL74sOqf4jYYITgkSdDZzhAf5RWXmRSaFRjL36Bbk7jq+xYSUChXiNjLSffhApwXvpkUqe1QytlzhgnON2AjV7h10Kcd5E/+xMlmeDZ4E+MjIblIcohKAtDrRyA4NJmAaL5Kc/difTuxW7uugLH5yzTOzYGvYJpPFKkTYdrgrMD0wxg84kI44oWBpW4tJXYJKUUkgLPnroHYoMS0mvFMjGT/x/Y64s4kFsqlK7eJ87nhKTzf+ZfXGCFkPps+AXPiRAPAbrTwxa0OBxgOQ2ypExMWzwBgoBEiL8RArFLf0EHoAeauUCq5mvLxiyHps3R5Gry0LI2kEWo2RwqlQDhFp3dknyCOMbWskrSZTbNp3NRubLrF+NDphgnFwJvQ3OrlA0T99eEfsPK9HIGvKKiuafUMgEbTS6YjFjfSA54iCNInNIJ2qoOHLjA207mTx7khFJ1uCILmJpJZgrXjNoDx8Ql45fVMcgs44htUHaSrKLkCFMoHq3MZmwWLPxFtx2KNAweq7S7h1kZW8OV5AgBpMy4j7O4QzU9doURwAdvavf1ujt137eW4kzCNfr790DMAD3p1hIzh0MFuI2SeYkUXxvEoW7vFS7fzpFFMtqeXkULvvPubZwCQz4gxVKHmf1Et+vybq3Yydn4cRbjTSYNC8CveyHKaUru01CMrlAoeqQHyejHZk3uOTurIAn43tj2kklIVaRhWQugxABzrwj5uqyu5vLbHW+5390OAyB687DUl7N1XBUUlDRAde4BPFyHtxkX/P/usEinCUK2KO4SU4MYlDNCRRkYVgslOt3ILADJUsb0WWx3uEFKCt4M1InWLtlxq9Zo8GPSUzIlRi9nPx4uelLPS0nof1nyYT7qaVcMiUrmUqufmj/nJ/jKHNaVq1EBZ+SUoOaLi2St2Jlc+osnfRmUAMgAZgAxABiADkAF4IH8DHJ9HZXOkDL4AAAAASUVORK5CYII=
// @grant        GM.setClipboard
// @grant        GM.addStyle
// @run-at       document-end
// @license      MIT

// ==/UserScript==

const DELAY_MILLI_SECONDS = 1000;

const style = `
  #jira-enhance-copy-button {
    position: relative;
    color: var(--jira-issue-status-default-color);
    background-color: var(--jira-issue-status-default-bgcolor);
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 3px 12px;
    margin-top: 10px;
    border-radius: 3.01px;
    cursor: pointer;
    transition: background-color 0.3s ease;
  }

 #jira-enhance-copy-button:hover {
    background-color: var(--jira-issue-status-hover-default-bgcolor);
 }

 #jira-enhance-copy-button-message {
    position: absolute;
    padding: 4px 8px;
    border-radius: 3px;
    bottom: -100%;
    left: 50%;
    transform: translateX(-50%);
    background-color: #e6e6e6;
    font-size: 12px;
    color: var(--jira-issue-status-default-color);
    z-index: 1000;
    color: var(--jira-issue-status-default-color);;
    opacity: 0;
    transition: opacity 0.3s ease;
 }
 #jira-enhance-copy-button-message.active {
    opacity: 1;
 }
 #jira-enhance-copy-button-message::before {
    content: "";
    position: absolute;
    top: -5px;
    left: 50%;
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-bottom: 5px solid#e6e6e6;
    transform: translateX(-50%);
 }
`;

GM.addStyle(style);

(function () {
  "use strict";

  function runScript(
    config = {
      summaryId: "#summary-val",
      issueKeyId: "#key-val",
      toolbarPrimaryId:
        "#stalker > div > div.command-bar > div > div > div > div.aui-toolbar2-primary",
    }
  ) {
    try {
      // Your code here...
      const $summary = document.querySelector(config.summaryId);
      const $issue_key = document.querySelector(config.issueKeyId);
      const $toolbar_primary = document.querySelector(config.toolbarPrimaryId);

      const metadata = {
        issue_key: $issue_key.textContent,
        summary: $summary.textContent,
      };

      /**
       * Git Enhance
       */
      const git = {
        generateCommitMsg(issueKey, summary, options = { prefix: "fix:" }) {
          const { prefix } = options;
          const commitMsg = `${prefix} [${issueKey}] ${summary}`;
          return commitMsg;
        },
      };

      /**
       * Jira Enhance
       */
      const jira = {
        getIssueInfo(metadata) {
          return {
            issue_key: metadata.issue_key,
            summary: metadata.summary,
            commit_msg: git.generateCommitMsg(
              metadata.issue_key,
              metadata.summary
            ),
          };
        },
        logIssueInfo(info) {
          group("=== Jira Enhance Message ===");
          Object.entries(info).forEach(([key, value]) => {
            log("info")(key, value);
          });
          groupEnd();
        },
      };

      const issueInfo = jira.getIssueInfo(metadata);

      // 添加复制按钮
      appendCopyButton($toolbar_primary, issueInfo.commit_msg);

      // 打印issue信息
      jira.logIssueInfo(issueInfo);
    } catch (error) {
      log("error")(error);
    }
  }

  window.addEventListener("load", () => {
    runScript();
  });
})();

// DOM
function appendCopyButton(target, copyText = "") {
  if (!target) {
    throw new Error("target is required");
  }
  const $button = createToolbarButton({
    onClick: () => {
      GM.setClipboard(JSON.stringify(copyText));
      const $message = $button.querySelector(
        "#jira-enhance-copy-button-message"
      );
      $message.classList.add("active");
      setTimeout(() => {
        $message.classList.remove("active");
      }, DELAY_MILLI_SECONDS);
      log("info")("Copy to clipboard");
    },
  });
  appendToolbarButton(target, $button);
}

function createToolbarButton(buttonProperties) {
  const defaultProperties = {
    id: "jira-enhance-copy-button",
    icon: "fa-solid fa-clipboard",
    text: "复制提交信息",
    onClick: () => {},
  };
  const { id, icon, text, onClick } = {
    ...defaultProperties,
    ...buttonProperties,
  };
  const $button = document.createElement("div");
  const $message = document.createElement("div");
  $button.id = id;
  $button.innerHTML = `<i class="${icon}"></i> ${text}`;
  $button.addEventListener("click", onClick);
  $message.id = `${id}-message`;
  $message.innerHTML = `Copied`;
  $button.appendChild($message);
  return $button;
}

function appendToolbarButton(target, button) {
  if (!target) {
    throw new Error("target is required");
  }
  if (!button) {
    throw new Error("button is required");
  }
  const isCopyButtonExist = target.querySelector(`#${button.id}`);
  if (isCopyButtonExist) return;
  target.appendChild(button);
}

/**
 * Common Utils
 */
function debounce(
  fn,
  options = { delay: DELAY_MILLI_SECONDS, immediate: false }
) {
  const { delay, immediate } = options;
  let timer = null;

  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }

    if (immediate && !timer) {
      fn.apply(this, args);
      timer = setTimeout(() => {
        timer = null;
      }, delay);
    } else {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

function throttle(
  fn,
  options = { delay: DELAY_MILLI_SECONDS, immediate: false }
) {
  const { delay, immediate } = options;
  let timer = null;
  let isFirst = true;
  return function (...args) {
    if (timer) return;
    if (immediate && isFirst) {
      fn.apply(this, args);
      isFirst = false;
    }
    timer = setTimeout(() => {
      fn.apply(this, args);
      timer = null;
    }, delay);
  };
}

/**
 * Console Utils
 */
function log(type) {
  const typeColor = {
    info: "#0000FF",
    warn: "#FFA500",
    error: "#FF0000",
  };
  return function (...args) {
    const color = typeColor[type] || "#000";
    const style = `background-color: ${color}; color: #fff;`;
    console.error(`%c[${type}]`, style, ...args);
  };
}

function group(...args) {
  const style = `background-color:#cf1fdc; color: #fff;`;
  console.group.call(console, `%c${args[0]}`, style, ...args.slice(1));
}

function groupEnd(...args) {
  console.groupEnd.call(console, ...args);
}