GitHub Repo Tree Generator

Generate, filter, and share a clean directory tree for any GitHub repo

目前為 2025-08-01 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         GitHub Repo Tree Generator
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Generate, filter, and share a clean directory tree for any GitHub repo
// @author       Azad-sl
// @homepage     https://github.com/Azad-sl/GitTree
// @license      MIT
// @match        https://github.com/*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        GM_download
// @require      https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// ==/UserScript==

(function() {
    'use strict';

    // 创建并添加样式
    GM_addStyle(`
        /* 弹窗样式 */
        .gittree-modal {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 9999;
        }

        .gittree-modal-content {
            background-color: white;
            border-radius: 18px;
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
            width: 90%;
            max-width: 800px;
            max-height: 90vh;
            overflow-y: auto;
            padding: 2rem;
            position: relative;
        }

        .gittree-close-btn {
            position: absolute;
            top: 1rem;
            right: 1rem;
            background: none;
            border: none;
            font-size: 1.5rem;
            cursor: pointer;
            color: #6e6e73;
        }

        .gittree-close-btn:hover {
            color: #1d1d1f;
        }

        /* GitHub页面上的按钮样式 */
        .gittree-btn {
            background-color: #238636;
            color: white;
            border: none;
            border-radius: 6px;
            padding: 5px 12px;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
            margin-left: 8px;
        }

        .gittree-btn:hover {
            background-color: #2ea043;
        }

        /* GitTree应用样式 */
        .gittree-container {
            box-sizing: border-box;
            position: relative;
            width: 100%;
            max-width: 800px;
            background-color: #fff;
            border: 1px solid #d2d2d7;
            border-radius: 18px;
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.05);
            padding: 2rem 2.5rem 2.5rem;
        }

        .gittree-container h1 {
            text-align: center;
            font-size: 2rem;
            font-weight: 600;
            margin-top: 0;
            margin-bottom: 0.5rem;
        }

        .gittree-container .subtitle {
            text-align: center;
            font-size: 1rem;
            color: #6e6e73;
            margin-bottom: 2rem;
        }

        .gittree-container pre#gittree-result {
            font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
            font-size: 0.9rem;
            color: #1d1d1f;
            white-space: pre;
            padding: 1.5rem;
            margin: 0;
            max-height: 50vh;
            overflow: auto;
            line-height: 1.6;
            background-color: #fff;
        }

        .gittree-container pre#gittree-result.error {
            color: #d73a49;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
            white-space: normal;
        }

        .gittree-container .input-wrapper {
            display: flex;
            gap: 1rem;
            margin-bottom: 1rem;
        }

        .gittree-container input[type=text],
        .gittree-container input[type=number] {
            padding: 0.8rem 1rem;
            font-size: 1rem;
            border: 1px solid #d2d2d7;
            border-radius: 10px;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
            background-color: #fff;
            color: #1d1d1f;
            transition: border-color 0.2s, box-shadow 0.2s;
        }

        .gittree-container input[type=text] {
            flex-grow: 1;
        }

        .gittree-container input[type=text]:focus,
        .gittree-container input[type=number]:focus {
            border-color: #007aff;
            box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.2);
            outline: 0;
        }

        .gittree-container button#gittree-generateBtn {
            padding: 0.8rem 1.5rem;
            font-size: 1rem;
            font-weight: 600;
            color: #fff;
            background-color: #007aff;
            border: none;
            border-radius: 10px;
            cursor: pointer;
            transition: background-color 0.2s, transform 0.1s;
            display: flex;
            align-items: center;
            justify-content: center;
            white-space: nowrap;
        }

        .gittree-container button#gittree-generateBtn:hover {
            background-color: #0071e3;
        }

        .gittree-container button#gittree-generateBtn:active {
            transform: scale(0.98);
        }

        .gittree-container button#gittree-generateBtn:disabled {
            background-color: #a0a0a0;
            cursor: not-allowed;
            opacity: 0.8;
        }

        .gittree-container .spinner {
            border: 2px solid rgba(255, 255, 255, 0.2);
            border-radius: 50%;
            border-top-color: #fff;
            width: 16px;
            height: 16px;
            animation: spin 1s linear infinite;
            margin-right: 0.5rem;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .gittree-container .advanced-options {
            margin-top: 1rem;
        }

        .gittree-container .advanced-options summary {
            cursor: pointer;
            color: #6e6e73;
            font-size: 0.9rem;
            font-weight: 500;
            list-style-position: expert;
            display: inline-block;
            padding: 0.25rem 0.5rem;
            border-radius: 6px;
        }

        .gittree-container .advanced-options summary:hover {
            background-color: #f0f0f0;
        }

        .gittree-container .advanced-options-grid {
            display: grid;
            grid-template-columns: 1fr auto;
            gap: 1rem;
            margin-top: 0.75rem;
            align-items: center;
        }

        .gittree-container .advanced-options label {
            color: #6e6e73;
            font-size: 0.9rem;
        }

        .gittree-container input#gittree-depthInput {
            width: 70px;
            text-align: center;
        }

        .gittree-container input#gittree-excludeInput {
            width: 100%;
            box-sizing: border-box;
        }

        .gittree-container .result-wrapper {
            margin-top: 2rem;
            border: 1px solid #d2d2d7;
            border-radius: 12px;
            overflow: hidden;
        }

        .gittree-container .result-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0.5rem 0.75rem;
            background-color: #f0f0f0;
            border-bottom: 1px solid #d2d2d7;
        }

        .gittree-container .header-group {
            display: flex;
            align-items: center;
            gap: 0.5rem;
        }

        .gittree-container .header-group button {
            background: none;
            border: none;
            padding: 0.3rem 0.5rem;
            cursor: pointer;
            color: #6e6e73;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
            font-size: 0.8rem;
            display: flex;
            align-items: center;
            gap: 0.3rem;
            border-radius: 6px;
            transition: background-color 0.2s, color 0.2s;
        }

        .gittree-container .header-group button:hover {
            background-color: #e0e0e0;
            color: #1d1d1f;
        }

        .gittree-container .header-group button svg {
            width: 18px;
            height: 18px;
            stroke-width: 1.8;
        }

        .gittree-container .view-modes button.active {
            color: #007aff;
            background-color: rgba(0, 122, 255, 0.15);
        }

        .gittree-container .header-group button:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }

        .gittree-container #gittree-copyBtn .copy-icon {
            width: 1em;
            height: 1em;
            stroke-width: 2;
            vertical-align: middle;
        }

        /* macOS风格图片容器样式 */
        #gittree-image-card-container {
            position: absolute;
            left: -9999px;
            top: -9999px;
            padding: 4rem;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }

        #gittree-image-card-container .macos-window {
            width: 800px;
            background-color: #2e2e2e;
            border-radius: 10px;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.4);
            padding-bottom: 20px;
            border: 1px solid #444;
        }

        #gittree-image-card-container .title-bar {
            height: 48px;
            display: flex;
            align-items: center;
            justify-content: center;
            position: relative;
            padding: 0 15px;
            border-bottom: 1px solid #404040;
        }

        #gittree-image-card-container .traffic-lights {
            position: absolute;
            left: 15px;
            display: flex;
            gap: 8px;
        }

        #gittree-image-card-container .light {
            width: 12px;
            height: 12px;
            border-radius: 50%;
        }

        #gittree-image-card-container .light.red {
            background-color: #ff5f56;
        }

        #gittree-image-card-container .light.yellow {
            background-color: #ffbd2e;
        }

        #gittree-image-card-container .light.green {
            background-color: #27c93f;
        }

        #gittree-image-card-container .title-text {
            color: #b0b0b0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
            font-size: 14px;
            font-weight: 500;
        }

        #gittree-image-card-container .card-content {
            font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
            font-size: 14px;
            color: #e0e0e0;
            white-space: pre;
            padding: 20px;
            max-height: 80vh;
            overflow: auto;
            line-height: 1.6;
        }

        @media (max-width: 768px) {
            .gittree-container {
                padding: 1.5rem;
                border: none;
                border-radius: 0;
                box-shadow: none;
                background-color: #f3f3f8;
            }

            .gittree-container h1 {
                font-size: 1.5rem;
            }

            .gittree-container .subtitle {
                font-size: 0.9rem;
                margin-bottom: 2rem;
            }

            .gittree-container .input-wrapper {
                flex-direction: column;
            }

            .gittree-container button#gittree-generateBtn {
                padding: 1rem;
            }

            .gittree-container .advanced-options-grid {
                grid-template-columns: 1fr;
                gap: 0.5rem;
            }

            .gittree-container .advanced-options-grid label[for="gittree-depthInput"] {
                margin-top: 0.5rem;
            }

            .gittree-container .result-header {
                flex-direction: column;
                align-items: stretch;
                gap: 0.75rem;
            }

            .gittree-container .header-group {
                justify-content: space-around;
            }

            .gittree-container .export-tools button span {
                display: none;
            }

            .gittree-container .export-tools button {
                padding: 0.5rem;
            }
        }
.gittree-container footer {
    text-align: center;
    margin-top: 3rem;
    padding-bottom: 1rem;
    color: #6e6e73;
    font-size: 0.875rem;
}

.gittree-container footer a {
    color: #1d1d1f;
    text-decoration: none;
    font-weight: 500;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
}

.gittree-container footer a:hover {
    color: #007aff;
    text-decoration: underline;
}

.gittree-container footer .github-icon {
    width: 1em;
    height: 1em;
    fill: currentColor;
}

.gittree-container footer p {
    margin: 0.5rem 0 0;
}
    `);

    // 创建按钮并添加到GitHub页面
    function addGitTreeButton() {
        // 查找GitHub页面上的操作按钮区域
        const actionsContainer = document.querySelector('.pagehead-actions');
        if (!actionsContainer) return;

        // 创建GitTree按钮
        const gitTreeBtn = document.createElement('button');
        gitTreeBtn.className = 'gittree-btn';
        gitTreeBtn.textContent = 'Generate Tree';
        gitTreeBtn.addEventListener('click', showGitTreeModal);

        // 添加按钮到页面
        actionsContainer.appendChild(gitTreeBtn);
    }

    // 显示GitTree模态框
    function showGitTreeModal() {
        // 获取当前GitHub仓库的URL
        const currentRepoUrl = window.location.href;

        // 创建模态框
        const modal = document.createElement('div');
        modal.className = 'gittree-modal';

        // 创建模态框内容
        const modalContent = document.createElement('div');
        modalContent.className = 'gittree-modal-content';

        // 创建关闭按钮
        const closeBtn = document.createElement('button');
        closeBtn.className = 'gittree-close-btn';
        closeBtn.innerHTML = '×';
        closeBtn.addEventListener('click', () => {
            document.body.removeChild(modal);
        });

        // 创建GitTree应用容器
        const gitTreeContainer = document.createElement('div');
        gitTreeContainer.className = 'gittree-container';
        gitTreeContainer.innerHTML = `
            <h1>GitHub Repo Tree Generator</h1>
            <p class="subtitle">Generate, filter, and share a clean directory tree for any public GitHub repo.</p>
            <div class="input-wrapper">
              <input type="text" id="gittree-repoUrl" placeholder="e.g., https://github.com/Azad-sl/GitTree" value="${currentRepoUrl}">
              <button id="gittree-generateBtn"><span id="gittree-btn-text">Generate</span></button>
            </div>
            <details class="advanced-options">
              <summary>Advanced Options</summary>
              <div class="advanced-options-grid">
                <label for="gittree-excludeInput">Exclude items (comma-separated, \`*\` supported):</label>
                <label for="gittree-depthInput">Max Depth:</label>
                <input type="text" id="gittree-excludeInput" placeholder="e.g., node_modules, dist, *.log">
                <input type="number" id="gittree-depthInput" min="1" placeholder="∞">
              </div>
            </details>
            <div class="result-wrapper">
              <div class="result-header">
                <div class="header-group view-modes">
                  <button id="gittree-view-all" class="active" title="Full View"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M10 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4M14 9h7m-7 6h7M4 9h1m-1 6h1"/></svg> <span>All</span></button>
                  <button id="gittree-view-folders" title="Folders Only"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg> <span>Folders</span></button>
                </div>
                <div class="header-group export-tools">
                  <button id="gittree-downloadBatBtn" title="Generate .bat script (Windows)" disabled><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path><line x1="12" y1="11" x2="12" y2="17"></line><line x1="9" y1="14" x2="15" y2="14"></line></svg> <span id="gittree-downloadBatText">.bat</span></button>
                  <button id="gittree-downloadShBtn" title="Generate .sh script (macOS/Linux)" disabled><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" y1="19" x2="20" y2="19"></line></svg> <span>.sh</span></button>
                  <button id="gittree-copyMdBtn" title="Copy as Markdown" disabled><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.72"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.72-1.72"/></svg> <span id="gittree-copyMdText">Markdown</span></button>
                  <button id="gittree-exportBtn" title="Export as Image" disabled><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> <span id="gittree-exportText">Export</span></button>
                  <button id="gittree-copyBtn" title="Copy to Clipboard" disabled><svg class="copy-icon" id="gittree-copy-icon-clipboard" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg><svg class="copy-icon" id="gittree-copy-icon-check" style="display:none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"></path></svg> <span id="gittree-copy-text">Copy</span></button>
                </div>
              </div>
              <pre id="gittree-result">Generated tree will be displayed here...</pre>
            </div>
<footer>
    <p>Open-sourced on <a href="https://github.com/Azad-sl/GitTree" target="_blank" rel="noopener noreferrer"><svg class="github-icon" viewBox="0 0 16 16" version="1.1" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path></svg> Azad-sl / GitTree</a></p>
</footer>
        `;

        // 创建隐藏的图片容器
        const imageCardContainer = document.createElement('div');
        imageCardContainer.id = 'gittree-image-card-container';
        imageCardContainer.innerHTML = `
            <div class="macos-window">
                <div class="title-bar">
                    <div class="traffic-lights">
                        <div class="light red"></div>
                        <div class="light yellow"></div>
                        <div class="light green"></div>
                    </div>
                    <div class="title-text"></div>
                </div>
                <pre class="card-content"></pre>
            </div>
        `;

        // 组装模态框
        modalContent.appendChild(closeBtn);
        modalContent.appendChild(gitTreeContainer);
        modalContent.appendChild(imageCardContainer);
        modal.appendChild(modalContent);

        // 添加到页面
        document.body.appendChild(modal);

        // 点击模态框外部关闭模态框
        modal.addEventListener('click', (e) => {
            if (e.target === modal) {
                document.body.removeChild(modal);
            }
        });

        // 初始化GitTree应用
        initGitTreeApp();
    }

    // 初始化GitTree应用
    function initGitTreeApp() {
        const translations = {
            en: {
                pageTitle: "GitTree - GitHub Repo Tree Generator",
                mainTitle: "GitHub Repo Tree Generator",
                subtitle: "Generate, filter, and share a clean directory tree for any public GitHub repo.",
                repoUrl: "e.g., https://github.com/Azad-sl/GitTree",
                generate: "Generate",
                advancedOptions: "Advanced Options",
                excludeLabel: "Exclude items (comma-separated, `*` supported):",
                depthLabel: "Max Depth:",
                excludeInput: "e.g., node_modules, dist, *.log",
                resultPlaceholder: "Generated tree will be displayed here...",
                viewAll: "Full View",
                viewFolders: "Folders Only",
                downloadBat: "Generate .bat script (Windows)",
                downloadSh: "Generate .sh script (macOS/Linux)",
                copyMd: "Copy as Markdown",
                export: "Export as Image",
                copy: "Copy to Clipboard",
                exportBtn: "Export",
                copyBtn: "Copy",
                copied: "Copied!",
                exporting: "Exporting...",
                errorPrefix: "❌ Error: ",
                enterURL: "Please enter a GitHub repository URL.",
                invalidURL: "Invalid GitHub repository URL. Please check the format.",
                repoNotFound: "Repository or branch not found. Please check the URL.",
                apiLimit: "API rate limit exceeded. Please wait a moment and try again.",
                fetchFailed: "Failed to fetch tree data: ",
                noContent: "No items to display. Please check your filter conditions.",
                copyFailed: "Copy failed. Your browser might not support it or permission denied.",
                exportFailed: "Exporting image failed. Check console for details.",
                noBatContent: "No content to create."
            }
        };

        let currentLang = 'en';
        const resultDiv = document.getElementById('gittree-result');
        const repoUrlInput = document.getElementById('gittree-repoUrl');
        const generateBtn = document.getElementById('gittree-generateBtn');
        const excludeInput = document.getElementById('gittree-excludeInput');
        const depthInput = document.getElementById('gittree-depthInput');
        const copyBtn = document.getElementById('gittree-copyBtn');
        const copyMdBtn = document.getElementById('gittree-copyMdBtn');
        const exportBtn = document.getElementById('gittree-exportBtn');
        const downloadBatBtn = document.getElementById('gittree-downloadBatBtn');
        const downloadShBtn = document.getElementById('gittree-downloadShBtn');
        const viewAllBtn = document.getElementById('gittree-view-all');
        const viewFoldersBtn = document.getElementById('gittree-view-folders');
        const btnText = document.getElementById('gittree-btn-text');
        const copyText = document.getElementById('gittree-copy-text');
        const copyMdText = document.getElementById('gittree-copyMdText');
        const exportText = document.getElementById('gittree-exportText');
        const iconClipboard = document.getElementById('gittree-copy-icon-clipboard');
        const iconCheck = document.getElementById('gittree-copy-icon-check');
        let fullTreeData = [];
        let currentRepoInfo = null;
        let hasResultForCopy = false;
        let currentViewMode = "all";

        function setShareButtonsState(enabled) {
            hasResultForCopy = enabled;
            [copyBtn, exportBtn, copyMdBtn, downloadBatBtn, downloadShBtn].forEach(btn => btn.disabled = !enabled);
        }

        function generateShContent() {
            if (!currentRepoInfo || fullTreeData.length === 0) return '';
            const rootDir = currentRepoInfo.repo;
            const filteredItems = filterTree(fullTreeData, excludeInput.value.split(',').map(p => p.trim()).filter(Boolean), parseInt(depthInput.value, 10) || Infinity, currentViewMode);

            const commands = [
                '#!/bin/bash',
                '# Generated by GitTree',
                '',
                `echo "Creating directory structure for '${rootDir}'..."`,
                `mkdir -p "${rootDir}"`,
                `cd "${rootDir}"`,
                ''
            ];
            const dirsToCreate = new Set();
            const filesToCreate = new Set();
            for (const item of filteredItems) {
                if (item.type === 'tree') {
                    dirsToCreate.add(item.path);
                } else if (item.type === 'blob') {
                    const lastSlash = item.path.lastIndexOf('/');
                    if (lastSlash > -1) {
                       dirsToCreate.add(item.path.substring(0, lastSlash));
                    }
                    filesToCreate.add(item.path);
                }
            }

            if (dirsToCreate.size > 0) {
                commands.push('# Creating directories...');
                dirsToCreate.forEach(dir => commands.push(`mkdir -p "${dir}"`));
                commands.push('');
            }
            if (filesToCreate.size > 0) {
                commands.push('# Creating files...');
                filesToCreate.forEach(file => commands.push(`touch "${file}"`));
                commands.push('');
            }
            commands.push(`echo "Directory structure for '${rootDir}' created successfully!"`);
            return commands.join('\n'); // Use LF line endings for shell scripts
        }

        function generateBatContent() {
            if (!currentRepoInfo || fullTreeData.length === 0) return '';
            const rootDir = currentRepoInfo.repo;
            const filteredItems = filterTree(fullTreeData, excludeInput.value.split(',').map(p => p.trim()).filter(Boolean), parseInt(depthInput.value, 10) || Infinity, currentViewMode);
            const commands = ['@echo off', 'chcp 65001 > nul', `echo Creating directory structure for ${rootDir}...`, `if not exist "${rootDir}" ( md "${rootDir}" )`, `cd "${rootDir}"`, ''];
            filteredItems.sort((a,b) => a.path.localeCompare(b.path));
            for (const item of filteredItems) {
                const windowsPath = item.path.replace(/\//g, '\\');
                if (item.type === 'tree') {
                    commands.push(`if not exist "${windowsPath}" ( md "${windowsPath}" )`);
                } else if (item.type === 'blob') {
                    const lastBackslash = windowsPath.lastIndexOf('\\');
                    if (lastBackslash > -1) {
                        const dir = windowsPath.substring(0, lastBackslash);
                        commands.push(`if not exist "${dir}" ( md "${dir}" )`);
                    }
                    commands.push(`if not exist "${windowsPath}" ( type NUL > "${windowsPath}" )`);
                }
            }
            commands.push('', 'echo.', `echo Directory structure for "${rootDir}" created successfully!`, 'pause');
            return commands.join('\r\n');
        }

        function parseGitHubUrl(url) {
            const match = /https?:\/\/github\.com\/([^\/]+)\/([^\/\s]+)/.exec(url);
            return match ? { owner: match[1], repo: match[2].replace(".git", ""), branch: "main" } : null;
        }

        function showError(messageKey) {
            let message = translations[currentLang][messageKey] || messageKey;
            if (messageKey.startsWith('fetchFailed')) {
                message = translations[currentLang].fetchFailed + messageKey.replace('fetchFailed', '');
            }
            resultDiv.textContent = translations[currentLang].errorPrefix + message;
            resultDiv.className = "error";
        }

        function showSuccess(text) {
            resultDiv.textContent = text;
            resultDiv.className = "";
        }

        function setLoading(isLoading) {
            generateBtn.disabled = isLoading;
            if (isLoading) {
                btnText.textContent = "";
                const spinner = document.createElement("span");
                spinner.className = "spinner";
                generateBtn.prepend(spinner);
            } else {
                btnText.textContent = translations[currentLang].generate;
                const spinner = generateBtn.querySelector(".spinner");
                if (spinner) spinner.remove();
            }
        }

        async function fetchTreeData(owner, repo, branch) {
            let response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`);
            if (response.status === 404) {
                console.log("Branch 'main' not found, trying 'master'");
                response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/master?recursive=1`);
            }
            if (!response.ok) {
                if (response.status === 404) throw new Error('repoNotFound');
                if (response.status === 403) throw new Error('apiLimit');
                throw new Error('fetchFailed' + response.statusText);
            }
            const data = await response.json();
            if (data.truncated) {
                alert("Warning: The file list is too large and has been truncated by the GitHub API. The generated tree may be incomplete.");
            }
            return data.tree;
        }

        function filterTree(tree, excludePatterns, maxDepth, viewMode) {
            return tree.filter(item => {
                const depth = item.path.split('/').length;
                if (depth > maxDepth) return false;
                const isExcluded = excludePatterns.some(pattern => {
                    if (pattern.startsWith('*.')) {
                        return item.path.endsWith(pattern.substring(1));
                    }
                    return item.path.split('/').includes(pattern);
                });
                if (isExcluded) return false;
                if (viewMode === 'folders' && item.type !== 'tree') return false;
                return true;
            });
        }

        function buildTreeString(repoName, tree, maxDepth) {
            if (!tree || tree.length === 0) return translations[currentLang].noContent;
            const root = {};
            tree.forEach(item => {
                let path = item.path.split('/');
                let currentLevel = root;
                for (let i = 0; i < path.length; i++) {
                    let part = path[i];
                    if (!currentLevel[part]) {
                        currentLevel[part] = (i === path.length - 1 && item.type === 'blob') ? null : {};
                    }
                    currentLevel = currentLevel[part];
                }
            });
            let treeString = `${repoName}/\n`;
            const build = (node, prefix = "", level = 1) => {
                const entries = Object.keys(node).sort((a, b) => {
                    const aIsDir = node[a] !== null;
                    const bIsDir = node[b] !== null;
                    if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
                    return a.localeCompare(b);
                });
                entries.forEach((entry, index) => {
                    const isLast = index === entries.length - 1;
                    const connector = isLast ? "└── " : "├── ";
                    treeString += `${prefix}${connector}${entry}\n`;
                    const child = node[entry];
                    if (child !== null && level < maxDepth) {
                        const newPrefix = prefix + (isLast ? "    " : "│   ");
                        build(child, newPrefix, level + 1);
                    }
                });
            };
            build(root);
            return treeString.trim();
        }

        function renderTree() {
            if (!currentRepoInfo) return;
            const excludePatterns = excludeInput.value.split(',').map(p => p.trim()).filter(Boolean);
            const maxDepth = parseInt(depthInput.value, 10) || Infinity;
            const filteredTree = filterTree(fullTreeData, excludePatterns, maxDepth, currentViewMode);
            const treeString = buildTreeString(currentRepoInfo.repo, filteredTree, maxDepth);
            showSuccess(treeString);
            if (filteredTree.length > 0) {
                setShareButtonsState(true);
            } else {
                setShareButtonsState(false);
            }
        }

        function setViewMode(mode) {
            if (currentViewMode === mode) return;
            currentViewMode = mode;
            viewAllBtn.classList.toggle("active", mode === "all");
            viewFoldersBtn.classList.toggle("active", mode === "folders");
            if (hasResultForCopy) {
                renderTree();
            }
        }

        async function handleGenerationWrapper() {
            setShareButtonsState(false);
            const url = repoUrlInput.value.trim();
            if (!url) {
                showError('enterURL');
                return;
            }
            currentRepoInfo = parseGitHubUrl(url);
            if (!currentRepoInfo) {
                showError('invalidURL');
                return;
            }
            setLoading(true);
            try {
                const rawTree = await fetchTreeData(currentRepoInfo.owner, currentRepoInfo.repo, currentRepoInfo.branch);
                fullTreeData = rawTree;
                renderTree();
            } catch (error) {
                showError(error.message);
                fullTreeData = [];
                currentRepoInfo = null;
            } finally {
                setLoading(false);
            }
        }

        // 事件监听器
        generateBtn.addEventListener("click", handleGenerationWrapper);
        repoUrlInput.addEventListener("keydown", e => { if (e.key === "Enter") handleGenerationWrapper(); });
        viewAllBtn.addEventListener("click", () => setViewMode("all"));
        viewFoldersBtn.addEventListener("click", () => setViewMode("folders"));

        copyBtn.addEventListener("click", () => {
            if (!hasResultForCopy) return;
            GM_setClipboard(resultDiv.innerText).then(() => {
                copyText.textContent = translations[currentLang].copied;
                iconClipboard.style.display = "none";
                iconCheck.style.display = "inline-block";
                setTimeout(() => {
                    copyText.textContent = translations[currentLang].copyBtn;
                    iconClipboard.style.display = "inline-block";
                    iconCheck.style.display = "none";
                }, 2000);
            }).catch(e => {
                console.error("Copy failed:", e);
                alert(translations[currentLang].copyFailed);
            });
        });

        copyMdBtn.addEventListener('click', () => {
            if (!hasResultForCopy) return;
            const content = "```\n" + resultDiv.innerText + "\n```";
            GM_setClipboard(content).then(() => {
                const originalText = copyMdText.textContent;
                copyMdText.textContent = translations[currentLang].copied;
                setTimeout(() => {
                    copyMdText.textContent = originalText;
                }, 2000);
            }).catch(e => {
                console.error("Copy Markdown failed:", e);
                alert(translations[currentLang].copyFailed);
            });
        });

        exportBtn.addEventListener('click', async () => {
            if (!hasResultForCopy) return;
            exportText.textContent = translations[currentLang].exporting;
            exportBtn.disabled = true;
            const imageCardContainer = document.getElementById("gittree-image-card-container");
            const titleText = imageCardContainer.querySelector(".title-text");
            const cardContent = imageCardContainer.querySelector(".card-content");
            try {
                cardContent.style.maxHeight = "none";
                titleText.textContent = `${currentRepoInfo.owner}/${currentRepoInfo.repo}`;
                cardContent.textContent = resultDiv.textContent;
                const canvas = await html2canvas(imageCardContainer, {
                    useCORS: true,
                    backgroundColor: null
                });
                const link = document.createElement("a");
                link.download = `${currentRepoInfo.repo}-tree.png`;
                link.href = canvas.toDataURL("image/png");
                link.click();
            } catch (error) {
                console.error("Exporting image failed:", error);
                alert(translations[currentLang].exportFailed);
            } finally {
                cardContent.style.maxHeight = "";
                exportText.textContent = translations[currentLang].exportBtn;
                exportBtn.disabled = false;
            }
        });

        downloadBatBtn.addEventListener('click', () => {
            if (!hasResultForCopy || !currentRepoInfo) return;
            const content = generateBatContent();
            if (!content) {
                alert(translations[currentLang].noBatContent);
                return;
            }
            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = `create_${currentRepoInfo.repo}_structure.bat`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
        });

        downloadShBtn.addEventListener('click', () => {
            if (!hasResultForCopy || !currentRepoInfo) return;
            const content = generateShContent();
            if (!content) {
                alert(translations[currentLang].noBatContent);
                return;
            }
            const blob = new Blob([content], { type: 'application/x-shellscript' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = `create_${currentRepoInfo.repo}_structure.sh`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            URL.revokeObjectURL(url);
        });
    }

    // 等待页面加载完成
    window.addEventListener('load', () => {
        // 添加GitTree按钮
        addGitTreeButton();
    });
})();