// ==UserScript==
// @name Jira Cloud: Copy suggested Git branch name to Clipboard
// @version 1.1
// @description Adds a button to Jira Cloud that copies the suggested Git branch name to the clipboard.
// @author rasmusbe
// @license MIT
// @match https://*.atlassian.net/jira/software/projects/*/boards/*
// @match https://*.atlassian.net/browse/*
// @namespace rasmusbe/jirabranch
// ==/UserScript==
(function () {
"use strict";
const branchIcon = new Image();
branchIcon.src =
"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KDTwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIEdlbmVyYXRvcjogU1ZHIFJlcG8gTWl4ZXIgVG9vbHMgLS0+Cjxzdmcgd2lkdGg9IjgwMHB4IiBoZWlnaHQ9IjgwMHB4IiB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+DQogIDx0aXRsZT5icmFuY2g8L3RpdGxlPg0KICA8ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj4NCiAgICA8ZyBpZD0iaW52aXNpYmxlX2JveCIgZGF0YS1uYW1lPSJpbnZpc2libGUgYm94Ij4NCiAgICAgIDxyZWN0IHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgZmlsbD0ibm9uZSIvPg0KICAgIDwvZz4NCiAgICA8ZyBpZD0iaWNvbnNfUTIiIGRhdGEtbmFtZT0iaWNvbnMgUTIiPg0KICAgICAgPHBhdGggZD0iTTQ0LDlhNyw3LDAsMSwwLTksNi43VjE2YTYsNiwwLDAsMS02LDZIMjFhMTAuMywxMC4zLDAsMCwwLTYsMlYxNS43YTcsNywwLDEsMC00LDBWMzIuM2E3LDcsMCwxLDAsNCwwVjMyYTYsNiwwLDAsMSw2LTZoOEExMCwxMCwwLDAsMCwzOSwxNnYtLjNBNyw3LDAsMCwwLDQ0LDlaTTEwLDlhMywzLDAsMCwxLDYsMCwzLDMsMCwwLDEtNiwwWm02LDMwYTMsMywwLDEsMS0zLTNBMi45LDIuOSwwLDAsMSwxNiwzOVpNMzcsMTJhMi45LDIuOSwwLDAsMS0zLTMsMywzLDAsMCwxLDYsMEEyLjksMi45LDAsMCwxLDM3LDEyWiIvPg0KICAgIDwvZz4NCiAgPC9nPg0KPC9zdmc+";
branchIcon.width = "20";
const issueTitleNormalized = (issueTitle) =>
issueTitle
.trim()
.toLowerCase()
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/\W+/g, "-")
.toLowerCase();
const copyBranchName = (issueId, issueTitle) =>
navigator.clipboard.writeText(
`${issueId}-${issueTitleNormalized(issueTitle)}`,
);
const doContextMenu = (issueWithContextMenu) => {
const contextMenu = issueWithContextMenu.querySelector(
'[data-testid="software-context-menu.ui.context-menu-inner.context-menu-inner-container"]',
);
const issueTitle = issueWithContextMenu.querySelector(
'[data-testid="issue-field-summary-inline-edit.ui.read"] :first-child',
)?.textContent;
const issueId = issueWithContextMenu.querySelector(
'[data-testid="platform-card.common.ui.key.key"]',
)?.textContent;
if (!issueId || !issueTitle) {
return;
}
const contextMenuUl = contextMenu.querySelector("ul");
const copyMenuItem = [
...contextMenuUl.querySelectorAll("li > button"),
].find((button) => button.textContent.indexOf("Copy") !== -1);
const copyBranchItem = copyMenuItem.cloneNode(true);
copyBranchItem.setAttribute("id", "copyBranchName");
copyBranchItem.querySelector("span").innerText = "Copy issue branch";
copyMenuItem.insertAdjacentElement("afterend", copyBranchItem);
copyBranchItem.addEventListener("click", (e) => {
e.preventDefault();
copyBranchName(issueId, issueTitle);
contextMenu.remove();
});
};
const doIssueCard = (issueCard) => {
const issueIdLink = issueCard.querySelector(
'[data-testid="issue.views.issue-base.foundation.breadcrumbs.current-issue.item"]',
);
const issueId = issueIdLink?.textContent;
const issueTitle = issueCard.querySelector(
'[data-testid="issue.views.issue-base.foundation.summary.heading"]',
)?.textContent;
if (!issueId || !issueTitle) {
return;
}
const buttonNeighbor = issueCard.querySelector(
'[data-testid="issue.watchers.action-button.tooltip--container"]',
)?.parentElement.parentElement.parentElement.parentElement;
if (!buttonNeighbor) return;
console.log("Creating branch button");
const copyBranchNameButton = document.createElement("div");
copyBranchNameButton.setAttribute("id", "copyBranchName");
copyBranchNameButton.style.setProperty("width", "32px", "");
copyBranchNameButton.style.setProperty("display", "flex", "");
copyBranchNameButton.style.setProperty("cursor", "pointer", "");
copyBranchNameButton.style.setProperty("align-items", "center", "");
copyBranchNameButton.style.setProperty("justify-content", "center", "");
copyBranchNameButton.style.setProperty(
"transition",
"200ms filter linear",
"",
);
copyBranchNameButton.title = "Copy Branch Name";
copyBranchNameButton.appendChild(branchIcon);
copyBranchNameButton.addEventListener("click", () => {
copyBranchName(issueId, issueTitle);
copyBranchNameButton.style.setProperty(
"filter",
"invert(42%) sepia(93%) saturate(1352%) hue-rotate(87deg) brightness(119%) contrast(119%)",
"",
);
setTimeout(() => {
copyBranchNameButton.style.setProperty("filter", "", "");
}, 500);
});
buttonNeighbor.prepend(copyBranchNameButton);
};
const scanPage = () => {
if (!document.querySelector("#copyBranchName")) {
const issueWithContextMenu = document.querySelector(
'[data-testid="platform-board-kit.ui.card.card"]:has([data-testid="software-context-menu.ui.context-menu-inner.context-menu-inner-container"])',
);
const modalDialog = document.querySelector(
'[data-testid="issue.views.issue-details.issue-modal.modal-dialog"]',
);
const issueDetailsView = document.querySelector(
'[data-testid="issue.views.issue-details.issue-layout.issue-layout"]',
);
if (issueWithContextMenu) {
console.log("Context menu found");
doContextMenu(issueWithContextMenu);
} else if (modalDialog) {
console.log("Modal dialog found");
doIssueCard(modalDialog);
} else if (issueDetailsView) {
console.log("Issue details found");
doIssueCard(issueDetailsView);
}
}
setTimeout(() => requestAnimationFrame(scanPage), 500);
};
requestAnimationFrame(scanPage);
})();