GitHub Comment ID

Displays and allows copying of the comment ID on GitHub issue and pull request pages.

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey 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 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.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

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         GitHub Comment ID
// @namespace    https://github.com/Aiuanyu/GeminiChat2MD
// @version      0.4
// @description  Displays and allows copying of the comment ID on GitHub issue and pull request pages.
// @author       Aiuanyu
// @match        https://github.com/*/*/issues/*
// @match        https://github.com/*/*/pull/*
// @grant        none
// @license      MIT
// @history      0.4 Apply minor fixes from code review.
// @history      0.3 Fix UI overlap by injecting into a different element and using flexbox.
// @history      0.2 Add error handling for clipboard and adjust CSS
// @history      0.1 Initial release
// ==/UserScript==

(function() {
    'use strict';

    function addStyles() {
        const css = `
            .gh-comment-id-container {
                display: flex;
                align-items: center;
                font-size: 12px;
                color: var(--color-fg-muted);
                background-color: var(--color-canvas-subtle);
                border: 1px solid var(--color-border-default);
                border-radius: 6px;
                padding: 2px 6px;
                line-height: 1;
                margin-right: 8px; /* Space between ID and kebab menu */
            }
            .gh-comment-id-copy-button {
                background: none;
                border: none;
                cursor: pointer;
                margin-left: 5px;
                padding: 0;
                display: inline-flex;
                align-items: center;
                color: var(--color-fg-muted);
            }
            .gh-comment-id-copy-button:hover {
                color: var(--color-accent-fg);
            }
            .gh-comment-id-copy-button svg {
                fill: currentColor;
            }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = css;
        document.head.appendChild(styleSheet);
    }

    function processComment(comment) {
        if (!comment.id || comment.querySelector('.gh-comment-id-container')) {
            return; // Skip if no ID or already processed
        }

        // GitHub comment IDs follow a format like "issuecomment-123456789"
        const commentId = comment.id.split('-').pop();
        if (!/^\d+$/.test(commentId)) {
            // Check if the commentId is purely numeric
            return;
        }

        const actionsContainer = comment.querySelector('.timeline-comment-actions');
        if (!actionsContainer) {
            return; // No actions container found
        }

        // Create the container
        const container = document.createElement('div');
        container.className = 'gh-comment-id-container';

        // Create the ID text span
        const idSpan = document.createElement('span');
        idSpan.textContent = commentId;
        container.appendChild(idSpan);

        // Create the copy button
        const copyButton = document.createElement('button');
        copyButton.className = 'gh-comment-id-copy-button';
        copyButton.title = 'Copy Comment ID';
        copyButton.innerHTML = `<svg aria-hidden="true" height="12" viewBox="0 0 16 16" version="1.1" width="12" data-view-component="true" class="octicon octicon-copy"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>`;

        if (!navigator.clipboard) {
            copyButton.title = 'Clipboard API not available';
            copyButton.disabled = true;
        } else {
            copyButton.onclick = (e) => {
                e.stopPropagation(); // prevent triggering other click events
                navigator.clipboard.writeText(commentId).then(() => {
                    copyButton.title = 'Copied!';
                    setTimeout(() => { copyButton.title = 'Copy Comment ID'; }, 2000);
                }).catch(err => {
                    console.error('Failed to copy comment ID:', err);
                    copyButton.title = 'Copy failed';
                    setTimeout(() => { copyButton.title = 'Copy Comment ID'; }, 2000);
                });
            };
        }
        container.appendChild(copyButton);

        // Inject the UI
        actionsContainer.prepend(container);
    }

    function processAllComments() {
        document.querySelectorAll('.timeline-comment-group').forEach(processComment);
    }

    function init() {
        addStyles();
        processAllComments();

        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.addedNodes.length) {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches('.timeline-comment-group')) {
                                processComment(node);
                            } else {
                                node.querySelectorAll('.timeline-comment-group').forEach(processComment);
                            }
                        }
                    });
                }
            }
        });

        const discussionContainer = document.querySelector('#discussion_bucket');
        if (discussionContainer) {
            observer.observe(discussionContainer, {
                childList: true,
                subtree: true
            });
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();