Jira Enhance

Enhance Jira Features: Copy Commit Message

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

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

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

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.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Zateb bir user-style yöneticim var, yükleyeyim!)

// ==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);
}