Git仓库一键跳转HPX

在Git仓库页面添加跳转到HPX打包页面的按钮

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Git仓库一键跳转HPX
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  在Git仓库页面添加跳转到HPX打包页面的按钮
// @author       Dean
// @match        https://dev.sankuai.com/code/repo-detail/*
// @grant        GM_xmlhttpRequest
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 添加样式
    const style = document.createElement('style');
    style.textContent = `
        #zy_hpx_button {
            margin-right: 8px;
            position: relative;
            overflow: hidden;
        }
        .mtd-button-content {
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            z-index: 2;
        }
        .mtdicon-fast-forward {
            margin-right: 4px;
        }

        /* 节日装饰样式 */
        .festival-icon {
            position: absolute;
            pointer-events: none;
            font-size: 12px;
            z-index: 1;
        }

        /* 春节样式 */
        .spring-festival .festival-icon {
            animation: springFestival 2s infinite;
        }

        /* 圣诞节样式 */
        .christmas .festival-icon {
            animation: snowfall 3s infinite;
        }

        /* 万圣节样式 */
        .halloween .festival-icon {
            animation: spooky 3s infinite;
        }

        /* 元宵节样式 */
        .lantern-festival .festival-icon {
            animation: floating 3s infinite;
        }

        /* 动画效果 */
        @keyframes springFestival {
            0% { transform: scale(1) rotate(0deg); opacity: 1; }
            50% { transform: scale(1.2) rotate(180deg); opacity: 0.8; }
            100% { transform: scale(1) rotate(360deg); opacity: 1; }
        }

        @keyframes snowfall {
            0% { transform: translateY(-100%) rotate(0deg); opacity: 1; }
            100% { transform: translateY(100%) rotate(360deg); opacity: 0; }
        }

        @keyframes spooky {
            0% { transform: translateX(-20px) translateY(0); opacity: 1; }
            50% { transform: translateX(20px) translateY(-10px); opacity: 0.7; }
            100% { transform: translateX(-20px) translateY(0); opacity: 1; }
        }

        @keyframes floating {
            0% { transform: translateY(0) rotate(-5deg); }
            50% { transform: translateY(-10px) rotate(5deg); }
            100% { transform: translateY(0) rotate(-5deg); }
        }

        /* 光效装饰 */
        .festival-sparkle {
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: 0;
            pointer-events: none;
            z-index: 1;
        }
        .festival-sparkle::before,
        .festival-sparkle::after {
            content: '';
            position: absolute;
            width: 2px;
            height: 2px;
            border-radius: 50%;
            background: rgba(255,255,255,0.6);
            animation: sparkle 2s infinite;
        }
        .festival-sparkle::after {
            animation-delay: 1s;
        }

        @keyframes sparkle {
            0%, 100% { transform: translate(0, 0) scale(0); opacity: 0; }
            50% { transform: translate(20px, -20px) scale(1); opacity: 1; }
        }
    `;
    document.head.appendChild(style);

    // 页面加载完成后执行
    if (window.location.toString().indexOf('dev.sankuai.com/code/repo-detail') >= 0) {
        // 使用 MutationObserver 监听DOM变化
        const observer = new MutationObserver((mutations, observer) => {
            if ($(".btn-box").length > 0 && $("#zy_hpx_button").length === 0) {
                logger('检测到按钮容器');
                observer.disconnect(); // 停止观察
                inject(() => {});
            }
        });

        // 立即检查是否已存在按钮容器
        if ($(".btn-box").length > 0) {
            logger('按钮容器已存在');
            inject(() => {});
        } else {
            logger('等待按钮容器');
            // 开始观察
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        // 添加页面 URL 变化监听
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                logger('URL 发生变化');
                if (url.indexOf('dev.sankuai.com/code/repo-detail') >= 0) {
                    inject(() => {});
                }
            }
        }).observe(document, {subtree: true, childList: true});
    }

    // 缓存键名
    const CACHE_KEY = 'HPX_PROJECT_CACHE';
    const CACHE_EXPIRE = 24 * 60 * 60 * 1000; // 24小时缓存

    // 获取缓存的项目数据
    function getCachedProject(git) {
        try {
            const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
            const data = cache[git];
            if (data && (Date.now() - data.timestamp) < CACHE_EXPIRE) {
                return data.project;
            }
        } catch (e) {
            logger('读取缓存失败', e);
        }
        return null;
    }

    // 设置项目缓存
    function setCachedProject(git, project) {
        try {
            const cache = JSON.parse(localStorage.getItem(CACHE_KEY) || '{}');
            cache[git] = {
                project: project,
                timestamp: Date.now()
            };
            localStorage.setItem(CACHE_KEY, JSON.stringify(cache));
        } catch (e) {
            logger('设置缓存失败', e);
        }
    }

    // 入侵
    function inject(callback) {
        if ($(".btn-box").length <= 0) {
            logger('没有查到元素');
            return false;
        }
        logger('查到元素');

        // 先渲染一个加载中的按钮
        renderLoadingButton();

        // 查询git地址
        getGitAddress(function(git) {
            if (git.length <= 0) {
                removeButton();
                callback(true);
                return;
            }

            // 先检查缓存
            const cachedProject = getCachedProject(git);
            if (cachedProject) {
                logger('使用缓存数据');
                renderHPXButton(cachedProject);
                callback(true);

                // 异步更新缓存
                updateProjectCache(git);
                return;
            }

            // 无缓存时请求新数据
            requestProjectData(git, callback);
        });
    }

    // 异步更新缓存
    function updateProjectCache(git) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://hpx.sankuai.com/api/open/getProjectUrlList?repoUrl=' + git,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.data && data.data.length > 0) {
                        const project = data.data[data.data.length - 1];
                        setCachedProject(git, project);
                        logger('缓存已更新');
                    }
                } catch (e) {
                    logger('更新缓存失败', e);
                }
            }
        });
    }

    // 请求项目数据
    function requestProjectData(git, callback) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: 'https://hpx.sankuai.com/api/open/getProjectUrlList?repoUrl=' + git,
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    if (data.data && data.data.length > 0) {
                        const project = data.data[data.data.length - 1];
                        if (project.length > 0) {
                            logger('获取新数据');
                            setCachedProject(git, project);
                            renderHPXButton(project);
                            callback(true);
                            return;
                        }
                    }
                    // 如果没有获取到有效数据,移除loading按钮
                    removeButton();
                    callback(true);
                } catch (e) {
                    logger('请求数据失败', e);
                    removeButton();
                    callback(true);
                }
            },
            onerror: function() {
                logger('网络请求失败');
                removeButton();
                callback(true);
            }
        });
    }

    // 渲染加载中按钮
    function renderLoadingButton() {
        removeButton(); // 先移除已存在的按钮
        $(".btn-box").prepend(`
            <button id="zy_hpx_button" type="button" class="mtd-btn mtd-btn-primary">
                <span>
                    <div class="mtd-button-content">
                        <span class="mtdicon mtdicon-fast-forward"></span>
                        <span>Loading...</span>
                    </div>
                </span>
            </button>
        `);
    }

    // 渲染按钮
    function renderHPXButton(project) {
        removeButton(); // 先移除已存在的按钮
        const festival = getFestival();
        const festivalConfig = {
            'spring-festival': {
                icon: '🏮',
                text: '新年快乐',
                icons: ['🏮', '💰', '🧨', '🎊', '🐲', '福']
            },
            'lantern-festival': {
                icon: '🏮',
                text: '元宵节快乐',
                icons: ['🏮', '👻', '🌕', '⭐']
            },
            'halloween': {
                icon: '🎃',
                text: 'Happy Halloween',
                icons: ['🎃', '👻', '🦇', '🕷️', '🕸️']
            },
            'christmas': {
                icon: '🎄',
                text: 'Merry Xmas',
                icons: ['❄️', '🎄', '🎅', '🎁', '⛄', '🦌']
            }
        };

        // 在首部插入Button
        $(".btn-box").prepend(`
            <button id="zy_hpx_button" type="button" class="mtd-btn mtd-btn-primary ${festival}">
                ${festival ? '<div class="festival-sparkle"></div>' : ''}
                <span>
                    <div class="mtd-button-content">
                        <span class="mtdicon mtdicon-fast-forward"></span>
                        <span>Go to HyperloopX</span>
                        ${festival ? `<span style="margin-left: 4px">${festivalConfig[festival].icon}</span>` : ''}
                    </div>
                </span>
            </button>
        `);

        $("#zy_hpx_button").click(function(){
            // 点击效果
            if (festival) {
                const config = festivalConfig[festival];
                const icon = config.icons[Math.floor(Math.random() * config.icons.length)];
                const $icon = $(`<span class="festival-icon">${icon}</span>`);
                $icon.css({
                    left: '50%',
                    top: '50%',
                    transform: 'translate(-50%, -50%) scale(3)',
                    opacity: 0
                });
                $(this).append($icon);
                setTimeout(() => $icon.remove(), 500);
            }

            // 打开窗口
            window.open(project);
        });
    }

    // 统一的按钮移除函数
    function removeButton() {
        $("#zy_hpx_button").remove();
    }

    // 查询git地址
    function getGitAddress(callback) {
        var str = 'dev.sankuai.com/code/repo-detail';
        var index = window.location.toString().indexOf(str);
        var reset = window.location.toString().substring(index + str.length);
        var components = reset.split('/');

        if (components.length >= 3) {
            var url = 'https://dev.sankuai.com/rest/api/2.0/projects/' + components[1] + '/repos/' + components[2];
            $.get(url, {}, function(data){
                var git = '';
                for (let i = 0; i < data.links.clone.length; i++) {
                    let item = data.links.clone[i];
                    if (item.name === 'ssh') {
                        git = item.href;
                        break;
                    }
                }
                callback(git);
            });
        }
        return '';
    }

    // 获取当前节日
    function getFestival() {
        const date = new Date();
        const month = date.getMonth() + 1;
        const day = date.getDate();

        // 农历新年判断(这里使用简化判断,实际应该使用农历计算)
        if (month === 1 && day >= 20 || month === 2 && day <= 20) {
            return 'spring-festival';
        }

        // 元宵节
        if (month === 2 && day >= 24 && day <= 26) {
            return 'lantern-festival';
        }

        // 万圣节
        if (month === 10 && day >= 29 || month === 11 && day <= 2) {
            return 'halloween';
        }

        // 圣诞节
        if (month === 12 && day >= 20 && day <= 26) {
            return 'christmas';
        }

        return '';
    }

    // log
    function logger(log) {
        console.log("[go to HPX]", log);
    }
})();