LeetCode题单助手

LeetCode中英文站点增强工具,智能转换链接并显示做题状态

// ==UserScript==
// @name         LeetCode题单助手
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  LeetCode中英文站点增强工具,智能转换链接并显示做题状态
// @author       You
// @match        https://leetcode.cn/*
// @match        https://leetcode.com/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @connect      leetcode.com
// @connect      leetcode.cn
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        STORAGE_KEY: 'leetcode_progress_data',
        LAST_SYNC_KEY: 'leetcode_last_sync',
        SETTINGS_KEY: 'leetcode_settings',
        CACHE_DURATION: 2 * 60 * 60 * 1000,
        VERSION: '1.1'
    };

    const DEFAULT_SETTINGS = {
        autoSync: true,
        showDifficulty: true,
        showProgress: true,
        compactMode: false,
        hideCompleted: false,
        customCSS: ''
    };

    const DIFFICULTY_MAP = {
        1: { text: 'Easy', color: '#00b8a3' },
        2: { text: 'Med.', color: '#ffc01e' },
        3: { text: 'Hard', color: '#ff375f' }
    };

    const STATUS_MAP = {
        'ac': { emoji: '✅', text: 'Solved', color: '#52c41a', bgColor: '#f6ffed' },
        'notac': { emoji: '❌', text: 'Attempted', color: '#ff4d4f', bgColor: '#fff2f0' },
        null: { emoji: '⭕', text: 'Not Attempted', color: '#8c8c8c', bgColor: '#fafafa' }
    };

    console.log('🚀 LeetCode题单助手启动,版本:', CONFIG.VERSION);

    GM_addStyle(`
        .lc-converted {
            padding-left: 8px !important;
            transition: all 0.3s ease;
        }

        .lc-converted:hover {
            background-color: #f0f8ff !important;
            transform: translateX(2px);
        }

        .lc-status-container {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            margin-right: 8px;
        }

        .lc-status-badge {
            display: inline-flex;
            align-items: center;
            padding: 2px 6px;
            border-radius: 12px;
            font-size: 11px;
            font-weight: 500;
            border: 1px solid #d9d9d9;
            transition: transform 0.2s ease;
        }

        .lc-status-badge:hover {
            transform: scale(1.1);
        }

        .lc-difficulty-badge {
            display: inline-flex;
            align-items: center;
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 11px;
            font-weight: bold;
            color: white;
            min-width: 32px;
            justify-content: center;
        }

        .lc-control-panel {
            position: fixed;
            top: 80px;
            right: 20px;
            z-index: 9999;
            background: #ffffff;
            color: #333333;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            overflow: hidden;
            min-width: 200px;
            transition: all 0.3s ease;
        }

        [data-theme="dark"] .lc-control-panel,
        .dark .lc-control-panel,
        body[class*="dark"] .lc-control-panel {
            background: #1f1f1f !important;
            color: #ffffff !important;
            border: 1px solid #333;
        }

        .lc-panel-header {
            background: linear-gradient(135deg, #1890ff, #40a9ff);
            color: white;
            padding: 12px 16px;
            font-weight: bold;
            font-size: 14px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            cursor: pointer;
            user-select: none;
        }

        .lc-panel-toggle {
            font-size: 12px;
            opacity: 0.8;
            transition: transform 0.3s ease;
        }

        .lc-panel-toggle.collapsed {
            transform: rotate(180deg);
        }

        .lc-panel-content {
            padding: 12px;
            transition: all 0.3s ease;
            overflow: hidden;
        }

        .lc-control-panel.collapsed .lc-panel-content {
            max-height: 0;
            padding: 0 12px;
            opacity: 0;
        }

        .lc-control-panel:not(.collapsed) .lc-panel-content {
            max-height: 500px;
            opacity: 1;
        }

        .lc-button {
            width: 100%;
            padding: 8px 12px;
            margin-bottom: 8px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            gap: 6px;
        }

        .lc-button-primary {
            background: #1890ff;
            color: white;
        }

        .lc-button-primary:hover {
            background: #40a9ff;
        }

        .lc-button-secondary {
            background: #f0f0f0;
            color: #333;
        }

        .lc-button-secondary:hover {
            background: #e0e0e0;
        }

        [data-theme="dark"] .lc-button-secondary,
        .dark .lc-button-secondary,
        body[class*="dark"] .lc-button-secondary {
            background: #333 !important;
            color: #fff !important;
        }

        [data-theme="dark"] .lc-button-secondary:hover,
        .dark .lc-button-secondary:hover,
        body[class*="dark"] .lc-button-secondary:hover {
            background: #444 !important;
        }

        .lc-stats {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 6px;
            margin: 8px 0;
            font-size: 10px;
        }

        .lc-stat-item {
            text-align: center;
            padding: 4px;
            border-radius: 4px;
            background: #f9f9f9;
            color: #333;
        }

        [data-theme="dark"] .lc-stat-item,
        .dark .lc-stat-item,
        body[class*="dark"] .lc-stat-item {
            background: #333 !important;
            color: #fff !important;
        }

        .lc-progress-bar {
            width: 100%;
            height: 6px;
            background: #f0f0f0;
            border-radius: 3px;
            overflow: hidden;
            margin: 8px 0;
        }

        .lc-progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #52c41a, #73d13d);
            transition: width 0.3s ease;
        }

        .lc-settings {
            border-top: 1px solid #f0f0f0;
            margin-top: 8px;
            padding-top: 8px;
        }

        [data-theme="dark"] .lc-settings,
        .dark .lc-settings,
        body[class*="dark"] .lc-settings {
            border-top-color: #444 !important;
        }

        .lc-setting-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 6px;
            font-size: 11px;
            color: inherit;
        }

        .lc-switch {
            position: relative;
            width: 32px;
            height: 18px;
            background: #ccc;
            border-radius: 9px;
            cursor: pointer;
            transition: background 0.2s;
        }

        .lc-switch.active {
            background: #1890ff;
        }

        .lc-switch::after {
            content: '';
            position: absolute;
            top: 2px;
            left: 2px;
            width: 14px;
            height: 14px;
            background: white;
            border-radius: 50%;
            transition: transform 0.2s;
        }

        .lc-switch.active::after {
            transform: translateX(14px);
        }

        .lc-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            background: white;
            border-left: 4px solid #52c41a;
            padding: 16px 20px;
            border-radius: 4px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            max-width: 300px;
            animation: slideIn 0.3s ease;
        }

        @keyframes slideIn {
            from { transform: translateX(100%); opacity: 0; }
            to { transform: translateX(0); opacity: 1; }
        }

        .lc-hidden {
            opacity: 0.3;
            filter: grayscale(50%);
        }

        .lc-jump-button {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 4px 8px;
            margin-left: 12px;
            background: linear-gradient(135deg, #1890ff, #40a9ff);
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 12px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            text-decoration: none;
            box-shadow: 0 2px 4px rgba(24, 144, 255, 0.3);
        }

        .lc-jump-button:hover {
            background: linear-gradient(135deg, #40a9ff, #69c0ff);
            transform: translateY(-1px);
            box-shadow: 0 4px 8px rgba(24, 144, 255, 0.4);
            color: white;
            text-decoration: none;
        }

        .lc-jump-button:active {
            transform: translateY(0);
        }

        /* 新增的复制按钮样式 */
        .lc-copy-buttons-container {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            margin-left: 12px;
            flex-wrap: wrap;
        }

        .lc-copy-button {
            display: inline-flex;
            align-items: center;
            gap: 4px;
            padding: 6px 10px;
            background: linear-gradient(135deg, #52c41a, #73d13d);
            color: white;
            border: none;
            border-radius: 6px;
            font-size: 11px;
            font-weight: 500;
            cursor: pointer;
            transition: all 0.2s ease;
            box-shadow: 0 2px 4px rgba(82, 196, 26, 0.3);
            white-space: nowrap;
        }

        .lc-copy-button:hover {
            background: linear-gradient(135deg, #73d13d, #95de64);
            transform: translateY(-1px);
            box-shadow: 0 4px 8px rgba(82, 196, 26, 0.4);
        }

        .lc-copy-button:active {
            transform: translateY(0);
        }

        .lc-copy-button.copying {
            background: #faad14;
            transform: none;
        }

        .lc-copy-button.success {
            background: #52c41a;
            transform: none;
        }

        .lc-copy-button.error {
            background: #ff4d4f;
            transform: none;
        }

        /* 适配小屏幕 */
        @media (max-width: 768px) {
            .lc-copy-buttons-container {
                margin-left: 0;
                margin-top: 8px;
                width: 100%;
            }

            .lc-copy-button {
                flex: 1;
                justify-content: center;
                min-width: 0;
            }
        }
    `);

    class LeetCodeUltimate {
        constructor() {
            this.settings = { ...DEFAULT_SETTINGS, ...GM_getValue(CONFIG.SETTINGS_KEY, {}) };
            this.progressData = GM_getValue(CONFIG.STORAGE_KEY, {});
            this.isInternational = window.location.hostname === 'leetcode.com';
            this.isChinese = window.location.hostname === 'leetcode.cn';
            this.stats = { total: 0, solved: 0, attempted: 0 };

            this.init();
        }

        init() {
            if (this.isInternational) {
                this.handleInternationalSite();
            } else if (this.isChinese) {
                if (this.isProblemsDetailPage()) {
                    this.addJumpButton();
                    return;
                }
                this.handleChineseSite();
            }

            if (this.settings.customCSS) {
                GM_addStyle(this.settings.customCSS);
            }
        }

        isProblemsDetailPage() {
            const path = window.location.pathname;
            return /^\/problems\/[^\/]+\/(description|solutions|discuss|submissions|editorial)/.test(path) ||
                   /^\/problems\/[^\/]+\/$/.test(path);
        }

        addJumpButton() {
            setTimeout(() => {
                this.insertJumpButton();
            }, 1500);

            const observer = new MutationObserver(() => {
                if (!document.querySelector('.lc-jump-button')) {
                    setTimeout(() => this.insertJumpButton(), 500);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        insertJumpButton() {
            if (document.querySelector('.lc-jump-button')) {
                return;
            }

            const titleSelectors = [
                'h1[data-cy="question-title"]',
                '.question-title h1',
                '.question-content h1',
                '.css-v3d350',
                '[class*="title"]'
            ];

            let titleElement = null;
            for (const selector of titleSelectors) {
                try {
                    titleElement = document.querySelector(selector);
                    if (titleElement) break;
                } catch (e) {
                    continue;
                }
            }

            if (!titleElement) {
                const h1Elements = document.querySelectorAll('h1');
                for (const h1 of h1Elements) {
                    const text = h1.textContent.trim();
                    if (/^\d+\./.test(text) && text.length > 5) {
                        titleElement = h1;
                        break;
                    }
                }
            }

            if (!titleElement) {
                return;
            }

            const currentUrl = window.location.href;
            const targetUrl = this.getTargetUrl(currentUrl);

            if (!targetUrl) {
                return;
            }

            // 创建按钮容器
            const buttonsContainer = document.createElement('div');
            buttonsContainer.style.display = 'inline-flex';
            buttonsContainer.style.alignItems = 'center';
            buttonsContainer.style.gap = '8px';
            buttonsContainer.style.marginLeft = '12px';
            buttonsContainer.style.flexWrap = 'wrap';

            // 跳转按钮
            const jumpButton = document.createElement('a');
            jumpButton.className = 'lc-jump-button';
            jumpButton.href = targetUrl;
            jumpButton.target = '_blank';
            jumpButton.rel = 'noopener noreferrer';

            if (this.isChinese) {
                jumpButton.innerHTML = `
                    <span>🌍</span>
                    <span>English</span>
                `;
                jumpButton.title = '在LeetCode国际站打开 / Open on LeetCode.com';
            } else {
                jumpButton.innerHTML = `
                    <span>🇨🇳</span>
                    <span>中文</span>
                `;
                jumpButton.title = '在LeetCode中文站打开 / Open on LeetCode.cn';
            }

            buttonsContainer.appendChild(jumpButton);

            // 如果是国际站,添加复制按钮
            if (this.isInternational) {
                const copyButtonsContainer = this.createCopyButtons();
                buttonsContainer.appendChild(copyButtonsContainer);
            }

            titleElement.appendChild(buttonsContainer);
        }

        createCopyButtons() {
            const container = document.createElement('div');
            container.className = 'lc-copy-buttons-container';

            const buttons = [
                {
                    text: '📋 Title',
                    title: 'Copy problem title to clipboard',
                    action: () => this.copyTitle()
                },
                {
                    text: '📄 Problem',
                    title: 'Copy problem description to clipboard',
                    action: () => this.copyProblem()
                },
                {
                    text: '💻 Solution',
                    title: 'Copy my solution to clipboard',
                    action: () => this.copySolution()
                }
            ];

            buttons.forEach(buttonConfig => {
                const button = document.createElement('button');
                button.className = 'lc-copy-button';
                button.innerHTML = buttonConfig.text;
                button.title = buttonConfig.title;
                button.onclick = buttonConfig.action;
                container.appendChild(button);
            });

            return container;
        }

        async copyTitle() {
            const button = event.target.closest('.lc-copy-button');
            const originalText = button.innerHTML;

            try {
                button.classList.add('copying');
                button.innerHTML = '⏳ Copying...';

                let titleText = '';

                // 根据HTML结构,直接查找正确的标题元素
                const titleElement = document.querySelector('.text-title-large a');
                if (titleElement) {
                    titleText = titleElement.textContent.trim();
                }

                // 备用方案
                if (!titleText) {
                    const altSelectors = [
                        '.text-title-large',
                        '[class*="title-large"]',
                        'h1',
                        '.font-semibold a'
                    ];

                    for (const selector of altSelectors) {
                        const element = document.querySelector(selector);
                        if (element) {
                            let text = element.textContent.trim();
                            // 检查是否是题目标题格式
                            if (/^\d+\.\s*.+/.test(text) && text.length > 5) {
                                titleText = text;
                                break;
                            }
                        }
                    }
                }

                // 最后的备用方案:从URL提取
                if (!titleText) {
                    const urlMatch = window.location.pathname.match(/\/problems\/([^\/]+)/);
                    if (urlMatch) {
                        const slug = urlMatch[1];
                        // 从页面标题提取
                        const pageTitle = document.title;
                        const match = pageTitle.match(/^\d+\.\s*(.+?)\s*-\s*LeetCode/);
                        if (match) {
                            titleText = match[1]; // 只要题目名称,不要数字
                        } else {
                            // 从slug转换
                            titleText = slug.split('-').map(word =>
                                word.charAt(0).toUpperCase() + word.slice(1)
                            ).join(' ');
                        }
                    }
                }

                if (!titleText) {
                    throw new Error('Could not find problem title');
                }

                // 清理按钮文本和其他无关内容,同时移除题目编号
                titleText = titleText
                    .replace(/🌍\s*English|🇨🇳\s*中文|📋\s*Title|📄\s*Problem|💻\s*Solution/g, '')
                    .replace(/Solved\s*$/i, '')
                    .replace(/^\d+\.\s*/, '') // 移除开头的数字和点号
                    .trim();

                await navigator.clipboard.writeText(titleText);

                button.classList.remove('copying');
                button.classList.add('success');
                button.innerHTML = '✅ Copied!';

                this.showNotification('Title Copied', `"${titleText}" has been copied to clipboard`, 'success');

            } catch (error) {
                console.error('Copy title failed:', error);
                button.classList.remove('copying');
                button.classList.add('error');
                button.innerHTML = '❌ Failed';
                this.showNotification('Copy Failed', 'Failed to copy title to clipboard', 'error');
            }

            setTimeout(() => {
                button.className = 'lc-copy-button';
                button.innerHTML = originalText;
            }, 2000);
        }

        async copyProblem() {
            const button = event.target.closest('.lc-copy-button');
            const originalText = button.innerHTML;

            try {
                button.classList.add('copying');
                button.innerHTML = '⏳ Copying...';

                // 根据HTML结构查找题目描述容器
                let descriptionElement = document.querySelector('.elfjS[data-track-load="description_content"]');

                if (!descriptionElement) {
                    // 备用选择器
                    const backupSelectors = [
                        '.elfjS',
                        '[data-track-load="description_content"]',
                        '[class*="content"]',
                        '.description'
                    ];

                    for (const selector of backupSelectors) {
                        descriptionElement = document.querySelector(selector);
                        if (descriptionElement && descriptionElement.textContent.trim().length > 50) {
                            break;
                        }
                    }
                }

                if (!descriptionElement) {
                    throw new Error('Could not find problem description');
                }

                // 转换为Markdown格式
                const problemMarkdown = this.convertToMarkdown(descriptionElement);

                if (!problemMarkdown || problemMarkdown.length < 20) {
                    throw new Error('Problem description is too short or empty');
                }

                await navigator.clipboard.writeText(problemMarkdown);

                button.classList.remove('copying');
                button.classList.add('success');
                button.innerHTML = '✅ Copied!';

                this.showNotification('Problem Copied', `Problem description (${problemMarkdown.split('\n').length} lines, ${problemMarkdown.length} chars) has been copied with Markdown formatting`, 'success');

            } catch (error) {
                console.error('Copy problem failed:', error);
                button.classList.remove('copying');
                button.classList.add('error');
                button.innerHTML = '❌ Failed';
                this.showNotification('Copy Failed', error.message || 'Failed to copy problem description to clipboard', 'error');
            }

            setTimeout(() => {
                button.className = 'lc-copy-button';
                button.innerHTML = originalText;
            }, 2000);
        }

        convertToMarkdown(element) {
            // 创建元素副本以避免修改原DOM
            const clonedElement = element.cloneNode(true);

            // 移除不需要的元素
            const unwantedSelectors = [
                '.monaco-editor',
                '.CodeMirror',
                'button',
                '.lc-copy-button',
                '.lc-jump-button',
                '[class*="editor"]',
                '[class*="playground"]',
                '[class*="testcase"]',
                '[class*="submit"]',
                '[class*="run"]',
                'script',
                'style'
            ];

            unwantedSelectors.forEach(sel => {
                const elements = clonedElement.querySelectorAll(sel);
                elements.forEach(el => el.remove());
            });

            // 转换为Markdown
            let markdown = this.elementToMarkdown(clonedElement);

            // 最小化清理,保持原始格式
            markdown = markdown
                .replace(/\n\n\n+/g, '\n\n') // 只移除多余的连续空行
                .replace(/^\s+|\s+$/g, ''); // 只移除首尾空白

            return markdown;
        }

        elementToMarkdown(element) {
            let markdown = '';

            for (const node of element.childNodes) {
                if (node.nodeType === Node.TEXT_NODE) {
                    const text = node.textContent;
                    if (text) {
                        markdown += text;
                    }
                } else if (node.nodeType === Node.ELEMENT_NODE) {
                    const tagName = node.tagName.toLowerCase();

                    switch (tagName) {
                        case 'p':
                            const pContent = this.elementToMarkdown(node);
                            if (pContent.trim()) {
                                markdown += `${pContent}\n\n`;
                            }
                            break;
                        case 'strong':
                        case 'b':
                            const strongText = node.textContent;
                            if (strongText) {
                                markdown += `**${strongText}**`;
                            }
                            break;
                        case 'em':
                        case 'i':
                            const emText = node.textContent;
                            if (emText) {
                                markdown += `*${emText}*`;
                            }
                            break;
                        case 'code':
                            const codeText = node.textContent;
                            if (codeText) {
                                if (node.parentNode && node.parentNode.tagName.toLowerCase() === 'pre') {
                                    markdown += codeText; // 在pre中的code不需要额外处理
                                } else {
                                    markdown += `\`${codeText}\``;
                                }
                            }
                            break;
                        case 'pre':
                            const preContent = node.textContent;
                            if (preContent) {
                                markdown += `\n\`\`\`\n${preContent}\n\`\`\`\n\n`;
                            }
                            break;
                        case 'ul':
                            markdown += '\n';
                            const listItems = node.querySelectorAll('li');
                            listItems.forEach(li => {
                                const liText = li.textContent;
                                if (liText) {
                                    markdown += `* ${liText}\n`;
                                }
                            });
                            markdown += '\n';
                            break;
                        case 'ol':
                            markdown += '\n';
                            const orderedItems = node.querySelectorAll('li');
                            orderedItems.forEach((li, index) => {
                                const liText = li.textContent;
                                if (liText) {
                                    markdown += `${index + 1}. ${liText}\n`;
                                }
                            });
                            markdown += '\n';
                            break;
                        case 'blockquote':
                            const blockText = node.textContent;
                            if (blockText) {
                                const lines = blockText.split('\n');
                                lines.forEach(line => {
                                    if (line.trim()) {
                                        markdown += `> ${line.trim()}\n`;
                                    }
                                });
                                markdown += '\n';
                            }
                            break;
                        case 'img':
                            const alt = node.getAttribute('alt') || '';
                            const src = node.getAttribute('src') || '';
                            if (src) {
                                markdown += `![${alt}](${src})`;
                            }
                            break;
                        case 'a':
                            const linkText = node.textContent;
                            const href = node.getAttribute('href') || '';
                            if (linkText && href) {
                                markdown += `[${linkText}](${href})`;
                            } else if (linkText) {
                                markdown += linkText;
                            }
                            break;
                        case 'br':
                            markdown += '\n';
                            break;
                        case 'hr':
                            markdown += '\n---\n\n';
                            break;
                        case 'h1':
                        case 'h2':
                        case 'h3':
                        case 'h4':
                        case 'h5':
                        case 'h6':
                            const level = parseInt(tagName.charAt(1));
                            const headingText = node.textContent;
                            if (headingText) {
                                markdown += `\n${'#'.repeat(level)} ${headingText}\n\n`;
                            }
                            break;
                        case 'table':
                            markdown += this.tableToMarkdown(node);
                            break;
                        case 'div':
                        case 'span':
                            // 对于div和span,递归处理子元素
                            markdown += this.elementToMarkdown(node);
                            break;
                        default:
                            // 对于其他元素,递归处理子元素
                            markdown += this.elementToMarkdown(node);
                            break;
                    }
                }
            }

            return markdown;
        }

        tableToMarkdown(tableElement) {
            let markdown = '\n';
            const rows = tableElement.querySelectorAll('tr');

            if (rows.length === 0) return '';

            rows.forEach((row, rowIndex) => {
                const cells = row.querySelectorAll('td, th');
                if (cells.length === 0) return;

                const cellContents = Array.from(cells).map(cell => {
                    return cell.textContent.trim().replace(/\|/g, '\\|'); // 转义管道符
                });

                markdown += '| ' + cellContents.join(' | ') + ' |\n';

                // 添加表头分隔符
                if (rowIndex === 0) {
                    markdown += '| ' + cellContents.map(() => '---').join(' | ') + ' |\n';
                }
            });

            markdown += '\n';
            return markdown;
        }

        async copySolution() {
            const button = event.target.closest('.lc-copy-button');
            const originalText = button.innerHTML;

            try {
                button.classList.add('copying');
                button.innerHTML = '⏳ Copying...';

                let solutionText = '';

                // 方法1: 从Monaco Editor的view-lines获取完整代码
                const viewLines = document.querySelector('.view-lines');
                if (viewLines) {
                    const lineElements = viewLines.querySelectorAll('.view-line');

                    if (lineElements.length > 0) {
                        const lines = [];
                        lineElements.forEach((lineElement) => {
                            // 获取每行的文本内容,保持原始格式
                            function extractTextFromNode(node) {
                                if (node.nodeType === Node.TEXT_NODE) {
                                    return node.textContent;
                                } else if (node.nodeType === Node.ELEMENT_NODE) {
                                    let text = '';
                                    for (const child of node.childNodes) {
                                        text += extractTextFromNode(child);
                                    }
                                    return text;
                                }
                                return '';
                            }

                            let lineText = extractTextFromNode(lineElement);

                            // 如果这种方法失败,使用innerText作为备用
                            if (!lineText) {
                                lineText = lineElement.innerText || lineElement.textContent || '';
                            }

                            lines.push(lineText);
                        });

                        solutionText = lines.join('\n');
                    }
                }

                // 方法2: 尝试从Monaco Editor API获取
                if (!solutionText && window.monaco && window.monaco.editor) {
                    const editors = window.monaco.editor.getEditors();

                    if (editors.length > 0) {
                        for (let i = 0; i < editors.length; i++) {
                            try {
                                const editorValue = editors[i].getValue();
                                if (editorValue && editorValue.trim().length > solutionText.length) {
                                    solutionText = editorValue;
                                }
                            } catch (e) {
                                // 跳过这个编辑器,继续下一个
                            }
                        }
                    }
                }

                // 方法3: 尝试从textarea获取
                if (!solutionText || solutionText.trim().length < 10) {
                    const textareas = document.querySelectorAll('textarea');

                    textareas.forEach((textarea) => {
                        if (textarea.value && textarea.value.trim().length > 10) {
                            if (textarea.value.length > solutionText.length) {
                                solutionText = textarea.value;
                            }
                        }
                    });
                }

                // 方法4: 尝试从可编辑的div获取
                if (!solutionText || solutionText.trim().length < 10) {
                    const editableDivs = document.querySelectorAll('[contenteditable="true"]');

                    editableDivs.forEach((div) => {
                        const content = div.textContent || div.innerText;
                        if (content && content.trim().length > 10) {
                            // 检查是否看起来像代码
                            if (content.includes('def ') || content.includes('class ') ||
                                content.includes('function') || content.includes('var ') ||
                                content.includes('let ') || content.includes('const ') ||
                                content.includes('public ') || content.includes('private ') ||
                                content.includes('return ') || content.includes('if ') ||
                                content.includes('for ') || content.includes('while ')) {

                                if (content.length > solutionText.length) {
                                    solutionText = content;
                                }
                            }
                        }
                    });
                }

                // 方法5: 尝试从CodeMirror获取
                if (!solutionText || solutionText.trim().length < 10) {
                    const codeMirrorElements = document.querySelectorAll('.CodeMirror');

                    codeMirrorElements.forEach((cm) => {
                        if (cm.CodeMirror) {
                            try {
                                const cmValue = cm.CodeMirror.getValue();
                                if (cmValue && cmValue.length > solutionText.length) {
                                    solutionText = cmValue;
                                }
                            } catch (e) {
                                // 跳过这个CodeMirror实例
                            }
                        }
                    });
                }

                // 清理和验证代码
                if (solutionText) {
                    solutionText = solutionText.trim();
                }

                if (!solutionText || solutionText.trim().length < 5) {
                    throw new Error('Could not find solution code or editor is empty. Please make sure you have written code in the editor.');
                }

                await navigator.clipboard.writeText(solutionText);

                button.classList.remove('copying');
                button.classList.add('success');
                button.innerHTML = '✅ Copied!';

                const lineCount = solutionText.split('\n').length;
                this.showNotification('Solution Copied', `Your solution (${lineCount} lines, ${solutionText.length} characters) has been copied to clipboard`, 'success');

            } catch (error) {
                console.error('Copy solution failed:', error);
                button.classList.remove('copying');
                button.classList.add('error');
                button.innerHTML = '❌ Failed';
                this.showNotification('Copy Failed', error.message || 'Failed to copy solution. Make sure you have code in the editor.', 'error');
            }

            setTimeout(() => {
                button.className = 'lc-copy-button';
                button.innerHTML = originalText;
            }, 2000);
        }

        getTargetUrl(currentUrl) {
            try {
                const url = new URL(currentUrl);
                const pathname = url.pathname;
                const search = url.search;
                const hash = url.hash;

                if (this.isChinese) {
                    return `https://leetcode.com${pathname}${search}${hash}`;
                } else if (this.isInternational) {
                    return `https://leetcode.cn${pathname}${search}${hash}`;
                }

                return null;
            } catch (e) {
                return null;
            }
        }

        handleInternationalSite() {
            if (this.isProblemsDetailPage()) {
                this.addJumpButton();
                return;
            }

            setTimeout(() => {
                this.createControlPanel();
                if (this.settings.autoSync) {
                    this.checkAndAutoSync();
                }
            }, 2000);
        }

        createControlPanel() {
            const panel = document.createElement('div');
            panel.className = 'lc-control-panel';
            panel.innerHTML = `
                <div class="lc-panel-header">
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <span>📚</span>
                        <span>LeetCode Supporter / 题单助手</span>
                    </div>
                    <span class="lc-panel-toggle">▼</span>
                </div>
                <div class="lc-panel-content">
                    <button class="lc-button lc-button-primary" id="lc-sync-btn">
                        <span>📊</span>
                        <span>同步到中文站 / Sync to CN</span>
                    </button>
                    <button class="lc-button lc-button-secondary" id="lc-clear-btn">
                        <span>🗑️</span>
                        <span>清除缓存 / Clear Cache</span>
                    </button>
                    <div class="lc-stats" id="lc-stats">
                        <div class="lc-stat-item">
                            <div>📈 已同步 / Synced</div>
                            <div id="lc-sync-count">0</div>
                        </div>
                        <div class="lc-stat-item">
                            <div>⏰ 最后同步 / Last Sync</div>
                            <div id="lc-last-sync">未同步 / Not Synced</div>
                        </div>
                    </div>
                    <div class="lc-settings">
                        <div class="lc-setting-item">
                            <span>自动同步 / Auto Sync</span>
                            <div class="lc-switch ${this.settings.autoSync ? 'active' : ''}" data-setting="autoSync"></div>
                        </div>
                    </div>
                </div>
            `;

            document.body.appendChild(panel);
            this.bindInternationalEvents(panel);
            this.updateSyncStats();

            this.addPanelToggle(panel);
        }

        bindInternationalEvents(panel) {
            panel.querySelector('#lc-sync-btn').onclick = () => this.syncProgress();
            panel.querySelector('#lc-clear-btn').onclick = () => this.clearCache();

            panel.querySelectorAll('.lc-switch').forEach(sw => {
                sw.onclick = () => this.toggleSetting(sw.dataset.setting, sw);
            });
        }

        async checkAndAutoSync() {
            const lastSync = GM_getValue(CONFIG.LAST_SYNC_KEY, 0);
            if (Date.now() - lastSync > CONFIG.CACHE_DURATION) {
                setTimeout(() => this.syncProgress(), 3000);
            }
        }

        async syncProgress() {
            const button = document.querySelector('#lc-sync-btn');
            const originalHTML = button.innerHTML;
            button.innerHTML = '<span>⏳</span><span>同步中...</span>';
            button.disabled = true;

            try {
                let data = await this.fetchFromAPI();
                if (!data) {
                    data = await this.parseFromPage();
                }
                if (!data) {
                    data = await this.extractFromLocalStorage();
                }

                if (data && Object.keys(data).length > 0) {
                    this.saveProgressData(data);
                    button.innerHTML = '<span>✅</span><span>同步成功</span>';
                    this.showNotification('同步成功! / Sync Success!', `已同步 ${Object.keys(data).length} 个题目的状态 / Synced ${Object.keys(data).length} problems`, 'success');
                } else {
                    throw new Error('无法获取有效数据 / Cannot get valid data');
                }

            } catch (error) {
                console.error('❌ 同步失败:', error);
                button.innerHTML = '<span>❌</span><span>同步失败</span>';
                this.showNotification('同步失败 / Sync Failed', error.message || '同步过程中出现错误 / Error during sync', 'error');
            }

            setTimeout(() => {
                button.innerHTML = originalHTML;
                button.disabled = false;
                this.updateSyncStats();
            }, 2000);
        }

        async fetchFromAPI() {
            return new Promise((resolve) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'https://leetcode.com/api/problems/all/',
                    headers: {
                        'User-Agent': navigator.userAgent,
                        'Referer': 'https://leetcode.com/problemset/all/'
                    },
                    timeout: 10000,
                    onload: (response) => {
                        try {
                            if (response.status === 200) {
                                const apiData = JSON.parse(response.responseText);
                                if (apiData.stat_status_pairs) {
                                    const data = {};
                                    apiData.stat_status_pairs.forEach(item => {
                                        const slug = item.stat.question__title_slug;
                                        data[slug] = {
                                            titleSlug: slug,
                                            status: item.status,
                                            frontendId: item.stat.frontend_question_id,
                                            difficulty: item.difficulty?.level || null,
                                            title: item.stat.question__title,
                                            _raw_difficulty: item.difficulty
                                        };
                                    });

                                    resolve(data);
                                    return;
                                }
                            }
                            resolve(null);
                        } catch (e) {
                            resolve(null);
                        }
                    },
                    onerror: () => {
                        resolve(null);
                    }
                });
            });
        }

        async parseFromPage() {
            const data = {};

            await new Promise(resolve => setTimeout(resolve, 2000));

            const selectors = [
                'div[role="row"]',
                'tr[data-cy="question-row"]',
                '.question-list-table tbody tr'
            ];

            for (const selector of selectors) {
                const rows = document.querySelectorAll(selector);
                if (rows.length > 0) {
                    rows.forEach((row) => {
                        try {
                            const link = row.querySelector('a[href*="/problems/"]');
                            if (!link) return;

                            const slug = link.href.match(/\/problems\/([^\/\?]+)/)?.[1];
                            if (!slug) return;

                            let status = null;
                            const statusElements = row.querySelectorAll('[data-cy="status"], .text-green-s, .text-yellow, .status, svg');
                            statusElements.forEach(el => {
                                const text = el.textContent?.toLowerCase() || '';
                                const classList = el.classList?.toString() || '';

                                if (text.includes('✓') || classList.includes('text-green') || classList.includes('solved')) {
                                    status = 'ac';
                                } else if (text.includes('?') || classList.includes('text-yellow') || classList.includes('attempted')) {
                                    status = 'notac';
                                }
                            });

                            let difficulty = null;
                            const difficultySelectors = [
                                '[data-cy="difficulty"]',
                                '.difficulty',
                                '[class*="difficulty"]',
                                'span[class*="text-"]',
                                '.text-green-s', '.text-yellow', '.text-red'
                            ];

                            difficultySelectors.forEach(selector => {
                                const diffEl = row.querySelector(selector);
                                if (diffEl && !difficulty) {
                                    const diffText = diffEl.textContent?.toLowerCase() || '';
                                    const classList = diffEl.classList?.toString().toLowerCase() || '';

                                    if (diffText.includes('easy') || classList.includes('green')) {
                                        difficulty = 1;
                                    } else if (diffText.includes('medium') || classList.includes('yellow')) {
                                        difficulty = 2;
                                    } else if (diffText.includes('hard') || classList.includes('red')) {
                                        difficulty = 3;
                                    }
                                }
                            });

                            data[slug] = {
                                titleSlug: slug,
                                status: status,
                                difficulty: difficulty,
                                title: link.textContent.trim(),
                                _source: 'page_parse'
                            };

                        } catch (e) {
                        }
                    });

                    if (Object.keys(data).length > 0) {
                        return data;
                    }
                }
            }

            return null;
        }

        async extractFromLocalStorage() {
            try {
                const keys = Object.keys(localStorage);
                for (const key of keys) {
                    if (key.includes('leetcode') || key.includes('question')) {
                        try {
                            const value = JSON.parse(localStorage.getItem(key));
                            if (value && typeof value === 'object') {
                                if (value.questionData || value.questions || value.stat_status_pairs) {
                                }
                            }
                        } catch (e) {
                        }
                    }
                }
            } catch (e) {
            }
            return null;
        }

        saveProgressData(data) {
            GM_setValue(CONFIG.STORAGE_KEY, data);
            GM_setValue(CONFIG.LAST_SYNC_KEY, Date.now());
            console.log(`💾 保存了 ${Object.keys(data).length} 个题目的数据`);
        }

        clearCache() {
            GM_setValue(CONFIG.STORAGE_KEY, {});
            GM_setValue(CONFIG.LAST_SYNC_KEY, 0);
            this.updateSyncStats();
            this.showNotification('缓存已清除 / Cache Cleared', '所有同步数据已删除 / All sync data deleted', 'info');
        }

        updateSyncStats() {
            const data = GM_getValue(CONFIG.STORAGE_KEY, {});
            const lastSync = GM_getValue(CONFIG.LAST_SYNC_KEY, 0);

            const countEl = document.querySelector('#lc-sync-count');
            const timeEl = document.querySelector('#lc-last-sync');

            if (countEl) countEl.textContent = Object.keys(data).length;
            if (timeEl) {
                timeEl.textContent = lastSync ?
                    new Date(lastSync).toLocaleTimeString() : '未同步 / Not Synced';
            }
        }

        handleChineseSite() {
            setTimeout(() => {
                this.createChinesePanel();
                this.processPage();
                this.observeChanges();
            }, 1500);
        }

        createChinesePanel() {
            const data = GM_getValue(CONFIG.STORAGE_KEY, {});
            const panel = document.createElement('div');
            panel.className = 'lc-control-panel';
            panel.innerHTML = `
                <div class="lc-panel-header">
                    <div style="display: flex; align-items: center; gap: 8px;">
                        <span>📚</span>
                        <span>LeetCode Supporter / 题单助手</span>
                    </div>
                    <span class="lc-panel-toggle">▼</span>
                </div>
                <div class="lc-panel-content">
                    <div class="lc-stats">
                        <div class="lc-stat-item">
                            <div>✅ 已解决 / Solved</div>
                            <div id="lc-solved">0</div>
                        </div>
                        <div class="lc-stat-item">
                            <div>❌ 尝试中 / Attempted</div>
                            <div id="lc-attempted">0</div>
                        </div>
                        <div class="lc-stat-item">
                            <div>⭕ 未尝试 / Not Attempted</div>
                            <div id="lc-not-attempted">0</div>
                        </div>
                        <div class="lc-stat-item">
                            <div>📊 总题数 / Total</div>
                            <div id="lc-total">0</div>
                        </div>
                    </div>
                    <div class="lc-progress-bar">
                        <div class="lc-progress-fill" id="lc-progress"></div>
                    </div>
                    <button class="lc-button lc-button-secondary" id="lc-refresh-btn">
                        <span>🔄</span>
                        <span>刷新状态 / Refresh</span>
                    </button>
                    <div class="lc-settings">
                        <div class="lc-setting-item">
                            <span>显示难度 / Show Difficulty</span>
                            <div class="lc-switch ${this.settings.showDifficulty ? 'active' : ''}" data-setting="showDifficulty"></div>
                        </div>
                        <div class="lc-setting-item">
                            <span>隐藏已完成 / Hide Solved</span>
                            <div class="lc-switch ${this.settings.hideCompleted ? 'active' : ''}" data-setting="hideCompleted"></div>
                        </div>
                    </div>
                    ${Object.keys(data).length === 0 ? `
                        <div style="text-align: center; margin: 12px 0; font-size: 11px; color: #999;">
                            <div>⚠️ 未找到同步数据 / No sync data found</div>
                            <a href="https://leetcode.com/problemset/all/" target="_blank" style="color: #1890ff;">
                                前往国际站同步 / Go to LeetCode.com to sync
                            </a>
                        </div>
                    ` : ''}
                </div>
            `;

            document.body.appendChild(panel);
            this.bindChineseEvents(panel);

            this.addPanelToggle(panel);
        }

        bindChineseEvents(panel) {
            panel.querySelector('#lc-refresh-btn')?.addEventListener('click', () => {
                this.processPage();
                this.showNotification('已刷新 / Refreshed', '状态显示已更新 / Status display updated', 'info');
            });

            panel.querySelectorAll('.lc-switch').forEach(sw => {
                sw.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this.toggleSetting(sw.dataset.setting, sw);
                });
            });
        }

        processPage() {
            const data = GM_getValue(CONFIG.STORAGE_KEY, {});

            if (Object.keys(data).length === 0) {
                return;
            }

            document.querySelectorAll('.lc-status-container').forEach(el => el.remove());

            document.querySelectorAll('.lc-hidden').forEach(el => {
                el.classList.remove('lc-hidden');
            });

            this.processLinks(data);
            this.updateStats();
        }

        processLinks(data) {
            const links = Array.from(document.querySelectorAll('a[href*="/problems/"]'));

            if (links.length === 0) {
                return;
            }

            let processed = 0;

            links.forEach(link => {
                if (this.processLink(link, data)) {
                    processed++;
                }
            });

            if (processed > 0) {
                this.updateStats();
            }
        }

        processLink(linkElement, data) {
            const originalHref = linkElement.href;

            if (!originalHref.includes('/problems/')) {
                return false;
            }

            const slug = this.extractProblemSlug(originalHref);
            if (!slug) return false;

            if (originalHref.includes('leetcode.cn')) {
                linkElement.href = originalHref.replace('leetcode.cn', 'leetcode.com');
                linkElement.target = '_blank';
            }
            linkElement.classList.add('lc-converted');

            const problemData = data[slug];
            const status = problemData?.status;

            this.addEnhancedBadges(linkElement, problemData);

            if (this.settings.hideCompleted && status === 'ac') {
                const parentElement = linkElement.closest('tr, div[role="row"], li, .question-item, .problem-item');
                if (parentElement) {
                    parentElement.classList.add('lc-hidden');
                }
            }

            return true;
        }

        addEnhancedBadges(linkElement, problemData) {
            const existingContainer = linkElement.parentNode.querySelector('.lc-status-container');
            if (existingContainer) {
                existingContainer.remove();
            }

            const container = document.createElement('div');
            container.className = 'lc-status-container';

            if (this.settings.showDifficulty) {
                const difficulty = problemData?.difficulty;

                if (difficulty && DIFFICULTY_MAP[difficulty]) {
                    const diffInfo = DIFFICULTY_MAP[difficulty];
                    const diffBadge = document.createElement('span');
                    diffBadge.className = 'lc-difficulty-badge';
                    diffBadge.textContent = diffInfo.text;
                    diffBadge.title = `难度: ${diffInfo.text}`;
                    diffBadge.style.backgroundColor = diffInfo.color;
                    diffBadge.style.color = 'white';

                    container.appendChild(diffBadge);
                }
            }

            const status = problemData?.status;
            const statusInfo = STATUS_MAP[status] || STATUS_MAP[null];

            const statusBadge = document.createElement('span');
            statusBadge.className = 'lc-status-badge';
            statusBadge.textContent = statusInfo.text;
            statusBadge.title = `状态: ${statusInfo.text} - ${problemData?.titleSlug || 'unknown'}`;
            statusBadge.style.backgroundColor = statusInfo.bgColor;
            statusBadge.style.color = statusInfo.color;
            statusBadge.style.borderColor = statusInfo.color;

            container.appendChild(statusBadge);

            linkElement.parentNode.insertBefore(container, linkElement);
        }

        updateStats() {
            const data = GM_getValue(CONFIG.STORAGE_KEY, {});
            const links = document.querySelectorAll('.lc-converted');

            this.stats = { total: 0, solved: 0, attempted: 0, notAttempted: 0 };

            links.forEach(link => {
                const slug = this.extractProblemSlug(link.href);
                const problemData = data[slug];
                if (problemData) {
                    this.stats.total++;
                    if (problemData.status === 'ac') {
                        this.stats.solved++;
                    } else if (problemData.status === 'notac') {
                        this.stats.attempted++;
                    } else {
                        this.stats.notAttempted++;
                    }
                }
            });

            const solvedEl = document.querySelector('#lc-solved');
            const attemptedEl = document.querySelector('#lc-attempted');
            const notAttemptedEl = document.querySelector('#lc-not-attempted');
            const totalEl = document.querySelector('#lc-total');
            const progressEl = document.querySelector('#lc-progress');

            if (solvedEl) solvedEl.textContent = this.stats.solved;
            if (attemptedEl) attemptedEl.textContent = this.stats.attempted;
            if (notAttemptedEl) notAttemptedEl.textContent = this.stats.notAttempted;
            if (totalEl) totalEl.textContent = this.stats.total;
            if (progressEl && this.stats.total > 0) {
                const percentage = (this.stats.solved / this.stats.total) * 100;
                progressEl.style.width = percentage + '%';
            }
        }

        observeChanges() {
            let isProcessing = false;

            const observer = new MutationObserver((mutations) => {
                if (isProcessing) return;

                let shouldProcess = false;

                mutations.forEach(mutation => {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === 1) {
                            if (node.classList?.contains('lc-status-container') ||
                                node.classList?.contains('lc-control-panel') ||
                                node.classList?.contains('lc-notification') ||
                                node.closest?.('.lc-status-container, .lc-control-panel, .lc-notification')) {
                                return;
                            }

                            if (node.querySelector?.('a[href*="/problems/"]:not(.lc-converted)') ||
                                (node.matches?.('a[href*="/problems/"]') && !node.classList.contains('lc-converted'))) {
                                shouldProcess = true;
                            }
                        }
                    });
                });

                if (shouldProcess) {
                    isProcessing = true;
                    setTimeout(() => {
                        this.processPage();
                        isProcessing = false;
                    }, 1000);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        extractProblemSlug(url) {
            const match = url.match(/\/problems\/([^\/\?]+)/);
            return match ? match[1] : null;
        }

        toggleSetting(key, switchEl) {
            this.settings[key] = !this.settings[key];
            switchEl.classList.toggle('active', this.settings[key]);
            GM_setValue(CONFIG.SETTINGS_KEY, this.settings);

            if (key === 'showDifficulty' || key === 'hideCompleted') {
                this.processPage();
            }

            const settingMessages = {
                'showDifficulty': {
                    on: '显示难度 / Show Difficulty',
                    off: '隐藏难度 / Hide Difficulty'
                },
                'hideCompleted': {
                    on: '隐藏已完成 / Hide Solved',
                    off: '显示已完成 / Show Solved'
                },
                'autoSync': {
                    on: '自动同步 / Auto Sync',
                    off: '手动同步 / Manual Sync'
                }
            };

            const message = settingMessages[key];
            if (message) {
                const statusText = this.settings[key] ? message.on : message.off;
                this.showNotification('设置已更新 / Settings Updated', statusText, 'info');
            }
        }

        showNotification(title, message, type = 'info') {
            const colors = {
                success: '#52c41a',
                error: '#ff4d4f',
                info: '#1890ff',
                warning: '#faad14'
            };

            const notification = document.createElement('div');
            notification.className = 'lc-notification';
            notification.style.borderLeftColor = colors[type];
            notification.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 4px;">${title}</div>
                <div style="font-size: 12px; color: #666;">${message}</div>
            `;

            document.body.appendChild(notification);

            setTimeout(() => {
                notification.style.animation = 'slideIn 0.3s ease reverse';
                setTimeout(() => notification.remove(), 300);
            }, 3000);
        }

        addPanelToggle(panel) {
            const header = panel.querySelector('.lc-panel-header');
            const toggle = panel.querySelector('.lc-panel-toggle');

            const isCollapsed = GM_getValue('panel_collapsed', false);
            if (isCollapsed) {
                panel.classList.add('collapsed');
                toggle.classList.add('collapsed');
            }

            header.addEventListener('click', () => {
                const collapsed = panel.classList.toggle('collapsed');
                toggle.classList.toggle('collapsed', collapsed);

                GM_setValue('panel_collapsed', collapsed);
            });
        }

        initHotkeys() {
            document.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.shiftKey && e.key === 'L') {
                    e.preventDefault();
                    if (this.isInternational) {
                        this.syncProgress();
                    } else {
                        this.processPage();
                    }
                }

                if (e.ctrlKey && e.shiftKey && e.key === 'H') {
                    e.preventDefault();
                    if (this.isChinese) {
                        const switchEl = document.querySelector('[data-setting="hideCompleted"]');
                        if (switchEl) {
                            this.toggleSetting('hideCompleted', switchEl);
                        }
                    }
                }
            });
        }
    }

    function initialize() {
        const app = new LeetCodeUltimate();

        app.initHotkeys();

        const lastVersion = GM_getValue('last_version', '');
        if (lastVersion !== CONFIG.VERSION) {
            GM_setValue('last_version', CONFIG.VERSION);
            if (lastVersion) {
                app.showNotification(
                    '更新完成!',
                    `LeetCode题单助手已更新到 v${CONFIG.VERSION}`,
                    'success'
                );
            }
        }
    }

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

})();

QingJ © 2025

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