Jira New Tab Flow: Open Tickets in a New Tab, Not in Popup

Enhance your Jira experience open any clicked issue in a new tab

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Jira New Tab Flow: Open Tickets in a New Tab, Not in Popup
// @namespace    http://tampermonkey.net/
// @version      2
// @description  Enhance your Jira experience open any clicked issue in a new tab
// @icon         https://static-00.iconduck.com/assets.00/jira-icon-512x512-kkop6eik.png
// @author       Ameer Jamal
// @match        https://*.atlassian.net/jira/*
// @match        https://*.atlassian.com/jira/*
// @grant        none
// @require      https://unpkg.com/sweetalert@2/dist/sweetalert.min.js
// ==/UserScript==

(function() {
    'use strict';

    // Variables to store state information
    let lastSelectedIssue = null; // The last issue that was selected
    let isActive = true; // Whether the script is currently active
    let escapeHitCounter = 0; // Count of consecutive "Ctrl" key presses
    let justActivated = true; // Flag to indicate that the script was just activated
    let button = null; // Button for toggling script activation
    let buttonActiveText = 'Tickets Currently Open  in New Tab'; // Text for when the script is enabled
    let buttonInactiveText ='Tickets Currently Open in SideBar' ; // Text for when script is disabled
    let Red = "#DE350B"
    let Green = "#1F875A"
    // Function to toggle the active state of the script
    const toggleActiveState = () => {
        isActive = !isActive; // Toggle active state
        if (isActive) {
            justActivated = true;
            swal({ // Show a success alert when activated
                title: "Clicking on a ticket now opens it in a new tab",
                icon: "success",
                buttons: false,
                timer: 1500,
                allowEscapeKey: false
            });
        } else {
            lastSelectedIssue = null;
            swal({ // Show an error alert when deactivated
                title: "Clicking on a ticket opens in the sidebar as default",
                icon: "error",
                buttons: false,
                timer: 1500,
                allowEscapeKey: false
            });
        }

        // Update the button text and color based on the current state
        if (button) {
    button.textContent = !isActive ? buttonInactiveText : buttonActiveText; // Set the button text content
    button.style.color = 'white'; // Set the button text color
    button.style.backgroundColor = !isActive ? Red : Green; // Set the button background color
        }

        // Reset the control key press counter
        escapeHitCounter = 0;
    };

    // Function to check if a new issue is selected
    const checkForSelectedIssue = () => {
        if (!isActive) { // Skip if the script is not active
            return;
        }

        // Get the currently selected issue
        const urlParams = new URLSearchParams(window.location.search);
        const urlDomain = window.location.hostname;
        const selectedIssue = urlParams.get('selectedIssue');

        // Open the selected issue in a new tab if it's different from the last one
        if (selectedIssue && selectedIssue !== lastSelectedIssue && !justActivated) {
            window.open(`https://${urlDomain}/browse/${selectedIssue}`, '_blank');
        }

        lastSelectedIssue = selectedIssue;
        justActivated = false;
    };

    // Create a mutation observer to detect DOM changes
    const observer = new MutationObserver(checkForSelectedIssue);

    // Start observing the body of the page for changes in the child list and the subtree
    observer.observe(document.querySelector('body'), {
        childList: true,
        subtree: true
    });

    // Add an event listener for the keyup event
    window.addEventListener('keyup', (event) => {
        // Check if the "Ctrl" key was pressed
        if (event.key === 'Control') {
            escapeHitCounter += 1;

            // Toggle active state if the "Ctrl" key was pressed twice in a row
            if (escapeHitCounter === 2) {
                toggleActiveState();
            }

            // Reset the control key press counter after a short delay
            setTimeout(() => {
                escapeHitCounter = 0;
            }, 300);
        }
    });

    // Function to create the button for toggling script activation
    const createButton = () => {
    button = document.createElement('span'); // Create a new span element
    button.setAttribute('class', 'css-1gd7hga'); // Set the class attribute
    button.textContent = !isActive ? buttonInactiveText : buttonActiveText; // Set the button text content
    button.style.color = 'white'; // Set the button text color
    button.style.backgroundColor = !isActive ? Red : Green; // Set the button background color
    button.style.borderRadius = '10px'; // Round the corners of the button
    button.style.cursor = 'pointer'; // Change the cursor when hovering over the button
     // Add styles to make it look more like a button
    button.style.padding = "20px 15px"; // 20px top-bottom, 15px left-right
    button.style.margin = "20px 0"; // 20px top-bottom, 0 left-right
    button.style.cursor = "pointer"; // Change cursor to pointer on hover to indicate clickability

    button.style.textDecoration = 'none'; // Remove underline from text
    button.addEventListener('click', toggleActiveState); // Add an event listener for the click event

    return button;
    };

    // Function to replace the "Learn more" button with our custom button
    const replaceButton = () => {
        const learnMoreButton = document.querySelector('[data-item-description=true] .css-8nt2sa'); // Get the "Learn more" button

        if (learnMoreButton && learnMoreButton.textContent.includes('Learn more')) { // Check if the "Learn more" button exists
            const newButton = createButton(); // Create our custom button
            learnMoreButton.parentNode.replaceChild(newButton, learnMoreButton); // Replace the "Learn more" button with our custom button
        }
    };

    replaceButton(); // Replace the "Learn more" button immediately when the script is loaded

    // Create another mutation observer to detect when the "Learn more" button is added to the page
    const buttonObserver = new MutationObserver(replaceButton);

    // Start observing the body of the page for changes in the child list and the subtree
    buttonObserver.observe(document.querySelector('body'), {
        childList: true,
        subtree: true
    });
})();