GitHub Issue 快速编辑器

通过点击 GitHub Issue 标题栏快速进入编辑模式

当前为 2025-03-03 提交的版本,查看 最新版本

// ==UserScript==
// @name         GitHub Issue 快速编辑器
// @namespace    https://github.com/
// @version      0.1
// @description  通过点击 GitHub Issue 标题栏快速进入编辑模式
// @author       RainbowBird
// @match        https://github.com/*/*/issues/*
// @grant        none
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 添加全局样式
    const styles = `
        .issue-title-hover {
            transition: background-color 0.2s ease;
            position: relative;
            overflow: hidden;
        }

        .issue-title-hover:hover {
            background-color: rgba(180, 180, 180, 0.1);
        }

        .ripple {
            position: absolute;
            border-radius: 50%;
            background-color: rgba(0, 120, 215, 0.2);
            transform: scale(0);
            animation: ripple-effect 0.6s linear;
            z-index: 0;
        }

        @keyframes ripple-effect {
            to {
                transform: scale(2.5);
                opacity: 0;
            }
        }
    `;

    // 添加样式到页面
    const addStyle = (css) => {
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    };

    addStyle(styles);

    /**
     * GitHub Issue快速编辑器主类
     */
    class IssueQuickEditor {
        constructor() {
            this.initialized = false;
            this.titleBar = null;
            this.isEditing = false;
            this.config = {
                selectors: {
                    issueBody: '.IssueBodyViewer-module__IssueBody--MXyFt',
                    markdownBody: '[data-testid="markdown-body"]',
                    actionButton: 'button[aria-label="Issue body actions"]',
                    portalRoot: '#__primerPortalRoot__'
                },
                styles: {
                    cursor: 'pointer'
                },
                tooltips: {
                    titleBar: '点击编辑Issue'
                },
                maxAttempts: {
                    loadWait: 20,
                    menuItemWait: 10
                },
                animationDuration: {
                    ripple: 600
                }
            };
        }

        /**
         * 初始化编辑器
         */
        init() {
            if (this.initialized) {
                console.log('Issue快速编辑功能已初始化,跳过');
                return;
            }

            this.waitForElements()
                .then(() => this.setupTitleBar())
                .catch(error => console.error('初始化失败:', error));
        }

        /**
         * 等待关键元素加载
         */
        waitForElements() {
            return new Promise((resolve, reject) => {
                let attempts = 0;
                const checkInterval = setInterval(() => {
                    const issueBodyContainer = document.querySelector(this.config.selectors.issueBody);
                    attempts++;

                    if (issueBodyContainer) {
                        clearInterval(checkInterval);
                        resolve();
                    } else if (attempts >= this.config.maxAttempts.loadWait) {
                        clearInterval(checkInterval);
                        reject(new Error('页面元素加载超时'));
                    }
                }, 500);
            });
        }

        /**
         * 定位标题栏元素
         */
        findTitleBar() {
            const issueBodyContainer = document.querySelector(this.config.selectors.issueBody);
            if (!issueBodyContainer) {
                throw new Error('找不到Issue正文容器');
            }

            const titleBar = issueBodyContainer.previousElementSibling;
            if (!titleBar || titleBar.tagName !== 'DIV') {
                throw new Error('无法找到标题栏');
            }

            return titleBar;
        }

        /**
         * 查找菜单按钮
         */
        findMenuButton() {
            // 首先尝试找到"Issue body actions"按钮
            const actionButton = document.querySelector(this.config.selectors.actionButton);
            if (actionButton) return actionButton;

            // 备用方法
            const markdownBody = document.querySelector(this.config.selectors.markdownBody);
            if (!markdownBody) return null;

            const issueBodyModule = markdownBody.closest(this.config.selectors.issueBody);
            if (!issueBodyModule) return null;

            // 找到活动标头区域
            const activityHeader = issueBodyModule.previousElementSibling;
            if (!activityHeader) return null;

            // 查找任何aria-haspopup="true"的按钮
            return activityHeader.querySelector('button[aria-haspopup="true"]');
        }

        /**
         * 创建水波纹动画效果
         */
        createRippleEffect(event) {
            // 获取点击位置相对于标题栏的坐标
            const rect = this.titleBar.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;

            // 计算动画尺寸(取容器宽高的较大值)
            const size = Math.max(rect.width, rect.height);

            // 创建水波纹元素
            const ripple = document.createElement('span');
            ripple.className = 'ripple';
            ripple.style.left = `${x}px`;
            ripple.style.top = `${y}px`;
            ripple.style.width = `${size}px`;
            ripple.style.height = `${size}px`;

            // 添加到标题栏
            this.titleBar.appendChild(ripple);

            // 动画结束后移除元素
            setTimeout(() => {
                ripple.remove();
            }, this.config.animationDuration.ripple);
        }

        /**
         * 查找编辑选项
         */
        findEditOption() {
            return new Promise(resolve => {
                const findEditItem = () => {
                    // 从portal root查找
                    const portalRoot = document.getElementById('__primerPortalRoot__'.substring(1));
                    if (portalRoot) {
                        // 获取最后一个子div
                        const lastDiv = Array.from(portalRoot.children)
                        .filter(child => child.tagName === 'DIV')
                        .pop();

                        if (lastDiv) {
                            const ul = lastDiv.querySelector('ul[role="menu"]');
                            if (ul) {
                                // 查找包含"Edit"文本的li
                                const menuItems = ul.querySelectorAll('li');
                                for (const item of menuItems) {
                                    if (item.textContent.includes('Edit') ||
                                        item.textContent.includes('编辑')) {
                                        return item;
                                    }
                                }
                            }
                        }
                    }

                    // 回退方法
                    return Array.from(document.querySelectorAll('[role="menuitem"], li'))
                        .filter(item => {
                        const rect = item.getBoundingClientRect();
                        return rect.width > 0 && rect.height > 0;
                    })
                        .find(item => {
                        const text = item.textContent?.toLowerCase() || '';
                        return text.includes('edit') || text.includes('编辑');
                    });
                };

                // 查找逻辑
                let editItem = findEditItem();
                if (editItem) {
                    resolve(editItem);
                    return;
                }

                let attempts = 0;
                const intervalId = setInterval(() => {
                    editItem = findEditItem();
                    attempts++;

                    if (editItem || attempts >= this.config.maxAttempts.menuItemWait) {
                        clearInterval(intervalId);
                        resolve(editItem);
                    }
                }, 50);
            });
        }

        /**
         * 设置标题栏样式和事件
         */
        setupTitleBar() {
            try {
                this.titleBar = this.findTitleBar();

                // 设置样式
                this.titleBar.style.cursor = this.config.styles.cursor;
                this.titleBar.title = this.config.tooltips.titleBar;
                this.titleBar.classList.add('issue-title-hover');

                // 添加点击事件
                this.titleBar.addEventListener('click', this.handleTitleBarClick.bind(this));

                this.initialized = true;
                console.log('Issue快速编辑功能已启用 - 点击标题栏触发编辑');
            } catch (error) {
                console.error('设置标题栏失败:', error);
            }
        }

        /**
         * 处理标题栏点击事件
         */
        async handleTitleBarClick(event) {
            // 如果点击了链接或按钮,不触发编辑
            if (event.target.tagName === 'A' ||
                event.target.tagName === 'BUTTON' ||
                event.target.closest('a') ||
                event.target.closest('button')) {
                return;
            }

            // 如果已经在编辑中,防止重复点击
            if (this.isEditing) {
                return;
            }

            try {
                // 设置编辑状态
                this.isEditing = true;

                // 创建水波纹动画效果
                this.createRippleEffect(event);

                // 找到菜单按钮
                const menuButton = this.findMenuButton();
                if (!menuButton) {
                    throw new Error('找不到菜单按钮');
                }

                // 点击打开菜单
                menuButton.click();

                // 等待菜单打开并点击编辑选项
                const editOption = await this.findEditOption();
                if (!editOption) {
                    this.isEditing = false;
                    throw new Error('找不到编辑选项');
                }

                // 点击编辑选项
                editOption.click();

                // 监听编辑状态变化
                this.watchForEditModeEnd();

            } catch (error) {
                console.error('编辑操作失败:', error);
                this.isEditing = false;
            }
        }

        /**
         * 监听编辑模式结束
         */
        watchForEditModeEnd() {
            // 检查是否存在编辑表单
            const checkForEditForm = () => {
                const editForm = document.querySelector('form.js-quick-submit');
                if (!editForm) {
                    this.isEditing = false;
                    return;
                }
                setTimeout(checkForEditForm, 1000);
            };

            setTimeout(checkForEditForm, 1000);
        }
    }

    // 创建并初始化编辑器实例
    const editor = new IssueQuickEditor();

    // 页面加载后启动
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => editor.init());
    } else {
        setTimeout(() => editor.init(), 500);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址