Codetop Notes 增强

在 Codetop 题目列表每行“笔记”按钮旁插入自定义按钮(初版)

// ==UserScript==
// @name         Codetop Notes 增强
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  在 Codetop 题目列表每行“笔记”按钮旁插入自定义按钮(初版)
// @author       YourName
// @match        https://codetop.cc/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 工具函数:插入自定义按钮
    function insertCustomNoteButtons() {
        // 兼容所有“笔记”按钮(无论列号、class如何变化)
        const noteSpans = Array.from(document.querySelectorAll('table tr td .el-button > span'))
            .filter(span => span.textContent.trim() === '笔记');
        noteSpans.forEach(span => {
            const noteBtn = span.parentElement;
            const btnGroup = noteBtn.parentElement;
            // 避免重复插入
            if (btnGroup.querySelector('.ctn-custom-note-btn')) {
                // 按钮已存在,但要更新状态
                const existingBtn = btnGroup.querySelector('.ctn-custom-note-btn');
                let tr = existingBtn;
                while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
                if (tr) {
                    const key = getRowKeyFromBtn(existingBtn);
                    loadNote(key).then(content => {
                        updateButtonState(existingBtn, content);
                    }).catch(err => {
                        // 按钮状态更新失败不影响主要功能
                    });
                }
                return;
            }
            // 创建自定义按钮
            const btn = document.createElement('button');
            btn.className = noteBtn.className + ' ctn-custom-note-btn';
            btn.style.marginLeft = '6px';
            btn.innerHTML = '📝';
            btn.style.maxWidth = '40px';
            btn.style.padding = '0 8px';
            btn.style.fontSize = '16px';
            btn.style.whiteSpace = 'nowrap';
            btn.style.height = noteBtn.offsetHeight + 'px';
            btn.title = '自定义笔记';
            btn.addEventListener('click', showCustomNoteModal);
            btnGroup.insertBefore(btn, noteBtn.nextSibling);
            // 优化:如果该题存在笔记,按钮显示绿色
            let tr = btn;
            while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
            if (tr) {
                const key = getRowKeyFromBtn(btn);
                loadNote(key).then(content => {
                    updateButtonState(btn, content);
                }).catch(err => {
                    // 按钮状态更新失败不影响主要功能
                });
            }
        });
    }

    // IndexedDB 简单封装
    const DB_NAME = 'codetop_notes';
    const STORE_NAME = 'notes';

    // 只做最基础的open,不做任何超时、自动删除、reset、test等
    function openDB() {
        return new Promise((resolve, reject) => {
            const req = window.indexedDB.open(DB_NAME, 1);
            req.onupgradeneeded = function(e) {
                const db = e.target.result;
                if (!db.objectStoreNames.contains(STORE_NAME)) {
                    db.createObjectStore(STORE_NAME, { keyPath: 'key' });
                }
            };
            req.onsuccess = function(e) {
                resolve(e.target.result);
            };
            req.onerror = function(e) {
                console.error('数据库打开失败:', e);
                reject(e);
            };
            req.onblocked = function(e) {
                console.error('数据库被阻塞:', e);
                reject(new Error('数据库被阻塞'));
            };
        });
    }
    // 修改 saveNote 支持可选 updated_at 参数
    function saveNote(key, content, updated_at) {
        return openDB().then(db => {
            return new Promise((resolve, reject) => {
                const tx = db.transaction(STORE_NAME, 'readwrite');
                const store = tx.objectStore(STORE_NAME);
                const putRequest = store.put({
                    key,
                    content,
                    updated_at: typeof updated_at === 'number' ? updated_at : Date.now()
                });
                putRequest.onsuccess = () => {
                    resolve();
                };
                putRequest.onerror = (e) => {
                    console.error('保存笔记失败:', e);
                    reject(e);
                };
                tx.onerror = (e) => {
                    console.error('事务失败:', e);
                    reject(e);
                };
            });
        });
    }
    function loadNote(key) {
        return openDB().then(db => {
            return new Promise((resolve, reject) => {
                const tx = db.transaction(STORE_NAME, 'readonly');
                const store = tx.objectStore(STORE_NAME);
                const req = store.get(key);
                req.onsuccess = () => {
                    const result = req.result ? req.result.content : '';
                    resolve(result);
                };
                req.onerror = (e) => {
                    console.error('加载笔记失败:', e);
                    reject(e);
                };
                tx.onerror = (e) => {
                    console.error('事务失败:', e);
                    reject(e);
                };
            });
        });
    }

    // 更新按钮状态的工具函数
    // ... existing code ...
    function updateButtonState(btn, content) {
        if (content && content.trim()) {
            btn.style.background = '#e6a23c'; // 有内容时橙色
            btn.style.color = '#fff';
            btn.style.borderColor = '#e6a23c';
        } else {
            // 默认灰色
            btn.style.background = '#909399';
            btn.style.color = '#fff';
            btn.style.borderColor = '#909399';
        }
    }
    // ... existing code ...

    // 获取当前行的题目唯一 key
    function getRowKeyFromBtn(btn) {
        let tr = btn;
        while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
        if (!tr) {
            return '';
        }

        // 优先用 tr 的 data-row-key 或 data-id
        if (tr.dataset && (tr.dataset.rowKey || tr.dataset.id)) {
            return tr.dataset.rowKey || tr.dataset.id;
        }

        // 依次检查前两个td,优先用a标签href
        const tds = tr.querySelectorAll('td');
        for (let i = 0; i < Math.min(2, tds.length); i++) {
            const a = tds[i].querySelector('a');
            if (a && a.href) {
                return a.href;
            }
        }
        // 如果没有a标签,再用前两个td的文本
        for (let i = 0; i < Math.min(2, tds.length); i++) {
            const text = tds[i].textContent.trim();
            if (text) {
                return `${tr.rowIndex || ''}_${text}`;
            }
        }
        // 兜底:用整行文本+行号
        const key = `${tr.rowIndex || ''}_${tr.textContent.trim()}`;
        return key;
    }

    // 简单浮层(Modal)实现
    function showCustomNoteModal(e) {
        // 若已存在则不重复弹出
        if (document.querySelector('.ctn-modal-mask')) return;
        const btn = e.currentTarget;
        const noteKey = getRowKeyFromBtn(btn);
        // 先渲染 modal 骨架和 loading
        const mask = document.createElement('div');
        mask.className = 'ctn-modal-mask';
        mask.style = `
            position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.3);z-index:9999;display:flex;align-items:center;justify-content:center;`;
        const modal = document.createElement('div');
        modal.className = 'ctn-modal';
        // 全屏样式
        modal.style = `
            background:#fff;
            padding:0;
            border-radius:0;
            width:100vw;
            height:100vh;
            max-width:100vw;
            max-height:100vh;
            box-shadow:none;
            position:relative;
            display:flex;
            flex-direction:row;
            gap:0;
            overflow:hidden;
        `;
        // 关闭按钮
        const closeBtn = document.createElement('span');
        closeBtn.innerHTML = '&times;';
        closeBtn.style = 'position:absolute;right:32px;top:24px;font-size:32px;cursor:pointer;z-index:2;color:#d4d4d4;background:rgba(30,30,30,0.8);border-radius:50%;width:40px;height:40px;display:flex;align-items:center;justify-content:center;transition:all 0.2s;';
        closeBtn.title = '关闭';
        closeBtn.addEventListener('mouseenter', () => {
            closeBtn.style.background = 'rgba(255,255,255,0.1)';
            closeBtn.style.color = '#ffffff';
        });
        closeBtn.addEventListener('mouseleave', () => {
            closeBtn.style.background = 'rgba(30,30,30,0.8)';
            closeBtn.style.color = '#d4d4d4';
        });
        closeBtn.onclick = () => {
            mask.remove();
            document.removeEventListener('keydown', escListener);
        };
        // ESC 键关闭浮层
        function escListener(ev) {
            if (ev.key === 'Escape') {
                mask.remove();
                document.removeEventListener('keydown', escListener);
            }
        }
        document.addEventListener('keydown', escListener);
        // 左右两栏骨架
        const left = document.createElement('div');
        left.style = 'flex:5;min-width:0;height:100vh;max-height:100vh;overflow:auto;display:flex;flex-direction:column;padding:48px 32px 32px 48px;box-sizing:border-box;background:#1e1e1e;';
        left.innerHTML = '<div style="padding:32px;text-align:center;color:#d4d4d4;">加载编辑器中...</div>';
        const right = document.createElement('div');
        right.style = 'flex:5;min-width:0;height:100vh;max-height:100vh;overflow:auto;border-left:1px solid #3c3c3c;padding:48px 48px 32px 48px;box-sizing:border-box;background:#2d2d30;color:#d4d4d4;';
        right.innerHTML = '<div style="padding:32px;text-align:center;color:#d4d4d4;">加载预览中...</div>';

        // 为右侧面板添加自定义滚动条样式
        right.style.setProperty('scrollbar-width', 'thin');
        right.style.setProperty('scrollbar-color', '#424242 #2d2d30');
        // 组装
        modal.appendChild(closeBtn);
        modal.appendChild(left);
        modal.appendChild(right);
        mask.appendChild(modal);
        document.body.appendChild(mask);
        // 加载依赖后再初始化编辑器和预览
        loadEasyMDE(() => {
            left.innerHTML = '';
            right.innerHTML = '';
            const textarea = document.createElement('textarea');
            textarea.id = 'ctn-md-editor';
            // 保存按钮
            const saveBtn = document.createElement('button');
            saveBtn.textContent = '保存';
            saveBtn.style = 'margin:12px 0 0 0;align-self:flex-end;padding:6px 18px;background:#0e639c;color:#fff;border:1px solid #1177bb;border-radius:4px;cursor:pointer;font-size:16px;transition:background 0.2s;';
            saveBtn.addEventListener('mouseenter', () => {
                saveBtn.style.background = '#1177bb';
            });
            saveBtn.addEventListener('mouseleave', () => {
                saveBtn.style.background = '#0e639c';
            });
            // 保存提示
            const saveTip = document.createElement('span');
            saveTip.style = 'margin-left:12px;color:#4fc1ff;font-size:14px;display:none;';
            saveTip.textContent = '已保存!';
            left.appendChild(textarea);
            left.appendChild(saveBtn);
            left.appendChild(saveTip);
            right.innerHTML = '<div style="font-weight:bold;margin-bottom:8px;color:#569cd6;font-size:18px;border-bottom:1px solid #3c3c3c;padding-bottom:8px;">📖 实时预览</div><div id="ctn-md-preview" style="min-height:320px;"></div>';
            // 初始化 EasyMDE
            const easyMDE = new window.EasyMDE({
                element: textarea,
                autoDownloadFontAwesome: false,
                status: false,
                toolbar: false, // 禁用工具栏,保持简洁
                minHeight: '320px',
                spellChecker: false,
                placeholder: '请输入 Markdown 笔记...',
                theme: 'dark',
                styleSelectedText: false
            });

            // 设置编辑器暗色主题样式
            setTimeout(() => {
                const editor = easyMDE.codemirror;
                const wrapper = editor.getWrapperElement();

                // 设置编辑器暗色主题
                wrapper.style.background = '#1e1e1e';
                wrapper.style.color = '#d4d4d4';
                wrapper.style.border = '1px solid #3c3c3c';
                wrapper.style.borderRadius = '6px';

                // 设置编辑器内部样式
                const editorElement = wrapper.querySelector('.CodeMirror');
                if (editorElement) {
                    editorElement.style.background = '#1e1e1e';
                    editorElement.style.color = '#d4d4d4';
                    editorElement.style.fontFamily = 'Consolas, "Courier New", monospace';
                    editorElement.style.fontSize = '14px';
                    editorElement.style.lineHeight = '1.5';
                }

                // 设置光标颜色
                const cursorElements = wrapper.querySelectorAll('.CodeMirror-cursor');
                cursorElements.forEach(cursor => {
                    cursor.style.borderColor = '#d4d4d4';
                });

                // 设置选中文本样式
                const style = document.createElement('style');
                style.textContent = `
                    .CodeMirror-dark .CodeMirror-selected { background: #264f78; }
                    .CodeMirror-dark .CodeMirror-line::selection,
                    .CodeMirror-dark .CodeMirror-line > span::selection,
                    .CodeMirror-dark .CodeMirror-line > span > span::selection { background: #264f78; }
                    .CodeMirror-dark .CodeMirror-activeline-background { background: #2a2a2a; }
                    .CodeMirror-dark .CodeMirror-gutters { background: #252526; border-right: 1px solid #3c3c3c; }
                    .CodeMirror-dark .CodeMirror-linenumber { color: #858585; }

                    /* Markdown 语法高亮 */
                    .CodeMirror-dark .cm-header { color: #569cd6; font-weight: bold; }
                    .CodeMirror-dark .cm-header-1 { color: #569cd6; font-size: 1.4em; }
                    .CodeMirror-dark .cm-header-2 { color: #569cd6; font-size: 1.3em; }
                    .CodeMirror-dark .cm-header-3 { color: #569cd6; font-size: 1.2em; }
                    .CodeMirror-dark .cm-quote { color: #6a9955; font-style: italic; }
                    .CodeMirror-dark .cm-strong { color: #d4d4d4; font-weight: bold; }
                    .CodeMirror-dark .cm-em { color: #d4d4d4; font-style: italic; }
                    .CodeMirror-dark .cm-link { color: #4fc1ff; text-decoration: underline; }
                    .CodeMirror-dark .cm-url { color: #4fc1ff; }
                    .CodeMirror-dark .cm-comment { color: #6a9955; }
                    .CodeMirror-dark .cm-string { color: #ce9178; }
                    .CodeMirror-dark .cm-keyword { color: #569cd6; }
                    .CodeMirror-dark .cm-builtin { color: #dcdcaa; }
                    .CodeMirror-dark .cm-variable-2 { color: #9cdcfe; }
                    .CodeMirror-dark .cm-variable-3 { color: #4ec9b0; }
                    .CodeMirror-dark .cm-tag { color: #569cd6; }
                    .CodeMirror-dark .cm-attribute { color: #9cdcfe; }
                    .CodeMirror-dark .cm-number { color: #b5cea8; }
                    .CodeMirror-dark .cm-atom { color: #569cd6; }
                    .CodeMirror-dark .cm-meta { color: #dcdcaa; }
                    .CodeMirror-dark .cm-bracket { color: #d4d4d4; }

                    /* 代码块样式 */
                    .CodeMirror-dark .cm-formatting-code-block,
                    .CodeMirror-dark .cm-formatting-code { color: #808080; }
                    .CodeMirror-dark .cm-comment.cm-formatting-code-block {
                        background: #2d2d30;
                        color: #ce9178;
                        border-radius: 3px;
                        padding: 1px 3px;
                    }

                    /* 列表样式 */
                    .CodeMirror-dark .cm-formatting-list { color: #569cd6; font-weight: bold; }

                    /* 分割线样式 */
                    .CodeMirror-dark .cm-hr { color: #808080; font-weight: bold; }

                    /* 工具栏隐藏(如果存在) */
                    .CodeMirror-dark + .editor-toolbar { display: none !important; }

                    /* 滚动条样式 */
                    .CodeMirror-dark .CodeMirror-scrollbar-filler,
                    .CodeMirror-dark .CodeMirror-gutter-filler { background: #1e1e1e; }
                    .CodeMirror-dark .CodeMirror-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
                    .CodeMirror-dark .CodeMirror-scroll::-webkit-scrollbar-track { background: #2d2d30; }
                    .CodeMirror-dark .CodeMirror-scroll::-webkit-scrollbar-thumb { background: #424242; border-radius: 5px; }
                    .CodeMirror-dark .CodeMirror-scroll::-webkit-scrollbar-thumb:hover { background: #4f4f4f; }

                    /* 焦点样式 */
                    .CodeMirror-dark.CodeMirror-focused .CodeMirror-selected { background: #264f78; }

                    /* Placeholder 样式 */
                    .CodeMirror-dark .CodeMirror-placeholder { color: #717171; }
                    .CodeMirror-dark .CodeMirror-empty.CodeMirror-focused .CodeMirror-placeholder { color: #717171; }

                    /* 预览区域暗色主题样式 */
                    #ctn-md-preview {
                        background: #2d2d30;
                        color: #d4d4d4;
                        border-radius: 6px;
                        padding: 16px;
                        line-height: 1.6;
                        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
                    }

                    #ctn-md-preview h1, #ctn-md-preview h2, #ctn-md-preview h3,
                    #ctn-md-preview h4, #ctn-md-preview h5, #ctn-md-preview h6 {
                        color: #569cd6;
                        border-bottom: 1px solid #3c3c3c;
                        padding-bottom: 0.3em;
                        margin-top: 24px;
                        margin-bottom: 16px;
                    }

                    #ctn-md-preview h1 { font-size: 2em; }
                    #ctn-md-preview h2 { font-size: 1.5em; }
                    #ctn-md-preview h3 { font-size: 1.25em; }
                    #ctn-md-preview h4 { font-size: 1em; }
                    #ctn-md-preview h5 { font-size: 0.875em; }
                    #ctn-md-preview h6 { font-size: 0.85em; }

                    #ctn-md-preview p {
                        margin-bottom: 16px;
                        color: #d4d4d4;
                    }

                    #ctn-md-preview code {
                        background: #1e1e1e;
                        color: #f8f8f2;
                        padding: 2px 6px;
                        border-radius: 3px;
                        font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
                        font-size: 0.875em;
                        border: 1px solid #3c3c3c;
                    }

                    #ctn-md-preview pre {
                        background: #1e1e1e;
                        border: 1px solid #3c3c3c;
                        border-radius: 6px;
                        padding: 16px;
                        overflow-x: auto;
                        margin: 16px 0;
                    }

                    #ctn-md-preview pre code {
                        background: transparent;
                        border: none;
                        padding: 0;
                        color: inherit;
                    }

                    #ctn-md-preview blockquote {
                        background: #2a2a2a;
                        border-left: 4px solid #6a9955;
                        padding: 8px 16px;
                        margin: 16px 0;
                        color: #d4d4d4;
                        font-style: italic;
                    }

                    #ctn-md-preview ul, #ctn-md-preview ol {
                        padding-left: 24px;
                        margin: 16px 0;
                    }

                    #ctn-md-preview li {
                        margin: 4px 0;
                        color: #d4d4d4;
                    }

                    #ctn-md-preview a {
                        color: #4fc1ff;
                        text-decoration: none;
                        border-bottom: 1px solid transparent;
                        transition: border-color 0.2s;
                    }

                    #ctn-md-preview a:hover {
                        border-bottom-color: #4fc1ff;
                    }

                    #ctn-md-preview table {
                        border-collapse: collapse;
                        width: 100%;
                        margin: 16px 0;
                        background: #252526;
                        border: 1px solid #3c3c3c;
                        border-radius: 6px;
                        overflow: hidden;
                    }

                    #ctn-md-preview th, #ctn-md-preview td {
                        border: 1px solid #3c3c3c;
                        padding: 8px 12px;
                        text-align: left;
                    }

                    #ctn-md-preview th {
                        background: #1e1e1e;
                        color: #569cd6;
                        font-weight: bold;
                    }

                    #ctn-md-preview td {
                        color: #d4d4d4;
                    }

                    #ctn-md-preview hr {
                        border: none;
                        border-top: 2px solid #3c3c3c;
                        margin: 24px 0;
                    }

                    #ctn-md-preview img {
                        max-width: 100%;
                        height: auto;
                        border-radius: 6px;
                        border: 1px solid #3c3c3c;
                    }

                    #ctn-md-preview strong {
                        color: #e6db74;
                        font-weight: bold;
                    }

                    #ctn-md-preview em {
                        color: #ae81ff;
                        font-style: italic;
                    }

                    /* 自定义滚动条 - 预览区域 */
                    #ctn-md-preview::-webkit-scrollbar { width: 10px; }
                    #ctn-md-preview::-webkit-scrollbar-track { background: #2d2d30; }
                    #ctn-md-preview::-webkit-scrollbar-thumb { background: #424242; border-radius: 5px; }
                    #ctn-md-preview::-webkit-scrollbar-thumb:hover { background: #4f4f4f; }

                    /* 右侧面板滚动条样式 */
                    .ctn-modal div[style*="background:#2d2d30"]::-webkit-scrollbar { width: 12px; }
                    .ctn-modal div[style*="background:#2d2d30"]::-webkit-scrollbar-track { background: #2d2d30; }
                    .ctn-modal div[style*="background:#2d2d30"]::-webkit-scrollbar-thumb { background: #424242; border-radius: 6px; }
                    .ctn-modal div[style*="background:#2d2d30"]::-webkit-scrollbar-thumb:hover { background: #4f4f4f; }
                `;
                document.head.appendChild(style);

                // 应用暗色主题类
                wrapper.classList.add('CodeMirror-dark');

                // 启用markdown模式和语法高亮
                const mode = window.CodeMirror && window.CodeMirror.modes && window.CodeMirror.modes.gfm
                    ? 'gfm'
                    : window.CodeMirror && window.CodeMirror.modes && window.CodeMirror.modes.markdown
                    ? 'markdown'
                    : 'text/plain';

                editor.setOption('mode', mode);
                editor.setOption('theme', 'default');
                editor.setOption('lineNumbers', false);
                editor.setOption('lineWrapping', true);
                editor.setOption('highlightFormatting', true);
                editor.setOption('tokenTypeOverrides', {
                    header: 'header',
                    quote: 'quote',
                    list1: 'variable-2',
                    list2: 'variable-3',
                    list3: 'keyword',
                    hr: 'hr',
                    image: 'tag',
                    formatting: 'meta',
                    linkInline: 'link',
                    linkEmail: 'link',
                    linkText: 'link',
                    linkHref: 'string'
                });

                // 刷新编辑器
                editor.refresh();
            }, 100);
            // 加载笔记内容
            loadNote(noteKey).then(content => {
                easyMDE.value(content);
                updatePreview();
            });
            // 实时预览
            function updatePreview() {
                const md = easyMDE.value();
                let renderMarkdown = md => md;
                if (window.marked) {
                    renderMarkdown = typeof window.marked === 'function'
                        ? window.marked
                        : (window.marked.marked ? window.marked.marked : renderMarkdown);
                }

                const previewContainer = document.getElementById('ctn-md-preview');
                previewContainer.innerHTML = renderMarkdown(md);

                // 应用代码高亮
                previewContainer.querySelectorAll('pre code').forEach(block => {
                    block.classList.add('hljs');
                    if (window.hljs && typeof window.hljs.highlightElement === 'function') {
                        // 清除之前的高亮
                        block.removeAttribute('data-highlighted');
                        window.hljs.highlightElement(block);
                    }
                });

                // 如果没有内容,显示提示
                if (!md.trim()) {
                    previewContainer.innerHTML = '<div style="text-align:center;color:#858585;padding:40px;font-style:italic;">✍️ 在左侧编辑器中输入 Markdown 内容,这里会实时显示预览效果</div>';
                }
            }
            easyMDE.codemirror.on('change', updatePreview);
            // 保存按钮事件
            saveBtn.onclick = () => {
                const val = easyMDE.value();
                saveNote(noteKey, val).then(() => {
                    saveTip.style.display = '';
                    setTimeout(() => { saveTip.style.display = 'none'; }, 1200);
                    // 保存成功后更新按钮状态
                    updateButtonState(btn, val);
                }).catch(err => {
                    console.error('保存失败:', err);
                    alert('保存失败,请重试');
                });
            };
        });
    }

    // 动态加载 EasyMDE、marked、highlight.js
    function loadEasyMDE(cb) {
        ensureFontAwesome();
        // 先加载 CodeMirror markdown 模式
        loadCodeMirrorMarkdown(() => {
            // EasyMDE
            if (!window.EasyMDE) {
                const link = document.createElement('link');
                link.rel = 'stylesheet';
                link.href = 'https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css';
                document.head.appendChild(link);
                const script = document.createElement('script');
                script.src = 'https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js';
                script.onload = () => {
                    loadMarked(cb);
                };
                script.onerror = () => {
                    alert('EasyMDE 加载失败,请检查网络');
                };
                document.body.appendChild(script);
            } else {
                loadMarked(cb);
            }
        });
    }

    // 加载 CodeMirror markdown 模式
    function loadCodeMirrorMarkdown(cb) {
        if (window.CodeMirror && window.CodeMirror.modes && window.CodeMirror.modes.markdown) {
            cb();
            return;
        }

        // 加载 CodeMirror markdown 模式
        const script = document.createElement('script');
        script.src = 'https://cdn.jsdelivr.net/npm/[email protected]/mode/markdown/markdown.min.js';
        script.onload = () => {
            // 加载 CodeMirror overlay 模式(markdown依赖)
            const overlayScript = document.createElement('script');
            overlayScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/addon/mode/overlay.min.js';
            overlayScript.onload = () => {
                // 加载 GFM 模式
                const gfmScript = document.createElement('script');
                gfmScript.src = 'https://cdn.jsdelivr.net/npm/[email protected]/mode/gfm/gfm.min.js';
                gfmScript.onload = cb;
                gfmScript.onerror = cb; // 即使加载失败也继续
                document.body.appendChild(gfmScript);
            };
            overlayScript.onerror = cb;
            document.body.appendChild(overlayScript);
        };
        script.onerror = cb;
        document.body.appendChild(script);
    }
    // 动态引入 FontAwesome 图标库
    function ensureFontAwesome() {
        if (!document.querySelector('link[href*="font-awesome"]')) {
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = 'https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css';
            document.head.appendChild(link);
        }
    }
    function loadMarked(cb) {
        if (!window.marked) {
            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
            script.onload = () => {
                loadHLJS(cb);
            };
            script.onerror = () => {
                alert('marked 加载失败,请检查网络');
            };
            document.body.appendChild(script);
        } else {
            loadHLJS(cb);
        }
    }
    function loadHLJS(cb) {
        if (!window.hljs) {
            const link = document.createElement('link');
            link.rel = 'stylesheet';
            link.href = 'https://unpkg.com/@highlightjs/[email protected]/styles/vs2015.min.css'; // 使用暗色主题
            document.head.appendChild(link);
            const script = document.createElement('script');
            script.src = 'https://unpkg.com/@highlightjs/[email protected]/highlight.min.js';
            script.onload = () => {
                // 配置 marked 的 highlight 选项
                if (window.marked && window.hljs) {
                    window.marked.setOptions({
                        highlight: function(code, lang) {
                            if (window.hljs.getLanguage(lang)) {
                                return window.hljs.highlight(code, { language: lang }).value;
                            }
                            return window.hljs.highlightAuto(code).value;
                        }
                    });
                }
                cb();
            };
            script.onerror = () => {
                alert('highlight.js 加载失败,请检查网络');
            };
            document.body.appendChild(script);
        } else {
            // 配置 marked 的 highlight 选项
            if (window.marked && window.hljs) {
                window.marked.setOptions({
                    highlight: function(code, lang) {
                        if (window.hljs.getLanguage(lang)) {
                            return window.hljs.highlight(code, { language: lang }).value;
                        }
                        return window.hljs.highlightAuto(code).value;
                    }
                });
            }
            cb();
        }
    }

    // 监听表格变化,保证按钮持续插入
    function observeTable() {
        // 监听整个页面的变化,不只是表格
        const targetNode = document.body;
        const observer = new MutationObserver((mutations) => {
            let shouldUpdate = false;

            mutations.forEach((mutation) => {
                // 检查是否有新增的节点包含表格行
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 检查是否是表格相关的变化
                            if (node.classList?.contains('el-table__body') ||
                                node.querySelector?.('.el-table__body') ||
                                node.querySelector?.('td.el-table_1_column_6') ||
                                node.tagName === 'TR' ||
                                node.classList?.contains('el-table__row') ||
                                node.querySelector?.('.el-table__row')) {
                                shouldUpdate = true;
                            }
                        }
                    });

                    // 检查移除的节点
                    mutation.removedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName === 'TR' ||
                                node.classList?.contains('el-table__row')) {
                                shouldUpdate = true;
                            }
                        }
                    });
                }

                // 检查属性变化(可能的翻页触发)
                if (mutation.type === 'attributes' &&
                    (mutation.attributeName === 'class' ||
                     mutation.attributeName === 'style' ||
                     mutation.attributeName === 'data-key')) {
                    const target = mutation.target;
                    if (target.classList?.contains('el-table') ||
                        target.closest?.('.el-table') ||
                        target.classList?.contains('el-pagination') ||
                        target.closest?.('.el-pagination')) {
                        shouldUpdate = true;
                    }
                }
            });

            if (shouldUpdate) {
                // 延迟执行,确保DOM完全更新
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 100);

                // 再次更新确保状态正确
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 300);
            }
        });

        observer.observe(targetNode, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['class', 'style']
        });

        // 额外监听分页按钮点击
        observePaginationClicks();

        // 定期检查(后备方案)
        setInterval(() => {
            insertCustomNoteButtons();
        }, 2000); // 每2秒检查一次,增加频率
    }

    // 监听分页按钮点击
    function observePaginationClicks() {
        // 监听分页相关的点击事件
        document.addEventListener('click', (e) => {
            const target = e.target;
            // 检查是否点击了分页相关按钮
            if (target.closest('.el-pagination') ||
                target.closest('.el-pager') ||
                target.classList.contains('btn-prev') ||
                target.classList.contains('btn-next') ||
                target.classList.contains('number') ||
                target.closest('.el-pagination__jump') ||
                target.closest('.el-pagination__sizes')) {

                // 立即尝试更新一次
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 500);

                // 再次延迟更新(确保加载完成)
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 1000);

                // 最后一次更新(确保状态正确)
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 1500);
            }
        });

        // 监听键盘事件(可能的分页快捷键)
        document.addEventListener('keydown', (e) => {
            if (e.key === 'PageUp' || e.key === 'PageDown' ||
                (e.key === 'Enter' && e.target.closest('.el-pagination'))) {
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 800);
            }
        });

        // 监听URL变化(可能的路由变化)
        let currentUrl = window.location.href;
        setInterval(() => {
            if (window.location.href !== currentUrl) {
                currentUrl = window.location.href;
                setTimeout(() => {
                    insertCustomNoteButtons();
                }, 1000);
            }
        }, 1000);
    }

    // 初始化
    function init() {
        insertCustomNoteButtons();
        observeTable();
    }

    // 页面插入导出/导入按钮区(只保留主按钮)
    function insertExportButton() {
        if (document.querySelector('.ctn-export-notes-btn-group')) return;
        const group = document.createElement('div');
        group.className = 'ctn-export-notes-btn-group';
        group.style = `
            position:fixed;
            right:36px;
            bottom:36px;
            z-index:10000;
            display:flex;
            flex-direction:column;
            gap:18px;
            align-items:flex-end;
        `;
        // 导出按钮
        const exportBtn = document.createElement('button');
        exportBtn.className = 'ctn-export-notes-btn';
        exportBtn.textContent = '导出笔记';
        exportBtn.style = btnStyle();
        exportBtn.onclick = exportAllNotes;
        // codetop导入按钮
        const importCodetopBtn = document.createElement('button');
        importCodetopBtn.className = 'ctn-import-codetop-btn';
        importCodetopBtn.textContent = 'codetop官方笔记 导入';
        importCodetopBtn.style = btnStyle('#67c23a');
        importCodetopBtn.onclick = showImportCodetopModal;
        // 插件导入按钮
        const importPluginBtn = document.createElement('button');
        importPluginBtn.className = 'ctn-import-plugin-btn';
        importPluginBtn.textContent = '插件笔记 导入';
        importPluginBtn.style = btnStyle('#e6a23c');
        importPluginBtn.onclick = showImportPluginModal;
        // 云同步按钮
        const syncBtn = document.createElement('button');
        syncBtn.className = 'ctn-sync-notes-btn';
        syncBtn.textContent = '云同步';
        syncBtn.style = btnStyle('#f56c6c');
        syncBtn.onclick = mergeSyncAllNotes;
        // 组装
        group.appendChild(exportBtn);
        group.appendChild(importCodetopBtn);
        group.appendChild(importPluginBtn);
        group.appendChild(syncBtn);
        document.body.appendChild(group);
    }
    function btnStyle(bg) {
        return `
            background:${bg || '#409EFF'};
            color:#fff;
            border:none;
            border-radius:24px;
            padding:12px 28px;
            font-size:18px;
            box-shadow:0 2px 8px rgba(0,0,0,0.12);
            cursor:pointer;
        `;
    }
    // codetop导入弹窗
    function showImportCodetopModal() {
        showImportModal('codetop');
    }
    // 插件导入弹窗
    function showImportPluginModal() {
        showImportModal('plugin');
    }
    // 通用导入弹窗
    function showImportModal(type) {
        if (document.querySelector('.ctn-modal-mask')) {
            return;
        }
        // 遮罩
        const mask = document.createElement('div');
        mask.className = 'ctn-modal-mask';
        mask.style = 'position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.3);z-index:99999;display:flex;align-items:center;justify-content:center;';
        // 弹窗
        const modal = document.createElement('div');
        modal.className = 'ctn-modal';
        modal.style = 'background:#fff;padding:32px 32px 24px 32px;border-radius:12px;min-width:420px;max-width:90vw;box-shadow:0 2px 16px rgba(0,0,0,0.15);position:relative;';
        // 关闭按钮
        const closeBtn = document.createElement('span');
        closeBtn.innerHTML = '&times;';
        closeBtn.style = 'position:absolute;right:18px;top:12px;font-size:28px;cursor:pointer;z-index:2;';
        closeBtn.title = '关闭';
        closeBtn.onclick = () => mask.remove();
        // 标题
        const title = document.createElement('div');
        title.style = 'font-size:20px;font-weight:bold;margin-bottom:18px;';
        title.textContent = type === 'codetop' ? '从 codetop 导入笔记' : '从插件导入笔记';
        // 内容区
        const content = document.createElement('div');
        content.style = 'margin-bottom:18px;';
        if (type === 'codetop') {
            content.innerHTML = '<textarea style="width:100%;height:120px;font-size:16px;padding:8px;box-sizing:border-box;resize:vertical;" placeholder="粘贴 codetop API 返回的 JSON 或 JSON 数组..."></textarea>';
        } else {
            content.innerHTML = '<input type="file" accept="application/json" style="font-size:16px;">';
        }
        // 导入按钮
        const importBtn = document.createElement('button');
        importBtn.textContent = '导入';
        importBtn.style = 'margin-top:8px;padding:8px 32px;background:#409EFF;color:#fff;border:none;border-radius:6px;font-size:16px;cursor:pointer;';
        // 提示
        const tip = document.createElement('div');
        tip.style = 'margin-top:12px;color:#67c23a;font-size:15px;display:none;';
        tip.textContent = '导入成功!';
        // 组装
        modal.appendChild(closeBtn);
        modal.appendChild(title);
        modal.appendChild(content);
        modal.appendChild(importBtn);
        modal.appendChild(tip);
        mask.appendChild(modal);
        document.body.appendChild(mask);
        // 导入逻辑
        importBtn.onclick = () => {
            // 保存原始状态
            const originalText = importBtn.textContent;
            const originalBackground = importBtn.style.background;

            // 设置导入中状态
            importBtn.disabled = true;
            importBtn.textContent = '导入中...';
            importBtn.style.background = '#909399'; // 灰色
            importBtn.style.cursor = 'not-allowed';
            importBtn.style.opacity = '0.6';

            // 恢复按钮状态的函数
            const restoreButton = () => {
                importBtn.disabled = false;
                importBtn.textContent = originalText;
                importBtn.style.background = originalBackground;
                importBtn.style.cursor = 'pointer';
                importBtn.style.opacity = '1';
            };

            if (type === 'codetop') {
                const val = content.querySelector('textarea').value;
                if (!val.trim()) {
                    alert('请输入要导入的JSON数据');
                    restoreButton();
                    return;
                }
                let arr;
                try {
                    arr = JSON.parse(val);
                } catch (e) {
                    console.error('JSON解析失败:', e);
                    alert('JSON 格式错误: ' + e.message);
                    restoreButton();
                    return;
                }
                if (!Array.isArray(arr)) arr = [arr];
                batchImportNotes(arr, type, tip, restoreButton);
            } else {
                const file = content.querySelector('input[type=file]').files[0];
                if (!file) {
                    alert('请选择文件');
                    restoreButton();
                    return;
                }
                const reader = new FileReader();
                reader.onload = function(e) {
                    let arr;
                    try {
                        arr = JSON.parse(e.target.result);
                    } catch (err) {
                        console.error('文件JSON解析失败:', err);
                        alert('JSON 格式错误: ' + err.message);
                        restoreButton();
                        return;
                    }
                    if (!Array.isArray(arr)) arr = [arr];
                    batchImportNotes(arr, type, tip, restoreButton);
                };
                reader.onerror = function(e) {
                    console.error('文件读取失败:', e);
                    alert('文件读取失败');
                    restoreButton();
                };
                reader.readAsText(file);
            }
        };
    }
    // 批量导入
    function batchImportNotes(arr, type, tip, callback) {

        // 先检查 IndexedDB 是否可用
        openDB().then(() => {
            return openDB();
        }).then(db => {
            const tx = db.transaction(STORE_NAME, 'readwrite');
            const store = tx.objectStore(STORE_NAME);
            let count = 0;
            let importedKeys = [];
            let processedCount = 0;
            let errors = [];

            if (arr.length === 0) {
                tip.textContent = '没有找到可导入的数据';
                tip.style.display = '';
                setTimeout(() => { tip.style.display = 'none'; }, 3000);
                if (callback) callback();
                return;
            }

            arr.forEach((item, index) => {
                let key, content;
                if (type === 'codetop') {
                    const slug = item.leetcodeInfo && item.leetcodeInfo.slug_title;
                    if (!slug) {
                        processedCount++;
                        checkComplete();
                        return;
                    }
                    key = `https://leetcode.cn/problems/${slug}`;
                    content = item.content || '';
                } else {
                    key = item.key || '';
                    content = item.content || '';
                }
                if (key) { // 只要有key就尝试导入,即使content为空
                    const putRequest = store.put({
                        key,
                        content: content || '', // 确保content不为undefined
                        updated_at: Date.now(),
                        ...(item.leetcodeInfo ? { leetcodeInfo: item.leetcodeInfo } : {})
                    });
                    putRequest.onsuccess = () => {
                        importedKeys.push(key);
                        count++;
                        processedCount++;
                        checkComplete();
                    };
                    putRequest.onerror = (e) => {
                        errors.push(`${key}: ${e.message || e}`);
                        processedCount++;
                        checkComplete();
                    };
                } else {
                    processedCount++;
                    checkComplete();
                }
            });

            function checkComplete() {
                if (processedCount === arr.length) {
                    let message = `导入完成!成功导入 ${count} 条,跳过 ${arr.length - count} 条。`;
                    if (errors.length > 0) {
                        message += `\n错误 ${errors.length} 条`;
                    }
                    tip.textContent = message;
                    tip.style.color = count > 0 ? '#67c23a' : '#f56c6c';
                    tip.style.display = '';
                    tip.style.whiteSpace = 'pre-line'; // 支持换行显示
                    setTimeout(() => { tip.style.display = 'none'; }, 4000);
                    // 导入完成后刷新按钮状态
                    setTimeout(() => {
                        insertCustomNoteButtons();
                    }, 100);
                    if (callback) callback();
                }
            }

            tx.onerror = (e) => {
                console.error('事务失败:', e);
                tip.textContent = '导入失败,请重试';
                tip.style.color = '#f56c6c';
                tip.style.display = '';
                setTimeout(() => { tip.style.display = 'none'; }, 3000);
                if (callback) callback();
            };
        }).catch(err => {
            console.error('打开数据库失败:', err);
            tip.textContent = '数据库错误,请重试';
            tip.style.color = '#f56c6c';
            tip.style.display = '';
            setTimeout(() => { tip.style.display = 'none'; }, 3000);
            if (callback) callback();
        });
    }

    // 导出所有笔记为 JSON 文件
    function exportAllNotes() {
        // 获取导出按钮并设置状态
        const exportBtn = document.querySelector('.ctn-export-notes-btn');
        const originalText = exportBtn ? exportBtn.textContent : '导出笔记';
        const originalStyle = exportBtn ? exportBtn.style.cssText : '';

        if (exportBtn) {
            exportBtn.textContent = '导出中...';
            exportBtn.disabled = true;
            exportBtn.style.background = '#909399'; // 灰色
            exportBtn.style.cursor = 'not-allowed';
            exportBtn.style.opacity = '0.6';
        }

        const restoreButton = () => {
            if (exportBtn) {
                exportBtn.textContent = originalText;
                exportBtn.disabled = false;
                exportBtn.style.cssText = originalStyle;
            }
        };

        openDB().then(db => {
            const tx = db.transaction(STORE_NAME, 'readonly');
            const store = tx.objectStore(STORE_NAME);
            const req = store.getAll();
            req.onsuccess = () => {
                const data = req.result || [];
                if (data.length === 0) {
                    alert('没有笔记可以导出');
                    restoreButton();
                    return;
                }
                const json = JSON.stringify(data, null, 2);
                const blob = new Blob([json], { type: 'application/json' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                const date = new Date().toISOString().slice(0, 10);
                a.href = url;
                a.download = `codetop_notes_${date}.json`;
                document.body.appendChild(a);
                a.click();
                setTimeout(() => {
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                    restoreButton(); // 导出完成后恢复按钮
                }, 100);
            };
            req.onerror = (e) => {
                console.error('导出失败:', e);
                alert('导出失败,请重试');
                restoreButton();
            };
        }).catch(err => {
            console.error('打开数据库失败:', err);
            alert('数据库错误,无法导出');
            restoreButton();
        });
    }

    // 新增:key 哈希函数(SHA-256)
    async function hashKey(str) {
        const buf = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(str));
        return Array.from(new Uint8Array(buf)).map(x => x.toString(16).padStart(2, '0')).join('');
    }
    // 云端获取笔记(用哈希key,支持 updatedTime)
    async function fetchNoteFromCloud(key) {
        const hash = await hashKey(key);
        return fetch(`https://paste.tans.fun/api/note/${encodeURIComponent(hash)}`)
            .then(res => res.json())
            .then(json => {
                if (json.code === 0 && json.data && typeof json.data.value === 'string') {
                    let updated_at = 0;
                    if (json.data.updatedTime) {
                        // 处理云端时间:云端存储的是 +8 时区的时间戳,需要转换为 UTC
                        let timestamp = new Date(json.data.updatedTime).getTime();
                        if (!isNaN(timestamp)) {
                            // 减去8小时转换为 UTC 时间戳
                            timestamp = timestamp - (8 * 60 * 60 * 1000);
                        }
                        updated_at = isNaN(timestamp) ? Date.now() : timestamp;
                    }
                    return {
                        key: json.data.key,
                        content: json.data.value,
                        updated_at
                    };
                }
                return null;
            }).catch(() => null);
    }
    // 云端保存笔记(返回 updatedTime)
    async function saveNoteToCloud(key, value) {
        if (!key || typeof key !== 'string' || !key.trim()) {
            console.error('云同步失败:key 不合法', key);
            alert('云同步失败:key 不合法,已跳过该条笔记');
            return Promise.resolve(false);
        }
        const hash = await hashKey(key);
        return fetch('https://paste.tans.fun/api/note', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({key: hash, value, url: key})
        }).then(res => res.json())
          .then(json => {
              if (json.code === 0) {
                  if (json.data && json.data.updatedTime) {
                      // 处理云端返回时间:云端存储的是 +8 时区的时间戳,需要转换为 UTC
                      let serverTime = new Date(json.data.updatedTime).getTime();
                      if (!isNaN(serverTime)) {
                          // 减去8小时转换为 UTC 时间戳
                          serverTime = serverTime - (8 * 60 * 60 * 1000);
                      }
                      const finalTime = isNaN(serverTime) ? Date.now() : serverTime;
                      return finalTime;
                  }
                  // 没有 updatedTime,返回当前本地时间
                  return Date.now();
              }
              throw new Error(json.message || '云端保存失败');
          });
    }
    // 合并同步主逻辑(云端操作用哈希key)
    async function mergeSyncAllNotes() {
        // 获取同步按钮并设置为禁用状态
        const syncBtn = document.querySelector('.ctn-sync-notes-btn');
        const originalText = syncBtn ? syncBtn.textContent : '云同步';
        const originalStyle = syncBtn ? syncBtn.style.cssText : '';

        if (syncBtn) {
            syncBtn.textContent = '同步中...';
            syncBtn.disabled = true;
            syncBtn.style.background = '#909399'; // 灰色
            syncBtn.style.cursor = 'not-allowed';
            syncBtn.style.opacity = '0.6';
        }

        try {
            const db = await openDB();
            const tx = db.transaction(STORE_NAME, 'readonly');
            const store = tx.objectStore(STORE_NAME);

            const localNotes = await new Promise((resolve, reject) => {
                const req = store.getAll();
                req.onsuccess = () => resolve(req.result || []);
                req.onerror = () => reject(new Error('读取本地笔记失败'));
            });

            if (localNotes.length === 0) {
                alert('本地没有笔记可同步');
                return;
            }

            let updatedCount = 0, uploadedCount = 0, skippedCount = 0;
            const totalNotes = localNotes.length;

            for (let i = 0; i < localNotes.length; i++) {
                const note = localNotes[i];
                const key = note.key;
                const localContent = note.content;
                const localUpdated = note.updated_at || 0;

                // 更新按钮显示进度
                if (syncBtn) {
                    syncBtn.textContent = `同步中... (${i + 1}/${totalNotes})`;
                }

                // key 校验和调试输出
                if (!key || typeof key !== 'string' || !key.trim()) {
                    skippedCount++;
                    continue;
                }

                // 拉取云端(用哈希key)
                const cloudNote = await fetchNoteFromCloud(key);
                let cloudUpdated = 0;
                if (cloudNote && typeof cloudNote.updated_at === 'number') {
                    cloudUpdated = cloudNote.updated_at;
                }

                // 详细日志输出:本地和云端更新时间(已转换为UTC)

                if (!cloudNote || !cloudNote.content) {
                    // 云端无内容,上传本地
                    const serverTime = await saveNoteToCloud(key, localContent);
                    if (serverTime) {
                        await saveNote(key, localContent, serverTime); // 用云端时间更新本地
                    }
                    uploadedCount++;
                } else {
                    // 比较时间戳(添加容错机制:如果时间差小于1分钟则认为相同)
                    const timeDiff = Math.abs(cloudUpdated - localUpdated);
                    const isTimeSimilar = timeDiff < 60000; // 1分钟内认为相同

                    if (isTimeSimilar) {
                        skippedCount++;
                    } else if (localUpdated > cloudUpdated) {
                        // 本地较新,上传
                        const serverTime = await saveNoteToCloud(key, localContent);
                        if (serverTime) {
                            await saveNote(key, localContent, serverTime); // 用云端时间更新本地
                        }
                        uploadedCount++;
                    } else if (cloudUpdated > localUpdated) {
                        // 云端较新,写回本地
                        await saveNote(key, cloudNote.content, cloudNote.updated_at);
                        updatedCount++;
                    } else {
                        // 一致,跳过
                        skippedCount++;
                    }
                }
            }

            alert(`云同步完成!上传${uploadedCount}条,下载${updatedCount}条,跳过${skippedCount}条。`);

        } catch (error) {
            console.error('云同步失败:', error);
            alert('云同步失败:' + error.message);
        } finally {
            // 恢复按钮状态
            if (syncBtn) {
                syncBtn.textContent = originalText;
                syncBtn.disabled = false;
                syncBtn.style.cssText = originalStyle;
            }
        }
    }

    // 页面加载后执行
    window.addEventListener('load', () => {
        setTimeout(init, 300); // 延迟,确保表格渲染
        setTimeout(insertExportButton, 1200); // 插入导出/导入按钮
    });
})();